@effindomv2/fui-as 0.1.37 → 0.1.39

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 (118) hide show
  1. package/browser/routed-app-conventions.ts +1 -1
  2. package/browser/src/common-harness.ts +1 -27
  3. package/browser/src/index.ts +19 -26
  4. package/browser/src/routed-harness.ts +1 -194
  5. package/browser/src/shared-browser.ts +6 -0
  6. package/package.json +23 -13
  7. package/scripts/build.sh +47 -46
  8. package/scripts/framework-host-services.ts +1 -1
  9. package/scripts/generate-host-events.ts +15 -241
  10. package/scripts/generate-host-services.ts +13 -261
  11. package/scripts/hostgen/assemblyscript-host-events.ts +165 -0
  12. package/scripts/hostgen/assemblyscript-host-services.ts +207 -0
  13. package/scripts/hostgen/common.ts +73 -0
  14. package/scripts/hostgen/registry.ts +159 -0
  15. package/src/Fui.ts +65 -5
  16. package/src/FuiExports.ts +12 -1
  17. package/src/bindings/ui.ts +157 -8
  18. package/src/controls/Button.ts +98 -71
  19. package/src/controls/Checkbox.ts +23 -21
  20. package/src/controls/ComboBox.ts +1201 -0
  21. package/src/controls/ContextMenu.ts +279 -165
  22. package/src/controls/Dialog.ts +91 -17
  23. package/src/controls/Dropdown.ts +101 -333
  24. package/src/controls/NavLink.ts +29 -6
  25. package/src/controls/Popup.ts +10 -5
  26. package/src/controls/ProgressBar.ts +1 -2
  27. package/src/controls/RadioButton.ts +34 -13
  28. package/src/controls/RadioGroup.ts +32 -19
  29. package/src/controls/Slider.ts +21 -23
  30. package/src/controls/Switch.ts +34 -17
  31. package/src/controls/index.ts +2 -1
  32. package/src/controls/internal/ButtonPresenter.ts +5 -2
  33. package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -10
  34. package/src/controls/internal/DropdownChevronPresenter.ts +3 -3
  35. package/src/controls/internal/DropdownFieldPresenter.ts +6 -6
  36. package/src/controls/internal/DropdownOptionRowPresenter.ts +5 -4
  37. package/src/controls/internal/PressableLabeledControl.ts +4 -7
  38. package/src/controls/internal/RadioIndicatorPresenter.ts +7 -7
  39. package/src/controls/internal/SelectablePopupList.ts +463 -0
  40. package/src/controls/internal/SliderPresenter.ts +5 -5
  41. package/src/controls/internal/SwitchIndicatorPresenter.ts +63 -17
  42. package/src/controls/internal/TextInputCore.ts +307 -41
  43. package/src/controls/internal/TextInputPresenter.ts +2 -2
  44. package/src/controls/templating.ts +10 -0
  45. package/src/core/Application.ts +17 -8
  46. package/src/core/Bitmap.ts +192 -3
  47. package/src/core/ContextMenuManager.ts +128 -44
  48. package/src/core/DebugApi.ts +17 -2
  49. package/src/core/DragGesture.ts +8 -7
  50. package/src/core/EventRouter.ts +332 -12
  51. package/src/core/Fetch.ts +15 -7
  52. package/src/core/FetchFfi.ts +1 -15
  53. package/src/core/File.ts +75 -34
  54. package/src/core/FocusAdornerManager.ts +3 -3
  55. package/src/core/FocusVisibility.ts +7 -0
  56. package/src/core/FrameScheduler.ts +104 -5
  57. package/src/core/ImageSampling.ts +43 -0
  58. package/src/core/Logger.ts +2 -0
  59. package/src/core/MobileTextSelectionToolbarManager.ts +901 -0
  60. package/src/core/Node.ts +1039 -97
  61. package/src/core/Platform.ts +16 -16
  62. package/src/core/SelectionHandleAdornerManager.ts +697 -0
  63. package/src/core/Theme.ts +34 -20
  64. package/src/core/ToolTipManager.ts +3 -3
  65. package/src/core/Typography.ts +411 -19
  66. package/src/core/Worker.ts +69 -33
  67. package/src/core/event_exports.ts +123 -5
  68. package/src/core/ffi.ts +5 -865
  69. package/src/core/generated/FetchHostAbi.ts +10 -0
  70. package/src/core/generated/FrameworkHostServices.ts +1 -1
  71. package/src/core/generated/HostAbi.ts +220 -0
  72. package/src/core/generated/UiAbi.ts +356 -0
  73. package/src/core/generated/UiEnums.ts +186 -0
  74. package/src/drawing/DrawCallback.ts +17 -1
  75. package/src/drawing/DrawContext.ts +140 -32
  76. package/src/nodes/CustomDrawable.ts +8 -1
  77. package/src/nodes/FlexBox.ts +40 -11
  78. package/src/nodes/Grid.ts +5 -5
  79. package/src/nodes/Image.ts +20 -1
  80. package/src/nodes/RichText.ts +36 -25
  81. package/src/nodes/ScrollBar.ts +2 -1
  82. package/src/nodes/ScrollBox.ts +7 -2
  83. package/src/nodes/ScrollView.ts +17 -6
  84. package/src/nodes/Svg.ts +18 -1
  85. package/src/nodes/Text.ts +1 -1
  86. package/src/nodes/TextCore.ts +115 -20
  87. package/src/nodes/index.ts +1 -1
  88. package/src/text/DynamicTextLayout.ts +275 -0
  89. package/src/text/TextLayout.ts +308 -0
  90. package/src/worker/Worker.ts +3 -36
  91. package/src/worker/WorkerJob.ts +5 -9
  92. package/src/worker/ffi.ts +0 -6
  93. package/browser/src/common-harness/host-imports.ts +0 -447
  94. package/browser/src/common-harness/interop.ts +0 -39
  95. package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
  96. package/browser/src/common-harness/managed-harness-canvas-host.ts +0 -72
  97. package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
  98. package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
  99. package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
  100. package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
  101. package/browser/src/common-harness/managed-harness-session.ts +0 -15
  102. package/browser/src/common-harness/managed-harness.ts +0 -1472
  103. package/browser/src/common-harness/managed-history.ts +0 -168
  104. package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
  105. package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
  106. package/browser/src/common-harness/text-session-bridge.ts +0 -452
  107. package/browser/src/common-harness/types.ts +0 -206
  108. package/browser/src/common-harness/ui-chrome.ts +0 -206
  109. package/browser/src/common-harness/ui-imports.ts +0 -559
  110. package/browser/src/common-harness/wasm-module-cache.ts +0 -47
  111. package/browser/src/file-processing-worker.ts +0 -89
  112. package/browser/src/host-events.ts +0 -118
  113. package/browser/src/host-services.ts +0 -483
  114. package/browser/src/persisted-ui-state.ts +0 -206
  115. package/browser/src/routed-app-conventions.ts +0 -292
  116. package/browser/src/worker-bootstrap.ts +0 -916
  117. package/browser/src/worker-manager.ts +0 -234
  118. package/browser/src/worker-types.ts +0 -68
