on master help sign in
.travis.yml+40−22
since splitsnapshots
unifiedsplit
@@ -1,29 +1,47 @@
1−sudo: required
1+language: nix
2−services:
2+nix: 2.2.2
3− - docker
4
3
5−cache:
4+jobs:
6− directories:
5+ include:
7− - .cabal
6+ - stage: Build
8− - .ghc
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
9
24
10−env:
25+install:
11−# - CABALVER=1.24 GHCVER=7.10.3 OSVER=xenial
26+- sudo mkdir -p /etc/nix
12− - CABALVER=1.24 GHCVER=8.0.1 OSVER=xenial
27+- echo "substituters = https://cache.nixos.org https://hydra.iohk.io" | sudo tee -a /etc/nix/nix.conf > /dev/null
13− - CABALVER=1.24 GHCVER=7.10.3 OSVER=trusty
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
14− - CABALVER=1.24 GHCVER=8.0.1 OSVER=trusty
15−
16−before_install:
17− - docker build -t ghc -f travis/$OSVER.Dockerfile travis
18
29
19script:
30script:
20− - docker run -v `pwd`:/build ghc
31+- nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle.components.library
21− sh -x -c "
32+- nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle-warp.components.library
22− apt-get install -y cabal-install-$CABALVER ghc-$GHCVER &&
33+- nix-build --argstr haskellCompiler $GHCVER -A hsPkgs.jsaddle-clib.components.library
23− 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 &&
34+- |
24− ./travis/build.sh"
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+
25notifications:
44notifications:
26 irc:
45 irc:
27 channels: "irc.freenode.net#ghcjs"
46 channels: "irc.freenode.net#ghcjs"
28− skip_join: true
29− email: true
47+ email: true
README.md+34−14
@@ -13,7 +13,8 @@ import Control.Monad.IO.Class (MonadIO(..))
13import Control.Concurrent.MVar (takeMVar, putMVar, newEmptyMVar)
13import Control.Concurrent.MVar (takeMVar, putMVar, newEmptyMVar)
14import Control.Lens ((^.))
14import Control.Lens ((^.))
15import Language.Javascript.JSaddle
15import 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)
17
18
18main = run 3709 $ do
19main = run 3709 $ do
19 doc <- jsg "document"
20 doc <- jsg "document"
@@ -79,7 +80,7 @@ main = run 3708 $ do
79 appendChild newParagraph text
80 appendChild newParagraph text
80 appendChild body (Just newParagraph)
81 appendChild body (Just newParagraph)
81 return ()
82 return ()
82−
83+
83 -- Make an exit button
84 -- Make an exit button
84 exitMVar <- liftIO newEmptyMVar
85 exitMVar <- liftIO newEmptyMVar
85 Just exit <- createElement doc (Just "span")
86 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
102
103
103## How does it work on GHC
104## How does it work on GHC
104
105
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+There are a number of different JSaddle runners to choose from
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.
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.
107
119
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.
109
122
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
110−Older versions relied on WebKitGTK and JavaScriptCore, but in WekKit2 the interface JSaddle used is only available to WebKit Extensions
126+WebKitGTK. With the newer WebKit2 interface this level of access 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
127+The general advice for people migrating from WebKit1 to WebKit2 seems to be to use JavaScript.
113−to send JavaScript code from the host application, but it does not seem to provide any advantages over the WebSockets approach.
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.
114
131
115### JSVal
132### JSVal
116
133
@@ -120,8 +137,8 @@ The first five keys `[0..4]` are
120these values. Both the Haskell code and `jsaddle.js` can allocate new `JSVal` keys. The Haskell code allocates negative
137these values. Both the Haskell code and `jsaddle.js` can allocate new `JSVal` keys. The Haskell code allocates negative
121keys and `jsaddle.js` allocates positive keys to avoid clashing.
138keys and `jsaddle.js` allocates positive keys to avoid clashing.
122
139
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
140+The haskell code will not know the value 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
141+that are sort of "hey call this function and put the result in the map with this key"). This allows for the
125lazy execution of the JSaddle code.
142lazy execution of the JSaddle code.
126
143
127A finalizer is added on the Haskell code and an asynchronous `FreeJSVal` command is automatically sent to `jsaddle.js`
144A 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
129
146
130### JSStrings
147### JSStrings
131
148
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`.
133
150
134### Lazy Execution
151### Lazy Execution
135
152
@@ -161,7 +178,7 @@ force all the pending asynchronous commands to be executed.
161It 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.
178It 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.
162
179
163For the best performance you may want to write both JS FFI and JSaddle wrappers
180For 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`.
165
182
166For instance here is `getElementById` from the `ghcjs-dom-jsffi` (used by `ghcjs-dom` when compiled with GHCJS)
183For instance here is `getElementById` from the `ghcjs-dom-jsffi` (used by `ghcjs-dom` when compiled with GHCJS)
167
184
@@ -196,7 +213,10 @@ JSaddle does not support exceptions well. When compiled with GHCJS an exception
196at the point the exception is thrown.
213at the point the exception is thrown.
197
214
198When using GHC the Haskell executions will probably continue for a while before the exception is received at all by the Haskell code
215When 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.
200When it reaches the synchronous command a haskell type JSException will be thrown (this may not be the thread that initiated the
217When it reaches the synchronous command a haskell type JSException will be thrown (this may not be the thread that initiated the
201command that caused the exception). You can use `syncPoint` and `syncAfter` to force the exception to be thrown.
218command that caused the exception). You can use `syncPoint` and `syncAfter` to force the exception to be thrown.
202There are `JSM` versions of `catch` and `bracket` that also include a `syncPoint` call.
219There 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−
3packages:
1packages:
4 jsaddle/
2 jsaddle/
5 jsaddle-warp/
3 jsaddle-warp/
6 jsaddle-wkwebview/
4 jsaddle-wkwebview/
7−-- jsaddle-webkitgtk/
8 jsaddle-webkit2gtk/
5 jsaddle-webkit2gtk/
6+ jsaddle-clib/
7+
8+constraints: haskell-gi-overloading == 0.0
9
9
10−package gi-atk
10+allow-newer: websockets:random, splitmix:time, *:time, *:unix, *:process, stm:base
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
jsaddle-clib/LICENSEnew file+21
@@ -0,0 +1,21 @@
1+Copyright (c) 2010-2012 Hamish Mackenzie, Victor Nazarov, Luite Stegeman
2+
3+Permission is hereby granted, free of charge, to any person obtaining
4+a copy of this software and associated documentation files (the
5+"Software"), to deal in the Software without restriction, including
6+without limitation the rights to use, copy, modify, merge, publish,
7+distribute, sublicense, and/or sell copies of the Software, and to
8+permit persons to whom the Software is furnished to do so, subject to
9+the following conditions:
10+
11+The above copyright notice and this permission notice shall be
12+included in all copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
jsaddle-clib/Setup.lhsnew file+6
@@ -0,0 +1,6 @@
1+#!/usr/bin/runhaskell
2+> module Main where
3+> import Distribution.Simple
4+> main :: IO ()
5+> main = defaultMain
6+
jsaddle-clib/cbits/include/jsaddle.hnew file+24
@@ -0,0 +1,24 @@
1+#ifndef JSADDLE_H
2+#define JSADDLE_H
3+
4+typedef struct native_callbacks {
5+ void (* jsaddleStart) ();
6+ void (* jsaddleResult) (char *);
7+ char * (* jsaddleSyncResult) (char *);
8+ char * jsaddleJsData;
9+ char * jsaddleHtmlData;
10+} native_callbacks;
11+
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+
24+#endif
jsaddle-clib/jsaddle-clib.cabalnew file+40
@@ -0,0 +1,40 @@
1+name: jsaddle-clib
2+version: 0.9.8.0
3+cabal-version: >=1.10
4+build-type: Simple
5+license: MIT
6+license-file: LICENSE
7+maintainer: Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
8+synopsis: Interface for JavaScript that works with GHCJS and GHC
9+description:
10+ This package provides an EDSL for calling JavaScript that
11+ can be used both from GHCJS and GHC.
12+category: Web, Javascript
13+author: Hamish Mackenzie
14+
15+source-repository head
16+ type: git
17+ location: https://github.com/ghcjs/jsaddle
18+
19+library
20+ exposed-modules:
21+ Language.Javascript.JSaddle.CLib
22+ Language.Javascript.JSaddle.CLib.Internal
23+ build-depends:
24+ aeson >=0.8.0.2 && <1.6,
25+ base <5,
26+ base-compat >=0.9.0 && <0.12,
27+ bytestring >=0.10.6.0 && <0.11,
28+ jsaddle >= 0.9.0.0 && <0.10,
29+ data-default,
30+ text
31+ default-language: Haskell2010
32+ hs-source-dirs: src
33+ Include-dirs: cbits/include
34+ Includes: jsaddle.h
35+ Install-includes: jsaddle.h
36+ ghc-options: -ferror-spans -Wall
37+ if impl(ghcjs)
38+ hs-source-dirs: src-ghcjs
39+ else
40+ hs-source-dirs: src-ghc
jsaddle-clib/src-ghc/Language/Javascript/JSaddle/CLib.hsnew file+157
@@ -0,0 +1,157 @@
1+{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
2+module Language.Javascript.JSaddle.CLib
3+ ( jsaddleInit
4+ , NativeCallbacks (..)
5+ , AppCallbacks (..)
6+ , AppConfig (..)
7+ , pokeAppConfig
8+ , appConfigToAppCallbacks
9+ ) where
10+
11+import Prelude ()
12+import Prelude.Compat
13+
14+import Control.Monad (void)
15+import Control.Concurrent (forkIO)
16+
17+import Data.Aeson (encode, decode)
18+import Data.ByteString (useAsCString, packCString)
19+import Data.ByteString.Char8 (unpack)
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
24+
25+import Foreign.C.String (CString, newCString)
26+import Foreign.Ptr (FunPtr, Ptr)
27+import Foreign.Storable (poke)
28+import Foreign.Marshal.Utils (new)
29+
30+import Language.Javascript.JSaddle (JSM)
31+import Language.Javascript.JSaddle.Run (runJavaScript)
32+import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
33+
34+import Language.Javascript.JSaddle.CLib.Internal
35+
36+foreign import ccall safe "dynamic"
37+ mkCallback :: FunPtr (CString -> IO ()) -> CString -> IO ()
38+
39+foreign import ccall safe "wrapper"
40+ wrapStartCallback :: IO () -> IO (FunPtr (IO ()))
41+
42+foreign import ccall safe "wrapper"
43+ wrapMessageCallback :: (CString -> IO ()) -> IO (FunPtr (CString -> IO ()))
44+
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+
51+jsaddleInit :: JSM () -> FunPtr (CString -> IO ()) -> IO (Ptr NativeCallbacks)
52+jsaddleInit jsm evaluateJavascriptAsyncPtr = do
53+ let evaluateJavascriptAsync = mkCallback evaluateJavascriptAsyncPtr
54+ (processResult, processSyncResult, start) <- runJavaScript (\batch ->
55+ useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");")
56+ evaluateJavascriptAsync) jsm
57+ jsaddleStartPtr <- wrapStartCallback $ void $ forkIO start
58+ jsaddleResultPtr <- wrapMessageCallback $ \s -> do
59+ result <- decode . fromStrict <$> packCString s
60+ case result of
61+ Nothing -> error $ "jsaddle message decode failed: " <> show result
62+ 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
68+ jsaddleJsPtr <- newCString $ unpack $ toStrict jsaddleJs
69+ jsaddleHtmlPtr <- newCString $ unpack $ toStrict indexHtml
70+ new NativeCallbacks
71+ { _nativeCallbacks_jsaddleStart = jsaddleStartPtr
72+ , _nativeCallbacks_jsaddleResult = jsaddleResultPtr
73+ , _nativeCallbacks_jsaddleSyncResult = jsaddleSyncResultPtr
74+ , _nativeCallbacks_jsaddleJsData = jsaddleJsPtr
75+ , _nativeCallbacks_jsaddleHtmlData = jsaddleHtmlPtr
76+ }
77+
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+
137+jsaddleJs :: ByteString
138+jsaddleJs = ghcjsHelpers <> "\
139+ \runJSaddleBatch = (function() {\n\
140+ \ " <> initState <> "\n\
141+ \ return function(batch) {\n\
142+ \ " <> runBatch (\a -> "jsaddle.postMessage(JSON.stringify(" <> a <> "));")
143+ (Just (\a -> "JSON.parse(jsaddle.syncMessage(JSON.stringify(" <> a <> ")))")) <> "\
144+ \ };\n\
145+ \})();\n\
146+ \jsaddle.postReady();\n"
147+
148+indexHtml :: ByteString
149+indexHtml =
150+ "<!DOCTYPE html>\n\
151+ \<html>\n\
152+ \<head>\n\
153+ \<title>JSaddle</title>\n\
154+ \</head>\n\
155+ \<body>\n\
156+ \</body>\n\
157+ \</html>"
jsaddle-clib/src-ghcjs/Language/Javascript/JSaddle/CLib.hsnew file+7
@@ -0,0 +1,7 @@
1+module Language.Javascript.JSaddle.CLib
2+ ( run
3+ ) where
4+
5+run :: IO () -> IO ()
6+run = id
7+{-# INLINE run #-}
jsaddle-clib/src/Language/Javascript/JSaddle/CLib/Internal.hscnew file+67
@@ -0,0 +1,67 @@
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+module Language.Javascript.JSaddle.CLib.Internal where
3+
4+import Foreign
5+import Foreign.C
6+
7+#include "jsaddle.h"
8+
9+data NativeCallbacks = NativeCallbacks
10+ { _nativeCallbacks_jsaddleStart :: !(FunPtr (IO ())) -- void (jsaddleStart *)()
11+ , _nativeCallbacks_jsaddleResult :: !(FunPtr (CString -> IO ())) -- void (jsaddleResult *)(char *)
12+ , _nativeCallbacks_jsaddleSyncResult :: !(FunPtr (CString -> IO CString)) -- char * (jsaddleSyncResult *)(char *)
13+ , _nativeCallbacks_jsaddleJsData :: !CString -- char * jsaddleJsData
14+ , _nativeCallbacks_jsaddleHtmlData :: !CString -- char * jsaddleHtmlData
15+ }
16+
17+instance Storable NativeCallbacks where
18+ sizeOf _ = #{size native_callbacks}
19+ alignment _ = #{alignment native_callbacks}
20+ poke p nc = do
21+ #{poke native_callbacks, jsaddleStart} p $ _nativeCallbacks_jsaddleStart nc
22+ #{poke native_callbacks, jsaddleResult} p $ _nativeCallbacks_jsaddleResult nc
23+ #{poke native_callbacks, jsaddleSyncResult} p $ _nativeCallbacks_jsaddleSyncResult nc
24+ #{poke native_callbacks, jsaddleJsData} p $ _nativeCallbacks_jsaddleJsData nc
25+ #{poke native_callbacks, jsaddleHtmlData} p $ _nativeCallbacks_jsaddleHtmlData nc
26+ peek p = NativeCallbacks
27+ <$> #{peek native_callbacks, jsaddleStart} p
28+ <*> #{peek native_callbacks, jsaddleResult} p
29+ <*> #{peek native_callbacks, jsaddleSyncResult} p
30+ <*> #{peek native_callbacks, jsaddleJsData} p
31+ <*> #{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/jsaddle-warp.cabal+33−12
@@ -1,5 +1,5 @@
1name: jsaddle-warp
1name: jsaddle-warp
2−version: 0.8.2.0
2+version: 0.9.8.0
3cabal-version: >=1.10
3cabal-version: >=1.10
4build-type: Simple
4build-type: Simple
5license: MIT
5license: MIT
@@ -10,7 +10,7 @@ description:
10 This package provides an EDSL for calling JavaScript that
10 This package provides an EDSL for calling JavaScript that
11 can be used both from GHCJS and GHC. When using GHC
11 can be used both from GHCJS and GHC. When using GHC
12 the application is run using Warp and WebSockets to
12 the application is run using Warp and WebSockets to
13− drive a small JavaScipt helper.
13+ drive a small JavaScript helper.
14category: Web, Javascript
14category: Web, Javascript
15author: Hamish Mackenzie
15author: Hamish Mackenzie
16
16
@@ -24,19 +24,23 @@ library
24 if !impl(ghcjs -any)
24 if !impl(ghcjs -any)
25 exposed-modules:
25 exposed-modules:
26 Language.Javascript.JSaddle.WebSockets
26 Language.Javascript.JSaddle.WebSockets
27+ other-modules:
28+ Language.Javascript.JSaddle.WebSockets.Compat
27 build-depends:
29 build-depends:
28− aeson >=0.8.0.2 && <1.1,
30+ aeson >=0.8.0.2 && <1.6,
31+ bytestring >=0.10.6.0 && <0.11,
29− containers >=0.5.6.2 && <0.6,
32+ containers >=0.5.6.2 && <0.7,
33+ foreign-store >=0.2 && <0.3,
30− http-types >=0.8.6 && <0.10,
34+ http-types >=0.8.6 && <0.13,
31− jsaddle >=0.8.0.0 && <0.9,
35+ jsaddle >=0.9.4.0 && <0.10,
32− stm >=2.4.4 && <2.5,
36+ stm >=2.4.4 && <2.6,
33 text >=1.2.1.3 && <1.3,
37 text >=1.2.1.3 && <1.3,
34− time >=1.5.0.1 && <1.7,
38+ time >=1.5.0.1 && <1.11,
35 transformers >=0.4.2.0 && <0.6,
39 transformers >=0.4.2.0 && <0.6,
36 wai >=3.0.3.0 && <3.3,
40 wai >=3.0.3.0 && <3.3,
37 wai-websockets >=3.0.0.6 && <3.1,
41 wai-websockets >=3.0.0.6 && <3.1,
38− warp >=3.1.2 && <3.3,
42+ warp >=3.1.2 && <3.4,
39− websockets >=0.9.5.0 && <0.10
43+ websockets >=0.9.5.0 && <0.13
40 exposed-modules:
44 exposed-modules:
41 Language.Javascript.JSaddle.Warp
45 Language.Javascript.JSaddle.Warp
42 build-depends:
46 build-depends:
@@ -51,17 +55,34 @@ test-suite test-tool
51
55
52 build-depends:
56 build-depends:
53 QuickCheck -any,
57 QuickCheck -any,
58+ aeson -any,
54 bytestring -any,
59 bytestring -any,
60+ containers -any,
55 deepseq -any,
61 deepseq -any,
56− doctest >=0.10.1 && <0.12,
62+ doctest >=0.10.1 && <0.19,
63+ exceptions -any,
57 filepath >=1.4.0.0 && <1.5,
64 filepath >=1.4.0.0 && <1.5,
65+ foreign-store -any,
58 ghc-prim -any,
66 ghc-prim -any,
67+ http-types -any,
59 jsaddle -any,
68 jsaddle -any,
60 jsaddle-warp -any,
69 jsaddle-warp -any,
61 lens -any,
70 lens -any,
71+ network -any,
62 ref-tf -any,
72 ref-tf -any,
63 primitive -any,
73 primitive -any,
64− process >=1.2.3.0 && <1.5
74+ process >=1.2.3.0 && <1.7,
75+ random -any,
76+ stm -any,
77+ text -any,
78+ time -any,
79+ transformers -any,
80+ unliftio-core -any,
81+ wai -any,
82+ wai-websockets -any,
83+ warp -any,
84+ webdriver >=0.8.4 && <0.10,
85+ websockets -any
65 type: exitcode-stdio-1.0
86 type: exitcode-stdio-1.0
66 main-is: DocTest.hs
87 main-is: DocTest.hs
67 build-depends:
88 build-depends:
jsaddle-warp/src/Language/Javascript/JSaddle/Test.hs+13−12
@@ -17,21 +17,22 @@ module Language.Javascript.JSaddle.Test (
17 , listWindowProperties
17 , listWindowProperties
18) where
18) where
19
19
20−import Control.Applicative
21import Prelude hiding((!!), catch)
20import Prelude hiding((!!), catch)
22−import Control.Monad.Trans.Reader (runReaderT)
21+import Control.Applicative
23−import Language.Javascript.JSaddle
22+import Control.Concurrent (threadDelay, forkIO, ThreadId)
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 ((<>))
29import Control.Concurrent.MVar
23import Control.Concurrent.MVar
30 (tryTakeMVar, putMVar, takeMVar, newEmptyMVar, MVar)
24 (tryTakeMVar, putMVar, takeMVar, newEmptyMVar, MVar)
31−import System.IO.Unsafe (unsafePerformIO)
25+import Control.Lens.Getter ((^.))
32−import Control.Concurrent (threadDelay, forkIO, ThreadId)
26+import Control.Monad (forever, forM)
27+import Control.Monad.IO.Class (MonadIO(..))
28+import Control.Monad.Trans.Reader (runReaderT)
29+import Data.Monoid ((<>))
33import Data.Text (Text)
30import Data.Text (Text)
31+import qualified Data.Text as T
32+import Language.Javascript.JSaddle
34import Language.Javascript.JSaddle.Warp (run)
33import Language.Javascript.JSaddle.Warp (run)
34+import System.IO.Unsafe (unsafePerformIO)
35+import System.Timeout (timeout)
35
36
36context :: MVar JSContextRef
37context :: MVar JSContextRef
37context = unsafePerformIO newEmptyMVar
38context = unsafePerformIO newEmptyMVar
@@ -52,12 +53,12 @@ startServer =
52
53
53-- >>> testJSaddle $ ((global ^. js "console" . js "log") # ["Hello"])
54-- >>> testJSaddle $ ((global ^. js "console" . js "log") # ["Hello"])
54testJSaddle :: ToJSVal val => JSM val -> IO ()
55testJSaddle :: ToJSVal val => JSM val -> IO ()
55−testJSaddle f = do
56+testJSaddle f = timeout 10000000 (do
56 startServer
57 startServer
57 c <- takeMVar context
58 c <- takeMVar context
58 runReaderT (unJSM $ (f >>= valToText >>= liftIO . putStrLn . T.unpack)
59 runReaderT (unJSM $ (f >>= valToText >>= liftIO . putStrLn . T.unpack)
59 `catch` \ (JSException e) -> valToText e >>= liftIO . putStrLn . T.unpack) c
60 `catch` \ (JSException e) -> valToText e >>= liftIO . putStrLn . T.unpack) c
60− putMVar context c
61+ putMVar context c) >>= maybe (putStrLn "testJSaddle timed out") return
61
62
62debugLog :: String -> IO ()
63debugLog :: String -> IO ()
63debugLog _ = return ()
64debugLog _ = return ()
jsaddle-warp/src/Language/Javascript/JSaddle/Warp.hs+3−4
@@ -17,8 +17,7 @@ module Language.Javascript.JSaddle.Warp (
17 -- * Running JSM over WebSockets
17 -- * Running JSM over WebSockets
18 run
18 run
19#ifndef ghcjs_HOST_OS
19#ifndef ghcjs_HOST_OS
20− , jsaddleOr
20+ , module Language.Javascript.JSaddle.WebSockets
21− , jsaddleApp
22#endif
21#endif
23) where
22) where
24
23
@@ -29,7 +28,7 @@ import Network.WebSockets (defaultConnectionOptions)
29
28
30import Language.Javascript.JSaddle.Types (JSM)
29import Language.Javascript.JSaddle.Types (JSM)
31import Language.Javascript.JSaddle.Run (syncPoint)
30import Language.Javascript.JSaddle.Run (syncPoint)
32−import Language.Javascript.JSaddle.WebSockets (jsaddleOr, jsaddleApp)
31+import Language.Javascript.JSaddle.WebSockets
33#endif
32#endif
34
33
35-- | Run the given 'JSM' action as the main entry point. Either directly
34-- | Run the given 'JSM' action as the main entry point. Either directly
@@ -40,6 +39,6 @@ run _port = id
40#else
39#else
41run :: Int -> JSM () -> IO ()
40run :: Int -> JSM () -> IO ()
42run port f =
41run port f =
43− runSettings (setPort port (setTimeout 3600 defaultSettings)) $
42+ runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
44 jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
43 jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
45#endif
44#endif
jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets.hs+287−40
@@ -1,5 +1,7 @@
1{-# LANGUAGE LambdaCase #-}
1{-# LANGUAGE LambdaCase #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+{-# LANGUAGE RecursiveDo #-}
3-----------------------------------------------------------------------------
5-----------------------------------------------------------------------------
4--
6--
5-- Module : Language.Javascript.JSaddle.WebSockets
7-- Module : Language.Javascript.JSaddle.WebSockets
@@ -16,57 +18,302 @@ module Language.Javascript.JSaddle.WebSockets (
16 -- * Running JSM over WebSockets
18 -- * Running JSM over WebSockets
17 jsaddleOr
19 jsaddleOr
18 , jsaddleApp
20 , jsaddleApp
21+ , jsaddleWithAppOr
22+ , jsaddleAppWithJs
23+ , jsaddleAppWithJsOr
24+ , jsaddleAppPartial
25+ , jsaddleJs
26+ , jsaddleJs'
27+ , debug
28+ , debugOr
29+ , debugWrapper
19) where
30) where
20
31
21−import Control.Monad (forever)
32+import Control.Monad (when, void, forever)
22−import Control.Concurrent (forkIO)
33+import Control.Concurrent (killThread, forkIO, threadDelay)
34+import Control.Exception (handle, AsyncException, throwIO, fromException, finally)
23
35
24import Data.Monoid ((<>))
36import Data.Monoid ((<>))
25import Data.Aeson (encode, decode)
37import Data.Aeson (encode, decode)
26
38
27−import Network.Wai (Application)
39+import Network.Wai
40+ (Middleware, lazyRequestBody, Application, Request, Response,
41+ ResponseReceived)
28import Network.WebSockets
42import Network.WebSockets
29− (ConnectionOptions(..), sendTextData, receiveDataMessage,
43+ (defaultConnectionOptions, ConnectionOptions(..), sendTextData,
30− acceptRequest, ServerApp)
44+ receiveDataMessage, acceptRequest, ServerApp, sendPing)
31−import qualified Network.WebSockets as WS (DataMessage(..))
32import Network.Wai.Handler.WebSockets (websocketsOr)
45import Network.Wai.Handler.WebSockets (websocketsOr)
46+import Network.HTTP.Types (Status(..))
33
47
34−import Language.Javascript.JSaddle.Types (JSM(..))
48+import Language.Javascript.JSaddle.Types (JSM(..), JSContextRef(..))
35import qualified Network.Wai as W
49import qualified Network.Wai as W
36− (responseLBS, requestMethod, Application, pathInfo)
50+ (responseLBS, requestMethod, pathInfo, modifyResponse, responseStatus)
37−import Data.Text (Text)
51+import qualified Data.Text as T (pack)
38import qualified Network.HTTP.Types as H
52import qualified Network.HTTP.Types as H
39− (status403, status200, status405)
53+ (status403, status200)
40−import Language.Javascript.JSaddle.Run (runJavaScript)
54+import Language.Javascript.JSaddle.Run (syncPoint, runJavaScript)
41−import Language.Javascript.JSaddle.Run.Files (indexHtml, jsaddleJs)
55+import Language.Javascript.JSaddle.Run.Files (indexHtml, runBatch, ghcjsHelpers, initState)
42−
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+
43−jsaddleOr :: ConnectionOptions -> JSM () -> Application -> Application
77+jsaddleOr :: ConnectionOptions -> JSM () -> Application -> IO Application
44−jsaddleOr opts entryPoint = websocketsOr opts wsApp
78+jsaddleOr opts entryPoint otherApp = do
45− where
79+ syncHandlers <- newIORef M.empty
80+ asyncHandlers <- newIORef M.empty
46− wsApp :: ServerApp
81+ let wsApp :: ServerApp
47− wsApp pending_conn = do
82+ wsApp pending_conn = do
48− conn <- acceptRequest pending_conn
83+ conn <- acceptRequest pending_conn
84+ initMsg <- receiveDataMessage conn
85+ case getTextMessageByteString initMsg of
86+ Just "" -> do
49− (processResult, start) <- runJavaScript (sendTextData conn . encode) entryPoint
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 ->
50− _ <- forkIO . forever $
99+ forever $
51− receiveDataMessage conn >>= \case
100+ receiveDataMessage conn >>= \msg -> case getTextMessageByteString msg of
52− (WS.Text t) ->
101+ Just t ->
53− case decode t of
102+ case decode t of
54− Nothing -> error $ "jsaddle WebSocket decode failed : " <> show t
103+ Nothing -> error $ "jsaddle Results decode failed : " <> show t
55− Just r -> processResult r
104+ Just r -> processResult r
105+ _ -> error "jsaddle WebSocket unexpected binary data"
56 _ -> error "jsaddle WebSocket unexpected binary data"
106 _ -> error "jsaddle WebSocket unexpected binary data"
57− start
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+
58
140
59jsaddleApp :: Application
141jsaddleApp :: Application
60−jsaddleApp req = jsaddleAppPieces (W.pathInfo req) req
142+jsaddleApp = jsaddleAppWithJs $ jsaddleJs False
61−
143+
62−jsaddleAppPieces :: [Text] -> W.Application
144+jsaddleAppWithJs :: ByteString -> Application
63−jsaddleAppPieces _ req sendResponse
145+jsaddleAppWithJs js req sendResponse =
64− | W.requestMethod req `notElem` ["GET", "HEAD"] = sendResponse $ W.responseLBS
146+ jsaddleAppWithJsOr js
65− H.status405
66− [("Content-Type", "text/plain")]
147+ (\_ _ -> sendResponse $ W.responseLBS H.status403 [("Content-Type", "text/plain")] "Forbidden")
67− "Only GET or HEAD is supported"
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
68−jsaddleAppPieces [] _req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
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
69−jsaddleAppPieces ["jsaddle.js"] _req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] jsaddleJs
169+ ("GET", ["jsaddle.js"]) -> Just $ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] js
70−jsaddleAppPieces _rawPieces _req sendResponse = sendResponse $ W.responseLBS H.status403
170+ _ -> Nothing
71− [ ("Content-Type", "text/plain")
171+
72− ] "Forbidden"
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+84−68
@@ -11,15 +11,17 @@ import System.IO (stderr, BufferMode(..), stdout, hSetBuffering)
11import System.FilePath ((</>))
11import System.FilePath ((</>))
12import System.Exit (exitFailure, exitWith, ExitCode(..))
12import System.Exit (exitFailure, exitWith, ExitCode(..))
13import System.Process (readProcess, system)
13import System.Process (readProcess, system)
14−import Control.Concurrent (forkIO)
14+import Control.Concurrent
15+ (takeMVar, newEmptyMVar, forkIO, threadDelay, putMVar)
15import Control.Monad (void)
16import Control.Monad (void)
17+import Control.Monad.IO.Class (MonadIO(..))
16import Data.Monoid ((<>))
18import Data.Monoid ((<>))
17import System.Environment (getArgs)
19import System.Environment (getArgs)
18−import Language.Javascript.JSaddle.Run.Files (jsaddleJs)
20+import Test.WebDriver (runSession, defaultConfig, openPage, closeSession)
19−import qualified Data.ByteString.Lazy.Char8 as BS (unpack)
20
21
21main :: IO ()
22main :: IO ()
22main = do
23main = do
24+ putStrLn "Testing JSaddle"
23 jsaddlePath <- getArgs >>= \case
25 jsaddlePath <- getArgs >>= \case
24 [arg] -> return arg
26 [arg] -> return arg
25 _ -> do
27 _ -> do
@@ -27,70 +29,84 @@ main = do
27 exitFailure
29 exitFailure
28 hSetBuffering stdout LineBuffering
30 hSetBuffering stdout LineBuffering
29 hSetBuffering stderr LineBuffering
31 hSetBuffering stderr LineBuffering
32+ putStrLn "Checking for phantomjs"
30− node <- system "nodejs --version" >>= \case
33+ node <- system "phantomjs --version" >>= \case
31− ExitSuccess -> return "nodejs"
34+ ExitSuccess -> return ()
32− _ -> return "node"
33− system (node <> " --version") >>= \case
34− ExitSuccess -> putStrLn "Node.js found"
35 e -> do
35 e -> do
36− putStrLn "Node.js not found"
36+ putStrLn "phantomjs not found"
37 exitWith e
37 exitWith e
38+ putStrLn "Starting phantomjs"
38− forkIO . void $ readProcess node ["--harmony"] (BS.unpack jsaddleJs) >>= putStr
39+ forkIO . void $ readProcess "phantomjs" ["--webdriver=4444"] "" >>= putStr
40+ threadDelay 5000000
41+ putStrLn "Running Tests"
42+ done <- newEmptyMVar
43+ forkIO $ do
39− doctest [
44+ liftIO $ doctest [
40− "-hide-all-packages",
45+ "-hide-all-packages",
41− "-package=base-" ++ VERSION_base,
46+ "-package=base-" ++ VERSION_base,
42− "-package=lens-" ++ VERSION_lens,
47+ "-package=lens-" ++ VERSION_lens,
43− "-package=text-" ++ VERSION_text,
48+ "-package=text-" ++ VERSION_text,
44− "-package=bytestring-" ++ VERSION_bytestring,
49+ "-package=bytestring-" ++ VERSION_bytestring,
45− "-package=transformers-" ++ VERSION_transformers,
50+ "-package=transformers-" ++ VERSION_transformers,
46− "-package=websockets-" ++ VERSION_websockets,
51+ "-package=websockets-" ++ VERSION_websockets,
47− "-package=primitive-" ++ VERSION_primitive,
52+ "-package=primitive-" ++ VERSION_primitive,
48− "-package=aeson-" ++ VERSION_aeson,
53+ "-package=aeson-" ++ VERSION_aeson,
49− "-package=websockets-" ++ VERSION_websockets,
54+ "-package=websockets-" ++ VERSION_websockets,
50− "-package=wai-" ++ VERSION_wai,
55+ "-package=wai-" ++ VERSION_wai,
51− "-package=wai-websockets-" ++ VERSION_wai_websockets,
56+ "-package=wai-websockets-" ++ VERSION_wai_websockets,
52− "-package=warp-" ++ VERSION_warp,
57+ "-package=warp-" ++ VERSION_warp,
53− "-package=http-types-" ++ VERSION_http_types,
58+ "-package=http-types-" ++ VERSION_http_types,
54− "-package=stm-" ++ VERSION_stm,
59+ "-package=stm-" ++ VERSION_stm,
55− "-package=time-" ++ VERSION_time,
60+ "-package=time-" ++ VERSION_time,
56− "-package=containers-" ++ VERSION_containers,
61+ "-package=containers-" ++ VERSION_containers,
57− "-package=process-" ++ VERSION_process,
62+ "-package=process-" ++ VERSION_process,
58− "-package=filepath-" ++ VERSION_filepath,
63+ "-package=filepath-" ++ VERSION_filepath,
59− "-package=ref-tf-" ++ VERSION_ref_tf,
64+ "-package=ref-tf-" ++ VERSION_ref_tf,
60− "-package=deepseq-" ++ VERSION_deepseq,
65+ "-package=deepseq-" ++ VERSION_deepseq,
61− "-package=ghc-prim-" ++ VERSION_ghc_prim,
66+ "-package=ghc-prim-" ++ VERSION_ghc_prim,
62− "-i" <> jsaddlePath </> "jsaddle-warp/src",
67+ "-package=exceptions-" ++ VERSION_exceptions,
63− "-i" <> jsaddlePath </> "jsaddle-warp/src-ghc",
68+ "-package=unliftio-core-" ++ VERSION_unliftio_core,
69+ "-package=random-" ++ VERSION_random,
70+ "-package=foreign-store-" ++ VERSION_foreign_store,
71+ "-i" <> "src",
72+ "-i" <> "src-ghc",
64− jsaddlePath </> "jsaddle-warp/src/Language/Javascript/JSaddle/Test.hs",
73+ "src/Language/Javascript/JSaddle/Test.hs",
65− "-i" <> jsaddlePath </> "jsaddle",
74+ "-i" <> jsaddlePath,
66− jsaddlePath </> "jsaddle/src-ghc/Data/JSString/Text.hs",
75+ jsaddlePath </> "src-ghc/Data/JSString/Text.hs",
67− jsaddlePath </> "jsaddle/src-ghc/Data/JSString/Internal/Type.hs",
76+ jsaddlePath </> "src-ghc/Data/JSString/Internal/Type.hs",
68− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Foreign.hs",
77+ jsaddlePath </> "src-ghc/GHCJS/Foreign.hs",
69− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Foreign/Internal.hs",
78+ jsaddlePath </> "src-ghc/GHCJS/Foreign/Internal.hs",
70− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Internal/Types.hs",
79+ jsaddlePath </> "src-ghc/GHCJS/Internal/Types.hs",
71− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Marshal.hs",
80+ jsaddlePath </> "src-ghc/GHCJS/Marshal.hs",
72− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Marshal/Pure.hs",
81+ jsaddlePath </> "src-ghc/GHCJS/Marshal/Pure.hs",
73− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Marshal/Internal.hs",
82+ jsaddlePath </> "src-ghc/GHCJS/Marshal/Internal.hs",
74− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Prim.hs",
83+ jsaddlePath </> "src-ghc/GHCJS/Prim.hs",
75− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Prim/Internal.hs",
84+ jsaddlePath </> "src-ghc/GHCJS/Prim/Internal.hs",
76− jsaddlePath </> "jsaddle/src-ghc/GHCJS/Types.hs",
85+ jsaddlePath </> "src-ghc/GHCJS/Types.hs",
77− jsaddlePath </> "jsaddle/src-ghc/JavaScript/Array.hs",
86+ jsaddlePath </> "src-ghc/JavaScript/Array.hs",
78− jsaddlePath </> "jsaddle/src-ghc/JavaScript/Array/Internal.hs",
87+ jsaddlePath </> "src-ghc/JavaScript/Array/Internal.hs",
79− jsaddlePath </> "jsaddle/src-ghc/JavaScript/Object/Internal.hs",
88+ jsaddlePath </> "src-ghc/JavaScript/Object/Internal.hs",
80− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle.hs",
89+ jsaddlePath </> "src/Language/Javascript/JSaddle.hs",
81− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Arguments.hs",
90+ jsaddlePath </> "src/Language/Javascript/JSaddle/Arguments.hs",
82− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Classes.hs",
91+ jsaddlePath </> "src/Language/Javascript/JSaddle/Classes.hs",
83− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Classes/Internal.hs",
92+ jsaddlePath </> "src/Language/Javascript/JSaddle/Classes/Internal.hs",
93+ jsaddlePath </> "src/Language/Javascript/JSaddle/Debug.hs",
84− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Evaluate.hs",
94+ jsaddlePath </> "src/Language/Javascript/JSaddle/Evaluate.hs",
85− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Exception.hs",
95+ jsaddlePath </> "src/Language/Javascript/JSaddle/Exception.hs",
86− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Monad.hs",
96+ jsaddlePath </> "src/Language/Javascript/JSaddle/Monad.hs",
87− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Marshal/String.hs",
97+ jsaddlePath </> "src/Language/Javascript/JSaddle/Marshal/String.hs",
88− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Native.hs",
98+ jsaddlePath </> "src/Language/Javascript/JSaddle/Native.hs",
89− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Native/Internal.hs",
99+ jsaddlePath </> "src/Language/Javascript/JSaddle/Native/Internal.hs",
90− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Object.hs",
100+ jsaddlePath </> "src/Language/Javascript/JSaddle/Object.hs",
91− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Properties.hs",
101+ jsaddlePath </> "src/Language/Javascript/JSaddle/Properties.hs",
92− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Run.hs",
102+ jsaddlePath </> "src/Language/Javascript/JSaddle/Run.hs",
93− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs",
103+ jsaddlePath </> "src/Language/Javascript/JSaddle/Run/Files.hs",
94− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/String.hs",
104+ jsaddlePath </> "src/Language/Javascript/JSaddle/String.hs",
95− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Types.hs",
105+ jsaddlePath </> "src/Language/Javascript/JSaddle/Types.hs",
96− jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Value.hs" ]
106+ 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
112+ closeSession
jsaddle-webkit2gtk/jsaddle-webkit2gtk.cabal+14−11
@@ -1,5 +1,5 @@
1name: jsaddle-webkit2gtk
1name: jsaddle-webkit2gtk
2−version: 0.8.2.0
2+version: 0.9.8.0
3cabal-version: >=1.10
3cabal-version: >=1.10
4build-type: Simple
4build-type: Simple
5license: MIT
5license: MIT
@@ -26,20 +26,23 @@ library
26 base <5
26 base <5
27 if !impl(ghcjs -any)
27 if !impl(ghcjs -any)
28 build-depends:
28 build-depends:
29− aeson >=0.8.0.2 && <1.1,
29+ aeson >=0.8.0.2 && <1.6,
30 base <5,
30 base <5,
31 bytestring >=0.10.6.0 && <0.11,
31 bytestring >=0.10.6.0 && <0.11,
32− directory >=1.0.0.2 && <1.3,
32+ directory >=1.0.0.2 && <1.4,
33− gi-glib >=2.0.6 && <2.1,
33+ gi-glib >=2.0.14 && <2.1,
34− gi-gio >=2.0.6 && <2.1,
34+ gi-gio >=2.0.14 && <2.1,
35− gi-gtk >=3.0.6 && <3.1,
35+ gi-gtk >=3.0.17 && <3.1,
36− gi-webkit2 >=4.0.6 && <4.1,
36+ gi-webkit2 >=4.0.14 && <4.1,
37− gi-javascriptcore >=4.0.6 && <4.1,
37+ gi-javascriptcore >=4.0.14 && <4.1,
38− haskell-gi-base >=0.20 && <0.21,
38+ haskell-gi-base >=0.20 && <0.26,
39+ haskell-gi-overloading >=0.0 && < 2.0,
39− jsaddle >=0.8.0.0 && <0.9,
40+ jsaddle >=0.9.4.0 && <0.10,
40 text >=1.2.1.3 && <1.3,
41 text >=1.2.1.3 && <1.3,
41− unix >=2.3.1.0 && <2.8,
42 webkit2gtk3-javascriptcore >=0.14.0.0 && <0.15
42 webkit2gtk3-javascriptcore >=0.14.0.0 && <0.15
43+ if !os(windows)
44+ build-depends:
45+ unix >=2.3.1.0 && <2.8
43 default-language: Haskell2010
46 default-language: Haskell2010
44 hs-source-dirs: src
47 hs-source-dirs: src
45 ghc-options: -ferror-spans -Wall
48 ghc-options: -ferror-spans -Wall
jsaddle-webkit2gtk/src/Language/Javascript/JSaddle/WebKitGTK.hs+50−46
@@ -34,87 +34,75 @@ import Control.Monad (void)
34import Control.Monad.IO.Class (MonadIO(..), MonadIO)
34import Control.Monad.IO.Class (MonadIO(..), MonadIO)
35import Control.Concurrent (forkIO, yield)
35import Control.Concurrent (forkIO, yield)
36
36
37+#ifndef mingw32_HOST_OS
37import System.Posix.Signals (installHandler, keyboardSignal, Handler(Catch))
38import System.Posix.Signals (installHandler, keyboardSignal, Handler(Catch))
39+#endif
38import System.Directory (getCurrentDirectory)
40import System.Directory (getCurrentDirectory)
39
41
40import Data.Monoid ((<>))
42import Data.Monoid ((<>))
41import Data.ByteString.Lazy (toStrict, fromStrict)
43import Data.ByteString.Lazy (toStrict, fromStrict)
42import qualified Data.ByteString.Lazy as LB (ByteString)
44import qualified Data.ByteString.Lazy as LB (ByteString)
43import Data.Aeson (encode, decode)
45import Data.Aeson (encode, decode)
44−import Data.Text (Text)
45import qualified Data.Text as T (pack)
46import qualified Data.Text as T (pack)
46−import Data.Text.Foreign (fromPtr)
47import Data.Text.Encoding (decodeUtf8, encodeUtf8)
47import Data.Text.Encoding (decodeUtf8, encodeUtf8)
48
48
49−import Foreign.Ptr (castPtr, nullPtr)
50−
51import Data.GI.Base.BasicTypes (GObject)
49import Data.GI.Base.BasicTypes (GObject)
52import Data.GI.Base.Signals
50import Data.GI.Base.Signals
53 (connectSignalFunPtr, SignalConnectMode(..), SignalConnectMode,
51 (connectSignalFunPtr, SignalConnectMode(..), SignalConnectMode,
54 SignalHandlerId)
52 SignalHandlerId)
55−import Data.GI.Base.ManagedPtr (withManagedPtr)
56
53
57import GI.GLib (timeoutAdd, idleAdd, pattern PRIORITY_HIGH, pattern PRIORITY_DEFAULT)
54import GI.GLib (timeoutAdd, idleAdd, pattern PRIORITY_HIGH, pattern PRIORITY_DEFAULT)
58import qualified GI.Gtk as Gtk (main, init)
55import qualified GI.Gtk as Gtk (main, init)
59import GI.Gtk
56import GI.Gtk
60 (windowSetPosition, windowSetDefaultSize, windowNew,
57 (windowSetPosition, windowSetDefaultSize, windowNew,
61− scrolledWindowNew, noAdjustment, containerAdd,
58+ scrolledWindowNew, Adjustment, containerAdd,
62 WindowType(..), WindowPosition(..), widgetDestroy,
59 WindowType(..), WindowPosition(..), widgetDestroy,
63 widgetGetToplevel, widgetShowAll, onWidgetDestroy,
60 widgetGetToplevel, widgetShowAll, onWidgetDestroy,
64 mainQuit)
61 mainQuit)
65−import GI.Gio (noCancellable)
62+import GI.Gio (Cancellable)
66−import GI.JavaScriptCore (Value(..), GlobalContext(..))
63+import GI.JavaScriptCore (valueToString)
67import GI.WebKit2
64import GI.WebKit2
65+ (scriptDialogPromptSetText, scriptDialogPromptGetDefaultText,
66+ scriptDialogGetMessage, scriptDialogGetDialogType,
67+ onWebViewScriptDialog, WebView,
68− (WebView, setSettingsEnableWriteConsoleMessagesToStdout,
68+ setSettingsEnableWriteConsoleMessagesToStdout,
69− setSettingsEnableJavascript,
69+ setSettingsEnableJavascript, webViewNewWithUserContentManager,
70− webViewNewWithUserContentManager, userContentManagerNew,
70+ userContentManagerNew,
71 userContentManagerRegisterScriptMessageHandler,
71 userContentManagerRegisterScriptMessageHandler,
72− javascriptResultGetValue, javascriptResultGetGlobalContext,
72+ javascriptResultGetJsValue,
73 webViewGetUserContentManager,
73 webViewGetUserContentManager,
74 mk_UserContentManagerScriptMessageReceivedCallback,
74 mk_UserContentManagerScriptMessageReceivedCallback,
75 wrap_UserContentManagerScriptMessageReceivedCallback,
75 wrap_UserContentManagerScriptMessageReceivedCallback,
76 webViewRunJavascript, LoadEvent(..),
76 webViewRunJavascript, LoadEvent(..),
77 UserContentManagerScriptMessageReceivedCallback, webViewLoadHtml,
77 UserContentManagerScriptMessageReceivedCallback, webViewLoadHtml,
78 onWebViewLoadChanged, setSettingsEnableDeveloperExtras,
78 onWebViewLoadChanged, setSettingsEnableDeveloperExtras,
79− webViewSetSettings, webViewGetSettings)
79+ webViewSetSettings, webViewGetSettings, ScriptDialogType(..))
80−
81−import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
82− (JSValueRef, JSContextRef)
83−import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
84− (jsstringgetcharactersptr, jsstringgetlength, jsstringrelease)
85−import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
86− (jsvaluetostringcopy)
87
80
88−import Language.Javascript.JSaddle (JSM, Results)
81+import Language.Javascript.JSaddle (JSM, Results, Batch)
89import Language.Javascript.JSaddle.Run (runJavaScript)
82import Language.Javascript.JSaddle.Run (runJavaScript)
90import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
83import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
91
84
85+noAdjustment :: Maybe Adjustment
86+noAdjustment = Nothing
87+
88+noCancellable :: Maybe Cancellable
89+noCancellable = Nothing
90+
92quitWebView :: WebView -> IO ()
91quitWebView :: WebView -> IO ()
93quitWebView wv = postGUIAsync $ do w <- widgetGetToplevel wv --TODO: Shouldn't this be postGUISync?
92quitWebView wv = postGUIAsync $ do w <- widgetGetToplevel wv --TODO: Shouldn't this be postGUISync?
94 widgetDestroy w
93 widgetDestroy w
95
94
96installQuitHandler :: WebView -> IO ()
95installQuitHandler :: WebView -> IO ()
96+#ifdef mingw32_HOST_OS
97+installQuitHandler wv = return () -- TODO: Maybe figure something out here for Windows users.
98+#else
97installQuitHandler wv = void $ installHandler keyboardSignal (Catch (quitWebView wv)) Nothing
99installQuitHandler wv = void $ installHandler keyboardSignal (Catch (quitWebView wv)) Nothing
100+#endif
98
101
99postGUIAsync :: IO () -> IO ()
102postGUIAsync :: IO () -> IO ()
100postGUIAsync action =
103postGUIAsync action =
101 void . idleAdd PRIORITY_DEFAULT $ action >> return False
104 void . idleAdd PRIORITY_DEFAULT $ action >> return False
102
105
103−withJSContextRef :: GlobalContext -> (JSContextRef -> IO a) -> IO a
104−withJSContextRef (GlobalContext ctx) f = withManagedPtr ctx (f . castPtr)
105−
106−withJSValueRef :: Value -> (JSValueRef -> IO a) -> IO a
107−withJSValueRef (Value ptr) f = withManagedPtr ptr (f . castPtr)
108−
109−valueToText :: JSContextRef -> JSValueRef -> IO Text
110−valueToText ctxRef s = do
111− jsstring <- jsvaluetostringcopy ctxRef s nullPtr
112− l <- jsstringgetlength jsstring
113− p <- jsstringgetcharactersptr jsstring
114− result <- fromPtr (castPtr p) (fromIntegral l)
115− jsstringrelease jsstring
116− return result
117−
118run :: JSM () -> IO ()
106run :: JSM () -> IO ()
119run main = do
107run main = do
120 _ <- Gtk.init Nothing
108 _ <- Gtk.init Nothing
@@ -139,17 +127,17 @@ run main = do
139 void . onWebViewLoadChanged webView $ \case
127 void . onWebViewLoadChanged webView $ \case
140 LoadEventFinished -> runInWebView main webView
128 LoadEventFinished -> runInWebView main webView
141 _ -> return ()
129 _ -> return ()
142− webViewLoadHtml webView "" . Just $ "file://" <> T.pack pwd <> "/"
130+ webViewLoadHtml webView "" . Just $ "file://" <> T.pack pwd <> "/index.html"
143 installQuitHandler webView
131 installQuitHandler webView
144 Gtk.main
132 Gtk.main
145
133
146runInWebView :: JSM () -> WebView -> IO ()
134runInWebView :: JSM () -> WebView -> IO ()
147runInWebView f webView = do
135runInWebView f webView = do
148− (processResults, start) <- runJavaScript (\batch -> postGUIAsync $
136+ (processResults, syncResults, start) <- runJavaScript (\batch -> postGUIAsync $
149 webViewRunJavascript webView (decodeUtf8 . toStrict $ "runJSaddleBatch(" <> encode batch <> ");") noCancellable Nothing)
137 webViewRunJavascript webView (decodeUtf8 . toStrict $ "runJSaddleBatch(" <> encode batch <> ");") noCancellable Nothing)
150 f
138 f
151
139
152− addJSaddleHandler webView processResults
140+ addJSaddleHandler webView processResults syncResults
153 webViewRunJavascript webView (decodeUtf8 $ toStrict jsaddleJs) noCancellable . Just $
141 webViewRunJavascript webView (decodeUtf8 $ toStrict jsaddleJs) noCancellable . Just $
154 \_obj _asyncResult ->
142 \_obj _asyncResult ->
155 void $ forkIO start
143 void $ forkIO start
@@ -163,17 +151,32 @@ connectUserContentManagerScriptMessageReceived obj cb after = liftIO $ do
163 let cb' = wrap_UserContentManagerScriptMessageReceivedCallback cb
151 let cb' = wrap_UserContentManagerScriptMessageReceivedCallback cb
164 cb'' <- mk_UserContentManagerScriptMessageReceivedCallback cb'
152 cb'' <- mk_UserContentManagerScriptMessageReceivedCallback cb'
165 connectSignalFunPtr obj "script-message-received::jsaddle" cb'' after
153 connectSignalFunPtr obj "script-message-received::jsaddle" cb'' after
154+#if MIN_VERSION_haskell_gi_base(0,23,0)
155+ Nothing
156+#endif
166
157
167−addJSaddleHandler :: WebView -> (Results -> IO ()) -> IO ()
158+addJSaddleHandler :: WebView -> (Results -> IO ()) -> (Results -> IO Batch) -> IO ()
168−addJSaddleHandler webView processResult = do
159+addJSaddleHandler webView processResult syncResults = do
169 manager <- webViewGetUserContentManager webView
160 manager <- webViewGetUserContentManager webView
170 _ <- onUserContentManagerScriptMessageReceived manager $ \result -> do
161 _ <- onUserContentManagerScriptMessageReceived manager $ \result -> do
171− ctx <- javascriptResultGetGlobalContext result
162+ arg <- javascriptResultGetJsValue result
172− arg <- javascriptResultGetValue result
163+ bs <- encodeUtf8 <$> valueToString arg
173− bs <- withJSContextRef ctx $ \ctxRef ->
174− withJSValueRef arg $ \argRef ->
175− encodeUtf8 <$> valueToText ctxRef argRef
176 mapM_ processResult (decode (fromStrict bs))
164 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+
177 void $ userContentManagerRegisterScriptMessageHandler manager "jsaddle"
180 void $ userContentManagerRegisterScriptMessageHandler manager "jsaddle"
178
181
179jsaddleJs :: LB.ByteString
182jsaddleJs :: LB.ByteString
@@ -182,6 +185,7 @@ jsaddleJs = ghcjsHelpers <> mconcat
182 , initState
185 , initState
183 , "\nreturn function(batch) {\n"
186 , "\nreturn function(batch) {\n"
184 , runBatch (\a -> "window.webkit.messageHandlers.jsaddle.postMessage(JSON.stringify(" <> a <> "));\n")
187 , runBatch (\a -> "window.webkit.messageHandlers.jsaddle.postMessage(JSON.stringify(" <> a <> "));\n")
188+ (Just (\a -> "JSON.parse(window.prompt(\"JSaddleSync\", JSON.stringify(" <> a <> ")))"))
185 , "};\n"
189 , "};\n"
186 , "})()"
190 , "})()"
187 ]
191 ]
jsaddle-webkitgtk/jsaddle-webkitgtk.cabal+7−5
@@ -1,5 +1,5 @@
1name: jsaddle-webkitgtk
1name: jsaddle-webkitgtk
2−version: 0.8.2.0
2+version: 0.9.0.0
3cabal-version: >=1.10
3cabal-version: >=1.10
4build-type: Simple
4build-type: Simple
5license: MIT
5license: MIT
@@ -23,19 +23,21 @@ library
23 exposed-modules:
23 exposed-modules:
24 Language.Javascript.JSaddle.WebKitGTK
24 Language.Javascript.JSaddle.WebKitGTK
25 build-depends:
25 build-depends:
26− aeson >=0.8.0.2 && <1.1,
26+ aeson >=0.8.0.2 && <1.3,
27 base <5,
27 base <5,
28 bytestring >=0.10.6.0 && <0.11,
28 bytestring >=0.10.6.0 && <0.11,
29− directory >=1.0.0.2 && <1.3,
29+ directory >=1.0.0.2 && <1.4,
30 gi-glib >=2.0.6 && <2.1,
30 gi-glib >=2.0.6 && <2.1,
31 gi-gtk >=3.0.6 && <3.1,
31 gi-gtk >=3.0.6 && <3.1,
32 gi-webkit >=3.0.6 && <3.1,
32 gi-webkit >=3.0.6 && <3.1,
33 gi-javascriptcore >=3.0.6 && <3.1,
33 gi-javascriptcore >=3.0.6 && <3.1,
34 haskell-gi-base >=0.20 && <0.21,
34 haskell-gi-base >=0.20 && <0.21,
35− jsaddle >=0.8.0.0 && <0.9,
35+ jsaddle >=0.9.0.0 && <0.10,
36 text >=1.2.1.3 && <1.3,
36 text >=1.2.1.3 && <1.3,
37− unix >=2.3.1.0 && <2.8,
38 webkitgtk3-javascriptcore >=0.14.0.0 && <0.15
37 webkitgtk3-javascriptcore >=0.14.0.0 && <0.15
38+ if !os(windows)
39+ build-depends:
40+ unix >=2.3.1.0 && <2.8
39 default-language: Haskell2010
41 default-language: Haskell2010
40 hs-source-dirs: src
42 hs-source-dirs: src
41 ghc-options: -ferror-spans -Wall
43 ghc-options: -ferror-spans -Wall
jsaddle-webkitgtk/src/Language/Javascript/JSaddle/WebKitGTK.hs+9−4
@@ -35,8 +35,9 @@ import qualified Control.Exception as E (catch)
35import Control.Exception (SomeException(..))
35import Control.Exception (SomeException(..))
36import Control.Concurrent (forkIO, yield)
36import Control.Concurrent (forkIO, yield)
37
37
38+#ifndef mingw32_HOST_OS
38−import System.Posix.Signals
39+import System.Posix.Signals (installHandler, keyboardSignal, Handler(Catch))
39− (Handler(..), keyboardSignal, installHandler)
40+#endif
40import System.Directory (getCurrentDirectory)
41import System.Directory (getCurrentDirectory)
41
42
42import Data.Monoid ((<>))
43import Data.Monoid ((<>))
@@ -95,7 +96,11 @@ quitWebView wv = postGUIAsync $ do w <- widgetGetToplevel wv --TODO: Shouldn't t
95 widgetDestroy w
96 widgetDestroy w
96
97
97installQuitHandler :: WebView -> IO ()
98installQuitHandler :: WebView -> IO ()
99+#ifdef mingw32_HOST_OS
100+installQuitHandler wv = return () -- TODO: Maybe figure something out here for Windows users.
101+#else
98installQuitHandler wv = void $ installHandler keyboardSignal (Catch (quitWebView wv)) Nothing
102installQuitHandler wv = void $ installHandler keyboardSignal (Catch (quitWebView wv)) Nothing
103+#endif
99
104
100postGUIAsync :: IO () -> IO ()
105postGUIAsync :: IO () -> IO ()
101postGUIAsync action =
106postGUIAsync action =
@@ -180,7 +185,7 @@ runInWebView f webView = do
180 jsvalueprotect ctxRef result
185 jsvalueprotect ctxRef result
181 return result
186 return result
182
187
183− (processResults, start) <- runJavaScript (\batch -> postGUIAsync $
188+ (processResults, _, start) <- runJavaScript (\batch -> postGUIAsync $
184 withJSContextRef ctx $ \ctxRef ->
189 withJSContextRef ctx $ \ctxRef ->
185 withJSString (decodeUtf8 . toStrict $ encode batch) $ \script -> do
190 withJSString (decodeUtf8 . toStrict $ encode batch) $ \script -> do
186 val <- jsvaluemakestring ctxRef script
191 val <- jsvaluemakestring ctxRef script
@@ -217,7 +222,7 @@ jsaddleJs = ghcjsHelpers <> mconcat
217 , initState
222 , initState
218 , "\nreturn function(batchJSON) {\n"
223 , "\nreturn function(batchJSON) {\n"
219 , " var batch = JSON.parse(batchJSON);\n"
224 , " var batch = JSON.parse(batchJSON);\n"
220− , runBatch (\a -> "window.jsaddleCallback(JSON.stringify(" <> a <> "));\n")
225+ , runBatch (\a -> "window.jsaddleCallback(JSON.stringify(" <> a <> "));\n") Nothing
221 , "};\n"
226 , "};\n"
222 , "})()\n"
227 , "})()\n"
223 ]
228 ]
jsaddle-wkwebview/cbits-cocoa/AppDelegate.m+71−7
@@ -2,7 +2,10 @@
2#import <Cocoa/Cocoa.h>
2#import <Cocoa/Cocoa.h>
3#import <WebKit/WebKit.h>
3#import <WebKit/WebKit.h>
4
4
5+extern void callIO(HsStablePtr);
6+extern bool callWithCStringReturningBool(const char * _Nonnull, HsStablePtr);
7+extern void callWithCString(const char * _Nonnull, HsStablePtr);
5−extern void withWebView(WKWebView *, HsStablePtr);
8+extern void callWithWebView(WKWebView *, HsStablePtr);
6
9
7@interface AppDelegate : NSObject <NSApplicationDelegate>
10@interface AppDelegate : NSObject <NSApplicationDelegate>
8@property (nonatomic, assign) IBOutlet NSWindow *window;
11@property (nonatomic, assign) IBOutlet NSWindow *window;
@@ -11,6 +14,12 @@ extern void withWebView(WKWebView *, HsStablePtr);
11- (instancetype)initApp:(HsStablePtr)handler progName:(NSString *)progName;
14- (instancetype)initApp:(HsStablePtr)handler progName:(NSString *)progName;
12@end
15@end
13
16
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+
14@implementation AppDelegate
23@implementation AppDelegate
15
24
16- (void)applicationWillTerminate:(NSNotification *)aNotification {
25- (void)applicationWillTerminate:(NSNotification *)aNotification {
@@ -22,7 +31,8 @@ extern void withWebView(WKWebView *, HsStablePtr);
22 if (self) {
31 if (self) {
23 _handler = handler;
32 _handler = handler;
24 NSRect contentSize = NSMakeRect(0.0, 500.0, 1000.0, 700.0);
33 NSRect contentSize = NSMakeRect(0.0, 500.0, 1000.0, 700.0);
25− NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
34+ // TODO replace with once nix has newer Cocoa NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
35+ NSUInteger windowStyleMask = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
26 _window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
36 _window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
27 _window.backgroundColor = [NSColor whiteColor];
37 _window.backgroundColor = [NSColor whiteColor];
28 _window.title = progName;
38 _window.title = progName;
@@ -30,18 +40,39 @@ extern void withWebView(WKWebView *, HsStablePtr);
30 return self;
40 return self;
31}
41}
32
42
43+-(BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
44+ bool b = callWithCStringReturningBool([filename UTF8String], global_applicationOpenFile);
45+ return b ? YES : NO;
46+}
47+
33-(void)applicationWillFinishLaunching:(NSNotification *)notification {
48-(void)applicationWillFinishLaunching:(NSNotification *)notification {
34 WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
49 WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init];
50+ if (global_developerExtrasEnabled) {
35− [theConfiguration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
51+ [theConfiguration.preferences setValue:@YES forKey:@"developerExtrasEnabled"];
52+ }
36− WKWebView *webView = [[WKWebView alloc] initWithFrame:_window.contentView.frame configuration:theConfiguration];
53+ WKWebView *webView = [[WKWebView alloc] initWithFrame: [_window.contentView frame] configuration:theConfiguration];
37 [_window setContentView:webView];
54 [_window setContentView:webView];
38− 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);
39}
69}
40
70
41-(void)applicationDidFinishLaunching:(NSNotification *)notification {
71-(void)applicationDidFinishLaunching:(NSNotification *)notification {
42 [_window orderFrontRegardless];
72 [_window orderFrontRegardless];
43 [_window center];
73 [_window center];
44 [NSApp activateIgnoringOtherApps:YES];
74 [NSApp activateIgnoringOtherApps:YES];
75+ callIO(global_didFinishLaunchingWithOptions);
45}
76}
46
77
47-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
78-(BOOL) applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app {
@@ -50,13 +81,46 @@ extern void withWebView(WKWebView *, HsStablePtr);
50
81
51@end
82@end
52
83
53−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) {
54 @autoreleasepool {
105 @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;
55 NSApplication *application = [NSApplication sharedApplication];
111 NSApplication *application = [NSApplication sharedApplication];
56 AppDelegate *appDelegate = [[AppDelegate alloc] initApp:handler progName:[NSString stringWithCString:progName encoding:NSUTF8StringEncoding]];
112 AppDelegate *appDelegate = [[AppDelegate alloc] initApp:handler progName:[NSString stringWithCString:progName encoding:NSUTF8StringEncoding]];
57 [application setDelegate:appDelegate];
113 [application setDelegate:appDelegate];
58 const char * _Nonnull argv [] = {"", 0};
114 const char * _Nonnull argv [] = {"", 0};
59− NSApplicationMain(0, argv);
115+ [application setActivationPolicy:NSApplicationActivationPolicyRegular];
116+ [application
117+ performSelectorOnMainThread:@selector(run)
118+ withObject:nil
119+ waitUntilDone:YES];
60 }
120 }
61}
121}
62
122
123+BOOL openApp(NSURL * url) {
124+ NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
125+ return [workspace openURL:url];
126+}
jsaddle-wkwebview/cbits-uikit/AppDelegate.m+152−2
@@ -1,57 +1,207 @@
1#import "AppDelegate.h"
1#import "AppDelegate.h"
2#import "ViewController.h"
2#import "ViewController.h"
3+#import <UserNotifications/UserNotifications.h>
4+#import <stdint.h>
3
5
4−@interface AppDelegate ()
6+extern void callIO(HsStablePtr);
7+extern void callWithCString(const char * _Nonnull, HsStablePtr);
8+extern void callWithCIntCString(int n, const char * _Nonnull, HsStablePtr);
5
9
10+@interface AppDelegate ()
6
11
7@end
12@end
8
13
9HsStablePtr globalHandler = 0;
14HsStablePtr globalHandler = 0;
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;
31+HsStablePtr global_didRegisterForRemoteNotificationsWithDeviceToken = 0;
32+HsStablePtr global_didFailToRegisterForRemoteNotificationsWithError = 0;
10
33
11@implementation AppDelegate
34@implementation AppDelegate
12
35
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+
13- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
42- (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.
14 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
44 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
15 // Override point for customization after application launch.
45 // Override point for customization after application launch.
16 self.window.rootViewController = [[ViewController alloc] initWithHandler:globalHandler];
46 self.window.rootViewController = [[ViewController alloc] initWithHandler:globalHandler];
17 self.window.backgroundColor = [UIColor whiteColor];
47 self.window.backgroundColor = [UIColor whiteColor];
18 [self.window makeKeyAndVisible];
48 [self.window makeKeyAndVisible];
49+ UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
50+ center.delegate = self;
51+ if (global_requestAuthorizationWithOptions) {
52+ UNAuthorizationOptions options = (UNAuthorizationOptions)0;
53+ if (global_requestAuthorizationOptionBadge) {
54+ options = options | UNAuthorizationOptionBadge;
55+ }
56+ if (global_requestAuthorizationOptionSound) {
57+ options = options | UNAuthorizationOptionSound;
58+ }
59+ if (global_requestAuthorizationOptionAlert) {
60+ options = options | UNAuthorizationOptionAlert;
61+ }
62+ if (global_requestAuthorizationOptionCarPlay) {
63+ options = options | UNAuthorizationOptionCarPlay;
64+ }
65+ [center requestAuthorizationWithOptions:(options)
66+ completionHandler:^(BOOL granted, NSError * _Nullable error) {
67+ // Handler used to alter application behavior based on types of notifications authorized
68+ }];
69+ if (global_registerForRemoteNotifications) {
70+ [application registerForRemoteNotifications];
71+ }
72+ }
73+ callIO(global_didFinishLaunchingWithOptions);
19 return YES;
74 return YES;
20}
75}
21
76
22- (void)applicationWillResignActive:(UIApplication *)application {
77- (void)applicationWillResignActive:(UIApplication *)application {
23 // 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.
78 // 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.
24 // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
79 // 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);
25}
81}
26
82
27
83
28- (void)applicationDidEnterBackground:(UIApplication *)application {
84- (void)applicationDidEnterBackground:(UIApplication *)application {
29 // 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.
85 // 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.
30 // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
86 // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
87+ callIO(global_applicationDidEnterBackground);
31}
88}
32
89
33
90
34- (void)applicationWillEnterForeground:(UIApplication *)application {
91- (void)applicationWillEnterForeground:(UIApplication *)application {
35 // 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.
92 // 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);
36}
94}
37
95
38
96
39- (void)applicationDidBecomeActive:(UIApplication *)application {
97- (void)applicationDidBecomeActive:(UIApplication *)application {
40 // 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.
98 // 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);
41}
100}
42
101
43
102
44- (void)applicationWillTerminate:(UIApplication *)application {
103- (void)applicationWillTerminate:(UIApplication *)application {
45 // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
104 // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
105+ callIO(global_applicationWillTerminate);
106+}
107+
108+- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
109+ // Tells the delegate that the app successfully registered with Apple Push Notification service (APNs).
110+ NSString *deviceTokenString = [deviceToken base64EncodedStringWithOptions: 0];
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);
129+}
130+
131+- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
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;
46}
150}
47
151
48@end
152@end
49
153
50−void runInWKWebView(HsStablePtr handler, const char * _Nonnull progName) {
154+void runInWKWebView(HsStablePtr handler,
155+ const char * _Nonnull progName,
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) {
51 @autoreleasepool {
175 @autoreleasepool {
52 globalHandler = handler;
176 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;
187+ global_requestAuthorizationWithOptions = hs_requestAuthorizationWithOptions;
188+ global_requestAuthorizationOptionBadge = hs_requestAuthorizationOptionBadge;
189+ global_requestAuthorizationOptionSound = hs_requestAuthorizationOptionSound;
190+ global_requestAuthorizationOptionAlert = hs_requestAuthorizationOptionAlert;
191+ global_requestAuthorizationOptionCarPlay = hs_requestAuthorizationOptionCarPlay;
192+ global_registerForRemoteNotifications = hs_registerForRemoteNotifications;
193+ global_didRegisterForRemoteNotificationsWithDeviceToken = hs_didRegisterForRemoteNotificationsWithDeviceToken;
194+ global_didFailToRegisterForRemoteNotificationsWithError = hs_didFailToRegisterForRemoteNotificationsWithError;
53 const char * _Nonnull argv [] = {"", 0};
195 const char * _Nonnull argv [] = {"", 0};
54 UIApplicationMain(0, argv, nil, NSStringFromClass([AppDelegate class]));
196 UIApplicationMain(0, argv, nil, NSStringFromClass([AppDelegate class]));
55 }
197 }
56}
198}
57
199
200+BOOL openApp(NSURL * url) {
201+ UIApplication *app = [UIApplication sharedApplication];
202+ if ([app canOpenURL:url]) {
203+ [app openURL:url];
204+ return true;
205+ }
206+ return false;
207+}
jsaddle-wkwebview/cbits-uikit/ViewController.m+2−2
@@ -1,6 +1,6 @@
1#import "ViewController.h"
1#import "ViewController.h"
2
2
3−extern void withWebView(WKWebView *, HsStablePtr);
3+extern void callWithWebView(WKWebView *, HsStablePtr);
4
4
5@interface ViewController ()
5@interface ViewController ()
6
6
@@ -27,7 +27,7 @@ extern void withWebView(WKWebView *, HsStablePtr);
27
27
28- (void)viewDidLoad {
28- (void)viewDidLoad {
29 [super viewDidLoad];
29 [super viewDidLoad];
30− withWebView(_webView, _handler);
30+ callWithWebView(_webView, _handler);
31}
31}
32
32
33
33
jsaddle-wkwebview/cbits/WKWebView.m+79−10
@@ -2,26 +2,32 @@
2#import <Foundation/Foundation.h>
2#import <Foundation/Foundation.h>
3#import <WebKit/WebKit.h>
3#import <WebKit/WebKit.h>
4
4
5−extern void jsaddleStart(HsStablePtr);
5+BOOL openApp(NSURL * url);
6−extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
7
6
8−@interface JSaddleHandler : NSObject<WKNavigationDelegate, WKScriptMessageHandler>
7+@interface JSaddleHandler : NSObject<WKNavigationDelegate, WKScriptMessageHandler, WKUIDelegate>
9
8
10@property (nonatomic, assign) HsStablePtr startHandler;
9@property (nonatomic, assign) HsStablePtr startHandler;
11@property (nonatomic, assign) HsStablePtr resultHandler;
10@property (nonatomic, assign) HsStablePtr resultHandler;
11+@property (nonatomic, assign) HsStablePtr syncHandler;
12+@property (nonatomic, assign) void (^completionHandler)(NSString *result);
12
13
13−- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler;
14+- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler syncHandler:(HsStablePtr)syncHandler;
14
15
15@end
16@end
16
17
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+
17@implementation JSaddleHandler
22@implementation JSaddleHandler
18
23
19−- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler
24+- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler syncHandler:(HsStablePtr)syncHandler
20{
25{
21 self = [super init];
26 self = [super init];
22 if (self) {
27 if (self) {
23 _startHandler = startHandler;
28 _startHandler = startHandler;
24 _resultHandler = resultHandler;
29 _resultHandler = resultHandler;
30+ _syncHandler = syncHandler;
25 }
31 }
26 return self;
32 return self;
27}
33}
@@ -37,19 +43,82 @@ extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
37 jsaddleResult(self.resultHandler, [s UTF8String]);
43 jsaddleResult(self.resultHandler, [s UTF8String]);
38}
44}
39
45
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+
58+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
59+{
60+ NSURL *url = navigationAction.request.URL;
61+
62+ if(!navigationAction.targetFrame && ![url.scheme isEqualToString:@"file"]) {
63+ if(openApp(url))
64+ decisionHandler(WKNavigationActionPolicyCancel);
65+ else
66+ decisionHandler(WKNavigationActionPolicyAllow);
67+ }
68+ else
69+ decisionHandler(WKNavigationActionPolicyAllow);
70+}
71+
40@end
72@end
41
73
42−void addJSaddleHandler(WKWebView *webView, HsStablePtr startHandler, HsStablePtr resultHandler) {
74+void addJSaddleHandler(WKWebView *webView, HsStablePtr startHandler, HsStablePtr resultHandler, HsStablePtr syncHandler) {
43− JSaddleHandler * handler = [[JSaddleHandler alloc] initHandler:startHandler resultHandler:resultHandler];
75+ JSaddleHandler * handler = [[JSaddleHandler alloc] initHandler:startHandler resultHandler:resultHandler syncHandler:syncHandler];
44 [[[webView configuration] userContentController] addScriptMessageHandler:handler name:@"jsaddle"];
76 [[[webView configuration] userContentController] addScriptMessageHandler:handler name:@"jsaddle"];
77+#ifdef USE_UIKIT
78+ webView.scrollView.bounces = NO;
79+#endif
45 webView.navigationDelegate = handler;
80 webView.navigationDelegate = handler;
81+ webView.UIDelegate = handler;
46}
82}
47
83
48void evaluateJavaScript(WKWebView *webView, const char * _Nonnull js) {
84void evaluateJavaScript(WKWebView *webView, const char * _Nonnull js) {
49− [webView evaluateJavaScript:[NSString stringWithCString:js encoding:NSUTF8StringEncoding] completionHandler:NULL];
85+ NSString *jsString = [[NSString alloc] initWithCString:js encoding:NSUTF8StringEncoding];
86+ dispatch_async(dispatch_get_main_queue(), ^{
87+ [webView evaluateJavaScript:jsString completionHandler:NULL];
88+ [jsString release];
89+ });
90+}
91+
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];
103+ });
50}
104}
51
105
52−void loadHTMLString(WKWebView *webView, const char * _Nonnull html) {
106+const char * mainBundleResourcePathC() {
53− [webView loadHTMLString:[NSString stringWithCString:html encoding:NSUTF8StringEncoding] baseURL:NULL];
107+ NSBundle *main = [NSBundle mainBundle];
108+ if(!main) return NULL;
109+ NSString *path = main.resourcePath;
110+ if(!path) return NULL;
111+ return [path UTF8String];
54}
112}
55
113
114+void loadBundleFile(WKWebView *webView, const char * _Nonnull file, const char * _Nonnull allowing) {
115+ NSBundle *main = [NSBundle mainBundle];
116+ if(main) {
117+ NSString *path = main.resourcePath;
118+ if(path) {
119+ NSURL *fileUrl = [NSURL fileURLWithPath:[NSString stringWithFormat: @"%@/%@", path, [NSString stringWithCString:file encoding:NSUTF8StringEncoding]]];
120+ NSURL *allowingUrl = [NSURL fileURLWithPath:[NSString stringWithFormat: @"%@/%@", path, [NSString stringWithCString:allowing encoding:NSUTF8StringEncoding]]];
121+ [webView loadFileURL:fileUrl allowingReadAccessToURL:allowingUrl];
122+ }
123+ }
124+}
jsaddle-wkwebview/default.nixnew file+14
@@ -0,0 +1,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+32−15
@@ -1,5 +1,5 @@
1name: jsaddle-wkwebview
1name: jsaddle-wkwebview
2−version: 0.8.2.0
2+version: 0.9.8.0
3cabal-version: >=1.10
3cabal-version: >=1.10
4build-type: Simple
4build-type: Simple
5license: MIT
5license: MIT
@@ -14,6 +14,10 @@ description:
14category: Web, Javascript
14category: Web, Javascript
15author: Hamish Mackenzie
15author: Hamish Mackenzie
16
16
17+flag include-app-delegate
18+ description: Include default AppDelegate C sources.
19+ default: True
20+
17source-repository head
21source-repository head
18 type: git
22 type: git
19 location: https://github.com/ghcjs/jsaddle
23 location: https://github.com/ghcjs/jsaddle
@@ -21,28 +25,41 @@ source-repository head
21library
25library
22 exposed-modules:
26 exposed-modules:
23 Language.Javascript.JSaddle.WKWebView
27 Language.Javascript.JSaddle.WKWebView
24− Language.Javascript.JSaddle.WKWebView.Internal
25 build-depends:
28 build-depends:
26− aeson >=0.8.0.2 && <1.1,
27− base <5,
29+ base <5
28− bytestring >=0.10.6.0 && <0.11,
29− jsaddle >= 0.8.0.0 && <0.9
30 default-language: Haskell2010
30 default-language: Haskell2010
31 hs-source-dirs: src
31 hs-source-dirs: src
32− frameworks: Foundation, WebKit
33 ghc-options: -ferror-spans -Wall
32 ghc-options: -ferror-spans -Wall
34− c-sources:
33+ if os(linux)
35− cbits/WKWebView.m
34+ buildable: False
36 if impl(ghcjs)
35 if impl(ghcjs)
37 hs-source-dirs: src-ghcjs
36 hs-source-dirs: src-ghcjs
38 else
37 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
39 hs-source-dirs: src-ghc
49 hs-source-dirs: src-ghc
50+ c-sources:
51+ cbits/WKWebView.m
52+ cc-options: -Wno-everything
40 if os(ios)
53 if os(ios)
41− frameworks: UIKit
54+ frameworks: UIKit, UserNotifications
55+ if flag(include-app-delegate)
42− c-sources:
56+ c-sources:
43− cbits-uikit/AppDelegate.m
57+ cbits-uikit/AppDelegate.m
44− cbits-uikit/ViewController.m
58+ cbits-uikit/ViewController.m
59+ cpp-options: -DUSE_UIKIT
45 else
60 else
46 frameworks: Cocoa
61 frameworks: Cocoa
62+ if flag(include-app-delegate)
47− c-sources:
63+ c-sources:
48− cbits-cocoa/AppDelegate.m
64+ cbits-cocoa/AppDelegate.m
65+ cpp-options: -DUSE_COCOA
jsaddle-wkwebview/src-ghc/Language/Javascript/JSaddle/WKWebView.hs+205−5
@@ -1,19 +1,219 @@
1module Language.Javascript.JSaddle.WKWebView
1module Language.Javascript.JSaddle.WKWebView
2 ( jsaddleMain
2 ( jsaddleMain
3+ , jsaddleMainHTMLWithBaseURL
4+ , jsaddleMainFile
3 , WKWebView(..)
5 , WKWebView(..)
4 , run
6 , run
7+ , run'
8+ , runWithAppConfig
9+ , runHTMLWithBaseURL
10+ , runFile
11+ , mainBundleResourcePath
12+ , ApplicationState (..)
13+ , AppDelegateConfig (..)
14+ , AppDelegateNotificationConfig (..)
15+ , AuthorizationOption (..)
5 ) where
16 ) where
6
17
18+import Data.ByteString (ByteString)
19+import Data.Default (Default, def)
20+import Data.Set (Set)
21+import qualified Data.Set as Set
22+import Data.Word
23+import Foreign.Marshal.Utils (fromBool)
7−import Language.Javascript.JSaddle.WKWebView.Internal (jsaddleMain, WKWebView(..))
24+import Language.Javascript.JSaddle.WKWebView.Internal
25+ (jsaddleMain, jsaddleMainHTMLWithBaseURL, jsaddleMainFile, WKWebView(..), mainBundleResourcePath)
8import System.Environment (getProgName)
26import System.Environment (getProgName)
9import Foreign.C.String (CString, withCString)
27import Foreign.C.String (CString, withCString)
28+import Foreign.C.Types (CBool, CInt)
10import Foreign.StablePtr (StablePtr, newStablePtr)
29import Foreign.StablePtr (StablePtr, newStablePtr)
11import Language.Javascript.JSaddle (JSM)
30import Language.Javascript.JSaddle (JSM)
12
31
13−foreign import ccall runInWKWebView :: StablePtr (WKWebView -> IO ()) -> CString -> IO ()
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 ()
14
56
57+data AppDelegateConfig = AppDelegateConfig
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.
86+ }
87+
88+instance Default AppDelegateConfig where
89+ def = AppDelegateConfig
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
103+ }
104+
105+data AuthorizationOption = AuthorizationOption_Badge
106+ | AuthorizationOption_Sound
107+ | AuthorizationOption_Alert
108+ | AuthorizationOption_CarPlay
109+ deriving (Show, Read, Eq, Ord)
110+
111+data ApplicationState = ApplicationState_Active
112+ | ApplicationState_Inactive
113+ | ApplicationState_Background
114+ | ApplicationState_Unknown CInt
115+ deriving (Show, Read, Eq, Ord)
116+
117+data AppDelegateNotificationConfig = AppDelegateNotificationConfig
118+ { _appDelegateNotificationConfig_requestAuthorizationWithOptions :: Maybe (Set AuthorizationOption)
119+ , _appDelegateNotificationConfig_registerForRemoteNotifications :: Bool
120+ , _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken :: CString -> IO ()
121+ , _appDelegateNotificationConfig_didFailToRegisterForRemoteNotificationsWithError :: CString -> IO ()
122+ }
123+
124+instance Default AppDelegateNotificationConfig where
125+ def = AppDelegateNotificationConfig
126+ { _appDelegateNotificationConfig_requestAuthorizationWithOptions = Nothing
127+ , _appDelegateNotificationConfig_registerForRemoteNotifications = False
128+ , _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken = \_ -> return ()
129+ , _appDelegateNotificationConfig_didFailToRegisterForRemoteNotificationsWithError = \_ -> return ()
130+ }
131+
132+
133+-- | Run JSaddle in a WKWebView
15run :: JSM () -> IO ()
134run :: JSM () -> IO ()
16−run f = do
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
140+
141+-- | Run JSaddle in a WKWebView first loading the specified file
142+-- from the mainBundle (relative to the resourcePath).
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
17− handler <- newStablePtr (jsaddleMain f)
166+ handler <- newStablePtr main
18 progName <- getProgName
167 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
189+ let ncfg = _appDelegateConfig_appDelegateNotificationConfig cfg
190+ (requestAuthorizationWithOptions, authorizationOptions) =
191+ case _appDelegateNotificationConfig_requestAuthorizationWithOptions ncfg of
192+ Nothing -> (False, Set.empty)
193+ Just opts -> (True, opts)
194+ registerForRemoteNotifications = _appDelegateNotificationConfig_registerForRemoteNotifications ncfg
195+ didRegisterForRemoteNotificationsWithDeviceToken <- newStablePtr $ _appDelegateNotificationConfig_didRegisterForRemoteNotificationsWithDeviceToken ncfg
196+ didFailToRegisterForRemoteNotificationsWithError <- newStablePtr $ _appDelegateNotificationConfig_didFailToRegisterForRemoteNotificationsWithError ncfg
19− withCString progName $ runInWKWebView handler
197+ withCString progName $ \pn -> runInWKWebView
198+ handler
199+ pn
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)
216+ didRegisterForRemoteNotificationsWithDeviceToken
217+ didFailToRegisterForRemoteNotificationsWithError
218+ (fromBool $ _appDelegateConfig_developerExtrasEnabled cfg)
219+ applicationOpenFile
jsaddle-wkwebview/src/Language/Javascript/JSaddle/WKWebView/Internal.hs+109−16
@@ -1,47 +1,101 @@
1{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
1{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
2module Language.Javascript.JSaddle.WKWebView.Internal
2module Language.Javascript.JSaddle.WKWebView.Internal
3 ( jsaddleMain
3 ( jsaddleMain
4+ , jsaddleMainHTMLWithBaseURL
5+ , jsaddleMainFile
4 , WKWebView(..)
6 , WKWebView(..)
7+ , mainBundleResourcePath
5 ) where
8 ) where
6
9
7import Control.Monad (void, join)
10import Control.Monad (void, join)
8−import Control.Concurrent (forkIO)
11+import Control.Concurrent (forkIO, forkOS)
12+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
9
13
10import Data.Monoid ((<>))
14import Data.Monoid ((<>))
11import Data.ByteString (useAsCString, packCString)
15import Data.ByteString (useAsCString, packCString)
16+import qualified Data.ByteString as BS (ByteString)
12import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
17import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
13import Data.Aeson (encode, decode)
18import Data.Aeson (encode, decode)
19+import qualified Data.Text as T (pack)
20+import Data.Text.Encoding (encodeUtf8)
14
21
15import Foreign.C.String (CString)
22import Foreign.C.String (CString)
16−import Foreign.Ptr (Ptr)
23+import Foreign.C.Types (CBool(..), CInt(..))
24+import Foreign.Ptr (Ptr, nullPtr)
17import Foreign.StablePtr (StablePtr, newStablePtr, deRefStablePtr)
25import Foreign.StablePtr (StablePtr, newStablePtr, deRefStablePtr)
18
26
19−import Language.Javascript.JSaddle (Results, JSM)
27+import Language.Javascript.JSaddle (Results, Batch, JSM)
20import Language.Javascript.JSaddle.Run (runJavaScript)
28import Language.Javascript.JSaddle.Run (runJavaScript)
21import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
29import Language.Javascript.JSaddle.Run.Files (initState, runBatch, ghcjsHelpers)
22
30
31+import System.Directory (getCurrentDirectory)
32+
23newtype WKWebView = WKWebView (Ptr WKWebView)
33newtype WKWebView = WKWebView (Ptr WKWebView)
34+newtype JSaddleHandler = JSaddleHandler (Ptr JSaddleHandler)
24
35
25foreign export ccall jsaddleStart :: StablePtr (IO ()) -> IO ()
36foreign export ccall jsaddleStart :: StablePtr (IO ()) -> IO ()
26foreign export ccall jsaddleResult :: StablePtr (Results -> IO ()) -> CString -> IO ()
37foreign export ccall jsaddleResult :: StablePtr (Results -> IO ()) -> CString -> IO ()
38+foreign export ccall jsaddleSyncResult :: StablePtr (Results -> IO Batch) -> JSaddleHandler -> CString -> IO ()
27−foreign export ccall withWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> 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 ()
28−foreign import ccall addJSaddleHandler :: WKWebView -> StablePtr (IO ()) -> StablePtr (Results -> IO ()) -> IO ()
44+foreign import ccall addJSaddleHandler :: WKWebView -> StablePtr (IO ()) -> StablePtr (Results -> IO ()) -> StablePtr (Results -> IO Batch) -> IO ()
29−foreign import ccall loadHTMLString :: WKWebView -> CString -> IO ()
45+foreign import ccall loadHTMLStringWithBaseURL :: WKWebView -> CString -> CString -> IO ()
46+foreign import ccall loadBundleFile :: WKWebView -> CString -> CString -> IO ()
30foreign import ccall evaluateJavaScript :: WKWebView -> CString -> IO ()
47foreign import ccall evaluateJavaScript :: WKWebView -> CString -> IO ()
48+foreign import ccall completeSync :: JSaddleHandler -> CString -> IO ()
49+foreign import ccall mainBundleResourcePathC :: IO CString
31
50
51+-- | Run JSaddle in WKWebView
32jsaddleMain :: JSM () -> WKWebView -> IO ()
52jsaddleMain :: JSM () -> WKWebView -> IO ()
33jsaddleMain f webView = do
53jsaddleMain 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 =
66+ jsaddleMain' f webView $
67+ useAsCString baseURL $ \url ->
68+ useAsCString initialHTML $ \html ->
69+ loadHTMLStringWithBaseURL webView html url
70+
71+-- | Run JSaddle in a WKWebView first loading the specified file
72+-- from the mainBundle (relative to the resourcePath).
73+jsaddleMainFile :: BS.ByteString -- ^ The file to navigate to.
74+ -> BS.ByteString -- ^ The path to allow read access to.
75+ -> JSM () -> WKWebView -> IO ()
76+jsaddleMainFile url allowing f webView =
77+ jsaddleMain' f webView $
78+ useAsCString url $ \u ->
79+ useAsCString allowing $ \a ->
80+ loadBundleFile webView u a
81+
82+jsaddleMain' :: JSM () -> WKWebView -> IO () -> IO ()
83+jsaddleMain' f webView loadHtml = do
84+ ready <- newEmptyMVar
85+
34− (processResult, start) <- runJavaScript (\batch ->
86+ (processResult, syncResult, start) <- runJavaScript (\batch ->
35 useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $
87 useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $
36 evaluateJavaScript webView)
88 evaluateJavaScript webView)
37 f
89 f
38
90
39− startHandler <- newStablePtr (void $ forkIO start)
91+ startHandler <- newStablePtr (putMVar ready ())
40 resultHandler <- newStablePtr processResult
92 resultHandler <- newStablePtr processResult
93+ syncResultHandler <- newStablePtr syncResult
41− addJSaddleHandler webView startHandler resultHandler
94+ addJSaddleHandler webView startHandler resultHandler syncResultHandler
42−
95+ loadHtml
43− useAsCString (toStrict indexHtml) $ loadHTMLString webView
96+ void . forkOS $ do
97+ takeMVar ready
44− useAsCString (toStrict jsaddleJs) $ evaluateJavaScript webView
98+ useAsCString (toStrict jsaddleJs) (evaluateJavaScript webView) >> void (forkIO start)
45
99
46jsaddleStart :: StablePtr (IO ()) -> IO ()
100jsaddleStart :: StablePtr (IO ()) -> IO ()
47jsaddleStart ptrHandler = join (deRefStablePtr ptrHandler)
101jsaddleStart ptrHandler = join (deRefStablePtr ptrHandler)
@@ -51,20 +105,52 @@ jsaddleResult ptrHandler s = do
51 processResult <- deRefStablePtr ptrHandler
105 processResult <- deRefStablePtr ptrHandler
52 result <- packCString s
106 result <- packCString s
53 case decode (fromStrict result) of
107 case decode (fromStrict result) of
54− Nothing -> error $ "jsaddle WebSocket decode failed : " <> show result
108+ Nothing -> error $ "jsaddle Results decode failed : " <> show result
55 Just r -> processResult r
109 Just r -> processResult r
56
110
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+
57−withWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
122+callWithWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
58−withWebView webView ptrF = do
123+callWithWebView webView ptrF = do
59 f <- deRefStablePtr ptrF
124 f <- deRefStablePtr ptrF
60 f webView
125 f webView
61
126
127+callWithCString :: CString -> StablePtr (CString -> IO ()) -> IO ()
128+callWithCString c fptr = do
129+ f <- deRefStablePtr fptr
130+ f c
131+
132+callWithCIntCString :: CInt -> CString -> StablePtr (CInt -> CString -> IO ()) -> IO ()
133+callWithCIntCString n s fptr = do
134+ f <- deRefStablePtr fptr
135+ f n s
136+
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
146+
62jsaddleJs :: ByteString
147jsaddleJs :: ByteString
63jsaddleJs = ghcjsHelpers <> "\
148jsaddleJs = ghcjsHelpers <> "\
64 \runJSaddleBatch = (function() {\n\
149 \runJSaddleBatch = (function() {\n\
65 \ " <> initState <> "\n\
150 \ " <> initState <> "\n\
66 \ return function(batch) {\n\
151 \ return function(batch) {\n\
67− \ " <> 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 <> ")))")) <> "\
68 \ };\n\
154 \ };\n\
69 \})();\n\
155 \})();\n\
70 \"
156 \"
@@ -79,3 +165,10 @@ indexHtml =
79 \<body>\n\
165 \<body>\n\
80 \</body>\n\
166 \</body>\n\
81 \</html>"
167 \</html>"
168+
169+mainBundleResourcePath :: IO (Maybe BS.ByteString)
170+mainBundleResourcePath = do
171+ bs <- mainBundleResourcePathC
172+ if bs == nullPtr
173+ then return Nothing
174+ else Just <$> packCString bs
jsaddle/jsaddle.cabal+32−15
@@ -1,5 +1,5 @@
1name: jsaddle
1name: jsaddle
2−version: 0.8.0.1
2+version: 0.9.8.0
3cabal-version: >=1.10
3cabal-version: >=1.10
4build-type: Simple
4build-type: Simple
5license: MIT
5license: MIT
@@ -10,7 +10,7 @@ description:
10 This package provides an EDSL for calling JavaScript that
10 This package provides an EDSL for calling JavaScript that
11 can be used both from GHCJS and GHC. When using GHC
11 can be used both from GHCJS and GHC. When using GHC
12 the application is run using Warp and WebSockets to
12 the application is run using Warp and WebSockets to
13− drive a small JavaScipt helper.
13+ drive a small JavaScript helper.
14category: Web, Javascript
14category: Web, Javascript
15author: Hamish Mackenzie
15author: Hamish Mackenzie
16
16
@@ -19,6 +19,14 @@ source-repository head
19 location: https://github.com/ghcjs/jsaddle
19 location: https://github.com/ghcjs/jsaddle
20 subdir: jsaddle
20 subdir: jsaddle
21
21
22+flag call-stacks
23+ description: Include HasCallStack constraint on calls unchecked calls
24+ default: False
25+
26+flag check-unchecked
27+ description: Fail unchecked calls when they are called (rather than when the result is evaluated)
28+ default: False
29+
22library
30library
23
31
24 if impl(ghcjs -any)
32 if impl(ghcjs -any)
@@ -28,18 +36,20 @@ library
28 else
36 else
29 build-depends:
37 build-depends:
30 attoparsec >=0.11 && <0.14,
38 attoparsec >=0.11 && <0.14,
31− containers >=0.5.6.2 && <0.6,
39+ containers >=0.5.6.2 && <0.7,
32 deepseq >=1.3 && < 1.5,
40 deepseq >=1.3 && < 1.5,
33 filepath >=1.4.0.0 && <1.5,
41 filepath >=1.4.0.0 && <1.5,
34 ghc-prim,
42 ghc-prim,
35− http-types >=0.8.6 && <0.10,
43+ http-types >=0.8.6 && <0.13,
36− process >=1.2.3.0 && <1.5,
44+ process >=1.2.3.0 && <1.7,
45+ random >= 1.1 && < 1.3,
37 ref-tf >=0.4.0.1 && <0.5,
46 ref-tf >=0.4.0.1 && <0.5,
38 scientific >=0.3 && <0.4,
47 scientific >=0.3 && <0.4,
39− stm >=2.4.4 && <2.5,
48+ stm >=2.4.4 && <2.6,
40− time >=1.5.0.1 && <1.7,
49+ time >=1.5.0.1 && <1.11,
41 unordered-containers >=0.2 && <0.3,
50 unordered-containers >=0.2 && <0.3,
42− vector >=0.10 && <0.12
51+ vector >=0.10 && <0.13,
52+ unliftio-core >=0.1 && < 0.3
43 exposed-modules:
53 exposed-modules:
44 Data.JSString
54 Data.JSString
45 Data.JSString.Internal
55 Data.JSString.Internal
@@ -53,6 +63,7 @@ library
53 GHCJS.Prim
63 GHCJS.Prim
54 GHCJS.Prim.Internal
64 GHCJS.Prim.Internal
55 GHCJS.Types
65 GHCJS.Types
66+ GHCJS.Concurrent
56 GHCJS.Foreign
67 GHCJS.Foreign
57 GHCJS.Foreign.Internal
68 GHCJS.Foreign.Internal
58 GHCJS.Internal.Types
69 GHCJS.Internal.Types
@@ -73,8 +84,10 @@ library
73 JavaScript.Array.Internal
84 JavaScript.Array.Internal
74 JavaScript.Object
85 JavaScript.Object
75 JavaScript.Object.Internal
86 JavaScript.Object.Internal
87+ Language.Javascript.JSaddle.Debug
76 Language.Javascript.JSaddle.Native
88 Language.Javascript.JSaddle.Native
77 Language.Javascript.JSaddle.Native.Internal
89 Language.Javascript.JSaddle.Native.Internal
90+ Language.Javascript.JSaddle.Null
78 hs-source-dirs: src-ghc
91 hs-source-dirs: src-ghc
79 exposed-modules:
92 exposed-modules:
80 Language.Javascript.JSaddle
93 Language.Javascript.JSaddle
@@ -94,16 +107,20 @@ library
94 Language.Javascript.JSaddle.Value
107 Language.Javascript.JSaddle.Value
95 Language.Javascript.JSaddle.Types
108 Language.Javascript.JSaddle.Types
96 build-depends:
109 build-depends:
97− aeson >=0.8.0.2 && <1.1,
110+ aeson >=0.11.3.0 && <1.6,
98− base <5,
111+ base >=4.9 && <5,
112+ base-compat >=0.9.0 && <0.12,
99− base64-bytestring >=1.0.0.1 && <1.1,
113+ base64-bytestring >=1.0.0.1 && <1.2,
100 bytestring >=0.10.6.0 && <0.11,
114 bytestring >=0.10.6.0 && <0.11,
115+ exceptions >=0.8 && <0.11,
101− lens >=3.8.5 && <4.16,
116+ lens >=3.8.5 && <5.1,
102− primitive >=0.6.1.0 && <0.7,
117+ primitive >=0.6.1.0 && <0.8,
103 text >=1.2.1.3 && <1.3,
118 text >=1.2.1.3 && <1.3,
104 transformers >=0.4.2.0 && <0.6
119 transformers >=0.4.2.0 && <0.6
105 default-language: Haskell2010
120 default-language: Haskell2010
106 hs-source-dirs: src
121 hs-source-dirs: src
107 ghc-options: -ferror-spans -Wall
122 ghc-options: -ferror-spans -Wall
108−
123+ if flag(check-unchecked)
109−
124+ cpp-options: -DCHECK_UNCHECKED
125+ if flag(call-stacks) || flag(check-unchecked)
126+ cpp-options: -DJSADDLE_HAS_CALL_STACK
jsaddle/src-ghc/Data/JSString/Internal/Type.hs+2−1
@@ -38,10 +38,11 @@ import Data.Aeson (ToJSON(..), FromJSON(..))
38import Data.Data (Data)
38import Data.Data (Data)
39-- import Data.Text.Internal.Unsafe.Char (ord)
39-- import Data.Text.Internal.Unsafe.Char (ord)
40import Data.Typeable (Typeable)
40import Data.Typeable (Typeable)
41+import Data.Semigroup (Semigroup)
41import GHC.Exts (Char(..), ord#, andI#, (/=#), isTrue#)
42import GHC.Exts (Char(..), ord#, andI#, (/=#), isTrue#)
42
43
43-- | A wrapper around a JavaScript string
44-- | 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)
45
46
46instance NFData JSString where rnf !_ = ()
47instance NFData JSString where rnf !_ = ()
47
48
jsaddle/src-ghc/GHCJS/Buffer.hs+2−2
@@ -52,7 +52,7 @@ getInt32Array (SomeBuffer buf) = GHCJSPure $ I.SomeTypedArray <$> buf ^. js "i3"
52{-# INLINE getInt32Array #-}
52{-# INLINE getInt32Array #-}
53
53
54getUint8Array :: SomeBuffer any -> GHCJSPure (I.SomeUint8Array any)
54getUint8Array :: 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"
56{-# INLINE getUint8Array #-}
56{-# INLINE getUint8Array #-}
57
57
58getUint16Array :: SomeBuffer any -> GHCJSPure (I.SomeUint16Array any)
58getUint16Array :: SomeBuffer any -> GHCJSPure (I.SomeUint16Array any)
@@ -85,7 +85,7 @@ clone = js_clone
85
85
86fromByteString :: ByteString -> GHCJSPure (Buffer, Int, Int)
86fromByteString :: ByteString -> GHCJSPure (Buffer, Int, Int)
87fromByteString bs = GHCJSPure $ do
87fromByteString bs = GHCJSPure $ do
88− buffer <- SomeBuffer <$> jsg1 "h$newByteArrayBase64String" (decodeUtf8 $ B64.encode bs)
88+ buffer <- SomeBuffer <$> jsg1 "h$newByteArrayFromBase64String" (decodeUtf8 $ B64.encode bs)
89 return (buffer, 0, BS.length bs)
89 return (buffer, 0, BS.length bs)
90{-# INLINE fromByteString #-}
90{-# INLINE fromByteString #-}
91
91
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
27 (valueToBool)
27 (valueToBool)
28import Data.Typeable (Typeable)
28import Data.Typeable (Typeable)
29import GHCJS.Prim (isNull, isUndefined)
29import GHCJS.Prim (isNull, isUndefined)
30+import System.IO.Unsafe (unsafePerformIO)
31+import Data.IORef (newIORef)
30
32
31jsTrue :: JSVal
33jsTrue :: JSVal
32−jsTrue = JSVal 3
34+jsTrue = JSVal . unsafePerformIO $ newIORef 3
33−{-# INLINE jsTrue #-}
35+{-# NOINLINE jsTrue #-}
34
36
35jsFalse :: JSVal
37jsFalse :: JSVal
36−jsFalse = JSVal 2
38+jsFalse = JSVal . unsafePerformIO $ newIORef 2
37−{-# INLINE jsFalse #-}
39+{-# NOINLINE jsFalse #-}
38
40
39jsNull :: JSVal
41jsNull :: JSVal
40−jsNull = JSVal 0
42+jsNull = JSVal . unsafePerformIO $ newIORef 0
41−{-# INLINE jsNull #-}
43+{-# NOINLINE jsNull #-}
42
44
43toJSBool :: Bool -> JSVal
45toJSBool :: Bool -> JSVal
44−toJSBool b = JSVal $ if b then 3 else 2
46+toJSBool b = JSVal . unsafePerformIO . newIORef $ if b then 3 else 2
45−{-# INLINE toJSBool #-}
47+{-# NOINLINE toJSBool #-}
46
48
47jsUndefined :: JSVal
49jsUndefined :: JSVal
48−jsUndefined = JSVal 1
50+jsUndefined = JSVal . unsafePerformIO $ newIORef 1
49−{-# INLINE jsUndefined #-}
51+{-# NOINLINE jsUndefined #-}
50
52
51isTruthy :: JSVal -> GHCJSPure Bool
53isTruthy :: JSVal -> GHCJSPure Bool
52isTruthy = GHCJSPure . valueToBool
54isTruthy = GHCJSPure . valueToBool
jsaddle/src-ghc/GHCJS/Marshal/Internal.hs+21−2
@@ -1,4 +1,4 @@
1−{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable, DefaultSignatures,
1+{-# LANGUAGE CPP, ScopedTypeVariables, DeriveDataTypeable, DefaultSignatures,
2 TypeOperators, TupleSections, FlexibleContexts, FlexibleInstances,
2 TypeOperators, TupleSections, FlexibleContexts, FlexibleInstances,
3 LambdaCase
3 LambdaCase
4 #-}
4 #-}
@@ -34,7 +34,7 @@ import qualified Data.JSString.Internal.Type as JSS
34import qualified JavaScript.Object.Internal as OI (Object(..), create, setProp, getProp)
34import qualified JavaScript.Object.Internal as OI (Object(..), create, setProp, getProp)
35import qualified JavaScript.Array.Internal as AI (SomeJSArray(..), create, push, read, fromListIO, toListIO)
35import qualified JavaScript.Array.Internal as AI (SomeJSArray(..), create, push, read, fromListIO, toListIO)
36
36
37−import Language.Javascript.JSaddle.Types (JSM, MutableJSArray, GHCJSPure(..), ghcjsPure, ghcjsPureMap)
37+import Language.Javascript.JSaddle.Types (JSM, MutableJSArray, GHCJSPure(..), ghcjsPure, ghcjsPureMap, JSadddleHasCallStack)
38import Language.Javascript.JSaddle.String (textToStr)
38import Language.Javascript.JSaddle.String (textToStr)
39
39
40data Purity = PureShared -- ^ conversion is pure even if the original value is shared
40data Purity = PureShared -- ^ conversion is pure even if the original value is shared
@@ -61,17 +61,36 @@ class ToJSVal a where
61 default toJSVal :: (Generic a, GToJSVal (Rep a ())) => a -> JSM JSVal
61 default toJSVal :: (Generic a, GToJSVal (Rep a ())) => a -> JSM JSVal
62 toJSVal = toJSVal_generic id
62 toJSVal = toJSVal_generic id
63
63
64+fromJustWithStack :: JSadddleHasCallStack => Maybe a -> a
65+fromJustWithStack Nothing = error "fromJSValUnchecked: fromJSVal result was Nothing"
66+fromJustWithStack (Just x) = x
67+
64class FromJSVal a where
68class FromJSVal a where
65 fromJSVal :: JSVal -> JSM (Maybe a)
69 fromJSVal :: JSVal -> JSM (Maybe a)
66
70
71+#if MIN_VERSION_base(4,9,0) && defined(JSADDLE_HAS_CALL_STACK)
72+ fromJSValUnchecked :: JSadddleHasCallStack => JSVal -> JSM a
73+#ifdef CHECK_UNCHECKED
74+ fromJSValUnchecked v = fromJSVal v >>= \case
75+ Nothing -> error "fromJSValUnchecked: fromJSVal result was Nothing"
76+ Just x -> return x
77+#else
78+ fromJSValUnchecked = fmap fromJustWithStack . fromJSVal
79+#endif
80+#else
67 fromJSValUnchecked :: JSVal -> JSM a
81 fromJSValUnchecked :: JSVal -> JSM a
68 fromJSValUnchecked = fmap fromJust . fromJSVal
82 fromJSValUnchecked = fmap fromJust . fromJSVal
83+#endif
69 {-# INLINE fromJSValUnchecked #-}
84 {-# INLINE fromJSValUnchecked #-}
70
85
71 fromJSValListOf :: JSVal -> JSM (Maybe [a])
86 fromJSValListOf :: JSVal -> JSM (Maybe [a])
72 fromJSValListOf = fmap sequence . (mapM fromJSVal <=< AI.toListIO . coerce) -- fixme should check that it's an array
87 fromJSValListOf = fmap sequence . (mapM fromJSVal <=< AI.toListIO . coerce) -- fixme should check that it's an array
73
88
89+#if MIN_VERSION_base(4,9,0) && defined(JSADDLE_HAS_CALL_STACK)
90+ fromJSValUncheckedListOf :: JSadddleHasCallStack => JSVal -> JSM [a]
91+#else
74 fromJSValUncheckedListOf :: JSVal -> JSM [a]
92 fromJSValUncheckedListOf :: JSVal -> JSM [a]
93+#endif
75 fromJSValUncheckedListOf = mapM fromJSValUnchecked <=< AI.toListIO . coerce
94 fromJSValUncheckedListOf = mapM fromJSValUnchecked <=< AI.toListIO . coerce
76
95
77 -- default fromJSVal :: PFromJSVal a => JSVal a -> JSM (Maybe a)
96 -- default fromJSVal :: PFromJSVal a => JSVal a -> JSM (Maybe a)
jsaddle/src-ghc/GHCJS/Prim/Internal.hs+5−3
@@ -17,6 +17,8 @@ import Unsafe.Coerce (unsafeCoerce)
17import Data.Aeson (ToJSON(..), FromJSON(..))
17import Data.Aeson (ToJSON(..), FromJSON(..))
18
18
19import qualified GHC.Exception as Ex
19import qualified GHC.Exception as Ex
20+import Data.IORef (newIORef, IORef)
21+import System.IO.Unsafe (unsafePerformIO)
20
22
21-- A reference to a particular JavaScript value inside the JavaScript context
23-- A reference to a particular JavaScript value inside the JavaScript context
22type JSValueRef = Int64
24type JSValueRef = Int64
@@ -25,7 +27,7 @@ type JSValueRef = Int64
25 JSVal is a boxed type that can be used as FFI
27 JSVal is a boxed type that can be used as FFI
26 argument or result.
28 argument or result.
27-}
29-}
28−newtype JSVal = JSVal JSValueRef deriving(Show, ToJSON, FromJSON)
30+newtype JSVal = JSVal (IORef JSValueRef)
29
31
30instance NFData JSVal where
32instance NFData JSVal where
31 rnf x = x `seq` ()
33 rnf x = x `seq` ()
@@ -49,8 +51,8 @@ mkJSException ref =
49 return (JSException (unsafeCoerce ref) "")
51 return (JSException (unsafeCoerce ref) "")
50
52
51jsNull :: JSVal
53jsNull :: JSVal
52−jsNull = JSVal 0
54+jsNull = JSVal . unsafePerformIO $ newIORef 0
53−{-# INLINE jsNull #-}
55+{-# NOINLINE jsNull #-}
54
56
55{- | If a synchronous thread tries to do something that can only
57{- | If a synchronous thread tries to do something that can only
56 be done asynchronously, and the thread is set up to not
58 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
27import GHC.Types
27import GHC.Types
28import GHC.Prim
28import GHC.Prim
29import GHC.Ptr
29import GHC.Ptr
30+import GHC.IORef
31+import GHC.IO.Unsafe (unsafePerformIO)
30
32
31import Control.DeepSeq
33import Control.DeepSeq
32import Unsafe.Coerce
34import Unsafe.Coerce
33
35
34−type Ref# = Int64
36+type Ref# = IORef Int64
35
37
36mkRef :: Ref# -> JSVal
38mkRef :: Ref# -> JSVal
37mkRef = JSVal
39mkRef = JSVal
38{-# INLINE mkRef #-}
40{-# INLINE mkRef #-}
39
41
40nullRef :: JSVal
42nullRef :: JSVal
41−nullRef = JSVal 0
43+nullRef = JSVal . unsafePerformIO $ newIORef 0
42−{-# INLINE nullRef #-}
44+{-# NOINLINE nullRef #-}
43
45
44--toPtr :: JSVal -> Ptr a
46--toPtr :: JSVal -> Ptr a
45--toPtr (JSVal x) = unsafeCoerce (Ptr' x 0#)
47--toPtr (JSVal x) = unsafeCoerce (Ptr' x 0#)
jsaddle/src-ghc/JavaScript/Array.hs+2−2
@@ -48,7 +48,7 @@ import Language.Javascript.JSaddle.Types
48 (JSM, JSVal, SomeJSArray(..), JSArray,
48 (JSM, JSVal, SomeJSArray(..), JSArray,
49 MutableJSArray, GHCJSPure(..))
49 MutableJSArray, GHCJSPure(..))
50import Control.Lens.Operators ((^.))
50import Control.Lens.Operators ((^.))
51−import Language.Javascript.JSaddle.Object (js2, js0, (##), js1, js)
51+import Language.Javascript.JSaddle.Object (js2, js0, (<##), js1, js)
52import Language.Javascript.JSaddle.Value (valToNumber)
52import Language.Javascript.JSaddle.Value (valToNumber)
53import JavaScript.Array.Internal (create, fromList, fromListIO, toList, toListIO, index, read, push)
53import JavaScript.Array.Internal (create, fromList, fromListIO, toList, toListIO, index, read, push)
54
54
@@ -69,7 +69,7 @@ append (SomeJSArray x) (SomeJSArray y) = SomeJSArray <$> x ^. js1 "concat" y
69{-# INLINE append #-}
69{-# INLINE append #-}
70
70
71write :: Int -> JSVal -> MutableJSArray -> JSM ()
71write :: Int -> JSVal -> MutableJSArray -> JSM ()
72−write n e (SomeJSArray x) = void $ (x ## n) e
72+write n e (SomeJSArray x) = void $ (x <## n) e
73{-# INLINE write #-}
73{-# INLINE write #-}
74
74
75pop :: MutableJSArray -> JSM JSVal
75pop :: MutableJSArray -> JSM JSVal
jsaddle/src-ghc/JavaScript/Array/Internal.hs−2
@@ -21,8 +21,6 @@ import Data.JSString.Internal.Type (JSString(..))
21import Language.Javascript.JSaddle.Types (JSM, SomeJSArray(..), JSArray, MutableJSArray, STJSArray, Object(..), GHCJSPure(..))
21import Language.Javascript.JSaddle.Types (JSM, SomeJSArray(..), JSArray, MutableJSArray, STJSArray, Object(..), GHCJSPure(..))
22import Language.Javascript.JSaddle.Native.Internal
22import Language.Javascript.JSaddle.Native.Internal
23 (newArray, getPropertyByName, getPropertyAtIndex, callAsFunction, valueToNumber)
23 (newArray, getPropertyByName, getPropertyAtIndex, callAsFunction, valueToNumber)
24−import Language.Javascript.JSaddle.Run
25− (Command(..), Result(..), AsyncCommand(..), sendCommand, sendLazyCommand)
26
24
27create :: JSM MutableJSArray
25create :: JSM MutableJSArray
28create = SomeJSArray <$> newArray []
26create = SomeJSArray <$> newArray []
jsaddle/src/Language/Javascript/JSaddle.hs+5−5
@@ -9,7 +9,7 @@
9-- | This package provides an EDSL for calling JavaScript that
9-- | This package provides an EDSL for calling JavaScript that
10-- can be used both from GHCJS and GHC. When using GHC
10-- can be used both from GHCJS and GHC. When using GHC
11-- the application is run using Warp and WebSockets to
11-- the application is run using Warp and WebSockets to
12−-- drive a small JavaScipt helper.
12+-- drive a small JavaScript helper.
13-----------------------------------------------------------------------------
13-----------------------------------------------------------------------------
14
14
15module Language.Javascript.JSaddle (
15module Language.Javascript.JSaddle (
@@ -35,10 +35,10 @@ module Language.Javascript.JSaddle (
35 --
35 --
36 -- Where it makes sense code examples are given in two forms. One
36 -- Where it makes sense code examples are given in two forms. One
37 -- that uses 'eval' to run a purely JavaScript version and one that
37 -- 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.
39
39
40 -- * Calling Haskell from JavaScript
40 -- * 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
42 -- convert a Haskell function in the JSM monad into a javascript
42 -- convert a Haskell function in the JSM monad into a javascript
43 -- value.
43 -- value.
44
44
@@ -52,8 +52,8 @@ module Language.Javascript.JSaddle (
52 -- and the server will run the Haskell code. The JSaddle parts will
52 -- and the server will run the Haskell code. The JSaddle parts will
53 -- be executed by sending commands back to the browser.
53 -- be executed by sending commands back to the browser.
54
54
55− -- Although the code JavaScript code is executed in the the strict order
55+ -- Although the JavaScript code is executed in the strict order
56− -- set out by the EDSL it done asynchonously to the Haskell code.
56+ -- set out by the EDSL it is done asynchronously to the Haskell code.
57 -- This improves the performance by reducing the number of round trips
57 -- This improves the performance by reducing the number of round trips
58 -- needed between the Haskell and JavaScript code.
58 -- needed between the Haskell and JavaScript code.
59
59
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)
50 -> JSM JSVal
50 -> JSM JSVal
51#ifdef ghcjs_HOST_OS
51#ifdef ghcjs_HOST_OS
52eval script = liftIO $ js_eval (toJSString script)
52eval script = liftIO $ js_eval (toJSString script)
53−foreign import javascript unsafe "$r = eval($1);"
53+foreign import javascript safe "$r = eval($1);"
54 js_eval :: JSString -> IO JSVal
54 js_eval :: JSString -> IO JSVal
55#else
55#else
56eval = evaluateScript . toJSString
56eval = evaluateScript . toJSString
jsaddle/src/Language/Javascript/JSaddle/Monad.hs+3−26
@@ -38,13 +38,12 @@ module Language.Javascript.JSaddle.Monad (
38 , bracket
38 , bracket
39) where
39) where
40
40
41−import Prelude hiding (read)
42#ifndef ghcjs_HOST_OS
41#ifndef ghcjs_HOST_OS
43−import Control.Monad.Trans.Reader (runReaderT, ask, ReaderT(..))
42+import Control.Monad.Trans.Reader (runReaderT, ask)
44#endif
43#endif
45import Control.Monad.IO.Class (MonadIO(..))
44import Control.Monad.IO.Class (MonadIO(..))
46−import qualified Control.Exception as E (Exception, catch, bracket)
45+import Control.Monad.Catch (catch, bracket)
47−import Language.Javascript.JSaddle.Types (JSM(..), MonadJSM, liftJSM, JSContextRef(..))
46+import Language.Javascript.JSaddle.Types (JSM(..), MonadJSM, liftJSM, JSContextRef)
48import Language.Javascript.JSaddle.Run (syncPoint, syncAfter, waitForAnimationFrame, nextAnimationFrame)
47import Language.Javascript.JSaddle.Run (syncPoint, syncAfter, waitForAnimationFrame, nextAnimationFrame)
49
48
50-- | Gets the JavaScript context from the monad
49-- | Gets the JavaScript context from the monad
@@ -66,25 +65,3 @@ runJSM f = liftIO . runReaderT (unJSM f)
66-- | Alternative version of 'runJSM'
65-- | Alternative version of 'runJSM'
67runJSaddle :: MonadIO m => JSContextRef -> JSM a -> m a
66runJSaddle :: MonadIO m => JSContextRef -> JSM a -> m a
68runJSaddle = flip runJSM
67runJSaddle = 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 #-}
1-----------------------------------------------------------------------------
4-----------------------------------------------------------------------------
2--
5--
3-- Module : Language.Javascript.JSaddle.Native
6-- Module : Language.Javascript.JSaddle.Native
@@ -27,7 +30,8 @@ module Language.Javascript.JSaddle.Native.Internal (
27 , callAsFunction
30 , callAsFunction
28 , callAsConstructor
31 , callAsConstructor
29 , newEmptyObject
32 , newEmptyObject
30− , newCallback
33+ , newAsyncCallback
34+ , newSyncCallback
31 , newArray
35 , newArray
32 , evaluateScript
36 , evaluateScript
33 , deRefVal
37 , deRefVal
@@ -44,7 +48,6 @@ module Language.Javascript.JSaddle.Native.Internal (
44) where
48) where
45
49
46import Control.Monad.IO.Class (MonadIO(..))
50import Control.Monad.IO.Class (MonadIO(..))
47−import Control.Monad.Primitive (touch)
48
51
49import Data.Aeson (Value)
52import Data.Aeson (Value)
50
53
@@ -54,32 +57,36 @@ import Language.Javascript.JSaddle.Types
54 JSStringReceived(..), JSStringForSend(..), JSObjectForSend(..))
57 JSStringReceived(..), JSStringForSend(..), JSObjectForSend(..))
55import Language.Javascript.JSaddle.Monad (askJSM)
58import Language.Javascript.JSaddle.Monad (askJSM)
56import Language.Javascript.JSaddle.Run
59import Language.Javascript.JSaddle.Run
57− (Command(..), AsyncCommand(..), Result(..), sendCommand,
60+ (Command(..), Result(..), sendCommand,
58 sendAsyncCommand, sendLazyCommand, wrapJSVal)
61 sendAsyncCommand, sendLazyCommand, wrapJSVal)
62+import GHC.IORef (IORef(..), readIORef)
63+import GHC.STRef (STRef(..))
64+import GHC.IO (IO(..))
65+import GHC.Base (touch#)
59
66
60wrapJSString :: MonadIO m => JSStringReceived -> m JSString
67wrapJSString :: MonadIO m => JSStringReceived -> m JSString
61wrapJSString (JSStringReceived ref) = return $ JSString ref
68wrapJSString (JSStringReceived ref) = return $ JSString ref
62
69
70+touchIORef :: IORef a -> IO ()
71+touchIORef (IORef (STRef r#)) = IO $ \s -> case touch# r# s of s' -> (# s', () #)
72+
63withJSVal :: MonadIO m => JSVal -> (JSValueForSend -> m a) -> m a
73withJSVal :: MonadIO m => JSVal -> (JSValueForSend -> m a) -> m a
64−withJSVal v@(JSVal ref) f =
74+withJSVal (JSVal ref) f = do
65− do result <- f (JSValueForSend ref)
75+ result <- (f . JSValueForSend) =<< liftIO (readIORef ref)
66− liftIO $ touch v
76+ liftIO $ touchIORef ref
67 return result
77 return result
68
78
69withJSVals :: MonadIO m => [JSVal] -> ([JSValueForSend] -> m a) -> m a
79withJSVals :: MonadIO m => [JSVal] -> ([JSValueForSend] -> m a) -> m a
70withJSVals v f =
80withJSVals v f =
71− do result <- f (map (\(JSVal ref) -> JSValueForSend ref) v)
81+ do result <- f =<< mapM (\(JSVal ref) -> liftIO $ JSValueForSend <$> readIORef ref) v
72− liftIO $ mapM_ touch v
82+ liftIO $ mapM_ (\(JSVal ref) -> touchIORef ref) v
73 return result
83 return result
74
84
75withObject :: MonadIO m => Object -> (JSObjectForSend -> m a) -> m a
85withObject :: MonadIO m => Object -> (JSObjectForSend -> m a) -> m a
76withObject (Object o) f = withJSVal o (f . JSObjectForSend)
86withObject (Object o) f = withJSVal o (f . JSObjectForSend)
77
87
78withJSString :: MonadIO m => JSString -> (JSStringForSend -> m a) -> m a
88withJSString :: MonadIO m => JSString -> (JSStringForSend -> m a) -> m a
79−withJSString v@(JSString ref) f =
89+withJSString (JSString ref) f = f (JSStringForSend ref)
80− do result <- f (JSStringForSend ref)
81− liftIO $ touch v
82− return result
83
90
84setPropertyByName :: JSString -> JSVal -> Object -> JSM ()
91setPropertyByName :: JSString -> JSVal -> Object -> JSM ()
85setPropertyByName name val this =
92setPropertyByName name val this =
@@ -136,13 +143,21 @@ newEmptyObject :: JSM Object
136newEmptyObject = Object <$> sendLazyCommand NewEmptyObject
143newEmptyObject = Object <$> sendLazyCommand NewEmptyObject
137{-# INLINE newEmptyObject #-}
144{-# INLINE newEmptyObject #-}
138
145
139−newCallback :: JSCallAsFunction -> JSM Object
146+newAsyncCallback :: JSCallAsFunction -> JSM Object
140−newCallback f = do
147+newAsyncCallback f = do
141− object <- Object <$> sendLazyCommand NewCallback
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
142 add <- addCallback <$> askJSM
157 add <- addCallback <$> askJSM
143 liftIO $ add object f
158 liftIO $ add object f
144 return object
159 return object
145−{-# INLINE newCallback #-}
160+{-# INLINE newSyncCallback #-}
146
161
147newArray :: [JSVal] -> JSM JSVal
162newArray :: [JSVal] -> JSM JSVal
148newArray xs = withJSVals xs $ \xs' -> sendLazyCommand (NewArray xs')
163newArray xs = withJSVals xs $ \xs' -> sendLazyCommand (NewArray xs')
@@ -157,13 +172,14 @@ deRefVal value = withJSVal value $ sendCommand . DeRefVal
157{-# INLINE deRefVal #-}
172{-# INLINE deRefVal #-}
158
173
159valueToBool :: JSVal -> JSM Bool
174valueToBool :: JSVal -> JSM Bool
175+valueToBool v@(JSVal ref) = liftIO (readIORef ref) >>= \case
160−valueToBool (JSVal 0) = return False -- null
176+ 0 -> return False -- null
161−valueToBool (JSVal 1) = return False -- undefined
177+ 1 -> return False -- undefined
162−valueToBool (JSVal 2) = return False -- false
178+ 2 -> return False -- false
163−valueToBool (JSVal 3) = return True -- true
179+ 3 -> return True -- true
164−valueToBool v = withJSVal v $ \rval -> do
180+ _ -> withJSVal v $ \rval -> do
165− ~(ValueToBoolResult result) <- sendCommand (ValueToBool rval)
181+ ~(ValueToBoolResult result) <- sendCommand (ValueToBool rval)
166− return result
182+ return result
167{-# INLINE valueToBool #-}
183{-# INLINE valueToBool #-}
168
184
169valueToNumber :: JSVal -> JSM Double
185valueToNumber :: JSVal -> JSM Double
@@ -192,17 +208,25 @@ valueToJSONValue value = withJSVal value $ \rval -> do
192{-# INLINE valueToJSONValue #-}
208{-# INLINE valueToJSONValue #-}
193
209
194isNull :: JSVal -> JSM Bool
210isNull :: JSVal -> JSM Bool
211+isNull v@(JSVal ref) = liftIO (readIORef ref) >>= \case
195−isNull (JSVal 0) = return True
212+ 0 -> return True -- null
213+ 1 -> return False -- undefined
214+ 2 -> return False -- false
215+ 3 -> return False -- true
196−isNull v = withJSVal v $ \rval -> do
216+ _ -> withJSVal v $ \rval -> do
197− ~(IsNullResult result) <- sendCommand $ IsNull rval
217+ ~(IsNullResult result) <- sendCommand $ IsNull rval
198− return result
218+ return result
199{-# INLINE isNull #-}
219{-# INLINE isNull #-}
200
220
201isUndefined :: JSVal -> JSM Bool
221isUndefined :: JSVal -> JSM Bool
202−isUndefined (JSVal 1) = return True
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
203−isUndefined v = withJSVal v $ \rval -> do
227+ _ -> withJSVal v $ \rval -> do
204− ~(IsUndefinedResult result) <- sendCommand $ IsUndefined rval
228+ ~(IsUndefinedResult result) <- sendCommand $ IsUndefined rval
205− return result
229+ return result
206{-# INLINE isUndefined #-}
230{-# INLINE isUndefined #-}
207
231
208strictEqual :: JSVal -> JSVal -> JSM Bool
232strictEqual :: 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 (
66 -- * Calling Haskell From JavaScript
66 -- * Calling Haskell From JavaScript
67 , Function(..)
67 , Function(..)
68 , function
68 , function
69+ , asyncFunction
69 , freeFunction
70 , freeFunction
70 , fun
71 , fun
71 , JSCallAsFunction
72 , JSCallAsFunction
@@ -102,7 +103,7 @@ import Data.Coerce (coerce)
102#ifdef ghcjs_HOST_OS
103#ifdef ghcjs_HOST_OS
103import GHCJS.Types (nullRef)
104import GHCJS.Types (nullRef)
104import GHCJS.Foreign.Callback
105import GHCJS.Foreign.Callback
105− (releaseCallback, syncCallback2, OnBlocked(..), Callback)
106+ (releaseCallback, syncCallback2, asyncCallback2, OnBlocked(..), Callback)
106import GHCJS.Marshal (ToJSVal(..))
107import GHCJS.Marshal (ToJSVal(..))
107import JavaScript.Array (MutableJSArray)
108import JavaScript.Array (MutableJSArray)
108import qualified JavaScript.Array as Array (toListIO, fromListIO)
109import qualified JavaScript.Array as Array (toListIO, fromListIO)
@@ -115,12 +116,13 @@ import Language.Javascript.JSaddle.Types
115#else
116#else
116import GHCJS.Marshal.Internal (ToJSVal(..))
117import GHCJS.Marshal.Internal (ToJSVal(..))
117import Language.Javascript.JSaddle.Native
118import Language.Javascript.JSaddle.Native
118− (newCallback, callAsFunction, callAsConstructor)
119+ (newAsyncCallback, newSyncCallback, callAsFunction, callAsConstructor)
119import Language.Javascript.JSaddle.Monad (askJSM, JSM)
120import Language.Javascript.JSaddle.Monad (askJSM, JSM)
120import Language.Javascript.JSaddle.Types
121import Language.Javascript.JSaddle.Types
121− (JSString, Object(..), SomeJSArray(..),
122+ (JSValueForSend(..), AsyncCommand(..), JSString, Object(..),
122− JSVal(..), JSCallAsFunction, JSContextRef(..))
123+ SomeJSArray(..), JSVal(..), JSCallAsFunction, JSContextRef(..))
123import JavaScript.Object.Internal (create, listProps)
124import JavaScript.Object.Internal (create, listProps)
125+import Language.Javascript.JSaddle.Run (sendAsyncCommand)
124#endif
126#endif
125import JavaScript.Array.Internal (fromListIO)
127import JavaScript.Array.Internal (fromListIO)
126import Language.Javascript.JSaddle.Value (valToObject)
128import Language.Javascript.JSaddle.Value (valToObject)
@@ -130,6 +132,8 @@ import Language.Javascript.JSaddle.Arguments (MakeArgs(..))
130import Control.Monad.IO.Class (MonadIO(..))
132import Control.Monad.IO.Class (MonadIO(..))
131import Language.Javascript.JSaddle.Properties
133import Language.Javascript.JSaddle.Properties
132import Control.Lens (IndexPreservingGetter, to)
134import Control.Lens (IndexPreservingGetter, to)
135+import Data.IORef (newIORef, readIORef)
136+import System.IO.Unsafe (unsafePerformIO)
133
137
134-- $setup
138-- $setup
135-- >>> import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
139-- >>> import Control.Concurrent.MVar (newEmptyMVar, takeMVar, putMVar)
@@ -435,9 +439,16 @@ newtype Function = Function {functionObject :: Object}
435#endif
439#endif
436
440
437
441
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+
438-- ^ Make a JavaScript function object that wraps a Haskell function.
447-- ^ 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`).
439function :: JSCallAsFunction -- ^ Haskell function to call
450function :: 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
441 -- call the Haskell one when it is called
452 -- call the Haskell one when it is called
442#ifdef ghcjs_HOST_OS
453#ifdef ghcjs_HOST_OS
443function f = do
454function f = do
@@ -445,11 +456,26 @@ function f = do
445 rargs <- Array.toListIO (coerce args)
456 rargs <- Array.toListIO (coerce args)
446 f this this rargs -- TODO pass function object through
457 f this this rargs -- TODO pass function object through
447 Function callback <$> makeFunctionWithCallback callback
458 Function callback <$> makeFunctionWithCallback callback
448−foreign import javascript unsafe "$r = function () { $1(this, arguments); }"
449− makeFunctionWithCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO Object
450#else
459#else
451function f = do
460function 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
453 return $ Function object
479 return $ Function object
454#endif
480#endif
455
481
@@ -458,9 +484,16 @@ freeFunction :: Function -> JSM ()
458freeFunction (Function callback _) = liftIO $
484freeFunction (Function callback _) = liftIO $
459 releaseCallback callback
485 releaseCallback callback
460#else
486#else
461−freeFunction (Function object) = do
487+freeFunction (Function (Object (JSVal objectRef))) = do
462− free <- freeCallback <$> askJSM
488+ -- By now the callback should ideally have been removed from whatever
463− liftIO $ free object
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))
464#endif
497#endif
465
498
466instance ToJSVal Function where
499instance ToJSVal Function where
@@ -493,13 +526,12 @@ array args = do
493 Object . coerce <$> fromListIO rargs
526 Object . coerce <$> fromListIO rargs
494
527
495-- | JavaScript's global object
528-- | JavaScript's global object
496−global :: Object
497#ifdef ghcjs_HOST_OS
529#ifdef ghcjs_HOST_OS
498−global = js_window
499−foreign import javascript unsafe "$r = window"
530+foreign import javascript unsafe "$r = globalThis"
500− js_window :: Object
531+ global :: Object
501#else
532#else
502−global = Object (JSVal 4)
533+global :: Object
534+global = Object . JSVal . unsafePerformIO $ newIORef 4
503#endif
535#endif
504
536
505-- | Get a list containing the property names present on a given object
537-- | Get a list containing the property names present on a given object
@@ -524,7 +556,7 @@ objCallAsFunction :: MakeArgs args
524objCallAsFunction f this args = do
556objCallAsFunction f this args = do
525 rargs <- makeArgs args >>= liftIO . Array.fromListIO
557 rargs <- makeArgs args >>= liftIO . Array.fromListIO
526 liftIO $ js_apply f this rargs
558 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)"
528 js_apply :: Object -> Object -> MutableJSArray -> IO JSVal
560 js_apply :: Object -> Object -> MutableJSArray -> IO JSVal
529#else
561#else
530objCallAsFunction f this args = do
562objCallAsFunction f this args = do
@@ -544,7 +576,7 @@ objCallAsConstructor :: MakeArgs args
544objCallAsConstructor f args = do
576objCallAsConstructor f args = do
545 rargs <- makeArgs args >>= liftIO . Array.fromListIO
577 rargs <- makeArgs args >>= liftIO . Array.fromListIO
546 liftIO $ js_new f rargs
578 liftIO $ js_new f rargs
547−foreign import javascript unsafe "\
579+foreign import javascript safe "\
548 switch($2.length) {\
580 switch($2.length) {\
549 case 0 : $r = new $1(); break;\
581 case 0 : $r = new $1(); break;\
550 case 1 : $r = new $1($2[0]); break;\
582 case 1 : $r = new $1($2[0]); break;\
@@ -580,5 +612,5 @@ nullObject :: Object
580#ifdef ghcjs_HOST_OS
612#ifdef ghcjs_HOST_OS
581nullObject = Object nullRef
613nullObject = Object nullRef
582#else
614#else
583−nullObject = Object (JSVal 0)
615+nullObject = Object . JSVal . unsafePerformIO $ newIORef 0
584#endif
616#endif
jsaddle/src/Language/Javascript/JSaddle/Run.hs+166−71
@@ -1,6 +1,9 @@
1+{-# LANGUAGE BangPatterns #-}
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE CPP #-}
2{-# LANGUAGE LambdaCase #-}
3{-# LANGUAGE LambdaCase #-}
3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE MagicHash #-}
6+{-# LANGUAGE UnboxedTuples #-}
4-----------------------------------------------------------------------------
7-----------------------------------------------------------------------------
5--
8--
6-- Module : Language.Javascript.JSaddle.Run
9-- Module : Language.Javascript.JSaddle.Run
@@ -19,6 +22,7 @@ module Language.Javascript.JSaddle.Run (
19 , syncAfter
22 , syncAfter
20 , waitForAnimationFrame
23 , waitForAnimationFrame
21 , nextAnimationFrame
24 , nextAnimationFrame
25+ , enableLogging
22#ifndef ghcjs_HOST_OS
26#ifndef ghcjs_HOST_OS
23 -- * Functions used to implement JSaddle using JSON messaging
27 -- * Functions used to implement JSaddle using JSON messaging
24 , runJavaScript
28 , runJavaScript
@@ -33,58 +37,67 @@ module Language.Javascript.JSaddle.Run (
33) where
37) where
34
38
35#ifdef ghcjs_HOST_OS
39#ifdef ghcjs_HOST_OS
36−import Language.Javascript.JSaddle.Types (JSM)
40+import Language.Javascript.JSaddle.Types (JSM, syncPoint, syncAfter)
37import qualified JavaScript.Web.AnimationFrame as GHCJS
41import qualified JavaScript.Web.AnimationFrame as GHCJS
38 (waitForAnimationFrame)
42 (waitForAnimationFrame)
39#else
43#else
40−import Control.Exception (throwIO)
44+import Control.Exception (throwIO, evaluate)
41−import Control.Monad (void, when, forever, zipWithM_)
45+import Control.Monad (void, when, zipWithM_)
42import Control.Monad.IO.Class (MonadIO(..))
46import Control.Monad.IO.Class (MonadIO(..))
43import Control.Monad.Trans.Reader (ask, runReaderT)
47import Control.Monad.Trans.Reader (ask, runReaderT)
44import Control.Monad.STM (atomically)
48import Control.Monad.STM (atomically)
45−import Control.Concurrent (forkIO)
49+import Control.Concurrent (forkIO, myThreadId)
46import Control.Concurrent.STM.TChan
50import Control.Concurrent.STM.TChan
47 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
51 (tryReadTChan, TChan, readTChan, writeTChan, newTChanIO)
48import Control.Concurrent.STM.TVar
52import Control.Concurrent.STM.TVar
49− (writeTVar, readTVar, readTVarIO, modifyTVar, newTVarIO)
53+ (writeTVar, readTVar, readTVarIO, modifyTVar', newTVarIO)
50import Control.Concurrent.MVar
54import Control.Concurrent.MVar
51− (MVar, MVar, putMVar, takeMVar, newEmptyMVar)
55+ (tryTakeMVar, MVar, putMVar, takeMVar, newMVar, newEmptyMVar, readMVar, modifyMVar)
52
56
53import System.IO.Unsafe (unsafeInterleaveIO)
57import System.IO.Unsafe (unsafeInterleaveIO)
54import System.Mem.Weak (addFinalizer)
58import System.Mem.Weak (addFinalizer)
59+import System.Random
55
60
61+import GHC.Base (IO(..), mkWeak#)
62+import GHC.Conc (ThreadId(..))
56import Data.Monoid ((<>))
63import Data.Monoid ((<>))
57−import qualified Data.Text as T (unpack)
64+import qualified Data.Text as T (unpack, pack)
58−import qualified Data.Map as M (lookup, delete, insert, empty)
65+import qualified Data.Map as M (lookup, delete, insert, empty, size)
66+import qualified Data.Set as S (empty, member, insert, delete)
59import Data.Time.Clock (getCurrentTime,diffUTCTime)
67import Data.Time.Clock (getCurrentTime,diffUTCTime)
68+import Data.IORef
69+ (mkWeakIORef, newIORef, atomicWriteIORef, readIORef)
60
70
61import Language.Javascript.JSaddle.Types
71import Language.Javascript.JSaddle.Types
62− (Command(..), AsyncCommand(..), Result(..), Results(..), JSContextRef(..), JSVal(..),
72+ (Command(..), AsyncCommand(..), Result(..), BatchResults(..), Results(..), JSContextRef(..), JSVal(..),
63− Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
73+ Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..), syncPoint, syncAfter, sendCommand)
64import Language.Javascript.JSaddle.Exception (JSException(..))
74import Language.Javascript.JSaddle.Exception (JSException(..))
65−-- import Language.Javascript.JSaddle.Native.Internal (wrapJSVal)
66−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
79+import GHC.Stats (getGCStatsEnabled, getGCStats, GCStats(..))
80+#endif
81+import Data.Foldable (forM_)
67#endif
82#endif
68
83
69−-- | Forces execution of pending asyncronous code
70−syncPoint :: JSM ()
71−#ifdef ghcjs_HOST_OS
84+#ifndef ghcjs_HOST_OS
72−syncPoint = return ()
85+#if MIN_VERSION_base(4,11,0)
86+currentBytesUsed = gcdetails_live_bytes . gc
73#else
87#else
74−syncPoint = do
88+getRTSStatsEnabled = getGCStatsEnabled
75− SyncResult <- sendCommand Sync
89+getRTSStats = getGCStats
76− return ()
90+#endif
77#endif
91#endif
78
92
79−-- | Forces execution of pending asyncronous code after performing `f`
93+-- | Enable (or disable) JSaddle logging
80−syncAfter :: JSM a -> JSM a
94+enableLogging :: Bool -> JSM ()
81#ifdef ghcjs_HOST_OS
95#ifdef ghcjs_HOST_OS
82−syncAfter = id
96+enableLogging _ = return ()
83#else
97#else
84−syncAfter f = do
98+enableLogging v = do
85− result <- f
99+ f <- doEnableLogging <$> JSM ask
86− syncPoint
100+ liftIO $ f v
87− return result
88#endif
101#endif
89
102
90-- | On GHCJS this is 'JavaScript.Web.AnimationFrame.waitForAnimationFrame'.
103-- | On GHCJS this is 'JavaScript.Web.AnimationFrame.waitForAnimationFrame'.
@@ -112,17 +125,12 @@ nextAnimationFrame f = do
112 syncAfter (f t)
125 syncAfter (f t)
113
126
114#ifndef ghcjs_HOST_OS
127#ifndef ghcjs_HOST_OS
115−sendCommand :: Command -> JSM Result
116−sendCommand cmd = do
117− s <- doSendCommand <$> JSM ask
118− liftIO $ s cmd
119−
120sendLazyCommand :: (JSValueForSend -> AsyncCommand) -> JSM JSVal
128sendLazyCommand :: (JSValueForSend -> AsyncCommand) -> JSM JSVal
121sendLazyCommand cmd = do
129sendLazyCommand cmd = do
122 nextRefTVar <- nextRef <$> JSM ask
130 nextRefTVar <- nextRef <$> JSM ask
123 n <- liftIO . atomically $ do
131 n <- liftIO . atomically $ do
124 n <- subtract 1 <$> readTVar nextRefTVar
132 n <- subtract 1 <$> readTVar nextRefTVar
125− writeTVar nextRefTVar n
133+ writeTVar nextRefTVar $! n
126 return n
134 return n
127 s <- doSendAsyncCommand <$> JSM ask
135 s <- doSendAsyncCommand <$> JSM ask
128 liftIO $ s (cmd $ JSValueForSend n)
136 liftIO $ s (cmd $ JSValueForSend n)
@@ -133,52 +141,112 @@ sendAsyncCommand cmd = do
133 s <- doSendAsyncCommand <$> JSM ask
141 s <- doSendAsyncCommand <$> JSM ask
134 liftIO $ s cmd
142 liftIO $ s cmd
135
143
136−runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), IO ())
144+runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Results -> IO (), Results -> IO Batch, IO ())
137runJavaScript sendBatch entryPoint = do
145runJavaScript sendBatch entryPoint = do
146+ contextId' <- randomIO
138 startTime' <- getCurrentTime
147 startTime' <- getCurrentTime
139− recvChan <- newTChanIO
148+ recvMVar <- newEmptyMVar
149+ lastAsyncBatch <- newEmptyMVar
140 commandChan <- newTChanIO
150 commandChan <- newTChanIO
141 callbacks <- newTVarIO M.empty
151 callbacks <- newTVarIO M.empty
142 nextRef' <- newTVarIO 0
152 nextRef' <- newTVarIO 0
153+ finalizerThreads' <- newMVar S.empty
154+ animationFrameHandlers' <- newMVar []
155+ loggingEnabled <- newIORef False
156+ liveRefs' <- newMVar S.empty
143 let ctx = JSContextRef {
157 let ctx = JSContextRef {
158+ contextId = contextId'
144− startTime = startTime'
159+ , startTime = startTime'
145− , doSendCommand = \cmd -> cmd `deepseq` do
160+ , doSendCommand = \cmd -> cmd `deepseq` do
146− result <- newEmptyMVar
161+ result <- newEmptyMVar
147− atomically $ writeTChan commandChan (Right (cmd, result))
162+ atomically $ writeTChan commandChan (Right (cmd, result))
148− unsafeInterleaveIO $
163+ unsafeInterleaveIO $
149− takeMVar result >>= \case
164+ takeMVar result >>= \case
165+ (ThrowJSValue v) -> do
166+ jsval <- wrapJSVal' ctx v
150− (ThrowJSValue (JSValueReceived v)) -> throwIO $ JSException (JSVal v)
167+ throwIO $ JSException jsval
151− r -> return r
168+ r -> return r
152− , doSendAsyncCommand = \cmd -> cmd `deepseq` atomically (writeTChan commandChan $ Left cmd)
169+ , doSendAsyncCommand = \cmd -> cmd `deepseq` atomically (writeTChan commandChan $ Left cmd)
170+ , addCallback = \(Object (JSVal ioref)) cb -> do
171+ val <- readIORef ioref
153− , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar callbacks (M.insert val cb)
172+ atomically $ modifyTVar' callbacks (M.insert val cb)
154− , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar callbacks (M.delete val)
173+ , nextRef = nextRef'
155− , nextRef = nextRef'
174+ , doEnableLogging = atomicWriteIORef loggingEnabled
175+ , finalizerThreads = finalizerThreads'
176+ , animationFrameHandlers = animationFrameHandlers'
177+ , liveRefs = liveRefs'
156− }
178+ }
179+ processResults :: Bool -> Results -> IO ()
157− let processResults = \case
180+ processResults syncCallbacks = \case
158 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
181 (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
159− (Callback f this a) -> do
182+ (Callback n br (JSValueReceived fNumber) f this a) -> do
183+ putMVar recvMVar (n, br)
160− f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
184+ f' <- runReaderT (unJSM $ wrapJSVal f) ctx
161 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
185 this' <- runReaderT (unJSM $ wrapJSVal this) ctx
162 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
186 args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
187+ logInfo (("Call " <> show fNumber <> " ") <>)
163 (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
188 (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
164 Nothing -> liftIO $ putStrLn "Callback called after it was freed"
189 Nothing -> liftIO $ putStrLn "Callback called after it was freed"
190+ Just cb -> void . forkIO $ do
165− Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
191+ runReaderT (unJSM $ cb f' this' args) ctx
166− m -> atomically $ writeTChan recvChan m
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
206+ logInfo s =
207+ readIORef loggingEnabled >>= \case
208+ True -> do
209+ currentBytesUsedStr <- getRTSStatsEnabled >>= \case
210+ True -> show . currentBytesUsed <$> getRTSStats
211+ False -> return "??"
212+ cbCount <- M.size <$> readTVarIO callbacks
213+ putStrLn . s $ "M " <> currentBytesUsedStr <> " CB " <> show cbCount <> " "
214+ False -> return ()
215+ _ <- forkIO . numberForeverFromM_ 1 $ \nBatch ->
167− _ <- forkIO . forever $ readBatch commandChan >>= \case
216+ readBatch nBatch commandChan >>= \case
168− (batch, resultMVars) -> do
217+ (batch@(Batch cmds _ _), resultMVars) -> do
218+ logInfo (\x -> "Sync " <> x <> show (length cmds, last cmds))
219+ _ <- tryTakeMVar lastAsyncBatch
220+ putMVar lastAsyncBatch batch
169 sendBatch batch
221 sendBatch batch
170− atomically (readTChan recvChan) >>= \case
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
171− Success results -> zipWithM_ putMVar resultMVars results
227+ zipWithM_ putMVar resultMVars results
228+ forM_ callbacksToFree $ \(JSValueReceived val) ->
229+ atomically (modifyTVar' callbacks (M.delete val))
172− Failure results exception -> do
230+ (_, Failure callbacksToFree results exception err) -> do
173 -- The exception will only be rethrown in Haskell if/when one of the
231 -- The exception will only be rethrown in Haskell if/when one of the
174 -- missing results (if any) is evaluated.
232 -- missing results (if any) is evaluated.
175 putStrLn "A JavaScript exception was thrown! (may not reach Haskell code)"
233 putStrLn "A JavaScript exception was thrown! (may not reach Haskell code)"
234+ putStrLn err
176 zipWithM_ putMVar resultMVars $ results <> repeat (ThrowJSValue exception)
235 zipWithM_ putMVar resultMVars $ results <> repeat (ThrowJSValue exception)
177− _ -> error "Unexpected jsaddle results"
236+ forM_ callbacksToFree $ \(JSValueReceived val) ->
237+ atomically (modifyTVar' callbacks (M.delete val))
178− return (processResults, runReaderT (unJSM entryPoint) ctx)
238+ return (asyncResults, syncResults, runReaderT (unJSM entryPoint) ctx)
179 where
239 where
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
180− readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
248+ readBatch :: Int -> TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, [MVar Result])
181− readBatch chan = do
249+ readBatch nBatch chan = do
182 first <- atomically $ readTChan chan -- We want at least one command to send
250 first <- atomically $ readTChan chan -- We want at least one command to send
183 loop first ([], [])
251 loop first ([], [])
184 where
252 where
@@ -189,28 +257,55 @@ runJavaScript sendBatch entryPoint = do
189 let cmds = Right syncCmd:cmds'
257 let cmds = Right syncCmd:cmds'
190 resultMVars = resultMVar:resultMVars'
258 resultMVars = resultMVar:resultMVars'
191 atomically (tryReadTChan chan) >>= \case
259 atomically (tryReadTChan chan) >>= \case
192− Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
260+ Nothing -> return (Batch (reverse cmds) False nBatch, reverse resultMVars)
193 Just cmd -> loop cmd (cmds, resultMVars)
261 Just cmd -> loop cmd (cmds, resultMVars)
194 loop (Left asyncCmd) (cmds', resultMVars) = do
262 loop (Left asyncCmd) (cmds', resultMVars) = do
195 let cmds = Left asyncCmd:cmds'
263 let cmds = Left asyncCmd:cmds'
196 atomically (tryReadTChan chan) >>= \case
264 atomically (tryReadTChan chan) >>= \case
197− Nothing -> return (Batch (reverse cmds) False, reverse resultMVars)
265+ Nothing -> return (Batch (reverse cmds) False nBatch, reverse resultMVars)
198 Just cmd -> loop cmd (cmds, resultMVars)
266 Just cmd -> loop cmd (cmds, resultMVars)
199 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
267 -- When we have seen a SyncWithAnimationFrame command only a synchronous command should end the batch
200 loopAnimation :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
268 loopAnimation :: Either AsyncCommand (Command, MVar Result) -> ([Either AsyncCommand Command], [MVar Result]) -> IO (Batch, [MVar Result])
201 loopAnimation (Right (Sync, resultMVar)) (cmds, resultMVars) =
269 loopAnimation (Right (Sync, resultMVar)) (cmds, resultMVars) =
202− return (Batch (reverse (Right Sync:cmds)) True, reverse (resultMVar:resultMVars))
270+ return (Batch (reverse (Right Sync:cmds)) True nBatch, reverse (resultMVar:resultMVars))
203 loopAnimation (Right (syncCmd, resultMVar)) (cmds, resultMVars) =
271 loopAnimation (Right (syncCmd, resultMVar)) (cmds, resultMVars) =
204 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Right syncCmd:cmds, resultMVar:resultMVars)
272 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Right syncCmd:cmds, resultMVar:resultMVars)
205 loopAnimation (Left asyncCmd) (cmds, resultMVars) =
273 loopAnimation (Left asyncCmd) (cmds, resultMVars) =
206 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)
274 atomically (readTChan chan) >>= \cmd -> loopAnimation cmd (Left asyncCmd:cmds, resultMVars)
207
275
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+
208wrapJSVal :: JSValueReceived -> JSM JSVal
281wrapJSVal :: JSValueReceived -> JSM JSVal
282+wrapJSVal v = do
283+ ctx <- JSM ask
284+ liftIO $ wrapJSVal' ctx v
285+
286+wrapJSVal' :: JSContextRef -> JSValueReceived -> IO JSVal
209−wrapJSVal (JSValueReceived ref) = do
287+wrapJSVal' ctx (JSValueReceived n) = do
210− -- TODO make sure this ref has not already been wrapped (perhaps only in debug version)
288+ ref <- liftIO $ newIORef n
211− let result = JSVal ref
289+ when (n >= 5 || n < 0) $
212− when (ref >= 5) $ do
290+#ifdef JSADDLE_CHECK_WRAPJSVAL
213− ctx <- JSM ask
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
214− liftIO . addFinalizer result $ doSendAsyncCommand ctx $ FreeRef $ JSValueForSend ref
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)
215− return result
310+ return (JSVal ref)
216#endif
311#endif
jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+132−56
@@ -13,7 +13,6 @@
13
13
14module Language.Javascript.JSaddle.Run.Files (
14module Language.Javascript.JSaddle.Run.Files (
15 indexHtml
15 indexHtml
16− , jsaddleJs
17 , initState
16 , initState
18 , runBatch
17 , runBatch
19 , ghcjsHelpers
18 , ghcjsHelpers
@@ -31,25 +30,38 @@ indexHtml =
31 \</head>\n\
30 \</head>\n\
32 \<body>\n\
31 \<body>\n\
33 \</body>\n\
32 \</body>\n\
34− \<script src=\"jsaddle.js\"></script>\n\
33+ \<script src=\"/jsaddle.js\"></script>\n\
35 \</html>"
34 \</html>"
36
35
37initState :: ByteString
36initState :: ByteString
38initState = "\
37initState = "\
39 \ var jsaddle_values = new Map();\n\
38 \ var jsaddle_values = new Map();\n\
39+ \ var jsaddle_free = new Map();\n\
40 \ jsaddle_values.set(0, null);\n\
40 \ jsaddle_values.set(0, null);\n\
41 \ jsaddle_values.set(1, undefined);\n\
41 \ jsaddle_values.set(1, undefined);\n\
42 \ jsaddle_values.set(2, false);\n\
42 \ jsaddle_values.set(2, false);\n\
43 \ jsaddle_values.set(3, true);\n\
43 \ jsaddle_values.set(3, true);\n\
44− \ jsaddle_values.set(4, window);\n\
44+ \ jsaddle_values.set(4, globalThis);\n\
45 \ var jsaddle_index = 100;\n\
45 \ 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\
46 \"
50 \"
47
51
48−runBatch :: (ByteString -> ByteString) -> ByteString
52+runBatch :: (ByteString -> ByteString) -> Maybe (ByteString -> ByteString) -> ByteString
49−runBatch send = "\
53+runBatch send sendSync = "\
54+ \ var runBatch = function(firstBatch, initialSyncDepth) {\n\
50 \ var processBatch = function(timestamp) {\n\
55 \ var processBatch = function(timestamp) {\n\
56+ \ var batch = firstBatch;\n\
57+ \ var callbacksToFree = [];\n\
51− \ var results = [];\n\
58+ \ var results = [];\n\
59+ \ inCallback++;\n\
52− \ try {\n\
60+ \ try {\n\
61+ \ syncDepth = initialSyncDepth || 0;\n\
62+ \ for(;;){\n\
63+ \ if(batch[2] === expectedBatch) {\n\
64+ \ expectedBatch++;\n\
53 \ var nCommandsLength = batch[0].length;\n\
65 \ var nCommandsLength = batch[0].length;\n\
54 \ for (var nCommand = 0; nCommand != nCommandsLength; nCommand++) {\n\
66 \ for (var nCommand = 0; nCommand != nCommandsLength; nCommand++) {\n\
55 \ var cmd = batch[0][nCommand];\n\
67 \ var cmd = batch[0][nCommand];\n\
@@ -57,7 +69,15 @@ runBatch send = "\
57 \ var d = cmd.Left;\n\
69 \ var d = cmd.Left;\n\
58 \ switch (d.tag) {\n\
70 \ switch (d.tag) {\n\
59 \ case \"FreeRef\":\n\
71 \ case \"FreeRef\":\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\
60− \ jsaddle_values.delete(d.contents);\n\
79+ \ jsaddle_values.delete(refsToFree[nRef]);\n\
80+ \ jsaddle_free.delete(d.contents);\n\
61 \ break;\n\
81 \ break;\n\
62 \ case \"SetPropertyByName\":\n\
82 \ case \"SetPropertyByName\":\n\
63 \ jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);\n\
83 \ jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);\n\
@@ -73,6 +93,10 @@ runBatch send = "\
73 \ var n = d.contents[1];\n\
93 \ var n = d.contents[1];\n\
74 \ jsaddle_values.set(n, d.contents[0]);\n\
94 \ jsaddle_values.set(n, d.contents[0]);\n\
75 \ break;\n\
95 \ 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\
76 \ case \"GetPropertyByName\":\n\
100 \ case \"GetPropertyByName\":\n\
77 \ var n = d.contents[2];\n\
101 \ var n = d.contents[2];\n\
78 \ jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);\n\
102 \ jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);\n\
@@ -89,10 +113,12 @@ runBatch send = "\
89 \ var n = d.contents;\n\
113 \ var n = d.contents;\n\
90 \ jsaddle_values.set(n, {});\n\
114 \ jsaddle_values.set(n, {});\n\
91 \ break;\n\
115 \ break;\n\
92− \ case \"NewCallback\":\n\
116+ \ case \"NewAsyncCallback\":\n\
93 \ (function() {\n\
117 \ (function() {\n\
94 \ var nFunction = d.contents;\n\
118 \ var nFunction = d.contents;\n\
119+ \ var func = function() {\n\
120+ \ var nFunctionInFunc = ++jsaddle_index;\n\
95− \ jsaddle_values.set(nFunction, function() {\n\
121+ \ jsaddle_values.set(nFunctionInFunc, func);\n\
96 \ var nThis = ++jsaddle_index;\n\
122 \ var nThis = ++jsaddle_index;\n\
97 \ jsaddle_values.set(nThis, this);\n\
123 \ jsaddle_values.set(nThis, this);\n\
98 \ var args = [];\n\
124 \ var args = [];\n\
@@ -101,8 +127,41 @@ runBatch send = "\
101 \ jsaddle_values.set(nArg, arguments[i]);\n\
127 \ jsaddle_values.set(nArg, arguments[i]);\n\
102 \ args[i] = nArg;\n\
128 \ args[i] = nArg;\n\
103 \ }\n\
129 \ }\n\
104− \ " <> send "{\"tag\": \"Callback\", \"contents\": [nFunction, nThis, args]}" <> "\n\
130+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [lastResults[0], lastResults[1], nFunction, nFunctionInFunc, nThis, args]}" <> "\n\
131+ \ };\n\
132+ \ jsaddle_values.set(nFunction, func);\n\
105− \ })})();\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\
106 \ break;\n\
165 \ break;\n\
107 \ case \"CallAsFunction\":\n\
166 \ case \"CallAsFunction\":\n\
108 \ var n = d.contents[3];\n\
167 \ var n = d.contents[3];\n\
@@ -145,9 +204,15 @@ runBatch send = "\
145 \ jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));\n\
204 \ jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));\n\
146 \ break;\n\
205 \ break;\n\
147 \ case \"SyncWithAnimationFrame\":\n\
206 \ case \"SyncWithAnimationFrame\":\n\
148− \ var n = d.contents[0];\n\
207+ \ var n = d.contents;\n\
149 \ jsaddle_values.set(n, timestamp);\n\
208 \ jsaddle_values.set(n, timestamp);\n\
150 \ break;\n\
209 \ break;\n\
210+ \ case \"StartSyncBlock\":\n\
211+ \ syncDepth++;\n\
212+ \ break;\n\
213+ \ case \"EndSyncBlock\":\n\
214+ \ syncDepth--;\n\
215+ \ break;\n\
151 \ default:\n\
216 \ default:\n\
152 \ " <> send "{\"tag\": \"ProtocolError\", \"contents\": e.data}" <> "\n\
217 \ " <> send "{\"tag\": \"ProtocolError\", \"contents\": e.data}" <> "\n\
153 \ return;\n\
218 \ return;\n\
@@ -182,7 +247,7 @@ runBatch send = "\
182 \ (v === true ) ? [3, \"\"] :\n\
247 \ (v === true ) ? [3, \"\"] :\n\
183 \ (typeof v === \"number\") ? [-1, v.toString()] :\n\
248 \ (typeof v === \"number\") ? [-1, v.toString()] :\n\
184 \ (typeof v === \"string\") ? [-2, v]\n\
249 \ (typeof v === \"string\") ? [-2, v]\n\
185− \ : [n, \"\"];\n\
250+ \ : [-3, \"\"];\n\
186 \ results.push({\"tag\": \"DeRefValResult\", \"contents\": c});\n\
251 \ results.push({\"tag\": \"DeRefValResult\", \"contents\": c});\n\
187 \ break;\n\
252 \ break;\n\
188 \ case \"IsNull\":\n\
253 \ case \"IsNull\":\n\
@@ -210,55 +275,66 @@ runBatch send = "\
210 \ }\n\
275 \ }\n\
211 \ }\n\
276 \ }\n\
212 \ }\n\
277 \ }\n\
213− \ " <> send "{\"tag\": \"Success\", \"contents\": results}" <> "\n\
278+ \ if(syncDepth <= 0) {\n\
214− \ }\n\
279+ \ lastResults = [batch[2], {\"tag\": \"Success\", \"contents\": [callbacksToFree, results]}];\n\
215− \ catch (err) {\n\
280+ \ " <> send "{\"tag\": \"BatchResults\", \"contents\": [lastResults[0], lastResults[1]]}" <> "\n\
216− \ var n = ++jsaddle_index;\n\
281+ \ break;\n\
217− \ jsaddle_values.set(n, err);\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 ->
218− \ " <> send "{\"tag\": \"Failure\", \"contents\": [results,n]}" <> "\n\
308+ " " <> send "{\"tag\": \"Duplicate\", \"contents\": [batch[2], expectedBatch]}" <> "\n\
309+ \ break;\n"
310+ ) <>
311+ " }\n\
312+ \ }\n\
219 \ }\n\
313 \ }\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\
220 \ };\n\
329 \ };\n\
221− \ if(batch[1]) {\n\
330+ \ if(batch[1] && (initialSyncDepth || 0) === 0) {\n\
222 \ window.requestAnimationFrame(processBatch);\n\
331 \ window.requestAnimationFrame(processBatch);\n\
223 \ }\n\
332 \ }\n\
224 \ else {\n\
333 \ else {\n\
225 \ processBatch(window.performance ? window.performance.now() : null);\n\
334 \ processBatch(window.performance ? window.performance.now() : null);\n\
226 \ }\n\
335 \ }\n\
227− \"
336+ \ };\n\
228−
337+ \ runBatch(batch);\n\
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\
262 \"
338 \"
263
339
264ghcjsHelpers :: ByteString
340ghcjsHelpers :: ByteString
jsaddle/src/Language/Javascript/JSaddle/Types.hs+173−26
@@ -1,6 +1,5 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2#ifdef ghcjs_HOST_OS
2#ifdef ghcjs_HOST_OS
3−{-# LANGUAGE ConstraintKinds #-}
4{-# OPTIONS_GHC -Wno-dodgy-exports #-}
3{-# OPTIONS_GHC -Wno-dodgy-exports #-}
5#else
4#else
6{-# LANGUAGE TypeFamilies #-}
5{-# LANGUAGE TypeFamilies #-}
@@ -8,11 +7,14 @@
8{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9{-# LANGUAGE DeriveGeneric #-}
8{-# LANGUAGE DeriveGeneric #-}
10{-# LANGUAGE DataKinds #-}
9{-# LANGUAGE DataKinds #-}
11−{-# LANGUAGE KindSignatures #-}
12{-# LANGUAGE PolyKinds #-}
10{-# LANGUAGE PolyKinds #-}
13{-# LANGUAGE FlexibleContexts #-}
11{-# LANGUAGE FlexibleContexts #-}
14{-# LANGUAGE DefaultSignatures #-}
12{-# LANGUAGE DefaultSignatures #-}
13+{-# LANGUAGE ImplicitParams #-}
15#endif
14#endif
15+{-# LANGUAGE ConstraintKinds #-}
16+{-# LANGUAGE KindSignatures #-}
17+{-# LANGUAGE AllowAmbiguousTypes #-}
16-----------------------------------------------------------------------------
18-----------------------------------------------------------------------------
17--
19--
18-- Module : Language.Javascript.JSaddle.Types
20-- Module : Language.Javascript.JSaddle.Types
@@ -53,8 +55,17 @@ module Language.Javascript.JSaddle.Types (
53 , Nullable(..)
55 , Nullable(..)
54 , JSCallAsFunction
56 , JSCallAsFunction
55
57
56− -- * JavaScript Context Commands
58+ -- * Debugging
59+ , JSadddleHasCallStack
60+
61+ -- * Sync JSM
62+ , syncPoint
63+ , syncAfter
64+
57#ifndef ghcjs_HOST_OS
65#ifndef ghcjs_HOST_OS
66+ , sendCommand
67+
68+ -- * JavaScript Context Commands
58 , MutabilityType(..)
69 , MutabilityType(..)
59 , Mutable
70 , Mutable
60 , Immutable
71 , Immutable
@@ -69,6 +80,7 @@ module Language.Javascript.JSaddle.Types (
69 , Command(..)
80 , Command(..)
70 , Batch(..)
81 , Batch(..)
71 , Result(..)
82 , Result(..)
83+ , BatchResults(..)
72 , Results(..)
84 , Results(..)
73#endif
85#endif
74) where
86) where
@@ -83,14 +95,27 @@ import GHCJS.Nullable (Nullable(..))
83import GHCJS.Prim.Internal (JSVal(..), JSValueRef)
95import GHCJS.Prim.Internal (JSVal(..), JSValueRef)
84import Data.JSString.Internal.Type (JSString(..))
96import Data.JSString.Internal.Type (JSString(..))
85import Control.DeepSeq (NFData(..))
97import Control.DeepSeq (NFData(..))
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(..))
86−import Control.Monad.Trans.Reader (ReaderT(..))
105+import Control.Monad.Trans.Reader (ReaderT(..), ask)
106+import Control.Monad.Trans.RWS.Lazy as Lazy (RWST(..))
87−import Control.Monad.Trans.State.Lazy (StateT(..))
107+import Control.Monad.Trans.RWS.Strict as Strict (RWST(..))
108+import Control.Monad.Trans.State.Lazy as Lazy (StateT(..))
88−import qualified Control.Monad.Trans.State.Strict as Strict
109+import Control.Monad.Trans.State.Strict as Strict (StateT(..))
89− (StateT(..))
110+import Control.Monad.Trans.Writer.Lazy as Lazy (WriterT(..))
111+import Control.Monad.Trans.Writer.Strict as Strict (WriterT(..))
90import Control.Monad.Trans.Class (MonadTrans(..))
112import Control.Monad.Trans.Class (MonadTrans(..))
91import Control.Monad.Fix (MonadFix)
113import Control.Monad.Fix (MonadFix)
92import Control.Monad.Ref (MonadAtomicRef(..), MonadRef(..))
114import Control.Monad.Ref (MonadAtomicRef(..), MonadRef(..))
93import Control.Concurrent.STM.TVar (TVar)
115import Control.Concurrent.STM.TVar (TVar)
116+import Control.Concurrent.MVar (MVar)
117+import Data.Int (Int64)
118+import Data.Set (Set)
94import Data.Text (Text)
119import Data.Text (Text)
95import Data.Time.Clock (UTCTime(..))
120import Data.Time.Clock (UTCTime(..))
96import Data.Typeable (Typeable)
121import Data.Typeable (Typeable)
@@ -98,6 +123,14 @@ import Data.Coerce (coerce, Coercible)
98import Data.Aeson
123import Data.Aeson
99 (defaultOptions, genericToEncoding, ToJSON(..), FromJSON(..), Value)
124 (defaultOptions, genericToEncoding, ToJSON(..), FromJSON(..), Value)
100import GHC.Generics (Generic)
125import GHC.Generics (Generic)
126+import Control.Monad.IO.Unlift (MonadUnliftIO)
127+import qualified Control.Monad.Fail as Fail
128+#endif
129+
130+#if MIN_VERSION_base(4,9,0) && defined(CHECK_UNCHECKED)
131+import GHC.Stack (HasCallStack)
132+#else
133+import GHC.Exts (Constraint)
101#endif
134#endif
102
135
103-- | Identifies a JavaScript execution context.
136-- | Identifies a JavaScript execution context.
@@ -108,12 +141,16 @@ import GHC.Generics (Generic)
108type JSContextRef = ()
141type JSContextRef = ()
109#else
142#else
110data JSContextRef = JSContextRef {
143data JSContextRef = JSContextRef {
144+ contextId :: Int64
111− startTime :: UTCTime
145+ , startTime :: UTCTime
112− , doSendCommand :: Command -> IO Result
146+ , doSendCommand :: Command -> IO Result
113− , doSendAsyncCommand :: AsyncCommand -> IO ()
147+ , doSendAsyncCommand :: AsyncCommand -> IO ()
114− , addCallback :: Object -> JSCallAsFunction -> IO ()
148+ , addCallback :: Object -> JSCallAsFunction -> IO ()
115− , freeCallback :: Object -> IO ()
116− , nextRef :: TVar JSValueRef
149+ , nextRef :: TVar JSValueRef
150+ , doEnableLogging :: Bool -> IO ()
151+ , finalizerThreads :: MVar (Set Text)
152+ , animationFrameHandlers :: MVar [Double -> JSM ()]
153+ , liveRefs :: MVar (Set Int64)
117}
154}
118#endif
155#endif
119
156
@@ -129,9 +166,56 @@ data JSContextRef = JSContextRef {
129type JSM = IO
166type JSM = IO
130#else
167#else
131newtype JSM a = JSM { unJSM :: ReaderT JSContextRef IO a }
168newtype JSM a = JSM { unJSM :: ReaderT JSContextRef IO a }
132− 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 ()
200+#endif
201+
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
133#endif
211#endif
134
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
135
219
136-- | Type we can give to functions that are pure when using ghcjs, but
220-- | Type we can give to functions that are pure when using ghcjs, but
137-- live in JSM when using jsaddle.
221-- live in JSM when using jsaddle.
@@ -180,21 +264,64 @@ type MonadJSM = MonadIO
180#else
264#else
181class (Applicative m, MonadIO m) => MonadJSM m where
265class (Applicative m, MonadIO m) => MonadJSM m where
182 liftJSM' :: JSM a -> m a
266 liftJSM' :: JSM a -> m a
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)
183− {-# MINIMAL liftJSM' #-}
270+ {-# INLINE liftJSM' #-}
184
271
185instance MonadJSM JSM where
272instance MonadJSM JSM where
186 liftJSM' = id
273 liftJSM' = id
187 {-# INLINE liftJSM' #-}
274 {-# INLINE liftJSM' #-}
188
275
189−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
190 liftJSM' = lift . liftJSM'
285 liftJSM' = lift . liftJSM'
191 {-# INLINE liftJSM' #-}
286 {-# INLINE liftJSM' #-}
192
287
193−instance MonadJSM m => MonadJSM (StateT r m) where
288+instance (MonadJSM m) => MonadJSM (IdentityT m) where
194 liftJSM' = lift . liftJSM'
289 liftJSM' = lift . liftJSM'
195 {-# INLINE liftJSM' #-}
290 {-# INLINE liftJSM' #-}
196
291
292+instance (MonadJSM m) => MonadJSM (ListT m) where
293+ liftJSM' = lift . liftJSM'
294+ {-# INLINE liftJSM' #-}
295+
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+
197−instance MonadJSM m => MonadJSM (Strict.StateT r m) where
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
198 liftJSM' = lift . liftJSM'
325 liftJSM' = lift . liftJSM'
199 {-# INLINE liftJSM' #-}
326 {-# INLINE liftJSM' #-}
200
327
@@ -269,7 +396,7 @@ type MutableJSArray = SomeJSArray Mutable
269type STJSArray s = SomeJSArray (STMutable s)
396type STJSArray s = SomeJSArray (STMutable s)
270
397
271-- | See 'JavaScript.Object.Internal.Object'
398-- | See 'JavaScript.Object.Internal.Object'
272−newtype Object = Object JSVal deriving(Show, ToJSON, FromJSON)
399+newtype Object = Object JSVal
273
400
274-- | See 'GHCJS.Nullable.Nullable'
401-- | See 'GHCJS.Nullable.Nullable'
275newtype Nullable a = Nullable a
402newtype Nullable a = Nullable a
@@ -293,7 +420,8 @@ newtype JSStringForSend = JSStringForSend Text deriving(Show, ToJSON, FromJSON,
293instance NFData JSStringForSend
420instance NFData JSStringForSend
294
421
295-- | Command sent to a JavaScript context for execution asynchronously
422-- | Command sent to a JavaScript context for execution asynchronously
296−data AsyncCommand = FreeRef JSValueForSend
423+data AsyncCommand = FreeRef Text JSValueForSend
424+ | FreeRefs Text
297 | SetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
425 | SetPropertyByName JSObjectForSend JSStringForSend JSValueForSend
298 | SetPropertyAtIndex JSObjectForSend Int JSValueForSend
426 | SetPropertyAtIndex JSObjectForSend Int JSValueForSend
299 | StringToValue JSStringForSend JSValueForSend
427 | StringToValue JSStringForSend JSValueForSend
@@ -304,11 +432,15 @@ data AsyncCommand = FreeRef JSValueForSend
304 | CallAsFunction JSObjectForSend JSObjectForSend [JSValueForSend] JSValueForSend
432 | CallAsFunction JSObjectForSend JSObjectForSend [JSValueForSend] JSValueForSend
305 | CallAsConstructor JSObjectForSend [JSValueForSend] JSValueForSend
433 | CallAsConstructor JSObjectForSend [JSValueForSend] JSValueForSend
306 | NewEmptyObject JSValueForSend
434 | NewEmptyObject JSValueForSend
435+ | NewAsyncCallback JSValueForSend
436+ | NewSyncCallback JSValueForSend
307− | NewCallback JSValueForSend
437+ | FreeCallback JSValueForSend
308 | NewArray [JSValueForSend] JSValueForSend
438 | NewArray [JSValueForSend] JSValueForSend
309 | EvaluateScript JSStringForSend JSValueForSend
439 | EvaluateScript JSStringForSend JSValueForSend
310 | SyncWithAnimationFrame JSValueForSend
440 | SyncWithAnimationFrame JSValueForSend
441+ | StartSyncBlock
442+ | EndSyncBlock
311− deriving (Show, Generic)
443+ deriving (Show, Generic)
312
444
313instance ToJSON AsyncCommand where
445instance ToJSON AsyncCommand where
314 toEncoding = genericToEncoding defaultOptions
446 toEncoding = genericToEncoding defaultOptions
@@ -340,7 +472,7 @@ instance FromJSON Command
340instance NFData Command
472instance NFData Command
341
473
342-- | Batch of commands that can be sent together to the JavaScript context
474-- | Batch of commands that can be sent together to the JavaScript context
343−data Batch = Batch [Either AsyncCommand Command] Bool
475+data Batch = Batch [Either AsyncCommand Command] Bool Int
344 deriving (Show, Generic)
476 deriving (Show, Generic)
345
477
346instance ToJSON Batch where
478instance ToJSON Batch where
@@ -371,9 +503,18 @@ instance ToJSON Result where
371
503
372instance FromJSON Result
504instance FromJSON Result
373
505
374−data Results = Success [Result]
506+data BatchResults = Success [JSValueReceived] [Result]
375− | Failure [Result] JSValueReceived
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
376− | Callback JSValueReceived JSValueReceived [JSValueReceived]
517+ | Callback Int BatchResults JSValueReceived JSValueReceived JSValueReceived [JSValueReceived]
377 | ProtocolError Text
518 | ProtocolError Text
378 deriving (Show, Generic)
519 deriving (Show, Generic)
379
520
@@ -383,5 +524,11 @@ instance ToJSON Results where
383instance FromJSON Results
524instance FromJSON Results
384#endif
525#endif
385
526
527+-- | Like HasCallStack, but only when jsaddle cabal flag check-unchecked is set
528+#if MIN_VERSION_base(4,9,0) && defined(CHECK_UNCHECKED)
529+type JSadddleHasCallStack = HasCallStack
530+#else
531+type JSadddleHasCallStack = (() :: Constraint)
532+#endif
386
533
387
534
jsaddle/src/Language/Javascript/JSaddle/Value.hs+5−3
@@ -618,15 +618,17 @@ foreign import javascript unsafe "$r = ($1 === undefined)?0:\
618 (typeof $1===\"string\")?4:\
618 (typeof $1===\"string\")?4:\
619 (typeof $1===\"object\")?5:-1;" jsrefGetType :: JSVal -> Int
619 (typeof $1===\"object\")?5:-1;" jsrefGetType :: JSVal -> Int
620#else
620#else
621−deRefVal value = toJSVal value >>= N.deRefVal >>= \result -> return $
621+deRefVal value = do
622+ v <- toJSVal value
623+ result <- N.deRefVal v
622− case result of
624+ return $ case result of
623 DeRefValResult 0 _ -> ValNull
625 DeRefValResult 0 _ -> ValNull
624 DeRefValResult 1 _ -> ValUndefined
626 DeRefValResult 1 _ -> ValUndefined
625 DeRefValResult 2 _ -> ValBool False
627 DeRefValResult 2 _ -> ValBool False
626 DeRefValResult 3 _ -> ValBool True
628 DeRefValResult 3 _ -> ValBool True
627 DeRefValResult (-1) s -> ValNumber (read (T.unpack s))
629 DeRefValResult (-1) s -> ValNumber (read (T.unpack s))
628 DeRefValResult (-2) s -> ValString s
630 DeRefValResult (-2) s -> ValString s
629− DeRefValResult ref _ -> ValObject (Object (JSVal ref))
631+ DeRefValResult (-3) _ -> ValObject (Object v)
630 _ -> error "Unexpected result dereferencing JSaddle value"
632 _ -> error "Unexpected result dereferencing JSaddle value"
631#endif
633#endif
632
634
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+3−14
@@ -6,19 +6,8 @@ GHCVER=`ghc --numeric-version`
6
6
7JSADDLE_WARP_VERSION=`head -n2 jsaddle-warp/jsaddle-warp.cabal | tail -n1 | sed 's/[^0-9.]//g'`
7JSADDLE_WARP_VERSION=`head -n2 jsaddle-warp/jsaddle-warp.cabal | tail -n1 | sed 's/[^0-9.]//g'`
8
8
9−if [[ -d .cabal && -d .ghc ]]; then
10− cp -a .cabal .ghc /root
11−fi
12−
13−npm install ws
14−
15cabal update
9cabal update
16−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'
11+cd jsaddle-warp
17−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 .
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
18−
19−# update the cache
20−rm -rf .cabal
21−cp -a /root/.cabal ./
22−rm -rf .ghc
23−cp -a /root/.ghc ./
24
13
travis/trusty.Dockerfile+1−1
@@ -4,7 +4,7 @@ RUN apt-get update && \
4 apt-get -y install software-properties-common && \
4 apt-get -y install software-properties-common && \
5 add-apt-repository -y ppa:hvr/ghc && \
5 add-apt-repository -y ppa:hvr/ghc && \
6 apt-get update && \
6 apt-get update && \
7− apt-get -y install happy-1.19.5 alex-3.1.7 nodejs npm
7+ apt-get -y install happy-1.19.5 alex-3.1.7 phantomjs
8
8
9RUN mkdir /build
9RUN mkdir /build
10WORKDIR /build
10WORKDIR /build
travis/vivid.Dockerfile+1−1
@@ -4,7 +4,7 @@ RUN apt-get update && \
4 apt-get -y install software-properties-common && \
4 apt-get -y install software-properties-common && \
5 add-apt-repository -y ppa:hvr/ghc && \
5 add-apt-repository -y ppa:hvr/ghc && \
6 apt-get update && \
6 apt-get update && \
7− apt-get -y install happy-1.19.5 alex-3.1.7 nodejs npm
7+ apt-get -y install happy-1.19.5 alex-3.1.7 phantomjs
8
8
9RUN mkdir /build
9RUN mkdir /build
10WORKDIR /build
10WORKDIR /build
travis/wily.Dockerfile+1−1
@@ -4,7 +4,7 @@ RUN apt-get update && \
4 apt-get -y install software-properties-common && \
4 apt-get -y install software-properties-common && \
5 add-apt-repository -y ppa:hvr/ghc && \
5 add-apt-repository -y ppa:hvr/ghc && \
6 apt-get update && \
6 apt-get update && \
7− apt-get -y install happy-1.19.5 alex-3.1.7 nodejs npm
7+ apt-get -y install happy-1.19.5 alex-3.1.7 phantomjs
8
8
9RUN mkdir /build
9RUN mkdir /build
10WORKDIR /build
10WORKDIR /build
travis/xenial.Dockerfile+1−1
@@ -4,7 +4,7 @@ RUN apt-get update && \
4 apt-get -y install software-properties-common && \
4 apt-get -y install software-properties-common && \
5 add-apt-repository -y ppa:hvr/ghc && \
5 add-apt-repository -y ppa:hvr/ghc && \
6 apt-get update && \
6 apt-get update && \
7− apt-get -y install happy-1.19.5 alex-3.1.7 nodejs npm
7+ apt-get -y install happy-1.19.5 alex-3.1.7 phantomjs
8
8
9RUN mkdir /build
9RUN mkdir /build
10WORKDIR /build
10WORKDIR /build