xbintsc 0.5.36 → 0.5.37
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/dist/src/cli/help.d.ts +1 -1
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/gui/index.js +12 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/tests/e2e/gui-browser.test.js +65 -0
- package/dist/tests/e2e/gui-browser.test.js.map +1 -1
- package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
- package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
- package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
- package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
- package/dist/tests/e2e/gui-dom.test.js +215 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -1
- package/dist/tests/e2e/gui-events.test.js +112 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -1
- package/doc/gui/api.md +38 -9
- package/doc/gui/canvas.md +49 -18
- package/doc/gui/dom.md +51 -4
- package/doc/zh-CN/gui/api.md +33 -4
- package/doc/zh-CN/gui/canvas.md +44 -19
- package/doc/zh-CN/gui/dom.md +45 -3
- package/package.json +7 -7
- package/runtime/ext_gui/browser.cpp +34 -49
- package/runtime/ext_gui/browser_abort.cpp +249 -0
- package/runtime/ext_gui/browser_dialogs.cpp +76 -0
- package/runtime/ext_gui/browser_media.cpp +176 -0
- package/runtime/ext_gui/browser_navigation.cpp +130 -0
- package/runtime/ext_gui/canvas.cpp +88 -30
- package/runtime/ext_gui/canvas.h +74 -9
- package/runtime/ext_gui/canvas_image_data.cpp +143 -0
- package/runtime/ext_gui/canvas_internal.h +8 -5
- package/runtime/ext_gui/canvas_pattern.cpp +90 -0
- package/runtime/ext_gui/canvas_raster.cpp +31 -9
- package/runtime/ext_gui/canvas_style.cpp +4 -2
- package/runtime/ext_gui/dom.h +4 -0
- package/runtime/ext_gui/dom_api.cpp +1 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
- package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
- package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
- package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
- package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
- package/runtime/ext_gui/dom_api_document.cpp +58 -5
- package/runtime/ext_gui/dom_api_events.cpp +93 -17
- package/runtime/ext_gui/dom_api_internal.h +55 -2
- package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
- package/runtime/ext_gui/dom_api_node.cpp +40 -19
- package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
- package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
- package/runtime/ext_gui/dom_api_proto.cpp +35 -0
- package/runtime/ext_gui/dom_api_style.cpp +223 -13
- package/runtime/ext_gui/gui.cpp +59 -5
- package/runtime/ext_gui/gui_engine.h +47 -0
- package/runtime/ext_gui/gui_events.cpp +80 -9
- package/runtime/ext_gui/listener_options.h +43 -0
- package/runtime/ext_gui/paint.cpp +28 -0
- package/runtime/ext_gui/paint.h +4 -0
- package/runtime/ext_gui/window.cpp +14 -65
- package/runtime/ext_gui/window_events.cpp +125 -0
- package/runtime/rt.h +26 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_builtins.c +0 -21
- package/runtime/xt_containers/access.inc +94 -2
- package/runtime/xt_containers/objects.inc +29 -31
- package/src/driver/version.ts +1 -1
- package/src/extensions/gui/index.ts +12 -0
|
@@ -93,17 +93,31 @@ METHOD(doc_create_element) {
|
|
|
93
93
|
return xt_gui_node_handle(win, win->document->createElement(tag));
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
/** `createElementNS(ns, name)`: the
|
|
97
|
-
*
|
|
96
|
+
/** `createElementNS(ns, name)`: the namespace is recorded (so `namespaceURI`
|
|
97
|
+
* answers it) and the qualified name's prefix is preserved. HTML names are
|
|
98
|
+
* lowercased, but names in another namespace keep their case — SVG is
|
|
99
|
+
* case-sensitive (`linearGradient`, `clipPath`). */
|
|
98
100
|
METHOD(doc_create_element_ns) {
|
|
99
101
|
(void)env;
|
|
100
102
|
XtGuiWindow *win = resolve_window(self);
|
|
101
103
|
if (win == nullptr || win->document == nullptr || argc < 2) return XT_UNDEFINED;
|
|
104
|
+
std::string ns = to_string_value(xt_arg(argc, argv, 0));
|
|
102
105
|
std::string name = to_string_value(xt_arg(argc, argv, 1));
|
|
106
|
+
std::string prefix;
|
|
103
107
|
size_t colon = name.rfind(':');
|
|
104
|
-
if (colon != std::string::npos)
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
if (colon != std::string::npos) {
|
|
109
|
+
prefix = name.substr(0, colon);
|
|
110
|
+
name = name.substr(colon + 1);
|
|
111
|
+
}
|
|
112
|
+
const bool html = ns.empty() || ns == "http://www.w3.org/1999/xhtml";
|
|
113
|
+
if (html) {
|
|
114
|
+
for (char &c : name) c = (char)std::tolower((unsigned char)c);
|
|
115
|
+
}
|
|
116
|
+
xtgui::Node *node = win->document->createElement(name);
|
|
117
|
+
if (node == nullptr) return XT_UNDEFINED;
|
|
118
|
+
node->ns = ns;
|
|
119
|
+
node->ns_prefix = prefix;
|
|
120
|
+
return xt_gui_node_handle(win, node);
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
/** `document.hidden` / `visibilityState`: a window in the main loop is always
|
|
@@ -124,6 +138,15 @@ METHOD(doc_visibility_state_get) {
|
|
|
124
138
|
return xt_string_from_cstr("visible");
|
|
125
139
|
}
|
|
126
140
|
|
|
141
|
+
/** `document.hasFocus()`: true after `window.focus()` until `window.blur()`. */
|
|
142
|
+
METHOD(doc_has_focus) {
|
|
143
|
+
(void)env;
|
|
144
|
+
(void)argc;
|
|
145
|
+
(void)argv;
|
|
146
|
+
XtGuiWindow *win = resolve_window(self);
|
|
147
|
+
return win != nullptr && win->focused ? XT_TRUE : XT_FALSE;
|
|
148
|
+
}
|
|
149
|
+
|
|
127
150
|
/** `document.readyState`: the page is fully parsed before any script runs. */
|
|
128
151
|
METHOD(doc_ready_state_get) {
|
|
129
152
|
(void)self;
|
|
@@ -149,6 +172,36 @@ METHOD(doc_title_get) {
|
|
|
149
172
|
return xt_string_from_cstr(text.c_str());
|
|
150
173
|
}
|
|
151
174
|
|
|
175
|
+
/** `document.title = text`: replace the text of the document's `<title>`.
|
|
176
|
+
* A page without one gets a `<title>` (and a `<head>`, if the parser did not
|
|
177
|
+
* create one), so the assignment is not silently lost. */
|
|
178
|
+
METHOD(doc_title_set) {
|
|
179
|
+
(void)env;
|
|
180
|
+
XtGuiWindow *win = resolve_window(self);
|
|
181
|
+
if (win == nullptr || win->document == nullptr) return XT_UNDEFINED;
|
|
182
|
+
std::string text;
|
|
183
|
+
if (argc >= 1) text = to_string_value(xt_arg(argc, argv, 0));
|
|
184
|
+
xtgui::Node *root = const_cast<xtgui::Node *>(win->document->root());
|
|
185
|
+
xtgui::Node *title = const_cast<xtgui::Node *>(find_by_tag(root, "title"));
|
|
186
|
+
if (title == nullptr) {
|
|
187
|
+
xtgui::Node *head = const_cast<xtgui::Node *>(find_by_tag(root, "head"));
|
|
188
|
+
if (head == nullptr) {
|
|
189
|
+
head = win->document->createElement("head");
|
|
190
|
+
if (head == nullptr) return XT_UNDEFINED;
|
|
191
|
+
/* `<head>` belongs before the body, not after it. */
|
|
192
|
+
xtgui::Node *body = const_cast<xtgui::Node *>(find_by_tag(root, "body"));
|
|
193
|
+
win->document->insertBefore(root, head, body);
|
|
194
|
+
}
|
|
195
|
+
title = win->document->createElement("title");
|
|
196
|
+
if (title == nullptr) return XT_UNDEFINED;
|
|
197
|
+
win->document->appendChild(head, title);
|
|
198
|
+
}
|
|
199
|
+
title->children.clear();
|
|
200
|
+
if (!text.empty()) title->addText(text);
|
|
201
|
+
mark_dirty(win);
|
|
202
|
+
return XT_UNDEFINED;
|
|
203
|
+
}
|
|
204
|
+
|
|
152
205
|
METHOD(doc_default_view_get) {
|
|
153
206
|
(void)env;
|
|
154
207
|
(void)argc;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include "dom_api_internal.h"
|
|
7
|
+
#include "listener_options.h"
|
|
7
8
|
|
|
8
9
|
#include <string>
|
|
9
10
|
#include <vector>
|
|
@@ -15,8 +16,14 @@ static xt_value make_dom_event(XtGuiWindow *win, const char *type, const xtgui::
|
|
|
15
16
|
xt_value event = xt_object_new_with_proto(event_proto());
|
|
16
17
|
xt_object_set(event, xt_string_from_cstr("type"), xt_string_from_cstr(type));
|
|
17
18
|
xt_object_set(event, xt_string_from_cstr("target"), xt_gui_node_handle(win, target));
|
|
19
|
+
xt_object_set(event, xt_string_from_cstr("currentTarget"), XT_NULL);
|
|
18
20
|
xt_object_set(event, xt_string_from_cstr("defaultPrevented"), XT_FALSE);
|
|
19
21
|
xt_object_set(event, xt_string_from_cstr("bubbles"), XT_TRUE);
|
|
22
|
+
xt_object_set(event, xt_string_from_cstr("cancelable"), XT_TRUE);
|
|
23
|
+
xt_object_set(event, xt_string_from_cstr("composed"), XT_TRUE);
|
|
24
|
+
xt_object_set(event, xt_string_from_cstr("isTrusted"), XT_TRUE);
|
|
25
|
+
xt_object_set(event, xt_string_from_cstr("eventPhase"), xt_number(0));
|
|
26
|
+
xt_object_set(event, xt_string_from_cstr("timeStamp"), xt_number((double)SDL_GetTicks()));
|
|
20
27
|
if (XT_IS_OBJECT(legacy)) {
|
|
21
28
|
static const char *const fields[] = {
|
|
22
29
|
"x", "y", "clientX", "clientY", "button", "clicks",
|
|
@@ -31,23 +38,43 @@ static xt_value make_dom_event(XtGuiWindow *win, const char *type, const xtgui::
|
|
|
31
38
|
return event;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
41
|
+
/** Set `field` to `value` unless `init` already supplied one. */
|
|
42
|
+
static void default_field(xt_value event, const char *field, xt_value value) {
|
|
43
|
+
if (XT_IS_UNDEFINED(xt_object_get_cstr(event, field))) {
|
|
44
|
+
xt_object_set(event, xt_string_from_cstr(field), value);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** A detached event object, as `new Event(type, init)` /
|
|
49
|
+
* `new CustomEvent(type, init)` (and the other `*Event` constructors) builds:
|
|
50
|
+
* no target yet, the standard fields, and every member of `init` — so
|
|
51
|
+
* `composed`, `detail` and the mouse/keyboard fields all survive construction
|
|
52
|
+
* instead of being silently dropped. */
|
|
36
53
|
xt_value event_construct(xt_value type, xt_value init) {
|
|
37
54
|
std::string name = to_string_value(type);
|
|
38
55
|
xt_value event = xt_object_new_with_proto(event_proto());
|
|
56
|
+
if (XT_IS_OBJECT(init)) {
|
|
57
|
+
xt_value keys = xt_object_keys(init);
|
|
58
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(keys));
|
|
59
|
+
for (int32_t i = 0; i < count; i++) {
|
|
60
|
+
xt_value key = xt_array_get(keys, xt_number((double)i));
|
|
61
|
+
xt_object_set(event, key, xt_object_get(init, key));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
39
64
|
xt_object_set(event, xt_string_from_cstr("type"), xt_string_from_cstr(name.c_str()));
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
default_field(event, "target", XT_NULL);
|
|
66
|
+
default_field(event, "currentTarget", XT_NULL);
|
|
67
|
+
default_field(event, "defaultPrevented", XT_FALSE);
|
|
68
|
+
default_field(event, "bubbles", XT_FALSE);
|
|
69
|
+
default_field(event, "cancelable", XT_FALSE);
|
|
70
|
+
default_field(event, "composed", XT_FALSE);
|
|
71
|
+
default_field(event, "isTrusted", XT_FALSE);
|
|
72
|
+
default_field(event, "eventPhase", xt_number(0));
|
|
73
|
+
default_field(event, "detail", XT_NULL);
|
|
74
|
+
default_field(event, "timeStamp", xt_number((double)SDL_GetTicks()));
|
|
75
|
+
/* Marks an event object built here, so `dispatchEvent(ev)` can reuse it (an
|
|
76
|
+
* event keeps its identity through dispatch, as in the DOM). */
|
|
77
|
+
xt_object_set(event, xt_string_from_cstr("__xt_gui_event"), XT_TRUE);
|
|
51
78
|
return event;
|
|
52
79
|
}
|
|
53
80
|
|
|
@@ -64,18 +91,32 @@ void remove_listener(XtGuiWindow *win, xtgui::Node *node, const std::string &typ
|
|
|
64
91
|
}
|
|
65
92
|
}
|
|
66
93
|
|
|
94
|
+
/** Call one listener batch. `phase` mirrors the DOM constants (1 capture,
|
|
95
|
+
* 2 at target, 3 bubble) so `event.eventPhase` is observable, and a passive
|
|
96
|
+
* listener runs with `preventDefault()` disabled. */
|
|
67
97
|
static bool call_node_listeners(XtGuiWindow *win, xtgui::Node *node, const char *type,
|
|
68
|
-
xt_value event, bool capture) {
|
|
98
|
+
xt_value event, bool capture, int32_t phase) {
|
|
69
99
|
auto found = win->node_listeners.find(node);
|
|
70
100
|
if (found == win->node_listeners.end()) return false;
|
|
71
101
|
std::vector<XtGuiNodeListener> snapshot = found->second;
|
|
102
|
+
xt_value previousPassive = xt_object_get_cstr(event, "__xt_gui_passive");
|
|
72
103
|
for (const XtGuiNodeListener &listener : snapshot) {
|
|
73
104
|
if (listener.type != type || listener.capture != capture) continue;
|
|
105
|
+
/* An aborted `AbortSignal` removes the listener. Drop it here so it also
|
|
106
|
+
* stops consuming the slot for future dispatches. */
|
|
107
|
+
if (xt_gui_signal_aborted(listener.signal)) {
|
|
108
|
+
remove_listener(win, node, listener.type, listener.fn, listener.capture);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
74
111
|
if (listener.once) remove_listener(win, node, listener.type, listener.fn, listener.capture);
|
|
75
112
|
xt_value self = xt_gui_node_handle(win, node);
|
|
76
113
|
xt_object_set(event, xt_string_from_cstr("currentTarget"), self);
|
|
114
|
+
xt_object_set(event, xt_string_from_cstr("eventPhase"), xt_number((double)phase));
|
|
115
|
+
xt_object_set(event, xt_string_from_cstr("__xt_gui_passive"),
|
|
116
|
+
listener.passive ? XT_TRUE : XT_FALSE);
|
|
77
117
|
xt_value args[1] = {event};
|
|
78
118
|
xt_call_with_this(listener.fn, self, 1, args);
|
|
119
|
+
xt_object_set(event, xt_string_from_cstr("__xt_gui_passive"), previousPassive);
|
|
79
120
|
if (value_truthy(xt_object_get_cstr(event, "__stopImmediate"))) return true;
|
|
80
121
|
if (value_truthy(xt_object_get_cstr(event, "__stop"))) return true;
|
|
81
122
|
}
|
|
@@ -88,6 +129,9 @@ METHOD(event_prevent_default) {
|
|
|
88
129
|
(void)env;
|
|
89
130
|
(void)argc;
|
|
90
131
|
(void)argv;
|
|
132
|
+
/* A listener registered with `{ passive: true }` promised not to cancel: the
|
|
133
|
+
* browser ignores the call (and warns), so the engine ignores it too. */
|
|
134
|
+
if (value_truthy(xt_object_get_cstr(self, "__xt_gui_passive"))) return XT_UNDEFINED;
|
|
91
135
|
xt_object_set(self, xt_string_from_cstr("defaultPrevented"), XT_TRUE);
|
|
92
136
|
return XT_UNDEFINED;
|
|
93
137
|
}
|
|
@@ -109,6 +153,22 @@ METHOD(event_stop_immediate_propagation) {
|
|
|
109
153
|
return XT_UNDEFINED;
|
|
110
154
|
}
|
|
111
155
|
|
|
156
|
+
/** `event.composedPath()`: the propagation path recorded by the dispatcher,
|
|
157
|
+
* target first, as element handles. A detached event has an empty path. */
|
|
158
|
+
METHOD(event_composed_path) {
|
|
159
|
+
(void)env;
|
|
160
|
+
(void)argc;
|
|
161
|
+
(void)argv;
|
|
162
|
+
xt_value result = xt_array_new(0, nullptr);
|
|
163
|
+
xt_value path = xt_object_get_cstr(self, "__xt_gui_path");
|
|
164
|
+
if (!XT_IS_ARRAY(path)) return result;
|
|
165
|
+
int32_t count = (int32_t)xt_to_number(xt_array_length(path));
|
|
166
|
+
for (int32_t i = 0; i < count; i++) {
|
|
167
|
+
xt_array_push(result, xt_array_get(path, xt_number((double)i)));
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
|
|
112
172
|
#undef METHOD
|
|
113
173
|
|
|
114
174
|
} // namespace domapi
|
|
@@ -123,13 +183,29 @@ void xt_gui_emit_dom_event(XtGuiWindow *win, const char *type, const xtgui::Node
|
|
|
123
183
|
path.push_back(node);
|
|
124
184
|
}
|
|
125
185
|
xt_value event = make_dom_event(win, type, target, legacy);
|
|
186
|
+
/* `element.dispatchEvent(ev)` hands over a real event object: dispatch that
|
|
187
|
+
* same object (identity is observable), only rebinding its target/path. */
|
|
188
|
+
if (XT_IS_OBJECT(legacy) && value_truthy(xt_object_get_cstr(legacy, "__xt_gui_event"))) {
|
|
189
|
+
event = legacy;
|
|
190
|
+
xt_object_set(event, xt_string_from_cstr("target"), xt_gui_node_handle(win, target));
|
|
191
|
+
xt_object_set(event, xt_string_from_cstr("currentTarget"), XT_NULL);
|
|
192
|
+
xt_object_set(event, xt_string_from_cstr("eventPhase"), xt_number(0));
|
|
193
|
+
}
|
|
194
|
+
xt_value handlePath = xt_array_new(0, nullptr);
|
|
195
|
+
for (xtgui::Node *node : path) xt_array_push(handlePath, xt_gui_node_handle(win, node));
|
|
196
|
+
xt_object_set(event, xt_string_from_cstr("__xt_gui_path"), handlePath);
|
|
197
|
+
bool bubbles = value_truthy(xt_object_get_cstr(event, "bubbles"));
|
|
126
198
|
/* Capture phase: root -> target. */
|
|
127
199
|
for (auto it = path.rbegin(); it != path.rend(); ++it) {
|
|
128
|
-
|
|
200
|
+
int32_t phase = *it == target ? 2 : 1;
|
|
201
|
+
if (call_node_listeners(win, *it, type, event, true, phase)) return;
|
|
129
202
|
}
|
|
130
|
-
/* Bubble phase: target -> root
|
|
203
|
+
/* Bubble phase: target -> root; a non-bubbling event stops after the target
|
|
204
|
+
* (its non-capture listeners still run there). */
|
|
131
205
|
for (auto it = path.begin(); it != path.end(); ++it) {
|
|
132
|
-
if (
|
|
206
|
+
if (!bubbles && *it != target) break;
|
|
207
|
+
int32_t phase = *it == target ? 2 : 3;
|
|
208
|
+
if (call_node_listeners(win, *it, type, event, false, phase)) return;
|
|
133
209
|
}
|
|
134
210
|
}
|
|
135
211
|
|
|
@@ -27,6 +27,7 @@ extern xt_value g_document_proto;
|
|
|
27
27
|
extern xt_value g_event_proto;
|
|
28
28
|
extern xt_value g_class_list_proto;
|
|
29
29
|
extern xt_value g_style_proto;
|
|
30
|
+
extern xt_value g_computed_style_proto;
|
|
30
31
|
extern xt_value g_canvas_proto;
|
|
31
32
|
extern xt_value g_canvas_gradient_proto;
|
|
32
33
|
extern xt_value g_webgl_proto;
|
|
@@ -49,10 +50,16 @@ xt_value document_proto();
|
|
|
49
50
|
xt_value event_proto();
|
|
50
51
|
xt_value class_list_proto();
|
|
51
52
|
xt_value style_proto();
|
|
53
|
+
xt_value computed_style_proto();
|
|
52
54
|
xt_value canvas_proto();
|
|
53
55
|
xt_value canvas_gradient_proto();
|
|
54
56
|
xt_value webgl_proto();
|
|
55
57
|
xt_value webgl2_proto();
|
|
58
|
+
/** `AbortController` / `AbortSignal` prototypes and constructors. */
|
|
59
|
+
xt_value abort_controller_proto();
|
|
60
|
+
xt_value abort_signal_proto();
|
|
61
|
+
xt_value abort_controller_new();
|
|
62
|
+
xt_value abort_signal_new(bool aborted);
|
|
56
63
|
/** The prototype a WebGL object of `kind` inherits (`buffer`, `texture`, …),
|
|
57
64
|
* so `buffer instanceof WebGLBuffer` holds. */
|
|
58
65
|
xt_value webgl_object_proto(const std::string &kind);
|
|
@@ -60,6 +67,16 @@ xt_value webgl_object_proto(const std::string &kind);
|
|
|
60
67
|
/** Create/return the 2D context for a pending `<canvas>` handle (before load). */
|
|
61
68
|
xt_value xt_canvas_pending_context(xt_value self, XtGuiWindow *win);
|
|
62
69
|
|
|
70
|
+
/* -- Path2D --------------------------------------------------------------- */
|
|
71
|
+
|
|
72
|
+
/** True when `value` is a `Path2D` handle. */
|
|
73
|
+
bool path2d_is(xt_value value);
|
|
74
|
+
/** The user-space subpaths a `Path2D` handle holds. */
|
|
75
|
+
std::vector<xtgui::CanvasSubpath> path2d_subpaths(xt_value value);
|
|
76
|
+
/** `new Path2D()` / `new Path2D(other)`. */
|
|
77
|
+
xt_value path2d_create(xt_value source);
|
|
78
|
+
xt_value path2d_proto();
|
|
79
|
+
|
|
63
80
|
/* -- canvas helpers shared by `dom_api_canvas*.cpp` ----------------------- */
|
|
64
81
|
|
|
65
82
|
/** The engine canvas behind a 2D context handle, or null when stale/foreign. */
|
|
@@ -157,7 +174,9 @@ METHOD(doc_create_element_ns);
|
|
|
157
174
|
METHOD(doc_hidden_get);
|
|
158
175
|
METHOD(doc_visibility_state_get);
|
|
159
176
|
METHOD(doc_ready_state_get);
|
|
177
|
+
METHOD(doc_has_focus);
|
|
160
178
|
METHOD(doc_title_get);
|
|
179
|
+
METHOD(doc_title_set);
|
|
161
180
|
METHOD(doc_default_view_get);
|
|
162
181
|
METHOD(doc_create_text_node);
|
|
163
182
|
METHOD(doc_body_get);
|
|
@@ -166,6 +185,9 @@ METHOD(doc_document_element_get);
|
|
|
166
185
|
/* node getters */
|
|
167
186
|
METHOD(node_tag_name_get);
|
|
168
187
|
METHOD(node_node_type_get);
|
|
188
|
+
METHOD(node_namespace_uri_get);
|
|
189
|
+
METHOD(node_local_name_get);
|
|
190
|
+
METHOD(node_prefix_get);
|
|
169
191
|
METHOD(node_id_get);
|
|
170
192
|
METHOD(node_id_set);
|
|
171
193
|
METHOD(node_class_name_get);
|
|
@@ -196,6 +218,10 @@ METHOD(node_query_selector);
|
|
|
196
218
|
METHOD(node_query_selector_all);
|
|
197
219
|
METHOD(node_matches);
|
|
198
220
|
METHOD(node_append_child);
|
|
221
|
+
METHOD(node_append);
|
|
222
|
+
METHOD(node_prepend);
|
|
223
|
+
METHOD(node_replace_children);
|
|
224
|
+
METHOD(node_insert_adjacent_html);
|
|
199
225
|
METHOD(node_insert_before);
|
|
200
226
|
METHOD(node_remove_child);
|
|
201
227
|
METHOD(node_replace_child);
|
|
@@ -219,6 +245,7 @@ xt_value event_construct(xt_value type, xt_value init);
|
|
|
219
245
|
METHOD(event_prevent_default);
|
|
220
246
|
METHOD(event_stop_propagation);
|
|
221
247
|
METHOD(event_stop_immediate_propagation);
|
|
248
|
+
METHOD(event_composed_path);
|
|
222
249
|
|
|
223
250
|
/* media (`<video>`/`<audio>`) */
|
|
224
251
|
METHOD(node_play);
|
|
@@ -285,11 +312,21 @@ METHOD(canvas_stroke);
|
|
|
285
312
|
METHOD(canvas_clear_rect);
|
|
286
313
|
METHOD(canvas_fill_rect);
|
|
287
314
|
METHOD(canvas_stroke_rect);
|
|
288
|
-
METHOD(canvas_measure_text);
|
|
289
|
-
METHOD(canvas_fill_text);
|
|
315
|
+
METHOD(canvas_measure_text);METHOD(canvas_fill_text);
|
|
290
316
|
METHOD(canvas_stroke_text);
|
|
291
317
|
METHOD(canvas_draw_image);
|
|
292
318
|
METHOD(canvas_get_image_data);
|
|
319
|
+
METHOD(canvas_get_context_attributes);
|
|
320
|
+
METHOD(canvas_put_image_data);
|
|
321
|
+
METHOD(canvas_create_image_data);
|
|
322
|
+
METHOD(canvas_create_pattern);
|
|
323
|
+
METHOD(canvas_to_data_url);
|
|
324
|
+
METHOD(canvas_to_blob);
|
|
325
|
+
/** The cached `CanvasPattern` handle for `id` on `canvas`. */
|
|
326
|
+
xt_value canvas_pattern_handle(xt_value context, const xtgui::Canvas2D *canvas, uint64_t id);
|
|
327
|
+
/** The pattern id stored on a handle, or 0 when it is not a pattern for
|
|
328
|
+
* `canvas` (stale or created by another canvas). */
|
|
329
|
+
uint64_t canvas_pattern_id(xt_value handle, const xtgui::Canvas2D *canvas);
|
|
293
330
|
METHOD(canvas_ellipse);
|
|
294
331
|
METHOD(canvas_round_rect);
|
|
295
332
|
METHOD(canvas_line_cap_get);
|
|
@@ -316,7 +353,23 @@ METHOD(class_list_contains);
|
|
|
316
353
|
/* style methods */
|
|
317
354
|
METHOD(style_set_property);
|
|
318
355
|
METHOD(style_get_property_value);
|
|
356
|
+
METHOD(style_get_property_priority);
|
|
319
357
|
METHOD(style_remove_property);
|
|
358
|
+
METHOD(style_css_text_get);
|
|
359
|
+
METHOD(style_css_text_set);
|
|
360
|
+
METHOD(style_length_get);
|
|
361
|
+
METHOD(style_item);
|
|
362
|
+
/** Install the dynamic named-property hooks on a `style` handle
|
|
363
|
+
* (`style.width = "50%"`). */
|
|
364
|
+
void install_style_hooks(xt_value style);
|
|
365
|
+
|
|
366
|
+
/* `dataset` (a DOMStringMap over `data-*` attributes) and computed style */
|
|
367
|
+
METHOD(node_dataset_get);
|
|
368
|
+
METHOD(computed_style_get_property_value);
|
|
369
|
+
METHOD(computed_style_get_property_priority);
|
|
370
|
+
/** Build the computed-style handle for an element (and optional pseudo). */
|
|
371
|
+
xt_value computed_style_handle(xt_value element, xt_value pseudo);
|
|
372
|
+
xt_value computed_style_proto();
|
|
320
373
|
|
|
321
374
|
/* WebGL context: state and queries */
|
|
322
375
|
METHOD(webgl_canvas_get);
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* xbintsc GUI engine — the parent/child convenience mutators.
|
|
3
|
+
*
|
|
4
|
+
* `append`, `prepend`, `replaceChildren` and `insertAdjacentHTML` are the APIs
|
|
5
|
+
* modern pages actually use to build DOM (they accept nodes and plain strings
|
|
6
|
+
* in one call); the engine previously only had `appendChild`/`innerHTML`.
|
|
7
|
+
* Strings become text nodes, as in the DOM.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
#include "dom_api_internal.h"
|
|
11
|
+
|
|
12
|
+
#include <memory>
|
|
13
|
+
#include <string>
|
|
14
|
+
#include <vector>
|
|
15
|
+
|
|
16
|
+
namespace domapi {
|
|
17
|
+
|
|
18
|
+
namespace {
|
|
19
|
+
|
|
20
|
+
/** A node handle, or a new text node for a string argument. */
|
|
21
|
+
xtgui::Node *argument_node(XtGuiWindow *win, xt_value value) {
|
|
22
|
+
xtgui::Node *node = resolve_node(value, nullptr);
|
|
23
|
+
if (node != nullptr) return node;
|
|
24
|
+
if (XT_IS_UNDEFINED(value) || value == XT_NULL) return nullptr;
|
|
25
|
+
return win->document->createTextNode(to_string_value(value));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
void insert_argument(XtGuiWindow *win, xtgui::Node *parent, xt_value value, xtgui::Node *before) {
|
|
29
|
+
xtgui::Node *node = argument_node(win, value);
|
|
30
|
+
if (node == nullptr) return;
|
|
31
|
+
if (before != nullptr) {
|
|
32
|
+
win->document->insertBefore(parent, node, before);
|
|
33
|
+
} else {
|
|
34
|
+
win->document->appendChild(parent, node);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Copy a parsed fragment's children into `dest`, inserting before `before`
|
|
39
|
+
* (nullptr appends). The parser's nodes belong to a standalone tree, so they are
|
|
40
|
+
* recreated through the document rather than adopted. */
|
|
41
|
+
void import_children(XtGuiWindow *win, xtgui::Node *dest, const xtgui::Node *parsed,
|
|
42
|
+
xtgui::Node *before) {
|
|
43
|
+
for (const std::unique_ptr<xtgui::Node> &child : parsed->children) {
|
|
44
|
+
xtgui::Node *copy = nullptr;
|
|
45
|
+
if (child->isText()) {
|
|
46
|
+
copy = win->document->createTextNode(child->text);
|
|
47
|
+
} else {
|
|
48
|
+
copy = win->document->createElement(child->tag);
|
|
49
|
+
if (copy == nullptr) continue;
|
|
50
|
+
for (const xtgui::Attr &attr : child->attrs) copy->attrs.push_back(attr);
|
|
51
|
+
import_children(win, copy, child.get(), nullptr);
|
|
52
|
+
}
|
|
53
|
+
if (copy == nullptr) continue;
|
|
54
|
+
if (before != nullptr) {
|
|
55
|
+
win->document->insertBefore(dest, copy, before);
|
|
56
|
+
} else {
|
|
57
|
+
win->document->appendChild(dest, copy);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
xtgui::Node *next_sibling(xtgui::Node *parent, xtgui::Node *node) {
|
|
63
|
+
if (parent == nullptr) return nullptr;
|
|
64
|
+
for (size_t i = 0; i + 1 < parent->children.size(); i++) {
|
|
65
|
+
if (parent->children[i].get() == node) return parent->children[i + 1].get();
|
|
66
|
+
}
|
|
67
|
+
return nullptr;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
xtgui::Node *first_child(xtgui::Node *node) {
|
|
71
|
+
return node->children.empty() ? nullptr : node->children.front().get();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
} // namespace
|
|
75
|
+
|
|
76
|
+
#define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
|
|
77
|
+
|
|
78
|
+
METHOD(node_append) {
|
|
79
|
+
(void)env;
|
|
80
|
+
XtGuiWindow *win = nullptr;
|
|
81
|
+
xtgui::Node *node = resolve_node(self, &win);
|
|
82
|
+
if (node == nullptr || win == nullptr) return XT_UNDEFINED;
|
|
83
|
+
for (int32_t i = 0; i < argc; i++) insert_argument(win, node, xt_arg(argc, argv, i), nullptr);
|
|
84
|
+
mark_dirty(win);
|
|
85
|
+
return XT_UNDEFINED;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
METHOD(node_prepend) {
|
|
89
|
+
(void)env;
|
|
90
|
+
XtGuiWindow *win = nullptr;
|
|
91
|
+
xtgui::Node *node = resolve_node(self, &win);
|
|
92
|
+
if (node == nullptr || win == nullptr) return XT_UNDEFINED;
|
|
93
|
+
/* The reference stays the original first child; inserting each argument before
|
|
94
|
+
* it keeps the argument order. */
|
|
95
|
+
xtgui::Node *reference = first_child(node);
|
|
96
|
+
for (int32_t i = 0; i < argc; i++) {
|
|
97
|
+
insert_argument(win, node, xt_arg(argc, argv, i), reference);
|
|
98
|
+
}
|
|
99
|
+
mark_dirty(win);
|
|
100
|
+
return XT_UNDEFINED;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
METHOD(node_replace_children) {
|
|
104
|
+
(void)env;
|
|
105
|
+
XtGuiWindow *win = nullptr;
|
|
106
|
+
xtgui::Node *node = resolve_node(self, &win);
|
|
107
|
+
if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
|
|
108
|
+
/* Detach node arguments first: one of them may be a current child that the
|
|
109
|
+
* replacement keeps. */
|
|
110
|
+
for (int32_t i = 0; i < argc; i++) {
|
|
111
|
+
xtgui::Node *child = resolve_node(xt_arg(argc, argv, i), nullptr);
|
|
112
|
+
if (child == nullptr) continue;
|
|
113
|
+
if (child->parent == node) win->document->removeChild(node, child);
|
|
114
|
+
}
|
|
115
|
+
std::vector<xtgui::Node *> existing;
|
|
116
|
+
for (const std::unique_ptr<xtgui::Node> &child : node->children) {
|
|
117
|
+
existing.push_back(child.get());
|
|
118
|
+
}
|
|
119
|
+
for (xtgui::Node *child : existing) {
|
|
120
|
+
win->document->removeChild(node, child);
|
|
121
|
+
discard_subtree(win, child);
|
|
122
|
+
}
|
|
123
|
+
for (int32_t i = 0; i < argc; i++) insert_argument(win, node, xt_arg(argc, argv, i), nullptr);
|
|
124
|
+
mark_dirty(win);
|
|
125
|
+
return XT_UNDEFINED;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** `insertAdjacentHTML(position, html)`; `position` is beforebegin,
|
|
129
|
+
* afterbegin, beforeend or afterend. Scripts in the fragment do not run. */
|
|
130
|
+
METHOD(node_insert_adjacent_html) {
|
|
131
|
+
(void)env;
|
|
132
|
+
XtGuiWindow *win = nullptr;
|
|
133
|
+
xtgui::Node *node = resolve_node(self, &win);
|
|
134
|
+
if (node == nullptr || win == nullptr || win->document == nullptr || argc < 2) {
|
|
135
|
+
return XT_UNDEFINED;
|
|
136
|
+
}
|
|
137
|
+
std::string position = to_string_value(xt_arg(argc, argv, 0));
|
|
138
|
+
std::string html = to_string_value(xt_arg(argc, argv, 1));
|
|
139
|
+
std::unique_ptr<xtgui::Node> parsed = xtgui::xt_html_parse(html);
|
|
140
|
+
if (!parsed) return XT_UNDEFINED;
|
|
141
|
+
if (position == "beforebegin" || position == "afterend") {
|
|
142
|
+
xtgui::Node *parent = node->parent;
|
|
143
|
+
if (parent == nullptr || parent->isDocument()) return XT_UNDEFINED;
|
|
144
|
+
xtgui::Node *reference = position == "beforebegin" ? node : next_sibling(parent, node);
|
|
145
|
+
import_children(win, parent, parsed.get(), reference);
|
|
146
|
+
} else if (position == "afterbegin") {
|
|
147
|
+
import_children(win, node, parsed.get(), first_child(node));
|
|
148
|
+
} else if (position == "beforeend") {
|
|
149
|
+
import_children(win, node, parsed.get(), nullptr);
|
|
150
|
+
} else {
|
|
151
|
+
return XT_UNDEFINED;
|
|
152
|
+
}
|
|
153
|
+
mark_dirty(win);
|
|
154
|
+
return XT_UNDEFINED;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#undef METHOD
|
|
158
|
+
|
|
159
|
+
} // namespace domapi
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
#include "dom_api_internal.h"
|
|
7
|
+
#include "listener_options.h"
|
|
7
8
|
|
|
8
9
|
#include <algorithm>
|
|
9
10
|
#include <cmath>
|
|
@@ -217,13 +218,19 @@ METHOD(node_get_bounding_rect) {
|
|
|
217
218
|
if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
|
|
218
219
|
const xtgui::LayoutBox *box = win->document->boxOf(node);
|
|
219
220
|
if (box == nullptr) return XT_UNDEFINED;
|
|
220
|
-
/* Like the DOM: the rectangle of the *transformed* border box.
|
|
221
|
+
/* Like the DOM: the rectangle of the *transformed* border box. The legacy
|
|
222
|
+
* `top`/`right`/`bottom`/`left` members are the same rectangle in the other
|
|
223
|
+
* spelling; libraries read both. */
|
|
221
224
|
xtgui::LayoutRect rect = xtgui::xt_layout_transformed_bounds(box);
|
|
222
225
|
xt_value object = xt_object_new();
|
|
223
226
|
xt_object_set(object, xt_string_from_cstr("x"), xt_number(rect.x));
|
|
224
227
|
xt_object_set(object, xt_string_from_cstr("y"), xt_number(rect.y));
|
|
225
228
|
xt_object_set(object, xt_string_from_cstr("width"), xt_number(rect.width));
|
|
226
229
|
xt_object_set(object, xt_string_from_cstr("height"), xt_number(rect.height));
|
|
230
|
+
xt_object_set(object, xt_string_from_cstr("top"), xt_number(rect.y));
|
|
231
|
+
xt_object_set(object, xt_string_from_cstr("right"), xt_number(rect.x + rect.width));
|
|
232
|
+
xt_object_set(object, xt_string_from_cstr("bottom"), xt_number(rect.y + rect.height));
|
|
233
|
+
xt_object_set(object, xt_string_from_cstr("left"), xt_number(rect.x));
|
|
227
234
|
return object;
|
|
228
235
|
}
|
|
229
236
|
|
|
@@ -252,12 +259,30 @@ METHOD(node_contains) {
|
|
|
252
259
|
|
|
253
260
|
METHOD(node_focus) {
|
|
254
261
|
(void)env;
|
|
255
|
-
(void)argc;
|
|
256
|
-
(void)argv;
|
|
257
262
|
XtGuiWindow *win = nullptr;
|
|
258
263
|
xtgui::Node *node = resolve_node(self, &win);
|
|
259
264
|
if (node == nullptr || win == nullptr || win->document == nullptr) return XT_UNDEFINED;
|
|
265
|
+
/* `{ preventScroll: true }` keeps the current page offset; otherwise the
|
|
266
|
+
* focused box is scrolled into view, like the browser default. */
|
|
267
|
+
bool prevent_scroll = false;
|
|
268
|
+
xt_value options = xt_arg(argc, argv, 0);
|
|
269
|
+
if (XT_IS_OBJECT(options)) {
|
|
270
|
+
prevent_scroll = value_truthy(xt_object_get_cstr(options, "preventScroll"));
|
|
271
|
+
}
|
|
260
272
|
if (win->document->setFocus(node)) win->geometry.dirty = 1;
|
|
273
|
+
const xtgui::LayoutBox *box = win->document->boxOf(node);
|
|
274
|
+
if (!prevent_scroll && box != nullptr) {
|
|
275
|
+
float viewport_width = 0;
|
|
276
|
+
float viewport_height = 0;
|
|
277
|
+
xt_gui_window_viewport(win, &viewport_width, &viewport_height);
|
|
278
|
+
float x = win->scroll_x;
|
|
279
|
+
float y = win->scroll_y;
|
|
280
|
+
if (box->x < x) x = box->x;
|
|
281
|
+
else if (box->x + box->width > x + viewport_width) x = box->x + box->width - viewport_width;
|
|
282
|
+
if (box->y < y) y = box->y;
|
|
283
|
+
else if (box->y + box->height > y + viewport_height) y = box->y + box->height - viewport_height;
|
|
284
|
+
xt_gui_scroll_to(win, x, y);
|
|
285
|
+
}
|
|
261
286
|
return XT_UNDEFINED;
|
|
262
287
|
}
|
|
263
288
|
|
|
@@ -290,20 +315,18 @@ METHOD(node_add_event_listener) {
|
|
|
290
315
|
if (node == nullptr || win == nullptr || argc < 2) return self;
|
|
291
316
|
xt_value fn = xt_arg(argc, argv, 1);
|
|
292
317
|
if (!XT_IS_FUNCTION(fn)) return self;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
once = value_truthy(xt_object_get_cstr(options, "once"));
|
|
299
|
-
} else if (XT_IS_BOOL(options)) {
|
|
300
|
-
capture = value_truthy(options);
|
|
301
|
-
}
|
|
318
|
+
XtGuiListenerOptions options = xt_gui_listener_options(xt_arg(argc, argv, 2));
|
|
319
|
+
/* `{ signal: controller.signal }` removes the listener: an already-aborted
|
|
320
|
+
* signal means it was never installed, and a later abort is honoured at
|
|
321
|
+
* dispatch time (see `call_node_listeners`). */
|
|
322
|
+
if (xt_gui_signal_aborted(options.signal)) return self;
|
|
302
323
|
XtGuiNodeListener listener;
|
|
303
324
|
listener.type = to_string_value(xt_arg(argc, argv, 0));
|
|
304
325
|
listener.fn = fn;
|
|
305
|
-
listener.once = once;
|
|
306
|
-
listener.capture = capture;
|
|
326
|
+
listener.once = options.once;
|
|
327
|
+
listener.capture = options.capture;
|
|
328
|
+
listener.passive = options.passive;
|
|
329
|
+
listener.signal = options.signal;
|
|
307
330
|
win->node_listeners[node].push_back(listener);
|
|
308
331
|
return self;
|
|
309
332
|
}
|
|
@@ -313,11 +336,9 @@ METHOD(node_remove_event_listener) {
|
|
|
313
336
|
XtGuiWindow *win = nullptr;
|
|
314
337
|
xtgui::Node *node = resolve_node(self, &win);
|
|
315
338
|
if (node == nullptr || win == nullptr || argc < 2) return self;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
else if (XT_IS_BOOL(options)) capture = value_truthy(options);
|
|
320
|
-
remove_listener(win, node, to_string_value(xt_arg(argc, argv, 0)), xt_arg(argc, argv, 1), capture);
|
|
339
|
+
XtGuiListenerOptions options = xt_gui_listener_options(xt_arg(argc, argv, 2));
|
|
340
|
+
remove_listener(win, node, to_string_value(xt_arg(argc, argv, 0)), xt_arg(argc, argv, 1),
|
|
341
|
+
options.capture);
|
|
321
342
|
return self;
|
|
322
343
|
}
|
|
323
344
|
|