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.
- package/LICENSES/jsmdcui.txt +22 -0
- package/README.md +102 -2
- package/frontends/wayland/LICENSE-land +21 -0
- package/frontends/wayland/README.md +123 -0
- package/frontends/wayland/casty/LICENSE +21 -0
- package/frontends/wayland/casty/bookmarks.js +32 -0
- package/frontends/wayland/casty/click.js +96 -0
- package/frontends/wayland/casty/commands.js +61 -0
- package/frontends/wayland/casty/config.js +45 -0
- package/frontends/wayland/casty/download-directory.js +28 -0
- package/frontends/wayland/casty/download.js +49 -0
- package/frontends/wayland/casty/hints.js +334 -0
- package/frontends/wayland/casty/keys.js +56 -0
- package/frontends/wayland/casty/page-commands.js +80 -0
- package/frontends/wayland/casty/prompt-width.js +148 -0
- package/frontends/wayland/casty/urlbar.js +606 -0
- package/frontends/wayland/client.js +88 -0
- package/frontends/wayland/clipboard.js +80 -0
- package/frontends/wayland/index.js +116 -0
- package/frontends/wayland/input.js +56 -0
- package/frontends/wayland/protocol.js +55 -0
- package/frontends/wayland/protocols/wayland.xml +3248 -0
- package/frontends/wayland/protocols/xdg-shell.xml +1415 -0
- package/frontends/wayland/transport.js +120 -0
- package/frontends/wayland/urlbar.js +158 -0
- package/index.js +28 -1
- package/lib/clipboard-command.js +10 -0
- package/lib/clipboard-powershell.js +22 -0
- package/lib/clipboard.js +57 -0
- package/lib/document-types.js +11 -0
- package/lib/headless-shell.js +78 -13
- package/lib/layout/text-layout.js +17 -9
- package/lib/paint/display-list.js +5 -1
- package/lib/paint/frame.js +35 -3
- package/lib/paint/raw-frames.js +47 -0
- package/lib/qml/README.md +971 -0
- package/lib/qml/builtins.js +700 -0
- package/lib/qml/canvas-dom.js +115 -0
- package/lib/qml/document.js +1176 -0
- package/lib/qml/format.js +9 -0
- package/lib/qml/host.js +324 -0
- package/lib/qml/images.js +199 -0
- package/lib/qml/launcher.js +132 -0
- package/lib/qml/loader.js +517 -0
- package/lib/qml/media-renderer.js +49 -0
- package/lib/qml/media.js +86 -0
- package/lib/qml/models.js +622 -0
- package/lib/qml/parser.js +381 -0
- package/lib/qml/qs +5 -0
- package/lib/qml/qscasty +3 -0
- package/lib/qml/qsheadless +3 -0
- package/lib/qml/qswayland +3 -0
- package/lib/qml/reactive.js +159 -0
- package/lib/qml/runtime.js +3381 -0
- package/lib/qml/shapes.js +243 -0
- package/lib/qml/text.js +109 -0
- package/lib/qml/textures.js +123 -0
- package/lib/qml/wayland-clipboard.js +32 -0
- package/lib/qml/worker-thread.js +70 -0
- package/lib/render-tree/index.js +16 -1
- package/lib/renderer/bindings.js +11 -0
- package/lib/renderer/canvas-2d.js +13 -0
- package/lib/renderer/compositor-channel.js +103 -0
- package/lib/renderer/compositor.js +25 -33
- package/lib/renderer/flags.js +1 -1
- package/lib/renderer/host.js +128 -92
- package/lib/renderer/main.js +38 -4
- package/lib/renderer/page-realm.js +32 -1
- package/lib/renderer/page-renderer.js +250 -21
- package/lib/renderer/text-input-paint.js +54 -0
- package/lib/renderer/text-input.js +57 -0
- package/lib/style/computed-style.js +2 -2
- package/package.json +1 -1
- package/test/clipboard.test.js +54 -0
- package/test/fixtures/browser-raw-demo.html +36 -0
- package/test/fixtures/flea-helpers/Dockerfile +36 -0
- package/test/fixtures/flea-helpers/bat.mjs +43 -0
- package/test/fixtures/flea-helpers/bin/bat +3 -0
- package/test/fixtures/flea-helpers/bin/bwrap +10 -0
- package/test/fixtures/flea-helpers/bin/bwrap-real-filter +9 -0
- package/test/fixtures/flea-helpers/bin/glow +3 -0
- package/test/fixtures/flea-helpers/bin/qs +3 -0
- package/test/fixtures/flea-helpers/bin/unsupported +3 -0
- package/test/fixtures/flea-helpers/check-highlighting.mjs +47 -0
- package/test/fixtures/flea-helpers/check-media.mjs +29 -0
- package/test/fixtures/flea-helpers/check-preview.mjs +27 -0
- package/test/fixtures/flea-helpers/check.mjs +36 -0
- package/test/fixtures/flea-helpers/highlight-cases.json +227 -0
- package/test/fixtures/flea-helpers/source-mime.xml +28 -0
- package/test/fixtures/flea-helpers/syntax-compat.mjs +23 -0
- package/test/fixtures/flea-thumbnail-stub/flea-placeholder.thumbnailer +4 -0
- package/test/fixtures/flea-thumbnail-stub/flea-thumbnail-stub +3 -0
- package/test/fixtures/qml/flea/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea/LICENSE +21 -0
- package/test/fixtures/qml/flea/README.md +25 -0
- package/test/fixtures/qml/flea/Theme.qml +8 -0
- package/test/fixtures/qml/flea/demo.qml +24 -0
- package/test/fixtures/qml/flea/qmldir +3 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Flea +21 -0
- package/test/fixtures/qml/flea-theme/LICENSE-Omarchy +20 -0
- package/test/fixtures/qml/flea-theme/README.md +70 -0
- package/test/fixtures/qml/flea-theme/button.qml +17 -0
- package/test/fixtures/qml/flea-theme/demo.qml +27 -0
- package/test/fixtures/qml/flea-theme/field.qml +21 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/BorderGeometry.js +373 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Color.qml +258 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/I18n.qml +118 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Numbers.js +19 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Plural.js +91 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Style.qml +561 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/Util.qml +188 -0
- package/test/fixtures/qml/flea-theme/imports/qs/Commons/qmldir +13 -0
- package/test/fixtures/qml/flea-theme/segment.qml +17 -0
- package/test/fixtures/qml/flea-theme/settings.qml +58 -0
- package/test/fixtures/qml/flea-theme/text-metrics.qml +10 -0
- package/test/fixtures/qml/flea-theme/ui/DialogButton.qml +150 -0
- package/test/fixtures/qml/flea-theme/ui/DialogField.qml +147 -0
- package/test/fixtures/qml/flea-theme/ui/EscapeHint.qml +11 -0
- package/test/fixtures/qml/flea-theme/ui/Glass.qml +114 -0
- package/test/fixtures/qml/flea-theme/ui/Glyph.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/GlyphSolid.qml +22 -0
- package/test/fixtures/qml/flea-theme/ui/Reveal.qml +54 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsRuler.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/SettingsSegment.qml +85 -0
- package/test/fixtures/qml/flea-theme/ui/StableModel.qml +89 -0
- package/test/fixtures/qml/flea-theme/ui/Theme.qml +460 -0
- package/test/fixtures/qml/flea-theme/ui/ThemeColors.qml +43 -0
- package/test/fixtures/qml/flea-theme/ui/ViewState.qml +565 -0
- package/test/fixtures/qml/flea-theme/ui/js/Anchor.js +379 -0
- package/test/fixtures/qml/flea-theme/ui/js/AnchorHold.js +162 -0
- package/test/fixtures/qml/flea-theme/ui/js/Archive.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/BackReturn.js +110 -0
- package/test/fixtures/qml/flea-theme/ui/js/Buttons.js +54 -0
- package/test/fixtures/qml/flea-theme/ui/js/ColumnMenu.js +111 -0
- package/test/fixtures/qml/flea-theme/ui/js/Columns.js +298 -0
- package/test/fixtures/qml/flea-theme/ui/js/Contrast.js +157 -0
- package/test/fixtures/qml/flea-theme/ui/js/Density.js +44 -0
- package/test/fixtures/qml/flea-theme/ui/js/DirSizes.js +129 -0
- package/test/fixtures/qml/flea-theme/ui/js/ExtThumbs.js +115 -0
- package/test/fixtures/qml/flea-theme/ui/js/Filter.js +284 -0
- package/test/fixtures/qml/flea-theme/ui/js/FolderSorts.js +108 -0
- package/test/fixtures/qml/flea-theme/ui/js/Format.js +305 -0
- package/test/fixtures/qml/flea-theme/ui/js/Icons.js +206 -0
- package/test/fixtures/qml/flea-theme/ui/js/Input.js +39 -0
- package/test/fixtures/qml/flea-theme/ui/js/KeyName.js +64 -0
- package/test/fixtures/qml/flea-theme/ui/js/Keymap.js +370 -0
- package/test/fixtures/qml/flea-theme/ui/js/Kinds.js +145 -0
- package/test/fixtures/qml/flea-theme/ui/js/Lists.js +11 -0
- package/test/fixtures/qml/flea-theme/ui/js/MakeDefault.js +186 -0
- package/test/fixtures/qml/flea-theme/ui/js/Match.js +27 -0
- package/test/fixtures/qml/flea-theme/ui/js/Material.js +109 -0
- package/test/fixtures/qml/flea-theme/ui/js/Menu.js +513 -0
- package/test/fixtures/qml/flea-theme/ui/js/Motion.js +16 -0
- package/test/fixtures/qml/flea-theme/ui/js/Mounts.js +320 -0
- package/test/fixtures/qml/flea-theme/ui/js/MouseNav.js +13 -0
- package/test/fixtures/qml/flea-theme/ui/js/Nav.js +328 -0
- package/test/fixtures/qml/flea-theme/ui/js/Palette.js +33 -0
- package/test/fixtures/qml/flea-theme/ui/js/Places.js +236 -0
- package/test/fixtures/qml/flea-theme/ui/js/Protocols.js +296 -0
- package/test/fixtures/qml/flea-theme/ui/js/RecentMode.js +154 -0
- package/test/fixtures/qml/flea-theme/ui/js/Search.js +396 -0
- package/test/fixtures/qml/flea-theme/ui/js/Settings.js +476 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsAbout.js +99 -0
- package/test/fixtures/qml/flea-theme/ui/js/SettingsShelf.js +52 -0
- package/test/fixtures/qml/flea-theme/ui/js/Sort.js +127 -0
- package/test/fixtures/qml/flea-theme/ui/js/StateWords.js +42 -0
- package/test/fixtures/qml/flea-theme/ui/js/Swap.js +166 -0
- package/test/fixtures/qml/flea-theme/ui/js/TextSize.js +107 -0
- package/test/fixtures/qml/flea-theme/ui/js/Thumbs.js +158 -0
- package/test/fixtures/qml/flea-theme/ui/js/UiState.js +438 -0
- package/test/fixtures/qml/flea-theme/ui/js/Update.js +132 -0
- package/test/fixtures/qml/flea-theme/ui/js/UserDirs.js +56 -0
- package/test/fixtures/qml/flea-theme/ui/qmldir +227 -0
- package/test/fixtures/qml/hello.qml +26 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabel.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/VersionLabelNew.qml +2 -0
- package/test/fixtures/qml/imports/Demo/Toolkit/qmldir +3 -0
- package/test/fixtures/qml/imports/components/Badge.qml +15 -0
- package/test/fixtures/qml/imports/logic/base.js +2 -0
- package/test/fixtures/qml/imports/logic/counter.js +4 -0
- package/test/fixtures/qml/imports/logic/math.js +3 -0
- package/test/fixtures/qml/imports/logic/shared.js +3 -0
- package/test/fixtures/qml/imports/main.qml +21 -0
- package/test/fixtures/qml/imports/theme/Theme.qml +8 -0
- package/test/fixtures/qml/imports/theme/qmldir +2 -0
- package/test/fixtures/qml/multiply.qml +61 -0
- package/test/fixtures/qml-canvas-dom.js +90 -0
- package/test/fixtures/qml-media-audio.js +39 -0
- package/test/fixtures/raw-capture-benchmark.json +76 -0
- package/test/helpers/flea-sandbox.js +48 -0
- package/test/helpers/quickshell-fixture.js +125 -0
- package/test/html-clipboard-wayland.test.js +47 -0
- package/test/index.test.js +13 -0
- package/test/manual-casty-without-qml.js +87 -0
- package/test/manual-flea-application.js +49 -0
- package/test/manual-flea-binary.js +62 -0
- package/test/manual-flea-qml.js +9 -0
- package/test/manual-flea-settings.js +40 -0
- package/test/manual-flea-theme.js +40 -0
- package/test/manual-qml-removal.js +46 -0
- package/test/manual-without-qml.js +45 -0
- package/test/qml-canvas-dom.test.js +37 -0
- package/test/qml-clipboard-bridge.test.js +25 -0
- package/test/qml-clipboard-wayland.test.js +40 -0
- package/test/qml-connections-timing.test.js +73 -0
- package/test/qml-dynamic.test.js +401 -0
- package/test/qml-flea-app.test.js +146 -0
- package/test/qml-flea-backend.test.js +78 -0
- package/test/qml-flea-columns-backend.test.js +38 -0
- package/test/qml-flea-compile.test.js +78 -0
- package/test/qml-flea-launcher.test.js +88 -0
- package/test/qml-flea-markdown-backend.test.js +31 -0
- package/test/qml-flea-media.test.js +28 -0
- package/test/qml-flea-navigation-backend.test.js +35 -0
- package/test/qml-flea-navigation-timing.test.js +147 -0
- package/test/qml-flea-preview-backend.test.js +53 -0
- package/test/qml-flea-previews.test.js +163 -0
- package/test/qml-flea-rows.test.js +101 -0
- package/test/qml-flea-textures.test.js +59 -0
- package/test/qml-flea-view-switch.test.js +33 -0
- package/test/qml-flea-wayland.test.js +68 -0
- package/test/qml-flea.test.js +34 -0
- package/test/qml-host.test.js +238 -0
- package/test/qml-imports.test.js +102 -0
- package/test/qml-inherited-handlers.test.js +51 -0
- package/test/qml-input-selection.test.js +66 -0
- package/test/qml-interactions.test.js +309 -0
- package/test/qml-language.test.js +167 -0
- package/test/qml-launcher.test.js +54 -0
- package/test/qml-layout-text.test.js +205 -0
- package/test/qml-media.test.js +63 -0
- package/test/qml-persisted-delegates.test.js +45 -0
- package/test/qml-quickshell-controls.test.js +65 -0
- package/test/qml-quickshell-settings.test.js +62 -0
- package/test/qml-repeater.test.js +72 -0
- package/test/qml-style-updates.test.js +124 -0
- package/test/qml-textures.test.js +66 -0
- package/test/qml-views.test.js +332 -0
- package/test/qml-worker.test.js +69 -0
- package/test/qml.test.js +83 -0
- package/test/raw-capture.test.js +152 -0
- package/test/renderer-host.test.js +11 -0
- package/test/text-input-pointer.test.js +31 -0
- package/test/text-input-scroll.test.js +29 -0
- package/test/text-input.test.js +47 -0
- package/test/wayland-frontend.test.js +83 -0
- package/test/wayland-urlbar.test.js +38 -0
- package/test/window-resize.test.js +36 -0
- 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
|
+
}
|
package/lib/qml/text.js
ADDED
|
@@ -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
|
+
}
|
package/lib/render-tree/index.js
CHANGED
|
@@ -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,
|
|
155
|
+
return this.#node(element, controlType, nodesById, {
|
|
141
156
|
tagName: element.tagName,
|
|
142
157
|
style: boxStyle,
|
|
143
158
|
children: Object.freeze(children),
|
package/lib/renderer/bindings.js
CHANGED
|
@@ -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);
|