diff --git a/Package.swift b/Package.swift index d9f33839e..f602d81bf 100644 --- a/Package.swift +++ b/Package.swift @@ -1,7 +1,40 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.10 import PackageDescription +let embeddedSwiftSettings: [SwiftSetting] = [ + .enableExperimentalFeature("Embedded"), + .interoperabilityMode(.Cxx), + .unsafeFlags([ + "-wmo", "-disable-cmo", + "-Xfrontend", "-gnone", + "-Xfrontend", "-disable-stack-protector", + "-Xfrontend", "-emit-empty-object-file" + ]) +] + +let embeddedCSettings: [CSetting] = [ + .unsafeFlags(["-fdeclspec"]) +] + +let linkerSettings: [LinkerSetting] = [ + .unsafeFlags([ + "-Xclang-linker", "-nostdlib", + "-Xlinker", "--no-entry" + ]) +] + +let libcSettings: [CSetting] = [ + .define("LACKS_TIME_H"), + .define("LACKS_SYS_TYPES_H"), + .define("LACKS_STDLIB_H"), + .define("LACKS_STRING_H"), + .define("LACKS_SYS_MMAN_H"), + .define("LACKS_FCNTL_H"), + .define("NO_MALLOC_STATS", to: "1"), + .define("__wasilibc_unmodified_upstream"), +] + let package = Package( name: "JavaScriptKit", products: [ @@ -9,6 +42,14 @@ let package = Package( .library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]), .library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]), .library(name: "JavaScriptEventLoopTestSupport", targets: ["JavaScriptEventLoopTestSupport"]), + // Embedded + .library(name: "JavaScriptKitEmbedded", targets: ["JavaScriptKitEmbedded"]), + .library(name: "JavaScriptEventLoopEmbedded", targets: ["JavaScriptEventLoopEmbedded"]), + .library(name: "JavaScriptBigIntSupportEmbedded", targets: ["JavaScriptBigIntSupportEmbedded"]), + .library(name: "JavaScriptEventLoopTestSupportEmbedded", targets: ["JavaScriptEventLoopTestSupportEmbedded"]), + ], + dependencies: [ + .package(url: "https://github.com/swifweb/String16", branch: "0.1.0") ], targets: [ .target( @@ -36,11 +77,64 @@ let package = Package( ), .target(name: "_CJavaScriptEventLoopTestSupport"), .testTarget( - name: "JavaScriptEventLoopTestSupportTests", - dependencies: [ - "JavaScriptKit", - "JavaScriptEventLoopTestSupport" - ] + name: "JavaScriptEventLoopTestSupportTests", + dependencies: [ + "JavaScriptKit", + "JavaScriptEventLoopTestSupport" + ] + ), + // Embedded + .target( + name: "JavaScriptKitEmbedded", + dependencies: [ + "_CJavaScriptKitEmbedded", + .product(name: "String16", package: "String16") + ], +// resources: [.copy("Runtime")], // FIXME: doesn't work with embedded because trying to import Foundation + cSettings: embeddedCSettings, + swiftSettings: embeddedSwiftSettings, + linkerSettings: linkerSettings + ), + .target( + name: "_CJavaScriptKitEmbedded", + cSettings: libcSettings + ), + .target( + name: "JavaScriptBigIntSupportEmbedded", + dependencies: ["_CJavaScriptBigIntSupportEmbedded", "JavaScriptKitEmbedded"], + cSettings: embeddedCSettings, + swiftSettings: embeddedSwiftSettings, + linkerSettings: linkerSettings + ), + .target( + name: "_CJavaScriptBigIntSupportEmbedded", + dependencies: ["_CJavaScriptKitEmbedded"], + cSettings: libcSettings + ), + .target( + name: "JavaScriptEventLoopEmbedded", + dependencies: ["JavaScriptKitEmbedded", "_CJavaScriptEventLoopEmbedded"], + cSettings: embeddedCSettings, + swiftSettings: embeddedSwiftSettings, + linkerSettings: linkerSettings + ), + .target( + name: "_CJavaScriptEventLoopEmbedded", + cSettings: libcSettings + ), + .target( + name: "JavaScriptEventLoopTestSupportEmbedded", + dependencies: [ + "_CJavaScriptEventLoopTestSupportEmbedded", + "JavaScriptEventLoopEmbedded", + ], + cSettings: embeddedCSettings, + swiftSettings: embeddedSwiftSettings, + linkerSettings: linkerSettings + ), + .target( + name: "_CJavaScriptEventLoopTestSupportEmbedded", + cSettings: libcSettings ), ] ) diff --git a/Package@swift-5.7.swift b/Package@swift-5.7.swift new file mode 100644 index 000000000..d9f33839e --- /dev/null +++ b/Package@swift-5.7.swift @@ -0,0 +1,46 @@ +// swift-tools-version:5.7 + +import PackageDescription + +let package = Package( + name: "JavaScriptKit", + products: [ + .library(name: "JavaScriptKit", targets: ["JavaScriptKit"]), + .library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]), + .library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]), + .library(name: "JavaScriptEventLoopTestSupport", targets: ["JavaScriptEventLoopTestSupport"]), + ], + targets: [ + .target( + name: "JavaScriptKit", + dependencies: ["_CJavaScriptKit"], + resources: [.copy("Runtime")] + ), + .target(name: "_CJavaScriptKit"), + .target( + name: "JavaScriptBigIntSupport", + dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"] + ), + .target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]), + .target( + name: "JavaScriptEventLoop", + dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"] + ), + .target(name: "_CJavaScriptEventLoop"), + .target( + name: "JavaScriptEventLoopTestSupport", + dependencies: [ + "_CJavaScriptEventLoopTestSupport", + "JavaScriptEventLoop", + ] + ), + .target(name: "_CJavaScriptEventLoopTestSupport"), + .testTarget( + name: "JavaScriptEventLoopTestSupportTests", + dependencies: [ + "JavaScriptKit", + "JavaScriptEventLoopTestSupport" + ] + ), + ] +) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 605ce2d06..d94b5831f 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -21,7 +21,8 @@ export class SwiftRuntime { private options: SwiftRuntimeOptions; private version: number = 708; - private textDecoder = new TextDecoder("utf-8"); + private textDecoderUTF8 = new TextDecoder("utf-8"); + private textDecoderUTF16 = new TextDecoder("utf-16"); private textEncoder = new TextEncoder(); // Only support utf-8 constructor(options?: SwiftRuntimeOptions) { @@ -210,6 +211,13 @@ export class SwiftRuntime { memory.writeUint32(bytes_ptr_result, bytes_ptr); return bytes.length; }, + swjs_encode_utf16_string: (ref: ref, bytes_ptr_result: pointer) => { + const memory = this.memory; + const bytes = memory.getObject(ref); + const bytes_ptr = memory.retain(bytes); + memory.writeUint32(bytes_ptr_result, bytes_ptr); + return bytes.length; + }, swjs_decode_string: ( // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer this.options.sharedMemory == true @@ -218,7 +226,27 @@ export class SwiftRuntime { const bytes = memory .bytes() .slice(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); + const string = this.textDecoderUTF8.decode(bytes); + return memory.retain(string); + }) + : ((bytes_ptr: pointer, length: number) => { + const memory = this.memory; + const bytes = memory + .bytes() + .subarray(bytes_ptr, bytes_ptr + length); + const string = this.textDecoderUTF8.decode(bytes); + return memory.retain(string); + }) + ), + swjs_decode_utf16_string: ( + // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer + this.options.sharedMemory == true + ? ((bytes_ptr: pointer, length: number) => { + const memory = this.memory; + const bytes = memory + .bytes() + .slice(bytes_ptr, bytes_ptr + length); + const string = this.textDecoderUTF16.decode(bytes); return memory.retain(string); }) : ((bytes_ptr: pointer, length: number) => { @@ -226,7 +254,7 @@ export class SwiftRuntime { const bytes = memory .bytes() .subarray(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); + const string = this.textDecoderUTF16.decode(bytes); return memory.retain(string); }) ), diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts index 55f945b64..1d92ea7b4 100644 --- a/Runtime/src/types.ts +++ b/Runtime/src/types.ts @@ -49,7 +49,9 @@ export interface ImportedFunctions { payload2_ptr: pointer ): JavaScriptValueKind; swjs_encode_string(ref: number, bytes_ptr_result: pointer): number; + swjs_encode_utf16_string(ref: number, bytes_ptr_result: pointer): number; swjs_decode_string(bytes_ptr: pointer, length: number): number; + swjs_decode_utf16_string(bytes_ptr: pointer, length: number): number; swjs_load_string(ref: number, buffer: pointer): void; swjs_call_function( ref: number, diff --git a/Sources/JavaScriptBigIntSupportEmbedded b/Sources/JavaScriptBigIntSupportEmbedded new file mode 120000 index 000000000..5c5d6dcd5 --- /dev/null +++ b/Sources/JavaScriptBigIntSupportEmbedded @@ -0,0 +1 @@ +JavaScriptBigIntSupport \ No newline at end of file diff --git a/Sources/JavaScriptEventLoopEmbedded b/Sources/JavaScriptEventLoopEmbedded new file mode 120000 index 000000000..9006e107f --- /dev/null +++ b/Sources/JavaScriptEventLoopEmbedded @@ -0,0 +1 @@ +JavaScriptEventLoop \ No newline at end of file diff --git a/Sources/JavaScriptEventLoopTestSupportEmbedded b/Sources/JavaScriptEventLoopTestSupportEmbedded new file mode 120000 index 000000000..78cbd41d3 --- /dev/null +++ b/Sources/JavaScriptEventLoopTestSupportEmbedded @@ -0,0 +1 @@ +JavaScriptEventLoopTestSupport \ No newline at end of file diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 90dba72d8..d4a1645ba 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -5,7 +5,11 @@ public class JSArray: JSBridgedClass { public static let constructor = JSObject.global.Array.function static func isArray(_ object: JSObject) -> Bool { + #if hasFeature(Embedded) + constructor!.isArray!(object.jsValue).boolean! + #else constructor!.isArray!(object).boolean! + #endif } public let jsObject: JSObject @@ -51,9 +55,15 @@ extension JSArray: RandomAccessCollection { return nil } index += 1 + #if hasFeature(Embedded) + guard jsObject.hasOwnProperty!(currentIndex.jsValue).boolean! else { + return next() + } + #else guard jsObject.hasOwnProperty!(currentIndex).boolean! else { return next() } + #endif let value = jsObject[currentIndex] return value } @@ -91,11 +101,18 @@ extension JSArray: RandomAccessCollection { } } +#if hasFeature(Embedded) +private func getObjectValuesLength(_ object: JSObject) -> Int { + let values = object.filter!((JSClosure { _ in true.jsValue }).jsValue).object! + return Int(values.length.number!) +} +#else private let alwaysTrue = JSClosure { _ in .boolean(true) } private func getObjectValuesLength(_ object: JSObject) -> Int { let values = object.filter!(alwaysTrue).object! return Int(values.length.number!) } +#endif public extension JSValue { var array: JSArray? { diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index 767374125..e372dbb40 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -1,3 +1,7 @@ +#if hasFeature(Embedded) +import String16 +#endif + /** A wrapper around the [JavaScript `Date` class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) that exposes its properties in a type-safe way. This doesn't 100% match the JS API, for example @@ -18,7 +22,11 @@ public final class JSDate: JSBridgedClass { */ public init(millisecondsSinceEpoch: Double? = nil) { if let milliseconds = millisecondsSinceEpoch { + #if hasFeature(Embedded) + jsObject = Self.constructor!.new(milliseconds.jsValue) + #else jsObject = Self.constructor!.new(milliseconds) + #endif } else { jsObject = Self.constructor!.new() } @@ -36,7 +44,11 @@ public final class JSDate: JSBridgedClass { seconds: Int = 0, milliseconds: Int = 0 ) { + #if hasFeature(Embedded) + jsObject = Self.constructor!.new(year.jsValue, monthIndex.jsValue, day.jsValue, hours.jsValue, minutes.jsValue, seconds.jsValue, milliseconds.jsValue) + #else jsObject = Self.constructor!.new(year, monthIndex, day, hours, minutes, seconds, milliseconds) + #endif } public init(unsafelyWrapping jsObject: JSObject) { @@ -49,7 +61,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getFullYear!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setFullYear!(newValue.jsValue) + #else _ = jsObject.setFullYear!(newValue) + #endif } } @@ -59,7 +75,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getMonth!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setMonth!(newValue.jsValue) + #else _ = jsObject.setMonth!(newValue) + #endif } } @@ -69,7 +89,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getDate!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setDate!(newValue.jsValue) + #else _ = jsObject.setDate!(newValue) + #endif } } @@ -84,7 +108,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getHours!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setHours!(newValue.jsValue) + #else _ = jsObject.setHours!(newValue) + #endif } } @@ -94,7 +122,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getMinutes!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setMinutes!(newValue.jsValue) + #else _ = jsObject.setMinutes!(newValue) + #endif } } @@ -104,7 +136,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getSeconds!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setSeconds!(newValue.jsValue) + #else _ = jsObject.setSeconds!(newValue) + #endif } } @@ -114,7 +150,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getMilliseconds!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setMilliseconds!(newValue.jsValue) + #else _ = jsObject.setMilliseconds!(newValue) + #endif } } @@ -124,7 +164,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCFullYear!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCFullYear!(newValue.jsValue) + #else _ = jsObject.setUTCFullYear!(newValue) + #endif } } @@ -134,7 +178,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCMonth!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCMonth!(newValue.jsValue) + #else _ = jsObject.setUTCMonth!(newValue) + #endif } } @@ -144,7 +192,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCDate!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCDate!(newValue.jsValue) + #else _ = jsObject.setUTCDate!(newValue) + #endif } } @@ -159,7 +211,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCHours!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCHours!(newValue.jsValue) + #else _ = jsObject.setUTCHours!(newValue) + #endif } } @@ -169,7 +225,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCMinutes!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCMinutes!(newValue.jsValue) + #else _ = jsObject.setUTCMinutes!(newValue) + #endif } } @@ -179,7 +239,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCSeconds!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCSeconds!(newValue.jsValue) + #else _ = jsObject.setUTCSeconds!(newValue) + #endif } } @@ -189,7 +253,11 @@ public final class JSDate: JSBridgedClass { Int(jsObject.getUTCMilliseconds!().number!) } set { + #if hasFeature(Embedded) + _ = jsObject.setUTCMilliseconds!(newValue.jsValue) + #else _ = jsObject.setUTCMilliseconds!(newValue) + #endif } } @@ -200,28 +268,52 @@ public final class JSDate: JSBridgedClass { /// Returns a string conforming to ISO 8601 that contains date and time, e.g. /// `"2020-09-15T08:56:54.811Z"`. + #if hasFeature(Embedded) + public func toISOString() -> String16 { + jsObject.toISOString!().string! + } + #else public func toISOString() -> String { jsObject.toISOString!().string! } + #endif /// Returns a string with date parts in a format defined by user's locale, e.g. `"9/15/2020"`. + #if hasFeature(Embedded) + public func toLocaleDateString() -> String16 { + jsObject.toLocaleDateString!().string! + } + #else public func toLocaleDateString() -> String { jsObject.toLocaleDateString!().string! } + #endif /// Returns a string with time parts in a format defined by user's locale, e.g. `"10:04:14"`. + #if hasFeature(Embedded) + public func toLocaleTimeString() -> String16 { + jsObject.toLocaleTimeString!().string! + } + #else public func toLocaleTimeString() -> String { jsObject.toLocaleTimeString!().string! } + #endif /** Returns a string formatted according to [rfc7231](https://tools.ietf.org/html/rfc7231#section-7.1.1.1) and modified according to [ecma-262](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-date.prototype.toutcstring), e.g. `Tue, 15 Sep 2020 09:04:40 GMT`. */ + #if hasFeature(Embedded) + public func toUTCString() -> String16 { + jsObject.toUTCString!().string! + } + #else public func toUTCString() -> String { jsObject.toUTCString!().string! } + #endif /** Number of milliseconds since midnight 01 January 1970 UTC to the present moment ignoring leap seconds. diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift index e9b006c81..b59c244ee 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSError.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift @@ -1,3 +1,7 @@ +#if hasFeature(Embedded) +import String16 +#endif + /** A wrapper around [the JavaScript `Error` class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that exposes its properties in a type-safe way. @@ -10,28 +14,52 @@ public final class JSError: Error, JSBridgedClass { public let jsObject: JSObject /// Creates a new instance of the JavaScript `Error` class with a given message. + #if hasFeature(Embedded) + public init(message: String16) { + jsObject = Self.constructor!.new(arguments: [JSString(message).jsValue]) + } + #else public init(message: String) { jsObject = Self.constructor!.new([message]) } + #endif public init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } /// The error message of the underlying `Error` object. + #if hasFeature(Embedded) + public var message: String16 { + jsObject.message.string! + } + #else public var message: String { jsObject.message.string! } + #endif /// The name (usually corresponds to the name of the underlying class) of a given error. + #if hasFeature(Embedded) + public var name: String16 { + jsObject.name.string! + } + #else public var name: String { jsObject.name.string! } + #endif /// The JavaScript call stack that led to the creation of this error object. + #if hasFeature(Embedded) + public var stack: String16? { + jsObject.stack.string + } + #else public var stack: String? { jsObject.stack.string } + #endif /// Creates a new `JSValue` from this `JSError` instance. public var jsValue: JSValue { @@ -41,5 +69,9 @@ public final class JSError: Error, JSBridgedClass { extension JSError: CustomStringConvertible { /// The textual representation of this error. + #if hasFeature(Embedded) + public var description: String16 { jsObject.description } + #else public var description: String { jsObject.description } + #endif } diff --git a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift index 4b366d812..2e2767ac3 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSPromise.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSPromise.swift @@ -55,18 +55,43 @@ public final class JSPromise: JSBridgedClass { } return .undefined } + #if hasFeature(Embedded) + self.init(unsafelyWrapping: Self.constructor!.new(closure.jsValue)) + #else self.init(unsafelyWrapping: Self.constructor!.new(closure)) + #endif } + #if hasFeature(Embedded) + public static func resolve(_ value: JSValue) -> JSPromise { + self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!) + } + #else public static func resolve(_ value: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!) } + #endif + #if hasFeature(Embedded) + public static func reject(_ reason: JSValue) -> JSPromise { + self.init(unsafelyWrapping: Self.constructor!.reject!(reason).object!) + } + #else public static func reject(_ reason: ConvertibleToJSValue) -> JSPromise { self.init(unsafelyWrapping: Self.constructor!.reject!(reason).object!) } + #endif /// Schedules the `success` closure to be invoked on successful completion of `self`. + #if hasFeature(Embedded) + @discardableResult + public func then(success: @escaping (JSValue) -> JSValue) -> JSPromise { + let closure = JSOneshotClosure { + success($0[0]).jsValue + } + return JSPromise(unsafelyWrapping: jsObject.then!(closure.jsValue).object!) + } + #else @discardableResult public func then(success: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise { let closure = JSOneshotClosure { @@ -74,7 +99,9 @@ public final class JSPromise: JSBridgedClass { } return JSPromise(unsafelyWrapping: jsObject.then!(closure).object!) } + #endif + #if !hasFeature(Embedded) #if compiler(>=5.5) /// Schedules the `success` closure to be invoked on successful completion of `self`. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @@ -86,12 +113,13 @@ public final class JSPromise: JSBridgedClass { return JSPromise(unsafelyWrapping: jsObject.then!(closure).object!) } #endif + #endif /// Schedules the `success` closure to be invoked on successful completion of `self`. @discardableResult public func then( - success: @escaping (JSValue) -> ConvertibleToJSValue, - failure: @escaping (JSValue) -> ConvertibleToJSValue + success: @escaping (JSValue) -> JSValue, + failure: @escaping (JSValue) -> JSValue ) -> JSPromise { let successClosure = JSOneshotClosure { success($0[0]).jsValue @@ -99,9 +127,14 @@ public final class JSPromise: JSBridgedClass { let failureClosure = JSOneshotClosure { failure($0[0]).jsValue } + #if hasFeature(Embedded) + return JSPromise(unsafelyWrapping: jsObject.then!(successClosure.jsValue, failureClosure.jsValue).object!) + #else return JSPromise(unsafelyWrapping: jsObject.then!(successClosure, failureClosure).object!) + #endif } + #if !hasFeature(Embedded) #if compiler(>=5.5) /// Schedules the `success` closure to be invoked on successful completion of `self`. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @@ -118,16 +151,22 @@ public final class JSPromise: JSBridgedClass { return JSPromise(unsafelyWrapping: jsObject.then!(successClosure, failureClosure).object!) } #endif + #endif /// Schedules the `failure` closure to be invoked on rejected completion of `self`. @discardableResult - public func `catch`(failure: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise { + public func `catch`(failure: @escaping (JSValue) -> JSValue) -> JSPromise { let closure = JSOneshotClosure { failure($0[0]).jsValue } + #if hasFeature(Embedded) + return .init(unsafelyWrapping: jsObject.catch!(closure.jsValue).object!) + #else return .init(unsafelyWrapping: jsObject.catch!(closure).object!) + #endif } + #if !hasFeature(Embedded) #if compiler(>=5.5) /// Schedules the `failure` closure to be invoked on rejected completion of `self`. @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @@ -139,6 +178,7 @@ public final class JSPromise: JSBridgedClass { return .init(unsafelyWrapping: jsObject.catch!(closure).object!) } #endif + #endif /// Schedules the `failure` closure to be invoked on either successful or rejected /// completion of `self`. @@ -148,6 +188,10 @@ public final class JSPromise: JSBridgedClass { successOrFailure() return .undefined } + #if hasFeature(Embedded) + return .init(unsafelyWrapping: jsObject.finally!(closure.jsValue).object!) + #else return .init(unsafelyWrapping: jsObject.finally!(closure).object!) + #endif } } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift index 228b7e83d..f9fe79806 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift @@ -14,7 +14,12 @@ public final class JSTimer { /// Indicates whether this timer instance calls its callback repeatedly at a given delay. public let isRepeating: Bool + #if hasFeature(Embedded) + private let closure: JSClosure? + private let oneShotClosure: JSOneshotClosure? + #else private let closure: JSClosureProtocol + #endif /** Node.js and browser APIs are slightly different. `setTimeout`/`setInterval` return an object in Node.js, while browsers return a number. Fortunately, clearTimeout and clearInterval take @@ -35,22 +40,33 @@ public final class JSTimer { */ public init(millisecondsDelay: Double, isRepeating: Bool = false, callback: @escaping () -> ()) { if isRepeating { - closure = JSClosure { _ in + let closure: JSClosure = JSClosure { _ in callback() return .undefined } + #if hasFeature(Embedded) + self.closure = closure + self.oneShotClosure = nil + self.value = JSObject.global.setInterval.function!(closure.jsValue, millisecondsDelay.jsValue) + #else + self.closure = closure + self.value = global.setInterval.function!(closure, millisecondsDelay) + #endif } else { - closure = JSOneshotClosure { _ in + let oneShotClosure: JSOneshotClosure = JSOneshotClosure { _ in callback() return .undefined } + #if hasFeature(Embedded) + self.closure = nil + self.oneShotClosure = oneShotClosure + self.value = JSObject.global.setTimeout.function!(oneShotClosure.jsValue, millisecondsDelay.jsValue) + #else + self.closure = oneShotClosure + self.value = global.setTimeout.function!(oneShotClosure, millisecondsDelay) + #endif } self.isRepeating = isRepeating - if isRepeating { - value = global.setInterval.function!(closure, millisecondsDelay) - } else { - value = global.setTimeout.function!(closure, millisecondsDelay) - } } /** Makes a corresponding `clearTimeout` or `clearInterval` call, depending on whether this timer @@ -60,9 +76,16 @@ public final class JSTimer { deinit { if isRepeating { global.clearInterval.function!(value) + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS + closure?.release() + #endif } else { global.clearTimeout.function!(value) + #if hasFeature(Embedded) + oneShotClosure?.release() + #else + closure.release() + #endif } - closure.release() } } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 963419c99..47a22db95 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -1,5 +1,6 @@ // // Created by Manuel Burghard. Licensed unter MIT. +// Improved for Embedded Swift by Mikhail Isaev. Licensed under MIT. // import _CJavaScriptKit @@ -10,14 +11,20 @@ public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValu static var typedArrayClass: JSFunction { get } } -/// 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 var jsObject: JSObject +public protocol JSTypedArrayable: AnyObject { + associatedtype Element: TypedArrayElement + + static var constructor: JSFunction? { get } + var jsObject: JSObject { get } + + subscript (_ index: Int) -> Element { get set } - public subscript(_ index: Int) -> Element { + init (length: Int) + init(unsafelyWrapping jsObject: JSObject) +} + +extension JSTypedArrayable { + public subscript (_ index: Int) -> Element { get { return Element.construct(from: jsObject[index])! } @@ -26,26 +33,10 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh } } - /// Initialize a new instance of TypedArray in JavaScript environment with given length. - /// All the elements will be initialized to zero. - /// - /// - Parameter length: The number of elements that will be allocated. - public init(length: Int) { - jsObject = Self.constructor!.new(length) - } - - public required init(unsafelyWrapping jsObject: JSObject) { - self.jsObject = jsObject - } - - public required convenience init(arrayLiteral elements: Element...) { - self.init(elements) - } - /// Initialize a new instance of TypedArray in JavaScript environment with given elements. /// /// - Parameter array: The array that will be copied to create a new instance of TypedArray - public convenience init(_ array: [Element]) { + public init(_ array: [Element]) { let jsArrayRef = array.withUnsafeBufferPointer { ptr in _create_typed_array(Self.constructor!.id, ptr.baseAddress!, Int32(array.count)) } @@ -53,7 +44,7 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh } /// Convenience initializer for `Sequence`. - public convenience init(_ sequence: S) where S.Element == Element { + public init(_ sequence: S) where S.Element == Element { self.init(Array(sequence)) } @@ -63,7 +54,31 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh public var lengthInBytes: Int { Int(jsObject["byteLength"].number!) } +} + +/// 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 final class JSTypedArray: JSBridgedClass, JSTypedArrayable, ExpressibleByArrayLiteral where Element: TypedArrayElement { + public class var constructor: JSFunction? { Element.typedArrayClass } + public var jsObject: JSObject + + /// Initialize a new instance of TypedArray in JavaScript environment with given length. + /// All the elements will be initialized to zero. + /// + /// - Parameter length: The number of elements that will be allocated. + public init (length: Int) { + jsObject = Self.constructor!.new(length.jsValue) + } + public required init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + public required convenience init(arrayLiteral elements: Element...) { + self.init(elements) + } + + #if !hasFeature(Embedded) /// Calls the given closure with a pointer to a copy of the underlying bytes of the /// array's storage. /// @@ -92,7 +107,9 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh let result = try body(bufferPtr) return result } + #endif + #if !hasFeature(Embedded) #if compiler(>=5.5) /// Calls the given async closure with a pointer to a copy of the underlying bytes of the /// array's storage. @@ -124,11 +141,23 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh return result } #endif + #endif } // MARK: - Int and UInt support // FIXME: Should be updated to support wasm64 when that becomes available. +#if hasFeature(Embedded) +func valueForBitWidth(typeName: StaticString, bitWidth: Int, when32: T) -> T { + if bitWidth == 32 { + return when32 + } else if bitWidth == 64 { + fatalError() + } else { + fatalError() + } +} +#else func valueForBitWidth(typeName: String, bitWidth: Int, when32: T) -> T { if bitWidth == 32 { return when32 @@ -138,6 +167,7 @@ func valueForBitWidth(typeName: String, bitWidth: Int, when32: T) -> T { fatalError("Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)") } } +#endif extension Int: TypedArrayElement { public static var typedArrayClass: JSFunction = @@ -160,8 +190,30 @@ extension UInt8: TypedArrayElement { /// 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! } +public final class JSUInt8ClampedArray: JSBridgedClass, JSTypedArrayable, ExpressibleByArrayLiteral { + public typealias Element = UInt8 + public class var constructor: JSFunction? { JSObject.global.Uint8ClampedArray.function! } + public var jsObject: JSObject + + /// Initialize a new instance of TypedArray in JavaScript environment with given length. + /// All the elements will be initialized to zero. + /// + /// - Parameter length: The number of elements that will be allocated. + public init (length: Int) { + #if hasFeature(Embedded) + jsObject = Self.constructor!.new(length.jsValue) + #else + jsObject = Self.constructor!.new(length) + #endif + } + + public required init(unsafelyWrapping jsObject: JSObject) { + self.jsObject = jsObject + } + + public required convenience init(arrayLiteral elements: Element...) { + self.init(elements) + } } extension Int16: TypedArrayElement { diff --git a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift index 1f43658f0..22a9b9285 100644 --- a/Sources/JavaScriptKit/ConstructibleFromJSValue.swift +++ b/Sources/JavaScriptKit/ConstructibleFromJSValue.swift @@ -1,3 +1,7 @@ +#if hasFeature(Embedded) +import String16 +#endif + /// Types conforming to this protocol can be constructed from `JSValue`. public protocol ConstructibleFromJSValue { /// Construct an instance of `Self`, if possible, from the given `JSValue`. @@ -14,11 +18,19 @@ extension Bool: ConstructibleFromJSValue { } } +#if hasFeature(Embedded) +extension String16: ConstructibleFromJSValue { + public static func construct(from value: JSValue) -> String16? { + value.string + } +} +#else extension String: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> String? { value.string } } +#endif extension JSString: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> JSString? { @@ -35,16 +47,21 @@ extension Double: ConstructibleFromJSValue {} extension Float: ConstructibleFromJSValue {} extension SignedInteger where Self: ConstructibleFromJSValue { + #if !hasFeature(Embedded) public init(_ bigInt: JSBigIntExtended) { self.init(bigInt.int64Value) } + #endif + public static func construct(from value: JSValue) -> Self? { if let number = value.number { return Self(number) } + #if !hasFeature(Embedded) if let bigInt = value.bigInt as? JSBigIntExtended { return Self(bigInt) } + #endif return nil } } @@ -55,16 +72,20 @@ extension Int32: ConstructibleFromJSValue {} extension Int64: ConstructibleFromJSValue {} extension UnsignedInteger where Self: ConstructibleFromJSValue { + #if !hasFeature(Embedded) public init(_ bigInt: JSBigIntExtended) { self.init(bigInt.uInt64Value) } + #endif public static func construct(from value: JSValue) -> Self? { if let number = value.number { return Self(number) } + #if !hasFeature(Embedded) if let bigInt = value.bigInt as? JSBigIntExtended { return Self(bigInt) } + #endif return nil } } diff --git a/Sources/JavaScriptKit/ConvertibleToJSValue.swift b/Sources/JavaScriptKit/ConvertibleToJSValue.swift index ebf24c74c..5fce0523a 100644 --- a/Sources/JavaScriptKit/ConvertibleToJSValue.swift +++ b/Sources/JavaScriptKit/ConvertibleToJSValue.swift @@ -1,4 +1,7 @@ import _CJavaScriptKit +#if hasFeature(Embedded) +import String16 +#endif /// Objects that can be converted to a JavaScript value, preferably in a lossless manner. public protocol ConvertibleToJSValue { @@ -6,10 +9,12 @@ public protocol ConvertibleToJSValue { var jsValue: JSValue { get } } +#if !hasFeature(Embedded) extension ConvertibleToJSValue { @available(*, deprecated, message: "Use the .jsValue property instead") public func jsValue() -> JSValue { jsValue } } +#endif public typealias JSValueCompatible = ConvertibleToJSValue & ConstructibleFromJSValue @@ -47,9 +52,15 @@ extension Double: ConvertibleToJSValue { public var jsValue: JSValue { .number(self) } } +#if hasFeature(Embedded) +extension String16: ConvertibleToJSValue { + public var jsValue: JSValue { .string(JSString(self)) } +} +#else extension String: ConvertibleToJSValue { public var jsValue: JSValue { .string(JSString(self)) } } +#endif extension UInt8: ConvertibleToJSValue { public var jsValue: JSValue { .number(Double(self)) } @@ -88,6 +99,17 @@ extension JSObject: JSValueCompatible { private let objectConstructor = JSObject.global.Object.function! private let arrayConstructor = JSObject.global.Array.function! +#if hasFeature(Embedded) +extension Dictionary where Value == JSValue, Key == String16 { + public var jsValue: JSValue { + let object = objectConstructor.new() + for (key, value) in self { + object[key] = value.jsValue + } + return .object(object) + } +} +#else extension Dictionary where Value == ConvertibleToJSValue, Key == String { public var jsValue: JSValue { let object = objectConstructor.new() @@ -97,7 +119,19 @@ extension Dictionary where Value == ConvertibleToJSValue, Key == String { return .object(object) } } - +#endif + +#if hasFeature(Embedded) +extension Dictionary: ConvertibleToJSValue where Value: ConvertibleToJSValue, Key == String16 { + public var jsValue: JSValue { + let object = objectConstructor.new() + for (key, value) in self { + object[key] = value.jsValue + } + return .object(object) + } +} +#else extension Dictionary: ConvertibleToJSValue where Value: ConvertibleToJSValue, Key == String { public var jsValue: JSValue { let object = objectConstructor.new() @@ -107,7 +141,28 @@ extension Dictionary: ConvertibleToJSValue where Value: ConvertibleToJSValue, Ke return .object(object) } } - +#endif + +#if hasFeature(Embedded) +extension Dictionary: ConstructibleFromJSValue where Value: ConstructibleFromJSValue, Key == String16 { + public static func construct(from value: JSValue) -> Self? { + guard + let objectRef = value.object, + let keys: [String16] = objectConstructor.keys!(objectRef.jsValue).fromJSValue() + else { return nil } + + var entries = [(String16, Value)]() + entries.reserveCapacity(keys.count) + for key in keys { + guard let value: Value = objectRef[key].fromJSValue() else { + return nil + } + entries.append((key, value)) + } + return Dictionary(uniqueKeysWithValues: entries) + } +} +#else extension Dictionary: ConstructibleFromJSValue where Value: ConstructibleFromJSValue, Key == String { public static func construct(from value: JSValue) -> Self? { guard @@ -126,6 +181,7 @@ extension Dictionary: ConstructibleFromJSValue where Value: ConstructibleFromJSV return Dictionary(uniqueKeysWithValues: entries) } } +#endif extension Optional: ConstructibleFromJSValue where Wrapped: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> Self? { @@ -139,6 +195,16 @@ extension Optional: ConstructibleFromJSValue where Wrapped: ConstructibleFromJSV } } +#if hasFeature(Embedded) +extension Optional: ConvertibleToJSValue where Wrapped == JSValue { + public var jsValue: JSValue { + switch self { + case .none: return .null + case let .some(wrapped): return wrapped + } + } +} +#else extension Optional: ConvertibleToJSValue where Wrapped: ConvertibleToJSValue { public var jsValue: JSValue { switch self { @@ -147,7 +213,19 @@ extension Optional: ConvertibleToJSValue where Wrapped: ConvertibleToJSValue { } } } +#endif +#if hasFeature(Embedded) +extension Array: ConvertibleToJSValue where Element == JSValue { + public var jsValue: JSValue { + let array = arrayConstructor.new(count.jsValue) + for (index, element) in enumerated() { + array[index] = element + } + return .object(array) + } +} +#else extension Array: ConvertibleToJSValue where Element: ConvertibleToJSValue { public var jsValue: JSValue { let array = arrayConstructor.new(count) @@ -167,6 +245,7 @@ extension Array where Element == ConvertibleToJSValue { return .object(array) } } +#endif extension Array: ConstructibleFromJSValue where Element: ConstructibleFromJSValue { public static func construct(from value: JSValue) -> [Element]? { @@ -190,6 +269,28 @@ extension Array: ConstructibleFromJSValue where Element: ConstructibleFromJSValu extension RawJSValue: ConvertibleToJSValue { public var jsValue: JSValue { + #if hasFeature(Embedded) + switch kind { + case 0: + return .boolean(payload1 != 0) + case 1: + return .number(payload2) + case 2: + return .string(JSString(jsRef: payload1)) + case 3: + return .object(JSObject(id: UInt32(payload1))) + case 4: + return .null + case 6: + return .function(JSFunction(id: UInt32(payload1))) + case 7: + return .symbol(JSSymbol(id: UInt32(payload1))) + case 8: + return .bigInt(JSBigInt(id: UInt32(payload1))) + default: + return .undefined + } + #else switch kind { case .boolean: return .boolean(payload1 != 0) @@ -210,41 +311,78 @@ extension RawJSValue: ConvertibleToJSValue { case .bigInt: return .bigInt(JSBigInt(id: UInt32(payload1))) } + #endif } } extension JSValue { func withRawJSValue(_ body: (RawJSValue) -> T) -> T { + #if hasFeature(Embedded) + let kind: UInt + #else let kind: JavaScriptValueKind + #endif let payload1: JavaScriptPayload1 var payload2: JavaScriptPayload2 = 0 switch self { case let .boolean(boolValue): + #if hasFeature(Embedded) + kind = 0 + #else kind = .boolean + #endif payload1 = boolValue ? 1 : 0 case let .number(numberValue): + #if hasFeature(Embedded) + kind = 2 + #else kind = .number + #endif payload1 = 0 payload2 = numberValue case let .string(string): return string.withRawJSValue(body) case let .object(ref): + #if hasFeature(Embedded) + kind = 3 + #else kind = .object + #endif payload1 = JavaScriptPayload1(ref.id) case .null: + #if hasFeature(Embedded) + kind = 4 + #else kind = .null + #endif payload1 = 0 case .undefined: + #if hasFeature(Embedded) + kind = 5 + #else kind = .undefined + #endif payload1 = 0 case let .function(functionRef): + #if hasFeature(Embedded) + kind = 6 + #else kind = .function + #endif payload1 = JavaScriptPayload1(functionRef.id) case let .symbol(symbolRef): + #if hasFeature(Embedded) + kind = 7 + #else kind = .symbol + #endif payload1 = JavaScriptPayload1(symbolRef.id) case let .bigInt(bigIntRef): + #if hasFeature(Embedded) + kind = 8 + #else kind = .bigInt + #endif payload1 = JavaScriptPayload1(bigIntRef.id) } let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2) @@ -252,6 +390,27 @@ extension JSValue { } } +#if hasFeature(Embedded) +extension Array where Element == JSValue { + func withRawJSValues(_ body: ([RawJSValue]) -> T) -> T { + // fast path for empty array + guard self.count != 0 else { return body([]) } + + func _withRawJSValues( + _ values: [JSValue], _ index: Int, + _ results: inout [RawJSValue], _ body: ([RawJSValue]) -> T + ) -> T { + if index == values.count { return body(results) } + return values[index].withRawJSValue { (rawValue) -> T in + results.append(rawValue) + return _withRawJSValues(values, index + 1, &results, body) + } + } + var _results = [RawJSValue]() + return _withRawJSValues(self, 0, &_results, body) + } +} +#else extension Array where Element == ConvertibleToJSValue { func withRawJSValues(_ body: ([RawJSValue]) -> T) -> T { // fast path for empty array @@ -277,3 +436,4 @@ extension Array where Element: ConvertibleToJSValue { [ConvertibleToJSValue].withRawJSValues(self)(body) } } +#endif diff --git a/Sources/JavaScriptKit/Deprecated.swift b/Sources/JavaScriptKit/Deprecated.swift index 0d81888c9..3a5a24642 100644 --- a/Sources/JavaScriptKit/Deprecated.swift +++ b/Sources/JavaScriptKit/Deprecated.swift @@ -1,3 +1,4 @@ +#if !hasFeature(Embedded) @available(*, deprecated, renamed: "JSObject") public typealias JSObjectRef = JSObject @@ -15,3 +16,4 @@ public typealias JSValueConstructible = ConstructibleFromJSValue @available(*, deprecated, renamed: "JSValueCompatible") public typealias JSValueCodable = JSValueCompatible +#endif diff --git a/Sources/JavaScriptKit/Features.swift b/Sources/JavaScriptKit/Features.swift index e479003c5..41814303a 100644 --- a/Sources/JavaScriptKit/Features.swift +++ b/Sources/JavaScriptKit/Features.swift @@ -3,7 +3,7 @@ enum LibraryFeatures { } @_cdecl("_library_features") -func _library_features() -> Int32 { +public func _library_features() -> Int32 { // FIXME: it's public because _cdecl isn't visible outside in Embedded Swift var features: Int32 = 0 #if !JAVASCRIPTKIT_WITHOUT_WEAKREFS features |= LibraryFeatures.weakRefs diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift b/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift index 104d194e3..082973682 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSBigInt.swift @@ -30,9 +30,17 @@ public final class JSBigInt: JSObject { public func clamped(bitSize: Int, signed: Bool) -> JSBigInt { if signed { + #if hasFeature(Embedded) + return constructor.asIntN!(bitSize.jsValue, self.jsValue).bigInt! + #else return constructor.asIntN!(bitSize, self).bigInt! + #endif } else { + #if hasFeature(Embedded) + return constructor.asUintN!(bitSize.jsValue, self.jsValue).bigInt! + #else return constructor.asUintN!(bitSize, self).bigInt! + #endif } } } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift index 441dd2a6c..1cc24d2e8 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift @@ -1,4 +1,7 @@ import _CJavaScriptKit +#if hasFeature(Embedded) +import String16 +#endif /// `JSClosureProtocol` wraps Swift closure objects for use in JavaScript. Conforming types /// are responsible for managing the lifetime of the closure they wrap, but can delegate that @@ -15,6 +18,24 @@ public protocol JSClosureProtocol: JSValueCompatible { public class JSOneshotClosure: JSObject, JSClosureProtocol { private var hostFuncRef: JavaScriptHostFuncRef = 0 + #if hasFeature(Embedded) + public init(_ body: @escaping ([JSValue]) -> JSValue, file: StaticString = #fileID, line: UInt32 = #line) { + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. + super.init(id: 0) + + // 2. Create a new JavaScript function which calls the given Swift function. + hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) + id = withExtendedLifetime(JSString(String16(stringLiteral: file))) { file in + _create_function(hostFuncRef, line, file.asInternalJSRef()) + } + + // 3. Retain the given body in static storage by `funcRef`. + JSClosure.sharedClosures[hostFuncRef] = (self, { + defer { self.release() } + return body($0) + }) + } + #else public init(_ body: @escaping ([JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) { // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. super.init(id: 0) @@ -31,13 +52,16 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol { return body($0) }) } + #endif + #if !hasFeature(Embedded) #if compiler(>=5.5) @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public static func async(_ body: @escaping ([JSValue]) async throws -> JSValue) -> JSOneshotClosure { JSOneshotClosure(makeAsyncClosure(body)) } #endif + #endif /// Release this function resource. /// After calling `release`, calling this function from JavaScript will fail. @@ -71,7 +95,8 @@ public class JSClosure: JSFunction, JSClosureProtocol { #if JAVASCRIPTKIT_WITHOUT_WEAKREFS private var isReleased: Bool = false #endif - + + #if !hasFeature(Embedded) @available(*, deprecated, message: "This initializer will be removed in the next minor version update. Please use `init(_ body: @escaping ([JSValue]) -> JSValue)` and add `return .undefined` to the end of your closure") @_disfavoredOverload public convenience init(_ body: @escaping ([JSValue]) -> ()) { @@ -80,37 +105,57 @@ public class JSClosure: JSFunction, JSClosureProtocol { return .undefined }) } + #endif - public init(_ body: @escaping ([JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) { + #if hasFeature(Embedded) + public init(_ body: @escaping ([JSValue]) -> JSValue, file: StaticString = #fileID, line: UInt32 = #line) { // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. super.init(id: 0) // 2. Create a new JavaScript function which calls the given Swift function. hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) - id = withExtendedLifetime(JSString(file)) { file in + id = withExtendedLifetime(JSString(String16(stringLiteral: file))) { file in _create_function(hostFuncRef, line, file.asInternalJSRef()) } // 3. Retain the given body in static storage by `funcRef`. Self.sharedClosures[hostFuncRef] = (self, body) } + #else + public init(_ body: @escaping ([JSValue]) -> JSValue, file: String = #fileID, line: UInt32 = #line) { + // 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`. + super.init(id: 0) - #if compiler(>=5.5) - @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) - public static func async(_ body: @escaping ([JSValue]) async throws -> JSValue) -> JSClosure { - JSClosure(makeAsyncClosure(body)) - } - #endif - - #if JAVASCRIPTKIT_WITHOUT_WEAKREFS - deinit { - guard isReleased else { - fatalError("release() must be called on JSClosure objects manually before they are deallocated") + // 2. Create a new JavaScript function which calls the given Swift function. + hostFuncRef = JavaScriptHostFuncRef(bitPattern: ObjectIdentifier(self)) + id = withExtendedLifetime(JSString(file)) { file in + _create_function(hostFuncRef, line, file.asInternalJSRef()) } + + // 3. Retain the given body in static storage by `funcRef`. + Self.sharedClosures[hostFuncRef] = (self, body) } #endif + +// #if compiler(>=5.5)//FIXME +// @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) +// public static func async(_ body: @escaping ([JSValue]) async throws(JSValue) -> JSValue) -> JSClosure { +// JSClosure(makeAsyncClosure(body)) +// } +// #endif + + #if JAVASCRIPTKIT_WITHOUT_WEAKREFS + deinit { + guard isReleased else { + // FIXME + fatalError()//("release() must be called on JSClosure objects manually before they are deallocated") + return + } + } + #endif } +#if !hasFeature(Embedded) #if compiler(>=5.5) @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) private func makeAsyncClosure(_ body: @escaping ([JSValue]) async throws -> JSValue) -> (([JSValue]) -> JSValue) { @@ -132,6 +177,7 @@ private func makeAsyncClosure(_ body: @escaping ([JSValue]) async throws -> JSVa } } #endif +#endif // MARK: - `JSClosure` mechanism note // @@ -168,8 +214,14 @@ private func makeAsyncClosure(_ body: @escaping ([JSValue]) async throws -> JSVa // └─────────────────────┴──────────────────────────┘ /// Returns true if the host function has been already released, otherwise false. +#if hasFeature(Embedded) +public typealias JSHFR = JavaScriptHostFuncRef +public typealias RJSV = RawJSValue +public typealias JSOR = JavaScriptObjectRef +#endif + @_cdecl("_call_host_function_impl") -func _call_host_function_impl( +public func _call_host_function_impl( // FIXME: it's public because _cdecl isn't visible outside in Embedded Swift _ hostFuncRef: JavaScriptHostFuncRef, _ argv: UnsafePointer, _ argc: Int32, _ callbackFuncRef: JavaScriptObjectRef @@ -177,7 +229,9 @@ func _call_host_function_impl( guard let (_, hostFunc) = JSClosure.sharedClosures[hostFuncRef] else { return true } - let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map(\.jsValue) + let arguments = UnsafeBufferPointer(start: argv, count: Int(argc)).map { + $0.jsValue + } let result = hostFunc(arguments) let callbackFuncRef = JSFunction(id: callbackFuncRef) _ = callbackFuncRef(result) @@ -200,7 +254,7 @@ extension JSClosure { } @_cdecl("_free_host_function_impl") -func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} +public func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) {} // FIXME: it's public because _cdecl isn't visible outside in Embedded Swift #else @@ -212,7 +266,8 @@ extension JSClosure { } @_cdecl("_free_host_function_impl") -func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { +public func _free_host_function_impl(_ hostFuncRef: JavaScriptHostFuncRef) { // FIXME: it's public because _cdecl isn't visible outside in Embedded Swift JSClosure.sharedClosures[hostFuncRef] = nil } + #endif diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index 1de95fd36..0fb9f25fa 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -16,31 +16,59 @@ public class JSFunction: JSObject { /// - this: The value to be passed as the `this` parameter to this function. /// - arguments: Arguments to be passed to this function. /// - Returns: The result of this call. + #if hasFeature(Embedded) + @discardableResult + public func callAsFunction(this: JSObject, arguments: [JSValue]) -> JSValue { + invokeNonThrowingJSFunction(arguments: arguments, this: this).jsValue + } + #else @discardableResult public func callAsFunction(this: JSObject, arguments: [ConvertibleToJSValue]) -> JSValue { invokeNonThrowingJSFunction(arguments: arguments, this: this).jsValue } + #endif /// Call this function with given `arguments`. /// - Parameters: /// - arguments: Arguments to be passed to this function. /// - Returns: The result of this call. + #if hasFeature(Embedded) + @discardableResult + public func callAsFunction(arguments: [JSValue]) -> JSValue { + invokeNonThrowingJSFunction(arguments: arguments).jsValue + } + #else @discardableResult public func callAsFunction(arguments: [ConvertibleToJSValue]) -> JSValue { invokeNonThrowingJSFunction(arguments: arguments).jsValue } + #endif /// A variadic arguments version of `callAsFunction`. + #if hasFeature(Embedded) + @discardableResult + public func callAsFunction(this: JSObject, _ arguments: JSValue...) -> JSValue { + self(this: this, arguments: arguments) + } + #else @discardableResult public func callAsFunction(this: JSObject, _ arguments: ConvertibleToJSValue...) -> JSValue { self(this: this, arguments: arguments) } + #endif /// A variadic arguments version of `callAsFunction`. + #if hasFeature(Embedded) + @discardableResult + public func callAsFunction(_ arguments: JSValue...) -> JSValue { + self(arguments: arguments) + } + #else @discardableResult public func callAsFunction(_ arguments: ConvertibleToJSValue...) -> JSValue { self(arguments: arguments) } + #endif /// Instantiate an object from this function as a constructor. /// @@ -52,6 +80,15 @@ public class JSFunction: JSObject { /// /// - Parameter arguments: Arguments to be passed to this constructor function. /// - Returns: A new instance of this constructor. + #if hasFeature(Embedded) + public func new(arguments: [JSValue]) -> JSObject { + arguments.withRawJSValues { rawValues in + rawValues.withUnsafeBufferPointer { bufferPointer in + JSObject(id: _call_new(self.id, bufferPointer.baseAddress!, Int32(bufferPointer.count))) + } + } + } + #else public func new(arguments: [ConvertibleToJSValue]) -> JSObject { arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer in @@ -59,6 +96,7 @@ public class JSFunction: JSObject { } } } + #endif /// A modifier to call this function as a throwing function /// @@ -80,19 +118,40 @@ public class JSFunction: JSObject { } /// A variadic arguments version of `new`. - public func new(_ arguments: ConvertibleToJSValue...) -> JSObject { + #if hasFeature(Embedded) + public func new(_ arguments: JSValue...) -> JSObject { new(arguments: arguments) } - - @available(*, unavailable, message: "Please use JSClosure instead") - public static func from(_: @escaping ([JSValue]) -> JSValue) -> JSFunction { - fatalError("unavailable") + #else + public func new(_ arguments: ConvertibleToJSValue...) -> JSObject { + new(arguments: arguments) } + #endif override public var jsValue: JSValue { .function(self) } + #if hasFeature(Embedded) + final func invokeNonThrowingJSFunction(arguments: [JSValue]) -> RawJSValue { + let id = self.id + return arguments.withRawJSValues { rawValues in + rawValues.withUnsafeBufferPointer { bufferPointer in + let argv = bufferPointer.baseAddress + let argc = bufferPointer.count + var payload1 = JavaScriptPayload1() + var payload2 = JavaScriptPayload2() + let resultBitPattern = _call_function_no_catch( + id, argv, Int32(argc), + &payload1, &payload2 + ) + let kindAndFlags = JSValueKindAndFlags.decode(resultBitPattern) + assert(!kindAndFlags.isException) + return RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2) + } + } + } + #else final func invokeNonThrowingJSFunction(arguments: [ConvertibleToJSValue]) -> RawJSValue { let id = self.id return arguments.withRawJSValues { rawValues in @@ -112,7 +171,28 @@ public class JSFunction: JSObject { } } } + #endif + #if hasFeature(Embedded) + final func invokeNonThrowingJSFunction(arguments: [JSValue], this: JSObject) -> RawJSValue { + let id = self.id + return arguments.withRawJSValues { rawValues in + rawValues.withUnsafeBufferPointer { bufferPointer in + let argv = bufferPointer.baseAddress + let argc = bufferPointer.count + var payload1 = JavaScriptPayload1() + var payload2 = JavaScriptPayload2() + let resultBitPattern = _call_function_with_this_no_catch(this.id, + id, argv, Int32(argc), + &payload1, &payload2 + ) + let kindAndFlags = JSValueKindAndFlags.decode(resultBitPattern) + assert(!kindAndFlags.isException) + return RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2) + } + } + } + #else final func invokeNonThrowingJSFunction(arguments: [ConvertibleToJSValue], this: JSObject) -> RawJSValue { let id = self.id return arguments.withRawJSValues { rawValues in @@ -132,4 +212,21 @@ public class JSFunction: JSObject { } } } + #endif +} + +#if hasFeature(Embedded) +struct JSValueKindAndFlags { + let kind: UInt + let isException: Bool + + static func decode(_ resultBitPattern: UInt32) -> JSValueKindAndFlags { + .init( + // Extract the kind (first 4 bits) + kind: UInt(UInt8(resultBitPattern & 0b00000000000000000000000000001111)), + // Extract the isException (5th bit) + isException: (resultBitPattern & 0b00000000000000000000000000010000) != 0 + ) + } } +#endif diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 04e7f3d59..a4c0d02b6 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -1,4 +1,7 @@ import _CJavaScriptKit +#if hasFeature(Embedded) +import String16 +#endif /// `JSObject` represents an object in JavaScript and supports dynamic member lookup. /// Any member access like `object.foo` will dynamically request the JavaScript and Swift @@ -33,6 +36,15 @@ public class JSObject: Equatable { /// /// - Parameter name: The name of this object's member to access. /// - Returns: The `name` member method binding this object as `this` context. + #if hasFeature(Embedded) + @_disfavoredOverload + public subscript(_ name: String16) -> ((JSValue...) -> JSValue)? { + guard let function = self[name].function else { return nil } + return { (arguments: JSValue...) in + function(this: self, arguments: arguments) + } + } + #else @_disfavoredOverload public subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)? { guard let function = self[name].function else { return nil } @@ -40,6 +52,14 @@ public class JSObject: Equatable { function(this: self, arguments: arguments) } } + #endif + + #if hasFeature(Embedded) + @_disfavoredOverload + public subscript(_ name: StaticString) -> ((JSValue...) -> JSValue)? { + self[String16(stringLiteral: name)] + } + #endif /// Returns the `name` member method binding this object as `this` context. /// @@ -51,6 +71,15 @@ public class JSObject: Equatable { /// /// - Parameter name: The name of this object's member to access. /// - Returns: The `name` member method binding this object as `this` context. + #if hasFeature(Embedded) + @_disfavoredOverload + public subscript(_ name: JSString) -> ((JSValue...) -> JSValue)? { + guard let function = self[name].function else { return nil } + return { (arguments: JSValue...) in + function(this: self, arguments: arguments) + } + } + #else @_disfavoredOverload public subscript(_ name: JSString) -> ((ConvertibleToJSValue...) -> JSValue)? { guard let function = self[name].function else { return nil } @@ -58,28 +87,54 @@ public class JSObject: Equatable { function(this: self, arguments: arguments) } } + #endif + #if hasFeature(Embedded) + /// A convenience method of `subscript(_ name: String16) -> ((JSValue...) -> JSValue)?` + /// to access the member through Dynamic Member Lookup. + @_disfavoredOverload + public subscript(dynamicMember name: String16) -> ((JSValue...) -> JSValue)? { + self[name] + } + #else /// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?` /// to access the member through Dynamic Member Lookup. @_disfavoredOverload public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue)? { self[name] } + #endif + #if hasFeature(Embedded) + /// A convenience method of `subscript(_ name: String16) -> JSValue` + /// to access the member through Dynamic Member Lookup. + public subscript(dynamicMember name: String16) -> JSValue { + get { self[name] } + set { self[name] = newValue } + } + #else /// A convenience method of `subscript(_ name: String) -> JSValue` /// to access the member through Dynamic Member Lookup. public subscript(dynamicMember name: String) -> JSValue { get { self[name] } set { self[name] = newValue } } + #endif /// Access the `name` member dynamically through JavaScript and Swift runtime bridge library. /// - Parameter name: The name of this object's member to access. /// - Returns: The value of the `name` member of this object. + #if hasFeature(Embedded) + public subscript(_ name: String16) -> JSValue { + get { getJSValue(this: self, name: JSString(name)) } + set { setJSValue(this: self, name: JSString(name), value: newValue) } + } + #else public subscript(_ name: String) -> JSValue { get { getJSValue(this: self, name: JSString(name)) } set { setJSValue(this: self, name: JSString(name), value: newValue) } } + #endif /// Access the `name` member dynamically through JavaScript and Swift runtime bridge library. /// - Parameter name: The name of this object's member to access. @@ -174,7 +229,11 @@ public class JSObject: Equatable { } extension JSObject: CustomStringConvertible { + #if hasFeature(Embedded) + public var description: String16 { self.toString!().string! } + #else public var description: String { self.toString!().string! } + #endif } extension JSObject: Hashable { @@ -200,6 +259,15 @@ public class JSThrowingObject { /// Returns the `name` member method binding this object as `this` context. /// - Parameter name: The name of this object's member to access. /// - Returns: The `name` member method binding this object as `this` context. + #if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 + @_disfavoredOverload + public subscript(_ name: String16) -> ((JSValue...) throws(JSValue) -> JSValue)? { + guard let function = base[name].function?.throws else { return nil } + return { [base] (arguments: JSValue...) throws(JSValue) in + try function(this: base, arguments: arguments) + } + } + #else @_disfavoredOverload public subscript(_ name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)? { guard let function = base[name].function?.throws else { return nil } @@ -207,11 +275,21 @@ public class JSThrowingObject { try function(this: base, arguments: arguments) } } + #endif + #if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 + /// A convenience method of `subscript(_ name: String16) -> ((JSValue...) throws(JSValue) -> JSValue)?` + /// to access the member through Dynamic Member Lookup. + @_disfavoredOverload + public subscript(dynamicMember name: String16) -> ((JSValue...) throws(JSValue) -> JSValue)? { + self[name] + } + #else /// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)?` /// to access the member through Dynamic Member Lookup. @_disfavoredOverload public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)? { self[name] } + #endif } diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift index 5621793d0..9e18e7935 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSString.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSString.swift @@ -1,5 +1,107 @@ +#if hasFeature(Embedded) +import String16 +import Foundation +#endif import _CJavaScriptKit +#if hasFeature(Embedded) +/// `JSString` represents a string in JavaScript and supports bridging string between JavaScript and Swift. +/// +/// Conversion between `String16` and `JSString` can be: +/// +/// ```swift +/// // Convert `String16` to `JSString` +/// let jsString: JSString = ... +/// let swiftString: String = String(jsString) +/// +/// // Convert `JSString` to `String16` +/// let swiftString: String = ... +/// let jsString: JSString = JSString(swiftString) +/// ``` +/// +public struct JSString: CustomStringConvertible, Equatable { + /// The internal representation of JS compatible string + /// The initializers of this type must initialize `jsRef` or `buffer`. + /// And the uninitialized one will be lazily initialized + class Guts { + var shouldDealocateRef: Bool = false + lazy var jsRef: JavaScriptObjectRef = { + self.shouldDealocateRef = true + let intRepresentation = Int(bitPattern: buffer.pointer) + return _decode_string(Int32(intRepresentation), Int32(buffer.bytesCount)) + }() + + lazy var buffer: String16 = { + var bytesRef: JavaScriptObjectRef = 0 + let bytesLength = Int(_encode_string(jsRef, &bytesRef)) + // +1 for null terminator + let buffer = __malloc(Int(bytesLength + 1)).assumingMemoryBound(to: UInt8.self) + defer { + __free(buffer) + _release(bytesRef) + } + _load_string(bytesRef, buffer) + buffer[bytesLength] = 0 + return .init(bytesCount: bytesLength, pointer: buffer) + }() + + init(from stringValue: String16) { + self.buffer = stringValue + } + + init(from jsRef: JavaScriptObjectRef) { + self.jsRef = jsRef + self.shouldDealocateRef = true + } + + deinit { + guard shouldDealocateRef else { return } + _release(jsRef) + } + } + + let guts: Guts + + var string16: String16 { guts.buffer } + + internal init(jsRef: JavaScriptObjectRef) { + self.guts = Guts(from: jsRef) + } + + /// Instantiate a new `JSString` with given UTF-16 String. + public init(_ stringValue: String16) { + self.guts = Guts(from: stringValue) + } + + /// A Swift representation of this `JSString`. + /// Note that this accessor may copy the JS string value into Swift side memory. + public var description: String16 { guts.buffer } + + /// Returns a Boolean value indicating whether two strings are equal values. + /// + /// - Parameters: + /// - lhs: A string to compare. + /// - rhs: Another string to compare. + public static func == (lhs: JSString, rhs: JSString) -> Bool { + return lhs.guts.buffer == rhs.guts.buffer + } +} + +// MARK: - Internal Helpers +extension JSString { + + func asInternalJSRef() -> JavaScriptObjectRef { + guts.jsRef + } + + func withRawJSValue(_ body: (RawJSValue) -> T) -> T { + let rawValue = RawJSValue( + kind: 1, payload1: guts.jsRef, payload2: 0 + ) + return body(rawValue) + } +} +#else /// `JSString` represents a string in JavaScript and supports bridging string between JavaScript and Swift. /// /// Conversion between `Swift.String` and `JSString` can be: @@ -87,7 +189,6 @@ extension JSString: ExpressibleByStringLiteral { } } - // MARK: - Internal Helpers extension JSString { @@ -102,3 +203,4 @@ extension JSString { return body(rawValue) } } +#endif diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift b/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift index f5d194e25..dc0585f34 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSSymbol.swift @@ -1,4 +1,7 @@ import _CJavaScriptKit +#if hasFeature(Embedded) +import String16 +#endif private let Symbol = JSObject.global.Symbol.function! @@ -6,40 +9,81 @@ private let Symbol = JSObject.global.Symbol.function! /// class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Symbol) /// that exposes its properties in a type-safe and Swifty way. public class JSSymbol: JSObject { + #if hasFeature(Embedded) + public var name: String16? { self["description"].string } + #else public var name: String? { self["description"].string } + #endif + #if hasFeature(Embedded) + public init(_ description: JSString) { + // can’t do `self =` so we have to get the ID manually + let result = Symbol.invokeNonThrowingJSFunction(arguments: [description.jsValue]) + precondition(result.kind == 7) + super.init(id: UInt32(result.payload1)) + } + #else public init(_ description: JSString) { // can’t do `self =` so we have to get the ID manually let result = Symbol.invokeNonThrowingJSFunction(arguments: [description]) precondition(result.kind == .symbol) super.init(id: UInt32(result.payload1)) } + #endif + #if hasFeature(Embedded) + @_disfavoredOverload + public convenience init(_ description: String16) { + self.init(JSString(description)) + } + #else @_disfavoredOverload public convenience init(_ description: String) { self.init(JSString(description)) } + #endif override init(id: JavaScriptObjectRef) { super.init(id: id) } public static func `for`(key: JSString) -> JSSymbol { + #if hasFeature(Embedded) + Symbol.for!(key.jsValue).symbol! + #else Symbol.for!(key).symbol! + #endif } - + #if hasFeature(Embedded) + @_disfavoredOverload + public static func `for`(key: String16) -> JSSymbol { + Symbol.for!(key.jsValue).symbol! + } + #else @_disfavoredOverload public static func `for`(key: String) -> JSSymbol { Symbol.for!(key).symbol! } + #endif public static func key(for symbol: JSSymbol) -> JSString? { + #if hasFeature(Embedded) + Symbol.keyFor!(symbol.jsValue).jsString + #else Symbol.keyFor!(symbol).jsString + #endif } + #if hasFeature(Embedded) + @_disfavoredOverload + public static func key(for symbol: JSSymbol) -> String16? { + Symbol.keyFor!(symbol.jsValue).string + } + #else @_disfavoredOverload public static func key(for symbol: JSSymbol) -> String? { Symbol.keyFor!(symbol).string } + #endif override public var jsValue: JSValue { .symbol(self) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift index 705899000..e7443033b 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSThrowingFunction.swift @@ -14,16 +14,30 @@ public class JSThrowingFunction { /// - this: The value to be passed as the `this` parameter to this function. /// - arguments: Arguments to be passed to this function. /// - Returns: The result of this call. + #if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 + @discardableResult + public func callAsFunction(this: JSObject? = nil, arguments: [JSValue]) throws(JSValue) -> JSValue { + try invokeJSFunction(base, arguments: arguments, this: this) + } + #else @discardableResult public func callAsFunction(this: JSObject? = nil, arguments: [ConvertibleToJSValue]) throws -> JSValue { try invokeJSFunction(base, arguments: arguments, this: this) } + #endif /// A variadic arguments version of `callAsFunction`. + #if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 + @discardableResult + public func callAsFunction(this: JSObject? = nil, _ arguments: JSValue...) throws(JSValue) -> JSValue { + try self(this: this, arguments: arguments) + } + #else @discardableResult public func callAsFunction(this: JSObject? = nil, _ arguments: ConvertibleToJSValue...) throws -> JSValue { try self(this: this, arguments: arguments) } + #endif /// Instantiate an object from this function as a throwing constructor. /// @@ -35,6 +49,30 @@ public class JSThrowingFunction { /// /// - Parameter arguments: Arguments to be passed to this constructor function. /// - Returns: A new instance of this constructor. + #if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 + public func new(arguments: [JSValue]) throws(JSValue) -> JSObject { + try arguments.withRawJSValues { rawValues -> Result in + rawValues.withUnsafeBufferPointer { bufferPointer in + let argv = bufferPointer.baseAddress + let argc = bufferPointer.count + + var exceptionKind: UInt32 = 0 + var exceptionPayload1 = JavaScriptPayload1() + var exceptionPayload2 = JavaScriptPayload2() + let resultObj = _call_throwing_new( + self.base.id, argv, Int32(argc), + &exceptionKind, &exceptionPayload1, &exceptionPayload2 + ) + let kindAndFlags = JSValueKindAndFlags.decode(exceptionKind) + if kindAndFlags.isException { + let exception = RawJSValue(kind: kindAndFlags.kind, payload1: exceptionPayload1, payload2: exceptionPayload2) + return .failure(exception.jsValue) + } + return .success(JSObject(id: resultObj)) + } + }.get() + } + #else public func new(arguments: [ConvertibleToJSValue]) throws -> JSObject { try arguments.withRawJSValues { rawValues -> Result in rawValues.withUnsafeBufferPointer { bufferPointer in @@ -56,13 +94,53 @@ public class JSThrowingFunction { } }.get() } + #endif /// A variadic arguments version of `new`. + #if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 + public func new(_ arguments: JSValue...) throws(JSValue) -> JSObject { + try new(arguments: arguments) + } + #else public func new(_ arguments: ConvertibleToJSValue...) throws -> JSObject { try new(arguments: arguments) } + #endif } +#if hasFeature(Embedded) && swift(>=5.10) // FIXME: change to >=6.0 +private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [JSValue], this: JSObject?) throws(JSValue) -> JSValue { + let id = jsFunc.id + let (result, isException) = arguments.withRawJSValues { rawValues in + rawValues.withUnsafeBufferPointer { bufferPointer -> (JSValue, Bool) in + let argv = bufferPointer.baseAddress + let argc = bufferPointer.count + let kindAndFlags: JSValueKindAndFlags + var payload1 = JavaScriptPayload1() + var payload2 = JavaScriptPayload2() + if let thisId = this?.id { + let resultBitPattern = _call_function_with_this( + thisId, id, argv, Int32(argc), + &payload1, &payload2 + ) + kindAndFlags = JSValueKindAndFlags.decode(resultBitPattern) + } else { + let resultBitPattern = _call_function( + id, argv, Int32(argc), + &payload1, &payload2 + ) + kindAndFlags = JSValueKindAndFlags.decode(resultBitPattern) + } + let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2) + return (result.jsValue, kindAndFlags.isException) + } + } + if isException { + throw result + } + return result +} +#else private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSValue], this: JSObject?) throws -> JSValue { let id = jsFunc.id let (result, isException) = arguments.withRawJSValues { rawValues in @@ -94,3 +172,4 @@ private func invokeJSFunction(_ jsFunc: JSFunction, arguments: [ConvertibleToJSV } return result } +#endif diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index dcc0a3857..78477b755 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -1,3 +1,7 @@ +#if hasFeature(Embedded) +import String16 +#endif + /// Use this protocol when your type has no single JavaScript class. /// For example, a union type of multiple classes or primitive values. public protocol JSBridgedType: JSValueCompatible, CustomStringConvertible { @@ -9,8 +13,11 @@ public extension JSBridgedType { static func construct(from value: JSValue) -> Self? { Self(from: value) } - + #if hasFeature(Embedded) + var description: String16 { jsValue.description } + #else var description: String { jsValue.description } + #endif } /// Conform to this protocol when your Swift class wraps a JavaScript class. diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 58b28e079..2642d5a0f 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -1,4 +1,7 @@ import _CJavaScriptKit +#if hasFeature(Embedded) +import String16 +#endif /// `JSValue` represents a value in JavaScript. @dynamicMemberLookup @@ -28,9 +31,15 @@ public enum JSValue: Equatable { /// Note that this accessor may copy the JS string value into Swift side memory. /// /// To avoid the copying, please consider the `jsString` instead. + #if hasFeature(Embedded) + public var string: String16? { + jsString.map({ $0.string16 }) + } + #else public var string: String? { jsString.map(String.init) } + #endif /// Returns the `JSString` value of this JS value if the type is string. /// If not, returns `nil`. @@ -101,18 +110,33 @@ public enum JSValue: Equatable { } public extension JSValue { + #if hasFeature(Embedded) + /// An unsafe convenience method of `JSObject.subscript(_ name: String16) -> ((JSValue...) -> JSValue)?` + /// - Precondition: `self` must be a JavaScript Object and specified member should be a callable object. + subscript(dynamicMember name: String16) -> ((JSValue...) -> JSValue) { + object![dynamicMember: name]! + } + #else /// An unsafe convenience method of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?` /// - Precondition: `self` must be a JavaScript Object and specified member should be a callable object. subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) { object![dynamicMember: name]! } + #endif /// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue` /// - Precondition: `self` must be a JavaScript Object. + #if hasFeature(Embedded) + subscript(dynamicMember name: String16) -> JSValue { + get { self.object![name] } + set { self.object![name] = newValue } + } + #else subscript(dynamicMember name: String) -> JSValue { get { self.object![name] } set { self.object![name] = newValue } } + #endif /// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue` /// - Precondition: `self` must be a JavaScript Object. @@ -131,10 +155,17 @@ public extension JSValue { } public extension JSValue { + #if hasFeature(Embedded) + static func string(_ value: String16) -> JSValue { + .string(JSString(value)) + } + #else static func string(_ value: String) -> JSValue { .string(JSString(value)) } - + #endif + + #if !hasFeature(Embedded) /// Deprecated: Please create `JSClosure` directly and manage its lifetime manually. /// /// Migrate this usage @@ -168,13 +199,16 @@ public extension JSValue { static func function(_ closure: JSClosure) -> JSValue { .object(closure) } + #endif } +#if !hasFeature(Embedded) extension JSValue: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self = .string(JSString(value)) } } +#endif extension JSValue: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int32) { @@ -200,7 +234,11 @@ public func getJSValue(this: JSObject, name: JSString) -> JSValue { this.id, name.asInternalJSRef(), &rawValue.payload1, &rawValue.payload2 ) + #if hasFeature(Embedded) + rawValue.kind = UInt(rawBitPattern) + #else rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self) + #endif return rawValue.jsValue } @@ -216,7 +254,11 @@ public func getJSValue(this: JSObject, index: Int32) -> JSValue { this.id, index, &rawValue.payload1, &rawValue.payload2 ) + #if hasFeature(Embedded) + rawValue.kind = UInt(rawBitPattern) + #else rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self) + #endif return rawValue.jsValue } @@ -234,7 +276,11 @@ public func getJSValue(this: JSObject, symbol: JSSymbol) -> JSValue { this.id, symbol.id, &rawValue.payload1, &rawValue.payload2 ) + #if hasFeature(Embedded) + rawValue.kind = UInt(rawBitPattern) + #else rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self) + #endif return rawValue.jsValue } @@ -262,9 +308,17 @@ public extension JSValue { } extension JSValue: CustomStringConvertible { + #if hasFeature(Embedded) + public var description: String16 { + // per https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value + // this always returns a string + JSObject.global.String.function!(self).string! + } + #else public var description: String { // per https://tc39.es/ecma262/multipage/text-processing.html#sec-string-constructor-string-value // this always returns a string JSObject.global.String.function!(self).string! } + #endif } diff --git a/Sources/JavaScriptKit/JSValueDecoder.swift b/Sources/JavaScriptKit/JSValueDecoder.swift index b1d59af63..ccd6d4505 100644 --- a/Sources/JavaScriptKit/JSValueDecoder.swift +++ b/Sources/JavaScriptKit/JSValueDecoder.swift @@ -1,3 +1,4 @@ +#if !hasFeature(Embedded) private struct _Decoder: Decoder { fileprivate let node: JSValue @@ -248,3 +249,4 @@ public class JSValueDecoder { return try T(from: decoder) } } +#endif diff --git a/Sources/JavaScriptKit/Runtime/index.js b/Sources/JavaScriptKit/Runtime/index.js index 2aaabce65..64cc9e53c 100644 --- a/Sources/JavaScriptKit/Runtime/index.js +++ b/Sources/JavaScriptKit/Runtime/index.js @@ -198,7 +198,8 @@ class SwiftRuntime { constructor(options) { this.version = 708; - this.textDecoder = new TextDecoder("utf-8"); + this.textDecoderUTF8 = new TextDecoder("utf-8"); + this.textDecoderUTF16 = new TextDecoder("utf-16"); this.textEncoder = new TextEncoder(); // Only support utf-8 /** @deprecated Use `wasmImports` instead */ this.importObjects = () => this.wasmImports; @@ -319,6 +320,13 @@ memory.writeUint32(bytes_ptr_result, bytes_ptr); return bytes.length; }, + swjs_encode_utf16_string: (ref, bytes_ptr_result) => { + const memory = this.memory; + const bytes = memory.getObject(ref); + const bytes_ptr = memory.retain(bytes); + memory.writeUint32(bytes_ptr_result, bytes_ptr); + return bytes.length; + }, swjs_decode_string: ( // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer this.options.sharedMemory == true @@ -327,7 +335,26 @@ const bytes = memory .bytes() .slice(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); + const string = this.textDecoderUTF8.decode(bytes); + return memory.retain(string); + }) + : ((bytes_ptr, length) => { + const memory = this.memory; + const bytes = memory + .bytes() + .subarray(bytes_ptr, bytes_ptr + length); + const string = this.textDecoderUTF8.decode(bytes); + return memory.retain(string); + })), + swjs_decode_utf16_string: ( + // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer + this.options.sharedMemory == true + ? ((bytes_ptr, length) => { + const memory = this.memory; + const bytes = memory + .bytes() + .slice(bytes_ptr, bytes_ptr + length); + const string = this.textDecoderUTF16.decode(bytes); return memory.retain(string); }) : ((bytes_ptr, length) => { @@ -335,7 +362,7 @@ const bytes = memory .bytes() .subarray(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); + const string = this.textDecoderUTF16.decode(bytes); return memory.retain(string); })), swjs_load_string: (ref, buffer) => { diff --git a/Sources/JavaScriptKit/Runtime/index.mjs b/Sources/JavaScriptKit/Runtime/index.mjs index 52de118b5..23fe7d885 100644 --- a/Sources/JavaScriptKit/Runtime/index.mjs +++ b/Sources/JavaScriptKit/Runtime/index.mjs @@ -192,7 +192,8 @@ class Memory { class SwiftRuntime { constructor(options) { this.version = 708; - this.textDecoder = new TextDecoder("utf-8"); + this.textDecoderUTF8 = new TextDecoder("utf-8"); + this.textDecoderUTF16 = new TextDecoder("utf-16"); this.textEncoder = new TextEncoder(); // Only support utf-8 /** @deprecated Use `wasmImports` instead */ this.importObjects = () => this.wasmImports; @@ -313,6 +314,13 @@ class SwiftRuntime { memory.writeUint32(bytes_ptr_result, bytes_ptr); return bytes.length; }, + swjs_encode_utf16_string: (ref, bytes_ptr_result) => { + const memory = this.memory; + const bytes = memory.getObject(ref); + const bytes_ptr = memory.retain(bytes); + memory.writeUint32(bytes_ptr_result, bytes_ptr); + return bytes.length; + }, swjs_decode_string: ( // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer this.options.sharedMemory == true @@ -321,7 +329,26 @@ class SwiftRuntime { const bytes = memory .bytes() .slice(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); + const string = this.textDecoderUTF8.decode(bytes); + return memory.retain(string); + }) + : ((bytes_ptr, length) => { + const memory = this.memory; + const bytes = memory + .bytes() + .subarray(bytes_ptr, bytes_ptr + length); + const string = this.textDecoderUTF8.decode(bytes); + return memory.retain(string); + })), + swjs_decode_utf16_string: ( + // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer + this.options.sharedMemory == true + ? ((bytes_ptr, length) => { + const memory = this.memory; + const bytes = memory + .bytes() + .slice(bytes_ptr, bytes_ptr + length); + const string = this.textDecoderUTF16.decode(bytes); return memory.retain(string); }) : ((bytes_ptr, length) => { @@ -329,7 +356,7 @@ class SwiftRuntime { const bytes = memory .bytes() .subarray(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); + const string = this.textDecoderUTF16.decode(bytes); return memory.retain(string); })), swjs_load_string: (ref, buffer) => { diff --git a/Sources/JavaScriptKitEmbedded b/Sources/JavaScriptKitEmbedded new file mode 120000 index 000000000..c55e08690 --- /dev/null +++ b/Sources/JavaScriptKitEmbedded @@ -0,0 +1 @@ +JavaScriptKit \ No newline at end of file diff --git a/Sources/_CJavaScriptBigIntSupportEmbedded b/Sources/_CJavaScriptBigIntSupportEmbedded new file mode 120000 index 000000000..e7aa41d26 --- /dev/null +++ b/Sources/_CJavaScriptBigIntSupportEmbedded @@ -0,0 +1 @@ +_CJavaScriptBigIntSupport \ No newline at end of file diff --git a/Sources/_CJavaScriptEventLoopEmbedded b/Sources/_CJavaScriptEventLoopEmbedded new file mode 120000 index 000000000..abc572290 --- /dev/null +++ b/Sources/_CJavaScriptEventLoopEmbedded @@ -0,0 +1 @@ +_CJavaScriptEventLoop \ No newline at end of file diff --git a/Sources/_CJavaScriptEventLoopTestSupportEmbedded b/Sources/_CJavaScriptEventLoopTestSupportEmbedded new file mode 120000 index 000000000..11b86651c --- /dev/null +++ b/Sources/_CJavaScriptEventLoopTestSupportEmbedded @@ -0,0 +1 @@ +_CJavaScriptEventLoopTestSupport \ No newline at end of file diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index 0bcc5eaca..2057098ad 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -1,8 +1,18 @@ #include "_CJavaScriptKit.h" +#if __wasm32__ +#if __Embedded +#if __has_include("malloc.h") +#include +#endif +extern void *malloc(size_t size); +extern void free(void *ptr); +extern void *memset (void *, int, size_t); +extern void *memcpy (void *__restrict, const void *__restrict, size_t); +#else #include #include -#if __wasm32__ +#endif bool _call_host_function_impl(const JavaScriptHostFuncRef host_func_ref, const RawJSValue *argv, const int argc, diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index a9d8738af..1eb46134e 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -1,7 +1,12 @@ #ifndef _CJavaScriptKit_h #define _CJavaScriptKit_h +#if __Embedded +#include +#else #include +#endif + #include #include @@ -25,10 +30,17 @@ typedef enum __attribute__((enum_extensibility(closed))) { JavaScriptValueKindBigInt = 8, } JavaScriptValueKind; +#if __Embedded +typedef struct { + unsigned long kind: 4; + bool isException: 1; +} JavaScriptValueKindAndFlags; +#else typedef struct { JavaScriptValueKind kind: 31; bool isException: 1; } JavaScriptValueKindAndFlags; +#endif typedef unsigned JavaScriptPayload1; typedef double JavaScriptPayload2; @@ -66,11 +78,19 @@ typedef double JavaScriptPayload2; /// payload1: `JavaScriptObjectRef` /// payload2: 0 /// +#if __Embedded +typedef struct { + unsigned long kind; + JavaScriptPayload1 payload1; + JavaScriptPayload2 payload2; +} RawJSValue; +#else typedef struct { JavaScriptValueKind kind; JavaScriptPayload1 payload1; JavaScriptPayload2 payload2; } RawJSValue; +#endif #if __wasm32__ @@ -81,6 +101,15 @@ typedef struct { /// @param kind A kind of JavaScript value to set the target object. /// @param payload1 The first payload of JavaScript value to set the target object. /// @param payload2 The second payload of JavaScript value to set the target object. +#if __Embedded +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_set_prop"))) +extern void _set_prop(const JavaScriptObjectRef _this, + const JavaScriptObjectRef prop, + const unsigned long kind, + const JavaScriptPayload1 payload1, + const JavaScriptPayload2 payload2); +#else __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_set_prop"))) extern void _set_prop(const JavaScriptObjectRef _this, @@ -88,6 +117,7 @@ extern void _set_prop(const JavaScriptObjectRef _this, const JavaScriptValueKind kind, const JavaScriptPayload1 payload1, const JavaScriptPayload2 payload2); +#endif /// `_get_prop` gets a value of `_this` JavaScript object. /// @@ -112,6 +142,15 @@ extern uint32_t _get_prop( /// @param kind A kind of JavaScript value to set the target object. /// @param payload1 The first payload of JavaScript value to set the target object. /// @param payload2 The second payload of JavaScript value to set the target object. +#if __Embedded +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_set_subscript"))) +extern void _set_subscript(const JavaScriptObjectRef _this, + const int index, + const unsigned long kind, + const JavaScriptPayload1 payload1, + const JavaScriptPayload2 payload2); +#else __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_set_subscript"))) extern void _set_subscript(const JavaScriptObjectRef _this, @@ -119,6 +158,7 @@ extern void _set_subscript(const JavaScriptObjectRef _this, const JavaScriptValueKind kind, const JavaScriptPayload1 payload1, const JavaScriptPayload2 payload2); +#endif /// `_get_subscript` gets a value of `_this` JavaScript object. /// @@ -151,9 +191,15 @@ extern int _encode_string(const JavaScriptObjectRef str_obj, JavaScriptObjectRef /// @param bytes_ptr A `uint8_t` byte sequence to decode. /// @param length The length of `bytes_ptr`. /// @result The decoded JavaScript string object. +#if __Embedded +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_decode_string"))) +extern JavaScriptObjectRef _decode_string(const int pointer, const int length); +#else __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_decode_string"))) extern JavaScriptObjectRef _decode_string(const unsigned char *bytes_ptr, const int length); +#endif /// `_load_string` loads the actual bytes sequence of `bytes` into `buffer` which is a Swift side memory address. /// @@ -265,6 +311,15 @@ extern JavaScriptObjectRef _call_new(const JavaScriptObjectRef ref, /// @param exception_payload1 A result pointer of first payload of JavaScript value of thrown exception. /// @param exception_payload2 A result pointer of second payload of JavaScript value of thrown exception. /// @returns A reference to the constructed object. +#if __Embedded +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_call_throwing_new"))) +extern JavaScriptObjectRef _call_throwing_new(const JavaScriptObjectRef ref, + const RawJSValue *argv, const int argc, + uint32_t *exception_kind, + JavaScriptPayload1 *exception_payload1, + JavaScriptPayload2 *exception_payload2); +#else __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_call_throwing_new"))) extern JavaScriptObjectRef _call_throwing_new(const JavaScriptObjectRef ref, @@ -272,6 +327,7 @@ extern JavaScriptObjectRef _call_throwing_new(const JavaScriptObjectRef ref, JavaScriptValueKindAndFlags *exception_kind, JavaScriptPayload1 *exception_payload1, JavaScriptPayload2 *exception_payload2); +#endif /// `_instanceof` acts like JavaScript `instanceof` operator. /// diff --git a/Sources/_CJavaScriptKitEmbedded b/Sources/_CJavaScriptKitEmbedded new file mode 120000 index 000000000..ac4c4c36e --- /dev/null +++ b/Sources/_CJavaScriptKitEmbedded @@ -0,0 +1 @@ +_CJavaScriptKit \ No newline at end of file