From 4c7ea176f58180421ac00a3272d05c2fce6df386 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 27 Mar 2025 09:23:51 +0000 Subject: [PATCH 1/4] Unlock `JSTypedArray` for Embedded Swift --- .../BasicObjects/JSTypedArray.swift | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 2d6fc33b..3104fa1c 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -1,11 +1,11 @@ // // Created by Manuel Burghard. Licensed unter MIT. // -#if !hasFeature(Embedded) import _CJavaScriptKit /// A protocol that allows a Swift numeric type to be mapped to the JavaScript TypedArray that holds integers of its type -public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValue { +public protocol TypedArrayElement { + associatedtype Element: ConvertibleToJSValue, ConstructibleFromJSValue = Self /// The constructor function for the TypedArray class for this particular kind of number static var typedArrayClass: JSFunction { get } } @@ -13,8 +13,9 @@ public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValu /// A wrapper around all [JavaScript `TypedArray` /// classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) /// that exposes their properties in a type-safe way. -public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement { - public class var constructor: JSFunction? { Element.typedArrayClass } +public final class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral where Traits: TypedArrayElement { + public typealias Element = Traits.Element + public class var constructor: JSFunction? { Traits.typedArrayClass } public var jsObject: JSObject public subscript(_ index: Int) -> Element { @@ -176,13 +177,6 @@ extension UInt8: TypedArrayElement { public static var typedArrayClass: JSFunction { JSObject.global.Uint8Array.function! } } -/// A wrapper around [the JavaScript `Uint8ClampedArray` -/// class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array) -/// that exposes its properties in a type-safe and Swifty way. -public class JSUInt8ClampedArray: JSTypedArray { - override public class var constructor: JSFunction? { JSObject.global.Uint8ClampedArray.function! } -} - extension Int16: TypedArrayElement { public static var typedArrayClass: JSFunction { JSObject.global.Int16Array.function! } } @@ -206,4 +200,10 @@ extension Float32: TypedArrayElement { extension Float64: TypedArrayElement { public static var typedArrayClass: JSFunction { JSObject.global.Float64Array.function! } } -#endif + +public enum JSUInt8Clamped: TypedArrayElement { + public typealias Element = UInt8 + public static var typedArrayClass: JSFunction { JSObject.global.Uint8ClampedArray.function! } +} + +public typealias JSUInt8ClampedArray = JSTypedArray From f41f2340279641428ef3a3072867489015926660 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 27 Mar 2025 09:24:30 +0000 Subject: [PATCH 2/4] Add an example of using TypedArray in an embedded app --- .../Embedded/Sources/EmbeddedApp/main.swift | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/Examples/Embedded/Sources/EmbeddedApp/main.swift b/Examples/Embedded/Sources/EmbeddedApp/main.swift index 3f8c18ca..37b2334b 100644 --- a/Examples/Embedded/Sources/EmbeddedApp/main.swift +++ b/Examples/Embedded/Sources/EmbeddedApp/main.swift @@ -11,18 +11,49 @@ var divElement = document.createElement("div") divElement.innerText = .string("Count \(count)") _ = document.body.appendChild(divElement) -var buttonElement = document.createElement("button") -buttonElement.innerText = "Click me" -buttonElement.onclick = JSValue.object( +var clickMeElement = document.createElement("button") +clickMeElement.innerText = "Click me" +clickMeElement.onclick = JSValue.object( JSClosure { _ in count += 1 divElement.innerText = .string("Count \(count)") return .undefined } ) +_ = document.body.appendChild(clickMeElement) -_ = document.body.appendChild(buttonElement) +var encodeResultElement = document.createElement("pre") +var textInputElement = document.createElement("input") +textInputElement.type = "text" +textInputElement.placeholder = "Enter text to encode to UTF-8" +textInputElement.oninput = JSValue.object( + JSClosure { _ in + let textEncoder = JSObject.global.TextEncoder.function!.new() + let encode = textEncoder.encode.function! + let encodedData = JSTypedArray( + unsafelyWrapping: encode(this: textEncoder, textInputElement.value).object! + ) + encodeResultElement.innerText = .string( + encodedData.withUnsafeBytes { bytes in + bytes.map { hex($0) }.joined(separator: " ") + } + ) + return .undefined + } +) +let encoderContainer = document.createElement("div") +_ = encoderContainer.appendChild(textInputElement) +_ = encoderContainer.appendChild(encodeResultElement) +_ = document.body.appendChild(encoderContainer) func print(_ message: String) { _ = JSObject.global.console.log(message) } + +func hex(_ value: UInt8) -> String { + var result = "0x" + let hexChars: [Character] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"] + result.append(hexChars[Int(value / 16)]) + result.append(hexChars[Int(value % 16)]) + return result +} From af86aee0601a84b99fc8d7e88eba4fd27d8d3a8c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 27 Mar 2025 09:41:47 +0000 Subject: [PATCH 3/4] Fix JSTypedArrayTests to follow API change --- Tests/JavaScriptKitTests/JSTypedArrayTests.swift | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Tests/JavaScriptKitTests/JSTypedArrayTests.swift b/Tests/JavaScriptKitTests/JSTypedArrayTests.swift index 0465b1e4..a4649879 100644 --- a/Tests/JavaScriptKitTests/JSTypedArrayTests.swift +++ b/Tests/JavaScriptKitTests/JSTypedArrayTests.swift @@ -17,8 +17,8 @@ final class JSTypedArrayTests: XCTestCase { } func testTypedArray() { - func checkArray(_ array: [T]) where T: TypedArrayElement & Equatable { - XCTAssertEqual(toString(JSTypedArray(array).jsValue.object!), jsStringify(array)) + func checkArray(_ array: [T]) where T: TypedArrayElement & Equatable, T.Element == T { + XCTAssertEqual(toString(JSTypedArray(array).jsValue.object!), jsStringify(array)) checkArrayUnsafeBytes(array) } @@ -30,20 +30,20 @@ final class JSTypedArrayTests: XCTestCase { array.map({ String(describing: $0) }).joined(separator: ",") } - func checkArrayUnsafeBytes(_ array: [T]) where T: TypedArrayElement & Equatable { - let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in + func checkArrayUnsafeBytes(_ array: [T]) where T: TypedArrayElement & Equatable, T.Element == T { + let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in Array(buffer) } XCTAssertEqual(copyOfArray, array) } let numbers = [UInt8](0...255) - let typedArray = JSTypedArray(numbers) + let typedArray = JSTypedArray(numbers) XCTAssertEqual(typedArray[12], 12) XCTAssertEqual(numbers.count, typedArray.lengthInBytes) let numbersSet = Set(0...255) - let typedArrayFromSet = JSTypedArray(numbersSet) + let typedArrayFromSet = JSTypedArray(numbersSet) XCTAssertEqual(typedArrayFromSet.jsObject.length, 256) XCTAssertEqual(typedArrayFromSet.lengthInBytes, 256 * MemoryLayout.size) @@ -63,7 +63,7 @@ final class JSTypedArrayTests: XCTestCase { 0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude, .leastNormalMagnitude, 42, ] - let jsFloat32Array = JSTypedArray(float32Array) + let jsFloat32Array = JSTypedArray(float32Array) for (i, num) in float32Array.enumerated() { XCTAssertEqual(num, jsFloat32Array[i]) } @@ -72,7 +72,7 @@ final class JSTypedArrayTests: XCTestCase { 0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude, .leastNormalMagnitude, 42, ] - let jsFloat64Array = JSTypedArray(float64Array) + let jsFloat64Array = JSTypedArray(float64Array) for (i, num) in float64Array.enumerated() { XCTAssertEqual(num, jsFloat64Array[i]) } From e99b99096ed322b19fefef49ccdbdab795aa1be3 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 27 Mar 2025 09:52:56 +0000 Subject: [PATCH 4/4] Remove possible use of `fatalError` in `JSTypedArray` --- .../BasicObjects/JSTypedArray.swift | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 3104fa1c..47919b17 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -140,33 +140,28 @@ public final class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiter } } -// MARK: - Int and UInt support - -// FIXME: Should be updated to support wasm64 when that becomes available. -func valueForBitWidth(typeName: String, bitWidth: Int, when32: T) -> T { - if bitWidth == 32 { - return when32 - } else if bitWidth == 64 { - fatalError("64-bit \(typeName)s are not yet supported in JSTypedArray") - } else { - fatalError( - "Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)" - ) - } -} - extension Int: TypedArrayElement { - public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue } - private static let _typedArrayClass = LazyThreadLocal(initialize: { - valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: JSObject.global.Int32Array).function! - }) + public static var typedArrayClass: JSFunction { + #if _pointerBitWidth(_32) + return JSObject.global.Int32Array.function! + #elseif _pointerBitWidth(_64) + return JSObject.global.Int64Array.function! + #else + #error("Unsupported pointer width") + #endif + } } extension UInt: TypedArrayElement { - public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue } - private static let _typedArrayClass = LazyThreadLocal(initialize: { - valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObject.global.Uint32Array).function! - }) + public static var typedArrayClass: JSFunction { + #if _pointerBitWidth(_32) + return JSObject.global.Uint32Array.function! + #elseif _pointerBitWidth(_64) + return JSObject.global.Uint64Array.function! + #else + #error("Unsupported pointer width") + #endif + } } extension Int8: TypedArrayElement {