@swiftbrowser/native 0.18.3 → 0.21.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.
Files changed (102) hide show
  1. package/README.md +53 -11
  2. package/bin/swiftbrowser-native.mjs +47 -0
  3. package/dist/blob.d.ts +85 -11
  4. package/dist/blob.d.ts.map +1 -1
  5. package/dist/blob.js +184 -34
  6. package/dist/blob.js.map +1 -1
  7. package/dist/bridge.d.ts +66 -8
  8. package/dist/bridge.d.ts.map +1 -1
  9. package/dist/bridge.js.map +1 -1
  10. package/dist/encoding.d.ts +13 -0
  11. package/dist/encoding.d.ts.map +1 -1
  12. package/dist/encoding.js +24 -0
  13. package/dist/encoding.js.map +1 -1
  14. package/dist/fetch.d.ts +27 -13
  15. package/dist/fetch.d.ts.map +1 -1
  16. package/dist/fetch.js +117 -45
  17. package/dist/fetch.js.map +1 -1
  18. package/dist/hostRequests.d.ts +24 -6
  19. package/dist/hostRequests.d.ts.map +1 -1
  20. package/dist/hostRequests.js +108 -15
  21. package/dist/hostRequests.js.map +1 -1
  22. package/dist/index.d.ts +1 -2
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +1 -2
  25. package/dist/index.js.map +1 -1
  26. package/dist/ios.d.ts +79 -0
  27. package/dist/ios.d.ts.map +1 -0
  28. package/dist/ios.js +181 -0
  29. package/dist/ios.js.map +1 -0
  30. package/dist/runtime.d.ts.map +1 -1
  31. package/dist/runtime.js +16 -7
  32. package/dist/runtime.js.map +1 -1
  33. package/dist/subtle.d.ts +1 -1
  34. package/dist/subtle.d.ts.map +1 -1
  35. package/dist/subtle.js +9 -5
  36. package/dist/subtle.js.map +1 -1
  37. package/dist/webSocket.d.ts +12 -5
  38. package/dist/webSocket.d.ts.map +1 -1
  39. package/dist/webSocket.js +37 -11
  40. package/dist/webSocket.js.map +1 -1
  41. package/package.json +7 -3
  42. package/src/blob.ts +215 -34
  43. package/src/bridge.ts +56 -8
  44. package/src/encoding.ts +22 -0
  45. package/src/fetch.ts +111 -48
  46. package/src/hostRequests.ts +129 -22
  47. package/src/index.ts +1 -2
  48. package/src/ios.ts +253 -0
  49. package/src/runtime.ts +23 -8
  50. package/src/subtle.ts +10 -6
  51. package/src/webSocket.ts +37 -13
  52. package/swift/Packages/SwiftBrowserDSP/Package.swift +31 -0
  53. package/swift/Packages/SwiftBrowserDSP/README.md +42 -0
  54. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ProgramNode.swift +306 -0
  55. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSP/ScopeView.swift +79 -0
  56. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Machine.swift +1561 -0
  57. package/swift/Packages/SwiftBrowserDSP/Sources/SwiftBrowserDSPCore/Program.swift +483 -0
  58. package/swift/Packages/SwiftBrowserLocalModel/Package.swift +35 -0
  59. package/swift/Packages/SwiftBrowserLocalModel/README.md +66 -0
  60. package/swift/Packages/SwiftBrowserLocalModel/Sources/SwiftBrowserLocalModel/LlamaEngine.swift +223 -0
  61. package/swift/Packages/SwiftBrowserLocalModel/Sources/SwiftBrowserLocalModel/LocalLanguageModel.swift +409 -0
  62. package/swift/Packages/SwiftBrowserShader/Package.swift +31 -0
  63. package/swift/Packages/SwiftBrowserShader/README.md +45 -0
  64. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderRenderer.swift +103 -0
  65. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShader/ShaderView.swift +168 -0
  66. package/swift/Packages/SwiftBrowserShader/Sources/SwiftBrowserShaderCore/ShaderProps.swift +120 -0
  67. package/swift/Sources/SwiftBrowserNative/Host/AudioEngineBridge.swift +921 -0
  68. package/swift/Sources/SwiftBrowserNative/Host/AudioEngineNodes.swift +70 -0
  69. package/swift/Sources/SwiftBrowserNative/Host/BodyStream.swift +166 -0
  70. package/swift/Sources/SwiftBrowserNative/Host/FilesBridge.swift +144 -10
  71. package/swift/Sources/SwiftBrowserNative/Host/HostBridge.swift +151 -27
  72. package/swift/Sources/SwiftBrowserNative/Host/HostRequestBridge.swift +139 -40
  73. package/swift/Sources/SwiftBrowserNative/Host/JSHost.swift +116 -0
  74. package/swift/Sources/SwiftBrowserNative/Host/JavaScriptCoreHost.swift +141 -2
  75. package/swift/Sources/SwiftBrowserNative/Host/LanguageModelBridge.swift +511 -0
  76. package/swift/Sources/SwiftBrowserNative/Host/LanguageModelProvider.swift +195 -0
  77. package/swift/Sources/SwiftBrowserNative/Host/WebSocketBridge.swift +24 -11
  78. package/swift/Sources/SwiftBrowserNative/Host/WorkerHost.swift +3 -2
  79. package/swift/Sources/SwiftBrowserNative/Runtime/NativeFunctions.swift +1 -1
  80. package/swift/Sources/SwiftBrowserNative/Runtime/ViewRegistry.swift +57 -0
  81. package/swift/Sources/SwiftBrowserNative/Views/ElementView.swift +12 -7
  82. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Controls.swift +18 -1
  83. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Drawing.swift +378 -0
  84. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Lists.swift +41 -1
  85. package/swift/Sources/SwiftBrowserNative/Views/Kinds/More.swift +88 -17
  86. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Navigation.swift +87 -21
  87. package/swift/Sources/SwiftBrowserNative/Views/Kinds/PinchTracker.swift +200 -0
  88. package/swift/Sources/SwiftBrowserNative/Views/Kinds/PopTap.swift +103 -0
  89. package/swift/Sources/SwiftBrowserNative/Views/Kinds/Stacks.swift +3 -2
  90. package/swift/Sources/SwiftBrowserNative/Views/StyleModifier.swift +146 -0
  91. package/swift/Sources/SwiftBrowserProtocol/Drawing.swift +306 -0
  92. package/swift/Sources/SwiftBrowserProtocol/Props.swift +31 -2
  93. package/swift/Sources/SwiftBrowserProtocol/RendererEvent.swift +24 -3
  94. package/swift/Sources/SwiftBrowserProtocol/Style.swift +87 -0
  95. package/swift/Sources/SwiftBrowserTree/OpTree.swift +25 -0
  96. package/wasm/SwiftBrowserNative.wasm +2 -2
  97. package/dist/native.d.ts +0 -19
  98. package/dist/native.d.ts.map +0 -1
  99. package/dist/native.js +0 -17
  100. package/dist/native.js.map +0 -1
  101. package/src/native.ts +0 -35
  102. package/swift/Sources/SwiftBrowserNative/Runtime/NativeRegistry.swift +0 -53
