@swifttui/web 0.0.6
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/AGENTS.md +52 -0
- package/README.md +116 -0
- package/cli.ts +168 -0
- package/index.html +50 -0
- package/index.ts +8 -0
- package/manifest.ts +1 -0
- package/package.json +33 -0
- package/src/AccessibilityTree.ts +262 -0
- package/src/BoxDrawingRenderer.ts +585 -0
- package/src/PublicEntrypointBoundary.test.ts +20 -0
- package/src/WebHostApp.test.ts +222 -0
- package/src/WebHostApp.ts +269 -0
- package/src/WebHostSceneManifest.test.ts +38 -0
- package/src/WebHostSceneManifest.ts +156 -0
- package/src/WebHostSceneRuntime.test.ts +1752 -0
- package/src/WebHostSceneRuntime.ts +955 -0
- package/src/WebHostSurfaceTransport.test.ts +362 -0
- package/src/WebHostSurfaceTransport.ts +648 -0
- package/src/WebHostTerminalStyle.test.ts +123 -0
- package/src/WebHostTerminalStyle.ts +471 -0
- package/src/WebHostTestFixtures.ts +10 -0
- package/src/WebSocketSceneBridge.test.ts +198 -0
- package/src/WebSocketSceneBridge.ts +233 -0
- package/src/browser.ts +59 -0
- package/src/wasi/BrowserWASIBridge.test.ts +168 -0
- package/src/wasi/BrowserWASIBridge.ts +167 -0
- package/src/wasi/SharedInputQueue.test.ts +146 -0
- package/src/wasi/SharedInputQueue.ts +199 -0
- package/src/wasi/StdIOPipe.ts +72 -0
- package/src/wasi/WasiPollScheduler.test.ts +176 -0
- package/src/wasi/WasiPollScheduler.ts +305 -0
- package/src/wasi/WasmSceneRuntime.ts +205 -0
- package/src/wasi/WasmSceneWorker.ts +182 -0
- package/style.css +15 -0
- package/testing.ts +1 -0
- package/tsconfig.json +29 -0
- package/wasi-worker.ts +1 -0
- package/wasi.ts +4 -0
- package/websocket.ts +1 -0
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
import {
|
|
2
|
+
encodeWebHostTerminalRenderStyleBase64,
|
|
3
|
+
type WebHostTerminalStyle,
|
|
4
|
+
} from "./WebHostTerminalStyle.ts";
|
|
5
|
+
|
|
6
|
+
export interface WebHostSurfaceStyle {
|
|
7
|
+
fg?: string;
|
|
8
|
+
bg?: string;
|
|
9
|
+
em?: number;
|
|
10
|
+
underline?: WebHostSurfaceLineStyle;
|
|
11
|
+
strikethrough?: WebHostSurfaceLineStyle;
|
|
12
|
+
opacity?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface WebHostSurfaceLineStyle {
|
|
16
|
+
pattern: "solid" | "dot" | "dash" | "dashDot" | "dashDotDot" | "double" | "curly";
|
|
17
|
+
color?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type WebHostSurfaceCell = [
|
|
21
|
+
x: number,
|
|
22
|
+
text: string,
|
|
23
|
+
span: number,
|
|
24
|
+
styleIndex: number,
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export type WebHostSurfaceRect = [
|
|
28
|
+
x: number,
|
|
29
|
+
y: number,
|
|
30
|
+
width: number,
|
|
31
|
+
height: number,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export type WebHostSurfaceSize = [
|
|
35
|
+
width: number,
|
|
36
|
+
height: number,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export type WebHostAccessibilityPoint = [
|
|
40
|
+
x: number,
|
|
41
|
+
y: number,
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
export type WebHostAccessibilityLiveRegion = "off" | "polite" | "assertive";
|
|
45
|
+
|
|
46
|
+
export interface WebHostAccessibilityNode {
|
|
47
|
+
id: string;
|
|
48
|
+
parentId?: string;
|
|
49
|
+
rect: WebHostSurfaceRect;
|
|
50
|
+
role: string;
|
|
51
|
+
label?: string;
|
|
52
|
+
hint?: string;
|
|
53
|
+
liveRegion?: WebHostAccessibilityLiveRegion;
|
|
54
|
+
cursorAnchor?: WebHostAccessibilityPoint;
|
|
55
|
+
isFocused?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface WebHostAccessibilityAnnouncement {
|
|
59
|
+
message: string;
|
|
60
|
+
politeness: WebHostAccessibilityLiveRegion;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type WebHostSurfaceImageFormat = "png" | "jpeg" | "gif";
|
|
64
|
+
|
|
65
|
+
export interface WebHostSurfaceImage {
|
|
66
|
+
id: string;
|
|
67
|
+
format: WebHostSurfaceImageFormat;
|
|
68
|
+
bounds: WebHostSurfaceRect;
|
|
69
|
+
visibleBounds: WebHostSurfaceRect;
|
|
70
|
+
scalingMode: "stretch" | "fit" | "fill";
|
|
71
|
+
pixelSize?: WebHostSurfaceSize;
|
|
72
|
+
dataBase64?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type WebHostSurfaceDamageRange = [
|
|
76
|
+
start: number,
|
|
77
|
+
end: number,
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export type WebHostSurfaceDamageTextRow = [
|
|
81
|
+
row: number,
|
|
82
|
+
ranges: WebHostSurfaceDamageRange[],
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
export interface WebHostSurfaceDamage {
|
|
86
|
+
textRows: WebHostSurfaceDamageTextRow[];
|
|
87
|
+
requiresFullTextRepaint: boolean;
|
|
88
|
+
requiresFullGraphicsReplay: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface WebHostSurfaceFrame {
|
|
92
|
+
version: 1 | 2;
|
|
93
|
+
sequence?: number;
|
|
94
|
+
width: number;
|
|
95
|
+
height: number;
|
|
96
|
+
styles: Array<WebHostSurfaceStyle | null>;
|
|
97
|
+
rows: WebHostSurfaceCell[][];
|
|
98
|
+
images?: WebHostSurfaceImage[];
|
|
99
|
+
damage?: WebHostSurfaceDamage;
|
|
100
|
+
accessibilityTree?: WebHostAccessibilityNode[];
|
|
101
|
+
accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type WebHostSurfaceDeltaRow = [
|
|
105
|
+
row: number,
|
|
106
|
+
cells: WebHostSurfaceCell[],
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
export interface WebHostSurfaceDeltaFrame {
|
|
110
|
+
version: 3;
|
|
111
|
+
encoding: "delta";
|
|
112
|
+
sequence?: number;
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
styles: Array<WebHostSurfaceStyle | null>;
|
|
116
|
+
deltaRows: WebHostSurfaceDeltaRow[];
|
|
117
|
+
images?: WebHostSurfaceImage[];
|
|
118
|
+
damage?: WebHostSurfaceDamage;
|
|
119
|
+
accessibilityTree?: WebHostAccessibilityNode[];
|
|
120
|
+
accessibilityAnnouncements?: WebHostAccessibilityAnnouncement[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface WebHostRuntimeIssue {
|
|
124
|
+
severity: "warning" | "error";
|
|
125
|
+
code: string;
|
|
126
|
+
message: string;
|
|
127
|
+
description: string;
|
|
128
|
+
identity?: string;
|
|
129
|
+
source?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface WebHostFrameDiagnosticRecord {
|
|
133
|
+
format: "swift-tui-frame-diagnostics-v1";
|
|
134
|
+
header: string[];
|
|
135
|
+
fields: string[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export type WebHostOutputRecord =
|
|
139
|
+
| { type: "surface"; frame: WebHostSurfaceFrame }
|
|
140
|
+
| { type: "clipboard"; text: string }
|
|
141
|
+
| { type: "runtimeIssue"; issue: WebHostRuntimeIssue }
|
|
142
|
+
| { type: "frameDiagnostic"; diagnostic: WebHostFrameDiagnosticRecord }
|
|
143
|
+
| { type: "text"; text: string };
|
|
144
|
+
|
|
145
|
+
export interface WebHostOutputSink {
|
|
146
|
+
presentSurface(frame: WebHostSurfaceFrame): void;
|
|
147
|
+
writeClipboard?(text: string): void | Promise<void>;
|
|
148
|
+
notifyRuntimeIssue?(issue: WebHostRuntimeIssue): void;
|
|
149
|
+
recordFrameDiagnostic?(diagnostic: WebHostFrameDiagnosticRecord): void;
|
|
150
|
+
writeOutput?(text: string): void;
|
|
151
|
+
writeError?(text: string): void;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface WebHostKeyInput {
|
|
155
|
+
key:
|
|
156
|
+
| "return"
|
|
157
|
+
| "space"
|
|
158
|
+
| "tab"
|
|
159
|
+
| "arrowLeft"
|
|
160
|
+
| "arrowRight"
|
|
161
|
+
| "arrowUp"
|
|
162
|
+
| "arrowDown"
|
|
163
|
+
| "backspace"
|
|
164
|
+
| "escape"
|
|
165
|
+
| "home"
|
|
166
|
+
| "end"
|
|
167
|
+
| "character";
|
|
168
|
+
character?: string;
|
|
169
|
+
modifiers?: number;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface WebHostMouseInput {
|
|
173
|
+
kind: "down" | "up" | "moved" | "dragged" | "scrolled";
|
|
174
|
+
x: number;
|
|
175
|
+
y: number;
|
|
176
|
+
button?: "primary" | "middle" | "secondary";
|
|
177
|
+
deltaX?: number;
|
|
178
|
+
deltaY?: number;
|
|
179
|
+
modifiers?: number;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const recordPrefix = "\u001E";
|
|
183
|
+
const textEncoder = new TextEncoder();
|
|
184
|
+
|
|
185
|
+
export class WebHostOutputDecoder {
|
|
186
|
+
private readonly textDecoder = new TextDecoder();
|
|
187
|
+
private bufferedText = "";
|
|
188
|
+
private lastSurfaceFrame?: WebHostSurfaceFrame;
|
|
189
|
+
|
|
190
|
+
feed(
|
|
191
|
+
chunk: Uint8Array
|
|
192
|
+
): WebHostOutputRecord[] {
|
|
193
|
+
this.bufferedText += this.textDecoder.decode(chunk, { stream: true });
|
|
194
|
+
const records: WebHostOutputRecord[] = [];
|
|
195
|
+
|
|
196
|
+
while (true) {
|
|
197
|
+
const newlineIndex = this.bufferedText.indexOf("\n");
|
|
198
|
+
if (newlineIndex < 0) {
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const line = this.bufferedText.slice(0, newlineIndex);
|
|
203
|
+
this.bufferedText = this.bufferedText.slice(newlineIndex + 1);
|
|
204
|
+
records.push(this.decodeLine(line));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (this.bufferedText.length > 4096 && !this.bufferedText.startsWith(recordPrefix)) {
|
|
208
|
+
records.push({ type: "text", text: this.bufferedText });
|
|
209
|
+
this.bufferedText = "";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return records;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
flush(): WebHostOutputRecord[] {
|
|
216
|
+
if (!this.bufferedText) {
|
|
217
|
+
return [];
|
|
218
|
+
}
|
|
219
|
+
const text = this.bufferedText;
|
|
220
|
+
this.bufferedText = "";
|
|
221
|
+
return [this.decodeLine(text)];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private decodeLine(
|
|
225
|
+
line: string
|
|
226
|
+
): WebHostOutputRecord {
|
|
227
|
+
if (line.startsWith(`${recordPrefix}clipboard:`)) {
|
|
228
|
+
try {
|
|
229
|
+
const record = JSON.parse(line.slice(`${recordPrefix}clipboard:`.length));
|
|
230
|
+
if (isWebHostClipboardRecord(record)) {
|
|
231
|
+
return { type: "clipboard", text: record.text };
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
// Fall through to the text path below so malformed output remains visible.
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return { type: "text", text: `${line}\n` };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (line.startsWith(`${recordPrefix}runtimeIssue:`)) {
|
|
241
|
+
try {
|
|
242
|
+
const record = JSON.parse(line.slice(`${recordPrefix}runtimeIssue:`.length));
|
|
243
|
+
if (isWebHostRuntimeIssue(record)) {
|
|
244
|
+
return { type: "runtimeIssue", issue: record };
|
|
245
|
+
}
|
|
246
|
+
} catch {
|
|
247
|
+
// Fall through to the text path below so malformed output remains visible.
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return { type: "text", text: `${line}\n` };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (line.startsWith(`${recordPrefix}frameDiagnostic:`)) {
|
|
254
|
+
try {
|
|
255
|
+
const record = JSON.parse(line.slice(`${recordPrefix}frameDiagnostic:`.length));
|
|
256
|
+
if (isWebHostFrameDiagnosticRecord(record)) {
|
|
257
|
+
return { type: "frameDiagnostic", diagnostic: record };
|
|
258
|
+
}
|
|
259
|
+
} catch {
|
|
260
|
+
// Fall through to the text path below so malformed output remains visible.
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { type: "text", text: `${line}\n` };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!line.startsWith(`${recordPrefix}surface:`)) {
|
|
267
|
+
return { type: "text", text: `${line}\n` };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const frame = JSON.parse(line.slice(`${recordPrefix}surface:`.length));
|
|
272
|
+
if (isWebHostSurfaceFrame(frame)) {
|
|
273
|
+
this.lastSurfaceFrame = frame;
|
|
274
|
+
return { type: "surface", frame };
|
|
275
|
+
}
|
|
276
|
+
if (isWebHostSurfaceDeltaFrame(frame)) {
|
|
277
|
+
const materialized = this.materializeDeltaFrame(frame);
|
|
278
|
+
if (materialized) {
|
|
279
|
+
this.lastSurfaceFrame = materialized;
|
|
280
|
+
return { type: "surface", frame: materialized };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
// Fall through to the text path below so malformed output remains visible.
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return { type: "text", text: `${line}\n` };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private materializeDeltaFrame(
|
|
291
|
+
frame: WebHostSurfaceDeltaFrame
|
|
292
|
+
): WebHostSurfaceFrame | undefined {
|
|
293
|
+
const baseline = this.lastSurfaceFrame;
|
|
294
|
+
if (!baseline || baseline.width !== frame.width || baseline.height !== frame.height) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const rows = baseline.rows.slice();
|
|
299
|
+
for (const [row, cells] of frame.deltaRows) {
|
|
300
|
+
if (!Number.isSafeInteger(row) || row < 0 || row >= frame.height) {
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
rows[row] = cells;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return {
|
|
307
|
+
version: baseline.version,
|
|
308
|
+
sequence: frame.sequence,
|
|
309
|
+
width: frame.width,
|
|
310
|
+
height: frame.height,
|
|
311
|
+
styles: frame.styles,
|
|
312
|
+
rows,
|
|
313
|
+
images: frame.images,
|
|
314
|
+
damage: frame.damage,
|
|
315
|
+
accessibilityTree: frame.accessibilityTree,
|
|
316
|
+
accessibilityAnnouncements: frame.accessibilityAnnouncements,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function isWebHostClipboardRecord(
|
|
322
|
+
value: unknown
|
|
323
|
+
): value is { text: string } {
|
|
324
|
+
return !!value && typeof value === "object" && typeof (value as { text?: unknown }).text === "string";
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function isWebHostRuntimeIssue(
|
|
328
|
+
value: unknown
|
|
329
|
+
): value is WebHostRuntimeIssue {
|
|
330
|
+
if (!value || typeof value !== "object") {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const record = value as Partial<WebHostRuntimeIssue>;
|
|
334
|
+
return (record.severity === "warning" || record.severity === "error")
|
|
335
|
+
&& typeof record.code === "string"
|
|
336
|
+
&& typeof record.message === "string"
|
|
337
|
+
&& typeof record.description === "string"
|
|
338
|
+
&& (record.identity === undefined || typeof record.identity === "string")
|
|
339
|
+
&& (record.source === undefined || typeof record.source === "string");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function isWebHostFrameDiagnosticRecord(
|
|
343
|
+
value: unknown
|
|
344
|
+
): value is WebHostFrameDiagnosticRecord {
|
|
345
|
+
if (!value || typeof value !== "object") {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
const record = value as Partial<WebHostFrameDiagnosticRecord>;
|
|
349
|
+
return record.format === "swift-tui-frame-diagnostics-v1"
|
|
350
|
+
&& Array.isArray(record.header)
|
|
351
|
+
&& record.header.every((field) => typeof field === "string")
|
|
352
|
+
&& Array.isArray(record.fields)
|
|
353
|
+
&& record.fields.every((field) => typeof field === "string");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function encodeResizeControlMessage(
|
|
357
|
+
columns: number,
|
|
358
|
+
rows: number,
|
|
359
|
+
cellWidth?: number,
|
|
360
|
+
cellHeight?: number
|
|
361
|
+
): Uint8Array {
|
|
362
|
+
const normalizedColumns = Math.max(1, Math.round(columns));
|
|
363
|
+
const normalizedRows = Math.max(1, Math.round(rows));
|
|
364
|
+
if (cellWidth && cellHeight) {
|
|
365
|
+
return textEncoder.encode(
|
|
366
|
+
`${recordPrefix}resize:${normalizedColumns}:${normalizedRows}:${Math.max(1, Math.round(cellWidth))}:${Math.max(1, Math.round(cellHeight))}\n`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return textEncoder.encode(`${recordPrefix}resize:${normalizedColumns}:${normalizedRows}\n`);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function encodeRenderStyleControlMessage(
|
|
374
|
+
style: WebHostTerminalStyle
|
|
375
|
+
): Uint8Array {
|
|
376
|
+
const encoded = encodeWebHostTerminalRenderStyleBase64(style);
|
|
377
|
+
return textEncoder.encode(`${recordPrefix}style:${encoded}\n`);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function encodeKeyInputMessage(
|
|
381
|
+
input: WebHostKeyInput
|
|
382
|
+
): Uint8Array {
|
|
383
|
+
const modifiers = Math.max(0, Math.round(input.modifiers ?? 0));
|
|
384
|
+
if (input.key === "character") {
|
|
385
|
+
return textEncoder.encode(
|
|
386
|
+
`${recordPrefix}key:character:${encodeURIComponent(input.character ?? "")}:${modifiers}\n`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
return textEncoder.encode(`${recordPrefix}key:${input.key}:${modifiers}\n`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function encodePasteInputMessage(
|
|
393
|
+
text: string
|
|
394
|
+
): Uint8Array {
|
|
395
|
+
return textEncoder.encode(`${recordPrefix}paste:${encodeURIComponent(text)}\n`);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function encodeMouseInputMessage(
|
|
399
|
+
input: WebHostMouseInput
|
|
400
|
+
): Uint8Array {
|
|
401
|
+
return textEncoder.encode(
|
|
402
|
+
recordPrefix + [
|
|
403
|
+
"mouse",
|
|
404
|
+
input.kind,
|
|
405
|
+
formatCellCoordinate(input.x),
|
|
406
|
+
formatCellCoordinate(input.y),
|
|
407
|
+
input.button ?? "none",
|
|
408
|
+
Math.round(input.deltaX ?? 0),
|
|
409
|
+
Math.round(input.deltaY ?? 0),
|
|
410
|
+
Math.max(0, Math.round(input.modifiers ?? 0)),
|
|
411
|
+
].join(":") + "\n"
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function formatCellCoordinate(
|
|
416
|
+
value: number
|
|
417
|
+
): string {
|
|
418
|
+
return Number.isFinite(value) ? String(value) : "0";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function isWebHostSurfaceFrame(
|
|
422
|
+
value: unknown
|
|
423
|
+
): value is WebHostSurfaceFrame {
|
|
424
|
+
if (!value || typeof value !== "object") {
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
const frame = value as Partial<WebHostSurfaceFrame>;
|
|
428
|
+
return (frame.version === 1 || frame.version === 2)
|
|
429
|
+
&& (
|
|
430
|
+
frame.sequence === undefined
|
|
431
|
+
|| (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)
|
|
432
|
+
)
|
|
433
|
+
&& typeof frame.width === "number"
|
|
434
|
+
&& typeof frame.height === "number"
|
|
435
|
+
&& Array.isArray(frame.styles)
|
|
436
|
+
&& Array.isArray(frame.rows)
|
|
437
|
+
&& frame.rows.every(isWebHostSurfaceRow)
|
|
438
|
+
&& (frame.images === undefined || isWebHostSurfaceImages(frame.images))
|
|
439
|
+
&& (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))
|
|
440
|
+
&& (
|
|
441
|
+
frame.accessibilityTree === undefined
|
|
442
|
+
|| isWebHostAccessibilityNodes(frame.accessibilityTree)
|
|
443
|
+
)
|
|
444
|
+
&& (
|
|
445
|
+
frame.accessibilityAnnouncements === undefined
|
|
446
|
+
|| isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function isWebHostSurfaceDeltaFrame(
|
|
451
|
+
value: unknown
|
|
452
|
+
): value is WebHostSurfaceDeltaFrame {
|
|
453
|
+
if (!value || typeof value !== "object") {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
const frame = value as Partial<WebHostSurfaceDeltaFrame>;
|
|
457
|
+
return frame.version === 3
|
|
458
|
+
&& frame.encoding === "delta"
|
|
459
|
+
&& (
|
|
460
|
+
frame.sequence === undefined
|
|
461
|
+
|| (Number.isSafeInteger(frame.sequence) && frame.sequence >= 0)
|
|
462
|
+
)
|
|
463
|
+
&& typeof frame.width === "number"
|
|
464
|
+
&& typeof frame.height === "number"
|
|
465
|
+
&& Array.isArray(frame.styles)
|
|
466
|
+
&& Array.isArray(frame.deltaRows)
|
|
467
|
+
&& frame.deltaRows.every(isWebHostSurfaceDeltaRow)
|
|
468
|
+
&& (frame.images === undefined || isWebHostSurfaceImages(frame.images))
|
|
469
|
+
&& (frame.damage === undefined || isWebHostSurfaceDamage(frame.damage))
|
|
470
|
+
&& (
|
|
471
|
+
frame.accessibilityTree === undefined
|
|
472
|
+
|| isWebHostAccessibilityNodes(frame.accessibilityTree)
|
|
473
|
+
)
|
|
474
|
+
&& (
|
|
475
|
+
frame.accessibilityAnnouncements === undefined
|
|
476
|
+
|| isWebHostAccessibilityAnnouncements(frame.accessibilityAnnouncements)
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function isWebHostSurfaceDeltaRow(
|
|
481
|
+
value: unknown
|
|
482
|
+
): value is WebHostSurfaceDeltaRow {
|
|
483
|
+
return Array.isArray(value)
|
|
484
|
+
&& value.length === 2
|
|
485
|
+
&& Number.isSafeInteger(value[0])
|
|
486
|
+
&& value[0] >= 0
|
|
487
|
+
&& isWebHostSurfaceRow(value[1]);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function isWebHostSurfaceRow(
|
|
491
|
+
value: unknown
|
|
492
|
+
): value is WebHostSurfaceCell[] {
|
|
493
|
+
return Array.isArray(value) && value.every(isWebHostSurfaceCell);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
function isWebHostSurfaceCell(
|
|
497
|
+
value: unknown
|
|
498
|
+
): value is WebHostSurfaceCell {
|
|
499
|
+
return Array.isArray(value)
|
|
500
|
+
&& value.length === 4
|
|
501
|
+
&& Number.isSafeInteger(value[0])
|
|
502
|
+
&& value[0] >= 0
|
|
503
|
+
&& typeof value[1] === "string"
|
|
504
|
+
&& Number.isSafeInteger(value[2])
|
|
505
|
+
&& value[2] >= 1
|
|
506
|
+
&& Number.isSafeInteger(value[3])
|
|
507
|
+
&& value[3] >= 0;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function isWebHostAccessibilityNodes(
|
|
511
|
+
value: unknown
|
|
512
|
+
): value is WebHostAccessibilityNode[] {
|
|
513
|
+
return Array.isArray(value) && value.every(isWebHostAccessibilityNode);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function isWebHostAccessibilityNode(
|
|
517
|
+
value: unknown
|
|
518
|
+
): value is WebHostAccessibilityNode {
|
|
519
|
+
if (!value || typeof value !== "object") {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
const node = value as Partial<WebHostAccessibilityNode>;
|
|
523
|
+
return typeof node.id === "string"
|
|
524
|
+
&& (node.parentId === undefined || typeof node.parentId === "string")
|
|
525
|
+
&& isWebHostSurfaceRect(node.rect)
|
|
526
|
+
&& typeof node.role === "string"
|
|
527
|
+
&& (node.label === undefined || typeof node.label === "string")
|
|
528
|
+
&& (node.hint === undefined || typeof node.hint === "string")
|
|
529
|
+
&& (
|
|
530
|
+
node.liveRegion === undefined
|
|
531
|
+
|| node.liveRegion === "off"
|
|
532
|
+
|| node.liveRegion === "polite"
|
|
533
|
+
|| node.liveRegion === "assertive"
|
|
534
|
+
)
|
|
535
|
+
&& (node.cursorAnchor === undefined || isWebHostAccessibilityPoint(node.cursorAnchor))
|
|
536
|
+
&& (node.isFocused === undefined || typeof node.isFocused === "boolean");
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function isWebHostAccessibilityPoint(
|
|
540
|
+
value: unknown
|
|
541
|
+
): value is WebHostAccessibilityPoint {
|
|
542
|
+
return Array.isArray(value)
|
|
543
|
+
&& value.length === 2
|
|
544
|
+
&& value.every((entry) => typeof entry === "number");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
function isWebHostAccessibilityAnnouncements(
|
|
548
|
+
value: unknown
|
|
549
|
+
): value is WebHostAccessibilityAnnouncement[] {
|
|
550
|
+
return Array.isArray(value) && value.every(isWebHostAccessibilityAnnouncement);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function isWebHostAccessibilityAnnouncement(
|
|
554
|
+
value: unknown
|
|
555
|
+
): value is WebHostAccessibilityAnnouncement {
|
|
556
|
+
if (!value || typeof value !== "object") {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
const announcement = value as Partial<WebHostAccessibilityAnnouncement>;
|
|
560
|
+
return typeof announcement.message === "string"
|
|
561
|
+
&& (
|
|
562
|
+
announcement.politeness === "off"
|
|
563
|
+
|| announcement.politeness === "polite"
|
|
564
|
+
|| announcement.politeness === "assertive"
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function isWebHostSurfaceImages(
|
|
569
|
+
value: unknown
|
|
570
|
+
): value is WebHostSurfaceImage[] {
|
|
571
|
+
return Array.isArray(value) && value.every(isWebHostSurfaceImage);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function isWebHostSurfaceImage(
|
|
575
|
+
value: unknown
|
|
576
|
+
): value is WebHostSurfaceImage {
|
|
577
|
+
if (!value || typeof value !== "object") {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
const image = value as Partial<WebHostSurfaceImage>;
|
|
581
|
+
return typeof image.id === "string"
|
|
582
|
+
&& isWebHostSurfaceImageFormat(image.format)
|
|
583
|
+
&& isWebHostSurfaceRect(image.bounds)
|
|
584
|
+
&& isWebHostSurfaceRect(image.visibleBounds)
|
|
585
|
+
&& isWebHostSurfaceScalingMode(image.scalingMode)
|
|
586
|
+
&& (image.pixelSize === undefined || isWebHostSurfaceSize(image.pixelSize))
|
|
587
|
+
&& (image.dataBase64 === undefined || typeof image.dataBase64 === "string");
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function isWebHostSurfaceDamage(
|
|
591
|
+
value: unknown
|
|
592
|
+
): value is WebHostSurfaceDamage {
|
|
593
|
+
if (!value || typeof value !== "object") {
|
|
594
|
+
return false;
|
|
595
|
+
}
|
|
596
|
+
const damage = value as Partial<WebHostSurfaceDamage>;
|
|
597
|
+
return Array.isArray(damage.textRows)
|
|
598
|
+
&& damage.textRows.every(isWebHostSurfaceDamageTextRow)
|
|
599
|
+
&& typeof damage.requiresFullTextRepaint === "boolean"
|
|
600
|
+
&& typeof damage.requiresFullGraphicsReplay === "boolean";
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function isWebHostSurfaceDamageTextRow(
|
|
604
|
+
value: unknown
|
|
605
|
+
): value is WebHostSurfaceDamageTextRow {
|
|
606
|
+
return Array.isArray(value)
|
|
607
|
+
&& value.length === 2
|
|
608
|
+
&& typeof value[0] === "number"
|
|
609
|
+
&& Array.isArray(value[1])
|
|
610
|
+
&& value[1].every(isWebHostSurfaceDamageRange);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function isWebHostSurfaceDamageRange(
|
|
614
|
+
value: unknown
|
|
615
|
+
): value is WebHostSurfaceDamageRange {
|
|
616
|
+
return Array.isArray(value)
|
|
617
|
+
&& value.length === 2
|
|
618
|
+
&& typeof value[0] === "number"
|
|
619
|
+
&& typeof value[1] === "number";
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function isWebHostSurfaceImageFormat(
|
|
623
|
+
value: unknown
|
|
624
|
+
): value is WebHostSurfaceImageFormat {
|
|
625
|
+
return value === "png" || value === "jpeg" || value === "gif";
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function isWebHostSurfaceRect(
|
|
629
|
+
value: unknown
|
|
630
|
+
): value is WebHostSurfaceRect {
|
|
631
|
+
return Array.isArray(value)
|
|
632
|
+
&& value.length === 4
|
|
633
|
+
&& value.every((entry) => typeof entry === "number");
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function isWebHostSurfaceSize(
|
|
637
|
+
value: unknown
|
|
638
|
+
): value is WebHostSurfaceSize {
|
|
639
|
+
return Array.isArray(value)
|
|
640
|
+
&& value.length === 2
|
|
641
|
+
&& value.every((entry) => typeof entry === "number");
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function isWebHostSurfaceScalingMode(
|
|
645
|
+
value: unknown
|
|
646
|
+
): value is WebHostSurfaceImage["scalingMode"] {
|
|
647
|
+
return value === "stretch" || value === "fit" || value === "fill";
|
|
648
|
+
}
|