on master help sign in
53 files changed+1879−764
since splitsnapshots
unifiedsplit
.travis.yml+40−24 README.md+34−14 cabal.project+4−35 default.nix−8 jsaddle-clib/cbits/include/jsaddle.h+13 jsaddle-clib/default.nix−9 jsaddle-clib/jsaddle-clib.cabal+7−4 jsaddle-clib/src-ghc/Language/Javascript/JSaddle/CLib.hs+90−8 jsaddle-clib/src/Language/Javascript/JSaddle/CLib/Internal.hsc+39 jsaddle-warp/default.nix−26 jsaddle-warp/jsaddle-warp.cabal+21−13 jsaddle-warp/src/Language/Javascript/JSaddle/Test.hs+13−12 jsaddle-warp/src/Language/Javascript/JSaddle/Warp.hs+3−4 jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets.hs+286−55 jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets/Compat.hs+29 jsaddle-warp/tests/DocTest.hs+20−7 jsaddle-webkit2gtk/default.nix−16 jsaddle-webkit2gtk/jsaddle-webkit2gtk.cabal+10−9 jsaddle-webkit2gtk/src/Language/Javascript/JSaddle/WebKitGTK.hs+44−46 jsaddle-webkitgtk/default.nix−16 jsaddle-webkitgtk/jsaddle-webkitgtk.cabal+3−3 jsaddle-webkitgtk/src/Language/Javascript/JSaddle/WebKitGTK.hs+2−2 jsaddle-wkwebview/cbits-cocoa/AppDelegate.m+65−6 jsaddle-wkwebview/cbits-uikit/AppDelegate.m+109−23 jsaddle-wkwebview/cbits-uikit/ViewController.m+2−2 jsaddle-wkwebview/cbits/WKWebView.m+42−18 jsaddle-wkwebview/default.nix+14−14 jsaddle-wkwebview/jsaddle-wkwebview.cabal+20−11 jsaddle-wkwebview/src-ghc/Language/Javascript/JSaddle/WKWebView.hs+159−39 jsaddle-wkwebview/src/Language/Javascript/JSaddle/WKWebView/Internal.hs+69−25 jsaddle/default.nix−17 jsaddle/jsaddle.cabal+20−15 jsaddle/src-ghc/Data/JSString/Internal/Type.hs+2−1 jsaddle/src-ghc/GHCJS/Buffer.hs+2−2 jsaddle/src-ghc/GHCJS/Concurrent.hs+22 jsaddle/src-ghc/GHCJS/Foreign/Internal.hs+12−10 jsaddle/src-ghc/GHCJS/Prim/Internal.hs+5−3 jsaddle/src-ghc/GHCJS/Types.hs+5−3 jsaddle/src-ghc/JavaScript/Array.hs+2−2 jsaddle/src-ghc/JavaScript/Array/Internal.hs−2 jsaddle/src/Language/Javascript/JSaddle.hs+5−5 jsaddle/src/Language/Javascript/JSaddle/Debug.hs+48 jsaddle/src/Language/Javascript/JSaddle/Evaluate.hs+1−1 jsaddle/src/Language/Javascript/JSaddle/Monad.hs+3−26 jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs+55−31 jsaddle/src/Language/Javascript/JSaddle/Null.hs+56 jsaddle/src/Language/Javascript/JSaddle/Object.hs+51−19 jsaddle/src/Language/Javascript/JSaddle/Run.hs+150−82 jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+132−56 jsaddle/src/Language/Javascript/JSaddle/Types.hs+155−25 jsaddle/src/Language/Javascript/JSaddle/Value.hs+5−3 nix/hix.nix+8 travis/build.sh+2−12
.travis.yml+40−24
@@ -1,31 +1,47 @@
1−sudo: required
2−services:
3− - docker
1+language: nix
2+nix: 2.2.2
43
5−cache:
6− directories:
7− - .cabal
8− - .ghc
4+jobs:
5+ include:
6+ - stage: Build
7+ env: OS=linux GHCVER=ghc865
8+ - stage: Build
9+ env: OS=linux GHCVER=ghc881
10+ - env: OS=macos GHCVER=ghc865
11+ os: osx
12+ # Fix OSX, see https://travis-ci.community/t/cannot-use-nix-support-on-osx/2927
13+ language: generic
14+ before_install:
15+ - curl https://nixos.org/nix/install | sh
16+ - source /Users/travis/.nix-profile/etc/profile.d/nix.sh
17+ - env: OS=macos GHCVER=ghc881
18+ os: osx
19+ # Fix OSX, see https://travis-ci.community/t/cannot-use-nix-support-on-osx/2927
20+ language: generic
21+ before_install:
22+ - curl https://nixos.org/nix/install | sh
23+ - source /Users/travis/.nix-profile/etc/profile.d/nix.sh
924
10−env:
11−# - CABALVER=1.24 GHCVER=7.10.3 OSVER=xenial
12− - CABALVER=1.24 GHCVER=8.0.1 OSVER=xenial
13− - CABALVER=1.24 GHCVER=8.0.2 OSVER=xenial
14− - CABALVER=1.24 GHCVER=7.10.3 OSVER=trusty
15− - CABALVER=1.24 GHCVER=8.0.1 OSVER=trusty
16− - CABALVER=1.24 GHCVER=8.0.2 OSVER=trusty
17−
18−before_install:
19− - docker build -t ghc -f travis/$OSVER.Dockerfile travis
25+install:
26+- sudo mkdir -p /etc/nix
27+- echo "substituters = https://cache.nixos.org https://hydra.iohk.io" | sudo tee -a /etc/nix/nix.conf > /dev/null
28+- echo "trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=" | sudo tee -a /etc/nix/nix.conf > /dev/null
2029
2130script:
22− - docker run -v `pwd`:/build ghc
23− sh -x -c "
24− apt-get install -y cabal-install-$CABALVER ghc-$GHCVER &&
25− export PATH=\$HOME/.cabal/bin:/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:/opt/alex/3.1.7/bin:/opt/happy/1.19.5/bin:\$PATH &&
26− ./travis/build.sh"
31+- nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle.components.library
32+- nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle-warp.components.library
33+- nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle-clib.components.library
34+- |
35+ if [ "$OS" == "macos" ]; then
36+ nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle-wkwebview.components.library
37+ fi
38+# TODO try to find how to get working webkitgtk with nix on macOS
39+- |
40+ if [ "$OS" != "macos" ]; then
41+ nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle-webkit2gtk.components.library
42+ fi
43+
2744notifications:
2845 irc:
2946 channels: "irc.freenode.net#ghcjs"
30− skip_join: true
31− email: true
47+ email: true
README.md+34−14
@@ -13,7 +13,8 @@ import Control.Monad.IO.Class (MonadIO(..))
1313import Control.Concurrent.MVar (takeMVar, putMVar, newEmptyMVar)
1414import Control.Lens ((^.))
1515import Language.Javascript.JSaddle
16− (run, jsg, js, js1, jss, fun, valToNumber, syncPoint)
16+ (jsg, js, js1, jss, fun, valToNumber, syncPoint)
17+import Language.Javascript.JSaddle.Warp (run)
1718
1819main = run 3709 $ do
1920 doc <- jsg "document"
@@ -79,7 +80,7 @@ main = run 3708 $ do
7980 appendChild newParagraph text
8081 appendChild body (Just newParagraph)
8182 return ()
82−
83+
8384 -- Make an exit button
8485 exitMVar <- liftIO newEmptyMVar
8586 Just exit <- createElement doc (Just "span")
@@ -102,15 +103,31 @@ When compiled with GHC this code will run a Warp web server you can connect to a
102103
103104## How does it work on GHC
104105
105−As of version 0.5 it runs a small warp server that provides [index.html](https://github.com/ghcjs/jsaddle/blob/master/data/index.html) and
106−[jsaddle.js](https://github.com/ghcjs/jsaddle/blob/master/data/jsaddle.js). The [jsaddle.js](https://github.com/ghcjs/jsaddle/blob/master/data/jsaddle.js) code connects to the server using WebSockets and processes JSON commands from the server and sends the result back over the same WebSocket.
106+There are a number of different JSaddle runners to choose from
107+
108+* [jsaddle-warp](https://hackage.haskell.org/package/jsaddle-warp) - runs JSaddle in a warp server with a web browser connected to it.
109+* [jsaddle-webkit2gtk](https://hackage.haskell.org/package/jsaddle-webkit2gtk) - runs JSaddle in a WebKitGTK window.
110+* [jsaddle-wkwebview](https://hackage.haskell.org/package/jsaddle-wkwebview) - runs JSaddle in a WKWebView on iOS or macOS.
111+* [jsaddle-clib](https://hackage.haskell.org/package/jsaddle-clib) - C interface used to run JSaddle on Android using JNI.
112+
113+In all of these cases a web control or browser of some sort is used. An
114+[HTML file](./jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs#L24)
115+and a [small JavaScript command interpreter](./jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs#L340)
116+are loaded into it. Then the native GHC compiled JSaddle code runs. In order to interact with the browser
117+it sends commands to the JavaScript command interpreter. The mechanism for sending the commands differs
118+for the different runners, but they are always combined into batches and encoded in JSON.
107119
108−### Why use WebSockets?
120+Haskell callbacks from JavaScript are supported by sending JSON back from the command interpreter. These can be synchronous (blocking the
121+JavaScript thread until the Haskell callback completes) or asynchronous.
109122
110−Older versions relied on WebKitGTK and JavaScriptCore, but in WekKit2 the interface JSaddle used is only available to WebKit Extensions
111−so most of the benefits of this interface are lost (extensions are not in the same process as Gtk and RPC would have been needed).
112−The general advice for people migrating from WebKit1 to WebKit2 seems to be to use JavaScript. There is an interface in WebKit2
113−to send JavaScript code from the host application, but it does not seem to provide any advantages over the WebSockets approach.
123+### Why use a JavaScript command interpreter?
124+
125+Older versions of JSaddle relied on the WebKit1 interface to WebKitGTK and JavaScriptCore that is only supported in older versions of
126+WebKitGTK. With the newer WebKit2 interface this level of access is only available to WebKit Extensions.
127+The general advice for people migrating from WebKit1 to WebKit2 seems to be to use JavaScript.
128+
129+As a bonus we have been able to support a number of different platforms with the JavaScript command interpreter and it should
130+be easy to support more in the future.
114131
115132### JSVal
116133
@@ -120,8 +137,8 @@ The first five keys `[0..4]` are
120137these values. Both the Haskell code and `jsaddle.js` can allocate new `JSVal` keys. The Haskell code allocates negative
121138keys and `jsaddle.js` allocates positive keys to avoid clashing.
122139
123−The haskell code will not know the value of the to go with the key, but it will include it in commands to the server
124−that are sort of "hey call this function and and put the result in the map with this key"). This allows for the
140+The haskell code will not know the value to go with the key, but it will include it in commands to the server
141+that are sort of "hey call this function and put the result in the map with this key"). This allows for the
125142lazy execution of the JSaddle code.
126143
127144A finalizer is added on the Haskell code and an asynchronous `FreeJSVal` command is automatically sent to `jsaddle.js`
@@ -129,7 +146,7 @@ when the JSVal is no longer reachable. The `jsaddle.js` code then deletes the k
129146
130147### JSStrings
131148
132−These are haskell `Text` type and so reside on the native side of the WebSocket. If you want to make avoid transferring large string values over the WebSocket use `toJSVal` to conevt it to a `JSVal`.
149+These are haskell `Text` type and so reside on the native side of the WebSocket. If you want to avoid transferring large string values over the WebSocket use `toJSVal` to convert it to a `JSVal`.
133150
134151### Lazy Execution
135152
@@ -161,7 +178,7 @@ force all the pending asynchronous commands to be executed.
161178It uses a handful of JS FFI calls to execute JavaScript functions indirectly. This indirection will be small compared to the overhead of the WebSockets approach (used when JSaddle is compiled with GHC), but it will be significant compared to hand crafted JS FFI calls.
162179
163180For the best performance you may want to write both JS FFI and JSaddle wrappers
164−For your JavaScript code. This is the approach taken by recent versions of `ghcjs-dom`.
181+for your JavaScript code. This is the approach taken by `ghcjs-dom`.
165182
166183For instance here is `getElementById` from the `ghcjs-dom-jsffi` (used by `ghcjs-dom` when compiled with GHCJS)
167184
@@ -196,7 +213,10 @@ JSaddle does not support exceptions well. When compiled with GHCJS an exception
196213at the point the exception is thrown.
197214
198215When using GHC the Haskell executions will probably continue for a while before the exception is received at all by the Haskell code
199−(because the lazy execution of the JS code). The exception will be thrown when the the next synchronous command is executed.
216+(because the lazy execution of the JS code). The exception will be thrown when the next synchronous command is executed.
200217When it reaches the synchronous command a haskell type JSException will be thrown (this may not be the thread that initiated the
201218command that caused the exception). You can use `syncPoint` and `syncAfter` to force the exception to be thrown.
202219There are `JSM` versions of `catch` and `bracket` that also include a `syncPoint` call.
220+
221+## Building with Stack
222+The `jsaddle-webkit2gtk` runner can be difficult to build with stack. See [this issue](https://github.com/ghcjs/jsaddle/issues/38#issuecomment-331392995) if you get stuck.
cabal.project+4−35
@@ -1,41 +1,10 @@
1−flags: -overloaded-methods -overloaded-signals -overloaded-properties
2−
31packages:
42 jsaddle/
53 jsaddle-warp/
64 jsaddle-wkwebview/
7−-- jsaddle-webkitgtk/
85 jsaddle-webkit2gtk/
6+ jsaddle-clib/
7+
8+constraints: haskell-gi-overloading == 0.0
99
10−package gi-atk
11− flags: -overloaded-methods -overloaded-signals -overloaded-properties
12−package gi-cairo
13− flags: -overloaded-methods -overloaded-signals -overloaded-properties
14−package gi-gdk
15− flags: -overloaded-methods -overloaded-signals -overloaded-properties
16−package gi-gdkpixbuf
17− flags: -overloaded-methods -overloaded-signals -overloaded-properties
18−package gi-gio
19− flags: -overloaded-methods -overloaded-signals -overloaded-properties
20−package gi-glib
21− flags: -overloaded-methods -overloaded-signals -overloaded-properties
22−package gi-gobject
23− flags: -overloaded-methods -overloaded-signals -overloaded-properties
24−package gi-gtk
25− flags: -overloaded-methods -overloaded-signals -overloaded-properties
26−package gi-gtk-hs
27− flags: -overloaded-methods -overloaded-signals -overloaded-properties
28−package gi-gtkosxapplication
29− flags: -overloaded-methods -overloaded-signals -overloaded-properties
30−package gi-gtksource
31− flags: -overloaded-methods -overloaded-signals -overloaded-properties
32−package gi-javascriptcore
33− flags: -overloaded-methods -overloaded-signals -overloaded-properties
34−package gi-pango
35− flags: -overloaded-methods -overloaded-signals -overloaded-properties
36−package gi-soup
37− flags: -overloaded-methods -overloaded-signals -overloaded-properties
38−package gi-webkit
39− flags: -overloaded-methods -overloaded-signals -overloaded-properties
40−package gi-webkit2
41− flags: -overloaded-methods -overloaded-signals -overloaded-properties
10+allow-newer: websockets:random, splitmix:time, *:time, *:unix, *:process, stm:base
default.nixdeleted−8
@@ -1,8 +0,0 @@
1−haskellPackages: {
2− jsaddle = haskellPackages.callPackage ./jsaddle {};
3− jsaddle-warp = haskellPackages.callPackage ./jsaddle-warp {};
4− jsaddle-wkwebview = haskellPackages.callPackage ./jsaddle-wkwebview {};
5− jsaddle-webkit2gtk = haskellPackages.callPackage ./jsaddle-webkit2gtk {};
6− jsaddle-webkitgtk = haskellPackages.callPackage ./jsaddle-webkitgtk {};
7− jsaddle-clib = haskellPackages.callPackage ./jsaddle-clib {};
8−}
jsaddle-clib/cbits/include/jsaddle.h+13
@@ -4,8 +4,21 @@
44typedef struct native_callbacks {
55 void (* jsaddleStart) ();
66 void (* jsaddleResult) (char *);
7+ char * (* jsaddleSyncResult) (char *);
78 char * jsaddleJsData;
89 char * jsaddleHtmlData;
910} native_callbacks;
1011
12+typedef struct app_callbacks {
13+ void (* mainActivity_onCreate) ();
14+ void (* mainActivity_onStart) ();
15+ void (* mainActivity_onResume) ();
16+ void (* mainActivity_onPause) ();
17+ void (* mainActivity_onStop) ();
18+ void (* mainActivity_onDestroy) ();
19+ void (* mainActivity_onRestart) ();
20+ void (* mainActivity_onNewIntent) (char *, char *);
21+ void (* firebaseInstanceIdService_sendRegistrationToServer) (char *);
22+} app_callbacks;
23+
1124#endif
jsaddle-clib/default.nixdeleted−9
@@ -1,9 +0,0 @@
1−{ mkDerivation, aeson, base, bytestring, jsaddle, stdenv }:
2−mkDerivation {
3− pname = "jsaddle-clib";
4− version = "0.8.2.0";
5− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
6− libraryHaskellDepends = [ aeson base bytestring jsaddle ];
7− description = "Interface for JavaScript that works with GHCJS and GHC";
8− license = stdenv.lib.licenses.mit;
9−}
jsaddle-clib/jsaddle-clib.cabal+7−4
@@ -1,5 +1,5 @@
11name: jsaddle-clib
2−version: 0.8.3.0
2+version: 0.9.8.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -21,16 +21,19 @@ library
2121 Language.Javascript.JSaddle.CLib
2222 Language.Javascript.JSaddle.CLib.Internal
2323 build-depends:
24− aeson >=0.8.0.2 && <1.2,
24+ aeson >=0.8.0.2 && <1.6,
2525 base <5,
26+ base-compat >=0.9.0 && <0.12,
2627 bytestring >=0.10.6.0 && <0.11,
27− jsaddle >= 0.8.0.0 && <0.9
28+ jsaddle >= 0.9.0.0 && <0.10,
29+ data-default,
30+ text
2831 default-language: Haskell2010
2932 hs-source-dirs: src
3033 Include-dirs: cbits/include
3134 Includes: jsaddle.h
3235 Install-includes: jsaddle.h
33− ghc-options: -ferror-spans -Wall -Werror
36+ ghc-options: -ferror-spans -Wall
3437 if impl(ghcjs)
3538 hs-source-dirs: src-ghcjs
3639 else
jsaddle-clib/src-ghc/Language/Javascript/JSaddle/CLib.hs+90−8
@@ -2,19 +2,29 @@
22module Language.Javascript.JSaddle.CLib
33 ( jsaddleInit
44 , NativeCallbacks (..)
5+ , AppCallbacks (..)
6+ , AppConfig (..)
7+ , pokeAppConfig
8+ , appConfigToAppCallbacks
59 ) where
610
11+import Prelude ()
12+import Prelude.Compat
13+
714import Control.Monad (void)
815import Control.Concurrent (forkIO)
916
10−import Data.Monoid ((<>))
17+import Data.Aeson (encode, decode)
1118import Data.ByteString (useAsCString, packCString)
12−import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
1319import Data.ByteString.Char8 (unpack)
14−import Data.Aeson (encode, decode)
20+import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
21+import Data.Default (def, Default)
22+import Data.Text (Text)
23+import qualified Data.Text.Encoding as T
1524
1625import Foreign.C.String (CString, newCString)
1726import Foreign.Ptr (FunPtr, Ptr)
27+import Foreign.Storable (poke)
1828import Foreign.Marshal.Utils (new)
1929
2030import Language.Javascript.JSaddle (JSM)
@@ -32,33 +42,105 @@ foreign import ccall safe "wrapper"
3242foreign import ccall safe "wrapper"
3343 wrapMessageCallback :: (CString -> IO ()) -> IO (FunPtr (CString -> IO ()))
3444
45+foreign import ccall safe "wrapper"
46+ wrapMessageCallback2 :: (CString -> CString -> IO ()) -> IO (FunPtr (CString -> CString -> IO ()))
47+
48+foreign import ccall safe "wrapper"
49+ wrapSyncCallback :: (CString -> IO CString) -> IO (FunPtr (CString -> IO CString))
50+
3551jsaddleInit :: JSM () -> FunPtr (CString -> IO ()) -> IO (Ptr NativeCallbacks)
3652jsaddleInit jsm evaluateJavascriptAsyncPtr = do
3753 let evaluateJavascriptAsync = mkCallback evaluateJavascriptAsyncPtr
38− (processResult, start) <- runJavaScript (\batch ->
39− useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $
54+ (processResult, processSyncResult, start) <- runJavaScript (\batch ->
55+ useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");")
4056 evaluateJavascriptAsync) jsm
41− jsaddleStartPtr <- wrapStartCallback $ void $ forkIO $ start
57+ jsaddleStartPtr <- wrapStartCallback $ void $ forkIO start
4258 jsaddleResultPtr <- wrapMessageCallback $ \s -> do
4359 result <- decode . fromStrict <$> packCString s
4460 case result of
4561 Nothing -> error $ "jsaddle message decode failed: " <> show result
4662 Just r -> processResult r
63+ jsaddleSyncResultPtr <- wrapSyncCallback $ \s -> do
64+ result <- decode . fromStrict <$> packCString s
65+ case result of
66+ Nothing -> error $ "jsaddle message decode failed: " <> show result
67+ Just r -> newCString =<< unpack . toStrict . encode <$> processSyncResult r
4768 jsaddleJsPtr <- newCString $ unpack $ toStrict jsaddleJs
4869 jsaddleHtmlPtr <- newCString $ unpack $ toStrict indexHtml
49− new $ NativeCallbacks
70+ new NativeCallbacks
5071 { _nativeCallbacks_jsaddleStart = jsaddleStartPtr
5172 , _nativeCallbacks_jsaddleResult = jsaddleResultPtr
73+ , _nativeCallbacks_jsaddleSyncResult = jsaddleSyncResultPtr
5274 , _nativeCallbacks_jsaddleJsData = jsaddleJsPtr
5375 , _nativeCallbacks_jsaddleHtmlData = jsaddleHtmlPtr
5476 }
5577
78+data AppConfig = AppConfig
79+ { _appConfig_mainActivityOnCreate :: IO ()
80+ , _appConfig_mainActivityOnStart :: IO ()
81+ , _appConfig_mainActivityOnResume :: IO ()
82+ , _appConfig_mainActivityOnPause :: IO ()
83+ , _appConfig_mainActivityOnStop :: IO ()
84+ , _appConfig_mainActivityOnDestroy :: IO ()
85+ , _appConfig_mainActivityOnRestart :: IO ()
86+ , _appConfig_mainActivityOnNewIntent :: (Text -> Text -> IO ())
87+ , _appConfig_firebaseInstanceIdServiceSendRegistrationToServer :: Text -> IO ()
88+ }
89+
90+instance Default AppConfig where
91+ def = AppConfig
92+ { _appConfig_mainActivityOnCreate = return ()
93+ , _appConfig_mainActivityOnStart = return ()
94+ , _appConfig_mainActivityOnResume = return ()
95+ , _appConfig_mainActivityOnPause = return ()
96+ , _appConfig_mainActivityOnStop = return ()
97+ , _appConfig_mainActivityOnDestroy = return ()
98+ , _appConfig_mainActivityOnRestart = return ()
99+ , _appConfig_mainActivityOnNewIntent = \_ _ -> return ()
100+ , _appConfig_firebaseInstanceIdServiceSendRegistrationToServer = \_ -> return ()
101+ }
102+
103+appConfigToAppCallbacks :: AppConfig -> IO AppCallbacks
104+appConfigToAppCallbacks c = do
105+ create <- wrapStartCallback $ _appConfig_mainActivityOnCreate c
106+ start <- wrapStartCallback $ _appConfig_mainActivityOnStart c
107+ resume <- wrapStartCallback $ _appConfig_mainActivityOnResume c
108+ pause <- wrapStartCallback $ _appConfig_mainActivityOnPause c
109+ stop <- wrapStartCallback $ _appConfig_mainActivityOnStop c
110+ destroy <- wrapStartCallback $ _appConfig_mainActivityOnDestroy c
111+ restart <- wrapStartCallback $ _appConfig_mainActivityOnRestart c
112+ newIntent <- wrapMessageCallback2 $ \intentAction intentData -> do
113+ intentAction' <- fromUtf8CString intentAction
114+ intentData' <- fromUtf8CString intentData
115+ _appConfig_mainActivityOnNewIntent c intentAction' intentData'
116+ firebaseRegPtr <- wrapMessageCallback $ \token -> do
117+ token' <- fromUtf8CString token
118+ _appConfig_firebaseInstanceIdServiceSendRegistrationToServer c token'
119+ return $ AppCallbacks
120+ { _appCallbacks_mainActivity_onCreate = create
121+ , _appCallbacks_mainActivity_onStart = start
122+ , _appCallbacks_mainActivity_onResume = resume
123+ , _appCallbacks_mainActivity_onPause = pause
124+ , _appCallbacks_mainActivity_onStop = stop
125+ , _appCallbacks_mainActivity_onDestroy = destroy
126+ , _appCallbacks_mainActivity_onRestart = restart
127+ , _appCallbacks_mainActivity_onNewIntent = newIntent
128+ , _appCallbacks_firebaseInstanceIdService_sendRegistrationToServer = firebaseRegPtr
129+ }
130+
131+fromUtf8CString :: CString -> IO Text
132+fromUtf8CString = fmap T.decodeUtf8 . packCString
133+
134+pokeAppConfig :: Ptr AppCallbacks -> AppConfig -> IO ()
135+pokeAppConfig ptr cfg = poke ptr =<< appConfigToAppCallbacks cfg
136+
56137jsaddleJs :: ByteString
57138jsaddleJs = ghcjsHelpers <> "\
58139 \runJSaddleBatch = (function() {\n\
59140 \ " <> initState <> "\n\
60141 \ return function(batch) {\n\
61− \ " <> runBatch (\a -> "jsaddle.postMessage(JSON.stringify(" <> a <> "));") <> "\
142+ \ " <> runBatch (\a -> "jsaddle.postMessage(JSON.stringify(" <> a <> "));")
143+ (Just (\a -> "JSON.parse(jsaddle.syncMessage(JSON.stringify(" <> a <> ")))")) <> "\
62144 \ };\n\
63145 \})();\n\
64146 \jsaddle.postReady();\n"
jsaddle-clib/src/Language/Javascript/JSaddle/CLib/Internal.hsc+39
@@ -9,6 +9,7 @@ import Foreign.C
99data NativeCallbacks = NativeCallbacks
1010 { _nativeCallbacks_jsaddleStart :: !(FunPtr (IO ())) -- void (jsaddleStart *)()
1111 , _nativeCallbacks_jsaddleResult :: !(FunPtr (CString -> IO ())) -- void (jsaddleResult *)(char *)
12+ , _nativeCallbacks_jsaddleSyncResult :: !(FunPtr (CString -> IO CString)) -- char * (jsaddleSyncResult *)(char *)
1213 , _nativeCallbacks_jsaddleJsData :: !CString -- char * jsaddleJsData
1314 , _nativeCallbacks_jsaddleHtmlData :: !CString -- char * jsaddleHtmlData
1415 }
@@ -19,10 +20,48 @@ instance Storable NativeCallbacks where
1920 poke p nc = do
2021 #{poke native_callbacks, jsaddleStart} p $ _nativeCallbacks_jsaddleStart nc
2122 #{poke native_callbacks, jsaddleResult} p $ _nativeCallbacks_jsaddleResult nc
23+ #{poke native_callbacks, jsaddleSyncResult} p $ _nativeCallbacks_jsaddleSyncResult nc
2224 #{poke native_callbacks, jsaddleJsData} p $ _nativeCallbacks_jsaddleJsData nc
2325 #{poke native_callbacks, jsaddleHtmlData} p $ _nativeCallbacks_jsaddleHtmlData nc
2426 peek p = NativeCallbacks
2527 <$> #{peek native_callbacks, jsaddleStart} p
2628 <*> #{peek native_callbacks, jsaddleResult} p
29+ <*> #{peek native_callbacks, jsaddleSyncResult} p
2730 <*> #{peek native_callbacks, jsaddleJsData} p
2831 <*> #{peek native_callbacks, jsaddleHtmlData} p
32+
33+data AppCallbacks = AppCallbacks
34+ { _appCallbacks_mainActivity_onCreate :: !(FunPtr (IO ()))
35+ , _appCallbacks_mainActivity_onStart :: !(FunPtr (IO ()))
36+ , _appCallbacks_mainActivity_onResume :: !(FunPtr (IO ()))
37+ , _appCallbacks_mainActivity_onPause :: !(FunPtr (IO ()))
38+ , _appCallbacks_mainActivity_onStop :: !(FunPtr (IO ()))
39+ , _appCallbacks_mainActivity_onDestroy :: !(FunPtr (IO ()))
40+ , _appCallbacks_mainActivity_onRestart :: !(FunPtr (IO ()))
41+ , _appCallbacks_mainActivity_onNewIntent :: !(FunPtr (CString -> CString -> IO ()))
42+ , _appCallbacks_firebaseInstanceIdService_sendRegistrationToServer :: !(FunPtr (CString -> IO ()))
43+ }
44+
45+instance Storable AppCallbacks where
46+ sizeOf _ = #{size app_callbacks}
47+ alignment _ = #{alignment app_callbacks}
48+ poke p nc = do
49+ #{poke app_callbacks, mainActivity_onCreate} p $ _appCallbacks_mainActivity_onCreate nc
50+ #{poke app_callbacks, mainActivity_onStart} p $ _appCallbacks_mainActivity_onStart nc
51+ #{poke app_callbacks, mainActivity_onResume} p $ _appCallbacks_mainActivity_onResume nc
52+ #{poke app_callbacks, mainActivity_onPause} p $ _appCallbacks_mainActivity_onPause nc
53+ #{poke app_callbacks, mainActivity_onStop} p $ _appCallbacks_mainActivity_onStop nc
54+ #{poke app_callbacks, mainActivity_onDestroy} p $ _appCallbacks_mainActivity_onDestroy nc
55+ #{poke app_callbacks, mainActivity_onRestart} p $ _appCallbacks_mainActivity_onRestart nc
56+ #{poke app_callbacks, mainActivity_onNewIntent} p $ _appCallbacks_mainActivity_onNewIntent nc
57+ #{poke app_callbacks, firebaseInstanceIdService_sendRegistrationToServer} p $ _appCallbacks_firebaseInstanceIdService_sendRegistrationToServer nc
58+ peek p = AppCallbacks
59+ <$> #{peek app_callbacks, mainActivity_onCreate} p
60+ <*> #{peek app_callbacks, mainActivity_onStart} p
61+ <*> #{peek app_callbacks, mainActivity_onResume} p
62+ <*> #{peek app_callbacks, mainActivity_onPause} p
63+ <*> #{peek app_callbacks, mainActivity_onStop} p
64+ <*> #{peek app_callbacks, mainActivity_onDestroy} p
65+ <*> #{peek app_callbacks, mainActivity_onRestart} p
66+ <*> #{peek app_callbacks, mainActivity_onNewIntent} p
67+ <*> #{peek app_callbacks, firebaseInstanceIdService_sendRegistrationToServer} p
jsaddle-warp/default.nixdeleted−26
@@ -1,26 +0,0 @@
1−{ 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, osx_sdk
5−, darwin, webdriver, phantomjs, ghc
6−}:
7−mkDerivation {
8− pname = "jsaddle-warp";
9− version = "0.8.2.0";
10− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
11− 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 if stdenv.isDarwin then [
16−# osx_sdk darwin.libobjc darwin.apple_sdk.libs.xpc
17− ] else [
18− ]);
19− testHaskellDepends = [
20− base bytestring deepseq doctest filepath ghc-prim jsaddle lens
21− primitive process QuickCheck ref-tf webdriver phantomjs
22− ];
23− testTarget = "--test-option=${jsaddle.src}";
24− description = "Interface for JavaScript that works with GHCJS and GHC";
25− license = stdenv.lib.licenses.mit;
26−}
jsaddle-warp/jsaddle-warp.cabal+21−13
@@ -1,5 +1,5 @@
11name: jsaddle-warp
2−version: 0.8.3.0
2+version: 0.9.8.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -10,7 +10,7 @@ description:
1010 This package provides an EDSL for calling JavaScript that
1111 can be used both from GHCJS and GHC. When using GHC
1212 the application is run using Warp and WebSockets to
13− drive a small JavaScipt helper.
13+ drive a small JavaScript helper.
1414category: Web, Javascript
1515author: Hamish Mackenzie
1616
@@ -24,19 +24,23 @@ library
2424 if !impl(ghcjs -any)
2525 exposed-modules:
2626 Language.Javascript.JSaddle.WebSockets
27+ other-modules:
28+ Language.Javascript.JSaddle.WebSockets.Compat
2729 build-depends:
28− aeson >=0.8.0.2 && <1.2,
29− containers >=0.5.6.2 && <0.6,
30− http-types >=0.8.6 && <0.10,
31− jsaddle >=0.8.0.0 && <0.9,
32− stm >=2.4.4 && <2.5,
30+ aeson >=0.8.0.2 && <1.6,
31+ bytestring >=0.10.6.0 && <0.11,
32+ containers >=0.5.6.2 && <0.7,
33+ foreign-store >=0.2 && <0.3,
34+ http-types >=0.8.6 && <0.13,
35+ jsaddle >=0.9.4.0 && <0.10,
36+ stm >=2.4.4 && <2.6,
3337 text >=1.2.1.3 && <1.3,
34− time >=1.5.0.1 && <1.8,
38+ time >=1.5.0.1 && <1.11,
3539 transformers >=0.4.2.0 && <0.6,
3640 wai >=3.0.3.0 && <3.3,
3741 wai-websockets >=3.0.0.6 && <3.1,
38− warp >=3.1.2 && <3.3,
39− websockets >=0.9.5.0 && <0.11
42+ warp >=3.1.2 && <3.4,
43+ websockets >=0.9.5.0 && <0.13
4044 exposed-modules:
4145 Language.Javascript.JSaddle.Warp
4246 build-depends:
@@ -55,8 +59,10 @@ test-suite test-tool
5559 bytestring -any,
5660 containers -any,
5761 deepseq -any,
58− doctest >=0.10.1 && <0.12,
62+ doctest >=0.10.1 && <0.19,
63+ exceptions -any,
5964 filepath >=1.4.0.0 && <1.5,
65+ foreign-store -any,
6066 ghc-prim -any,
6167 http-types -any,
6268 jsaddle -any,
@@ -65,15 +71,17 @@ test-suite test-tool
6571 network -any,
6672 ref-tf -any,
6773 primitive -any,
68− process >=1.2.3.0 && <1.5,
74+ process >=1.2.3.0 && <1.7,
75+ random -any,
6976 stm -any,
7077 text -any,
7178 time -any,
7279 transformers -any,
80+ unliftio-core -any,
7381 wai -any,
7482 wai-websockets -any,
7583 warp -any,
76− webdriver >=0.8.4 && <0.9,
84+ webdriver >=0.8.4 && <0.10,
7785 websockets -any
7886 type: exitcode-stdio-1.0
7987 main-is: DocTest.hs
jsaddle-warp/src/Language/Javascript/JSaddle/Test.hs+13−12
@@ -17,21 +17,22 @@ module Language.Javascript.JSaddle.Test (
1717 , listWindowProperties
1818) where
1919
20−import Control.Applicative
2120import Prelude hiding((!!), catch)
22−import Control.Monad.Trans.Reader (runReaderT)
23−import Language.Javascript.JSaddle
24−import qualified Data.Text as T
25−import Control.Monad.IO.Class (MonadIO(..))
26−import Control.Monad (forever, forM)
27−import Control.Lens.Getter ((^.))
28−import Data.Monoid ((<>))
21+import Control.Applicative
22+import Control.Concurrent (threadDelay, forkIO, ThreadId)
2923import Control.Concurrent.MVar
3024 (tryTakeMVar, putMVar, takeMVar, newEmptyMVar, MVar)
31−import System.IO.Unsafe (unsafePerformIO)
32−import Control.Concurrent (threadDelay, forkIO, ThreadId)
25+import Control.Lens.Getter ((^.))
26+import Control.Monad (forever, forM)
27+import Control.Monad.IO.Class (MonadIO(..))
28+import Control.Monad.Trans.Reader (runReaderT)
29+import Data.Monoid ((<>))
3330import Data.Text (Text)
31+import qualified Data.Text as T
32+import Language.Javascript.JSaddle
3433import Language.Javascript.JSaddle.Warp (run)
34+import System.IO.Unsafe (unsafePerformIO)
35+import System.Timeout (timeout)
3536
3637context :: MVar JSContextRef
3738context = unsafePerformIO newEmptyMVar
@@ -52,12 +53,12 @@ startServer =
5253
5354-- >>> testJSaddle $ ((global ^. js "console" . js "log") # ["Hello"])
5455testJSaddle :: ToJSVal val => JSM val -> IO ()
55−testJSaddle f = do
56+testJSaddle f = timeout 10000000 (do
5657 startServer
5758 c <- takeMVar context
5859 runReaderT (unJSM $ (f >>= valToText >>= liftIO . putStrLn . T.unpack)
5960 `catch` \ (JSException e) -> valToText e >>= liftIO . putStrLn . T.unpack) c
60− putMVar context c
61+ putMVar context c) >>= maybe (putStrLn "testJSaddle timed out") return
6162
6263debugLog :: String -> IO ()
6364debugLog _ = return ()
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+286−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,302 @@ module Language.Javascript.JSaddle.WebSockets (
1618 -- * Running JSM over WebSockets
1719 jsaddleOr
1820 , jsaddleApp
21+ , jsaddleWithAppOr
22+ , jsaddleAppWithJs
23+ , jsaddleAppWithJsOr
24+ , jsaddleAppPartial
25+ , jsaddleJs
26+ , jsaddleJs'
27+ , debug
28+ , debugOr
29+ , debugWrapper
1930) where
2031
21−import Control.Monad (forever)
22−import Control.Concurrent (forkIO, threadDelay)
23−import Control.Exception (handle, AsyncException, throwIO, fromException)
32+import Control.Monad (when, void, forever)
33+import Control.Concurrent (killThread, forkIO, threadDelay)
34+import Control.Exception (handle, AsyncException, throwIO, fromException, finally)
2435
2536import Data.Monoid ((<>))
2637import Data.Aeson (encode, decode)
2738
28−import Network.Wai (Application)
39+import Network.Wai
40+ (Middleware, lazyRequestBody, Application, Request, Response,
41+ ResponseReceived)
2942import Network.WebSockets
30− (ConnectionOptions(..), sendTextData, receiveDataMessage,
31− acceptRequest, ServerApp, sendPing)
32−import qualified Network.WebSockets as WS (DataMessage(..))
43+ (defaultConnectionOptions, ConnectionOptions(..), sendTextData,
44+ receiveDataMessage, acceptRequest, ServerApp, sendPing)
3345import Network.Wai.Handler.WebSockets (websocketsOr)
46+import Network.HTTP.Types (Status(..))
3447
35−import Language.Javascript.JSaddle.Types (JSM(..))
48+import Language.Javascript.JSaddle.Types (JSM(..), JSContextRef(..))
3649import qualified Network.Wai as W
37− (responseLBS, requestMethod, Application, pathInfo)
38−import Data.Text (Text)
50+ (responseLBS, requestMethod, pathInfo, modifyResponse, responseStatus)
3951import qualified Data.Text as T (pack)
4052import 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 WebSocket decode failed : " <> show t
57− Just r -> processResult r
53+ (status403, status200)
54+import Language.Javascript.JSaddle.Run (syncPoint, runJavaScript)
55+import Language.Javascript.JSaddle.Run.Files (indexHtml, runBatch, ghcjsHelpers, initState)
56+import Language.Javascript.JSaddle.Debug
57+ (removeContext, addContext)
58+import Data.Maybe (fromMaybe)
59+import qualified Data.Map as M (empty, insert, lookup)
60+import Data.IORef
61+ (readIORef, newIORef, atomicModifyIORef')
62+import Data.ByteString.Lazy (ByteString)
63+import qualified Data.ByteString.Lazy as LBS
64+ (toStrict, stripPrefix)
65+import Control.Concurrent.MVar
66+ (tryTakeMVar, MVar, tryPutMVar, modifyMVar_, putMVar, takeMVar,
67+ readMVar, newMVar, newEmptyMVar, modifyMVar)
68+import Network.Wai.Handler.Warp
69+ (defaultSettings, setTimeout, setPort, runSettings)
70+import Foreign.Store (readStore, lookupStore, writeStore, Store(..))
71+import Language.Javascript.JSaddle (askJSM)
72+import Control.Monad.IO.Class (MonadIO(..))
73+
74+import Language.Javascript.JSaddle.WebSockets.Compat (getTextMessageByteString)
75+import qualified Data.Text.Encoding as T (decodeUtf8)
76+
77+jsaddleOr :: ConnectionOptions -> JSM () -> Application -> IO Application
78+jsaddleOr opts entryPoint otherApp = do
79+ syncHandlers <- newIORef M.empty
80+ asyncHandlers <- newIORef M.empty
81+ let wsApp :: ServerApp
82+ wsApp pending_conn = do
83+ conn <- acceptRequest pending_conn
84+ initMsg <- receiveDataMessage conn
85+ case getTextMessageByteString initMsg of
86+ Just "" -> do
87+ rec (processResult, processSyncResult, start) <- runJavaScript (sendTextData conn . encode) $ do
88+ syncKey <- T.pack . show . contextId <$> askJSM
89+ liftIO $ atomicModifyIORef' syncHandlers (\m -> (M.insert syncKey processSyncResult m, ()))
90+ liftIO $ atomicModifyIORef' asyncHandlers (\m -> (M.insert syncKey processResult m, ()))
91+ liftIO $ sendTextData conn (encode syncKey)
92+ entryPoint
93+ start
94+ waitTillClosed conn
95+ Just syncKey ->
96+ M.lookup (T.decodeUtf8 $ LBS.toStrict syncKey) <$> readIORef syncHandlers >>= \case
97+ Nothing -> error "jsaddle missing sync message handler"
98+ Just processResult ->
99+ forever $
100+ receiveDataMessage conn >>= \msg -> case getTextMessageByteString msg of
101+ Just t ->
102+ case decode t of
103+ Nothing -> error $ "jsaddle Results decode failed : " <> show t
104+ Just r -> processResult r
105+ _ -> error "jsaddle WebSocket unexpected binary data"
58106 _ -> 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 ()
107+
108+ -- Based on Network.WebSocket.forkPingThread
109+ waitTillClosed conn = ignore `handle` go 1
110+ where
111+ go :: Int -> IO ()
112+ go i = do
113+ threadDelay (10 * 1000 * 1000)
114+ sendPing conn (T.pack $ show i)
115+ go (i + 1)
116+
117+ ignore e = case fromException e of
118+ Just async -> throwIO (async :: AsyncException)
119+ Nothing -> return ()
120+
121+ syncHandler :: Application
122+ syncHandler req sendResponse = case (W.requestMethod req, W.pathInfo req) of
123+ ("POST", ["sync", syncKey]) -> do
124+ body <- lazyRequestBody req
125+ case decode body of
126+ Nothing -> error $ "jsaddle sync message decode failed : " <> show body
127+ Just result ->
128+ M.lookup syncKey <$> readIORef syncHandlers >>= \case
129+ Nothing -> error "jsaddle missing sync message handler"
130+ Just handler -> do
131+ next <- encode <$> handler result
132+ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/json")] next
133+ (method, _) -> (catch404 otherApp) req sendResponse
134+ where catch404 = W.modifyResponse $ \resp ->
135+ case (method, W.responseStatus resp) of
136+ ("GET", Status 404 _) -> indexResponse
137+ _ -> resp
138+ return $ websocketsOr opts wsApp syncHandler
139+
74140
75141jsaddleApp :: 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"
142+jsaddleApp = jsaddleAppWithJs $ jsaddleJs False
143+
144+jsaddleAppWithJs :: ByteString -> Application
145+jsaddleAppWithJs js req sendResponse =
146+ jsaddleAppWithJsOr js
147+ (\_ _ -> sendResponse $ W.responseLBS H.status403 [("Content-Type", "text/plain")] "Forbidden")
148+ req sendResponse
149+
150+jsaddleAppWithJsOr :: ByteString -> Application -> Application
151+jsaddleAppWithJsOr js otherApp req sendResponse =
152+ fromMaybe (otherApp req sendResponse)
153+ (jsaddleAppPartialWithJs js req sendResponse)
154+
155+jsaddleWithAppOr :: ConnectionOptions -> JSM () -> Application -> IO Application
156+jsaddleWithAppOr opts entryPoint otherApp = jsaddleOr opts entryPoint $ \req sendResponse ->
157+ (fromMaybe (otherApp req sendResponse)
158+ (jsaddleAppPartial req sendResponse))
159+
160+jsaddleAppPartial :: Request -> (Response -> IO ResponseReceived) -> Maybe (IO ResponseReceived)
161+jsaddleAppPartial = jsaddleAppPartialWithJs $ jsaddleJs False
162+
163+indexResponse :: Response
164+indexResponse = W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
165+
166+jsaddleAppPartialWithJs :: ByteString -> Request -> (Response -> IO ResponseReceived) -> Maybe (IO ResponseReceived)
167+jsaddleAppPartialWithJs js req sendResponse = case (W.requestMethod req, W.pathInfo req) of
168+ ("GET", []) -> Just $ sendResponse indexResponse
169+ ("GET", ["jsaddle.js"]) -> Just $ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] js
170+ _ -> Nothing
171+
172+jsaddleJs :: Bool -> ByteString
173+jsaddleJs = jsaddleJs' Nothing
174+
175+-- Use this to generate this string for embedding
176+-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
177+jsaddleJs' :: Maybe ByteString -> Bool -> ByteString
178+jsaddleJs' jsaddleUri refreshOnLoad = "\
179+ \if(typeof global !== \"undefined\" && typeof require === \"function\") {\n\
180+ \ global.window = global;\n\
181+ \ global.WebSocket = require('ws');\n\
182+ \}\n\
183+ \\n\
184+ \var connect = function() {\n\
185+ \ var wsaddress = "
186+ <> maybe "window.location.protocol.replace('http', 'ws')+\"//\"+window.location.hostname+(window.location.port?(\":\"+window.location.port):\"\")"
187+ (\ s -> "\"ws" <> s <> "\"")
188+ (jsaddleUri >>= LBS.stripPrefix "http")
189+ <> ";\n\
190+ \\n\
191+ \ var ws0 = new WebSocket(wsaddress);\n\
192+ \ var syncKey = \"\";\n\
193+ \\n\
194+ \ ws0.onopen = function(e) {\n\
195+ \ ws0.send(\"\");\n\
196+ \ var initialResults = [];\n\
197+ \ var ws = {send: function(m) {initialResults.push(m);}};\n\
198+ \ " <> initState <> "\n\
199+ \\n\
200+ \ ws0.onmessage = function(e) {\n\
201+ \ var batch = JSON.parse(e.data);\n\
202+ \ if(inCallback > 0) {\n\
203+ \ asyncBatch = batch;\n\
204+ \ return;\n\
205+ \ }\n\
206+ \ if(typeof batch === \"string\") {\n\
207+ \ syncKey = batch;\n\
208+ \ var ws1 = new WebSocket(wsaddress);\n\
209+ \ ws1.onopen = function(e) {\n\
210+ \ ws1.send(syncKey);\n\
211+ \ initialResults.forEach(function(m){ ws1.send(m); });\n\
212+ \ initialResults = null;\n\
213+ \ ws = ws1;\n\
214+ \ }\n" <>
215+ (if refreshOnLoad
216+ then " var xhr = new XMLHttpRequest();\n\
217+ \ xhr.open('POST', '/reload/'+syncKey, true);\n\
218+ \ xhr.onreadystatechange = function() {\n\
219+ \ if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200)\n\
220+ \ setTimeout(function(){window.location.reload();}, 100);\n\
221+ \ };\n\
222+ \ xhr.send();\n"
223+ else "") <>
224+ " return;\n\
225+ \ }\n\
226+ \\n\
227+ \ " <> runBatch (\a -> "ws.send(JSON.stringify(" <> a <> "));")
228+ (Just (\a -> "(function(){\n\
229+ \ var xhr = new XMLHttpRequest();\n\
230+ \ xhr.open('POST', '" <> fromMaybe "" jsaddleUri <> "/sync/'+syncKey, false);\n\
231+ \ xhr.setRequestHeader(\"Content-type\", \"application/json\");\n\
232+ \ xhr.send(JSON.stringify(" <> a <> "));\n\
233+ \ return JSON.parse(xhr.response);})()")) <> "\
234+ \ };\n\
235+ \ };\n\
236+ \ ws0.onerror = function() {\n\
237+ \ setTimeout(connect, 1000);\n\
238+ \ };\n\
239+ \}\n\
240+ \\n\
241+ \ " <> ghcjsHelpers <> "\
242+ \connect();\n\
243+ \"
244+
245+-- | Start or restart the server.
246+-- To run this as part of every :reload use
247+-- > :def! reload (const $ return "::reload\nLanguage.Javascript.JSaddle.Warp.debug 3708 SomeMainModule.someMainFunction")
248+debug :: Int -> JSM () -> IO ()
249+debug port f = do
250+ debugWrapper $ \withRefresh registerContext ->
251+ runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
252+ jsaddleOr defaultConnectionOptions (registerContext >> f >> syncPoint) (withRefresh $ jsaddleAppWithJs $ jsaddleJs True)
253+ putStrLn $ "<a href=\"http://localhost:" <> show port <> "\">run</a>"
254+
255+debugOr :: Int -> JSM () -> Application -> IO ()
256+debugOr port f b = do
257+ debugWrapper $ \withRefresh registerContext ->
258+ runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
259+ jsaddleOr defaultConnectionOptions (registerContext >> f >> syncPoint) (withRefresh $ jsaddleAppWithJsOr (jsaddleJs True) b)
260+ putStrLn $ "<a href=\"http://localhost:" <> show port <> "\">run</a>"
261+
262+refreshMiddleware :: ((Response -> IO ResponseReceived) -> IO ResponseReceived) -> Middleware
263+refreshMiddleware refresh otherApp req sendResponse = case (W.requestMethod req, W.pathInfo req) of
264+ ("POST", ["reload", _syncKey]) -> refresh sendResponse
265+ _ -> otherApp req sendResponse
266+
267+debugWrapper :: (Middleware -> JSM () -> IO ()) -> IO ()
268+debugWrapper run = do
269+ reloadMVar <- newEmptyMVar
270+ reloadDoneMVars <- newMVar []
271+ contexts <- newMVar []
272+ let refresh sendResponse = do
273+ reloadDone <- newEmptyMVar
274+ modifyMVar_ reloadDoneMVars (return . (reloadDone:))
275+ readMVar reloadMVar
276+ r <- sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/json")] ("reload" :: ByteString)
277+ putMVar reloadDone ()
278+ return r
279+ start :: Int -> IO (IO Int)
280+ start expectedConnections = do
281+ serverDone <- newEmptyMVar
282+ ready <- newEmptyMVar
283+ let registerContext :: JSM ()
284+ registerContext = do
285+ uuid <- contextId <$> askJSM
286+ browsersConnected <- liftIO $ modifyMVar contexts (\ctxs -> return (uuid:ctxs, length ctxs + 1))
287+ addContext
288+ when (browsersConnected == expectedConnections) . void . liftIO $ tryPutMVar ready ()
289+ thread <- forkIO $
290+ finally (run (refreshMiddleware refresh) registerContext)
291+ (putMVar serverDone ())
292+ _ <- forkIO $ threadDelay 10000000 >> void (tryPutMVar ready ())
293+ when (expectedConnections /= 0) $ takeMVar ready
294+ return $ do
295+ putMVar reloadMVar ()
296+ ctxs <- takeMVar contexts
297+ mapM_ removeContext ctxs
298+ takeMVar reloadDoneMVars >>= mapM_ takeMVar
299+ tryTakeMVar serverDone >>= \case
300+ Nothing -> do
301+ killThread thread
302+ takeMVar serverDone
303+ Just _ -> return ()
304+ return $ length ctxs
305+ restarter :: MVar (Int -> IO (IO Int)) -> IO Int -> IO ()
306+ restarter mvar stop = do
307+ start' <- takeMVar mvar
308+ n <- stop
309+ start' n >>= restarter mvar
310+ lookupStore storeId >>= \case
311+ Nothing -> do
312+ restartMVar <- newMVar start
313+ void . forkIO $ restarter restartMVar (return 0)
314+ void $ writeStore (Store storeId) restartMVar
315+ Just shutdownStore -> do
316+ restartMVar :: MVar (Int -> IO (IO Int)) <- readStore shutdownStore
317+ void $ tryTakeMVar restartMVar
318+ putMVar restartMVar start
319+ where storeId = 354
jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets/Compat.hsnew file+29
@@ -0,0 +1,29 @@
1+{-# LANGUAGE CPP #-}
2+-----------------------------------------------------------------------------
3+--
4+-- Module : Language.Javascript.JSaddle.WebSockets.Compat
5+-- Copyright : (c) Hamish Mackenzie
6+-- License : MIT
7+--
8+-- Maintainer : Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
9+--
10+-- | This module is necessary to harmonize various versions of the websockets
11+-- package; we can't do this CPP inline in
12+-- Language.Javascript.JSaddle.WebSockets because that module uses multi-line
13+-- strings, which are incompatible with CPP
14+--
15+-----------------------------------------------------------------------------
16+module Language.Javascript.JSaddle.WebSockets.Compat where
17+
18+import Data.ByteString.Lazy (ByteString)
19+import qualified Network.WebSockets as WS (DataMessage(..))
20+
21+getTextMessageByteString :: WS.DataMessage -> Maybe ByteString
22+getTextMessageByteString msg = case msg of
23+#if MIN_VERSION_websockets(0,11,0)
24+ (WS.Text t _) ->
25+#else
26+ (WS.Text t) ->
27+#endif
28+ Just t
29+ _ -> Nothing
jsaddle-warp/tests/DocTest.hs+20−7
@@ -11,18 +11,17 @@ import System.IO (stderr, BufferMode(..), stdout, hSetBuffering)
1111import System.FilePath ((</>))
1212import System.Exit (exitFailure, exitWith, ExitCode(..))
1313import System.Process (readProcess, system)
14−import Control.Concurrent (forkIO)
14+import Control.Concurrent
15+ (takeMVar, newEmptyMVar, forkIO, threadDelay, putMVar)
1516import Control.Monad (void)
1617import Control.Monad.IO.Class (MonadIO(..))
1718import Data.Monoid ((<>))
1819import System.Environment (getArgs)
19−import Language.Javascript.JSaddle.Run.Files (jsaddleJs)
20−import qualified Data.ByteString.Lazy.Char8 as BS (unpack)
2120import Test.WebDriver (runSession, defaultConfig, openPage, closeSession)
2221
2322main :: IO ()
2423main = do
25− putStrLn "Hello"
24+ putStrLn "Testing JSaddle"
2625 jsaddlePath <- getArgs >>= \case
2726 [arg] -> return arg
2827 _ -> do
@@ -30,14 +29,18 @@ main = do
3029 exitFailure
3130 hSetBuffering stdout LineBuffering
3231 hSetBuffering stderr LineBuffering
32+ putStrLn "Checking for phantomjs"
3333 node <- system "phantomjs --version" >>= \case
3434 ExitSuccess -> return ()
3535 e -> do
3636 putStrLn "phantomjs not found"
3737 exitWith e
38− forkIO . void $ readProcess "phantomjs" ["--webdriver=4444"] (BS.unpack jsaddleJs) >>= putStr
39− runSession defaultConfig $ do
40− openPage "http://localhost:3709"
38+ putStrLn "Starting phantomjs"
39+ forkIO . void $ readProcess "phantomjs" ["--webdriver=4444"] "" >>= putStr
40+ threadDelay 5000000
41+ putStrLn "Running Tests"
42+ done <- newEmptyMVar
43+ forkIO $ do
4144 liftIO $ doctest [
4245 "-hide-all-packages",
4346 "-package=base-" ++ VERSION_base,
@@ -61,6 +64,10 @@ main = do
6164 "-package=ref-tf-" ++ VERSION_ref_tf,
6265 "-package=deepseq-" ++ VERSION_deepseq,
6366 "-package=ghc-prim-" ++ VERSION_ghc_prim,
67+ "-package=exceptions-" ++ VERSION_exceptions,
68+ "-package=unliftio-core-" ++ VERSION_unliftio_core,
69+ "-package=random-" ++ VERSION_random,
70+ "-package=foreign-store-" ++ VERSION_foreign_store,
6471 "-i" <> "src",
6572 "-i" <> "src-ghc",
6673 "src/Language/Javascript/JSaddle/Test.hs",
@@ -83,6 +90,7 @@ main = do
8390 jsaddlePath </> "src/Language/Javascript/JSaddle/Arguments.hs",
8491 jsaddlePath </> "src/Language/Javascript/JSaddle/Classes.hs",
8592 jsaddlePath </> "src/Language/Javascript/JSaddle/Classes/Internal.hs",
93+ jsaddlePath </> "src/Language/Javascript/JSaddle/Debug.hs",
8694 jsaddlePath </> "src/Language/Javascript/JSaddle/Evaluate.hs",
8795 jsaddlePath </> "src/Language/Javascript/JSaddle/Exception.hs",
8896 jsaddlePath </> "src/Language/Javascript/JSaddle/Monad.hs",
@@ -96,4 +104,9 @@ main = do
96104 jsaddlePath </> "src/Language/Javascript/JSaddle/String.hs",
97105 jsaddlePath </> "src/Language/Javascript/JSaddle/Types.hs",
98106 jsaddlePath </> "src/Language/Javascript/JSaddle/Value.hs" ]
107+ putMVar done ()
108+ threadDelay 5000000
109+ runSession defaultConfig $ do
110+ openPage "http://127.0.0.1:3709"
111+ liftIO $ takeMVar done
99112 closeSession
jsaddle-webkit2gtk/default.nixdeleted−16
@@ -1,16 +0,0 @@
1−{ mkDerivation, aeson, base, bytestring, directory, gi-gio, gi-glib
2−, gi-gtk, gi-javascriptcore, gi-webkit2, haskell-gi-base, jsaddle
3−, stdenv, text, unix, webkit2gtk3-javascriptcore
4−}:
5−mkDerivation {
6− pname = "jsaddle-webkit2gtk";
7− version = "0.8.2.2";
8− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
9− libraryHaskellDepends = [
10− aeson base bytestring directory gi-gio gi-glib gi-gtk
11− gi-javascriptcore gi-webkit2 haskell-gi-base jsaddle text unix
12− webkit2gtk3-javascriptcore
13− ];
14− description = "Interface for JavaScript that works with GHCJS and GHC";
15− license = stdenv.lib.licenses.mit;
16−}
jsaddle-webkit2gtk/jsaddle-webkit2gtk.cabal+10−9
@@ -1,5 +1,5 @@
11name: jsaddle-webkit2gtk
2−version: 0.8.3.0
2+version: 0.9.8.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -26,17 +26,18 @@ library
2626 base <5
2727 if !impl(ghcjs -any)
2828 build-depends:
29− aeson >=0.8.0.2 && <1.2,
29+ aeson >=0.8.0.2 && <1.6,
3030 base <5,
3131 bytestring >=0.10.6.0 && <0.11,
3232 directory >=1.0.0.2 && <1.4,
33− gi-glib >=2.0.6 && <2.1,
34− gi-gio >=2.0.6 && <2.1,
35− gi-gtk >=3.0.6 && <3.1,
36− gi-webkit2 >=4.0.6 && <4.1,
37− gi-javascriptcore >=4.0.6 && <4.1,
38− haskell-gi-base >=0.20 && <0.21,
39− jsaddle >=0.8.0.0 && <0.9,
33+ gi-glib >=2.0.14 && <2.1,
34+ gi-gio >=2.0.14 && <2.1,
35+ gi-gtk >=3.0.17 && <3.1,
36+ gi-webkit2 >=4.0.14 && <4.1,
37+ gi-javascriptcore >=4.0.14 && <4.1,
38+ haskell-gi-base >=0.20 && <0.26,
39+ haskell-gi-overloading >=0.0 && < 2.0,
40+ jsaddle >=0.9.4.0 && <0.10,
4041 text >=1.2.1.3 && <1.3,
4142 webkit2gtk3-javascriptcore >=0.14.0.0 && <0.15
4243 if !os(windows)
jsaddle-webkit2gtk/src/Language/Javascript/JSaddle/WebKitGTK.hs+44−46
@@ -43,54 +43,51 @@ import Data.Monoid ((<>))
4343import Data.ByteString.Lazy (toStrict, fromStrict)
4444import qualified Data.ByteString.Lazy as LB (ByteString)
4545import Data.Aeson (encode, decode)
46−import Data.Text (Text)
4746import qualified Data.Text as T (pack)
48−import Data.Text.Foreign (fromPtr)
4947import Data.Text.Encoding (decodeUtf8, encodeUtf8)
5048
51−import Foreign.Ptr (castPtr, nullPtr)
52−
5349import Data.GI.Base.BasicTypes (GObject)
5450import Data.GI.Base.Signals
5551 (connectSignalFunPtr, SignalConnectMode(..), SignalConnectMode,
5652 SignalHandlerId)
57−import Data.GI.Base.ManagedPtr (withManagedPtr)
5853
5954import GI.GLib (timeoutAdd, idleAdd, pattern PRIORITY_HIGH, pattern PRIORITY_DEFAULT)
6055import qualified GI.Gtk as Gtk (main, init)
6156import GI.Gtk
6257 (windowSetPosition, windowSetDefaultSize, windowNew,
63− scrolledWindowNew, noAdjustment, containerAdd,
58+ scrolledWindowNew, Adjustment, containerAdd,
6459 WindowType(..), WindowPosition(..), widgetDestroy,
6560 widgetGetToplevel, widgetShowAll, onWidgetDestroy,
6661 mainQuit)
67−import GI.Gio (noCancellable)
68−import GI.JavaScriptCore (Value(..), GlobalContext(..))
62+import GI.Gio (Cancellable)
63+import GI.JavaScriptCore (valueToString)
6964import GI.WebKit2
70− (WebView, setSettingsEnableWriteConsoleMessagesToStdout,
71− setSettingsEnableJavascript,
72− webViewNewWithUserContentManager, userContentManagerNew,
65+ (scriptDialogPromptSetText, scriptDialogPromptGetDefaultText,
66+ scriptDialogGetMessage, scriptDialogGetDialogType,
67+ onWebViewScriptDialog, WebView,
68+ setSettingsEnableWriteConsoleMessagesToStdout,
69+ setSettingsEnableJavascript, webViewNewWithUserContentManager,
70+ userContentManagerNew,
7371 userContentManagerRegisterScriptMessageHandler,
74− javascriptResultGetValue, javascriptResultGetGlobalContext,
72+ javascriptResultGetJsValue,
7573 webViewGetUserContentManager,
7674 mk_UserContentManagerScriptMessageReceivedCallback,
7775 wrap_UserContentManagerScriptMessageReceivedCallback,
7876 webViewRunJavascript, LoadEvent(..),
7977 UserContentManagerScriptMessageReceivedCallback, webViewLoadHtml,
8078 onWebViewLoadChanged, setSettingsEnableDeveloperExtras,
81− webViewSetSettings, webViewGetSettings)
82−
83−import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
84− (JSValueRef, JSContextRef)
85−import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
86− (jsstringgetcharactersptr, jsstringgetlength, jsstringrelease)
87−import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
88− (jsvaluetostringcopy)
79+ webViewSetSettings, webViewGetSettings, ScriptDialogType(..))
8980
90−import Language.Javascript.JSaddle (JSM, Results)
81+import Language.Javascript.JSaddle (JSM, Results, Batch)
9182import Language.Javascript.JSaddle.Run (runJavaScript)
9283import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
9384
85+noAdjustment :: Maybe Adjustment
86+noAdjustment = Nothing
87+
88+noCancellable :: Maybe Cancellable
89+noCancellable = Nothing
90+
9491quitWebView :: WebView -> IO ()
9592quitWebView wv = postGUIAsync $ do w <- widgetGetToplevel wv --TODO: Shouldn't this be postGUISync?
9693 widgetDestroy w
@@ -106,21 +103,6 @@ postGUIAsync :: IO () -> IO ()
106103postGUIAsync action =
107104 void . idleAdd PRIORITY_DEFAULT $ action >> return False
108105
109−withJSContextRef :: GlobalContext -> (JSContextRef -> IO a) -> IO a
110−withJSContextRef (GlobalContext ctx) f = withManagedPtr ctx (f . castPtr)
111−
112−withJSValueRef :: Value -> (JSValueRef -> IO a) -> IO a
113−withJSValueRef (Value ptr) f = withManagedPtr ptr (f . castPtr)
114−
115−valueToText :: JSContextRef -> JSValueRef -> IO Text
116−valueToText ctxRef s = do
117− jsstring <- jsvaluetostringcopy ctxRef s nullPtr
118− l <- jsstringgetlength jsstring
119− p <- jsstringgetcharactersptr jsstring
120− result <- fromPtr (castPtr p) (fromIntegral l)
121− jsstringrelease jsstring
122− return result
123−
124106run :: JSM () -> IO ()
125107run main = do
126108 _ <- Gtk.init Nothing
@@ -145,17 +127,17 @@ run main = do
145127 void . onWebViewLoadChanged webView $ \case
146128 LoadEventFinished -> runInWebView main webView
147129 _ -> return ()
148− webViewLoadHtml webView "" . Just $ "file://" <> T.pack pwd <> "/"
130+ webViewLoadHtml webView "" . Just $ "file://" <> T.pack pwd <> "/index.html"
149131 installQuitHandler webView
150132 Gtk.main
151133
152134runInWebView :: JSM () -> WebView -> IO ()
153135runInWebView f webView = do
154− (processResults, start) <- runJavaScript (\batch -> postGUIAsync $
136+ (processResults, syncResults, start) <- runJavaScript (\batch -> postGUIAsync $
155137 webViewRunJavascript webView (decodeUtf8 . toStrict $ "runJSaddleBatch(" <> encode batch <> ");") noCancellable Nothing)
156138 f
157139
158− addJSaddleHandler webView processResults
140+ addJSaddleHandler webView processResults syncResults
159141 webViewRunJavascript webView (decodeUtf8 $ toStrict jsaddleJs) noCancellable . Just $
160142 \_obj _asyncResult ->
161143 void $ forkIO start
@@ -169,17 +151,32 @@ connectUserContentManagerScriptMessageReceived obj cb after = liftIO $ do
169151 let cb' = wrap_UserContentManagerScriptMessageReceivedCallback cb
170152 cb'' <- mk_UserContentManagerScriptMessageReceivedCallback cb'
171153 connectSignalFunPtr obj "script-message-received::jsaddle" cb'' after
154+#if MIN_VERSION_haskell_gi_base(0,23,0)
155+ Nothing
156+#endif
172157
173−addJSaddleHandler :: WebView -> (Results -> IO ()) -> IO ()
174−addJSaddleHandler webView processResult = do
158+addJSaddleHandler :: WebView -> (Results -> IO ()) -> (Results -> IO Batch) -> IO ()
159+addJSaddleHandler webView processResult syncResults = do
175160 manager <- webViewGetUserContentManager webView
176161 _ <- onUserContentManagerScriptMessageReceived manager $ \result -> do
177− ctx <- javascriptResultGetGlobalContext result
178− arg <- javascriptResultGetValue result
179− bs <- withJSContextRef ctx $ \ctxRef ->
180− withJSValueRef arg $ \argRef ->
181− encodeUtf8 <$> valueToText ctxRef argRef
162+ arg <- javascriptResultGetJsValue result
163+ bs <- encodeUtf8 <$> valueToString arg
182164 mapM_ processResult (decode (fromStrict bs))
165+ _ <- onWebViewScriptDialog webView $ \dialog ->
166+ scriptDialogGetDialogType dialog >>= \case
167+ ScriptDialogTypePrompt ->
168+ scriptDialogGetMessage dialog >>= \case
169+ "JSaddleSync" -> do
170+ resultsText <- scriptDialogPromptGetDefaultText dialog
171+ case decode (fromStrict $ encodeUtf8 resultsText) of
172+ Just results -> do
173+ batch <- syncResults results
174+ scriptDialogPromptSetText dialog (decodeUtf8 . toStrict $ encode batch)
175+ return True
176+ Nothing -> return False
177+ _ -> return False
178+ _ -> return False
179+
183180 void $ userContentManagerRegisterScriptMessageHandler manager "jsaddle"
184181
185182jsaddleJs :: LB.ByteString
@@ -188,6 +185,7 @@ jsaddleJs = ghcjsHelpers <> mconcat
188185 , initState
189186 , "\nreturn function(batch) {\n"
190187 , runBatch (\a -> "window.webkit.messageHandlers.jsaddle.postMessage(JSON.stringify(" <> a <> "));\n")
188+ (Just (\a -> "JSON.parse(window.prompt(\"JSaddleSync\", JSON.stringify(" <> a <> ")))"))
191189 , "};\n"
192190 , "})()"
193191 ]
jsaddle-webkitgtk/default.nixdeleted−16
@@ -1,16 +0,0 @@
1−{ mkDerivation, aeson, base, bytestring, directory, gi-glib, gi-gtk
2−, gi-javascriptcore, gi-webkit, haskell-gi-base, jsaddle, stdenv
3−, text, unix, webkitgtk3-javascriptcore
4−}:
5−mkDerivation {
6− pname = "jsaddle-webkitgtk";
7− version = "0.8.2.2";
8− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
9− libraryHaskellDepends = [
10− aeson base bytestring directory gi-glib gi-gtk gi-javascriptcore
11− gi-webkit haskell-gi-base jsaddle text unix
12− webkitgtk3-javascriptcore
13− ];
14− description = "Interface for JavaScript that works with GHCJS and GHC";
15− license = stdenv.lib.licenses.mit;
16−}
jsaddle-webkitgtk/jsaddle-webkitgtk.cabal+3−3
@@ -1,5 +1,5 @@
11name: jsaddle-webkitgtk
2−version: 0.8.3.0
2+version: 0.9.0.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -23,7 +23,7 @@ library
2323 exposed-modules:
2424 Language.Javascript.JSaddle.WebKitGTK
2525 build-depends:
26− aeson >=0.8.0.2 && <1.2,
26+ aeson >=0.8.0.2 && <1.3,
2727 base <5,
2828 bytestring >=0.10.6.0 && <0.11,
2929 directory >=1.0.0.2 && <1.4,
@@ -32,7 +32,7 @@ library
3232 gi-webkit >=3.0.6 && <3.1,
3333 gi-javascriptcore >=3.0.6 && <3.1,
3434 haskell-gi-base >=0.20 && <0.21,
35− jsaddle >=0.8.0.0 && <0.9,
35+ jsaddle >=0.9.0.0 && <0.10,
3636 text >=1.2.1.3 && <1.3,
3737 webkitgtk3-javascriptcore >=0.14.0.0 && <0.15
3838 if !os(windows)
jsaddle-webkitgtk/src/Language/Javascript/JSaddle/WebKitGTK.hs+2−2
@@ -185,7 +185,7 @@ runInWebView f webView = do
185185 jsvalueprotect ctxRef result
186186 return result
187187
188− (processResults, start) <- runJavaScript (\batch -> postGUIAsync $
188+ (processResults, _, start) <- runJavaScript (\batch -> postGUIAsync $
189189 withJSContextRef ctx $ \ctxRef ->
190190 withJSString (decodeUtf8 . toStrict $ encode batch) $ \script -> do
191191 val <- jsvaluemakestring ctxRef script
@@ -222,7 +222,7 @@ jsaddleJs = ghcjsHelpers <> mconcat
222222 , initState
223223 , "\nreturn function(batchJSON) {\n"
224224 , " var batch = JSON.parse(batchJSON);\n"
225− , runBatch (\a -> "window.jsaddleCallback(JSON.stringify(" <> a <> "));\n")
225+ , runBatch (\a -> "window.jsaddleCallback(JSON.stringify(" <> a <> "));\n") Nothing
226226 , "};\n"
227227 , "})()\n"
228228 ]
jsaddle-wkwebview/cbits-cocoa/AppDelegate.m+65−6
@@ -2,7 +2,10 @@
22#import <Cocoa/Cocoa.h>
33#import <WebKit/WebKit.h>
44
5−extern void withWebView(WKWebView *, HsStablePtr);
5+extern void callIO(HsStablePtr);
6+extern bool callWithCStringReturningBool(const char * _Nonnull, HsStablePtr);
7+extern void callWithCString(const char * _Nonnull, HsStablePtr);
8+extern void callWithWebView(WKWebView *, HsStablePtr);
69
710@interface AppDelegate : NSObject <NSApplicationDelegate>
811@property (nonatomic, assign) IBOutlet NSWindow *window;
@@ -11,6 +14,12 @@ extern void withWebView(WKWebView *, HsStablePtr);
1114- (instancetype)initApp:(HsStablePtr)handler progName:(NSString *)progName;
1215@end
1316
17+HsStablePtr global_willFinishLaunchingWithOptions = 0;
18+HsStablePtr global_didFinishLaunchingWithOptions = 0;
19+HsStablePtr global_applicationUniversalLink = 0;
20+HsStablePtr global_applicationOpenFile = 0;
21+uint64_t global_developerExtrasEnabled = 1;
22+
1423@implementation AppDelegate
1524
1625- (void)applicationWillTerminate:(NSNotification *)aNotification {
@@ -31,18 +40,39 @@ extern void withWebView(WKWebView *, HsStablePtr);
3140 return self;
3241}
3342
43+-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
44+ bool b = callWithCStringReturningBool([filename UTF8String], global_applicationOpenFile);
45+ return b ? YES : NO;
46+}
47+
3448-(void)applicationWillFinishLaunching:(NSNotification *)notification {
3549 WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
36− [theConfiguration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
37− WKWebView *webView = [[WKWebView alloc] initWithFrame:_window.contentView.frame configuration:theConfiguration];
50+ if (global_developerExtrasEnabled) {
51+ [theConfiguration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
52+ }
53+ WKWebView *webView = [[WKWebView alloc] initWithFrame: [_window.contentView frame] configuration:theConfiguration];
3854 [_window setContentView:webView];
39− withWebView(webView, _handler);
55+ callWithWebView(webView, _handler);
56+ callIO(global_willFinishLaunchingWithOptions);
57+ // Listen to URL events
58+ NSAppleEventManager *manager = [NSAppleEventManager sharedAppleEventManager];
59+ [manager
60+ setEventHandler:self
61+ andSelector:@selector(applicationUniversalLink:)
62+ forEventClass:kInternetEventClass
63+ andEventID:kAEGetURL];
64+}
65+
66+- (void)applicationUniversalLink:(NSAppleEventDescriptor *)event {
67+ NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
68+ callWithCString([url UTF8String], global_applicationUniversalLink);
4069}
4170
4271-(void)applicationDidFinishLaunching:(NSNotification *)notification {
4372 [_window orderFrontRegardless];
4473 [_window center];
4574 [NSApp activateIgnoringOtherApps:YES];
75+ callIO(global_didFinishLaunchingWithOptions);
4676}
4777
4878-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
@@ -51,13 +81,42 @@ extern void withWebView(WKWebView *, HsStablePtr);
5181
5282@end
5383
54−void runInWKWebView(HsStablePtr handler, const char * _Nonnull progName) {
84+void runInWKWebView(HsStablePtr handler,
85+ const char * _Nonnull progName,
86+ HsStablePtr hs_willFinishLaunchingWithOptions,
87+ HsStablePtr hs_didFinishLaunchingWithOptions,
88+ HsStablePtr hs_applicationDidBecomeActive,
89+ HsStablePtr hs_applicationWillResignActive,
90+ HsStablePtr hs_applicationDidEnterBackground,
91+ HsStablePtr hs_applicationWillEnterForeground,
92+ HsStablePtr hs_applicationWillTerminate,
93+ HsStablePtr hs_applicationSignificantTimeChange,
94+ HsStablePtr hs_applicationUniversalLink,
95+ const uint64_t hs_requestAuthorizationWithOptions,
96+ const uint64_t hs_requestAuthorizationOptionBadge,
97+ const uint64_t hs_requestAuthorizationOptionSound,
98+ const uint64_t hs_requestAuthorizationOptionAlert,
99+ const uint64_t hs_requestAuthorizationOptionCarPlay,
100+ const uint64_t hs_registerForRemoteNotifications,
101+ HsStablePtr hs_didRegisterForRemoteNotificationsWithDeviceToken,
102+ HsStablePtr hs_didFailToRegisterForRemoteNotificationsWithError,
103+ const uint64_t hs_developerExtrasEnabled,
104+ HsStablePtr hs_applicationOpenFile) {
55105 @autoreleasepool {
106+ global_willFinishLaunchingWithOptions = hs_willFinishLaunchingWithOptions;
107+ global_didFinishLaunchingWithOptions = hs_didFinishLaunchingWithOptions;
108+ global_applicationUniversalLink = hs_applicationUniversalLink;
109+ global_applicationOpenFile = hs_applicationOpenFile;
110+ global_developerExtrasEnabled = hs_developerExtrasEnabled;
56111 NSApplication *application = [NSApplication sharedApplication];
57112 AppDelegate *appDelegate = [[AppDelegate alloc] initApp:handler progName:[NSString stringWithCString:progName encoding:NSUTF8StringEncoding]];
58113 [application setDelegate:appDelegate];
59114 const char * _Nonnull argv [] = {"", 0};
60− NSApplicationMain(0, argv);
115+ [application setActivationPolicy:NSApplicationActivationPolicyRegular];
116+ [application
117+ performSelectorOnMainThread:@selector(run)
118+ withObject:nil
119+ waitUntilDone:YES];
61120 }
62121}
63122
jsaddle-wkwebview/cbits-uikit/AppDelegate.m+109−23
@@ -1,104 +1,189 @@
11#import "AppDelegate.h"
22#import "ViewController.h"
33#import <UserNotifications/UserNotifications.h>
4+#import <stdint.h>
45
5−extern void handlerCString(const char * _Nonnull, HsStablePtr);
6+extern void callIO(HsStablePtr);
7+extern void callWithCString(const char * _Nonnull, HsStablePtr);
8+extern void callWithCIntCString(int n, const char * _Nonnull, HsStablePtr);
69
710@interface AppDelegate ()
811
912@end
1013
1114HsStablePtr globalHandler = 0;
12−BOOL global_requestAuthorizationWithOptions = NO;
13−BOOL global_requestAuthorizationOptionBadge = NO;
14−BOOL global_requestAuthorizationOptionSound = NO;
15−BOOL global_requestAuthorizationOptionAlert = NO;
16−BOOL global_requestAuthorizationOptionCarPlay = NO;
17−BOOL global_registerForRemoteNotifications = NO;
15+HsStablePtr global_willFinishLaunchingWithOptions = 0;
16+HsStablePtr global_didFinishLaunchingWithOptions = 0;
17+HsStablePtr global_applicationDidBecomeActive = 0;
18+HsStablePtr global_applicationWillResignActive = 0;
19+HsStablePtr global_applicationDidEnterBackground = 0;
20+HsStablePtr global_applicationWillEnterForeground = 0;
21+HsStablePtr global_applicationWillTerminate = 0;
22+HsStablePtr global_applicationSignificantTimeChange = 0;
23+HsStablePtr global_applicationUniversalLink = 0;
24+HsStablePtr global_applicationDidReceiveRemoteNotification = 0;
25+uint64_t global_requestAuthorizationWithOptions = 0;
26+uint64_t global_requestAuthorizationOptionBadge = 0;
27+uint64_t global_requestAuthorizationOptionSound = 0;
28+uint64_t global_requestAuthorizationOptionAlert = 0;
29+uint64_t global_requestAuthorizationOptionCarPlay = 0;
30+uint64_t global_registerForRemoteNotifications = 0;
1831HsStablePtr global_didRegisterForRemoteNotificationsWithDeviceToken = 0;
32+HsStablePtr global_didFailToRegisterForRemoteNotificationsWithError = 0;
1933
2034@implementation AppDelegate
2135
36+- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
37+ // Tells the delegate that the launch process has begun but that state restoration has not yet occurred.
38+ callIO(global_willFinishLaunchingWithOptions);
39+ return YES;
40+}
41+
2242- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
43+ // Tells the delegate that the launch process is almost done and the app is almost ready to run.
2344 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
2445 // Override point for customization after application launch.
2546 self.window.rootViewController = [[ViewController alloc] initWithHandler:globalHandler];
2647 self.window.backgroundColor = [UIColor whiteColor];
2748 [self.window makeKeyAndVisible];
2849 UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
50+ center.delegate = self;
2951 if (global_requestAuthorizationWithOptions) {
3052 UNAuthorizationOptions options = (UNAuthorizationOptions)0;
3153 if (global_requestAuthorizationOptionBadge) {
32− options = options + UNAuthorizationOptionBadge;
54+ options = options | UNAuthorizationOptionBadge;
3355 }
3456 if (global_requestAuthorizationOptionSound) {
35− options = options + UNAuthorizationOptionSound;
57+ options = options | UNAuthorizationOptionSound;
3658 }
3759 if (global_requestAuthorizationOptionAlert) {
38− options = options + UNAuthorizationOptionAlert;
60+ options = options | UNAuthorizationOptionAlert;
3961 }
4062 if (global_requestAuthorizationOptionCarPlay) {
41− options = options + UNAuthorizationOptionCarPlay;
63+ options = options | UNAuthorizationOptionCarPlay;
4264 }
4365 [center requestAuthorizationWithOptions:(options)
4466 completionHandler:^(BOOL granted, NSError * _Nullable error) {
4567 // Handler used to alter application behavior based on types of notifications authorized
4668 }];
69+ if (global_registerForRemoteNotifications) {
70+ [application registerForRemoteNotifications];
71+ }
4772 }
48− if (global_registerForRemoteNotifications) {
49− [application registerForRemoteNotifications];
50− }
73+ callIO(global_didFinishLaunchingWithOptions);
5174 return YES;
5275}
5376
5477- (void)applicationWillResignActive:(UIApplication *)application {
5578 // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
5679 // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
80+ callIO(global_applicationWillResignActive);
5781}
5882
5983
6084- (void)applicationDidEnterBackground:(UIApplication *)application {
6185 // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
6286 // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
87+ callIO(global_applicationDidEnterBackground);
6388}
6489
6590
6691- (void)applicationWillEnterForeground:(UIApplication *)application {
6792 // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
93+ callIO(global_applicationWillEnterForeground);
6894}
6995
7096
7197- (void)applicationDidBecomeActive:(UIApplication *)application {
7298 // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
99+ callIO(global_applicationDidBecomeActive);
73100}
74101
75102
76103- (void)applicationWillTerminate:(UIApplication *)application {
77104 // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
105+ callIO(global_applicationWillTerminate);
78106}
79107
80108- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
109+ // Tells the delegate that the app successfully registered with Apple Push Notification service (APNs).
81110 NSString *deviceTokenString = [deviceToken base64EncodedStringWithOptions: 0];
82− handlerCString([deviceTokenString UTF8String], global_didRegisterForRemoteNotificationsWithDeviceToken);
111+ callWithCString([deviceTokenString UTF8String], global_didRegisterForRemoteNotificationsWithDeviceToken);
112+}
113+
114+- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler {
115+ // Sent when the application receives remote notifications in the foreground or background
116+ // TODO Allow a configurable CString -> IO () to be passed into AppDelegateConfig
117+ if ([userInfo valueForKeyPath:@"aps.badge"] != nil) {
118+ [UIApplication sharedApplication].applicationIconBadgeNumber=[[[userInfo objectForKey:@"aps"] objectForKey:@"badge"] intValue];
119+ }
120+ NSError *error;
121+ NSData *jsonData = [NSJSONSerialization dataWithJSONObject:userInfo options:0 error:&error];
122+ if (jsonData) {
123+ NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
124+ callWithCIntCString((int) application.applicationState, [jsonString UTF8String], global_applicationDidReceiveRemoteNotification);
125+ } else {
126+ NSLog(@"jsaddle-wkwebview didReceiveRemoteNotification handler: error while serialising push notification as JSON");
127+ }
128+ completionHandler(UIBackgroundFetchResultNewData);
83129}
84130
85131- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
86− // Called when remote notification registration failed
132+ // Sent to the delegate when Apple Push Notification service cannot successfully complete the registration process.
133+ NSString *errorString = [error localizedDescription];
134+ callWithCString([errorString UTF8String], global_didFailToRegisterForRemoteNotificationsWithError);
135+}
136+
137+- (void)applicationSignificantTimeChange:(UIApplication *)application {
138+ // Tells the delegate when there is a significant change in the time.
139+ callIO(global_applicationSignificantTimeChange);
140+}
141+
142+- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
143+ // TODO: Reroute universal links when they're clicked in-app.
144+ // https://developer.apple.com/reference/webkit/wknavigationdelegate/1455643-webview?language=objc
145+ if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] && userActivity.webpageURL) {
146+ callWithCString([userActivity.webpageURL.absoluteString UTF8String], global_applicationUniversalLink);
147+ return YES;
148+ }
149+ return NO;
87150}
88151
89152@end
90153
91154void runInWKWebView(HsStablePtr handler,
92155 const char * _Nonnull progName,
93− const BOOL hs_requestAuthorizationWithOptions,
94− const BOOL hs_requestAuthorizationOptionBadge,
95− const BOOL hs_requestAuthorizationOptionSound,
96− const BOOL hs_requestAuthorizationOptionAlert,
97− const BOOL hs_requestAuthorizationOptionCarPlay,
98− const BOOL hs_registerForRemoteNotifications,
99− HsStablePtr hs_didRegisterForRemoteNotificationsWithDeviceToken) {
156+ HsStablePtr hs_willFinishLaunchingWithOptions,
157+ HsStablePtr hs_didFinishLaunchingWithOptions,
158+ HsStablePtr hs_applicationDidBecomeActive,
159+ HsStablePtr hs_applicationWillResignActive,
160+ HsStablePtr hs_applicationDidEnterBackground,
161+ HsStablePtr hs_applicationWillEnterForeground,
162+ HsStablePtr hs_applicationWillTerminate,
163+ HsStablePtr hs_applicationSignificantTimeChange,
164+ HsStablePtr hs_applicationUniversalLink,
165+ HsStablePtr hs_applicationDidReceiveRemoteNotification,
166+ const uint64_t hs_requestAuthorizationWithOptions,
167+ const uint64_t hs_requestAuthorizationOptionBadge,
168+ const uint64_t hs_requestAuthorizationOptionSound,
169+ const uint64_t hs_requestAuthorizationOptionAlert,
170+ const uint64_t hs_requestAuthorizationOptionCarPlay,
171+ const uint64_t hs_registerForRemoteNotifications,
172+ HsStablePtr hs_didRegisterForRemoteNotificationsWithDeviceToken,
173+ HsStablePtr hs_didFailToRegisterForRemoteNotificationsWithError,
174+ const uint64_t hs_developerExtrasEnabled) {
100175 @autoreleasepool {
101176 globalHandler = handler;
177+ global_willFinishLaunchingWithOptions = hs_willFinishLaunchingWithOptions;
178+ global_didFinishLaunchingWithOptions = hs_didFinishLaunchingWithOptions;
179+ global_applicationDidBecomeActive = hs_applicationDidBecomeActive;
180+ global_applicationWillResignActive = hs_applicationWillResignActive;
181+ global_applicationDidEnterBackground = hs_applicationDidEnterBackground;
182+ global_applicationWillEnterForeground = hs_applicationWillEnterForeground;
183+ global_applicationWillTerminate = hs_applicationWillTerminate;
184+ global_applicationSignificantTimeChange = hs_applicationSignificantTimeChange;
185+ global_applicationUniversalLink = hs_applicationUniversalLink;
186+ global_applicationDidReceiveRemoteNotification = hs_applicationDidReceiveRemoteNotification;
102187 global_requestAuthorizationWithOptions = hs_requestAuthorizationWithOptions;
103188 global_requestAuthorizationOptionBadge = hs_requestAuthorizationOptionBadge;
104189 global_requestAuthorizationOptionSound = hs_requestAuthorizationOptionSound;
@@ -106,6 +191,7 @@ void runInWKWebView(HsStablePtr handler,
106191 global_requestAuthorizationOptionCarPlay = hs_requestAuthorizationOptionCarPlay;
107192 global_registerForRemoteNotifications = hs_registerForRemoteNotifications;
108193 global_didRegisterForRemoteNotificationsWithDeviceToken = hs_didRegisterForRemoteNotificationsWithDeviceToken;
194+ global_didFailToRegisterForRemoteNotificationsWithError = hs_didFailToRegisterForRemoteNotificationsWithError;
109195 const char * _Nonnull argv [] = {"", 0};
110196 UIApplicationMain(0, argv, nil, NSStringFromClass([AppDelegate class]));
111197 }
jsaddle-wkwebview/cbits-uikit/ViewController.m+2−2
@@ -1,6 +1,6 @@
11#import "ViewController.h"
22
3−extern void withWebView(WKWebView *, HsStablePtr);
3+extern void callWithWebView(WKWebView *, HsStablePtr);
44
55@interface ViewController ()
66
@@ -27,7 +27,7 @@ extern void withWebView(WKWebView *, HsStablePtr);
2727
2828- (void)viewDidLoad {
2929 [super viewDidLoad];
30− withWebView(_webView, _handler);
30+ callWithWebView(_webView, _handler);
3131}
3232
3333
jsaddle-wkwebview/cbits/WKWebView.m+42−18
@@ -4,26 +4,30 @@
44
55BOOL openApp(NSURL * url);
66
7−extern void jsaddleStart(HsStablePtr);
8−extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
9−
10−@interface JSaddleHandler : NSObject<WKNavigationDelegate, WKScriptMessageHandler>
7+@interface JSaddleHandler : NSObject<WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate>
118
129@property (nonatomic, assign) HsStablePtr startHandler;
1310@property (nonatomic, assign) HsStablePtr resultHandler;
11+@property (nonatomic, assign) HsStablePtr syncHandler;
12+@property (nonatomic, assign) void (^completionHandler)(NSString *result);
1413
15−- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler;
14+- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler syncHandler:(HsStablePtr)syncHandler;
1615
1716@end
1817
18+extern void jsaddleStart(HsStablePtr);
19+extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
20+extern void jsaddleSyncResult(HsStablePtr, JSaddleHandler *, const char * _Nonnull result);
21+
1922@implementation JSaddleHandler
2023
21−- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler
24+- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler syncHandler:(HsStablePtr)syncHandler
2225{
2326 self = [super init];
2427 if (self) {
2528 _startHandler = startHandler;
2629 _resultHandler = resultHandler;
30+ _syncHandler = syncHandler;
2731 }
2832 return self;
2933}
@@ -39,15 +43,27 @@ extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
3943 jsaddleResult(self.resultHandler, [s UTF8String]);
4044}
4145
46+- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt
47+ defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString *result))completionHandler
48+{
49+ if([prompt isEqualToString:@"JSaddleSync"]) {
50+ _completionHandler = completionHandler;
51+ jsaddleSyncResult(self.syncHandler, self, [defaultText UTF8String]);
52+ }
53+ else {
54+ // TODO decide what if anything should go here
55+ }
56+}
57+
4258- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
4359{
4460 NSURL *url = navigationAction.request.URL;
4561
4662 if(!navigationAction.targetFrame && ![url.scheme isEqualToString:@"file"]) {
4763 if(openApp(url))
48− decisionHandler(WKNavigationActionPolicyCancel);
49− else
50− decisionHandler(WKNavigationActionPolicyAllow);
64+ decisionHandler(WKNavigationActionPolicyCancel);
65+ else
66+ decisionHandler(WKNavigationActionPolicyAllow);
5167 }
5268 else
5369 decisionHandler(WKNavigationActionPolicyAllow);
@@ -55,10 +71,14 @@ extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
5571
5672@end
5773
58−void addJSaddleHandler(WKWebView *webView, HsStablePtr startHandler, HsStablePtr resultHandler) {
59− JSaddleHandler * handler = [[JSaddleHandler alloc] initHandler:startHandler resultHandler:resultHandler];
74+void addJSaddleHandler(WKWebView *webView, HsStablePtr startHandler, HsStablePtr resultHandler, HsStablePtr syncHandler) {
75+ JSaddleHandler * handler = [[JSaddleHandler alloc] initHandler:startHandler resultHandler:resultHandler syncHandler:syncHandler];
6076 [[[webView configuration] userContentController] addScriptMessageHandler:handler name:@"jsaddle"];
77+#ifdef USE_UIKIT
78+ webView.scrollView.bounces = NO;
79+#endif
6180 webView.navigationDelegate = handler;
81+ webView.UIDelegate = handler;
6282}
6383
6484void evaluateJavaScript(WKWebView *webView, const char * _Nonnull js) {
@@ -69,10 +89,17 @@ void evaluateJavaScript(WKWebView *webView, const char * _Nonnull js) {
6989 });
7090}
7191
72−void loadHTMLString(WKWebView *webView, const char * _Nonnull html) {
73− NSString *htmlString = [NSString stringWithCString:html encoding:NSUTF8StringEncoding];
74− dispatch_async(dispatch_get_main_queue(), ^{
75− [webView loadHTMLString:htmlString baseURL:NULL];
92+void completeSync(JSaddleHandler *handler, const char * _Nonnull s) {
93+ NSString *string = [[NSString alloc] initWithCString:s encoding:NSUTF8StringEncoding];
94+ handler.completionHandler(string);
95+ handler.completionHandler = NULL;
96+}
97+
98+void loadHTMLStringWithBaseURL(WKWebView *webView, const char * _Nonnull html, const char * _Nonnull url) {
99+ NSString *htmlString = [NSString stringWithCString:html encoding:NSUTF8StringEncoding];
100+ NSURL *baseURL = [NSURL URLWithString:[[NSString alloc] initWithCString:url encoding:NSUTF8StringEncoding]];
101+ dispatch_async(dispatch_get_main_queue(), ^{
102+ [webView loadHTMLString:htmlString baseURL:baseURL];
76103 });
77104}
78105
@@ -95,6 +122,3 @@ void loadBundleFile(WKWebView *webView, const char * _Nonnull file, const char *
95122 }
96123 }
97124}
98−
99−
100−
jsaddle-wkwebview/default.nix+14−14
@@ -1,14 +1,14 @@
1−{ mkDerivation, stdenv, aeson, base, bytestring, jsaddle, data-default
2−, buildPackages
3−}:
4−
5−mkDerivation {
6− pname = "jsaddle-wkwebview";
7− version = "0.8.2.0";
8− src = ./.;
9− libraryHaskellDepends = [ aeson base bytestring jsaddle data-default ]
10− # should use `librarySystemDepends` but it is not propagated
11− ++ (with buildPackages; [ osx_sdk darwin.libobjc darwin.apple_sdk.libs.xpc ]);
12− description = "Interface for JavaScript that works with GHCJS and GHC";
13− license = stdenv.lib.licenses.mit;
14−}
1+{ filterSource ? (x: x) }:
2+self: super:
3+let
4+ pkgs = self.callPackage ({pkgs}: pkgs) {};
5+ inherit (pkgs) lib;
6+in
7+with pkgs.haskell.lib;
8+overrideCabal (self.callCabal2nix "jsaddle-wkwebview" (filterSource ./.) {}) (drv: {
9+ libraryFrameworkDepends = (drv.libraryFrameworkDepends or []) ++
10+ (if pkgs.stdenv.hostPlatform.useiOSPrebuilt then [
11+ "${pkgs.buildPackages.darwin.xcode}/Contents/Developer/Platforms/${pkgs.stdenv.hostPlatform.xcodePlatform}.platform/Developer/SDKs/${pkgs.stdenv.hostPlatform.xcodePlatform}.sdk/System"
12+ ] else (with pkgs.buildPackages.darwin.apple_sdk.frameworks; [ Cocoa WebKit ]));
13+ buildDepends = lib.optional (!pkgs.stdenv.hostPlatform.useiOSPrebuilt) [ pkgs.buildPackages.darwin.cf-private ];
14+})
jsaddle-wkwebview/jsaddle-wkwebview.cabal+20−11
@@ -1,5 +1,5 @@
11name: jsaddle-wkwebview
2−version: 0.8.3.0
2+version: 0.9.8.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -25,32 +25,41 @@ source-repository head
2525library
2626 exposed-modules:
2727 Language.Javascript.JSaddle.WKWebView
28− Language.Javascript.JSaddle.WKWebView.Internal
2928 build-depends:
30− aeson >=0.8.0.2 && <1.2,
31− base <5,
32− bytestring >=0.10.6.0 && <0.11,
33− jsaddle >= 0.8.0.0 && <0.9,
34− data-default,
35− containers
29+ base <5
3630 default-language: Haskell2010
3731 hs-source-dirs: src
38− frameworks: Foundation, WebKit
3932 ghc-options: -ferror-spans -Wall
40− c-sources:
41− cbits/WKWebView.m
33+ if os(linux)
34+ buildable: False
4235 if impl(ghcjs)
4336 hs-source-dirs: src-ghcjs
4437 else
38+ frameworks: Foundation, WebKit
39+ build-depends:
40+ aeson >=0.8.0.2 && <1.6,
41+ bytestring >=0.10.6.0 && <0.11,
42+ directory,
43+ jsaddle >= 0.9.4.0 && <0.10,
44+ text,
45+ data-default,
46+ containers
47+ exposed-modules:
48+ Language.Javascript.JSaddle.WKWebView.Internal
4549 hs-source-dirs: src-ghc
50+ c-sources:
51+ cbits/WKWebView.m
52+ cc-options: -Wno-everything
4653 if os(ios)
4754 frameworks: UIKit, UserNotifications
4855 if flag(include-app-delegate)
4956 c-sources:
5057 cbits-uikit/AppDelegate.m
5158 cbits-uikit/ViewController.m
59+ cpp-options: -DUSE_UIKIT
5260 else
5361 frameworks: Cocoa
5462 if flag(include-app-delegate)
5563 c-sources:
5664 cbits-cocoa/AppDelegate.m
65+ cpp-options: -DUSE_COCOA
jsaddle-wkwebview/src-ghc/Language/Javascript/JSaddle/WKWebView.hs+159−39
@@ -1,10 +1,15 @@
11module Language.Javascript.JSaddle.WKWebView
22 ( jsaddleMain
3+ , jsaddleMainHTMLWithBaseURL
34 , jsaddleMainFile
45 , WKWebView(..)
56 , run
7+ , run'
8+ , runWithAppConfig
9+ , runHTMLWithBaseURL
610 , runFile
711 , mainBundleResourcePath
12+ , ApplicationState (..)
813 , AppDelegateConfig (..)
914 , AppDelegateNotificationConfig (..)
1015 , AuthorizationOption (..)
@@ -14,32 +19,87 @@ import Data.ByteString (ByteString)
1419import Data.Default (Default, def)
1520import Data.Set (Set)
1621import qualified Data.Set as Set
17−import Language.Javascript.JSaddle.WKWebView.Internal (jsaddleMain, jsaddleMainFile, WKWebView(..), mainBundleResourcePath, boolToCChar)
22+import Data.Word
23+import Foreign.Marshal.Utils (fromBool)
24+import Language.Javascript.JSaddle.WKWebView.Internal
25+ (jsaddleMain, jsaddleMainHTMLWithBaseURL, jsaddleMainFile, WKWebView(..), mainBundleResourcePath)
1826import System.Environment (getProgName)
19−import Foreign.C.String (CString, withCString, peekCString)
20−import Foreign.C.Types
27+import Foreign.C.String (CString, withCString)
28+import Foreign.C.Types (CBool, CInt)
2129import Foreign.StablePtr (StablePtr, newStablePtr)
2230import Language.Javascript.JSaddle (JSM)
2331
24−foreign import ccall runInWKWebView :: StablePtr (WKWebView -> IO ())
25− -> CString
26− -> CChar -- whether to run requestAuthorizationWithOptions
27− -> CChar -- Ask for Badge authorization
28− -> CChar -- Ask for Sound authorization
29− -> CChar -- Ask for Alert authorization
30− -> CChar -- Ask for CarPlay authorization
31− -> CChar -- registerForRemoteNotifications
32− -> StablePtr (CString -> IO ()) -- didRegisterForRemoteNotificationsWithDeviceToken
33− -> IO ()
34−
32+foreign import ccall runInWKWebView
33+ :: StablePtr (WKWebView -> IO ())
34+ -> CString
35+ -> StablePtr (IO ()) -- willFinishLaunchingWithOptions
36+ -> StablePtr (IO ()) -- didFinishLaunchingWithOptions
37+ -> StablePtr (IO ()) -- applicationDidBecomeActive
38+ -> StablePtr (IO ()) -- applicationWillResignActive
39+ -> StablePtr (IO ()) -- applicationDidEnterBackground
40+ -> StablePtr (IO ()) -- applicationWillEnterForeground
41+ -> StablePtr (IO ()) -- applicationWillTerminate
42+ -> StablePtr (IO ()) -- applicationSignificantTimeChange
43+ -> StablePtr (CString -> IO ()) -- applicationUniversalLink
44+ -> StablePtr (CInt -> CString -> IO ()) -- applicationDidReceiveRemoteNotification
45+ -> Word64 -- whether to run requestAuthorizationWithOptions
46+ -> Word64 -- Ask for Badge authorization
47+ -> Word64 -- Ask for Sound authorization
48+ -> Word64 -- Ask for Alert authorization
49+ -> Word64 -- Ask for CarPlay authorization
50+ -> Word64 -- registerForRemoteNotifications
51+ -> StablePtr (CString -> IO ()) -- didRegisterForRemoteNotificationsWithDeviceToken
52+ -> StablePtr (CString -> IO ()) -- didFailToRegisterForRemoteNotificationsWithError
53+ -> Word64 -- developerExtrasEnabled
54+ -> StablePtr (CString -> IO CBool) -- applicationOpenFile
55+ -> IO ()
3556
3657data AppDelegateConfig = AppDelegateConfig
37− { _appDelegateConfig_appDelegateNotificationConfig :: AppDelegateNotificationConfig
58+ { _appDelegateConfig_willFinishLaunchingWithOptions :: IO ()
59+ , _appDelegateConfig_didFinishLaunchingWithOptions :: IO ()
60+ , _appDelegateConfig_applicationDidBecomeActive :: IO ()
61+ , _appDelegateConfig_applicationWillResignActive :: IO ()
62+ , _appDelegateConfig_applicationDidEnterBackground :: IO ()
63+ , _appDelegateConfig_applicationWillEnterForeground :: IO ()
64+ , _appDelegateConfig_applicationWillTerminate :: IO ()
65+ , _appDelegateConfig_applicationSignificantTimeChange :: IO ()
66+ , _appDelegateConfig_applicationUniversalLink :: CString -> IO ()
67+ -- ^ Called when the app is launched from a URL. 'CString' contains the URL.
68+ --
69+ -- The Core Foundation plist key CFBundleURLTypes controls which URL schemes
70+ -- the app should respond to.
71+ , _appDelegateConfig_applicationDidReceiveRemoteNotification :: ApplicationState -> CString -> IO ()
72+ -- ^ Called when the application receives a remote notification.
73+ -- 'ApplicationState' is the current state of the app.
74+ -- The 'CString' JSON-encoded user info contained in the notification.
75+ -- For more information see https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623117-application?language=objc
76+ , _appDelegateConfig_appDelegateNotificationConfig :: AppDelegateNotificationConfig
77+ , _appDelegateConfig_developerExtrasEnabled :: Bool
78+ -- ^ Allow devtools in the app. Defaults to 'True'
79+ , _appDelegateConfig_applicationOpenFile :: CString -> IO Bool
80+ -- ^ Called when the app is launched by opening a file. 'CString' contains
81+ -- the file path. Return value is 'True' if the file was successfully
82+ -- opened. Defaults to ignoring the file and returning 'False'.
83+ --
84+ -- The Core Foundation plist key CFBundleDocumentTypes controls which file
85+ -- types should be associated with the app.
3886 }
3987
4088instance Default AppDelegateConfig where
4189 def = AppDelegateConfig
42− { _appDelegateConfig_appDelegateNotificationConfig = def
90+ { _appDelegateConfig_willFinishLaunchingWithOptions = return ()
91+ , _appDelegateConfig_didFinishLaunchingWithOptions = return ()
92+ , _appDelegateConfig_applicationDidBecomeActive = return ()
93+ , _appDelegateConfig_applicationWillResignActive = return ()
94+ , _appDelegateConfig_applicationDidEnterBackground = return ()
95+ , _appDelegateConfig_applicationWillEnterForeground = return ()
96+ , _appDelegateConfig_applicationWillTerminate = return ()
97+ , _appDelegateConfig_applicationSignificantTimeChange = return ()
98+ , _appDelegateConfig_applicationUniversalLink = \_ -> return ()
99+ , _appDelegateConfig_applicationDidReceiveRemoteNotification = \_ _ -> return ()
100+ , _appDelegateConfig_appDelegateNotificationConfig = def
101+ , _appDelegateConfig_developerExtrasEnabled = True
102+ , _appDelegateConfig_applicationOpenFile = \_ -> return False
43103 }
44104
45105data AuthorizationOption = AuthorizationOption_Badge
@@ -48,38 +108,84 @@ data AuthorizationOption = AuthorizationOption_Badge
48108 | AuthorizationOption_CarPlay
49109 deriving (Show, Read, Eq, Ord)
50110
111+data ApplicationState = ApplicationState_Active
112+ | ApplicationState_Inactive
113+ | ApplicationState_Background
114+ | ApplicationState_Unknown CInt
115+ deriving (Show, Read, Eq, Ord)
116+
51117data AppDelegateNotificationConfig = AppDelegateNotificationConfig
52118 { _appDelegateNotificationConfig_requestAuthorizationWithOptions :: Maybe (Set AuthorizationOption)
53119 , _appDelegateNotificationConfig_registerForRemoteNotifications :: Bool
54120 , _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken :: CString -> IO ()
121+ , _appDelegateNotificationConfig_didFailToRegisterForRemoteNotificationsWithError :: CString -> IO ()
55122 }
56123
57124instance Default AppDelegateNotificationConfig where
58125 def = AppDelegateNotificationConfig
59− { _appDelegateNotificationConfig_requestAuthorizationWithOptions = Nothing
60− , _appDelegateNotificationConfig_registerForRemoteNotifications = False
61− , _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken = \_ -> return ()
62− }
126+ { _appDelegateNotificationConfig_requestAuthorizationWithOptions = Nothing
127+ , _appDelegateNotificationConfig_registerForRemoteNotifications = False
128+ , _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken = \_ -> return ()
129+ , _appDelegateNotificationConfig_didFailToRegisterForRemoteNotificationsWithError = \_ -> return ()
130+ }
63131
64132
65133-- | Run JSaddle in a WKWebView
66−run :: (CString -> IO ()) -> JSM () -> IO ()
67−run didRegisterForRemoteNotificationsWithDeviceToken f = do
68− handler <- newStablePtr (jsaddleMain f)
69− progName <- getProgName
70− deviceTokenHandler <- newStablePtr didRegisterForRemoteNotificationsWithDeviceToken
71− withCString progName $ \pn -> runInWKWebView handler pn 1 1 1 1 1 1 deviceTokenHandler
134+run :: JSM () -> IO ()
135+run f = run' def (jsaddleMain f)
136+
137+-- | Run JSaddle in a WKWebView
138+runWithAppConfig :: AppDelegateConfig -> JSM () -> IO ()
139+runWithAppConfig cfg = run' cfg . jsaddleMain
72140
73141-- | Run JSaddle in a WKWebView first loading the specified file
74142-- from the mainBundle (relative to the resourcePath).
75−runFile :: ByteString -- ^ The file to navigate to.
76− -> ByteString -- ^ The path to allow read access to.
77− -> AppDelegateConfig
78− -> JSM ()
79− -> IO ()
80−runFile url allowing cfg f = do
81− handler <- newStablePtr (jsaddleMainFile url allowing f)
143+runFile
144+ :: ByteString -- ^ The file to navigate to.
145+ -> ByteString -- ^ The path to allow read access to.
146+ -> AppDelegateConfig
147+ -> JSM ()
148+ -> IO ()
149+runFile url allowing cfg = run' cfg . jsaddleMainFile url allowing
150+
151+-- | Run JSaddle in a WKWebView first loading the specified html
152+-- as though it came from the specified URL
153+-- (calls WKWebKit function loadHTMLString).
154+runHTMLWithBaseURL
155+ :: ByteString -- ^ HTML to load.
156+ -> ByteString -- ^ pretend it came from this URL.
157+ -> AppDelegateConfig
158+ -> JSM ()
159+ -> IO ()
160+runHTMLWithBaseURL url allowing cfg = run' cfg . jsaddleMainHTMLWithBaseURL url allowing
161+
162+run' :: AppDelegateConfig
163+ -> (WKWebView -> IO ())
164+ -> IO ()
165+run' cfg main = do
166+ handler <- newStablePtr main
82167 progName <- getProgName
168+
169+ -- AppDelegate callbacks
170+ willFinishLaunchingWithOptions <- newStablePtr $ _appDelegateConfig_willFinishLaunchingWithOptions cfg
171+ didFinishLaunchingWithOptions <- newStablePtr $ _appDelegateConfig_didFinishLaunchingWithOptions cfg
172+ applicationDidBecomeActive <- newStablePtr $ _appDelegateConfig_applicationDidBecomeActive cfg
173+ applicationWillResignActive <- newStablePtr $ _appDelegateConfig_applicationWillResignActive cfg
174+ applicationDidEnterBackground <- newStablePtr $ _appDelegateConfig_applicationDidEnterBackground cfg
175+ applicationWillEnterForeground <- newStablePtr $ _appDelegateConfig_applicationWillEnterForeground cfg
176+ applicationWillTerminate <- newStablePtr $ _appDelegateConfig_applicationWillTerminate cfg
177+ applicationSignificantTimeChange <- newStablePtr $ _appDelegateConfig_applicationSignificantTimeChange cfg
178+ applicationUniversalLink <- newStablePtr $ _appDelegateConfig_applicationUniversalLink cfg
179+ applicationDidReceiveRemoteNotification <- newStablePtr $ \n s -> do
180+ let appState = case n of
181+ 0 -> ApplicationState_Active
182+ 1 -> ApplicationState_Inactive
183+ 2 -> ApplicationState_Background
184+ _ -> ApplicationState_Unknown n
185+ _appDelegateConfig_applicationDidReceiveRemoteNotification cfg appState s
186+ applicationOpenFile <- newStablePtr $ fmap fromBool . _appDelegateConfig_applicationOpenFile cfg
187+
188+ -- AppDelegate notification configuration
83189 let ncfg = _appDelegateConfig_appDelegateNotificationConfig cfg
84190 (requestAuthorizationWithOptions, authorizationOptions) =
85191 case _appDelegateNotificationConfig_requestAuthorizationWithOptions ncfg of
@@ -87,13 +193,27 @@ runFile url allowing cfg f = do
87193 Just opts -> (True, opts)
88194 registerForRemoteNotifications = _appDelegateNotificationConfig_registerForRemoteNotifications ncfg
89195 didRegisterForRemoteNotificationsWithDeviceToken <- newStablePtr $ _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken ncfg
196+ didFailToRegisterForRemoteNotificationsWithError <- newStablePtr $ _appDelegateNotificationConfig_didFailToRegisterForRemoteNotificationsWithError ncfg
90197 withCString progName $ \pn -> runInWKWebView
91198 handler
92199 pn
93− (boolToCChar requestAuthorizationWithOptions)
94− (boolToCChar $ Set.member AuthorizationOption_Badge authorizationOptions)
95− (boolToCChar $ Set.member AuthorizationOption_Sound authorizationOptions)
96− (boolToCChar $ Set.member AuthorizationOption_Alert authorizationOptions)
97− (boolToCChar $ Set.member AuthorizationOption_CarPlay authorizationOptions)
98− (boolToCChar registerForRemoteNotifications)
200+ willFinishLaunchingWithOptions
201+ didFinishLaunchingWithOptions
202+ applicationDidBecomeActive
203+ applicationWillResignActive
204+ applicationDidEnterBackground
205+ applicationWillEnterForeground
206+ applicationWillTerminate
207+ applicationSignificantTimeChange
208+ applicationUniversalLink
209+ applicationDidReceiveRemoteNotification
210+ (fromBool requestAuthorizationWithOptions)
211+ (fromBool $ Set.member AuthorizationOption_Badge authorizationOptions)
212+ (fromBool $ Set.member AuthorizationOption_Sound authorizationOptions)
213+ (fromBool $ Set.member AuthorizationOption_Alert authorizationOptions)
214+ (fromBool $ Set.member AuthorizationOption_CarPlay authorizationOptions)
215+ (fromBool registerForRemoteNotifications)
99216 didRegisterForRemoteNotificationsWithDeviceToken
217+ didFailToRegisterForRemoteNotificationsWithError
218+ (fromBool $ _appDelegateConfig_developerExtrasEnabled cfg)
219+ applicationOpenFile
jsaddle-wkwebview/src/Language/Javascript/JSaddle/WKWebView/Internal.hs+69−25
@@ -1,11 +1,10 @@
11{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
22module Language.Javascript.JSaddle.WKWebView.Internal
33 ( jsaddleMain
4+ , jsaddleMainHTMLWithBaseURL
45 , jsaddleMainFile
56 , WKWebView(..)
67 , mainBundleResourcePath
7− , boolFromCChar
8− , boolToCChar
98 ) where
109
1110import Control.Monad (void, join)
@@ -17,40 +16,64 @@ import Data.ByteString (useAsCString, packCString)
1716import qualified Data.ByteString as BS (ByteString)
1817import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
1918import Data.Aeson (encode, decode)
19+import qualified Data.Text as T (pack)
20+import Data.Text.Encoding (encodeUtf8)
2021
2122import Foreign.C.String (CString)
22−import Foreign.C.Types (CChar (..))
23+import Foreign.C.Types (CBool(..), CInt(..))
2324import Foreign.Ptr (Ptr, nullPtr)
2425import Foreign.StablePtr (StablePtr, newStablePtr, deRefStablePtr)
2526
26−import Language.Javascript.JSaddle (Results, JSM)
27+import Language.Javascript.JSaddle (Results, Batch, JSM)
2728import Language.Javascript.JSaddle.Run (runJavaScript)
2829import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
2930
31+import System.Directory (getCurrentDirectory)
32+
3033newtype WKWebView = WKWebView (Ptr WKWebView)
34+newtype JSaddleHandler = JSaddleHandler (Ptr JSaddleHandler)
3135
3236foreign export ccall jsaddleStart :: StablePtr (IO ()) -> IO ()
3337foreign export ccall jsaddleResult :: StablePtr (Results -> IO ()) -> CString -> IO ()
34−foreign export ccall withWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
35−foreign export ccall handlerCString :: CString -> StablePtr (CString -> IO ()) -> IO ()
36−foreign import ccall addJSaddleHandler :: WKWebView -> StablePtr (IO ()) -> StablePtr (Results -> IO ()) -> IO ()
37−foreign import ccall loadHTMLString :: WKWebView -> CString -> IO ()
38+foreign export ccall jsaddleSyncResult :: StablePtr (Results -> IO Batch) -> JSaddleHandler -> CString -> IO ()
39+foreign export ccall callWithWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
40+foreign export ccall callWithCIntCString :: CInt -> CString -> StablePtr (CInt -> CString -> IO ()) -> IO ()
41+foreign export ccall callWithCString :: CString -> StablePtr (CString -> IO ()) -> IO ()
42+foreign export ccall callWithCStringReturningBool :: CString -> StablePtr (CString -> IO CBool) -> IO CBool
43+foreign export ccall callIO :: StablePtr (IO ()) -> IO ()
44+foreign import ccall addJSaddleHandler :: WKWebView -> StablePtr (IO ()) -> StablePtr (Results -> IO ()) -> StablePtr (Results -> IO Batch) -> IO ()
45+foreign import ccall loadHTMLStringWithBaseURL :: WKWebView -> CString -> CString -> IO ()
3846foreign import ccall loadBundleFile :: WKWebView -> CString -> CString -> IO ()
3947foreign import ccall evaluateJavaScript :: WKWebView -> CString -> IO ()
48+foreign import ccall completeSync :: JSaddleHandler -> CString -> IO ()
4049foreign import ccall mainBundleResourcePathC :: IO CString
4150
4251-- | Run JSaddle in WKWebView
4352jsaddleMain :: JSM () -> WKWebView -> IO ()
4453jsaddleMain f webView = do
54+ pwd <- getCurrentDirectory
55+ let baseURL = encodeUtf8 $ "file://" <> T.pack pwd <> "/index.html"
56+ jsaddleMainHTMLWithBaseURL (toStrict indexHtml) baseURL f webView
57+
58+-- | Run JSaddle in WKWebView with initial html and base url
59+jsaddleMainHTMLWithBaseURL
60+ :: BS.ByteString -- ^ HTML
61+ -> BS.ByteString -- ^ Base URL
62+ -> JSM ()
63+ -> WKWebView
64+ -> IO ()
65+jsaddleMainHTMLWithBaseURL initialHTML baseURL f webView =
4566 jsaddleMain' f webView $
46− useAsCString (toStrict indexHtml) $ loadHTMLString webView
67+ useAsCString baseURL $ \url ->
68+ useAsCString initialHTML $ \html ->
69+ loadHTMLStringWithBaseURL webView html url
4770
4871-- | Run JSaddle in a WKWebView first loading the specified file
4972-- from the mainBundle (relative to the resourcePath).
5073jsaddleMainFile :: BS.ByteString -- ^ The file to navigate to.
5174 -> BS.ByteString -- ^ The path to allow read access to.
5275 -> JSM () -> WKWebView -> IO ()
53−jsaddleMainFile url allowing f webView = do
76+jsaddleMainFile url allowing f webView =
5477 jsaddleMain' f webView $
5578 useAsCString url $ \u ->
5679 useAsCString allowing $ \a ->
@@ -60,14 +83,15 @@ jsaddleMain' :: JSM () -> WKWebView -> IO () -> IO ()
6083jsaddleMain' f webView loadHtml = do
6184 ready <- newEmptyMVar
6285
63− (processResult, start) <- runJavaScript (\batch ->
86+ (processResult, syncResult, start) <- runJavaScript (\batch ->
6487 useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $
6588 evaluateJavaScript webView)
6689 f
6790
6891 startHandler <- newStablePtr (putMVar ready ())
6992 resultHandler <- newStablePtr processResult
70− addJSaddleHandler webView startHandler resultHandler
93+ syncResultHandler <- newStablePtr syncResult
94+ addJSaddleHandler webView startHandler resultHandler syncResultHandler
7195 loadHtml
7296 void . forkOS $ do
7397 takeMVar ready
@@ -81,31 +105,52 @@ jsaddleResult ptrHandler s = do
81105 processResult <- deRefStablePtr ptrHandler
82106 result <- packCString s
83107 case decode (fromStrict result) of
84− Nothing -> error $ "jsaddle WebSocket decode failed : " <> show result
108+ Nothing -> error $ "jsaddle Results decode failed : " <> show result
85109 Just r -> processResult r
86110
87−withWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
88−withWebView webView ptrF = do
111+jsaddleSyncResult :: StablePtr (Results -> IO Batch) -> JSaddleHandler -> CString -> IO ()
112+jsaddleSyncResult ptrHandler jsaddleHandler s = do
113+ syncProcessResult <- deRefStablePtr ptrHandler
114+ result <- packCString s
115+ case decode (fromStrict result) of
116+ Nothing -> error $ "jsaddle Results decode failed : " <> show result
117+ Just r -> do -- void . forkIO $ do
118+ batch <- syncProcessResult r
119+ useAsCString (toStrict $ encode batch) $
120+ completeSync jsaddleHandler
121+
122+callWithWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
123+callWithWebView webView ptrF = do
89124 f <- deRefStablePtr ptrF
90125 f webView
91126
92−handlerCString :: CString -> StablePtr (CString -> IO ()) -> IO ()
93−handlerCString c fptr = do
94− f <- deRefStablePtr fptr
95− f c
127+callWithCString :: CString -> StablePtr (CString -> IO ()) -> IO ()
128+callWithCString c fptr = do
129+ f <- deRefStablePtr fptr
130+ f c
96131
97−boolToCChar :: Bool -> CChar
98−boolToCChar a = if a then 1 else 0
132+callWithCIntCString :: CInt -> CString -> StablePtr (CInt -> CString -> IO ()) -> IO ()
133+callWithCIntCString n s fptr = do
134+ f <- deRefStablePtr fptr
135+ f n s
99136
100−boolFromCChar :: CChar -> Bool
101−boolFromCChar a = a /= 0
137+callWithCStringReturningBool :: CString -> StablePtr (CString -> IO CBool) -> IO CBool
138+callWithCStringReturningBool c fptr = do
139+ f <- deRefStablePtr fptr
140+ f c
141+
142+callIO :: StablePtr (IO ()) -> IO ()
143+callIO ptr = do
144+ f <- deRefStablePtr ptr
145+ f
102146
103147jsaddleJs :: ByteString
104148jsaddleJs = ghcjsHelpers <> "\
105149 \runJSaddleBatch = (function() {\n\
106150 \ " <> initState <> "\n\
107151 \ return function(batch) {\n\
108− \ " <> runBatch (\a -> "window.webkit.messageHandlers.jsaddle.postMessage(JSON.stringify(" <> a <> "));") <> "\
152+ \ " <> runBatch (\a -> "window.webkit.messageHandlers.jsaddle.postMessage(JSON.stringify(" <> a <> "));\n")
153+ (Just (\a -> "JSON.parse(window.prompt(\"JSaddleSync\", JSON.stringify(" <> a <> ")))")) <> "\
109154 \ };\n\
110155 \})();\n\
111156 \"
@@ -127,4 +172,3 @@ mainBundleResourcePath = do
127172 if bs == nullPtr
128173 then return Nothing
129174 else Just <$> packCString bs
130−
jsaddle/default.nixdeleted−17
@@ -1,17 +0,0 @@
1−{ mkDerivation, aeson, attoparsec, base, base64-bytestring
2−, bytestring, containers, deepseq, filepath, ghc-prim, http-types
3−, lens, primitive, process, ref-tf, scientific, stdenv, stm, text
4−, time, transformers, unordered-containers, vector
5−}:
6−mkDerivation {
7− pname = "jsaddle";
8− version = "0.8.0.1";
9− src = builtins.filterSource (path: type: !(builtins.elem (baseNameOf path) [ ".git" "dist" ])) ./.;
10− libraryHaskellDepends = [
11− aeson attoparsec base base64-bytestring bytestring containers
12− deepseq filepath ghc-prim http-types lens primitive process ref-tf
13− scientific stm text time transformers unordered-containers vector
14− ];
15− description = "Interface for JavaScript that works with GHCJS and GHC";
16− license = stdenv.lib.licenses.mit;
17−}
jsaddle/jsaddle.cabal+20−15
@@ -1,5 +1,5 @@
11name: jsaddle
2−version: 0.8.3.0
2+version: 0.9.8.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -10,7 +10,7 @@ description:
1010 This package provides an EDSL for calling JavaScript that
1111 can be used both from GHCJS and GHC. When using GHC
1212 the application is run using Warp and WebSockets to
13− drive a small JavaScipt helper.
13+ drive a small JavaScript helper.
1414category: Web, Javascript
1515author: Hamish Mackenzie
1616
@@ -36,18 +36,20 @@ library
3636 else
3737 build-depends:
3838 attoparsec >=0.11 && <0.14,
39− containers >=0.5.6.2 && <0.6,
39+ containers >=0.5.6.2 && <0.7,
4040 deepseq >=1.3 && < 1.5,
4141 filepath >=1.4.0.0 && <1.5,
4242 ghc-prim,
43− http-types >=0.8.6 && <0.10,
44− process >=1.2.3.0 && <1.5,
43+ http-types >=0.8.6 && <0.13,
44+ process >=1.2.3.0 && <1.7,
45+ random >= 1.1 && < 1.3,
4546 ref-tf >=0.4.0.1 && <0.5,
4647 scientific >=0.3 && <0.4,
47− stm >=2.4.4 && <2.5,
48− time >=1.5.0.1 && <1.8,
48+ stm >=2.4.4 && <2.6,
49+ time >=1.5.0.1 && <1.11,
4950 unordered-containers >=0.2 && <0.3,
50− vector >=0.10 && <0.12
51+ vector >=0.10 && <0.13,
52+ unliftio-core >=0.1 && < 0.3
5153 exposed-modules:
5254 Data.JSString
5355 Data.JSString.Internal
@@ -61,6 +63,7 @@ library
6163 GHCJS.Prim
6264 GHCJS.Prim.Internal
6365 GHCJS.Types
66+ GHCJS.Concurrent
6467 GHCJS.Foreign
6568 GHCJS.Foreign.Internal
6669 GHCJS.Internal.Types
@@ -81,8 +84,10 @@ library
8184 JavaScript.Array.Internal
8285 JavaScript.Object
8386 JavaScript.Object.Internal
87+ Language.Javascript.JSaddle.Debug
8488 Language.Javascript.JSaddle.Native
8589 Language.Javascript.JSaddle.Native.Internal
90+ Language.Javascript.JSaddle.Null
8691 hs-source-dirs: src-ghc
8792 exposed-modules:
8893 Language.Javascript.JSaddle
@@ -102,12 +107,14 @@ library
102107 Language.Javascript.JSaddle.Value
103108 Language.Javascript.JSaddle.Types
104109 build-depends:
105− aeson >=0.8.0.2 && <1.2,
106− base <5,
107− base64-bytestring >=1.0.0.1 && <1.1,
110+ aeson >=0.11.3.0 && <1.6,
111+ base >=4.9 && <5,
112+ base-compat >=0.9.0 && <0.12,
113+ base64-bytestring >=1.0.0.1 && <1.2,
108114 bytestring >=0.10.6.0 && <0.11,
109− lens >=3.8.5 && <4.16,
110− primitive >=0.6.1.0 && <0.7,
115+ exceptions >=0.8 && <0.11,
116+ lens >=3.8.5 && <5.1,
117+ primitive >=0.6.1.0 && <0.8,
111118 text >=1.2.1.3 && <1.3,
112119 transformers >=0.4.2.0 && <0.6
113120 default-language: Haskell2010
@@ -117,5 +124,3 @@ library
117124 cpp-options: -DCHECK_UNCHECKED
118125 if flag(call-stacks) || flag(check-unchecked)
119126 cpp-options: -DJSADDLE_HAS_CALL_STACK
120−
121−
jsaddle/src-ghc/Data/JSString/Internal/Type.hs+2−1
@@ -38,10 +38,11 @@ import Data.Aeson (ToJSON(..), FromJSON(..))
3838import Data.Data (Data)
3939-- import Data.Text.Internal.Unsafe.Char (ord)
4040import Data.Typeable (Typeable)
41+import Data.Semigroup (Semigroup)
4142import GHC.Exts (Char(..), ord#, andI#, (/=#), isTrue#)
4243
4344-- | A wrapper around a JavaScript string
44−newtype JSString = JSString Text deriving(Show, Read, IsString, Monoid, Ord, Eq, Data, ToJSON, FromJSON, Typeable)
45+newtype JSString = JSString Text deriving(Show, Read, IsString, Semigroup, Monoid, Ord, Eq, Data, ToJSON, FromJSON, Typeable)
4546
4647instance NFData JSString where rnf !_ = ()
4748
jsaddle/src-ghc/GHCJS/Buffer.hs+2−2
@@ -52,7 +52,7 @@ getInt32Array (SomeBuffer buf) = GHCJSPure $ I.SomeTypedArray <$> buf ^. js "i3"
5252{-# INLINE getInt32Array #-}
5353
5454getUint8Array :: SomeBuffer any -> GHCJSPure (I.SomeUint8Array any)
55−getUint8Array (SomeBuffer buf) = GHCJSPure $ I.SomeTypedArray <$> buf ^. js "u3"
55+getUint8Array (SomeBuffer buf) = GHCJSPure $ I.SomeTypedArray <$> buf ^. js "u8"
5656{-# INLINE getUint8Array #-}
5757
5858getUint16Array :: SomeBuffer any -> GHCJSPure (I.SomeUint16Array any)
@@ -85,7 +85,7 @@ clone = js_clone
8585
8686fromByteString :: ByteString -> GHCJSPure (Buffer, Int, Int)
8787fromByteString bs = GHCJSPure $ do
88− buffer <- SomeBuffer <$> jsg1 "h$newByteArrayBase64String" (decodeUtf8 $ B64.encode bs)
88+ buffer <- SomeBuffer <$> jsg1 "h$newByteArrayFromBase64String" (decodeUtf8 $ B64.encode bs)
8989 return (buffer, 0, BS.length bs)
9090{-# INLINE fromByteString #-}
9191
jsaddle/src-ghc/GHCJS/Concurrent.hsnew file+22
@@ -0,0 +1,22 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+
3+module GHCJS.Concurrent ( OnBlocked(..)
4+ , WouldBlockException(..)
5+ ) where
6+
7+import GHCJS.Prim
8+
9+import Data.Data
10+
11+{- |
12+ The runtime tries to run synchronous threads to completion. Sometimes it's
13+ not possible to continue running a thread, for example when the thread
14+ tries to take an empty 'MVar'. The runtime can then either throw a
15+ 'WouldBlockException', aborting the blocking action, or continue the
16+ thread asynchronously.
17+ -}
18+
19+data OnBlocked = ContinueAsync -- ^ continue the thread asynchronously if blocked
20+ | ThrowWouldBlock -- ^ throw 'WouldBlockException' if blocked
21+ deriving (Data, Typeable, Enum, Show, Eq, Ord)
22+
jsaddle/src-ghc/GHCJS/Foreign/Internal.hs+12−10
@@ -27,26 +27,28 @@ import Language.Javascript.JSaddle.Native.Internal
2727 (valueToBool)
2828import Data.Typeable (Typeable)
2929import GHCJS.Prim (isNull, isUndefined)
30+import System.IO.Unsafe (unsafePerformIO)
31+import Data.IORef (newIORef)
3032
3133jsTrue :: JSVal
32−jsTrue = JSVal 3
33−{-# INLINE jsTrue #-}
34+jsTrue = JSVal . unsafePerformIO $ newIORef 3
35+{-# NOINLINE jsTrue #-}
3436
3537jsFalse :: JSVal
36−jsFalse = JSVal 2
37−{-# INLINE jsFalse #-}
38+jsFalse = JSVal . unsafePerformIO $ newIORef 2
39+{-# NOINLINE jsFalse #-}
3840
3941jsNull :: JSVal
40−jsNull = JSVal 0
41−{-# INLINE jsNull #-}
42+jsNull = JSVal . unsafePerformIO $ newIORef 0
43+{-# NOINLINE jsNull #-}
4244
4345toJSBool :: Bool -> JSVal
44−toJSBool b = JSVal $ if b then 3 else 2
45−{-# INLINE toJSBool #-}
46+toJSBool b = JSVal . unsafePerformIO . newIORef $ if b then 3 else 2
47+{-# NOINLINE toJSBool #-}
4648
4749jsUndefined :: JSVal
48−jsUndefined = JSVal 1
49−{-# INLINE jsUndefined #-}
50+jsUndefined = JSVal . unsafePerformIO $ newIORef 1
51+{-# NOINLINE jsUndefined #-}
5052
5153isTruthy :: JSVal -> GHCJSPure Bool
5254isTruthy = GHCJSPure . valueToBool
jsaddle/src-ghc/GHCJS/Prim/Internal.hs+5−3
@@ -17,6 +17,8 @@ import Unsafe.Coerce (unsafeCoerce)
1717import Data.Aeson (ToJSON(..), FromJSON(..))
1818
1919import qualified GHC.Exception as Ex
20+import Data.IORef (newIORef, IORef)
21+import System.IO.Unsafe (unsafePerformIO)
2022
2123-- A reference to a particular JavaScript value inside the JavaScript context
2224type JSValueRef = Int64
@@ -25,7 +27,7 @@ type JSValueRef = Int64
2527 JSVal is a boxed type that can be used as FFI
2628 argument or result.
2729-}
28−newtype JSVal = JSVal JSValueRef deriving(Show, ToJSON, FromJSON)
30+newtype JSVal = JSVal (IORef JSValueRef)
2931
3032instance NFData JSVal where
3133 rnf x = x `seq` ()
@@ -49,8 +51,8 @@ mkJSException ref =
4951 return (JSException (unsafeCoerce ref) "")
5052
5153jsNull :: JSVal
52−jsNull = JSVal 0
53−{-# INLINE jsNull #-}
54+jsNull = JSVal . unsafePerformIO $ newIORef 0
55+{-# NOINLINE jsNull #-}
5456
5557{- | If a synchronous thread tries to do something that can only
5658 be done asynchronously, and the thread is set up to not
jsaddle/src-ghc/GHCJS/Types.hs+5−3
@@ -27,19 +27,21 @@ import GHC.Int
2727import GHC.Types
2828import GHC.Prim
2929import GHC.Ptr
30+import GHC.IORef
31+import GHC.IO.Unsafe (unsafePerformIO)
3032
3133import Control.DeepSeq
3234import Unsafe.Coerce
3335
34−type Ref# = Int64
36+type Ref# = IORef Int64
3537
3638mkRef :: Ref# -> JSVal
3739mkRef = JSVal
3840{-# INLINE mkRef #-}
3941
4042nullRef :: JSVal
41−nullRef = JSVal 0
42−{-# INLINE nullRef #-}
43+nullRef = JSVal . unsafePerformIO $ newIORef 0
44+{-# NOINLINE nullRef #-}
4345
4446--toPtr :: JSVal -> Ptr a
4547--toPtr (JSVal x) = unsafeCoerce (Ptr' x 0#)
jsaddle/src-ghc/JavaScript/Array.hs+2−2
@@ -48,7 +48,7 @@ import Language.Javascript.JSaddle.Types
4848 (JSM, JSVal, SomeJSArray(..), JSArray,
4949 MutableJSArray, GHCJSPure(..))
5050import Control.Lens.Operators ((^.))
51−import Language.Javascript.JSaddle.Object (js2, js0, (##), js1, js)
51+import Language.Javascript.JSaddle.Object (js2, js0, (<##), js1, js)
5252import Language.Javascript.JSaddle.Value (valToNumber)
5353import JavaScript.Array.Internal (create, fromList, fromListIO, toList, toListIO, index, read, push)
5454
@@ -69,7 +69,7 @@ append (SomeJSArray x) (SomeJSArray y) = SomeJSArray <$> x ^. js1 "concat" y
6969{-# INLINE append #-}
7070
7171write :: Int -> JSVal -> MutableJSArray -> JSM ()
72−write n e (SomeJSArray x) = void $ (x ## n) e
72+write n e (SomeJSArray x) = void $ (x <## n) e
7373{-# INLINE write #-}
7474
7575pop :: MutableJSArray -> JSM JSVal
jsaddle/src-ghc/JavaScript/Array/Internal.hs−2
@@ -21,8 +21,6 @@ import Data.JSString.Internal.Type (JSString(..))
2121import Language.Javascript.JSaddle.Types (JSM, SomeJSArray(..), JSArray, MutableJSArray, STJSArray, Object(..), GHCJSPure(..))
2222import Language.Javascript.JSaddle.Native.Internal
2323 (newArray, getPropertyByName, getPropertyAtIndex, callAsFunction, valueToNumber)
24−import Language.Javascript.JSaddle.Run
25− (Command(..), Result(..), AsyncCommand(..), sendCommand, sendLazyCommand)
2624
2725create :: JSM MutableJSArray
2826create = SomeJSArray <$> newArray []
jsaddle/src/Language/Javascript/JSaddle.hs+5−5
@@ -9,7 +9,7 @@
99-- | This package provides an EDSL for calling JavaScript that
1010-- can be used both from GHCJS and GHC. When using GHC
1111-- the application is run using Warp and WebSockets to
12−-- drive a small JavaScipt helper.
12+-- drive a small JavaScript helper.
1313-----------------------------------------------------------------------------
1414
1515module Language.Javascript.JSaddle (
@@ -35,10 +35,10 @@ module Language.Javascript.JSaddle (
3535 --
3636 -- Where it makes sense code examples are given in two forms. One
3737 -- that uses 'eval' to run a purely JavaScript version and one that
38− -- uses more of the JSaddle EDSL feature being demonstated.
38+ -- uses more of the JSaddle EDSL feature being demonstrated.
3939
4040 -- * Calling Haskell from JavaScript
41− -- | You can call back into haskell from JavaScript using 'fun' to
41+ -- | You can call back into Haskell from JavaScript using 'fun' to
4242 -- convert a Haskell function in the JSM monad into a javascript
4343 -- value.
4444
@@ -52,8 +52,8 @@ module Language.Javascript.JSaddle (
5252 -- and the server will run the Haskell code. The JSaddle parts will
5353 -- be executed by sending commands back to the browser.
5454
55− -- Although the code JavaScript code is executed in the the strict order
56− -- set out by the EDSL it done asynchonously to the Haskell code.
55+ -- Although the JavaScript code is executed in the strict order
56+ -- set out by the EDSL it is done asynchronously to the Haskell code.
5757 -- This improves the performance by reducing the number of round trips
5858 -- needed between the Haskell and JavaScript code.
5959
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.Int (Int64)
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 => Int64 -> m ()
41+removeContext cid =
42+ liftIO $ atomicModifyIORef' contexts $ \c -> (filter ((/= cid) . 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/Evaluate.hs+1−1
@@ -50,7 +50,7 @@ eval :: (ToJSString script)
5050 -> JSM JSVal
5151#ifdef ghcjs_HOST_OS
5252eval script = liftIO $ js_eval (toJSString script)
53−foreign import javascript unsafe "$r = eval($1);"
53+foreign import javascript safe "$r = eval($1);"
5454 js_eval :: JSString -> IO JSVal
5555#else
5656eval = evaluateScript . toJSString
jsaddle/src/Language/Javascript/JSaddle/Monad.hs+3−26
@@ -38,13 +38,12 @@ module Language.Javascript.JSaddle.Monad (
3838 , bracket
3939) where
4040
41−import Prelude hiding (read)
4241#ifndef ghcjs_HOST_OS
43−import Control.Monad.Trans.Reader (runReaderT, ask, ReaderT(..))
42+import Control.Monad.Trans.Reader (runReaderT, ask)
4443#endif
4544import Control.Monad.IO.Class (MonadIO(..))
46−import qualified Control.Exception as E (Exception, catch, bracket)
47−import Language.Javascript.JSaddle.Types (JSM(..), MonadJSM, liftJSM, JSContextRef(..))
45+import Control.Monad.Catch (catch, bracket)
46+import Language.Javascript.JSaddle.Types (JSM(..), MonadJSM, liftJSM, JSContextRef)
4847import Language.Javascript.JSaddle.Run (syncPoint, syncAfter, waitForAnimationFrame, nextAnimationFrame)
4948
5049-- | Gets the JavaScript context from the monad
@@ -66,25 +65,3 @@ runJSM f = liftIO . runReaderT (unJSM f)
6665-- | Alternative version of 'runJSM'
6766runJSaddle :: MonadIO m => JSContextRef -> JSM a -> m a
6867runJSaddle = flip runJSM
69−
70−
71−-- | Wrapped version of 'E.catch' that runs in a MonadIO that works
72−-- a bit better with 'JSM'
73−catch :: E.Exception e
74− => JSM b
75− -> (e -> JSM b)
76− -> JSM b
77−t `catch` c = do
78− r <- askJSM
79− liftIO (runJSM (syncAfter t) r `E.catch` \e -> runJSM (c e) r)
80−
81−-- | Wrapped version of 'E.bracket' that runs in a MonadIO that works
82−-- a bit better with 'JSM'
83−bracket :: JSM a -> (a -> JSM b) -> (a -> JSM c) -> JSM c
84−bracket aquire release f = do
85− r <- askJSM
86− liftIO $ E.bracket
87− (runJSM (syncAfter aquire) r)
88− (\x -> runJSM (syncAfter $ release x) r)
89− (\x -> runJSM (syncAfter $ f x) r)
90−
jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs+55−31
@@ -1,3 +1,6 @@
1+{-# LANGUAGE LambdaCase #-}
2+{-# LANGUAGE MagicHash #-}
3+{-# LANGUAGE UnboxedTuples #-}
14-----------------------------------------------------------------------------
25--
36-- Module : Language.Javascript.JSaddle.Native
@@ -27,7 +30,8 @@ module Language.Javascript.JSaddle.Native.Internal (
2730 , callAsFunction
2831 , callAsConstructor
2932 , newEmptyObject
30− , newCallback
33+ , newAsyncCallback
34+ , newSyncCallback
3135 , newArray
3236 , evaluateScript
3337 , deRefVal
@@ -44,7 +48,6 @@ module Language.Javascript.JSaddle.Native.Internal (
4448) where
4549
4650import Control.Monad.IO.Class (MonadIO(..))
47−import Control.Monad.Primitive (touch)
4851
4952import Data.Aeson (Value)
5053
@@ -54,32 +57,36 @@ import Language.Javascript.JSaddle.Types
5457 JSStringReceived(..), JSStringForSend(..), JSObjectForSend(..))
5558import Language.Javascript.JSaddle.Monad (askJSM)
5659import Language.Javascript.JSaddle.Run
57− (Command(..), AsyncCommand(..), Result(..), sendCommand,
60+ (Command(..), Result(..), sendCommand,
5861 sendAsyncCommand, sendLazyCommand, wrapJSVal)
62+import GHC.IORef (IORef(..), readIORef)
63+import GHC.STRef (STRef(..))
64+import GHC.IO (IO(..))
65+import GHC.Base (touch#)
5966
6067wrapJSString :: MonadIO m => JSStringReceived -> m JSString
6168wrapJSString (JSStringReceived ref) = return $ JSString ref
6269
70+touchIORef :: IORef a -> IO ()
71+touchIORef (IORef (STRef r#)) = IO $ \s -> case touch# r# s of s' -> (# s', () #)
72+
6373withJSVal :: MonadIO m => JSVal -> (JSValueForSend -> m a) -> m a
64−withJSVal v@(JSVal ref) f =
65− do result <- f (JSValueForSend ref)
66− liftIO $ touch v
74+withJSVal (JSVal ref) f = do
75+ result <- (f . JSValueForSend) =<< liftIO (readIORef ref)
76+ liftIO $ touchIORef ref
6777 return result
6878
6979withJSVals :: MonadIO m => [JSVal] -> ([JSValueForSend] -> m a) -> m a
7080withJSVals v f =
71− do result <- f (map (\(JSVal ref) -> JSValueForSend ref) v)
72− liftIO $ mapM_ touch v
81+ do result <- f =<< mapM (\(JSVal ref) -> liftIO $ JSValueForSend <$> readIORef ref) v
82+ liftIO $ mapM_ (\(JSVal ref) -> touchIORef ref) v
7383 return result
7484
7585withObject :: MonadIO m => Object -> (JSObjectForSend -> m a) -> m a
7686withObject (Object o) f = withJSVal o (f . JSObjectForSend)
7787
7888withJSString :: MonadIO m => JSString -> (JSStringForSend -> m a) -> m a
79−withJSString v@(JSString ref) f =
80− do result <- f (JSStringForSend ref)
81− liftIO $ touch v
82− return result
89+withJSString (JSString ref) f = f (JSStringForSend ref)
8390
8491setPropertyByName :: JSString -> JSVal -> Object -> JSM ()
8592setPropertyByName name val this =
@@ -136,13 +143,21 @@ newEmptyObject :: JSM Object
136143newEmptyObject = Object <$> sendLazyCommand NewEmptyObject
137144{-# INLINE newEmptyObject #-}
138145
139−newCallback :: JSCallAsFunction -> JSM Object
140−newCallback f = do
141− object <- Object <$> sendLazyCommand NewCallback
146+newAsyncCallback :: JSCallAsFunction -> JSM Object
147+newAsyncCallback f = do
148+ object <- Object <$> sendLazyCommand NewAsyncCallback
149+ add <- addCallback <$> askJSM
150+ liftIO $ add object f
151+ return object
152+{-# INLINE newAsyncCallback #-}
153+
154+newSyncCallback :: JSCallAsFunction -> JSM Object
155+newSyncCallback f = do
156+ object <- Object <$> sendLazyCommand NewSyncCallback
142157 add <- addCallback <$> askJSM
143158 liftIO $ add object f
144159 return object
145−{-# INLINE newCallback #-}
160+{-# INLINE newSyncCallback #-}
146161
147162newArray :: [JSVal] -> JSM JSVal
148163newArray xs = withJSVals xs $ \xs' -> sendLazyCommand (NewArray xs')
@@ -157,13 +172,14 @@ deRefVal value = withJSVal value $ sendCommand . DeRefVal
157172{-# INLINE deRefVal #-}
158173
159174valueToBool :: JSVal -> JSM Bool
160−valueToBool (JSVal 0) = return False -- null
161−valueToBool (JSVal 1) = return False -- undefined
162−valueToBool (JSVal 2) = return False -- false
163−valueToBool (JSVal 3) = return True -- true
164−valueToBool v = withJSVal v $ \rval -> do
165− ~(ValueToBoolResult result) <- sendCommand (ValueToBool rval)
166− return result
175+valueToBool v@(JSVal ref) = liftIO (readIORef ref) >>= \case
176+ 0 -> return False -- null
177+ 1 -> return False -- undefined
178+ 2 -> return False -- false
179+ 3 -> return True -- true
180+ _ -> withJSVal v $ \rval -> do
181+ ~(ValueToBoolResult result) <- sendCommand (ValueToBool rval)
182+ return result
167183{-# INLINE valueToBool #-}
168184
169185valueToNumber :: JSVal -> JSM Double
@@ -192,17 +208,25 @@ valueToJSONValue value = withJSVal value $ \rval -> do
192208{-# INLINE valueToJSONValue #-}
193209
194210isNull :: JSVal -> JSM Bool
195−isNull (JSVal 0) = return True
196−isNull v = withJSVal v $ \rval -> do
197− ~(IsNullResult result) <- sendCommand $ IsNull rval
198− return result
211+isNull v@(JSVal ref) = liftIO (readIORef ref) >>= \case
212+ 0 -> return True -- null
213+ 1 -> return False -- undefined
214+ 2 -> return False -- false
215+ 3 -> return False -- true
216+ _ -> withJSVal v $ \rval -> do
217+ ~(IsNullResult result) <- sendCommand $ IsNull rval
218+ return result
199219{-# INLINE isNull #-}
200220
201221isUndefined :: JSVal -> JSM Bool
202−isUndefined (JSVal 1) = return True
203−isUndefined v = withJSVal v $ \rval -> do
204− ~(IsUndefinedResult result) <- sendCommand $ IsUndefined rval
205− return result
222+isUndefined v@(JSVal ref) = liftIO (readIORef ref) >>= \case
223+ 0 -> return False -- null
224+ 1 -> return True -- undefined
225+ 2 -> return False -- false
226+ 3 -> return False -- true
227+ _ -> withJSVal v $ \rval -> do
228+ ~(IsUndefinedResult result) <- sendCommand $ IsUndefined rval
229+ return result
206230{-# INLINE isUndefined #-}
207231
208232strictEqual :: JSVal -> JSVal -> JSM Bool
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/Object.hs+51−19
@@ -66,6 +66,7 @@ module Language.Javascript.JSaddle.Object (
6666 -- * Calling Haskell From JavaScript
6767 , Function(..)
6868 , function
69+ , asyncFunction
6970 , freeFunction
7071 , fun
7172 , JSCallAsFunction
@@ -102,7 +103,7 @@ import Data.Coerce (coerce)
102103#ifdef ghcjs_HOST_OS
103104import GHCJS.Types (nullRef)
104105import GHCJS.Foreign.Callback
105− (releaseCallback, syncCallback2, OnBlocked(..), Callback)
106+ (releaseCallback, syncCallback2, asyncCallback2, OnBlocked(..), Callback)
106107import GHCJS.Marshal (ToJSVal(..))
107108import JavaScript.Array (MutableJSArray)
108109import qualified JavaScript.Array as Array (toListIO, fromListIO)
@@ -115,12 +116,13 @@ import Language.Javascript.JSaddle.Types
115116#else
116117import GHCJS.Marshal.Internal (ToJSVal(..))
117118import Language.Javascript.JSaddle.Native
118− (newCallback, callAsFunction, callAsConstructor)
119+ (newAsyncCallback, newSyncCallback, callAsFunction, callAsConstructor)
119120import Language.Javascript.JSaddle.Monad (askJSM, JSM)
120121import Language.Javascript.JSaddle.Types
121− (JSString, Object(..), SomeJSArray(..),
122− JSVal(..), JSCallAsFunction, JSContextRef(..))
122+ (JSValueForSend(..), AsyncCommand(..), JSString, Object(..),
123+ SomeJSArray(..), JSVal(..), JSCallAsFunction, JSContextRef(..))
123124import JavaScript.Object.Internal (create, listProps)
125+import Language.Javascript.JSaddle.Run (sendAsyncCommand)
124126#endif
125127import JavaScript.Array.Internal (fromListIO)
126128import Language.Javascript.JSaddle.Value (valToObject)
@@ -130,6 +132,8 @@ import Language.Javascript.JSaddle.Arguments (MakeArgs(..))
130132import Control.Monad.IO.Class (MonadIO(..))
131133import Language.Javascript.JSaddle.Properties
132134import Control.Lens (IndexPreservingGetter, to)
135+import Data.IORef (newIORef, readIORef)
136+import System.IO.Unsafe (unsafePerformIO)
133137
134138-- $setup
135139-- >>> import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -435,9 +439,16 @@ newtype Function = Function {functionObject :: Object}
435439#endif
436440
437441
442+#ifdef ghcjs_HOST_OS
443+foreign import javascript unsafe "$r = function () { $1(this, arguments); }"
444+ makeFunctionWithCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO Object
445+#endif
446+
438447-- ^ Make a JavaScript function object that wraps a Haskell function.
448+-- Calls made to the function will be synchronous where possible
449+-- (on GHCJS it uses on `syncCallback2` with `ContinueAsync`).
439450function :: JSCallAsFunction -- ^ Haskell function to call
440− -> JSM Function -- ^ Returns a JavaScript function object that will
451+ -> JSM Function -- ^ Returns a JavaScript function object that will
441452 -- call the Haskell one when it is called
442453#ifdef ghcjs_HOST_OS
443454function f = do
@@ -445,11 +456,26 @@ function f = do
445456 rargs <- Array.toListIO (coerce args)
446457 f this this rargs -- TODO pass function object through
447458 Function callback <$> makeFunctionWithCallback callback
448−foreign import javascript unsafe "$r = function () { $1(this, arguments); }"
449− makeFunctionWithCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO Object
450459#else
451460function f = do
452− object <- newCallback f
461+ object <- newSyncCallback f
462+ return $ Function object
463+#endif
464+
465+-- ^ Make a JavaScript function object that wraps a Haskell function.
466+-- Calls made to the function will be Asynchronous.
467+asyncFunction :: JSCallAsFunction -- ^ Haskell function to call
468+ -> JSM Function -- ^ Returns a JavaScript function object that will
469+ -- call the Haskell one when it is called
470+#ifdef ghcjs_HOST_OS
471+asyncFunction f = do
472+ callback <- asyncCallback2 $ \this args -> do
473+ rargs <- Array.toListIO (coerce args)
474+ f this this rargs -- TODO pass function object through
475+ Function callback <$> makeFunctionWithCallback callback
476+#else
477+asyncFunction f = do
478+ object <- newAsyncCallback f
453479 return $ Function object
454480#endif
455481
@@ -458,9 +484,16 @@ freeFunction :: Function -> JSM ()
458484freeFunction (Function callback _) = liftIO $
459485 releaseCallback callback
460486#else
461−freeFunction (Function object) = do
462− free <- freeCallback <$> askJSM
463− liftIO $ free object
487+freeFunction (Function (Object (JSVal objectRef))) = do
488+ -- By now the callback should ideally have been removed from whatever
489+ -- events it was added to.
490+ -- In case a call to the callback is still pending (perhaps just being sent
491+ -- on the JS side) we use FreeCallback to queue the callback to be freed when
492+ -- the next batch of results comes back fro JS.
493+ -- We are not using withJSVal to keep JS value "alive" because FreeCallback
494+ -- does not use the it.
495+ n <- liftIO $ readIORef objectRef
496+ sendAsyncCommand (FreeCallback (JSValueForSend n))
464497#endif
465498
466499instance ToJSVal Function where
@@ -493,13 +526,12 @@ array args = do
493526 Object . coerce <$> fromListIO rargs
494527
495528-- | JavaScript's global object
496−global :: Object
497529#ifdef ghcjs_HOST_OS
498−global = js_window
499−foreign import javascript unsafe "$r = window"
500− js_window :: Object
530+foreign import javascript unsafe "$r = globalThis"
531+ global :: Object
501532#else
502−global = Object (JSVal 4)
533+global :: Object
534+global = Object . JSVal . unsafePerformIO $ newIORef 4
503535#endif
504536
505537-- | Get a list containing the property names present on a given object
@@ -524,7 +556,7 @@ objCallAsFunction :: MakeArgs args
524556objCallAsFunction f this args = do
525557 rargs <- makeArgs args >>= liftIO . Array.fromListIO
526558 liftIO $ js_apply f this rargs
527−foreign import javascript unsafe "$r = $1.apply($2, $3)"
559+foreign import javascript safe "$r = $1.apply($2, $3)"
528560 js_apply :: Object -> Object -> MutableJSArray -> IO JSVal
529561#else
530562objCallAsFunction f this args = do
@@ -544,7 +576,7 @@ objCallAsConstructor :: MakeArgs args
544576objCallAsConstructor f args = do
545577 rargs <- makeArgs args >>= liftIO . Array.fromListIO
546578 liftIO $ js_new f rargs
547−foreign import javascript unsafe "\
579+foreign import javascript safe "\
548580 switch($2.length) {\
549581 case 0 : $r = new $1(); break;\
550582 case 1 : $r = new $1($2[0]); break;\
@@ -580,5 +612,5 @@ nullObject :: Object
580612#ifdef ghcjs_HOST_OS
581613nullObject = Object nullRef
582614#else
583−nullObject = Object (JSVal 0)
615+nullObject = Object . JSVal . unsafePerformIO $ newIORef 0
584616#endif
jsaddle/src/Language/Javascript/JSaddle/Run.hs+150−82
@@ -1,6 +1,9 @@
1+{-# LANGUAGE BangPatterns #-}
12{-# LANGUAGE CPP #-}
23{-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE MagicHash #-}
6+{-# LANGUAGE UnboxedTuples #-}
47-----------------------------------------------------------------------------
58--
69-- Module : Language.Javascript.JSaddle.Run
@@ -34,40 +37,58 @@ module Language.Javascript.JSaddle.Run (
3437) where
3538
3639#ifdef ghcjs_HOST_OS
37−import Language.Javascript.JSaddle.Types (JSM)
40+import Language.Javascript.JSaddle.Types (JSM, syncPoint, syncAfter)
3841import qualified JavaScript.Web.AnimationFrame as GHCJS
3942 (waitForAnimationFrame)
4043#else
41−import Control.Exception (throwIO)
42−import Control.Monad (void, when, forever, zipWithM_)
44+import Control.Exception (throwIO, evaluate)
45+import Control.Monad (void, when, zipWithM_)
4346import Control.Monad.IO.Class (MonadIO(..))
4447import Control.Monad.Trans.Reader (ask, runReaderT)
4548import Control.Monad.STM (atomically)
46−import Control.Concurrent (forkIO)
49+import Control.Concurrent (forkIO, myThreadId)
4750import Control.Concurrent.STM.TChan
4851 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
4952import Control.Concurrent.STM.TVar
5053 (writeTVar, readTVar, readTVarIO, modifyTVar', newTVarIO)
5154import Control.Concurrent.MVar
52− (MVar, MVar, putMVar, takeMVar, newEmptyMVar)
55+ (tryTakeMVar, MVar, putMVar, takeMVar, newMVar, newEmptyMVar, readMVar, modifyMVar)
5356
5457import System.IO.Unsafe (unsafeInterleaveIO)
5558import System.Mem.Weak (addFinalizer)
59+import System.Random
5660
61+import GHC.Base (IO(..), mkWeak#)
62+import GHC.Conc (ThreadId(..))
5763import Data.Monoid ((<>))
58−import qualified Data.Text as T (unpack)
64+import qualified Data.Text as T (unpack, pack)
5965import qualified Data.Map as M (lookup, delete, insert, empty, size)
66+import qualified Data.Set as S (empty, member, insert, delete)
6067import Data.Time.Clock (getCurrentTime,diffUTCTime)
61−import Data.IORef (newIORef, atomicWriteIORef, readIORef)
68+import Data.IORef
69+ (mkWeakIORef, newIORef, atomicWriteIORef, readIORef)
6270
6371import Language.Javascript.JSaddle.Types
64− (Command(..), AsyncCommand(..), Result(..), Results(..), JSContextRef(..), JSVal(..),
65− Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
72+ (Command(..), AsyncCommand(..), Result(..), BatchResults(..), Results(..), JSContextRef(..), JSVal(..),
73+ Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..), syncPoint, syncAfter, sendCommand)
6674import Language.Javascript.JSaddle.Exception (JSException(..))
67−-- import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
68−import Control.DeepSeq (deepseq)
75+import Control.DeepSeq (force, deepseq)
76+#if MIN_VERSION_base(4,11,0)
77+import GHC.Stats (getRTSStatsEnabled, getRTSStats, RTSStats(..), gcdetails_live_bytes, gc)
78+#else
6979import GHC.Stats (getGCStatsEnabled, getGCStats, GCStats(..))
7080#endif
81+import Data.Foldable (forM_)
82+#endif
83+
84+#ifndef ghcjs_HOST_OS
85+#if MIN_VERSION_base(4,11,0)
86+currentBytesUsed = gcdetails_live_bytes . gc
87+#else
88+getRTSStatsEnabled = getGCStatsEnabled
89+getRTSStats = getGCStats
90+#endif
91+#endif
7192
7293-- | Enable (or disable) JSaddle logging
7394enableLogging :: Bool -> JSM ()
@@ -79,27 +100,6 @@ enableLogging v = do
79100 liftIO $ f v
80101#endif
81102
82−-- | Forces execution of pending asyncronous code
83−syncPoint :: JSM ()
84−#ifdef ghcjs_HOST_OS
85−syncPoint = return ()
86−#else
87−syncPoint = do
88− SyncResult <- sendCommand Sync
89− return ()
90−#endif
91−
92−-- | Forces execution of pending asyncronous code after performing `f`
93−syncAfter :: JSM a -> JSM a
94−#ifdef ghcjs_HOST_OS
95−syncAfter = id
96−#else
97−syncAfter f = do
98− result <- f
99− syncPoint
100− return result
101−#endif
102−
103103-- | On GHCJS this is 'JavaScript.Web.AnimationFrame.waitForAnimationFrame'.
104104-- On GHC it will delay the execution of the current batch of asynchronous
105105-- command when they are sent to JavaScript. It will not delay the Haskell
@@ -125,17 +125,12 @@ nextAnimationFrame f = do
125125 syncAfter (f t)
126126
127127#ifndef ghcjs_HOST_OS
128−sendCommand :: Command -> JSM Result
129−sendCommand cmd = do
130− s <- doSendCommand <$> JSM ask
131− liftIO $ s cmd
132−
133128sendLazyCommand :: (JSValueForSend -> AsyncCommand) -> JSM JSVal
134129sendLazyCommand cmd = do
135130 nextRefTVar <- nextRef <$> JSM ask
136131 n <- liftIO . atomically $ do
137132 n <- subtract 1 <$> readTVar nextRefTVar
138− writeTVar nextRefTVar n
133+ writeTVar nextRefTVar $! n
139134 return n
140135 s <- doSendAsyncCommand <$> JSM ask
141136 liftIO $ s (cmd $ JSValueForSend n)
@@ -146,66 +141,112 @@ sendAsyncCommand cmd = do
146141 s <- doSendAsyncCommand <$> JSM ask
147142 liftIO $ s cmd
148143
149−runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), IO ())
144+runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), Results -> IO Batch, IO ())
150145runJavaScript sendBatch entryPoint = do
146+ contextId' <- randomIO
151147 startTime' <- getCurrentTime
152148 recvMVar <- newEmptyMVar
149+ lastAsyncBatch <- newEmptyMVar
153150 commandChan <- newTChanIO
154151 callbacks <- newTVarIO M.empty
155152 nextRef' <- newTVarIO 0
153+ finalizerThreads' <- newMVar S.empty
154+ animationFrameHandlers' <- newMVar []
156155 loggingEnabled <- newIORef False
156+ liveRefs' <- newMVar S.empty
157157 let ctx = JSContextRef {
158− startTime = startTime'
159− , doSendCommand = \cmd -> cmd `deepseq` do
160− result <- newEmptyMVar
161− atomically $ writeTChan commandChan (Right (cmd, result))
162− unsafeInterleaveIO $
163− takeMVar result >>= \case
164− (ThrowJSValue (JSValueReceived v)) -> throwIO $ JSException (JSVal v)
165− r -> return r
166− , doSendAsyncCommand = \cmd -> cmd `deepseq` atomically (writeTChan commandChan $ Left cmd)
167− , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar' callbacks (M.insert val cb)
168− , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar' callbacks (M.delete val)
169− , nextRef = nextRef'
170− , doEnableLogging = atomicWriteIORef loggingEnabled
171− }
172− let processResults = \case
158+ contextId = contextId'
159+ , startTime = startTime'
160+ , doSendCommand = \cmd -> cmd `deepseq` do
161+ result <- newEmptyMVar
162+ atomically $ writeTChan commandChan (Right (cmd, result))
163+ unsafeInterleaveIO $
164+ takeMVar result >>= \case
165+ (ThrowJSValue v) -> do
166+ jsval <- wrapJSVal' ctx v
167+ throwIO $ JSException jsval
168+ r -> return r
169+ , doSendAsyncCommand = \cmd -> cmd `deepseq` atomically (writeTChan commandChan $ Left cmd)
170+ , addCallback = \(Object (JSVal ioref)) cb -> do
171+ val <- readIORef ioref
172+ atomically $ modifyTVar' callbacks (M.insert val cb)
173+ , nextRef = nextRef'
174+ , doEnableLogging = atomicWriteIORef loggingEnabled
175+ , finalizerThreads = finalizerThreads'
176+ , animationFrameHandlers = animationFrameHandlers'
177+ , liveRefs = liveRefs'
178+ }
179+ processResults :: Bool -> Results -> IO ()
180+ processResults syncCallbacks = \case
173181 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
174− (Callback f this a) -> do
175− logInfo ("Call " <>)
176− f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
182+ (Callback n br (JSValueReceived fNumber) f this a) -> do
183+ putMVar recvMVar (n, br)
184+ f' <- runReaderT (unJSM $ wrapJSVal f) ctx
177185 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
178186 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
187+ logInfo (("Call " <> show fNumber <> " ") <>)
179188 (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
180189 Nothing -> liftIO $ putStrLn "Callback called after it was freed"
181− Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
182− m -> putMVar recvMVar m
190+ Just cb -> void . forkIO $ do
191+ runReaderT (unJSM $ cb f' this' args) ctx
192+ when syncCallbacks $
193+ doSendAsyncCommand ctx EndSyncBlock
194+ Duplicate nBatch nExpected -> do
195+ putStrLn $ "Error : Unexpected Duplicate. syncCallbacks=" <> show syncCallbacks <>
196+ " nBatch=" <> show nBatch <> " nExpected=" <> show nExpected
197+ void $ doSendCommand ctx Sync
198+ BatchResults n br -> putMVar recvMVar (n, br)
199+ asyncResults :: Results -> IO ()
200+ asyncResults results =
201+ void . forkIO $ processResults False results
202+ syncResults :: Results -> IO Batch
203+ syncResults results = do
204+ void . forkIO $ processResults True results
205+ readMVar lastAsyncBatch
183206 logInfo s =
184207 readIORef loggingEnabled >>= \case
185208 True -> do
186− currentBytesUsedStr <- getGCStatsEnabled >>= \case
187− True -> show . currentBytesUsed <$> getGCStats
209+ currentBytesUsedStr <- getRTSStatsEnabled >>= \case
210+ True -> show . currentBytesUsed <$> getRTSStats
188211 False -> return "??"
189212 cbCount <- M.size <$> readTVarIO callbacks
190− putStrLn . s $ "M " <> currentBytesUsedStr <> "CB " <> show cbCount
213+ putStrLn . s $ "M " <> currentBytesUsedStr <> " CB " <> show cbCount <> " "
191214 False -> return ()
192− _ <- forkIO . forever $ readBatch commandChan >>= \case
193− (batch@(Batch cmds _), resultMVars) -> do
215+ _ <- forkIO . numberForeverFromM_ 1 $ \nBatch ->
216+ readBatch nBatch commandChan >>= \case
217+ (batch@(Batch cmds _ _), resultMVars) -> do
194218 logInfo (\x -> "Sync " <> x <> show (length cmds, last cmds))
219+ _ <- tryTakeMVar lastAsyncBatch
220+ putMVar lastAsyncBatch batch
195221 sendBatch batch
196− takeMVar recvMVar >>= \case
197− Success results | length results /= length resultMVars -> error "Unexpected number of jsaddle results"
198− | otherwise -> zipWithM_ putMVar resultMVars results
199− Failure results exception -> do
222+ takeResult recvMVar nBatch >>= \case
223+ (n, _) | n /= nBatch -> error $ "Unexpected jsaddle results (expected batch " <> show nBatch <> ", got batch " <> show n <> ")"
224+ (_, Success callbacksToFree results)
225+ | length results /= length resultMVars -> error "Unexpected number of jsaddle results"
226+ | otherwise -> do
227+ zipWithM_ putMVar resultMVars results
228+ forM_ callbacksToFree $ \(JSValueReceived val) ->
229+ atomically (modifyTVar' callbacks (M.delete val))
230+ (_, Failure callbacksToFree results exception err) -> do
200231 -- The exception will only be rethrown in Haskell if/when one of the
201232 -- missing results (if any) is evaluated.
202233 putStrLn "A JavaScript exception was thrown! (may not reach Haskell code)"
234+ putStrLn err
203235 zipWithM_ putMVar resultMVars $ results <> repeat (ThrowJSValue exception)
204− _ -> error "Unexpected jsaddle results"
205− return (processResults, runReaderT (unJSM entryPoint) ctx)
236+ forM_ callbacksToFree $ \(JSValueReceived val) ->
237+ atomically (modifyTVar' callbacks (M.delete val))
238+ return (asyncResults, syncResults, runReaderT (unJSM entryPoint) ctx)
206239 where
207− readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
208− readBatch chan = do
240+ numberForeverFromM_ :: (Monad m, Enum n) => n -> (n -> m a) -> m ()
241+ numberForeverFromM_ !n f = do
242+ _ <- f n
243+ numberForeverFromM_ (succ n) f
244+ takeResult recvMVar nBatch =
245+ takeMVar recvMVar >>= \case
246+ (n, _) | n < nBatch -> takeResult recvMVar nBatch
247+ r -> return r
248+ readBatch :: Int -> TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
249+ readBatch nBatch chan = do
209250 first <- atomically $ readTChan chan -- We want at least one command to send
210251 loop first ([], [])
211252 where
@@ -216,28 +257,55 @@ runJavaScript sendBatch entryPoint = do
216257 let cmds = Right syncCmd:cmds'
217258 resultMVars = resultMVar:resultMVars'
218259 atomically (tryReadTChan chan) >>= \case
219− Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
260+ Nothing -> return (Batch (reverse cmds) False nBatch, reverse resultMVars)
220261 Just cmd -> loop cmd (cmds, resultMVars)
221262 loop (Left asyncCmd) (cmds', resultMVars) = do
222263 let cmds = Left asyncCmd:cmds'
223264 atomically (tryReadTChan chan) >>= \case
224− Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
265+ Nothing -> return (Batch (reverse cmds) False nBatch, reverse resultMVars)
225266 Just cmd -> loop cmd (cmds, resultMVars)
226267 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
227268 loopAnimation :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
228269 loopAnimation (Right (Sync, resultMVar)) (cmds, resultMVars) =
229− return (Batch (reverse (Right Sync:cmds)) True, reverse (resultMVar:resultMVars))
270+ return (Batch (reverse (Right Sync:cmds)) True nBatch, reverse (resultMVar:resultMVars))
230271 loopAnimation (Right (syncCmd, resultMVar)) (cmds, resultMVars) =
231272 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Right syncCmd:cmds, resultMVar:resultMVars)
232273 loopAnimation (Left asyncCmd) (cmds, resultMVars) =
233274 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)
234275
276+addThreadFinalizer :: ThreadId -> IO () -> IO ()
277+addThreadFinalizer t@(ThreadId t#) (IO finalizer) =
278+ IO $ \s -> case mkWeak# t# t finalizer s of { (# s1, _ #) -> (# s1, () #) }
279+
280+
235281wrapJSVal :: JSValueReceived -> JSM JSVal
236−wrapJSVal (JSValueReceived ref) = do
237− -- TODO make sure this ref has not already been wrapped (perhaps only in debug version)
238− let result = JSVal ref
239− when (ref >= 5) $ do
240− ctx <- JSM ask
241− liftIO . addFinalizer result $ doSendAsyncCommand ctx $ FreeRef $ JSValueForSend ref
242− return result
282+wrapJSVal v = do
283+ ctx <- JSM ask
284+ liftIO $ wrapJSVal' ctx v
285+
286+wrapJSVal' :: JSContextRef -> JSValueReceived -> IO JSVal
287+wrapJSVal' ctx (JSValueReceived n) = do
288+ ref <- liftIO $ newIORef n
289+ when (n >= 5 || n < 0) $
290+#ifdef JSADDLE_CHECK_WRAPJSVAL
291+ do lr <- takeMVar $ liveRefs ctx
292+ if n `S.member` lr
293+ then do
294+ putStrLn $ "JS Value Ref " <> show n <> " already wrapped"
295+ putMVar (liveRefs ctx) lr
296+ else putMVar (liveRefs ctx) =<< evaluate (S.insert n lr)
297+#endif
298+ void . mkWeakIORef ref $ do
299+ ft <- takeMVar $ finalizerThreads ctx
300+ t <- myThreadId
301+ let tname = T.pack $ show t
302+ doSendAsyncCommand ctx $ FreeRef tname $ JSValueForSend n
303+ if tname `S.member` ft
304+ then putMVar (finalizerThreads ctx) ft
305+ else do
306+ addThreadFinalizer t $ do
307+ modifyMVar (finalizerThreads ctx) $ \s -> return (S.delete tname s, ())
308+ doSendAsyncCommand ctx $ FreeRefs tname
309+ putMVar (finalizerThreads ctx) =<< evaluate (S.insert tname ft)
310+ return (JSVal ref)
243311#endif
jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+132−56
@@ -13,7 +13,6 @@
1313
1414module Language.Javascript.JSaddle.Run.Files (
1515 indexHtml
16− , jsaddleJs
1716 , initState
1817 , runBatch
1918 , ghcjsHelpers
@@ -31,25 +30,38 @@ indexHtml =
3130 \</head>\n\
3231 \<body>\n\
3332 \</body>\n\
34− \<script src=\"jsaddle.js\"></script>\n\
33+ \<script src=\"/jsaddle.js\"></script>\n\
3534 \</html>"
3635
3736initState :: ByteString
3837initState = "\
3938 \ var jsaddle_values = new Map();\n\
39+ \ var jsaddle_free = new Map();\n\
4040 \ jsaddle_values.set(0, null);\n\
4141 \ jsaddle_values.set(1, undefined);\n\
4242 \ jsaddle_values.set(2, false);\n\
4343 \ jsaddle_values.set(3, true);\n\
44− \ jsaddle_values.set(4, window);\n\
44+ \ jsaddle_values.set(4, globalThis);\n\
4545 \ var jsaddle_index = 100;\n\
46+ \ var expectedBatch = 1;\n\
47+ \ var lastResults = [0, {\"tag\": \"Success\", \"contents\": [[], []]}];\n\
48+ \ var inCallback = 0;\n\
49+ \ var asyncBatch = null;\n\
4650 \"
4751
48−runBatch :: (ByteString -> ByteString) -> ByteString
49−runBatch send = "\
52+runBatch :: (ByteString -> ByteString) -> Maybe (ByteString -> ByteString) -> ByteString
53+runBatch send sendSync = "\
54+ \ var runBatch = function(firstBatch, initialSyncDepth) {\n\
5055 \ var processBatch = function(timestamp) {\n\
51− \ var results = [];\n\
52− \ try {\n\
56+ \ var batch = firstBatch;\n\
57+ \ var callbacksToFree = [];\n\
58+ \ var results = [];\n\
59+ \ inCallback++;\n\
60+ \ try {\n\
61+ \ syncDepth = initialSyncDepth || 0;\n\
62+ \ for(;;){\n\
63+ \ if(batch[2] === expectedBatch) {\n\
64+ \ expectedBatch++;\n\
5365 \ var nCommandsLength = batch[0].length;\n\
5466 \ for (var nCommand = 0; nCommand != nCommandsLength; nCommand++) {\n\
5567 \ var cmd = batch[0][nCommand];\n\
@@ -57,7 +69,15 @@ runBatch send = "\
5769 \ var d = cmd.Left;\n\
5870 \ switch (d.tag) {\n\
5971 \ case \"FreeRef\":\n\
60− \ jsaddle_values.delete(d.contents);\n\
72+ \ var refsToFree = jsaddle_free.get(d.contents[0]) || [];\n\
73+ \ refsToFree.push(d.contents[1]);\n\
74+ \ jsaddle_free.set(d.contents[0], refsToFree);\n\
75+ \ break;\n\
76+ \ case \"FreeRefs\":\n\
77+ \ var refsToFree = jsaddle_free.get(d.contents) || [];\n\
78+ \ for(var nRef = 0; nRef != refsToFree.length; nRef++)\n\
79+ \ jsaddle_values.delete(refsToFree[nRef]);\n\
80+ \ jsaddle_free.delete(d.contents);\n\
6181 \ break;\n\
6282 \ case \"SetPropertyByName\":\n\
6383 \ jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);\n\
@@ -73,6 +93,10 @@ runBatch send = "\
7393 \ var n = d.contents[1];\n\
7494 \ jsaddle_values.set(n, d.contents[0]);\n\
7595 \ break;\n\
96+ \ case \"JSONValueToValue\":\n\
97+ \ var n = d.contents[1];\n\
98+ \ jsaddle_values.set(n, d.contents[0]);\n\
99+ \ break;\n\
76100 \ case \"GetPropertyByName\":\n\
77101 \ var n = d.contents[2];\n\
78102 \ jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);\n\
@@ -89,10 +113,12 @@ runBatch send = "\
89113 \ var n = d.contents;\n\
90114 \ jsaddle_values.set(n, {});\n\
91115 \ break;\n\
92− \ case \"NewCallback\":\n\
116+ \ case \"NewAsyncCallback\":\n\
93117 \ (function() {\n\
94118 \ var nFunction = d.contents;\n\
95− \ jsaddle_values.set(nFunction, function() {\n\
119+ \ var func = function() {\n\
120+ \ var nFunctionInFunc = ++jsaddle_index;\n\
121+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
96122 \ var nThis = ++jsaddle_index;\n\
97123 \ jsaddle_values.set(nThis, this);\n\
98124 \ var args = [];\n\
@@ -101,8 +127,41 @@ runBatch send = "\
101127 \ jsaddle_values.set(nArg, arguments[i]);\n\
102128 \ args[i] = nArg;\n\
103129 \ }\n\
104− \ " <> send "{\"tag\": \"Callback\", \"contents\": [nFunction, nThis, args]}" <> "\n\
105− \ })})();\n\
130+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
131+ \ };\n\
132+ \ jsaddle_values.set(nFunction, func);\n\
133+ \ })();\n\
134+ \ break;\n\
135+ \ case \"NewSyncCallback\":\n\
136+ \ (function() {\n\
137+ \ var nFunction = d.contents;\n\
138+ \ var func = function() {\n\
139+ \ var nFunctionInFunc = ++jsaddle_index;\n\
140+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
141+ \ var nThis = ++jsaddle_index;\n\
142+ \ jsaddle_values.set(nThis, this);\n\
143+ \ var args = [];\n\
144+ \ for (var i = 0; i != arguments.length; i++) {\n\
145+ \ var nArg = ++jsaddle_index;\n\
146+ \ jsaddle_values.set(nArg, arguments[i]);\n\
147+ \ args[i] = nArg;\n\
148+ \ }\n" <> (
149+ case sendSync of
150+ Just s ->
151+ " if(inCallback > 0) {\n\
152+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
153+ \ } else {\n\
154+ \ runBatch(" <> s "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> ", 1);\n\
155+ \ }\n"
156+ Nothing ->
157+ " " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n"
158+ ) <>
159+ " };\n\
160+ \ jsaddle_values.set(nFunction, func);\n\
161+ \ })();\n\
162+ \ break;\n\
163+ \ case \"FreeCallback\":\n\
164+ \ callbacksToFree.push(d.contents);\n\
106165 \ break;\n\
107166 \ case \"CallAsFunction\":\n\
108167 \ var n = d.contents[3];\n\
@@ -145,9 +204,15 @@ runBatch send = "\
145204 \ jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));\n\
146205 \ break;\n\
147206 \ case \"SyncWithAnimationFrame\":\n\
148− \ var n = d.contents[0];\n\
207+ \ var n = d.contents;\n\
149208 \ jsaddle_values.set(n, timestamp);\n\
150209 \ break;\n\
210+ \ case \"StartSyncBlock\":\n\
211+ \ syncDepth++;\n\
212+ \ break;\n\
213+ \ case \"EndSyncBlock\":\n\
214+ \ syncDepth--;\n\
215+ \ break;\n\
151216 \ default:\n\
152217 \ " <> send "{\"tag\": \"ProtocolError\", \"contents\": e.data}" <> "\n\
153218 \ return;\n\
@@ -182,7 +247,7 @@ runBatch send = "\
182247 \ (v === true ) ? [3, \"\"] :\n\
183248 \ (typeof v === \"number\") ? [-1, v.toString()] :\n\
184249 \ (typeof v === \"string\") ? [-2, v]\n\
185− \ : [n, \"\"];\n\
250+ \ : [-3, \"\"];\n\
186251 \ results.push({\"tag\": \"DeRefValResult\", \"contents\": c});\n\
187252 \ break;\n\
188253 \ case \"IsNull\":\n\
@@ -210,55 +275,66 @@ runBatch send = "\
210275 \ }\n\
211276 \ }\n\
212277 \ }\n\
213− \ " <> send "{\"tag\": \"Success\", \"contents\": results}" <> "\n\
214− \ }\n\
215− \ catch (err) {\n\
216− \ var n = ++jsaddle_index;\n\
217− \ jsaddle_values.set(n, err);\n\
218− \ " <> send "{\"tag\": \"Failure\", \"contents\": [results,n]}" <> "\n\
278+ \ if(syncDepth <= 0) {\n\
279+ \ lastResults = [batch[2], {\"tag\": \"Success\", \"contents\": [callbacksToFree, results]}];\n\
280+ \ " <> send "{\"tag\": \"BatchResults\", \"contents\": [lastResults[0], lastResults[1]]}" <> "\n\
281+ \ break;\n\
282+ \ } else {\n" <> (
283+ case sendSync of
284+ Just s ->
285+ " lastResults = [batch[2], {\"tag\": \"Success\", \"contents\": [callbacksToFree, results]}];\n\
286+ \ batch = " <> s "{\"tag\": \"BatchResults\", \"contents\": [lastResults[0], lastResults[1]]}" <> ";\n\
287+ \ results = [];\n\
288+ \ callbacksToFree = [];\n"
289+ Nothing ->
290+ " " <> send "{\"tag\": \"BatchResults\", \"contents\": [batch[2], {\"tag\": \"Success\", \"contents\": [callbacksToFree, results]}]}" <> "\n\
291+ \ break;\n"
292+ ) <>
293+ " }\n\
294+ \ } else {\n\
295+ \ if(syncDepth <= 0) {\n\
296+ \ break;\n\
297+ \ } else {\n" <> (
298+ case sendSync of
299+ Just s ->
300+ " if(batch[2] === expectedBatch - 1) {\n\
301+ \ batch = " <> s "{\"tag\": \"BatchResults\", \"contents\": [lastResults[0], lastResults[1]]}" <> ";\n\
302+ \ } else {\n\
303+ \ batch = " <> s "{\"tag\": \"Duplicate\", \"contents\": [batch[2], expectedBatch]}" <> ";\n\
304+ \ }\n\
305+ \ results = [];\n\
306+ \ callbacksToFree = [];\n"
307+ Nothing ->
308+ " " <> send "{\"tag\": \"Duplicate\", \"contents\": [batch[2], expectedBatch]}" <> "\n\
309+ \ break;\n"
310+ ) <>
311+ " }\n\
312+ \ }\n\
219313 \ }\n\
314+ \ }\n\
315+ \ catch (err) {\n\
316+ \ var n = ++jsaddle_index;\n\
317+ \ jsaddle_values.set(n, err);\n\
318+ \ console.log(err);\n\
319+ \ " <> send "{\"tag\": \"BatchResults\", \"contents\": [batch[2], {\"tag\": \"Failure\", \"contents\": [callbacksToFree, results, n, String(err)]}]}" <> "\n\
320+ \ }\n\
321+ \ if(inCallback == 1) {\n\
322+ \ while(asyncBatch !== null) {\n\
323+ \ var b = asyncBatch;\n\
324+ \ asyncBatch = null;\n\
325+ \ if(b[2] == expectedBatch) runBatch(b);\n\
326+ \ }\n\
327+ \ }\n\
328+ \ inCallback--;\n\
220329 \ };\n\
221− \ if(batch[1]) {\n\
330+ \ if(batch[1] && (initialSyncDepth || 0) === 0) {\n\
222331 \ window.requestAnimationFrame(processBatch);\n\
223332 \ }\n\
224333 \ else {\n\
225334 \ processBatch(window.performance ? window.performance.now() : null);\n\
226335 \ }\n\
227− \"
228−
229−-- Use this to generate this string for embedding
230−-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
231−jsaddleJs :: ByteString
232−jsaddleJs = "\
233− \if(typeof global !== \"undefined\") {\n\
234− \ global.window = global;\n\
235− \ global.WebSocket = require('ws');\n\
236− \}\n\
237− \\n\
238− \var connect = function() {\n\
239− \ var wsaddress =\n\
240− \ typeof window.location === \"undefined\"\n\
241− \ ? \"ws://localhost:3709/\"\n\
242− \ : window.location.protocol.replace('http', 'ws')+\"//\"+window.location.hostname+(window.location.port?(\":\"+window.location.port):\"\");\n\
243− \\n\
244− \ var ws = new WebSocket(wsaddress);\n\
245− \\n\
246− \ ws.onopen = function(e) {\n\
247− \ " <> initState <> "\
248− \\n\
249− \ ws.onmessage = function(e) {\n\
250− \ var batch = JSON.parse(e.data);\n\
251− \\n\
252− \ " <> runBatch (\a -> "ws.send(JSON.stringify(" <> a <> "));") <> "\
253− \ };\n\
254− \ };\n\
255− \ ws.onerror = function() {\n\
256− \ setTimeout(connect, 1000);\n\
257− \ };\n\
258− \}\n\
259− \\n\
260− \ " <> ghcjsHelpers <> "\
261− \connect();\n\
336+ \ };\n\
337+ \ runBatch(batch);\n\
262338 \"
263339
264340ghcjsHelpers :: ByteString
jsaddle/src/Language/Javascript/JSaddle/Types.hs+155−25
@@ -14,6 +14,7 @@
1414#endif
1515{-# LANGUAGE ConstraintKinds #-}
1616{-# LANGUAGE KindSignatures #-}
17+{-# LANGUAGE AllowAmbiguousTypes #-}
1718-----------------------------------------------------------------------------
1819--
1920-- Module : Language.Javascript.JSaddle.Types
@@ -57,8 +58,14 @@ module Language.Javascript.JSaddle.Types (
5758 -- * Debugging
5859 , JSadddleHasCallStack
5960
60− -- * JavaScript Context Commands
61+ -- * Sync JSM
62+ , syncPoint
63+ , syncAfter
64+
6165#ifndef ghcjs_HOST_OS
66+ , sendCommand
67+
68+ -- * JavaScript Context Commands
6269 , MutabilityType(..)
6370 , Mutable
6471 , Immutable
@@ -73,6 +80,7 @@ module Language.Javascript.JSaddle.Types (
7380 , Command(..)
7481 , Batch(..)
7582 , Result(..)
83+ , BatchResults(..)
7684 , Results(..)
7785#endif
7886) where
@@ -87,14 +95,27 @@ import GHCJS.Nullable (Nullable(..))
8795import GHCJS.Prim.Internal (JSVal(..), JSValueRef)
8896import Data.JSString.Internal.Type (JSString(..))
8997import Control.DeepSeq (NFData(..))
90−import Control.Monad.Trans.Reader (ReaderT(..))
91−import Control.Monad.Trans.State.Lazy (StateT(..))
92−import qualified Control.Monad.Trans.State.Strict as Strict
93− (StateT(..))
98+import Control.Monad.Catch (MonadThrow, MonadCatch(..), MonadMask(..))
99+import Control.Monad.Trans.Cont (ContT(..))
100+import Control.Monad.Trans.Error (Error(..), ErrorT(..))
101+import Control.Monad.Trans.Except (ExceptT(..))
102+import Control.Monad.Trans.Identity (IdentityT(..))
103+import Control.Monad.Trans.List (ListT(..))
104+import Control.Monad.Trans.Maybe (MaybeT(..))
105+import Control.Monad.Trans.Reader (ReaderT(..), ask)
106+import Control.Monad.Trans.RWS.Lazy as Lazy (RWST(..))
107+import Control.Monad.Trans.RWS.Strict as Strict (RWST(..))
108+import Control.Monad.Trans.State.Lazy as Lazy (StateT(..))
109+import Control.Monad.Trans.State.Strict as Strict (StateT(..))
110+import Control.Monad.Trans.Writer.Lazy as Lazy (WriterT(..))
111+import Control.Monad.Trans.Writer.Strict as Strict (WriterT(..))
94112import Control.Monad.Trans.Class (MonadTrans(..))
95113import Control.Monad.Fix (MonadFix)
96114import Control.Monad.Ref (MonadAtomicRef(..), MonadRef(..))
97115import Control.Concurrent.STM.TVar (TVar)
116+import Control.Concurrent.MVar (MVar)
117+import Data.Int (Int64)
118+import Data.Set (Set)
98119import Data.Text (Text)
99120import Data.Time.Clock (UTCTime(..))
100121import Data.Typeable (Typeable)
@@ -102,6 +123,8 @@ import Data.Coerce (coerce, Coercible)
102123import Data.Aeson
103124 (defaultOptions, genericToEncoding, ToJSON(..), FromJSON(..), Value)
104125import GHC.Generics (Generic)
126+import Control.Monad.IO.Unlift (MonadUnliftIO)
127+import qualified Control.Monad.Fail as Fail
105128#endif
106129
107130#if MIN_VERSION_base(4,9,0) && defined(CHECK_UNCHECKED)
@@ -118,13 +141,16 @@ import GHC.Exts (Constraint)
118141type JSContextRef = ()
119142#else
120143data JSContextRef = JSContextRef {
121− startTime :: UTCTime
122− , doSendCommand :: Command -> IO Result
123− , doSendAsyncCommand :: AsyncCommand -> IO ()
124− , addCallback :: Object -> JSCallAsFunction -> IO ()
125− , freeCallback :: Object -> IO ()
126− , nextRef :: TVar JSValueRef
127− , doEnableLogging :: Bool -> IO ()
144+ contextId :: Int64
145+ , startTime :: UTCTime
146+ , doSendCommand :: Command -> IO Result
147+ , doSendAsyncCommand :: AsyncCommand -> IO ()
148+ , addCallback :: Object -> JSCallAsFunction -> IO ()
149+ , nextRef :: TVar JSValueRef
150+ , doEnableLogging :: Bool -> IO ()
151+ , finalizerThreads :: MVar (Set Text)
152+ , animationFrameHandlers :: MVar [Double -> JSM ()]
153+ , liveRefs :: MVar (Set Int64)
128154}
129155#endif
130156
@@ -140,9 +166,56 @@ data JSContextRef = JSContextRef {
140166type JSM = IO
141167#else
142168newtype JSM a = JSM { unJSM :: ReaderT JSContextRef IO a }
143− deriving (Functor, Applicative, Monad, MonadIO, MonadFix)
169+ deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadThrow, MonadUnliftIO, Fail.MonadFail)
170+
171+instance MonadCatch JSM where
172+ t `catch` c = JSM (unJSM (syncAfter t) `catch` \e -> unJSM (c e))
173+
174+instance MonadMask JSM where
175+ mask a = JSM $ mask $ \unmask -> unJSM (a $ q unmask)
176+ where q :: (ReaderT JSContextRef IO a -> ReaderT JSContextRef IO a) -> JSM a -> JSM a
177+ q unmask (JSM b) = syncAfter . JSM $ unmask b
178+ uninterruptibleMask a =
179+ JSM $ uninterruptibleMask $ \unmask -> unJSM (a $ q unmask)
180+ where q :: (ReaderT JSContextRef IO a -> ReaderT JSContextRef IO a) -> JSM a -> JSM a
181+ q unmask (JSM b) = syncAfter . JSM $ unmask b
182+#if MIN_VERSION_exceptions(0,9,0)
183+ generalBracket acquire release use =
184+ JSM $ generalBracket
185+ (unJSM acquire)
186+ (\resource exitCase -> unJSM $ release resource exitCase)
187+ (unJSM . syncAfter . use)
188+#endif
189+
190+#endif
191+
192+-- | Forces execution of pending asyncronous code
193+syncPoint :: JSM ()
194+#ifdef ghcjs_HOST_OS
195+syncPoint = return ()
196+#else
197+syncPoint = do
198+ SyncResult <- sendCommand Sync
199+ return ()
144200#endif
145201
202+-- | Forces execution of pending asyncronous code after performing `f`
203+syncAfter :: JSM a -> JSM a
204+#ifdef ghcjs_HOST_OS
205+syncAfter = id
206+#else
207+syncAfter f = do
208+ result <- f
209+ syncPoint
210+ return result
211+#endif
212+
213+#ifndef ghcjs_HOST_OS
214+sendCommand :: Command -> JSM Result
215+sendCommand cmd = do
216+ s <- doSendCommand <$> JSM ask
217+ liftIO $ s cmd
218+#endif
146219
147220-- | Type we can give to functions that are pure when using ghcjs, but
148221-- live in JSM when using jsaddle.
@@ -191,21 +264,64 @@ type MonadJSM = MonadIO
191264#else
192265class (Applicative m, MonadIO m) => MonadJSM m where
193266 liftJSM' :: JSM a -> m a
194− {-# MINIMAL liftJSM' #-}
267+
268+ default liftJSM' :: (MonadJSM m', MonadTrans t, m ~ t m') => JSM a -> m a
269+ liftJSM' = lift . (liftJSM' :: MonadJSM m' => JSM a -> m' a)
270+ {-# INLINE liftJSM' #-}
195271
196272instance MonadJSM JSM where
197273 liftJSM' = id
198274 {-# INLINE liftJSM' #-}
199275
200−instance MonadJSM m => MonadJSM (ReaderT e m) where
276+instance (MonadJSM m) => MonadJSM (ContT r m) where
277+ liftJSM' = lift . liftJSM'
278+ {-# INLINE liftJSM' #-}
279+
280+instance (Error e, MonadJSM m) => MonadJSM (ErrorT e m) where
281+ liftJSM' = lift . liftJSM'
282+ {-# INLINE liftJSM' #-}
283+
284+instance (MonadJSM m) => MonadJSM (ExceptT e m) where
285+ liftJSM' = lift . liftJSM'
286+ {-# INLINE liftJSM' #-}
287+
288+instance (MonadJSM m) => MonadJSM (IdentityT m) where
201289 liftJSM' = lift . liftJSM'
202290 {-# INLINE liftJSM' #-}
203291
204−instance MonadJSM m => MonadJSM (StateT r m) where
292+instance (MonadJSM m) => MonadJSM (ListT m) where
205293 liftJSM' = lift . liftJSM'
206294 {-# INLINE liftJSM' #-}
207295
208−instance MonadJSM m => MonadJSM (Strict.StateT r m) where
296+instance (MonadJSM m) => MonadJSM (MaybeT m) where
297+ liftJSM' = lift . liftJSM'
298+ {-# INLINE liftJSM' #-}
299+
300+instance (MonadJSM m) => MonadJSM (ReaderT r m) where
301+ liftJSM' = lift . liftJSM'
302+ {-# INLINE liftJSM' #-}
303+
304+instance (Monoid w, MonadJSM m) => MonadJSM (Lazy.RWST r w s m) where
305+ liftJSM' = lift . liftJSM'
306+ {-# INLINE liftJSM' #-}
307+
308+instance (Monoid w, MonadJSM m) => MonadJSM (Strict.RWST r w s m) where
309+ liftJSM' = lift . liftJSM'
310+ {-# INLINE liftJSM' #-}
311+
312+instance (MonadJSM m) => MonadJSM (Lazy.StateT s m) where
313+ liftJSM' = lift . liftJSM'
314+ {-# INLINE liftJSM' #-}
315+
316+instance (MonadJSM m) => MonadJSM (Strict.StateT s m) where
317+ liftJSM' = lift . liftJSM'
318+ {-# INLINE liftJSM' #-}
319+
320+instance (Monoid w, MonadJSM m) => MonadJSM (Lazy.WriterT w m) where
321+ liftJSM' = lift . liftJSM'
322+ {-# INLINE liftJSM' #-}
323+
324+instance (Monoid w, MonadJSM m) => MonadJSM (Strict.WriterT w m) where
209325 liftJSM' = lift . liftJSM'
210326 {-# INLINE liftJSM' #-}
211327
@@ -280,7 +396,7 @@ type MutableJSArray = SomeJSArray Mutable
280396type STJSArray s = SomeJSArray (STMutable s)
281397
282398-- | See 'JavaScript.Object.Internal.Object'
283−newtype Object = Object JSVal deriving(Show, ToJSON, FromJSON)
399+newtype Object = Object JSVal
284400
285401-- | See 'GHCJS.Nullable.Nullable'
286402newtype Nullable a = Nullable a
@@ -304,7 +420,8 @@ newtype JSStringForSend = JSStringForSend Text deriving(Show, ToJSON, FromJSON,
304420instance NFData JSStringForSend
305421
306422-- | Command sent to a JavaScript context for execution asynchronously
307−data AsyncCommand = FreeRef JSValueForSend
423+data AsyncCommand = FreeRef Text JSValueForSend
424+ | FreeRefs Text
308425 | SetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
309426 | SetPropertyAtIndex JSObjectForSend Int JSValueForSend
310427 | StringToValue JSStringForSend JSValueForSend
@@ -315,11 +432,15 @@ data AsyncCommand = FreeRef JSValueForSend
315432 | CallAsFunction JSObjectForSend JSObjectForSend [JSValueForSend] JSValueForSend
316433 | CallAsConstructor JSObjectForSend [JSValueForSend] JSValueForSend
317434 | NewEmptyObject JSValueForSend
318− | NewCallback JSValueForSend
435+ | NewAsyncCallback JSValueForSend
436+ | NewSyncCallback JSValueForSend
437+ | FreeCallback JSValueForSend
319438 | NewArray [JSValueForSend] JSValueForSend
320439 | EvaluateScript JSStringForSend JSValueForSend
321440 | SyncWithAnimationFrame JSValueForSend
322− deriving (Show, Generic)
441+ | StartSyncBlock
442+ | EndSyncBlock
443+ deriving (Show, Generic)
323444
324445instance ToJSON AsyncCommand where
325446 toEncoding = genericToEncoding defaultOptions
@@ -351,7 +472,7 @@ instance FromJSON Command
351472instance NFData Command
352473
353474-- | Batch of commands that can be sent together to the JavaScript context
354−data Batch = Batch [Either AsyncCommand Command] Bool
475+data Batch = Batch [Either AsyncCommand Command] Bool Int
355476 deriving (Show, Generic)
356477
357478instance ToJSON Batch where
@@ -382,9 +503,18 @@ instance ToJSON Result where
382503
383504instance FromJSON Result
384505
385−data Results = Success [Result]
386− | Failure [Result] JSValueReceived
387− | Callback JSValueReceived JSValueReceived [JSValueReceived]
506+data BatchResults = Success [JSValueReceived] [Result]
507+ | Failure [JSValueReceived] [Result] JSValueReceived String
508+ deriving (Show, Generic)
509+
510+instance ToJSON BatchResults where
511+ toEncoding = genericToEncoding defaultOptions
512+
513+instance FromJSON BatchResults
514+
515+data Results = BatchResults Int BatchResults
516+ | Duplicate Int Int
517+ | Callback Int BatchResults JSValueReceived JSValueReceived JSValueReceived [JSValueReceived]
388518 | ProtocolError Text
389519 deriving (Show, Generic)
390520
jsaddle/src/Language/Javascript/JSaddle/Value.hs+5−3
@@ -618,15 +618,17 @@ foreign import javascript unsafe "$r = ($1 === undefined)?0:\
618618 (typeof $1===\"string\")?4:\
619619 (typeof $1===\"object\")?5:-1;" jsrefGetType :: JSVal -> Int
620620#else
621−deRefVal value = toJSVal value >>= N.deRefVal >>= \result -> return $
622− case result of
621+deRefVal value = do
622+ v <- toJSVal value
623+ result <- N.deRefVal v
624+ return $ case result of
623625 DeRefValResult 0 _ -> ValNull
624626 DeRefValResult 1 _ -> ValUndefined
625627 DeRefValResult 2 _ -> ValBool False
626628 DeRefValResult 3 _ -> ValBool True
627629 DeRefValResult (-1) s -> ValNumber (read (T.unpack s))
628630 DeRefValResult (-2) s -> ValString s
629− DeRefValResult ref _ -> ValObject (Object (JSVal ref))
631+ DeRefValResult (-3) _ -> ValObject (Object v)
630632 _ -> error "Unexpected result dereferencing JSaddle value"
631633#endif
632634
nix/hix.nixnew file+8
@@ -0,0 +1,8 @@
1+{pkgs, ...}:
2+{ modules = [({pkgs, ...}: {
3+ packages.webkit2gtk3-javascriptcore.components.library.doHaddock = false;
4+ })];
5+ shell.buildInputs = [ pkgs.nodejs ];
6+ shell.tools.cabal = {};
7+ shell.crossPlatforms = p: [ p.ghcjs ];
8+}
travis/build.sh+2−12
@@ -6,18 +6,8 @@ GHCVER=`ghc --numeric-version`
66
77JSADDLE_WARP_VERSION=`head -n2 jsaddle-warp/jsaddle-warp.cabal | tail -n1 | sed 's/[^0-9.]//g'`
88
9−if [[ -d .cabal && -d .ghc ]]; then
10− cp -a .cabal .ghc /root
11−fi
12−
139cabal update
14−cabal new-build --constraint='Cabal>=1.24.2.0' 'jsaddle-warp:lib:jsaddle-warp' 'jsaddle-warp:test:test-tool'
10+cabal new-build 'jsaddle-warp:lib:jsaddle-warp' 'jsaddle-warp:test:test-tool'
1511cd jsaddle-warp
16−GHC_PACKAGE_PATH=/opt/ghc/$GHCVER/lib/ghc-$GHCVER/package.conf.d:~/.cabal/store/ghc-$GHCVER/package.db:./dist-newstyle/packagedb/ghc-$GHCVER jsaddle_datadir=`pwd` ../dist-newstyle/build/jsaddle-warp-$JSADDLE_WARP_VERSION/build/test-tool/test-tool ../jsaddle-warp
17−
18−# update the cache
19−rm -rf .cabal
20−cp -a /root/.cabal ./
21−rm -rf .ghc
22−cp -a /root/.ghc ./
12+GHC_PACKAGE_PATH=/opt/ghc/$GHCVER/lib/ghc-$GHCVER/package.conf.d:~/.cabal/store/ghc-$GHCVER/package.db:../dist-newstyle/packagedb/ghc-$GHCVER ../dist-newstyle/build/x86_64-linux/ghc-$GHCVER/jsaddle-warp-$JSADDLE_WARP_VERSION/t/test-tool/build/test-tool/test-tool ../jsaddle
2313