@effindomv2/fui-as 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 (137) hide show
  1. package/LICENSE.md +7 -0
  2. package/browser/src/common-harness/host-imports.ts +430 -0
  3. package/browser/src/common-harness/interop.ts +39 -0
  4. package/browser/src/common-harness/managed-harness-bitmap-host.ts +92 -0
  5. package/browser/src/common-harness/managed-harness-fetch-host.ts +201 -0
  6. package/browser/src/common-harness/managed-harness-file-host.ts +1101 -0
  7. package/browser/src/common-harness/managed-harness-file-payloads.ts +143 -0
  8. package/browser/src/common-harness/managed-harness-file-types.ts +106 -0
  9. package/browser/src/common-harness/managed-harness-session.ts +15 -0
  10. package/browser/src/common-harness/managed-harness.ts +1323 -0
  11. package/browser/src/common-harness/managed-history.ts +168 -0
  12. package/browser/src/common-harness/persisted-restore-policy.ts +50 -0
  13. package/browser/src/common-harness/persisted-ui-state-controller.ts +309 -0
  14. package/browser/src/common-harness/text-session-bridge.ts +452 -0
  15. package/browser/src/common-harness/types.ts +205 -0
  16. package/browser/src/common-harness/ui-chrome.ts +191 -0
  17. package/browser/src/common-harness/ui-imports.ts +529 -0
  18. package/browser/src/common-harness/wasm-module-cache.ts +47 -0
  19. package/browser/src/common-harness.ts +27 -0
  20. package/browser/src/file-processing-worker.ts +89 -0
  21. package/browser/src/host-events.ts +97 -0
  22. package/browser/src/host-services.ts +203 -0
  23. package/browser/src/index.ts +62 -0
  24. package/browser/src/persisted-ui-state.ts +206 -0
  25. package/browser/src/routed-harness.ts +198 -0
  26. package/browser/src/worker-bootstrap.ts +483 -0
  27. package/browser/src/worker-manager.ts +230 -0
  28. package/browser/src/worker-types.ts +50 -0
  29. package/package.json +89 -0
  30. package/scripts/build-demo-as.sh +91 -0
  31. package/scripts/build.sh +325 -0
  32. package/scripts/generate-host-events.ts +175 -0
  33. package/scripts/generate-host-services.ts +157 -0
  34. package/src/Fui.ts +205 -0
  35. package/src/FuiExports.ts +55 -0
  36. package/src/FuiPrimitives.ts +15 -0
  37. package/src/FuiWorker.ts +3 -0
  38. package/src/FuiWorkerExports.ts +6 -0
  39. package/src/bindings/ui.ts +531 -0
  40. package/src/color.ts +86 -0
  41. package/src/controls/AntiSelectionArea.ts +23 -0
  42. package/src/controls/Button.ts +750 -0
  43. package/src/controls/Checkbox.ts +181 -0
  44. package/src/controls/ContextMenu.ts +885 -0
  45. package/src/controls/ControlTemplateSet.ts +37 -0
  46. package/src/controls/Dialog.ts +355 -0
  47. package/src/controls/Dropdown.ts +856 -0
  48. package/src/controls/Form.ts +110 -0
  49. package/src/controls/NavLink.ts +211 -0
  50. package/src/controls/Popup.ts +129 -0
  51. package/src/controls/ProgressBar.ts +180 -0
  52. package/src/controls/RadioButton.ts +135 -0
  53. package/src/controls/RadioGroup.ts +244 -0
  54. package/src/controls/SelectionArea.ts +75 -0
  55. package/src/controls/Slider.ts +471 -0
  56. package/src/controls/Switch.ts +132 -0
  57. package/src/controls/TextArea.ts +20 -0
  58. package/src/controls/TextInput.ts +7 -0
  59. package/src/controls/index.ts +18 -0
  60. package/src/controls/internal/ButtonPresenter.ts +95 -0
  61. package/src/controls/internal/CheckboxIndicatorPresenter.ts +93 -0
  62. package/src/controls/internal/DropdownChevronPresenter.ts +67 -0
  63. package/src/controls/internal/DropdownFieldPresenter.ts +110 -0
  64. package/src/controls/internal/DropdownOptionRowPresenter.ts +82 -0
  65. package/src/controls/internal/PopupPresenter.ts +198 -0
  66. package/src/controls/internal/PressableIndicatorPresenter.ts +32 -0
  67. package/src/controls/internal/PressableLabeledControl.ts +221 -0
  68. package/src/controls/internal/RadioIndicatorPresenter.ts +73 -0
  69. package/src/controls/internal/SliderPresenter.ts +157 -0
  70. package/src/controls/internal/SwitchIndicatorPresenter.ts +72 -0
  71. package/src/controls/internal/TextInputCore.ts +695 -0
  72. package/src/controls/internal/TextInputPresenter.ts +72 -0
  73. package/src/controls/templating.ts +54 -0
  74. package/src/core/Action.ts +94 -0
  75. package/src/core/Actions.ts +37 -0
  76. package/src/core/Animation.ts +412 -0
  77. package/src/core/Application.ts +328 -0
  78. package/src/core/Assets.ts +264 -0
  79. package/src/core/AttachedProperties.ts +32 -0
  80. package/src/core/Bitmap.ts +70 -0
  81. package/src/core/BoundCallback.ts +104 -0
  82. package/src/core/Callbacks.ts +17 -0
  83. package/src/core/ContextMenuManager.ts +466 -0
  84. package/src/core/DebugApi.ts +30 -0
  85. package/src/core/Disposable.ts +10 -0
  86. package/src/core/DragDropManager.ts +179 -0
  87. package/src/core/DragGesture.ts +184 -0
  88. package/src/core/DynamicAssetIds.ts +24 -0
  89. package/src/core/Errors.ts +48 -0
  90. package/src/core/EventRouter.ts +408 -0
  91. package/src/core/ExternalDropManager.ts +122 -0
  92. package/src/core/Fetch.ts +264 -0
  93. package/src/core/FetchFfi.ts +15 -0
  94. package/src/core/File.ts +1002 -0
  95. package/src/core/FocusAdornerManager.ts +263 -0
  96. package/src/core/FocusVisibility.ts +36 -0
  97. package/src/core/FrameScheduler.ts +28 -0
  98. package/src/core/KeyboardScroll.ts +161 -0
  99. package/src/core/KeyboardScrollTracker.ts +386 -0
  100. package/src/core/Logger.ts +80 -0
  101. package/src/core/Navigation.ts +13 -0
  102. package/src/core/Node.ts +1708 -0
  103. package/src/core/PersistedState.ts +102 -0
  104. package/src/core/PersistedUiState.ts +142 -0
  105. package/src/core/Platform.ts +219 -0
  106. package/src/core/Signal.ts +89 -0
  107. package/src/core/Theme.ts +365 -0
  108. package/src/core/Timers.ts +129 -0
  109. package/src/core/ToolTip.ts +122 -0
  110. package/src/core/ToolTipManager.ts +459 -0
  111. package/src/core/Transitions.ts +34 -0
  112. package/src/core/Typography.ts +204 -0
  113. package/src/core/Worker.ts +196 -0
  114. package/src/core/bind.ts +37 -0
  115. package/src/core/event_exports.ts +596 -0
  116. package/src/core/ffi.ts +728 -0
  117. package/src/host-services/runtime.ts +25 -0
  118. package/src/nodes/FlexBox.ts +789 -0
  119. package/src/nodes/GradientStop.ts +9 -0
  120. package/src/nodes/Grid.ts +183 -0
  121. package/src/nodes/Image.ts +189 -0
  122. package/src/nodes/Portal.ts +14 -0
  123. package/src/nodes/RichText.ts +312 -0
  124. package/src/nodes/ScrollBar.ts +570 -0
  125. package/src/nodes/ScrollBox.ts +415 -0
  126. package/src/nodes/ScrollState.ts +10 -0
  127. package/src/nodes/ScrollView.ts +511 -0
  128. package/src/nodes/Svg.ts +142 -0
  129. package/src/nodes/Text.ts +145 -0
  130. package/src/nodes/TextCore.ts +558 -0
  131. package/src/nodes/VirtualList.ts +431 -0
  132. package/src/nodes/helpers.ts +25 -0
  133. package/src/nodes/index.ts +14 -0
  134. package/src/tsconfig.json +7 -0
  135. package/src/worker/Worker.ts +169 -0
  136. package/src/worker/WorkerJob.ts +65 -0
  137. package/src/worker/ffi.ts +23 -0
