buninu-browser 0.2.4 → 0.4.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 (249) hide show
  1. package/LICENSES/jsmdcui.txt +22 -0
  2. package/README.md +102 -2
  3. package/frontends/wayland/LICENSE-land +21 -0
  4. package/frontends/wayland/README.md +123 -0
  5. package/frontends/wayland/casty/LICENSE +21 -0
  6. package/frontends/wayland/casty/bookmarks.js +32 -0
  7. package/frontends/wayland/casty/click.js +96 -0
  8. package/frontends/wayland/casty/commands.js +61 -0
  9. package/frontends/wayland/casty/config.js +45 -0
  10. package/frontends/wayland/casty/download-directory.js +28 -0
  11. package/frontends/wayland/casty/download.js +49 -0
  12. package/frontends/wayland/casty/hints.js +334 -0
  13. package/frontends/wayland/casty/keys.js +56 -0
  14. package/frontends/wayland/casty/page-commands.js +80 -0
  15. package/frontends/wayland/casty/prompt-width.js +148 -0
  16. package/frontends/wayland/casty/urlbar.js +606 -0
  17. package/frontends/wayland/client.js +88 -0
  18. package/frontends/wayland/clipboard.js +80 -0
  19. package/frontends/wayland/index.js +116 -0
  20. package/frontends/wayland/input.js +56 -0
  21. package/frontends/wayland/protocol.js +55 -0
  22. package/frontends/wayland/protocols/wayland.xml +3248 -0
  23. package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
  24. package/frontends/wayland/transport.js +120 -0
  25. package/frontends/wayland/urlbar.js +158 -0
  26. package/index.js +28 -1
  27. package/lib/clipboard-command.js +10 -0
  28. package/lib/clipboard-powershell.js +22 -0
  29. package/lib/clipboard.js +57 -0
  30. package/lib/document-types.js +11 -0
  31. package/lib/headless-shell.js +78 -13
  32. package/lib/layout/text-layout.js +17 -9
  33. package/lib/paint/display-list.js +5 -1
  34. package/lib/paint/frame.js +35 -3
  35. package/lib/paint/raw-frames.js +47 -0
  36. package/lib/qml/README.md +971 -0
  37. package/lib/qml/builtins.js +700 -0
  38. package/lib/qml/canvas-dom.js +115 -0
  39. package/lib/qml/document.js +1176 -0
  40. package/lib/qml/format.js +9 -0
  41. package/lib/qml/host.js +324 -0
  42. package/lib/qml/images.js +199 -0
  43. package/lib/qml/launcher.js +132 -0
  44. package/lib/qml/loader.js +517 -0
  45. package/lib/qml/media-renderer.js +49 -0
  46. package/lib/qml/media.js +86 -0
  47. package/lib/qml/models.js +622 -0
  48. package/lib/qml/parser.js +381 -0
  49. package/lib/qml/qs +5 -0
  50. package/lib/qml/qscasty +3 -0
  51. package/lib/qml/qsheadless +3 -0
  52. package/lib/qml/qswayland +3 -0
  53. package/lib/qml/reactive.js +159 -0
  54. package/lib/qml/runtime.js +3381 -0
  55. package/lib/qml/shapes.js +243 -0
  56. package/lib/qml/text.js +109 -0
  57. package/lib/qml/textures.js +123 -0
  58. package/lib/qml/wayland-clipboard.js +32 -0
  59. package/lib/qml/worker-thread.js +70 -0
  60. package/lib/render-tree/index.js +16 -1
  61. package/lib/renderer/bindings.js +11 -0
  62. package/lib/renderer/canvas-2d.js +13 -0
  63. package/lib/renderer/compositor-channel.js +103 -0
  64. package/lib/renderer/compositor.js +25 -33
  65. package/lib/renderer/flags.js +1 -1
  66. package/lib/renderer/host.js +128 -92
  67. package/lib/renderer/main.js +38 -4
  68. package/lib/renderer/page-realm.js +32 -1
  69. package/lib/renderer/page-renderer.js +250 -21
  70. package/lib/renderer/text-input-paint.js +54 -0
  71. package/lib/renderer/text-input.js +57 -0
  72. package/lib/style/computed-style.js +2 -2
  73. package/package.json +1 -1
  74. package/test/clipboard.test.js +54 -0
  75. package/test/fixtures/browser-raw-demo.html +36 -0
  76. package/test/fixtures/flea-helpers/Dockerfile +36 -0
  77. package/test/fixtures/flea-helpers/bat.mjs +43 -0
  78. package/test/fixtures/flea-helpers/bin/bat +3 -0
  79. package/test/fixtures/flea-helpers/bin/bwrap +10 -0
  80. package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
  81. package/test/fixtures/flea-helpers/bin/glow +3 -0
  82. package/test/fixtures/flea-helpers/bin/qs +3 -0
  83. package/test/fixtures/flea-helpers/bin/unsupported +3 -0
  84. package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
  85. package/test/fixtures/flea-helpers/check-media.mjs +29 -0
  86. package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
  87. package/test/fixtures/flea-helpers/check.mjs +36 -0
  88. package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
  89. package/test/fixtures/flea-helpers/source-mime.xml +28 -0
  90. package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
  91. package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
  92. package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
  93. package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
  94. package/test/fixtures/qml/flea/LICENSE +21 -0
  95. package/test/fixtures/qml/flea/README.md +25 -0
  96. package/test/fixtures/qml/flea/Theme.qml +8 -0
  97. package/test/fixtures/qml/flea/demo.qml +24 -0
  98. package/test/fixtures/qml/flea/qmldir +3 -0
  99. package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
  100. package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
  101. package/test/fixtures/qml/flea-theme/README.md +70 -0
  102. package/test/fixtures/qml/flea-theme/button.qml +17 -0
  103. package/test/fixtures/qml/flea-theme/demo.qml +27 -0
  104. package/test/fixtures/qml/flea-theme/field.qml +21 -0
  105. package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
  106. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
  107. package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
  108. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
  109. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
  110. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
  111. package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
  112. package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
  113. package/test/fixtures/qml/flea-theme/segment.qml +17 -0
  114. package/test/fixtures/qml/flea-theme/settings.qml +58 -0
  115. package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
  116. package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
  117. package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
  118. package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
  119. package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
  120. package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
  121. package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
  122. package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
  123. package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
  124. package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
  125. package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
  126. package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
  127. package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
  128. package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
  129. package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
  130. package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
  131. package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
  132. package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
  133. package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
  134. package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
  135. package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
  136. package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
  137. package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
  138. package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
  139. package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
  140. package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
  141. package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
  142. package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
  143. package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
  144. package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
  145. package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
  146. package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
  147. package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
  148. package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
  149. package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
  150. package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
  151. package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
  152. package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
  153. package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
  154. package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
  155. package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
  156. package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
  157. package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
  158. package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
  159. package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
  160. package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
  161. package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
  162. package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
  163. package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
  164. package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
  165. package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
  166. package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
  167. package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
  168. package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
  169. package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
  170. package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
  171. package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
  172. package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
  173. package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
  174. package/test/fixtures/qml/hello.qml +26 -0
  175. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
  176. package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
  177. package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
  178. package/test/fixtures/qml/imports/components/Badge.qml +15 -0
  179. package/test/fixtures/qml/imports/logic/base.js +2 -0
  180. package/test/fixtures/qml/imports/logic/counter.js +4 -0
  181. package/test/fixtures/qml/imports/logic/math.js +3 -0
  182. package/test/fixtures/qml/imports/logic/shared.js +3 -0
  183. package/test/fixtures/qml/imports/main.qml +21 -0
  184. package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
  185. package/test/fixtures/qml/imports/theme/qmldir +2 -0
  186. package/test/fixtures/qml/multiply.qml +61 -0
  187. package/test/fixtures/qml-canvas-dom.js +90 -0
  188. package/test/fixtures/qml-media-audio.js +39 -0
  189. package/test/fixtures/raw-capture-benchmark.json +76 -0
  190. package/test/helpers/flea-sandbox.js +48 -0
  191. package/test/helpers/quickshell-fixture.js +125 -0
  192. package/test/html-clipboard-wayland.test.js +47 -0
  193. package/test/index.test.js +13 -0
  194. package/test/manual-casty-without-qml.js +87 -0
  195. package/test/manual-flea-application.js +49 -0
  196. package/test/manual-flea-binary.js +62 -0
  197. package/test/manual-flea-qml.js +9 -0
  198. package/test/manual-flea-settings.js +40 -0
  199. package/test/manual-flea-theme.js +40 -0
  200. package/test/manual-qml-removal.js +46 -0
  201. package/test/manual-without-qml.js +45 -0
  202. package/test/qml-canvas-dom.test.js +37 -0
  203. package/test/qml-clipboard-bridge.test.js +25 -0
  204. package/test/qml-clipboard-wayland.test.js +40 -0
  205. package/test/qml-connections-timing.test.js +73 -0
  206. package/test/qml-dynamic.test.js +401 -0
  207. package/test/qml-flea-app.test.js +146 -0
  208. package/test/qml-flea-backend.test.js +78 -0
  209. package/test/qml-flea-columns-backend.test.js +38 -0
  210. package/test/qml-flea-compile.test.js +78 -0
  211. package/test/qml-flea-launcher.test.js +88 -0
  212. package/test/qml-flea-markdown-backend.test.js +31 -0
  213. package/test/qml-flea-media.test.js +28 -0
  214. package/test/qml-flea-navigation-backend.test.js +35 -0
  215. package/test/qml-flea-navigation-timing.test.js +147 -0
  216. package/test/qml-flea-preview-backend.test.js +53 -0
  217. package/test/qml-flea-previews.test.js +163 -0
  218. package/test/qml-flea-rows.test.js +101 -0
  219. package/test/qml-flea-textures.test.js +59 -0
  220. package/test/qml-flea-view-switch.test.js +33 -0
  221. package/test/qml-flea-wayland.test.js +68 -0
  222. package/test/qml-flea.test.js +34 -0
  223. package/test/qml-host.test.js +238 -0
  224. package/test/qml-imports.test.js +102 -0
  225. package/test/qml-inherited-handlers.test.js +51 -0
  226. package/test/qml-input-selection.test.js +66 -0
  227. package/test/qml-interactions.test.js +309 -0
  228. package/test/qml-language.test.js +167 -0
  229. package/test/qml-launcher.test.js +54 -0
  230. package/test/qml-layout-text.test.js +205 -0
  231. package/test/qml-media.test.js +63 -0
  232. package/test/qml-persisted-delegates.test.js +45 -0
  233. package/test/qml-quickshell-controls.test.js +65 -0
  234. package/test/qml-quickshell-settings.test.js +62 -0
  235. package/test/qml-repeater.test.js +72 -0
  236. package/test/qml-style-updates.test.js +124 -0
  237. package/test/qml-textures.test.js +66 -0
  238. package/test/qml-views.test.js +332 -0
  239. package/test/qml-worker.test.js +69 -0
  240. package/test/qml.test.js +83 -0
  241. package/test/raw-capture.test.js +152 -0
  242. package/test/renderer-host.test.js +11 -0
  243. package/test/text-input-pointer.test.js +31 -0
  244. package/test/text-input-scroll.test.js +29 -0
  245. package/test/text-input.test.js +47 -0
  246. package/test/wayland-frontend.test.js +83 -0
  247. package/test/wayland-urlbar.test.js +38 -0
  248. package/test/window-resize.test.js +36 -0
  249. package/tools/bench-raw-capture.js +32 -0
