@salve-software/react-native-nitro-jsdom 1.0.1 → 2.0.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/android/CMakeLists.txt +18 -0
- package/cpp/HybridHtmlSandbox.cpp +20 -3
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +279 -67
- package/cpp/lexbor/LexborDocument.hpp +43 -10
- package/cpp/quickjs/DOMBindings.cpp +37 -1
- package/cpp/quickjs/DOMBindingsInternal.cpp +101 -2
- package/cpp/quickjs/DOMBindingsInternal.hpp +56 -1
- package/cpp/quickjs/QuickJSRuntime.cpp +32 -4
- package/cpp/quickjs/QuickJSRuntime.hpp +50 -1
- package/cpp/quickjs/bindings/AbortBindings.cpp +128 -0
- package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +164 -0
- package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +290 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +25 -0
- package/cpp/quickjs/bindings/ClassListBindings.cpp +3 -2
- package/cpp/quickjs/bindings/CookieBindings.cpp +118 -0
- package/cpp/quickjs/bindings/CookieBindings.hpp +23 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +341 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.hpp +50 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.cpp +111 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.hpp +20 -0
- package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
- package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +239 -4
- package/cpp/quickjs/bindings/ElementBindings.cpp +556 -44
- package/cpp/quickjs/bindings/EventBindings.cpp +414 -8
- package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
- package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
- package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
- package/cpp/quickjs/bindings/FetchBindings.cpp +45 -6
- package/cpp/quickjs/bindings/FormBindings.cpp +365 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +20 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +228 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.hpp +16 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.cpp +171 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.hpp +27 -0
- package/cpp/quickjs/bindings/SlotBindings.cpp +161 -0
- package/cpp/quickjs/bindings/SlotBindings.hpp +44 -0
- package/cpp/quickjs/bindings/TemplateBindings.cpp +42 -0
- package/cpp/quickjs/bindings/TemplateBindings.hpp +29 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.cpp +117 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.hpp +14 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +120 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +295 -0
- package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +552 -0
- package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
- package/cpp/quickjs/bindings/XmlSerializerBindings.cpp +44 -0
- package/cpp/quickjs/bindings/XmlSerializerBindings.hpp +24 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
- package/package.json +17 -2
- package/src/classes/JSDOM/JSDOM.class.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
#include "ClassListBindings.hpp"
|
|
3
3
|
#include "DatasetBindings.hpp"
|
|
4
4
|
#include "StyleBindings.hpp"
|
|
5
|
+
#include "LiveCollectionBindings.hpp"
|
|
6
|
+
#include "DOMExceptionBindings.hpp"
|
|
5
7
|
#include "../DOMBindingsInternal.hpp"
|
|
6
8
|
#include "../QuickJSRuntime.hpp"
|
|
7
9
|
#include "../MutationObservers.hpp"
|
|
@@ -9,6 +11,9 @@
|
|
|
9
11
|
#include <lexbor/html/html.h>
|
|
10
12
|
#include <lexbor/dom/dom.h>
|
|
11
13
|
#include <lexbor/dom/interfaces/character_data.h>
|
|
14
|
+
#include <lexbor/dom/interfaces/attr.h>
|
|
15
|
+
#include <lexbor/ns/const.h>
|
|
16
|
+
#include <lexbor/ns/ns.h>
|
|
12
17
|
#include <cctype>
|
|
13
18
|
#include <cstring>
|
|
14
19
|
#include <optional>
|
|
@@ -27,10 +32,18 @@ JSValue js_el_get_tagName(JSContext* ctx, JSValue this_val) {
|
|
|
27
32
|
auto* el = unwrap_element(ctx, this_val);
|
|
28
33
|
if (!el) return JS_NewString(ctx, "");
|
|
29
34
|
size_t len = 0;
|
|
30
|
-
|
|
35
|
+
// qualified_name (prefix:localName) when the element has a namespace
|
|
36
|
+
// prefix (createElementNS('...', 'prefix:name')), falling back to the
|
|
37
|
+
// plain local name otherwise.
|
|
38
|
+
const lxb_char_t* name = lxb_dom_element_qualified_name(el, &len);
|
|
31
39
|
if (!name || len == 0) return JS_NewString(ctx, "");
|
|
32
40
|
std::string r(reinterpret_cast<const char*>(name), len);
|
|
33
|
-
|
|
41
|
+
// Only HTML-namespace elements are uppercased (per spec); SVG/MathML/custom
|
|
42
|
+
// namespace elements keep their (lexbor-lowercased) local name as-is.
|
|
43
|
+
auto* node = reinterpret_cast<lxb_dom_node_t*>(el);
|
|
44
|
+
if (node->ns == LXB_NS_HTML) {
|
|
45
|
+
for (auto& c : r) c = (char)std::toupper((unsigned char)c);
|
|
46
|
+
}
|
|
34
47
|
return JS_NewStringLen(ctx, r.c_str(), r.size());
|
|
35
48
|
}
|
|
36
49
|
|
|
@@ -100,6 +113,196 @@ JSValue js_el_set_className(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
100
113
|
return JS_UNDEFINED;
|
|
101
114
|
}
|
|
102
115
|
|
|
116
|
+
// ── Form element properties (value / checked) ────────────────────────────────
|
|
117
|
+
// Simplified attribute-reflection model, matching this file's existing id/
|
|
118
|
+
// className getters: no separate "dirty value" IDL flag like real browsers,
|
|
119
|
+
// just direct reflection so scripts that set then read these back (the
|
|
120
|
+
// common embedded-widget pattern) see consistent results.
|
|
121
|
+
|
|
122
|
+
JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val);
|
|
123
|
+
|
|
124
|
+
bool element_tag_is(lxb_dom_element_t* el, const char* tag) {
|
|
125
|
+
size_t len = 0;
|
|
126
|
+
const lxb_char_t* name = lxb_dom_element_local_name(el, &len);
|
|
127
|
+
if (!name || len != strlen(tag)) return false;
|
|
128
|
+
for (size_t i = 0; i < len; i++) {
|
|
129
|
+
if (std::tolower(name[i]) != std::tolower((unsigned char)tag[i])) return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// HTMLOptionElement's value: the "value" attribute if present, else the
|
|
135
|
+
// option's text content.
|
|
136
|
+
std::string option_effective_value(lxb_dom_element_t* option) {
|
|
137
|
+
size_t len = 0;
|
|
138
|
+
const lxb_char_t* val = lxb_dom_element_get_attribute(option,
|
|
139
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5, &len);
|
|
140
|
+
if (val) return std::string(reinterpret_cast<const char*>(val), len);
|
|
141
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(option);
|
|
142
|
+
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
143
|
+
if (!text) return "";
|
|
144
|
+
std::string result(reinterpret_cast<char*>(text), len);
|
|
145
|
+
lxb_dom_document_destroy_text(node->owner_document, text);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
JSValue js_select_get_value(JSContext* ctx, lxb_dom_element_t* select_el) {
|
|
150
|
+
auto options = get_doc(ctx)->querySelectorAllFromEl(select_el, "option");
|
|
151
|
+
void* chosen = nullptr;
|
|
152
|
+
for (void* opt : options) {
|
|
153
|
+
if (lxb_dom_element_has_attribute(static_cast<lxb_dom_element_t*>(opt),
|
|
154
|
+
reinterpret_cast<const lxb_char_t*>("selected"), 8)) { chosen = opt; break; }
|
|
155
|
+
}
|
|
156
|
+
if (!chosen && !options.empty()) chosen = options.front();
|
|
157
|
+
if (!chosen) return JS_NewString(ctx, "");
|
|
158
|
+
return JS_NewString(ctx, option_effective_value(static_cast<lxb_dom_element_t*>(chosen)).c_str());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
void js_select_set_value(JSContext* ctx, lxb_dom_element_t* select_el, const std::string& value) {
|
|
162
|
+
auto options = get_doc(ctx)->querySelectorAllFromEl(select_el, "option");
|
|
163
|
+
auto* rctx = get_ctx(ctx);
|
|
164
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
165
|
+
auto* attr_name = reinterpret_cast<const lxb_char_t*>("selected");
|
|
166
|
+
|
|
167
|
+
for (void* opt : options) {
|
|
168
|
+
auto* opt_el = static_cast<lxb_dom_element_t*>(opt);
|
|
169
|
+
bool should_select = option_effective_value(opt_el) == value;
|
|
170
|
+
bool has_selected = lxb_dom_element_has_attribute(opt_el, attr_name, 8);
|
|
171
|
+
|
|
172
|
+
std::optional<std::string> old_val;
|
|
173
|
+
if (has_obs && has_selected && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
174
|
+
size_t len = 0;
|
|
175
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(opt_el, attr_name, 8, &len);
|
|
176
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
bool changed = false;
|
|
180
|
+
if (should_select && !has_selected) {
|
|
181
|
+
lxb_dom_element_set_attribute(opt_el, attr_name, 8, reinterpret_cast<const lxb_char_t*>(""), 0);
|
|
182
|
+
changed = true;
|
|
183
|
+
} else if (!should_select && has_selected) {
|
|
184
|
+
lxb_dom_element_remove_attribute(opt_el, attr_name, 8);
|
|
185
|
+
changed = true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (changed && has_obs) {
|
|
189
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(opt_el), "selected", old_val);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
JSValue js_el_get_value(JSContext* ctx, JSValue this_val) {
|
|
195
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
196
|
+
if (!el) return JS_NewString(ctx, "");
|
|
197
|
+
if (element_tag_is(el, "select")) return js_select_get_value(ctx, el);
|
|
198
|
+
if (element_tag_is(el, "textarea")) return js_el_get_textContent(ctx, this_val);
|
|
199
|
+
size_t len = 0;
|
|
200
|
+
const lxb_char_t* val = lxb_dom_element_get_attribute(el,
|
|
201
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5, &len);
|
|
202
|
+
if (val) return JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len);
|
|
203
|
+
|
|
204
|
+
// A checkbox/radio with no "value" attribute defaults its value to "on".
|
|
205
|
+
if (element_tag_is(el, "input")) {
|
|
206
|
+
size_t type_len = 0;
|
|
207
|
+
const lxb_char_t* type_val = lxb_dom_element_get_attribute(el,
|
|
208
|
+
reinterpret_cast<const lxb_char_t*>("type"), 4, &type_len);
|
|
209
|
+
std::string type_str = type_val ? std::string(reinterpret_cast<const char*>(type_val), type_len) : "";
|
|
210
|
+
for (auto& c : type_str) c = (char)std::tolower((unsigned char)c);
|
|
211
|
+
if (type_str == "checkbox" || type_str == "radio") return JS_NewString(ctx, "on");
|
|
212
|
+
}
|
|
213
|
+
return JS_NewString(ctx, "");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
JSValue js_el_set_value(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
217
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
218
|
+
if (!el) return JS_UNDEFINED;
|
|
219
|
+
const char* str = JS_ToCString(ctx, val);
|
|
220
|
+
if (!str) return JS_UNDEFINED;
|
|
221
|
+
|
|
222
|
+
if (element_tag_is(el, "select")) {
|
|
223
|
+
js_select_set_value(ctx, el, str);
|
|
224
|
+
} else if (element_tag_is(el, "textarea")) {
|
|
225
|
+
auto* rctx = get_ctx(ctx);
|
|
226
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
227
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
228
|
+
|
|
229
|
+
std::vector<void*> old_children;
|
|
230
|
+
lxb_dom_node_t* child = node->first_child;
|
|
231
|
+
while (child) { old_children.push_back(child); child = child->next; }
|
|
232
|
+
if (!old_children.empty()) {
|
|
233
|
+
invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
234
|
+
if (has_obs) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
doc_for_node(ctx, el)->setTextContentOnEl(el, str);
|
|
238
|
+
|
|
239
|
+
if (has_obs) {
|
|
240
|
+
std::vector<void*> new_children;
|
|
241
|
+
child = node->first_child;
|
|
242
|
+
while (child) { new_children.push_back(child); child = child->next; }
|
|
243
|
+
rctx->mutation_observers->notifyChildList(ctx, node, new_children, {}, nullptr, nullptr);
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
auto* rctx = get_ctx(ctx);
|
|
247
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
248
|
+
std::optional<std::string> old_val;
|
|
249
|
+
if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
250
|
+
size_t len = 0;
|
|
251
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el,
|
|
252
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5, &len);
|
|
253
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
254
|
+
}
|
|
255
|
+
lxb_dom_element_set_attribute(el,
|
|
256
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5,
|
|
257
|
+
reinterpret_cast<const lxb_char_t*>(str), strlen(str));
|
|
258
|
+
if (has_obs) {
|
|
259
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), "value", old_val);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
JS_FreeCString(ctx, str);
|
|
264
|
+
return JS_UNDEFINED;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
JSValue js_el_get_checked(JSContext* ctx, JSValue this_val) {
|
|
268
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
269
|
+
if (!el) return JS_FALSE;
|
|
270
|
+
bool has = lxb_dom_element_has_attribute(el,
|
|
271
|
+
reinterpret_cast<const lxb_char_t*>("checked"), 7);
|
|
272
|
+
return JS_NewBool(ctx, has);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
JSValue js_el_set_checked(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
276
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
277
|
+
if (!el) return JS_UNDEFINED;
|
|
278
|
+
bool checked = JS_ToBool(ctx, val) > 0;
|
|
279
|
+
auto* attr_name = reinterpret_cast<const lxb_char_t*>("checked");
|
|
280
|
+
bool has = lxb_dom_element_has_attribute(el, attr_name, 7);
|
|
281
|
+
|
|
282
|
+
auto* rctx = get_ctx(ctx);
|
|
283
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
284
|
+
std::optional<std::string> old_val;
|
|
285
|
+
if (has_obs && has && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
286
|
+
size_t len = 0;
|
|
287
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el, attr_name, 7, &len);
|
|
288
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (checked && !has) {
|
|
292
|
+
lxb_dom_element_set_attribute(el, attr_name, 7, reinterpret_cast<const lxb_char_t*>(""), 0);
|
|
293
|
+
if (has_obs) {
|
|
294
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), "checked", std::nullopt);
|
|
295
|
+
}
|
|
296
|
+
} else if (!checked && has) {
|
|
297
|
+
lxb_dom_element_remove_attribute(el, attr_name, 7);
|
|
298
|
+
if (has_obs) {
|
|
299
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), "checked", old_val);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return JS_UNDEFINED;
|
|
304
|
+
}
|
|
305
|
+
|
|
103
306
|
JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val) {
|
|
104
307
|
lxb_dom_node_t* node = unwrap_node(ctx, this_val);
|
|
105
308
|
if (!node) return JS_NewString(ctx, "");
|
|
@@ -144,11 +347,11 @@ JSValue js_el_set_textContent(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
144
347
|
lxb_dom_node_t* child = node->first_child;
|
|
145
348
|
while (child) { old_children.push_back(child); child = child->next; }
|
|
146
349
|
if (!old_children.empty()) {
|
|
147
|
-
|
|
350
|
+
invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
148
351
|
if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
149
352
|
}
|
|
150
353
|
}
|
|
151
|
-
|
|
354
|
+
doc_for_node(ctx, node)->setTextContentOnEl(el, str);
|
|
152
355
|
if (has_observers) {
|
|
153
356
|
std::vector<void*> new_children;
|
|
154
357
|
lxb_dom_node_t* child = node->first_child;
|
|
@@ -189,11 +392,11 @@ JSValue js_el_set_innerHTML(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
189
392
|
}
|
|
190
393
|
|
|
191
394
|
if (!old_children.empty()) {
|
|
192
|
-
|
|
395
|
+
invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
193
396
|
if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
194
397
|
}
|
|
195
398
|
|
|
196
|
-
|
|
399
|
+
doc_for_node(ctx, el)->setInnerHTMLOnEl(el, html);
|
|
197
400
|
JS_FreeCString(ctx, html);
|
|
198
401
|
|
|
199
402
|
if (has_observers) {
|
|
@@ -223,16 +426,8 @@ JSValue js_el_get_parentElement(JSContext* ctx, JSValue this_val) {
|
|
|
223
426
|
|
|
224
427
|
JSValue js_el_get_children(JSContext* ctx, JSValue this_val) {
|
|
225
428
|
auto* el = unwrap_element(ctx, this_val);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
uint32_t idx = 0;
|
|
229
|
-
lxb_dom_node_t* child = lxb_dom_interface_node(el)->first_child;
|
|
230
|
-
while (child) {
|
|
231
|
-
if (child->type == LXB_DOM_NODE_TYPE_ELEMENT)
|
|
232
|
-
JS_SetPropertyUint32(ctx, arr, idx++, make_element(ctx, lxb_dom_interface_element(child)));
|
|
233
|
-
child = child->next;
|
|
234
|
-
}
|
|
235
|
-
return arr;
|
|
429
|
+
if (!el) return JS_NewArray(ctx);
|
|
430
|
+
return LiveCollectionBindings::makeChildren(ctx, el);
|
|
236
431
|
}
|
|
237
432
|
|
|
238
433
|
JSValue js_el_get_firstElementChild(JSContext* ctx, JSValue this_val) {
|
|
@@ -346,14 +541,20 @@ JSValue js_el_set_nodeValue(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
346
541
|
return JS_UNDEFINED;
|
|
347
542
|
}
|
|
348
543
|
|
|
544
|
+
JSValue js_el_get_data_length(JSContext* ctx, JSValue this_val) {
|
|
545
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
546
|
+
if (!node) return JS_UNDEFINED;
|
|
547
|
+
if (node->type != LXB_DOM_NODE_TYPE_TEXT && node->type != LXB_DOM_NODE_TYPE_COMMENT) return JS_UNDEFINED;
|
|
548
|
+
JSValue str_val = js_el_get_nodeValue(ctx, this_val);
|
|
549
|
+
JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
|
|
550
|
+
JS_FreeValue(ctx, str_val);
|
|
551
|
+
return len_val;
|
|
552
|
+
}
|
|
553
|
+
|
|
349
554
|
JSValue js_el_get_childNodes(JSContext* ctx, JSValue this_val) {
|
|
350
555
|
auto* node = unwrap_node(ctx, this_val);
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
uint32_t idx = 0;
|
|
354
|
-
lxb_dom_node_t* child = node->first_child;
|
|
355
|
-
while (child) { JS_SetPropertyUint32(ctx, arr, idx++, make_element(ctx, child)); child = child->next; }
|
|
356
|
-
return arr;
|
|
556
|
+
if (!node) return JS_NewArray(ctx);
|
|
557
|
+
return LiveCollectionBindings::makeChildNodes(ctx, node);
|
|
357
558
|
}
|
|
358
559
|
|
|
359
560
|
JSValue js_el_get_firstChild(JSContext* ctx, JSValue this_val) {
|
|
@@ -471,6 +672,93 @@ JSValue js_el_hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
471
672
|
return JS_NewBool(ctx, has);
|
|
472
673
|
}
|
|
473
674
|
|
|
675
|
+
// ── Namespaced attribute methods (getAttributeNS/setAttributeNS/...) ─────────
|
|
676
|
+
// Simplification: this sandbox doesn't track a real (namespace, localName)
|
|
677
|
+
// identity per attribute the way createElementNS()/namespaceURI() do for
|
|
678
|
+
// elements — building that would mean hand-constructing lxb_dom_attr_t nodes
|
|
679
|
+
// with their own ns/prefix fields, a much larger and riskier lift than the
|
|
680
|
+
// element side. Instead, attributes are matched by local name only (the
|
|
681
|
+
// qualified name with any "prefix:" stripped), which is spec-accurate for
|
|
682
|
+
// the common real-world case (a single xlink:href-style attribute) but not
|
|
683
|
+
// for two attributes sharing a local name across different namespaces — a
|
|
684
|
+
// case that essentially never comes up in embedded-widget scripts.
|
|
685
|
+
lxb_dom_attr_t* find_attr_by_local_name(lxb_dom_element_t* el, const std::string& localName) {
|
|
686
|
+
for (lxb_dom_attr_t* attr = lxb_dom_element_first_attribute(el); attr; attr = lxb_dom_element_next_attribute(attr)) {
|
|
687
|
+
size_t len = 0;
|
|
688
|
+
const lxb_char_t* qname = lxb_dom_attr_qualified_name(attr, &len);
|
|
689
|
+
if (!qname) continue;
|
|
690
|
+
std::string q(reinterpret_cast<const char*>(qname), len);
|
|
691
|
+
auto colon = q.find(':');
|
|
692
|
+
std::string local = (colon == std::string::npos) ? q : q.substr(colon + 1);
|
|
693
|
+
if (local == localName) return attr;
|
|
694
|
+
}
|
|
695
|
+
return nullptr;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
JSValue js_el_getAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
699
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
700
|
+
if (!el || argc < 2) return JS_NULL;
|
|
701
|
+
const char* local = JS_ToCString(ctx, argv[1]);
|
|
702
|
+
if (!local) return JS_NULL;
|
|
703
|
+
auto* attr = find_attr_by_local_name(el, local);
|
|
704
|
+
JS_FreeCString(ctx, local);
|
|
705
|
+
if (!attr) return JS_NULL;
|
|
706
|
+
size_t len = 0;
|
|
707
|
+
const lxb_char_t* val = lxb_dom_attr_value(attr, &len);
|
|
708
|
+
return val ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len) : JS_NewString(ctx, "");
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
JSValue js_el_hasAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
712
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
713
|
+
if (!el || argc < 2) return JS_FALSE;
|
|
714
|
+
const char* local = JS_ToCString(ctx, argv[1]);
|
|
715
|
+
if (!local) return JS_FALSE;
|
|
716
|
+
bool has = find_attr_by_local_name(el, local) != nullptr;
|
|
717
|
+
JS_FreeCString(ctx, local);
|
|
718
|
+
return JS_NewBool(ctx, has);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
JSValue js_el_setAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
722
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
723
|
+
if (!el || argc < 3) return JS_UNDEFINED;
|
|
724
|
+
const char* qualified_name = JS_ToCString(ctx, argv[1]);
|
|
725
|
+
const char* value = JS_ToCString(ctx, argv[2]);
|
|
726
|
+
if (qualified_name && value) {
|
|
727
|
+
std::string qn(qualified_name);
|
|
728
|
+
auto colon = qn.find(':');
|
|
729
|
+
std::string local = (colon == std::string::npos) ? qn : qn.substr(colon + 1);
|
|
730
|
+
|
|
731
|
+
auto* existing = find_attr_by_local_name(el, local);
|
|
732
|
+
if (existing) {
|
|
733
|
+
lxb_dom_attr_set_existing_value(existing,
|
|
734
|
+
reinterpret_cast<const lxb_char_t*>(value), strlen(value));
|
|
735
|
+
} else {
|
|
736
|
+
lxb_dom_element_set_attribute(el,
|
|
737
|
+
reinterpret_cast<const lxb_char_t*>(qualified_name), qn.size(),
|
|
738
|
+
reinterpret_cast<const lxb_char_t*>(value), strlen(value));
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (qualified_name) JS_FreeCString(ctx, qualified_name);
|
|
742
|
+
if (value) JS_FreeCString(ctx, value);
|
|
743
|
+
return JS_UNDEFINED;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
JSValue js_el_removeAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
747
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
748
|
+
if (!el || argc < 2) return JS_UNDEFINED;
|
|
749
|
+
const char* local = JS_ToCString(ctx, argv[1]);
|
|
750
|
+
if (local) {
|
|
751
|
+
auto* attr = find_attr_by_local_name(el, local);
|
|
752
|
+
if (attr) {
|
|
753
|
+
size_t qlen = 0;
|
|
754
|
+
const lxb_char_t* qname = lxb_dom_attr_qualified_name(attr, &qlen);
|
|
755
|
+
if (qname) lxb_dom_element_remove_attribute(el, qname, qlen);
|
|
756
|
+
}
|
|
757
|
+
JS_FreeCString(ctx, local);
|
|
758
|
+
}
|
|
759
|
+
return JS_UNDEFINED;
|
|
760
|
+
}
|
|
761
|
+
|
|
474
762
|
// Inserts `node` via `insert_one`, expanding DocumentFragments into their
|
|
475
763
|
// children first (per the DOM's "insert a node" algorithm — a fragment is
|
|
476
764
|
// never itself part of the resulting tree, only its children are moved in).
|
|
@@ -551,8 +839,7 @@ JSValue js_el_insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
551
839
|
if (ref) {
|
|
552
840
|
lxb_dom_node_t* receiver_node = unwrap_node(ctx, this_val);
|
|
553
841
|
if (!receiver_node || ref->parent != receiver_node) {
|
|
554
|
-
|
|
555
|
-
return JS_EXCEPTION;
|
|
842
|
+
return throw_dom_exception(ctx, "NotFoundError", "reference node is not a child of this node");
|
|
556
843
|
}
|
|
557
844
|
parent_node = receiver_node;
|
|
558
845
|
inserted = expand_and_insert(new_node, [&](lxb_dom_node_t* n) {
|
|
@@ -619,8 +906,7 @@ JSValue js_el_replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
619
906
|
auto* old_node = unwrap_node(ctx, argv[1]);
|
|
620
907
|
if (!new_node || !old_node) return JS_NULL;
|
|
621
908
|
if (old_node->parent != parent_node) {
|
|
622
|
-
|
|
623
|
-
return JS_EXCEPTION;
|
|
909
|
+
return throw_dom_exception(ctx, "NotFoundError", "the node to be replaced is not a child of this node");
|
|
624
910
|
}
|
|
625
911
|
|
|
626
912
|
lxb_dom_node_t* prev_sib = old_node->prev;
|
|
@@ -643,12 +929,15 @@ JSValue js_el_replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
643
929
|
return JS_DupValue(ctx, argv[1]); // returns the replaced (old) child, per spec
|
|
644
930
|
}
|
|
645
931
|
|
|
646
|
-
|
|
932
|
+
// `context_node` supplies the owning document for a string argument coerced
|
|
933
|
+
// into a new text node (see doc_for_node()) — irrelevant when `val` is
|
|
934
|
+
// already a node.
|
|
935
|
+
lxb_dom_node_t* js_to_node_or_text(JSContext* ctx, JSValue val, lxb_dom_node_t* context_node) {
|
|
647
936
|
auto* node = unwrap_node(ctx, val);
|
|
648
937
|
if (node) return node;
|
|
649
938
|
const char* str = JS_ToCString(ctx, val);
|
|
650
939
|
if (!str) return nullptr;
|
|
651
|
-
void* text =
|
|
940
|
+
void* text = doc_for_node(ctx, context_node)->createTextNode(str);
|
|
652
941
|
JS_FreeCString(ctx, str);
|
|
653
942
|
return static_cast<lxb_dom_node_t*>(text);
|
|
654
943
|
}
|
|
@@ -660,7 +949,7 @@ JSValue js_el_before(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
|
|
|
660
949
|
|
|
661
950
|
std::vector<void*> inserted;
|
|
662
951
|
for (int i = 0; i < argc; i++) {
|
|
663
|
-
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
952
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
|
|
664
953
|
if (!n) continue;
|
|
665
954
|
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
|
|
666
955
|
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
@@ -682,7 +971,7 @@ JSValue js_el_after(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
|
682
971
|
|
|
683
972
|
std::vector<void*> inserted;
|
|
684
973
|
for (int i = 0; i < argc; i++) {
|
|
685
|
-
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
974
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
|
|
686
975
|
if (!n) continue;
|
|
687
976
|
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
|
|
688
977
|
if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent, one);
|
|
@@ -706,7 +995,7 @@ JSValue js_el_replaceWith(JSContext* ctx, JSValue this_val, int argc, JSValue* a
|
|
|
706
995
|
|
|
707
996
|
std::vector<void*> inserted;
|
|
708
997
|
for (int i = 0; i < argc; i++) {
|
|
709
|
-
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
998
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
|
|
710
999
|
if (!n) continue;
|
|
711
1000
|
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
|
|
712
1001
|
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
@@ -728,7 +1017,7 @@ JSValue js_el_append(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
|
|
|
728
1017
|
|
|
729
1018
|
std::vector<void*> inserted;
|
|
730
1019
|
for (int i = 0; i < argc; i++) {
|
|
731
|
-
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
1020
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], parent_node);
|
|
732
1021
|
if (!n) continue;
|
|
733
1022
|
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_child(parent_node, one); });
|
|
734
1023
|
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
@@ -749,7 +1038,7 @@ JSValue js_el_prepend(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
|
|
|
749
1038
|
|
|
750
1039
|
std::vector<void*> inserted;
|
|
751
1040
|
for (int i = 0; i < argc; i++) {
|
|
752
|
-
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
1041
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], parent_node);
|
|
753
1042
|
if (!n) continue;
|
|
754
1043
|
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
|
|
755
1044
|
if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent_node, one);
|
|
@@ -770,11 +1059,12 @@ JSValue js_el_closest(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
|
|
|
770
1059
|
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
771
1060
|
if (!sel) return JS_NULL;
|
|
772
1061
|
|
|
1062
|
+
auto* doc = doc_for_node(ctx, lxb_dom_interface_node(el));
|
|
773
1063
|
JSValue result = JS_NULL;
|
|
774
1064
|
for (lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
775
1065
|
node && node->type == LXB_DOM_NODE_TYPE_ELEMENT;
|
|
776
1066
|
node = node->parent) {
|
|
777
|
-
if (
|
|
1067
|
+
if (doc->matchesSelector(lxb_dom_interface_element(node), sel)) {
|
|
778
1068
|
result = make_element(ctx, node);
|
|
779
1069
|
break;
|
|
780
1070
|
}
|
|
@@ -797,7 +1087,7 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
|
|
|
797
1087
|
JS_FreeCString(ctx, position);
|
|
798
1088
|
|
|
799
1089
|
lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
800
|
-
auto parsed =
|
|
1090
|
+
auto parsed = doc_for_node(ctx, node)->parseFragmentNodes(el, html);
|
|
801
1091
|
JS_FreeCString(ctx, html);
|
|
802
1092
|
if (parsed.empty()) return JS_UNDEFINED;
|
|
803
1093
|
|
|
@@ -835,8 +1125,7 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
|
|
|
835
1125
|
next_sib = ref;
|
|
836
1126
|
} else {
|
|
837
1127
|
for (void* n : parsed) lxb_dom_node_destroy_deep(static_cast<lxb_dom_node_t*>(n));
|
|
838
|
-
|
|
839
|
-
return JS_EXCEPTION;
|
|
1128
|
+
return throw_dom_exception(ctx, "SyntaxError", ("invalid insertAdjacentHTML position '" + pos + "'").c_str());
|
|
840
1129
|
}
|
|
841
1130
|
|
|
842
1131
|
auto* rctx = get_ctx(ctx);
|
|
@@ -851,7 +1140,7 @@ JSValue js_el_matches(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
|
|
|
851
1140
|
if (!el || argc < 1) return JS_FALSE;
|
|
852
1141
|
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
853
1142
|
if (!sel) return JS_FALSE;
|
|
854
|
-
bool result =
|
|
1143
|
+
bool result = doc_for_node(ctx, el)->matchesSelector(el, sel);
|
|
855
1144
|
JS_FreeCString(ctx, sel);
|
|
856
1145
|
return JS_NewBool(ctx, result);
|
|
857
1146
|
}
|
|
@@ -861,7 +1150,7 @@ JSValue js_el_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
861
1150
|
if (!el || argc < 1) return JS_NULL;
|
|
862
1151
|
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
863
1152
|
if (!sel) return JS_NULL;
|
|
864
|
-
void* found =
|
|
1153
|
+
void* found = doc_for_node(ctx, el)->querySelectorFromEl(el, sel);
|
|
865
1154
|
JS_FreeCString(ctx, sel);
|
|
866
1155
|
return make_element(ctx, found);
|
|
867
1156
|
}
|
|
@@ -871,7 +1160,7 @@ JSValue js_el_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSVal
|
|
|
871
1160
|
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
872
1161
|
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
873
1162
|
if (!sel) return JS_NewArray(ctx);
|
|
874
|
-
auto results =
|
|
1163
|
+
auto results = doc_for_node(ctx, el)->querySelectorAllFromEl(el, sel);
|
|
875
1164
|
JS_FreeCString(ctx, sel);
|
|
876
1165
|
return make_element_array(ctx, results);
|
|
877
1166
|
}
|
|
@@ -881,9 +1170,9 @@ JSValue js_el_getElementsByClassName(JSContext* ctx, JSValue this_val, int argc,
|
|
|
881
1170
|
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
882
1171
|
const char* names = JS_ToCString(ctx, argv[0]);
|
|
883
1172
|
if (!names) return JS_NewArray(ctx);
|
|
884
|
-
|
|
1173
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, el, classNames_to_selector(names));
|
|
885
1174
|
JS_FreeCString(ctx, names);
|
|
886
|
-
return
|
|
1175
|
+
return result;
|
|
887
1176
|
}
|
|
888
1177
|
|
|
889
1178
|
JSValue js_el_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
@@ -891,9 +1180,9 @@ JSValue js_el_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, J
|
|
|
891
1180
|
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
892
1181
|
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
893
1182
|
if (!tag) return JS_NewArray(ctx);
|
|
894
|
-
|
|
1183
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, el, tag);
|
|
895
1184
|
JS_FreeCString(ctx, tag);
|
|
896
|
-
return
|
|
1185
|
+
return result;
|
|
897
1186
|
}
|
|
898
1187
|
|
|
899
1188
|
JSValue js_el_getAttributeNames(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
@@ -966,6 +1255,25 @@ JSValue js_el_toggleAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu
|
|
|
966
1255
|
return JS_NewBool(ctx, force_present);
|
|
967
1256
|
}
|
|
968
1257
|
|
|
1258
|
+
JSValue js_el_getBoundingClientRect(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
1259
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
1260
|
+
if (!el) return JS_NULL;
|
|
1261
|
+
JSValue rect = JS_NewObject(ctx);
|
|
1262
|
+
static const char* kFields[] = { "x", "y", "width", "height", "top", "right", "bottom", "left" };
|
|
1263
|
+
for (const char* field : kFields) {
|
|
1264
|
+
JS_SetPropertyStr(ctx, rect, field, JS_NewInt32(ctx, 0));
|
|
1265
|
+
}
|
|
1266
|
+
return rect;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
JSValue js_el_getClientRects(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
1270
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
1271
|
+
if (!el) return JS_NewArray(ctx);
|
|
1272
|
+
JSValue arr = JS_NewArray(ctx);
|
|
1273
|
+
JS_SetPropertyUint32(ctx, arr, 0, js_el_getBoundingClientRect(ctx, this_val, argc, argv));
|
|
1274
|
+
return arr;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
969
1277
|
JSValue js_el_isSameNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
970
1278
|
auto* node = unwrap_node(ctx, this_val);
|
|
971
1279
|
if (!node || argc < 1) return JS_FALSE;
|
|
@@ -1022,6 +1330,105 @@ JSValue js_el_isEqualNode(JSContext* ctx, JSValue this_val, int argc, JSValue* a
|
|
|
1022
1330
|
return JS_NewBool(ctx, nodes_equal(node, other));
|
|
1023
1331
|
}
|
|
1024
1332
|
|
|
1333
|
+
void collect_text_descendants(lxb_dom_node_t* node, std::vector<void*>& out) {
|
|
1334
|
+
for (lxb_dom_node_t* child = node->first_child; child; child = child->next) {
|
|
1335
|
+
if (child->type == LXB_DOM_NODE_TYPE_TEXT) out.push_back(child);
|
|
1336
|
+
else if (child->first_child) collect_text_descendants(child, out);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
JSValue js_el_normalize(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
1341
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1342
|
+
if (!node) return JS_UNDEFINED;
|
|
1343
|
+
|
|
1344
|
+
// normalize() only merges/drops Text nodes (leaves, no children of their
|
|
1345
|
+
// own), so a flat invalidate is enough — no need for the *_deep variant.
|
|
1346
|
+
std::vector<void*> text_nodes;
|
|
1347
|
+
collect_text_descendants(node, text_nodes);
|
|
1348
|
+
if (!text_nodes.empty()) {
|
|
1349
|
+
invalidate_node_cache_batch(ctx, get_ctx(ctx), text_nodes);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
doc_for_node(ctx, node)->normalize(node);
|
|
1353
|
+
return JS_UNDEFINED;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
JSValue js_el_compareDocumentPosition(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
1357
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1358
|
+
if (!node || argc < 1) return JS_NewInt32(ctx, 0);
|
|
1359
|
+
auto* other = unwrap_node(ctx, argv[0]);
|
|
1360
|
+
if (!other) return JS_NewInt32(ctx, 0);
|
|
1361
|
+
return JS_NewInt32(ctx, doc_for_node(ctx, node)->compareDocumentPosition(node, other));
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
JSValue js_canonicalize_root_node(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
1365
|
+
if (argc < 1) return JS_UNDEFINED;
|
|
1366
|
+
auto* node = unwrap_node(ctx, argv[0]);
|
|
1367
|
+
if (node && node->type == LXB_DOM_NODE_TYPE_DOCUMENT && doc_for_node(ctx, node) == get_doc(ctx)) {
|
|
1368
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
1369
|
+
JSValue doc_val = JS_GetPropertyStr(ctx, global, "document");
|
|
1370
|
+
JS_FreeValue(ctx, global);
|
|
1371
|
+
return doc_val;
|
|
1372
|
+
}
|
|
1373
|
+
return JS_DupValue(ctx, argv[0]);
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
JSValue js_el_get_ownerDocument(JSContext* ctx, JSValue this_val) {
|
|
1377
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1378
|
+
if (!node) return JS_NULL;
|
|
1379
|
+
if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT) return JS_NULL;
|
|
1380
|
+
|
|
1381
|
+
LexborDocument* owner = doc_for_node(ctx, node);
|
|
1382
|
+
if (owner == get_doc(ctx)) {
|
|
1383
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
1384
|
+
JSValue doc_val = JS_GetPropertyStr(ctx, global, "document");
|
|
1385
|
+
JS_FreeValue(ctx, global);
|
|
1386
|
+
return doc_val;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
JSValue wrapper = get_document_wrapper(ctx, owner);
|
|
1390
|
+
if (!JS_IsUndefined(wrapper)) return wrapper;
|
|
1391
|
+
|
|
1392
|
+
void* html_doc = owner->documentHtmlPtr();
|
|
1393
|
+
void* doc_node = lxb_dom_interface_node(
|
|
1394
|
+
lxb_dom_interface_document(static_cast<lxb_html_document_t*>(html_doc)));
|
|
1395
|
+
return make_element(ctx, doc_node);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
JSValue js_el_get_namespaceURI(JSContext* ctx, JSValue this_val) {
|
|
1399
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1400
|
+
if (!node) return JS_NULL;
|
|
1401
|
+
LexborDocument* owner = doc_for_node(ctx, node);
|
|
1402
|
+
std::string uri = owner ? owner->namespaceURI(node) : "";
|
|
1403
|
+
if (uri.empty()) return JS_NULL;
|
|
1404
|
+
return JS_NewStringLen(ctx, uri.data(), uri.size());
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Element.prefix — the namespace prefix set via createElementNS('ns', 'prefix:local'),
|
|
1408
|
+
// or null for an element with no prefix (the overwhelmingly common case).
|
|
1409
|
+
JSValue js_el_get_prefix(JSContext* ctx, JSValue this_val) {
|
|
1410
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
1411
|
+
if (!el) return JS_NULL;
|
|
1412
|
+
auto* node = reinterpret_cast<lxb_dom_node_t*>(el);
|
|
1413
|
+
if (node->prefix == 0) return JS_NULL;
|
|
1414
|
+
const lxb_ns_prefix_data_t* data = lxb_ns_prefix_data_by_id(node->owner_document->prefix, node->prefix);
|
|
1415
|
+
if (!data) return JS_NULL;
|
|
1416
|
+
const lxb_char_t* str = lexbor_hash_entry_str(&data->entry);
|
|
1417
|
+
if (!str) return JS_NULL;
|
|
1418
|
+
return JS_NewStringLen(ctx, reinterpret_cast<const char*>(str), data->entry.length);
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// Node.baseURI — always the document's URL in this sandbox (no <base> element
|
|
1422
|
+
// support/per-node override), so this just forwards to location.href.
|
|
1423
|
+
JSValue js_el_get_baseURI(JSContext* ctx, JSValue) {
|
|
1424
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
1425
|
+
JSValue location = JS_GetPropertyStr(ctx, global, "location");
|
|
1426
|
+
JS_FreeValue(ctx, global);
|
|
1427
|
+
JSValue href = JS_GetPropertyStr(ctx, location, "href");
|
|
1428
|
+
JS_FreeValue(ctx, location);
|
|
1429
|
+
return href;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1025
1432
|
} // namespace
|
|
1026
1433
|
|
|
1027
1434
|
void ElementBindings::install(JSContext* ctx) {
|
|
@@ -1048,11 +1455,18 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1048
1455
|
JS_SetPropertyStr(ctx, node_proto, "replaceWith", JS_NewCFunction(ctx, js_el_replaceWith, "replaceWith", 0));
|
|
1049
1456
|
JS_SetPropertyStr(ctx, node_proto, "isSameNode", JS_NewCFunction(ctx, js_el_isSameNode, "isSameNode", 1));
|
|
1050
1457
|
JS_SetPropertyStr(ctx, node_proto, "isEqualNode", JS_NewCFunction(ctx, js_el_isEqualNode, "isEqualNode", 1));
|
|
1458
|
+
JS_SetPropertyStr(ctx, node_proto, "normalize", JS_NewCFunction(ctx, js_el_normalize, "normalize", 0));
|
|
1459
|
+
JS_SetPropertyStr(ctx, node_proto, "compareDocumentPosition", JS_NewCFunction(ctx, js_el_compareDocumentPosition, "compareDocumentPosition", 1));
|
|
1051
1460
|
|
|
1052
1461
|
define_prop(ctx, node_proto, "nodeType", js_el_get_nodeType, nullptr);
|
|
1053
1462
|
define_prop(ctx, node_proto, "nodeName", js_el_get_nodeName, nullptr);
|
|
1054
1463
|
define_prop(ctx, node_proto, "nodeValue", js_el_get_nodeValue, js_el_set_nodeValue);
|
|
1464
|
+
define_prop(ctx, node_proto, "data", js_el_get_nodeValue, js_el_set_nodeValue);
|
|
1465
|
+
define_prop(ctx, node_proto, "length", js_el_get_data_length, nullptr);
|
|
1055
1466
|
define_prop(ctx, node_proto, "textContent", js_el_get_textContent, js_el_set_textContent);
|
|
1467
|
+
define_prop(ctx, node_proto, "ownerDocument", js_el_get_ownerDocument, nullptr);
|
|
1468
|
+
define_prop(ctx, node_proto, "namespaceURI", js_el_get_namespaceURI, nullptr);
|
|
1469
|
+
define_prop(ctx, node_proto, "baseURI", js_el_get_baseURI, nullptr);
|
|
1056
1470
|
define_prop(ctx, node_proto, "childNodes", js_el_get_childNodes, nullptr);
|
|
1057
1471
|
define_prop(ctx, node_proto, "firstChild", js_el_get_firstChild, nullptr);
|
|
1058
1472
|
define_prop(ctx, node_proto, "lastChild", js_el_get_lastChild, nullptr);
|
|
@@ -1061,6 +1475,8 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1061
1475
|
define_prop(ctx, node_proto, "parentNode", js_el_get_parentNode, nullptr);
|
|
1062
1476
|
define_prop(ctx, node_proto, "parentElement", js_el_get_parentElement, nullptr);
|
|
1063
1477
|
|
|
1478
|
+
define_node_type_constants(ctx, node_proto);
|
|
1479
|
+
|
|
1064
1480
|
JS_SetClassProto(ctx, js_node_class_id, JS_DupValue(ctx, node_proto));
|
|
1065
1481
|
|
|
1066
1482
|
// ── Element proto: inherits Node, adds Element-specific APIs ─────────────
|
|
@@ -1071,10 +1487,15 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1071
1487
|
JS_SetPropertyStr(ctx, proto, "setAttribute", JS_NewCFunction(ctx, js_el_setAttribute, "setAttribute", 2));
|
|
1072
1488
|
JS_SetPropertyStr(ctx, proto, "removeAttribute", JS_NewCFunction(ctx, js_el_removeAttribute, "removeAttribute", 1));
|
|
1073
1489
|
JS_SetPropertyStr(ctx, proto, "hasAttribute", JS_NewCFunction(ctx, js_el_hasAttribute, "hasAttribute", 1));
|
|
1490
|
+
JS_SetPropertyStr(ctx, proto, "getAttributeNS", JS_NewCFunction(ctx, js_el_getAttributeNS, "getAttributeNS", 2));
|
|
1491
|
+
JS_SetPropertyStr(ctx, proto, "setAttributeNS", JS_NewCFunction(ctx, js_el_setAttributeNS, "setAttributeNS", 3));
|
|
1492
|
+
JS_SetPropertyStr(ctx, proto, "removeAttributeNS", JS_NewCFunction(ctx, js_el_removeAttributeNS, "removeAttributeNS", 2));
|
|
1493
|
+
JS_SetPropertyStr(ctx, proto, "hasAttributeNS", JS_NewCFunction(ctx, js_el_hasAttributeNS, "hasAttributeNS", 2));
|
|
1074
1494
|
JS_SetPropertyStr(ctx, proto, "toggleAttribute", JS_NewCFunction(ctx, js_el_toggleAttribute, "toggleAttribute", 2));
|
|
1075
1495
|
JS_SetPropertyStr(ctx, proto, "getAttributeNames", JS_NewCFunction(ctx, js_el_getAttributeNames, "getAttributeNames", 0));
|
|
1076
1496
|
JS_SetPropertyStr(ctx, proto, "remove", JS_NewCFunction(ctx, js_el_remove, "remove", 0));
|
|
1077
1497
|
JS_SetPropertyStr(ctx, proto, "matches", JS_NewCFunction(ctx, js_el_matches, "matches", 1));
|
|
1498
|
+
JS_SetPropertyStr(ctx, proto, "webkitMatchesSelector", JS_NewCFunction(ctx, js_el_matches, "webkitMatchesSelector", 1));
|
|
1078
1499
|
JS_SetPropertyStr(ctx, proto, "querySelector", JS_NewCFunction(ctx, js_el_querySelector, "querySelector", 1));
|
|
1079
1500
|
JS_SetPropertyStr(ctx, proto, "querySelectorAll", JS_NewCFunction(ctx, js_el_querySelectorAll, "querySelectorAll", 1));
|
|
1080
1501
|
JS_SetPropertyStr(ctx, proto, "getElementsByClassName", JS_NewCFunction(ctx, js_el_getElementsByClassName, "getElementsByClassName", 1));
|
|
@@ -1083,12 +1504,18 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1083
1504
|
JS_SetPropertyStr(ctx, proto, "insertAdjacentHTML", JS_NewCFunction(ctx, js_el_insertAdjacentHTML, "insertAdjacentHTML", 2));
|
|
1084
1505
|
JS_SetPropertyStr(ctx, proto, "append", JS_NewCFunction(ctx, js_el_append, "append", 0));
|
|
1085
1506
|
JS_SetPropertyStr(ctx, proto, "prepend", JS_NewCFunction(ctx, js_el_prepend, "prepend", 0));
|
|
1507
|
+
JS_SetPropertyStr(ctx, proto, "getBoundingClientRect", JS_NewCFunction(ctx, js_el_getBoundingClientRect, "getBoundingClientRect", 0));
|
|
1508
|
+
JS_SetPropertyStr(ctx, proto, "getClientRects", JS_NewCFunction(ctx, js_el_getClientRects, "getClientRects", 0));
|
|
1086
1509
|
|
|
1087
1510
|
define_prop(ctx, proto, "tagName", js_el_get_tagName, nullptr);
|
|
1511
|
+
define_prop(ctx, proto, "prefix", js_el_get_prefix, nullptr);
|
|
1088
1512
|
define_prop(ctx, proto, "id", js_el_get_id, js_el_set_id);
|
|
1089
1513
|
define_prop(ctx, proto, "className", js_el_get_className, js_el_set_className);
|
|
1514
|
+
define_prop(ctx, proto, "value", js_el_get_value, js_el_set_value);
|
|
1515
|
+
define_prop(ctx, proto, "checked", js_el_get_checked, js_el_set_checked);
|
|
1090
1516
|
define_prop(ctx, proto, "innerHTML", js_el_get_innerHTML, js_el_set_innerHTML);
|
|
1091
1517
|
define_prop(ctx, proto, "outerHTML", js_el_get_outerHTML, nullptr);
|
|
1518
|
+
define_prop(ctx, proto, "innerText", js_el_get_textContent, js_el_set_textContent);
|
|
1092
1519
|
define_prop(ctx, proto, "children", js_el_get_children, nullptr);
|
|
1093
1520
|
define_prop(ctx, proto, "firstElementChild", js_el_get_firstElementChild, nullptr);
|
|
1094
1521
|
define_prop(ctx, proto, "lastElementChild", js_el_get_lastElementChild, nullptr);
|
|
@@ -1101,6 +1528,91 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1101
1528
|
define_prop(ctx, proto, "attributes", js_el_get_attributes, nullptr);
|
|
1102
1529
|
|
|
1103
1530
|
JS_SetClassProto(ctx, js_element_class_id, proto);
|
|
1531
|
+
|
|
1532
|
+
JSValue node_proto_ref = JS_GetClassProto(ctx, js_node_class_id);
|
|
1533
|
+
JSValue element_proto_ref = JS_GetClassProto(ctx, js_element_class_id);
|
|
1534
|
+
|
|
1535
|
+
JSValue node_ctor = define_global_constructor(ctx, "Node", node_proto_ref);
|
|
1536
|
+
define_node_type_constants(ctx, node_ctor);
|
|
1537
|
+
JS_FreeValue(ctx, node_ctor);
|
|
1538
|
+
JSValue element_ctor = define_global_constructor(ctx, "Element", element_proto_ref);
|
|
1539
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
1540
|
+
JS_SetPropertyStr(ctx, global, "HTMLElement", element_ctor);
|
|
1541
|
+
JS_SetPropertyStr(ctx, global, "__nativeCanonicalizeRootNode",
|
|
1542
|
+
JS_NewCFunction(ctx, js_canonicalize_root_node, "__nativeCanonicalizeRootNode", 1));
|
|
1543
|
+
JS_FreeValue(ctx, global);
|
|
1544
|
+
|
|
1545
|
+
JS_FreeValue(ctx, node_proto_ref);
|
|
1546
|
+
JS_FreeValue(ctx, element_proto_ref);
|
|
1547
|
+
|
|
1548
|
+
static const char* kGetRootNodeScript = R"JS(
|
|
1549
|
+
(function() {
|
|
1550
|
+
Node.prototype.getRootNode = function(options) {
|
|
1551
|
+
var composed = !!(options && options.composed);
|
|
1552
|
+
var node = this;
|
|
1553
|
+
while (true) {
|
|
1554
|
+
var parent = node.parentNode;
|
|
1555
|
+
if (parent) { node = parent; continue; }
|
|
1556
|
+
if (composed && node.host) { node = node.host; continue; }
|
|
1557
|
+
return __nativeCanonicalizeRootNode(node);
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
|
|
1561
|
+
Object.defineProperty(Node.prototype, 'isConnected', {
|
|
1562
|
+
get: function() { return this.getRootNode() === document; },
|
|
1563
|
+
enumerable: true,
|
|
1564
|
+
configurable: true,
|
|
1565
|
+
});
|
|
1566
|
+
|
|
1567
|
+
function locateNamespace(node, prefix) {
|
|
1568
|
+
if (!node) return null;
|
|
1569
|
+
if (node.nodeType === 1) {
|
|
1570
|
+
if (node.namespaceURI !== null && node.prefix === prefix) return node.namespaceURI;
|
|
1571
|
+
var attrs = node.attributes;
|
|
1572
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
1573
|
+
var a = attrs[i];
|
|
1574
|
+
if (prefix === null && a.name === 'xmlns') return a.value || null;
|
|
1575
|
+
if (prefix !== null && a.name === 'xmlns:' + prefix) return a.value || null;
|
|
1576
|
+
}
|
|
1577
|
+
return locateNamespace(node.parentElement, prefix);
|
|
1578
|
+
}
|
|
1579
|
+
if (node.nodeType === 9) {
|
|
1580
|
+
return node.documentElement ? locateNamespace(node.documentElement, prefix) : null;
|
|
1581
|
+
}
|
|
1582
|
+
if (node.nodeType === 10 || node.nodeType === 11) return null;
|
|
1583
|
+
return locateNamespace(node.parentElement, prefix);
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
Node.prototype.lookupNamespaceURI = function(prefix) {
|
|
1587
|
+
if (prefix === undefined || prefix === '') prefix = null;
|
|
1588
|
+
return locateNamespace(this, prefix);
|
|
1589
|
+
};
|
|
1590
|
+
|
|
1591
|
+
Node.prototype.isDefaultNamespace = function(namespace) {
|
|
1592
|
+
if (namespace === undefined || namespace === '') namespace = null;
|
|
1593
|
+
return this.lookupNamespaceURI(null) === namespace;
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
Node.prototype.lookupPrefix = function(namespace) {
|
|
1597
|
+
if (!namespace) return null;
|
|
1598
|
+
var node = this.nodeType === 9 ? this.documentElement : this;
|
|
1599
|
+
while (node && node.nodeType === 1) {
|
|
1600
|
+
if (node.namespaceURI === namespace && node.prefix !== null) return node.prefix;
|
|
1601
|
+
var attrs = node.attributes;
|
|
1602
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
1603
|
+
var a = attrs[i];
|
|
1604
|
+
if (a.name.indexOf('xmlns:') === 0 && a.value === namespace) return a.name.slice(6);
|
|
1605
|
+
}
|
|
1606
|
+
node = node.parentElement;
|
|
1607
|
+
}
|
|
1608
|
+
return null;
|
|
1609
|
+
};
|
|
1610
|
+
})();
|
|
1611
|
+
)JS";
|
|
1612
|
+
JSValue root_node_result = JS_Eval(ctx, kGetRootNodeScript, strlen(kGetRootNodeScript),
|
|
1613
|
+
"<get-root-node-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
1614
|
+
if (JS_IsException(root_node_result)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
1615
|
+
JS_FreeValue(ctx, root_node_result);
|
|
1104
1616
|
}
|
|
1105
1617
|
|
|
1106
1618
|
} // namespace margelo::nitro::nitrojsdom
|