11import _CJavaScriptKit
22
3+ #if canImport(wasi_pthread)
4+ import wasi_pthread
5+ #else
6+ import Foundation // for pthread_t on non-wasi platforms
7+ #endif
8+
39/// `JSObject` represents an object in JavaScript and supports dynamic member lookup.
410/// Any member access like `object.foo` will dynamically request the JavaScript and Swift
511/// runtime bridge library for a member with the specified name in this object.
@@ -18,9 +24,35 @@ import _CJavaScriptKit
1824public class JSObject : Equatable {
1925 @_spi ( JSObject_id)
2026 public var id : JavaScriptObjectRef
27+
28+ #if _runtime(_multithreaded)
29+ private let ownerThread : pthread_t
30+ #endif
31+
2132 @_spi ( JSObject_id)
2233 public init ( id: JavaScriptObjectRef ) {
2334 self . id = id
35+ self . ownerThread = pthread_self ( )
36+ }
37+
38+ /// Asserts that the object is being accessed from the owner thread.
39+ ///
40+ /// - Parameter hint: A string to provide additional context for debugging.
41+ ///
42+ /// NOTE: Accessing a `JSObject` from a thread other than the thread it was created on
43+ /// is a programmer error and will result in a runtime assertion failure because JavaScript
44+ /// object spaces are not shared across threads backed by Web Workers.
45+ private func assertOnOwnerThread( hint: @autoclosure ( ) -> String ) {
46+ #if _runtime(_multithreaded)
47+ precondition ( pthread_equal ( ownerThread, pthread_self ( ) ) != 0 , " JSObject is being accessed from a thread other than the owner thread: \( hint ( ) ) " )
48+ #endif
49+ }
50+
51+ /// Asserts that the two objects being compared are owned by the same thread.
52+ private static func assertSameOwnerThread( lhs: JSObject , rhs: JSObject , hint: @autoclosure ( ) -> String ) {
53+ #if _runtime(_multithreaded)
54+ precondition ( pthread_equal ( lhs. ownerThread, rhs. ownerThread) != 0 , " JSObject is being accessed from a thread other than the owner thread: \( hint ( ) ) " )
55+ #endif
2456 }
2557
2658#if !hasFeature(Embedded)
@@ -79,32 +111,56 @@ public class JSObject: Equatable {
79111 /// - Parameter name: The name of this object's member to access.
80112 /// - Returns: The value of the `name` member of this object.
81113 public subscript( _ name: String ) -> JSValue {
82- get { getJSValue ( this: self , name: JSString ( name) ) }
83- set { setJSValue ( this: self , name: JSString ( name) , value: newValue) }
114+ get {
115+ assertOnOwnerThread ( hint: " reading ' \( name) ' property " )
116+ return getJSValue ( this: self , name: JSString ( name) )
117+ }
118+ set {
119+ assertOnOwnerThread ( hint: " writing ' \( name) ' property " )
120+ setJSValue ( this: self , name: JSString ( name) , value: newValue)
121+ }
84122 }
85123
86124 /// Access the `name` member dynamically through JavaScript and Swift runtime bridge library.
87125 /// - Parameter name: The name of this object's member to access.
88126 /// - Returns: The value of the `name` member of this object.
89127 public subscript( _ name: JSString ) -> JSValue {
90- get { getJSValue ( this: self , name: name) }
91- set { setJSValue ( this: self , name: name, value: newValue) }
128+ get {
129+ assertOnOwnerThread ( hint: " reading '<<JSString>>' property " )
130+ return getJSValue ( this: self , name: name)
131+ }
132+ set {
133+ assertOnOwnerThread ( hint: " writing '<<JSString>>' property " )
134+ setJSValue ( this: self , name: name, value: newValue)
135+ }
92136 }
93137
94138 /// Access the `index` member dynamically through JavaScript and Swift runtime bridge library.
95139 /// - Parameter index: The index of this object's member to access.
96140 /// - Returns: The value of the `index` member of this object.
97141 public subscript( _ index: Int ) -> JSValue {
98- get { getJSValue ( this: self , index: Int32 ( index) ) }
99- set { setJSValue ( this: self , index: Int32 ( index) , value: newValue) }
142+ get {
143+ assertOnOwnerThread ( hint: " reading ' \( index) ' property " )
144+ return getJSValue ( this: self , index: Int32 ( index) )
145+ }
146+ set {
147+ assertOnOwnerThread ( hint: " writing ' \( index) ' property " )
148+ setJSValue ( this: self , index: Int32 ( index) , value: newValue)
149+ }
100150 }
101151
102152 /// Access the `symbol` member dynamically through JavaScript and Swift runtime bridge library.
103153 /// - Parameter symbol: The name of this object's member to access.
104154 /// - Returns: The value of the `name` member of this object.
105155 public subscript( _ name: JSSymbol ) -> JSValue {
106- get { getJSValue ( this: self , symbol: name) }
107- set { setJSValue ( this: self , symbol: name, value: newValue) }
156+ get {
157+ assertOnOwnerThread ( hint: " reading '<<JSSymbol>>' property " )
158+ return getJSValue ( this: self , symbol: name)
159+ }
160+ set {
161+ assertOnOwnerThread ( hint: " writing '<<JSSymbol>>' property " )
162+ setJSValue ( this: self , symbol: name, value: newValue)
163+ }
108164 }
109165
110166#if !hasFeature(Embedded)
@@ -134,7 +190,8 @@ public class JSObject: Equatable {
134190 /// - Parameter constructor: The constructor function to check.
135191 /// - Returns: The result of `instanceof` in the JavaScript environment.
136192 public func isInstanceOf( _ constructor: JSFunction ) -> Bool {
137- swjs_instanceof ( id, constructor. id)
193+ assertOnOwnerThread ( hint: " calling 'isInstanceOf' " )
194+ return swjs_instanceof ( id, constructor. id)
138195 }
139196
140197 static let _JS_Predef_Value_Global : JavaScriptObjectRef = 0
@@ -145,21 +202,32 @@ public class JSObject: Equatable {
145202
146203 // `JSObject` storage itself is immutable, and use of `JSObject.global` from other
147204 // 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)
205+ #if _runtime(_multithreaded)
206+ @LazyThreadLocal ( initialize: {
207+ return JSObject ( id: _JS_Predef_Value_Global)
208+ } )
209+ private static var _global : JSObject
151210 #else
152- static let _global = JSObject ( id: _JS_Predef_Value_Global)
211+ #if compiler(>=5.10)
212+ nonisolated ( unsafe)
213+ static let _global = JSObject ( id: _JS_Predef_Value_Global)
214+ #else
215+ static let _global = JSObject ( id: _JS_Predef_Value_Global)
216+ #endif
153217 #endif
154218
155- deinit { swjs_release ( id) }
219+ deinit {
220+ assertOnOwnerThread ( hint: " deinitializing " )
221+ swjs_release ( id)
222+ }
156223
157224 /// Returns a Boolean value indicating whether two values point to same objects.
158225 ///
159226 /// - Parameters:
160227 /// - lhs: A object to compare.
161228 /// - rhs: Another object to compare.
162229 public static func == ( lhs: JSObject , rhs: JSObject ) -> Bool {
230+ assertSameOwnerThread ( lhs: lhs, rhs: rhs, hint: " comparing two JSObjects for equality " )
163231 return lhs. id == rhs. id
164232 }
165233
0 commit comments