@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,72 @@
1
+ import { cloneSemanticTree } from '../../semantic';
2
+ import type {
3
+ OpenCanvasApi,
4
+ OpenCanvasFindOptions,
5
+ UiModule,
6
+ } from '../../core-types';
7
+ import { findTextInOpenCanvasDocuments } from '../find-session';
8
+ import { FindController } from './find-controller';
9
+ import { SemanticController } from './semantic-controller';
10
+ import { TextDocumentController } from './text-documents';
11
+
12
+ interface OpenCanvasApiAdapterOptions {
13
+ readonly ui: UiModule;
14
+ readonly semantic: SemanticController;
15
+ readonly find: FindController;
16
+ readonly textDocuments: TextDocumentController;
17
+ readonly commitFrame: () => void;
18
+ readonly flushPendingCommit: () => Uint32Array | null;
19
+ }
20
+
21
+ export class OpenCanvasApiAdapter {
22
+ private readonly api: OpenCanvasApi;
23
+
24
+ public constructor(private readonly options: OpenCanvasApiAdapterOptions) {
25
+ this.api = {
26
+ getSemanticTree: () => cloneSemanticTree(this.options.semantic.getSemanticTree()),
27
+ getBoundingBox: (handle: string) => this.options.semantic.getBoundingBox(handle),
28
+ getTextVisibleBounds: (handle: string) => {
29
+ const bounds = this.options.textDocuments.readVisibleTextBounds(handle);
30
+ return bounds === null ? null : { ...bounds };
31
+ },
32
+ getTextDocument: (handle: string) => {
33
+ const snapshot = this.options.textDocuments.readTextDocumentSnapshot(handle);
34
+ return snapshot === null ? null : { ...snapshot.document };
35
+ },
36
+ getRangeRects: (handle: string, start: number, end: number) =>
37
+ this.options.textDocuments.readRangeRects(handle, start, end).map((rect) => ({ ...rect })),
38
+ findText: (query: string, options?: OpenCanvasFindOptions) => {
39
+ const results = findTextInOpenCanvasDocuments(this.options.textDocuments.readFindDocuments(), query, options);
40
+ return {
41
+ query: results.query,
42
+ options: { ...results.options },
43
+ matches: results.matches.map((match) => ({ ...match })),
44
+ };
45
+ },
46
+ setFindState: (state, revealActive = false) => this.options.find.setFindState(state, revealActive),
47
+ getFindState: () => this.options.find.getFindState(),
48
+ setFindMatch: (match) => this.options.find.activateFindMatch(match, false),
49
+ revealRange: (handle: string, start: number, end: number) => {
50
+ const range = this.options.textDocuments.resolveTextRange(handle, start, end);
51
+ if (range === null) {
52
+ return false;
53
+ }
54
+ if (this.options.ui._ui_reveal_text_range(range.handleArg, range.start, range.end) === 0) {
55
+ return false;
56
+ }
57
+ this.options.commitFrame();
58
+ this.options.flushPendingCommit();
59
+ return true;
60
+ },
61
+ };
62
+ window.__OPEN_CANVAS_API__ = this.api;
63
+ }
64
+
65
+ public getApi(): OpenCanvasApi {
66
+ return this.api;
67
+ }
68
+
69
+ public destroy(): void {
70
+ delete window.__OPEN_CANVAS_API__;
71
+ }
72
+ }
@@ -0,0 +1,133 @@
1
+ import { cloneSemanticTree, HiddenDomProjector, parseSemanticBuffer } from '../../semantic';
2
+ import type { SemanticNode, UiModule } from '../../core-types';
3
+ import type { BridgeInteractionState } from '../local-types';
4
+ import { extractSemanticBuffer } from '../utils/heap';
5
+ import { TextDocumentController } from './text-documents';
6
+
7
+ const SEMANTIC_ANNOUNCEMENT_DELAY_MS = 50;
8
+
9
+ export class SemanticController {
10
+ private readonly projector: HiddenDomProjector;
11
+ private semanticTree: SemanticNode[] = [];
12
+ private semanticTextLayoutsByHandle: Record<string, {
13
+ readonly bounds: SemanticNode['bounds'];
14
+ } | undefined> = {};
15
+ private semanticAnnouncementTimer: number | null = null;
16
+ private scheduledSemanticAnnouncementHandle: string | null = null;
17
+
18
+ public constructor(
19
+ canvas: HTMLCanvasElement,
20
+ private readonly ui: UiModule,
21
+ private readonly interactionState: BridgeInteractionState,
22
+ private readonly textDocuments: TextDocumentController,
23
+ ) {
24
+ this.projector = new HiddenDomProjector(canvas);
25
+ }
26
+
27
+ public syncSize(logicalWidth: number, logicalHeight: number): void {
28
+ this.projector.syncSize(logicalWidth, logicalHeight);
29
+ }
30
+
31
+ public syncSemanticState(): void {
32
+ this.semanticTree = parseSemanticBuffer(extractSemanticBuffer(this.ui));
33
+ for (const node of this.semanticTree) {
34
+ if (node.roleName !== 'textbox') {
35
+ continue;
36
+ }
37
+ if (this.interactionState.textByHandle[node.handle] !== undefined) {
38
+ continue;
39
+ }
40
+ if (node.label.length === 0) {
41
+ continue;
42
+ }
43
+ this.interactionState.textByHandle[node.handle] = node.label;
44
+ }
45
+ this.semanticTextLayoutsByHandle = this.buildSemanticTextLayouts();
46
+ this.projector.update(
47
+ this.semanticTree,
48
+ this.interactionState.textByHandle,
49
+ this.semanticTextLayoutsByHandle,
50
+ );
51
+ const focusedHandle = this.interactionState.getFocusedHandle();
52
+ for (const handle of this.interactionState.consumePendingSemanticAnnouncements()) {
53
+ if (focusedHandle !== null && focusedHandle === handle) {
54
+ this.scheduleSemanticAnnouncement(handle);
55
+ }
56
+ }
57
+ window.__bridgeSemanticTree = cloneSemanticTree(this.semanticTree);
58
+ }
59
+
60
+ public getSemanticTree(): readonly SemanticNode[] {
61
+ return this.semanticTree;
62
+ }
63
+
64
+ public getBoundingBox(handle: string): SemanticNode['bounds'] | null {
65
+ const node = this.semanticTree.find((entry) => entry.handle === handle);
66
+ return node === undefined ? null : { ...node.bounds };
67
+ }
68
+
69
+ public destroy(): void {
70
+ this.cancelPendingSemanticAnnouncement();
71
+ this.semanticTree = [];
72
+ this.semanticTextLayoutsByHandle = {};
73
+ window.__bridgeSemanticTree = [];
74
+ this.projector.destroy();
75
+ }
76
+
77
+ private cancelPendingSemanticAnnouncement(): void {
78
+ if (this.semanticAnnouncementTimer !== null) {
79
+ window.clearTimeout(this.semanticAnnouncementTimer);
80
+ this.semanticAnnouncementTimer = null;
81
+ }
82
+ this.scheduledSemanticAnnouncementHandle = null;
83
+ }
84
+
85
+ private scheduleSemanticAnnouncement(handle: string): void {
86
+ this.scheduledSemanticAnnouncementHandle = handle;
87
+ if (this.semanticAnnouncementTimer !== null) {
88
+ window.clearTimeout(this.semanticAnnouncementTimer);
89
+ }
90
+ this.semanticAnnouncementTimer = window.setTimeout(() => {
91
+ this.semanticAnnouncementTimer = null;
92
+ const targetHandle = this.scheduledSemanticAnnouncementHandle;
93
+ this.scheduledSemanticAnnouncementHandle = null;
94
+ if (targetHandle === null || this.interactionState.getFocusedHandle() !== targetHandle) {
95
+ return;
96
+ }
97
+ this.projector.announceNode(targetHandle, this.semanticTree, this.interactionState.textByHandle);
98
+ }, SEMANTIC_ANNOUNCEMENT_DELAY_MS);
99
+ }
100
+
101
+ private buildSemanticTextLayouts(): Record<string, {
102
+ readonly bounds: SemanticNode['bounds'];
103
+ } | undefined> {
104
+ const layouts = Object.create(null) as Record<string, {
105
+ readonly bounds: SemanticNode['bounds'];
106
+ } | undefined>;
107
+ for (const node of this.semanticTree) {
108
+ if (node.label.length === 0) {
109
+ continue;
110
+ }
111
+ const visibleTextBounds = this.textDocuments.readVisibleTextBounds(node.handle);
112
+ if (visibleTextBounds === null) {
113
+ continue;
114
+ }
115
+ const left = Math.max(node.bounds.x, visibleTextBounds.x);
116
+ const top = Math.max(node.bounds.y, visibleTextBounds.y);
117
+ const right = Math.min(node.bounds.x + node.bounds.width, visibleTextBounds.x + visibleTextBounds.width);
118
+ const bottom = Math.min(node.bounds.y + node.bounds.height, visibleTextBounds.y + visibleTextBounds.height);
119
+ if (right <= left || bottom <= top) {
120
+ continue;
121
+ }
122
+ layouts[node.handle] = {
123
+ bounds: {
124
+ x: left,
125
+ y: top,
126
+ width: right - left,
127
+ height: bottom - top,
128
+ },
129
+ };
130
+ }
131
+ return layouts;
132
+ }
133
+ }
@@ -0,0 +1,234 @@
1
+ import type {
2
+ OpenCanvasTextDocument,
3
+ SemanticNode,
4
+ UiModule,
5
+ } from '../../core-types';
6
+ import type { FindOnPageDocument } from '../../find-on-page';
7
+ import { handleToBigInt, toHeapPointer } from '../utils/encoding';
8
+
9
+ const INVALID_TEXT_DOCUMENT_LENGTH = 0xFFFFFFFF;
10
+ const openCanvasTextDecoder = new TextDecoder();
11
+
12
+ export interface TextDocumentMeta {
13
+ readonly handleArg: number | bigint;
14
+ readonly byteLength: number;
15
+ }
16
+
17
+ export interface ResolvedTextRange extends TextDocumentMeta {
18
+ readonly start: number;
19
+ readonly end: number;
20
+ }
21
+
22
+ export class TextDocumentController {
23
+ public constructor(private readonly ui: UiModule) {}
24
+
25
+ public readTextDocumentMeta(handle: string): TextDocumentMeta | null {
26
+ const handleArg = this.toUiHandleArgument(handle);
27
+ if (handleArg === null) {
28
+ return null;
29
+ }
30
+ const byteLength = this.ui._ui_get_text_document_utf8_length(handleArg);
31
+ if (byteLength < 0 || (byteLength >>> 0) === INVALID_TEXT_DOCUMENT_LENGTH) {
32
+ return null;
33
+ }
34
+ return { handleArg, byteLength };
35
+ }
36
+
37
+ public resolveTextRange(handle: string, start: number, end: number): ResolvedTextRange | null {
38
+ const meta = this.readTextDocumentMeta(handle);
39
+ const range = this.normalizeByteRange(start, end);
40
+ if (meta === null || range === null || range.end > meta.byteLength) {
41
+ return null;
42
+ }
43
+ return {
44
+ handleArg: meta.handleArg,
45
+ byteLength: meta.byteLength,
46
+ start: range.start,
47
+ end: range.end,
48
+ };
49
+ }
50
+
51
+ public readTextDocumentSnapshot(
52
+ handle: string,
53
+ ): { readonly document: OpenCanvasTextDocument; readonly byteLength: number } | null {
54
+ const meta = this.readTextDocumentMeta(handle);
55
+ if (meta === null) {
56
+ return null;
57
+ }
58
+ if (meta.byteLength === 0) {
59
+ return {
60
+ document: { handle, text: '' },
61
+ byteLength: 0,
62
+ };
63
+ }
64
+
65
+ const textAllocation = toHeapPointer(this.ui, this.ui._malloc(meta.byteLength));
66
+ const textPtr = textAllocation.ptr;
67
+ const textOffset = textAllocation.offset;
68
+ if (textOffset === 0) {
69
+ throw new Error('ui text document malloc failed.');
70
+ }
71
+
72
+ try {
73
+ const copied = this.ui._ui_copy_text_document_utf8(meta.handleArg, textPtr, meta.byteLength);
74
+ this.ui.refreshHeapViews?.();
75
+ if (copied === 0) {
76
+ return null;
77
+ }
78
+ const text = openCanvasTextDecoder.decode(this.ui.HEAPU8.slice(textOffset, textOffset + meta.byteLength));
79
+ return {
80
+ document: { handle, text },
81
+ byteLength: meta.byteLength,
82
+ };
83
+ } finally {
84
+ this.ui._free(textPtr);
85
+ }
86
+ }
87
+
88
+ public readVisibleTextBounds(handle: string): SemanticNode['bounds'] | null {
89
+ const handleArg = this.toUiHandleArgument(handle);
90
+ if (handleArg === null) {
91
+ return null;
92
+ }
93
+ const allocation = toHeapPointer(this.ui, this.ui._malloc(16));
94
+ if (allocation.offset === 0) {
95
+ throw new Error('ui visible text bounds malloc failed.');
96
+ }
97
+ const xPtr = allocation.ptr;
98
+ const yPtr = this.addPointerOffset(allocation.ptr, 4);
99
+ const widthPtr = this.addPointerOffset(allocation.ptr, 8);
100
+ const heightPtr = this.addPointerOffset(allocation.ptr, 12);
101
+ try {
102
+ const copied = this.ui._ui_get_text_visible_bounds(handleArg, xPtr, yPtr, widthPtr, heightPtr);
103
+ this.ui.refreshHeapViews?.();
104
+ if (copied === 0) {
105
+ return null;
106
+ }
107
+ const base = allocation.offset >>> 2;
108
+ return {
109
+ x: this.ui.HEAPF32[base] ?? 0,
110
+ y: this.ui.HEAPF32[base + 1] ?? 0,
111
+ width: this.ui.HEAPF32[base + 2] ?? 0,
112
+ height: this.ui.HEAPF32[base + 3] ?? 0,
113
+ };
114
+ } finally {
115
+ this.ui._free(allocation.ptr);
116
+ }
117
+ }
118
+
119
+ public readRangeRects(handle: string, start: number, end: number): SemanticNode['bounds'][] {
120
+ const range = this.resolveTextRange(handle, start, end);
121
+ if (range === null) {
122
+ return [];
123
+ }
124
+
125
+ const rectCount = this.ui._ui_get_text_range_rect_count(range.handleArg, range.start, range.end);
126
+ if (rectCount === 0) {
127
+ return [];
128
+ }
129
+
130
+ const rectWordsAllocation = toHeapPointer(this.ui, this.ui._malloc(rectCount * 16));
131
+ const rectWordsPtr = rectWordsAllocation.ptr;
132
+ const rectWordsOffset = rectWordsAllocation.offset;
133
+ if (rectWordsOffset === 0) {
134
+ throw new Error('ui range rect malloc failed.');
135
+ }
136
+
137
+ try {
138
+ const copiedCount = this.ui._ui_copy_text_range_rects(
139
+ range.handleArg,
140
+ range.start,
141
+ range.end,
142
+ rectWordsPtr,
143
+ rectCount,
144
+ );
145
+ this.ui.refreshHeapViews?.();
146
+ if (copiedCount === 0) {
147
+ return [];
148
+ }
149
+ const words = this.ui.HEAPF32.slice(rectWordsOffset >>> 2, (rectWordsOffset >>> 2) + (copiedCount * 4));
150
+ const rects: SemanticNode['bounds'][] = [];
151
+ for (let index = 0; index < copiedCount; index += 1) {
152
+ const base = index * 4;
153
+ rects.push({
154
+ x: words[base] ?? 0,
155
+ y: words[base + 1] ?? 0,
156
+ width: words[base + 2] ?? 0,
157
+ height: words[base + 3] ?? 0,
158
+ });
159
+ }
160
+ return rects;
161
+ } finally {
162
+ this.ui._free(rectWordsPtr);
163
+ }
164
+ }
165
+
166
+ public readFindDocuments(): FindOnPageDocument[] {
167
+ const documents: FindOnPageDocument[] = [];
168
+ for (const handle of this.readTextSnapshotHandles()) {
169
+ const snapshot = this.readTextDocumentSnapshot(handle);
170
+ if (snapshot === null) {
171
+ continue;
172
+ }
173
+ documents.push(snapshot.document);
174
+ }
175
+ return documents;
176
+ }
177
+
178
+ private readTextSnapshotHandles(): string[] {
179
+ const handleCount = this.ui._ui_get_text_snapshot_handle_count();
180
+ if (handleCount === 0) {
181
+ return [];
182
+ }
183
+
184
+ const handleWordsAllocation = toHeapPointer(this.ui, this.ui._malloc(handleCount * 8));
185
+ const handleWordsPtr = handleWordsAllocation.ptr;
186
+ const handleWordsOffset = handleWordsAllocation.offset;
187
+ if (handleWordsOffset === 0) {
188
+ throw new Error('ui text snapshot handle malloc failed.');
189
+ }
190
+
191
+ try {
192
+ const copiedCount = this.ui._ui_copy_text_snapshot_handles(handleWordsPtr, handleCount);
193
+ this.ui.refreshHeapViews?.();
194
+ if (copiedCount === 0) {
195
+ return [];
196
+ }
197
+ const words = this.ui.HEAPU32.slice(handleWordsOffset >>> 2, (handleWordsOffset >>> 2) + (copiedCount * 2));
198
+ const handles: string[] = [];
199
+ for (let index = 0; index < copiedCount; index += 1) {
200
+ const low = words[index * 2] ?? 0;
201
+ const high = words[(index * 2) + 1] ?? 0;
202
+ handles.push(((BigInt(high) << 32n) | BigInt(low)).toString());
203
+ }
204
+ return handles;
205
+ } finally {
206
+ this.ui._free(handleWordsPtr);
207
+ }
208
+ }
209
+
210
+ private toUiHandleArgument(handle: string): bigint | null {
211
+ try {
212
+ return handleToBigInt(handle);
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+
218
+ private normalizeByteRange(
219
+ start: number,
220
+ end: number,
221
+ ): { readonly start: number; readonly end: number } | null {
222
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < 0) {
223
+ return null;
224
+ }
225
+ return {
226
+ start: Math.min(start, end),
227
+ end: Math.max(start, end),
228
+ };
229
+ }
230
+
231
+ private addPointerOffset(pointer: number | bigint, offset: number): number | bigint {
232
+ return typeof pointer === 'bigint' ? pointer + BigInt(offset) : pointer + offset;
233
+ }
234
+ }