packages feed

webdriver-precore-0.2.0.0: src/WebDriverPreCore/BiDi/BrowsingContext.hs

module WebDriverPreCore.BiDi.BrowsingContext
  ( Activate (..),
    CaptureScreenshot (..),
    ScreenShotOrigin (..),
    ClipRectangle (..),
    ImageFormat (..),
    Close (..),
    Create (..),
    CreateType (..),
    GetTree (..),
    HandleUserPrompt (..),
    LocateNodes (..),
    Navigate (..),
    Print (..),
    Orientation (..),
    PageRange (..),
    Reload (..),
    SetViewport (..),
    TraverseHistory (..),
    MatchType (..),
    Locator (..),
    ReadinessState (..),
    UserPromptType (..),
    PrintMargin (..),
    PrintPage (..),
    Viewport (..),
    GetTreeResult (..),
    LocateNodesResult (..),
    CaptureScreenshotResult (..),
    PrintResult (..),
    Info (..),
    NavigateResult (..),
    BrowsingContextEvent (..),
    NavigationInfo (..),
    DownloadEnd (..),
    DownloadWillBegin (..),
    HistoryUpdated (..),
    UserPromptClosed (..),
    UserPromptOpened (..),
    Navigation (..),
  )
where

import Data.Aeson (FromJSON (..), GFromJSON, KeyValue (..), Options (..), ToJSON (..), Value (..), Zero, defaultOptions, genericParseJSON, genericToJSON, object, withObject, (.:), (.=))
import Data.Aeson.Types (Parser)
import Data.Functor ((<&>))
import Data.Maybe (catMaybes)
import Data.Text (Text, pack, unpack)
import GHC.Generics ( Generic(Rep) )
import WebDriverPreCore.BiDi.Capabilities (UserPromptHandlerType)
import WebDriverPreCore.BiDi.CoreTypes (BrowsingContext, JSInt, JSUInt, NodeRemoteValue, UserContext, KnownSubscriptionType (..), ClientWindow, URL (..))
import WebDriverPreCore.BiDi.Script (SharedReference)
import AesonUtils (enumCamelCase, fromJSONCamelCase, opt, parseJSONOmitNothing, toJSONOmitNothing)

-- ######### REMOTE #########

-- |  for activate command
newtype Activate = MkActivate
  { context :: BrowsingContext
  }
  deriving (Show, Eq, Generic)

instance ToJSON Activate

-- |  for captureScreenshot command
data CaptureScreenshot = MkCaptureScreenshot
  { context :: BrowsingContext,
    origin :: Maybe ScreenShotOrigin,
    format :: Maybe ImageFormat,
    clip :: Maybe ClipRectangle
  }
  deriving (Show, Eq, Generic)

instance ToJSON CaptureScreenshot where
  toJSON :: CaptureScreenshot -> Value
  toJSON (MkCaptureScreenshot {context, origin, format, clip}) =
    object $
      [ "context" .= context
      ]
        <> catMaybes
          [ opt "origin" origin,
            opt "format" format,
            opt "clip" clip
          ]

data ScreenShotOrigin = Viewport | Document deriving (Show, Eq, Generic)

instance ToJSON ScreenShotOrigin where
  toJSON :: ScreenShotOrigin -> Value
  toJSON = enumCamelCase

-- | Clip rectangle for screenshots
data ClipRectangle
  = BoxClipRectangle
      { x :: Float,
        y :: Float,
        width :: Float,
        height :: Float
      }
  | ElementClipRectangle
      { element :: Text -- script.SharedReference
      }
  deriving (Show, Eq, Generic)

instance ToJSON ClipRectangle where
  toJSON :: ClipRectangle -> Value
  toJSON = \case
    BoxClipRectangle {x, y, width, height} ->
      object
        [ "type" .= "box",
          "x" .= x,
          "y" .= y,
          "width" .= width,
          "height" .= height
        ]
    ElementClipRectangle {element} ->
      object
        [ "type" .= "element",
          "element" .= element
        ]

