Skip to content

Commit 80f496d

Browse files
committed
Add resource finalization on Window Sub.
This introduces the `bracket` pattern to `Sub`, specifically on `window`, this allows resource finalization and works will with top-level, long running `Sub`, or dynamically-generated `Sub`. - [x] Introduce `removeEventListener`, windowRemoveEventListener - [x] Refactor `Subscription.Window` - [x] Use empty `MVar` to block on processing portion of `Sub` - [x] Use `bracket` pattern
1 parent 0e89eaf commit 80f496d

File tree

3 files changed

+71
-30
lines changed

3 files changed

+71
-30
lines changed

src/Miso/FFI/Internal.hs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@ module Miso.FFI.Internal
2929
, syncPoint
3030
-- * Events
3131
, addEventListener
32+
, removeEventListener
3233
, eventPreventDefault
3334
, eventStopPropagation
3435
-- * Window
3536
, windowAddEventListener
37+
, windowRemoveEventListener
3638
, windowInnerHeight
3739
, windowInnerWidth
3840
-- * Performance
@@ -191,22 +193,48 @@ addEventListener
191193
-> (JSVal -> JSM ())
192194
-- ^ Callback which will be called when the event occurs,
193195
-- the event will be passed to it as a parameter.
194-
-> JSM ()
196+
-> JSM Function
195197
addEventListener self name cb = do
196-
_ <- self # "addEventListener" $ (name, asyncFunction handle)
197-
pure ()
198+
cb_ <- asyncFunction handle
199+
void $ self # "addEventListener" $ (name, cb_)
200+
pure cb_
198201
where
199202
handle _ _ [] = error "addEventListener: no args, impossible"
200203
handle _ _ (x:_) = cb x
201204
-----------------------------------------------------------------------------
205+
-- | Register an event listener on given target.
206+
removeEventListener
207+
:: JSVal
208+
-- ^ Event target on which we want to register event listener
209+
-> MisoString
210+
-- ^ Type of event to listen to (e.g. "click")
211+
-> Function
212+
-- ^ Callback which will be called when the event occurs,
213+
-- the event will be passed to it as a parameter.
214+
-> JSM ()
215+
removeEventListener self name cb =
216+
void $ self # "removeEventListener" $ (name, cb)
217+
-----------------------------------------------------------------------------
218+
-- | Registers an event listener on window
219+
windowRemoveEventListener
220+
:: MisoString
221+
-- ^ Type of event to listen to (e.g. "click")
222+
-> Function
223+
-- ^ Callback which will be called when the event occurs,
224+
-- the event will be passed to it as a parameter.
225+
-> JSM ()
226+
windowRemoveEventListener name cb = do
227+
win <- jsg "window"
228+
removeEventListener win name cb
229+
-----------------------------------------------------------------------------
202230
-- | Registers an event listener on window
203231
windowAddEventListener
204232
:: MisoString
205233
-- ^ Type of event to listen to (e.g. "click")
206234
-> (JSVal -> JSM ())
207235
-- ^ Callback which will be called when the event occurs,
208236
-- the event will be passed to it as a parameter.
209-
-> JSM ()
237+
-> JSM Function
210238
windowAddEventListener name cb = do
211239
win <- jsg "window"
212240
addEventListener win name cb

src/Miso/Subscription/History.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ uriSub = \f sink -> do
9292
void . FFI.forkJSM . forever $ do
9393
liftIO (wait chan)
9494
sink . f =<< getURI
95-
FFI.windowAddEventListener (ms "popstate") $ \_ ->
95+
void $ FFI.windowAddEventListener (ms "popstate") $ \_ ->
9696
sink . f =<< getURI
9797
-----------------------------------------------------------------------------
9898
pushStateNoModel :: URI -> JSM ()