@@ -0,0 +1,243 @@
1
+ // Solid QML paths keep the existing SVG DOM path. A gradient path uses a normal
2
+ // page canvas because this browser's inline-SVG painter has no gradient support.
3
+ // No image encoding, readback or extra CanvasKit instance is involved.
4
+ export function createQmlShapes(api) {
5
+ const { elements, parents, owners, children, cssColor } = api;
6
+ const roots = new WeakMap(),
7
+ canvases = new WeakMap();
8
+ function path(ctx, source) {
9
+ const tokens =
10
+ String(source).match(
11
+ /[a-zA-Z]|[-+]?(?:\d*\.\d+|\d+\.?\d*)(?:[eE][-+]?\d+)?/g,
12
+ ) || [];
13
+ let i = 0,
14
+ x = 0,
15
+ y = 0,
16
+ sx = 0,
17
+ sy = 0,
18
+ cx = 0,
19
+ cy = 0,
20
+ previous = '',
21
+ command = '';
22
+ const number = () => {
23
+ const n = Number(tokens[i++]);
24
+ if (!Number.isFinite(n)) throw Error('Invalid QML SVG path');
25
+ return n;
26
+ };
27
+ const point = (relative) => {
28
+ const a = number(),
29
+ b = number();
30
+ return [a + (relative ? x : 0), b + (relative ? y : 0)];
31
+ };
32
+ while (i < tokens.length) {
33
+ if (/^[a-zA-Z]$/.test(tokens[i])) command = tokens[i++];
34
+ if (!command) throw Error('Missing SVG path command');
35
+ const relative = command === command.toLowerCase(),
36
+ kind = command.toUpperCase();
37
+ if (kind === 'Z') {
38
+ ctx.closePath();
39
+ x = sx;
40
+ y = sy;
41
+ previous = kind;
42
+ command = '';
43
+ continue;
44
+ }
45
+ if (kind === 'M' || kind === 'L' || kind === 'T') {
46
+ const [nx, ny] = point(relative);
47
+ if (kind === 'M') {
48
+ ctx.moveTo(nx, ny);
49
+ sx = nx;
50
+ sy = ny;
51
+ command = relative ? 'l' : 'L';
52
+ } else if (kind === 'T') {
53
+ const a = ['Q', 'T'].includes(previous) ? 2 * x - cx : x,
54
+ b = ['Q', 'T'].includes(previous) ? 2 * y - cy : y;
55
+ ctx.quadraticCurveTo(a, b, nx, ny);
56
+ cx = a;
57
+ cy = b;
58
+ } else ctx.lineTo(nx, ny);
59
+ x = nx;
60
+ y = ny;
61
+ } else if (kind === 'H') {
62
+ x = number() + (relative ? x : 0);
63
+ ctx.lineTo(x, y);
64
+ } else if (kind === 'V') {
65
+ y = number() + (relative ? y : 0);
66
+ ctx.lineTo(x, y);
67
+ } else if (kind === 'C') {
68
+ const a = point(relative),
69
+ b = point(relative),
70
+ end = point(relative);
71
+ ctx.bezierCurveTo(...a, ...b, ...end);
72
+ [cx, cy] = b;
73
+ [x, y] = end;
74
+ } else if (kind === 'S') {
75
+ const a = ['C', 'S'].includes(previous)
76
+ ? [2 * x - cx, 2 * y - cy]
77
+ : [x, y],
78
+ b = point(relative),
79
+ end = point(relative);
80
+ ctx.bezierCurveTo(...a, ...b, ...end);
81
+ [cx, cy] = b;
82
+ [x, y] = end;
83
+ } else if (kind === 'Q') {
84
+ const a = point(relative),
85
+ end = point(relative);
86
+ ctx.quadraticCurveTo(...a, ...end);
87
+ [cx, cy] = a;
88
+ [x, y] = end;
89
+ } else if (kind === 'A') {
90
+ let rx = Math.abs(number()),
91
+ ry = Math.abs(number());
92
+ const phi = (number() * Math.PI) / 180,
93
+ large = number() !== 0,
94
+ sweep = number() !== 0,
95
+ [nx, ny] = point(relative);
96
+ if (!rx || !ry) ctx.lineTo(nx, ny);
97
+ else if (x !== nx || y !== ny) {
98
+ const cos = Math.cos(phi),
99
+ sin = Math.sin(phi),
100
+ dx = (x - nx) / 2,
101
+ dy = (y - ny) / 2,
102
+ px = cos * dx + sin * dy,
103
+ py = -sin * dx + cos * dy;
104
+ const scale = (px * px) / (rx * rx) + (py * py) / (ry * ry);
105
+ if (scale > 1) {
106
+ rx *= Math.sqrt(scale);
107
+ ry *= Math.sqrt(scale);
108
+ }
109
+ const numerator = Math.max(
110
+ 0,
111
+ rx * rx * ry * ry - rx * rx * py * py - ry * ry * px * px,
112
+ ),
113
+ denominator = rx * rx * py * py + ry * ry * px * px;
114
+ const factor =
115
+ (large === sweep ? -1 : 1) * Math.sqrt(numerator / denominator),
116
+ ax = (factor * rx * py) / ry,
117
+ ay = (-factor * ry * px) / rx;
118
+ const centerX = cos * ax - sin * ay + (x + nx) / 2,
119
+ centerY = sin * ax + cos * ay + (y + ny) / 2;
120
+ const start = Math.atan2((py - ay) / ry, (px - ax) / rx),
121
+ end = Math.atan2((-py - ay) / ry, (-px - ax) / rx);
122
+ ctx.ellipse(centerX, centerY, rx, ry, phi, start, end, !sweep);
123
+ }
124
+ x = nx;
125
+ y = ny;
126
+ } else throw Error('Unsupported QML SVG command: ' + command);
127
+ previous = kind;
128
+ }
129
+ }
130
+ function make(v, type, element, parent) {
131
+ if (type === 'Shape') {
132
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
133
+ svg.style.cssText =
134
+ 'position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none';
135
+ while (element.firstChild) svg.appendChild(element.firstChild);
136
+ element.appendChild(svg);
137
+ roots.set(v, svg);
138
+ } else if (type === 'ShapePath' && roots.has(parent))
139
+ roots.get(parent).appendChild(element);
140
+ }
141
+ function paint(v, type) {
142
+ if (type === 'Shape') {
143
+ const svg = roots.get(v);
144
+ svg.setAttribute('width', String(v.width));
145
+ svg.setAttribute('height', String(v.height));
146
+ svg.setAttribute(
147
+ 'viewBox',
148
+ '0 0 ' + (v.width || 1) + ' ' + (v.height || 1),
149
+ );
150
+ }
151
+ if (type !== 'ShapePath') return;
152
+ const element = elements.get(v),
153
+ root = parents.get(v),
154
+ gradient = v.fillGradient,
155
+ fillItem = v.fillItem;
156
+ if (!gradient && !fillItem) {
157
+ const old = canvases.get(v);
158
+ if (old) old.style.display = 'none';
159
+ return;
160
+ }
161
+ if (gradient && owners.get(gradient)?.type !== 'LinearGradient')
162
+ throw Error('Unsupported QML shape gradient');
163
+ const host = elements.get(root);
164
+ let canvas = canvases.get(v);
165
+ if (!canvas) {
166
+ canvas = document.createElement('canvas');
167
+ canvas.setAttribute('data-qml-gradient', '');
168
+ canvas.style.cssText =
169
+ 'position:absolute;left:0;top:0;pointer-events:none';
170
+ host.appendChild(canvas);
171
+ canvases.set(v, canvas);
172
+ api.cleanup(() => {
173
+ canvas.width = 1;
174
+ canvas.height = 1;
175
+ canvas.remove();
176
+ });
177
+ }
178
+ canvas.style.display = 'block';
179
+ element.style.display = 'none';
180
+ const width = Math.max(1, Math.ceil(root.width)),
181
+ height = Math.max(1, Math.ceil(root.height));
182
+ if (canvas.width !== width) canvas.width = width;
183
+ if (canvas.height !== height) canvas.height = height;
184
+ canvas.style.width = root.width + 'px';
185
+ canvas.style.height = root.height + 'px';
186
+ const context = canvas.getContext('2d');
187
+ context.clearRect(0, 0, width, height);
188
+ context.beginPath();
189
+ for (const child of children(v)) {
190
+ const type = owners.get(child)?.type;
191
+ if (type === 'PathSvg') path(context, child.path);
192
+ else if (type === 'PathRectangle') {
193
+ const radii = [
194
+ 'topLeftRadius',
195
+ 'topRightRadius',
196
+ 'bottomRightRadius',
197
+ 'bottomLeftRadius',
198
+ ].map((key) => child[key] ?? child.radius ?? 0);
199
+ // CanvasKit's 2D shim lacks roundRect; build the same path with arcTo.
200
+ const x = child.x, y = child.y, w = child.width, h = child.height;
201
+ const r = radii.map(value => Math.max(0, Number(value) || 0));
202
+ const factor = Math.min(1, w / (r[0] + r[1] || 1), w / (r[2] + r[3] || 1),
203
+ h / (r[0] + r[3] || 1), h / (r[1] + r[2] || 1));
204
+ for (let i = 0; i < 4; i++) r[i] *= Math.max(0, factor);
205
+ context.moveTo(x + r[0], y);
206
+ context.lineTo(x + w - r[1], y);context.arcTo(x + w, y, x + w, y + r[1], r[1]);
207
+ context.lineTo(x + w, y + h - r[2]);context.arcTo(x + w, y + h, x + w - r[2], y + h, r[2]);
208
+ context.lineTo(x + r[3], y + h);context.arcTo(x, y + h, x, y + h - r[3], r[3]);
209
+ context.lineTo(x, y + r[0]);context.arcTo(x, y, x + r[0], y, r[0]);
210
+ context.closePath();
211
+ }
212
+ }
213
+ if (fillItem) {
214
+ const bitmap = api.texture(fillItem);
215
+ if (bitmap) {
216
+ context.save();
217
+ context.clip(v.fillRule === 0 ? 'evenodd' : 'nonzero');
218
+ context.drawImage(bitmap, 0, 0, width, height);
219
+ context.restore();
220
+ }
221
+ } else {
222
+ const fill = context.createLinearGradient(gradient.x1, gradient.y1, gradient.x2, gradient.y2);
223
+ for (const stop of children(gradient))
224
+ fill.addColorStop(Math.max(0, Math.min(1, stop.position)), cssColor(stop.color));
225
+ context.fillStyle = fill;
226
+ context.fill(v.fillRule === 0 ? 'evenodd' : 'nonzero');
227
+ }
228
+ if (v.strokeWidth > 0) {
229
+ context.strokeStyle = cssColor(v.strokeColor);
230
+ context.lineWidth = v.strokeWidth;
231
+ context.lineCap =
232
+ v.capStyle === 32 ? 'round' : v.capStyle === 16 ? 'square' : 'butt';
233
+ context.lineJoin =
234
+ v.joinStyle === 128 ? 'round' : v.joinStyle === 64 ? 'bevel' : 'miter';
235
+ context.setLineDash(
236
+ v.strokeStyle === 2 ? v.dashPattern.map((n) => n * v.strokeWidth) : [],
237
+ );
238
+ context.lineDashOffset = v.dashOffset * v.strokeWidth;
239
+ context.stroke();
240
+ }
241
+ }
242
+ return { make, paint };
243
+ }
@@ -0,0 +1,109 @@
1
+ // Qt Text's markup is content, not generated QML HTML. Build an inert, restricted
2
+ // DOM fragment; embedded scripts, handlers and external resource tags are never
3
+ // transferred to the live document. Rendering stays in the browser's CSS engine.
4
+ export function createQmlText(document, markdown) {
5
+ const cache = new WeakMap();
6
+ const tags = new Set(
7
+ 'a b strong i em u s strike sub sup span font br p div pre code ul ol li h1 h2 h3 h4 h5 h6 blockquote table thead tbody tr td th hr'.split(
8
+ ' ',
9
+ ),
10
+ );
11
+ const blocks = new Set(
12
+ 'p div pre li h1 h2 h3 h4 h5 h6 blockquote tr'.split(' '),
13
+ );
14
+ function copy(source, target, collapse = false, pre = false) {
15
+ for (const child of source.childNodes) {
16
+ if (child.nodeType === 3) {
17
+ // Markdown emits formatting newlines between block tags. They are not
18
+ // extra text lines, and must not inflate a Text item's implicitHeight.
19
+ if (/^\s*$/.test(child.textContent) &&
20
+ (blocks.has(child.previousSibling?.localName) || blocks.has(child.nextSibling?.localName))) continue;
21
+ const text = collapse && !pre ? child.textContent.replace(/[ \t\r\n\f]+/g, ' ') : child.textContent;
22
+ target.appendChild(document.createTextNode(text));
23
+ continue;
24
+ }
25
+ if (child.nodeType !== 1) continue;
26
+ const tag = child.localName.toLowerCase();
27
+ if (['script', 'style', 'iframe', 'object', 'embed'].includes(tag))
28
+ continue;
29
+ if (!tags.has(tag)) {
30
+ copy(child, target, collapse, pre);
31
+ continue;
32
+ }
33
+ const node = document.createElement(tag === 'font' ? 'span' : tag);
34
+ if (blocks.has(tag) || ['blockquote', 'ul', 'ol'].includes(tag))
35
+ node.style.margin = '0';
36
+ if (tag === 'a') {
37
+ const href = child.getAttribute('href');
38
+ if (href && !/^\s*(?:javascript|vbscript|data):/i.test(href)) {
39
+ node.setAttribute('href', href);
40
+ node.style.pointerEvents = 'auto';
41
+ }
42
+ }
43
+ if (tag === 'font') {
44
+ if (child.hasAttribute('color'))
45
+ node.style.color = child.getAttribute('color');
46
+ if (child.hasAttribute('face'))
47
+ node.style.fontFamily = child.getAttribute('face');
48
+ }
49
+ for (const key of [
50
+ 'color',
51
+ 'background-color',
52
+ 'font-family',
53
+ 'font-size',
54
+ 'font-weight',
55
+ 'font-style',
56
+ 'text-decoration',
57
+ 'vertical-align',
58
+ 'white-space',
59
+ ]) {
60
+ const value = child.style?.getPropertyValue(key);
61
+ if (value && !/url\s*\(|expression\s*\(/i.test(value))
62
+ node.style.setProperty(key, value);
63
+ }
64
+ copy(child, node, collapse, pre || tag === 'pre');
65
+ target.appendChild(node);
66
+ }
67
+ }
68
+ const plain = (node) => {
69
+ let out = '';
70
+ for (const child of node.childNodes) {
71
+ if (child.nodeType === 3) out += child.textContent;
72
+ else if (child.nodeType === 1) {
73
+ if (child.localName === 'br') out += '\n';
74
+ else {
75
+ out += plain(child);
76
+ if (blocks.has(child.localName) && !out.endsWith('\n')) out += '\n';
77
+ }
78
+ }
79
+ }
80
+ return out;
81
+ };
82
+ return (value) => {
83
+ const source = String(value.text),
84
+ format = value.textFormat ?? 0;
85
+ let entry = cache.get(value);
86
+ if (entry?.source === source && entry.format === format) return entry;
87
+ const container = entry?.container ?? document.createElement('span');
88
+ container.style.display = 'contents';
89
+ container.replaceChildren();
90
+ const isPlain =
91
+ format === 0 ||
92
+ (format === 2 && !/<[a-z][\w:-]*(?:\s|\/?>)/i.test(source));
93
+ if (isPlain) container.textContent = source;
94
+ else if ([1, 2, 3, 4].includes(format)) {
95
+ const html = format === 4 ? markdown(source) : source;
96
+ const template = document.createElement('template');
97
+ template.innerHTML = html;
98
+ copy(template.content, container, format === 4);
99
+ } else throw Error('Unsupported QML textFormat: ' + format);
100
+ entry = {
101
+ source,
102
+ format,
103
+ container,
104
+ plain: isPlain ? source : plain(container).replace(/\n$/, ''),
105
+ };
106
+ cache.set(value, entry);
107
+ return entry;
108
+ };
109
+ }
@@ -0,0 +1,123 @@
1
+ // Serialized into the QML realm. Snapshot ownership stays in ordinary canvases.
2
+ export function createQmlTextures(api) {
3
+ const { elements, state, effect, untrack, signal, cleanup } = api;
4
+ const entries = new WeakMap(), refs = new WeakMap(), pending = new Set();
5
+ const reference = value => {
6
+ let ref = refs.get(value);
7
+ if (!ref) refs.set(value, ref = state({ count: 0, hidden: 0 }));
8
+ return ref;
9
+ };
10
+ function attach(value, delta, hide) {
11
+ if (!value) return;
12
+ const ref = reference(value);
13
+ ref.count += delta;
14
+ if (hide) ref.hidden += delta;
15
+ const element = elements.get(value);
16
+ if (element) {
17
+ if (ref.count) element.setAttribute('data-qml-texture-source', '');
18
+ else element.removeAttribute('data-qml-texture-source');
19
+ if (ref.hidden || (!value.visible && ref.count)) element.setAttribute('data-qml-source-hidden', '');
20
+ else element.removeAttribute('data-qml-source-hidden');
21
+ }
22
+ }
23
+ function make(v, type, element) {
24
+ if (type !== 'ShaderEffectSource') return;
25
+ if (!api.host) throw Error('ShaderEffectSource requires a local QML host renderer');
26
+ Object.assign(v, { sourceItem: null, sourceRect: { x: 0, y: 0, width: 0, height: 0 },
27
+ textureSize: { width: 0, height: 0 }, hideSource: false, live: true,
28
+ scheduledUpdateCompleted: signal() });
29
+ const canvas = document.createElement('canvas');
30
+ canvas.width = canvas.height = 1;
31
+ canvas.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none';
32
+ canvas.setAttribute('data-qml-texture', '');
33
+ element.appendChild(canvas);
34
+ const entry = { canvas, revision: state({ value: 0 }), ready: false };
35
+ entries.set(v, entry);
36
+ let source = null, hiding = false, observer, frame, disposed = false, requested = true;
37
+ const queue = () => {
38
+ if (!disposed && frame === undefined) { pending.add(capture); frame = requestAnimationFrame(capture); }
39
+ };
40
+ const capture = () => {
41
+ if (frame !== undefined) cancelAnimationFrame(frame);
42
+ pending.delete(capture);
43
+ frame = undefined;
44
+ if (disposed || !source || !requested) return;
45
+ const input = elements.get(source);
46
+ if (!input?.isConnected || !(source.width > 0 && source.height > 0)) return;
47
+ const rect = v.sourceRect;
48
+ const sw = rect.width > 0 ? rect.width : source.width;
49
+ const sh = rect.height > 0 ? rect.height : source.height;
50
+ const w = Math.ceil(v.textureSize.width > 0 ? v.textureSize.width : sw);
51
+ const h = Math.ceil(v.textureSize.height > 0 ? v.textureSize.height : sh);
52
+ if (!(w > 0 && h > 0) || w > 8192 || h > 8192 || w * h > 16777216)
53
+ throw Error('QML texture exceeds snapshot size limit');
54
+ requested = false;
55
+ if (canvas.width !== w) canvas.width = w;
56
+ if (canvas.height !== h) canvas.height = h;
57
+ const ctx = canvas.getContext('2d');
58
+ ctx.clearRect(0, 0, w, h);
59
+ ctx.drawImage(input, rect.width > 0 ? rect.x : 0, rect.height > 0 ? rect.y : 0, sw, sh, 0, 0, w, h);
60
+ entry.ready = true;
61
+ entry.revision.value++;
62
+ // Only signal once pixels are actually in the destination canvas.
63
+ v.scheduledUpdateCompleted();
64
+ };
65
+ v.scheduleUpdate = () => { requested = true; queue(); };
66
+ effect(() => {
67
+ const next = v.sourceItem, hide = v.hideSource;
68
+ const live = v.live;
69
+ const size = v.textureSize, rect = v.sourceRect;
70
+ size.width; size.height; rect.x; rect.y; rect.width; rect.height;
71
+ if (next) { next.width; next.height; next.visible; }
72
+ untrack(() => {
73
+ if (next !== source || hide !== hiding) {
74
+ observer?.disconnect();
75
+ attach(source, -1, hiding);
76
+ source = next; hiding = hide;
77
+ attach(source, 1, hiding);
78
+ if (source) {
79
+ const input = elements.get(source);
80
+ if (!input) throw Error('ShaderEffectSource.sourceItem must be an Item');
81
+ observer = new MutationObserver(() => {
82
+ if (v.live) { requested = true; queue(); }
83
+ });
84
+ observer.observe(input, { subtree: true, attributes: true, childList: true, characterData: true });
85
+ } else {
86
+ entry.ready = false;
87
+ canvas.width = canvas.height = 1;
88
+ entry.revision.value++;
89
+ }
90
+ }
91
+ if (live || !entry.ready || requested) { requested = true; queue(); }
92
+ });
93
+ });
94
+ cleanup(() => {
95
+ disposed = true;
96
+ pending.delete(capture);
97
+ observer?.disconnect();
98
+ if (frame !== undefined) cancelAnimationFrame(frame);
99
+ untrack(() => attach(source, -1, hiding));
100
+ canvas.width = canvas.height = 1;
101
+ canvas.remove();
102
+ entries.delete(v);
103
+ });
104
+ }
105
+ function keepLayout(v) {
106
+ const ref = reference(v);
107
+ const keep = ref.count > 0;
108
+ const element = elements.get(v);
109
+ const hidden = ref.hidden > 0 || (keep && !v.visible);
110
+ if (element && element.hasAttribute('data-qml-source-hidden') !== hidden) {
111
+ if (hidden) element.setAttribute('data-qml-source-hidden', '');
112
+ else element.removeAttribute('data-qml-source-hidden');
113
+ }
114
+ return keep;
115
+ }
116
+ function image(v) {
117
+ const entry = entries.get(v);
118
+ if (!entry) throw Error('ShapePath.fillItem currently requires ShaderEffectSource');
119
+ entry.revision.value;
120
+ return entry.ready ? entry.canvas : null;
121
+ }
122
+ return { make, keepLayout, image, flush: () => { for (const capture of [...pending]) capture(); } };
123
+ }
@@ -0,0 +1,32 @@
1
+ // Optional local-QML bridge to the frontend's standard Wayland clipboard.
2
+ // Ordinary HTML never needs this HTTP endpoint. Create it lazily before the
3
+ // first local QML navigation, including navigation after an about:blank launch.
4
+ export async function attachQmlClipboard(client,browser,done) {
5
+ const clipboard=client.clipboard;
6
+ if(!clipboard)return;
7
+ const navigate=browser.context.navigate;
8
+ let server,setup,closed=false;
9
+ const prepare=()=>setup??=(async()=>{
10
+ const token=crypto.randomUUID();
11
+ server=Bun.serve({hostname:'127.0.0.1',port:0,maxRequestBodySize:1024*1024+1024,
12
+ async fetch(request) {
13
+ if(request.method!=='POST'||new URL(request.url).pathname!=='/'+token||request.headers.has('origin'))return new Response(null,{status:403});
14
+ try {
15
+ const {operation,text}=await request.json();
16
+ if(operation==='read')return Response.json({text:await clipboard.read()});
17
+ if(operation!=='write'||typeof text!=='string')return new Response(null,{status:400});
18
+ await clipboard.write(text);return Response.json({});
19
+ }catch{return new Response(null,{status:503});}
20
+ }});
21
+ await browser.context.addInitScript(`globalThis.__qmlHost?.setClipboardEndpoint(${JSON.stringify(`http://127.0.0.1:${server.port}/${token}`)});`);
22
+ })();
23
+ const wrapped=async function(url,...args) {
24
+ if(!closed&&/^file:.*\.qml(?:[?#]|$)/i.test(String(url)))await prepare();
25
+ return navigate.call(this,url,...args);
26
+ };
27
+ browser.context.navigate=wrapped;
28
+ done.finally(()=>{
29
+ closed=true;server?.stop(true);
30
+ if(browser.context.navigate===wrapped)browser.context.navigate=navigate;
31
+ });
32
+ }
@@ -0,0 +1,70 @@
1
+ // Local WorkerScript application code executes on its own Bun worker thread.
2
+ // Qt.include uses one script global scope; it is not the QML import resolver.
3
+ import { parentPort, workerData } from 'node:worker_threads';
4
+ import { readFileSync } from 'node:fs';
5
+ import { createContext, Script } from 'node:vm';
6
+ let current = workerData.url,
7
+ bytes = 0,
8
+ depth = 0;
9
+ const context = createContext({
10
+ console,
11
+ WorkerScript: {
12
+ onMessage: null,
13
+ sendMessage: (message) =>
14
+ parentPort.postMessage({ kind: 'message', message }),
15
+ },
16
+ });
17
+ const run = (url) => {
18
+ if (new URL(url).protocol !== 'file:')
19
+ throw Error('WorkerScript imports must be local files');
20
+ if (++depth > 48) throw Error('WorkerScript include depth exceeded');
21
+ const previous = current;
22
+ current = url;
23
+ try {
24
+ const source = readFileSync(new URL(url), 'utf8');
25
+ bytes += Buffer.byteLength(source);
26
+ if (bytes > 4 * 1024 * 1024)
27
+ throw Error('WorkerScript source limit exceeded');
28
+ // Like Qt.include in a WorkerScript, directives supply no import aliases.
29
+ const code = source.replace(
30
+ /^\s*\.(?:pragma\s+library|import\s+[^\n]+)\s*$/gm,
31
+ '',
32
+ );
33
+ new Script(code, { filename: url }).runInContext(context);
34
+ } finally {
35
+ current = previous;
36
+ depth--;
37
+ }
38
+ };
39
+ context.Qt = {
40
+ include: (relative, callback) => {
41
+ let result;
42
+ try {
43
+ run(new URL(relative, current).href);
44
+ result = { status: 0 };
45
+ } catch (exception) {
46
+ result = { status: 3, exception };
47
+ }
48
+ callback?.(result);
49
+ return result;
50
+ },
51
+ };
52
+ try {
53
+ run(current);
54
+ parentPort.postMessage({ kind: 'ready' });
55
+ parentPort.on('message', (message) => {
56
+ try {
57
+ context.WorkerScript.onMessage?.(message);
58
+ } catch (error) {
59
+ parentPort.postMessage({
60
+ kind: 'error',
61
+ error: String(error.stack || error),
62
+ });
63
+ }
64
+ });
65
+ } catch (error) {
66
+ parentPort.postMessage({
67
+ kind: 'error',
68
+ error: String(error.stack || error),
69
+ });
70
+ }
@@ -1,6 +1,8 @@
1
1
  import { extractSvg } from "./svg.js";
2
2
  import { computeElementStyle, controlKind } from "../style/computed-style.js";
3
3
 
4
+ import {textInputSelection} from '../renderer/text-input-paint.js';
5
+
4
6
  const PLACEHOLDER_COLOR = "rgba(117, 117, 117, 1)";
5
7
  const CHECKED_COLOR = "rgba(0, 117, 255, 1)";
6
8
  const DEFAULT_SUMMARY = Object.freeze({ tagName: "SUMMARY", getAttribute: () => null, hasAttribute: () => false });
@@ -99,6 +101,7 @@ export class RenderTreeBuilder {
99
101
  let keys = this.#pseudoKeys.get(element);
100
102
  if (!keys) this.#pseudoKeys.set(element, keys = { before: {}, after: {}, beforeText: {}, afterText: {} });
101
103
  keys.value ??= {};
104
+ const controlType=boxType(style)==="inline"?"inline-block":boxType(style);
102
105
  let boxStyle = style;
103
106
  let text = "";
104
107
  let textStyle = style;
@@ -134,10 +137,22 @@ export class RenderTreeBuilder {
134
137
  textStyle = Object.freeze({ ...style, color: PLACEHOLDER_COLOR });
135
138
  }
136
139
  }
140
+
141
+ if (control === "text" || control === "textarea") {
142
+ const selected = textInputSelection(element, text, textStyle, (part, type, properties, domNode) => {
143
+ keys.inputSelection ??= {};
144
+ keys.inputSelection[part] ??= {};
145
+ return this.#node(domNode, type, nodesById, properties, keys.inputSelection[part]);
146
+ });
147
+ if (selected) return this.#node(element, controlType, nodesById, {
148
+ tagName: element.tagName, style: boxStyle, children: Object.freeze(selected),
149
+ });
150
+ }
151
+
137
152
  const children = text
138
153
  ? [this.#node(element, "text", nodesById, { text, style: textStyle, pseudo: "value", children: Object.freeze([]) }, keys.value)]
139
154
  : [];
140
- return this.#node(element, "inline-block", nodesById, {
155
+ return this.#node(element, controlType, nodesById, {
141
156
  tagName: element.tagName,
142
157
  style: boxStyle,
143
158
  children: Object.freeze(children),
@@ -21,6 +21,7 @@ const isCanvasSizeAttribute = (node, name, namespace = null) => !namespace
21
21
  && node.namespaceURI === "http://www.w3.org/1999/xhtml" && node.tagName === "CANVAS"
22
22
  && (name === "width" || name === "height");
23
23
  const REFLECTED_PROPERTIES = [
24
+ "selectionStart", "selectionEnd", "selectionDirection", "maxLength",
24
25
  "value", "checked", "disabled", "type", "name", "href", "src", "alt", "title",
25
26
  "placeholder", "selected", "readOnly", "required", "min", "max", "step", "rel", "target",
26
27
  "htmlFor", "lang", "dir", "tabIndex", "hidden", "defaultValue", "action", "method",
@@ -1097,6 +1098,16 @@ class ElementBinding extends ParentBinding {
1097
1098
 
1098
1099
  get validity() { return this.#validity ??= realmOf(this).wrapValidity(nodeOf(this).validity); }
1099
1100
 
1101
+ setSelectionRange(start, end, direction = 'none') {
1102
+ if(arguments.length<2)throw new TypeError('setSelectionRange requires start and end');
1103
+ nodeOf(this).setSelectionRange(Number(start),Number(end),String(direction));touch(this);
1104
+ }
1105
+ setRangeText(replacement, ...args) {
1106
+ if(!arguments.length)throw new TypeError('setRangeText requires replacement');
1107
+ nodeOf(this).setRangeText(String(replacement),...args.map((v,i)=>i<2?Number(v):String(v)));touch(this);
1108
+ }
1109
+ select() {nodeOf(this).select();touch(this);}
1110
+
1100
1111
  setCustomValidity(message) {
1101
1112
  if (!arguments.length) throw new TypeError("setCustomValidity requires a message");
1102
1113
  nodeOf(this).setCustomValidity(String(message));
@@ -14,6 +14,10 @@
14
14
  * whole WASM heap. The copy handed back is only the requested rectangle.
15
15
  */
16
16
 
17
+ // qml handle begin: consume local-QML DOM snapshots without exposing native images
18
+ import { drawQmlDomImage } from "../qml/canvas-dom.js";
19
+ // qml handle end
20
+
17
21
  let CanvasKit = null;
18
22
 
19
23
  /** The CanvasKit `loadGraphics` instantiated. Page canvases share it. */
@@ -225,6 +229,15 @@ function open(node) {
225
229
  if (property === "drawImage" && resolveImage) {
226
230
  source = resolveImage(args[0]);
227
231
  if (!source?.image) return;
232
+
233
+ // qml handle begin: temporary DOM images have synchronous ownership
234
+ if (drawQmlDomImage(source, state.node.deref(), context, args)) {
235
+ if (source.originClean === false) state.originClean = false;
236
+ changed(state);
237
+ return;
238
+ }
239
+ // qml handle end
240
+
228
241
  args[0] = source.image;
229
242
  }
230
243
  const result = context[property](...args);