-- | Image format specification
data ImageFormat = MkImageFormat
  { imageType :: Text,
    quality :: Maybe Float
  }
  deriving (Show, Eq, Generic)

instance ToJSON ImageFormat where
  toJSON :: ImageFormat -> Value
  toJSON (MkImageFormat {imageType, quality}) =
    object $
      [ "type" .= imageType
      ]
        <> catMaybes
          [ opt "quality" quality
          ]

-- |  for close command
data Close = MkClose
  { context :: BrowsingContext,
    promptUnload :: Maybe Bool
  }
  deriving (Show, Eq, Generic)

instance ToJSON Close where
  toJSON :: Close -> Value
  toJSON (MkClose {context, promptUnload}) =
    object $
      [ "context" .= context
      ]
        <> catMaybes
          [ opt "promptUnload" promptUnload
          ]

-- |  for create command
data Create = MkCreate
  { createType :: CreateType,
    background :: Bool,
    referenceContext :: Maybe BrowsingContext,
    userContext :: Maybe UserContext
  }
  deriving (Show, Eq, Generic)

instance ToJSON Create where
  toJSON :: Create -> Value
  toJSON (MkCreate {createType, referenceContext, background, userContext}) =
    object $
      [ "type" .= createType,
        "background" .= background
      ]
        <> catMaybes
          [ opt "referenceContext" referenceContext,
            opt "userContext" userContext
          ]

-- |  for getTree command
data GetTree = MkGetTree
  { maxDepth :: Maybe JSUInt,
    root :: Maybe BrowsingContext
  }
  deriving (Show, Eq, Generic)

instance ToJSON GetTree where
  toJSON :: GetTree -> Value
  toJSON = toJSONOmitNothing

-- |  for handleUserPrompt command
data HandleUserPrompt = MkHandleUserPrompt
  { context :: BrowsingContext,
    accept :: Maybe Bool,
    userText :: Maybe Text
  }
  deriving (Show, Eq, Generic)

instance ToJSON HandleUserPrompt where
  toJSON :: HandleUserPrompt -> Value
  toJSON = genericToJSON defaultOptions {omitNothingFields = True}

-- |  for locateNodes command
data LocateNodes = MkLocateNodes
  { context :: BrowsingContext,
    locator :: Locator,
    maxNodeCount :: Maybe JSUInt,
    serializationOptions :: Maybe Value, -- script.SerializationOptions
    startNodes :: Maybe [SharedReference] -- script.SharedReference
  }
  deriving (Show, Eq, Generic)

instance ToJSON LocateNodes where
  toJSON :: LocateNodes -> Value
  toJSON = toJSONOmitNothing

-- |  for navigate command
data Navigate = MkNavigate
  { context :: BrowsingContext,
    url :: URL,
    wait :: Maybe ReadinessState
  }
  deriving (Show, Eq, Generic)

instance ToJSON Navigate where
  toJSON :: Navigate -> Value
  toJSON = toJSONOmitNothing

-- |  for print command
data Print = MkPrint
  { context :: BrowsingContext,
    background :: Maybe Bool,
    margin :: Maybe PrintMargin,
    orientation :: Maybe Orientation,
    page :: Maybe PrintPage,
    pageRanges :: Maybe [PageRange],
    scale :: Maybe Float,
    shrinkToFit :: Maybe Bool
  }
  deriving (Show, Eq, Generic)

instance ToJSON Print where
  toJSON :: Print -> Value
  toJSON = toJSONOmitNothing

data Orientation = Portrait | Landscape deriving (Show, Eq, Generic)

instance ToJSON Orientation where
  toJSON :: Orientation -> Value
  toJSON = enumCamelCase

data PageRange
  = Page Word
  | Range
      { fromPage :: Word,
        toPage :: Word
      }
  deriving (Show, Eq)

instance ToJSON PageRange where
  toJSON :: PageRange -> Value
  toJSON = \case
    Page p -> Number $ fromIntegral p
    Range {fromPage, toPage} -> String (pack (show fromPage <> "-" <> show toPage))

