11import _CJavaScriptKit
22
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+
311/// `JSObject` represents an object in JavaScript and supports dynamic member lookup.
412/// Any member access like `object.foo` will dynamically request the JavaScript and Swift
513/// runtime bridge library for a member with the specified name in this object.
@@ -16,11 +24,43 @@ import _CJavaScriptKit
1624/// reference counting system.
1725@dynamicMemberLookup
1826public class JSObject : Equatable {
27+ internal static var constructor : JSFunction { _constructor }
28+ @LazyThreadLocal ( initialize: { JSObject . global. Object. function! } )
29+ internal static var _constructor : JSFunction
30+
1931 @_spi ( JSObject_id)
2032 public var id : JavaScriptObjectRef
33+
34+ #if compiler(>=6.1) && _runtime(_multithreaded)
35+ private let ownerThread : pthread_t
36+ #endif
37+
2138 @_spi ( JSObject_id)
2239 public init ( id: JavaScriptObjectRef ) {
2340 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
2464 }
2565
2666#if !hasFeature(Embedded)
@@ -79,32 +119,56 @@ public class JSObject: Equatable {
79119 /// - Parameter name: The name of this object's member to access.
80120 /// - Returns: The value of the `name` member of this object.
81121 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+ }
84130 }
85131
86132 /// Access the `name` member dynamically through JavaScript and Swift runtime bridge library.
87133 /// - Parameter name: The name of this object's member to access.
88134 /// - Returns: The value of the `name` member of this object.
89135 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+ }
92144 }
93145
94146 /// Access the `index` member dynamically through JavaScript and Swift runtime bridge library.
95147 /// - Parameter index: The index of this object's member to access.
96148 /// - Returns: The value of the `index` member of this object.
97149 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+ }
100158 }
101159
102160 /// Access the `symbol` member dynamically through JavaScript and Swift runtime bridge library.
103161 /// - Parameter symbol: The name of this object's member to access.
104162 /// - Returns: The value of the `name` member of this object.
105163 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+ }
108172 }
109173
110174#if !hasFeature(Embedded)
@@ -134,7 +198,8 @@ public class JSObject: Equatable {
134198 /// - Parameter constructor: The constructor function to check.
135199 /// - Returns: The result of `instanceof` in the JavaScript environment.
136200 public func isInstanceOf( _ constructor: JSFunction ) -> Bool {
137- swjs_instanceof ( id, constructor. id)
201+ assertOnOwnerThread ( hint: " calling 'isInstanceOf' " )
202+ return swjs_instanceof ( id, constructor. id)
138203 }
139204
140205 static let _JS_Predef_Value_Global : JavaScriptObjectRef = 0
@@ -143,23 +208,23 @@ public class JSObject: Equatable {
143208 /// This allows access to the global properties and global names by accessing the `JSObject` returned.
144209 public static var global : JSObject { return _global }
145210
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
154215
155- deinit { swjs_release ( id) }
216+ deinit {
217+ assertOnOwnerThread ( hint: " deinitializing " )
218+ swjs_release ( id)
219+ }
156220
157221 /// Returns a Boolean value indicating whether two values point to same objects.
158222 ///
159223 /// - Parameters:
160224 /// - lhs: A object to compare.
161225 /// - rhs: Another object to compare.
162226 public static func == ( lhs: JSObject , rhs: JSObject ) -> Bool {
227+ assertSameOwnerThread ( lhs: lhs, rhs: rhs, hint: " comparing two JSObjects for equality " )
163228 return lhs. id == rhs. id
164229 }
165230
0 commit comments