on master help sign in
Merge remote-tracking branch 'upstream/master' into darwin-depends

# Conflicts: # jsaddle-warp/default.nix

commit96b2abef0b15215d8c84fd7f97c9acb1013f4296 authorRyan Trinkle <ryan.trinkle@gmail.com> dateSun 25 Jun 2017, 15:04 (9 years ago) parentecb87f5903c374
17 files changed+511−123
jsaddle-clib/cbits/include/jsaddle.h+1 jsaddle-clib/src-ghc/Language/Javascript/JSaddle/CLib.hs+16−3 jsaddle-clib/src/Language/Javascript/JSaddle/CLib/Internal.hsc+3 jsaddle-warp/default.nix+13−12 jsaddle-warp/jsaddle-warp.cabal+6−2 jsaddle-warp/src/Language/Javascript/JSaddle/Warp.hs+3−4 jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets.hs+212−55 jsaddle-wkwebview/cbits-uikit/AppDelegate.m+13 jsaddle-wkwebview/src-ghc/Language/Javascript/JSaddle/WKWebView.hs+5 jsaddle/default.nix+2−1 jsaddle/jsaddle.cabal+5−1 jsaddle/src/Language/Javascript/JSaddle/Debug.hs+48 jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs+3−3 jsaddle/src/Language/Javascript/JSaddle/Null.hs+56 jsaddle/src/Language/Javascript/JSaddle/Run.hs+38−16 jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+27−10 jsaddle/src/Language/Javascript/JSaddle/Types.hs+60−16
jsaddle-clib/cbits/include/jsaddle.h+1
@@ -17,6 +17,7 @@ typedef struct app_callbacks {
1717 void (* mainActivity_onStop) ();
1818 void (* mainActivity_onDestroy) ();
1919 void (* mainActivity_onRestart) ();
20+ void (* mainActivity_onNewIntent) (char *, char *);
2021 void (* firebaseInstanceIdService_sendRegistrationToServer) (char *);
2122} app_callbacks;
2223
jsaddle-clib/src-ghc/Language/Javascript/JSaddle/CLib.hs+16−3
@@ -18,9 +18,9 @@ import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
1818import Data.Default (def, Default)
1919import Data.Monoid ((<>))
2020import Data.Text (Text)
21−import qualified Data.Text as T
21+import qualified Data.Text.Encoding as T
2222
23−import Foreign.C.String (CString, newCString, peekCString)
23+import Foreign.C.String (CString, newCString)
2424import Foreign.Ptr (FunPtr, Ptr)
2525import Foreign.Storable (poke)
2626import Foreign.Marshal.Utils (new)
@@ -40,6 +40,9 @@ foreign import ccall safe "wrapper"
4040foreign import ccall safe "wrapper"
4141 wrapMessageCallback :: (CString -> IO ()) -> IO (FunPtr (CString -> IO ()))
4242
43+foreign import ccall safe "wrapper"
44+ wrapMessageCallback2 :: (CString -> CString -> IO ()) -> IO (FunPtr (CString -> CString -> IO ()))
45+
4346foreign import ccall safe "wrapper"
4447 wrapSyncCallback :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString))
4548
@@ -78,6 +81,7 @@ data AppConfig = AppConfig
7881 , _appConfig_mainActivityOnStop :: IO ()
7982 , _appConfig_mainActivityOnDestroy :: IO ()
8083 , _appConfig_mainActivityOnRestart :: IO ()
84+ , _appConfig_mainActivityOnNewIntent :: (Text -> Text -> IO ())
8185 , _appConfig_firebaseInstanceIdServiceSendRegistrationToServer :: Text -> IO ()
8286 }
8387
@@ -90,6 +94,7 @@ instance Default AppConfig where
9094 , _appConfig_mainActivityOnStop = return ()
9195 , _appConfig_mainActivityOnDestroy = return ()
9296 , _appConfig_mainActivityOnRestart = return ()
97+ , _appConfig_mainActivityOnNewIntent = \_ _ -> return ()
9398 , _appConfig_firebaseInstanceIdServiceSendRegistrationToServer = \_ -> return ()
9499 }
95100
@@ -102,8 +107,12 @@ appConfigToAppCallbacks c = do
102107 stop <- wrapStartCallback $ _appConfig_mainActivityOnStop c
103108 destroy <- wrapStartCallback $ _appConfig_mainActivityOnDestroy c
104109 restart <- wrapStartCallback $ _appConfig_mainActivityOnRestart c
110+ newIntent <- wrapMessageCallback2 $ \intentAction intentData -> do
111+ intentAction' <- fromUtf8CString intentAction
112+ intentData' <- fromUtf8CString intentData
113+ _appConfig_mainActivityOnNewIntent c intentAction' intentData'
105114 firebaseRegPtr <- wrapMessageCallback $ \token -> do
106− token' <- T.pack <$> peekCString token
115+ token' <- fromUtf8CString token
107116 _appConfig_firebaseInstanceIdServiceSendRegistrationToServer c token'
108117 return $ AppCallbacks
109118 { _appCallbacks_mainActivity_onCreate = create
@@ -113,9 +122,13 @@ appConfigToAppCallbacks c = do
113122 , _appCallbacks_mainActivity_onStop = stop
114123 , _appCallbacks_mainActivity_onDestroy = destroy
115124 , _appCallbacks_mainActivity_onRestart = restart
125+ , _appCallbacks_mainActivity_onNewIntent = newIntent
116126 , _appCallbacks_firebaseInstanceIdService_sendRegistrationToServer = firebaseRegPtr
117127 }
118128
129+fromUtf8CString :: CString -> IO Text
130+fromUtf8CString = fmap T.decodeUtf8 . packCString
131+
119132pokeAppConfig :: Ptr AppCallbacks -> AppConfig -> IO ()
120133pokeAppConfig ptr cfg = poke ptr =<< appConfigToAppCallbacks cfg
121134
jsaddle-clib/src/Language/Javascript/JSaddle/CLib/Internal.hsc+3
@@ -38,6 +38,7 @@ data AppCallbacks = AppCallbacks
3838 , _appCallbacks_mainActivity_onStop :: !(FunPtr (IO ()))
3939 , _appCallbacks_mainActivity_onDestroy :: !(FunPtr (IO ()))
4040 , _appCallbacks_mainActivity_onRestart :: !(FunPtr (IO ()))
41+ , _appCallbacks_mainActivity_onNewIntent :: !(FunPtr (CString -> CString -> IO ()))
4142 , _appCallbacks_firebaseInstanceIdService_sendRegistrationToServer :: !(FunPtr (CString -> IO ()))
4243 }
4344
@@ -52,6 +53,7 @@ instance Storable AppCallbacks where
5253 #{poke app_callbacks, mainActivity_onStop} p $ _appCallbacks_mainActivity_onStop nc
5354 #{poke app_callbacks, mainActivity_onDestroy} p $ _appCallbacks_mainActivity_onDestroy nc
5455 #{poke app_callbacks, mainActivity_onRestart} p $ _appCallbacks_mainActivity_onRestart nc
56+ #{poke app_callbacks, mainActivity_onNewIntent} p $ _appCallbacks_mainActivity_onNewIntent nc
5557 #{poke app_callbacks, firebaseInstanceIdService_sendRegistrationToServer} p $ _appCallbacks_firebaseInstanceIdService_sendRegistrationToServer nc
5658 peek p = AppCallbacks
5759 <$> #{peek app_callbacks, mainActivity_onCreate} p
@@ -61,4 +63,5 @@ instance Storable AppCallbacks where
6163 <*> #{peek app_callbacks, mainActivity_onStop} p
6264 <*> #{peek app_callbacks, mainActivity_onDestroy} p
6365 <*> #{peek app_callbacks, mainActivity_onRestart} p
66+ <*> #{peek app_callbacks, mainActivity_onNewIntent} p
6467 <*> #{peek app_callbacks, firebaseInstanceIdService_sendRegistrationToServer} p
jsaddle-warp/default.nix+13−12
@@ -1,22 +1,23 @@
11{ mkDerivation, aeson, base, bytestring, containers, deepseq
2−, doctest, filepath, ghc-prim, http-types, jsaddle, lens, primitive
3−, process, QuickCheck, ref-tf, stdenv, stm, text, time
4−, transformers, wai, wai-websockets, warp, websockets
5−, darwin, webdriver, phantomjs, ghc
2+, doctest, filepath, foreign-store, ghc-prim, http-types, jsaddle
3+, lens, network, primitive, process, QuickCheck, ref-tf, stdenv
4+, stm, text, time, transformers, uuid, uuid-types, wai
5+, wai-websockets, warp, webdriver, websockets
66}:
77mkDerivation {
88 pname = "jsaddle-warp";
9− version = "0.9.0.0";
9+ version = "0.9.2.0";
1010 src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
1111 libraryHaskellDepends = [
12− aeson base containers http-types jsaddle stm text time transformers
13− wai wai-websockets warp websockets
14− ] ++ (if ghc.isGhcjs or false then [
15− ] else [
16− ]);
12+ aeson base bytestring containers foreign-store http-types jsaddle
13+ stm text time transformers uuid uuid-types wai wai-websockets warp
14+ websockets
15+ ];
1716 testHaskellDepends = [
18− base bytestring deepseq doctest filepath ghc-prim jsaddle lens
19− primitive process QuickCheck ref-tf webdriver phantomjs
17+ aeson base bytestring containers deepseq doctest filepath ghc-prim
18+ http-types jsaddle lens network primitive process QuickCheck ref-tf
19+ stm text time transformers wai wai-websockets warp webdriver
20+ websockets
2021 ];
2122 testTarget = "--test-option=${jsaddle.src}";
2223 description = "Interface for JavaScript that works with GHCJS and GHC";
jsaddle-warp/jsaddle-warp.cabal+6−2
@@ -1,5 +1,5 @@
11name: jsaddle-warp
2−version: 0.9.0.0
2+version: 0.9.2.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -26,13 +26,17 @@ library
2626 Language.Javascript.JSaddle.WebSockets
2727 build-depends:
2828 aeson >=0.8.0.2 && <1.3,
29+ bytestring >=0.10.6.0 && <0.11,
2930 containers >=0.5.6.2 && <0.6,
31+ foreign-store >=0.2 && <0.3,
3032 http-types >=0.8.6 && <0.10,
31− jsaddle >=0.9.0.0 && <0.10,
33+ jsaddle >=0.9.2.0 && <0.10,
3234 stm >=2.4.4 && <2.5,
3335 text >=1.2.1.3 && <1.3,
3436 time >=1.5.0.1 && <1.8,
3537 transformers >=0.4.2.0 && <0.6,
38+ uuid >=1.3.13 && <1.4,
39+ uuid-types >=1.0.3 && <1.1,
3640 wai >=3.0.3.0 && <3.3,
3741 wai-websockets >=3.0.0.6 && <3.1,
3842 warp >=3.1.2 && <3.3,
jsaddle-warp/src/Language/Javascript/JSaddle/Warp.hs+3−4
@@ -17,8 +17,7 @@ module Language.Javascript.JSaddle.Warp (
1717 -- * Running JSM over WebSockets
1818 run
1919#ifndef ghcjs_HOST_OS
20− , jsaddleOr
21− , jsaddleApp
20+ , module Language.Javascript.JSaddle.WebSockets
2221#endif
2322) where
2423
@@ -29,7 +28,7 @@ import Network.WebSockets (defaultConnectionOptions)
2928
3029import Language.Javascript.JSaddle.Types (JSM)
3130import Language.Javascript.JSaddle.Run (syncPoint)
32−import Language.Javascript.JSaddle.WebSockets (jsaddleOr, jsaddleApp)
31+import Language.Javascript.JSaddle.WebSockets
3332#endif
3433
3534-- | Run the given 'JSM' action as the main entry point. Either directly
@@ -40,6 +39,6 @@ run _port = id
4039#else
4140run :: Int -> JSM () -> IO ()
4241run port f =
43− runSettings (setPort port (setTimeout 3600 defaultSettings)) $
42+ runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
4443 jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
4544#endif
jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets.hs+212−55
@@ -1,5 +1,7 @@
11{-# LANGUAGE LambdaCase #-}
22{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE RecursiveDo #-}
35-----------------------------------------------------------------------------
46--
57-- Module : Language.Javascript.JSaddle.WebSockets
@@ -16,73 +18,228 @@ module Language.Javascript.JSaddle.WebSockets (
1618 -- * Running JSM over WebSockets
1719 jsaddleOr
1820 , jsaddleApp
21+ , jsaddleWithAppOr
22+ , jsaddleAppPartial
23+ , debug
24+ , debugWrapper
1925) where
2026
21−import Control.Monad (forever)
22−import Control.Concurrent (forkIO, threadDelay)
23−import Control.Exception (handle, AsyncException, throwIO, fromException)
27+import Control.Monad (when, join, void, forever)
28+import Control.Concurrent (killThread, forkIO, threadDelay)
29+import Control.Exception (handle, AsyncException, throwIO, fromException, finally)
2430
2531import Data.Monoid ((<>))
2632import Data.Aeson (encode, decode)
2733
28−import Network.Wai (Application)
34+import Network.Wai
35+ (Middleware, lazyRequestBody, Application, Request, Response,
36+ ResponseReceived)
2937import Network.WebSockets
30− (ConnectionOptions(..), sendTextData, receiveDataMessage,
31− acceptRequest, ServerApp, sendPing)
38+ (defaultConnectionOptions, ConnectionOptions(..), sendTextData,
39+ receiveDataMessage, acceptRequest, ServerApp, sendPing)
3240import qualified Network.WebSockets as WS (DataMessage(..))
3341import Network.Wai.Handler.WebSockets (websocketsOr)
3442
35−import Language.Javascript.JSaddle.Types (JSM(..))
43+import Language.Javascript.JSaddle.Types (JSM(..), JSContextRef(..))
3644import qualified Network.Wai as W
37− (responseLBS, requestMethod, Application, pathInfo)
38−import Data.Text (Text)
45+ (responseLBS, requestMethod, pathInfo)
3946import qualified Data.Text as T (pack)
4047import qualified Network.HTTP.Types as H
41− (status403, status200, status405)
42−import Language.Javascript.JSaddle.Run (runJavaScript)
43−import Language.Javascript.JSaddle.Run.Files (indexHtml, jsaddleJs)
44−
45−jsaddleOr :: ConnectionOptions -> JSM () -> Application -> Application
46−jsaddleOr opts entryPoint = websocketsOr opts wsApp
47− where
48− wsApp :: ServerApp
49− wsApp pending_conn = do
50− conn <- acceptRequest pending_conn
51− (processResult, _, start) <- runJavaScript (sendTextData conn . encode) entryPoint
52− _ <- forkIO . forever $
53− receiveDataMessage conn >>= \case
54− (WS.Text t) ->
55− case decode t of
56− Nothing -> error $ "jsaddle Results decode failed : " <> show t
57− Just r -> processResult r
58− _ -> error "jsaddle WebSocket unexpected binary data"
59− start
60− waitTillClosed conn
61−
62− -- Based on Network.WebSocket.forkPingThread
63− waitTillClosed conn = ignore `handle` go 1
64− where
65− go :: Int -> IO ()
66− go i = do
67− threadDelay (1 * 1000 * 1000)
68− sendPing conn (T.pack $ show i)
69− go (i + 1)
70−
71− ignore e = case fromException e of
72− Just async -> throwIO (async :: AsyncException)
73− Nothing -> return ()
48+ (status403, status200)
49+import Language.Javascript.JSaddle.Run (syncPoint, runJavaScript)
50+import Language.Javascript.JSaddle.Run.Files (indexHtml, runBatch, ghcjsHelpers, initState)
51+import Language.Javascript.JSaddle.Debug
52+ (removeContext, addContext)
53+import Data.Maybe (fromMaybe)
54+import qualified Data.Map as M (empty, insert, lookup)
55+import Data.IORef
56+ (writeIORef, IORef, readIORef, newIORef, atomicModifyIORef')
57+import Data.ByteString.Lazy (ByteString)
58+import Data.UUID.Types (toText)
59+import Control.Concurrent.MVar
60+ (tryPutMVar, modifyMVar_, putMVar, takeMVar, readMVar, newMVar,
61+ newEmptyMVar, modifyMVar)
62+import Network.Wai.Handler.Warp
63+ (defaultSettings, setTimeout, setPort, runSettings)
64+import Foreign.Store (newStore, readStore, lookupStore)
65+import Language.Javascript.JSaddle (askJSM)
66+import Control.Monad.IO.Class (MonadIO(..))
67+
68+jsaddleOr :: ConnectionOptions -> JSM () -> Application -> IO Application
69+jsaddleOr opts entryPoint otherApp = do
70+ syncHandlers <- newIORef M.empty
71+ let wsApp :: ServerApp
72+ wsApp pending_conn = do
73+ conn <- acceptRequest pending_conn
74+ rec (processResult, processSyncResult, start) <- runJavaScript (sendTextData conn . encode) $ do
75+ syncKey <- toText . contextId <$> askJSM
76+ liftIO $ atomicModifyIORef' syncHandlers (\m -> (M.insert syncKey processSyncResult m, ()))
77+ liftIO $ sendTextData conn (encode syncKey)
78+ entryPoint
79+ _ <- forkIO . forever $
80+ receiveDataMessage conn >>= \case
81+ (WS.Text t) ->
82+ case decode t of
83+ Nothing -> error $ "jsaddle Results decode failed : " <> show t
84+ Just r -> processResult r
85+ _ -> error "jsaddle WebSocket unexpected binary data"
86+ start
87+ waitTillClosed conn
88+
89+ -- Based on Network.WebSocket.forkPingThread
90+ waitTillClosed conn = ignore `handle` go 1
91+ where
92+ go :: Int -> IO ()
93+ go i = do
94+ threadDelay (1 * 1000 * 1000)
95+ sendPing conn (T.pack $ show i)
96+ go (i + 1)
97+
98+ ignore e = case fromException e of
99+ Just async -> throwIO (async :: AsyncException)
100+ Nothing -> return ()
101+
102+ syncHandler :: Application
103+ syncHandler req sendResponse = case (W.requestMethod req, W.pathInfo req) of
104+ ("POST", ["sync", syncKey]) -> do
105+ body <- lazyRequestBody req
106+ case decode body of
107+ Nothing -> error $ "jsaddle sync message decode failed : " <> show body
108+ Just result ->
109+ M.lookup syncKey <$> readIORef syncHandlers >>= \case
110+ Nothing -> error "jsaddle missing sync message handler"
111+ Just handler -> do
112+ next <- encode <$> handler result
113+ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/json")] next
114+ _ -> otherApp req sendResponse
115+ return $ websocketsOr opts wsApp syncHandler
116+
74117
75118jsaddleApp :: Application
76−jsaddleApp req = jsaddleAppPieces (W.pathInfo req) req
77−
78−jsaddleAppPieces :: [Text] -> W.Application
79−jsaddleAppPieces _ req sendResponse
80− | W.requestMethod req `notElem` ["GET", "HEAD"] = sendResponse $ W.responseLBS
81− H.status405
82− [("Content-Type", "text/plain")]
83− "Only GET or HEAD is supported"
84−jsaddleAppPieces [] _req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
85−jsaddleAppPieces ["jsaddle.js"] _req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] jsaddleJs
86−jsaddleAppPieces _rawPieces _req sendResponse = sendResponse $ W.responseLBS H.status403
87− [ ("Content-Type", "text/plain")
88− ] "Forbidden"
119+jsaddleApp req sendResponse =
120+ fromMaybe
121+ (sendResponse $ W.responseLBS H.status403 [("Content-Type", "text/plain")] "Forbidden")
122+ (jsaddleAppPartial req sendResponse)
123+
124+jsaddleWithAppOr :: ConnectionOptions -> JSM () -> Application -> IO Application
125+jsaddleWithAppOr opts entryPoint otherApp = jsaddleOr opts entryPoint $ \req sendResponse ->
126+ (fromMaybe (otherApp req sendResponse)
127+ (jsaddleAppPartial req sendResponse))
128+
129+jsaddleAppPartial :: Request -> (Response -> IO ResponseReceived) -> Maybe (IO ResponseReceived)
130+jsaddleAppPartial req sendResponse = case (W.requestMethod req, W.pathInfo req) of
131+ ("GET", []) -> Just $ sendResponse $ W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
132+ ("GET", ["jsaddle.js"]) -> Just $ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] jsaddleJs
133+ _ -> Nothing
134+
135+-- Use this to generate this string for embedding
136+-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
137+jsaddleJs :: ByteString
138+jsaddleJs = "\
139+ \if(typeof global !== \"undefined\") {\n\
140+ \ global.window = global;\n\
141+ \ global.WebSocket = require('ws');\n\
142+ \}\n\
143+ \\n\
144+ \var connect = function() {\n\
145+ \ var wsaddress = window.location.protocol.replace('http', 'ws')+\"//\"+window.location.hostname+(window.location.port?(\":\"+window.location.port):\"\");\n\
146+ \\n\
147+ \ var ws = new WebSocket(wsaddress);\n\
148+ \ var syncKey = \"\";\n\
149+ \\n\
150+ \ ws.onopen = function(e) {\n\
151+ \ " <> initState <> "\n\
152+ \ var inXhrSend = false;\n\
153+ \\n\
154+ \ ws.onmessage = function(e) {\n\
155+ \ if(inXhrSend) return;\n\
156+ \ var batch = JSON.parse(e.data);\n\
157+ \ if(typeof batch === \"string\") {\n\
158+ \ syncKey = batch;\n\
159+ \ var xhr = new XMLHttpRequest();\n\
160+ \ xhr.open('POST', '/reload/'+syncKey, true);\n\
161+ \ xhr.onreadystatechange = function() {\n\
162+ \ if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200)\n\
163+ \ setTimeout(function(){window.location.reload();}, 500);\n\
164+ \ };\n\
165+ \ xhr.send();\n\
166+ \ return;\n\
167+ \ }\n\
168+ \\n\
169+ \ " <> runBatch (\a -> "ws.send(JSON.stringify(" <> a <> "));")
170+ (Just (\a -> "(function(){\n\
171+ \ inXhrSend = true;\n\
172+ \ var xhr = new XMLHttpRequest();\n\
173+ \ xhr.open('POST', '/sync/'+syncKey, false);\n\
174+ \ xhr.setRequestHeader(\"Content-type\", \"application/json\");\n\
175+ \ xhr.send(JSON.stringify(" <> a <> "));\n\
176+ \ inXhrSend = false;\n\
177+ \ return JSON.parse(xhr.response);})()")) <> "\
178+ \ };\n\
179+ \ };\n\
180+ \ ws.onerror = function() {\n\
181+ \ setTimeout(connect, 1000);\n\
182+ \ };\n\
183+ \}\n\
184+ \\n\
185+ \ " <> ghcjsHelpers <> "\
186+ \connect();\n\
187+ \"
188+
189+-- | Start or restart the server.
190+-- To run this as part of every :reload use
191+-- > :def! reload (const $ return "::reload\nLanguage.Javascript.JSaddle.Warp.debug 3708 SomeMainModule.someMainFunction")
192+debug :: Int -> JSM () -> IO ()
193+debug port f = do
194+ debugWrapper $ \refreshMiddleware registerContext ->
195+ runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
196+ jsaddleOr defaultConnectionOptions (registerContext >> f >> syncPoint) (refreshMiddleware jsaddleApp)
197+ putStrLn $ "<a href=\"http://localhost:" <> show port <> "\">run</a>"
198+
199+debugWrapper :: (Middleware -> JSM () -> IO ()) -> IO ()
200+debugWrapper run = do
201+ reloadMVar <- newEmptyMVar
202+ reloadDoneMVars <- newMVar []
203+ contexts <- newMVar []
204+ let refreshMiddleware :: Middleware
205+ refreshMiddleware otherApp req sendResponse = case (W.requestMethod req, W.pathInfo req) of
206+ ("POST", ["reload", _syncKey]) -> do
207+ reloadDone <- newEmptyMVar
208+ modifyMVar_ reloadDoneMVars (return . (reloadDone:))
209+ readMVar reloadMVar
210+ r <- sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/json")] ("reload" :: ByteString)
211+ putMVar reloadDone ()
212+ return r
213+ _ -> otherApp req sendResponse
214+ start :: Int -> IO (IO Int)
215+ start expectedConnections = do
216+ serverDone <- newEmptyMVar
217+ ready <- newEmptyMVar
218+ let registerContext :: JSM ()
219+ registerContext = do
220+ uuid <- contextId <$> askJSM
221+ browsersConnected <- liftIO $ modifyMVar contexts (\ctxs -> return (uuid:ctxs, length ctxs + 1))
222+ addContext
223+ when (browsersConnected == expectedConnections) . void . liftIO $ tryPutMVar ready ()
224+ thread <- forkIO $
225+ finally (run refreshMiddleware registerContext)
226+ (putMVar serverDone ())
227+ _ <- forkIO $ threadDelay 10000000 >> void (tryPutMVar ready ())
228+ when (expectedConnections /= 0) $ takeMVar ready
229+ return $ do
230+ putMVar reloadMVar ()
231+ ctxs <- takeMVar contexts
232+ mapM_ removeContext ctxs
233+ takeMVar reloadDoneMVars >>= mapM_ takeMVar
234+ killThread thread
235+ takeMVar serverDone
236+ return $ length ctxs
237+ lookupStore shutdown_0 >>= \case
238+ Nothing -> do
239+ shutdownRef <- newIORef =<< start 0
240+ void $ newStore shutdownRef
241+ Just shutdownStore -> do
242+ shutdownRef :: IORef (IO Int) <- readStore shutdownStore
243+ expectedConnections <- join (readIORef shutdownRef)
244+ writeIORef shutdownRef =<< start expectedConnections
245+ where shutdown_0 = 0
jsaddle-wkwebview/cbits-uikit/AppDelegate.m+13
@@ -19,6 +19,7 @@ HsStablePtr global_applicationDidEnterBackground = 0;
1919HsStablePtr global_applicationWillEnterForeground = 0;
2020HsStablePtr global_applicationWillTerminate = 0;
2121HsStablePtr global_applicationSignificantTimeChange = 0;
22+HsStablePtr global_applicationUniversalLink = 0;
2223uint64_t global_requestAuthorizationWithOptions = 0;
2324uint64_t global_requestAuthorizationOptionBadge = 0;
2425uint64_t global_requestAuthorizationOptionSound = 0;
@@ -119,6 +120,16 @@ HsStablePtr global_didFailToRegisterForRemoteNotificationsWithError = 0;
119120 callIO(global_applicationSignificantTimeChange);
120121}
121122
123+- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
124+ // TODO: Reroute universal links when they're clicked in-app.
125+ // https://developer.apple.com/reference/webkit/wknavigationdelegate/1455643-webview?language=objc
126+ if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] && userActivity.webpageURL) {
127+ callWithCString([userActivity.webpageURL.absoluteString UTF8String], global_applicationUniversalLink);
128+ return YES;
129+ }
130+ return NO;
131+}
132+
122133@end
123134
124135void runInWKWebView(HsStablePtr handler,
@@ -131,6 +142,7 @@ void runInWKWebView(HsStablePtr handler,
131142 HsStablePtr hs_applicationWillEnterForeground,
132143 HsStablePtr hs_applicationWillTerminate,
133144 HsStablePtr hs_applicationSignificantTimeChange,
145+ HsStablePtr hs_applicationUniversalLink,
134146 const uint64_t hs_requestAuthorizationWithOptions,
135147 const uint64_t hs_requestAuthorizationOptionBadge,
136148 const uint64_t hs_requestAuthorizationOptionSound,
@@ -149,6 +161,7 @@ void runInWKWebView(HsStablePtr handler,
149161 global_applicationWillEnterForeground = hs_applicationWillEnterForeground;
150162 global_applicationWillTerminate = hs_applicationWillTerminate;
151163 global_applicationSignificantTimeChange = hs_applicationSignificantTimeChange;
164+ global_applicationUniversalLink = hs_applicationUniversalLink;
152165 global_requestAuthorizationWithOptions = hs_requestAuthorizationWithOptions;
153166 global_requestAuthorizationOptionBadge = hs_requestAuthorizationOptionBadge;
154167 global_requestAuthorizationOptionSound = hs_requestAuthorizationOptionSound;
jsaddle-wkwebview/src-ghc/Language/Javascript/JSaddle/WKWebView.hs+5
@@ -34,6 +34,7 @@ foreign import ccall runInWKWebView
3434 -> StablePtr (IO ()) -- applicationWillEnterForeground
3535 -> StablePtr (IO ()) -- applicationWillTerminate
3636 -> StablePtr (IO ()) -- applicationSignificantTimeChange
37+ -> StablePtr (CString -> IO ()) -- applicationUniversalLink
3738 -> Word64 -- whether to run requestAuthorizationWithOptions
3839 -> Word64 -- Ask for Badge authorization
3940 -> Word64 -- Ask for Sound authorization
@@ -53,6 +54,7 @@ data AppDelegateConfig = AppDelegateConfig
5354 , _appDelegateConfig_applicationWillEnterForeground :: IO ()
5455 , _appDelegateConfig_applicationWillTerminate :: IO ()
5556 , _appDelegateConfig_applicationSignificantTimeChange :: IO ()
57+ , _appDelegateConfig_applicationUniversalLink :: CString -> IO ()
5658 , _appDelegateConfig_appDelegateNotificationConfig :: AppDelegateNotificationConfig
5759 }
5860
@@ -66,6 +68,7 @@ instance Default AppDelegateConfig where
6668 , _appDelegateConfig_applicationWillEnterForeground = return ()
6769 , _appDelegateConfig_applicationWillTerminate = return ()
6870 , _appDelegateConfig_applicationSignificantTimeChange = return ()
71+ , _appDelegateConfig_applicationUniversalLink = \_ -> return ()
6972 , _appDelegateConfig_appDelegateNotificationConfig = def
7073 }
7174
@@ -127,6 +130,7 @@ run' mUrl cfg f = do
127130 applicationWillEnterForeground <- newStablePtr $ _appDelegateConfig_applicationWillEnterForeground cfg
128131 applicationWillTerminate <- newStablePtr $ _appDelegateConfig_applicationWillTerminate cfg
129132 applicationSignificantTimeChange <- newStablePtr $ _appDelegateConfig_applicationSignificantTimeChange cfg
133+ applicationUniversalLink <- newStablePtr $ _appDelegateConfig_applicationUniversalLink cfg
130134
131135 -- AppDelegate notification configuration
132136 let ncfg = _appDelegateConfig_appDelegateNotificationConfig cfg
@@ -148,6 +152,7 @@ run' mUrl cfg f = do
148152 applicationWillEnterForeground
149153 applicationWillTerminate
150154 applicationSignificantTimeChange
155+ applicationUniversalLink
151156 (fromBool requestAuthorizationWithOptions)
152157 (fromBool $ Set.member AuthorizationOption_Badge authorizationOptions)
153158 (fromBool $ Set.member AuthorizationOption_Sound authorizationOptions)
jsaddle/default.nix+2−1
@@ -1,7 +1,7 @@
11{ mkDerivation, aeson, attoparsec, base, base64-bytestring
22, bytestring, containers, deepseq, filepath, ghc-prim, http-types
33, lens, primitive, process, ref-tf, scientific, stdenv, stm, text
4−, time, transformers, unordered-containers, vector
4+, time, transformers, unordered-containers, vector, uuid, uuid-types
55}:
66mkDerivation {
77 pname = "jsaddle";
@@ -11,6 +11,7 @@ mkDerivation {
1111 aeson attoparsec base base64-bytestring bytestring containers
1212 deepseq filepath ghc-prim http-types lens primitive process ref-tf
1313 scientific stm text time transformers unordered-containers vector
14+ uuid uuid-types
1415 ];
1516 description = "Interface for JavaScript that works with GHCJS and GHC";
1617 license = stdenv.lib.licenses.mit;
jsaddle/jsaddle.cabal+5−1
@@ -1,5 +1,5 @@
11name: jsaddle
2−version: 0.9.0.0
2+version: 0.9.2.1
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -47,6 +47,8 @@ library
4747 stm >=2.4.4 && <2.5,
4848 time >=1.5.0.1 && <1.8,
4949 unordered-containers >=0.2 && <0.3,
50+ uuid >=1.3.13 && <1.4,
51+ uuid-types >=1.0.3 && <1.1,
5052 vector >=0.10 && <0.13
5153 exposed-modules:
5254 Data.JSString
@@ -81,8 +83,10 @@ library
8183 JavaScript.Array.Internal
8284 JavaScript.Object
8385 JavaScript.Object.Internal
86+ Language.Javascript.JSaddle.Debug
8487 Language.Javascript.JSaddle.Native
8588 Language.Javascript.JSaddle.Native.Internal
89+ Language.Javascript.JSaddle.Null
8690 hs-source-dirs: src-ghc
8791 exposed-modules:
8892 Language.Javascript.JSaddle
jsaddle/src/Language/Javascript/JSaddle/Debug.hsnew file+48
@@ -0,0 +1,48 @@
1+{-# LANGUAGE LambdaCase #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+-----------------------------------------------------------------------------
4+--
5+-- Module : Language.Javascript.JSaddle.WebSockets
6+-- Copyright : (c) Hamish Mackenzie
7+-- License : MIT
8+--
9+-- Maintainer : Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
10+--
11+-- |
12+--
13+-----------------------------------------------------------------------------
14+
15+module Language.Javascript.JSaddle.Debug (
16+ contexts
17+ , addContext
18+ , removeContext
19+ , runOnAll
20+ , runOnAll_
21+) where
22+
23+import Language.Javascript.JSaddle
24+ (runJSM, askJSM, JSM, JSContextRef(..))
25+import Data.IORef (readIORef, atomicModifyIORef', newIORef, IORef)
26+import System.IO.Unsafe (unsafePerformIO)
27+import Data.Monoid ((<>))
28+import Control.Monad.IO.Class (MonadIO(..))
29+import Data.UUID (UUID)
30+
31+contexts :: IORef [JSContextRef]
32+contexts = unsafePerformIO $ newIORef []
33+{-# NOINLINE contexts #-}
34+
35+addContext :: JSM ()
36+addContext = do
37+ ctx <- askJSM
38+ liftIO $ atomicModifyIORef' contexts $ \c -> (c <> [ctx], ())
39+
40+removeContext :: MonadIO m => UUID -> m ()
41+removeContext uuid =
42+ liftIO $ atomicModifyIORef' contexts $ \c -> (filter ((/= uuid) . contextId) c, ())
43+
44+runOnAll :: MonadIO m => JSM a -> m [a]
45+runOnAll f = liftIO (readIORef contexts) >>= mapM (runJSM f)
46+
47+runOnAll_ :: MonadIO m => JSM a -> m ()
48+runOnAll_ f = liftIO (readIORef contexts) >>= mapM_ (runJSM f)
jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs+3−3
@@ -62,9 +62,9 @@ wrapJSString :: MonadIO m => JSStringReceived -> m JSString
6262wrapJSString (JSStringReceived ref) = return $ JSString ref
6363
6464withJSVal :: MonadIO m => JSVal -> (JSValueForSend -> m a) -> m a
65−withJSVal v@(JSVal ref) f =
66− do result <- f (JSValueForSend ref)
67− liftIO $ touch v
65+withJSVal (JSVal ref) f = do
66+ result <- f (JSValueForSend ref)
67+ liftIO $ touch ref
6868 return result
6969
7070withJSVals :: MonadIO m => [JSVal] -> ([JSValueForSend] -> m a) -> m a
jsaddle/src/Language/Javascript/JSaddle/Null.hsnew file+56
@@ -0,0 +1,56 @@
1+{-# LANGUAGE LambdaCase #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+-----------------------------------------------------------------------------
4+--
5+-- Module : Language.Javascript.JSaddle.WebSockets
6+-- Copyright : (c) Hamish Mackenzie
7+-- License : MIT
8+--
9+-- Maintainer : Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
10+--
11+-- |
12+--
13+-----------------------------------------------------------------------------
14+
15+module Language.Javascript.JSaddle.Null (
16+ run
17+) where
18+
19+import Language.Javascript.JSaddle.Types
20+ (BatchResults(..), JSM, JSStringReceived(..), Batch(..),
21+ Results(..), Result(..), Command(..))
22+import Control.Concurrent.Chan (readChan, writeChan, newChan)
23+import Language.Javascript.JSaddle.Run (runJavaScript)
24+import Control.Concurrent (forkIO)
25+import Control.Monad (forever)
26+import Data.Aeson (Value(..))
27+import Data.Maybe (mapMaybe)
28+
29+-- | This is for performance testing JSaddle code that does not need to
30+-- to read anything back from the JavaScript context.
31+-- Anthing that does try to read will get JS null, 0 or "" back (depending
32+-- on how the value is read).
33+run :: JSM () -> IO ()
34+run f = do
35+ batches <- newChan
36+ (processResult, _processSyncResult, start) <- runJavaScript (writeChan batches) f
37+ _ <- forkIO $ forever $
38+ readChan batches >>= \case
39+ Batch commands _ batchNumber ->
40+ processResult $ BatchResults batchNumber . Success $ mapMaybe (\case
41+ Left _ -> Nothing
42+ Right command -> Just $
43+ case command of
44+ DeRefVal _ -> DeRefValResult 0 ""
45+ ValueToBool _ -> ValueToBoolResult False
46+ ValueToNumber _ -> ValueToNumberResult 0
47+ ValueToString _ -> ValueToStringResult (JSStringReceived "")
48+ ValueToJSON _ -> ValueToJSONResult (JSStringReceived "null")
49+ ValueToJSONValue _ -> ValueToJSONValueResult Null
50+ IsNull _ -> IsNullResult True
51+ IsUndefined _ -> IsUndefinedResult False
52+ StrictEqual _ _ -> StrictEqualResult False
53+ InstanceOf _ _ -> InstanceOfResult False
54+ PropertyNames _ -> PropertyNamesResult []
55+ Sync -> SyncResult) commands
56+ start
jsaddle/src/Language/Javascript/JSaddle/Run.hs+38−16
@@ -2,6 +2,8 @@
22{-# LANGUAGE CPP #-}
33{-# LANGUAGE LambdaCase #-}
44{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE MagicHash #-}
6+{-# LANGUAGE UnboxedTuples #-}
57-----------------------------------------------------------------------------
68--
79-- Module : Language.Javascript.JSaddle.Run
@@ -39,25 +41,29 @@ import Language.Javascript.JSaddle.Types (JSM)
3941import qualified JavaScript.Web.AnimationFrame as GHCJS
4042 (waitForAnimationFrame)
4143#else
42−import Control.Exception (throwIO)
43−import Control.Monad (void, when, forever, zipWithM_)
44+import Control.Exception (throwIO, evaluate)
45+import Control.Monad (void, when, zipWithM_)
4446import Control.Monad.IO.Class (MonadIO(..))
4547import Control.Monad.Trans.Reader (ask, runReaderT)
4648import Control.Monad.STM (atomically)
47−import Control.Concurrent (forkIO)
49+import Control.Concurrent (forkIO, myThreadId)
4850import Control.Concurrent.STM.TChan
4951 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
5052import Control.Concurrent.STM.TVar
5153 (writeTVar, readTVar, readTVarIO, modifyTVar', newTVarIO)
5254import Control.Concurrent.MVar
53− (tryTakeMVar, MVar, putMVar, takeMVar, newEmptyMVar, readMVar)
55+ (tryTakeMVar, MVar, putMVar, takeMVar, newMVar, newEmptyMVar, readMVar, modifyMVar)
5456
5557import System.IO.Unsafe (unsafeInterleaveIO)
5658import System.Mem.Weak (addFinalizer)
5759
60+import GHC.Base (IO(..), mkWeak#)
61+import GHC.Conc (ThreadId(..))
5862import Data.Monoid ((<>))
59−import qualified Data.Text as T (unpack)
63+import qualified Data.Text as T (unpack, pack)
6064import qualified Data.Map as M (lookup, delete, insert, empty, size)
65+import qualified Data.Set as S (empty, member, insert, delete)
66+import Data.UUID.V4 (nextRandom)
6167import Data.Time.Clock (getCurrentTime,diffUTCTime)
6268import Data.IORef (newIORef, atomicWriteIORef, readIORef)
6369
@@ -65,11 +71,8 @@ import Language.Javascript.JSaddle.Types
6571 (Command(..), AsyncCommand(..), Result(..), BatchResults(..), Results(..), JSContextRef(..), JSVal(..),
6672 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
6773import Language.Javascript.JSaddle.Exception (JSException(..))
68−-- import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
6974import Control.DeepSeq (deepseq)
7075import GHC.Stats (getGCStatsEnabled, getGCStats, GCStats(..))
71−import Data.Maybe (isJust)
72−import Data.Foldable (forM_)
7376#endif
7477
7578-- | Enable (or disable) JSaddle logging
@@ -138,7 +141,7 @@ sendLazyCommand cmd = do
138141 nextRefTVar <- nextRef <$> JSM ask
139142 n <- liftIO . atomically $ do
140143 n <- subtract 1 <$> readTVar nextRefTVar
141− writeTVar nextRefTVar n
144+ writeTVar nextRefTVar $! n
142145 return n
143146 s <- doSendAsyncCommand <$> JSM ask
144147 liftIO $ s (cmd $ JSValueForSend n)
@@ -151,15 +154,18 @@ sendAsyncCommand cmd = do
151154
152155runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), Results -> IO Batch, IO ())
153156runJavaScript sendBatch entryPoint = do
157+ contextId' <- nextRandom
154158 startTime' <- getCurrentTime
155159 recvMVar <- newEmptyMVar
156160 lastAsyncBatch <- newEmptyMVar
157161 commandChan <- newTChanIO
158162 callbacks <- newTVarIO M.empty
159163 nextRef' <- newTVarIO 0
164+ finalizerThreads' <- newMVar S.empty
160165 loggingEnabled <- newIORef False
161166 let ctx = JSContextRef {
162− startTime = startTime'
167+ contextId = contextId'
168+ , startTime = startTime'
163169 , doSendCommand = \cmd -> cmd `deepseq` do
164170 result <- newEmptyMVar
165171 atomically $ writeTChan commandChan (Right (cmd, result))
@@ -172,13 +178,14 @@ runJavaScript sendBatch entryPoint = do
172178 , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar' callbacks (M.delete val)
173179 , nextRef = nextRef'
174180 , doEnableLogging = atomicWriteIORef loggingEnabled
181+ , finalizerThreads = finalizerThreads'
175182 }
176183 let processResults :: Bool -> Results -> IO ()
177184 processResults syncCallbacks = \case
178185 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
179− (Callback n br f this a) -> do
186+ (Callback n br (JSValueReceived fNumber) f this a) -> do
180187 putMVar recvMVar (n, br)
181− f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
188+ f' <- runReaderT (unJSM $ wrapJSVal f) ctx
182189 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
183190 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
184191 logInfo (("Call " <> show fNumber <> " ") <>)
@@ -207,7 +214,7 @@ runJavaScript sendBatch entryPoint = do
207214 True -> show . currentBytesUsed <$> getGCStats
208215 False -> return "??"
209216 cbCount <- M.size <$> readTVarIO callbacks
210− putStrLn . s $ "M " <> currentBytesUsedStr <> "CB " <> show cbCount
217+ putStrLn . s $ "M " <> currentBytesUsedStr <> " CB " <> show cbCount <> " "
211218 False -> return ()
212219 _ <- forkIO . numberForeverFromM_ 1 $ \nBatch ->
213220 readBatch nBatch commandChan >>= \case
@@ -229,7 +236,7 @@ runJavaScript sendBatch entryPoint = do
229236 where
230237 numberForeverFromM_ :: (Monad m, Enum n) => n -> (n -> m a) -> m ()
231238 numberForeverFromM_ !n f = do
232− f n
239+ _ <- f n
233240 numberForeverFromM_ (succ n) f
234241 takeResult recvMVar nBatch =
235242 takeMVar recvMVar >>= \case
@@ -263,12 +270,27 @@ runJavaScript sendBatch entryPoint = do
263270 loopAnimation (Left asyncCmd) (cmds, resultMVars) =
264271 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)
265272
273+addThreadFinalizer :: ThreadId -> IO () -> IO ()
274+addThreadFinalizer t@(ThreadId t#) (IO finalizer) =
275+ IO $ \s -> case mkWeak# t# t finalizer s of { (# s1, _ #) -> (# s1, () #) }
276+
266277wrapJSVal :: JSValueReceived -> JSM JSVal
267278wrapJSVal (JSValueReceived ref) = do
268279 -- TODO make sure this ref has not already been wrapped (perhaps only in debug version)
269280 let result = JSVal ref
270− when (ref >= 5) $ do
281+ when (ref >= 5 || ref < 0) $ do
271282 ctx <- JSM ask
272− liftIO . addFinalizer result $ doSendAsyncCommand ctx $ FreeRef $ JSValueForSend ref
283+ liftIO . addFinalizer ref $ do
284+ ft <- takeMVar $ finalizerThreads ctx
285+ t <- myThreadId
286+ let tname = T.pack $ show t
287+ doSendAsyncCommand ctx $ FreeRef tname $ JSValueForSend ref
288+ if tname `S.member` ft
289+ then putMVar (finalizerThreads ctx) ft
290+ else do
291+ addThreadFinalizer t $ do
292+ modifyMVar (finalizerThreads ctx) $ \s -> return (S.delete tname s, ())
293+ doSendAsyncCommand ctx $ FreeRefs tname
294+ putMVar (finalizerThreads ctx) =<< evaluate (S.insert tname ft)
273295 return result
274296#endif
jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+27−10
@@ -37,6 +37,7 @@ indexHtml =
3737initState :: ByteString
3838initState = "\
3939 \ var jsaddle_values = new Map();\n\
40+ \ var jsaddle_free = new Map();\n\
4041 \ jsaddle_values.set(0, null);\n\
4142 \ jsaddle_values.set(1, undefined);\n\
4243 \ jsaddle_values.set(2, false);\n\
@@ -67,7 +68,15 @@ runBatch send sendSync = "\
6768 \ var d = cmd.Left;\n\
6869 \ switch (d.tag) {\n\
6970 \ case \"FreeRef\":\n\
70− \ jsaddle_values.delete(d.contents);\n\
71+ \ var refsToFree = jsaddle_free.get(d.contents[0]) || [];\n\
72+ \ refsToFree.push(d.contents[1]);\n\
73+ \ jsaddle_free.set(d.contents[0], refsToFree);\n\
74+ \ break;\n\
75+ \ case \"FreeRefs\":\n\
76+ \ var refsToFree = jsaddle_free.get(d.contents) || [];\n\
77+ \ for(var nRef = 0; nRef != refsToFree.length; nRef++)\n\
78+ \ jsaddle_values.delete(refsToFree[nRef]);\n\
79+ \ jsaddle_free.delete(d.contents);\n\
7180 \ break;\n\
7281 \ case \"SetPropertyByName\":\n\
7382 \ jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);\n\
@@ -102,7 +111,9 @@ runBatch send sendSync = "\
102111 \ case \"NewAsyncCallback\":\n\
103112 \ (function() {\n\
104113 \ var nFunction = d.contents;\n\
105− \ jsaddle_values.set(nFunction, function() {\n\
114+ \ var func = function() {\n\
115+ \ var nFunctionInFunc = ++jsaddle_index;\n\
116+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
106117 \ var nThis = ++jsaddle_index;\n\
107118 \ jsaddle_values.set(nThis, this);\n\
108119 \ var args = [];\n\
@@ -111,13 +122,17 @@ runBatch send sendSync = "\
111122 \ jsaddle_values.set(nArg, arguments[i]);\n\
112123 \ args[i] = nArg;\n\
113124 \ }\n\
114− \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> "\n\
115− \ })})();\n\
125+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
126+ \ };\n\
127+ \ jsaddle_values.set(nFunction, func);\n\
128+ \ })();\n\
116129 \ break;\n\
117130 \ case \"NewSyncCallback\":\n\
118131 \ (function() {\n\
119132 \ var nFunction = d.contents;\n\
120− \ jsaddle_values.set(nFunction, function() {\n\
133+ \ var func = function() {\n\
134+ \ var nFunctionInFunc = ++jsaddle_index;\n\
135+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
121136 \ var nThis = ++jsaddle_index;\n\
122137 \ jsaddle_values.set(nThis, this);\n\
123138 \ var args = [];\n\
@@ -129,14 +144,16 @@ runBatch send sendSync = "\
129144 case sendSync of
130145 Just s ->
131146 " if(inCallback > 0) {\n\
132− \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> "\n\
147+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
133148 \ } else {\n\
134− \ runBatch(" <> s "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> ", 1);\n\
149+ \ runBatch(" <> s "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> ", 1);\n\
135150 \ }\n"
136151 Nothing ->
137− " " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nThis, args]}" <> "\n"
152+ " " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n"
138153 ) <>
139− " })})();\n\
154+ " };\n\
155+ \ jsaddle_values.set(nFunction, func);\n\
156+ \ })();\n\
140157 \ break;\n\
141158 \ case \"CallAsFunction\":\n\
142159 \ var n = d.contents[3];\n\
@@ -179,7 +196,7 @@ runBatch send sendSync = "\
179196 \ jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));\n\
180197 \ break;\n\
181198 \ case \"SyncWithAnimationFrame\":\n\
182− \ var n = d.contents[0];\n\
199+ \ var n = d.contents;\n\
183200 \ jsaddle_values.set(n, timestamp);\n\
184201 \ break;\n\
185202 \ case \"StartSyncBlock\":\n\
jsaddle/src/Language/Javascript/JSaddle/Types.hs+60−16
@@ -106,7 +106,10 @@ import Control.Monad.Trans.Class (MonadTrans(..))
106106import Control.Monad.Fix (MonadFix)
107107import Control.Monad.Ref (MonadAtomicRef(..), MonadRef(..))
108108import Control.Concurrent.STM.TVar (TVar)
109+import Control.Concurrent.MVar (MVar)
110+import Data.Set (Set)
109111import Data.Text (Text)
112+import Data.UUID (UUID)
110113import Data.Time.Clock (UTCTime(..))
111114import Data.Typeable (Typeable)
112115import Data.Coerce (coerce, Coercible)
@@ -129,13 +132,15 @@ import GHC.Exts (Constraint)
129132type JSContextRef = ()
130133#else
131134data JSContextRef = JSContextRef {
132− startTime :: UTCTime
135+ contextId :: UUID
136+ , startTime :: UTCTime
133137 , doSendCommand :: Command -> IO Result
134138 , doSendAsyncCommand :: AsyncCommand -> IO ()
135139 , addCallback :: Object -> JSCallAsFunction -> IO ()
136140 , freeCallback :: Object -> IO ()
137141 , nextRef :: TVar JSValueRef
138142 , doEnableLogging :: Bool -> IO ()
143+ , finalizerThreads :: MVar (Set Text)
139144}
140145#endif
141146
@@ -211,19 +216,57 @@ instance MonadJSM JSM where
211216 liftJSM' = id
212217 {-# INLINE liftJSM' #-}
213218
214−instance (MonadJSM m) => MonadJSM (ContT r m)
215−instance (Error e, MonadJSM m) => MonadJSM (ErrorT e m)
216−instance (MonadJSM m) => MonadJSM (ExceptT e m)
217−instance (MonadJSM m) => MonadJSM (IdentityT m)
218−instance (MonadJSM m) => MonadJSM (ListT m)
219−instance (MonadJSM m) => MonadJSM (MaybeT m)
220−instance (MonadJSM m) => MonadJSM (ReaderT r m)
221−instance (Monoid w, MonadJSM m) => MonadJSM (Lazy.RWST r w s m)
222−instance (Monoid w, MonadJSM m) => MonadJSM (Strict.RWST r w s m)
223−instance (MonadJSM m) => MonadJSM (Lazy.StateT s m)
224−instance (MonadJSM m) => MonadJSM (Strict.StateT s m)
225−instance (Monoid w, MonadJSM m) => MonadJSM (Lazy.WriterT w m)
226−instance (Monoid w, MonadJSM m) => MonadJSM (Strict.WriterT w m)
219+instance (MonadJSM m) => MonadJSM (ContT r m) where
220+ liftJSM' = lift . liftJSM'
221+ {-# INLINE liftJSM' #-}
222+
223+instance (Error e, MonadJSM m) => MonadJSM (ErrorT e m) where
224+ liftJSM' = lift . liftJSM'
225+ {-# INLINE liftJSM' #-}
226+
227+instance (MonadJSM m) => MonadJSM (ExceptT e m) where
228+ liftJSM' = lift . liftJSM'
229+ {-# INLINE liftJSM' #-}
230+
231+instance (MonadJSM m) => MonadJSM (IdentityT m) where
232+ liftJSM' = lift . liftJSM'
233+ {-# INLINE liftJSM' #-}
234+
235+instance (MonadJSM m) => MonadJSM (ListT m) where
236+ liftJSM' = lift . liftJSM'
237+ {-# INLINE liftJSM' #-}
238+
239+instance (MonadJSM m) => MonadJSM (MaybeT m) where
240+ liftJSM' = lift . liftJSM'
241+ {-# INLINE liftJSM' #-}
242+
243+instance (MonadJSM m) => MonadJSM (ReaderT r m) where
244+ liftJSM' = lift . liftJSM'
245+ {-# INLINE liftJSM' #-}
246+
247+instance (Monoid w, MonadJSM m) => MonadJSM (Lazy.RWST r w s m) where
248+ liftJSM' = lift . liftJSM'
249+ {-# INLINE liftJSM' #-}
250+
251+instance (Monoid w, MonadJSM m) => MonadJSM (Strict.RWST r w s m) where
252+ liftJSM' = lift . liftJSM'
253+ {-# INLINE liftJSM' #-}
254+
255+instance (MonadJSM m) => MonadJSM (Lazy.StateT s m) where
256+ liftJSM' = lift . liftJSM'
257+ {-# INLINE liftJSM' #-}
258+
259+instance (MonadJSM m) => MonadJSM (Strict.StateT s m) where
260+ liftJSM' = lift . liftJSM'
261+ {-# INLINE liftJSM' #-}
262+
263+instance (Monoid w, MonadJSM m) => MonadJSM (Lazy.WriterT w m) where
264+ liftJSM' = lift . liftJSM'
265+ {-# INLINE liftJSM' #-}
266+
267+instance (Monoid w, MonadJSM m) => MonadJSM (Strict.WriterT w m) where
268+ liftJSM' = lift . liftJSM'
269+ {-# INLINE liftJSM' #-}
227270
228271instance MonadRef JSM where
229272 type Ref JSM = Ref IO
@@ -320,7 +363,8 @@ newtype JSStringForSend = JSStringForSend Text deriving(Show, ToJSON, FromJSON,
320363instance NFData JSStringForSend
321364
322365-- | Command sent to a JavaScript context for execution asynchronously
323−data AsyncCommand = FreeRef JSValueForSend
366+data AsyncCommand = FreeRef Text JSValueForSend
367+ | FreeRefs Text
324368 | SetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
325369 | SetPropertyAtIndex JSObjectForSend Int JSValueForSend
326370 | StringToValue JSStringForSend JSValueForSend
@@ -412,7 +456,7 @@ instance FromJSON BatchResults
412456
413457data Results = BatchResults Int BatchResults
414458 | Duplicate Int Int
415− | Callback Int BatchResults JSValueReceived JSValueReceived [JSValueReceived]
459+ | Callback Int BatchResults JSValueReceived JSValueReceived JSValueReceived [JSValueReceived]
416460 | ProtocolError Text
417461 deriving (Show, Generic)
418462