-- |  for reload command
data Reload = MkReload
  { context :: BrowsingContext,
    ignoreCache :: Maybe Bool,
    wait :: Maybe ReadinessState
  }
  deriving (Show, Eq, Generic)

instance ToJSON Reload where
  toJSON :: Reload -> Value
  toJSON = toJSONOmitNothing

-- |  for setViewport command
data SetViewport = MkSetViewport
  { context :: Maybe BrowsingContext,
    viewport :: Maybe (Maybe Viewport), -- Viewport or null
    devicePixelRatio :: Maybe (Maybe Float), -- Float or null
    userContexts :: Maybe [Text] -- browser.UserContext
  }
  deriving (Show, Eq, Generic)

instance ToJSON SetViewport where
  toJSON :: SetViewport -> Value
  toJSON = toJSONOmitNothing

-- |  for traverseHistory command
data TraverseHistory = MkTraverseHistory
  { context :: BrowsingContext,
    delta :: JSInt
  }
  deriving (Show, Eq, Generic)

-- | Text matching type for InnerText locator
data MatchType = Full | Partial
  deriving (Show, Eq, Generic)

instance ToJSON MatchType where
  toJSON :: MatchType -> Value
  toJSON = enumCamelCase

-- | Different types of locators for elements
data Locator
  = Accessibility
      { name :: Maybe Text,
        role :: Maybe Text
      }
  | CSS
      { value :: Text
      }
  | Context
      { context :: BrowsingContext
      }
  | InnerText
      { value :: Text,
        ignoreCase :: Maybe Bool,
        matchType :: Maybe MatchType,
        maxDepth :: Maybe JSUInt
      }
  | XPath
      { value :: Text
      }
  deriving (Show, Eq)

instance ToJSON Locator where
  toJSON :: Locator -> Value
  toJSON = \case
    Accessibility {name, role} ->
      object $
        [ "type" .= "accessibility",
          "value"
            .= ( object $
                   catMaybes
                     [ opt "name" name,
                       opt "role" role
                     ]
               )
        ]
    CSS {value} ->
      object
        [ "type" .= "css",
          "value" .= value
        ]
    Context {context} ->
      object
        [ "type" .= "context",
          "value" .= object ["context" .= context]
        ]
    InnerText {value, ignoreCase, matchType, maxDepth} ->
      object $
        [ "type" .= "innerText",
          "value" .= value
        ]
          <> catMaybes
            [ opt "ignoreCase" ignoreCase,
              opt "matchType" matchType,
              opt "maxDepth" maxDepth
            ]
    XPath {value} ->
      object
        [ "type" .= "xpath",
          "value" .= value
        ]

-- | Readiness state of a browsing context
data ReadinessState = None | Interactive | Complete
  deriving (Show, Eq, Generic)

instance ToJSON ReadinessState where
  toJSON :: ReadinessState -> Value
  toJSON = enumCamelCase

-- | User prompt types
data UserPromptType = Alert | BeforeUnload | Confirm | Prompt
  deriving (Show, Eq, Generic)

instance ToJSON UserPromptType where
  toJSON :: UserPromptType -> Value
  toJSON = enumCamelCase

instance FromJSON UserPromptType where
  parseJSON :: Value -> Parser UserPromptType
  parseJSON = fromJSONCamelCase

-- | Type of browsing context to create
data CreateType = Tab | Window
  deriving (Show, Eq, Generic)

instance ToJSON CreateType where
  toJSON :: CreateType -> Value
  toJSON = enumCamelCase

-- | Print margin
data PrintMargin = MkPrintMargin
  { bottom :: Maybe Float,
    left :: Maybe Float,
    right :: Maybe Float,
    top :: Maybe Float
  }
  deriving (Show, Eq, Generic)

-- | Print page
data PrintPage = MkPrintPage
  { height :: Maybe Float,
    width :: Maybe Float
  }
  deriving (Show, Eq, Generic)

