on 776e2fa help sign in
618 lines 24 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE EmptyDataDecls #-}
3{-# LANGUAGE ExistentialQuantification #-}
4{-# LANGUAGE FlexibleContexts #-}
5{-# LANGUAGE FlexibleInstances #-}
6{-# LANGUAGE ForeignFunctionInterface #-}
7{-# LANGUAGE FunctionalDependencies #-}
8{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9{-# LANGUAGE MultiParamTypeClasses #-}
10{-# LANGUAGE RankNTypes #-}
11{-# LANGUAGE StandaloneDeriving #-}
12{-# LANGUAGE TemplateHaskell #-}
13{-# LANGUAGE TypeFamilies #-}
14{-# LANGUAGE TypeSynonymInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
16#ifdef __GHCJS__
17{-# LANGUAGE JavaScriptFFI #-}
18#endif
19module Foreign.JavaScript.TH ( module Foreign.JavaScript.TH
20 , Safety (..)
21 ) where
23import Reflex.Class
24import Reflex.DynamicWriter
25import Reflex.EventWriter
26import Reflex.Host.Class
27import Reflex.PerformEvent.Base
28import Reflex.PerformEvent.Class
29import Reflex.PostBuild.Base
30import Reflex.Requester.Base
32import Language.Haskell.TH
34#ifdef __GHCJS__
35import qualified GHCJS.Buffer as JS
36import GHCJS.DOM
37import GHCJS.DOM.Types hiding (Text, fromJSString)
38import qualified GHCJS.DOM.Types as JS
39import qualified GHCJS.Foreign as JS
40import qualified GHCJS.Foreign.Callback as JS
41import qualified GHCJS.Foreign.Callback.Internal (Callback (..))
42import qualified GHCJS.Marshal as JS
43import qualified GHCJS.Marshal.Pure as JS
44import qualified GHCJS.Types as JS
45import qualified JavaScript.Array as JS
46import qualified JavaScript.Array.Internal (SomeJSArray (..))
47import qualified JavaScript.Object as JS
48import qualified JavaScript.Object.Internal (Object (..))
49import qualified JavaScript.TypedArray.ArrayBuffer as JSArrayBuffer
51import Data.Hashable
52import Data.Word
53import Foreign.C.Types
54import Foreign.Ptr
55import Text.Encoding.Z
56#else
57import Foreign.Marshal
58import Graphics.UI.Gtk.WebKit.DOM.Node
59import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
60import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSObjectRef
61import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
62import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
63import Graphics.UI.Gtk.WebKit.JavaScriptCore.WebFrame
64import Graphics.UI.Gtk.WebKit.WebView
65import System.Glib.FFI
66#endif
68import Control.Concurrent
69import Control.Monad
70import Control.Monad.Exception
71import Control.Monad.Fix
72import Control.Monad.IO.Class
73import Control.Monad.Primitive
74import Control.Monad.Reader
75import Control.Monad.Ref
76import Control.Monad.State
77import qualified Control.Monad.State.Strict as Strict
78import Control.Monad.Trans.Control
79import Data.ByteString (ByteString)
80import qualified Data.ByteString as BS
81import Data.Coerce
82import Data.Monoid
83import Data.Text (Text)
84import qualified Data.Text as T
86class Monad m => HasWebView m where
87 type WebViewPhantom m :: *
88 askWebView :: m (WebViewSingleton (WebViewPhantom m))
90instance HasWebView m => HasWebView (ReaderT r m) where
91 type WebViewPhantom (ReaderT r m) = WebViewPhantom m
92 askWebView = lift askWebView
94instance HasWebView m => HasWebView (StateT r m) where
95 type WebViewPhantom (StateT r m) = WebViewPhantom m
96 askWebView = lift askWebView
98instance HasWebView m => HasWebView (Strict.StateT r m) where
99 type WebViewPhantom (Strict.StateT r m) = WebViewPhantom m
100 askWebView = lift askWebView
102instance HasWebView m => HasWebView (PostBuildT t m) where
103 type WebViewPhantom (PostBuildT t m) = WebViewPhantom m
104 askWebView = lift askWebView
106instance (ReflexHost t, HasWebView (HostFrame t)) => HasWebView (PerformEventT t m) where
107 type WebViewPhantom (PerformEventT t m) = WebViewPhantom (HostFrame t)
108 askWebView = PerformEventT $ lift askWebView
110instance HasWebView m => HasWebView (DynamicWriterT t w m) where
111 type WebViewPhantom (DynamicWriterT t w m) = WebViewPhantom m
112 askWebView = lift askWebView
114instance HasWebView m => HasWebView (EventWriterT t w m) where
115 type WebViewPhantom (EventWriterT t w m) = WebViewPhantom m
116 askWebView = lift askWebView
118instance HasWebView m => HasWebView (RequesterT t request response m) where
119 type WebViewPhantom (RequesterT t request response m) = WebViewPhantom m
120 askWebView = lift askWebView
122newtype WithWebView x m a = WithWebView { unWithWebView :: ReaderT (WebViewSingleton x) m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadTrans, MonadException, MonadAsyncException)
124instance PrimMonad m => PrimMonad (WithWebView x m) where
125 type PrimState (WithWebView x m) = PrimState m
126 primitive = lift . primitive
128instance MonadAdjust t m => MonadAdjust t (WithWebView x m) where
129 runWithReplace a0 a' = WithWebView $ runWithReplace (coerce a0) (coerceEvent a')
130 traverseDMapWithKeyWithAdjust f dm0 dm' = WithWebView $ traverseDMapWithKeyWithAdjust (\k v -> unWithWebView $ f k v) (coerce dm0) (coerceEvent dm')
131 traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = WithWebView $ traverseDMapWithKeyWithAdjustWithMove (\k v -> unWithWebView $ f k v) (coerce dm0) (coerceEvent dm')
133instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (WithWebView x m) where
134 {-# INLINABLE newEventWithTrigger #-}
135 newEventWithTrigger = lift . newEventWithTrigger
136 {-# INLINABLE newFanEventWithTrigger #-}
137 newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
139instance MonadSubscribeEvent t m => MonadSubscribeEvent t (WithWebView x m) where
140 {-# INLINABLE subscribeEvent #-}
141 subscribeEvent = lift . subscribeEvent
143instance MonadReflexHost t m => MonadReflexHost t (WithWebView x m) where
144 type ReadPhase (WithWebView x m) = ReadPhase m
145 {-# INLINABLE fireEventsAndRead #-}
146 fireEventsAndRead dm a = lift $ fireEventsAndRead dm a
147 {-# INLINABLE runHostFrame #-}
148 runHostFrame = lift . runHostFrame
150instance MonadSample t m => MonadSample t (WithWebView x m) where
151 {-# INLINABLE sample #-}
152 sample = lift . sample
154instance MonadHold t m => MonadHold t (WithWebView x m) where
155 {-# INLINABLE hold #-}
156 hold v0 = lift . hold v0
157 {-# INLINABLE holdDyn #-}
158 holdDyn v0 = lift . holdDyn v0
159 {-# INLINABLE holdIncremental #-}
160 holdIncremental v0 = lift . holdIncremental v0
162instance MonadTransControl (WithWebView x) where
163 type StT (WithWebView x) a = StT (ReaderT (WebViewSingleton x)) a
164 {-# INLINABLE liftWith #-}
165 liftWith = defaultLiftWith WithWebView unWithWebView
166 {-# INLINABLE restoreT #-}
167 restoreT = defaultRestoreT WithWebView
169instance PerformEvent t m => PerformEvent t (WithWebView x m) where
170 type Performable (WithWebView x m) = WithWebView x (Performable m)
171 {-# INLINABLE performEvent_ #-}
172 performEvent_ e = liftWith $ \run -> performEvent_ $ fmap run e
173 {-# INLINABLE performEvent #-}
174 performEvent e = liftWith $ \run -> performEvent $ fmap run e
176runWithWebView :: WithWebView x m a -> WebViewSingleton x -> m a
177runWithWebView = runReaderT . unWithWebView
179instance (Monad m) => HasWebView (WithWebView x m) where
180 type WebViewPhantom (WithWebView x m) = x
181 askWebView = WithWebView ask
183instance MonadRef m => MonadRef (WithWebView x m) where
184 type Ref (WithWebView x m) = Ref m
185 newRef = lift . newRef
186 readRef = lift . readRef
187 writeRef r = lift . writeRef r
189instance MonadAtomicRef m => MonadAtomicRef (WithWebView x m) where
190 atomicModifyRef r = lift . atomicModifyRef r
192withWebViewSingleton :: WebView -> (forall x. WebViewSingleton x -> r) -> r
193withWebViewSingleton wv f = f $ WebViewSingleton wv
195-- | Warning: `withWebViewSingletonMono` does not provide the same guarantees that `withWebViewSingleton` does.
196withWebViewSingletonMono :: WebView -> (WebViewSingleton () -> r) -> r
197withWebViewSingletonMono wv f = f $ WebViewSingleton wv
199-- | A singleton type for a given WebView; we use this to statically guarantee that different WebViews (and thus different javscript contexts) don't get mixed up
200newtype WebViewSingleton x = WebViewSingleton { unWebViewSingleton :: WebView }
202#ifdef __GHCJS__
203type JSFFI_Internal = JS.MutableJSArray -> IO JS.JSVal
204newtype JSFFI = JSFFI JSFFI_Internal
205#else
206newtype JSFFI = JSFFI String
207#endif
209newtype JSFun x = JSFun { unJSFun :: JSRef x }
211instance ToJS x (JSFun x) where
212 withJS (JSFun r) f = f r
214instance FromJS x (JSFun x) where
215 fromJS = return . JSFun
217class IsJSContext x where
218 data JSRef x
220class (Monad m, MonadIO (JSM m), MonadFix (JSM m), MonadJS x (JSM m)) => HasJS x m | m -> x where
221 type JSM m :: * -> *
222 liftJS :: JSM m a -> m a
224instance HasJS x m => HasJS x (ReaderT r m) where
225 type JSM (ReaderT r m) = JSM m
226 liftJS = lift . liftJS
228instance (HasJS x m, ReflexHost t) => HasJS x (PostBuildT t m) where
229 type JSM (PostBuildT t m) = JSM m
230 liftJS = lift . liftJS
232instance (HasJS x (HostFrame t), ReflexHost t) => HasJS x (PerformEventT t m) where
233 type JSM (PerformEventT t m) = JSM (HostFrame t)
234 liftJS = PerformEventT . lift . liftJS
236instance HasJS x m => HasJS x (DynamicWriterT t w m) where
237 type JSM (DynamicWriterT t w m) = JSM m
238 liftJS = lift . liftJS
240instance HasJS x m => HasJS x (RequesterT t request response m) where
241 type JSM (RequesterT t request response m) = JSM m
242 liftJS = lift . liftJS
244-- | A Monad that is capable of executing JavaScript
245class Monad m => MonadJS x m | m -> x where
246 runJS :: JSFFI -> [JSRef x] -> m (JSRef x)
247 forkJS :: m () -> m ThreadId
248 mkJSUndefined :: m (JSRef x)
249 isJSNull :: JSRef x -> m Bool
250 isJSUndefined :: JSRef x -> m Bool
251 fromJSBool :: JSRef x -> m Bool
252 fromJSString :: JSRef x -> m String
253 fromJSArray :: JSRef x -> m [JSRef x]
254 fromJSUint8Array :: JSRef x -> m ByteString
255 fromJSNumber :: JSRef x -> m Double
256 withJSBool :: Bool -> (JSRef x -> m r) -> m r
257 withJSString :: String -> (JSRef x -> m r) -> m r
258 withJSNumber :: Double -> (JSRef x -> m r) -> m r
259 withJSArray :: [JSRef x] -> (JSRef x -> m r) -> m r
260 withJSUint8Array :: ByteString -> (JSUint8Array x -> m r) -> m r
261 -- | Create a JSFun with zero arguments; should be equilvant to `syncCallback AlwaysRetain True` in GHCJS
262 mkJSFun :: ([JSRef x] -> m (JSRef x)) -> m (JSFun x) --TODO: Support 'this', exceptions
263 freeJSFun :: JSFun x -> m ()
264 setJSProp :: String -> JSRef x -> JSRef x -> m ()
265 getJSProp :: String -> JSRef x -> m (JSRef x)
266 withJSNode :: Node -> (JSRef x -> m r) -> m r
268#ifdef __GHCJS__
270data JSCtx_IO
272instance MonadIO m => HasJS JSCtx_IO (WithWebView x m) where
273 type JSM (WithWebView x m) = IO
274 liftJS = liftIO
276instance IsJSContext JSCtx_IO where
277 newtype JSRef JSCtx_IO = JSRef_IO { unJSRef_IO :: JS.JSVal }
279instance MonadJS JSCtx_IO IO where
280 runJS (JSFFI f) l = fmap JSRef_IO . f =<< JS.fromListIO (coerce l)
281 forkJS = forkIO
282 mkJSUndefined = return $ JSRef_IO JS.jsUndefined
283 isJSNull (JSRef_IO r) = return $ JS.isNull r
284 isJSUndefined (JSRef_IO r) = return $ JS.isUndefined r
285 fromJSBool (JSRef_IO r) = return $ JS.fromJSBool r
286 fromJSString (JSRef_IO r) = return $ JS.fromJSString $ JS.pFromJSVal r
287 fromJSArray (JSRef_IO r) = fmap coerce $ JS.toListIO $ coerce r
288 fromJSUint8Array (JSRef_IO r) = fmap (JS.toByteString 0 Nothing . JS.createFromArrayBuffer) $ JSArrayBuffer.unsafeFreeze $ JS.pFromJSVal r --TODO: Assert that this is immutable
289 fromJSNumber (JSRef_IO r) = do
290 Just n <- JS.fromJSVal r
291 return n
292 withJSBool b f = f $ JSRef_IO $ JS.toJSBool b
293 withJSString s f = f $ JSRef_IO $ JS.pToJSVal $ JS.toJSString s
294 withJSNumber n f = do
295 r <- JS.toJSVal n
296 f $ JSRef_IO r
297 withJSArray l f = do
298 r <- JS.fromListIO $ coerce l
299 f $ JSRef_IO $ coerce r
300 withJSUint8Array payload f = BS.useAsCString payload $ \cStr -> do
301 ba <- extractByteArray cStr $ BS.length payload
302 f $ JSUint8Array $ JSRef_IO ba
303 mkJSFun f = do
304 cb <- JS.syncCallback1' $ \args -> do
305 l <- JS.toListIO $ coerce args
306 JSRef_IO result <- f $ coerce l
307 return result
308 fmap (JSFun . JSRef_IO) $ funWithArguments $ coerce cb
309 freeJSFun (JSFun (JSRef_IO r)) = JS.releaseCallback $ coerce r
310 setJSProp s (JSRef_IO v) (JSRef_IO o) = JS.setProp (JS.toJSString s) v $ coerce o
311 getJSProp s (JSRef_IO o) = do
312 r <- JS.getProp (JS.toJSString s) $ coerce o
313 return $ JSRef_IO r
314 withJSNode n f = f $ JSRef_IO $ unNode n
316foreign import javascript unsafe "new Uint8Array($1_1.buf, $1_2, $2)" extractByteArray :: Ptr CChar -> Int -> IO JS.JSVal
318foreign import javascript unsafe "function(){ return $1(arguments); }" funWithArguments :: JS.Callback (JS.MutableJSArray -> IO a) -> IO JS.JSVal
320#else
322-- | Make a JS string available that is not wrapped up as a JSValueRef
323withJSStringRaw :: MonadAsyncException m => String -> (JSStringRef -> m r) -> m r
324withJSStringRaw str a = do
325 bracket (liftIO $ jsstringcreatewithutf8cstring str) (liftIO . jsstringrelease) $ \strRef -> do
326 a strRef
328fromJSNumberRaw :: (MonadIO m, HasJSContext m) => JSValueRef -> m Double
329fromJSNumberRaw val = do
330 jsContext <- askJSContext
331 liftIO $ jsvaluetonumber jsContext val nullPtr --TODO: Exceptions
334data JSCtx_JavaScriptCore x
336instance IsJSContext (JSCtx_JavaScriptCore x) where
337 newtype JSRef (JSCtx_JavaScriptCore x) = JSRef_JavaScriptCore { unJSRef_JavaScriptCore :: JSValueRef }
339instance MonadIO m => HasJS (JSCtx_JavaScriptCore x) (WithWebView x m) where
340 type JSM (WithWebView x m) = WithWebView x IO
341 liftJS a = do
342 wv <- askWebView
343 liftIO $ runWithWebView a wv
345newtype WithJSContext x m a = WithJSContext { unWithJSContext :: ReaderT JSContextRef m a } deriving (Functor, Applicative, Monad, MonadIO, MonadFix, MonadTrans, MonadException, MonadAsyncException)
347runWithJSContext :: WithJSContext x m a -> JSContextRef -> m a
348runWithJSContext = runReaderT . unWithJSContext
350class Monad m => HasJSContext m where
351 askJSContext :: m JSContextRef
353instance MonadIO m => HasJSContext (WithWebView x m) where
354 askJSContext = do
355 wv <- askWebView
356 liftIO $ webFrameGetGlobalContext =<< webViewGetMainFrame (unWebViewSingleton wv)
358instance Monad m => HasJSContext (WithJSContext x m) where
359 askJSContext = WithJSContext ask
361lowerWithJSContext :: (HasJSContext m, MonadIO m) => WithJSContext x IO a -> m a
362lowerWithJSContext a = do
363 c <- askJSContext
364 liftIO $ runWithJSContext a c
366liftWithWebViewThroughWithJSContext :: (HasJSContext m, HasWebView m, MonadIO m, MonadTrans t, Monad m1)
367 => ((t1 -> t m1 a) -> WithJSContext x IO b)
368 -> (t1 -> WithWebView (WebViewPhantom m) m1 a)
369 -> m b
370liftWithWebViewThroughWithJSContext f a = do
371 wv <- askWebView
372 lowerWithJSContext $ f $ \b' -> lift $ runWithWebView (a b') wv
374instance MonadJS (JSCtx_JavaScriptCore x) (WithWebView x IO) where
375 forkJS a = do
376 wv <- askWebView
377 liftIO $ forkIO $ runWithWebView a wv
378 mkJSFun a = do
379 wv <- askWebView
380 lowerWithJSContext $ mkJSFun $ \args -> lift $ runWithWebView (a args) wv
381 runJS expr args = lowerWithJSContext $ runJS expr args
382 mkJSUndefined = lowerWithJSContext mkJSUndefined
383 isJSNull = lowerWithJSContext . isJSNull
384 isJSUndefined = lowerWithJSContext . isJSUndefined
385 fromJSBool = lowerWithJSContext . fromJSBool
386 fromJSString = lowerWithJSContext . fromJSString
387 fromJSArray = lowerWithJSContext . fromJSArray
388 fromJSUint8Array = lowerWithJSContext . fromJSUint8Array
389 fromJSNumber = lowerWithJSContext . fromJSNumber
390 freeJSFun = lowerWithJSContext . freeJSFun
391 withJSBool = liftWithWebViewThroughWithJSContext . withJSBool
392 withJSString = liftWithWebViewThroughWithJSContext . withJSString
393 withJSNumber = liftWithWebViewThroughWithJSContext . withJSNumber
394 withJSArray = liftWithWebViewThroughWithJSContext . withJSArray
395 withJSUint8Array = liftWithWebViewThroughWithJSContext . withJSUint8Array
396 withJSNode = liftWithWebViewThroughWithJSContext . withJSNode
397 setJSProp propName valRef objRef = lowerWithJSContext $ setJSProp propName valRef objRef
398 getJSProp propName objRef = lowerWithJSContext $ getJSProp propName objRef
400instance MonadJS (JSCtx_JavaScriptCore x) (WithJSContext x IO) where
401 runJS (JSFFI body) args = do
402 jsContext <- askJSContext
403 withJSArray args $ \(JSRef_JavaScriptCore this) -> liftIO $ do
404 result <- bracket (jsstringcreatewithutf8cstring body) jsstringrelease $ \script -> jsevaluatescript jsContext script this nullPtr 1 nullPtr
405 return $ JSRef_JavaScriptCore result --TODO: Protect and unprotect result
406 forkJS a = do
407 c <- askJSContext
408 liftIO $ forkIO $ runWithJSContext a c
409 mkJSUndefined = do
410 jsContext <- askJSContext
411 fmap JSRef_JavaScriptCore $ liftIO $ jsvaluemakeundefined jsContext
412 isJSNull (JSRef_JavaScriptCore r) = do
413 jsContext <- askJSContext
414 liftIO $ jsvalueisnull jsContext r
415 isJSUndefined (JSRef_JavaScriptCore r) = do
416 jsContext <- askJSContext
417 liftIO $ jsvalueisundefined jsContext r
418 fromJSBool (JSRef_JavaScriptCore r) = do
419 jsContext <- askJSContext
420 liftIO $ jsvaluetoboolean jsContext r
421 fromJSString (JSRef_JavaScriptCore r) = do
422 jsContext <- askJSContext
423 liftIO $ do
424 s <- jsvaluetostringcopy jsContext r nullPtr --TODO: Deal with exceptions
425 l <- jsstringgetmaximumutf8cstringsize s
426 allocaBytes (fromIntegral l) $ \ps -> do
427 _ <- jsstringgetutf8cstring'_ s ps (fromIntegral l)
428 peekCString ps
429 withJSBool b a = do
430 jsContext <- askJSContext
431 valRef <- liftIO $ jsvaluemakeboolean jsContext b
432 a $ JSRef_JavaScriptCore valRef
433 withJSString str a = do
434 jsContext <- askJSContext
435 withJSStringRaw str $ \strRef -> do
436 valRef <- liftIO $ jsvaluemakestring jsContext strRef
437 a $ JSRef_JavaScriptCore valRef --TODO: Protect/unprotect valRef
438 withJSNumber n a = do
439 jsContext <- askJSContext
440 valRef <- liftIO $ jsvaluemakenumber jsContext n
441 a $ JSRef_JavaScriptCore valRef --TODO: Protect/unprotect valRef
442 withJSArray elems a = do
443 jsContext <- askJSContext
444 let numElems = length elems
445 bracket (liftIO $ mallocArray numElems) (liftIO . free) $ \elemsArr -> do
446 liftIO $ pokeArray elemsArr $ coerce elems
447 bracket (liftIO $ jsobjectmakearray jsContext (fromIntegral numElems) elemsArr nullPtr) (const $ return ()) $ \arrRef -> do --TODO: Do we need to protect/unprotect the array object?
448 a $ JSRef_JavaScriptCore arrRef --TODO: Protect/unprotect valRef
449 --TODO: When supported by webkitgtk3-javascriptcore, go directly from C string to Uint8Array without creating each item individually
450 withJSUint8Array payload f = withJSArrayFromList (BS.unpack payload) $ \x -> do
451 payloadRef <- runJS (JSFFI "new Uint8Array(this[0])") [x]
452 f $ JSUint8Array payloadRef
453 fromJSArray (JSRef_JavaScriptCore a) = do
454 jsContext <- askJSContext
455 lenRef <- withJSStringRaw "length" $ \lengthStr -> do
456 liftIO $ jsobjectgetproperty jsContext a lengthStr nullPtr --TODO: Exceptions
457 lenDouble <- fromJSNumberRaw lenRef
458 let len = round lenDouble
459 liftIO $ forM [0..len-1] $ \i -> do
460 JSRef_JavaScriptCore <$> jsobjectgetpropertyatindex jsContext a i nullPtr --TODO: Exceptions
461 fromJSUint8Array a = do
462 vals <- fromJSArray a
463 doubles <- mapM fromJSNumber vals
464 return $ BS.pack $ map round doubles
465 fromJSNumber (JSRef_JavaScriptCore val) = do
466 jsContext <- askJSContext
467 liftIO $ jsvaluetonumber jsContext val nullPtr --TODO: Exceptions
468 mkJSFun a = do
469 jsContext <- askJSContext
470 cb <- liftIO $ mkJSObjectCallAsFunctionCallback $ \_ _ _ argc argv _ -> do --TODO: Use the exception pointer to handle haskell exceptions
471 args <- forM [0..fromIntegral (argc-1)] $ \n -> do
472 x <- peekElemOff argv n
473 jsvalueprotect jsContext x
474 return $ JSRef_JavaScriptCore x --TODO: Unprotect eventually
475 unJSRef_JavaScriptCore <$> runWithJSContext (a args) jsContext
476 cbRef <- liftIO $ jsobjectmakefunctionwithcallback jsContext nullPtr cb
477 liftIO $ jsvalueprotect jsContext cbRef
478 return $ JSFun $ JSRef_JavaScriptCore cbRef
479 freeJSFun (JSFun (JSRef_JavaScriptCore f)) = do
480 jsContext <- askJSContext
481 liftIO $ jsvalueunprotect jsContext f
482 setJSProp propName (JSRef_JavaScriptCore valRef) (JSRef_JavaScriptCore objRef) = do
483 withJSStringRaw propName $ \propNameRaw -> do
484 jsContext <- askJSContext
485 liftIO $ jsobjectsetproperty jsContext objRef propNameRaw valRef 0 nullPtr --TODO: property attribute, exceptions
486 getJSProp propName (JSRef_JavaScriptCore objRef) = do
487 withJSStringRaw propName $ \propNameRaw -> do
488 jsContext <- askJSContext
489 liftIO $ JSRef_JavaScriptCore <$> jsobjectgetproperty jsContext objRef propNameRaw nullPtr --TODO: property attribute, exceptions
490 withJSNode = error "withJSNode is only supported in ghcjs"
492#endif
494class FromJS x a where
495 fromJS :: MonadJS x m => JSRef x -> m a
497instance FromJS x () where
498 fromJS _ = return () --TODO: Should this do some kind of checking for the js value?
500instance FromJS x Bool where
501 fromJS = fromJSBool
503instance ToJS x Bool where
504 withJS = withJSBool
506instance FromJS x String where
507 fromJS = fromJSString
509instance FromJS x Text where
510 fromJS s = T.pack <$> fromJSString s
512instance FromJS x a => FromJS x (Maybe a) where
513 fromJS x = do
514 n <- isJSNull x
515 if n then return Nothing else Just <$> fromJS x
517class ToJS x a where
518 withJS :: MonadJS x m => a -> (JSRef x -> m r) -> m r
520instance ToJS x (JSRef x) where
521 withJS r = ($ r)
523instance FromJS x (JSRef x) where
524 fromJS = return
526instance ToJS x String where
527 withJS = withJSString
529instance ToJS x Text where
530 withJS = withJSString . T.unpack
532newtype JSArray a = JSArray { unJSArray :: [a] }
534instance ToJS x a => ToJS x (JSArray a) where
535 withJS = withJSArrayFromList . unJSArray
537instance FromJS x a => FromJS x (JSArray a) where
538 fromJS = fmap JSArray . mapM fromJS <=< fromJSArray
540withJSArrayFromList :: (ToJS x a, MonadJS x m) => [a] -> (JSRef x -> m r) -> m r
541withJSArrayFromList as f = go as []
542 where go [] jsRefs = withJSArray (reverse jsRefs) f
543 go (h:t) jsRefs = withJS h $ \hRef -> go t (hRef : jsRefs)
545newtype JSUint8Array x = JSUint8Array { unJSUint8Array :: JSRef x }
547instance ToJS x (JSUint8Array x) where
548 withJS (JSUint8Array r) = ($ r)
550instance FromJS x (JSUint8Array x) where
551 fromJS = return . JSUint8Array
553instance ToJS x Word8 where
554 withJS n = withJSNumber $ fromIntegral n --TODO: Check things; throw exceptions
556instance ToJS x Int where
557 withJS n = withJSNumber $ fromIntegral n --TODO: Check things; throw exceptions
559instance FromJS x Int where
560 fromJS = fmap round . fromJSNumber --TODO: Check things; throw exceptions
562instance ToJS x Double where
563 withJS = withJSNumber
565instance FromJS x Double where
566 fromJS = fromJSNumber
568instance ToJS x Node where
569 withJS = withJSNode
572importJS :: Safety -> String -> String -> Q Type -> Q [Dec]
573importJS safety body name qt = do
574 t <- qt
575 let (argTypes, _) = parseType t
576 argNames <- forM argTypes $ \_ -> do
577 arg <- newName "arg"
578 argRef <- newName "argRef"
579 return (arg, argRef)
580 (jsffiDecs, jsffiExp) <- mkJSFFI safety body
581 let go [] = [| runJS $(return jsffiExp) $(listE $ map (varE . snd) argNames) >>= fromJS
582 |]
583 go ((arg, argRef) : args) = [| withJS $(varE arg) $ $(lamE [varP argRef] $ go args) |]
584 e <- lamE (map (varP. fst) argNames) $ go argNames
585 let n = mkName name
586 return $ jsffiDecs ++
587 [ SigD n t
588 , ValD (VarP n) (NormalB e) []
589 ]
591mkJSFFI :: Safety -> String -> Q ([Dec], Exp)
592#ifdef __GHCJS__
593mkJSFFI safety body = do
594 -- n <- newName "jsffi" --TODO: Should use newName, but that doesn't seem to work with ghcjs
595 l <- location
596 n <- newName $ "jsffi_" <> zEncodeString (loc_package l <> ":" <> loc_module l) <> "_" <> show (abs (hash (show safety, body)))
597 t <- [t| JSFFI_Internal |]
598 let wrappedBody = "(function(){ return (" <> body <> "); }).apply($1)"
599 let decs = [ForeignD $ ImportF JavaScript safety wrappedBody n t]
600 e <- [| JSFFI $(varE n) |]
601 return (decs, e)
602#else
603mkJSFFI _ body = do
604 e <- [| JSFFI body |]
605 return ([], e)
606#endif
608parseType :: Type -> ([Type], Type)
609parseType (ForallT _ [AppT (AppT (ConT monadJs) (VarT _)) (VarT m)] funType)
610 | monadJs == ''MonadJS = go funType
611 where go t = case t of
612 AppT (AppT ArrowT arg) t' ->
613 let (args, result) = go t'
614 in (arg : args, result)
615 AppT (VarT m') result
616 | m' == m -> ([], result)
617 _ -> error $ "parseType: can't parse type " <> show t
618parseType t = error $ "parseType: can't parse type " <> show t