@effindomv2/runtime 0.1.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 (92) hide show
  1. package/LICENSE.md +6 -0
  2. package/dist/bridge.js +4 -0
  3. package/dist/bridge.js.map +7 -0
  4. package/dist/effindom.v2.manifest.json +68 -0
  5. package/dist/fonts/NotoColorEmoji.ttf +0 -0
  6. package/dist/fonts/NotoEmoji-Regular.ttf +0 -0
  7. package/dist/fonts/NotoSans-Bold.ttf +0 -0
  8. package/dist/fonts/NotoSans-BoldItalic.ttf +0 -0
  9. package/dist/fonts/NotoSans-Italic.ttf +0 -0
  10. package/dist/fonts/NotoSans-Regular.ttf +0 -0
  11. package/dist/fonts/NotoSansMono-Bold.ttf +0 -0
  12. package/dist/fonts/NotoSansMono-Regular.ttf +0 -0
  13. package/dist/fonts/NotoSansSymbols2-Regular.ttf +0 -0
  14. package/dist/harness.js +2 -0
  15. package/dist/harness.js.map +7 -0
  16. package/dist/index.html +53 -0
  17. package/dist/runtime/effindom-core-v2.wasm32-simd.JQXIaRaN0-JahfIVFiSLE49WzzCENvef_2EDEm09nJs.wasm +0 -0
  18. package/dist/runtime/effindom-core-v2.wasm32-simd.y7RzpkMARiFeRkpgiqKQsAfv4Hf17NYdpni-6aLNhMs.js.symbols +10079 -0
  19. package/dist/runtime/effindom-core-v2.wasm32-simd.yhT7DGUv4soEv4W91WVZl3T7T_ecKojk5_IcnwL79a0.js +1 -0
  20. package/dist/runtime/effindom-core-v2.wasm32.JSfMkp9ertJzSZxA-_xz3yacrJUhswxlwbqbJLRIuqw.wasm +0 -0
  21. package/dist/runtime/effindom-core-v2.wasm32.xNgsQv7dCwf8Uy-PfJSoRNyk9-q1OSogUwkk5g6ZBjk.js.symbols +10088 -0
  22. package/dist/runtime/effindom-core-v2.wasm32.yhT7DGUv4soEv4W91WVZl3T7T_ecKojk5_IcnwL79a0.js +1 -0
  23. package/dist/runtime/effindom-core-v2.wasm64-simd.GkByf-CPorNOs1CORny_8JjVk8Z3piiFq92r-uw1Syc.js +1 -0
  24. package/dist/runtime/effindom-core-v2.wasm64-simd.p4P98oRu2wEWxtRRW8RHr27JhGeWvWlziZXDM_z3Nc4.js.symbols +10286 -0
  25. package/dist/runtime/effindom-core-v2.wasm64-simd.y75FYXRwhQrpaDGYbZWrohGDv0AmjTb-EjXwOjBIgnM.wasm +0 -0
  26. package/dist/runtime/effindom-core-v2.wasm64.GkByf-CPorNOs1CORny_8JjVk8Z3piiFq92r-uw1Syc.js +1 -0
  27. package/dist/runtime/effindom-core-v2.wasm64.emhE1_CJs4_zXp8wiQS_5lYpUQ0OchmXgxksi0ykaBs.js.symbols +10298 -0
  28. package/dist/runtime/effindom-core-v2.wasm64.sO-Yu70cfN8Qs3a5iEp6cbFPaiOchqcMKUzryu4npNo.wasm +0 -0
  29. package/dist/runtime/effindom-ui-v2.wasm32-simd.0Mas1XD03eYvemryTioWaZOBuBA5ij7MFlTa8CgEZWs.wasm +0 -0
  30. package/dist/runtime/effindom-ui-v2.wasm32-simd.ThSDClMnSWdwf9d89JZfYor0G1Z6OxR4lOc75rNRuD4.js.symbols +1890 -0
  31. package/dist/runtime/effindom-ui-v2.wasm32-simd.wved0xEV4EKXVNBU3Sx7giD4faxD2YII9sQ2N_wCP4I.js +2 -0
  32. package/dist/runtime/effindom-ui-v2.wasm32.H7kYg99bT9ADGh0uUvj6H9Dk1L058nVFLv_4R79IXW8.js.symbols +1900 -0
  33. package/dist/runtime/effindom-ui-v2.wasm32.tp53X7nHfG_EUq29naDyElfnqhMw2D1Tr1T-BJAYO7w.wasm +0 -0
  34. package/dist/runtime/effindom-ui-v2.wasm32.wved0xEV4EKXVNBU3Sx7giD4faxD2YII9sQ2N_wCP4I.js +2 -0
  35. package/dist/runtime/effindom-ui-v2.wasm64-simd.86tk9Z3xIpgTOykET_8Nn9iUVJnp1AzOHW4fVQRGtQE.wasm +0 -0
  36. package/dist/runtime/effindom-ui-v2.wasm64-simd.RQaXil22Chu63-vxK9oOuX8wUY044kbo190oYIbBU4M.js.symbols +1918 -0
  37. package/dist/runtime/effindom-ui-v2.wasm64-simd.ZS1KEAg0XQex-VXkfgpBHE8MIoqPF8qpaf8nOjANb_U.js +2 -0
  38. package/dist/runtime/effindom-ui-v2.wasm64.YSwpMFbr-Q1SBe0Ze8mub1u1PqsvSz3QIYuA3eaUMME.js.symbols +1924 -0
  39. package/dist/runtime/effindom-ui-v2.wasm64.ZS1KEAg0XQex-VXkfgpBHE8MIoqPF8qpaf8nOjANb_U.js +2 -0
  40. package/dist/runtime/effindom-ui-v2.wasm64.ioQ9DuM6gR_EjlfRHdF8EvNPBcKCs0PQbbY9-cjTV6Y.wasm +0 -0
  41. package/dist/runtime/icudt_minimal.962CX1q0-Nbv-OqXPaub5piYTOLumUk-nEvemcvvnpw.dat +0 -0
  42. package/package.json +62 -0
  43. package/scripts/build.sh +279 -0
  44. package/scripts/build_assets.sh +51 -0
  45. package/scripts/font_assets.sh +52 -0
  46. package/scripts/generate_manifest.py +121 -0
  47. package/scripts/stage_package_assets.sh +42 -0
  48. package/src/bridge/commit-policy.ts +10 -0
  49. package/src/bridge/events/canvas-geometry.ts +78 -0
  50. package/src/bridge/events/key-router.ts +187 -0
  51. package/src/bridge/events/pointer-router.ts +619 -0
  52. package/src/bridge/events/semantic-hit-testing.ts +27 -0
  53. package/src/bridge/events.ts +54 -0
  54. package/src/bridge/find-dialog.ts +690 -0
  55. package/src/bridge/find-session.ts +158 -0
  56. package/src/bridge/font-catalog.ts +51 -0
  57. package/src/bridge/google-fonts.ts +63 -0
  58. package/src/bridge/incremental-font-packages.ts +216 -0
  59. package/src/bridge/init.ts +77 -0
  60. package/src/bridge/interaction/editor-model.ts +371 -0
  61. package/src/bridge/interaction/editor-mutations.ts +495 -0
  62. package/src/bridge/interaction/editor-session.ts +628 -0
  63. package/src/bridge/interaction/logs.ts +23 -0
  64. package/src/bridge/interaction/text-encoding.ts +51 -0
  65. package/src/bridge/interaction.ts +86 -0
  66. package/src/bridge/local-types.ts +105 -0
  67. package/src/bridge/platform.ts +68 -0
  68. package/src/bridge/pointer-move-coalescer.ts +41 -0
  69. package/src/bridge/pull-to-refresh.ts +124 -0
  70. package/src/bridge/render-loop.ts +268 -0
  71. package/src/bridge/runtime/asset-manager.ts +202 -0
  72. package/src/bridge/runtime/find-controller.ts +269 -0
  73. package/src/bridge/runtime/font-manager.ts +691 -0
  74. package/src/bridge/runtime/open-canvas-api.ts +72 -0
  75. package/src/bridge/runtime/semantic-controller.ts +133 -0
  76. package/src/bridge/runtime/text-documents.ts +234 -0
  77. package/src/bridge/runtime.ts +315 -0
  78. package/src/bridge/touch-gesture.ts +159 -0
  79. package/src/bridge/utils/assets.ts +572 -0
  80. package/src/bridge/utils/backends.ts +163 -0
  81. package/src/bridge/utils/encoding.ts +128 -0
  82. package/src/bridge/utils/fetch.ts +147 -0
  83. package/src/bridge/utils/heap.ts +118 -0
  84. package/src/bridge.ts +93 -0
  85. package/src/clipboard.ts +139 -0
  86. package/src/core-types.ts +595 -0
  87. package/src/find-on-page.ts +284 -0
  88. package/src/harness.ts +53 -0
  89. package/src/index.ts +40 -0
  90. package/src/open-canvas.ts +108 -0
  91. package/src/runtime-config.ts +96 -0
  92. package/src/semantic.ts +905 -0