-- | Viewport dimensions
data Viewport = MkViewport
  { width :: JSUInt,
    height :: JSUInt
  }
  deriving (Show, Eq, Generic)

-- ######### Local #########

instance FromJSON NavigateResult

newtype GetTreeResult = MkGetTreeResult
  { contexts :: [Info]
  }
  deriving stock (Show, Eq, Generic)

instance FromJSON GetTreeResult

newtype LocateNodesResult = MkLocateNodesResult
  { nodes :: [NodeRemoteValue]
  }
  deriving newtype (Show, Eq)
  deriving stock (Generic)

instance FromJSON LocateNodesResult

newtype CaptureScreenshotResult = MkCaptureScreenshotResult
  { base64Text :: Text
  }
  deriving newtype (Show, Eq)

parseTextData :: Text -> Value -> Parser Text
parseTextData description = withObject (unpack description) $ flip (.:) "data"

instance FromJSON CaptureScreenshotResult where
  parseJSON :: Value -> Parser CaptureScreenshotResult
  parseJSON = fmap MkCaptureScreenshotResult . parseTextData "CaptureScreenshotResult"

newtype PrintResult = MkPrintResult
  { base64Text :: Text
  }
  deriving newtype (Show, Eq)

instance FromJSON PrintResult where
  parseJSON :: Value -> Parser PrintResult
  parseJSON = fmap MkPrintResult . parseTextData "PrintResult"

data Info = MkInfo
  { children :: Maybe [Info],
    clientWindow :: ClientWindow, 
    context :: BrowsingContext,
    originalOpener :: Maybe BrowsingContext,
    url :: URL,
    userContext :: UserContext, 
    parent :: Maybe BrowsingContext
  }
  deriving (Show, Eq, Generic)

instance FromJSON Info where
  parseJSON :: Value -> Parser Info
  parseJSON = parseJSONOmitNothing

data NavigateResult = MkNavigateResult
  { navigation :: Maybe Text,
    url :: URL
  }
  deriving (Show, Eq, Generic)

-- | Event from a browsing context
data BrowsingContextEvent
  = ContextCreated Info
  | ContextDestroyed Info
  | DomContentLoaded NavigationInfo
  | DownloadEnd
  | DownloadWillBegin DownloadWillBegin
  | FragmentNavigated NavigationInfo
  | HistoryUpdated HistoryUpdated
  | Load NavigationInfo
  | NavigationAborted NavigationInfo
  | NavigationCommitted NavigationInfo
  | NavigationFailed NavigationInfo
  | NavigationStarted NavigationInfo
  | UserPromptClosed UserPromptClosed
  | UserPromptOpened UserPromptOpened
  deriving (Show, Eq, Generic)

instance FromJSON BrowsingContextEvent where
  parseJSON :: Value -> Parser BrowsingContextEvent
  parseJSON = withObject "BrowsingContextEvent" $ \o -> do
    typ <- o .: "method"
    params <- o .: "params"
    let parsedPrms :: forall a b. (FromJSON a) => (a -> b) -> Parser b
        parsedPrms = (<&>) (parseJSON params)
    case typ of
      BrowsingContextContextCreated -> parsedPrms ContextCreated
      BrowsingContextContextDestroyed -> parsedPrms ContextDestroyed
      BrowsingContextDomContentLoaded -> parsedPrms DomContentLoaded
      BrowsingContextDownloadEnd -> pure DownloadEnd
      BrowsingContextDownloadWillBegin -> parsedPrms DownloadWillBegin
      BrowsingContextFragmentNavigated -> parsedPrms FragmentNavigated
      BrowsingContextHistoryUpdated -> parsedPrms HistoryUpdated
      BrowsingContextLoad -> parsedPrms Load
      BrowsingContextNavigationAborted -> parsedPrms NavigationAborted
      BrowsingContextNavigationCommitted -> parsedPrms NavigationCommitted
      BrowsingContextNavigationFailed -> parsedPrms NavigationFailed
      BrowsingContextNavigationStarted -> parsedPrms NavigationStarted
      BrowsingContextUserPromptClosed -> parsedPrms UserPromptClosed
      BrowsingContextUserPromptOpened -> parsedPrms UserPromptOpened
      _ -> fail $ "Unknown BrowsingContextEvent type: " <> show typ

