expo-modules-core 57.0.2 → 57.0.4
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 +19 -1
- package/ExpoModulesCore.podspec +15 -7
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/kotlin/types/ColorCompat.kt +148 -0
- package/android/src/main/java/expo/modules/kotlin/types/ColorTypeConverter.kt +14 -17
- package/android/src/main/java/expo/modules/kotlin/types/TypeConverterProvider.kt +2 -1
- package/ios/Core/AppContext.swift +4 -0
- package/ios/Core/Classes/ClassDefinition.swift +20 -7
- package/ios/Core/Convertibles/Convertibles+Color.swift +162 -63
- package/ios/Core/ExpoModulesMacros.swift +11 -2
- package/ios/Core/Functions/ConcurrentFunctionDefinition.swift +55 -9
- package/ios/Core/ModuleHolder.swift +1 -1
- package/ios/Core/Protocols/AnyModule.swift +2 -2
- package/ios/Core/SharedObjects/SharedObject.swift +14 -0
- package/ios/Core/SharedObjects/SharedObjectNativeState.swift +40 -12
- package/ios/Core/SharedObjects/SharedObjectRegistry.swift +72 -57
- package/ios/Core/Views/SwiftUI/ExpoSwiftUI.swift +12 -0
- package/ios/Core/Views/SwiftUI/SwiftUIVirtualView.swift +21 -5
- package/ios/JS/EXReactSchedulerDispatch.h +38 -7
- package/ios/JS/EXReactSchedulerDispatch.mm +29 -2
- package/package.json +4 -4
- package/prebuilds/output/debug/xcframeworks/ExpoModulesCore.tar.gz +0 -0
- package/prebuilds/output/debug/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoModulesCore.tar.gz +0 -0
- package/prebuilds/output/release/xcframeworks/ExpoModulesWorklets.tar.gz +0 -0
|
@@ -28,7 +28,8 @@ extension UIColor: Convertible {
|
|
|
28
28
|
// Handle `PlatformColor` and `DynamicColorIOS`
|
|
29
29
|
if let opaqueValue = value as? [String: Any] {
|
|
30
30
|
if let semanticName = opaqueValue["semantic"] as? String,
|
|
31
|
-
let color = resolveNamedColor(name: semanticName)
|
|
31
|
+
let color = resolveNamedColor(name: semanticName)
|
|
32
|
+
{
|
|
32
33
|
return color as! Self
|
|
33
34
|
}
|
|
34
35
|
if let semanticArray = opaqueValue["semantic"] as? [String] {
|
|
@@ -40,7 +41,8 @@ extension UIColor: Convertible {
|
|
|
40
41
|
}
|
|
41
42
|
if let appearances = opaqueValue["dynamic"] as? [String: Any],
|
|
42
43
|
let lightColor = try appearances["light"].map({ try UIColor.convert(from: $0) }),
|
|
43
|
-
let darkColor = try appearances["dark"].map({ try UIColor.convert(from: $0) })
|
|
44
|
+
let darkColor = try appearances["dark"].map({ try UIColor.convert(from: $0) })
|
|
45
|
+
{
|
|
44
46
|
let highContrastLightColor = try appearances["highContrastLight"].map({ try UIColor.convert(from: $0) })
|
|
45
47
|
let highContrastDarkColor = try appearances["highContrastDark"].map({ try UIColor.convert(from: $0) })
|
|
46
48
|
|
|
@@ -166,9 +168,7 @@ private func uiColorWithComponents(_ components: [Double]) throws -> UIColor {
|
|
|
166
168
|
|
|
167
169
|
// MARK: - Color String Parsing
|
|
168
170
|
|
|
169
|
-
|
|
170
|
-
Parses a color string (hex, rgb, hsl, hwb) into a UIColor.
|
|
171
|
-
*/
|
|
171
|
+
/// Parses a color string (hex, rgb, hsl, hwb) into a UIColor.
|
|
172
172
|
private func colorFromString(_ value: String) throws -> UIColor {
|
|
173
173
|
let input = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
174
174
|
let lowercased = input.lowercased()
|
|
@@ -233,80 +233,183 @@ private func colorFromRgba(_ rgba: UInt64) throws -> UIColor {
|
|
|
233
233
|
|
|
234
234
|
// MARK: - CSS Color Function Regex Patterns
|
|
235
235
|
|
|
236
|
-
|
|
237
|
-
|
|
236
|
+
// These are built from RegexBuilder primitives only. Composing regex-literal fragments
|
|
237
|
+
// (e.g. /hsla?\(\s*/) inside a builder miscompiles on some OS runtime versions
|
|
238
|
+
// (observed on the iOS 27.0 beta simulator) and the patterns silently stop matching.
|
|
239
|
+
|
|
240
|
+
/// A CSS number like `-12`, `+1.5`, `.5` or `100`.
|
|
241
|
+
nonisolated(unsafe) private let cssNumber = Regex {
|
|
242
|
+
Optionally {
|
|
243
|
+
CharacterClass.anyOf("-+")
|
|
244
|
+
}
|
|
245
|
+
ZeroOrMore(.digit)
|
|
246
|
+
Optionally {
|
|
247
|
+
"."
|
|
248
|
+
}
|
|
249
|
+
OneOrMore(.digit)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
nonisolated(unsafe) private let cssNumberOrPercent = Regex {
|
|
253
|
+
cssNumber
|
|
254
|
+
Optionally {
|
|
255
|
+
"%"
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
nonisolated(unsafe) private let commaSeparator = Regex {
|
|
260
|
+
ZeroOrMore(.whitespace)
|
|
261
|
+
","
|
|
262
|
+
ZeroOrMore(.whitespace)
|
|
263
|
+
}
|
|
238
264
|
|
|
239
265
|
nonisolated(unsafe) private let rgbCommaRegex = Regex {
|
|
240
|
-
|
|
241
|
-
Capture { cssNumberOrPercent }
|
|
242
|
-
/\s*,\s*/
|
|
243
|
-
Capture { cssNumberOrPercent }
|
|
244
|
-
/\s*,\s*/
|
|
245
|
-
Capture { cssNumberOrPercent }
|
|
266
|
+
"rgb"
|
|
246
267
|
Optionally {
|
|
247
|
-
|
|
248
|
-
|
|
268
|
+
"a"
|
|
269
|
+
}
|
|
270
|
+
"("
|
|
271
|
+
ZeroOrMore(.whitespace)
|
|
272
|
+
Capture {
|
|
273
|
+
cssNumberOrPercent
|
|
274
|
+
}
|
|
275
|
+
commaSeparator
|
|
276
|
+
Capture {
|
|
277
|
+
cssNumberOrPercent
|
|
278
|
+
}
|
|
279
|
+
commaSeparator
|
|
280
|
+
Capture {
|
|
281
|
+
cssNumberOrPercent
|
|
249
282
|
}
|
|
250
|
-
|
|
283
|
+
Optionally {
|
|
284
|
+
commaSeparator
|
|
285
|
+
Capture {
|
|
286
|
+
cssNumberOrPercent
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
ZeroOrMore(.whitespace)
|
|
290
|
+
")"
|
|
251
291
|
}.ignoresCase()
|
|
252
292
|
|
|
253
293
|
nonisolated(unsafe) private let rgbSpaceRegex = Regex {
|
|
254
|
-
|
|
255
|
-
Capture { cssNumberOrPercent }
|
|
256
|
-
/\s+/
|
|
257
|
-
Capture { cssNumberOrPercent }
|
|
258
|
-
/\s+/
|
|
259
|
-
Capture { cssNumberOrPercent }
|
|
294
|
+
"rgb"
|
|
260
295
|
Optionally {
|
|
261
|
-
|
|
262
|
-
|
|
296
|
+
"a"
|
|
297
|
+
}
|
|
298
|
+
"("
|
|
299
|
+
ZeroOrMore(.whitespace)
|
|
300
|
+
Capture {
|
|
301
|
+
cssNumberOrPercent
|
|
263
302
|
}
|
|
264
|
-
|
|
303
|
+
OneOrMore(.whitespace)
|
|
304
|
+
Capture {
|
|
305
|
+
cssNumberOrPercent
|
|
306
|
+
}
|
|
307
|
+
OneOrMore(.whitespace)
|
|
308
|
+
Capture {
|
|
309
|
+
cssNumberOrPercent
|
|
310
|
+
}
|
|
311
|
+
Optionally {
|
|
312
|
+
ZeroOrMore(.whitespace)
|
|
313
|
+
"/"
|
|
314
|
+
ZeroOrMore(.whitespace)
|
|
315
|
+
Capture {
|
|
316
|
+
cssNumberOrPercent
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
ZeroOrMore(.whitespace)
|
|
320
|
+
")"
|
|
265
321
|
}.ignoresCase()
|
|
266
322
|
|
|
267
323
|
nonisolated(unsafe) private let hslCommaRegex = Regex {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
324
|
+
"hsl"
|
|
325
|
+
Optionally {
|
|
326
|
+
"a"
|
|
327
|
+
}
|
|
328
|
+
"("
|
|
329
|
+
ZeroOrMore(.whitespace)
|
|
330
|
+
Capture {
|
|
331
|
+
cssNumber
|
|
332
|
+
}
|
|
333
|
+
commaSeparator
|
|
334
|
+
Capture {
|
|
335
|
+
cssNumber
|
|
336
|
+
}
|
|
337
|
+
"%"
|
|
338
|
+
commaSeparator
|
|
339
|
+
Capture {
|
|
340
|
+
cssNumber
|
|
341
|
+
}
|
|
342
|
+
"%"
|
|
343
|
+
ZeroOrMore(.whitespace)
|
|
275
344
|
Optionally {
|
|
276
|
-
|
|
277
|
-
|
|
345
|
+
","
|
|
346
|
+
ZeroOrMore(.whitespace)
|
|
347
|
+
Capture {
|
|
348
|
+
cssNumberOrPercent
|
|
349
|
+
}
|
|
278
350
|
}
|
|
279
|
-
|
|
351
|
+
ZeroOrMore(.whitespace)
|
|
352
|
+
")"
|
|
280
353
|
}.ignoresCase()
|
|
281
354
|
|
|
282
355
|
nonisolated(unsafe) private let hslSpaceRegex = Regex {
|
|
283
|
-
|
|
284
|
-
Capture { cssNumber }
|
|
285
|
-
/\s+/
|
|
286
|
-
Capture { cssNumber }
|
|
287
|
-
/%\s+/
|
|
288
|
-
Capture { cssNumber }
|
|
289
|
-
/%\s*/
|
|
356
|
+
"hsl"
|
|
290
357
|
Optionally {
|
|
291
|
-
|
|
292
|
-
|
|
358
|
+
"a"
|
|
359
|
+
}
|
|
360
|
+
"("
|
|
361
|
+
ZeroOrMore(.whitespace)
|
|
362
|
+
Capture {
|
|
363
|
+
cssNumber
|
|
364
|
+
}
|
|
365
|
+
OneOrMore(.whitespace)
|
|
366
|
+
Capture {
|
|
367
|
+
cssNumber
|
|
368
|
+
}
|
|
369
|
+
"%"
|
|
370
|
+
OneOrMore(.whitespace)
|
|
371
|
+
Capture {
|
|
372
|
+
cssNumber
|
|
293
373
|
}
|
|
294
|
-
|
|
374
|
+
"%"
|
|
375
|
+
ZeroOrMore(.whitespace)
|
|
376
|
+
Optionally {
|
|
377
|
+
"/"
|
|
378
|
+
ZeroOrMore(.whitespace)
|
|
379
|
+
Capture {
|
|
380
|
+
cssNumberOrPercent
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
ZeroOrMore(.whitespace)
|
|
384
|
+
")"
|
|
295
385
|
}.ignoresCase()
|
|
296
386
|
|
|
297
387
|
nonisolated(unsafe) private let hwbRegex = Regex {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
388
|
+
"hwb("
|
|
389
|
+
ZeroOrMore(.whitespace)
|
|
390
|
+
Capture {
|
|
391
|
+
cssNumber
|
|
392
|
+
}
|
|
393
|
+
OneOrMore(.whitespace)
|
|
394
|
+
Capture {
|
|
395
|
+
cssNumber
|
|
396
|
+
}
|
|
397
|
+
"%"
|
|
398
|
+
OneOrMore(.whitespace)
|
|
399
|
+
Capture {
|
|
400
|
+
cssNumber
|
|
401
|
+
}
|
|
402
|
+
"%"
|
|
403
|
+
ZeroOrMore(.whitespace)
|
|
305
404
|
Optionally {
|
|
306
|
-
|
|
307
|
-
|
|
405
|
+
"/"
|
|
406
|
+
ZeroOrMore(.whitespace)
|
|
407
|
+
Capture {
|
|
408
|
+
cssNumberOrPercent
|
|
409
|
+
}
|
|
308
410
|
}
|
|
309
|
-
|
|
411
|
+
ZeroOrMore(.whitespace)
|
|
412
|
+
")"
|
|
310
413
|
}.ignoresCase()
|
|
311
414
|
|
|
312
415
|
// MARK: - CSS Color Function Parsers
|
|
@@ -387,8 +490,7 @@ private func hwbToUIColor(h: CGFloat, w: CGFloat, b: CGFloat, a: CGFloat) -> UIC
|
|
|
387
490
|
}
|
|
388
491
|
|
|
389
492
|
private func colorFromRGBString(_ rgbString: String) throws -> UIColor {
|
|
390
|
-
if let match = rgbString.wholeMatch(of: rgbCommaRegex) ?? rgbString.wholeMatch(of: rgbSpaceRegex)
|
|
391
|
-
{
|
|
493
|
+
if let match = rgbString.wholeMatch(of: rgbCommaRegex) ?? rgbString.wholeMatch(of: rgbSpaceRegex) {
|
|
392
494
|
let r = parseRgbComponent(match.1)
|
|
393
495
|
let g = parseRgbComponent(match.2)
|
|
394
496
|
let b = parseRgbComponent(match.3)
|
|
@@ -399,8 +501,7 @@ private func colorFromRGBString(_ rgbString: String) throws -> UIColor {
|
|
|
399
501
|
}
|
|
400
502
|
|
|
401
503
|
private func colorFromHSLString(_ hslString: String) throws -> UIColor {
|
|
402
|
-
if let match = hslString.wholeMatch(of: hslCommaRegex) ?? hslString.wholeMatch(of: hslSpaceRegex)
|
|
403
|
-
{
|
|
504
|
+
if let match = hslString.wholeMatch(of: hslCommaRegex) ?? hslString.wholeMatch(of: hslSpaceRegex) {
|
|
404
505
|
let h = CGFloat(Double(match.1) ?? 0)
|
|
405
506
|
let s = CGFloat(min(max((Double(match.2) ?? 0) / 100.0, 0), 1))
|
|
406
507
|
let l = CGFloat(min(max((Double(match.3) ?? 0) / 100.0, 0), 1))
|
|
@@ -421,10 +522,8 @@ private func colorFromHWBString(_ hwbString: String) throws -> UIColor {
|
|
|
421
522
|
throw InvalidHWBColorException(hwbString)
|
|
422
523
|
}
|
|
423
524
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
and additionally the transparent color.
|
|
427
|
-
*/
|
|
525
|
+
/// Color components for named colors following the [CSS3/SVG specification](https://www.w3.org/TR/css-color-3/#svg-color)
|
|
526
|
+
/// and additionally the transparent color.
|
|
428
527
|
private let namedColors: [String: [Double]] = [
|
|
429
528
|
"aliceblue": [240, 248, 255, 255],
|
|
430
529
|
"antiquewhite": [250, 235, 215, 255],
|
|
@@ -71,7 +71,7 @@ public macro Event(_ name: String? = nil, sync: Bool = false) =
|
|
|
71
71
|
/// `expo-modules-core` calls it automatically and merges the result into the module's
|
|
72
72
|
/// definition, so the user doesn't have to reference it from `definition()`. `@JS` functions are
|
|
73
73
|
/// additionally bound directly into the module's JS object by a synthesized
|
|
74
|
-
/// `_decorateModule(object:in:
|
|
74
|
+
/// `_decorateModule(object:in:)`.
|
|
75
75
|
///
|
|
76
76
|
/// The module's JavaScript name is synthesized into a `_jsName` static and read by core, so there
|
|
77
77
|
/// is no `Name(…)` DSL entry. It defaults to the class name; pass `@ExpoModule("CustomName")` to
|
|
@@ -100,6 +100,12 @@ public macro ExpoModule(_ name: String? = nil, classes: [Any.Type] = []) =
|
|
|
100
100
|
/// The companion `@ExpoModule(classes: [Foo.self])` wires the class into the module's
|
|
101
101
|
/// exposed surface.
|
|
102
102
|
///
|
|
103
|
+
/// `@JS` methods and properties are bound directly onto the class prototype by an override of
|
|
104
|
+
/// `_decorateSharedObject(prototype:in:)`, and a single `@JS init(...)` becomes an override
|
|
105
|
+
/// of `_constructSharedObject(this:arguments:in:)` that builds the native instance from the
|
|
106
|
+
/// JS arguments. Core calls both when building the class, so the synthesized `Class(…)` block carries
|
|
107
|
+
/// no DSL entries for the `@JS` members.
|
|
108
|
+
///
|
|
103
109
|
/// Usage:
|
|
104
110
|
///
|
|
105
111
|
/// @SharedObject
|
|
@@ -113,7 +119,10 @@ public macro ExpoModule(_ name: String? = nil, classes: [Any.Type] = []) =
|
|
|
113
119
|
/// @JS
|
|
114
120
|
/// var size: Int { 42 }
|
|
115
121
|
/// }
|
|
116
|
-
@attached(
|
|
122
|
+
@attached(
|
|
123
|
+
member,
|
|
124
|
+
names:
|
|
125
|
+
named(_synthesizedClassDefinition), named(_decorateSharedObject), named(_constructSharedObject))
|
|
117
126
|
@attached(memberAttribute)
|
|
118
127
|
public macro SharedObject(_ name: String? = nil) =
|
|
119
128
|
#externalMacro(module: "ExpoModulesMacros", type: "SharedObjectMacro")
|
|
@@ -57,15 +57,20 @@ public class ConcurrentFunctionDefinition<Args, FirstArgType, ReturnType>: AnyCo
|
|
|
57
57
|
|
|
58
58
|
@JavaScriptActor
|
|
59
59
|
func call(_ appContext: AppContext, this: JavaScriptValue, arguments: consuming JavaScriptValuesBuffer) async throws -> JavaScriptValue {
|
|
60
|
+
let argumentsTuple: Args
|
|
60
61
|
do {
|
|
61
|
-
try
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
argumentsTuple = try decodeArguments(appContext: appContext, this: this, arguments: arguments)
|
|
63
|
+
} catch let error as Exception {
|
|
64
|
+
throw FunctionCallException(name).causedBy(error)
|
|
65
|
+
} catch {
|
|
66
|
+
throw UnexpectedException(error)
|
|
67
|
+
}
|
|
68
|
+
return try await call(appContext, argumentsTuple: argumentsTuple)
|
|
69
|
+
}
|
|
65
70
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
@JavaScriptActor
|
|
72
|
+
private func call(_ appContext: AppContext, argumentsTuple: sending Args) async throws -> JavaScriptValue {
|
|
73
|
+
do {
|
|
69
74
|
// Safe to mark as nonisolated(unsafe) — the tuple contains fully converted native values
|
|
70
75
|
// with no references back to JS objects, so it can safely cross the actor boundary.
|
|
71
76
|
nonisolated(unsafe) let nonisolatedArgumentsTuple = argumentsTuple
|
|
@@ -90,16 +95,57 @@ public class ConcurrentFunctionDefinition<Args, FirstArgType, ReturnType>: AnyCo
|
|
|
90
95
|
|
|
91
96
|
@JavaScriptActor
|
|
92
97
|
func build(appContext: AppContext) throws -> JavaScriptObject {
|
|
93
|
-
return try appContext.runtime.
|
|
98
|
+
return try appContext.runtime.createFunction(name) { [weak appContext, self] this, arguments in
|
|
94
99
|
guard let appContext else {
|
|
95
100
|
throw Exceptions.AppContextLost()
|
|
96
101
|
}
|
|
97
|
-
|
|
102
|
+
|
|
103
|
+
let runtime = try appContext.runtime
|
|
104
|
+
let promise = try JavaScriptPromise(runtime)
|
|
105
|
+
let argumentsTuple: Args
|
|
106
|
+
|
|
107
|
+
do {
|
|
108
|
+
argumentsTuple = try self.decodeArguments(appContext: appContext, this: this, arguments: arguments)
|
|
109
|
+
} catch let error as Exception {
|
|
110
|
+
promise.reject(FunctionCallException(self.name).causedBy(error))
|
|
111
|
+
return promise.asValue()
|
|
112
|
+
} catch {
|
|
113
|
+
promise.reject(UnexpectedException(error))
|
|
114
|
+
return promise.asValue()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
runtime.schedule {
|
|
118
|
+
do {
|
|
119
|
+
let result = try await self.call(appContext, argumentsTuple: argumentsTuple)
|
|
120
|
+
promise.resolve(result)
|
|
121
|
+
} catch {
|
|
122
|
+
promise.reject(error)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return promise.asValue()
|
|
98
127
|
}.asObject()
|
|
99
128
|
}
|
|
100
129
|
|
|
101
130
|
// MARK: - Privates
|
|
102
131
|
|
|
132
|
+
@JavaScriptActor
|
|
133
|
+
private func decodeArguments(
|
|
134
|
+
appContext: AppContext,
|
|
135
|
+
this: JavaScriptValue,
|
|
136
|
+
arguments: consuming JavaScriptValuesBuffer
|
|
137
|
+
) throws -> Args {
|
|
138
|
+
try validateArgumentsNumber(function: self, received: arguments.count)
|
|
139
|
+
|
|
140
|
+
let nativeArguments = try toNativeClosureArguments(converter: appContext.converter, fn: self, this: this, arguments: arguments)
|
|
141
|
+
|
|
142
|
+
guard let argumentsTuple: Args = try Conversions.toTuple(nativeArguments) else {
|
|
143
|
+
throw ArgumentConversionException()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return argumentsTuple
|
|
147
|
+
}
|
|
148
|
+
|
|
103
149
|
@MainActor
|
|
104
150
|
private func callBodyOnMainActor(_ args: sending Args) async throws -> sending ReturnType {
|
|
105
151
|
return try await body(args)
|
|
@@ -139,7 +139,7 @@ public final class ModuleHolder {
|
|
|
139
139
|
|
|
140
140
|
// Install the `@JS` members the `@ExpoModule` macro binds directly into the JS object
|
|
141
141
|
// (the direct-JSI path). A no-op for modules that don't use the macro.
|
|
142
|
-
try module._decorateModule(object: object, in: appContext.runtime
|
|
142
|
+
try module._decorateModule(object: object, in: appContext.runtime)
|
|
143
143
|
|
|
144
144
|
return object
|
|
145
145
|
} catch {
|
|
@@ -34,7 +34,7 @@ public protocol AnyModule: AnyObject, AnyArgument {
|
|
|
34
34
|
/// host functions are installed alongside the DSL-described surface. Framework-internal
|
|
35
35
|
/// (leading underscore). Modules that don't use the macro fall back to the default no-op.
|
|
36
36
|
@JavaScriptActor
|
|
37
|
-
func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime
|
|
37
|
+
func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
public extension AnyModule {
|
|
@@ -53,7 +53,7 @@ public extension AnyModule {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
@JavaScriptActor
|
|
56
|
-
func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime
|
|
56
|
+
func _decorateModule(object: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws {
|
|
57
57
|
// No-op by default — only `@ExpoModule`-macro modules synthesize a real implementation.
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -83,6 +83,20 @@ open class SharedObject: AnySharedObject {
|
|
|
83
83
|
public func emit<P: AnyArgument>(event: String, arguments: sending P) {
|
|
84
84
|
emit(event: event, payload: arguments)
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
// MARK: - Macro-synthesized JSI hooks
|
|
88
|
+
|
|
89
|
+
/// Binds the class's `@JS` members onto its JavaScript prototype. Overridden by the `@SharedObject`
|
|
90
|
+
/// macro; a `class func` (not a protocol requirement) so dispatch resolves per subclass.
|
|
91
|
+
@JavaScriptActor
|
|
92
|
+
open class func _decorateSharedObject(prototype: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) throws {}
|
|
93
|
+
|
|
94
|
+
/// Builds a native instance from the JS constructor arguments, or `nil` to use the DSL `Constructor`
|
|
95
|
+
/// path. Overridden by the `@SharedObject` macro from the class's `@JS init`.
|
|
96
|
+
@JavaScriptActor
|
|
97
|
+
open class func _constructSharedObject(this: JavaScriptValue, arguments: borrowing JavaScriptValuesBuffer, in runtime: JavaScriptRuntime) throws -> SharedObject? {
|
|
98
|
+
return nil
|
|
99
|
+
}
|
|
86
100
|
}
|
|
87
101
|
|
|
88
102
|
extension SharedObject: EventEmitter {
|
|
@@ -2,24 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
import ExpoModulesJSI
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
/// Native state attached to a `SharedObject`'s JS object(s). Holds the paired native `SharedObject` so
|
|
6
|
+
/// callers that already have the JS object can recover the native counterpart via
|
|
7
|
+
/// `JavaScriptObject.getNativeState(as:)` without going through `SharedObjectRegistry`.
|
|
8
|
+
///
|
|
9
|
+
/// A single instance is shared across every runtime the native object is exposed to: its underlying C++
|
|
10
|
+
/// `jsi::NativeState` is attached to each runtime's JS object (the same `shared_ptr`, reused via
|
|
11
|
+
/// `JavaScriptNativeState.acquireShared`), and this instance maps each runtime to its paired JS object so
|
|
12
|
+
/// the native object can recover *its* JS counterpart in any given runtime.
|
|
11
13
|
internal final class SharedObjectNativeState: JavaScriptNativeState {
|
|
12
14
|
internal let native: SharedObject
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
internal var pairedWeakObject: JavaScriptWeakObject?
|
|
16
|
+
/// The JS object paired with the native object in each runtime, keyed by runtime identity and held
|
|
17
|
+
/// weakly. One native object can be exposed to several runtimes (e.g. the main and UI runtimes, or
|
|
18
|
+
/// worklet contexts), each with its own JS counterpart.
|
|
19
|
+
private var pairedObjects: [JavaScriptRuntime.ID: JavaScriptRef<JavaScriptWeakObject>] = [:]
|
|
20
20
|
|
|
21
21
|
internal init(native: SharedObject, factory: @escaping JavaScriptNativeState.Factory) {
|
|
22
22
|
self.native = native
|
|
23
23
|
super.init(factory: factory)
|
|
24
24
|
}
|
|
25
|
+
|
|
26
|
+
/// Records `jsObject` as the native object's JS counterpart in `runtime`. The caller is responsible
|
|
27
|
+
/// for having attached this native state to `jsObject` (`jsObject.setNativeState(self)`), which reuses
|
|
28
|
+
/// the shared C++ pointee across runtimes.
|
|
29
|
+
internal func setJavaScriptObject(_ jsObject: borrowing JavaScriptObject, in runtime: JavaScriptRuntime) {
|
|
30
|
+
// Drop entries whose runtime or JS object has gone away before inserting, so the map doesn't
|
|
31
|
+
// accumulate dead keys for runtimes that paired once and were torn down.
|
|
32
|
+
pruneExpired()
|
|
33
|
+
pairedObjects[runtime.id] = jsObject.createWeak().ref()
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// Returns the JS object paired with the native object in `runtime`, or `nil` if there is no live
|
|
37
|
+
/// pairing there (none was created, or the JS object has been garbage-collected).
|
|
38
|
+
internal func javaScriptObject(in runtime: JavaScriptRuntime) -> JavaScriptObject? {
|
|
39
|
+
// Unwrap the ref first so the dictionary subscript's optional doesn't nest with `withValue`'s,
|
|
40
|
+
// which would form a `JavaScriptObject??` that the non-`Copyable` element can't be wrapped in.
|
|
41
|
+
guard let ref = pairedObjects[runtime.id] else {
|
|
42
|
+
return nil
|
|
43
|
+
}
|
|
44
|
+
return ref.withValue { $0?.lock() }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private func pruneExpired() {
|
|
48
|
+
pairedObjects = pairedObjects.filter { _, ref in
|
|
49
|
+
// Keep the entry only while its weak JS object still resolves; an empty ref is dropped.
|
|
50
|
+
return ref.withValue { $0?.lock() != nil } ?? false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
25
53
|
}
|