From b123600211484f47be475ac366aa5c87d2c7706c Mon Sep 17 00:00:00 2001 From: Manuel Burghard Date: Sun, 7 Jun 2020 16:09:27 +0200 Subject: [PATCH 01/24] Changes related to WebIDL support. --- .../FundamentalObjects/JSFunction.swift | 18 ++- .../FundamentalObjects/JSObject.swift | 4 + Sources/JavaScriptKit/JSValue.swift | 64 +++++++- .../JavaScriptKit/JSValueConvertible.swift | 137 +++++++++++++++--- Sources/JavaScriptKit/Support.swift | 57 ++++++++ 5 files changed, 255 insertions(+), 25 deletions(-) create mode 100644 Sources/JavaScriptKit/Support.swift diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index 92a52d0be..64e3d43eb 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -43,10 +43,7 @@ public class JSFunction: JSObject { let argv = bufferPointer.baseAddress let argc = bufferPointer.count var resultObj = JavaScriptObjectRef() - _call_new( - self.id, argv, Int32(argc), - &resultObj - ) + _call_new(self.id, argv, Int32(argc), &resultObj) return JSObject(id: resultObj) } } @@ -57,6 +54,10 @@ public class JSFunction: JSObject { fatalError("unavailable") } + public override class func construct(from value: JSValue) -> Self? { + return value.function as? Self + } + override public func jsValue() -> JSValue { .function(self) } @@ -89,6 +90,15 @@ public class JSClosure: JSFunction { } } + public required convenience init(jsValue: JSValue) { + switch jsValue { + case let .function(fun): + self.init { fun(arguments: $0) } + default: + fatalError() + } + } + public func release() { Self.sharedFunctions[hostFuncRef] = nil isReleased = true diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 40bc4658b..84b11b159 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -43,6 +43,10 @@ public class JSObject: Equatable { return lhs.id == rhs.id } + public class func construct(from value: JSValue) -> Self? { + return value.object as? Self + } + public func jsValue() -> JSValue { .object(self) } diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 4ac7a4579..980402481 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -37,14 +37,51 @@ public enum JSValue: Equatable { } } - public var isNull: Bool { return self == .null } - public var isUndefined: Bool { return self == .undefined } public var function: JSFunction? { switch self { case let .function(function): return function default: return nil } } + + public var isBoolean: Bool { + guard case .boolean = self else { return false } + return true + } + + public var isString: Bool { + guard case .string = self else { return false } + return true + } + + public var isNumber: Bool { + guard case .number = self else { return false } + return true + } + + public var isObject: Bool { + guard case .object = self else { return false } + return true + } + + public var isNull: Bool { + return self == .null + } + + public var isUndefined: Bool { + return self == .undefined + } + + public var isFunction: Bool { + guard case .function = self else { return false } + return true + } +} + +extension JSValue { + public func fromJSValue() -> Type? where Type: JSValueConstructible { + return Type.construct(from: self) + } } extension JSValue { @@ -60,7 +97,13 @@ extension JSValue: ExpressibleByStringLiteral { } extension JSValue: ExpressibleByIntegerLiteral { - public init(integerLiteral value: Double) { + public init(integerLiteral value: Int32) { + self = .number(Double(value)) + } +} + +extension JSValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { self = .number(value) } } @@ -94,3 +137,18 @@ public func setJSValue(this: JSObject, index: Int32, value: JSValue) { rawValue.payload1, rawValue.payload2, rawValue.payload3) } } + +extension JSValue { + public func isInstanceOf(_ constructor: JSFunction) -> Bool { + switch self { + case .boolean, .string, .number: + return false + case let .object(ref): + return ref.isInstanceOf(constructor) + case let .function(ref): + return ref.isInstanceOf(constructor) + case .null, .undefined: + fatalError() + } + } +} diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index bc3b3491e..85e4cf6b1 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -1,10 +1,47 @@ import _CJavaScriptKit +public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { + static var constructor: JSFunction? { get } + + var objectRef: JSObject { get } + init(objectRef: JSObject) +} + +extension JSBridgedType { + public var description: String { + return objectRef.toString!().fromJSValue()! + } +} + public protocol JSValueConvertible { func jsValue() -> JSValue } -extension JSValue: JSValueConvertible { +public typealias JSValueCodable = JSValueConvertible & JSValueConstructible + +extension JSBridgedType { + public static func canDecode(from jsValue: JSValue) -> Bool { + if let constructor = Self.constructor { + return jsValue.isInstanceOf(constructor) + } else { + return jsValue.isObject + } + } + + public init(jsValue: JSValue) { + self.init(objectRef: jsValue.object!) + } + + public func jsValue() -> JSValue { + return JSValue.object(objectRef) + } +} + +extension JSValue: JSValueCodable { + public static func construct(from value: JSValue) -> Self? { + return value + } + public func jsValue() -> JSValue { self } } @@ -16,20 +53,20 @@ extension Int: JSValueConvertible { public func jsValue() -> JSValue { .number(Double(self)) } } -extension Int8: JSValueConvertible { +extension UInt: JSValueConvertible { public func jsValue() -> JSValue { .number(Double(self)) } } -extension Int16: JSValueConvertible { +extension Float: JSValueConvertible { public func jsValue() -> JSValue { .number(Double(self)) } } -extension Int32: JSValueConvertible { - public func jsValue() -> JSValue { .number(Double(self)) } +extension Double: JSValueConvertible { + public func jsValue() -> JSValue { .number(self) } } -extension UInt: JSValueConvertible { - public func jsValue() -> JSValue { .number(Double(self)) } +extension String: JSValueConvertible { + public func jsValue() -> JSValue { .string(self) } } extension UInt8: JSValueConvertible { @@ -41,22 +78,30 @@ extension UInt16: JSValueConvertible { } extension UInt32: JSValueConvertible { - public func jsValue() -> JSValue { .number(Double(self)) } + public func jsValue() -> JSValue { .number(Double(self)) } } -extension Float: JSValueConvertible { +extension UInt64: JSValueConvertible { public func jsValue() -> JSValue { .number(Double(self)) } } -extension Double: JSValueConvertible { - public func jsValue() -> JSValue { .number(self) } +extension Int8: JSValueConvertible { + public func jsValue() -> JSValue { .number(Double(self)) } } -extension String: JSValueConvertible { - public func jsValue() -> JSValue { .string(self) } +extension Int16: JSValueConvertible { + public func jsValue() -> JSValue { .number(Double(self)) } +} + +extension Int32: JSValueConvertible { + public func jsValue() -> JSValue { .number(Double(self)) } } -extension JSObject: JSValueConvertible { +extension Int64: JSValueConvertible { + public func jsValue() -> JSValue { .number(Double(self)) } +} + +extension JSObject: JSValueCodable { // `JSObject.jsValue` is defined in JSObject.swift to be able to overridden // from `JSFunction` } @@ -79,17 +124,54 @@ extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key } } -private let Array = JSObject.global.Array.function! +private let NativeJSArray = JSObject.global.Array.function! +extension Dictionary: JSValueConstructible where Value: JSValueConstructible, Key == String { + public static func construct(from value: JSValue) -> Self? { + if let objectRef = value.object, + let keys: [String] = Object.keys!(objectRef.jsValue()).fromJSValue() { + var entries = [(String, 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) + } + return nil + } +} + +extension Optional: JSValueConstructible where Wrapped: JSValueConstructible { + public static func construct(from value: JSValue) -> Self? { + switch value { + case .null, .undefined: + return nil + default: + return Wrapped.construct(from: value) + } + } +} + +extension Optional: JSValueConvertible where Wrapped: JSValueConvertible { + public func jsValue() -> JSValue { + switch self { + case .none: return .null + case let .some(wrapped): return wrapped.jsValue() + } + } +} extension Array where Element: JSValueConvertible { public func jsValue() -> JSValue { - Swift.Array.jsValue(self)() + Array.jsValue(self)() } } extension Array: JSValueConvertible where Element == JSValueConvertible { public func jsValue() -> JSValue { - let array = Array.new(count) + let array = NativeJSArray.new(count) for (index, element) in enumerated() { array[index] = element.jsValue() } @@ -97,6 +179,25 @@ extension Array: JSValueConvertible where Element == JSValueConvertible { } } +extension Array: JSValueConstructible where Element: JSValueConstructible { + public static func construct(from value: JSValue) -> [Element]? { + if let objectRef = value.object, + objectRef.isInstanceOf(JSObject.global.Array.function!) { + let count: Int = objectRef.length.fromJSValue()! + var array = [Element]() + array.reserveCapacity(count) + + for i in 0 ..< count { + guard let value: Element = objectRef[i].fromJSValue() else { return nil } + array.append(value) + } + + return array + } + return nil + } +} + extension RawJSValue: JSValueConvertible { public func jsValue() -> JSValue { switch kind { @@ -192,6 +293,6 @@ extension Array where Element == JSValueConvertible { extension Array where Element: JSValueConvertible { func withRawJSValues(_ body: ([RawJSValue]) -> T) -> T { - Swift.Array.withRawJSValues(self)(body) + Array.withRawJSValues(self)(body) } } diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift new file mode 100644 index 000000000..5d613916b --- /dev/null +++ b/Sources/JavaScriptKit/Support.swift @@ -0,0 +1,57 @@ +protocol _AnyJSValueCodable: JSValueConvertible {} + +public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { + public static let void = AnyJSValueCodable.construct(from: .undefined) + + private struct Box: _AnyJSValueCodable { + let value: T + + func jsValue() -> JSValue { + value.jsValue() + } + } + + private struct ConcreteBox: _AnyJSValueCodable { + let value: JSValue + + func jsValue() -> JSValue { + return value + } + } + + private let value: _AnyJSValueCodable + + public init(_ value: T) where T: JSValueConvertible { + self.value = Box(value: value) + } + private init(boxed value: _AnyJSValueCodable) { + self.value = value + } + + public static func construct(from value: JSValue) -> AnyJSValueCodable? { + self.init(boxed: ConcreteBox(value: value)) + } + + public init(nilLiteral _: ()) { + value = ConcreteBox(value: .null) + } + + public func jsValue() -> JSValue { + value.jsValue() + } + + public func fromJSValue() -> Type? { + self.jsValue().fromJSValue() + } +} + +public func staticCast(_ ref: JSBridgedType) -> Type { + return Type(objectRef: ref.objectRef) +} + +public func dynamicCast(_ ref: JSBridgedType) -> Type? { + guard let constructor = Type.constructor, ref.objectRef.isInstanceOf(constructor) else { + return nil + } + return staticCast(ref) +} From e6b6705993c801f9d1aa4471dc8b8686fcccc47b Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Sun, 2 Aug 2020 17:23:32 -0400 Subject: [PATCH 02/24] Rename constructor --- Sources/JavaScriptKit/JSValueConvertible.swift | 8 ++------ Sources/JavaScriptKit/Support.swift | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 85e4cf6b1..71a5f7858 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -1,7 +1,7 @@ import _CJavaScriptKit public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { - static var constructor: JSFunction? { get } + static var classRef: JSFunction { get } var objectRef: JSObject { get } init(objectRef: JSObject) @@ -21,11 +21,7 @@ public typealias JSValueCodable = JSValueConvertible & JSValueConstructible extension JSBridgedType { public static func canDecode(from jsValue: JSValue) -> Bool { - if let constructor = Self.constructor { - return jsValue.isInstanceOf(constructor) - } else { - return jsValue.isObject - } + jsValue.isInstanceOf(Self.classRef) } public init(jsValue: JSValue) { diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index 5d613916b..9b516999f 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -50,7 +50,7 @@ public func staticCast(_ ref: JSBridgedType) -> Type { } public func dynamicCast(_ ref: JSBridgedType) -> Type? { - guard let constructor = Type.constructor, ref.objectRef.isInstanceOf(constructor) else { + guard ref.objectRef.isInstanceOf(Type.classRef) else { return nil } return staticCast(ref) From 5982befec793754bd0e7668f5584df284e686dbd Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Mon, 3 Aug 2020 17:34:32 -0400 Subject: [PATCH 03/24] wip --- Sources/JavaScriptKit/FundamentalObjects/JSObject.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 84b11b159..06283bdda 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -8,13 +8,18 @@ public class JSObject: Equatable { } @_disfavoredOverload - public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? { + public subscript(_ name: String) -> ((JSValueConvertible...) -> JSValue)? { guard let function = self[name].function else { return nil } return { (arguments: JSValueConvertible...) in function(this: self, arguments: arguments) } } + @_disfavoredOverload + public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? { + self[name] + } + public subscript(dynamicMember name: String) -> JSValue { get { self[name] } set { self[name] = newValue } From a5ce5e40eb8e88dc051b0b1034a61820ff076791 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Mon, 3 Aug 2020 22:03:19 -0400 Subject: [PATCH 04/24] Add JSAbstractBridgedType --- Sources/JavaScriptKit/JSValueConvertible.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 71a5f7858..ffa4e1644 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -1,18 +1,22 @@ import _CJavaScriptKit -public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { - static var classRef: JSFunction { get } - +// Use this protocol when your type has no single JavaScript class. +// For example, a union type of multiple classes. +public protocol JSAbstractBridgedType: JSValueCodable, CustomStringConvertible { var objectRef: JSObject { get } init(objectRef: JSObject) } -extension JSBridgedType { +extension JSAbstractBridgedType { public var description: String { return objectRef.toString!().fromJSValue()! } } +public protocol JSBridgedType: JSAbstractBridgedType { + static var classRef: JSFunction { get } +} + public protocol JSValueConvertible { func jsValue() -> JSValue } From 0f79f43726c96adcadb47821aaaf8eef4ef9c2d9 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Mon, 3 Aug 2020 22:09:26 -0400 Subject: [PATCH 05/24] More updates --- Sources/JavaScriptKit/JSValueConvertible.swift | 14 ++++++++------ Sources/JavaScriptKit/Support.swift | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index ffa4e1644..8e9010e1a 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -23,17 +23,19 @@ public protocol JSValueConvertible { public typealias JSValueCodable = JSValueConvertible & JSValueConstructible -extension JSBridgedType { - public static func canDecode(from jsValue: JSValue) -> Bool { - jsValue.isInstanceOf(Self.classRef) - } - +extension JSAbstractBridgedType { public init(jsValue: JSValue) { self.init(objectRef: jsValue.object!) } public func jsValue() -> JSValue { - return JSValue.object(objectRef) + .object(objectRef) + } +} + +extension JSBridgedType { + public static func canDecode(from jsValue: JSValue) -> Bool { + jsValue.isInstanceOf(Self.classRef) } } diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index 9b516999f..1993afc1c 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -45,7 +45,7 @@ public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { } } -public func staticCast(_ ref: JSBridgedType) -> Type { +public func staticCast(_ ref: JSAbstractBridgedType) -> Type { return Type(objectRef: ref.objectRef) } From fbf9f76ad0b13004400758f531a436f108eb361a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 15:26:32 -0400 Subject: [PATCH 06/24] Move & rename JS*BridgedType --- Sources/JavaScriptKit/JSBridgedType.swift | 46 +++++++++++++++++++ .../JavaScriptKit/JSValueConvertible.swift | 33 ------------- Sources/JavaScriptKit/Support.swift | 11 ----- 3 files changed, 46 insertions(+), 44 deletions(-) create mode 100644 Sources/JavaScriptKit/JSBridgedType.swift diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift new file mode 100644 index 000000000..45e74859d --- /dev/null +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -0,0 +1,46 @@ +// Use this protocol when your type has no single JavaScript class. +// For example, a union type of multiple classes. +public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { + var objectRef: JSObject { get } + init?(objectRef: JSObject) + static func canDecode(_ object: JSObject) -> Bool +} + +extension JSBridgedType { + public static func construct(from value: JSValue) -> Self? { + guard let object = value.object, canDecode(object) else { + return nil + } + return Self.init(objectRef: object) + } + + public func jsValue() -> JSValue { + .object(objectRef) + } + + public var description: String { + return objectRef.toString!().fromJSValue()! + } +} + + +public protocol JSBridgedClass: JSBridgedType { + static var classRef: JSFunction { get } +} + +extension JSBridgedClass { + public static func canDecode(from jsValue: JSValue) -> Bool { + jsValue.isInstanceOf(Self.classRef) + } +} + +public func staticCast(_ ref: JSBridgedType) -> Type? { + return Type(objectRef: ref.objectRef) +} + +public func dynamicCast(_ ref: JSBridgedClass) -> Type? { + guard ref.objectRef.isInstanceOf(Type.classRef) else { + return nil + } + return staticCast(ref) +} diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 8e9010e1a..774f125ea 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -1,44 +1,11 @@ import _CJavaScriptKit -// Use this protocol when your type has no single JavaScript class. -// For example, a union type of multiple classes. -public protocol JSAbstractBridgedType: JSValueCodable, CustomStringConvertible { - var objectRef: JSObject { get } - init(objectRef: JSObject) -} - -extension JSAbstractBridgedType { - public var description: String { - return objectRef.toString!().fromJSValue()! - } -} - -public protocol JSBridgedType: JSAbstractBridgedType { - static var classRef: JSFunction { get } -} - public protocol JSValueConvertible { func jsValue() -> JSValue } public typealias JSValueCodable = JSValueConvertible & JSValueConstructible -extension JSAbstractBridgedType { - public init(jsValue: JSValue) { - self.init(objectRef: jsValue.object!) - } - - public func jsValue() -> JSValue { - .object(objectRef) - } -} - -extension JSBridgedType { - public static func canDecode(from jsValue: JSValue) -> Bool { - jsValue.isInstanceOf(Self.classRef) - } -} - extension JSValue: JSValueCodable { public static func construct(from value: JSValue) -> Self? { return value diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index 1993afc1c..f732ea607 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -44,14 +44,3 @@ public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { self.jsValue().fromJSValue() } } - -public func staticCast(_ ref: JSAbstractBridgedType) -> Type { - return Type(objectRef: ref.objectRef) -} - -public func dynamicCast(_ ref: JSBridgedType) -> Type? { - guard ref.objectRef.isInstanceOf(Type.classRef) else { - return nil - } - return staticCast(ref) -} From e31a989830cbfdfb8a8ce5445dfdd95774557b50 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 15:48:05 -0400 Subject: [PATCH 07/24] wip --- Sources/JavaScriptKit/JSBridgedType.swift | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 45e74859d..5d12d3fbb 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -3,14 +3,11 @@ public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { var objectRef: JSObject { get } init?(objectRef: JSObject) - static func canDecode(_ object: JSObject) -> Bool } extension JSBridgedType { public static func construct(from value: JSValue) -> Self? { - guard let object = value.object, canDecode(object) else { - return nil - } + guard let object = value.object else { return nil } return Self.init(objectRef: object) } @@ -26,11 +23,13 @@ extension JSBridgedType { public protocol JSBridgedClass: JSBridgedType { static var classRef: JSFunction { get } + init(withCompatibleObject objectRef: JSObject) } extension JSBridgedClass { - public static func canDecode(from jsValue: JSValue) -> Bool { - jsValue.isInstanceOf(Self.classRef) + public init?(objectRef: JSObject) { + guard objectRef.isInstanceOf(Self.classRef) else { return nil } + self.init(withCompatibleObject: objectRef) } } From e4d657e0f41c3f34f6b914ae579a8b3a5687e2bd Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 16:50:57 -0400 Subject: [PATCH 08/24] Update JSTypedArray.swift --- Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 0994708ce..d267a5488 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -39,6 +39,11 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera self.init(unsafe: jsObject) } + public init(wrapping jsObject: JSObjectRef) { + _retain(jsObject.id) + super.init(id: jsObject.id) + } + required public convenience init(arrayLiteral elements: Element...) { self.init(elements) } From c07db3d25ed19a3b32c5256ae3c9043de82ffa43 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 16:51:38 -0400 Subject: [PATCH 09/24] Update JSTypedArray.swift --- Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index d267a5488..d29fd01c6 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -39,7 +39,7 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera self.init(unsafe: jsObject) } - public init(wrapping jsObject: JSObjectRef) { + public init(objectRef jsObject: JSObjectRef) { _retain(jsObject.id) super.init(id: jsObject.id) } From be1f916934c8c8ba9f582cb0df2dd7d32c5ee9ae Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 16:52:55 -0400 Subject: [PATCH 10/24] Update JSTypedArray.swift --- Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index d29fd01c6..1173e788f 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -39,7 +39,8 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera self.init(unsafe: jsObject) } - public init(objectRef jsObject: JSObjectRef) { + public init?(objectRef jsObject: JSObjectRef) { + guard jsObject.isInstanceOf(Element.typedArrayClass) else { return nil } _retain(jsObject.id) super.init(id: jsObject.id) } From ba3316ebc96b8c03c86c4966970b759cb273fd3f Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 16:57:23 -0400 Subject: [PATCH 11/24] Update JSTypedArray.swift --- .../BasicObjects/JSTypedArray.swift | 36 ++++++------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 1173e788f..6137786a7 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -9,40 +9,24 @@ public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { static var typedArrayClass: JSFunction { get } } -public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement { - let ref: JSObject - public func jsValue() -> JSValue { - .object(ref) - } - +public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement { + public static var classRef: JSFunction { Element.typedArrayClass } + public var objectRef: JSObject public subscript(_ index: Int) -> Element { get { - return Element.construct(from: getJSValue(this: ref, index: Int32(index)))! + return Element.construct(from: objectRef[index])! } set { - setJSValue(this: ref, index: Int32(index), value: newValue.jsValue()) + self.objectRef[index] = newValue.jsValue() } } - // This private initializer assumes that the passed object is TypedArray - private init(unsafe object: JSObject) { - self.ref = object - } - - public init?(_ object: JSObject) { - guard object.isInstanceOf(Element.typedArrayClass) else { return nil } - self.ref = object - } - - public convenience init(length: Int) { - let jsObject = Element.typedArrayClass.new(length) - self.init(unsafe: jsObject) + public init(length: Int) { + objectRef = Element.typedArrayClass.new(length) } - public init?(objectRef jsObject: JSObjectRef) { - guard jsObject.isInstanceOf(Element.typedArrayClass) else { return nil } - _retain(jsObject.id) - super.init(id: jsObject.id) + required public init(withCompatibleObject jsObject: JSObject) { + objectRef = jsObject } required public convenience init(arrayLiteral elements: Element...) { @@ -54,7 +38,7 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - self.init(unsafe: JSObject(id: resultObj)) + self.init(withCompatibleObject: JSObject(id: resultObj)) } public convenience init(_ stride: StrideTo) where Element: Strideable { From 5fe4b96a69e82f1f6ada482ddf71851b4ace23c2 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Fri, 14 Aug 2020 17:21:03 -0400 Subject: [PATCH 12/24] Allow JSBridgedType (but not JSBridgedClass) to hold non-objects --- Sources/JavaScriptKit/JSBridgedType.swift | 34 +++++++---------------- Sources/JavaScriptKit/JSValue.swift | 19 +++++++++++++ Sources/JavaScriptKit/Support.swift | 11 ++++++++ 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 5d12d3fbb..351a88423 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -1,45 +1,31 @@ // Use this protocol when your type has no single JavaScript class. // For example, a union type of multiple classes. public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { - var objectRef: JSObject { get } - init?(objectRef: JSObject) + var value: JSValue { get } + init?(from value: JSValue) } extension JSBridgedType { public static func construct(from value: JSValue) -> Self? { - guard let object = value.object else { return nil } - return Self.init(objectRef: object) + return Self.init(from: value) } - public func jsValue() -> JSValue { - .object(objectRef) - } + public func jsValue() -> JSValue { value } - public var description: String { - return objectRef.toString!().fromJSValue()! - } + public var description: String { value.description } } public protocol JSBridgedClass: JSBridgedType { static var classRef: JSFunction { get } + var objectRef: JSObject { get } init(withCompatibleObject objectRef: JSObject) } extension JSBridgedClass { - public init?(objectRef: JSObject) { - guard objectRef.isInstanceOf(Self.classRef) else { return nil } - self.init(withCompatibleObject: objectRef) - } -} - -public func staticCast(_ ref: JSBridgedType) -> Type? { - return Type(objectRef: ref.objectRef) -} - -public func dynamicCast(_ ref: JSBridgedClass) -> Type? { - guard ref.objectRef.isInstanceOf(Type.classRef) else { - return nil + public var value: JSValue { objectRef.jsValue() } + public init?(from value: JSValue) { + guard let object = value.object, object.isInstanceOf(Self.classRef) else { return nil } + self.init(withCompatibleObject: object) } - return staticCast(ref) } diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 980402481..090d102f6 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -152,3 +152,22 @@ extension JSValue { } } } + +extension JSValue: CustomStringConvertible { + public var description: String { + switch self { + case let .boolean(boolean): + return boolean.description + case .string(let string): + return string + case .number(let number): + return number.description + case .object(let object), .function(let object as JSObject): + return object.toString!().fromJSValue()! + case .null: + return "null" + case .undefined: + return "undefined" + } + } +} diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index f732ea607..038baefd8 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -44,3 +44,14 @@ public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { self.jsValue().fromJSValue() } } + +public func staticCast(_ ref: JSBridgedType) -> Type? { + return Type(from: ref.value) +} + +public func dynamicCast(_ ref: JSBridgedClass) -> Type? { + guard ref.objectRef.isInstanceOf(Type.classRef) else { + return nil + } + return staticCast(ref) +} From b62b78e0460c9b712a5edc5c4575c19f56fe1195 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 15 Sep 2020 10:09:20 -0400 Subject: [PATCH 13/24] Make JS{Array,Date,Error} conform to JSBridgedClass --- .../JavaScriptKit/BasicObjects/JSArray.swift | 42 +++++++++++-------- .../JavaScriptKit/BasicObjects/JSDate.swift | 8 +++- .../JavaScriptKit/BasicObjects/JSError.swift | 8 +++- .../BasicObjects/JSTypedArray.swift | 12 +++--- Sources/JavaScriptKit/JSBridgedType.swift | 10 ++--- Sources/JavaScriptKit/Support.swift | 2 +- 6 files changed, 49 insertions(+), 33 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 4a8a6aecc..d4199111a 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -1,15 +1,23 @@ -public class JSArray { - static let classObject = JSObject.global.Array.function! +public class JSArray: JSBridgedClass { + public static let constructor = JSObject.global.Array.function! static func isArray(_ object: JSObject) -> Bool { - classObject.isArray!(object).boolean! + constructor.isArray!(object).boolean! } - let ref: JSObject + public let jsObject: JSObject - public init?(_ ref: JSObject) { - guard Self.isArray(ref) else { return nil } - self.ref = ref + public required convenience init?(from value: JSValue) { + guard let object = value.object else { return nil } + self.init(object) + } + + public convenience init?(_ jsObject: JSObject) { + guard Self.isArray(jsObject) else { return nil } + self.init(withCompatibleObject: jsObject) + } + public required init(withCompatibleObject jsObject: JSObject) { + self.jsObject = jsObject } } @@ -17,32 +25,32 @@ extension JSArray: RandomAccessCollection { public typealias Element = JSValue public func makeIterator() -> Iterator { - Iterator(ref: ref) + Iterator(jsObject: jsObject) } public class Iterator: IteratorProtocol { - let ref: JSObject + let jsObject: JSObject var index = 0 - init(ref: JSObject) { - self.ref = ref + init(jsObject: JSObject) { + self.jsObject = jsObject } public func next() -> Element? { let currentIndex = index - guard currentIndex < Int(ref.length.number!) else { + guard currentIndex < Int(jsObject.length.number!) else { return nil } index += 1 - guard ref.hasOwnProperty!(currentIndex).boolean! else { + guard jsObject.hasOwnProperty!(currentIndex).boolean! else { return next() } - let value = ref[currentIndex] + let value = jsObject[currentIndex] return value } } public subscript(position: Int) -> JSValue { - ref[position] + jsObject[position] } public var startIndex: Int { 0 } @@ -62,14 +70,14 @@ extension JSArray: RandomAccessCollection { /// array.count // 2 /// ``` public var length: Int { - return Int(ref.length.number!) + Int(jsObject.length.number!) } /// The number of elements in that array **not** including empty hole. /// Note that `count` syncs with the number that `Iterator` can iterate. /// See also: `JSArray.length` public var count: Int { - return getObjectValuesLength(ref) + getObjectValuesLength(jsObject) } } diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index e871c4ce6..ec54595be 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -6,9 +6,9 @@ in the naming. Parts of the JavaScript `Date` API that are not consistent across implementations are not exposed in a type-safe manner, you should access the underlying `jsObject` property if you need those. */ -public final class JSDate { +public final class JSDate: JSBridgedClass { /// The constructor function used to create new `Date` objects. - private static let constructor = JSObject.global.Date.function! + public static let constructor = JSObject.global.Date.function! /// The underlying JavaScript `Date` object. public let jsObject: JSObject @@ -39,6 +39,10 @@ public final class JSDate { jsObject = Self.constructor.new(year, monthIndex, day, hours, minutes, seconds, milliseconds) } + public init(withCompatibleObject jsObject: JSObject) { + self.jsObject = jsObject + } + /// Year of this date in local time zone. public var fullYear: Int { get { diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift index 4dff7585b..ad728d640 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSError.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift @@ -2,9 +2,9 @@ class](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) that exposes its properties in a type-safe way. */ -public final class JSError: Error { +public final class JSError: Error, JSBridgedClass { /// The constructor function used to create new `Error` objects. - private static let constructor = JSObject.global.Error.function! + public static let constructor = JSObject.global.Error.function! /// The underlying JavaScript `Error` object. public let jsObject: JSObject @@ -14,6 +14,10 @@ public final class JSError: Error { jsObject = Self.constructor.new([message]) } + public init(withCompatibleObject jsObject: JSObject) { + self.jsObject = jsObject + } + /// The error message of the underlying `Error` object. public var message: String { jsObject.message.string! diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 6137786a7..747a71fd7 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -10,23 +10,23 @@ public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { } public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement { - public static var classRef: JSFunction { Element.typedArrayClass } - public var objectRef: JSObject + public static var constructor: JSFunction { Element.typedArrayClass } + public var jsObject: JSObject public subscript(_ index: Int) -> Element { get { - return Element.construct(from: objectRef[index])! + return Element.construct(from: jsObject[index])! } set { - self.objectRef[index] = newValue.jsValue() + self.jsObject[index] = newValue.jsValue() } } public init(length: Int) { - objectRef = Element.typedArrayClass.new(length) + jsObject = Element.typedArrayClass.new(length) } required public init(withCompatibleObject jsObject: JSObject) { - objectRef = jsObject + self.jsObject = jsObject } required public convenience init(arrayLiteral elements: Element...) { diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 351a88423..6d54ec89a 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -17,15 +17,15 @@ extension JSBridgedType { public protocol JSBridgedClass: JSBridgedType { - static var classRef: JSFunction { get } - var objectRef: JSObject { get } - init(withCompatibleObject objectRef: JSObject) + static var constructor: JSFunction { get } + var jsObject: JSObject { get } + init(withCompatibleObject jsObject: JSObject) } extension JSBridgedClass { - public var value: JSValue { objectRef.jsValue() } + public var value: JSValue { jsObject.jsValue() } public init?(from value: JSValue) { - guard let object = value.object, object.isInstanceOf(Self.classRef) else { return nil } + guard let object = value.object, object.isInstanceOf(Self.constructor) else { return nil } self.init(withCompatibleObject: object) } } diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index 038baefd8..41fe76c90 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -50,7 +50,7 @@ public func staticCast(_ ref: JSBridgedType) -> Type? { } public func dynamicCast(_ ref: JSBridgedClass) -> Type? { - guard ref.objectRef.isInstanceOf(Type.classRef) else { + guard ref.jsObject.isInstanceOf(Type.constructor) else { return nil } return staticCast(ref) From 8b181c76236ac33ef034f0903df009f2fa144f60 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 15 Sep 2020 10:28:57 -0400 Subject: [PATCH 14/24] Clean up AnyJSValueConvertible --- Sources/JavaScriptKit/Support.swift | 31 ++++++++--------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index 41fe76c90..750c1ce38 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -1,9 +1,9 @@ -protocol _AnyJSValueCodable: JSValueConvertible {} +protocol _AnyJSValueConvertible: JSValueConvertible {} -public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { - public static let void = AnyJSValueCodable.construct(from: .undefined) +public struct AnyJSValueConvertible: JSValueCodable, ExpressibleByNilLiteral { + public static let void = AnyJSValueConvertible.construct(from: .undefined) - private struct Box: _AnyJSValueCodable { + private struct Box: _AnyJSValueConvertible { let value: T func jsValue() -> JSValue { @@ -11,7 +11,7 @@ public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { } } - private struct ConcreteBox: _AnyJSValueCodable { + private struct ConcreteBox: _AnyJSValueConvertible { let value: JSValue func jsValue() -> JSValue { @@ -19,16 +19,16 @@ public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { } } - private let value: _AnyJSValueCodable + private let value: _AnyJSValueConvertible public init(_ value: T) where T: JSValueConvertible { self.value = Box(value: value) } - private init(boxed value: _AnyJSValueCodable) { + private init(boxed value: _AnyJSValueConvertible) { self.value = value } - public static func construct(from value: JSValue) -> AnyJSValueCodable? { + public static func construct(from value: JSValue) -> AnyJSValueConvertible? { self.init(boxed: ConcreteBox(value: value)) } @@ -39,19 +39,4 @@ public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { public func jsValue() -> JSValue { value.jsValue() } - - public func fromJSValue() -> Type? { - self.jsValue().fromJSValue() - } -} - -public func staticCast(_ ref: JSBridgedType) -> Type? { - return Type(from: ref.value) -} - -public func dynamicCast(_ ref: JSBridgedClass) -> Type? { - guard ref.jsObject.isInstanceOf(Type.constructor) else { - return nil - } - return staticCast(ref) } From de5f52e88d39f1a579123fd6d8f78bff69939646 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 15 Sep 2020 12:14:12 -0400 Subject: [PATCH 15/24] Docs for JSTypedArray --- Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 747a71fd7..e8d0e6180 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -4,14 +4,19 @@ import _CJavaScriptKit +/// A protocol that allows a Swift numeric type to be mapped to the JavaScript TypedArray that holds integers of its type public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { + /// The kind of typed array that should be created on the JS side static var typedArrayKind: JavaScriptTypedArrayKind { get } + /// The constructor function for the TypedArray class for this particular kind of number static var typedArrayClass: JSFunction { get } } +/// A wrapper around all JavaScript [`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) classes that exposes their properties in a type-safe way. public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement { public static var constructor: JSFunction { Element.typedArrayClass } public var jsObject: JSObject + public subscript(_ index: Int) -> Element { get { return Element.construct(from: jsObject[index])! @@ -21,6 +26,7 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh } } + /// Create a TypedArray with the provided number of elements allocated. All the elements will be initialized to zero. public init(length: Int) { jsObject = Element.typedArrayClass.new(length) } @@ -33,6 +39,7 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh self.init(elements) } + /// Convert an array of numbers into a JavaScript TypedArray public convenience init(_ array: [Element]) { var resultObj = JavaScriptObjectRef() array.withUnsafeBufferPointer { ptr in From 479ad20b07f36a2db1cb5a410f12a7ae587d1b49 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 15 Sep 2020 12:25:36 -0400 Subject: [PATCH 16/24] Docs for JSValueCoable-adjacent types --- Sources/JavaScriptKit/JSBridgedType.swift | 16 +++++++++++++--- Sources/JavaScriptKit/JSValueConstructible.swift | 2 ++ Sources/JavaScriptKit/JSValueConvertible.swift | 2 ++ Sources/JavaScriptKit/Support.swift | 1 + 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 6d54ec89a..79feaf36c 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -1,7 +1,10 @@ -// Use this protocol when your type has no single JavaScript class. -// For example, a union type of multiple classes. +/// 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: JSValueCodable, CustomStringConvertible { + /// This is the value your class wraps. var value: JSValue { get } + + /// If your class is incompatible with the provided value, return `nil`. init?(from value: JSValue) } @@ -15,10 +18,17 @@ extension JSBridgedType { public var description: String { value.description } } - +/// Conform to this protocol when your Swift class wraps a JavaScript class. public protocol JSBridgedClass: JSBridgedType { + /// The constructor function for the JavaScript class static var constructor: JSFunction { get } + + /// The JavaScript object wrapped by this instance. + /// You may assume that `jsObject instanceof Self.constructor` var jsObject: JSObject { get } + + /// Create an instannce wrapping the given JavaScript object. + /// You may assume that `jsObject instanceof Self.constructor` init(withCompatibleObject jsObject: JSObject) } diff --git a/Sources/JavaScriptKit/JSValueConstructible.swift b/Sources/JavaScriptKit/JSValueConstructible.swift index 2a312bf65..9f60a55f2 100644 --- a/Sources/JavaScriptKit/JSValueConstructible.swift +++ b/Sources/JavaScriptKit/JSValueConstructible.swift @@ -1,4 +1,6 @@ +/// Objects that can be constructed from a JavaScript value public protocol JSValueConstructible { + /// Return `nil` if the value is not compatible with the conforming Swift type. static func construct(from value: JSValue) -> Self? } diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 774f125ea..d7c6098cf 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -1,6 +1,8 @@ import _CJavaScriptKit +/// Objects that can be converted to a JavaScript value, preferably in a lossless manner. public protocol JSValueConvertible { + /// Create a JSValue that represents this object func jsValue() -> JSValue } diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift index 750c1ce38..8b5cfdb44 100644 --- a/Sources/JavaScriptKit/Support.swift +++ b/Sources/JavaScriptKit/Support.swift @@ -1,5 +1,6 @@ protocol _AnyJSValueConvertible: JSValueConvertible {} +/// A type-erased wrapper for types conforming to JSValueConvertible public struct AnyJSValueConvertible: JSValueCodable, ExpressibleByNilLiteral { public static let void = AnyJSValueConvertible.construct(from: .undefined) From b4edc1954a8608bc7e553f9511c2464edfb95139 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Wed, 16 Sep 2020 10:11:30 -0400 Subject: [PATCH 17/24] Update based on review suggestions Co-Authored-By: Yuta Saito <11702759+kateinoigakukun@users.noreply.github.com> --- .../FundamentalObjects/JSFunction.swift | 9 ---- .../JavaScriptKit/JSValueConvertible.swift | 50 ++++++++++--------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift index 64e3d43eb..6b62099c0 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSFunction.swift @@ -90,15 +90,6 @@ public class JSClosure: JSFunction { } } - public required convenience init(jsValue: JSValue) { - switch jsValue { - case let .function(fun): - self.init { fun(arguments: $0) } - default: - fatalError() - } - } - public func release() { Self.sharedFunctions[hostFuncRef] = nil isReleased = true diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index d7c6098cf..b9bdb5b06 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -98,19 +98,20 @@ extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key private let NativeJSArray = JSObject.global.Array.function! extension Dictionary: JSValueConstructible where Value: JSValueConstructible, Key == String { public static func construct(from value: JSValue) -> Self? { - if let objectRef = value.object, - let keys: [String] = Object.keys!(objectRef.jsValue()).fromJSValue() { - var entries = [(String, Value)]() - entries.reserveCapacity(keys.count) - for key in keys { - guard let value: Value = objectRef[key].fromJSValue() else { - return nil - } - entries.append((key, value)) + guard + let objectRef = value.object, + let keys: [String] = Object.keys!(objectRef.jsValue()).fromJSValue() + else { return nil } + + var entries = [(String, Value)]() + entries.reserveCapacity(keys.count) + for key in keys { + guard let value: Value = objectRef[key].fromJSValue() else { + return nil } - return Dictionary(uniqueKeysWithValues: entries) + entries.append((key, value)) } - return nil + return Dictionary(uniqueKeysWithValues: entries) } } @@ -152,20 +153,21 @@ extension Array: JSValueConvertible where Element == JSValueConvertible { extension Array: JSValueConstructible where Element: JSValueConstructible { public static func construct(from value: JSValue) -> [Element]? { - if let objectRef = value.object, - objectRef.isInstanceOf(JSObject.global.Array.function!) { - let count: Int = objectRef.length.fromJSValue()! - var array = [Element]() - array.reserveCapacity(count) - - for i in 0 ..< count { - guard let value: Element = objectRef[i].fromJSValue() else { return nil } - array.append(value) - } - - return array + guard + let objectRef = value.object, + objectRef.isInstanceOf(JSObject.global.Array.function!) + else { return nil } + + let count: Int = objectRef.length.fromJSValue()! + var array = [Element]() + array.reserveCapacity(count) + + for i in 0 ..< count { + guard let value: Element = objectRef[i].fromJSValue() else { return nil } + array.append(value) } - return nil + + return array } } From cabda58bc52812ea3cece7be91a4faa57710cf7c Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Wed, 16 Sep 2020 12:31:04 -0400 Subject: [PATCH 18/24] Remove AnyJSValueConvertible --- Sources/JavaScriptKit/Support.swift | 43 ----------------------------- 1 file changed, 43 deletions(-) delete mode 100644 Sources/JavaScriptKit/Support.swift diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift deleted file mode 100644 index 8b5cfdb44..000000000 --- a/Sources/JavaScriptKit/Support.swift +++ /dev/null @@ -1,43 +0,0 @@ -protocol _AnyJSValueConvertible: JSValueConvertible {} - -/// A type-erased wrapper for types conforming to JSValueConvertible -public struct AnyJSValueConvertible: JSValueCodable, ExpressibleByNilLiteral { - public static let void = AnyJSValueConvertible.construct(from: .undefined) - - private struct Box: _AnyJSValueConvertible { - let value: T - - func jsValue() -> JSValue { - value.jsValue() - } - } - - private struct ConcreteBox: _AnyJSValueConvertible { - let value: JSValue - - func jsValue() -> JSValue { - return value - } - } - - private let value: _AnyJSValueConvertible - - public init(_ value: T) where T: JSValueConvertible { - self.value = Box(value: value) - } - private init(boxed value: _AnyJSValueConvertible) { - self.value = value - } - - public static func construct(from value: JSValue) -> AnyJSValueConvertible? { - self.init(boxed: ConcreteBox(value: value)) - } - - public init(nilLiteral _: ()) { - value = ConcreteBox(value: .null) - } - - public func jsValue() -> JSValue { - value.jsValue() - } -} From e19788aab5dc91b51656529e65faeb1cc46c7dd3 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Wed, 16 Sep 2020 12:41:58 -0400 Subject: [PATCH 19/24] Remove is* helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It’s better to use guard/if-let instead. --- Sources/JavaScriptKit/JSValue.swift | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 32bc53dbb..62f818ad4 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -55,31 +55,6 @@ public enum JSValue: Equatable { } } - public var isBoolean: Bool { - guard case .boolean = self else { return false } - return true - } - - public var isString: Bool { - guard case .string = self else { return false } - return true - } - - public var isNumber: Bool { - guard case .number = self else { return false } - return true - } - - public var isObject: Bool { - guard case .object = self else { return false } - return true - } - - public var isFunction: Bool { - guard case .function = self else { return false } - return true - } - /// Returns the `true` if this JS value is null. /// If not, returns `false`. public var isNull: Bool { From ff1f4ac73f71f3229de388639638d57f0b6466fa Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 17 Sep 2020 15:57:19 -0400 Subject: [PATCH 20/24] =?UTF-8?q?withCompatibleObject=20=E2=86=92=20unsafe?= =?UTF-8?q?lyWrapping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/JavaScriptKit/BasicObjects/JSArray.swift | 4 ++-- Sources/JavaScriptKit/BasicObjects/JSDate.swift | 2 +- Sources/JavaScriptKit/BasicObjects/JSError.swift | 2 +- Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift | 4 ++-- Sources/JavaScriptKit/JSBridgedType.swift | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 100feda37..39ed7c9b7 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -20,10 +20,10 @@ public class JSArray: JSBridgedClass { /// - Parameter object: A `JSObject` expected to be a JavaScript Array public convenience init?(_ jsObject: JSObject) { guard Self.isArray(jsObject) else { return nil } - self.init(withCompatibleObject: jsObject) + self.init(unsafelyWrapping: jsObject) } - public required init(withCompatibleObject jsObject: JSObject) { + public required init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } } diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index ec54595be..651a39d1b 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -39,7 +39,7 @@ public final class JSDate: JSBridgedClass { jsObject = Self.constructor.new(year, monthIndex, day, hours, minutes, seconds, milliseconds) } - public init(withCompatibleObject jsObject: JSObject) { + public init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } diff --git a/Sources/JavaScriptKit/BasicObjects/JSError.swift b/Sources/JavaScriptKit/BasicObjects/JSError.swift index ad728d640..8edd3f690 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSError.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSError.swift @@ -14,7 +14,7 @@ public final class JSError: Error, JSBridgedClass { jsObject = Self.constructor.new([message]) } - public init(withCompatibleObject jsObject: JSObject) { + public init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index de43c690c..371a09007 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -33,7 +33,7 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh jsObject = Element.typedArrayClass.new(length) } - required public init(withCompatibleObject jsObject: JSObject) { + required public init(unsafelyWrapping jsObject: JSObject) { self.jsObject = jsObject } @@ -48,7 +48,7 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayClass.id, ptr.baseAddress!, Int32(array.count), &resultObj) } - self.init(withCompatibleObject: JSObject(id: resultObj)) + self.init(unsafelyWrapping: JSObject(id: resultObj)) } /// Convenience initializer for `Sequence`. diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 79feaf36c..349677e6c 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -29,13 +29,13 @@ public protocol JSBridgedClass: JSBridgedType { /// Create an instannce wrapping the given JavaScript object. /// You may assume that `jsObject instanceof Self.constructor` - init(withCompatibleObject jsObject: JSObject) + init(unsafelyWrapping jsObject: JSObject) } extension JSBridgedClass { public var value: JSValue { jsObject.jsValue() } public init?(from value: JSValue) { guard let object = value.object, object.isInstanceOf(Self.constructor) else { return nil } - self.init(withCompatibleObject: object) + self.init(unsafelyWrapping: object) } } From c74e3d1cdd8b6a8fa7d2bada0eecdc9416b5aadf Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 17 Sep 2020 15:58:53 -0400 Subject: [PATCH 21/24] Apply suggestions from code review Co-authored-by: Max Desiatov --- Sources/JavaScriptKit/BasicObjects/JSArray.swift | 4 ++-- Sources/JavaScriptKit/JSBridgedType.swift | 2 +- Sources/JavaScriptKit/JSValueConvertible.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSArray.swift b/Sources/JavaScriptKit/BasicObjects/JSArray.swift index 39ed7c9b7..142145a34 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSArray.swift @@ -36,8 +36,8 @@ extension JSArray: RandomAccessCollection { } public class Iterator: IteratorProtocol { - let jsObject: JSObject - var index = 0 + private let jsObject: JSObject + private var index = 0 init(jsObject: JSObject) { self.jsObject = jsObject } diff --git a/Sources/JavaScriptKit/JSBridgedType.swift b/Sources/JavaScriptKit/JSBridgedType.swift index 349677e6c..d47be81ab 100644 --- a/Sources/JavaScriptKit/JSBridgedType.swift +++ b/Sources/JavaScriptKit/JSBridgedType.swift @@ -24,7 +24,7 @@ public protocol JSBridgedClass: JSBridgedType { static var constructor: JSFunction { get } /// The JavaScript object wrapped by this instance. - /// You may assume that `jsObject instanceof Self.constructor` + /// You may assume that `jsObject instanceof Self.constructor == true` var jsObject: JSObject { get } /// Create an instannce wrapping the given JavaScript object. diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index b9bdb5b06..0fdbc5bd9 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -266,6 +266,6 @@ extension Array where Element == JSValueConvertible { extension Array where Element: JSValueConvertible { func withRawJSValues(_ body: ([RawJSValue]) -> T) -> T { - Array.withRawJSValues(self)(body) + [JSValueConvertible].withRawJSValues(self)(body) } } From ed9f8d478108e5c7c6fbf969df814b490a898e1a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 17 Sep 2020 16:00:32 -0400 Subject: [PATCH 22/24] Rename Object and Array constructors --- Sources/JavaScriptKit/JSValueConvertible.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 0fdbc5bd9..e8008b9de 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -77,7 +77,8 @@ extension JSObject: JSValueCodable { // from `JSFunction` } -private let Object = JSObject.global.Object.function! +private let objectConstructor = JSObject.global.Object.function! +private let arrayConstructor = JSObject.global.Array.function! extension Dictionary where Value: JSValueConvertible, Key == String { public func jsValue() -> JSValue { @@ -87,7 +88,7 @@ extension Dictionary where Value: JSValueConvertible, Key == String { extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key == String { public func jsValue() -> JSValue { - let object = Object.new() + let object = objectConstructor.new() for (key, value) in self { object[key] = value.jsValue() } @@ -95,12 +96,11 @@ extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key } } -private let NativeJSArray = JSObject.global.Array.function! extension Dictionary: JSValueConstructible where Value: JSValueConstructible, Key == String { public static func construct(from value: JSValue) -> Self? { guard let objectRef = value.object, - let keys: [String] = Object.keys!(objectRef.jsValue()).fromJSValue() + let keys: [String] = objectConstructor.keys!(objectRef.jsValue()).fromJSValue() else { return nil } var entries = [(String, Value)]() @@ -143,7 +143,7 @@ extension Array where Element: JSValueConvertible { extension Array: JSValueConvertible where Element == JSValueConvertible { public func jsValue() -> JSValue { - let array = NativeJSArray.new(count) + let array = arrayConstructor.new(count) for (index, element) in enumerated() { array[index] = element.jsValue() } From 29a30e756b98aacffa7258e89e213fba01aadad1 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 17 Sep 2020 16:03:41 -0400 Subject: [PATCH 23/24] Document & fix the behavior of isInstanceOf --- Sources/JavaScriptKit/FundamentalObjects/JSObject.swift | 4 ++-- Sources/JavaScriptKit/JSValue.swift | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 1fb1ce0d9..11054bf14 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -69,9 +69,9 @@ public class JSObject: Equatable { set { setJSValue(this: self, index: Int32(index), value: newValue) } } - /// Return `true` if this object is an instance of the `constructor`. Return `false`, if not. + /// Return `true` if this value is an instance of the passed `constructor` function. /// - Parameter constructor: The constructor function to check. - /// - Returns: The result of `instanceof` in JavaScript environment. + /// - Returns: The result of `instanceof` in the JavaScript environment. public func isInstanceOf(_ constructor: JSFunction) -> Bool { _instanceof(id, constructor.id) } diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 62f818ad4..c388d0e04 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -155,16 +155,18 @@ public func setJSValue(this: JSObject, index: Int32, value: JSValue) { } extension JSValue { + /// Return `true` if this value is an instance of the passed `constructor` function. + /// Returns `false` for everything except objects and functions. + /// - Parameter constructor: The constructor function to check. + /// - Returns: The result of `instanceof` in the JavaScript environment. public func isInstanceOf(_ constructor: JSFunction) -> Bool { switch self { - case .boolean, .string, .number: + case .boolean, .string, .number, .null, .undefined: return false case let .object(ref): return ref.isInstanceOf(constructor) case let .function(ref): return ref.isInstanceOf(constructor) - case .null, .undefined: - fatalError() } } } From 767d05de679b7006ff0b81f5a86e23640d568160 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 17 Sep 2020 22:33:13 -0400 Subject: [PATCH 24/24] Fix indent --- Sources/JavaScriptKit/FundamentalObjects/JSObject.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift index 11054bf14..1342922f7 100644 --- a/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift +++ b/Sources/JavaScriptKit/FundamentalObjects/JSObject.swift @@ -69,7 +69,7 @@ public class JSObject: Equatable { set { setJSValue(this: self, index: Int32(index), value: newValue) } } - /// Return `true` if this value is an instance of the passed `constructor` function. + /// Return `true` if this value is an instance of the passed `constructor` function. /// - Parameter constructor: The constructor function to check. /// - Returns: The result of `instanceof` in the JavaScript environment. public func isInstanceOf(_ constructor: JSFunction) -> Bool {