Background.purs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. module PureTabs.Background where
  2. import Browser.Runtime as Runtime
  3. import Browser.Tabs (Tab, TabId, WindowId, query, removeOne, activateTab)
  4. import Browser.Tabs.OnActivated as TabsOnActivated
  5. import Browser.Tabs.OnCreated as TabsOnCreated
  6. import Browser.Tabs.OnRemoved as TabsOnRemoved
  7. import Browser.Tabs.OnUpdated (ChangeInfo(..))
  8. import Browser.Tabs.OnUpdated as TabsOnUpdated
  9. import Browser.Utils (Listener, mkListenerOne, mkListenerTwo, mkListenerUnit)
  10. import Control.Alt ((<$>))
  11. import Control.Alternative (pure, (*>))
  12. import Control.Bind ((>>=))
  13. import Control.Category (identity, (>>>))
  14. import Data.Array (fromFoldable)
  15. import Data.Foldable (for_)
  16. import Data.Function (flip)
  17. import Data.Lens (_Just, over, preview, set)
  18. import Data.Lens.At (at)
  19. import Data.Lens.Iso.Newtype (_Newtype)
  20. import Data.List (List, foldr, foldMap)
  21. import Data.Map (empty, lookup, values)
  22. import Data.Maybe (Maybe(..), maybe)
  23. import Data.Monoid ((<>))
  24. import Data.Newtype (unwrap)
  25. import Data.Show (show)
  26. import Data.Unit (unit)
  27. import Debug.Trace (traceM)
  28. import Effect (Effect)
  29. import Effect.Aff (Aff, launchAff_)
  30. import Effect.Class (liftEffect)
  31. import Effect.Console (log)
  32. import Effect.Ref as Ref
  33. import Prelude (Unit, bind, ($), discard, (<<<))
  34. import PureTabs.Model (BackgroundEvent(..), GlobalState, SidebarEvent(..), _active, _port, _portFromWindow, _portFromWindowId, _tabFromTabIdAndWindow, _tabFromWindow, _windows, tabsToGlobalState)
  35. type Ports
  36. = Ref.Ref (List Runtime.Port)
  37. main :: Effect Unit
  38. main = do
  39. log "starting background"
  40. launchAff_ runMain
  41. where
  42. runMain :: Aff Unit
  43. runMain = do
  44. allTabs <- query
  45. liftEffect
  46. $ do
  47. state <- Ref.new $ tabsToGlobalState allTabs
  48. initializeBackground state
  49. log "all listener initialized"
  50. initializeBackground :: Ref.Ref GlobalState -> Effect Unit
  51. initializeBackground ref = do
  52. TabsOnCreated.addListener $ onTabCreated ref
  53. (mkListenerTwo $ onTabDeleted ref) >>= TabsOnRemoved.addListener
  54. TabsOnActivated.addListener $ onTabActived ref
  55. TabsOnUpdated.addListener $ onTabUpdated ref
  56. (mkListenerOne $ onConnect ref) >>= Runtime.onConnectAddListener
  57. onTabCreated :: (Ref.Ref GlobalState) -> Tab -> Effect Unit
  58. onTabCreated stateRef tab' = do
  59. state <- Ref.modify (set (_tabFromWindow tab') (Just tab')) stateRef
  60. log $ "tabId: " <> (show tab.id) <> " windowId " <> show tab.windowId
  61. case (preview (_portFromWindow tab') state) of
  62. Nothing -> pure unit
  63. Just port -> do
  64. _ <- Runtime.postMessageJson port $ BgTabCreated tab'
  65. log $ "tab " <> (show tab.id) <> " created: " <> tab.title
  66. where
  67. tab = unwrap tab'
  68. onTabUpdated :: (Ref.Ref GlobalState) -> TabId -> ChangeInfo -> Tab -> Effect Unit
  69. onTabUpdated stateRef tid cinfo tab' = do
  70. state <- Ref.modify (set (_tabFromWindow tab') (Just tab')) stateRef
  71. case (preview (_portFromWindow tab') state) of
  72. Nothing -> pure unit
  73. Just port -> Runtime.postMessageJson port $ BgTabUpdated tid cinfo tab'
  74. onTabActived :: (Ref.Ref GlobalState) -> TabsOnActivated.ActiveInfo -> Effect Unit
  75. onTabActived stateRef (TabsOnActivated.ActiveInfo aInfo) = do
  76. traceM aInfo
  77. state <- Ref.modify (updateGlobalState aInfo.previousTabId aInfo.tabId) stateRef
  78. case (preview (_portFromWindowId aInfo.windowId) state) of
  79. Nothing -> pure unit
  80. Just port -> Runtime.postMessageJson port $ BgTabActived aInfo.previousTabId aInfo.tabId
  81. where
  82. updateGlobalState :: (Maybe TabId) -> TabId -> GlobalState -> GlobalState
  83. updateGlobalState prev new state =
  84. let
  85. -- TODO: we have the windowId, we can directly get the tab from that
  86. -- without using _tabFromTabIdAndWindow that goes through all the windows.
  87. prevTab = prev >>= _tabFromTabIdAndWindow state
  88. prevTabF :: GlobalState -> GlobalState
  89. prevTabF = maybe identity (\t -> set (_activeTab t) false) prevTab
  90. newTab = _tabFromTabIdAndWindow state new
  91. newTabF :: GlobalState -> GlobalState
  92. newTabF = maybe identity (\t -> set (_activeTab t) true) newTab
  93. _activeTab t = (_tabFromWindow t) <<< _Just <<< _Newtype <<< _active
  94. in
  95. (prevTabF >>> newTabF) state
  96. onTabDeleted :: (Ref.Ref GlobalState) -> TabId -> TabsOnRemoved.RemoveInfo -> Effect Unit
  97. onTabDeleted stateRef tabId info = do
  98. state <- Ref.read stateRef
  99. let
  100. allTabs = _tabFromTabIdAndWindow state tabId
  101. newState = foldr (\t -> set (_tabFromWindow t) Nothing) state allTabs
  102. Ref.write newState stateRef
  103. for_ allTabs \t -> do
  104. let
  105. port = preview (_portFromWindow t) state
  106. maybe (pure unit) ((flip Runtime.postMessageJson) (BgTabDeleted tabId)) port
  107. onConnect :: (Ref.Ref GlobalState) -> Runtime.Port -> Effect Unit
  108. onConnect stateRef' port = do
  109. -- create a temporary listener ref that will only be held until the sidebar has sent its current window
  110. listenerRef <- Ref.new Nothing
  111. initialListener <-
  112. Runtime.onMessageJsonAddListener port $ windowListener
  113. $ onNewWindowId port stateRef' listenerRef
  114. -- XXX: is it possible a message arrive *before* this is executed ?
  115. -- theoretically yes, and this means this way of doing is unsafe, but it's
  116. -- difficult for a handler to remove itself otherwise.
  117. Ref.write (Just initialListener) listenerRef
  118. where
  119. windowListener :: (WindowId -> Effect Unit) -> SidebarEvent -> Effect Unit
  120. windowListener callback msg = case msg of
  121. SbHasWindowId winId -> log ("bg: created winId " <> show winId) *> callback winId
  122. _ -> pure unit
  123. onNewWindowId ::
  124. forall a.
  125. Runtime.Port ->
  126. (Ref.Ref GlobalState) ->
  127. ( Ref.Ref
  128. ( Maybe
  129. (Listener a)
  130. )
  131. ) ->
  132. WindowId -> Effect Unit
  133. onNewWindowId port stateRef listenerRef winId = do
  134. -- initial state of the current window
  135. r <- initWindowState port stateRef winId
  136. -- remove the previous onMessage listener
  137. ogListener <- Ref.read listenerRef
  138. foldMap (\l -> Runtime.onMessageRemoveListener port l) ogListener
  139. Ref.write Nothing listenerRef
  140. -- send initial tabs
  141. maybe (pure unit)
  142. (\w -> Runtime.postMessageJson port $ BgInitialTabList $ fromFoldable $ values w.tabs)
  143. (lookup winId r.windows)
  144. -- add the new onMessage listener
  145. sidebarListener <- Runtime.onMessageJsonAddListener port $ manageSidebar stateRef port
  146. onDisconnectListener <- mkListenerUnit $ onDisconnect stateRef winId sidebarListener
  147. Runtime.portOnDisconnect port onDisconnectListener
  148. initWindowState :: Runtime.Port -> (Ref.Ref GlobalState) -> WindowId -> Effect GlobalState
  149. initWindowState port ref winId =
  150. (flip Ref.modify) ref
  151. $ over (_windows <<< (at winId))
  152. ( case _ of
  153. Nothing -> Just $ { tabs: empty, port: Just port }
  154. Just win -> Just $ set _port (Just port) win
  155. )
  156. -- TODO don't pass the full ref, but only a set of function to manipulate/access
  157. -- the data required
  158. manageSidebar :: (Ref.Ref GlobalState) -> Runtime.Port -> SidebarEvent -> Effect Unit
  159. manageSidebar stateRef port (SbTabDeleted tabId) = launchAff_ $ removeOne tabId
  160. manageSidebar stateRef port (SbTabActived tabId) = launchAff_ $ activateTab tabId
  161. manageSidebar stateRef port msg = pure unit
  162. onDisconnect :: forall a. (Ref.Ref GlobalState) -> WindowId -> Listener a -> Effect Unit
  163. onDisconnect stateRef winId listener = Ref.modify_ (set (_windows <<< (at winId) <<< _Just <<< _port) Nothing) stateRef