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