@@ -0,0 +1,572 @@
1
+ import { EdBackendType, EdDeviceState } from '../../core-types';
2
+ import type {
3
+ BridgeLoaderInfo,
4
+ BridgeLogs,
5
+ CoreModule,
6
+ UiFactory,
7
+ UiModule,
8
+ } from '../../core-types';
9
+ import type {
10
+ ArchitectureSelection,
11
+ PreparedRuntimeAssets,
12
+ PreparedWasmAsset,
13
+ RequestedRendererBackend,
14
+ RuntimeManifest,
15
+ } from '../local-types';
16
+ import type { EdBackendType as EdBackendTypeValue } from '../../core-types';
17
+ import { DEFAULT_BACKEND_LADDER } from './backends';
18
+ import { fetchBinaryAsset, fetchScriptSource, fetchWithRetry, loadScriptResource, resolveAssetUrl, verifyFetchedIntegrity, ASSET_FETCH_ATTEMPTS } from './fetch';
19
+ import { writeBytesToHeap } from './heap';
20
+
21
+ declare const HEAPU8: Uint8Array | undefined;
22
+ declare const HEAPU32: Uint32Array | undefined;
23
+
24
+ declare global {
25
+ interface EffinDomRuntimeConfig {
26
+ manifestUrl?: string;
27
+ }
28
+
29
+ interface Window {
30
+ __effindomRuntime?: EffinDomRuntimeConfig;
31
+ }
32
+ }
33
+
34
+ interface LoadedRuntimeManifest {
35
+ readonly manifest: RuntimeManifest;
36
+ readonly manifestUrl: string;
37
+ }
38
+
39
+ const MEMORY64_VALIDATION_MODULE_BYTES = new Uint8Array([
40
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0f, 0x03, 0x60,
41
+ 0x02, 0x7f, 0x7e, 0x01, 0x7f, 0x60, 0x01, 0x7e, 0x00, 0x60, 0x00, 0x01,
42
+ 0x7e, 0x03, 0x04, 0x03, 0x00, 0x01, 0x02, 0x04, 0x05, 0x01, 0x70, 0x05,
43
+ 0x01, 0x01, 0x05, 0x06, 0x01, 0x05, 0x82, 0x02, 0x82, 0x02, 0x06, 0x08,
44
+ 0x01, 0x7e, 0x01, 0x42, 0x80, 0x88, 0x04, 0x0b, 0x07, 0x68, 0x05, 0x06,
45
+ 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x04, 0x6d, 0x61, 0x69,
46
+ 0x6e, 0x00, 0x00, 0x19, 0x5f, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65,
47
+ 0x63, 0x74, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
48
+ 0x74, 0x61, 0x62, 0x6c, 0x65, 0x01, 0x00, 0x19, 0x5f, 0x65, 0x6d, 0x73,
49
+ 0x63, 0x72, 0x69, 0x70, 0x74, 0x65, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x63,
50
+ 0x6b, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x00, 0x01, 0x1c,
51
+ 0x65, 0x6d, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x65, 0x6e, 0x5f, 0x73,
52
+ 0x74, 0x61, 0x63, 0x6b, 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x75, 0x72,
53
+ 0x72, 0x65, 0x6e, 0x74, 0x00, 0x02, 0x0a, 0x12, 0x03, 0x04, 0x00, 0x41,
54
+ 0x00, 0x0b, 0x06, 0x00, 0x20, 0x00, 0x24, 0x00, 0x0b, 0x04, 0x00, 0x23,
55
+ 0x00, 0x0b,
56
+ ]);
57
+
58
+ const SIMD_VALIDATION_MODULE_BYTES = new Uint8Array([
59
+ 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
60
+ 0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
61
+ 0x03, 0x02, 0x01, 0x00,
62
+ 0x0a, 0x17, 0x01, 0x15, 0x00, 0xfd, 0x0c,
63
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
64
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
65
+ 0x1a, 0x0b,
66
+ ]);
67
+
68
+ function requireTextElement(id: string): HTMLElement {
69
+ const element = document.getElementById(id);
70
+ if (!(element instanceof HTMLElement)) {
71
+ throw new Error(`Expected #${id} element.`);
72
+ }
73
+ return element;
74
+ }
75
+
76
+ export function showIcuError(message: string): void {
77
+ const errorBox = document.getElementById('icu-error');
78
+ const messageNode = document.getElementById('icu-error-message');
79
+ if (errorBox instanceof HTMLElement && messageNode instanceof HTMLElement) {
80
+ messageNode.textContent = message;
81
+ errorBox.style.display = 'block';
82
+ return;
83
+ }
84
+
85
+ const overlay = document.getElementById('effindom-loading-overlay');
86
+ const overlayTitle = document.getElementById('effindom-loading-title');
87
+ const overlayDetail = document.getElementById('effindom-loading-detail');
88
+ if (
89
+ overlay instanceof HTMLElement &&
90
+ overlayTitle instanceof HTMLElement &&
91
+ overlayDetail instanceof HTMLElement
92
+ ) {
93
+ overlay.dataset.state = 'error';
94
+ overlay.hidden = false;
95
+ overlay.setAttribute('aria-hidden', 'false');
96
+ overlayTitle.textContent = 'The typesetter dragon sneezed on the runtime.';
97
+ overlayDetail.textContent = message;
98
+ }
99
+ }
100
+
101
+ export function createErrorWithCause(message: string, cause: unknown): Error {
102
+ const wrappedError = new Error(message) as Error & { cause?: unknown };
103
+ wrappedError.cause = cause;
104
+ return wrappedError;
105
+ }
106
+
107
+ export async function waitForAnimationFrame(): Promise<void> {
108
+ await new Promise<void>((resolve) => {
109
+ requestAnimationFrame(() => {
110
+ resolve();
111
+ });
112
+ });
113
+ }
114
+
115
+ export function delay(ms: number): Promise<void> {
116
+ return new Promise<void>((resolve) => {
117
+ window.setTimeout(resolve, ms);
118
+ });
119
+ }
120
+
121
+ export function clearRecordMap<T>(recordMap: Record<string, T>): void {
122
+ for (const key of Object.keys(recordMap)) {
123
+ Reflect.deleteProperty(recordMap, key);
124
+ }
125
+ }
126
+
127
+ export function resetBridgeLogs(logs: BridgeLogs): void {
128
+ logs.pointerEvents.length = 0;
129
+ logs.focusEvents.length = 0;
130
+ logs.textChanges.length = 0;
131
+ logs.selectionChanges.length = 0;
132
+ logs.crossSelectionChanges.length = 0;
133
+ logs.clipboardWrites.length = 0;
134
+ logs.clipboardReadRequests.length = 0;
135
+ logs.scrollEvents.length = 0;
136
+ logs.missingFontCoverageRequests.length = 0;
137
+ logs.incrementalFontPackageRequests.length = 0;
138
+ }
139
+
140
+ function supportsMemory64(): boolean {
141
+ try {
142
+ new WebAssembly.Memory({ initial: 1, maximum: 1, index: 'i64' } as WebAssembly.MemoryDescriptor & { index: 'i64' });
143
+ if (WebAssembly.validate !== undefined) {
144
+ return WebAssembly.validate(MEMORY64_VALIDATION_MODULE_BYTES);
145
+ }
146
+ void new WebAssembly.Module(MEMORY64_VALIDATION_MODULE_BYTES);
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ function supportsSimd(): boolean {
154
+ try {
155
+ if (WebAssembly.validate !== undefined) {
156
+ return WebAssembly.validate(SIMD_VALIDATION_MODULE_BYTES);
157
+ }
158
+ void new WebAssembly.Module(SIMD_VALIDATION_MODULE_BYTES);
159
+ return true;
160
+ } catch {
161
+ return false;
162
+ }
163
+ }
164
+
165
+ function normalizeWasmArchitecture(requestedArchitecture: string | null | undefined): ArchitectureSelection['requestedArchitecture'] {
166
+ const value = (requestedArchitecture ?? 'auto').toLowerCase();
167
+ if (value === 'wasm32' || value === 'wasm32-simd' || value === 'wasm64' || value === 'wasm64-simd') {
168
+ return value;
169
+ }
170
+ return 'auto';
171
+ }
172
+
173
+ function getManifestArchitectureEntry(
174
+ manifest: RuntimeManifest,
175
+ architecture: Exclude<ArchitectureSelection['requestedArchitecture'], 'auto'>,
176
+ ): ArchitectureSelection['manifestEntry'] | null {
177
+ return manifest.architectures[architecture] ?? null;
178
+ }
179
+
180
+ function selectManifestArchitecture(manifest: RuntimeManifest, requestedArchitecture: string | null | undefined): ArchitectureSelection {
181
+ const normalizedRequest = normalizeWasmArchitecture(requestedArchitecture);
182
+ const memory64Supported = supportsMemory64();
183
+ const simdSupported = supportsSimd();
184
+ const availableArchitectures = Object.keys(manifest.architectures).filter((architecture) => manifest.architectures[architecture as Exclude<ArchitectureSelection['requestedArchitecture'], 'auto'>] !== undefined);
185
+ let selectedArchitecture: Exclude<ArchitectureSelection['requestedArchitecture'], 'auto'> | null = null;
186
+ let selectionReason: string;
187
+
188
+ const architectureUsesMemory64 = (architecture: string): boolean => architecture.startsWith('wasm64');
189
+ const architectureUsesSimd = (architecture: string): boolean => architecture.endsWith('-simd');
190
+
191
+ if (normalizedRequest !== 'auto') {
192
+ const explicitEntry = getManifestArchitectureEntry(manifest, normalizedRequest);
193
+ if (explicitEntry === null) {
194
+ throw new Error(`Manifest does not include ${normalizedRequest} bundles.`);
195
+ }
196
+ if (architectureUsesMemory64(normalizedRequest) && !memory64Supported) {
197
+ throw new Error(`${normalizedRequest} was explicitly requested but this browser does not support Memory64.`);
198
+ }
199
+ if (architectureUsesSimd(normalizedRequest) && !simdSupported) {
200
+ throw new Error(`${normalizedRequest} was explicitly requested but this browser does not support WebAssembly SIMD.`);
201
+ }
202
+ selectedArchitecture = normalizedRequest;
203
+ selectionReason = `Explicit ${normalizedRequest} request.`;
204
+ } else {
205
+ const preferredArchitectures: Exclude<ArchitectureSelection['requestedArchitecture'], 'auto'>[] = [];
206
+ if (memory64Supported && simdSupported) {
207
+ preferredArchitectures.push('wasm64-simd');
208
+ }
209
+ if (memory64Supported) {
210
+ preferredArchitectures.push('wasm64');
211
+ }
212
+ if (simdSupported) {
213
+ preferredArchitectures.push('wasm32-simd');
214
+ }
215
+ preferredArchitectures.push('wasm32');
216
+
217
+ for (const candidate of preferredArchitectures) {
218
+ if (getManifestArchitectureEntry(manifest, candidate) !== null) {
219
+ selectedArchitecture = candidate;
220
+ break;
221
+ }
222
+ }
223
+
224
+ if (selectedArchitecture === null) {
225
+ throw new Error('Manifest does not expose any wasm bundle architectures compatible with this browser.');
226
+ }
227
+
228
+ if (selectedArchitecture === 'wasm64-simd') {
229
+ selectionReason = 'Browser supports Memory64 + SIMD, so EffinDom selected the wasm64-simd bundle set.';
230
+ } else if (selectedArchitecture === 'wasm64') {
231
+ selectionReason = 'Browser supports Memory64, so EffinDom selected the wasm64 bundle set.';
232
+ } else if (selectedArchitecture === 'wasm32-simd') {
233
+ selectionReason = 'Browser supports SIMD, so EffinDom selected the wasm32-simd bundle set.';
234
+ } else {
235
+ selectionReason = 'Browser selected the wasm32 bundle set.';
236
+ }
237
+ }
238
+
239
+ const manifestEntry = getManifestArchitectureEntry(manifest, selectedArchitecture);
240
+ if (manifestEntry === null) {
241
+ throw new Error(`Manifest entry for ${selectedArchitecture} is missing.`);
242
+ }
243
+
244
+ return {
245
+ requestedArchitecture: normalizedRequest,
246
+ selectedArchitecture,
247
+ availableArchitectures,
248
+ memory64Supported,
249
+ simdSupported,
250
+ selectionReason,
251
+ manifestEntry,
252
+ };
253
+ }
254
+
255
+ function readManifestUrl(): string {
256
+ const runtimeConfig = window.__effindomRuntime;
257
+ if (runtimeConfig === undefined) {
258
+ throw new Error('Missing effindom-runtime-config.js. Expected window.__effindomRuntime.manifestUrl before bridge.js loads.');
259
+ }
260
+ if (typeof runtimeConfig.manifestUrl !== 'string' || runtimeConfig.manifestUrl.length === 0) {
261
+ throw new Error('Malformed effindom-runtime-config.js. Expected window.__effindomRuntime.manifestUrl to be a non-empty string.');
262
+ }
263
+ return runtimeConfig.manifestUrl;
264
+ }
265
+
266
+ function resolveManifestAssetUrl(manifestUrl: string, assetUrl: string): string {
267
+ return new URL(assetUrl, manifestUrl).toString();
268
+ }
269
+
270
+ async function loadRuntimeManifest(): Promise<LoadedRuntimeManifest> {
271
+ const manifestUrl = resolveAssetUrl(readManifestUrl());
272
+ const manifest = await fetchWithRetry<RuntimeManifest>(
273
+ manifestUrl,
274
+ ASSET_FETCH_ATTEMPTS,
275
+ async (response) => await response.json() as RuntimeManifest,
276
+ { cache: 'no-store' },
277
+ );
278
+ return {
279
+ manifest,
280
+ manifestUrl,
281
+ };
282
+ }
283
+
284
+ async function instantiatePreparedWasm(
285
+ preparedAsset: PreparedWasmAsset,
286
+ imports: WebAssembly.Imports,
287
+ ): Promise<{ readonly instance: WebAssembly.Instance; readonly module: WebAssembly.Module; readonly compileMode: 'cached-module' }> {
288
+ const module = await preparedAsset.modulePromise;
289
+ const instance = await WebAssembly.instantiate(module, imports);
290
+ return { instance, module, compileMode: 'cached-module' };
291
+ }
292
+
293
+ export async function loadIcuData(ui: UiModule, preparedAssets: PreparedRuntimeAssets): Promise<void> {
294
+ const bytes = await preparedAssets.icu.bytesPromise;
295
+ const heapBytes = writeBytesToHeap(ui, bytes);
296
+ try {
297
+ ui._ui_register_icu_data(heapBytes.ptr, heapBytes.len);
298
+ } finally {
299
+ heapBytes.dispose();
300
+ }
301
+ }
302
+
303
+ function describeAbortReason(value: unknown, fallback: string): string {
304
+ if (typeof value === 'string' && value.length > 0) {
305
+ return value;
306
+ }
307
+ if (value instanceof Error && value.message.length > 0) {
308
+ return value.message;
309
+ }
310
+ return fallback;
311
+ }
312
+
313
+ function prepareWasmAsset(url: string, integrity: string | null): PreparedWasmAsset {
314
+ const requestInit: RequestInit = {
315
+ credentials: 'same-origin',
316
+ cache: 'force-cache',
317
+ };
318
+ if (integrity !== null) {
319
+ requestInit.integrity = integrity;
320
+ }
321
+ const bytesPromise = fetchWithRetry<ArrayBuffer>(
322
+ url,
323
+ ASSET_FETCH_ATTEMPTS,
324
+ async (response) => await response.arrayBuffer(),
325
+ requestInit,
326
+ ).then(async (buffer) => await verifyFetchedIntegrity(url, buffer, integrity));
327
+ return {
328
+ url,
329
+ integrity,
330
+ bytesPromise,
331
+ modulePromise: bytesPromise.then(async (buffer) => await WebAssembly.compile(buffer)),
332
+ };
333
+ }
334
+
335
+ const coreScriptRunnerCache = new Map<string, Promise<(module: CoreModule) => void>>();
336
+
337
+ async function getCoreScriptRunner(scriptUrl: string, integrity: string | null | undefined): Promise<(module: CoreModule) => void> {
338
+ const absoluteUrl = resolveAssetUrl(scriptUrl);
339
+ const cacheKey = `${absoluteUrl}::${integrity ?? ''}`;
340
+ let runnerPromise = coreScriptRunnerCache.get(cacheKey);
341
+ if (runnerPromise === undefined) {
342
+ runnerPromise = fetchScriptSource(absoluteUrl, integrity).then((sourceText) => {
343
+ const wrappedSource = `${sourceText}\n//# sourceURL=${absoluteUrl.replace(/\s/g, '%20')}`;
344
+ return new Function('Module', wrappedSource) as (module: CoreModule) => void;
345
+ });
346
+ coreScriptRunnerCache.set(cacheKey, runnerPromise);
347
+ }
348
+ return await runnerPromise;
349
+ }
350
+
351
+ export async function loadCoreModule(
352
+ bundle: PreparedRuntimeAssets['coreBundle'],
353
+ preparedWasm: PreparedWasmAsset,
354
+ canvas: HTMLCanvasElement,
355
+ loaderInfo: BridgeLoaderInfo,
356
+ ): Promise<CoreModule> {
357
+ return await new Promise<CoreModule>((resolve, reject) => {
358
+ const module: CoreModule = {
359
+ HEAPU8: new Uint8Array(),
360
+ HEAPU32: new Uint32Array(),
361
+ usesMemory64: loaderInfo.selectedWasmArchitecture.startsWith('wasm64'),
362
+ locateFile: (path) => {
363
+ if (path.endsWith('.wasm')) {
364
+ return resolveAssetUrl(bundle.wasm);
365
+ }
366
+ return resolveAssetUrl(path);
367
+ },
368
+ instantiateWasm: (imports, receiveInstance) => {
369
+ void instantiatePreparedWasm(preparedWasm, imports).then((result) => {
370
+ loaderInfo.coreCompileMode = result.compileMode;
371
+ receiveInstance(result.instance, result.module);
372
+ }).catch((error: unknown) => {
373
+ reject(createErrorWithCause('Failed to instantiate Core wasm.', error));
374
+ });
375
+ return {};
376
+ },
377
+ onAbort: (what) => {
378
+ reject(new Error(describeAbortReason(what, 'Core wasm aborted.')));
379
+ },
380
+ refreshHeapViews: () => {
381
+ if (module.wasmMemory !== undefined) {
382
+ const buffer = module.wasmMemory.buffer;
383
+ module.HEAPU8 = new Uint8Array(buffer);
384
+ module.HEAPU32 = new Uint32Array(buffer);
385
+ } else if (typeof HEAPU8 !== 'undefined') {
386
+ // Fallback for older Emscripten builds that don't expose Module["memory"].
387
+ module.HEAPU8 = HEAPU8;
388
+ if (typeof HEAPU32 !== 'undefined') {
389
+ module.HEAPU32 = HEAPU32;
390
+ }
391
+ }
392
+ },
393
+ canvas,
394
+ onRuntimeInitialized: () => {
395
+ // Emscripten assigns Module["memory"] = wasmMemory when the WASM instance
396
+ // is processed, before onRuntimeInitialized fires.
397
+ const emMemory = (module as { memory?: unknown }).memory;
398
+ if (emMemory instanceof WebAssembly.Memory) {
399
+ module.wasmMemory = emMemory;
400
+ }
401
+ module.refreshHeapViews?.();
402
+ resolve(module);
403
+ },
404
+ _malloc: () => 0,
405
+ _free: () => undefined,
406
+ _ed_init: () => undefined,
407
+ _ed_init_webgl: () => undefined,
408
+ _ed_init_sw: () => undefined,
409
+ _ed_resize: () => undefined,
410
+ _ed_register_font: () => undefined,
411
+ _ed_unregister_font: () => undefined,
412
+ _ed_register_svg: () => undefined,
413
+ _ed_register_texture_rgba: () => undefined,
414
+ _ed_unregister_texture: () => undefined,
415
+ _ed_execute_command_buffer: () => undefined,
416
+ _ed_reset_scene: () => undefined,
417
+ _ed_render_frame: () => undefined,
418
+ _ed_clear_focus_state: () => undefined,
419
+ _ed_clear_text_input_state: () => undefined,
420
+ _ed_recover_device: () => undefined,
421
+ _ed_hit_test: () => 0,
422
+ _ed_get_sw_framebuffer: () => 0,
423
+ _ed_get_backend_type: () => EdBackendType.NONE,
424
+ _ed_get_device_state: () => EdDeviceState.OK,
425
+ _ed_notify_webgl_context_lost: () => undefined,
426
+ _ed_debug_simulate_device_lost: () => undefined,
427
+ };
428
+ void getCoreScriptRunner(bundle.js, bundle.js_integrity ?? null).then((runCoreScript) => {
429
+ runCoreScript(module);
430
+ }).catch(reject);
431
+ });
432
+ }
433
+
434
+ export async function loadUiModule(
435
+ bundle: PreparedRuntimeAssets['uiBundle'],
436
+ preparedWasm: PreparedWasmAsset,
437
+ loaderInfo: BridgeLoaderInfo,
438
+ ): Promise<UiModule> {
439
+ if (window.EffinDomUiV2ModuleFactory === undefined) {
440
+ await loadScriptResource(bundle.js, bundle.js_integrity ?? null);
441
+ }
442
+ if (window.EffinDomUiV2ModuleFactory === undefined) {
443
+ throw new Error('EffinDomUiV2ModuleFactory did not load.');
444
+ }
445
+ let rejectInstantiation: ((reason: unknown) => void) | null = null;
446
+ const instantiationFailure = new Promise<never>((_, reject) => {
447
+ rejectInstantiation = reject;
448
+ });
449
+ const modulePromise = window.EffinDomUiV2ModuleFactory({
450
+ locateFile: (path: string) => {
451
+ if (path.endsWith('.wasm')) {
452
+ return resolveAssetUrl(bundle.wasm);
453
+ }
454
+ return resolveAssetUrl(path);
455
+ },
456
+ instantiateWasm: (imports: WebAssembly.Imports, receiveInstance: (instance: WebAssembly.Instance, module?: WebAssembly.Module) => void) => {
457
+ void instantiatePreparedWasm(preparedWasm, imports).then((result) => {
458
+ loaderInfo.uiCompileMode = result.compileMode;
459
+ receiveInstance(result.instance, result.module);
460
+ }).catch((error: unknown) => {
461
+ rejectInstantiation?.(createErrorWithCause('Failed to instantiate Ui wasm.', error));
462
+ });
463
+ return {};
464
+ },
465
+ onAbort: (what: unknown) => {
466
+ rejectInstantiation?.(new Error(describeAbortReason(what, 'Ui wasm aborted.')));
467
+ },
468
+ });
469
+ const ui = await Promise.race([modulePromise, instantiationFailure]);
470
+ ui.usesMemory64 = loaderInfo.selectedWasmArchitecture.startsWith('wasm64');
471
+ const uiEmMemory = (ui as { memory?: unknown }).memory;
472
+ if (uiEmMemory instanceof WebAssembly.Memory) {
473
+ ui.wasmMemory = uiEmMemory;
474
+ }
475
+ ui.refreshHeapViews = () => {
476
+ if (ui.wasmMemory !== undefined) {
477
+ const buffer = ui.wasmMemory.buffer;
478
+ ui.HEAPU8 = new Uint8Array(buffer);
479
+ ui.HEAPU32 = new Uint32Array(buffer);
480
+ }
481
+ };
482
+ ui.refreshHeapViews();
483
+ return ui;
484
+ }
485
+
486
+ function readRequestedArchitecture(): string | null {
487
+ return new URLSearchParams(window.location.search).get('arch');
488
+ }
489
+
490
+ function readRequestedRendererBackend(): RequestedRendererBackend {
491
+ const value = new URLSearchParams(window.location.search).get('backend')?.toLowerCase() ?? 'auto';
492
+ if (value === 'webgpu' || value === 'graphite') {
493
+ return 'webgpu';
494
+ }
495
+ if (value === 'webgl2' || value === 'ganesh') {
496
+ return 'webgl2';
497
+ }
498
+ if (value === 'software' || value === 'raster' || value === 'cpu') {
499
+ return 'cpu';
500
+ }
501
+ return 'auto';
502
+ }
503
+
504
+ export function buildBackendLadder(requestedBackend: RequestedRendererBackend): readonly EdBackendTypeValue[] {
505
+ if (requestedBackend === 'webgpu') {
506
+ // TEMPORARY: explicit ?backend=webgpu currently downgrades to WebGL2/CPU.
507
+ return [EdBackendType.WEBGL2, EdBackendType.CPU];
508
+ }
509
+ if (requestedBackend === 'webgl2') {
510
+ return [EdBackendType.WEBGL2, EdBackendType.CPU];
511
+ }
512
+ if (requestedBackend === 'cpu') {
513
+ return [EdBackendType.CPU];
514
+ }
515
+ return DEFAULT_BACKEND_LADDER;
516
+ }
517
+
518
+ export async function prepareRuntimeAssets(): Promise<PreparedRuntimeAssets> {
519
+ const loadedManifest = await loadRuntimeManifest();
520
+ const manifest = loadedManifest.manifest;
521
+ const manifestUrl = loadedManifest.manifestUrl;
522
+ const selection = selectManifestArchitecture(manifest, readRequestedArchitecture());
523
+ const requestedRendererBackend = readRequestedRendererBackend();
524
+ const coreBundle = {
525
+ ...selection.manifestEntry.core,
526
+ js: resolveManifestAssetUrl(manifestUrl, selection.manifestEntry.core.js),
527
+ wasm: resolveManifestAssetUrl(manifestUrl, selection.manifestEntry.core.wasm),
528
+ };
529
+ const uiBundle = {
530
+ ...selection.manifestEntry.ui,
531
+ js: resolveManifestAssetUrl(manifestUrl, selection.manifestEntry.ui.js),
532
+ wasm: resolveManifestAssetUrl(manifestUrl, selection.manifestEntry.ui.wasm),
533
+ };
534
+ const icuAsset = manifest.assets?.icu;
535
+ if (icuAsset === undefined) {
536
+ throw new Error('Manifest is missing the ICU asset descriptor.');
537
+ }
538
+
539
+ const loaderInfo: BridgeLoaderInfo = {
540
+ manifestHash: manifest.manifest_hash ?? null,
541
+ requestedWasmArchitecture: selection.requestedArchitecture,
542
+ requestedRendererBackend,
543
+ selectedWasmArchitecture: selection.selectedArchitecture,
544
+ availableWasmArchitectures: selection.availableArchitectures,
545
+ memory64Supported: selection.memory64Supported,
546
+ simdSupported: selection.simdSupported,
547
+ coreCompileMode: 'buffer',
548
+ uiCompileMode: 'buffer',
549
+ icuDataUrl: resolveManifestAssetUrl(manifestUrl, icuAsset.url),
550
+ activeRenderer: 'none',
551
+ deviceRecoveryCount: 0,
552
+ };
553
+
554
+ return {
555
+ manifest,
556
+ selection,
557
+ loaderInfo,
558
+ coreBundle,
559
+ uiBundle,
560
+ coreWasm: {
561
+ ...prepareWasmAsset(coreBundle.wasm, coreBundle.wasm_integrity ?? null),
562
+ },
563
+ uiWasm: {
564
+ ...prepareWasmAsset(uiBundle.wasm, uiBundle.wasm_integrity ?? null),
565
+ },
566
+ icu: {
567
+ url: resolveManifestAssetUrl(manifestUrl, icuAsset.url),
568
+ integrity: icuAsset.integrity ?? null,
569
+ bytesPromise: fetchBinaryAsset(resolveManifestAssetUrl(manifestUrl, icuAsset.url), icuAsset.integrity ?? null),
570
+ },
571
+ };
572
+ }