expo-modules-jsi 56.0.3 → 56.0.5
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.
- package/CHANGELOG.md +11 -0
- package/apple/APINotes/jsi.apinotes +8 -0
- package/apple/Sources/ExpoModulesJSI/Protocols/JSIRepresentable.swift +19 -19
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRef.swift +2 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/JavaScriptRuntime.swift +98 -30
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptBigInt.swift +2 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptError.swift +2 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptFunction.swift +2 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptObject.swift +2 -2
- package/apple/Sources/ExpoModulesJSI/Runtime/Values/JavaScriptValue.swift +2 -2
- package/apple/Sources/ExpoModulesJSI-Cxx/TypedArray.cpp +3 -3
- package/apple/Sources/ExpoModulesJSI-Cxx/include/CppError.h +5 -4
- package/apple/Sources/ExpoModulesJSI-Cxx/include/HostObject.h +2 -2
- package/apple/Sources/ExpoModulesJSI-Cxx/include/IRuntimeCompat.h +22 -0
- package/apple/Sources/ExpoModulesJSI-Cxx/include/JSIUtils.h +37 -25
- package/apple/Sources/ExpoModulesJSI-Cxx/include/TypedArray.h +4 -4
- package/apple/Tests/JavaScriptRuntimeTests.swift +60 -0
- package/apple/scripts/build-xcframework.sh +9 -2
- package/apple/scripts/test.sh +7 -22
- package/apple/scripts/xcframework-helpers.sh +18 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.5 — 2026-05-15
|
|
14
|
+
|
|
15
|
+
_This version does not introduce any user-facing changes._
|
|
16
|
+
|
|
17
|
+
## 56.0.4 — 2026-05-13
|
|
18
|
+
|
|
19
|
+
### 🐛 Bug fixes
|
|
20
|
+
|
|
21
|
+
- [iOS] Fixed xcframework build cache not invalidating when React-jsi headers change. ([#45735](https://github.com/expo/expo/pull/45735) by [@tsapeta](https://github.com/tsapeta))
|
|
22
|
+
- [iOS] Added support for `facebook::jsi::IRuntime` so the package builds against React Native 0.86+, while staying compatible with 0.85 and react-native-tvos. ([#45728](https://github.com/expo/expo/pull/45728) by [@zoontek](https://github.com/zoontek))
|
|
23
|
+
|
|
13
24
|
## 56.0.3 — 2026-05-11
|
|
14
25
|
|
|
15
26
|
### 🐛 Bug fixes
|
|
@@ -14,3 +14,11 @@ Namespaces:
|
|
|
14
14
|
SwiftImportAs: reference
|
|
15
15
|
SwiftReleaseOp: immortal
|
|
16
16
|
SwiftRetainOp: immortal
|
|
17
|
+
- Name: IRuntime
|
|
18
|
+
SwiftImportAs: reference
|
|
19
|
+
SwiftReleaseOp: immortal
|
|
20
|
+
SwiftRetainOp: immortal
|
|
21
|
+
- Name: ICast
|
|
22
|
+
SwiftImportAs: reference
|
|
23
|
+
SwiftReleaseOp: immortal
|
|
24
|
+
SwiftRetainOp: immortal
|
|
@@ -7,21 +7,21 @@ internal import ExpoModulesJSI_Cxx
|
|
|
7
7
|
*/
|
|
8
8
|
internal protocol JSIRepresentable: JavaScriptRepresentable, Sendable, ~Copyable {
|
|
9
9
|
/**
|
|
10
|
-
Creates an instance of this type from the given `facebook.jsi.Value` in `facebook.jsi.
|
|
10
|
+
Creates an instance of this type from the given `facebook.jsi.Value` in `facebook.jsi.IRuntime`.
|
|
11
11
|
*/
|
|
12
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
12
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Self
|
|
13
13
|
/**
|
|
14
14
|
Creates a JSI value representing this value in the given JSI runtime.
|
|
15
15
|
*/
|
|
16
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
16
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
internal extension JSIRepresentable {
|
|
20
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
20
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Self {
|
|
21
21
|
FatalError.unimplemented()
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
24
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
25
25
|
FatalError.unimplemented()
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -29,11 +29,11 @@ internal extension JSIRepresentable {
|
|
|
29
29
|
// MARK: - Implementations
|
|
30
30
|
|
|
31
31
|
extension Bool: JSIRepresentable {
|
|
32
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
32
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Bool {
|
|
33
33
|
return value.getBool()
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
36
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
37
37
|
return facebook.jsi.Value(self)
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -41,19 +41,19 @@ extension Bool: JSIRepresentable {
|
|
|
41
41
|
internal protocol JSIRepresentableNumber: JSIRepresentable {}
|
|
42
42
|
|
|
43
43
|
extension JSIRepresentableNumber {
|
|
44
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
44
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Int where Self: FixedWidthInteger {
|
|
45
45
|
return Int(value.getNumber())
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
48
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Double where Self: BinaryFloatingPoint {
|
|
49
49
|
return value.getNumber()
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
52
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value where Self: FixedWidthInteger {
|
|
53
53
|
return facebook.jsi.Value(Double(self))
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
56
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value where Self: BinaryFloatingPoint {
|
|
57
57
|
return facebook.jsi.Value(Double(self))
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -74,30 +74,30 @@ extension Float64: JSIRepresentableNumber {}
|
|
|
74
74
|
extension CGFloat: JSIRepresentableNumber {}
|
|
75
75
|
|
|
76
76
|
extension String: JSIRepresentable {
|
|
77
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
77
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> String {
|
|
78
78
|
return String(value.getString(runtime).utf8(runtime))
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
81
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
82
82
|
return facebook.jsi.Value(runtime, facebook.jsi.String.createFromUtf8(runtime, std.string(self)))
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
extension Optional: JSIRepresentable where Wrapped: JSIRepresentable {
|
|
87
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
87
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Self {
|
|
88
88
|
if value.isNull() || value.isUndefined() {
|
|
89
89
|
return nil
|
|
90
90
|
}
|
|
91
91
|
return Wrapped.fromJSIValue(value, in: runtime)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
94
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
95
95
|
return self?.toJSIValue(in: runtime) ?? .null()
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
extension Array: JSIRepresentable where Element: JSIRepresentable {
|
|
100
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
100
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Array<Element> {
|
|
101
101
|
let jsiArray = value.getObject(runtime).getArray(runtime)
|
|
102
102
|
let size = jsiArray.size(runtime)
|
|
103
103
|
var result: Self = []
|
|
@@ -110,7 +110,7 @@ extension Array: JSIRepresentable where Element: JSIRepresentable {
|
|
|
110
110
|
return result
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
113
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
114
114
|
let jsiArray = facebook.jsi.Array(runtime, count)
|
|
115
115
|
|
|
116
116
|
for index in 0..<count {
|
|
@@ -121,7 +121,7 @@ extension Array: JSIRepresentable where Element: JSIRepresentable {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
extension Dictionary: JSIRepresentable where Key == String, Value: JSIRepresentable {
|
|
124
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
124
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> Dictionary<Key, Value> {
|
|
125
125
|
let object = value.getObject(runtime)
|
|
126
126
|
let propertyNames = object.getPropertyNames(runtime)
|
|
127
127
|
let size = propertyNames.size(runtime)
|
|
@@ -137,7 +137,7 @@ extension Dictionary: JSIRepresentable where Key == String, Value: JSIRepresenta
|
|
|
137
137
|
return result
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
140
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
141
141
|
let object = facebook.jsi.Object(runtime)
|
|
142
142
|
|
|
143
143
|
for (key, value) in self {
|
|
@@ -91,11 +91,11 @@ public final class JavaScriptRef<T: JavaScriptType & ~Copyable>: JavaScriptType,
|
|
|
91
91
|
|
|
92
92
|
extension JavaScriptRef: JavaScriptRepresentable where T: JavaScriptRepresentable & ~Copyable {}
|
|
93
93
|
extension JavaScriptRef: JSIRepresentable where T: JSIRepresentable & ~Copyable {
|
|
94
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
94
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptRef {
|
|
95
95
|
FatalError.unimplemented()
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
98
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
99
99
|
return take()?.toJSIValue(in: runtime) ?? .undefined()
|
|
100
100
|
}
|
|
101
101
|
}
|
|
@@ -22,16 +22,38 @@ internal import ExpoModulesJSI_Cxx
|
|
|
22
22
|
*/
|
|
23
23
|
open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
24
24
|
/**
|
|
25
|
-
The underlying JSI runtime this `JavaScriptRuntime` points to
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
The underlying JSI runtime this `JavaScriptRuntime` points to, exposed as
|
|
26
|
+
`IRuntime` — the abstract base interface that virtually all JSI value/object/
|
|
27
|
+
function methods take (`Value::getString`, `Object::setProperty`,
|
|
28
|
+
`Function::call`, …) since RN 0.86 split the API. Stored as the upcast result
|
|
29
|
+
of `runtimePointee` because Swift's C++ interop does not auto-upcast between
|
|
30
|
+
two `SwiftImportAs: reference` types.
|
|
31
|
+
|
|
32
|
+
Use ``runtimePointee`` instead when you specifically need a `jsi::Runtime&`
|
|
33
|
+
(e.g. constructing an `expo.RuntimeScheduler` whose binding lookup is typed on
|
|
34
|
+
`Runtime&` upstream).
|
|
35
|
+
|
|
36
|
+
Note that `facebook.jsi.IRuntime` and `facebook.jsi.Runtime` are imported as
|
|
37
|
+
reference types so for the Swift compiler they are treated like classes.
|
|
38
|
+
This is important because they:
|
|
39
|
+
- are abstract classes with many virtual methods. Swift/C++ interop does not support calling pure virtual methods on value types.
|
|
40
|
+
- are non-copyable. As value types, we would have to "borrow" them from React Native in an unsafe manner.
|
|
41
|
+
*/
|
|
42
|
+
internal let pointee: facebook.jsi.IRuntime
|
|
43
|
+
internal let runtimePointee: facebook.jsi.Runtime
|
|
33
44
|
internal let scheduler: expo.RuntimeScheduler
|
|
34
45
|
|
|
46
|
+
/**
|
|
47
|
+
Thread ID of the JavaScript thread, captured at construction time. Used by `isOnJavaScriptThread()`
|
|
48
|
+
for a fast integer comparison instead of `Thread.current.name == "..."`.
|
|
49
|
+
Assumes runtime initializers always run on the JS thread.
|
|
50
|
+
*/
|
|
51
|
+
private let jsThreadID: UInt64 = {
|
|
52
|
+
var id: UInt64 = 0
|
|
53
|
+
pthread_threadid_np(nil, &id)
|
|
54
|
+
return id
|
|
55
|
+
}()
|
|
56
|
+
|
|
35
57
|
/**
|
|
36
58
|
Actor for running runtime work.
|
|
37
59
|
*/
|
|
@@ -43,7 +65,8 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
43
65
|
`init(unsafePointer:nativeScheduler:dispatch:)` instead.
|
|
44
66
|
*/
|
|
45
67
|
internal init(_ runtime: facebook.jsi.Runtime) {
|
|
46
|
-
self.
|
|
68
|
+
self.runtimePointee = runtime
|
|
69
|
+
self.pointee = expo.iruntime(runtime)
|
|
47
70
|
self.scheduler = expo.RuntimeScheduler()
|
|
48
71
|
}
|
|
49
72
|
|
|
@@ -52,7 +75,9 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
52
75
|
no React scheduler is wired up.
|
|
53
76
|
*/
|
|
54
77
|
public init() {
|
|
55
|
-
|
|
78
|
+
let runtime = expo.createHermesRuntime()
|
|
79
|
+
self.runtimePointee = runtime
|
|
80
|
+
self.pointee = expo.iruntime(runtime)
|
|
56
81
|
self.scheduler = expo.RuntimeScheduler()
|
|
57
82
|
}
|
|
58
83
|
|
|
@@ -63,7 +88,8 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
63
88
|
*/
|
|
64
89
|
public init(unsafePointer: UnsafeMutableRawPointer) {
|
|
65
90
|
let runtime = unsafeBitCast(unsafePointer, to: facebook.jsi.Runtime.self)
|
|
66
|
-
self.
|
|
91
|
+
self.runtimePointee = runtime
|
|
92
|
+
self.pointee = expo.iruntime(runtime)
|
|
67
93
|
self.scheduler = expo.RuntimeScheduler()
|
|
68
94
|
}
|
|
69
95
|
|
|
@@ -85,7 +111,8 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
85
111
|
) {
|
|
86
112
|
let runtime = unsafeBitCast(unsafePointer, to: facebook.jsi.Runtime.self)
|
|
87
113
|
let fn = unsafeBitCast(dispatch, to: expo.RuntimeScheduler.ScheduleFn.self)
|
|
88
|
-
self.
|
|
114
|
+
self.runtimePointee = runtime
|
|
115
|
+
self.pointee = expo.iruntime(runtime)
|
|
89
116
|
self.scheduler = expo.RuntimeScheduler(scheduler, fn)
|
|
90
117
|
}
|
|
91
118
|
|
|
@@ -94,7 +121,7 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
94
121
|
The pointer is valid only for the duration of the closure and must not be stored or escaped.
|
|
95
122
|
*/
|
|
96
123
|
public func withUnsafePointee<R>(_ body: (UnsafeMutableRawPointer) throws -> R) rethrows -> R {
|
|
97
|
-
return try body(Unmanaged<facebook.jsi.Runtime>.passUnretained(
|
|
124
|
+
return try body(Unmanaged<facebook.jsi.Runtime>.passUnretained(runtimePointee).toOpaque())
|
|
98
125
|
}
|
|
99
126
|
|
|
100
127
|
/**
|
|
@@ -346,7 +373,7 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
346
373
|
let argumentsRef = arguments.copy().ref()
|
|
347
374
|
|
|
348
375
|
// Switch to asynchronous context.
|
|
349
|
-
self.schedule
|
|
376
|
+
self.schedule {
|
|
350
377
|
// Invoke the asynchronous function and resolve/reject the promise.
|
|
351
378
|
do {
|
|
352
379
|
let result = try await function(this, argumentsRef.take())
|
|
@@ -383,11 +410,10 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
383
410
|
|
|
384
411
|
public func schedule(
|
|
385
412
|
priority: SchedulerPriority = .normal,
|
|
386
|
-
taskName: String? = "[JS] runtime.schedule (\(#function))",
|
|
387
413
|
@_implicitSelfCapture _ closure: @escaping @JavaScriptActor () async throws -> Void
|
|
388
414
|
) -> Void {
|
|
389
415
|
schedule(priority: priority) {
|
|
390
|
-
Task.immediate_polyfill
|
|
416
|
+
Task.immediate_polyfill {
|
|
391
417
|
try await closure()
|
|
392
418
|
}
|
|
393
419
|
}
|
|
@@ -398,8 +424,14 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
398
424
|
Not available in async contexts to prevent blocking the cooperative thread pool.
|
|
399
425
|
*/
|
|
400
426
|
@available(*, noasync)
|
|
427
|
+
@discardableResult
|
|
401
428
|
public func execute<R: Sendable>(@_implicitSelfCapture _ closure: @escaping @JavaScriptActor () throws -> R) throws -> sending R {
|
|
429
|
+
if isOnJavaScriptThread() {
|
|
430
|
+
return try JavaScriptActor.assumeIsolated(closure)
|
|
431
|
+
}
|
|
432
|
+
|
|
402
433
|
var result: Result<R, any Error>!
|
|
434
|
+
nonisolated(unsafe) let callerRunLoop = CFRunLoopGetCurrent()
|
|
403
435
|
|
|
404
436
|
scheduler.scheduleTask(.ImmediatePriority) {
|
|
405
437
|
do {
|
|
@@ -407,12 +439,23 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
407
439
|
} catch {
|
|
408
440
|
result = .failure(error)
|
|
409
441
|
}
|
|
442
|
+
// Wake the caller's run loop so its `CFRunLoopRunInMode(...)` returns immediately
|
|
443
|
+
// instead of waiting out the timeout backstop.
|
|
444
|
+
CFRunLoopPerformBlock(callerRunLoop, CFRunLoopMode.commonModes.rawValue) {}
|
|
445
|
+
CFRunLoopWakeUp(callerRunLoop)
|
|
410
446
|
}
|
|
411
447
|
|
|
412
|
-
//
|
|
413
|
-
// this
|
|
448
|
+
// Pump the caller's run loop until the task finishes. As opposed to DispatchSemaphore
|
|
449
|
+
// or DispatchGroup, this lets the run loop continue to process other events in the meantime,
|
|
450
|
+
// and the spin is also faster than a real kernel-mediated context switch when the JS work
|
|
451
|
+
// is short (the common case). The 100ms timeout is a backstop in case the wakeup is missed;
|
|
452
|
+
// the common path is woken by `CFRunLoopWakeUp` from the scheduled block above.
|
|
453
|
+
//
|
|
454
|
+
// `CFRunLoopRunInMode` is the C API rather than `RunLoop.current.run(mode:before:)` to
|
|
455
|
+
// avoid the per-iteration `+[NSRunLoop currentRunLoop]` autorelease push and `Date()`
|
|
456
|
+
// allocation that dominated the caller-thread profile otherwise.
|
|
414
457
|
while result == nil {
|
|
415
|
-
|
|
458
|
+
CFRunLoopRunInMode(.commonModes, 0.1, false)
|
|
416
459
|
}
|
|
417
460
|
return try result.get()
|
|
418
461
|
}
|
|
@@ -422,26 +465,40 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
422
465
|
Not available in async contexts to prevent blocking the cooperative thread pool.
|
|
423
466
|
*/
|
|
424
467
|
@available(*, noasync)
|
|
468
|
+
@discardableResult
|
|
425
469
|
public func execute<R: Sendable>(
|
|
426
|
-
taskName: String? = "[JS] runtime.execute (\(#function))",
|
|
427
470
|
@_implicitSelfCapture _ closure: @escaping @JavaScriptActor () async throws -> R
|
|
428
471
|
) throws -> sending R {
|
|
429
472
|
let result = NonisolatedUnsafeVar<Result<R, any Error>>()
|
|
473
|
+
let runInline = isOnJavaScriptThread()
|
|
474
|
+
// Wrapped in `NonisolatedUnsafeVar` instead of `nonisolated(unsafe) let`
|
|
475
|
+
// to work around a Swift 6.2.3 compiler bug.
|
|
476
|
+
let callerRunLoop = NonisolatedUnsafeVar(CFRunLoopGetCurrent())
|
|
430
477
|
|
|
431
|
-
|
|
432
|
-
Task.immediate_polyfill(
|
|
478
|
+
func body() -> Void {
|
|
479
|
+
Task.immediate_polyfill(priority: .high) {
|
|
433
480
|
do {
|
|
434
481
|
result.value = .success(try await closure())
|
|
435
482
|
} catch {
|
|
436
483
|
result.value = .failure(error)
|
|
437
484
|
}
|
|
485
|
+
// Wake the caller's run loop so its `CFRunLoopRunInMode(...)` returns immediately
|
|
486
|
+
// instead of waiting out the timeout backstop.
|
|
487
|
+
CFRunLoopPerformBlock(callerRunLoop.value, CFRunLoopMode.commonModes.rawValue) {}
|
|
488
|
+
CFRunLoopWakeUp(callerRunLoop.value)
|
|
438
489
|
}
|
|
439
490
|
}
|
|
491
|
+
if runInline {
|
|
492
|
+
body()
|
|
493
|
+
} else {
|
|
494
|
+
scheduler.scheduleTask(.ImmediatePriority, body)
|
|
495
|
+
}
|
|
440
496
|
|
|
441
|
-
//
|
|
442
|
-
//
|
|
497
|
+
// Pump the caller's run loop until the task finishes. See the sync overload above for
|
|
498
|
+
// the rationale on `CFRunLoopRunInMode` vs. `RunLoop.current.run(...)` and on pumping
|
|
499
|
+
// the run loop instead of blocking on a semaphore.
|
|
443
500
|
while result.value == nil {
|
|
444
|
-
|
|
501
|
+
CFRunLoopRunInMode(.commonModes, 0.1, false)
|
|
445
502
|
}
|
|
446
503
|
return try result.value.get()
|
|
447
504
|
}
|
|
@@ -449,9 +506,13 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
449
506
|
/**
|
|
450
507
|
Asynchronously executes a sync closure on the JavaScript runtime thread, awaiting its completion without blocking.
|
|
451
508
|
*/
|
|
509
|
+
@discardableResult
|
|
452
510
|
public func execute<R: Sendable>(
|
|
453
511
|
@_implicitSelfCapture _ closure: @escaping @JavaScriptActor () throws -> R
|
|
454
512
|
) async throws -> sending R {
|
|
513
|
+
if isOnJavaScriptThread() {
|
|
514
|
+
return try JavaScriptActor.assumeIsolated(closure)
|
|
515
|
+
}
|
|
455
516
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
456
517
|
scheduler.scheduleTask(.ImmediatePriority) {
|
|
457
518
|
do {
|
|
@@ -466,13 +527,16 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
466
527
|
/**
|
|
467
528
|
Asynchronously executes an async closure on the JavaScript runtime thread, awaiting its completion without blocking.
|
|
468
529
|
*/
|
|
530
|
+
@discardableResult
|
|
469
531
|
public func execute<R: Sendable>(
|
|
470
|
-
taskName: String? = "[JS] runtime.execute (async \(#function))",
|
|
471
532
|
@_implicitSelfCapture _ closure: @escaping @JavaScriptActor () async throws -> R
|
|
472
533
|
) async throws -> sending R {
|
|
534
|
+
if isOnJavaScriptThread() {
|
|
535
|
+
return try await Task.immediate_polyfill(priority: .high, operation: closure).value
|
|
536
|
+
}
|
|
473
537
|
return try await withUnsafeThrowingContinuation { continuation in
|
|
474
538
|
scheduler.scheduleTask(.ImmediatePriority) {
|
|
475
|
-
Task.immediate_polyfill(
|
|
539
|
+
Task.immediate_polyfill(priority: .high) { @JavaScriptActor in
|
|
476
540
|
do {
|
|
477
541
|
continuation.resume(returning: try await closure())
|
|
478
542
|
} catch {
|
|
@@ -486,14 +550,18 @@ open class JavaScriptRuntime: Equatable, @unchecked Sendable {
|
|
|
486
550
|
/**
|
|
487
551
|
Checks whether the function is called on the JavaScript thread.
|
|
488
552
|
*/
|
|
489
|
-
|
|
490
|
-
|
|
553
|
+
@inline(__always)
|
|
554
|
+
public final func isOnJavaScriptThread() -> Bool {
|
|
555
|
+
var current: UInt64 = 0
|
|
556
|
+
pthread_threadid_np(nil, ¤t)
|
|
557
|
+
return current == jsThreadID
|
|
491
558
|
}
|
|
492
559
|
|
|
493
560
|
/**
|
|
494
561
|
Asserts whether we are on the JavaScript thread. Helpful for debugging threading issues.
|
|
495
562
|
*/
|
|
496
|
-
|
|
563
|
+
@inline(__always)
|
|
564
|
+
public final func assertThread(file: String = #file, function: String = #function, line: Int = #line) {
|
|
497
565
|
assert(isOnJavaScriptThread(), "Function '\(function)' is not run on the JavaScript thread (\(file):\(line))")
|
|
498
566
|
}
|
|
499
567
|
|
|
@@ -308,11 +308,11 @@ extension JavaScriptBigInt: JavaScriptRepresentable {
|
|
|
308
308
|
}
|
|
309
309
|
|
|
310
310
|
extension JavaScriptBigInt: JSIRepresentable {
|
|
311
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
311
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptBigInt {
|
|
312
312
|
fatalError("Not implemented: Use JavaScriptValue.getBigInt() instead")
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
315
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
316
316
|
return asJSIValue()
|
|
317
317
|
}
|
|
318
318
|
}
|
|
@@ -67,11 +67,11 @@ extension JavaScriptError: JavaScriptRepresentable {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
extension JavaScriptError: JSIRepresentable {
|
|
70
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
70
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptError {
|
|
71
71
|
FatalError.unimplemented()
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
74
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
75
75
|
return asJSIValue()
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -130,11 +130,11 @@ extension JavaScriptFunction: JavaScriptRepresentable {
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
extension JavaScriptFunction: JSIRepresentable {
|
|
133
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
133
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptFunction {
|
|
134
134
|
FatalError.unimplemented()
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
137
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
138
138
|
return asJSIValue()
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -587,11 +587,11 @@ extension JavaScriptObject: JavaScriptRepresentable {
|
|
|
587
587
|
}
|
|
588
588
|
|
|
589
589
|
extension JavaScriptObject: JSIRepresentable {
|
|
590
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
590
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptObject {
|
|
591
591
|
FatalError.unimplemented()
|
|
592
592
|
}
|
|
593
593
|
|
|
594
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
594
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
595
595
|
return asJSIValue()
|
|
596
596
|
}
|
|
597
597
|
}
|
|
@@ -704,11 +704,11 @@ extension JavaScriptValue: JavaScriptRepresentable {
|
|
|
704
704
|
}
|
|
705
705
|
|
|
706
706
|
extension JavaScriptValue: JSIRepresentable {
|
|
707
|
-
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.
|
|
707
|
+
static func fromJSIValue(_ value: borrowing facebook.jsi.Value, in runtime: facebook.jsi.IRuntime) -> JavaScriptValue {
|
|
708
708
|
FatalError.unimplemented()
|
|
709
709
|
}
|
|
710
710
|
|
|
711
|
-
func toJSIValue(in runtime: facebook.jsi.
|
|
711
|
+
func toJSIValue(in runtime: facebook.jsi.IRuntime) -> facebook.jsi.Value {
|
|
712
712
|
return facebook.jsi.Value(runtime, pointee)
|
|
713
713
|
}
|
|
714
714
|
}
|
|
@@ -23,7 +23,7 @@ TypedArrayKind getTypedArrayKindForName(const std::string &name) {
|
|
|
23
23
|
return nameToKindMap.at(name);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
TypedArrayKind getTypedArrayKind(jsi::
|
|
26
|
+
TypedArrayKind getTypedArrayKind(jsi::IRuntime &runtime, const jsi::Object &jsObj) {
|
|
27
27
|
auto constructorName = jsObj.getPropertyAsObject(runtime, "constructor")
|
|
28
28
|
.getProperty(runtime, "name")
|
|
29
29
|
.asString(runtime)
|
|
@@ -31,7 +31,7 @@ TypedArrayKind getTypedArrayKind(jsi::Runtime &runtime, const jsi::Object &jsObj
|
|
|
31
31
|
return getTypedArrayKindForName(constructorName);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
jsi::ArrayBuffer getTypedArrayBuffer(jsi::
|
|
34
|
+
jsi::ArrayBuffer getTypedArrayBuffer(jsi::IRuntime &runtime, const jsi::Object &jsObj) {
|
|
35
35
|
auto buffer = jsObj.getProperty(runtime, "buffer");
|
|
36
36
|
if (buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime)) {
|
|
37
37
|
return buffer.asObject(runtime).getArrayBuffer(runtime);
|
|
@@ -39,7 +39,7 @@ jsi::ArrayBuffer getTypedArrayBuffer(jsi::Runtime &runtime, const jsi::Object &j
|
|
|
39
39
|
throw std::runtime_error("no ArrayBuffer attached");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
bool isTypedArray(jsi::
|
|
42
|
+
bool isTypedArray(jsi::IRuntime &runtime, const jsi::Object &jsObj) {
|
|
43
43
|
jsi::Object ArrayBuffer = runtime
|
|
44
44
|
.global()
|
|
45
45
|
.getPropertyAsObject(runtime, "ArrayBuffer");
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
#include <exception>
|
|
4
4
|
#include <memory>
|
|
5
5
|
#include <swift/bridging>
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
#include "IRuntimeCompat.h"
|
|
7
8
|
|
|
8
9
|
namespace expo {
|
|
9
10
|
|
|
@@ -62,7 +63,7 @@ public:
|
|
|
62
63
|
Returns the underlying JavaScript value of the wrapped `jsi::JSError`.
|
|
63
64
|
This is a JavaScript Error object with all its properties preserved.
|
|
64
65
|
*/
|
|
65
|
-
inline jsi::Value asValue(jsi::
|
|
66
|
+
inline jsi::Value asValue(jsi::IRuntime &runtime) noexcept {
|
|
66
67
|
return jsi::Value(runtime, jsError.value());
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -72,7 +73,7 @@ public:
|
|
|
72
73
|
using `getCurrent()`. The function returns `nullptr` when an exception occurs.
|
|
73
74
|
*/
|
|
74
75
|
template <typename Result>
|
|
75
|
-
inline static Result tryCatch(jsi::
|
|
76
|
+
inline static Result tryCatch(jsi::IRuntime &runtime, Result(^block)(void)) {
|
|
76
77
|
try {
|
|
77
78
|
return block();
|
|
78
79
|
} catch (jsi::JSError e) {
|
|
@@ -116,7 +117,7 @@ public:
|
|
|
116
117
|
Sets the current thread's error by creating a `jsi::JSError` from the given message.
|
|
117
118
|
Called from Swift when a host function closure throws a plain error.
|
|
118
119
|
*/
|
|
119
|
-
inline static void setCurrent(jsi::
|
|
120
|
+
inline static void setCurrent(jsi::IRuntime &runtime, const std::string &message) {
|
|
120
121
|
_current = std::make_unique<CppError>(jsi::JSError(runtime, message));
|
|
121
122
|
}
|
|
122
123
|
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
#include <string>
|
|
6
6
|
#include <vector>
|
|
7
|
-
#include <jsi/jsi.h>
|
|
8
7
|
|
|
9
8
|
#include "CppError.h"
|
|
10
9
|
#include "HostObjectCallbacks.h"
|
|
10
|
+
#include "IRuntimeCompat.h"
|
|
11
11
|
|
|
12
12
|
namespace jsi = facebook::jsi;
|
|
13
13
|
|
|
@@ -47,7 +47,7 @@ public:
|
|
|
47
47
|
return _callbacks.getPropertyNames();
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
inline static jsi::Object makeObject(jsi::
|
|
50
|
+
inline static jsi::Object makeObject(jsi::IRuntime &runtime, HostObjectCallbacks callbacks) {
|
|
51
51
|
return jsi::Object::createFromHostObject(runtime, std::make_shared<HostObject>(callbacks));
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
#ifdef __cplusplus
|
|
3
|
+
|
|
4
|
+
#include <jsi/jsi.h>
|
|
5
|
+
|
|
6
|
+
// React Native 0.86 split the JSI API into the abstract `jsi::IRuntime` base
|
|
7
|
+
// and the concrete `jsi::Runtime` derived class — most value/object/function
|
|
8
|
+
// methods now take `IRuntime&` instead of `Runtime&`. Older React Native
|
|
9
|
+
// versions (e.g. react-native-tvos 0.85) only have `jsi::Runtime`. Alias
|
|
10
|
+
// `IRuntime` to `Runtime` there so the same source compiles against both.
|
|
11
|
+
#if __has_include(<cxxreact/ReactNativeVersion.h>)
|
|
12
|
+
#include <cxxreact/ReactNativeVersion.h>
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
#if !defined(REACT_NATIVE_VERSION_MAJOR) || \
|
|
16
|
+
(REACT_NATIVE_VERSION_MAJOR == 0 && REACT_NATIVE_VERSION_MINOR < 86)
|
|
17
|
+
namespace facebook::jsi {
|
|
18
|
+
using IRuntime = Runtime;
|
|
19
|
+
} // namespace facebook::jsi
|
|
20
|
+
#endif
|
|
21
|
+
|
|
22
|
+
#endif // __cplusplus
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
#pragma once
|
|
4
4
|
#ifdef __cplusplus
|
|
5
5
|
|
|
6
|
-
#include <jsi/jsi.h>
|
|
7
6
|
#include <hermes/hermes.h>
|
|
7
|
+
|
|
8
8
|
#include "HostFunctionClosure.h"
|
|
9
9
|
#include "CppError.h"
|
|
10
|
+
#include "IRuntimeCompat.h"
|
|
10
11
|
#include "MemoryBuffer.h"
|
|
11
12
|
#include "NativeState.h"
|
|
12
13
|
#include "TypedArray.h"
|
|
@@ -19,24 +20,35 @@ namespace jsi = facebook::jsi;
|
|
|
19
20
|
|
|
20
21
|
namespace expo {
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
Upcasts a `jsi::Runtime&` to a `jsi::IRuntime&`. RN 0.86 split the JSI API into
|
|
25
|
+
the abstract `IRuntime` base and concrete `Runtime` derived class — most
|
|
26
|
+
value/object/function methods now take `IRuntime&`. Swift's C++ interop does
|
|
27
|
+
not auto-upcast between two `SwiftImportAs: reference` types, so Swift can't
|
|
28
|
+
pass a `Runtime` reference where `IRuntime` is expected without this helper.
|
|
29
|
+
*/
|
|
30
|
+
inline jsi::IRuntime &iruntime(jsi::Runtime &runtime) noexcept {
|
|
31
|
+
return runtime;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
inline jsi::Value valueFromFunction(jsi::IRuntime &runtime, const jsi::Function &function) {
|
|
23
35
|
return jsi::Value(runtime, function);
|
|
24
36
|
}
|
|
25
37
|
|
|
26
38
|
// `jsi::Object::setProperty` is a template function that Swift does not support. We need to provide specialized versions.
|
|
27
|
-
inline void setProperty(jsi::
|
|
39
|
+
inline void setProperty(jsi::IRuntime &runtime, const jsi::Object &object, const char *name, const jsi::Value value) {
|
|
28
40
|
object.setProperty(runtime, name, value);
|
|
29
41
|
}
|
|
30
42
|
|
|
31
|
-
inline void setProperty(jsi::
|
|
43
|
+
inline void setProperty(jsi::IRuntime &runtime, const jsi::Array &array, const char *name, const jsi::Value &value) {
|
|
32
44
|
array.setProperty(runtime, name, value);
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
inline void setValueAtIndex(jsi::
|
|
47
|
+
inline void setValueAtIndex(jsi::IRuntime &runtime, const jsi::Array &array, size_t index, const jsi::Value &value) {
|
|
36
48
|
array.setValueAtIndex(runtime, index, value);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
inline void setArrayLength(jsi::
|
|
51
|
+
inline void setArrayLength(jsi::IRuntime &runtime, const jsi::Array &array, long length) {
|
|
40
52
|
auto oldLength = (int)array.size(runtime);
|
|
41
53
|
auto newLength = (int)length;
|
|
42
54
|
|
|
@@ -52,15 +64,15 @@ inline void setArrayLength(jsi::Runtime &runtime, const jsi::Array &array, long
|
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
|
|
55
|
-
inline jsi::Value getProperty(jsi::
|
|
67
|
+
inline jsi::Value getProperty(jsi::IRuntime &runtime, const jsi::Array &array, const jsi::PropNameID &name) {
|
|
56
68
|
return array.getProperty(runtime, name);
|
|
57
69
|
}
|
|
58
70
|
|
|
59
|
-
inline jsi::Value valueFromArray(jsi::
|
|
71
|
+
inline jsi::Value valueFromArray(jsi::IRuntime &runtime, const jsi::Array &array) {
|
|
60
72
|
return jsi::Value(runtime, array);
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
inline const jsi::Value valueFromError(jsi::
|
|
75
|
+
inline const jsi::Value valueFromError(jsi::IRuntime &runtime, const jsi::JSError &error) {
|
|
64
76
|
return jsi::Value(runtime, error.value());
|
|
65
77
|
}
|
|
66
78
|
|
|
@@ -68,7 +80,7 @@ inline std::shared_ptr<const jsi::Buffer> makeSharedStringBuffer(const std::stri
|
|
|
68
80
|
return std::make_shared<jsi::StringBuffer>(source);
|
|
69
81
|
}
|
|
70
82
|
|
|
71
|
-
inline jsi::Function createHostFunction(jsi::
|
|
83
|
+
inline jsi::Function createHostFunction(jsi::IRuntime &runtime, const jsi::PropNameID &propName, HostFunctionClosure *closure) {
|
|
72
84
|
auto closurePtr = std::shared_ptr<HostFunctionClosure>(closure);
|
|
73
85
|
return jsi::Function::createFromHostFunction(runtime, propName, 0, [closurePtr](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *_Nonnull args, size_t count) -> jsi::Value {
|
|
74
86
|
auto result = closurePtr->call(thisValue, args, count);
|
|
@@ -82,7 +94,7 @@ inline jsi::Function createHostFunction(jsi::Runtime &runtime, const jsi::PropNa
|
|
|
82
94
|
});
|
|
83
95
|
}
|
|
84
96
|
|
|
85
|
-
inline jsi::Function createHostFunction(jsi::
|
|
97
|
+
inline jsi::Function createHostFunction(jsi::IRuntime &runtime, const char *name, HostFunctionClosure *closure) {
|
|
86
98
|
jsi::PropNameID propName = jsi::PropNameID::forAscii(runtime, name);
|
|
87
99
|
return createHostFunction(runtime, propName, closure);
|
|
88
100
|
}
|
|
@@ -93,31 +105,31 @@ inline jsi::Function createHostFunction(jsi::Runtime &runtime, const char *name,
|
|
|
93
105
|
and by JS engines themselves. Wraps the templated `jsi::Object::isHostObject<T>` so the
|
|
94
106
|
check is callable from Swift, where C++ function templates cannot be imported directly.
|
|
95
107
|
*/
|
|
96
|
-
inline bool isHostObject(jsi::
|
|
108
|
+
inline bool isHostObject(jsi::IRuntime &runtime, const jsi::Object &object) {
|
|
97
109
|
return object.isHostObject<jsi::HostObject>(runtime);
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
jsi::Runtime* createHermesRuntime();
|
|
101
113
|
|
|
102
|
-
inline jsi::Value evaluateJavaScript(jsi::
|
|
114
|
+
inline jsi::Value evaluateJavaScript(jsi::IRuntime &runtime, const std::shared_ptr<const jsi::Buffer>& buffer, const std::string& sourceURL) {
|
|
103
115
|
return expo::CppError::tryCatch(runtime, ^{
|
|
104
116
|
return runtime.evaluateJavaScript(buffer, sourceURL);
|
|
105
117
|
});
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
inline jsi::Value callFunction(jsi::
|
|
120
|
+
inline jsi::Value callFunction(jsi::IRuntime &runtime, const jsi::Function &function, const jsi::Value *_Nullable args, size_t count) {
|
|
109
121
|
return expo::CppError::tryCatch(runtime, ^{
|
|
110
122
|
return function.call(runtime, args, count);
|
|
111
123
|
});
|
|
112
124
|
}
|
|
113
125
|
|
|
114
|
-
inline jsi::Value callFunctionWithThis(jsi::
|
|
126
|
+
inline jsi::Value callFunctionWithThis(jsi::IRuntime &runtime, const jsi::Function &function, const jsi::Object &jsThis, const jsi::Value *_Nullable args, size_t count) {
|
|
115
127
|
return expo::CppError::tryCatch(runtime, ^{
|
|
116
128
|
return function.callWithThis(runtime, jsThis, args, count);
|
|
117
129
|
});
|
|
118
130
|
}
|
|
119
131
|
|
|
120
|
-
inline jsi::Value callAsConstructor(jsi::
|
|
132
|
+
inline jsi::Value callAsConstructor(jsi::IRuntime &runtime, const jsi::Function &function, const jsi::Value *_Nullable args, size_t count) {
|
|
121
133
|
return expo::CppError::tryCatch(runtime, ^{
|
|
122
134
|
return function.callAsConstructor(runtime, args, count);
|
|
123
135
|
});
|
|
@@ -129,28 +141,28 @@ inline jsi::Value callAsConstructor(jsi::Runtime &runtime, const jsi::Function &
|
|
|
129
141
|
* Converts a `jsi::ArrayBuffer` to a `jsi::Value`. Needed because Swift/C++ interop
|
|
130
142
|
* does not implicitly upcast `ArrayBuffer` to `Object` for the `Value` constructor.
|
|
131
143
|
*/
|
|
132
|
-
inline jsi::Value valueFromArrayBuffer(jsi::
|
|
144
|
+
inline jsi::Value valueFromArrayBuffer(jsi::IRuntime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
|
|
133
145
|
return jsi::Value(runtime, arrayBuffer);
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
/**
|
|
137
149
|
* Returns the size of the array buffer storage in bytes.
|
|
138
150
|
*/
|
|
139
|
-
inline size_t arrayBufferSize(jsi::
|
|
151
|
+
inline size_t arrayBufferSize(jsi::IRuntime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
|
|
140
152
|
return arrayBuffer.size(runtime);
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
/**
|
|
144
156
|
* Returns a pointer to the underlying data of the array buffer.
|
|
145
157
|
*/
|
|
146
|
-
inline uint8_t *arrayBufferData(jsi::
|
|
158
|
+
inline uint8_t *arrayBufferData(jsi::IRuntime &runtime, const jsi::ArrayBuffer &arrayBuffer) {
|
|
147
159
|
return arrayBuffer.data(runtime);
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
/**
|
|
151
163
|
* Creates a new array buffer of the given size with zero-initialized memory.
|
|
152
164
|
*/
|
|
153
|
-
inline jsi::ArrayBuffer createArrayBuffer(jsi::
|
|
165
|
+
inline jsi::ArrayBuffer createArrayBuffer(jsi::IRuntime &runtime, size_t size) {
|
|
154
166
|
uint8_t *data = new uint8_t[size]();
|
|
155
167
|
auto buffer = std::make_shared<MemoryBuffer>(data, size, [data]() { delete[] data; });
|
|
156
168
|
return jsi::ArrayBuffer(runtime, std::move(buffer));
|
|
@@ -161,7 +173,7 @@ inline jsi::ArrayBuffer createArrayBuffer(jsi::Runtime &runtime, size_t size) {
|
|
|
161
173
|
* The cleanup function is called (with the cleanup context) when the ArrayBuffer is deallocated.
|
|
162
174
|
*/
|
|
163
175
|
inline jsi::ArrayBuffer createArrayBuffer(
|
|
164
|
-
jsi::
|
|
176
|
+
jsi::IRuntime &runtime,
|
|
165
177
|
uint8_t *data,
|
|
166
178
|
size_t size,
|
|
167
179
|
void *_Nonnull cleanupContext,
|
|
@@ -179,20 +191,20 @@ inline jsi::ArrayBuffer createArrayBuffer(
|
|
|
179
191
|
|
|
180
192
|
// MARK: - Native state
|
|
181
193
|
|
|
182
|
-
inline bool hasNativeState(jsi::
|
|
194
|
+
inline bool hasNativeState(jsi::IRuntime &runtime, const jsi::Object &object) {
|
|
183
195
|
return object.hasNativeState<expo::NativeState>(runtime);
|
|
184
196
|
}
|
|
185
197
|
|
|
186
|
-
inline void setNativeState(jsi::
|
|
198
|
+
inline void setNativeState(jsi::IRuntime &runtime, const jsi::Object &object, expo::NativeState &nativeState) {
|
|
187
199
|
std::shared_ptr<expo::NativeState> nativeStatePtr = std::shared_ptr<expo::NativeState>(&nativeState);
|
|
188
200
|
object.setNativeState(runtime, nativeStatePtr);
|
|
189
201
|
}
|
|
190
202
|
|
|
191
|
-
inline void unsetNativeState(jsi::
|
|
203
|
+
inline void unsetNativeState(jsi::IRuntime &runtime, const jsi::Object &object) {
|
|
192
204
|
object.setNativeState(runtime, nullptr);
|
|
193
205
|
}
|
|
194
206
|
|
|
195
|
-
inline expo::NativeState *_Nullable getNativeState(jsi::
|
|
207
|
+
inline expo::NativeState *_Nullable getNativeState(jsi::IRuntime &runtime, const jsi::Object &object) {
|
|
196
208
|
if (!object.hasNativeState<expo::NativeState>(runtime)) {
|
|
197
209
|
// JSI's implementation asserts if `hasNativeState` returns true, but we prefer to make it nullable.
|
|
198
210
|
return nullptr;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
#pragma once
|
|
6
6
|
|
|
7
|
-
#include
|
|
7
|
+
#include "IRuntimeCompat.h"
|
|
8
8
|
|
|
9
9
|
namespace jsi = facebook::jsi;
|
|
10
10
|
|
|
@@ -26,19 +26,19 @@ enum class TypedArrayKind {
|
|
|
26
26
|
BigUint64Array = 11,
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
bool isTypedArray(jsi::
|
|
29
|
+
bool isTypedArray(jsi::IRuntime &runtime, const jsi::Object &jsObj);
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Returns the `TypedArrayKind` of the given typed-array object, derived from its
|
|
33
33
|
* `constructor.name` (e.g. `Uint8Array`, `Float32Array`).
|
|
34
34
|
*/
|
|
35
|
-
TypedArrayKind getTypedArrayKind(jsi::
|
|
35
|
+
TypedArrayKind getTypedArrayKind(jsi::IRuntime &runtime, const jsi::Object &jsObj);
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Returns the underlying `ArrayBuffer` backing the given typed-array object.
|
|
39
39
|
* Throws if the object has no attached ArrayBuffer.
|
|
40
40
|
*/
|
|
41
|
-
jsi::ArrayBuffer getTypedArrayBuffer(jsi::
|
|
41
|
+
jsi::ArrayBuffer getTypedArrayBuffer(jsi::IRuntime &runtime, const jsi::Object &jsObj);
|
|
42
42
|
|
|
43
43
|
} // namespace expo
|
|
44
44
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Testing
|
|
2
2
|
import ExpoModulesJSI
|
|
3
|
+
import Foundation
|
|
3
4
|
|
|
4
5
|
@Suite
|
|
5
6
|
@JavaScriptActor
|
|
@@ -118,6 +119,49 @@ struct JavaScriptRuntimeTests {
|
|
|
118
119
|
#expect(result == 100)
|
|
119
120
|
}
|
|
120
121
|
|
|
122
|
+
// The execute<R> overloads have a same-thread fast path and a cross-thread path that
|
|
123
|
+
// schedules the closure onto the JS thread and pumps the caller's run loop until it
|
|
124
|
+
// completes. The tests above run on `@JavaScriptActor` (the JS thread), so they only
|
|
125
|
+
// exercise the fast path. The next three hop off the JS thread first to cover the
|
|
126
|
+
// cross-thread scheduling + run-loop pump. The sync overloads of `execute` are
|
|
127
|
+
// `@available(*, noasync)`, so the caller must be a real synchronous thread — wrapping
|
|
128
|
+
// in `Task.detached` would stay on the cooperative pool and trip the noasync diagnostic.
|
|
129
|
+
|
|
130
|
+
@Test
|
|
131
|
+
func `execute sync from off-thread caller`() async throws {
|
|
132
|
+
let runtime = self.runtime
|
|
133
|
+
let result = try await onSyncOffThread {
|
|
134
|
+
try runtime.execute { @JavaScriptActor in
|
|
135
|
+
return runtime.global().hasProperty("Object") ? 1 : 0
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
#expect(result == 1)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@Test
|
|
142
|
+
func `execute blocking-async from off-thread caller`() async throws {
|
|
143
|
+
let runtime = self.runtime
|
|
144
|
+
let result = try await onSyncOffThread {
|
|
145
|
+
try runtime.execute { @JavaScriptActor () async in
|
|
146
|
+
await Task.yield()
|
|
147
|
+
return runtime.global().hasProperty("Object") ? 1 : 0
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
#expect(result == 1)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@Test
|
|
154
|
+
func `execute sync rethrows from off-thread caller`() async throws {
|
|
155
|
+
let runtime = self.runtime
|
|
156
|
+
await #expect(throws: ScriptEvaluationError.self) {
|
|
157
|
+
try await onSyncOffThread {
|
|
158
|
+
try runtime.execute { @JavaScriptActor in
|
|
159
|
+
try runtime.eval("invalid syntax +++")
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
121
165
|
// MARK: - Host objects
|
|
122
166
|
|
|
123
167
|
@Test
|
|
@@ -698,3 +742,19 @@ struct JavaScriptRuntimeTests {
|
|
|
698
742
|
#expect(result.getInt() == 3)
|
|
699
743
|
}
|
|
700
744
|
}
|
|
745
|
+
|
|
746
|
+
/// Runs `body` on a freshly spawned synchronous thread and bridges the result back into the
|
|
747
|
+
/// async test. The thread has a real run loop, which the cross-thread `execute` path pumps.
|
|
748
|
+
private func onSyncOffThread<R: Sendable>(
|
|
749
|
+
_ body: @escaping @Sendable () throws -> R
|
|
750
|
+
) async throws -> R {
|
|
751
|
+
return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<R, any Error>) in
|
|
752
|
+
Thread.detachNewThread {
|
|
753
|
+
do {
|
|
754
|
+
continuation.resume(returning: try body())
|
|
755
|
+
} catch {
|
|
756
|
+
continuation.resume(throwing: error)
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
# - Cleans .swiftinterface files for cross-compiler compatibility
|
|
16
16
|
#
|
|
17
17
|
# Usage:
|
|
18
|
-
#
|
|
18
|
+
# ./build-xcframework.sh [--clean]
|
|
19
19
|
#
|
|
20
20
|
# Environment:
|
|
21
|
-
# PODS_ROOT
|
|
21
|
+
# PODS_ROOT Path to the CocoaPods Pods directory. Defaults to
|
|
22
|
+
# $EXPO_ROOT_DIR/apps/bare-expo/ios/Pods (set by direnv).
|
|
22
23
|
# PLATFORM_NAME (optional) Build for a specific platform (e.g. iphoneos, iphonesimulator).
|
|
23
24
|
# When unset, builds for both iphoneos and iphonesimulator.
|
|
24
25
|
|
|
@@ -36,6 +37,8 @@ BUILD_PRODUCTS_PATH="${DERIVED_DATA_PATH}/Build/Products"
|
|
|
36
37
|
|
|
37
38
|
source "${PACKAGE_DIR}/scripts/xcframework-helpers.sh"
|
|
38
39
|
|
|
40
|
+
resolve_pods_root "$PACKAGE_DIR"
|
|
41
|
+
|
|
39
42
|
CLEAN=false
|
|
40
43
|
|
|
41
44
|
while [[ $# -gt 0 ]]; do
|
|
@@ -74,6 +77,10 @@ SOURCE_FILES=(
|
|
|
74
77
|
"${PACKAGE_DIR}/scripts/build-xcframework.sh"
|
|
75
78
|
"${PACKAGE_DIR}/scripts/create-stub-xcframework.sh"
|
|
76
79
|
"${PACKAGE_DIR}/scripts/xcframework-helpers.sh"
|
|
80
|
+
# JSI headers we compile against. `cat` follows the symlinks CocoaPods
|
|
81
|
+
# installs into Pods/Headers/Public so the real header contents get hashed.
|
|
82
|
+
"${PODS_ROOT}/Headers/Public/React-jsi/jsi/jsi.h"
|
|
83
|
+
"${PODS_ROOT}/Headers/Public/React-jsi/jsi/jsi-inl.h"
|
|
77
84
|
)
|
|
78
85
|
|
|
79
86
|
compute_hash() {
|
package/apple/scripts/test.sh
CHANGED
|
@@ -29,26 +29,11 @@ set -eo pipefail
|
|
|
29
29
|
|
|
30
30
|
PACKAGE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
# this script's location so the script still works outside a direnv shell.
|
|
38
|
-
|
|
39
|
-
if [[ -z "${PODS_ROOT:-}" ]]; then
|
|
40
|
-
: "${EXPO_ROOT_DIR:=$(cd "${PACKAGE_DIR}/../../.." && pwd)}"
|
|
41
|
-
PODS_ROOT="${EXPO_ROOT_DIR}/apps/bare-expo/ios/Pods"
|
|
42
|
-
fi
|
|
43
|
-
if [[ ! -d "$PODS_ROOT" ]]; then
|
|
44
|
-
echo "error: PODS_ROOT does not exist: $PODS_ROOT" >&2
|
|
45
|
-
echo " Run \`pod install\` in apps/bare-expo/ios first, or set PODS_ROOT explicitly." >&2
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
PODS_ROOT="$(cd "$PODS_ROOT" && pwd)"
|
|
49
|
-
# Export so xcodebuild forwards it to SwiftPM's manifest evaluation, where
|
|
50
|
-
# Package.swift reads it via resolvePodsRoot().
|
|
51
|
-
export PODS_ROOT
|
|
32
|
+
source "${PACKAGE_DIR}/scripts/xcframework-helpers.sh"
|
|
33
|
+
|
|
34
|
+
# Resolve PODS_ROOT with fallback logic (from xcframework-helpers.sh).
|
|
35
|
+
# Must be passed to subprocess calls, not exported, to avoid polluting the environment.
|
|
36
|
+
resolve_pods_root "$PACKAGE_DIR"
|
|
52
37
|
|
|
53
38
|
# --- Symlink xcframeworks for SwiftPM binary targets ---
|
|
54
39
|
|
|
@@ -79,7 +64,7 @@ link_xcframework "ReactNativeDependencies" \
|
|
|
79
64
|
|
|
80
65
|
# --- Generate the jsi module map ---
|
|
81
66
|
|
|
82
|
-
"${PACKAGE_DIR}/scripts/generate-modulemap.sh"
|
|
67
|
+
env PODS_ROOT="$PODS_ROOT" "${PACKAGE_DIR}/scripts/generate-modulemap.sh"
|
|
83
68
|
|
|
84
69
|
# --- Pick a simulator destination ---
|
|
85
70
|
|
|
@@ -113,7 +98,7 @@ fi
|
|
|
113
98
|
# --- Run the tests ---
|
|
114
99
|
|
|
115
100
|
cd "$PACKAGE_DIR"
|
|
116
|
-
exec xcodebuild test \
|
|
101
|
+
exec env PODS_ROOT="$PODS_ROOT" xcodebuild test \
|
|
117
102
|
-scheme ExpoModulesJSI \
|
|
118
103
|
-destination "$DESTINATION" \
|
|
119
104
|
-derivedDataPath "${PACKAGE_DIR}/.DerivedData" \
|
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
# canonical slice metadata and the Info.plist writer used by both scripts so
|
|
5
5
|
# the manifest is produced from a single place.
|
|
6
6
|
|
|
7
|
+
# resolve_pods_root PACKAGE_DIR
|
|
8
|
+
# Sets PODS_ROOT, preferring an explicit value (e.g. from CocoaPods build phase)
|
|
9
|
+
# over a fallback to bare-expo under EXPO_ROOT_DIR. Exits if PODS_ROOT doesn't exist.
|
|
10
|
+
resolve_pods_root() {
|
|
11
|
+
local package_dir="$1"
|
|
12
|
+
|
|
13
|
+
if [[ -z "${PODS_ROOT:-}" ]]; then
|
|
14
|
+
: "${EXPO_ROOT_DIR:=$(cd "${package_dir}/../../.." && pwd)}"
|
|
15
|
+
PODS_ROOT="${EXPO_ROOT_DIR}/apps/bare-expo/ios/Pods"
|
|
16
|
+
fi
|
|
17
|
+
if [[ ! -d "$PODS_ROOT" ]]; then
|
|
18
|
+
echo "error: PODS_ROOT does not exist: $PODS_ROOT" >&2
|
|
19
|
+
echo " Run \`pod install\` in apps/bare-expo/ios first, or set PODS_ROOT explicitly." >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
PODS_ROOT="$(cd "$PODS_ROOT" && pwd)"
|
|
23
|
+
}
|
|
24
|
+
|
|
7
25
|
# All slice IDs known to the xcframework, mapped to their plist metadata.
|
|
8
26
|
# CocoaPods reads Info.plist at `pod install` time and generates a per-slice
|
|
9
27
|
# copy script; any slice missing from this table will be skipped by
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-modules-jsi",
|
|
3
|
-
"version": "56.0.
|
|
3
|
+
"version": "56.0.5",
|
|
4
4
|
"description": "The JavaScript Interface for Expo Modules",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"sideEffects": [],
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"./apple/scripts/test.sh"
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "f26be3dd9396bf7c399a1d607865d0fabdbc0d64",
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "apple/scripts/build-xcframework.sh",
|
|
47
47
|
"test": "apple/scripts/test.sh"
|