Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
target: "wasm32-unknown-wasip1"
- os: ubuntu-24.04
toolchain:
download-url: https://download.swift.org/swift-6.3-branch/ubuntu2404/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-05-a/swift-6.3-DEVELOPMENT-SNAPSHOT-2025-12-05-a-ubuntu24.04.tar.gz
download-url: https://download.swift.org/swift-6.3-branch/ubuntu2404/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-03-05-a/swift-6.3-DEVELOPMENT-SNAPSHOT-2026-03-05-a-ubuntu24.04.tar.gz
wasi-backend: Node
target: "wasm32-unknown-wasip1"
- os: ubuntu-22.04
Expand Down Expand Up @@ -167,14 +167,16 @@ jobs:
- uses: actions/checkout@v6
- uses: ./.github/actions/install-swift
with:
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2026-02-02-a/swift-DEVELOPMENT-SNAPSHOT-2026-02-02-a-ubuntu22.04.tar.gz
download-url: https://download.swift.org/development/ubuntu2204/swift-DEVELOPMENT-SNAPSHOT-2026-03-09-a/swift-DEVELOPMENT-SNAPSHOT-2026-03-09-a-ubuntu22.04.tar.gz
- uses: swiftwasm/setup-swiftwasm@v2
id: setup-wasm32-unknown-wasip1
with: { target: wasm32-unknown-wasip1 }
- uses: swiftwasm/setup-swiftwasm@v2
id: setup-wasm32-unknown-wasip1-threads
with: { target: wasm32-unknown-wasip1-threads }
- run: ./Utilities/build-examples.sh
- run: |
swift --version
./Utilities/build-examples.sh
env:
SWIFT_SDK_ID_wasm32_unknown_wasip1_threads: ${{ steps.setup-wasm32-unknown-wasip1-threads.outputs.swift-sdk-id }}
SWIFT_SDK_ID_wasm32_unknown_wasip1: ${{ steps.setup-wasm32-unknown-wasip1.outputs.swift-sdk-id }}
Expand Down
2 changes: 1 addition & 1 deletion Examples/Embedded/Sources/EmbeddedApp/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import JavaScriptKit
let alert = JSObject.global.alert.object!
let document = JSObject.global.document

print("Hello from WASM, document title: \(document.title.string ?? "")")
print("Hello from Wasm, document title: \(document.title.string ?? "")")

var count = 0

