Skip to content

Commit 2aec44b

Browse files
Merge pull request #378 from swiftwasm/yt/add-foundation-compat
Add `JavaScriptFoundationCompat` module to provide utilities to interact Foundation types
2 parents 233d6b2 + da1675f commit 2aec44b

File tree

5 files changed

+134
-1
lines changed

5 files changed

+134
-1
lines changed

Benchmarks/Package.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ let package = Package(
1010
targets: [
1111
.executableTarget(
1212
name: "Benchmarks",
13-
dependencies: ["JavaScriptKit"],
13+
dependencies: [
14+
"JavaScriptKit",
15+
.product(name: "JavaScriptFoundationCompat", package: "JavaScriptKit"),
16+
],
1417
exclude: ["Generated/JavaScript", "bridge-js.d.ts"],
1518
swiftSettings: [
1619
.enableExperimentalFeature("Extern")

Benchmarks/Sources/Benchmarks.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import JavaScriptKit
2+
import JavaScriptFoundationCompat
3+
import Foundation
24

35
class Benchmark {
46
init(_ title: String) {
@@ -75,4 +77,22 @@ class Benchmark {
7577
}
7678
}
7779
}
80+
81+
do {
82+
let conversion = Benchmark("Conversion")
83+
let data = Data(repeating: 0, count: 10_000)
84+
conversion.testSuite("Data to JSTypedArray") {
85+
for _ in 0..<1_000_000 {
86+
_ = data.jsTypedArray
87+
}
88+
}
89+
90+
let uint8Array = data.jsTypedArray
91+
92+
conversion.testSuite("JSTypedArray to Data") {
93+
for _ in 0..<1_000_000 {
94+
_ = Data.construct(from: uint8Array)
95+
}
96+
}
97+
}
7898
}

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ let package = Package(
2121
.library(name: "JavaScriptKit", targets: ["JavaScriptKit"]),
2222
.library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]),
2323
.library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]),
24+
.library(name: "JavaScriptFoundationCompat", targets: ["JavaScriptFoundationCompat"]),
2425
.library(name: "JavaScriptEventLoopTestSupport", targets: ["JavaScriptEventLoopTestSupport"]),
2526
.plugin(name: "PackageToJS", targets: ["PackageToJS"]),
2627
.plugin(name: "BridgeJS", targets: ["BridgeJS"]),
@@ -106,6 +107,18 @@ let package = Package(
106107
"JavaScriptEventLoopTestSupport",
107108
]
108109
),
110+
.target(
111+
name: "JavaScriptFoundationCompat",
112+
dependencies: [
113+
"JavaScriptKit"
114+
]
115+
),
116+
.testTarget(
117+
name: "JavaScriptFoundationCompatTests",
118+
dependencies: [
119+
"JavaScriptFoundationCompat"
120+
]
121+
),
109122
.plugin(
110123
name: "PackageToJS",
111124
capability: .command(
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import Foundation
2+
import JavaScriptKit
3+
4+
/// Data <-> Uint8Array conversion. The conversion is lossless and copies the bytes at most once per conversion
5+
extension Data: ConvertibleToJSValue, ConstructibleFromJSValue {
6+
/// Convert a Data to a JSTypedArray<UInt8>.
7+
///
8+
/// - Returns: A Uint8Array that contains the bytes of the Data.
9+
public var jsTypedArray: JSTypedArray<UInt8> {
10+
self.withUnsafeBytes { buffer in
11+
return JSTypedArray<UInt8>(buffer: buffer.bindMemory(to: UInt8.self))
12+
}
13+
}
14+
15+
/// Convert a Data to a JSValue.
16+
///
17+
/// - Returns: A JSValue that contains the bytes of the Data as a Uint8Array.
18+
public var jsValue: JSValue { jsTypedArray.jsValue }
19+
20+
/// Construct a Data from a JSTypedArray<UInt8>.
21+
public static func construct(from uint8Array: JSTypedArray<UInt8>) -> Data? {
22+
// First, allocate the data storage
23+
var data = Data(count: uint8Array.lengthInBytes)
24+
// Then, copy the byte contents into the Data buffer
25+
data.withUnsafeMutableBytes { destinationBuffer in
26+
uint8Array.copyMemory(to: destinationBuffer.bindMemory(to: UInt8.self))
27+
}
28+
return data
29+
}
30+
31+
/// Construct a Data from a JSValue.
32+
///
33+
/// - Parameter jsValue: The JSValue to construct a Data from.
34+
/// - Returns: A Data, if the JSValue is a Uint8Array.
35+
public static func construct(from jsValue: JSValue) -> Data? {
36+
guard let uint8Array = JSTypedArray<UInt8>(from: jsValue) else {
37+
// If the JSValue is not a Uint8Array, fail.
38+
return nil
39+
}
40+
return construct(from: uint8Array)
41+
}
42+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import XCTest
2+
import Foundation
3+
import JavaScriptFoundationCompat
4+
import JavaScriptKit
5+
6+
final class DataJSValueTests: XCTestCase {
7+
func testDataToJSValue() {
8+
let data = Data([0x00, 0x01, 0x02, 0x03])
9+
let jsValue = data.jsValue
10+
11+
let uint8Array = JSTypedArray<UInt8>(from: jsValue)
12+
XCTAssertEqual(uint8Array?.lengthInBytes, 4)
13+
XCTAssertEqual(uint8Array?[0], 0x00)
14+
XCTAssertEqual(uint8Array?[1], 0x01)
15+
XCTAssertEqual(uint8Array?[2], 0x02)
16+
XCTAssertEqual(uint8Array?[3], 0x03)
17+
}
18+
19+
func testJSValueToData() {
20+
let jsValue = JSTypedArray<UInt8>([0x00, 0x01, 0x02, 0x03]).jsValue
21+
let data = Data.construct(from: jsValue)
22+
XCTAssertEqual(data, Data([0x00, 0x01, 0x02, 0x03]))
23+
}
24+
25+
func testDataToJSValue_withLargeData() {
26+
let data = Data(repeating: 0x00, count: 1024 * 1024)
27+
let jsValue = data.jsValue
28+
let uint8Array = JSTypedArray<UInt8>(from: jsValue)
29+
XCTAssertEqual(uint8Array?.lengthInBytes, 1024 * 1024)
30+
}
31+
32+
func testJSValueToData_withLargeData() {
33+
let jsValue = JSTypedArray<UInt8>(Array(repeating: 0x00, count: 1024 * 1024)).jsValue
34+
let data = Data.construct(from: jsValue)
35+
XCTAssertEqual(data?.count, 1024 * 1024)
36+
}
37+
38+
func testDataToJSValue_withEmptyData() {
39+
let data = Data()
40+
let jsValue = data.jsValue
41+
let uint8Array = JSTypedArray<UInt8>(from: jsValue)
42+
XCTAssertEqual(uint8Array?.lengthInBytes, 0)
43+
}
44+
45+
func testJSValueToData_withEmptyData() {
46+
let jsValue = JSTypedArray<UInt8>([]).jsValue
47+
let data = Data.construct(from: jsValue)
48+
XCTAssertEqual(data, Data())
49+
}
50+
51+
func testJSValueToData_withInvalidJSValue() {
52+
let data = Data.construct(from: JSObject().jsValue)
53+
XCTAssertNil(data)
54+
}
55+
}

0 commit comments

Comments
 (0)