1{-# LANGUAGE ForeignFunctionInterface, CPP, TemplateHaskell, TypeFamilies, TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses, StandaloneDeriving, GeneralizedNewtypeDeriving, ExistentialQuantification, FunctionalDependencies, EmptyDataDecls, FlexibleContexts, RankNTypes #-}
3{-# LANGUAGE JavaScriptFFI #-}
5module Foreign.JavaScript.TH ( module Foreign.JavaScript.TH
9import Language.Haskell.TH
12import qualified GHCJS.Marshal as JS
13import qualified GHCJS.Foreign as JS
14import qualified GHCJS.Types as JS
21import System.Glib.FFI hiding (withForeignPtr)
22import Graphics.UI.Gtk.WebKit.WebView
23import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSBase
24import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSObjectRef
25import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSStringRef
26import Graphics.UI.Gtk.WebKit.JavaScriptCore.JSValueRef
27import Graphics.UI.Gtk.WebKit.JavaScriptCore.WebFrame
28import Graphics.UI.Gtk.WebKit.DOM.Node
29import Graphics.UI.Gtk.WebKit.Types
33import Control.Monad.Fix
34import Control.Monad.IO.Class
35import Control.Monad.Exception
37import Data.ByteString (ByteString)
38import qualified Data.ByteString as BS
39import Control.Concurrent
42import Reflex.Dom (WithWebView, askWebView, runWithWebView, unWebViewSingleton)
45type JSFFI_Internal = JS.JSArray (JSRef JSCtx_IO) -> IO (JS.JSRef (JSRef JSCtx_IO))
46newtype JSFFI = JSFFI JSFFI_Internal
48newtype JSFFI = JSFFI String
51newtype JSFun x = JSFun { unJSFun :: JSRef x }
53instance ToJS x (JSFun x) where
54 withJS (JSFun r) f = f r
56instance FromJS x (JSFun x) where
57 fromJS = return . JSFun
59class IsJSContext x where
62class (MonadIO (JSM m), MonadFix (JSM m), MonadJS x (JSM m)) => HasJS x m | m -> x where
64 liftJS :: JSM m a -> m a
66-- | A Monad that is capable of executing JavaScript
67class Monad m => MonadJS x m | m -> x where
68 runJS :: JSFFI -> [JSRef x] -> m (JSRef x)
69 forkJS :: m () -> m ThreadId
70 mkJSUndefined :: m (JSRef x)
71 isJSNull :: JSRef x -> m Bool
72 isJSUndefined :: JSRef x -> m Bool
73 fromJSBool :: JSRef x -> m Bool
74 fromJSString :: JSRef x -> m String
75 fromJSArray :: JSRef x -> m [JSRef x]
76 fromJSUint8Array :: JSRef x -> m ByteString
77 fromJSNumber :: JSRef x -> m Double
78 withJSBool :: Bool -> (JSRef x -> m r) -> m r
79 withJSString :: String -> (JSRef x -> m r) -> m r
80 withJSNumber :: Double -> (JSRef x -> m r) -> m r
81 withJSArray :: [JSRef x] -> (JSRef x -> m r) -> m r
82 withJSUint8Array :: MonadJS x m => ByteString -> (JSUint8Array x -> m r) -> m r
83 -- | Create a JSFun with zero arguments; should be equilvant to `syncCallback AlwaysRetain True` in GHCJS
84 mkJSFun :: ([JSRef x] -> m (JSRef x)) -> m (JSFun x) --TODO: Support 'this', exceptions
85 freeJSFun :: JSFun x -> m ()
86 setJSProp :: String -> JSRef x -> JSRef x -> m ()
87 getJSProp :: String -> JSRef x -> m (JSRef x)
88 withJSNode :: Node -> (JSRef x -> m r) -> m r
94instance IsJSContext JSCtx_IO where
95 newtype JSRef JSCtx_IO = JSRef_IO { unJSRef_IO :: JS.JSRef (JSRef JSCtx_IO) }
97instance MonadJS JSCtx_IO IO where
98 runJS (JSFFI f) l = liftM JSRef_IO . f =<< JS.toArray (coerce l)
100 mkJSUndefined = return $ JSRef_IO JS.jsUndefined
101 isJSNull (JSRef_IO r) = return $ JS.isNull r
102 isJSUndefined (JSRef_IO r) = return $ JS.isUndefined r
103 fromJSBool (JSRef_IO r) = return $ JS.fromJSBool $ JS.castRef r
104 fromJSString (JSRef_IO r) = return $ JS.fromJSString $ JS.castRef r
105 fromJSArray (JSRef_IO r) = liftM coerce $ JS.fromArray $ JS.castRef r
106 fromJSUint8Array (JSRef_IO r) = JS.bufferByteString 0 0 $ JS.castRef r
107 fromJSNumber (JSRef_IO r) = do
108 Just n <- JS.fromJSRef $ JS.castRef r
110 withJSBool b f = f $ JSRef_IO $ JS.castRef $ JS.toJSBool b
111 withJSString s f = f $ JSRef_IO $ JS.castRef $ JS.toJSString s
112 withJSNumber n f = do
114 f $ JSRef_IO $ JS.castRef r
116 r <- JS.toArray $ coerce l
117 f $ JSRef_IO $ JS.castRef r
118 withJSUint8Array payload f = BS.useAsCString payload $ \cStr -> do
119 ba <- extractByteArray cStr $ BS.length payload
120 f $ JSUint8Array $ JSRef_IO ba
122 cb <- JS.syncCallback1 JS.AlwaysRetain True $ \args -> do
123 l <- JS.fromArray args
124 JSRef_IO result <- f $ coerce l
125 return $ JS.castRef result
126 liftM (JSFun . JSRef_IO) $ funWithArguments cb
127 freeJSFun (JSFun (JSRef_IO r)) = JS.release $ JS.castRef r
128 setJSProp s (JSRef_IO v) (JSRef_IO o) = JS.setProp s v o
129 getJSProp s (JSRef_IO o) = do
132 withJSNode n f = f $ JSRef_IO $ JS.castRef $ unNode n
134foreign import javascript unsafe "new Uint8Array($1_1.buf, $1_2, $2)" extractByteArray :: Ptr CChar -> Int -> IO (JS.JSRef (JSRef JSCtx_IO))
136foreign import javascript unsafe "function(){ return $1(arguments); }" funWithArguments :: JS.JSFun (JS.JSArray (JS.JSRef b) -> IO a) -> IO (JS.JSRef (JSRef JSCtx_IO))
140askJSContext :: MonadIO m => WithWebView x m JSContextRef
143 liftIO $ webFrameGetGlobalContext =<< webViewGetMainFrame (unWebViewSingleton wv)
145-- | Make a JS string available that is not wrapped up as a JSValueRef
146withJSStringRaw :: MonadAsyncException m => String -> (JSStringRef -> m r) -> m r
147withJSStringRaw str a = do
148 bracket (liftIO $ jsstringcreatewithutf8cstring str) (liftIO . jsstringrelease) $ \strRef -> do
151fromJSNumberRaw :: MonadIO m => JSValueRef -> WithWebView x m Double
152fromJSNumberRaw val = do
153 jsContext <- askJSContext
154 liftIO $ jsvaluetonumber jsContext val nullPtr --TODO: Exceptions
156foreign import ccall "wrapper" mkJSObjectCallAsFunctionCallback :: JSObjectCallAsFunctionCallback' -> IO (FunPtr JSObjectCallAsFunctionCallback')
158data JSCtx_JavaScriptCore x
160instance IsJSContext (JSCtx_JavaScriptCore x) where
161 newtype JSRef (JSCtx_JavaScriptCore x) = JSRef_JavaScriptCore { unJSRef_JavaScriptCore :: JSValueRef }
163instance MonadIO m => HasJS (JSCtx_JavaScriptCore x) (WithWebView x m) where
164 type JSM (WithWebView x m) = WithWebView x IO
167 liftIO $ runWithWebView a wv
169instance MonadJS (JSCtx_JavaScriptCore x) (WithWebView x IO) where
170 runJS (JSFFI body) args = do
171 jsContext <- askJSContext
172 withJSArray args $ \(JSRef_JavaScriptCore this) -> liftIO $ do
173 result <- bracket (jsstringcreatewithutf8cstring body) jsstringrelease $ \script -> jsevaluatescript jsContext script this nullPtr 1 nullPtr
174 return $ JSRef_JavaScriptCore result --TODO: Protect and unprotect result
177 liftIO $ forkIO $ runWithWebView a wv
179 jsContext <- askJSContext
180 liftM JSRef_JavaScriptCore $ liftIO $ jsvaluemakeundefined jsContext
181 isJSNull (JSRef_JavaScriptCore r) = do
182 jsContext <- askJSContext
183 liftIO $ jsvalueisnull jsContext r
184 isJSUndefined (JSRef_JavaScriptCore r) = do
185 jsContext <- askJSContext
186 liftIO $ jsvalueisundefined jsContext r
187 fromJSBool (JSRef_JavaScriptCore r) = do
188 jsContext <- askJSContext
189 liftIO $ jsvaluetoboolean jsContext r
190 fromJSString (JSRef_JavaScriptCore r) = do
191 jsContext <- askJSContext
193 s <- jsvaluetostringcopy jsContext r nullPtr --TODO: Deal with exceptions
194 l <- jsstringgetmaximumutf8cstringsize s
195 allocaBytes (fromIntegral l) $ \ps -> do
196 _ <- jsstringgetutf8cstring'_ s ps (fromIntegral l)
199 jsContext <- askJSContext
200 valRef <- liftIO $ jsvaluemakeboolean jsContext bRef
201 a $ JSRef_JavaScriptCore valRef
202 withJSString str a = do
203 jsContext <- askJSContext
204 withJSStringRaw str $ \strRef -> do
205 valRef <- liftIO $ jsvaluemakestring jsContext strRef
206 a $ JSRef_JavaScriptCore valRef --TODO: Protect/unprotect valRef
207 withJSNumber n a = do
208 jsContext <- askJSContext
209 valRef <- liftIO $ jsvaluemakenumber jsContext n
210 a $ JSRef_JavaScriptCore valRef --TODO: Protect/unprotect valRef
211 withJSArray elems a = do
212 jsContext <- askJSContext
213 let numElems = length elems
214 bracket (liftIO $ mallocArray numElems) (liftIO . free) $ \elemsArr -> do
215 liftIO $ pokeArray elemsArr $ coerce elems
216 bracket (liftIO $ jsobjectmakearray jsContext (fromIntegral numElems) elemsArr nullPtr) (const $ return ()) $ \arrRef -> do --TODO: Do we need to protect/unprotect the array object?
217 a $ JSRef_JavaScriptCore arrRef --TODO: Protect/unprotect valRef
218 --TODO: When supported by webkitgtk3-javascriptcore, go directly from C string to Uint8Array without creating each item individually
219 withJSUint8Array payload f = withJSArrayFromList (BS.unpack payload) $ \x -> do
220 payloadRef <- runJS (JSFFI "new Uint8Array(this[0])") [x]
221 f $ JSUint8Array payloadRef
222 fromJSArray (JSRef_JavaScriptCore a) = do
223 jsContext <- askJSContext
224 lenRef <- withJSStringRaw "length" $ \lengthStr -> do
225 liftIO $ jsobjectgetproperty jsContext a lengthStr nullPtr --TODO: Exceptions
226 lenDouble <- fromJSNumberRaw lenRef
227 let len = round lenDouble
228 liftIO $ forM [0..len-1] $ \i -> do
229 liftM JSRef_JavaScriptCore $ jsobjectgetpropertyatindex jsContext a i nullPtr --TODO: Exceptions
230 fromJSUint8Array a = do
231 vals <- fromJSArray a
232 doubles <- mapM fromJSNumber vals
233 return $ BS.pack $ map round doubles
234 fromJSNumber (JSRef_JavaScriptCore val) = do
235 jsContext <- askJSContext
236 liftIO $ jsvaluetonumber jsContext val nullPtr --TODO: Exceptions
239 jsContext <- askJSContext
240 cb <- liftIO $ mkJSObjectCallAsFunctionCallback $ \_ _ _ argc argv _ -> do --TODO: Use the exception pointer to handle haskell exceptions
241 args <- forM [0..fromIntegral (argc-1)] $ \n -> do
242 x <- peekElemOff argv n
243 jsvalueprotect jsContext x
244 return $ JSRef_JavaScriptCore x --TODO: Unprotect eventually
245 liftM unJSRef_JavaScriptCore $ runWithWebView (a args) wv
246 cbRef <- liftIO $ jsobjectmakefunctionwithcallback jsContext nullPtr cb
247 liftIO $ jsvalueprotect jsContext cbRef
248 return $ JSFun $ JSRef_JavaScriptCore cbRef
249 freeJSFun (JSFun (JSRef_JavaScriptCore f)) = do
250 jsContext <- askJSContext
251 liftIO $ jsvalueunprotect jsContext f
252 setJSProp propName (JSRef_JavaScriptCore valRef) (JSRef_JavaScriptCore objRef) = do
253 withJSStringRaw propName $ \propNameRaw -> do
254 jsContext <- askJSContext
255 liftIO $ jsobjectsetproperty jsContext objRef propNameRaw valRef 0 nullPtr --TODO: property attribute, exceptions
256 getJSProp propName (JSRef_JavaScriptCore objRef) = do
257 withJSStringRaw propName $ \propNameRaw -> do
258 jsContext <- askJSContext
259 liftIO $ jsobjectgetproperty jsContext objRef propNameRaw 0 nullPtr --TODO: property attribute, exceptions
260 withJSNode (Node n) f = do
261 jsContext <- askJSContext
262 withForeignPtr n $ \nPtr -> do
263 let acquire = liftIO $ do
264 jsNode <- webkit_dom_node_to_js jsContext nPtr
265 jsvalueprotect jsContext jsNode
267 release = liftIO . jsvalueunprotect jsContext
268 bracket acquire release $ f . JSRef_JavaScriptCore
270withForeignPtr :: MonadIO m => ForeignPtr a -> (Ptr a -> m b) -> m b
271withForeignPtr fo io = do
272 r <- io $ unsafeForeignPtrToPtr fo
273 liftIO $ touchForeignPtr fo
276foreign import ccall unsafe "webkit_dom_node_to_js" webkit_dom_node_to_js :: JSContextRef -> Ptr Node -> IO JSValueRef
280class FromJS x a where
281 fromJS :: MonadJS x m => JSRef x -> m a
283instance FromJS x () where
284 fromJS _ = return () --TODO: Should this do some kind of checking for the js value?
286instance FromJS x Bool where
289instance ToJS x Bool where
292instance FromJS x String where
293 fromJS = fromJSString
295instance FromJS x a => FromJS x (Maybe a) where
298 if n then return Nothing else liftM Just $ fromJS x
301 withJS :: MonadJS x m => a -> (JSRef x -> m r) -> m r
303instance ToJS x (JSRef x) where
306instance FromJS x (JSRef x) where
309instance ToJS x String where
310 withJS = withJSString
312newtype JSArray a = JSArray { unJSArray :: [a] }
314instance ToJS x a => ToJS x (JSArray a) where
315 withJS = withJSArrayFromList . unJSArray
317instance FromJS x a => FromJS x (JSArray a) where
318 fromJS = liftM JSArray . mapM fromJS <=< fromJSArray
320withJSArrayFromList :: (ToJS x a, MonadJS x m) => [a] -> (JSRef x -> m r) -> m r
321withJSArrayFromList as f = go as []
322 where go [] jsRefs = withJSArray (reverse jsRefs) f
323 go (h:t) jsRefs = withJS h $ \hRef -> go t (hRef : jsRefs)
325newtype JSUint8Array x = JSUint8Array { unJSUint8Array :: JSRef x }
327instance ToJS x (JSUint8Array x) where
328 withJS (JSUint8Array r) = ($ r)
330instance FromJS x (JSUint8Array x) where
331 fromJS = return . JSUint8Array
333instance ToJS x Word8 where
334 withJS n = withJSNumber $ fromIntegral n --TODO: Check things; throw exceptions
336instance ToJS x Int where
337 withJS n = withJSNumber $ fromIntegral n --TODO: Check things; throw exceptions
339instance FromJS x Int where
340 fromJS = liftM round . fromJSNumber --TODO: Check things; throw exceptions
342instance ToJS x Double where
343 withJS = withJSNumber
345instance FromJS x Double where
346 fromJS = fromJSNumber
348instance ToJS x Node where
352importJS :: Safety -> String -> String -> Q Type -> Q [Dec]
353importJS safety body name qt = do
355 let (argTypes, _) = parseType t
356 argNames <- forM argTypes $ \_ -> do
358 argRef <- newName "argRef"
360 (jsffiDecs, jsffiExp) <- mkJSFFI safety body
361 let go [] = [| runJS $(return jsffiExp) $(listE $ map (varE . snd) argNames) >>= fromJS
363 go ((arg, argRef) : args) = [| withJS $(varE arg) $ $(lamE [varP argRef] $ go args) |]
364 e <- lamE (map (varP. fst) argNames) $ go argNames
366 return $ jsffiDecs ++
368 , ValD (VarP n) (NormalB e) []
371mkJSFFI :: Safety -> String -> Q ([Dec], Exp)
373mkJSFFI safety body = do
374 -- n <- newName "jsffi" --TODO: Should use newName, but that doesn't seem to work with ghcjs
375 let n = mkName $ "jsffi_" ++ show (abs (hash (show safety, body)))
376 t <- [t| JSFFI_Internal |]
377 let wrappedBody = "(function(){ return (" ++ body ++ "); }).apply($1)"
378 let decs = [ForeignD $ ImportF JavaScript safety wrappedBody n t]
379 e <- [| JSFFI $(varE n) |]
383 e <- [| JSFFI body |]
387parseType :: Type -> ([Type], Type)
388parseType (ForallT _ [AppT (AppT (ConT monadJs) (VarT _)) (VarT m)] funType)
389 | monadJs == ''MonadJS = go funType
390 where go t = case t of
391 AppT (AppT ArrowT arg) t' ->
392 let (args, result) = go t'
393 in (arg : args, result)
394 AppT (VarT m') result
395 | m' == m -> ([], result)
396 _ -> error $ "parseType: can't parse type " ++ show t
397parseType t = error $ "parseType: can't parse type " ++ show t
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list