Skip to content

Gracefully handle unavailable JSBridgedClass #190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Sources/JavaScriptKit/BasicObjects/JSArray.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// A wrapper around [the JavaScript Array 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 JSArray: JSBridgedClass {
public static let constructor = JSObject.global.Array.function!
public static let constructor = JSObject.global.Array.function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For types like these where the global must be available, how do you feel about declaring constructor as JSFunction!?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this, but then you have to add an explicit JSFunction? type signature on top, which means you still have to unwrap with ! or ? wherever this constructor is used. So after all, there's no benefit of unwrapping it and then wrapping back again into JSFunction? to satisfy the protocol requirement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I had remembered the Foo! and Foo? types being compatible with each other but it seems that isn’t the case (i.e. you can’t conform to a protocol with a Foo? requirement using a member of type Foo!). So this seems like the best approach. 👍


static func isArray(_ object: JSObject) -> Bool {
constructor.isArray!(object).boolean!
constructor!.isArray!(object).boolean!
}

public let jsObject: JSObject
Expand Down Expand Up @@ -94,8 +94,8 @@ private func getObjectValuesLength(_ object: JSObject) -> Int {
return Int(values.length.number!)
}

extension JSValue {
public var array: JSArray? {
public extension JSValue {
Copy link
Contributor Author

@MaxDesiatov MaxDesiatov May 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix applied by the formatter, as the rest of similar changes here.

var array: JSArray? {
object.flatMap(JSArray.init)
}
}
60 changes: 30 additions & 30 deletions Sources/JavaScriptKit/BasicObjects/JSDate.swift
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
/** 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
`getMonth`/`setMonth` etc accessor methods are converted to properties, but the rest of it matches
in the naming. Parts of the JavaScript `Date` API that are not consistent across browsers and JS
implementations are not exposed in a type-safe manner, you should access the underlying `jsObject`
property if you need those.
*/
/** 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
`getMonth`/`setMonth` etc accessor methods are converted to properties, but the rest of it matches
in the naming. Parts of the JavaScript `Date` API that are not consistent across browsers and JS
implementations are not exposed in a type-safe manner, you should access the underlying `jsObject`
property if you need those.
*/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix applied by the formatter.

public final class JSDate: JSBridgedClass {
/// The constructor function used to create new `Date` objects.
public static let constructor = JSObject.global.Date.function!
public static let constructor = JSObject.global.Date.function

/// The underlying JavaScript `Date` object.
public let jsObject: JSObject

/** Creates a new instance of the JavaScript `Date` class with a given amount of milliseconds
that passed since midnight 01 January 1970 UTC.
*/
that passed since midnight 01 January 1970 UTC.
*/
public init(millisecondsSinceEpoch: Double? = nil) {
if let milliseconds = millisecondsSinceEpoch {
jsObject = Self.constructor.new(milliseconds)
jsObject = Self.constructor!.new(milliseconds)
} else {
jsObject = Self.constructor.new()
jsObject = Self.constructor!.new()
}
}

/** According to the standard, `monthIndex` is zero-indexed, where `11` is December. `day`
represents a day of the month starting at `1`.
*/
/** According to the standard, `monthIndex` is zero-indexed, where `11` is December. `day`
represents a day of the month starting at `1`.
*/
public init(
year: Int,
monthIndex: Int,
Expand All @@ -36,7 +36,7 @@ public final class JSDate: JSBridgedClass {
seconds: Int = 0,
milliseconds: Int = 0
) {
jsObject = Self.constructor.new(year, monthIndex, day, hours, minutes, seconds, milliseconds)
jsObject = Self.constructor!.new(year, monthIndex, day, hours, minutes, seconds, milliseconds)
}

public init(unsafelyWrapping jsObject: JSObject) {
Expand Down Expand Up @@ -198,7 +198,7 @@ public final class JSDate: JSBridgedClass {
Int(jsObject.getTimezoneOffset!().number!)
}

/// Returns a string conforming to ISO 8601 that contains date and time, e.g.
/// Returns a string conforming to ISO 8601 that contains date and time, e.g.
/// `"2020-09-15T08:56:54.811Z"`.
public func toISOString() -> String {
jsObject.toISOString!().string!
Expand All @@ -214,25 +214,25 @@ public final class JSDate: JSBridgedClass {
jsObject.toLocaleTimeString!().string!
}

/** 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`.
*/
/** 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`.
*/
public func toUTCString() -> String {
jsObject.toUTCString!().string!
}

/** Number of milliseconds since midnight 01 January 1970 UTC to the present moment ignoring
leap seconds.
*/
/** Number of milliseconds since midnight 01 January 1970 UTC to the present moment ignoring
leap seconds.
*/
public static func now() -> Double {
constructor.now!().number!
constructor!.now!().number!
}

/** Number of milliseconds since midnight 01 January 1970 UTC to the given date ignoring leap
seconds.
*/
/** Number of milliseconds since midnight 01 January 1970 UTC to the given date ignoring leap
seconds.
*/
public func valueOf() -> Double {
jsObject.valueOf!().number!
}
Expand Down
12 changes: 6 additions & 6 deletions Sources/JavaScriptKit/BasicObjects/JSError.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/** 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.
*/
/** 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.
*/
public final class JSError: Error, JSBridgedClass {
/// The constructor function used to create new JavaScript `Error` objects.
public static let constructor = JSObject.global.Error.function!
public static let constructor = JSObject.global.Error.function

/// The underlying JavaScript `Error` object.
public let jsObject: JSObject

/// Creates a new instance of the JavaScript `Error` class with a given message.
public init(message: String) {
jsObject = Self.constructor.new([message])
jsObject = Self.constructor!.new([message])
}

public init(unsafelyWrapping jsObject: JSObject) {
Expand Down
66 changes: 34 additions & 32 deletions Sources/JavaScriptKit/BasicObjects/JSPromise.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/** A wrapper around [the JavaScript `Promise` class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)
that exposes its functions in a type-safe and Swifty way. The `JSPromise` API is generic over both
`Success` and `Failure` types, which improves compatibility with other statically-typed APIs such
as Combine. If you don't know the exact type of your `Success` value, you should use `JSValue`, e.g.
`JSPromise<JSValue, JSError>`. In the rare case, where you can't guarantee that the error thrown
is of actual JavaScript `Error` type, you should use `JSPromise<JSValue, JSValue>`.
that exposes its functions in a type-safe and Swifty way. The `JSPromise` API is generic over both
`Success` and `Failure` types, which improves compatibility with other statically-typed APIs such
as Combine. If you don't know the exact type of your `Success` value, you should use `JSValue`, e.g.
`JSPromise<JSValue, JSError>`. In the rare case, where you can't guarantee that the error thrown
is of actual JavaScript `Error` type, you should use `JSPromise<JSValue, JSValue>`.

This doesn't 100% match the JavaScript API, as `then` overload with two callbacks is not available.
It's impossible to unify success and failure types from both callbacks in a single returned promise
without type erasure. You should chain `then` and `catch` in those cases to avoid type erasure.
*/
This doesn't 100% match the JavaScript API, as `then` overload with two callbacks is not available.
It's impossible to unify success and failure types from both callbacks in a single returned promise
without type erasure. You should chain `then` and `catch` in those cases to avoid type erasure.
*/
public final class JSPromise: JSBridgedClass {
/// The underlying JavaScript `Promise` object.
public let jsObject: JSObject
Expand All @@ -18,35 +18,35 @@ public final class JSPromise: JSBridgedClass {
.object(jsObject)
}

public static var constructor: JSFunction {
public static var constructor: JSFunction? {
JSObject.global.Promise.function!
}

/// This private initializer assumes that the passed object is a JavaScript `Promise`
public init(unsafelyWrapping object: JSObject) {
self.jsObject = object
jsObject = object
}

/** Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `jsObject`
is not an instance of JavaScript `Promise`, this initializer will return `nil`.
*/
is not an instance of JavaScript `Promise`, this initializer will return `nil`.
*/
public convenience init?(_ jsObject: JSObject) {
self.init(from: jsObject)
}

/** Creates a new `JSPromise` instance from a given JavaScript `Promise` object. If `value`
is not an object and is not an instance of JavaScript `Promise`, this function will
return `nil`.
*/
is not an object and is not an instance of JavaScript `Promise`, this function will
return `nil`.
*/
public static func construct(from value: JSValue) -> Self? {
guard case let .object(jsObject) = value else { return nil }
return Self.init(jsObject)
return Self(jsObject)
}

/** Creates a new `JSPromise` instance from a given `resolver` closure. `resolver` takes
two closure that your code should call to either resolve or reject this `JSPromise` instance.
*/
public convenience init(resolver: @escaping (@escaping (Result<JSValue, JSValue>) -> ()) -> ()) {
two closure that your code should call to either resolve or reject this `JSPromise` instance.
*/
public convenience init(resolver: @escaping (@escaping (Result<JSValue, JSValue>) -> Void) -> Void) {
let closure = JSOneshotClosure { arguments in
// The arguments are always coming from the `Promise` constructor, so we should be
// safe to assume their type here
Expand All @@ -63,19 +63,19 @@ public final class JSPromise: JSBridgedClass {
}
return .undefined
}
self.init(unsafelyWrapping: Self.constructor.new(closure))
self.init(unsafelyWrapping: Self.constructor!.new(closure))
}

public static func resolve(_ value: ConvertibleToJSValue) -> JSPromise {
self.init(unsafelyWrapping: Self.constructor.resolve!(value).object!)
self.init(unsafelyWrapping: Self.constructor!.resolve!(value).object!)
}

public static func reject(_ reason: ConvertibleToJSValue) -> JSPromise {
self.init(unsafelyWrapping: Self.constructor.reject!(reason).object!)
self.init(unsafelyWrapping: Self.constructor!.reject!(reason).object!)
}

/** Schedules the `success` closure to be invoked on sucessful completion of `self`.
*/
*/
@discardableResult
public func then(success: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise {
let closure = JSOneshotClosure {
Expand All @@ -85,10 +85,12 @@ public final class JSPromise: JSBridgedClass {
}

/** Schedules the `success` closure to be invoked on sucessful completion of `self`.
*/
*/
@discardableResult
public func then(success: @escaping (JSValue) -> ConvertibleToJSValue,
failure: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise {
public func then(
success: @escaping (JSValue) -> ConvertibleToJSValue,
failure: @escaping (JSValue) -> ConvertibleToJSValue
) -> JSPromise {
let successClosure = JSOneshotClosure {
success($0[0]).jsValue
}
Expand All @@ -99,7 +101,7 @@ public final class JSPromise: JSBridgedClass {
}

/** Schedules the `failure` closure to be invoked on rejected completion of `self`.
*/
*/
@discardableResult
public func `catch`(failure: @escaping (JSValue) -> ConvertibleToJSValue) -> JSPromise {
let closure = JSOneshotClosure {
Expand All @@ -108,11 +110,11 @@ public final class JSPromise: JSBridgedClass {
return .init(unsafelyWrapping: jsObject.catch!(closure).object!)
}

/** Schedules the `failure` closure to be invoked on either successful or rejected completion of
`self`.
*/
/** Schedules the `failure` closure to be invoked on either successful or rejected completion of
`self`.
*/
@discardableResult
public func finally(successOrFailure: @escaping () -> ()) -> JSPromise {
public func finally(successOrFailure: @escaping () -> Void) -> JSPromise {
let closure = JSOneshotClosure { _ in
successOrFailure()
return .undefined
Expand Down
22 changes: 14 additions & 8 deletions Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValu
/// A wrapper around all JavaScript [TypedArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
/// classes that exposes their properties in a type-safe way.
public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement {
public class var constructor: JSFunction { Element.typedArrayClass }
public class var constructor: JSFunction? { Element.typedArrayClass }
public var jsObject: JSObject

public subscript(_ index: Int) -> Element {
get {
return Element.construct(from: jsObject[index])!
}
set {
self.jsObject[index] = newValue.jsValue
jsObject[index] = newValue.jsValue
}
}

Expand All @@ -30,22 +30,23 @@ public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral wh
///
/// - Parameter length: The number of elements that will be allocated.
public init(length: Int) {
jsObject = Self.constructor.new(length)
jsObject = Self.constructor!.new(length)
}

required public init(unsafelyWrapping jsObject: JSObject) {
public required init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}

required public convenience init(arrayLiteral elements: Element...) {
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]) {
let jsArrayRef = array.withUnsafeBufferPointer { ptr in
_create_typed_array(Self.constructor.id, ptr.baseAddress!, Int32(array.count))
_create_typed_array(Self.constructor!.id, ptr.baseAddress!, Int32(array.count))
}
self.init(unsafelyWrapping: JSObject(id: jsArrayRef))
}
Expand Down Expand Up @@ -80,7 +81,7 @@ public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral wh
let rawBuffer = malloc(bytesLength)!
defer { free(rawBuffer) }
_load_typed_array(jsObject.id, rawBuffer.assumingMemoryBound(to: UInt8.self))
let length = lengthInBytes / MemoryLayout<Element>.size
let length = lengthInBytes / MemoryLayout<Element>.size
let boundPtr = rawBuffer.bindMemory(to: Element.self, capacity: length)
let bufferPtr = UnsafeBufferPointer<Element>(start: boundPtr, count: length)
let result = try body(bufferPtr)
Expand All @@ -105,6 +106,7 @@ extension Int: TypedArrayElement {
public static var typedArrayClass: JSFunction =
valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: JSObject.global.Int32Array).function!
}

extension UInt: TypedArrayElement {
public static var typedArrayClass: JSFunction =
valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObject.global.Uint32Array).function!
Expand All @@ -113,31 +115,35 @@ extension UInt: TypedArrayElement {
extension Int8: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Int8Array.function!
}

extension UInt8: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Uint8Array.function!
}

public class JSUInt8ClampedArray: JSTypedArray<UInt8> {
public class override var constructor: JSFunction { JSObject.global.Uint8ClampedArray.function! }
override public class var constructor: JSFunction? { JSObject.global.Uint8ClampedArray.function! }
}

extension Int16: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Int16Array.function!
}

extension UInt16: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Uint16Array.function!
}

extension Int32: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Int32Array.function!
}

extension UInt32: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Uint32Array.function!
}

extension Float32: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Float32Array.function!
}

extension Float64: TypedArrayElement {
public static var typedArrayClass = JSObject.global.Float64Array.function!
}
Loading