@@ -0,0 +1,529 @@
1
+ import type { BridgeRuntime, WasmHandleLike } from '@effindomv2/runtime';
2
+
3
+ import { addUiPointer, toBigIntHandle, type AppHandleLike } from './interop';
4
+
5
+ export interface PendingGradient {
6
+ startX: number;
7
+ startY: number;
8
+ endX: number;
9
+ endY: number;
10
+ stopCount: number;
11
+ offsets: number[];
12
+ colors: number[];
13
+ }
14
+
15
+ export interface UiImportDeps {
16
+ getRuntime(): BridgeRuntime;
17
+ readAppUtf8(ptr: number, len: number): string;
18
+ readAppFloats(ptr: number, count: number): Float32Array;
19
+ readAppBytes(ptr: number, len: number): Uint8Array;
20
+ withUiUtf8(text: string, callback: (ptr: WasmHandleLike | number, len: number) => void): void;
21
+ withUiGridData(
22
+ values: Float32Array,
23
+ types: Uint8Array,
24
+ callback: (valuesPtr: WasmHandleLike | number, typesPtr: WasmHandleLike | number) => void,
25
+ ): void;
26
+ withUiGradientData(
27
+ offsets: Float32Array,
28
+ colors: Uint32Array,
29
+ callback: (offsetsPtr: WasmHandleLike | number, colorsPtr: WasmHandleLike | number) => void,
30
+ ): void;
31
+ zeroPointer(): WasmHandleLike | number;
32
+ normalizePointer(ptr: WasmHandleLike | number): WasmHandleLike | number;
33
+ getCurrentMemory(): WebAssembly.Memory;
34
+ setLatestRootHandle(rootHandle: string | null): void;
35
+ updateState(): void;
36
+ queueHarnessFrame(): void;
37
+ syncUiHostCapabilities(): void;
38
+ resetUiState(): void;
39
+ pendingGradients: Map<string, PendingGradient>;
40
+ }
41
+
42
+ export function createUiImportModule(deps: UiImportDeps) {
43
+ return {
44
+ ui_reset(): void {
45
+ const runtime = deps.getRuntime();
46
+ runtime.ui._ui_reset();
47
+ deps.syncUiHostCapabilities();
48
+ deps.resetUiState();
49
+ },
50
+ ui_create_node(type: number): bigint {
51
+ return toBigIntHandle(deps.getRuntime().ui._ui_create_node(type));
52
+ },
53
+ ui_set_node_id(handle: AppHandleLike, ptr: number, len: number): void {
54
+ const runtime = deps.getRuntime();
55
+ const text = deps.readAppUtf8(ptr, len);
56
+ deps.withUiUtf8(text, (uiPtr, uiLen) => {
57
+ runtime.ui._ui_set_node_id(toBigIntHandle(handle), uiPtr, uiLen);
58
+ });
59
+ },
60
+ ui_delete_node(handle: AppHandleLike): void {
61
+ deps.getRuntime().ui._ui_delete_node(toBigIntHandle(handle));
62
+ },
63
+ ui_set_semantic_role(handle: AppHandleLike, role: number): void {
64
+ deps.getRuntime().ui._ui_set_semantic_role(toBigIntHandle(handle), role);
65
+ },
66
+ ui_set_semantic_label(handle: AppHandleLike, ptr: number, len: number): void {
67
+ const runtime = deps.getRuntime();
68
+ const text = deps.readAppUtf8(ptr, len);
69
+ deps.withUiUtf8(text, (uiPtr, uiLen) => {
70
+ runtime.ui._ui_set_semantic_label(toBigIntHandle(handle), uiPtr, uiLen);
71
+ });
72
+ },
73
+ ui_set_semantic_checked(handle: AppHandleLike, checkedState: number): void {
74
+ deps.getRuntime().ui._ui_set_semantic_checked(toBigIntHandle(handle), checkedState);
75
+ },
76
+ ui_set_semantic_selected(handle: AppHandleLike, hasSelected: number, selected: number): void {
77
+ deps.getRuntime().ui._ui_set_semantic_selected(toBigIntHandle(handle), hasSelected, selected);
78
+ },
79
+ ui_set_semantic_expanded(handle: AppHandleLike, hasExpanded: number, expanded: number): void {
80
+ deps.getRuntime().ui._ui_set_semantic_expanded(toBigIntHandle(handle), hasExpanded, expanded);
81
+ },
82
+ ui_set_semantic_disabled(handle: AppHandleLike, hasDisabled: number, disabled: number): void {
83
+ deps.getRuntime().ui._ui_set_semantic_disabled(toBigIntHandle(handle), hasDisabled, disabled);
84
+ },
85
+ ui_set_semantic_value_range(
86
+ handle: AppHandleLike,
87
+ hasValueRange: number,
88
+ valueNow: number,
89
+ valueMin: number,
90
+ valueMax: number,
91
+ ): void {
92
+ deps.getRuntime().ui._ui_set_semantic_value_range(toBigIntHandle(handle), hasValueRange, valueNow, valueMin, valueMax);
93
+ },
94
+ ui_set_semantic_orientation(handle: AppHandleLike, orientation: number): void {
95
+ deps.getRuntime().ui._ui_set_semantic_orientation(toBigIntHandle(handle), orientation);
96
+ },
97
+ ui_request_semantic_announcement(handle: AppHandleLike): void {
98
+ deps.getRuntime().ui._ui_request_semantic_announcement(toBigIntHandle(handle));
99
+ },
100
+ ui_push_semantic_scope(handle: AppHandleLike): number {
101
+ return deps.getRuntime().ui._ui_push_semantic_scope(toBigIntHandle(handle));
102
+ },
103
+ ui_remove_semantic_scope(token: number): void {
104
+ deps.getRuntime().ui._ui_remove_semantic_scope(token);
105
+ },
106
+ ui_node_add_child(parent: AppHandleLike, child: AppHandleLike): void {
107
+ deps.getRuntime().ui._ui_node_add_child(toBigIntHandle(parent), toBigIntHandle(child));
108
+ },
109
+ ui_node_remove_child(parent: AppHandleLike, child: AppHandleLike): void {
110
+ deps.getRuntime().ui._ui_node_remove_child(toBigIntHandle(parent), toBigIntHandle(child));
111
+ },
112
+ ui_set_is_portal(handle: AppHandleLike, flag: number): void {
113
+ deps.getRuntime().ui._ui_set_is_portal(toBigIntHandle(handle), flag);
114
+ },
115
+ ui_set_is_shared_size_scope(handle: AppHandleLike, flag: number): void {
116
+ deps.getRuntime().ui._ui_set_is_shared_size_scope(toBigIntHandle(handle), flag);
117
+ },
118
+ ui_set_root(handle: AppHandleLike): void {
119
+ const runtime = deps.getRuntime();
120
+ const rootHandle = toBigIntHandle(handle);
121
+ deps.setLatestRootHandle(rootHandle.toString());
122
+ runtime.ui._ui_set_root(rootHandle);
123
+ deps.updateState();
124
+ },
125
+ ui_set_width(handle: AppHandleLike, value: number, unit: number): void {
126
+ deps.getRuntime().ui._ui_set_width(toBigIntHandle(handle), value, unit);
127
+ },
128
+ ui_set_height(handle: AppHandleLike, value: number, unit: number): void {
129
+ deps.getRuntime().ui._ui_set_height(toBigIntHandle(handle), value, unit);
130
+ },
131
+ ui_set_flex_direction(handle: AppHandleLike, direction: number): void {
132
+ deps.getRuntime().ui._ui_set_flex_direction(toBigIntHandle(handle), direction);
133
+ },
134
+ ui_set_flex_grow(handle: AppHandleLike, grow: number): void {
135
+ deps.getRuntime().ui._ui_set_flex_grow(toBigIntHandle(handle), grow);
136
+ },
137
+ ui_set_flex_basis(handle: AppHandleLike, basis: number): void {
138
+ deps.getRuntime().ui._ui_set_flex_basis(toBigIntHandle(handle), basis);
139
+ },
140
+ ui_set_justify_content(handle: AppHandleLike, justify: number): void {
141
+ deps.getRuntime().ui._ui_set_justify_content(toBigIntHandle(handle), justify);
142
+ },
143
+ ui_set_align_items(handle: AppHandleLike, align: number): void {
144
+ deps.getRuntime().ui._ui_set_align_items(toBigIntHandle(handle), align);
145
+ },
146
+ ui_set_padding(handle: AppHandleLike, left: number, top: number, right: number, bottom: number): void {
147
+ deps.getRuntime().ui._ui_set_padding(toBigIntHandle(handle), left, top, right, bottom);
148
+ },
149
+ ui_set_margin(handle: AppHandleLike, left: number, top: number, right: number, bottom: number): void {
150
+ deps.getRuntime().ui._ui_set_margin(toBigIntHandle(handle), left, top, right, bottom);
151
+ },
152
+ ui_set_position_type(handle: AppHandleLike, positionType: number): void {
153
+ deps.getRuntime().ui._ui_set_position_type(toBigIntHandle(handle), positionType);
154
+ },
155
+ ui_set_position(handle: AppHandleLike, left: number, top: number, right: number, bottom: number): void {
156
+ deps.getRuntime().ui._ui_set_position(toBigIntHandle(handle), left, top, right, bottom);
157
+ },
158
+ ui_grid_set_columns(handle: AppHandleLike, count: number, valuesPtr: number, typesPtr: number): void {
159
+ const runtime = deps.getRuntime();
160
+ deps.withUiGridData(deps.readAppFloats(valuesPtr, count), deps.readAppBytes(typesPtr, count), (uiValuesPtr, uiTypesPtr) => {
161
+ runtime.ui._ui_grid_set_columns(toBigIntHandle(handle), count, uiValuesPtr, uiTypesPtr);
162
+ });
163
+ },
164
+ ui_grid_set_rows(handle: AppHandleLike, count: number, valuesPtr: number, typesPtr: number): void {
165
+ const runtime = deps.getRuntime();
166
+ deps.withUiGridData(deps.readAppFloats(valuesPtr, count), deps.readAppBytes(typesPtr, count), (uiValuesPtr, uiTypesPtr) => {
167
+ runtime.ui._ui_grid_set_rows(toBigIntHandle(handle), count, uiValuesPtr, uiTypesPtr);
168
+ });
169
+ },
170
+ ui_grid_set_column_shared_size_group(handle: AppHandleLike, index: number, ptr: number, len: number): void {
171
+ const runtime = deps.getRuntime();
172
+ const text = deps.readAppUtf8(ptr, len);
173
+ deps.withUiUtf8(text, (uiPtr, uiLen) => {
174
+ runtime.ui._ui_grid_set_column_shared_size_group(toBigIntHandle(handle), index, uiPtr, uiLen);
175
+ });
176
+ },
177
+ ui_grid_set_row_shared_size_group(handle: AppHandleLike, index: number, ptr: number, len: number): void {
178
+ const runtime = deps.getRuntime();
179
+ const text = deps.readAppUtf8(ptr, len);
180
+ deps.withUiUtf8(text, (uiPtr, uiLen) => {
181
+ runtime.ui._ui_grid_set_row_shared_size_group(toBigIntHandle(handle), index, uiPtr, uiLen);
182
+ });
183
+ },
184
+ ui_node_set_grid_placement(handle: AppHandleLike, row: number, col: number, rowSpan: number, colSpan: number): void {
185
+ deps.getRuntime().ui._ui_node_set_grid_placement(toBigIntHandle(handle), row, col, rowSpan, colSpan);
186
+ },
187
+ ui_set_bg_color(handle: AppHandleLike, color: number): void {
188
+ deps.getRuntime().ui._ui_set_bg_color(toBigIntHandle(handle), color);
189
+ },
190
+ ui_set_box_style(
191
+ handle: AppHandleLike,
192
+ bgColor: number,
193
+ topLeftRadius: number,
194
+ topRightRadius: number,
195
+ bottomRightRadius: number,
196
+ bottomLeftRadius: number,
197
+ borderWidth: number,
198
+ borderColor: number,
199
+ borderStyle: number,
200
+ borderDashOn: number,
201
+ borderDashOff: number,
202
+ ): void {
203
+ deps.getRuntime().ui._ui_set_box_style(
204
+ toBigIntHandle(handle),
205
+ bgColor,
206
+ topLeftRadius,
207
+ topRightRadius,
208
+ bottomRightRadius,
209
+ bottomLeftRadius,
210
+ borderWidth,
211
+ borderColor,
212
+ borderStyle,
213
+ borderDashOn,
214
+ borderDashOff,
215
+ );
216
+ },
217
+ ui_set_layer_effect(handle: AppHandleLike, opacity: number, blurSigma: number, blendMode: number): void {
218
+ deps.getRuntime().ui._ui_set_layer_effect(toBigIntHandle(handle), opacity, blurSigma, blendMode);
219
+ },
220
+ ui_set_drop_shadow(
221
+ handle: AppHandleLike,
222
+ color: number,
223
+ offsetX: number,
224
+ offsetY: number,
225
+ blurSigma: number,
226
+ spread: number,
227
+ ): void {
228
+ deps.getRuntime().ui._ui_set_drop_shadow(toBigIntHandle(handle), color, offsetX, offsetY, blurSigma, spread);
229
+ },
230
+ ui_set_background_blur(handle: AppHandleLike, blurSigma: number): void {
231
+ deps.getRuntime().ui._ui_set_background_blur(toBigIntHandle(handle), blurSigma);
232
+ },
233
+ ui_set_image(handle: AppHandleLike, textureId: number, objectFit: number): void {
234
+ deps.getRuntime().ui._ui_set_image(toBigIntHandle(handle), textureId, objectFit);
235
+ },
236
+ ui_set_image_nine(
237
+ handle: AppHandleLike,
238
+ textureId: number,
239
+ insetLeft: number,
240
+ insetTop: number,
241
+ insetRight: number,
242
+ insetBottom: number,
243
+ ): void {
244
+ deps.getRuntime().ui._ui_set_image_nine(
245
+ toBigIntHandle(handle),
246
+ textureId,
247
+ insetLeft,
248
+ insetTop,
249
+ insetRight,
250
+ insetBottom,
251
+ );
252
+ },
253
+ ui_set_svg(handle: AppHandleLike, svgId: number, tintColor: number): void {
254
+ deps.getRuntime().ui._ui_set_svg(toBigIntHandle(handle), svgId, tintColor);
255
+ },
256
+ ui_set_linear_gradient(
257
+ handle: AppHandleLike,
258
+ startX: number,
259
+ startY: number,
260
+ endX: number,
261
+ endY: number,
262
+ stopCount: number,
263
+ ): void {
264
+ deps.pendingGradients.set(toBigIntHandle(handle).toString(), {
265
+ startX,
266
+ startY,
267
+ endX,
268
+ endY,
269
+ stopCount,
270
+ offsets: [],
271
+ colors: [],
272
+ });
273
+ },
274
+ ui_push_linear_gradient_stop(handle: AppHandleLike, offset: number, color: number): void {
275
+ const runtime = deps.getRuntime();
276
+ const key = toBigIntHandle(handle).toString();
277
+ const pending = deps.pendingGradients.get(key);
278
+ if (pending === undefined) {
279
+ throw new Error('Gradient stop received before gradient header.');
280
+ }
281
+ pending.offsets.push(offset);
282
+ pending.colors.push(color >>> 0);
283
+ if (pending.offsets.length !== pending.stopCount) {
284
+ return;
285
+ }
286
+ deps.pendingGradients.delete(key);
287
+ deps.withUiGradientData(
288
+ Float32Array.from(pending.offsets),
289
+ Uint32Array.from(pending.colors),
290
+ (uiOffsetsPtr, uiColorsPtr) => {
291
+ runtime.ui._ui_set_linear_gradient(
292
+ toBigIntHandle(handle),
293
+ pending.startX,
294
+ pending.startY,
295
+ pending.endX,
296
+ pending.endY,
297
+ pending.stopCount,
298
+ uiOffsetsPtr,
299
+ uiColorsPtr,
300
+ );
301
+ },
302
+ );
303
+ },
304
+ ui_set_clip_to_bounds(handle: AppHandleLike, clip: number): void {
305
+ deps.getRuntime().ui._ui_set_clip_to_bounds(toBigIntHandle(handle), clip);
306
+ },
307
+ ui_set_visibility(handle: AppHandleLike, visibility: number): void {
308
+ deps.getRuntime().ui._ui_set_visibility(toBigIntHandle(handle), visibility);
309
+ },
310
+ ui_set_interactive(handle: AppHandleLike, flag: number): void {
311
+ deps.getRuntime().ui._ui_set_interactive(toBigIntHandle(handle), flag);
312
+ },
313
+ ui_set_scroll_proxy_target(handle: AppHandleLike, scrollHandle: AppHandleLike): void {
314
+ deps.getRuntime().ui._ui_set_scroll_proxy_target(toBigIntHandle(handle), toBigIntHandle(scrollHandle));
315
+ },
316
+ ui_set_scroll_enabled(handle: AppHandleLike, enabledX: number, enabledY: number): void {
317
+ deps.getRuntime().ui._ui_set_scroll_enabled(toBigIntHandle(handle), enabledX, enabledY);
318
+ },
319
+ ui_set_show_scrollbars(handle: AppHandleLike, showScrollbars: number): void {
320
+ deps.getRuntime().ui._ui_set_show_scrollbars(toBigIntHandle(handle), showScrollbars);
321
+ },
322
+ ui_set_scroll_friction(handle: AppHandleLike, friction: number): void {
323
+ deps.getRuntime().ui._ui_set_scroll_friction(toBigIntHandle(handle), friction);
324
+ },
325
+ ui_set_scroll_content_size(handle: AppHandleLike, contentWidth: number, contentHeight: number): void {
326
+ deps.getRuntime().ui._ui_set_scroll_content_size(toBigIntHandle(handle), contentWidth, contentHeight);
327
+ },
328
+ ui_set_focusable(handle: AppHandleLike, flag: number, tabIndex: number): void {
329
+ deps.getRuntime().ui._ui_set_focusable(toBigIntHandle(handle), flag, tabIndex);
330
+ },
331
+ ui_request_focus(handle: AppHandleLike): void {
332
+ deps.getRuntime().ui._ui_request_focus(toBigIntHandle(handle));
333
+ },
334
+ ui_set_font(handle: AppHandleLike, fontId: number, size: number): void {
335
+ const runtime = deps.getRuntime();
336
+ void runtime.ensureFont(fontId).catch((error: unknown) => {
337
+ const message = error instanceof Error ? error.message : String(error);
338
+ console.error(`[fui_host] font ${String(fontId)} failed to load on demand: ${message}`);
339
+ });
340
+ runtime.ui._ui_set_font(toBigIntHandle(handle), fontId, size);
341
+ },
342
+ ui_set_line_height(handle: AppHandleLike, lineHeight: number): void {
343
+ deps.getRuntime().ui._ui_set_line_height(toBigIntHandle(handle), lineHeight);
344
+ },
345
+ ui_register_font_fallback(fontId: number, fallbackFontId: number): void {
346
+ deps.getRuntime().registerFontFallback(fontId, fallbackFontId);
347
+ },
348
+ ui_set_text_color(handle: AppHandleLike, color: number): void {
349
+ deps.getRuntime().ui._ui_set_text_color(toBigIntHandle(handle), color);
350
+ },
351
+ ui_set_text_align(handle: AppHandleLike, align: number): void {
352
+ deps.getRuntime().ui._ui_set_text_align(toBigIntHandle(handle), align);
353
+ },
354
+ ui_set_text_vertical_align(handle: AppHandleLike, align: number): void {
355
+ deps.getRuntime().ui._ui_set_text_vertical_align(toBigIntHandle(handle), align);
356
+ },
357
+ ui_set_text_limits(handle: AppHandleLike, maxChars: number, maxLines: number): void {
358
+ deps.getRuntime().ui._ui_set_text_limits(toBigIntHandle(handle), maxChars, maxLines);
359
+ },
360
+ ui_set_text_wrapping(handle: AppHandleLike, wrap: number): void {
361
+ deps.getRuntime().ui._ui_set_text_wrapping(toBigIntHandle(handle), wrap);
362
+ },
363
+ ui_set_text_overflow(handle: AppHandleLike, overflow: number): void {
364
+ deps.getRuntime().ui._ui_set_text_overflow(toBigIntHandle(handle), overflow);
365
+ },
366
+ ui_set_text_overflow_fade(handle: AppHandleLike, horizontal: number, vertical: number): void {
367
+ deps.getRuntime().ui._ui_set_text_overflow_fade(toBigIntHandle(handle), horizontal, vertical);
368
+ },
369
+ ui_set_text_obscured(handle: AppHandleLike, obscured: number): void {
370
+ deps.getRuntime().ui._ui_set_text_obscured(toBigIntHandle(handle), obscured);
371
+ },
372
+ ui_set_editable(handle: AppHandleLike, editable: number): void {
373
+ deps.getRuntime().ui._ui_set_editable(toBigIntHandle(handle), editable);
374
+ },
375
+ ui_set_caret_color(handle: AppHandleLike, color: number): void {
376
+ deps.getRuntime().ui._ui_set_caret_color(toBigIntHandle(handle), color);
377
+ },
378
+ ui_set_selectable(handle: AppHandleLike, selectable: number, selectionColor: number): void {
379
+ deps.getRuntime().ui._ui_set_selectable(toBigIntHandle(handle), selectable, selectionColor);
380
+ },
381
+ ui_set_selection_area(handle: AppHandleLike, isArea: number): void {
382
+ deps.getRuntime().ui._ui_set_selection_area(toBigIntHandle(handle), isArea);
383
+ },
384
+ ui_set_selection_area_barrier(handle: AppHandleLike, isBarrier: number): void {
385
+ deps.getRuntime().ui._ui_set_selection_area_barrier(toBigIntHandle(handle), isBarrier);
386
+ },
387
+ ui_clear_selection(handle: AppHandleLike): void {
388
+ deps.getRuntime().ui._ui_clear_selection(toBigIntHandle(handle));
389
+ },
390
+ ui_retarget_selection(fromHandle: AppHandleLike, toHandle: AppHandleLike): void {
391
+ deps.getRuntime().ui._ui_retarget_selection(toBigIntHandle(fromHandle), toBigIntHandle(toHandle));
392
+ },
393
+ ui_is_point_in_selection(x: number, y: number): number {
394
+ return deps.getRuntime().ui._ui_is_point_in_selection(x, y);
395
+ },
396
+ ui_set_text_selection_range(handle: AppHandleLike, selectionStart: number, selectionEnd: number): void {
397
+ deps.getRuntime().ui._ui_set_text_selection_range(toBigIntHandle(handle), selectionStart, selectionEnd);
398
+ },
399
+ ui_clear_current_selection(): void {
400
+ deps.getRuntime().ui._ui_clear_current_selection();
401
+ },
402
+ ui_copy_current_selection(): void {
403
+ deps.getRuntime().ui._ui_copy_current_selection();
404
+ },
405
+ ui_can_undo_text_edit(handle: AppHandleLike): number {
406
+ return deps.getRuntime().ui._ui_can_undo_text_edit(toBigIntHandle(handle));
407
+ },
408
+ ui_can_redo_text_edit(handle: AppHandleLike): number {
409
+ return deps.getRuntime().ui._ui_can_redo_text_edit(toBigIntHandle(handle));
410
+ },
411
+ ui_has_text_selection(handle: AppHandleLike): number {
412
+ return deps.getRuntime().ui._ui_has_text_selection(toBigIntHandle(handle));
413
+ },
414
+ ui_undo_text_edit(handle: AppHandleLike): void {
415
+ deps.getRuntime().ui._ui_undo_text_edit(toBigIntHandle(handle));
416
+ },
417
+ ui_redo_text_edit(handle: AppHandleLike): void {
418
+ deps.getRuntime().ui._ui_redo_text_edit(toBigIntHandle(handle));
419
+ },
420
+ ui_copy_text_selection(handle: AppHandleLike): void {
421
+ deps.getRuntime().ui._ui_copy_text_selection(toBigIntHandle(handle));
422
+ },
423
+ ui_cut_text_selection(handle: AppHandleLike): void {
424
+ deps.getRuntime().ui._ui_cut_text_selection(toBigIntHandle(handle));
425
+ },
426
+ ui_replace_text_range(handle: AppHandleLike, start: number, end: number, ptr: number, len: number, caret: number): void {
427
+ deps.getRuntime().ui._ui_replace_text_range(toBigIntHandle(handle), start, end, ptr, len, caret);
428
+ },
429
+ ui_paste_text(handle: AppHandleLike): void {
430
+ deps.getRuntime().ui._ui_paste_text(toBigIntHandle(handle));
431
+ },
432
+ ui_select_all_text(handle: AppHandleLike): void {
433
+ deps.getRuntime().ui._ui_select_all_text(toBigIntHandle(handle));
434
+ },
435
+ ui_set_scroll_offset(handle: AppHandleLike, x: number, y: number): void {
436
+ deps.getRuntime().ui._ui_set_scroll_offset(toBigIntHandle(handle), x, y);
437
+ },
438
+ ui_clear_momentum_scroll(): void {
439
+ deps.getRuntime().ui._ui_clear_momentum_scroll();
440
+ },
441
+ ui_get_bounds(
442
+ handle: AppHandleLike,
443
+ outX: number,
444
+ outY: number,
445
+ outWidth: number,
446
+ outHeight: number,
447
+ ): number {
448
+ const runtime = deps.getRuntime();
449
+ const appMemory = deps.getCurrentMemory();
450
+ const boundsPtr = runtime.ui._malloc(16);
451
+ const boundsOffset = Number(boundsPtr);
452
+ runtime.ui.refreshHeapViews?.();
453
+ try {
454
+ const found = runtime.ui._ui_get_bounds(
455
+ toBigIntHandle(handle),
456
+ deps.normalizePointer(boundsPtr),
457
+ addUiPointer(runtime, boundsPtr, 4),
458
+ addUiPointer(runtime, boundsPtr, 8),
459
+ addUiPointer(runtime, boundsPtr, 12),
460
+ );
461
+ if (found === 0) {
462
+ return 0;
463
+ }
464
+
465
+ runtime.ui.refreshHeapViews?.();
466
+ const uiView = new DataView(runtime.ui.HEAPU8.buffer);
467
+ const appView = new DataView(appMemory.buffer);
468
+ appView.setFloat32(outX, uiView.getFloat32(boundsOffset, true), true);
469
+ appView.setFloat32(outY, uiView.getFloat32(boundsOffset + 4, true), true);
470
+ appView.setFloat32(outWidth, uiView.getFloat32(boundsOffset + 8, true), true);
471
+ appView.setFloat32(outHeight, uiView.getFloat32(boundsOffset + 12, true), true);
472
+ return 1;
473
+ } finally {
474
+ runtime.ui._free(boundsPtr);
475
+ }
476
+ },
477
+ ui_set_text(handle: AppHandleLike, ptr: number, len: number): void {
478
+ const runtime = deps.getRuntime();
479
+ const text = deps.readAppUtf8(ptr, len);
480
+ deps.withUiUtf8(text, (uiPtr, uiLen) => {
481
+ runtime.ui._ui_set_text(toBigIntHandle(handle), uiPtr, uiLen);
482
+ });
483
+ },
484
+ ui_set_text_style_runs(handle: AppHandleLike, runCount: number, runsWordsPtr: AppHandleLike): void {
485
+ const runtime = deps.getRuntime();
486
+ const clampedRunCount = Math.max(0, runCount | 0);
487
+ if (clampedRunCount === 0) {
488
+ runtime.ui._ui_set_text_style_runs(toBigIntHandle(handle), 0, deps.zeroPointer());
489
+ return;
490
+ }
491
+ const byteLength = clampedRunCount * 7 * 4;
492
+ const appPtr = Number(runsWordsPtr);
493
+ const appBytes = new Uint8Array(deps.getCurrentMemory().buffer, appPtr, byteLength);
494
+ const runs = new Uint32Array(appBytes.buffer, appBytes.byteOffset, clampedRunCount * 7);
495
+ const requestedFonts = new Set<number>();
496
+ for (let index = 0; index < clampedRunCount; index += 1) {
497
+ const fontId = runs[(index * 7) + 2] ?? 0;
498
+ if (fontId !== 0) {
499
+ requestedFonts.add(fontId);
500
+ }
501
+ }
502
+ for (const fontId of requestedFonts) {
503
+ void runtime.ensureFont(fontId).catch((error: unknown) => {
504
+ const message = error instanceof Error ? error.message : String(error);
505
+ console.error(`[fui_host] rich text font ${String(fontId)} failed to load on demand: ${message}`);
506
+ });
507
+ }
508
+ const uiPtr = runtime.ui._malloc(byteLength);
509
+ try {
510
+ runtime.ui.HEAPU8.set(appBytes, Number(uiPtr));
511
+ runtime.ui._ui_set_text_style_runs(
512
+ toBigIntHandle(handle),
513
+ clampedRunCount,
514
+ deps.normalizePointer(uiPtr),
515
+ );
516
+ } finally {
517
+ runtime.ui._free(uiPtr);
518
+ }
519
+ },
520
+ ui_commit_frame(): void {
521
+ const runtime = deps.getRuntime();
522
+ runtime.commitFrame();
523
+ deps.queueHarnessFrame();
524
+ },
525
+ ui_resize_window(width: number, height: number): void {
526
+ deps.getRuntime().ui._ui_resize_window(width, height);
527
+ },
528
+ };
529
+ }
@@ -0,0 +1,47 @@
1
+ import { getHostServiceImportNames, type HostServicesDefinition } from '../host-services';
2
+
3
+ export class WasmModuleCache {
4
+ private readonly wasmByteCache = new Map<string, Promise<ArrayBuffer>>();
5
+ private readonly wasmModuleCache = new Map<string, Promise<WebAssembly.Module>>();
6
+
7
+ async fetchWasmBytes(wasmPath: string): Promise<ArrayBuffer> {
8
+ const cached = this.wasmByteCache.get(wasmPath);
9
+ if (cached !== undefined) {
10
+ return cached;
11
+ }
12
+ const fetchPromise = fetch(wasmPath, { cache: 'no-store' }).then(async (response) => {
13
+ if (!response.ok) {
14
+ throw new Error(`Failed to load wasm app: ${wasmPath}`);
15
+ }
16
+ return response.arrayBuffer();
17
+ });
18
+ this.wasmByteCache.set(wasmPath, fetchPromise);
19
+ return fetchPromise;
20
+ }
21
+
22
+ async loadWasmModule(wasmPath: string): Promise<WebAssembly.Module> {
23
+ const cached = this.wasmModuleCache.get(wasmPath);
24
+ if (cached !== undefined) {
25
+ return cached;
26
+ }
27
+ const compilePromise = this.fetchWasmBytes(wasmPath).then((bytes) => WebAssembly.compile(bytes));
28
+ this.wasmModuleCache.set(wasmPath, compilePromise);
29
+ return compilePromise;
30
+ }
31
+ }
32
+
33
+ export function validateAppImports(wasmModule: WebAssembly.Module, hostServices: HostServicesDefinition | undefined): void {
34
+ const allowedHostServiceImports = getHostServiceImportNames(hostServices);
35
+ for (const imported of WebAssembly.Module.imports(wasmModule)) {
36
+ if (imported.kind !== 'function') {
37
+ throw new Error(`App import ${imported.module}.${imported.name} is not allowed.`);
38
+ }
39
+ if (imported.module === 'effindom_v2_ui' || imported.module === 'fui_host' || imported.module === 'env') {
40
+ continue;
41
+ }
42
+ if (imported.module === 'fui_host_service' && allowedHostServiceImports.has(imported.name)) {
43
+ continue;
44
+ }
45
+ throw new Error(`App import ${imported.module}.${imported.name} is not allowed.`);
46
+ }
47
+ }
@@ -0,0 +1,27 @@
1
+ export type {
2
+ HarnessAppOptions,
3
+ HarnessContext,
4
+ HarnessController,
5
+ HarnessDebugApi,
6
+ HarnessExports,
7
+ HarnessNavigationMode,
8
+ HarnessOptions,
9
+ HarnessState,
10
+ ManagedHarnessOptions,
11
+ ManagedHistoryState,
12
+ } from './common-harness/types';
13
+
14
+ export {
15
+ canManagedNavigateBack,
16
+ canManagedNavigateForward,
17
+ pushManagedHistoryEntry,
18
+ readManagedHistoryState,
19
+ replaceManagedHistoryEntry,
20
+ setCurrentManagedHistorySnapshotId,
21
+ syncManagedHistoryPop,
22
+ } from './common-harness/managed-history';
23
+
24
+ export {
25
+ startHarness,
26
+ startManagedHarness,
27
+ } from './common-harness/managed-harness';
@@ -0,0 +1,89 @@
1
+ /// <reference lib="webworker" />
2
+
3
+ interface FileProcessingWorkerStartMessage {
4
+ readonly type: "start";
5
+ readonly file: File;
6
+ readonly chunkSize: number;
7
+ }
8
+
9
+ interface FileProcessingWorkerNextMessage {
10
+ readonly type: "next";
11
+ }
12
+
13
+ interface FileProcessingWorkerCancelMessage {
14
+ readonly type: "cancel";
15
+ }
16
+
17
+ type FileProcessingWorkerInboundMessage =
18
+ | FileProcessingWorkerStartMessage
19
+ | FileProcessingWorkerNextMessage
20
+ | FileProcessingWorkerCancelMessage;
21
+
22
+ interface FileProcessingWorkerChunkMessage {
23
+ readonly type: "chunk";
24
+ readonly offsetBytes: number;
25
+ readonly bytes: ArrayBuffer;
26
+ readonly copiedBytes: number;
27
+ readonly totalBytes: number;
28
+ }
29
+
30
+ interface FileProcessingWorkerErrorMessage {
31
+ readonly type: "error";
32
+ readonly message: string;
33
+ }
34
+
35
+ let activeFile: File | null = null;
36
+ let activeChunkSize = 0;
37
+ let activeOffsetBytes = 0;
38
+ const workerScope = self as DedicatedWorkerGlobalScope;
39
+
40
+ function describeError(error: unknown): string {
41
+ return error instanceof Error ? error.message : String(error);
42
+ }
43
+
44
+ function postNextChunk(): void {
45
+ const file = activeFile;
46
+ if (file === null) {
47
+ return;
48
+ }
49
+ try {
50
+ const offsetBytes = activeOffsetBytes;
51
+ const nextOffset = Math.min(file.size, activeOffsetBytes + activeChunkSize);
52
+ const reader = new FileReaderSync();
53
+ const bytes = reader.readAsArrayBuffer(file.slice(activeOffsetBytes, nextOffset));
54
+ activeOffsetBytes = nextOffset;
55
+ const message: FileProcessingWorkerChunkMessage = {
56
+ type: "chunk",
57
+ offsetBytes,
58
+ bytes,
59
+ copiedBytes: activeOffsetBytes,
60
+ totalBytes: file.size,
61
+ };
62
+ workerScope.postMessage(message, [bytes]);
63
+ } catch (error: unknown) {
64
+ const failure: FileProcessingWorkerErrorMessage = {
65
+ type: "error",
66
+ message: describeError(error),
67
+ };
68
+ workerScope.postMessage(failure);
69
+ }
70
+ }
71
+
72
+ workerScope.onmessage = (event: MessageEvent<FileProcessingWorkerInboundMessage>) => {
73
+ const message = event.data;
74
+ if (message.type === "cancel") {
75
+ activeFile = null;
76
+ activeOffsetBytes = 0;
77
+ return;
78
+ }
79
+ if (message.type === "start") {
80
+ activeFile = message.file;
81
+ activeChunkSize = Math.max(1, Math.floor(message.chunkSize));
82
+ activeOffsetBytes = 0;
83
+ postNextChunk();
84
+ return;
85
+ }
86
+ if (activeFile !== null) {
87
+ postNextChunk();
88
+ }
89
+ };