@@ -28,6 +28,122 @@ public protocol JSHost: AnyObject {
28
28
 
29
29
  /// Throws the loaded scripts away: a fresh global object for the next `evaluate`.
30
30
  func reset()
31
+
32
+ /// Whether `installBytes` and `call(_:values:)` carry bytes as typed
33
+ /// arrays. An engine without it is given base64 in JSON instead
34
+ /// (docs/notes/bytes.md): the bridge installs `hostRequestBytes` only here.
35
+ var carriesBytes: Bool { get }
36
+
37
+ /// `install`, the arguments as `HostValue`s: a typed array or an
38
+ /// `ArrayBuffer` arrives as `.bytes`, copied out, anything else as
39
+ /// `.json`; a `.bytes` result goes back as a typed array of its kind.
40
+ func installBytes(_ name: String, _ body: @escaping ([HostValue]) -> HostValue?)
41
+
42
+ /// `call`, a `.bytes` argument made into a typed array of its kind.
43
+ @discardableResult
44
+ func call(_ function: String, values: [HostValue]) throws -> JSONValue
45
+ }
46
+
47
+ /// A JavaScript value at the engine boundary: JSON, or bytes crossing as a
48
+ /// typed array (`Float32Array`, `Uint8Array`, …) or an `ArrayBuffer`.
49
+ public enum HostValue: Sendable {
50
+ case json(JSONValue)
51
+ case bytes(HostBytes)
52
+ }
53
+
54
+ /// Bytes crossing the bridge, and the view they came from or go back as.
55
+ public struct HostBytes: Sendable {
56
+ public var data: Data
57
+ public var kind: BytesKind
58
+ public init(_ data: Data, kind: BytesKind) {
59
+ self.data = data
60
+ self.kind = kind
61
+ }
62
+ }
63
+
64
+ /// The typed arrays JavaScript has, and a bare `ArrayBuffer`.
65
+ public enum BytesKind: String, Sendable {
66
+ case int8, uint8, uint8Clamped, int16, uint16, int32, uint32, float32, float64, bigInt64, bigUint64, arrayBuffer
67
+ }
68
+
69
+ extension JSHost {
70
+ public var carriesBytes: Bool { false }
71
+
72
+ /// An engine without bytes: the arguments are JSON, as `install(_:_:)`
73
+ /// gives them, and a `.bytes` result goes back as base64.
74
+ public func installBytes(_ name: String, _ body: @escaping ([HostValue]) -> HostValue?) {
75
+ install(name, { (arguments: [JSONValue]) -> JSONValue? in
76
+ switch body(arguments.map(HostValue.json)) {
77
+ case .json(let json)?: return json
78
+ case .bytes(let bytes)?: return .string(bytes.data.base64EncodedString())
79
+ case nil: return nil
80
+ }
81
+ })
82
+ }
83
+
84
+ /// An engine without bytes: `.bytes` goes as base64, the form JavaScript
85
+ /// reads where a host has no `hostRequestBytes`.
86
+ @discardableResult
87
+ public func call(_ function: String, values: [HostValue]) throws -> JSONValue {
88
+ try call(function, values.map { value in
89
+ switch value {
90
+ case .json(let json): return json
91
+ case .bytes(let bytes): return .string(bytes.data.base64EncodedString())
92
+ }
93
+ })
94
+ }
95
+ }
96
+
97
+ /// The bytes a host request carries beside its JSON, and those its answer
98
+ /// collects, for the request's task (`RequestBytes.$current`): a bridge reads
99
+ /// a byte field with `data(_:)` and writes one with `value(_:)` without being
100
+ /// handed either list. An app that sent no views (a host without
101
+ /// `hostRequestBytes`, an older bundle) is answered in base64, as before.
102
+ final class RequestBytes: @unchecked Sendable {
103
+ @TaskLocal static var current: RequestBytes?
104
+
105
+ let incoming: [HostBytes]
106
+ /// Whether the app reads an answer's bytes beside its JSON.
107
+ let answers: Bool
108
+ private let lock = NSLock()
109
+ private var collected: [HostBytes] = []
110
+
111
+ init(incoming: [HostBytes], answers: Bool) {
112
+ self.incoming = incoming
113
+ self.answers = answers
114
+ }
115
+
116
+ /// The bytes the answer collected, in the order its JSON names them.
117
+ var outgoing: [HostBytes] {
118
+ lock.lock()
119
+ defer { lock.unlock() }
120
+ return collected
121
+ }
122
+
123
+ /// A request field's bytes: a view sent beside it (`{ "$bytes": n }`), or base64.
124
+ static func data(_ value: JSONValue?) -> Data? {
125
+ value?.bytes(from: Self.current?.incoming ?? [])
126
+ }
127
+
128
+ /// Bytes for an answer's field: a view beside the JSON where the app reads them so, else base64.
129
+ static func value(_ data: Data) -> JSONValue {
130
+ guard let current = Self.current, current.answers else { return .string(data.base64EncodedString()) }
131
+ current.lock.lock()
132
+ defer { current.lock.unlock() }
133
+ current.collected.append(HostBytes(data, kind: .uint8))
134
+ return .object(["$bytes": .number(Double(current.collected.count - 1))])
135
+ }
136
+ }
137
+
138
+ extension JSONValue {
139
+ /// The bytes a request's field holds: `{ "$bytes": n }` names the nth
140
+ /// view sent beside the JSON (`hostRequestBytes`), and a string is base64,
141
+ /// the form a host without bytes is given.
142
+ public func bytes(from bytes: [HostBytes]) -> Data? {
143
+ if let index = self["$bytes"].int { return bytes.indices.contains(index) ? bytes[index].data : nil }
144
+ if let encoded = string { return Data(base64Encoded: encoded) }
145
+ return nil
146
+ }
31
147
  }
