From ca6a08b121404b4233240d47f99705bceb052ae2 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 13 Sep 2020 19:22:37 +0100 Subject: [PATCH 1/8] Basic JSDate implementation with a simple test --- .../Sources/PrimaryTests/main.swift | 24 +++ .../JavaScriptKit/BasicObjects/JSDate.swift | 203 ++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 Sources/JavaScriptKit/BasicObjects/JSDate.swift diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 292e6f3a5..1af0f1161 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -394,3 +394,27 @@ try test("TypedArray_Mutation") { } try expectEqual(toString(array.jsValue().object!), jsStringify(Array(0..<100))) } + +try test("Date") { + let date1 = JSDate() + print(date1.toISOString()) + let date2 = JSDate(millisecondsSinceEpoch: date1.now()) + + try expectEqual(date1.now(), date2.now()) + try expectEqual(date1.fullYear, date2.fullYear) + try expectEqual(date1.month, date2.month) + try expectEqual(date1.date, date2.date) + try expectEqual(date1.day, date2.day) + try expectEqual(date1.hours, date2.hours) + try expectEqual(date1.minutes, date2.minutes) + try expectEqual(date1.seconds, date2.seconds) + try expectEqual(date1.milliseconds, date2.milliseconds) + try expectEqual(date1.utcFullYear, date2.utcFullYear) + try expectEqual(date1.utcMonth, date2.utcMonth) + try expectEqual(date1.utcDate, date2.utcDate) + try expectEqual(date1.utcDay, date2.utcDay) + try expectEqual(date1.utcHours, date2.utcHours) + try expectEqual(date1.utcMinutes, date2.utcMinutes) + try expectEqual(date1.utcSeconds, date2.utcSeconds) + try expectEqual(date1.utcMilliseconds, date2.utcMilliseconds) +} diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift new file mode 100644 index 000000000..3c97456d9 --- /dev/null +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -0,0 +1,203 @@ +public final class JSDate { + private static let constructor = JSObject.global.Date.function! + private let ref: JSObject + + public init(millisecondsSinceEpoch: Int? = nil) { + if let milliseconds = millisecondsSinceEpoch { + ref = Self.constructor.new(milliseconds) + } else { + ref = 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`. + */ + public init( + year: Int, + monthIndex: Int, + day: Int = 1, + hours: Int = 0, + minutes: Int = 0, + seconds: Int = 0, + milliseconds: Int = 0 + ) { + ref = Self.constructor.new(year, monthIndex, day, hours, minutes, seconds, milliseconds) + } + + /// Year of this date in local time zone. + public var fullYear: Int { + get { + Int(ref.getFullYear.function!().number!) + } + set { + ref.setFullYear.function!(newValue) + } + } + + /// Month of this date in `0–11` range in local time zone + public var month: Int { + get { + Int(ref.getMonth.function!().number!) + } + set { + ref.setMonth.function!(newValue) + } + } + + /// The day of the month in `1..31` range in local time zone. + public var date: Int { + get { + Int(ref.getDate.function!().number!) + } + set { + ref.setDate.function!(newValue) + } + } + + /// The day of the week in `0..6` range in local time zone. + public var day: Int { + Int(ref.getDay.function!().number!) + } + + /// The amount of hours in this day from `0..23` range in local time zone. + public var hours: Int { + get { + Int(ref.getHours.function!().number!) + } + set { + ref.setHours.function!(newValue) + } + } + + /// The amount of minutes in this hours from `0..59` range in local time zone. + public var minutes: Int { + get { + Int(ref.getMinutes.function!().number!) + } + set { + ref.setMinutes.function!(newValue) + } + } + + /// The amount of seconds in this minute from `0..59` range in local time zone. + public var seconds: Int { + get { + Int(ref.getSeconds.function!().number!) + } + set { + ref.setSeconds.function!(newValue) + } + } + + /// The amount of milliseconds in this second `0..999` range in local time zone. + public var milliseconds: Int { + get { + Int(ref.getMilliseconds.function!().number!) + } + set { + ref.setMilliseconds.function!(newValue) + } + } + + /// Year of this date in the UTC time zone + public var utcFullYear: Int { + get { + Int(ref.getFullYear.function!().number!) + } + set { + ref.setFullYear.function!(newValue) + } + } + + /// Month of this date in `0–11` range in the UTC time zone + public var utcMonth: Int { + get { + Int(ref.getMonth.function!().number!) + } + set { + ref.setMonth.function!(newValue) + } + } + + /// The day of the month in `1..31` range in the UTC time zone + public var utcDate: Int { + get { + Int(ref.getDate.function!().number!) + } + set { + ref.setDate.function!(newValue) + } + } + + /// The day of the week in `0..6` range in the UTC time zone + public var utcDay: Int { + Int(ref.getDay.function!().number!) + } + + /// The amount of hours in this day from `0..23` range in the UTC time zone + public var utcHours: Int { + get { + Int(ref.getHours.function!().number!) + } + set { + ref.setHours.function!(newValue) + } + } + + /// The amount of minutes in this hours from `0..59` range in the UTC time zone + public var utcMinutes: Int { + get { + Int(ref.getMinutes.function!().number!) + } + set { + ref.setMinutes.function!(newValue) + } + } + + /// The amount of seconds in this minute from `0..59` range in the UTC time zone + public var utcSeconds: Int { + get { + Int(ref.getSeconds.function!().number!) + } + set { + ref.setSeconds.function!(newValue) + } + } + + /// The amount of milliseconds in this second `0..999` range in the UTC time zone + public var utcMilliseconds: Int { + get { + Int(ref.getMilliseconds.function!().number!) + } + set { + ref.setMilliseconds.function!(newValue) + } + } + + /// Offset in minutes between the local time zone and UTC + public var timezoneOffset: Int { + Int(ref.getTimezoneOffset.function!().number!) + } + + public func toISOString() -> String { + ref.toISOString.function!().string! + } + + public func toLocaleDateString() -> String { + ref.toLocaleDateString.function!().string! + } + + public func toLocaleTimeString() -> String { + ref.toLocaleTimeString.function!().string! + } + + public func toUTCString() -> String { + ref.toUTCString.function!().string! + } + + /// Number of seconds since epoch ignoring leap seconds + public func now() -> Int { + Int(ref.now.function!().number!) + } +} From 6546f3ab884e5d35e43c967ae4029de40fb46846 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 13 Sep 2020 21:11:40 +0100 Subject: [PATCH 2/8] Fix tests, address PR feedback --- .../Sources/PrimaryTests/main.swift | 8 +- .../JavaScriptKit/BasicObjects/JSDate.swift | 87 ++++++++++--------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 1af0f1161..323c90f0c 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -396,11 +396,11 @@ try test("TypedArray_Mutation") { } try test("Date") { - let date1 = JSDate() - print(date1.toISOString()) - let date2 = JSDate(millisecondsSinceEpoch: date1.now()) + let date1Milliseconds = JSDate.now() + let date1 = JSDate(millisecondsSinceEpoch: date1Milliseconds) + let date2 = JSDate(millisecondsSinceEpoch: date1.valueOf()) - try expectEqual(date1.now(), date2.now()) + try expectEqual(date1.valueOf(), date2.valueOf()) try expectEqual(date1.fullYear, date2.fullYear) try expectEqual(date1.month, date2.month) try expectEqual(date1.date, date2.date) diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index 3c97456d9..3a4242766 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -1,8 +1,8 @@ public final class JSDate { private static let constructor = JSObject.global.Date.function! - private let ref: JSObject + public let ref: JSObject - public init(millisecondsSinceEpoch: Int? = nil) { + public init(millisecondsSinceEpoch: Double? = nil) { if let milliseconds = millisecondsSinceEpoch { ref = Self.constructor.new(milliseconds) } else { @@ -28,176 +28,183 @@ public final class JSDate { /// Year of this date in local time zone. public var fullYear: Int { get { - Int(ref.getFullYear.function!().number!) + Int(ref.getFullYear!().number!) } set { - ref.setFullYear.function!(newValue) + _ = ref.setFullYear!(newValue) } } /// Month of this date in `0–11` range in local time zone public var month: Int { get { - Int(ref.getMonth.function!().number!) + Int(ref.getMonth!().number!) } set { - ref.setMonth.function!(newValue) + _ = ref.setMonth!(newValue) } } /// The day of the month in `1..31` range in local time zone. public var date: Int { get { - Int(ref.getDate.function!().number!) + Int(ref.getDate!().number!) } set { - ref.setDate.function!(newValue) + _ = ref.setDate!(newValue) } } /// The day of the week in `0..6` range in local time zone. public var day: Int { - Int(ref.getDay.function!().number!) + Int(ref.getDay!().number!) } /// The amount of hours in this day from `0..23` range in local time zone. public var hours: Int { get { - Int(ref.getHours.function!().number!) + Int(ref.getHours!().number!) } set { - ref.setHours.function!(newValue) + _ = ref.setHours!(newValue) } } /// The amount of minutes in this hours from `0..59` range in local time zone. public var minutes: Int { get { - Int(ref.getMinutes.function!().number!) + Int(ref.getMinutes!().number!) } set { - ref.setMinutes.function!(newValue) + _ = ref.setMinutes!(newValue) } } /// The amount of seconds in this minute from `0..59` range in local time zone. public var seconds: Int { get { - Int(ref.getSeconds.function!().number!) + Int(ref.getSeconds!().number!) } set { - ref.setSeconds.function!(newValue) + _ = ref.setSeconds!(newValue) } } /// The amount of milliseconds in this second `0..999` range in local time zone. public var milliseconds: Int { get { - Int(ref.getMilliseconds.function!().number!) + Int(ref.getMilliseconds!().number!) } set { - ref.setMilliseconds.function!(newValue) + _ = ref.setMilliseconds!(newValue) } } /// Year of this date in the UTC time zone public var utcFullYear: Int { get { - Int(ref.getFullYear.function!().number!) + Int(ref.getFullYear!().number!) } set { - ref.setFullYear.function!(newValue) + _ = ref.setFullYear!(newValue) } } /// Month of this date in `0–11` range in the UTC time zone public var utcMonth: Int { get { - Int(ref.getMonth.function!().number!) + Int(ref.getMonth!().number!) } set { - ref.setMonth.function!(newValue) + _ = ref.setMonth!(newValue) } } /// The day of the month in `1..31` range in the UTC time zone public var utcDate: Int { get { - Int(ref.getDate.function!().number!) + Int(ref.getDate!().number!) } set { - ref.setDate.function!(newValue) + _ = ref.setDate!(newValue) } } /// The day of the week in `0..6` range in the UTC time zone public var utcDay: Int { - Int(ref.getDay.function!().number!) + Int(ref.getDay!().number!) } /// The amount of hours in this day from `0..23` range in the UTC time zone public var utcHours: Int { get { - Int(ref.getHours.function!().number!) + Int(ref.getHours!().number!) } set { - ref.setHours.function!(newValue) + _ = ref.setHours!(newValue) } } /// The amount of minutes in this hours from `0..59` range in the UTC time zone public var utcMinutes: Int { get { - Int(ref.getMinutes.function!().number!) + Int(ref.getMinutes!().number!) } set { - ref.setMinutes.function!(newValue) + _ = ref.setMinutes!(newValue) } } /// The amount of seconds in this minute from `0..59` range in the UTC time zone public var utcSeconds: Int { get { - Int(ref.getSeconds.function!().number!) + Int(ref.getSeconds!().number!) } set { - ref.setSeconds.function!(newValue) + _ = ref.setSeconds!(newValue) } } /// The amount of milliseconds in this second `0..999` range in the UTC time zone public var utcMilliseconds: Int { get { - Int(ref.getMilliseconds.function!().number!) + Int(ref.getMilliseconds!().number!) } set { - ref.setMilliseconds.function!(newValue) + _ = ref.setMilliseconds!(newValue) } } /// Offset in minutes between the local time zone and UTC public var timezoneOffset: Int { - Int(ref.getTimezoneOffset.function!().number!) + Int(ref.getTimezoneOffset!().number!) } public func toISOString() -> String { - ref.toISOString.function!().string! + ref.toISOString!().string! } public func toLocaleDateString() -> String { - ref.toLocaleDateString.function!().string! + ref.toLocaleDateString!().string! } public func toLocaleTimeString() -> String { - ref.toLocaleTimeString.function!().string! + ref.toLocaleTimeString!().string! } public func toUTCString() -> String { - ref.toUTCString.function!().string! + ref.toUTCString!().string! } - /// Number of seconds since epoch ignoring leap seconds - public func now() -> Int { - Int(ref.now.function!().number!) + /// Number of seconds since midnight 01 January 1970 UTC to the present moment ignoring leap + /// seconds + public static func now() -> Double { + constructor.now!().number! + } + + /// Number of seconds since midnight 01 January 1970 UTC to the present moment ignoring leap + /// seconds + public func valueOf() -> Double { + ref.valueOf!().number! } } From e134515d49f1a0fdab068d62db6f52362c29e45c Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 13 Sep 2020 21:24:41 +0100 Subject: [PATCH 3/8] Fix UTC properties, refine test expectations --- .../Sources/PrimaryTests/main.swift | 13 ++++++++ .../JavaScriptKit/BasicObjects/JSDate.swift | 30 +++++++++---------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 323c90f0c..a948e33b5 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -417,4 +417,17 @@ try test("Date") { try expectEqual(date1.utcMinutes, date2.utcMinutes) try expectEqual(date1.utcSeconds, date2.utcSeconds) try expectEqual(date1.utcMilliseconds, date2.utcMilliseconds) + + let date3 = JSDate(millisecondsSinceEpoch: 0) + try expectEqual(date3.valueOf(), 0) + try expectEqual(date3.utcFullYear, 1970) + try expectEqual(date3.utcMonth, 0) + try expectEqual(date3.utcDate, 1) + // the epoch date was on Friday + try expectEqual(date3.utcDay, 4) + try expectEqual(date3.utcHours, 0) + try expectEqual(date3.utcMinutes, 0) + try expectEqual(date3.utcSeconds, 0) + try expectEqual(date3.utcMilliseconds, 0) + try expectEqual(date3.toISOString(), "1970-01-01T00:00:00.000Z") } diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index 3a4242766..cfe398f96 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -103,75 +103,75 @@ public final class JSDate { /// Year of this date in the UTC time zone public var utcFullYear: Int { get { - Int(ref.getFullYear!().number!) + Int(ref.getUTCFullYear!().number!) } set { - _ = ref.setFullYear!(newValue) + _ = ref.setUTCFullYear!(newValue) } } /// Month of this date in `0–11` range in the UTC time zone public var utcMonth: Int { get { - Int(ref.getMonth!().number!) + Int(ref.getUTCMonth!().number!) } set { - _ = ref.setMonth!(newValue) + _ = ref.setUTCMonth!(newValue) } } /// The day of the month in `1..31` range in the UTC time zone public var utcDate: Int { get { - Int(ref.getDate!().number!) + Int(ref.getUTCDate!().number!) } set { - _ = ref.setDate!(newValue) + _ = ref.setUTCDate!(newValue) } } /// The day of the week in `0..6` range in the UTC time zone public var utcDay: Int { - Int(ref.getDay!().number!) + Int(ref.getUTCDay!().number!) } /// The amount of hours in this day from `0..23` range in the UTC time zone public var utcHours: Int { get { - Int(ref.getHours!().number!) + Int(ref.getUTCHours!().number!) } set { - _ = ref.setHours!(newValue) + _ = ref.setUTCHours!(newValue) } } /// The amount of minutes in this hours from `0..59` range in the UTC time zone public var utcMinutes: Int { get { - Int(ref.getMinutes!().number!) + Int(ref.getUTCMinutes!().number!) } set { - _ = ref.setMinutes!(newValue) + _ = ref.setUTCMinutes!(newValue) } } /// The amount of seconds in this minute from `0..59` range in the UTC time zone public var utcSeconds: Int { get { - Int(ref.getSeconds!().number!) + Int(ref.getUTCSeconds!().number!) } set { - _ = ref.setSeconds!(newValue) + _ = ref.setUTCSeconds!(newValue) } } /// The amount of milliseconds in this second `0..999` range in the UTC time zone public var utcMilliseconds: Int { get { - Int(ref.getMilliseconds!().number!) + Int(ref.getUTCMilliseconds!().number!) } set { - _ = ref.setMilliseconds!(newValue) + _ = ref.setUTCMilliseconds!(newValue) } } From cbea311bec3a2a95a7fb98b5430f0b9399ead729 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 13 Sep 2020 21:37:07 +0100 Subject: [PATCH 4/8] Implement Equatable and Comparable on JSDate --- .../TestSuites/Sources/PrimaryTests/main.swift | 3 +++ Sources/JavaScriptKit/BasicObjects/JSDate.swift | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index a948e33b5..0eda3b3bf 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -417,6 +417,7 @@ try test("Date") { try expectEqual(date1.utcMinutes, date2.utcMinutes) try expectEqual(date1.utcSeconds, date2.utcSeconds) try expectEqual(date1.utcMilliseconds, date2.utcMilliseconds) + try expectEqual(date1, date2) let date3 = JSDate(millisecondsSinceEpoch: 0) try expectEqual(date3.valueOf(), 0) @@ -430,4 +431,6 @@ try test("Date") { try expectEqual(date3.utcSeconds, 0) try expectEqual(date3.utcMilliseconds, 0) try expectEqual(date3.toISOString(), "1970-01-01T00:00:00.000Z") + + try expectEqual(date3 < date1, true) } diff --git a/Sources/JavaScriptKit/BasicObjects/JSDate.swift b/Sources/JavaScriptKit/BasicObjects/JSDate.swift index cfe398f96..2b21c0bc7 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSDate.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSDate.swift @@ -208,3 +208,13 @@ public final class JSDate { ref.valueOf!().number! } } + +extension JSDate: Comparable { + public static func ==(lhs: JSDate, rhs: JSDate) -> Bool { + return lhs.valueOf() == rhs.valueOf() + } + + public static func <(lhs: JSDate, rhs: JSDate) -> Bool { + return lhs.valueOf() < rhs.valueOf() + } +} \ No newline at end of file From 5c3d0b04276c506124cc059fa53c8b8169ca42e0 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Sun, 13 Sep 2020 21:52:33 +0100 Subject: [PATCH 5/8] Add JSTimer implementation with tests --- .../Sources/PrimaryTests/main.swift | 30 +++++++++++++++++++ .../JavaScriptKit/BasicObjects/JSTimer.swift | 29 ++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 Sources/JavaScriptKit/BasicObjects/JSTimer.swift diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 0eda3b3bf..3e292bf18 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -434,3 +434,33 @@ try test("Date") { try expectEqual(date3 < date1, true) } + +// make the timers global to prevent early deallocation +var timeout: JSTimer? +var interval: JSTimer? + +try test("Timer") { + let start = JSDate().valueOf() + let timeoutMilliseconds = 5.0 + + timeout = JSTimer(millisecondsDelay: timeoutMilliseconds, isRepeating: false) { + // verify that at least `timeoutMilliseconds` passed since the `timeout` timer started + try! expectEqual(start + timeoutMilliseconds <= JSDate().valueOf(), true) + } + + var count = 0.0 + let maxCount = 5.0 + interval = JSTimer(millisecondsDelay: 5, isRepeating: true) { + // verify that at least `timeoutMilliseconds * count` passed since the `timeout` + // timer started + try! expectEqual(start + timeoutMilliseconds * count <= JSDate().valueOf(), true) + + guard count < maxCount else { + // stop the timer after `maxCount` reached + interval = nil + return + } + + count += 1 + } +} diff --git a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift new file mode 100644 index 000000000..a164b2c45 --- /dev/null +++ b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift @@ -0,0 +1,29 @@ +/** This timer type hides `setInterval`/`clearInterval` and `setTimeout`/`clearTimeout` +pairs of calls for you, and also holds a reference to an underlying `JSClosure` instance. +To invalidate a timer you should deallocate your corresponding `JSTimer` instance. +*/ +public final class JSTimer { + private let closure: JSClosure + private let isRepeating: Bool + private let value: JSValue + private let global = JSObject.global + + public init(millisecondsDelay: Double, isRepeating: Bool, callback: @escaping () -> ()) { + closure = JSClosure { _ in callback() } + self.isRepeating = isRepeating + if isRepeating { + value = global.setInterval.function!(closure, millisecondsDelay) + } else { + value = global.setTimeout.function!(closure, millisecondsDelay) + } + } + + deinit { + if isRepeating { + global.clearInterval.function!(value) + } else { + global.clearTimeout.function!(value) + } + closure.release() + } +} From 74a464af9e07a839bef11f114d44b82a2f31aae1 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 15 Sep 2020 09:48:17 +0100 Subject: [PATCH 6/8] Add doc comments --- Sources/JavaScriptKit/BasicObjects/JSTimer.swift | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift index a164b2c45..ecbc684df 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift @@ -3,12 +3,20 @@ pairs of calls for you, and also holds a reference to an underlying `JSClosure` To invalidate a timer you should deallocate your corresponding `JSTimer` instance. */ public final class JSTimer { + /// Indicates whether this timer instance calls its callback repeatedly at a given delay. + public let isRepeating: Bool + private let closure: JSClosure - private let isRepeating: Bool + + /** Node.js and browser APIs are slightly different. `setTimeout`/`setInterval` return an object + in Node.js, while browsers return a number. Fortunately, clearTimeout and clearInterval take + corresponding types as their arguments, and we can store either as JSValue, so we can treat both + cases uniformly. + */ private let value: JSValue private let global = JSObject.global - public init(millisecondsDelay: Double, isRepeating: Bool, callback: @escaping () -> ()) { + public init(millisecondsDelay: Double, isRepeating: Bool = false, callback: @escaping () -> ()) { closure = JSClosure { _ in callback() } self.isRepeating = isRepeating if isRepeating { @@ -18,6 +26,10 @@ public final class JSTimer { } } + /** Makes a corresponding `clearTimeout` or `clearInterval` call, depending on whether this timer + instance is repeating. The `closure` instance is released manually here, as it is required for + bridged closure instances. + */ deinit { if isRepeating { global.clearInterval.function!(value) From e3258e5452ef1ff5d3326de851490006757f1c97 Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 15 Sep 2020 12:18:56 +0100 Subject: [PATCH 7/8] Improve `JSTimer` doc comments --- Sources/JavaScriptKit/BasicObjects/JSTimer.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift index ecbc684df..5e30540ca 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift @@ -1,4 +1,7 @@ -/** This timer type hides `setInterval`/`clearInterval` and `setTimeout`/`clearTimeout` +/** This timer type hides [`setInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval) +/ [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) and +[`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) +/ [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) pairs of calls for you, and also holds a reference to an underlying `JSClosure` instance. To invalidate a timer you should deallocate your corresponding `JSTimer` instance. */ @@ -16,6 +19,15 @@ public final class JSTimer { private let value: JSValue private let global = JSObject.global + /** + Creates a new timer instance that calls `setInterval` or `setTimeout` JavaScript functions for you + under the hood. + - Parameters: + - millisecondsDelay: the amount of milliseconds before the `callback` closure is executed. + - isRepeating: when `true` the `callback` closure is executed repeatedly at given + `millisecondsDelay` intervals indefinitely until the timer is deallocated. + - callback: the closure to be executed after a given `millisecondsDelay` interval. + */ public init(millisecondsDelay: Double, isRepeating: Bool = false, callback: @escaping () -> ()) { closure = JSClosure { _ in callback() } self.isRepeating = isRepeating From bce5e4eb1004a32a333ed77d8ef67881bc01675a Mon Sep 17 00:00:00 2001 From: Max Desiatov Date: Tue, 15 Sep 2020 12:28:05 +0100 Subject: [PATCH 8/8] Refine `JSTimer` doc comment --- Sources/JavaScriptKit/BasicObjects/JSTimer.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift index 5e30540ca..1ba7b8e42 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTimer.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTimer.swift @@ -2,8 +2,13 @@ / [`clearInterval`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval) and [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) / [`clearTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout) -pairs of calls for you, and also holds a reference to an underlying `JSClosure` instance. -To invalidate a timer you should deallocate your corresponding `JSTimer` instance. +pairs of calls for you. It intentionally doesn't match the JavaScript API, as a special care is +needed to hold a reference to the timer closure and to call `JSClosure.release()` on it when the +timer is deallocated. As a user, you have to hold a reference to a `JSTimer` instance for it to stay +valid. The `JSTimer` API is also intentionally trivial, the timer is started right away, and the +only way to invalidate the timer is to bring the reference count of the `JSTimer` instance to zero, +either by storing the timer in an optional property and assigning `nil` to it or by deallocating the +object that owns it for invalidation. */ public final class JSTimer { /// Indicates whether this timer instance calls its callback repeatedly at a given delay.