@thelacanians/vue-native-cli 0.4.15 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +329 -15
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Bridge/NativeBridge.kt +118 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Components/Factories/VViewFactory.kt +178 -1
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/GeneratedModuleRegistry.kt +28 -0
- package/native/android/VueNativeCore/src/main/kotlin/com/vuenative/core/Modules/NativeModuleRegistry.kt +3 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ComponentFactoryTest.kt +674 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/ErrorOverlayViewTest.kt +183 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/EventThrottleTest.kt +203 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/HotReloadManagerTest.kt +162 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/JSPolyfillsTest.kt +153 -0
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeBridgeTest.kt +6 -3
- package/native/android/VueNativeCore/src/test/kotlin/com/vuenative/core/NativeModuleTest.kt +475 -0
- package/native/android/gradle.properties +1 -0
- package/native/android/gradlew +1 -1
- package/native/ios/VueNativeCore/Package.swift +1 -1
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/EventThrottle.swift +1 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Bridge/NativeBridge.swift +143 -5
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VTextFactory.swift +43 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Components/Factories/VViewFactory.swift +116 -4
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Helpers/GestureWrapper.swift +100 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/GeneratedModuleRegistry.swift +28 -0
- package/native/ios/VueNativeCore/Sources/VueNativeCore/Modules/NativeModuleRegistry.swift +3 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/CertificatePinningTests.swift +190 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/ComponentFactoryTests.swift +585 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/EventThrottleTests.swift +161 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/HotReloadManagerTests.swift +88 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/JSPolyfillsTests.swift +319 -0
- package/native/ios/VueNativeCore/Tests/VueNativeCoreTests/NativeModuleTests.swift +400 -0
- package/native/macos/VueNativeMacOS/Package.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/ErrorOverlayView.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/EventThrottle.swift +58 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/HotReloadManager.swift +153 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSPolyfills.swift +696 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/JSRuntime.swift +347 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/NativeBridge.swift +877 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Bridge/VueNativeWindowController.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/ComponentRegistry.swift +209 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActionSheetFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VActivityIndicatorFactory.swift +85 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VAlertDialogFactory.swift +132 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VButtonFactory.swift +83 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VCheckboxFactory.swift +108 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VDropdownFactory.swift +155 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VImageFactory.swift +270 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VInputFactory.swift +257 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VKeyboardAvoidingFactory.swift +22 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VListFactory.swift +324 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VModalFactory.swift +231 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VOutlineViewFactory.swift +276 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPickerFactory.swift +134 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VPressableFactory.swift +120 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VProgressBarFactory.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRadioFactory.swift +193 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VRefreshControlFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSafeAreaFactory.swift +46 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VScrollViewFactory.swift +190 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSectionListFactory.swift +374 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSegmentedControlFactory.swift +125 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSliderFactory.swift +131 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSplitViewFactory.swift +215 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VStatusBarFactory.swift +25 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VSwitchFactory.swift +92 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VTextFactory.swift +336 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VToolbarFactory.swift +212 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VVideoFactory.swift +245 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VViewFactory.swift +314 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/Factories/VWebViewFactory.swift +162 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Components/NativeComponentFactory.swift +54 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/ClickableView.swift +100 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/Extensions.swift +23 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/GestureWrapper.swift +183 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Helpers/NSColor+Hex.swift +78 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/FlippedView.swift +19 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Layout/LayoutNode.swift +493 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AnimationModule.swift +354 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/AppStateModule.swift +62 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/BiometryModule.swift +60 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/CameraModule.swift +167 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ClipboardModule.swift +34 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DeviceInfoModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/DragDropModule.swift +50 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/FileDialogModule.swift +86 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/HapticsModule.swift +42 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/KeyboardModule.swift +28 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/LinkingModule.swift +49 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/MenuModule.swift +95 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NativeModuleRegistry.swift +63 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/NotificationsModule.swift +112 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/PermissionsModule.swift +149 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/ShareModule.swift +37 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Modules/WindowModule.swift +71 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Resources/vue-native-placeholder.js +2 -0
- package/native/macos/VueNativeMacOS/Sources/VueNativeMacOS/Styling/StyleEngine.swift +885 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/ComponentFactoryTests.swift +80 -0
- package/native/macos/VueNativeMacOS/Tests/VueNativeMacOSTests/VueNativeMacOSTests.swift +149 -0
- package/native/shared/VueNativeShared/AGENTS.md +129 -0
- package/native/shared/VueNativeShared/Package.swift +14 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/CertificatePinning.swift +134 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/EventThrottle.swift +78 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/HotReloadManager.swift +162 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/JSRuntime.swift +412 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AsyncStorageModule.swift +68 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/AudioModule.swift +359 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/DatabaseModule.swift +259 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/FileSystemModule.swift +233 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/GeolocationModule.swift +156 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/NetworkModule.swift +59 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/PerformanceModule.swift +113 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/SecureStorageModule.swift +119 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/Modules/WebSocketModule.swift +212 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeEventDispatcher.swift +6 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModule.swift +26 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/NativeModuleRegistry.swift +37 -0
- package/native/shared/VueNativeShared/Sources/VueNativeShared/SharedJSPolyfills.swift +673 -0
- package/native/shared/VueNativeShared/Tests/VueNativeSharedTests/VueNativeSharedTests.swift +44 -0
- package/package.json +8 -2
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
import JavaScriptCore
|
|
2
|
+
import AppKit
|
|
3
|
+
import Security
|
|
4
|
+
|
|
5
|
+
/// Registers browser-like APIs in JSContext that the Vue runtime and application code expect.
|
|
6
|
+
/// macOS port of the iOS JSPolyfills. All callbacks execute on the JS queue unless noted.
|
|
7
|
+
enum JSPolyfills {
|
|
8
|
+
|
|
9
|
+
// MARK: - Timer storage
|
|
10
|
+
|
|
11
|
+
private struct TimerEntry {
|
|
12
|
+
let timer: Timer
|
|
13
|
+
let callbackRef: JSManagedValue?
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private static let stateQueue = DispatchQueue(label: "com.vuenative.macos.polyfills.state")
|
|
17
|
+
private static var timers: [String: TimerEntry] = [:]
|
|
18
|
+
private static var nextTimerId: Int = 1
|
|
19
|
+
|
|
20
|
+
// MARK: - RAF storage
|
|
21
|
+
|
|
22
|
+
private static var displayLink: CVDisplayLink?
|
|
23
|
+
private static var displayLinkSource: DispatchSourceUserDataAdd?
|
|
24
|
+
private static var displayLinkSourcePtr: UnsafeMutableRawPointer?
|
|
25
|
+
private static var rafCallbacks: [String: JSValue] = [:]
|
|
26
|
+
private static var nextRafId: Int = 1
|
|
27
|
+
private static weak var rafRuntime: JSRuntime?
|
|
28
|
+
|
|
29
|
+
// MARK: - Reset
|
|
30
|
+
|
|
31
|
+
static func reset() {
|
|
32
|
+
let oldTimers: [String: TimerEntry] = stateQueue.sync {
|
|
33
|
+
let snapshot = timers
|
|
34
|
+
timers.removeAll()
|
|
35
|
+
rafCallbacks.removeAll()
|
|
36
|
+
nextTimerId = 1
|
|
37
|
+
nextRafId = 1
|
|
38
|
+
return snapshot
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
DispatchQueue.main.async {
|
|
42
|
+
for (_, entry) in oldTimers {
|
|
43
|
+
entry.timer.invalidate()
|
|
44
|
+
}
|
|
45
|
+
if let link = displayLink {
|
|
46
|
+
CVDisplayLinkStop(link)
|
|
47
|
+
}
|
|
48
|
+
displayLink = nil
|
|
49
|
+
displayLinkSource?.cancel()
|
|
50
|
+
displayLinkSource = nil
|
|
51
|
+
if let ptr = displayLinkSourcePtr {
|
|
52
|
+
Unmanaged<AnyObject>.fromOpaque(ptr).release()
|
|
53
|
+
displayLinkSourcePtr = nil
|
|
54
|
+
}
|
|
55
|
+
rafRuntime = nil
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// MARK: - Registration
|
|
60
|
+
|
|
61
|
+
static func register(in runtime: JSRuntime) {
|
|
62
|
+
guard let context = runtime.context else { return }
|
|
63
|
+
|
|
64
|
+
registerConsole(in: context)
|
|
65
|
+
registerTimers(in: context, runtime: runtime)
|
|
66
|
+
registerMicrotask(in: context)
|
|
67
|
+
registerRAF(in: context, runtime: runtime)
|
|
68
|
+
registerPerformance(in: context, runtime: runtime)
|
|
69
|
+
registerGlobalThis(in: context)
|
|
70
|
+
registerFetch(in: context, runtime: runtime)
|
|
71
|
+
registerBase64(in: context)
|
|
72
|
+
registerTextEncoding(in: context)
|
|
73
|
+
registerURL(in: context)
|
|
74
|
+
registerCrypto(in: context)
|
|
75
|
+
registerBridgeStubs(in: context)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// MARK: - Bridge callback stubs
|
|
79
|
+
|
|
80
|
+
private static func registerBridgeStubs(in context: JSContext) {
|
|
81
|
+
context.evaluateScript("""
|
|
82
|
+
if (typeof __VN_handleGlobalEvent === 'undefined') {
|
|
83
|
+
globalThis.__VN_handleGlobalEvent = function() {};
|
|
84
|
+
}
|
|
85
|
+
if (typeof __VN_handleEvent === 'undefined') {
|
|
86
|
+
globalThis.__VN_handleEvent = function() {};
|
|
87
|
+
}
|
|
88
|
+
if (typeof __VN_resolveCallback === 'undefined') {
|
|
89
|
+
globalThis.__VN_resolveCallback = function() {};
|
|
90
|
+
}
|
|
91
|
+
""")
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// MARK: - console.log / warn / error
|
|
95
|
+
|
|
96
|
+
private static func registerConsole(in context: JSContext) {
|
|
97
|
+
context.evaluateScript("var console = {};")
|
|
98
|
+
|
|
99
|
+
func formatArgs() -> String {
|
|
100
|
+
guard let args = JSContext.currentArguments() as? [JSValue], !args.isEmpty else {
|
|
101
|
+
return ""
|
|
102
|
+
}
|
|
103
|
+
return args.map { value in
|
|
104
|
+
value.isUndefined ? "undefined" : (value.toString() ?? "null")
|
|
105
|
+
}.joined(separator: " ")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let consoleLog: @convention(block) () -> Void = {
|
|
109
|
+
NSLog("[VueNative LOG] %@", formatArgs())
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let consoleWarn: @convention(block) () -> Void = {
|
|
113
|
+
NSLog("[VueNative WARN] %@", formatArgs())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let consoleError: @convention(block) () -> Void = {
|
|
117
|
+
NSLog("[VueNative ERROR] %@", formatArgs())
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let consoleDebug: @convention(block) () -> Void = {
|
|
121
|
+
NSLog("[VueNative DEBUG] %@", formatArgs())
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let consoleInfo: @convention(block) () -> Void = {
|
|
125
|
+
NSLog("[VueNative INFO] %@", formatArgs())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
guard let consoleObj = context.objectForKeyedSubscript("console") else {
|
|
129
|
+
NSLog("[VueNative macOS] Warning: failed to get console object")
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
consoleObj.setObject(consoleLog, forKeyedSubscript: "log" as NSString)
|
|
133
|
+
consoleObj.setObject(consoleWarn, forKeyedSubscript: "warn" as NSString)
|
|
134
|
+
consoleObj.setObject(consoleError, forKeyedSubscript: "error" as NSString)
|
|
135
|
+
consoleObj.setObject(consoleDebug, forKeyedSubscript: "debug" as NSString)
|
|
136
|
+
consoleObj.setObject(consoleInfo, forKeyedSubscript: "info" as NSString)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// MARK: - setTimeout / clearTimeout / setInterval / clearInterval
|
|
140
|
+
|
|
141
|
+
private static func registerTimers(in context: JSContext, runtime: JSRuntime) {
|
|
142
|
+
|
|
143
|
+
let setTimeout: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] callback, delay in
|
|
144
|
+
guard let runtime = runtime, let context = runtime.context else {
|
|
145
|
+
return JSValue(nullIn: JSContext.current())
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let delayMs = delay.isUndefined ? 0.0 : delay.toDouble()
|
|
149
|
+
let timerId: String = stateQueue.sync {
|
|
150
|
+
let id = String(nextTimerId)
|
|
151
|
+
nextTimerId += 1
|
|
152
|
+
return id
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let callbackRef = JSManagedValue(value: callback)
|
|
156
|
+
context.virtualMachine.addManagedReference(callbackRef, withOwner: context)
|
|
157
|
+
|
|
158
|
+
DispatchQueue.main.async {
|
|
159
|
+
let timer = Timer.scheduledTimer(withTimeInterval: max(delayMs / 1000.0, 0.001), repeats: false) { [weak runtime] _ in
|
|
160
|
+
guard let runtime = runtime else { return }
|
|
161
|
+
runtime.jsQueue.async { [weak runtime] in
|
|
162
|
+
guard let runtime = runtime, let context = runtime.context else { return }
|
|
163
|
+
let stillActive: Bool = stateQueue.sync {
|
|
164
|
+
guard timers[timerId] != nil else { return false }
|
|
165
|
+
timers.removeValue(forKey: timerId)
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
guard stillActive else { return }
|
|
169
|
+
if let cb = callbackRef?.value, !cb.isUndefined {
|
|
170
|
+
cb.call(withArguments: [])
|
|
171
|
+
context.evaluateScript("void 0;")
|
|
172
|
+
}
|
|
173
|
+
context.virtualMachine.removeManagedReference(callbackRef, withOwner: context)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
RunLoop.main.add(timer, forMode: .common)
|
|
177
|
+
stateQueue.async {
|
|
178
|
+
if timers[timerId] == nil {
|
|
179
|
+
timers[timerId] = TimerEntry(timer: timer, callbackRef: callbackRef)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return JSValue(object: timerId, in: context)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
let clearTimeout: @convention(block) (JSValue) -> Void = { [weak runtime] timerId in
|
|
188
|
+
guard let runtime = runtime, let context = runtime.context else { return }
|
|
189
|
+
guard let id = timerId.toString() else { return }
|
|
190
|
+
let entry: TimerEntry? = stateQueue.sync {
|
|
191
|
+
return timers.removeValue(forKey: id)
|
|
192
|
+
}
|
|
193
|
+
if let entry = entry {
|
|
194
|
+
context.virtualMachine.removeManagedReference(entry.callbackRef, withOwner: context)
|
|
195
|
+
DispatchQueue.main.async {
|
|
196
|
+
entry.timer.invalidate()
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let setInterval: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] callback, delay in
|
|
202
|
+
guard let runtime = runtime, let context = runtime.context else {
|
|
203
|
+
return JSValue(nullIn: JSContext.current())
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let delayMs = delay.isUndefined ? 0.0 : delay.toDouble()
|
|
207
|
+
let timerId: String = stateQueue.sync {
|
|
208
|
+
let id = String(nextTimerId)
|
|
209
|
+
nextTimerId += 1
|
|
210
|
+
return id
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let callbackRef = JSManagedValue(value: callback)
|
|
214
|
+
context.virtualMachine.addManagedReference(callbackRef, withOwner: context)
|
|
215
|
+
|
|
216
|
+
DispatchQueue.main.async {
|
|
217
|
+
let timer = Timer.scheduledTimer(withTimeInterval: max(delayMs / 1000.0, 0.001), repeats: true) { [weak runtime] _ in
|
|
218
|
+
guard let runtime = runtime else { return }
|
|
219
|
+
runtime.jsQueue.async { [weak runtime] in
|
|
220
|
+
guard let runtime = runtime, let context = runtime.context else { return }
|
|
221
|
+
let stillActive: Bool = stateQueue.sync { timers[timerId] != nil }
|
|
222
|
+
guard stillActive else { return }
|
|
223
|
+
if let cb = callbackRef?.value, !cb.isUndefined {
|
|
224
|
+
cb.call(withArguments: [])
|
|
225
|
+
context.evaluateScript("void 0;")
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
RunLoop.main.add(timer, forMode: .common)
|
|
230
|
+
stateQueue.async {
|
|
231
|
+
timers[timerId] = TimerEntry(timer: timer, callbackRef: callbackRef)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return JSValue(object: timerId, in: context)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
let clearInterval: @convention(block) (JSValue) -> Void = { [weak runtime] timerId in
|
|
239
|
+
guard let runtime = runtime, let context = runtime.context else { return }
|
|
240
|
+
guard let id = timerId.toString() else { return }
|
|
241
|
+
let entry: TimerEntry? = stateQueue.sync {
|
|
242
|
+
return timers.removeValue(forKey: id)
|
|
243
|
+
}
|
|
244
|
+
if let entry = entry {
|
|
245
|
+
context.virtualMachine.removeManagedReference(entry.callbackRef, withOwner: context)
|
|
246
|
+
DispatchQueue.main.async {
|
|
247
|
+
entry.timer.invalidate()
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
context.setObject(setTimeout, forKeyedSubscript: "setTimeout" as NSString)
|
|
253
|
+
context.setObject(clearTimeout, forKeyedSubscript: "clearTimeout" as NSString)
|
|
254
|
+
context.setObject(setInterval, forKeyedSubscript: "setInterval" as NSString)
|
|
255
|
+
context.setObject(clearInterval, forKeyedSubscript: "clearInterval" as NSString)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// MARK: - queueMicrotask
|
|
259
|
+
|
|
260
|
+
private static func registerMicrotask(in context: JSContext) {
|
|
261
|
+
context.evaluateScript("""
|
|
262
|
+
function queueMicrotask(callback) {
|
|
263
|
+
Promise.resolve().then(callback);
|
|
264
|
+
}
|
|
265
|
+
""")
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// MARK: - requestAnimationFrame / cancelAnimationFrame
|
|
269
|
+
|
|
270
|
+
private static func registerRAF(in context: JSContext, runtime: JSRuntime) {
|
|
271
|
+
|
|
272
|
+
let requestAnimationFrame: @convention(block) (JSValue) -> JSValue = { [weak runtime] callback in
|
|
273
|
+
guard let runtime = runtime, let context = runtime.context else {
|
|
274
|
+
return JSValue(nullIn: JSContext.current())
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let rafId: String = stateQueue.sync {
|
|
278
|
+
let id = String(nextRafId)
|
|
279
|
+
nextRafId += 1
|
|
280
|
+
rafCallbacks[id] = callback
|
|
281
|
+
return id
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Ensure CVDisplayLink is running
|
|
285
|
+
DispatchQueue.main.async {
|
|
286
|
+
if displayLink == nil {
|
|
287
|
+
rafRuntime = runtime
|
|
288
|
+
startDisplayLink(runtime: runtime)
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return JSValue(object: rafId, in: context)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let cancelAnimationFrame: @convention(block) (JSValue) -> Void = { [weak runtime] rafId in
|
|
296
|
+
_ = runtime
|
|
297
|
+
guard let id = rafId.toString() else { return }
|
|
298
|
+
stateQueue.async { rafCallbacks.removeValue(forKey: id) }
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
context.setObject(requestAnimationFrame, forKeyedSubscript: "requestAnimationFrame" as NSString)
|
|
302
|
+
context.setObject(cancelAnimationFrame, forKeyedSubscript: "cancelAnimationFrame" as NSString)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/// Start a CVDisplayLink for requestAnimationFrame on macOS.
|
|
306
|
+
/// CVDisplayLink fires on a background thread, so we use a DispatchSource
|
|
307
|
+
/// to coalesce signals and dispatch to the JS queue.
|
|
308
|
+
private static func startDisplayLink(runtime: JSRuntime) {
|
|
309
|
+
var link: CVDisplayLink?
|
|
310
|
+
CVDisplayLinkCreateWithActiveCGDisplays(&link)
|
|
311
|
+
guard let link = link else { return }
|
|
312
|
+
|
|
313
|
+
let source = DispatchSourceMakeUserDataAdd()
|
|
314
|
+
displayLinkSource = source
|
|
315
|
+
|
|
316
|
+
source.setEventHandler { [weak runtime] in
|
|
317
|
+
guard let runtime = runtime else { return }
|
|
318
|
+
let timestamp = CACurrentMediaTime() * 1000.0
|
|
319
|
+
fireRAFCallbacks(runtime: runtime, timestamp: timestamp)
|
|
320
|
+
}
|
|
321
|
+
source.resume()
|
|
322
|
+
|
|
323
|
+
// Wrap the DispatchSource in an Unmanaged pointer so the C callback
|
|
324
|
+
// can access it without capturing context (C function pointers cannot
|
|
325
|
+
// capture Swift closures).
|
|
326
|
+
let sourcePtr = Unmanaged.passRetained(source as AnyObject).toOpaque()
|
|
327
|
+
displayLinkSourcePtr = sourcePtr
|
|
328
|
+
|
|
329
|
+
CVDisplayLinkSetOutputCallback(link, { _, _, _, _, _, userInfo -> CVReturn in
|
|
330
|
+
guard let userInfo = userInfo else { return kCVReturnSuccess }
|
|
331
|
+
let src = Unmanaged<AnyObject>.fromOpaque(userInfo).takeUnretainedValue()
|
|
332
|
+
(src as? DispatchSourceUserDataAdd)?.add(data: 1)
|
|
333
|
+
return kCVReturnSuccess
|
|
334
|
+
}, sourcePtr)
|
|
335
|
+
|
|
336
|
+
CVDisplayLinkStart(link)
|
|
337
|
+
displayLink = link
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Fire all pending RAF callbacks. RAF is one-shot.
|
|
341
|
+
fileprivate static func fireRAFCallbacks(runtime: JSRuntime, timestamp: Double) {
|
|
342
|
+
let callbacks: [String: JSValue] = stateQueue.sync {
|
|
343
|
+
let snapshot = rafCallbacks
|
|
344
|
+
rafCallbacks.removeAll()
|
|
345
|
+
return snapshot
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
guard !callbacks.isEmpty else {
|
|
349
|
+
// No pending callbacks -- stop the display link
|
|
350
|
+
DispatchQueue.main.async {
|
|
351
|
+
if let link = displayLink {
|
|
352
|
+
CVDisplayLinkStop(link)
|
|
353
|
+
}
|
|
354
|
+
displayLink = nil
|
|
355
|
+
displayLinkSource?.cancel()
|
|
356
|
+
displayLinkSource = nil
|
|
357
|
+
}
|
|
358
|
+
return
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
runtime.jsQueue.async { [weak runtime] in
|
|
362
|
+
guard let runtime = runtime, let context = runtime.context else { return }
|
|
363
|
+
|
|
364
|
+
for (_, callback) in callbacks {
|
|
365
|
+
if !callback.isUndefined {
|
|
366
|
+
callback.call(withArguments: [timestamp])
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
context.evaluateScript("void 0;")
|
|
371
|
+
|
|
372
|
+
let isEmpty: Bool = stateQueue.sync { rafCallbacks.isEmpty }
|
|
373
|
+
if isEmpty {
|
|
374
|
+
DispatchQueue.main.async {
|
|
375
|
+
if let link = displayLink {
|
|
376
|
+
CVDisplayLinkStop(link)
|
|
377
|
+
}
|
|
378
|
+
displayLink = nil
|
|
379
|
+
displayLinkSource?.cancel()
|
|
380
|
+
displayLinkSource = nil
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// MARK: - performance.now()
|
|
387
|
+
|
|
388
|
+
private static func registerPerformance(in context: JSContext, runtime: JSRuntime) {
|
|
389
|
+
context.evaluateScript("var performance = {};")
|
|
390
|
+
|
|
391
|
+
let performanceNow: @convention(block) () -> Double = { [weak runtime] in
|
|
392
|
+
guard let runtime = runtime else { return 0 }
|
|
393
|
+
return (CFAbsoluteTimeGetCurrent() - runtime.startTime) * 1000.0
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
guard let perfObj = context.objectForKeyedSubscript("performance") else {
|
|
397
|
+
NSLog("[VueNative macOS] Warning: failed to get performance object")
|
|
398
|
+
return
|
|
399
|
+
}
|
|
400
|
+
perfObj.setObject(performanceNow, forKeyedSubscript: "now" as NSString)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// MARK: - globalThis
|
|
404
|
+
|
|
405
|
+
private static func registerGlobalThis(in context: JSContext) {
|
|
406
|
+
context.evaluateScript("""
|
|
407
|
+
if (typeof globalThis === 'undefined') {
|
|
408
|
+
var globalThis = this;
|
|
409
|
+
}
|
|
410
|
+
""")
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// MARK: - fetch
|
|
414
|
+
|
|
415
|
+
private static func registerFetch(in context: JSContext, runtime: JSRuntime) {
|
|
416
|
+
let fetch: @convention(block) (JSValue, JSValue) -> JSValue = { [weak runtime] urlValue, optionsValue in
|
|
417
|
+
guard let runtime = runtime, let context = runtime.context else {
|
|
418
|
+
return JSValue(undefinedIn: JSContext.current())
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let urlString = urlValue.toString() ?? ""
|
|
422
|
+
guard let url = URL(string: urlString) else {
|
|
423
|
+
return context.evaluateScript("Promise.reject(new TypeError('Invalid URL'))") ?? JSValue(undefinedIn: context)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
var request = URLRequest(url: url)
|
|
427
|
+
|
|
428
|
+
if !optionsValue.isUndefined && !optionsValue.isNull {
|
|
429
|
+
if let method = optionsValue.objectForKeyedSubscript("method")?.toString() {
|
|
430
|
+
request.httpMethod = method.uppercased()
|
|
431
|
+
} else {
|
|
432
|
+
request.httpMethod = "GET"
|
|
433
|
+
}
|
|
434
|
+
if let headersObj = optionsValue.objectForKeyedSubscript("headers"),
|
|
435
|
+
!headersObj.isUndefined, !headersObj.isNull,
|
|
436
|
+
let headers = headersObj.toDictionary() as? [String: String] {
|
|
437
|
+
for (key, val) in headers {
|
|
438
|
+
request.setValue(val, forHTTPHeaderField: key)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if let body = optionsValue.objectForKeyedSubscript("body"),
|
|
442
|
+
!body.isUndefined, !body.isNull,
|
|
443
|
+
let bodyStr = body.toString() {
|
|
444
|
+
request.httpBody = bodyStr.data(using: .utf8)
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
request.httpMethod = "GET"
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var resolveRef: JSValue?
|
|
451
|
+
var rejectRef: JSValue?
|
|
452
|
+
|
|
453
|
+
let captureExecutor: @convention(block) (JSValue, JSValue) -> Void = { resolve, reject in
|
|
454
|
+
resolveRef = resolve
|
|
455
|
+
rejectRef = reject
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
let promiseCtor = context.evaluateScript("""
|
|
459
|
+
(function(executor) {
|
|
460
|
+
return new Promise(executor);
|
|
461
|
+
})
|
|
462
|
+
""")
|
|
463
|
+
let captureBlock = JSValue(object: captureExecutor as AnyObject, in: context)
|
|
464
|
+
let promise = promiseCtor?.call(withArguments: [captureBlock as Any])
|
|
465
|
+
|
|
466
|
+
let task = URLSession.shared.dataTask(with: request) { [weak runtime] data, response, error in
|
|
467
|
+
guard let runtime = runtime else { return }
|
|
468
|
+
runtime.jsQueue.async { [weak runtime] in
|
|
469
|
+
guard runtime != nil, let context = runtime?.context else { return }
|
|
470
|
+
|
|
471
|
+
if let error = error {
|
|
472
|
+
let errMsg = error.localizedDescription
|
|
473
|
+
if let reject = rejectRef, !reject.isUndefined {
|
|
474
|
+
let errObj = context.evaluateScript("new Error(\(JSPolyfillsJSON.encode(errMsg)))")
|
|
475
|
+
reject.call(withArguments: [errObj as Any])
|
|
476
|
+
}
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
let httpResponse = response as? HTTPURLResponse
|
|
481
|
+
let status = httpResponse?.statusCode ?? 200
|
|
482
|
+
let ok = (200...299).contains(status)
|
|
483
|
+
let bodyData = data ?? Data()
|
|
484
|
+
let bodyString = String(data: bodyData, encoding: .utf8) ?? ""
|
|
485
|
+
|
|
486
|
+
var headersDict: [String: String] = [:]
|
|
487
|
+
if let httpResp = httpResponse {
|
|
488
|
+
for (k, v) in httpResp.allHeaderFields {
|
|
489
|
+
headersDict["\(k)"] = "\(v)"
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if let resolve = resolveRef, !resolve.isUndefined {
|
|
494
|
+
guard let responseObj = JSValue(newObjectIn: context) else {
|
|
495
|
+
NSLog("[VueNative macOS] Warning: failed to create response object")
|
|
496
|
+
return
|
|
497
|
+
}
|
|
498
|
+
responseObj.setObject(status, forKeyedSubscript: "status" as NSString)
|
|
499
|
+
responseObj.setObject(ok, forKeyedSubscript: "ok" as NSString)
|
|
500
|
+
responseObj.setObject(bodyString, forKeyedSubscript: "_body" as NSString)
|
|
501
|
+
|
|
502
|
+
if let headersObj = JSValue(newObjectIn: context) {
|
|
503
|
+
for (k, v) in headersDict {
|
|
504
|
+
headersObj.setObject(v, forKeyedSubscript: k as NSString)
|
|
505
|
+
}
|
|
506
|
+
responseObj.setObject(headersObj, forKeyedSubscript: "headers" as NSString)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
let bodyStringCopy = bodyString
|
|
510
|
+
let textMethod: @convention(block) () -> JSValue = {
|
|
511
|
+
return context.evaluateScript("Promise.resolve(\(JSPolyfillsJSON.encode(bodyStringCopy)))") ?? JSValue(undefinedIn: context)
|
|
512
|
+
}
|
|
513
|
+
responseObj.setObject(textMethod, forKeyedSubscript: "text" as NSString)
|
|
514
|
+
|
|
515
|
+
let jsonMethod: @convention(block) () -> JSValue = {
|
|
516
|
+
return context.evaluateScript("(function(s){ try { return Promise.resolve(JSON.parse(s)); } catch(e) { return Promise.reject(e); } })(\(JSPolyfillsJSON.encode(bodyStringCopy)))") ?? JSValue(undefinedIn: context)
|
|
517
|
+
}
|
|
518
|
+
responseObj.setObject(jsonMethod, forKeyedSubscript: "json" as NSString)
|
|
519
|
+
|
|
520
|
+
resolve.call(withArguments: [responseObj])
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
task.resume()
|
|
525
|
+
|
|
526
|
+
return promise ?? JSValue(undefinedIn: context)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
context.setObject(fetch, forKeyedSubscript: "fetch" as NSString)
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// MARK: - atob / btoa (Base64)
|
|
533
|
+
|
|
534
|
+
private static func registerBase64(in context: JSContext) {
|
|
535
|
+
let atob: @convention(block) (String) -> String = { encoded in
|
|
536
|
+
guard let data = Data(base64Encoded: encoded) else { return "" }
|
|
537
|
+
return String(data: data, encoding: .utf8) ?? ""
|
|
538
|
+
}
|
|
539
|
+
context.setObject(atob, forKeyedSubscript: "atob" as NSString)
|
|
540
|
+
|
|
541
|
+
let btoa: @convention(block) (String) -> String = { str in
|
|
542
|
+
guard let data = str.data(using: .utf8) else { return "" }
|
|
543
|
+
return data.base64EncodedString()
|
|
544
|
+
}
|
|
545
|
+
context.setObject(btoa, forKeyedSubscript: "btoa" as NSString)
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// MARK: - TextEncoder / TextDecoder
|
|
549
|
+
|
|
550
|
+
private static func registerTextEncoding(in context: JSContext) {
|
|
551
|
+
context.evaluateScript("""
|
|
552
|
+
class TextEncoder {
|
|
553
|
+
constructor(encoding = 'utf-8') { this.encoding = encoding; }
|
|
554
|
+
encode(str) {
|
|
555
|
+
const arr = [];
|
|
556
|
+
for (let i = 0; i < str.length; i++) {
|
|
557
|
+
let c = str.charCodeAt(i);
|
|
558
|
+
if (c < 0x80) { arr.push(c); }
|
|
559
|
+
else if (c < 0x800) { arr.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
|
|
560
|
+
else if (c < 0xd800 || c >= 0xe000) { arr.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
|
|
561
|
+
else { i++; c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)); arr.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
|
|
562
|
+
}
|
|
563
|
+
return new Uint8Array(arr);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
class TextDecoder {
|
|
567
|
+
constructor(encoding = 'utf-8') { this.encoding = encoding; }
|
|
568
|
+
decode(buffer) {
|
|
569
|
+
const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
|
|
570
|
+
let result = '';
|
|
571
|
+
for (let i = 0; i < bytes.length;) {
|
|
572
|
+
let c = bytes[i++];
|
|
573
|
+
if (c < 0x80) { result += String.fromCharCode(c); }
|
|
574
|
+
else if (c < 0xe0) { result += String.fromCharCode(((c & 0x1f) << 6) | (bytes[i++] & 0x3f)); }
|
|
575
|
+
else if (c < 0xf0) { result += String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f)); }
|
|
576
|
+
else { const cp = ((c & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f); result += String.fromCodePoint(cp); }
|
|
577
|
+
}
|
|
578
|
+
return result;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
globalThis.TextEncoder = TextEncoder;
|
|
582
|
+
globalThis.TextDecoder = TextDecoder;
|
|
583
|
+
""")
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// MARK: - URL / URLSearchParams
|
|
587
|
+
|
|
588
|
+
private static func registerURL(in context: JSContext) {
|
|
589
|
+
context.evaluateScript("""
|
|
590
|
+
if (typeof URL === 'undefined') {
|
|
591
|
+
class URL {
|
|
592
|
+
constructor(url, base) {
|
|
593
|
+
if (base) {
|
|
594
|
+
if (!url.match(/^[a-zA-Z]+:/)) {
|
|
595
|
+
const b = new URL(base);
|
|
596
|
+
url = b.origin + (url.startsWith('/') ? '' : '/') + url;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const match = url.match(/^([a-zA-Z]+:)\\/\\/([^/:]+)(:\\d+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$/);
|
|
600
|
+
if (!match) { this.href = url; this.protocol = ''; this.host = ''; this.hostname = ''; this.port = ''; this.pathname = '/'; this.search = ''; this.hash = ''; this.origin = ''; this.searchParams = new URLSearchParams(''); return; }
|
|
601
|
+
this.protocol = match[1] || '';
|
|
602
|
+
this.hostname = match[2] || '';
|
|
603
|
+
this.port = (match[3] || '').slice(1);
|
|
604
|
+
this.host = this.hostname + (this.port ? ':' + this.port : '');
|
|
605
|
+
this.pathname = match[4] || '/';
|
|
606
|
+
this.search = match[5] || '';
|
|
607
|
+
this.hash = match[6] || '';
|
|
608
|
+
this.origin = this.protocol + '//' + this.host;
|
|
609
|
+
this.href = url;
|
|
610
|
+
this.searchParams = new URLSearchParams(this.search);
|
|
611
|
+
}
|
|
612
|
+
toString() { return this.href; }
|
|
613
|
+
}
|
|
614
|
+
globalThis.URL = URL;
|
|
615
|
+
}
|
|
616
|
+
if (typeof URLSearchParams === 'undefined') {
|
|
617
|
+
class URLSearchParams {
|
|
618
|
+
constructor(init) {
|
|
619
|
+
this._params = [];
|
|
620
|
+
if (typeof init === 'string') {
|
|
621
|
+
init.replace(/^\\?/, '').split('&').filter(Boolean).forEach(p => {
|
|
622
|
+
const [k, ...v] = p.split('=');
|
|
623
|
+
this._params.push([decodeURIComponent(k), decodeURIComponent(v.join('='))]);
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
get(name) { const p = this._params.find(([k]) => k === name); return p ? p[1] : null; }
|
|
628
|
+
getAll(name) { return this._params.filter(([k]) => k === name).map(([,v]) => v); }
|
|
629
|
+
has(name) { return this._params.some(([k]) => k === name); }
|
|
630
|
+
set(name, value) { this.delete(name); this._params.push([name, String(value)]); }
|
|
631
|
+
append(name, value) { this._params.push([name, String(value)]); }
|
|
632
|
+
delete(name) { this._params = this._params.filter(([k]) => k !== name); }
|
|
633
|
+
toString() { return this._params.map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(v)).join('&'); }
|
|
634
|
+
forEach(cb) { this._params.forEach(([k, v]) => cb(v, k, this)); }
|
|
635
|
+
entries() { return this._params[Symbol.iterator](); }
|
|
636
|
+
keys() { return this._params.map(([k]) => k)[Symbol.iterator](); }
|
|
637
|
+
values() { return this._params.map(([,v]) => v)[Symbol.iterator](); }
|
|
638
|
+
[Symbol.iterator]() { return this.entries(); }
|
|
639
|
+
}
|
|
640
|
+
globalThis.URLSearchParams = URLSearchParams;
|
|
641
|
+
}
|
|
642
|
+
""")
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// MARK: - crypto.getRandomValues
|
|
646
|
+
|
|
647
|
+
private static func registerCrypto(in context: JSContext) {
|
|
648
|
+
let cryptoGetRandomValues: @convention(block) (JSValue) -> JSValue = { typedArray in
|
|
649
|
+
let length = typedArray.forProperty("length").toInt32()
|
|
650
|
+
if length > 0 {
|
|
651
|
+
var bytes = [UInt8](repeating: 0, count: Int(length))
|
|
652
|
+
_ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)
|
|
653
|
+
for i in 0..<Int(length) {
|
|
654
|
+
typedArray.setValue(bytes[i], at: i)
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return typedArray
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
context.evaluateScript("""
|
|
661
|
+
if (typeof crypto === 'undefined') {
|
|
662
|
+
globalThis.crypto = {};
|
|
663
|
+
}
|
|
664
|
+
""")
|
|
665
|
+
|
|
666
|
+
if let cryptoObj = context.objectForKeyedSubscript("crypto") {
|
|
667
|
+
cryptoObj.setObject(cryptoGetRandomValues, forKeyedSubscript: "getRandomValues" as NSString)
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// MARK: - JSON encode helper
|
|
673
|
+
|
|
674
|
+
private enum JSPolyfillsJSON {
|
|
675
|
+
static func encode(_ str: String) -> String {
|
|
676
|
+
if let data = try? JSONSerialization.data(withJSONObject: [str]),
|
|
677
|
+
let json = String(data: data, encoding: .utf8),
|
|
678
|
+
json.count >= 2 {
|
|
679
|
+
return String(json.dropFirst().dropLast())
|
|
680
|
+
}
|
|
681
|
+
let escaped = str
|
|
682
|
+
.replacingOccurrences(of: "\\", with: "\\\\")
|
|
683
|
+
.replacingOccurrences(of: "\"", with: "\\\"")
|
|
684
|
+
.replacingOccurrences(of: "\n", with: "\\n")
|
|
685
|
+
.replacingOccurrences(of: "\r", with: "\\r")
|
|
686
|
+
.replacingOccurrences(of: "\t", with: "\\t")
|
|
687
|
+
return "\"\(escaped)\""
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// MARK: - DispatchSource helper
|
|
692
|
+
|
|
693
|
+
/// Creates a DispatchSourceUserDataAdd on the main queue for coalescing CVDisplayLink signals.
|
|
694
|
+
private func DispatchSourceMakeUserDataAdd() -> DispatchSourceUserDataAdd {
|
|
695
|
+
return DispatchSource.makeUserDataAddSource(queue: .main)
|
|
696
|
+
}
|