32
148
 
33
149
  public struct JSError: Error, CustomStringConvertible {
@@ -25,8 +25,22 @@ public final class JavaScriptCoreHost: JSHost {
25
25
  installExceptionHandler()
26
26
  }
27
27
 
28
+ /// JavaScriptCore keeps a process-wide registry of its virtual machines
29
+ /// under a lock of its own, and making one on a thread while another is
30
+ /// being made or dropped elsewhere has crashed in that lock (`VM::VM` →
31
+ /// `unlockSlow`, "Invalid value for lock", the iOS 26.2 simulator under
32
+ /// the test suites running in parallel, each with contexts and workers
33
+ /// of its own). So every virtual machine this package makes or drops is
34
+ /// made or dropped one at a time, under this lock: the app's context
35
+ /// here and each worker's (WorkerHost.swift).
36
+ nonisolated private static let virtualMachineLock = NSLock()
37
+
38
+ nonisolated static func withVirtualMachineLock<T>(_ body: () throws -> T) rethrows -> T {
39
+ try virtualMachineLock.withLock(body)
40
+ }
41
+
28
42
  private static func makeContext() -> (JSContext, JSValue) {
29
- let context = JSContext()!
43
+ let context = withVirtualMachineLock { JSContext()! }
30
44
  context.name = "SwiftBrowser"
31
45
  let native = JSValue(newObjectIn: context)!
32
46
  context.setObject(native, forKeyedSubscript: "__sbNative" as NSString)
@@ -46,8 +60,10 @@ public final class JavaScriptCoreHost: JSHost {
46
60
 
47
61
  public func reset() {
48
62
  context.exceptionHandler = nil
49
- (context, native) = JavaScriptCoreHost.makeContext()
50
63
  pendingError = nil
64
+ let fresh = JavaScriptCoreHost.makeContext()
65
+ // The old context (and its virtual machine) goes under the lock too.
66
+ JavaScriptCoreHost.withVirtualMachineLock { (context, native) = fresh }
51
67
  installExceptionHandler()
52
68
  }
53
69
 
@@ -84,6 +100,42 @@ public final class JavaScriptCoreHost: JSHost {
84
100
  return result.map(JavaScriptCoreHost.json(from:)) ?? .null
85
101
  }
86
102
 
103
+ public var carriesBytes: Bool { true }
104
+
105
+ public func installBytes(_ name: String, _ body: @escaping ([HostValue]) -> HostValue?) {
106
+ let block: @convention(block) () -> Any? = {
107
+ let arguments = (JSContext.currentArguments() as? [JSValue]) ?? []
108
+ switch body(arguments.map(JavaScriptCoreHost.hostValue(from:))) {
109
+ case .json(let json)?: return json.anyObject
110
+ case .bytes(let bytes)?:
111
+ guard let context = JSContext.current() else { return nil }
112
+ return JavaScriptCoreHost.typedArray(bytes, in: context)
113
+ case nil: return nil
114
+ }
115
+ }
116
+ native.setObject(unsafeBitCast(block, to: AnyObject.self), forKeyedSubscript: name as NSString)
117
+ }
118
+
119
+ @discardableResult
120
+ public func call(_ function: String, values: [HostValue]) throws -> JSONValue {
121
+ guard let callee = context.objectForKeyedSubscript(function), !callee.isUndefined else {
122
+ throw JSError(message: "\(function) is not defined")
123
+ }
124
+ pendingError = nil
125
+ let arguments: [Any] = values.map { value in
126
+ switch value {
127
+ case .json(let json): return json.anyObject
128
+ case .bytes(let bytes): return JavaScriptCoreHost.typedArray(bytes, in: context)
129
+ }
130
+ }
131
+ let result = callee.call(withArguments: arguments)
132
+ if let error = pendingError {
133
+ pendingError = nil
134
+ throw error
135
+ }
136
+ return result.map(JavaScriptCoreHost.json(from:)) ?? .null
137
+ }
138
+
87
139
  public func hasGlobal(_ name: String) -> Bool {
88
140
  guard let value = context.objectForKeyedSubscript(name) else { return false }
89
141
  return !value.isUndefined
@@ -99,5 +151,92 @@ public final class JavaScriptCoreHost: JSHost {
99
151
  if value.isNumber { return .number(value.toDouble()) }
100
152
  return JSONValue(any: value.toObject())
101
153
  }
154
+
155
+ // MARK: Bytes
156
+
157
+ /// A JavaScript value as a `HostValue`: a typed array's or an
158
+ /// `ArrayBuffer`'s bytes copied out (docs/notes/bytes.md), else JSON. The
159
+ /// typed-array check comes first: `toObject` would turn a view into a
160
+ /// dictionary of its indices, slowly. The pointer the engine gives is
161
+ /// read inside this call, while no JavaScript runs.
162
+ nonisolated static func hostValue(from value: JSValue) -> HostValue {
163
+ guard !value.isUndefined, !value.isNull, value.isObject, let context = value.context?.jsGlobalContextRef else { return .json(json(from: value)) }
164
+ var exception: JSValueRef?
165
+ let type = JSValueGetTypedArrayType(context, value.jsValueRef, &exception)
166
+ guard type != kJSTypedArrayTypeNone, let kind = bytesKind(type), let object = JSValueToObject(context, value.jsValueRef, &exception) else {
167
+ return .json(json(from: value))
168
+ }
169
+ if kind == .arrayBuffer {
170
+ let length = JSObjectGetArrayBufferByteLength(context, object, &exception)
171
+ guard length > 0, let pointer = JSObjectGetArrayBufferBytesPtr(context, object, &exception) else { return .bytes(HostBytes(Data(), kind: kind)) }
172
+ return .bytes(HostBytes(Data(bytes: pointer, count: length), kind: kind))
173
+ }
174
+ // The pointer is the backing buffer's start; the view begins at its offset.
175
+ let length = JSObjectGetTypedArrayByteLength(context, object, &exception)
176
+ let offset = JSObjectGetTypedArrayByteOffset(context, object, &exception)
177
+ guard length > 0, let pointer = JSObjectGetTypedArrayBytesPtr(context, object, &exception) else { return .bytes(HostBytes(Data(), kind: kind)) }
178
+ return .bytes(HostBytes(Data(bytes: pointer + offset, count: length), kind: kind))
179
+ }
180
+
181
+ /// A typed array (or `ArrayBuffer`) the engine allocates, the bytes copied in.
182
+ nonisolated static func typedArray(_ bytes: HostBytes, in context: JSContext) -> JSValue {
183
+ let global = context.jsGlobalContextRef
184
+ let size = elementSize(bytes.kind)
185
+ let count = bytes.data.count / size
186
+ var exception: JSValueRef?
187
+ let type = bytes.kind == .arrayBuffer ? kJSTypedArrayTypeUint8Array : typedArrayType(bytes.kind)
188
+ guard let array = JSObjectMakeTypedArray(global, type, count, &exception) else { return JSValue(nullIn: context) }
189
+ if count > 0, let pointer = JSObjectGetTypedArrayBytesPtr(global, array, &exception) {
190
+ bytes.data.copyBytes(to: pointer.assumingMemoryBound(to: UInt8.self), count: count * size)
191
+ }
192
+ if bytes.kind == .arrayBuffer, let buffer = JSObjectGetTypedArrayBuffer(global, array, &exception) {
193
+ return JSValue(jsValueRef: buffer, in: context)
194
+ }
195
+ return JSValue(jsValueRef: array, in: context)
196
+ }
197
+
198
+ private nonisolated static func elementSize(_ kind: BytesKind) -> Int {
199
+ switch kind {
200
+ case .int8, .uint8, .uint8Clamped, .arrayBuffer: return 1
201
+ case .int16, .uint16: return 2
202
+ case .int32, .uint32, .float32: return 4
203
+ case .float64, .bigInt64, .bigUint64: return 8
204
+ }
205
+ }
206
+
207
+ private nonisolated static func bytesKind(_ type: JSTypedArrayType) -> BytesKind? {
208
+ switch type {
209
+ case kJSTypedArrayTypeInt8Array: return .int8
210
+ case kJSTypedArrayTypeUint8Array: return .uint8
211
+ case kJSTypedArrayTypeUint8ClampedArray: return .uint8Clamped
212
+ case kJSTypedArrayTypeInt16Array: return .int16
213
+ case kJSTypedArrayTypeUint16Array: return .uint16
214
+ case kJSTypedArrayTypeInt32Array: return .int32
215
+ case kJSTypedArrayTypeUint32Array: return .uint32
216
+ case kJSTypedArrayTypeFloat32Array: return .float32
217
+ case kJSTypedArrayTypeFloat64Array: return .float64
218
+ case kJSTypedArrayTypeBigInt64Array: return .bigInt64
219
+ case kJSTypedArrayTypeBigUint64Array: return .bigUint64
220
+ case kJSTypedArrayTypeArrayBuffer: return .arrayBuffer
221
+ default: return nil
222
+ }
223
+ }
224
+
225
+ private nonisolated static func typedArrayType(_ kind: BytesKind) -> JSTypedArrayType {
226
+ switch kind {
227
+ case .int8: return kJSTypedArrayTypeInt8Array
228
+ case .uint8: return kJSTypedArrayTypeUint8Array
229
+ case .uint8Clamped: return kJSTypedArrayTypeUint8ClampedArray
230
+ case .int16: return kJSTypedArrayTypeInt16Array
231
+ case .uint16: return kJSTypedArrayTypeUint16Array
232
+ case .int32: return kJSTypedArrayTypeInt32Array
233
+ case .uint32: return kJSTypedArrayTypeUint32Array
234
+ case .float32: return kJSTypedArrayTypeFloat32Array
235
+ case .float64: return kJSTypedArrayTypeFloat64Array
236
+ case .bigInt64: return kJSTypedArrayTypeBigInt64Array
237
+ case .bigUint64: return kJSTypedArrayTypeBigUint64Array
238
+ case .arrayBuffer: return kJSTypedArrayTypeArrayBuffer
239
+ }
240
+ }
102
241
  }
103
242
  #endif