Expand Down
35 changes: 35 additions & 0 deletions Sources/JavaScriptEventLoop/JSSending.swift
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,32 @@ extension JSSending {
/// - Parameter isolation: The actor isolation context for this call, used in Swift concurrency.
/// - Returns: The received object of type `T`.
/// - Throws: `JSSendingError` if the sending operation fails, or `JSException` if a JavaScript error occurs.
#if compiler(>=6.4)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public func receive(
isolation: isolated (any Actor)? = #isolation,
file: StaticString = #file,
line: UInt = #line
) async throws(JSException) -> T {
#if _runtime(_multithreaded)
let idInDestination = try await withCheckedThrowingContinuation { continuation in
let context = _JSSendingContext(continuation: continuation)
let idInSource = self.storage.idInSource
let transferring = self.storage.transferring ? [idInSource] : []
swjs_request_sending_object(
idInSource,
transferring,
Int32(transferring.count),
self.storage.sourceTid,
Unmanaged.passRetained(context).toOpaque()
)
}
return storage.construct(JSObject(id: idInDestination))
#else
return storage.construct(storage.sourceObject)
#endif
}
#else
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public func receive(
isolation: isolated (any Actor)? = #isolation,
Expand All @@ -250,6 +276,7 @@ extension JSSending {
return storage.construct(storage.sourceObject)
#endif
}
#endif

// 6.0 and below can't compile the following without a compiler crash.
#if compiler(>=6.1)
Expand Down Expand Up @@ -341,11 +368,19 @@ extension JSSending {

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
private final class _JSSendingContext: Sendable {
#if compiler(>=6.4)
let continuation: CheckedContinuation<JavaScriptObjectRef, JSException>

init(continuation: CheckedContinuation<JavaScriptObjectRef, JSException>) {
self.continuation = continuation
}
#else
let continuation: CheckedContinuation<JavaScriptObjectRef, Error>

init(continuation: CheckedContinuation<JavaScriptObjectRef, Error>) {
self.continuation = continuation
}
#endif
}

/// Error type representing failures during JavaScript object sending operations.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
// See: https://forums.swift.org/t/pitch-2-custom-main-and-global-executors/78437

#if compiler(>=6.3)
@_spi(ExperimentalCustomExecutors) import _Concurrency
@_spi(ExperimentalCustomExecutors) @_spi(ExperimentalScheduling) import _Concurrency
#else
import _Concurrency
#endif
#endif // #if compiler(>=6.3)
import _CJavaScriptKit

#if compiler(>=6.3)
Expand Down Expand Up @@ -40,27 +40,49 @@ extension JavaScriptEventLoop: SchedulingExecutor {
tolerance: C.Duration?,
clock: C
) {
#if hasFeature(Embedded)
#if compiler(>=6.4)
// In Embedded Swift, ContinuousClock and SuspendingClock are unavailable.
// Hand-off the scheduling work to the Clock implementation for custom clocks.
clock.enqueue(
job,
on: self,
at: clock.now.advanced(by: delay),
tolerance: tolerance
)
#else
fatalError(
"Delayed enqueue requires Swift 6.4+ in Embedded mode"
)
#endif // #if compiler(>=6.4) (Embedded)
#else // #if hasFeature(Embedded)
let duration: Duration
// Handle clocks we know
if let _ = clock as? ContinuousClock {
duration = delay as! ContinuousClock.Duration
} else if let _ = clock as? SuspendingClock {
duration = delay as! SuspendingClock.Duration
} else {
// Hand-off the scheduling work to Clock implementation for unknown clocks
#if compiler(>=6.4)
// Hand-off the scheduling work to Clock implementation for unknown clocks.
// Clock.enqueue is only available in the development branch (6.4+).
clock.enqueue(
job,
on: self,
at: clock.now.advanced(by: delay),
tolerance: tolerance
)
return
#else
fatalError("Unsupported clock type; only ContinuousClock and SuspendingClock are supported")
#endif // #if compiler(>=6.4) (non-Embedded)
}
let milliseconds = Self.delayInMilliseconds(from: duration)
self.enqueue(
UnownedJob(job),
withDelay: milliseconds
)
#endif // #if hasFeature(Embedded)
}

private static func delayInMilliseconds(from swiftDuration: Duration) -> Double {
Expand Down Expand Up @@ -111,4 +133,4 @@ extension JavaScriptEventLoop: ExecutorFactory {
}
}

#endif // compiler(>=6.3)
#endif // #if compiler(>=6.3)
7 changes: 5 additions & 2 deletions Sources/JavaScriptEventLoop/JavaScriptEventLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,16 @@ public final class JavaScriptEventLoop: SerialExecutor, @unchecked Sendable {
private static func installGlobalExecutorIsolated() {
guard !didInstallGlobalExecutor else { return }
didInstallGlobalExecutor = true
#if compiler(>=6.3)
#if compiler(>=6.3) && !hasFeature(Embedded)
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, visionOS 9999, *) {
// For Swift 6.3 and above, we can use the new `ExecutorFactory` API
_Concurrency._createExecutors(factory: JavaScriptEventLoop.self)
}
#else
// For Swift 6.1 and below, we need to install the global executor by hook API
// For Swift 6.1 and below, or Embedded Swift, we need to install
// the global executor by hook API. The ExecutorFactory mechanism
// does not work in Embedded Swift because ExecutorImpl.swift is
// excluded from the embedded Concurrency library.
installByLegacyHook()
#endif
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiter
/// used as the return value for the `withUnsafeBytes(_:)` method. The
/// argument is valid only for the duration of the closure's execution.
/// - Returns: The return value, if any, of the `body` closure parameter.
public func withUnsafeBytes<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R {
public func withUnsafeBytes<R, E: Error>(_ body: (UnsafeBufferPointer<Element>) throws(E) -> R) throws(E) -> R {
let buffer = UnsafeMutableBufferPointer<Element>.allocate(capacity: length)
defer { buffer.deallocate() }
copyMemory(to: buffer)
Expand All @@ -121,7 +121,9 @@ public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiter
/// argument is valid only for the duration of the closure's execution.
/// - Returns: The return value, if any, of the `body`async closure parameter.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public func withUnsafeBytesAsync<R>(_ body: (UnsafeBufferPointer<Element>) async throws -> R) async rethrows -> R {
public func withUnsafeBytesAsync<R, E: Error>(
_ body: (UnsafeBufferPointer<Element>) async throws(E) -> R
) async throws(E) -> R {
let buffer = UnsafeMutableBufferPointer<Element>.allocate(capacity: length)
defer { buffer.deallocate() }
copyMemory(to: buffer)
Expand Down
Loading