1
1
import _CJavaScriptKit
2
2
3
+ #if arch(wasm32)
4
+ #if canImport(wasi_pthread)
5
+ import wasi_pthread
6
+ #endif
7
+ #else
8
+ import Foundation // for pthread_t on non-wasi platforms
9
+ #endif
10
+
3
11
/// `JSObject` represents an object in JavaScript and supports dynamic member lookup.
4
12
/// Any member access like `object.foo` will dynamically request the JavaScript and Swift
5
13
/// runtime bridge library for a member with the specified name in this object.
@@ -16,11 +24,43 @@ import _CJavaScriptKit
16
24
/// reference counting system.
17
25
@dynamicMemberLookup
18
26
public class JSObject : Equatable {
27
+ internal static var constructor : JSFunction { _constructor }
28
+ @LazyThreadLocal ( initialize: { JSObject . global. Object. function! } )
29
+ internal static var _constructor : JSFunction
30
+
19
31
@_spi ( JSObject_id)
20
32
public var id : JavaScriptObjectRef
33
+
34
+ #if compiler(>=6.1) && _runtime(_multithreaded)
35
+ private let ownerThread : pthread_t
36
+ #endif
37
+
21
38
@_spi ( JSObject_id)
22
39
public init ( id: JavaScriptObjectRef ) {
23
40
self . id = id
41
+ #if compiler(>=6.1) && _runtime(_multithreaded)
42
+ self . ownerThread = pthread_self ( )
43
+ #endif
44
+ }
45
+
46
+ /// Asserts that the object is being accessed from the owner thread.
47
+ ///
48
+ /// - Parameter hint: A string to provide additional context for debugging.
49
+ ///
50
+ /// NOTE: Accessing a `JSObject` from a thread other than the thread it was created on
51
+ /// is a programmer error and will result in a runtime assertion failure because JavaScript
52
+ /// object spaces are not shared across threads backed by Web Workers.
53
+ private func assertOnOwnerThread( hint: @autoclosure ( ) -> String ) {
54
+ #if compiler(>=6.1) && _runtime(_multithreaded)
55
+ precondition ( pthread_equal ( ownerThread, pthread_self ( ) ) != 0 , " JSObject is being accessed from a thread other than the owner thread: \( hint ( ) ) " )
56
+ #endif
57
+ }
58
+
59
+ /// Asserts that the two objects being compared are owned by the same thread.
60
+ private static func assertSameOwnerThread( lhs: JSObject , rhs: JSObject , hint: @autoclosure ( ) -> String ) {
61
+ #if compiler(>=6.1) && _runtime(_multithreaded)
62
+ precondition ( pthread_equal ( lhs. ownerThread, rhs. ownerThread) != 0 , " JSObject is being accessed from a thread other than the owner thread: \( hint ( ) ) " )
63
+ #endif
24
64
}
25
65
26
66
#if !hasFeature(Embedded)
@@ -79,32 +119,56 @@ public class JSObject: Equatable {
79
119
/// - Parameter name: The name of this object's member to access.
80
120
/// - Returns: The value of the `name` member of this object.
81
121
public subscript( _ name: String ) -> JSValue {
82
- get { getJSValue ( this: self , name: JSString ( name) ) }
83
- set { setJSValue ( this: self , name: JSString ( name) , value: newValue) }
122
+ get {
123
+ assertOnOwnerThread ( hint: " reading ' \( name) ' property " )
124
+ return getJSValue ( this: self , name: JSString ( name) )
125
+ }
126
+ set {
127
+ assertOnOwnerThread ( hint: " writing ' \( name) ' property " )
128
+ setJSValue ( this: self , name: JSString ( name) , value: newValue)
129
+ }
84
130
}
85
131
86
132
/// Access the `name` member dynamically through JavaScript and Swift runtime bridge library.
87
133
/// - Parameter name: The name of this object's member to access.
88
134
/// - Returns: The value of the `name` member of this object.
89
135
public subscript( _ name: JSString ) -> JSValue {
90
- get { getJSValue ( this: self , name: name) }
91
- set { setJSValue ( this: self , name: name, value: newValue) }
136
+ get {
137
+ assertOnOwnerThread ( hint: " reading '<<JSString>>' property " )
138
+ return getJSValue ( this: self , name: name)
139
+ }
140
+ set {
141
+ assertOnOwnerThread ( hint: " writing '<<JSString>>' property " )
142
+ setJSValue ( this: self , name: name, value: newValue)
143
+ }
92
144
}
93
145
94
146
/// Access the `index` member dynamically through JavaScript and Swift runtime bridge library.
95
147
/// - Parameter index: The index of this object's member to access.
96
148
/// - Returns: The value of the `index` member of this object.
97
149
public subscript( _ index: Int ) -> JSValue {
98
- get { getJSValue ( this: self , index: Int32 ( index) ) }
99
- set { setJSValue ( this: self , index: Int32 ( index) , value: newValue) }
150
+ get {
151
+ assertOnOwnerThread ( hint: " reading ' \( index) ' property " )
152
+ return getJSValue ( this: self , index: Int32 ( index) )
153
+ }
154
+ set {
155
+ assertOnOwnerThread ( hint: " writing ' \( index) ' property " )
156
+ setJSValue ( this: self , index: Int32 ( index) , value: newValue)
157
+ }
100
158
}
101
159
102
160
/// Access the `symbol` member dynamically through JavaScript and Swift runtime bridge library.
103
161
/// - Parameter symbol: The name of this object's member to access.
104
162
/// - Returns: The value of the `name` member of this object.
105
163
public subscript( _ name: JSSymbol ) -> JSValue {
106
- get { getJSValue ( this: self , symbol: name) }
107
- set { setJSValue ( this: self , symbol: name, value: newValue) }
164
+ get {
165
+ assertOnOwnerThread ( hint: " reading '<<JSSymbol>>' property " )
166
+ return getJSValue ( this: self , symbol: name)
167
+ }
168
+ set {
169
+ assertOnOwnerThread ( hint: " writing '<<JSSymbol>>' property " )
170
+ setJSValue ( this: self , symbol: name, value: newValue)
171
+ }
108
172
}
109
173
110
174
#if !hasFeature(Embedded)
@@ -134,7 +198,8 @@ public class JSObject: Equatable {
134
198
/// - Parameter constructor: The constructor function to check.
135
199
/// - Returns: The result of `instanceof` in the JavaScript environment.
136
200
public func isInstanceOf( _ constructor: JSFunction ) -> Bool {
137
- swjs_instanceof ( id, constructor. id)
201
+ assertOnOwnerThread ( hint: " calling 'isInstanceOf' " )
202
+ return swjs_instanceof ( id, constructor. id)
138
203
}
139
204
140
205
static let _JS_Predef_Value_Global : JavaScriptObjectRef = 0
@@ -143,23 +208,23 @@ public class JSObject: Equatable {
143
208
/// This allows access to the global properties and global names by accessing the `JSObject` returned.
144
209
public static var global : JSObject { return _global }
145
210
146
- // `JSObject` storage itself is immutable, and use of `JSObject.global` from other
147
- // threads maintains the same semantics as `globalThis` in JavaScript.
148
- #if compiler(>=5.10)
149
- nonisolated ( unsafe)
150
- static let _global = JSObject ( id: _JS_Predef_Value_Global)
151
- #else
152
- static let _global = JSObject ( id: _JS_Predef_Value_Global)
153
- #endif
211
+ @LazyThreadLocal ( initialize: {
212
+ return JSObject ( id: _JS_Predef_Value_Global)
213
+ } )
214
+ private static var _global : JSObject
154
215
155
- deinit { swjs_release ( id) }
216
+ deinit {
217
+ assertOnOwnerThread ( hint: " deinitializing " )
218
+ swjs_release ( id)
219
+ }
156
220
157
221
/// Returns a Boolean value indicating whether two values point to same objects.
158
222
///
159
223
/// - Parameters:
160
224
/// - lhs: A object to compare.
161
225
/// - rhs: Another object to compare.
162
226
public static func == ( lhs: JSObject , rhs: JSObject ) -> Bool {
227
+ assertSameOwnerThread ( lhs: lhs, rhs: rhs, hint: " comparing two JSObjects for equality " )
163
228
return lhs. id == rhs. id
164
229
}
165
230
0 commit comments