Skip to content

Commit e4b50a4

Browse files
authored
Cleanup unused code, fix event handlers crash (#4)
Some weird names and typealiases were cleaned up (I don't think that `Int8ArrayOrInt16ArrayOrInt32ArrayOrUint8ArrayOrUint16ArrayOrUint32ArrayOrUint8ClampedArrayOrFloat32ArrayOrFloat64ArrayOrDataView` or `typealias VoidFunction = (() -> Void)` could be useful in any way). `GlobalEventHandlers` protocol which was only inherited by a single concrete type was removed with its extension becoming the extension of the corresponding class. I've also cleaned up event handlers to retain and release closures when needed, which makes our single basic test pass in browsers. Basic CI config to verify that is added here. A few other uses of `JSClosure` were updated accordingly to fix memory management. Unused `ClosureHandler` property wrapper is removed, leaving only `OptionalClosureHandler`. * Update to JavaScriptKit 0.9, add `Global` helpers * Cleanup unused code, fix event handlers crash * Add GitHub Actions workflow, assert in the test * Remove ClosureHandler, fix OptionalClosureHandler * Fix `MutationObserver` build error * Use `globalThis` instead of `global`
2 parents 44d49bc + 12bc9b7 commit e4b50a4

31 files changed

+668
-325
lines changed

.github/workflows/test.yml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Build and test
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
macos_test:
10+
runs-on: macos-11.0
11+
12+
steps:
13+
- uses: actions/checkout@v2
14+
- name: Run the test suite on macOS
15+
shell: bash
16+
run: |
17+
set -ex
18+
sudo xcode-select --switch /Applications/Xcode_12.3.app/Contents/Developer/
19+
20+
brew install swiftwasm/tap/carton
21+
22+
carton test --environment defaultBrowser

Sources/DOMKit/ECMAScript/Support.swift

+11-21
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,19 @@ public class ReadableStream: JSBridgedClass {
3838
}
3939
}
4040

41-
@propertyWrapper public struct ClosureHandler<ArgumentType: JSValueCompatible, ReturnType: JSValueCompatible> {
42-
41+
@propertyWrapper public final class OptionalClosureHandler<ArgumentType, ReturnType>
42+
where ArgumentType: JSValueCompatible, ReturnType: JSValueCompatible {
4343
let jsObject: JSObject
4444
let name: String
45+
var closure: JSClosure?
4546

4647
public init(jsObject: JSObject, name: String) {
4748
self.jsObject = jsObject
4849
self.name = name
4950
}
5051

51-
public var wrappedValue: (ArgumentType) -> ReturnType {
52-
get {
53-
{ arg in jsObject[name]!(arg).fromJSValue()! }
54-
}
55-
set {
56-
jsObject[name] = JSClosure { newValue($0[0].fromJSValue()!).jsValue() }.jsValue()
57-
}
58-
}
59-
}
60-
61-
@propertyWrapper public struct OptionalClosureHandler<ArgumentType: JSValueCompatible, ReturnType: JSValueCompatible> {
62-
63-
let jsObject: JSObject
64-
let name: String
65-
66-
public init(jsObject: JSObject, name: String) {
67-
self.jsObject = jsObject
68-
self.name = name
52+
deinit {
53+
closure?.release()
6954
}
7055

7156
public var wrappedValue: ((ArgumentType) -> ReturnType)? {
@@ -76,8 +61,13 @@ public class ReadableStream: JSBridgedClass {
7661
return { function($0.jsValue()).fromJSValue()! }
7762
}
7863
set {
64+
if let closure = closure {
65+
closure.release()
66+
}
7967
if let newValue = newValue {
80-
jsObject[name] = JSClosure { newValue($0[0].fromJSValue()!).jsValue() }.jsValue()
68+
let closure = JSClosure { newValue($0[0].fromJSValue()!).jsValue() }
69+
jsObject[name] = closure.jsValue()
70+
self.closure = closure
8171
} else {
8272
jsObject[name] = .null
8373
}

Sources/DOMKit/WebIDL/AddEventListenerOptions.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct AddEventListenerOptions: ExpressibleByDictionaryLiteral, JSBridged
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/AnyDocumentAndElementEventHandlers.swift

-16
This file was deleted.

Sources/DOMKit/WebIDL/AnyGlobalEventHandlers.swift

-16
This file was deleted.

Sources/DOMKit/WebIDL/ArrayBufferView.swift

+55-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,58 @@
55

66
import JavaScriptKit
77

8-
public typealias ArrayBufferView = Int8ArrayOrInt16ArrayOrInt32ArrayOrUint8ArrayOrUint16ArrayOrUint32ArrayOrUint8ClampedArrayOrFloat32ArrayOrFloat64ArrayOrDataView
8+
public enum ArrayBufferView: JSBridgedType {
9+
case int8Array(JSTypedArray<Int8>)
10+
case int16Array(JSTypedArray<Int16>)
11+
case int32Array(JSTypedArray<Int32>)
12+
case uint8Array(JSTypedArray<UInt8>)
13+
case uint16Array(JSTypedArray<UInt16>)
14+
case uint32Array(JSTypedArray<UInt32>)
15+
case uint8ClampedArray(JSTypedArray<UInt8>)
16+
case float32Array(JSTypedArray<Float>)
17+
case float64Array(JSTypedArray<Double>)
18+
case dataView(DataView)
19+
20+
public init?(from value: JSValue) {
21+
if let decoded: JSTypedArray<Int8> = value.fromJSValue() {
22+
self = .int8Array(decoded)
23+
} else if let decoded: JSTypedArray<Int16> = value.fromJSValue() {
24+
self = .int16Array(decoded)
25+
} else if let decoded: JSTypedArray<Int32> = value.fromJSValue() {
26+
self = .int32Array(decoded)
27+
} else if let decoded: JSTypedArray<UInt8> = value.fromJSValue() {
28+
self = .uint8Array(decoded)
29+
} else if let decoded: JSTypedArray<UInt16> = value.fromJSValue() {
30+
self = .uint16Array(decoded)
31+
} else if let decoded: JSTypedArray<UInt32> = value.fromJSValue() {
32+
self = .uint32Array(decoded)
33+
} else if let decoded: JSTypedArray<UInt8> = value.fromJSValue() {
34+
self = .uint8ClampedArray(decoded)
35+
} else if let decoded: JSTypedArray<Float> = value.fromJSValue() {
36+
self = .float32Array(decoded)
37+
} else if let decoded: JSTypedArray<Double> = value.fromJSValue() {
38+
self = .float64Array(decoded)
39+
} else if let decoded: DataView = value.fromJSValue() {
40+
self = .dataView(decoded)
41+
} else {
42+
return nil
43+
}
44+
}
45+
46+
public var value: JSValue { jsValue() }
47+
48+
public func jsValue() -> JSValue {
49+
switch self {
50+
case let .int8Array(v): return v.jsValue()
51+
case let .int16Array(v): return v.jsValue()
52+
case let .int32Array(v): return v.jsValue()
53+
case let .uint8Array(v): return v.jsValue()
54+
case let .uint16Array(v): return v.jsValue()
55+
case let .uint32Array(v): return v.jsValue()
56+
case let .uint8ClampedArray(v): return v.jsValue()
57+
case let .float32Array(v): return v.jsValue()
58+
case let .float64Array(v): return v.jsValue()
59+
case let .dataView(v): return v.jsValue()
60+
}
61+
}
62+
}

Sources/DOMKit/WebIDL/AssignedNodesOptions.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct AssignedNodesOptions: ExpressibleByDictionaryLiteral, JSBridgedTyp
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/BlobPropertyBag.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct BlobPropertyBag: ExpressibleByDictionaryLiteral, JSBridgedType {
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/CustomEventInit.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct CustomEventInit: ExpressibleByDictionaryLiteral, JSBridgedType {
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/ElementCreationOptions.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct ElementCreationOptions: ExpressibleByDictionaryLiteral, JSBridgedT
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/EndingType.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import JavaScriptKit
77

8-
public enum EndingType: String, JSValueCodable {
8+
public enum EndingType: String, JSValueCompatible {
99
public static func construct(from jsValue: JSValue) -> EndingType? {
1010
if let string = jsValue.string,
1111
let value = EndingType(rawValue: string)

Sources/DOMKit/WebIDL/EventInit.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct EventInit: ExpressibleByDictionaryLiteral, JSBridgedType {
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/EventListener.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
import JavaScriptKit
77

88
public protocol EventListener: JSBridgedType {
9-
func handleEvent(event: Event) -> Void
9+
func handleEvent(event: Event)
1010
}

Sources/DOMKit/WebIDL/EventListenerOptions.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct EventListenerOptions: ExpressibleByDictionaryLiteral, JSBridgedTyp
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/EventTarget.swift

+55-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,14 @@
66
import JavaScriptKit
77

88
public class EventTarget: JSBridgedClass {
9+
public struct Token {
10+
var type: String
11+
var index: Int
12+
}
913
public class var constructor: JSFunction { JSObject.global.EventTarget.function! }
1014

15+
var eventListeners = [String: [JSClosure]]()
16+
1117
public let jsObject: JSObject
1218

1319
public required init(unsafelyWrapping jsObject: JSObject) {
@@ -18,20 +24,63 @@ public class EventTarget: JSBridgedClass {
1824
self.init(unsafelyWrapping: EventTarget.constructor.new())
1925
}
2026

21-
public func addEventListener<EventListenerType: EventListener>(type: String, callback: EventListenerType?, options: AddEventListenerOptionsOrBool = [:]) -> Void {
27+
deinit {
28+
for (_, listeners) in eventListeners {
29+
for closure in listeners {
30+
closure.release()
31+
}
32+
}
33+
}
34+
35+
public func addEventListener<EventListenerType: EventListener>(
36+
type: String,
37+
options: AddEventListenerOptionsOrBool = [:],
38+
callback: EventListenerType) {
2239
_ = jsObject.addEventListener!(type.jsValue(), callback.jsValue(), options.jsValue())
2340
}
2441

25-
public func addEventListener(type: String, callback: ((Event) -> Void)?, options: AddEventListenerOptionsOrBool = [:]) {
26-
_ = jsObject.addEventListener!(type.jsValue(), callback == nil ? nil : JSClosure { callback!($0[0].fromJSValue()!) }, options.jsValue())
42+
public func addEventListener(
43+
type: String,
44+
options: AddEventListenerOptionsOrBool = [:],
45+
callback: @escaping (Event) -> ()
46+
) -> Token {
47+
let closure = JSClosure { callback($0[0].fromJSValue()!) }
48+
let token: Token
49+
let listeners: [JSClosure]
50+
if var existingListeners = eventListeners[type] {
51+
token = Token(type: type, index: existingListeners.count)
52+
existingListeners.append(closure)
53+
listeners = existingListeners
54+
} else {
55+
token = Token(type: type, index: 0)
56+
listeners = [closure]
57+
}
58+
eventListeners[type] = listeners
59+
60+
_ = jsObject.addEventListener!(type.jsValue(), closure, options.jsValue())
61+
62+
return token
2763
}
2864

29-
public func removeEventListener<EventListenerType: EventListener>(type: String, callback: EventListenerType?, options: EventListenerOptionsOrBool = [:]) -> Void {
65+
public func removeEventListener<EventListenerType: EventListener>(
66+
type: String,
67+
options: EventListenerOptionsOrBool = [:],
68+
callback: EventListenerType
69+
) {
3070
_ = jsObject.removeEventListener!(type.jsValue(), callback.jsValue(), options.jsValue())
3171
}
3272

33-
public func removeEventListener(type: String, callback: ((Event) -> Void)?, options: EventListenerOptionsOrBool = [:]) {
34-
_ = jsObject.removeEventListener!(type.jsValue(), callback == nil ? nil : JSClosure { callback!($0[0].fromJSValue()!) }, options.jsValue())
73+
public func removeEventListener(
74+
type: String,
75+
token: Token,
76+
options: EventListenerOptionsOrBool = [:]
77+
) {
78+
guard var listeners = eventListeners[type] else { return }
79+
80+
let closure = listeners[token.index]
81+
_ = jsObject.removeEventListener!(type.jsValue(), closure, options.jsValue())
82+
listeners.remove(at: token.index)
83+
closure.release()
3584
}
3685

3786
public func dispatchEvent(event: Event) -> Bool {

Sources/DOMKit/WebIDL/FilePropertyBag.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct FilePropertyBag: ExpressibleByDictionaryLiteral, JSBridgedType {
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

Sources/DOMKit/WebIDL/FocusOptions.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ public struct FocusOptions: ExpressibleByDictionaryLiteral, JSBridgedType {
1212

1313
private let dictionary: [String: JSValue]
1414

15-
public init(uniqueKeysWithValues elements: [(Key, JSValueConvertible)]) {
15+
public init(uniqueKeysWithValues elements: [(Key, ConvertibleToJSValue)]) {
1616
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
1717
}
1818

19-
public init(dictionaryLiteral elements: (Key, JSValueConvertible)...) {
19+
public init(dictionaryLiteral elements: (Key, ConvertibleToJSValue)...) {
2020
dictionary = Dictionary(uniqueKeysWithValues: elements.map { ($0.0.rawValue, $0.1.jsValue()) })
2121
}
2222

0 commit comments

Comments
 (0)