@@ -1,1066 +0,0 @@
1
- import type { BridgeRuntime } from '@effindomv2/runtime';
2
- import { computeModifiers, getPointerPosition } from '@effindomv2/runtime';
3
-
4
- import { writeExternalDropPayload, writeFileListPayload, writeWriterPayload } from './managed-harness-file-payloads';
5
- import {
6
- EXTERNAL_DRAG_EVENT_DROP,
7
- EXTERNAL_DRAG_EVENT_ENTER,
8
- EXTERNAL_DRAG_EVENT_LEAVE,
9
- EXTERNAL_DRAG_EVENT_OVER,
10
- EXTERNAL_DROP_ITEM_KIND_FILE,
11
- FILE_CAPABILITY_CHUNKED_READ,
12
- FILE_CAPABILITY_CHUNKED_WRITE,
13
- FILE_CAPABILITY_NATIVE_SAVE_PICKER,
14
- FILE_CAPABILITY_OPEN,
15
- FILE_CAPABILITY_PROCESS_WORKER_SAVE,
16
- FILE_CAPABILITY_READ,
17
- FILE_CAPABILITY_SAVE,
18
- FILE_SAVE_MODE_DOWNLOAD,
19
- FILE_SAVE_MODE_NATIVE_PICKER,
20
- FILE_STATUS_CANCELLED,
21
- FILE_STATUS_ERROR,
22
- FILE_STATUS_SUCCESS,
23
- type ActiveFileProcessingRecord,
24
- type ActiveFileWriterRecord,
25
- type ExternalHarnessDropItem,
26
- type SavePickerWindow,
27
- type StoredFileRecord,
28
- type WritableFileStreamLike,
29
- } from './managed-harness-file-types';
30
- import type { WorkerHostServicesBundleConfig } from '../worker-types';
31
- import type { HarnessAppSession } from './managed-harness-session';
32
-
33
- interface ManagedHarnessFileHostDependencies {
34
- getCurrentSession(): HarnessAppSession | null;
35
- getRuntime(): BridgeRuntime;
36
- readAppUtf8(ptr: number, len: number): string;
37
- readAppBytes(ptr: number, len: number): Uint8Array;
38
- writeTextCallbackPayload(session: HarnessAppSession, text: string, context: string): number;
39
- describeHarnessError(error: unknown): string;
40
- workerBootstrapUrl: string;
41
- getCurrentWorkerHostServices(): WorkerHostServicesBundleConfig | undefined;
42
- }
43
-
44
- const encoder = new TextEncoder();
45
-
46
- function copyBytesToArrayBuffer(bytes: Uint8Array): ArrayBuffer {
47
- const copied = new Uint8Array(bytes.byteLength);
48
- copied.set(bytes);
49
- return copied.buffer as ArrayBuffer;
50
- }
51
-
52
- function supportsNativeSavePicker(): boolean {
53
- return typeof (window as SavePickerWindow).showSaveFilePicker === 'function';
54
- }
55
-
56
- function getFileCapabilities(): number {
57
- let flags = FILE_CAPABILITY_OPEN | FILE_CAPABILITY_READ | FILE_CAPABILITY_SAVE | FILE_CAPABILITY_CHUNKED_READ;
58
- if (supportsNativeSavePicker()) {
59
- flags |= FILE_CAPABILITY_CHUNKED_WRITE | FILE_CAPABILITY_NATIVE_SAVE_PICKER;
60
- flags |= FILE_CAPABILITY_PROCESS_WORKER_SAVE;
61
- }
62
- return flags;
63
- }
64
-
65
- function resolveSuggestedName(suggestedName: string, fileExtension: string): string {
66
- const trimmedName = suggestedName.trim();
67
- const trimmedExtension = fileExtension.trim();
68
- if (trimmedName.length === 0) {
69
- if (trimmedExtension.length > 0) {
70
- return `export${trimmedExtension.startsWith('.') ? trimmedExtension : `.${trimmedExtension}`}`;
71
- }
72
- return 'export.bin';
73
- }
74
- if (trimmedExtension.length === 0) {
75
- return trimmedName;
76
- }
77
- const normalizedExtension = trimmedExtension.startsWith('.') ? trimmedExtension : `.${trimmedExtension}`;
78
- return trimmedName.endsWith(normalizedExtension) ? trimmedName : `${trimmedName}${normalizedExtension}`;
79
- }
80
-
81
- async function abortWritableStream(stream: WritableFileStreamLike): Promise<void> {
82
- if (typeof stream.abort === 'function') {
83
- await stream.abort();
84
- return;
85
- }
86
- await stream.close();
87
- }
88
-
89
- export function createManagedHarnessFileHost(dependencies: ManagedHarnessFileHostDependencies) {
90
- let nextStoredBrowserFileId = 1;
91
- let nextFileWriterId = 1;
92
- let nextExternalDropItemId = 1;
93
- const storedBrowserFiles = new Map<string, File>();
94
- const activeFileWriters = new Map<string, ActiveFileWriterRecord>();
95
- const activeFileProcessingRequests = new Map<number, ActiveFileProcessingRecord>();
96
- const cancelledFileProcessingRequestIds = new Set<number>();
97
- let activeExternalDropItems: Array<ExternalHarnessDropItem> = [];
98
-
99
- function emitFilePickResult(
100
- session: HarnessAppSession | null,
101
- requestId: number,
102
- status: number,
103
- files: readonly StoredFileRecord[] = [],
104
- message = '',
105
- ): void {
106
- if (session === null) {
107
- return;
108
- }
109
- const payloadLength = status === FILE_STATUS_SUCCESS
110
- ? writeFileListPayload(session, files)
111
- : dependencies.writeTextCallbackPayload(session, message, 'File picker result');
112
- session.exports.__fui_on_file_pick_result(
113
- requestId,
114
- status,
115
- payloadLength > 0 ? session.textBufferPtr : 0,
116
- payloadLength,
117
- );
118
- }
119
-
120
- function emitFileReadResult(
121
- session: HarnessAppSession | null,
122
- requestId: number,
123
- status: number,
124
- offsetBytes: bigint,
125
- fileSizeBytes: bigint,
126
- bytes: Uint8Array | null = null,
127
- message = '',
128
- ): void {
129
- if (session === null) {
130
- return;
131
- }
132
- let payloadLength = 0;
133
- if (status === FILE_STATUS_SUCCESS) {
134
- payloadLength = bytes?.length ?? 0;
135
- if (payloadLength > session.textBufferSize) {
136
- throw new Error('File read result exceeds the shared AssemblyScript text buffer.');
137
- }
138
- if (payloadLength > 0 && bytes !== null) {
139
- new Uint8Array(session.memory.buffer, session.textBufferPtr, payloadLength).set(bytes);
140
- }
141
- } else {
142
- payloadLength = dependencies.writeTextCallbackPayload(session, message, 'File read failure');
143
- }
144
- session.exports.__fui_on_file_read_result(
145
- requestId,
146
- status,
147
- offsetBytes,
148
- fileSizeBytes,
149
- payloadLength > 0 ? session.textBufferPtr : 0,
150
- payloadLength,
151
- );
152
- }
153
-
154
- function emitFileSaveResult(
155
- session: HarnessAppSession | null,
156
- requestId: number,
157
- status: number,
158
- writtenBytes: bigint,
159
- fileName = '',
160
- mode = FILE_SAVE_MODE_DOWNLOAD,
161
- message = '',
162
- ): void {
163
- if (session === null) {
164
- return;
165
- }
166
- const payloadLength = status === FILE_STATUS_SUCCESS
167
- ? writeWriterPayload(session, mode, fileName)
168
- : dependencies.writeTextCallbackPayload(session, message, 'File save failure');
169
- session.exports.__fui_on_file_save_result(
170
- requestId,
171
- status,
172
- writtenBytes,
173
- payloadLength > 0 ? session.textBufferPtr : 0,
174
- payloadLength,
175
- );
176
- }
177
-
178
- function emitFileWriterCreated(
179
- session: HarnessAppSession | null,
180
- requestId: number,
181
- status: number,
182
- writerId = '',
183
- fileName = '',
184
- mode = FILE_SAVE_MODE_NATIVE_PICKER,
185
- message = '',
186
- ): void {
187
- if (session === null) {
188
- return;
189
- }
190
- const payloadLength = status === FILE_STATUS_SUCCESS
191
- ? writeWriterPayload(session, mode, writerId, fileName)
192
- : dependencies.writeTextCallbackPayload(session, message, 'File writer creation failure');
193
- session.exports.__fui_on_file_writer_created(
194
- requestId,
195
- status,
196
- payloadLength > 0 ? session.textBufferPtr : 0,
197
- payloadLength,
198
- );
199
- }
200
-
201
- function emitFileWriteResult(
202
- session: HarnessAppSession | null,
203
- requestId: number,
204
- status: number,
205
- writtenBytes: bigint,
206
- totalWrittenBytes: bigint,
207
- message = '',
208
- ): void {
209
- if (session === null) {
210
- return;
211
- }
212
- const payloadLength = status === FILE_STATUS_SUCCESS
213
- ? 0
214
- : dependencies.writeTextCallbackPayload(session, message, 'File write failure');
215
- session.exports.__fui_on_file_write_result(
216
- requestId,
217
- status,
218
- writtenBytes,
219
- totalWrittenBytes,
220
- payloadLength > 0 ? session.textBufferPtr : 0,
221
- payloadLength,
222
- );
223
- }
224
-
225
- function emitFileFinishResult(
226
- session: HarnessAppSession | null,
227
- requestId: number,
228
- status: number,
229
- writtenBytes: bigint,
230
- fileName = '',
231
- mode = FILE_SAVE_MODE_NATIVE_PICKER,
232
- message = '',
233
- ): void {
234
- if (session === null) {
235
- return;
236
- }
237
- const payloadLength = status === FILE_STATUS_SUCCESS
238
- ? writeWriterPayload(session, mode, fileName)
239
- : dependencies.writeTextCallbackPayload(session, message, 'File writer finish failure');
240
- session.exports.__fui_on_file_finish_result(
241
- requestId,
242
- status,
243
- writtenBytes,
244
- payloadLength > 0 ? session.textBufferPtr : 0,
245
- payloadLength,
246
- );
247
- }
248
-
249
- function emitFileWorkerProcessProgress(
250
- session: HarnessAppSession | null,
251
- requestId: number,
252
- processedBytes: bigint,
253
- totalBytes: bigint,
254
- outputFileName: string | null,
255
- ): void {
256
- if (session === null) {
257
- return;
258
- }
259
- const payloadLength = dependencies.writeTextCallbackPayload(
260
- session,
261
- outputFileName ?? '',
262
- 'File worker process progress',
263
- );
264
- session.exports.__fui_on_file_worker_process_progress(
265
- requestId,
266
- processedBytes,
267
- totalBytes,
268
- payloadLength > 0 ? session.textBufferPtr : 0,
269
- payloadLength,
270
- );
271
- }
272
-
273
- function emitFileWorkerProcessChunk(
274
- session: HarnessAppSession | null,
275
- requestId: number,
276
- offsetBytes: bigint,
277
- fileSizeBytes: bigint,
278
- bytes: Uint8Array,
279
- ): void {
280
- if (session === null) {
281
- return;
282
- }
283
- const payloadLength = bytes.length;
284
- if (payloadLength > session.textBufferSize) {
285
- throw new Error('File worker process chunk exceeds the shared AssemblyScript text buffer.');
286
- }
287
- if (payloadLength > 0) {
288
- new Uint8Array(session.memory.buffer, session.textBufferPtr, payloadLength).set(bytes);
289
- }
290
- session.exports.__fui_on_file_worker_process_chunk(
291
- requestId,
292
- offsetBytes,
293
- fileSizeBytes,
294
- payloadLength > 0 ? session.textBufferPtr : 0,
295
- payloadLength,
296
- );
297
- }
298
-
299
- function emitFileWorkerProcessComplete(
300
- session: HarnessAppSession | null,
301
- requestId: number,
302
- processedBytes: bigint,
303
- outputFileName: string | null,
304
- workerResult: string | null = null,
305
- ): void {
306
- if (session === null) {
307
- return;
308
- }
309
- // Encode outputFileName\0workerResult so the wasm handler splits them.
310
- const combined = (outputFileName ?? '') + '\0' + (workerResult ?? '');
311
- const payloadLength = dependencies.writeTextCallbackPayload(
312
- session,
313
- combined,
314
- 'File worker process completion',
315
- );
316
- session.exports.__fui_on_file_worker_process_complete(
317
- requestId,
318
- processedBytes,
319
- payloadLength > 0 ? session.textBufferPtr : 0,
320
- payloadLength,
321
- );
322
- }
323
-
324
- function emitFileWorkerProcessError(
325
- session: HarnessAppSession | null,
326
- requestId: number,
327
- status: number,
328
- message: string,
329
- ): void {
330
- if (session === null) {
331
- return;
332
- }
333
- const payloadLength = dependencies.writeTextCallbackPayload(session, message, 'File worker process failure');
334
- session.exports.__fui_on_file_worker_process_error(
335
- requestId,
336
- status,
337
- payloadLength > 0 ? session.textBufferPtr : 0,
338
- payloadLength,
339
- );
340
- }
341
-
342
- function cleanupFileProcessingRequest(requestId: number): void {
343
- cancelledFileProcessingRequestIds.delete(requestId);
344
- activeFileProcessingRequests.delete(requestId);
345
- }
346
-
347
- function failFileProcessingRequest(record: ActiveFileProcessingRecord, status: number, message: string): void {
348
- cleanupFileProcessingRequest(record.requestId);
349
- if (dependencies.getCurrentSession() === record.session) {
350
- emitFileWorkerProcessError(record.session, record.requestId, status, message);
351
- }
352
- }
353
-
354
- async function startFileProcessing(
355
- requestId: number,
356
- session: HarnessAppSession,
357
- sourceFile: File,
358
- suggestedName: string,
359
- chunkBytes: number,
360
- saveToPickedFile: boolean,
361
- workerHostServices: WorkerHostServicesBundleConfig | undefined,
362
- ): Promise<void> {
363
- const workerId = requestId;
364
- const manifestUrl = new URL('./worker-manifest.json', dependencies.workerBootstrapUrl).toString();
365
- const manifestResponse = await fetch(manifestUrl);
366
- if (!manifestResponse.ok) {
367
- emitFileWorkerProcessError(session, requestId, FILE_STATUS_ERROR, 'Failed to load worker manifest.');
368
- return;
369
- }
370
- const manifest = await manifestResponse.json();
371
- const wasmUrl = manifest.entries?.['fileProcessorWorker'];
372
- if (typeof wasmUrl !== 'string' || wasmUrl.length === 0) {
373
- emitFileWorkerProcessError(session, requestId, FILE_STATUS_ERROR, 'Missing "fileProcessorWorker" entry in worker manifest.');
374
- return;
375
- }
376
- const resolvedWasmUrl = new URL(wasmUrl, manifestUrl).toString();
377
- const startProcessor = (targetFileName: string | null, stream: WritableFileStreamLike | null): void => {
378
- const worker = new Worker(dependencies.workerBootstrapUrl);
379
- const record: ActiveFileProcessingRecord = {
380
- requestId,
381
- session,
382
- sourceFileName: sourceFile.name,
383
- targetFileName,
384
- totalBytes: sourceFile.size,
385
- stream,
386
- saveToPickedFile,
387
- cancelled: false,
388
- worker,
389
- processedBytes: 0,
390
- };
391
- activeFileProcessingRequests.set(requestId, record);
392
- worker.addEventListener('message', (event: MessageEvent) => {
393
- const msg = event.data;
394
- if (msg.type === 'file-process-chunk' && stream !== null) {
395
- void stream.write(msg.bytes).catch((error: unknown) => {
396
- failFileProcessingRequest(record, FILE_STATUS_ERROR, dependencies.describeHarnessError(error));
397
- });
398
- } else if (msg.type === 'progress') {
399
- if (dependencies.getCurrentSession() === record.session) {
400
- const parts = (msg.text as string).split(' ');
401
- const processed = parseInt(parts[0] ?? '0', 10);
402
- record.processedBytes = processed;
403
- emitFileWorkerProcessProgress(
404
- record.session,
405
- record.requestId,
406
- BigInt(processed),
407
- BigInt(record.totalBytes),
408
- record.targetFileName,
409
- );
410
- }
411
- } else if (msg.type === 'complete') {
412
- if (dependencies.getCurrentSession() === record.session) {
413
- if (stream !== null) {
414
- void stream.close().catch(() => {
415
- // Swallow close errors.
416
- });
417
- }
418
- const hashText = (msg.text as string);
419
- emitFileWorkerProcessComplete(
420
- record.session,
421
- record.requestId,
422
- BigInt(record.processedBytes),
423
- record.targetFileName,
424
- hashText,
425
- );
426
- }
427
- cleanupFileProcessingRequest(record.requestId);
428
- } else if (msg.type === 'error') {
429
- failFileProcessingRequest(record, FILE_STATUS_ERROR, msg.text);
430
- }
431
- });
432
- worker.addEventListener('error', () => {
433
- failFileProcessingRequest(record, FILE_STATUS_ERROR, 'Worker crashed.');
434
- });
435
- worker.postMessage({
436
- type: 'start-file-process',
437
- workerId,
438
- file: sourceFile,
439
- wasmUrl: resolvedWasmUrl,
440
- entryName: 'fileProcessorWorker',
441
- chunkSize: Math.max(1, Math.floor(chunkBytes)),
442
- workerHostServices,
443
- });
444
- };
445
-
446
- if (!saveToPickedFile) {
447
- startProcessor(null, null);
448
- return;
449
- }
450
- if (!supportsNativeSavePicker()) {
451
- emitFileWorkerProcessError(session, requestId, FILE_STATUS_ERROR, 'Worker file processing requires the native save picker.');
452
- return;
453
- }
454
- const savePicker = (window as SavePickerWindow).showSaveFilePicker;
455
- if (typeof savePicker !== 'function') {
456
- emitFileWorkerProcessError(session, requestId, FILE_STATUS_ERROR, 'Worker file processing requires the native save picker.');
457
- return;
458
- }
459
- void savePicker({ suggestedName }).then((handle) =>
460
- handle.createWritable().then((writableStream) => {
461
- if (dependencies.getCurrentSession() !== session || cancelledFileProcessingRequestIds.has(requestId)) {
462
- void abortWritableStream(writableStream).catch(() => {
463
- // Ignore cleanup failures after the session moved on.
464
- });
465
- cancelledFileProcessingRequestIds.delete(requestId);
466
- return;
467
- }
468
- startProcessor(handle.name ?? suggestedName, writableStream);
469
- }),
470
- ).catch((error: unknown) => {
471
- cancelledFileProcessingRequestIds.delete(requestId);
472
- if (dependencies.getCurrentSession() !== session) {
473
- return;
474
- }
475
- emitFileWorkerProcessError(
476
- session,
477
- requestId,
478
- error instanceof DOMException && error.name === 'AbortError'
479
- ? FILE_STATUS_CANCELLED
480
- : FILE_STATUS_ERROR,
481
- dependencies.describeHarnessError(error),
482
- );
483
- });
484
- }
485
-
486
- function cancelFileProcessingRequest(requestId: number): void {
487
- cancelledFileProcessingRequestIds.add(requestId);
488
- const record = activeFileProcessingRequests.get(requestId);
489
- if (record === undefined) {
490
- return;
491
- }
492
- record.cancelled = true;
493
- record.worker.terminate();
494
- }
495
-
496
- function storeBrowserFile(file: File, prefix: string): StoredFileRecord {
497
- const id = `${prefix}-${String(nextStoredBrowserFileId++)}`;
498
- storedBrowserFiles.set(id, file);
499
- return {
500
- id,
501
- file,
502
- };
503
- }
504
-
505
- function snapshotStoredBrowserFile(file: File, prefix: string): ExternalHarnessDropItem {
506
- const stored = storeBrowserFile(file, prefix);
507
- return {
508
- id: stored.id,
509
- kind: EXTERNAL_DROP_ITEM_KIND_FILE,
510
- name: file.name,
511
- mimeType: file.type.length > 0 ? file.type : null,
512
- sizeBytes: file.size,
513
- };
514
- }
515
-
516
- function clearActiveExternalDropItems(): void {
517
- activeExternalDropItems = [];
518
- }
519
-
520
- function snapshotExternalDropItems(dataTransfer: DataTransfer | null): Array<ExternalHarnessDropItem> {
521
- if (dataTransfer === null) {
522
- return [];
523
- }
524
- const files = Array.from(dataTransfer.files ?? []);
525
- if (files.length > 0) {
526
- return files.map((file) => snapshotStoredBrowserFile(file, 'external-drop'));
527
- }
528
- const itemEntries = Array.from(dataTransfer.items ?? []);
529
- const fileEntries = itemEntries.filter((item) => item.kind === 'file');
530
- if (fileEntries.length > 0) {
531
- return fileEntries.map((item, index) => ({
532
- id: `external-drop-${String(nextExternalDropItemId++)}`,
533
- kind: EXTERNAL_DROP_ITEM_KIND_FILE,
534
- name: `Dropped file ${String(index + 1)}`,
535
- mimeType: item.type.length > 0 ? item.type : null,
536
- sizeBytes: 0,
537
- }));
538
- }
539
- const dragTypes = Array.from(dataTransfer.types ?? []);
540
- if (dragTypes.includes('Files')) {
541
- return [{
542
- id: `external-drop-${String(nextExternalDropItemId++)}`,
543
- kind: EXTERNAL_DROP_ITEM_KIND_FILE,
544
- name: 'Dropped file',
545
- mimeType: null,
546
- sizeBytes: 0,
547
- }];
548
- }
549
- return [];
550
- }
551
-
552
- function getExternalDropItems(dataTransfer: DataTransfer | null, reuseActive: boolean): Array<ExternalHarnessDropItem> {
553
- if (reuseActive && activeExternalDropItems.length > 0) {
554
- return activeExternalDropItems;
555
- }
556
- const items = snapshotExternalDropItems(dataTransfer);
557
- activeExternalDropItems = items;
558
- return items;
559
- }
560
-
561
- function mapExternalDropEffect(effect: number): DataTransfer['dropEffect'] {
562
- if ((effect & 2) !== 0) {
563
- return 'move';
564
- }
565
- if ((effect & 1) !== 0) {
566
- return 'copy';
567
- }
568
- if ((effect & 4) !== 0) {
569
- return 'link';
570
- }
571
- return 'none';
572
- }
573
-
574
- function dispatchExternalDragEvent(
575
- eventType: number,
576
- event: DragEvent,
577
- options: { readonly handle?: bigint; readonly reuseActiveItems?: boolean } = {},
578
- ): number {
579
- const session = dependencies.getCurrentSession();
580
- if (
581
- session === null ||
582
- session.textBufferPtr === 0 ||
583
- session.textBufferSize === 0
584
- ) {
585
- if (eventType === EXTERNAL_DRAG_EVENT_LEAVE || eventType === EXTERNAL_DRAG_EVENT_DROP) {
586
- clearActiveExternalDropItems();
587
- }
588
- return 0;
589
- }
590
- const items = eventType === EXTERNAL_DRAG_EVENT_LEAVE
591
- ? activeExternalDropItems
592
- : getExternalDropItems(
593
- event.dataTransfer,
594
- eventType === EXTERNAL_DRAG_EVENT_DROP ? false : (options.reuseActiveItems !== false),
595
- );
596
- if (items.length === 0 && eventType !== EXTERNAL_DRAG_EVENT_LEAVE) {
597
- return 0;
598
- }
599
- const runtime = dependencies.getRuntime();
600
- const position = getPointerPosition(runtime.canvas, event);
601
- const handle = options.handle ?? runtime.getHandleFromPoint(position.x, position.y);
602
- const payloadLength = items.length > 0 ? writeExternalDropPayload(session, items) : 0;
603
- const effect = session.exports.__fui_on_external_drag_event(
604
- eventType,
605
- handle,
606
- position.x,
607
- position.y,
608
- computeModifiers(event),
609
- payloadLength > 0 ? session.textBufferPtr : 0,
610
- payloadLength,
611
- );
612
- if (eventType === EXTERNAL_DRAG_EVENT_LEAVE || eventType === EXTERNAL_DRAG_EVENT_DROP) {
613
- clearActiveExternalDropItems();
614
- }
615
- return effect;
616
- }
617
-
618
- function cancelAllForSession(session: HarnessAppSession | null): void {
619
- clearActiveExternalDropItems();
620
- for (const [writerId, record] of activeFileWriters.entries()) {
621
- if (session !== null && record.session !== session) {
622
- continue;
623
- }
624
- activeFileWriters.delete(writerId);
625
- void abortWritableStream(record.stream).catch(() => {
626
- // Ignore disposal cleanup failures.
627
- });
628
- }
629
- for (const [requestId, record] of activeFileProcessingRequests.entries()) {
630
- if (session !== null && record.session !== session) {
631
- continue;
632
- }
633
- cancelFileProcessingRequest(requestId);
634
- }
635
- }
636
-
637
- return {
638
- cancelAllForSession,
639
- dispatchExternalDragEvent,
640
- mapExternalDropEffect,
641
- imports: {
642
- fui_file_capabilities(): number {
643
- return getFileCapabilities();
644
- },
645
- fui_file_pick(requestId: number, acceptPtr: number, acceptLen: number, multiple: boolean): void {
646
- const session = dependencies.getCurrentSession();
647
- if (session === null) {
648
- return;
649
- }
650
- const accept = dependencies.readAppUtf8(acceptPtr, acceptLen);
651
- const host = document.body ?? document.documentElement;
652
- const input = document.createElement('input');
653
- input.type = 'file';
654
- input.multiple = multiple;
655
- if (accept.length > 0) {
656
- input.accept = accept;
657
- }
658
- input.tabIndex = -1;
659
- input.style.position = 'fixed';
660
- input.style.left = '-10000px';
661
- input.style.top = '0';
662
- input.style.opacity = '0';
663
- host.appendChild(input);
664
- let finished = false;
665
- const complete = (status: number, files: readonly StoredFileRecord[] = [], message = '') => {
666
- if (finished) {
667
- return;
668
- }
669
- finished = true;
670
- window.removeEventListener('focus', handleFocus, true);
671
- input.remove();
672
- if (dependencies.getCurrentSession() !== session) {
673
- return;
674
- }
675
- emitFilePickResult(session, requestId, status, files, message);
676
- };
677
- const handleFocus = () => {
678
- window.setTimeout(() => {
679
- if (finished) {
680
- return;
681
- }
682
- const selected = Array.from(input.files ?? []).map((file) => storeBrowserFile(file, 'picked-file'));
683
- if (selected.length > 0) {
684
- complete(FILE_STATUS_SUCCESS, selected);
685
- return;
686
- }
687
- complete(FILE_STATUS_CANCELLED, [], 'File picker cancelled.');
688
- }, 0);
689
- };
690
- input.addEventListener('change', () => {
691
- const selected = Array.from(input.files ?? []).map((file) => storeBrowserFile(file, 'picked-file'));
692
- complete(FILE_STATUS_SUCCESS, selected);
693
- }, { once: true });
694
- window.addEventListener('focus', handleFocus, true);
695
- input.click();
696
- },
697
- fui_file_read_chunk(requestId: number, fileIdPtr: number, fileIdLen: number, offsetBytes: bigint | number, maxBytes: number): void {
698
- const session = dependencies.getCurrentSession();
699
- if (session === null) {
700
- return;
701
- }
702
- const fileId = dependencies.readAppUtf8(fileIdPtr, fileIdLen);
703
- const sourceFile = storedBrowserFiles.get(fileId);
704
- if (sourceFile === undefined) {
705
- emitFileReadResult(session, requestId, FILE_STATUS_ERROR, 0n, 0n, null, `Unknown browser file "${fileId}".`);
706
- return;
707
- }
708
- const numericOffset = typeof offsetBytes === 'bigint' ? Number(offsetBytes) : offsetBytes;
709
- if (!Number.isFinite(numericOffset) || numericOffset < 0) {
710
- emitFileReadResult(session, requestId, FILE_STATUS_ERROR, 0n, BigInt(sourceFile.size), null, 'File read offset was invalid.');
711
- return;
712
- }
713
- const safeOffset = Math.min(sourceFile.size, Math.floor(numericOffset));
714
- const clampedMaxBytes = Math.max(1, Math.min(Math.floor(maxBytes), session.textBufferSize));
715
- void sourceFile.slice(safeOffset, safeOffset + clampedMaxBytes).arrayBuffer().then((buffer) => {
716
- if (dependencies.getCurrentSession() !== session) {
717
- return;
718
- }
719
- emitFileReadResult(
720
- session,
721
- requestId,
722
- FILE_STATUS_SUCCESS,
723
- BigInt(safeOffset),
724
- BigInt(sourceFile.size),
725
- new Uint8Array(buffer),
726
- );
727
- }).catch((error: unknown) => {
728
- if (dependencies.getCurrentSession() !== session) {
729
- return;
730
- }
731
- emitFileReadResult(
732
- session,
733
- requestId,
734
- FILE_STATUS_ERROR,
735
- BigInt(safeOffset),
736
- BigInt(sourceFile.size),
737
- null,
738
- error instanceof Error ? error.message : String(error),
739
- );
740
- });
741
- },
742
- fui_file_save_text(
743
- requestId: number,
744
- suggestedNamePtr: number,
745
- suggestedNameLen: number,
746
- mimeTypePtr: number,
747
- mimeTypeLen: number,
748
- fileExtensionPtr: number,
749
- fileExtensionLen: number,
750
- textPtr: number,
751
- textLen: number,
752
- ): void {
753
- const session = dependencies.getCurrentSession();
754
- if (session === null) {
755
- return;
756
- }
757
- const suggestedName = resolveSuggestedName(
758
- dependencies.readAppUtf8(suggestedNamePtr, suggestedNameLen),
759
- dependencies.readAppUtf8(fileExtensionPtr, fileExtensionLen),
760
- );
761
- const mimeType = dependencies.readAppUtf8(mimeTypePtr, mimeTypeLen);
762
- const text = dependencies.readAppUtf8(textPtr, textLen);
763
- const encoded = encoder.encode(text);
764
- const finishDownload = () => {
765
- const blob = new Blob([encoded], {
766
- type: mimeType.length > 0 ? mimeType : undefined,
767
- });
768
- const url = URL.createObjectURL(blob);
769
- const anchor = document.createElement('a');
770
- anchor.href = url;
771
- anchor.download = suggestedName;
772
- document.body?.appendChild(anchor);
773
- anchor.click();
774
- anchor.remove();
775
- window.setTimeout(() => URL.revokeObjectURL(url), 0);
776
- if (dependencies.getCurrentSession() === session) {
777
- emitFileSaveResult(session, requestId, FILE_STATUS_SUCCESS, BigInt(encoded.length), suggestedName, FILE_SAVE_MODE_DOWNLOAD);
778
- }
779
- };
780
- const savePicker = (window as SavePickerWindow).showSaveFilePicker;
781
- if (typeof savePicker !== 'function') {
782
- finishDownload();
783
- return;
784
- }
785
- void savePicker({ suggestedName }).then((handle) => handle.createWritable().then(async (stream) => {
786
- await stream.write(text);
787
- await stream.close();
788
- if (dependencies.getCurrentSession() === session) {
789
- emitFileSaveResult(
790
- session,
791
- requestId,
792
- FILE_STATUS_SUCCESS,
793
- BigInt(encoded.length),
794
- handle.name ?? suggestedName,
795
- FILE_SAVE_MODE_NATIVE_PICKER,
796
- );
797
- }
798
- })).catch((error: unknown) => {
799
- if (dependencies.getCurrentSession() !== session) {
800
- return;
801
- }
802
- emitFileSaveResult(
803
- session,
804
- requestId,
805
- error instanceof DOMException && error.name === 'AbortError' ? FILE_STATUS_CANCELLED : FILE_STATUS_ERROR,
806
- 0n,
807
- '',
808
- FILE_SAVE_MODE_NATIVE_PICKER,
809
- error instanceof Error ? error.message : String(error),
810
- );
811
- });
812
- },
813
- fui_file_save_bytes(
814
- requestId: number,
815
- suggestedNamePtr: number,
816
- suggestedNameLen: number,
817
- mimeTypePtr: number,
818
- mimeTypeLen: number,
819
- fileExtensionPtr: number,
820
- fileExtensionLen: number,
821
- bytesPtr: number,
822
- bytesLen: number,
823
- ): void {
824
- const session = dependencies.getCurrentSession();
825
- if (session === null) {
826
- return;
827
- }
828
- const suggestedName = resolveSuggestedName(
829
- dependencies.readAppUtf8(suggestedNamePtr, suggestedNameLen),
830
- dependencies.readAppUtf8(fileExtensionPtr, fileExtensionLen),
831
- );
832
- const mimeType = dependencies.readAppUtf8(mimeTypePtr, mimeTypeLen);
833
- const bytes = dependencies.readAppBytes(bytesPtr, bytesLen);
834
- const copiedBytes = copyBytesToArrayBuffer(bytes);
835
- const finishDownload = () => {
836
- const blob = new Blob([copiedBytes], {
837
- type: mimeType.length > 0 ? mimeType : undefined,
838
- });
839
- const url = URL.createObjectURL(blob);
840
- const anchor = document.createElement('a');
841
- anchor.href = url;
842
- anchor.download = suggestedName;
843
- document.body?.appendChild(anchor);
844
- anchor.click();
845
- anchor.remove();
846
- window.setTimeout(() => URL.revokeObjectURL(url), 0);
847
- if (dependencies.getCurrentSession() === session) {
848
- emitFileSaveResult(session, requestId, FILE_STATUS_SUCCESS, BigInt(bytes.length), suggestedName, FILE_SAVE_MODE_DOWNLOAD);
849
- }
850
- };
851
- const savePicker = (window as SavePickerWindow).showSaveFilePicker;
852
- if (typeof savePicker !== 'function') {
853
- finishDownload();
854
- return;
855
- }
856
- void savePicker({ suggestedName }).then((handle) => handle.createWritable().then(async (stream) => {
857
- await stream.write(copiedBytes);
858
- await stream.close();
859
- if (dependencies.getCurrentSession() === session) {
860
- emitFileSaveResult(
861
- session,
862
- requestId,
863
- FILE_STATUS_SUCCESS,
864
- BigInt(bytes.length),
865
- handle.name ?? suggestedName,
866
- FILE_SAVE_MODE_NATIVE_PICKER,
867
- );
868
- }
869
- })).catch((error: unknown) => {
870
- if (dependencies.getCurrentSession() !== session) {
871
- return;
872
- }
873
- emitFileSaveResult(
874
- session,
875
- requestId,
876
- error instanceof DOMException && error.name === 'AbortError' ? FILE_STATUS_CANCELLED : FILE_STATUS_ERROR,
877
- 0n,
878
- '',
879
- FILE_SAVE_MODE_NATIVE_PICKER,
880
- error instanceof Error ? error.message : String(error),
881
- );
882
- });
883
- },
884
- fui_file_create_writer(
885
- requestId: number,
886
- suggestedNamePtr: number,
887
- suggestedNameLen: number,
888
- _mimeTypePtr: number,
889
- _mimeTypeLen: number,
890
- fileExtensionPtr: number,
891
- fileExtensionLen: number,
892
- ): void {
893
- const session = dependencies.getCurrentSession();
894
- if (session === null) {
895
- return;
896
- }
897
- const savePicker = (window as SavePickerWindow).showSaveFilePicker;
898
- if (typeof savePicker !== 'function') {
899
- emitFileWriterCreated(session, requestId, FILE_STATUS_ERROR, '', '', FILE_SAVE_MODE_NATIVE_PICKER, 'Chunked file writers require the native save picker.');
900
- return;
901
- }
902
- const suggestedName = resolveSuggestedName(
903
- dependencies.readAppUtf8(suggestedNamePtr, suggestedNameLen),
904
- dependencies.readAppUtf8(fileExtensionPtr, fileExtensionLen),
905
- );
906
- void savePicker({ suggestedName }).then((handle) => handle.createWritable().then((stream) => {
907
- const writerId = `writer-${String(nextFileWriterId++)}`;
908
- activeFileWriters.set(writerId, {
909
- id: writerId,
910
- session,
911
- fileName: handle.name ?? suggestedName,
912
- mode: FILE_SAVE_MODE_NATIVE_PICKER,
913
- stream,
914
- writtenBytes: 0,
915
- });
916
- if (dependencies.getCurrentSession() === session) {
917
- emitFileWriterCreated(
918
- session,
919
- requestId,
920
- FILE_STATUS_SUCCESS,
921
- writerId,
922
- handle.name ?? suggestedName,
923
- FILE_SAVE_MODE_NATIVE_PICKER,
924
- );
925
- }
926
- })).catch((error: unknown) => {
927
- if (dependencies.getCurrentSession() !== session) {
928
- return;
929
- }
930
- emitFileWriterCreated(
931
- session,
932
- requestId,
933
- error instanceof DOMException && error.name === 'AbortError' ? FILE_STATUS_CANCELLED : FILE_STATUS_ERROR,
934
- '',
935
- '',
936
- FILE_SAVE_MODE_NATIVE_PICKER,
937
- error instanceof Error ? error.message : String(error),
938
- );
939
- });
940
- },
941
- fui_file_writer_write_text(requestId: number, writerIdPtr: number, writerIdLen: number, textPtr: number, textLen: number): void {
942
- const session = dependencies.getCurrentSession();
943
- if (session === null) {
944
- return;
945
- }
946
- const writerId = dependencies.readAppUtf8(writerIdPtr, writerIdLen);
947
- const record = activeFileWriters.get(writerId);
948
- if (record === undefined) {
949
- emitFileWriteResult(session, requestId, FILE_STATUS_ERROR, 0n, 0n, `Unknown file writer "${writerId}".`);
950
- return;
951
- }
952
- const text = dependencies.readAppUtf8(textPtr, textLen);
953
- const encodedLength = encoder.encode(text).length;
954
- void record.stream.write(text).then(() => {
955
- record.writtenBytes += encodedLength;
956
- if (dependencies.getCurrentSession() === session) {
957
- emitFileWriteResult(
958
- session,
959
- requestId,
960
- FILE_STATUS_SUCCESS,
961
- BigInt(encodedLength),
962
- BigInt(record.writtenBytes),
963
- );
964
- }
965
- }).catch((error: unknown) => {
966
- if (dependencies.getCurrentSession() === session) {
967
- emitFileWriteResult(session, requestId, FILE_STATUS_ERROR, 0n, BigInt(record.writtenBytes), error instanceof Error ? error.message : String(error));
968
- }
969
- });
970
- },
971
- fui_file_writer_write_bytes(requestId: number, writerIdPtr: number, writerIdLen: number, bytesPtr: number, bytesLen: number): void {
972
- const session = dependencies.getCurrentSession();
973
- if (session === null) {
974
- return;
975
- }
976
- const writerId = dependencies.readAppUtf8(writerIdPtr, writerIdLen);
977
- const record = activeFileWriters.get(writerId);
978
- if (record === undefined) {
979
- emitFileWriteResult(session, requestId, FILE_STATUS_ERROR, 0n, 0n, `Unknown file writer "${writerId}".`);
980
- return;
981
- }
982
- const bytes = dependencies.readAppBytes(bytesPtr, bytesLen);
983
- const copiedBytes = copyBytesToArrayBuffer(bytes);
984
- void record.stream.write(copiedBytes).then(() => {
985
- record.writtenBytes += bytes.length;
986
- if (dependencies.getCurrentSession() === session) {
987
- emitFileWriteResult(
988
- session,
989
- requestId,
990
- FILE_STATUS_SUCCESS,
991
- BigInt(bytes.length),
992
- BigInt(record.writtenBytes),
993
- );
994
- }
995
- }).catch((error: unknown) => {
996
- if (dependencies.getCurrentSession() === session) {
997
- emitFileWriteResult(session, requestId, FILE_STATUS_ERROR, 0n, BigInt(record.writtenBytes), error instanceof Error ? error.message : String(error));
998
- }
999
- });
1000
- },
1001
- fui_file_writer_finish(requestId: number, writerIdPtr: number, writerIdLen: number): void {
1002
- const session = dependencies.getCurrentSession();
1003
- if (session === null) {
1004
- return;
1005
- }
1006
- const writerId = dependencies.readAppUtf8(writerIdPtr, writerIdLen);
1007
- const record = activeFileWriters.get(writerId);
1008
- if (record === undefined) {
1009
- emitFileFinishResult(session, requestId, FILE_STATUS_ERROR, 0n, '', FILE_SAVE_MODE_NATIVE_PICKER, `Unknown file writer "${writerId}".`);
1010
- return;
1011
- }
1012
- activeFileWriters.delete(writerId);
1013
- void record.stream.close().then(() => {
1014
- if (dependencies.getCurrentSession() === session) {
1015
- emitFileFinishResult(
1016
- session,
1017
- requestId,
1018
- FILE_STATUS_SUCCESS,
1019
- BigInt(record.writtenBytes),
1020
- record.fileName,
1021
- record.mode,
1022
- );
1023
- }
1024
- }).catch((error: unknown) => {
1025
- if (dependencies.getCurrentSession() === session) {
1026
- emitFileFinishResult(
1027
- session,
1028
- requestId,
1029
- FILE_STATUS_ERROR,
1030
- BigInt(record.writtenBytes),
1031
- '',
1032
- record.mode,
1033
- error instanceof Error ? error.message : String(error),
1034
- );
1035
- }
1036
- });
1037
- },
1038
- fui_file_process_worker_start(
1039
- requestId: number,
1040
- fileIdPtr: number,
1041
- fileIdLen: number,
1042
- suggestedNamePtr: number,
1043
- suggestedNameLen: number,
1044
- chunkBytes: number,
1045
- saveToPickedFile: boolean,
1046
- ): void {
1047
- const session = dependencies.getCurrentSession();
1048
- if (session === null) {
1049
- return;
1050
- }
1051
- const fileId = dependencies.readAppUtf8(fileIdPtr, fileIdLen);
1052
- const sourceFile = storedBrowserFiles.get(fileId);
1053
- if (sourceFile === undefined) {
1054
- emitFileWorkerProcessError(session, requestId, FILE_STATUS_ERROR, `Unknown browser file "${fileId}".`);
1055
- return;
1056
- }
1057
- const suggestedName = resolveSuggestedName(dependencies.readAppUtf8(suggestedNamePtr, suggestedNameLen), '');
1058
- const workerHostServices = dependencies.getCurrentWorkerHostServices();
1059
- void startFileProcessing(requestId, session, sourceFile, suggestedName, chunkBytes, saveToPickedFile, workerHostServices);
1060
- },
1061
- fui_file_process_worker_cancel(requestId: number): void {
1062
- cancelFileProcessingRequest(requestId);
1063
- },
1064
- },
1065
- };
1066
- }