pythonista-jscore-runtime 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
jscore_runtime.py ADDED
@@ -0,0 +1,2448 @@
1
+ """
2
+ Pythonista JSCore Runtime Framework - Execute JavaScript and WebAssembly with seamless interop support natively in Pythonista 3.
3
+ Develop apps with Python, JavaScript and WebAssembly libraries, components and code.
4
+
5
+ https://github.com/M4nw3l/pythonista-jscore-runtime
6
+ """
7
+
8
+ __version__ = '0.0.1'
9
+
10
+ from ctypes import *
11
+ from ctypes.util import find_library
12
+ from objc_util import *
13
+ from objc_util import (c, object_getClass, class_getName, objc_getProtocol)
14
+ import weakref
15
+ from datetime import (datetime, timezone)
16
+ import json, re
17
+ from pathlib import Path
18
+ import tempfile, shutil, os
19
+ import ui
20
+
21
+ NSDate = ObjCClass("NSDate")
22
+ NSFileManager = ObjCClass("NSFileManager")
23
+
24
+ #objective c helpers
25
+ class objc:
26
+ # load_library from rubicon
27
+ #https://github.com/beeware/rubicon-objc/blob/1a97f483fdd83f4fc31050ee863535e3ed962944/src/rubicon/objc/runtime.py#L77
28
+ _lib_path = ["/usr/lib"]
29
+ _framework_path = ["/System/Library/Frameworks"]
30
+ @staticmethod
31
+ def load_library(name):
32
+ path = find_library(name)
33
+ if path is not None:
34
+ return CDLL(path)
35
+
36
+ for loc in _lib_path:
37
+ try:
38
+ return CDLL(os.path.join(loc, "lib" + name + ".dylib"))
39
+ except OSError:
40
+ pass
41
+
42
+ for loc in _framework_path:
43
+ try:
44
+ return CDLL(os.path.join(loc, name + ".framework", name))
45
+ except OSError:
46
+ pass
47
+ raise ValueError(f"Library {name!r} not found")
48
+
49
+ @staticmethod
50
+ def const(dll, name, typ = c_void_p):
51
+ if issubclass(typ, c_void_p):
52
+ return ObjCInstance(typ.in_dll(dll, name))
53
+ return typ.in_dll(dll, name)
54
+
55
+ @staticmethod
56
+ def c_func(func, restype, *argtypes):
57
+ func.restype = restype
58
+ func.argtypes = argtypes
59
+ return staticmethod(func)
60
+
61
+ objc_allocateProtocol = c_func(c.objc_allocateProtocol, c_void_p, c_char_p)
62
+ objc_protocol_addMethodDescription = c_func(c.protocol_addMethodDescription, None, c_void_p, c_void_p, c_char_p, c_bool, c_bool)
63
+ objc_protocol_addProtocol = c_func(c.protocol_addProtocol, None, c_void_p, c_void_p)
64
+ objc_protocol_addProperty = c_func(c.protocol_addProperty, None, c_void_p, c_char_p, c_void_p, c_uint, c_bool, c_bool)
65
+ objc_registerProtocol = c_func(c.objc_registerProtocol, None, c_void_p)
66
+
67
+ @staticmethod
68
+ def getProtocol(name):
69
+ return objc_getProtocol(name.encode("ascii"))
70
+
71
+ @staticmethod
72
+ def allocateProtocol(name):
73
+ return objc.objc_allocateProtocol(name.encode("ascii"))
74
+
75
+ @staticmethod
76
+ def get_type_encoding(typ):
77
+ # https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
78
+ # objective-c type encoding from a source type reference string - covers a fair few cases but is incomplete.
79
+ # While it also cannot understand some c type definitions properly e.g definitions containing refernces to typedef names
80
+ enc = []
81
+ while typ.endswith('*'):
82
+ enc.append("^")
83
+ typ = typ[:len(typ)-1]
84
+ typ = typ.strip()
85
+ parts = typ.split(' ')
86
+ upper = False
87
+ val = None
88
+ while len(parts) > 0:
89
+ part = parts.pop(0)
90
+ if part == "unsigned":
91
+ upper = True
92
+ elif part == "char":
93
+ c = len(enc)
94
+ for i in range(c):
95
+ enc[i] = "*" if enc[i] == "^" else enc[i]
96
+ val = "c" if c == 0 else ""
97
+ elif part in ["int", "void", "short", "float", "double"]:
98
+ val = part[0]
99
+ elif part == "long":
100
+ val = "l" if val is None else "q"
101
+ elif part in ["bool", "BOOL", "_Bool"]:
102
+ val = "B"
103
+ array = typ.endswith("]")
104
+ ptr = len(enc) == 1
105
+ struct = val is None and len(enc) > 0
106
+ arrlen=""
107
+ if array:
108
+ idx = list(typ).index('[')
109
+ arrlen = typ[idx:len(typ)-1]
110
+ typ = typ[:idx]
111
+ if val is not None:
112
+ enc.append(val)
113
+ if struct:
114
+ enc.append("{")
115
+ enc.append(typ)
116
+ if ptr:
117
+ enc.append("=#}")
118
+ else:
119
+ enc.append("}")
120
+ if array:
121
+ enc.insert(0, "["+arrlen)
122
+ enc.append("]")
123
+ return "".join(enc)
124
+
125
+ @staticmethod
126
+ def protocol_addMethodDescription(protocol, method, required, types = None, instance = None):
127
+ # add a protocol method description from its objective-c definition
128
+ method = method.strip()
129
+ name = "".join(re.findall("([A-z0-9]+:)", method))
130
+ description = ""
131
+ if instance is None:
132
+ instance = method.startswith("-")
133
+ if not instance and not method.startswith("+"):
134
+ raise ValueError(f"Method type is not specified as class (+) or instance (-) for method '{method}'")
135
+ if types is not None:
136
+ if isinstance(types, str):
137
+ description = types
138
+ else:
139
+ description = "".join(types)
140
+ else:
141
+ types = re.findall("\\(([A-z0-9 \*_\[\]]+)\\)", method)
142
+ description = []
143
+ for typ in types:
144
+ enc = objc.get_type_encoding(typ)
145
+ description.append(enc)
146
+ description = "".join(description)
147
+ #print(name)
148
+ selector = sel(name)
149
+ description = description.encode("ascii")
150
+ objc.objc_protocol_addMethodDescription(protocol, selector, description, required, instance)
151
+
152
+ @staticmethod
153
+ def protocol_addProperty(protocol, property, required, types = None, instance = None):
154
+ raise NotImplementedError("TODO: protocol_addProperty")
155
+
156
+ @staticmethod
157
+ def protocol_addProtocol(protocol, parent):
158
+ objc.objc_protocol_addProtocol(protocol, parent)
159
+
160
+ @staticmethod
161
+ # create a protocol from an objc definition
162
+ def protocol(name, body = [], types = [], protocols=["NSObject"], debug = True):
163
+ basename = name
164
+ p = objc.getProtocol(name)
165
+ if p is not None and not debug:
166
+ return name
167
+ counter = 0
168
+ while debug:
169
+ p = objc.getProtocol(name)
170
+ if p is None:
171
+ break
172
+ name = f"{basename}_{counter}"
173
+ counter = counter + 1
174
+ p = objc.allocateProtocol(name)
175
+ for id in protocols:
176
+ parent = objc.getProtocol("NSObject")
177
+ if parent is None:
178
+ raise ValueError(f"Protocol not found '{id}'")
179
+ objc.protocol_addProtocol(p, parent)
180
+ required = True
181
+ typesLen = len(types)
182
+ for i in range(len(body)):
183
+ method = body[i].strip()
184
+ methodTypes = None
185
+ if i < typesLen:
186
+ t = types[i]
187
+ if isinstance(t, str):
188
+ t = t.strip()
189
+ if t != "":
190
+ methodTypes = t
191
+ elif t is not None and len(t) > 0:
192
+ methodTypes = t
193
+ if method == "@required":
194
+ required = True
195
+ elif method == "@optional":
196
+ required = False
197
+ elif not ":" in method:
198
+ objc.protocol_addProperty(p, method, required, methodTypes) # TODO: properties
199
+ print(method)
200
+ else:
201
+ objc.protocol_addMethodDescription(p, method, required, methodTypes)
202
+ objc.objc_registerProtocol(p)
203
+ #print(name)
204
+ return name
205
+
206
+ new_class = create_objc_class
207
+
208
+ @staticmethod
209
+ def ns_class(nsobject):
210
+ if not (isinstance(nsobject, c_void_p) or isinstance(nsobject, ObjCInstance)):
211
+ return None
212
+ objClass = ObjCInstance(object_getClass(nsobject))
213
+ objClassName = class_getName(objClass)
214
+ return objClass
215
+
216
+ @staticmethod
217
+ def ns_subclass_of(nsobject, objcClass, objClass=None):
218
+ if not (isinstance(nsobject, c_void_p) or isinstance(nsobject, ObjCInstance)):
219
+ return False
220
+ if objClass is None:
221
+ objClass = objc.ns_class(nsobject)
222
+ if objClass is None:
223
+ return False
224
+ return objClass.isSubclassOfClass_(objcClass)
225
+
226
+ @staticmethod
227
+ def ns_to_py(nsobject, objClass=None):
228
+ if objClass is None:
229
+ objClass = objc.ns_class(nsobject)
230
+ if objc.ns_subclass_of(nsobject, NSString, objClass):
231
+ v = str(nsobject)
232
+ return v
233
+ if objc.ns_subclass_of(nsobject, NSNumber, objClass):
234
+ nsnumber = nsobject
235
+ doubleValue = float(nsnumber.doubleValue())
236
+ intValue = int(nsnumber.longLongValue())
237
+ if doubleValue == intValue:
238
+ return intValue
239
+ return doubleValue
240
+ if objc.ns_subclass_of(nsobject, NSDate, objClass):
241
+ nsdate = nsobject
242
+ timestamp = nsdate.timeIntervalSince1970()
243
+ return datetime.fromtimestamp(timestamp, timezone.utc)
244
+ if objc.ns_subclass_of(nsobject, NSArray, objClass):
245
+ nsarray = nsobject
246
+ items = []
247
+ for i in range(nsarray.count()):
248
+ item = objc.ns_to_py(nsarray.objectAtIndex_(i))
249
+ items.append(item)
250
+ return items
251
+ if objc.ns_subclass_of(nsobject, NSDictionary, objClass):
252
+ nsdict = nsobject
253
+ keys = nsdict.allKeys()
254
+ values = nsdict.allValues()
255
+ items = {}
256
+ for i in range(nsdict.count()):
257
+ key = objc.ns_to_py(keys.objectAtIndex_(i))
258
+ value = objc.ns_to_py(values.objectAtIndex_(i))
259
+ items[key] = value
260
+ return items
261
+ className = "unknown"
262
+ if objClass is not None:
263
+ className = class_getName(objClass)
264
+ raise NotImplementedError("Unhandled NSObject type {objClass} ({className}) for {nsobject}.")
265
+
266
+ @staticmethod
267
+ def c_array(count, items = None, typ = c_byte, ptr = c_void_p):
268
+ if items is None:
269
+ if callable(count):
270
+ items = []
271
+ iter = count
272
+ count = 0
273
+ while True:
274
+ try:
275
+ item = iter(count)
276
+ if item is None:
277
+ break
278
+ count = count + 1
279
+ items.append(item)
280
+ except:
281
+ break
282
+ elif isinstance(count, bytes) or isinstance(count, list):
283
+ items = count
284
+ count = len(items)
285
+ if count == 0:
286
+ if ptr is None:
287
+ return None
288
+ return cast(c_void_p(None), ptr) # NULL
289
+ c_array_typ = typ * count
290
+ array = c_array_typ()
291
+ if items is None:
292
+ if ptr is None:
293
+ return array
294
+ return cast(array, ptr)
295
+ if isinstance(items, bytes) or isinstance(items, list):
296
+ for i in range(count):
297
+ array[i] = items[i]
298
+ elif callable(items):
299
+ for i in range(count):
300
+ array[i] = items(i)
301
+ else:
302
+ raise NotImplementedError()
303
+ if ptr is None:
304
+ return array
305
+ return cast(array, ptr)
306
+
307
+ @staticmethod
308
+ def c_array_p(count, items = None, typ = c_void_p, ptr = c_void_p):
309
+ return objc.c_array(count, items, typ, ptr)
310
+
311
+ @staticmethod
312
+ def nsdata_from_file(path, fileManager = None):
313
+ if fileManager is None:
314
+ fileManager = NSFileManager.defaultManager()
315
+ path = Path(str(path))
316
+ if not path.is_absolute():
317
+ path = path.cwd().joinpath(path)
318
+ if not path.exists():
319
+ raise FileNotFoundError(f"File not found at path '{path}'")
320
+ path = str(path)
321
+ data = fileManager.contentsAtPath_(path)
322
+ return data
323
+
324
+
325
+ #JavaScriptCore api
326
+ class jscore:
327
+ JSVirtualMachine = ObjCClass("JSVirtualMachine")
328
+ JSContext = ObjCClass("JSContext")
329
+ JSValue = ObjCClass("JSValue")
330
+ JSManagedValue = ObjCClass("JSManagedValue")
331
+ JSScript = ObjCClass("JSScript")
332
+ JSExport = ObjCClass("JSExport")
333
+ JSObjCClassInfo = ObjCClass("JSObjCClassInfo")
334
+ JSWrapperMap = ObjCClass("JSWrapperMap")
335
+ JSVMWrapperCache = ObjCClass("JSVMWrapperCache")
336
+ WTFWebFileManagerDelegate = ObjCClass("WTFWebFileManagerDelegate")
337
+
338
+ # c api
339
+ lib = objc.load_library("JavaScriptCore")
340
+
341
+ JSContextGetGroup = objc.c_func(lib.JSContextGetGroup, c_void_p, c_void_p)
342
+ JSContextGroupCreate = objc.c_func(lib.JSContextGroupCreate, c_void_p)
343
+ JSContextGroupRetain = objc.c_func(lib.JSContextGroupRetain, c_void_p, c_void_p)
344
+ JSContextGroupRelease = objc.c_func(lib.JSContextGroupRelease, None, c_void_p)
345
+ JSContextGetGlobalContext = objc.c_func(lib.JSContextGetGlobalContext, c_void_p, c_void_p)
346
+ JSContextGetGlobalObject = objc.c_func(lib.JSContextGetGlobalObject, c_void_p, c_void_p)
347
+
348
+ JSGlobalContextCreate = objc.c_func(lib.JSGlobalContextCreate, c_void_p, c_void_p)
349
+ JSGlobalContextCreateInGroup = objc.c_func(lib.JSGlobalContextCreateInGroup, c_void_p, c_void_p, c_void_p)
350
+ JSGlobalContextRetain = objc.c_func(lib.JSGlobalContextRetain, c_void_p, c_void_p)
351
+ JSGlobalContextRelease = objc.c_func(lib.JSGlobalContextRelease, None, c_void_p)
352
+ JSGlobalContextCopyName = objc.c_func(lib.JSGlobalContextCopyName, c_void_p, c_void_p)
353
+ JSGlobalContextSetName = objc.c_func(lib.JSGlobalContextSetName, None, c_void_p, c_void_p)
354
+ JSGlobalContextIsInspectable = objc.c_func(lib.JSGlobalContextIsInspectable, c_bool, c_void_p)
355
+ JSGlobalContextSetInspectable = objc.c_func(lib.JSGlobalContextSetInspectable, None, c_void_p, c_bool)
356
+
357
+ JSChar_p = POINTER(c_ushort)
358
+ JSStringCreateWithCharacters = objc.c_func(lib.JSStringCreateWithCharacters, c_void_p, c_void_p, c_size_t)
359
+ JSStringCreateWithUTF8CString = objc.c_func(lib.JSStringCreateWithUTF8CString, c_void_p, c_void_p)
360
+ JSStringRetain = objc.c_func(lib.JSStringRetain, c_void_p, c_void_p)
361
+ JSStringRelease = objc.c_func(lib.JSStringRelease, None, c_void_p)
362
+ JSStringGetLength = objc.c_func(lib.JSStringGetLength, c_size_t, c_void_p)
363
+ JSStringGetCharactersPtr = objc.c_func(lib.JSStringGetCharactersPtr, JSChar_p, c_void_p)
364
+ JSStringGetMaximumUTF8CStringSize = objc.c_func(lib.JSStringGetMaximumUTF8CStringSize, c_size_t, c_void_p)
365
+ JSStringGetUTF8CString = objc.c_func(lib.JSStringGetUTF8CString, c_size_t, c_void_p, c_void_p, c_size_t)
366
+ JSStringIsEqual = objc.c_func(lib.JSStringIsEqual, c_bool, c_void_p, c_void_p)
367
+ JSStringIsEqualToUTF8CString = objc.c_func(lib.JSStringIsEqualToUTF8CString, c_bool, c_void_p, c_void_p)
368
+ JSStringCreateWithCFString = objc.c_func(lib.JSStringCreateWithCFString, c_void_p, c_void_p)
369
+ JSStringCopyCFString = objc.c_func(lib.JSStringCopyCFString, c_void_p, c_void_p, c_void_p)
370
+
371
+ JSValueGetType = objc.c_func(lib.JSValueGetType, c_int, c_void_p, c_void_p)
372
+ JSValueIsUndefined = objc.c_func(lib.JSValueIsUndefined, c_bool, c_void_p, c_void_p)
373
+ JSValueIsNull = objc.c_func(lib.JSValueIsNull, c_bool, c_void_p, c_void_p)
374
+ JSValueIsBoolean = objc.c_func(lib.JSValueIsBoolean, c_bool, c_void_p, c_void_p)
375
+ JSValueIsNumber = objc.c_func(lib.JSValueIsNumber, c_bool, c_void_p, c_void_p)
376
+ JSValueIsString = objc.c_func(lib.JSValueIsString, c_bool, c_void_p, c_void_p)
377
+ JSValueIsSymbol = objc.c_func(lib.JSValueIsSymbol, c_bool, c_void_p, c_void_p)
378
+ JSValueIsObject = objc.c_func(lib.JSValueIsObject, c_bool, c_void_p, c_void_p)
379
+ JSValueIsObjectOfClass = objc.c_func(lib.JSValueIsObjectOfClass, c_bool, c_void_p, c_void_p, c_void_p)
380
+ JSValueIsArray = objc.c_func(lib.JSValueIsArray, c_bool, c_void_p, c_void_p)
381
+ JSValueIsDate = objc.c_func(lib.JSValueIsDate, c_bool, c_void_p, c_void_p)
382
+ JSValueGetTypedArrayType = objc.c_func(lib.JSValueGetTypedArrayType, c_int, c_void_p, c_void_p, c_void_p)
383
+ JSValueMakeUndefined = objc.c_func(lib.JSValueMakeUndefined, c_void_p, c_void_p)
384
+ JSValueMakeNull = objc.c_func(lib.JSValueMakeNull, c_void_p, c_void_p)
385
+ JSValueMakeBoolean = objc.c_func(lib.JSValueMakeBoolean, c_void_p, c_void_p, c_bool)
386
+ JSValueMakeNumber = objc.c_func(lib.JSValueMakeNumber, c_void_p, c_void_p, c_double)
387
+ JSValueMakeString = objc.c_func(lib.JSValueMakeString, c_void_p, c_void_p, c_void_p)
388
+ JSValueMakeSymbol = objc.c_func(lib.JSValueMakeSymbol, c_void_p, c_void_p, c_void_p)
389
+ JSValueToBoolean = objc.c_func(lib.JSValueToBoolean, c_bool, c_void_p, c_void_p)
390
+ JSValueToNumber = objc.c_func(lib.JSValueToNumber, c_double, c_void_p, c_void_p, c_void_p)
391
+ JSValueToStringCopy = objc.c_func(lib.JSValueToStringCopy, c_void_p, c_void_p, c_void_p, c_void_p)
392
+ JSValueToObject = objc.c_func(lib.JSValueToObject, c_void_p, c_void_p, c_void_p, c_void_p)
393
+ JSValueMakeFromJSONString = objc.c_func(lib.JSValueMakeFromJSONString, c_void_p, c_void_p, c_void_p)
394
+ JSValueCreateJSONString = objc.c_func(lib.JSValueCreateJSONString, c_void_p, c_void_p, c_void_p, c_uint, c_void_p)
395
+ JSValueIsEqual = objc.c_func(lib.JSValueIsEqual, c_bool, c_void_p, c_void_p, c_void_p, c_void_p)
396
+ JSValueIsStrictEqual = objc.c_func(lib.JSValueIsStrictEqual, c_bool, c_void_p, c_void_p, c_void_p)
397
+ JSValueIsInstanceOfConstructor = objc.c_func(lib.JSValueIsInstanceOfConstructor, c_bool, c_void_p, c_void_p, c_void_p, c_void_p)
398
+ JSValueProtect = objc.c_func(lib.JSValueProtect, None, c_void_p, c_void_p)
399
+ JSValueUnprotect = objc.c_func(lib.JSValueUnprotect, None, c_void_p, c_void_p)
400
+
401
+ JSObjectCallAsConstructor = objc.c_func(lib.JSObjectCallAsConstructor, c_void_p, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p)
402
+ JSObjectCallAsFunction = objc.c_func(lib.JSObjectCallAsFunction, c_void_p, c_void_p, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p)
403
+ JSObjectCopyPropertyNames = objc.c_func(lib.JSObjectCopyPropertyNames, c_void_p, c_void_p, c_void_p)
404
+ JSObjectDeleteProperty = objc.c_func(lib.JSObjectDeleteProperty, c_bool, c_void_p, c_void_p, c_void_p, c_void_p)
405
+ JSObjectGetPrivate = objc.c_func(lib.JSObjectGetPrivate, c_void_p, c_void_p)
406
+ JSObjectGetProperty = objc.c_func(lib.JSObjectGetProperty, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p)
407
+ JSObjectGetPropertyAtIndex = objc.c_func(lib.JSObjectGetPropertyAtIndex, c_void_p, c_void_p, c_void_p, c_uint, c_void_p)
408
+ JSObjectGetPrototype = objc.c_func(lib.JSObjectGetPrototype, c_void_p, c_void_p, c_void_p)
409
+ JSObjectHasProperty = objc.c_func(lib.JSObjectHasProperty, c_bool, c_void_p, c_void_p, c_void_p)
410
+ JSObjectIsConstructor = objc.c_func(lib.JSObjectIsConstructor, c_bool, c_void_p, c_void_p)
411
+ JSObjectIsFunction = objc.c_func(lib.JSObjectIsFunction, c_bool, c_void_p, c_void_p)
412
+ JSObjectMake = objc.c_func(lib.JSObjectMake, c_void_p, c_void_p, c_void_p, c_void_p)
413
+ JSObjectMakeArray = objc.c_func(lib.JSObjectMakeArray, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p)
414
+ JSObjectMakeConstructor = objc.c_func(lib.JSObjectMakeConstructor, c_void_p, c_void_p, c_void_p, c_void_p)
415
+ JSObjectMakeDate = objc.c_func(lib.JSObjectMakeDate, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p)
416
+ JSObjectMakeError = objc.c_func(lib.JSObjectMakeError, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p)
417
+ JSObjectMakeFunction = objc.c_func(lib.JSObjectMakeFunction, c_void_p, c_void_p, c_void_p, c_uint, c_void_p, c_void_p, c_void_p, c_int, c_void_p)
418
+ JSObjectCallAsFunctionCallback = CFUNCTYPE(c_void_p, c_void_p, c_void_p, c_void_p, c_ulong, c_void_p, c_void_p)
419
+ JSObjectMakeFunctionWithCallback = objc.c_func(lib.JSObjectMakeFunctionWithCallback, c_void_p, c_void_p, c_void_p, c_void_p)
420
+ JSObjectMakeRegExp = objc.c_func(lib.JSObjectMakeRegExp, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p)
421
+ JSObjectSetPrivate = objc.c_func(lib.JSObjectSetPrivate, c_bool, c_void_p, c_void_p)
422
+ JSObjectSetProperty = objc.c_func(lib.JSObjectSetProperty, None, c_void_p, c_void_p, c_void_p, c_void_p, c_uint, c_void_p)
423
+ JSObjectSetPropertyAtIndex = objc.c_func(lib.JSObjectSetPropertyAtIndex, None, c_void_p, c_void_p, c_uint, c_void_p, c_void_p)
424
+ JSObjectGetPropertyForKey = objc.c_func(lib.JSObjectGetPropertyForKey, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p)
425
+ JSObjectSetPrototype = objc.c_func(lib.JSObjectSetPrototype, None, c_void_p, c_void_p, c_void_p)
426
+ JSObjectDeletePropertyForKey = objc.c_func(lib.JSObjectDeletePropertyForKey, c_bool, c_void_p, c_void_p, c_void_p, c_void_p)
427
+ JSObjectHasPropertyForKey = objc.c_func(lib.JSObjectHasPropertyForKey, c_bool, c_void_p, c_void_p, c_void_p, c_void_p)
428
+ JSObjectSetPropertyForKey = objc.c_func(lib.JSObjectSetPropertyForKey, None, c_void_p, c_void_p, c_void_p, c_void_p, c_uint, c_void_p)
429
+ JSObjectMakeDeferredPromise = objc.c_func(lib.JSObjectMakeDeferredPromise, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p)
430
+
431
+ JSClassCreate = objc.c_func(lib.JSClassCreate, c_void_p, c_void_p)
432
+ JSClassRelease = objc.c_func(lib.JSClassRelease, None, c_void_p)
433
+ JSClassRetain = objc.c_func(lib.JSClassRetain, c_void_p, c_void_p)
434
+
435
+ JSPropertyNameAccumulatorAddName = objc.c_func(lib.JSPropertyNameAccumulatorAddName, None, c_void_p, c_void_p)
436
+ JSPropertyNameArrayGetCount = objc.c_func(lib.JSPropertyNameArrayGetCount, c_size_t, c_void_p)
437
+ JSPropertyNameArrayGetNameAtIndex = objc.c_func(lib.JSPropertyNameArrayGetNameAtIndex, c_void_p, c_void_p, c_size_t)
438
+ JSPropertyNameArrayRelease = objc.c_func(lib.JSPropertyNameArrayRelease, None, c_void_p)
439
+ JSPropertyNameArrayRetain = objc.c_func(lib.JSPropertyNameArrayRetain, c_void_p, c_void_p)
440
+
441
+ JSObjectMakeTypedArray = objc.c_func(lib.JSObjectMakeTypedArray, c_void_p, c_void_p, c_int, c_size_t, c_void_p)
442
+ JSObjectMakeTypedArrayWithBytesNoCopy = objc.c_func(lib.JSObjectMakeTypedArrayWithBytesNoCopy, c_void_p, c_void_p, c_int, c_void_p, c_size_t, c_void_p, c_void_p, c_void_p)
443
+ JSObjectMakeTypedArrayWithArrayBuffer = objc.c_func(lib.JSObjectMakeTypedArrayWithArrayBuffer, c_void_p, c_void_p, c_int, c_void_p, c_void_p)
444
+ JSObjectMakeTypedArrayWithArrayBufferAndOffset = objc.c_func(lib.JSObjectMakeTypedArrayWithArrayBufferAndOffset, c_void_p, c_void_p, c_int, c_void_p, c_size_t, c_size_t, c_void_p)
445
+ JSObjectGetTypedArrayBytesPtr = objc.c_func(lib.JSObjectGetTypedArrayBytesPtr, c_void_p, c_void_p, c_void_p, c_void_p)
446
+ JSObjectGetTypedArrayLength = objc.c_func(lib.JSObjectGetTypedArrayLength, c_size_t, c_void_p, c_void_p, c_void_p)
447
+ JSObjectGetTypedArrayByteLength = objc.c_func(lib.JSObjectGetTypedArrayByteLength, c_size_t, c_void_p, c_void_p, c_void_p)
448
+ JSObjectGetTypedArrayByteOffset = objc.c_func(lib.JSObjectGetTypedArrayByteOffset, c_size_t, c_void_p, c_void_p, c_void_p)
449
+ JSObjectGetTypedArrayBuffer = objc.c_func(lib.JSObjectGetTypedArrayBuffer, c_void_p, c_void_p, c_void_p, c_void_p)
450
+ JSObjectMakeArrayBufferWithBytesNoCopy = objc.c_func(lib.JSObjectMakeArrayBufferWithBytesNoCopy, c_void_p, c_void_p, c_void_p, c_size_t, c_void_p, c_void_p, c_void_p)
451
+ JSObjectGetArrayBufferByteLength = objc.c_func(lib.JSObjectGetArrayBufferByteLength, c_size_t, c_void_p, c_void_p, c_void_p)
452
+ JSObjectGetArrayBufferBytesPtr = objc.c_func(lib.JSObjectGetArrayBufferBytesPtr, c_void_p, c_void_p, c_void_p, c_void_p)
453
+
454
+ kJSTypedArrayTypeInt8Array = 0
455
+ kJSTypedArrayTypeInt16Array = 1
456
+ kJSTypedArrayTypeInt32Array = 2
457
+ kJSTypedArrayTypeUint8Array = 3
458
+ kJSTypedArrayTypeUint8ClampedArray = 4
459
+ kJSTypedArrayTypeUint16Array = 5
460
+ kJSTypedArrayTypeUint32Array = 6
461
+ kJSTypedArrayTypeFloat32Array = 7
462
+ kJSTypedArrayTypeFloat64Array = 8
463
+ kJSTypedArrayTypeArrayBuffer = 9
464
+ kJSTypedArrayTypeNone = 10
465
+ kJSTypedArrayTypeBigInt64Array = 11
466
+ kJSTypedArrayTypeBigUint64Array = 12
467
+
468
+ JSCheckScriptSyntax = objc.c_func(lib.JSCheckScriptSyntax, c_bool, c_void_p, c_void_p, c_void_p, c_int, c_void_p)
469
+ JSEvaluateScript = objc.c_func(lib.JSEvaluateScript, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_int, c_void_p)
470
+ JSGarbageCollect = objc.c_func(lib.JSGarbageCollect, None, c_void_p)
471
+
472
+ JSBigIntCreateWithDouble = objc.c_func(lib.JSBigIntCreateWithDouble, c_void_p, c_void_p, c_double, c_void_p)
473
+ JSBigIntCreateWithInt64 = objc.c_func(lib.JSBigIntCreateWithInt64, c_void_p, c_void_p, c_int64, c_void_p)
474
+ JSBigIntCreateWithString = objc.c_func(lib.JSBigIntCreateWithString, c_void_p, c_void_p, c_void_p, c_void_p)
475
+ JSBigIntCreateWithUInt64 = objc.c_func(lib.JSBigIntCreateWithUInt64, c_void_p, c_void_p, c_uint64, c_void_p)
476
+
477
+ JSValueCompare = objc.c_func(lib.JSValueCompare, c_int, c_void_p, c_void_p, c_void_p, c_void_p)
478
+ JSValueCompareDouble = objc.c_func(lib.JSValueCompareDouble, c_int, c_void_p, c_void_p, c_void_p, c_void_p)
479
+ JSValueCompareInt64 = objc.c_func(lib.JSValueCompareInt64, c_int, c_void_p, c_void_p, c_void_p, c_void_p)
480
+ JSValueCompareUInt64 = objc.c_func(lib.JSValueCompareUInt64, c_int, c_void_p, c_void_p, c_void_p, c_void_p)
481
+ JSValueIsBigInt = objc.c_func(lib.JSValueIsBigInt, c_bool, c_void_p, c_void_p)
482
+ JSValueToInt32 = objc.c_func(lib.JSValueToInt32, c_int32, c_void_p, c_void_p, c_void_p)
483
+ JSValueToInt64 = objc.c_func(lib.JSValueToInt64, c_int64, c_void_p, c_void_p, c_void_p)
484
+ JSValueToUInt32 = objc.c_func(lib.JSValueToUInt32, c_uint32, c_void_p, c_void_p, c_void_p)
485
+ JSValueToUInt64 = objc.c_func(lib.JSValueToUInt64, c_uint64, c_void_p, c_void_p, c_void_p)
486
+
487
+ # internal api (for module loader)
488
+
489
+ # https://github.com/WebKit/WebKit/blob/7321e0dc891bcc0dce916e8d71204e58d92641cd/Source/JavaScriptCore/API/JSScriptRefPrivate.h
490
+ #JSScriptRef
491
+ JSScriptCreateReferencingImmortalASCIIText = objc.c_func(lib.JSScriptCreateReferencingImmortalASCIIText, c_void_p, c_void_p, c_void_p, c_int, c_void_p, c_size_t, c_void_p, c_void_p)
492
+ JSScriptCreateFromString = objc.c_func(lib.JSScriptCreateFromString, c_void_p, c_void_p, c_void_p, c_int, c_void_p, c_void_p, c_void_p)
493
+ JSScriptRetain = objc.c_func(lib.JSScriptRetain, None, c_void_p)
494
+ JSScriptRelease = objc.c_func(lib.JSScriptRelease, None, c_void_p)
495
+ JSScriptEvaluate = objc.c_func(lib.JSScriptEvaluate, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p)
496
+
497
+ kJSScriptTypeProgram = 0
498
+ kJSScriptTypeModule = 1
499
+
500
+ # It is necessary to define the JSModuleLoaderDelegate protocol before we can use it as its not a public protocol, for original definition
501
+ # see: https://github.com/WebKit/WebKit/blob/3a620254c233064790f172eb54eee6db874be7b1/Source/JavaScriptCore/API/JSContextPrivate.h
502
+ JSModuleLoaderDelegate = objc.protocol("JSModuleLoaderDelegate", body=[
503
+ "@required",
504
+ "- (void)context:(JSContext *)context fetchModuleForIdentifier:(JSValue *)identifier withResolveHandler:(JSValue *)resolve andRejectHandler:(JSValue *)reject;",
505
+ "@optional",
506
+ "- (void)willEvaluateModule:(NSURL *)key;",
507
+ "- (void)didEvaluateModule:(NSURL *)key;"
508
+ ], debug=False)
509
+
510
+ # forwards calls to loader
511
+ def JSCoreModuleLoaderDelegate_context_fetchModuleForIdentifier_withResolveHandler_andRejectHandler_(_self,_cmd, _ctx, _id, _resolve, _reject):
512
+ loader = ObjCInstance(_self)._pyinstance()
513
+ module = javascript_value(ObjCInstance(_id)).value
514
+ resolve = javascript_function(ObjCInstance(_resolve))
515
+ reject = javascript_function(ObjCInstance(_reject))
516
+ loader.fetch_module(module, resolve, reject)
517
+
518
+ f = JSCoreModuleLoaderDelegate_context_fetchModuleForIdentifier_withResolveHandler_andRejectHandler_
519
+ f.argtypes = [c_void_p,c_void_p,c_void_p,c_void_p]
520
+ f.encoding = "@:@@@@@"
521
+
522
+ # forward to loader when defined (though does not appear to be called)
523
+ def JSCoreModuleLoaderDelegate_willEvaluateModule_(_self,_cmd,_url):
524
+ loader = ObjCInstance(_self)._pyinstance()
525
+ handler = getattr(loader, 'will_eval_module')
526
+ if handler is not None:
527
+ handler(ObjCInstance(_url))
528
+
529
+ f = JSCoreModuleLoaderDelegate_willEvaluateModule_
530
+ f.argtypes = [c_void_p]
531
+ f.encoding = "@:@@"
532
+
533
+ # forward to loader when defined (though does not appear to be called)
534
+ def JSCoreModuleLoaderDelegate_didEvaluateModule_(_self,_cmd,_url):
535
+ loader = ObjCInstance(_self)._pyinstance()
536
+ handler = getattr(loader, 'did_eval_module')
537
+ if handler is not None:
538
+ handler(ObjCInstance(_url))
539
+
540
+ f = JSCoreModuleLoaderDelegate_didEvaluateModule_
541
+ f.argtypes = [c_void_p]
542
+ f.encoding = "@:@@"
543
+
544
+ # JSModuleLoaderDelegate protocol implementation
545
+ JSCoreModuleLoaderDelegate = objc.new_class("JSCoreModuleLoaderDelegate", protocols=[JSModuleLoaderDelegate], methods = [
546
+ JSCoreModuleLoaderDelegate_context_fetchModuleForIdentifier_withResolveHandler_andRejectHandler_,
547
+ JSCoreModuleLoaderDelegate_willEvaluateModule_,
548
+ JSCoreModuleLoaderDelegate_didEvaluateModule_
549
+ ])
550
+
551
+ _runtime_vm = None
552
+ _runtimes = {}
553
+ _runtime_cleanups = []
554
+ @classmethod
555
+ def new_runtime(cls, runtime_class, *args, **kwargs):
556
+ if runtime_class is None:
557
+ raise ValueError("runtime_class must be specified")
558
+ return runtime_class(*args, **kwargs)
559
+
560
+ # runtime singleton access
561
+ @classmethod
562
+ def runtime(cls, runtime_class = None):
563
+ if runtime_class is None:
564
+ runtime_class = javascript_runtime
565
+ runtime = jscore._runtimes.get(runtime_class)
566
+ if runtime is None:
567
+ if cls._runtime_vm is None:
568
+ cls._runtime_vm = cls.vm_allocate()
569
+ runtime = cls.new_runtime(runtime_class, cls._runtime_vm)
570
+ jscore._runtimes[runtime_class] = runtime
571
+ return runtime
572
+
573
+ @classmethod
574
+ def vm_allocate(cls):
575
+ vm = jscore.JSVirtualMachine.alloc().init()
576
+ retain_global(vm)
577
+ return vm
578
+
579
+ @classmethod
580
+ def vm_deallocate(cls, vm):
581
+ release_global(vm)
582
+
583
+ @classmethod
584
+ def runtime_deallocate(cls, runtime, vm_owner):
585
+ vm = runtime.vm
586
+ if vm_owner:
587
+ cls.vm_deallocate(vm)
588
+ runtime.vm = None # always drop the vm reference as the runtime is done with it
589
+ runtime_scripts = list(runtime.scripts)
590
+ def cleanup():
591
+ released = [] # avoid releasing more than once
592
+ for script in runtime_scripts:
593
+ if not script in released:
594
+ if isinstance(script, jsscript_ref):
595
+ script.release()
596
+ else:
597
+ release_global(script)
598
+ released.append(script)
599
+ if vm_owner:
600
+ cleanup()
601
+ else:
602
+ cls._runtime_cleanups.append(cleanup)
603
+ key = runtime.__class__
604
+ rt = cls._runtimes.get(key)
605
+ if runtime is rt: # remove destroyed runtime if its a tracked singleton instance
606
+ del cls._runtimes[key]
607
+ if len(cls._runtimes) == 0:
608
+ cls.vm_deallocate(cls._runtime_vm) # if we destroyed the last singleton runtime reference cleanup vm
609
+ cls._runtime_vm = None
610
+ for cleanup in cls._runtime_cleanups:
611
+ cleanup()
612
+ cls._runtime_cleanups = []
613
+
614
+ _context_ref_context_lookup = {}
615
+ @classmethod
616
+ def context_allocate(cls, vm):
617
+ context = jscore.JSContext.alloc().initWithVirtualMachine_(vm)
618
+ retain_global(context)
619
+ context.setInspectable(True)
620
+ context_ref = context.JSGlobalContextRef()
621
+ cls._context_ref_context_lookup[context_ref.value] = context
622
+ return context
623
+
624
+ @classmethod
625
+ def context_deallocate(cls, context):
626
+ context_ref = context.JSGlobalContextRef()
627
+ del cls._context_ref_context_lookup[context_ref.value]
628
+ release_global(context)
629
+
630
+ @classmethod
631
+ def context_ref_to_context(cls, context_ref):
632
+ if context_ref is None:
633
+ return None
634
+ return cls._context_ref_context_lookup[context_ref.value]
635
+
636
+ @classmethod
637
+ def context_eval(cls, context, script, sourceUrl = None):
638
+ result = None
639
+ if sourceUrl is None or sourceUrl.strip() == '':
640
+ result = context.evaluateScript_(script)
641
+ else:
642
+ result = context.evaluateScript_withSourceUrl_(script, sourceUrl)
643
+ result = ObjCInstance(result)
644
+ ex = context.exception()
645
+ if ex is not None:
646
+ context.setException(None) # clear exception if set
647
+ return result, ex
648
+
649
+ # jscore values conversions
650
+ @classmethod
651
+ def jsstringref_to_py(cls, str_ref):
652
+ chars_len = jscore.JSStringGetLength(str_ref)
653
+ chars_ref = jscore.JSStringGetCharactersPtr(str_ref)
654
+ str_utf16 = string_at(chars_ref, chars_len*2)
655
+ str_decoded = str_utf16.decode('utf-16')
656
+ return str_decoded
657
+
658
+ @classmethod
659
+ def str_to_jsstringref(cls, str_py):
660
+ if str_py is None:
661
+ return None
662
+ str_py = str(str_py)
663
+ str_len = len(str_py)
664
+ str_utf16 = objc.c_array(str_py.encode("utf-16le"))
665
+ str_ref = jscore.JSStringCreateWithCharacters(str_utf16, str_len)
666
+ return cast(cls.JSStringRetain(str_ref), c_void_p)
667
+
668
+ @classmethod
669
+ def jsobjectref_keys(cls, context_ref, value_ref):
670
+ names = []
671
+ names_ref = jscore.JSObjectCopyPropertyNames(context_ref, value_ref)
672
+ count = jscore.JSPropertyNameArrayGetCount(names_ref)
673
+ for i in range(count):
674
+ str_ref = jscore.JSPropertyNameArrayGetNameAtIndex(names_ref, i)
675
+ name = cls.jsstringref_to_py(str_ref)
676
+ names.append(name)
677
+ jscore.JSPropertyNameArrayRelease(names_ref)
678
+ return names
679
+
680
+ @classmethod
681
+ def jsvalue_get_refs(cls, value):
682
+ context_ref = value.context().JSGlobalContextRef()
683
+ value_ref = value.JSValueRef()
684
+ return context_ref, value_ref
685
+
686
+ @classmethod
687
+ def jsvalue_jsobject_to_py(cls, value):
688
+ context_ref, value_ref = cls.jsvalue_get_refs(value)
689
+ if jscore.JSObjectIsFunction(context_ref, value_ref):
690
+ return javascript_function(value, context_ref, value_ref)
691
+ return cls.jsobjectref_to_py(context_ref, value_ref)
692
+
693
+ @classmethod
694
+ def jsvalue_to_py(cls, value):
695
+ if value is None or value.isNull():
696
+ return None
697
+
698
+ if javascript_value.is_undefined(value) or value.isUndefined():
699
+ return javascript_value.undefined
700
+
701
+ if value.isBoolean():
702
+ return value.toBool()
703
+
704
+ if value.isNumber() or value.isString() or value.isDate():
705
+ return objc.ns_to_py(value.toObject())
706
+
707
+ if value.isSymbol():
708
+ return javascript_symbol(value)
709
+
710
+ return cls.jsvalue_jsobject_to_py(value)
711
+
712
+ @classmethod
713
+ def jsvalue_is_object(cls, value):
714
+ if javascript_value.is_undefined(value) or not value.isObject():
715
+ return False
716
+ context_ref, value_ref = cls.jsvalue_get_refs(value)
717
+ if jscore.JSObjectIsFunction(context_ref, value_ref):
718
+ return False
719
+ return True
720
+
721
+ @classmethod
722
+ def jsvalue_is_array_type(cls, value, typedArrayType):
723
+ if not objc.ns_subclass_of(value, jscore.JSValue):
724
+ return False
725
+ context_ref, value_ref = cls.jsvalue_get_refs(value)
726
+ ex = c_void_p(None)
727
+ arrayType = cls.JSValueGetTypedArrayType(context_ref, value_ref, byref(ex))
728
+ return arrayType == typedArrayType
729
+
730
+ @classmethod
731
+ def jsobject_get_keys(cls, value):
732
+ if javascript_value.is_undefined(value) or not value.isObject():
733
+ return []
734
+ context_ref, value_ref = cls.jsvalue_get_refs(value)
735
+ if jscore.JSObjectIsFunction(context_ref, value_ref):
736
+ return []
737
+ return cls.jsobjectref_keys(context_ref, value_ref)
738
+
739
+ @classmethod
740
+ def jsobjectref_to_py(cls, context_ref, value_ref):
741
+ ex = c_void_p(None)
742
+ value_ref = cls.JSValueToObject(context_ref, value_ref, byref(ex))
743
+ if cls.JSObjectIsFunction(context_ref, value_ref):
744
+ str_ref = cls.JSValueToStringCopy(context_ref, value_ref, byref(ex))
745
+ source = None
746
+ if str_ref:
747
+ source = cls.jsstringref_to_py(str_ref)
748
+ return javascript_function(None, context_ref, value_ref, source)
749
+ names_ref = cls.JSObjectCopyPropertyNames(context_ref, value_ref)
750
+ count = cls.JSPropertyNameArrayGetCount(names_ref)
751
+ obj = None
752
+ if cls.JSValueIsArray(context_ref, value_ref):
753
+ obj = []
754
+ for i in range(count):
755
+ key_ref = cls.JSPropertyNameArrayGetNameAtIndex(names_ref, i)
756
+ jsvalue_ref = cls.JSObjectGetProperty(context_ref, value_ref, key_ref, byref(ex))
757
+ obj.append(cls.jsvalueref_to_py(context_ref, jsvalue_ref))
758
+ else:
759
+ prototype_ref = cls.JSObjectGetPrototype(context_ref, value_ref)
760
+ obj = cls.jsvalueref_to_py(context_ref, prototype_ref)
761
+ if javascript_value.is_null_or_undefined(obj):
762
+ obj = {}
763
+ for i in range(count):
764
+ key_ref = cls.JSPropertyNameArrayGetNameAtIndex(names_ref, i)
765
+ jsvalue_ref = cls.JSObjectGetProperty(context_ref, value_ref, key_ref, byref(ex))
766
+ key = cls.jsstringref_to_py(key_ref)
767
+ obj[key] = cls.jsvalueref_to_py(context_ref, jsvalue_ref)
768
+ cls.JSPropertyNameArrayRelease(names_ref)
769
+ return obj
770
+
771
+ @classmethod
772
+ def jsvalueref_to_py(cls, context_ref, value_ref):
773
+ if value_ref is None:
774
+ return None
775
+ if cls.JSValueIsUndefined(context_ref, value_ref):
776
+ return javascript_value.undefined
777
+ if cls.JSValueIsNull(context_ref, value_ref):
778
+ return None
779
+ if cls.JSValueIsBoolean(context_ref, value_ref):
780
+ return cls.JSValueToBoolean(context_ref, value_ref)
781
+ if cls.JSValueIsNumber(context_ref, value_ref):
782
+ ex = c_void_p(None)
783
+ return cls.JSValueToNumber(context_ref, value_ref, byref(ex))
784
+ if cls.JSValueIsString(context_ref, value_ref):
785
+ ex = c_void_p(None)
786
+ str_ref = cls.JSValueToStringCopy(context_ref, value_ref, byref(ex))
787
+ if str_ref:
788
+ return cls.jsstringref_to_py(str_ref)
789
+ return ""
790
+ if cls.JSValueIsDate(context_ref, value_ref):
791
+ ex = c_void_p(None)
792
+ str_ref = cls.JSValueCreateJSONString(context_ref, value_ref, 0, byref(ex))
793
+ json_date = cls.jsstringref_to_py(str_ref)
794
+ return datetime.strptime(json_date, '"%Y-%m-%dT%H:%M:%S.%fZ"').replace(tzinfo=timezone.utc)
795
+ if cls.JSValueIsSymbol(context_ref, value_ref):
796
+ ex = c_void_p(None)
797
+ str_ref = cls.JSValueToStringCopy(context_ref, value_ref, byref(ex))
798
+ symbol = cls.jsstringref_to_py(str_ref)
799
+ return javascript_symbol(symbol)
800
+ if cls.JSValueIsObject(context_ref, value_ref):
801
+ return cls.jsobjectref_to_py(context_ref, value_ref)
802
+ raise NotImplementedError("Unknown value_ref type")
803
+
804
+ @classmethod
805
+ def _py_to_jsvalueref(cls, context_ref, value, parent_ref = None):
806
+ if value is None:
807
+ return cls.JSValueMakeNull(context_ref)
808
+ if javascript_value.is_undefined(value):
809
+ return cls.JSValueMakeUndefined(context_ref)
810
+ if isinstance(value, c_void_p):
811
+ return value # assume a void pointer is a value ref
812
+ if objc.ns_subclass_of(value, cls.JSValue):
813
+ return value.JSValueRef() # return refs from existing JSValues
814
+ # convert
815
+ if isinstance(value, bool):
816
+ return cls.JSValueMakeBoolean(context_ref, value)
817
+ if isinstance(value, int) or isinstance(value, float):
818
+ return cls.JSValueMakeNumber(context_ref, value)
819
+ if isinstance(value, datetime):
820
+ value_utc = datetime.fromtimestamp(value.timestamp(), tz = timezone.utc)
821
+ value_str = value_utc.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
822
+ str_ref = jscore.str_to_jsstringref(value_str)
823
+ return cls.JSValueMakeFromJSONString(context_ref, str_ref)
824
+ if isinstance(value, str):
825
+ str_ref = jscore.str_to_jsstringref(value)
826
+ return cls.JSValueMakeString(context_ref, str_ref)
827
+ if isinstance(value, javascript_function):
828
+ source = str(value)
829
+ value_ref = javascript_function.from_source(source, context_ref).compile()
830
+ return value_ref
831
+ if isinstance(value, javascript_callback):
832
+ value_ref = value.get_jsvalue_ref(context_ref, parent_ref)
833
+ return value_ref
834
+ if isinstance(value, bytes):
835
+ count = len(value)
836
+ ex_ref = c_void_p(None)
837
+ value_ref = cls.JSObjectMakeTypedArray(context_ref, cls.kJSTypedArrayTypeUint8Array, count, byref(ex_ref))
838
+ bytes_ptr = jscore.JSObjectGetTypedArrayBytesPtr(context_ref, value_ref, byref(ex_ref))
839
+ if bytes_ptr is not None:
840
+ memmove(bytes_ptr, value, count)
841
+ return value_ref
842
+ if objc.ns_subclass_of(value, NSData):
843
+ count = value.length()
844
+ ex_ref = c_void_p(None)
845
+ value_ref = cls.JSObjectMakeTypedArray(context_ref, cls.kJSTypedArrayTypeUint8Array, count, byref(ex_ref))
846
+ bytes_ptr = jscore.JSObjectGetTypedArrayBytesPtr(context_ref, value_ref, byref(ex_ref))
847
+ if bytes_ptr is not None:
848
+ value.getBytes_length_(bytes_ptr, count)
849
+ return value_ref
850
+ if isinstance(value, list):
851
+ ex_ref = c_void_p(None)
852
+ value_ref = cls.JSObjectMakeArray(context_ref, 0, None, byref(ex_ref))
853
+ count = len(value)
854
+ for i in range(count):
855
+ val_ref = cls.py_to_jsvalueref(context_ref, value[i], value_ref)
856
+ cls.JSObjectSetPropertyAtIndex(context_ref, value_ref, i, val_ref, byref(ex_ref))
857
+ return value_ref
858
+ if isinstance(value, dict):
859
+ value_ref = cls.JSObjectMake(context_ref, None, None)
860
+ ex_ref = c_void_p(None)
861
+ for k,v in value.items():
862
+ key_ref = cls.str_to_jsstringref(k)
863
+ val_ref = cls.py_to_jsvalueref(context_ref, v, value_ref)
864
+ cls.JSObjectSetProperty(context_ref, value_ref, key_ref, val_ref, 0, byref(ex_ref))
865
+ return value_ref
866
+ typ = type(value)
867
+ raise NotImplementedError(f"Type '{typ}' for value '{value}' not supported.")
868
+
869
+ @classmethod
870
+ def py_to_jsvalueref(cls, context_ref, value, parent_ref = None):
871
+ value_ref = cls._py_to_jsvalueref(context_ref, value, parent_ref)
872
+ #cls.JSValueProtect(context_ref, value_ref)
873
+ return cast(value_ref, c_void_p) # ensure a c_void_p
874
+
875
+ @classmethod
876
+ def py_to_jsvalue(cls, context, value, parent = None):
877
+ if value is None:
878
+ return cls.JSValue.valueWithNullInContext_(context)
879
+ if javascript_value.is_undefined(value):
880
+ return cls.JSValue.valueWithUndefinedInContext_(context)
881
+ if objc.ns_subclass_of(value, jscore.JSValue):
882
+ return value # pass back jsvalue instances as-is
883
+ if isinstance(value, bool):
884
+ return cls.JSValue.valueWithBool_inContext_(value, context)
885
+ if isinstance(value, int) or isinstance(value, float) or isinstance(value, str):
886
+ return cls.JSValue.valueWithObject_inContext_(ns(value), context)
887
+ if isinstance(value, datetime):
888
+ timestamp = value.timestamp()
889
+ return cls.JSValue.valueWithObject_inContext_(cls.initWithTimeIntervalSince1970_(timestamp), context)
890
+ if isinstance(value, javascript_function):
891
+ jsvalue = value.jsvalue
892
+ if jsvalue is not None:
893
+ return jsvalue
894
+ if value.is_native:
895
+ raise ValueError("Cannot evaluate native functions (this shouldn't be reachable!')")
896
+ source = str(value)
897
+ # this obtains a new jsvalue for a javascript function by evaluating it in the context,
898
+ # further escaping and resolving may be required here...
899
+ jsvalue, ex = cls.context_eval(context, f'(function() {{ return ({source}); }})()')
900
+ return jsvalue
901
+ if isinstance(value, javascript_callback):
902
+ jsvalue = value.get_jsvalue(context, parent)
903
+ return jsvalue
904
+ if isinstance(value, bytes):
905
+ count = len(value)
906
+ jsvalue = cls.context_eval(context, f"new Uint8Array({count});")
907
+ context_ref, value_ref = cls.jsvalue_get_refs(jsvalue)
908
+ ex_ref = c_void_p(None)
909
+ bytes_ptr = jscore.JSObjectGetTypedArrayBytesPtr(context_ref, value_ref, byref(ex_ref))
910
+ if bytes_ptr is not None:
911
+ memmove(bytes_ptr, value, count)
912
+ return jsvalue
913
+ if objc.ns_subclass_of(value, NSData):
914
+ count = value.length()
915
+ jsvalue = cls.context_eval(context, f"new Uint8Array({count});")
916
+ context_ref, value_ref = cls.jsvalue_get_refs(jsvalue)
917
+ ex_ref = c_void_p(None)
918
+ bytes_ptr = jscore.JSObjectGetTypedArrayBytesPtr(context_ref, value_ref, byref(ex_ref))
919
+ if bytes_ptr is not None:
920
+ value.getBytes_length_(bytes_ptr, count)
921
+ return jsvalue
922
+ if isinstance(value, dict):
923
+ jsvalue = cls.JSValue.valueWithNewObjectInContext_(context)
924
+ for k,v in value.items():
925
+ val = cls.py_to_jsvalue(context, v, jsvalue)
926
+ jsvalue.setValue_forProperty_(val, k)
927
+ return jsvalue
928
+ if isinstance(value, list):
929
+ jsvalue = cls.JSValue.valueWithNewArrayInContext_(context)
930
+ for i in range(len(value)):
931
+ val = cls.py_to_jsvalue(context, value[i], jsvalue)
932
+ jsvalue.setValue_atIndex_(i, val)
933
+ return jsvalue
934
+ typ = type(value)
935
+ raise NotImplementedError(f"Type '{typ}' for value '{value}' not supported.")
936
+
937
+ @classmethod
938
+ def py_to_js(cls, value):
939
+ value = jsobject_accessor.unwrap(value)
940
+ return json.dumps(value, cls=javascript_encoder)
941
+
942
+
943
+ class javascript_encoder(json.JSONEncoder):
944
+ # An overriden json encoder to write javascript objects encoding functions / raw literal js
945
+ def __init__(self, *args, **kwargs):
946
+ super().__init__(*args, **kwargs)
947
+ self.raw = False
948
+
949
+ def default(self, obj):
950
+ if javascript_value.is_undefined(obj):
951
+ return None
952
+ elif isinstance(obj, datetime):
953
+ self.raw = True
954
+ return obj.replace(tzinfo=timezone.utc).strftime('new Date("%Y-%m-%dT%H:%M:%S.%fZ")')
955
+ elif isinstance(obj, bytes):
956
+ self.raw = True
957
+ return "".join(["new Uint8Array(",str(list(obj)),")"])
958
+ elif isinstance(obj, javascript_function):
959
+ self.raw = True
960
+ return str(obj)
961
+ return json.JSONEncoder.default(self, obj)
962
+
963
+ def raw_unescape(self, chunk):
964
+ raw = json.loads(chunk) #unescape
965
+ return str(raw) # ensure string result
966
+
967
+ def encode(self, o):
968
+ chunks = []
969
+ for chunk in super().iterencode(o):
970
+ if self.raw: # handle chunk as a literal raw string
971
+ chunk = self.raw_unescape(chunk) #unescape the string
972
+ self.raw = False
973
+ chunks.append(chunk)
974
+ return ''.join(chunks)
975
+
976
+
977
+ # types for js values
978
+ class javascript_undefined_value:
979
+ def __repr__(self):
980
+ return "undefined"
981
+
982
+ # not sure what this is...
983
+ class javascript_symbol:
984
+ def __init__(self, symbol):
985
+ self.symbol = symbol
986
+ print(f"javascript_symbol {symbol}")
987
+
988
+
989
+ class javascript_function:
990
+ def __init__(self, jsvalue = None, context_ref = None, value_ref = None, source = None):
991
+ self.jsvalue = jsvalue
992
+ self.context_ref = context_ref
993
+ self.value_ref = value_ref
994
+ self.source = source
995
+
996
+ def compile(self, context_ref = None):
997
+ if context_ref is None:
998
+ context_ref = self.context_ref
999
+ if context_ref is None:
1000
+ raise ImportError("Cannot compile function from source without context_ref")
1001
+ if self.source is None:
1002
+ raise ImportError("Cannot compile function with no source")
1003
+ self.source = self.source.strip()
1004
+ name_match = re.match("function([^\(]*)\(", self.source)
1005
+ fn_name = name_match.group(0).strip()
1006
+ params_match = re.match("function[^\(]*\(([^\)]*)\)", self.source)
1007
+ params = params_match.group(0).split(",")
1008
+ params_refs = []
1009
+ for p in params:
1010
+ param = p.strip()
1011
+ if param != "":
1012
+ param_ref = jscore.str_to_jsstringref(param)
1013
+ params_refs.append(param_ref)
1014
+ params_refs = objc.c_array(params_refs)
1015
+ name_ref = jscore.str_to_jsstringref(fn_name)
1016
+ body = body[body.index('{'):body.rindex('}')]
1017
+ body_ref = jscore.str_to_jsstringref(body)
1018
+ ex_ref = c_void_p(None)
1019
+ self.value_ref = jscore.JSObjectMakeFunction(context_ref, name_ref, params_count, params_refs, body_ref, None, 0, by_ref(ex_ref))
1020
+ self.context_ref = context_ref
1021
+ if ex_ref.value is not None:
1022
+ exception = jscore.jsvalueref_to_py(context_ref, ex_ref)
1023
+ raise ImportError("Exception compiling function: {exception}")
1024
+ return self.value_ref
1025
+
1026
+ def js_arg(self, context, arg):
1027
+ if javascript_value.is_null_or_undefined(arg):
1028
+ return jscore.py_to_jsvalue(context, arg)
1029
+ if isinstance(arg, dict):
1030
+ copy = {}
1031
+ for k,v in arg.items():
1032
+ copy[k]=self.js_arg(context, v)
1033
+ return copy
1034
+ if isinstance(arg, list):
1035
+ copy = []
1036
+ for i in range(len(arg)):
1037
+ copy.append(self.js_arg(context, arg[i]))
1038
+ return copy
1039
+ if isinstance(arg, javascript_function):
1040
+ return jscore.py_to_jsvalue(context, arg)
1041
+ return arg
1042
+
1043
+ def js_args(self, context, args):
1044
+ args = list(args)
1045
+ args = self.js_arg(context, args)
1046
+ nsargs = ns(args)
1047
+ return nsargs
1048
+
1049
+ def call(self, *args):
1050
+ if self.jsvalue is not None:
1051
+ context = self.jsvalue.context()
1052
+ nsargs = self.js_args(context, args)
1053
+ self.jsvalue.context().setException_(None)
1054
+ value = self.jsvalue.callWithArguments_(nsargs)
1055
+ exception = self.jsvalue.context().exception()
1056
+ if exception is not None:
1057
+ self.jsvalue.context().setException_(None)
1058
+ raise Exception(str(exception))
1059
+ return javascript_value(value)
1060
+
1061
+ if self.source is not None and self.value_ref is None:
1062
+ if self.context_ref is not None:
1063
+ self.compile()
1064
+ else:
1065
+ raise NotImplementedError("Cannot call source only functions without a context_ref")
1066
+
1067
+ if self.context_ref is not None and self.value_ref is not None:
1068
+ count = len(args)
1069
+ args_ref = None
1070
+ if count > 0:
1071
+ args_ref = objc.c_array(count, lambda i: jscore.py_to_jsvalueref(self.context_ref, args[i]))
1072
+ this_ref = None
1073
+ exception_ref = c_void_p(None)
1074
+ value_ref = jscore.JSObjectCallAsFunction(self.context_ref, self.value_ref, this_ref, count, args_ref, byref(exception_ref))
1075
+ if exception_ref.value is not None:
1076
+ raise Exception(jscore.jsvalueref_to_py(exception_ref))
1077
+ return javascript_value(None, self.context_ref, value_ref)
1078
+
1079
+ raise NotImplementedError("Cannot call this type of javascript_function")
1080
+
1081
+ @property
1082
+ def is_native(self):
1083
+ repr = str(self).strip()
1084
+ return re.fullmatch("function[^\{]+\{[^\[]+\[native code\][^\}]+}", repr) is not None
1085
+
1086
+ def __call__(self, *args):
1087
+ return self.call(*args).value
1088
+
1089
+ def __repr__(self):
1090
+ if self.source is not None:
1091
+ return self.source
1092
+ if self.jsvalue is not None:
1093
+ return str(self.jsvalue)
1094
+
1095
+ @classmethod
1096
+ def from_source(cls, source, context = None):
1097
+ context_ref = None
1098
+ if isinstance(context, c_void_p):
1099
+ context_ref = context
1100
+ elif context is not None:
1101
+ if isinstance(context, jscore_context):
1102
+ context = context.context
1103
+ if objc.ns_subclass_of(context, jscore.JSContext):
1104
+ context_ref = context.JSGlobalContextRef()
1105
+ return cls(source=source, context_ref=context_ref)
1106
+
1107
+
1108
+ class javascript_callback:
1109
+ def __init__(self, callback, name = None):
1110
+ self.callback = callback
1111
+ self.name = name
1112
+ self.callback_ref = None
1113
+ self.context_ref = None
1114
+ self.value_ref = None
1115
+ self._jsvalue = None
1116
+
1117
+ def compile(self, context_ref = None, parent_ref = None):
1118
+ if context_ref is None:
1119
+ context_ref = self.context_ref
1120
+ if context_ref is None:
1121
+ raise Exception("Context is required to compile callbacks")
1122
+ if self.value_ref is not None:
1123
+ raise Exception("Cannot recompile callbacks")
1124
+ if self.name is None:
1125
+ self.name = javascript_callback.unique_name()
1126
+ name_ref = jscore.str_to_jsstringref(self.name)
1127
+ self.callback_ref = jscore.JSObjectCallAsFunctionCallback(self._invoke_callback)
1128
+ value_ref = jscore.JSObjectMakeFunctionWithCallback(context_ref, name_ref, self.callback_ref)
1129
+ jscore.JSValueProtect(context_ref, value_ref)
1130
+ self.context_ref = context_ref
1131
+ self.value_ref = value_ref
1132
+ if parent_ref is None:
1133
+ parent_ref = jscore.JSContextGetGlobalObject(self.context_ref)
1134
+ ex = c_void_p(None)
1135
+ jscore.JSObjectSetProperty(self.context_ref, parent_ref, name_ref, value_ref, 0, byref(ex))
1136
+
1137
+ def get_jsvalue_ref(self, context_ref, parent_ref = None):
1138
+ if self.context_ref is not None and self.context_ref != context_ref:
1139
+ raise Exception("Cannot change context")
1140
+ if self.value_ref is None:
1141
+ self.compile(context_ref, parent_ref)
1142
+ return self.value_ref
1143
+
1144
+ def get_jsvalue(self, context, parent = None):
1145
+ context_ref = context.JSGlobalContextRef()
1146
+ parent_ref = None
1147
+ if parent is not None:
1148
+ parent_ref = parent.JSValueRef()
1149
+ if self._jsvalue is None:
1150
+ value_ref = self.get_jsvalue_ref(context_ref, parent_ref)
1151
+ if parent is None:
1152
+ parent = context.globalObject()
1153
+ self._jsvalue = parent.valueForProperty(self.name) # work around to obtain a jsvalue from jsvalue_ref
1154
+ return self._jsvalue
1155
+
1156
+ def _invoke_callback(self, ctx, funcObj, thisObj, args_count, args, ex):
1157
+ ctx = c_void_p(ctx)
1158
+ funcObj = c_void_p(funcObj)
1159
+ thisObj = c_void_p(thisObj)
1160
+ callback_args = []
1161
+ for i in range(args_count):
1162
+ arg = c_void_p.from_address(args + (i * sizeof(c_void_p)))
1163
+ arg_value = jscore.jsvalueref_to_py(ctx, arg)
1164
+ callback_args.append(arg_value)
1165
+ returnValue = self.callback(*callback_args)
1166
+ returnJSValue_ref = jscore.py_to_jsvalueref(ctx, returnValue)
1167
+ return returnJSValue_ref.value
1168
+
1169
+ _name_count = 0
1170
+ @classmethod
1171
+ def unique_name(cls):
1172
+ name = f"python_callback_{cls._name_count}"
1173
+ cls._name_count = cls._name_count + 1
1174
+ return name
1175
+
1176
+ @classmethod
1177
+ def is_callable(cls, func):
1178
+ return not isinstance(func, javascript_function) and callable(func)
1179
+
1180
+ @classmethod
1181
+ def wrap(cls, context, value, name = None):
1182
+ if javascript_callback.is_callable(value):
1183
+ return context.callback(value, name)
1184
+ if isinstance(value, dict):
1185
+ for k, v in value.items():
1186
+ value[k] = cls.wrap(context, v, k)
1187
+ elif isinstance(value, list):
1188
+ for i in range(len(value)):
1189
+ k = str(i)
1190
+ v = value[i]
1191
+ value[i] = cls.wrap(context, v, k)
1192
+ return value
1193
+
1194
+ class javascript_object(dict):
1195
+ def __init__(self, *args, **kwargs):
1196
+ super().__init__(*args, **kwargs)
1197
+ self.___init___ = True
1198
+
1199
+ def __getattr__(self, key):
1200
+ value = self.get(key, javascript_value.undefined)
1201
+ if isinstance(value, dict):
1202
+ value = javascript_object(value)
1203
+ return value
1204
+
1205
+ def __setattr__(self, key, value):
1206
+ if not self.__dict__.get("___init___", False):
1207
+ super().__setattr__(key, value)
1208
+ else:
1209
+ self[key] = value
1210
+
1211
+
1212
+ class javascript_value:
1213
+ undefined = javascript_undefined_value()
1214
+ @classmethod
1215
+ def is_undefined(cls, value):
1216
+ return value is cls.undefined
1217
+
1218
+ @classmethod
1219
+ def is_null(cls, value):
1220
+ return value is None
1221
+
1222
+ @classmethod
1223
+ def is_null_or_undefined(cls,value):
1224
+ return cls.is_null(value) or cls.is_undefined(value)
1225
+
1226
+ def __init__(self, jsvalue = None, context_ref = None, value_ref = None):
1227
+ if jsvalue is None and context_ref is None and value_ref is None:
1228
+ raise ArgumentError("Either jsvalue or context_ref and value_ref must be specified")
1229
+ self.jsvalue = jsvalue
1230
+ self.context_ref = context_ref
1231
+ self.value_ref = value_ref
1232
+ self._val = None
1233
+ self._cached = False
1234
+
1235
+ @property
1236
+ def value(self):
1237
+ if not self._cached:
1238
+ if self.jsvalue is not None:
1239
+ self._val = jscore.jsvalue_to_py(self.jsvalue)
1240
+ elif self.context_ref is not None and self.value_ref is not None:
1241
+ self._val = jscore.jsvalueref_to_py(self.context_ref, self.value_ref)
1242
+ else:
1243
+ self._val = javascript_value.undefined
1244
+ if isinstance(self._val, dict):
1245
+ self._val = javascript_object(self._val)
1246
+ self._cached = True
1247
+ return self._val
1248
+
1249
+ def __repr__(self):
1250
+ return str(self.value)
1251
+
1252
+
1253
+ class jsscript_ref:
1254
+ def __init__(self, runtime, url, source):
1255
+ self.runtime = runtime
1256
+ self.vm = runtime.vm
1257
+ self.context_group_ref = self.vm.JSContextGroupRef()
1258
+ self.url = url
1259
+ self.source = source
1260
+ self.url_ref = jscore.str_to_jsstringref(url)
1261
+ self.source_ref = jscore.str_to_jsstringref(source)
1262
+ error_ref = c_void_p(None)
1263
+ error_line = c_void_p(0)
1264
+ script_ref = jscore.JSScriptCreateFromString(self.context_group_ref, self.url_ref, 0, self.source_ref, byref(error_ref), byref(error_line))
1265
+ self.script_ref = script_ref
1266
+ self.error_ref = error_ref
1267
+ self.error_line = error_line
1268
+ if self.script_ref:
1269
+ jscore.JSScriptRetain(self.script_ref)
1270
+
1271
+ def release(self):
1272
+ if self.runtime.vm is not None:
1273
+ raise Exception("VM must be released before releasing scripts")
1274
+ jscore.JSScriptRelease(self.script_ref) # must outlive VM
1275
+ jscore.JSStringRelease(self.source_ref)
1276
+ jscore.JSStringRelease(self.url_ref)
1277
+
1278
+ def eval(self, context):
1279
+ context = context.context
1280
+ context_ref = context.JSGlobalContextRef()
1281
+ if not self.script_ref:
1282
+ line = self.error_line
1283
+ exception = jscore.jsvalueref_to_py(context_ref, self.error_ref)
1284
+ raise ImportError(f"Error importing script at {line}, {exception}")
1285
+ this_ref = context.globalObject()
1286
+ this_ref = this_ref.JSValueRef()
1287
+ exception_ref = c_void_p(None)
1288
+ value_ref = jscore.JSScriptEvaluate(context_ref, self.script_ref, this_ref, byref(exception_ref))
1289
+ value = jscore.jsvalueref_to_py(context_ref, value_ref)
1290
+ exception = jscore.jsvalueref_to_py(context_ref, exception_ref)
1291
+ return value, exception
1292
+
1293
+
1294
+ class jscore_module_loader:
1295
+ def __init__(self, context):
1296
+ self.runtime = context.runtime
1297
+ self.pycontext = context
1298
+ self.context = context.context
1299
+ self.scripts = {}
1300
+ self.modules = {}
1301
+ self.sources = {}
1302
+ self.delegate = jscore.JSCoreModuleLoaderDelegate.alloc().init().autorelease()
1303
+ retain_global(self.delegate)
1304
+ self.delegate._pyinstance = weakref.ref(self)
1305
+ self.context.setModuleLoaderDelegate_(self.delegate)
1306
+ self.evaluated = {}
1307
+
1308
+ def release(self):
1309
+ self.context.setModuleLoaderDelegate_(None)
1310
+ release_global(self.delegate)
1311
+ self.delegate = None
1312
+
1313
+ def fetch_module(self, module, resolve, reject):
1314
+ evaluated = self.evaluated.get(module)
1315
+ if evaluated is not None:
1316
+ if evaluated.exception is None:
1317
+ resolve(evaluated.jsvalue)
1318
+ else:
1319
+ reject(evaluated.exception)
1320
+ return
1321
+ script = self.modules.get(module)
1322
+ if script is None:
1323
+ script = self.load_file(module, jscore.kJSScriptTypeModule)
1324
+ source = self.sources.get(module)
1325
+ if script is not None and source is not None:
1326
+ result = self.pycontext.eval(source)
1327
+ exception = result.exception
1328
+ if exception is not None:
1329
+ reject(exception)
1330
+ print(f"Module load failed {module} {exception}")
1331
+ else:
1332
+ resolve(result.jsvalue)
1333
+ print(f"Module load success {module}")
1334
+ self.evaluated[module] = result
1335
+ else:
1336
+ reject(f"Module load failed {module}")
1337
+ print(f"Module load failed {module}")
1338
+
1339
+ def will_eval_module(self, url):
1340
+ print(f"will eval {url}")
1341
+
1342
+ def did_eval_module(self, url):
1343
+ print(f"did eval {url}")
1344
+
1345
+ def preprocess_source(self, scriptType, path, source):
1346
+ return source
1347
+
1348
+ def load_script_source(self, script, scriptType, path, sourceUrl, source = None):
1349
+ # we have to use lookups until we can decode strings directly from either:
1350
+ # WTF::String on script.source()
1351
+ # JSC:JSSourceCode* on script.jsSourceCode()
1352
+ # JSC::SourceCode on script.sourceCode()
1353
+ file_path = self.runtime.get_file_path(path)
1354
+ path_no_ext = str(file_path.with_suffix(''))
1355
+ if source is None:
1356
+ with open(file_path) as script_file:
1357
+ source = script_file.read()
1358
+ source = self.preprocess_source(scriptType, file_path, source)
1359
+ sources = self.sources
1360
+ sources[path] = source
1361
+ sources[path_no_ext] = source
1362
+ sources[f"file://{path_no_ext}"] = source
1363
+ sources[sourceUrl] = source
1364
+
1365
+ lookup = self.modules if scriptType == jscore.kJSScriptTypeModule else self.scripts
1366
+ lookup[source] = script
1367
+ lookup[path] = script
1368
+ lookup[path_no_ext] = script
1369
+ lookup[f"file://{path_no_ext}"] = script
1370
+ lookup[sourceUrl] = script
1371
+
1372
+ def load_source(self, source, scriptType, modulePath = None):
1373
+ lookup = self.modules if scriptType == jscore.kJSScriptTypeModule else self.scripts
1374
+ script = lookup.get(source)
1375
+ path = None
1376
+ if script is None:
1377
+ path = self.runtime.get_module_path(source, modulePath)
1378
+ script = lookup.get(f"file://{path}")
1379
+ if script is not None:
1380
+ return script
1381
+ script, sourceUrl, exception = self.runtime.load_source(source, scriptType, path)
1382
+ if exception is not None:
1383
+ raise ImportError(exception)
1384
+ self.load_script_source(script, scriptType, path, sourceUrl, source)
1385
+ return script
1386
+
1387
+ def load_file(self, path, scriptType):
1388
+ lookup = self.modules if scriptType == jscore.kJSScriptTypeModule else self.scripts
1389
+ script = lookup.get(path)
1390
+ if script is None:
1391
+ path = self.runtime.get_file_path(path)
1392
+ path = str(path)
1393
+ script = lookup.get(path)
1394
+ if script is not None:
1395
+ return script
1396
+ script, sourceUrl, exception = self.runtime.load_file(path, scriptType)
1397
+ if exception is not None:
1398
+ raise ImportError(exception)
1399
+ self.load_script_source(script, scriptType, path, sourceUrl)
1400
+ return script
1401
+
1402
+
1403
+ # base runtime framework
1404
+ class jscore_context:
1405
+ def __init__(self, runtime):
1406
+ self.runtime = runtime
1407
+ self.context = None
1408
+ self.depth = 0
1409
+ self.callbacks = None
1410
+
1411
+ def allocate(self):
1412
+ if self.context is not None:
1413
+ raise Exception("Context already allocated. Do not call allocate/deallocate manually.")
1414
+ self.context = jscore.context_allocate(self.runtime.vm)
1415
+ self.loader = jscore_module_loader(self)
1416
+ self.callbacks = {}
1417
+
1418
+ def deallocate(self):
1419
+ if self.context is None:
1420
+ raise Exception("Context already deallocated. Do not call allocate/deallocate manually.")
1421
+ self.loader.release()
1422
+ self.loader = None
1423
+ jscore.context_deallocate(self.context)
1424
+ self.context = None
1425
+ self.callbacks = None
1426
+
1427
+ def alloc(self):
1428
+ if self.context is not None:
1429
+ return
1430
+ self.allocate()
1431
+
1432
+ def destroy(self):
1433
+ if self.context is None:
1434
+ return
1435
+ self.deallocate()
1436
+
1437
+ def __enter__(self):
1438
+ if self.depth == 0:
1439
+ self.alloc()
1440
+ self.depth = self.depth + 1
1441
+ return self
1442
+
1443
+ def __exit__(self, exc_type, exc_val, exc_tb):
1444
+ self.depth = self.depth - 1
1445
+ if self.depth < 0:
1446
+ raise Exception("Exit must not be called before enter, use the with keyword.")
1447
+ if self.depth == 0:
1448
+ self.destroy()
1449
+ return self
1450
+
1451
+ class eval_result:
1452
+ def __init__(self, wrapper, exception):
1453
+ self.wrapper = wrapper
1454
+ self.jsvalue = wrapper.jsvalue
1455
+ self.exception = exception
1456
+
1457
+ @property
1458
+ def value(self):
1459
+ return self.wrapper.value
1460
+
1461
+ def __repr__(self):
1462
+ return str({"value":self.value, "exception":self.exception})
1463
+
1464
+ def eval(self, script, sourceUrl=None):
1465
+ self.alloc()
1466
+ result, ex = jscore.context_eval(self.context, script, sourceUrl)
1467
+ result = self.eval_result(javascript_value(result), ex)
1468
+ return result
1469
+
1470
+ def eval_jsscript(self, jsscript):
1471
+ if jsscript is None:
1472
+ raise ValueError("Null JSScript pointer")
1473
+ result = self.context.evaluateJSScript(jsscript) # crashes on null or invalid script ptr
1474
+ result = ObjCInstance(result)
1475
+ ex = self.context.exception()
1476
+ if ex is not None:
1477
+ self.context.setException(None) # clear exception if set
1478
+ result = self.eval_result(javascript_value(result), ex)
1479
+ return result
1480
+
1481
+ def eval_script_source(self, source, scriptType = jscore.kJSScriptTypeProgram, modulePath = None):
1482
+ script = self.loader.load_source(source, scriptType, modulePath)
1483
+ return self.eval_jsscript(script)
1484
+
1485
+ def eval_script_file(self, path, scriptType = jscore.kJSScriptTypeProgram):
1486
+ script = self.loader.load_file(path, scriptType)
1487
+ return self.eval_jsscript(script)
1488
+
1489
+ def eval_source(self, source):
1490
+ return self.eval_script_source(source, jscore.kJSScriptTypeProgram)
1491
+
1492
+ def eval_file(self, path):
1493
+ return self.eval_script_file(path, jscore.kJSScriptTypeProgram)
1494
+
1495
+ def eval_module_source(self, source, modulePath = None):
1496
+ return self.eval_script_source(source, jscore.kJSScriptTypeModule, modulePath)
1497
+
1498
+ def eval_module_file(self, path):
1499
+ return self.eval_script_file(path, jscore.kJSScriptTypeModule)
1500
+
1501
+ @property
1502
+ def context_ref(self):
1503
+ self.alloc()
1504
+ return self.context.JSGlobalContextRef()
1505
+
1506
+ def callback(self, func, name = None):
1507
+ if not javascript_callback.is_callable(func):
1508
+ raise Exception(f"'{func}' is not a python callable/function")
1509
+ key = func
1510
+ callback = self.callbacks.get(key, None)
1511
+ if callback is None:
1512
+ callback = javascript_callback(func, name)
1513
+ self.callbacks[key] = callback
1514
+ return callback
1515
+
1516
+
1517
+ class jscore_runtime:
1518
+ def __init__(self, vm = None):
1519
+ self.vm = vm
1520
+ self.vm_owner = self.vm is None
1521
+ self.depth = 0
1522
+ self.module_paths = {}
1523
+ self.scripts = []
1524
+
1525
+ def allocate(self):
1526
+ if self.vm is not None:
1527
+ raise Exception("VM already allocated. Do not call allocate/deallocate manually")
1528
+ self.vm = jscore.vm_allocate()
1529
+ self.vm_owner = True
1530
+
1531
+ def deallocate(self):
1532
+ if self.vm is None:
1533
+ raise Exception("VM already deallocated. Do not call allocate/deallocate manually")
1534
+ jscore.runtime_deallocate(self, self.vm_owner)
1535
+ self.vm = None
1536
+ self.scripts = []
1537
+ self.module_paths = {}
1538
+
1539
+ def alloc(self):
1540
+ if self.vm is not None:
1541
+ return
1542
+ self.allocate()
1543
+
1544
+ def destroy(self):
1545
+ if self.vm is None:
1546
+ return
1547
+ self.deallocate()
1548
+
1549
+ def get_module_path(self, source, modulePath = None):
1550
+ path = self.module_paths.get(source)
1551
+ if path is not None:
1552
+ return path
1553
+ if modulePath is not None:
1554
+ modulePath = self.get_file_path(modulePath)
1555
+ path = str(modulePath)
1556
+ if path is None:
1557
+ path = tempfile.mktemp(dir = Path.cwd())
1558
+ self.module_paths[source] = path
1559
+ return path
1560
+
1561
+ def get_file_path(self, path):
1562
+ path = str(path)
1563
+ if path.startswith("file://"):
1564
+ path = path[7:]
1565
+ p = Path(path)
1566
+ if not p.is_absolute():
1567
+ p = Path.cwd().joinpath(p)
1568
+ return p
1569
+
1570
+ def load_source(self, source, scriptType = jscore.kJSScriptTypeProgram, modulePath = None):
1571
+ loader = jscore.JSScript.scriptOfType_withSource_andSourceURL_andBytecodeCache_inVirtualMachine_error_
1572
+ sourceUrl = self.get_module_path(source, modulePath)
1573
+ return self.load_script(loader, source, scriptType, sourceUrl)
1574
+
1575
+ def load_file(self, path, scriptType = jscore.kJSScriptTypeProgram):
1576
+ p = self.get_file_path(path)
1577
+ sourceUrl = None
1578
+ if not p.is_file() or not p.exists():
1579
+ raise FileNotFoundError(f"Script file not found '{path}' ({p})")
1580
+ path = str(p)
1581
+ path = nsurl(path)
1582
+ sourceUrl = str(p)
1583
+ loader = jscore.JSScript.scriptOfType_memoryMappedFromASCIIFile_withSourceURL_andBytecodeCache_inVirtualMachine_error_
1584
+ return self.load_script(loader, path, scriptType, sourceUrl)
1585
+
1586
+ def load_script_ref(self, path = None, source = None, url = None):
1587
+ url = self.get_file_path(url)
1588
+ if source is None and path is not None:
1589
+ path = self.get_file_path(path)
1590
+ with open(path) as source_file:
1591
+ source = source_file.read()
1592
+ if source is None:
1593
+ raise ValueError("Source or source file path must be specified")
1594
+ url = self.get_module_path(source, url)
1595
+ url = f"file://{url}"
1596
+ script = jsscript_ref(self, url, source)
1597
+ self.scripts.append(script)
1598
+ return script
1599
+
1600
+ def load_script(self, loader, context, scriptType = jscore.kJSScriptTypeProgram, sourceUrl = None):
1601
+ if sourceUrl is None:
1602
+ raise ValueError("A valid source url is required")
1603
+ sourceUrl = nsurl(sourceUrl)
1604
+ error = c_void_p(None)
1605
+ #bytecodeCache requires a data vault if specified, so we don't...
1606
+ script = loader(scriptType, context, sourceUrl, None, self.vm, byref(error))
1607
+ retain_global(script) # DO NOT release JSScripts this will cause a crash while vm is alive
1608
+ if error:
1609
+ error = ObjCInstance(error)
1610
+ else:
1611
+ error = None
1612
+ sourceUrl = str(sourceUrl)
1613
+ self.scripts.append(script)
1614
+ return script, sourceUrl, error
1615
+
1616
+ def __enter__(self):
1617
+ if self.depth == 0:
1618
+ self.alloc()
1619
+ self.depth = self.depth + 1
1620
+ return self
1621
+
1622
+ def __exit__(self, exc_type, exc_val, exc_tb):
1623
+ self.depth = self.depth - 1
1624
+ if self.depth < 0:
1625
+ raise Exception("Exit must not be called before enter, use the with keyword.")
1626
+ if self.depth == 0:
1627
+ self.destroy()
1628
+ return self
1629
+
1630
+ def new_context(self):
1631
+ raise NotImplementedError()
1632
+
1633
+ def context(self):
1634
+ self.alloc()
1635
+ return self.new_context()
1636
+
1637
+ # helper class to determine the minimum set of changes to build a jsvalue in terms of javascript statements
1638
+ class jsvalue_evaluator:
1639
+ def __init__(self, context, parent = None):
1640
+ self.context = context
1641
+ self.parent = parent
1642
+
1643
+ def object_equal(self, x, y):
1644
+ if isinstance(x, dict):
1645
+ for k,v in x.items():
1646
+ try:
1647
+ if not self.item_equal(v, y[k]):
1648
+ return False
1649
+ except:
1650
+ return False
1651
+ return True
1652
+ elif isinstance(x, list):
1653
+ for i in range(len(x)):
1654
+ v = x[i]
1655
+ try:
1656
+ if not self.item_equal(v, y[i]):
1657
+ return False
1658
+ except:
1659
+ return False
1660
+ return True
1661
+ return False
1662
+
1663
+ def value_equal(self, x, y):
1664
+ if x is y or x == y:
1665
+ return True
1666
+ return str(x) == str(y) # repr compare
1667
+
1668
+ def item_equal(self, x, y):
1669
+ if isinstance(x, list) or isinstance(x, dict):
1670
+ return self.object_equal(x, y)
1671
+ return self.value_equal(x, y)
1672
+
1673
+ def eval_set(self, context, parent, key, value, current, equal = None):
1674
+ if isinstance(value, list):
1675
+ if (equal is None and self.object_equal(value, current)) or equal:
1676
+ return
1677
+ if javascript_value.is_null_or_undefined(current) or len(current) == 0:
1678
+ value = javascript_callback.wrap(self.context, value, key)
1679
+ jsvalue = jscore.py_to_jsvalue(context, value, parent)
1680
+ parent.setValue_forProperty_(jsvalue, key)
1681
+ return jsvalue
1682
+ jsvalue = parent.valueForProperty_(key)
1683
+ for i in range(len(value)):
1684
+ k = str(i)
1685
+ v = value[i]
1686
+ c = javascript_value.undefined
1687
+ try:
1688
+ c = current[i]
1689
+ except:
1690
+ pass
1691
+ if not self.item_equal(v, c):
1692
+ self.eval_set(context, jsvalue, k, v, c, False)
1693
+ return jsvalue
1694
+ elif isinstance(value, dict):
1695
+ if (equal is None and self.object_equal(value, current)) or equal:
1696
+ return
1697
+ if javascript_value.is_null_or_undefined(current) or len(current) == 0:
1698
+ value = javascript_callback.wrap(self.context, value, key)
1699
+ jsvalue = jscore.py_to_jsvalue(context, value, parent)
1700
+ return jsvalue
1701
+ jsvalue = parent.valueForProperty_(key)
1702
+ for k,v in value.items():
1703
+ c = javascript_value.undefined
1704
+ try:
1705
+ c = current[k]
1706
+ except:
1707
+ pass
1708
+ if not self.item_equal(v, c):
1709
+ self.eval_set(context, jsvalue, k, v, c, False)
1710
+ return jsvalue
1711
+ else:
1712
+ if (equal is None and self.value_equal(value, current)) or equal:
1713
+ return
1714
+ value = javascript_callback.wrap(self.context, value, key)
1715
+ jsvalue = jscore.py_to_jsvalue(context, value, parent)
1716
+ parent.setValue_forProperty_(jsvalue, key)
1717
+ return jsvalue
1718
+
1719
+ def set(self, key, value, current):
1720
+ value = jsobject_accessor.unwrap(value)
1721
+ current = jsobject_accessor.unwrap(current)
1722
+ jsvalue = self.parent
1723
+ context = self.context.context
1724
+ if jsvalue is None:
1725
+ jsvalue = context.globalObject()
1726
+ val = self.eval_set(context, jsvalue, key, value, current)
1727
+ if val is not None:
1728
+ jsvalue.setValue_forProperty_(val, key)
1729
+
1730
+ def set_self(self, value, current):
1731
+ value = jsobject_accessor.unwrap(value)
1732
+ current = jsobject_accessor.unwrap(current)
1733
+ jsvalue = self.parent
1734
+ context = self.context.context
1735
+ if isinstance(value, list):
1736
+ for i in range(len(value)):
1737
+ k = str(i)
1738
+ v = value[i]
1739
+ c = javascript_value.undefined
1740
+ try:
1741
+ c = current[i]
1742
+ except:
1743
+ pass
1744
+ self.eval_set(context, jsvalue, k, v, c)
1745
+ elif isinstance(value, dict):
1746
+ for k,v in value.items():
1747
+ c = javascript_value.undefined
1748
+ try:
1749
+ c = current[k]
1750
+ except:
1751
+ pass
1752
+ self.eval_set(context, jsvalue, k, v, c)
1753
+
1754
+
1755
+ # metaclass to map jsobjects to appear analogous to regular python objects
1756
+ class jsobject_accessor:
1757
+ def __init__(self, context, jsobject, path):
1758
+ self.___context___ = context
1759
+ self.___jsobject___ = jsobject
1760
+ self.___evaluator___ = jsvalue_evaluator(context, jsobject)
1761
+ self.___path___ = path
1762
+ self.___init___ = True
1763
+
1764
+ def ___get___(self, key):
1765
+ key = str(key)
1766
+ if not self.___jsobject___.hasProperty_(key):
1767
+ return javascript_value.undefined
1768
+ value = self.___jsobject___.valueForProperty(key)
1769
+ if jscore.jsvalue_is_object(value):
1770
+ path = key
1771
+ if self.___jsobject___.isArray():
1772
+ path = "".join([self.___path___,'[', path,']'])
1773
+ else:
1774
+ path = ".".join([self.___path___, path])
1775
+ return jsobject_accessor(self.___context___, value, path)
1776
+ return jscore.jsvalue_to_py(value)
1777
+
1778
+ def ___set___(self, key, value):
1779
+ current = self.___get___(key)
1780
+ path = key
1781
+ if self.___jsobject___.isArray():
1782
+ path = "".join([self.___path___,'[', path,']'])
1783
+ else:
1784
+ path = ".".join([self.___path___, path])
1785
+ self.___evaluator___.set(key, value, current)
1786
+
1787
+ def __len__(self):
1788
+ return len(jscore.jsobject_get_keys(self.___jsobject___))
1789
+
1790
+ def __repr__(self):
1791
+ return str(jscore.jsvalue_to_py(self.___jsobject___))
1792
+
1793
+ def __getattr__(self, key):
1794
+ return self.___get___(key)
1795
+
1796
+ def __setattr__(self, key, value):
1797
+ if not self.__dict__.get("___init___", False):
1798
+ super().__setattr__(key, value)
1799
+ else:
1800
+ self.___set___(key, value)
1801
+
1802
+ def __contains__(self, key):
1803
+ value = self.___get___(key)
1804
+ return not javascript_value.is_undefined(value)
1805
+
1806
+ def __getitem__(self, key):
1807
+ value = self.___get___(key)
1808
+ if javascript_value.is_undefined(value):
1809
+ raise IndexError(f'{key} is undefined.')
1810
+ return value
1811
+
1812
+ def __setitem__(self, key, value):
1813
+ self.___set___(k, value)
1814
+
1815
+ @classmethod
1816
+ def unwrap(cls, value):
1817
+ if isinstance(value, cls):
1818
+ return jscore.jsvalue_to_py(value.___jsobject___)
1819
+ return value
1820
+
1821
+
1822
+ # metaclass to map the main global context to appear analogous to a regular python object
1823
+ class javascript_context_accessor:
1824
+ def __init__(self, context):
1825
+ self.___context___ = context
1826
+ self.___globalObject___ = context.context.globalObject()
1827
+ self.___evaluator___ = jsvalue_evaluator(context, self.___globalObject___)
1828
+ self.___init___ = True
1829
+
1830
+ def ___get___(self, key):
1831
+ value = javascript_value.undefined
1832
+ if not isinstance(key, str):
1833
+ return value
1834
+ if self.___globalObject___.hasProperty_(key):
1835
+ value = self.___globalObject___.valueForProperty(key)
1836
+ else:
1837
+ result = self.___context___.eval(f'{key};')
1838
+ value = result.jsvalue
1839
+ if jscore.jsvalue_is_object(value):
1840
+ return jsobject_accessor(self.___context___, value, key)
1841
+ return jscore.jsvalue_to_py(value)
1842
+
1843
+ def ___set___(self, key, value):
1844
+ jsvalue = None
1845
+ if not self.___globalObject___.hasProperty_(key):
1846
+ result = self.___context___.eval(f'{key};')
1847
+ jsvalue = result.jsvalue
1848
+ if jscore.jsvalue_is_object(jsvalue) and (isinstance(value, list) or isinstance(value, dict)):
1849
+ evaluator = jsvalue_evaluator(self.___context___, jsvalue)
1850
+ current = jscore.jsvalue_to_py(jsvalue)
1851
+ evaluator.set_self(value, current)
1852
+ return
1853
+ else:
1854
+ jsvalue = self.___globalObject___.valueForProperty(key)
1855
+ current = javascript_value.undefined
1856
+ if jsvalue is not None:
1857
+ current = jscore.jsvalue_to_py(jsvalue)
1858
+ self.___evaluator___.set(key, value, current)
1859
+
1860
+
1861
+ def __getattr__(self, key):
1862
+ return self.___get___(key)
1863
+
1864
+ def __setattr__(self, key, value):
1865
+ if not self.__dict__.get("___init___", False):
1866
+ super().__setattr__(key, value)
1867
+ else:
1868
+ self.___set___(key, value)
1869
+
1870
+ def __contains__(self, key):
1871
+ value = self.___get___(key)
1872
+ return not javascript_value.is_undefined(value)
1873
+
1874
+ def __getitem__(self, key):
1875
+ value = self.___get___(key)
1876
+ if javascript_value.is_undefined(value):
1877
+ raise IndexError(f"'{key}' is undefined.")
1878
+ return value
1879
+
1880
+ def __setitem__(self, key, value):
1881
+ return self.___set___(key, value)
1882
+
1883
+ # concrete runtimes and contexts
1884
+
1885
+ # javascript
1886
+ class javascript_context(jscore_context):
1887
+ def __init__(self, runtime):
1888
+ super().__init__(runtime)
1889
+ self.accessor = None
1890
+
1891
+ def allocate(self):
1892
+ super().allocate()
1893
+ self.accessor = javascript_context_accessor(self)
1894
+
1895
+ def deallocate(self):
1896
+ self.accessor = None
1897
+ super().deallocate()
1898
+
1899
+ @property
1900
+ def js(self):
1901
+ self.alloc()
1902
+ return self.accessor
1903
+
1904
+
1905
+ class javascript_runtime(jscore_runtime):
1906
+ def new_context(self):
1907
+ return javascript_context(self)
1908
+
1909
+ # wasm (WebAssembly)
1910
+ class wasm_namespace:
1911
+ def __init__(self, imports = None):
1912
+ if imports is None:
1913
+ imports = {}
1914
+ self.___imports___ = imports
1915
+ self.___init___ = True
1916
+
1917
+ def ___get___(self, key):
1918
+ value = self.___imports___.get(key, javascript_value.undefined)
1919
+ if javascript_value.is_undefined(value):
1920
+ value = {}
1921
+ self.___set___(key, value)
1922
+ if isinstance(value, dict):
1923
+ return wasm_namespace(value)
1924
+ return value
1925
+
1926
+ def ___set___(self, key, value):
1927
+ self.___imports___[key] = value
1928
+
1929
+ def __getattr__(self, key):
1930
+ return self.___get___(key)
1931
+
1932
+ def __setattr__(self, key, value):
1933
+ if not self.__dict__.get("___init___", False):
1934
+ super().__setattr__(key, value)
1935
+ else:
1936
+ self.___set___(key, value)
1937
+
1938
+ def __contains__(self, key):
1939
+ value = self.___get___(key)
1940
+ return not javascript_value.is_undefined(value)
1941
+
1942
+ def __getitem__(self, key):
1943
+ value = self.___get___(key)
1944
+ if javascript_value.is_undefined(value):
1945
+ raise IndexError(f"'{key}' is undefined.")
1946
+ return value
1947
+
1948
+ def __setitem__(self, key, value):
1949
+ return self.___set___(key, value)
1950
+
1951
+ def __repr__(self):
1952
+ return str(self.___imports___)
1953
+
1954
+ class wasm_module:
1955
+ magic = b'\0asm'
1956
+ version = b'\1\0\0\0'
1957
+ header = magic + version
1958
+
1959
+ @classmethod
1960
+ def has_header(cls, data):
1961
+ header = cls.header
1962
+ header_len = len(header)
1963
+ index = 0
1964
+ for byte in data:
1965
+ if byte != header[index]:
1966
+ return False
1967
+ index = index + 1
1968
+ if index == header_len:
1969
+ return True
1970
+ return False
1971
+
1972
+ def __init__(self, data = None, name = None, imports = {}):
1973
+ self.name = name
1974
+ if self.name is not None:
1975
+ self.name = wasm_module.get_module_name(self.name)
1976
+ self.data = None
1977
+ self.nsdata = None
1978
+ self.context = None
1979
+ self.jsdata = None
1980
+ self._imports = imports
1981
+ self._namespace = wasm_namespace(self._imports)
1982
+ self.module = None
1983
+ self.instance = None
1984
+ if objc.ns_subclass_of(data, NSData):
1985
+ self.nsdata = data
1986
+ elif isinstance(data, list) or isinstance(data, bytes):
1987
+ self.data = []
1988
+ data = bytes(data)
1989
+ if not wasm_module.has_header(data):
1990
+ raise ArgumentError(f"Invalid wasm module. Modules must start with '{wasm_module.header}'.")
1991
+ self.data.append(data)
1992
+ elif isinstance(data, str) or isinstance(data, Path):
1993
+ self.nsdata = objc.nsdata_from_file(data)
1994
+ elif data is not None:
1995
+ raise ArgumentError("Unknown module data type "+type(data))
1996
+ else:
1997
+ self.data = []
1998
+
1999
+ def append(self, data):
2000
+ if self.data is None or self.nsdata is not None:
2001
+ raise Exception("NSData is read only")
2002
+ self.data.append(b''.join(data))
2003
+
2004
+ @property
2005
+ def bytes(self):
2006
+ if self.data is not None:
2007
+ bytes = b''.join(self.data)
2008
+ if not wasm_module.has_header(bytes):
2009
+ return wasm_module.header + bytes
2010
+ return bytes
2011
+ if self.nsdata is not None:
2012
+ return nsdata_to_bytes(self.nsdata)
2013
+ return b''
2014
+
2015
+ def load(self, context):
2016
+ if self.module is not None:
2017
+ return self.instance
2018
+ if self.nsdata is None and self.data is not None:
2019
+ self.nsdata = ns(self.bytes)
2020
+ self.data = None
2021
+ if self.nsdata is None:
2022
+ raise ImportError("Assembly data not loaded.")
2023
+ self.context = context
2024
+ bytes_len = self.nsdata.length()
2025
+ # Work around for MakeTypedArray returning NaN floats when wrapped in a JSValue
2026
+ self.jsdata = self.context.eval(f"new Uint8Array({bytes_len});").jsvalue
2027
+ context_ref, value_ref = jscore.jsvalue_get_refs(self.jsdata)
2028
+ ex = c_void_p(None)
2029
+ bytes_ptr = jscore.JSObjectGetTypedArrayBytesPtr(context_ref, value_ref, byref(ex))
2030
+ if bytes_ptr is None and ex.value is not None:
2031
+ raise ImportError(jscore.jsvalueref_to_py(context_ref, ex))
2032
+ # read nsdata directly into Uint8Array backing bytes
2033
+ self.nsdata.getBytes_length_(bytes_ptr, bytes_len)
2034
+ self.module, self.name = self.context._load_module_array(self.jsdata, self.name, self._imports)
2035
+ self.instance = self.module.instance
2036
+ return self.instance
2037
+
2038
+ @property
2039
+ def loaded(self):
2040
+ return self.instance is not None
2041
+
2042
+ @property
2043
+ def imports(self):
2044
+ return self._namespace
2045
+
2046
+ @property
2047
+ def exports(self):
2048
+ if not self.loaded:
2049
+ return {}
2050
+ return self.instance.exports
2051
+
2052
+ def free(self):
2053
+ self.data = None
2054
+ self.nsdata = None
2055
+ self.context = None
2056
+ self.jsdata = None
2057
+ self.module = None
2058
+ self.instance = None
2059
+
2060
+ def save(self, path):
2061
+ path = Path(str(path))
2062
+ if not path.is_absolute():
2063
+ path = path.cwd().joinpath(path)
2064
+ with open(path, "w") as module_file:
2065
+ module_file.write(self.bytes)
2066
+
2067
+ @classmethod
2068
+ def get_module_name(cls, path):
2069
+ path = str(path)
2070
+ if '/' not in path and '.' not in path:
2071
+ return path
2072
+ name = Path(str(path)).name.split('.wasm')[0]
2073
+ if '.' in name:
2074
+ name = name.split('.')[0]
2075
+ return name
2076
+
2077
+ @classmethod
2078
+ def from_file_py(cls, path):
2079
+ path = Path(str(path))
2080
+ if not path.is_absolute():
2081
+ path = path.cwd().joinpath(path)
2082
+ with open(path) as module_file:
2083
+ data = module_file.read()
2084
+ name = cls.get_module_name(path)
2085
+ return cls(data, name)
2086
+
2087
+ @classmethod
2088
+ def from_file(cls, path, fileManager = None):
2089
+ data = objc.nsdata_from_file(path, fileManager)
2090
+ name = cls.get_module_name(path)
2091
+ return cls(data, name)
2092
+
2093
+
2094
+ class wasm_context(jscore_context):
2095
+ def __init__(self, runtime):
2096
+ super().__init__(runtime)
2097
+ self._modules = {}
2098
+ self._imports = {}
2099
+ self._namespace = wasm_namespace(self._imports)
2100
+
2101
+ def allocate(self):
2102
+ super().allocate()
2103
+ self._load_module = self.eval("""
2104
+ const _jscore_wasm_modules = {}
2105
+ function _jscore_wasm_load(name, wasm_bin, namespace){
2106
+ if(namespace === null) { namespace = {}; }
2107
+ const wasm_module = new WebAssembly.Module(wasm_bin);
2108
+ const wasm_instance = new WebAssembly.Instance(wasm_module, namespace);
2109
+ const wasm_module_instance = {"instance": wasm_instance, "namespace": namespace, "module": wasm_module};
2110
+ _jscore_wasm_modules[name] = wasm_module_instance; // ensure module remains in scope
2111
+ return wasm_module_instance;
2112
+ };_jscore_wasm_load;""").value
2113
+
2114
+ def deallocate(self):
2115
+ for name,module in self._modules.items():
2116
+ module.free()
2117
+ self._modules = None
2118
+ super().deallocate()
2119
+
2120
+ @property
2121
+ def imports(self):
2122
+ return self._namespace
2123
+
2124
+ @property
2125
+ def modules(self):
2126
+ return dict(self._modules)
2127
+
2128
+ def module(self, name):
2129
+ name = wasm_module.get_module_name(name)
2130
+ return self._modules.get(name)
2131
+
2132
+ def module_instance(self, name):
2133
+ module = self.module(name)
2134
+ if module is None:
2135
+ return None
2136
+ return module.instance
2137
+
2138
+ def load_module(self, module):
2139
+ if isinstance(module, Path):
2140
+ module = wasm_module.from_file(module)
2141
+ if not isinstance(module, wasm_module):
2142
+ raise ArgumentError("Module must be wasm_module")
2143
+ result = self._modules.get(module.name)
2144
+ if result is not None:
2145
+ return result
2146
+ result = module.load(self)
2147
+ self._modules[module.name] = module
2148
+ return result
2149
+
2150
+ def _create_imports_namespace(self, imports = None):
2151
+ namespace = {}
2152
+ for k, v in self._imports.items():
2153
+ namespace[k] = v
2154
+ if imports is None:
2155
+ imports = {}
2156
+ for k, v in imports.items():
2157
+ namespace[k] = v
2158
+ if len(namespace) == 0:
2159
+ return None
2160
+ namespace = javascript_callback.wrap(self, namespace)
2161
+ jsnamespace = jscore.py_to_jsvalue(self.context, namespace)
2162
+ return jsnamespace
2163
+
2164
+ def _add_module_to_global_namespace(self, module, name): #?
2165
+ pass
2166
+
2167
+ def _load_module_array(self, module_data, name = None, imports = None):
2168
+ if not jscore.jsvalue_is_array_type(module_data, jscore.kJSTypedArrayTypeUint8Array):
2169
+ raise ArgumentError("Module array must be JSValue of an Uint8Array instance type.")
2170
+ if name is None:
2171
+ name = "wasm_module_"+str(len(self._modules))
2172
+ namespace = self._create_imports_namespace(imports)
2173
+ result = self._load_module(name, module_data, namespace)
2174
+ self._add_module_to_global_namespace(result, name)
2175
+ return result, name
2176
+
2177
+
2178
+ class wasm_runtime(jscore_runtime):
2179
+ def new_context(self):
2180
+ return wasm_context(self)
2181
+
2182
+
2183
+ if __name__ == '__main__':
2184
+ import console
2185
+
2186
+ console.clear()
2187
+
2188
+ runtime = jscore.runtime()
2189
+ context = runtime.context()
2190
+ expected_unset = object()
2191
+
2192
+ valueMatch = None
2193
+ arrayMatch = None
2194
+ objectMatch = None
2195
+
2196
+ def valueMatch(expected, value, values = {}, repr = False):
2197
+ if expected is None:
2198
+ return value is None
2199
+ if expected is javascript_value.undefined:
2200
+ return value is javascript_value.undefined
2201
+ if isinstance(expected, dict):
2202
+ if not isinstance(value, dict):
2203
+ return False
2204
+ return objectMatch(expected, value)
2205
+ elif isinstance(expected, list):
2206
+ if not isinstance(value, list):
2207
+ return False
2208
+ return arrayMatch(expected, value)
2209
+ elif expected is not value and expected != value:
2210
+ if repr and not isinstance(value,str):
2211
+ return expected == str(value)
2212
+ if callable(expected):
2213
+ expected = expected()
2214
+ values["expected"] = expected
2215
+ return valueMatch(expected, value, repr, values)
2216
+ if callable(value):
2217
+ value = value()
2218
+ values["value"] = value
2219
+ return valueMatch(expected, value, repr, values)
2220
+ return False
2221
+ return True
2222
+
2223
+ def arrayMatch(expected, value):
2224
+ if expected is None:
2225
+ return value is None
2226
+ if expected is javascript_value.undefined:
2227
+ return value is javascript_value.undefined
2228
+ if not isinstance(expected, list) or not isinstance(value, list):
2229
+ return False
2230
+ if len(expected) != len(value):
2231
+ return False
2232
+ for i in range(len(value)):
2233
+ e = expected[i]
2234
+ v = value[i]
2235
+ if not valueMatch(expected[i], value[i]):
2236
+ return False
2237
+ return True
2238
+
2239
+ def objectMatch(expected, value):
2240
+ if expected is None:
2241
+ return value is None
2242
+ if expected is javascript_value.undefined:
2243
+ return value is javascript_value.undefined
2244
+ if not isinstance(expected, dict) or not isinstance(value, dict):
2245
+ return False
2246
+ for k, v in expected.items():
2247
+ if not k in value:
2248
+ return False
2249
+ vv = value[k]
2250
+ if not valueMatch(v, vv):
2251
+ return False
2252
+ return True
2253
+
2254
+ def eval(script, expected=expected_unset, **kwargs):
2255
+ print(f'Execute:\n{script}\n')
2256
+ result = context.eval(script)
2257
+ value = result.value
2258
+ ex = result.exception
2259
+ print(f'Result:\n{value}\n')
2260
+ if not ex is None:
2261
+ print(f'Exception:\n{ex}')
2262
+ if expected is not expected_unset:
2263
+ values = {"expected":expected, "value":value}
2264
+ match = valueMatch(expected, value, values=values, **kwargs)
2265
+ expected = values["expected"]
2266
+ value = values["value"]
2267
+ print(f"Expected: {expected}\nActual: {value}\nPassed: {match}")
2268
+ print("-" * 35)
2269
+ return value
2270
+
2271
+ def header(text, end_only = False):
2272
+ if not end_only:
2273
+ print("")
2274
+ print("-" * 35)
2275
+ print(text)
2276
+ print("-" * 35)
2277
+ print("")
2278
+
2279
+ header("javascript runtime")
2280
+ print(runtime, context)
2281
+ header("primitives", True)
2282
+ eval("parseInt('1')", 1)
2283
+
2284
+ eval("1+1", 2)
2285
+
2286
+ eval("parseFloat('1.20')", 1.2)
2287
+
2288
+ eval("1.1 + 1.1", 2.2)
2289
+
2290
+ eval("1.02", 1.02)
2291
+
2292
+ eval("false", False)
2293
+
2294
+ eval("true", True)
2295
+
2296
+ eval("'c'", 'c')
2297
+
2298
+ header("strings")
2299
+
2300
+ eval('"string"', "string")
2301
+
2302
+ header("datetimes")
2303
+
2304
+ eval("new Date()")
2305
+
2306
+ header("exceptions")
2307
+ eval('throw "errooorrr";')
2308
+
2309
+ header("arrays")
2310
+ eval("[]", [])
2311
+
2312
+ eval("[ 1, 2 , 3 ]", [ 1, 2, 3 ])
2313
+
2314
+ eval("[ true, false, true, false ]", [ True, False, True, False ])
2315
+
2316
+ eval("[ 'a', 'b', 'c' ]", [ 'a', 'b', 'c' ])
2317
+
2318
+ eval('[ "abc" , "def", "ghi" ]', [ "abc" , "def", "ghi" ])
2319
+
2320
+ eval('[ [1,"2"], ["a"], [{"1":2, "obj":{}, "arr":[]}]]', [ [1,"2"], ["a"], [ {"1":2, "obj":{}, "arr":[] }]])
2321
+
2322
+ header("objects")
2323
+ eval('const obj = { "str": "str", "int": 1, "float": 1.4, "obj":{ "hello": "world"} }; obj;', {"str":"str", "int":1, "float": 1.4, "obj": {"hello": "world"}})
2324
+
2325
+ eval('const fn = function() { return 10; }; fn;', 10)
2326
+
2327
+ eval('const fnobj = { "fn": function() { return 10; }}; fnobj;', {"fn": 10})
2328
+
2329
+ header("wasm")
2330
+ #instantiate empty module
2331
+ eval('''(function(){
2332
+ const bin = new Uint8Array([0,97,115,109,1,0,0,0]);
2333
+ let result = null;
2334
+ try
2335
+ {
2336
+ const module = new WebAssembly.Module(bin);
2337
+ const instance = new WebAssembly.Instance(module);
2338
+ result = ''+module+' '+instance;
2339
+ }
2340
+ catch(ex)
2341
+ {
2342
+ result = ''+ex;
2343
+ }
2344
+ return result;
2345
+ })();
2346
+ //
2347
+ ''')
2348
+ # A memset test as described:
2349
+ # https://developer.apple.com/forums/thread/121040
2350
+ inst = eval('''(function(){
2351
+ const bin = new Uint8Array([0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11]);
2352
+ let result = null;
2353
+ try
2354
+ {
2355
+ const module = new WebAssembly.Module(bin);
2356
+ const instance = new WebAssembly.Instance(module);
2357
+ result = ''+module+' '+instance;
2358
+ result += '\\n'+instance.exports.test(4);
2359
+ return instance;
2360
+ }
2361
+ catch(ex)
2362
+ {
2363
+ result = ''+ex;
2364
+ }
2365
+ return result;
2366
+ })();
2367
+ //
2368
+ ''')
2369
+
2370
+ print(inst.exports.test.is_native)
2371
+
2372
+ header("context.js interop")
2373
+ header("Create new object", True)
2374
+ print("context.js.interop_obj = { 'test':{'object':[]}, 'int':1, 'double':2.45 }")
2375
+ context.js.interop_obj = { 'test':{'object':[]}, 'int':1, 'double':2.45 }
2376
+ print("Result:", context.js.interop_obj)
2377
+
2378
+ header("Modify object")
2379
+ print("context.js.interop_obj = { 'test':{'object':[1,2,3]}, 'int':1, 'double':2.45 }")
2380
+ context.js.interop_obj = { 'test':{'object':[1,2,3]}, 'int':1, 'double':2.45 }
2381
+ print("Result:", context.js.interop_obj)
2382
+
2383
+ header("Create new function")
2384
+ print('"interopfn" in context.js = ', "interopfn" in context.js)
2385
+ print('context.js.interopfn = javascript_function.from_body("function() { return 20; }")')
2386
+ context.js.interopfn = javascript_function.from_source("function() { return 20; }")
2387
+ print('"interopfn" in context.js = ', "interopfn" in context.js)
2388
+ print("Result:",context.js.interopfn, context.js.interopfn())
2389
+
2390
+ header("Define/Load function")
2391
+ print('"fndeftest" in context.js = ', "fndeftest" in context.js)
2392
+ print('context.eval("function fndeftest() { return 123; }")')
2393
+ context.eval("function fndeftest() { return 123; }")
2394
+ print('"fndeftest" in context.js = ', "fndeftest" in context.js)
2395
+ print("Result:", context.js.fndeftest, context.js.fndeftest())
2396
+
2397
+ header("Define python functions callable from js")
2398
+ context.js.pythonfn = lambda text: print(text)
2399
+ print("context.js.pythonfn = lambda text: print(text)")
2400
+ print(context.js.pythonfn)
2401
+ print("context.eval('pythonfn(\"Hello python\");')")
2402
+ context.eval('pythonfn("Hello python");')
2403
+ print()
2404
+ context.js.python_val = lambda: {"str": "Hello from python", "num":10, "list":[1,2,3]}
2405
+ print('context.js.python_val = lambda: {"str": "Hello from python", "num":10, "list":[1,2,3]}')
2406
+ print(context.js.python_val)
2407
+ print("context.eval('python_val();')")
2408
+ print(context.eval('python_val();'))
2409
+
2410
+ header("Modules/scripts")
2411
+ context.eval_module_source("function module_source() { return 10; }", "sourcetest.js")
2412
+ print(context.js.module_source)
2413
+ #context.eval_module_file("./test.js")
2414
+ #print(context.js.filetest)
2415
+ #str_ref = jscore.str_to_jsstringref("test test")
2416
+ #print(str_ref)
2417
+ #py_str = jscore.jsstringref_to_py(str_ref)
2418
+ #print(py_str)
2419
+ script_ref = runtime.load_script_ref(source="function script_ref(){ return 232; }; [1, '2', new Date(), script_ref, {'a':[]}];" , url="reftest.js")
2420
+ value, exception = script_ref.eval(context)
2421
+ print(value, exception, context.js.script_ref)
2422
+ context.destroy()
2423
+ runtime.destroy()
2424
+ print(jscore._runtimes)
2425
+
2426
+ header("wasm runtime")
2427
+ runtime = jscore.runtime(wasm_runtime)
2428
+ context = runtime.context()
2429
+ print(runtime, context)
2430
+
2431
+ module = wasm_module([0,97,115,109,1,0,0,0,1,6,1,96,1,127,1,127,3,2,1,0,5,3,1,0,1,7,8,1,4,116,101,115,116,0,0,10,16,1,14,0,32,0,65,1,54,2,0,32,0,40,2,0,11])
2432
+ context.load_module(module)
2433
+ print(module.exports)
2434
+
2435
+ #https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Using_the_JavaScript_API
2436
+ simple_module_path = Path("./simple.wasm")
2437
+ if simple_module_path.exists():
2438
+ header("simple.wasm")
2439
+ simple_module = wasm_module.from_file("./simple.wasm")
2440
+ simple_module.imports.my_namespace.imported_func = lambda *v: print(*v)
2441
+ context.load_module(simple_module)
2442
+ print(simple_module.exports)
2443
+ simple_module.exports.exported_func()
2444
+
2445
+ context.destroy()
2446
+ runtime.destroy()
2447
+ print(jscore._runtimes)
2448
+ print(jscore._runtime_vm)