@@ -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