{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} module GHCJS.DOM.Debug ( DomHasCallStack , debugEnabled , getElementStack , addDebugMenu , addDebugMenu' ) where import Control.Arrow (Arrow(..)) import Control.Monad.IO.Class (MonadIO(..)) import Data.Char (toLower) import Data.Foldable (forM_) import Data.Monoid ((<>)) import System.IO (stderr, hPutStrLn) import GHC.Stack (srcLocStartLine, srcLocFile, SrcLoc(..), HasCallStack) import GHC.Exts (Constraint) import GHCJS.DOM (currentDocumentUnchecked) import GHCJS.DOM.Types (HTMLDivElement(..), HTMLStyleElement(..), uncheckedCastTo, HTMLUListElement(..), MouseEvent(..), Document(..), Element(..), HTMLAnchorElement(..), MonadJSM, JSM, liftJSM, JSString) import GHCJS.DOM.Debug.Internal (DomHasCallStack, getElementStack, debugEnabled) import GHCJS.DOM.Document (createTextNode, getBodyUnchecked, getElementsByTagName, createElement) import GHCJS.DOM.DocumentOrShadowRoot (elementFromPoint) import GHCJS.DOM.Element (getTagName, setAttribute, getAttribute, setInnerHTML) import GHCJS.DOM.EventM (addListener, uiPageXY, mouseClientXY, mouseShiftKey, target, newListener, on, preventDefault) import GHCJS.DOM.EventTargetClosures (SaferEventListener(..)) import GHCJS.DOM.GlobalEventHandlers (click, contextMenu) import GHCJS.DOM.HTMLCollection (itemUnchecked) import GHCJS.DOM.HTMLStyleElement (setType) import GHCJS.DOM.Node (getParentElement, appendChild_) import GHCJS.DOM.NonElementParentNode (getElementById) addDebugMenu :: MonadJSM m => m (JSM ()) addDebugMenu = addDebugMenu' (const Nothing) addDebugMenu' :: MonadJSM m => (SrcLoc -> Maybe String) -> m (JSM ()) addDebugMenu' getSourceLink = liftJSM $ do let menuId = "ghcjs-dom-debug" :: JSString doc <- currentDocumentUnchecked style <- uncheckedCastTo HTMLStyleElement <$> createElement doc ("style" :: JSString) setType style ("text/css" :: JSString) setInnerHTML style $ mconcat [ "#", menuId, ", #", menuId, " ul ul{" , " display: block;" , " position: absolute;" , " box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);" , " padding: 2px;" , " z-index:1000;" , " margin: 0;" , " list-style-type: none;" , " list-style: none;" , " background-color:#fff;" , " color:#000;" , "}\n" , "#", menuId, " ul{" , " list-style-type: none;" , " margin: 0;" , " padding: 2px;" , " cursor: pointer;" , "}\n" , "#", menuId, " li{" , " white-space: nowrap;" , "}\n" , "#", menuId, " ul ul{" , " display: none;" , "}\n" , "#", menuId, ">li:hover{" , " background-color:#ddd;" , "}\n" , "#", menuId, " li:hover ul{" , " display: inline-block;" , " position: absolute;" , " z-index:1001;" , "}\n" ] getElementsByTagName doc ("head" :: JSString) >>= (`itemUnchecked` 0) >>= (`appendChild_` style) body <- getBodyUnchecked doc clickListener <- newListener $ do target >>= \case Just (t :: HTMLAnchorElement) -> getAttribute t ("hs-srcloc" :: JSString) >>= mapM_ (liftIO . hPutStrLn stderr . ("OPEN " <>)) Nothing -> return () getElementById doc menuId >>= mapM_ (\e -> setAttribute e ("style" :: JSString) ("display: none;" :: JSString)) on doc contextMenu $ mouseShiftKey >>= \case True -> ((fromIntegral *** fromIntegral) <$> mouseClientXY) >>= uncurry (elementFromPoint doc) >>= \case Just e -> do preventDefault menu <- getElementById doc menuId >>= \case Just menu -> setInnerHTML menu ("" :: JSString) >> return (uncheckedCastTo HTMLDivElement menu) Nothing -> do menu <- uncheckedCastTo HTMLDivElement <$> createElement doc ("div" :: JSString) setAttribute menu ("id" :: JSString) menuId appendChild_ body menu return menu if debugEnabled then do ul <- uncheckedCastTo HTMLUListElement <$> createElement doc ("ul" :: JSString) appendChild_ menu ul addMenu doc clickListener ul e else do a <- uncheckedCastTo HTMLAnchorElement <$> createElement doc ("a" :: JSString) appendChild_ menu a createTextNode doc ("The ghcjs-dom debug cabal flag is switched off" :: JSString) >>= appendChild_ a setAttribute a ("href" :: JSString) ("https://github.com/ghcjs/ghcjs-dom/blob/master/README.md#debug" :: JSString) setAttribute a ("target" :: JSString) ("_blank" :: JSString) liftJSM $ addListener a click clickListener False (pageX, pageY) <- uiPageXY setAttribute menu ("style" :: JSString) ("display: block; left: " <> show pageX <> "px; top:" <> show pageY <> "px;" ) Nothing -> return () False -> return () where addMenu :: MonadJSM m => Document -> SaferEventListener HTMLAnchorElement MouseEvent -> HTMLUListElement -> Element -> m () addMenu doc clickListener parentMenu = loop where loop e = do getElementStack e >>= mapM_ (addElementMenu doc clickListener parentMenu e) getParentElement e >>= mapM_ loop addElementMenu :: MonadJSM m => Document -> SaferEventListener HTMLAnchorElement MouseEvent -> HTMLUListElement -> Element -> [(String, SrcLoc)] -> m () addElementMenu doc clickListener parentMenu e cs = do tagName <- map toLower <$> getTagName e parentLi <- createElement doc ("li" :: JSString) appendChild_ parentMenu parentLi createTextNode doc tagName >>= appendChild_ parentLi ul <- createElement doc ("ul" :: JSString) appendChild_ parentLi ul forM_ (reverse cs) $ \(f, loc) -> do li <- createElement doc ("li" :: JSString) appendChild_ ul li a <- uncheckedCastTo HTMLAnchorElement <$> createElement doc ("a" :: JSString) appendChild_ li a createTextNode doc (f <> " " <> srcLocFile loc <> ":" <> show (srcLocStartLine loc)) >>= appendChild_ a forM_ (getSourceLink loc) $ \link -> do setAttribute a ("href" :: JSString) link setAttribute a ("target" :: JSString) ("_blank" :: JSString) setAttribute a ("hs-srcloc" :: JSString) $ prettySrcLoc loc liftJSM $ addListener a click clickListener False prettySrcLoc :: SrcLoc -> String prettySrcLoc SrcLoc {..} = mconcat [ srcLocFile , ":(" , show srcLocStartLine, "," , show srcLocStartCol , ")-(" , show srcLocEndLine , "," , show srcLocEndCol , ") in " , srcLocPackage, ":", srcLocModule ]