data NavigationInfo = MkNavigationInfo
  { context :: BrowsingContext,
    navigation :: Maybe Navigation,
    timestamp :: JSUInt,
    url :: URL
  }
  deriving (Show, Eq, Generic)

instance FromJSON NavigationInfo where
  parseJSON :: Value -> Parser NavigationInfo
  parseJSON = parseJSONOmitNothing

data DownloadEnd
  = DownloadCompleted
      { filePath :: Maybe Text,
        navigationInfo :: NavigationInfo
      }
  | DownloadCanceled {navigationInfo :: NavigationInfo}
  deriving (Show, Eq, Generic)

instance FromJSON DownloadEnd where
  parseJSON :: Value -> Parser DownloadEnd
  parseJSON = withObject "DownloadEnd" $ \v -> do
    status <- v .: "status"
    case status of
      "complete" -> do
        filePath <- v .: "filepath"
        context <- v .: "context"
        navigation <- v .: "navigation"
        timestamp <- v .: "timestamp"
        url <- v .: "url"
        let navigationInfo = MkNavigationInfo {context, navigation, timestamp, url}
        pure $ DownloadCompleted {filePath, navigationInfo}
      "canceled" -> do
        context <- v .: "context"
        navigation <- v .: "navigation"
        timestamp <- v .: "timestamp"
        url <- v .: "url"
        let navigationInfo = MkNavigationInfo {context, navigation, timestamp, url}
        pure $ DownloadCanceled {navigationInfo}
      _ -> fail $ "Unknown DownloadEnd status: " <> unpack status

data DownloadWillBegin = MkDownloadWillBegin
  { suggestedFilename :: Text,
    context :: BrowsingContext,
    navigation :: Maybe Navigation,
    timestamp :: JSUInt,
    url :: URL
  }
  deriving (Show, Eq, Generic)

instance FromJSON DownloadWillBegin where
  parseJSON :: Value -> Parser DownloadWillBegin
  parseJSON = parseJSONOmitNothing

data HistoryUpdated = MkHistoryUpdated
  { context :: BrowsingContext,
    timestamp :: JSUInt,
    url :: URL
  }
  deriving (Show, Eq, Generic)

instance FromJSON HistoryUpdated

data UserPromptClosed = MkUserPromptClosed
  { context :: BrowsingContext,
    accepted :: Bool,
    promptType :: UserPromptType,
    userText :: Maybe Text
  }
  deriving (Show, Eq, Generic)

promptParser :: forall a. (Generic a, GFromJSON Zero (Rep a)) => Value -> Parser a
promptParser =
  genericParseJSON
    defaultOptions
      { fieldLabelModifier = \case
          "promptType" -> "type"
          other -> other,
        omitNothingFields = True
      }

instance FromJSON UserPromptClosed where
  parseJSON :: Value -> Parser UserPromptClosed
  parseJSON = promptParser

data UserPromptOpened = MkUserPromptOpened
  { context :: BrowsingContext,
    handler :: UserPromptHandlerType,
    message :: Text,
    promptType :: UserPromptType,
    defaultValue :: Maybe Text
  }
  deriving (Show, Eq, Generic)

instance FromJSON UserPromptOpened where
  parseJSON :: Value -> Parser UserPromptOpened
  parseJSON = promptParser

newtype Navigation = MkNavigation
  { navigationId :: Text
  }
  deriving (Show, Eq, Generic)
  deriving newtype (FromJSON)

instance ToJSON TraverseHistory

instance ToJSON PrintMargin

instance ToJSON PrintPage

instance ToJSON Viewport