@@ -42,9 +42,39 @@ public struct JSRemote<T>: @unchecked Sendable {
4242
4343 private let storage : Storage
4444
45- fileprivate init ( sourceObject: JSObject , sourceTid: Int32 ) {
45+ fileprivate init ( sourceObject: JSObject ) {
46+ let sourceTid : Int32
47+ #if compiler(>=6.1) && _runtime(_multithreaded)
48+ sourceTid = sourceObject. ownerTid
49+ #else
50+ sourceTid = - 1
51+ #endif
4652 self . storage = Storage ( sourceObject: sourceObject, sourceTid: sourceTid)
4753 }
54+
55+ fileprivate func _withJSObject< R: Sendable , E: Error > (
56+ _ body: @Sendable @escaping ( JSObject) throws( E ) -> R
57+ ) async throws ( E) -> sending R {
58+ #if compiler(>=6.1) && _runtime(_multithreaded)
59+ if storage. sourceTid == swjs_get_worker_thread_id_cached ( ) {
60+ return try body ( storage. sourceObject)
61+ }
62+ let result : Result < R , E > = await withCheckedContinuation { continuation in
63+ let context = _JSRemoteContext (
64+ sourceObject: storage. sourceObject,
65+ body: body,
66+ continuation: continuation
67+ )
68+ swjs_request_remote_jsobject_body (
69+ storage. sourceTid,
70+ Unmanaged . passRetained ( context) . toOpaque ( )
71+ )
72+ }
73+ return try result. get ( )
74+ #else
75+ return try body ( storage. sourceObject)
76+ #endif
77+ }
4878}
4979
5080@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
@@ -62,11 +92,7 @@ extension JSRemote where T == JSObject {
6292 ///
6393 /// - Parameter object: The JavaScript object to reference remotely.
6494 public init ( _ object: JSObject ) {
65- #if compiler(>=6.1) && _runtime(_multithreaded)
66- self . init ( sourceObject: object, sourceTid: object. ownerTid)
67- #else
68- self . init ( sourceObject: object, sourceTid: - 1 )
69- #endif
95+ self . init ( sourceObject: object)
7096 }
7197
7298 /// Performs an operation with the underlying `JSObject` on its owning thread.
@@ -92,28 +118,66 @@ extension JSRemote where T == JSObject {
92118 public func withJSObject< R: Sendable , E: Error > (
93119 _ body: @Sendable @escaping ( JSObject) throws( E ) -> R
94120 ) async throws ( E) -> sending R {
95- #if compiler(>=6.1) && _runtime(_multithreaded)
96- if storage. sourceTid == swjs_get_worker_thread_id_cached ( ) {
97- return try body ( storage. sourceObject)
98- }
99- let result : Result < R , E > = await withCheckedContinuation { continuation in
100- let context = _JSRemoteContext (
101- sourceObject: storage. sourceObject,
102- body: body,
103- continuation: continuation
104- )
105- swjs_request_remote_jsobject_body (
106- storage. sourceTid,
107- Unmanaged . passRetained ( context) . toOpaque ( )
108- )
121+ try await _withJSObject ( body)
122+ }
123+ }
124+
125+ @available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
126+ extension JSRemote where T: _JSBridgedClass {
127+ /// Creates a remote handle for a `@JSClass`-imported object.
128+ ///
129+ /// The object remains owned by its current JavaScript thread. Access it later by calling
130+ /// `withJSObject(_:)`, which executes the closure on the owning thread when necessary.
131+ ///
132+ /// ## Example
133+ ///
134+ /// ```swift
135+ /// @JSClass struct Window {
136+ /// @JSGetter var location: Location
137+ /// }
138+ /// let remoteWindow = JSRemote(Window(unsafelyWrapping: JSObject.global))
139+ /// remoteWindow.withJSObject { window in
140+ /// print(window.location.href.string ?? "")
141+ /// }
142+ /// ```
143+ ///
144+ /// - Parameter object: The JavaScript object to reference remotely.
145+ public init ( _ object: T ) {
146+ self . init ( sourceObject: object. jsObject)
147+ }
148+
149+
150+ /// Performs an operation with the underlying `T` object on its owning thread.
151+ ///
152+ /// If the caller is already running on the thread that owns the object, `body` executes
153+ /// immediately. Otherwise, this method asynchronously requests execution on the owner and
154+ /// resumes when the closure completes.
155+ ///
156+ /// Use this API when the object must stay on its original thread but a result derived from
157+ /// that object needs to be produced in another Swift concurrency context.
158+ ///
159+ /// ## Example
160+ ///
161+ /// ```swift
162+ /// let location = try await remoteWindow.withJSObject { window in
163+ /// window.location.href.string ?? ""
164+ /// }
165+ /// ```
166+ ///
167+ /// - Parameter body: A sendable closure that receives the owned `T` object.
168+ /// - Returns: The value produced by `body`.
169+ /// - Throws: Any error thrown by `body`.
170+ public func withJSObject< R: Sendable , E: Error > (
171+ _ body: @Sendable @escaping ( T) throws( E ) -> R
172+ ) async throws ( E) -> sending R where T: SendableMetatype {
173+ try await _withJSObject { jsObject throws ( E) -> R in
174+ let object = T ( unsafelyWrapping: jsObject)
175+ return try body ( object)
109176 }
110- return try result. get ( )
111- #else
112- return try body ( storage. sourceObject)
113- #endif
114177 }
115178}
116179
180+
117181@available ( macOS 10 . 15 , iOS 13 . 0 , watchOS 6 . 0 , tvOS 13 . 0 , * )
118182private final class _JSRemoteContext : @unchecked Sendable {
119183 let invokeBody : ( ) -> Bool
0 commit comments