src/Miso/Subscription/Window.hs

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,26 @@ module Miso.Subscription.Window
1616
, windowCoordsSub
1717
, windowPointerMoveSub
1818
, windowSubWithOptions
19+
-- *** Types
20+
, Coord
1921
) where
2022
-----------------------------------------------------------------------------
21-
import Control.Monad
22-
import Language.Javascript.JSaddle
23-
import Data.Aeson.Types (parseEither)
23+
import Control.Monad.IO.Class (liftIO)
24+
import Control.Concurrent.MVar
25+
import Control.Monad
26+
import Language.Javascript.JSaddle
27+
import Data.Aeson.Types (parseEither)
2428
-----------------------------------------------------------------------------
25-
import Miso.Event
26-
import Miso.Effect
29+
import Miso.Event
30+
import Miso.Effect
2731
import qualified Miso.FFI.Internal as FFI
28-
import Miso.String
32+
import Miso.String
33+
import Miso.Canvas (Coord)
2934
-----------------------------------------------------------------------------
3035
-- | Captures window coordinates changes as they occur and writes them to
3136
-- an event sink
32-
windowCoordsSub :: ((Int, Int) -> action) -> Sub action
33-
windowCoordsSub f = \write -> do
34-
write . f =<< (,) <$> FFI.windowInnerHeight <*> FFI.windowInnerWidth
35-
FFI.windowAddEventListener "resize" $
36-
\windowEvent -> do
37-
target <- getProp "target" (Object windowEvent)
38-
Just w <- fromJSVal =<< getProp "innerWidth" (Object target)
39-
Just h <- fromJSVal =<< getProp "innerHeight" (Object target)
40-
write $ f (h, w)
37+
windowCoordsSub :: (Coord -> action) -> Sub action
38+
windowCoordsSub f = windowPointerMoveSub (f . client)
4139
-----------------------------------------------------------------------------
4240
-- | @windowSub eventName decoder toAction@ provides a subscription
4341
-- to listen to window level events.
@@ -47,18 +45,33 @@ windowSub = windowSubWithOptions defaultOptions
4745
-- | @windowSubWithOptions options eventName decoder toAction@ provides a
4846
-- subscription to listen to window level events.
4947
windowSubWithOptions :: Options -> MisoString -> Decoder r -> (r -> action) -> Sub action
50-
windowSubWithOptions Options{..} eventName Decoder{..} toAction = \write ->
51-
FFI.windowAddEventListener eventName $ \e -> do
52-
decodeAtVal <- toJSVal decodeAt
53-
Just v <- fromJSVal =<< FFI.eventJSON decodeAtVal e
54-
case parseEither decoder v of
55-
Left s -> error $ "Parse error on " <> unpack eventName <> ": " <> s
56-
Right r -> do
57-
when stopPropagation (FFI.eventStopPropagation e)
58-
when preventDefault (FFI.eventPreventDefault e)
59-
write (toAction r)
48+
windowSubWithOptions Options{..} eventName Decoder {..} toAction = \sink ->
49+
createSub acquire release sink
50+
where
51+
release =
52+
FFI.windowRemoveEventListener eventName
53+
acquire sink =
54+
FFI.windowAddEventListener eventName $ \e -> do
55+
decodeAtVal <- toJSVal decodeAt
56+
v <- fromJSValUnchecked =<< FFI.eventJSON decodeAtVal e
57+
case parseEither decoder v of
58+
Left s ->
59+
error $ "windowSubWithOptions: Parse error on " <> unpack eventName <> ": " <> s
60+
Right r -> do
61+
when stopPropagation (FFI.eventStopPropagation e)
62+
when preventDefault (FFI.eventPreventDefault e)
63+
sink (toAction r)
6064
-----------------------------------------------------------------------------
6165
-- | @window.addEventListener ("pointermove", (event) => handle(event))@
6266
-- A 'Sub' to handle @PointerEvent@s on window
6367
windowPointerMoveSub :: (PointerEvent -> action) -> Sub action
6468
windowPointerMoveSub = windowSub "pointermove" pointerDecoder
69+
-----------------------------------------------------------------------------
70+
createSub :: (Sink action -> JSM a) -> (a -> JSM b) -> Sub action
71+
createSub acquire release = \sink -> do
72+
mvar <- liftIO newEmptyMVar
73+
bracket
74+
(acquire sink)
75+
release
76+
(\_ -> liftIO (takeMVar mvar))
77+
-----------------------------------------------------------------------------

0 commit comments

Comments
 (0)