@salve-software/react-native-nitro-jsdom 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/android/CMakeLists.txt +14 -0
- package/cpp/HybridHtmlSandbox.cpp +16 -3
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +217 -67
- package/cpp/lexbor/LexborDocument.hpp +29 -10
- package/cpp/quickjs/DOMBindings.cpp +28 -0
- package/cpp/quickjs/DOMBindingsInternal.cpp +34 -2
- package/cpp/quickjs/DOMBindingsInternal.hpp +17 -1
- package/cpp/quickjs/QuickJSRuntime.cpp +19 -4
- package/cpp/quickjs/QuickJSRuntime.hpp +22 -1
- package/cpp/quickjs/bindings/AbortBindings.cpp +114 -0
- package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +137 -0
- package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +244 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +23 -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/DocumentBindings.cpp +84 -4
- package/cpp/quickjs/bindings/ElementBindings.cpp +264 -28
- package/cpp/quickjs/bindings/EventBindings.cpp +137 -3
- package/cpp/quickjs/bindings/FetchBindings.cpp +5 -1
- package/cpp/quickjs/bindings/FormBindings.cpp +142 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +16 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +183 -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 +41 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +286 -0
- package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +270 -0
- 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 +2 -2
- package/src/classes/JSDOM/JSDOM.class.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
#include "DocumentBindings.hpp"
|
|
2
|
+
#include "LiveCollectionBindings.hpp"
|
|
2
3
|
#include "../DOMBindingsInternal.hpp"
|
|
4
|
+
#include "../QuickJSRuntime.hpp"
|
|
3
5
|
#include "../../lexbor/LexborDocument.hpp"
|
|
4
6
|
#include <cstring>
|
|
7
|
+
#include <string>
|
|
5
8
|
|
|
6
9
|
namespace margelo::nitro::nitrojsdom {
|
|
7
10
|
|
|
@@ -38,18 +41,47 @@ JSValue js_doc_getElementsByClassName(JSContext* ctx, JSValue, int argc, JSValue
|
|
|
38
41
|
if (argc < 1) return JS_NewArray(ctx);
|
|
39
42
|
const char* names = JS_ToCString(ctx, argv[0]);
|
|
40
43
|
if (!names) return JS_NewArray(ctx);
|
|
41
|
-
|
|
44
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, nullptr, classNames_to_selector(names));
|
|
42
45
|
JS_FreeCString(ctx, names);
|
|
43
|
-
return
|
|
46
|
+
return result;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
JSValue js_doc_getElementsByTagName(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
47
50
|
if (argc < 1) return JS_NewArray(ctx);
|
|
48
51
|
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
49
52
|
if (!tag) return JS_NewArray(ctx);
|
|
50
|
-
|
|
53
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, nullptr, tag);
|
|
51
54
|
JS_FreeCString(ctx, tag);
|
|
52
|
-
return
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
JSValue js_doc_getElementsByName(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
59
|
+
if (argc < 1) return JS_NewArray(ctx);
|
|
60
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
61
|
+
if (!name) return JS_NewArray(ctx);
|
|
62
|
+
std::string selector = "[name=\"" + std::string(name) + "\"]";
|
|
63
|
+
JS_FreeCString(ctx, name);
|
|
64
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, selector);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// document.forms/.images/.scripts/.links — live HTMLCollections backed by the
|
|
68
|
+
// same LiveCollectionBindings::makeBySelector primitive that already backs
|
|
69
|
+
// getElementsByClassName/getElementsByTagName. .links matches jsdom: any <a>
|
|
70
|
+
// or <area> that actually has an href attribute (bare <a> anchors don't count).
|
|
71
|
+
JSValue js_doc_get_forms(JSContext* ctx, JSValue) {
|
|
72
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "form");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
JSValue js_doc_get_images(JSContext* ctx, JSValue) {
|
|
76
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "img");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
JSValue js_doc_get_scripts(JSContext* ctx, JSValue) {
|
|
80
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "script");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
JSValue js_doc_get_links(JSContext* ctx, JSValue) {
|
|
84
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "a[href], area[href]");
|
|
53
85
|
}
|
|
54
86
|
|
|
55
87
|
JSValue js_doc_createElement(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
@@ -96,6 +128,39 @@ JSValue js_doc_get_documentElement(JSContext* ctx, JSValue) {
|
|
|
96
128
|
return make_element(ctx, get_doc(ctx)->documentElement());
|
|
97
129
|
}
|
|
98
130
|
|
|
131
|
+
// document.doctype is exposed as a plain {name, publicId, systemId, nodeType,
|
|
132
|
+
// nodeName} object rather than routed through make_element()/the generic
|
|
133
|
+
// Node wrapper: DocumentType's `name` is an interned lxb_dom_attr_id_t (needs
|
|
134
|
+
// LexborDocument::doctypeName(), not the generic nodeName getter), and it has
|
|
135
|
+
// no other Node behavior real-world embedded scripts rely on (no children,
|
|
136
|
+
// not appendChild-able). Since it's a plain object rather than a cached
|
|
137
|
+
// node-pointer-keyed wrapper, the constructed object is cached directly on
|
|
138
|
+
// RuntimeContext (built once, on first access) so repeated `document.doctype`
|
|
139
|
+
// reads return the same JS object — the document itself never gets
|
|
140
|
+
// re-parsed mid-instance, so the cache never needs invalidation.
|
|
141
|
+
JSValue js_doc_get_doctype(JSContext* ctx, JSValue) {
|
|
142
|
+
void* dt = get_doc(ctx)->doctype();
|
|
143
|
+
if (!dt) return JS_NULL;
|
|
144
|
+
|
|
145
|
+
auto* rctx = get_ctx(ctx);
|
|
146
|
+
if (rctx && rctx->doctype_wrapper) {
|
|
147
|
+
return JS_DupValue(ctx, *static_cast<JSValue*>(rctx->doctype_wrapper));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
JSValue obj = JS_NewObject(ctx);
|
|
151
|
+
std::string name = get_doc(ctx)->doctypeName(dt);
|
|
152
|
+
JS_SetPropertyStr(ctx, obj, "name", JS_NewStringLen(ctx, name.data(), name.size()));
|
|
153
|
+
std::string publicId = get_doc(ctx)->doctypePublicId(dt);
|
|
154
|
+
JS_SetPropertyStr(ctx, obj, "publicId", JS_NewStringLen(ctx, publicId.data(), publicId.size()));
|
|
155
|
+
std::string systemId = get_doc(ctx)->doctypeSystemId(dt);
|
|
156
|
+
JS_SetPropertyStr(ctx, obj, "systemId", JS_NewStringLen(ctx, systemId.data(), systemId.size()));
|
|
157
|
+
JS_SetPropertyStr(ctx, obj, "nodeType", JS_NewInt32(ctx, 10));
|
|
158
|
+
JS_SetPropertyStr(ctx, obj, "nodeName", JS_NewStringLen(ctx, name.data(), name.size()));
|
|
159
|
+
|
|
160
|
+
if (rctx) rctx->doctype_wrapper = new JSValue(JS_DupValue(ctx, obj));
|
|
161
|
+
return obj;
|
|
162
|
+
}
|
|
163
|
+
|
|
99
164
|
const char* kDocumentTitleBootstrapScript = R"JS(
|
|
100
165
|
(function() {
|
|
101
166
|
Object.defineProperty(document, 'title', {
|
|
@@ -132,6 +197,7 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
132
197
|
JS_SetPropertyStr(ctx, doc, "querySelectorAll", JS_NewCFunction(ctx, js_doc_querySelectorAll, "querySelectorAll", 1));
|
|
133
198
|
JS_SetPropertyStr(ctx, doc, "getElementsByClassName", JS_NewCFunction(ctx, js_doc_getElementsByClassName, "getElementsByClassName", 1));
|
|
134
199
|
JS_SetPropertyStr(ctx, doc, "getElementsByTagName", JS_NewCFunction(ctx, js_doc_getElementsByTagName, "getElementsByTagName", 1));
|
|
200
|
+
JS_SetPropertyStr(ctx, doc, "getElementsByName", JS_NewCFunction(ctx, js_doc_getElementsByName, "getElementsByName", 1));
|
|
135
201
|
JS_SetPropertyStr(ctx, doc, "createElement", JS_NewCFunction(ctx, js_doc_createElement, "createElement", 1));
|
|
136
202
|
JS_SetPropertyStr(ctx, doc, "createTextNode", JS_NewCFunction(ctx, js_doc_createTextNode, "createTextNode", 1));
|
|
137
203
|
JS_SetPropertyStr(ctx, doc, "createComment", JS_NewCFunction(ctx, js_doc_createComment, "createComment", 1));
|
|
@@ -140,6 +206,20 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
140
206
|
define_prop(ctx, doc, "body", js_doc_get_body, nullptr);
|
|
141
207
|
define_prop(ctx, doc, "head", js_doc_get_head, nullptr);
|
|
142
208
|
define_prop(ctx, doc, "documentElement", js_doc_get_documentElement, nullptr);
|
|
209
|
+
define_prop(ctx, doc, "doctype", js_doc_get_doctype, nullptr);
|
|
210
|
+
define_prop(ctx, doc, "forms", js_doc_get_forms, nullptr);
|
|
211
|
+
define_prop(ctx, doc, "images", js_doc_get_images, nullptr);
|
|
212
|
+
define_prop(ctx, doc, "scripts", js_doc_get_scripts, nullptr);
|
|
213
|
+
define_prop(ctx, doc, "links", js_doc_get_links, nullptr);
|
|
214
|
+
|
|
215
|
+
RuntimeContext* rctx = get_ctx(ctx);
|
|
216
|
+
bool hidden = !(rctx && rctx->pretend_to_be_visual);
|
|
217
|
+
JS_SetPropertyStr(ctx, doc, "hidden", JS_NewBool(ctx, hidden));
|
|
218
|
+
|
|
219
|
+
JSValue document_proto = JS_NewObject(ctx);
|
|
220
|
+
JS_SetPrototype(ctx, doc, document_proto);
|
|
221
|
+
JS_FreeValue(ctx, define_global_constructor(ctx, "Document", document_proto));
|
|
222
|
+
JS_FreeValue(ctx, document_proto);
|
|
143
223
|
|
|
144
224
|
JS_SetPropertyStr(ctx, global, "document", doc);
|
|
145
225
|
JS_FreeValue(ctx, global);
|
|
@@ -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"
|
|
@@ -100,6 +102,196 @@ JSValue js_el_set_className(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
100
102
|
return JS_UNDEFINED;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
// ── Form element properties (value / checked) ────────────────────────────────
|
|
106
|
+
// Simplified attribute-reflection model, matching this file's existing id/
|
|
107
|
+
// className getters: no separate "dirty value" IDL flag like real browsers,
|
|
108
|
+
// just direct reflection so scripts that set then read these back (the
|
|
109
|
+
// common embedded-widget pattern) see consistent results.
|
|
110
|
+
|
|
111
|
+
JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val);
|
|
112
|
+
|
|
113
|
+
bool element_tag_is(lxb_dom_element_t* el, const char* tag) {
|
|
114
|
+
size_t len = 0;
|
|
115
|
+
const lxb_char_t* name = lxb_dom_element_local_name(el, &len);
|
|
116
|
+
if (!name || len != strlen(tag)) return false;
|
|
117
|
+
for (size_t i = 0; i < len; i++) {
|
|
118
|
+
if (std::tolower(name[i]) != std::tolower((unsigned char)tag[i])) return false;
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// HTMLOptionElement's value: the "value" attribute if present, else the
|
|
124
|
+
// option's text content.
|
|
125
|
+
std::string option_effective_value(lxb_dom_element_t* option) {
|
|
126
|
+
size_t len = 0;
|
|
127
|
+
const lxb_char_t* val = lxb_dom_element_get_attribute(option,
|
|
128
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5, &len);
|
|
129
|
+
if (val) return std::string(reinterpret_cast<const char*>(val), len);
|
|
130
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(option);
|
|
131
|
+
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
132
|
+
if (!text) return "";
|
|
133
|
+
std::string result(reinterpret_cast<char*>(text), len);
|
|
134
|
+
lxb_dom_document_destroy_text(node->owner_document, text);
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
JSValue js_select_get_value(JSContext* ctx, lxb_dom_element_t* select_el) {
|
|
139
|
+
auto options = get_doc(ctx)->querySelectorAllFromEl(select_el, "option");
|
|
140
|
+
void* chosen = nullptr;
|
|
141
|
+
for (void* opt : options) {
|
|
142
|
+
if (lxb_dom_element_has_attribute(static_cast<lxb_dom_element_t*>(opt),
|
|
143
|
+
reinterpret_cast<const lxb_char_t*>("selected"), 8)) { chosen = opt; break; }
|
|
144
|
+
}
|
|
145
|
+
if (!chosen && !options.empty()) chosen = options.front();
|
|
146
|
+
if (!chosen) return JS_NewString(ctx, "");
|
|
147
|
+
return JS_NewString(ctx, option_effective_value(static_cast<lxb_dom_element_t*>(chosen)).c_str());
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
void js_select_set_value(JSContext* ctx, lxb_dom_element_t* select_el, const std::string& value) {
|
|
151
|
+
auto options = get_doc(ctx)->querySelectorAllFromEl(select_el, "option");
|
|
152
|
+
auto* rctx = get_ctx(ctx);
|
|
153
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
154
|
+
auto* attr_name = reinterpret_cast<const lxb_char_t*>("selected");
|
|
155
|
+
|
|
156
|
+
for (void* opt : options) {
|
|
157
|
+
auto* opt_el = static_cast<lxb_dom_element_t*>(opt);
|
|
158
|
+
bool should_select = option_effective_value(opt_el) == value;
|
|
159
|
+
bool has_selected = lxb_dom_element_has_attribute(opt_el, attr_name, 8);
|
|
160
|
+
|
|
161
|
+
std::optional<std::string> old_val;
|
|
162
|
+
if (has_obs && has_selected && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
163
|
+
size_t len = 0;
|
|
164
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(opt_el, attr_name, 8, &len);
|
|
165
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
bool changed = false;
|
|
169
|
+
if (should_select && !has_selected) {
|
|
170
|
+
lxb_dom_element_set_attribute(opt_el, attr_name, 8, reinterpret_cast<const lxb_char_t*>(""), 0);
|
|
171
|
+
changed = true;
|
|
172
|
+
} else if (!should_select && has_selected) {
|
|
173
|
+
lxb_dom_element_remove_attribute(opt_el, attr_name, 8);
|
|
174
|
+
changed = true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (changed && has_obs) {
|
|
178
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(opt_el), "selected", old_val);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
JSValue js_el_get_value(JSContext* ctx, JSValue this_val) {
|
|
184
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
185
|
+
if (!el) return JS_NewString(ctx, "");
|
|
186
|
+
if (element_tag_is(el, "select")) return js_select_get_value(ctx, el);
|
|
187
|
+
if (element_tag_is(el, "textarea")) return js_el_get_textContent(ctx, this_val);
|
|
188
|
+
size_t len = 0;
|
|
189
|
+
const lxb_char_t* val = lxb_dom_element_get_attribute(el,
|
|
190
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5, &len);
|
|
191
|
+
if (val) return JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len);
|
|
192
|
+
|
|
193
|
+
// A checkbox/radio with no "value" attribute defaults its value to "on".
|
|
194
|
+
if (element_tag_is(el, "input")) {
|
|
195
|
+
size_t type_len = 0;
|
|
196
|
+
const lxb_char_t* type_val = lxb_dom_element_get_attribute(el,
|
|
197
|
+
reinterpret_cast<const lxb_char_t*>("type"), 4, &type_len);
|
|
198
|
+
std::string type_str = type_val ? std::string(reinterpret_cast<const char*>(type_val), type_len) : "";
|
|
199
|
+
for (auto& c : type_str) c = (char)std::tolower((unsigned char)c);
|
|
200
|
+
if (type_str == "checkbox" || type_str == "radio") return JS_NewString(ctx, "on");
|
|
201
|
+
}
|
|
202
|
+
return JS_NewString(ctx, "");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
JSValue js_el_set_value(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
206
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
207
|
+
if (!el) return JS_UNDEFINED;
|
|
208
|
+
const char* str = JS_ToCString(ctx, val);
|
|
209
|
+
if (!str) return JS_UNDEFINED;
|
|
210
|
+
|
|
211
|
+
if (element_tag_is(el, "select")) {
|
|
212
|
+
js_select_set_value(ctx, el, str);
|
|
213
|
+
} else if (element_tag_is(el, "textarea")) {
|
|
214
|
+
auto* rctx = get_ctx(ctx);
|
|
215
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
216
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
217
|
+
|
|
218
|
+
std::vector<void*> old_children;
|
|
219
|
+
lxb_dom_node_t* child = node->first_child;
|
|
220
|
+
while (child) { old_children.push_back(child); child = child->next; }
|
|
221
|
+
if (!old_children.empty()) {
|
|
222
|
+
invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
223
|
+
if (has_obs) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
get_doc(ctx)->setTextContentOnEl(el, str);
|
|
227
|
+
|
|
228
|
+
if (has_obs) {
|
|
229
|
+
std::vector<void*> new_children;
|
|
230
|
+
child = node->first_child;
|
|
231
|
+
while (child) { new_children.push_back(child); child = child->next; }
|
|
232
|
+
rctx->mutation_observers->notifyChildList(ctx, node, new_children, {}, nullptr, nullptr);
|
|
233
|
+
}
|
|
234
|
+
} else {
|
|
235
|
+
auto* rctx = get_ctx(ctx);
|
|
236
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
237
|
+
std::optional<std::string> old_val;
|
|
238
|
+
if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
239
|
+
size_t len = 0;
|
|
240
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el,
|
|
241
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5, &len);
|
|
242
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
243
|
+
}
|
|
244
|
+
lxb_dom_element_set_attribute(el,
|
|
245
|
+
reinterpret_cast<const lxb_char_t*>("value"), 5,
|
|
246
|
+
reinterpret_cast<const lxb_char_t*>(str), strlen(str));
|
|
247
|
+
if (has_obs) {
|
|
248
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), "value", old_val);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
JS_FreeCString(ctx, str);
|
|
253
|
+
return JS_UNDEFINED;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
JSValue js_el_get_checked(JSContext* ctx, JSValue this_val) {
|
|
257
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
258
|
+
if (!el) return JS_FALSE;
|
|
259
|
+
bool has = lxb_dom_element_has_attribute(el,
|
|
260
|
+
reinterpret_cast<const lxb_char_t*>("checked"), 7);
|
|
261
|
+
return JS_NewBool(ctx, has);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
JSValue js_el_set_checked(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
265
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
266
|
+
if (!el) return JS_UNDEFINED;
|
|
267
|
+
bool checked = JS_ToBool(ctx, val) > 0;
|
|
268
|
+
auto* attr_name = reinterpret_cast<const lxb_char_t*>("checked");
|
|
269
|
+
bool has = lxb_dom_element_has_attribute(el, attr_name, 7);
|
|
270
|
+
|
|
271
|
+
auto* rctx = get_ctx(ctx);
|
|
272
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
273
|
+
std::optional<std::string> old_val;
|
|
274
|
+
if (has_obs && has && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
275
|
+
size_t len = 0;
|
|
276
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el, attr_name, 7, &len);
|
|
277
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (checked && !has) {
|
|
281
|
+
lxb_dom_element_set_attribute(el, attr_name, 7, reinterpret_cast<const lxb_char_t*>(""), 0);
|
|
282
|
+
if (has_obs) {
|
|
283
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), "checked", std::nullopt);
|
|
284
|
+
}
|
|
285
|
+
} else if (!checked && has) {
|
|
286
|
+
lxb_dom_element_remove_attribute(el, attr_name, 7);
|
|
287
|
+
if (has_obs) {
|
|
288
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), "checked", old_val);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return JS_UNDEFINED;
|
|
293
|
+
}
|
|
294
|
+
|
|
103
295
|
JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val) {
|
|
104
296
|
lxb_dom_node_t* node = unwrap_node(ctx, this_val);
|
|
105
297
|
if (!node) return JS_NewString(ctx, "");
|
|
@@ -144,7 +336,7 @@ JSValue js_el_set_textContent(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
144
336
|
lxb_dom_node_t* child = node->first_child;
|
|
145
337
|
while (child) { old_children.push_back(child); child = child->next; }
|
|
146
338
|
if (!old_children.empty()) {
|
|
147
|
-
|
|
339
|
+
invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
148
340
|
if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
149
341
|
}
|
|
150
342
|
}
|
|
@@ -189,7 +381,7 @@ JSValue js_el_set_innerHTML(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
189
381
|
}
|
|
190
382
|
|
|
191
383
|
if (!old_children.empty()) {
|
|
192
|
-
|
|
384
|
+
invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
193
385
|
if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
194
386
|
}
|
|
195
387
|
|
|
@@ -223,16 +415,8 @@ JSValue js_el_get_parentElement(JSContext* ctx, JSValue this_val) {
|
|
|
223
415
|
|
|
224
416
|
JSValue js_el_get_children(JSContext* ctx, JSValue this_val) {
|
|
225
417
|
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;
|
|
418
|
+
if (!el) return JS_NewArray(ctx);
|
|
419
|
+
return LiveCollectionBindings::makeChildren(ctx, el);
|
|
236
420
|
}
|
|
237
421
|
|
|
238
422
|
JSValue js_el_get_firstElementChild(JSContext* ctx, JSValue this_val) {
|
|
@@ -348,12 +532,8 @@ JSValue js_el_set_nodeValue(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
348
532
|
|
|
349
533
|
JSValue js_el_get_childNodes(JSContext* ctx, JSValue this_val) {
|
|
350
534
|
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;
|
|
535
|
+
if (!node) return JS_NewArray(ctx);
|
|
536
|
+
return LiveCollectionBindings::makeChildNodes(ctx, node);
|
|
357
537
|
}
|
|
358
538
|
|
|
359
539
|
JSValue js_el_get_firstChild(JSContext* ctx, JSValue this_val) {
|
|
@@ -551,8 +731,7 @@ JSValue js_el_insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
551
731
|
if (ref) {
|
|
552
732
|
lxb_dom_node_t* receiver_node = unwrap_node(ctx, this_val);
|
|
553
733
|
if (!receiver_node || ref->parent != receiver_node) {
|
|
554
|
-
|
|
555
|
-
return JS_EXCEPTION;
|
|
734
|
+
return throw_dom_exception(ctx, "NotFoundError", "reference node is not a child of this node");
|
|
556
735
|
}
|
|
557
736
|
parent_node = receiver_node;
|
|
558
737
|
inserted = expand_and_insert(new_node, [&](lxb_dom_node_t* n) {
|
|
@@ -619,8 +798,7 @@ JSValue js_el_replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue*
|
|
|
619
798
|
auto* old_node = unwrap_node(ctx, argv[1]);
|
|
620
799
|
if (!new_node || !old_node) return JS_NULL;
|
|
621
800
|
if (old_node->parent != parent_node) {
|
|
622
|
-
|
|
623
|
-
return JS_EXCEPTION;
|
|
801
|
+
return throw_dom_exception(ctx, "NotFoundError", "the node to be replaced is not a child of this node");
|
|
624
802
|
}
|
|
625
803
|
|
|
626
804
|
lxb_dom_node_t* prev_sib = old_node->prev;
|
|
@@ -835,8 +1013,7 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
|
|
|
835
1013
|
next_sib = ref;
|
|
836
1014
|
} else {
|
|
837
1015
|
for (void* n : parsed) lxb_dom_node_destroy_deep(static_cast<lxb_dom_node_t*>(n));
|
|
838
|
-
|
|
839
|
-
return JS_EXCEPTION;
|
|
1016
|
+
return throw_dom_exception(ctx, "SyntaxError", ("invalid insertAdjacentHTML position '" + pos + "'").c_str());
|
|
840
1017
|
}
|
|
841
1018
|
|
|
842
1019
|
auto* rctx = get_ctx(ctx);
|
|
@@ -881,9 +1058,9 @@ JSValue js_el_getElementsByClassName(JSContext* ctx, JSValue this_val, int argc,
|
|
|
881
1058
|
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
882
1059
|
const char* names = JS_ToCString(ctx, argv[0]);
|
|
883
1060
|
if (!names) return JS_NewArray(ctx);
|
|
884
|
-
|
|
1061
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, el, classNames_to_selector(names));
|
|
885
1062
|
JS_FreeCString(ctx, names);
|
|
886
|
-
return
|
|
1063
|
+
return result;
|
|
887
1064
|
}
|
|
888
1065
|
|
|
889
1066
|
JSValue js_el_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
@@ -891,9 +1068,9 @@ JSValue js_el_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, J
|
|
|
891
1068
|
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
892
1069
|
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
893
1070
|
if (!tag) return JS_NewArray(ctx);
|
|
894
|
-
|
|
1071
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, el, tag);
|
|
895
1072
|
JS_FreeCString(ctx, tag);
|
|
896
|
-
return
|
|
1073
|
+
return result;
|
|
897
1074
|
}
|
|
898
1075
|
|
|
899
1076
|
JSValue js_el_getAttributeNames(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
@@ -966,6 +1143,17 @@ JSValue js_el_toggleAttribute(JSContext* ctx, JSValue this_val, int argc, JSValu
|
|
|
966
1143
|
return JS_NewBool(ctx, force_present);
|
|
967
1144
|
}
|
|
968
1145
|
|
|
1146
|
+
JSValue js_el_getBoundingClientRect(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
1147
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
1148
|
+
if (!el) return JS_NULL;
|
|
1149
|
+
JSValue rect = JS_NewObject(ctx);
|
|
1150
|
+
static const char* kFields[] = { "x", "y", "width", "height", "top", "right", "bottom", "left" };
|
|
1151
|
+
for (const char* field : kFields) {
|
|
1152
|
+
JS_SetPropertyStr(ctx, rect, field, JS_NewInt32(ctx, 0));
|
|
1153
|
+
}
|
|
1154
|
+
return rect;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
969
1157
|
JSValue js_el_isSameNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
970
1158
|
auto* node = unwrap_node(ctx, this_val);
|
|
971
1159
|
if (!node || argc < 1) return JS_FALSE;
|
|
@@ -1022,6 +1210,37 @@ JSValue js_el_isEqualNode(JSContext* ctx, JSValue this_val, int argc, JSValue* a
|
|
|
1022
1210
|
return JS_NewBool(ctx, nodes_equal(node, other));
|
|
1023
1211
|
}
|
|
1024
1212
|
|
|
1213
|
+
void collect_text_descendants(lxb_dom_node_t* node, std::vector<void*>& out) {
|
|
1214
|
+
for (lxb_dom_node_t* child = node->first_child; child; child = child->next) {
|
|
1215
|
+
if (child->type == LXB_DOM_NODE_TYPE_TEXT) out.push_back(child);
|
|
1216
|
+
else if (child->first_child) collect_text_descendants(child, out);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
JSValue js_el_normalize(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
1221
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1222
|
+
if (!node) return JS_UNDEFINED;
|
|
1223
|
+
|
|
1224
|
+
// normalize() only merges/drops Text nodes (leaves, no children of their
|
|
1225
|
+
// own), so a flat invalidate is enough — no need for the *_deep variant.
|
|
1226
|
+
std::vector<void*> text_nodes;
|
|
1227
|
+
collect_text_descendants(node, text_nodes);
|
|
1228
|
+
if (!text_nodes.empty()) {
|
|
1229
|
+
invalidate_node_cache_batch(ctx, get_ctx(ctx), text_nodes);
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
get_doc(ctx)->normalize(node);
|
|
1233
|
+
return JS_UNDEFINED;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
JSValue js_el_compareDocumentPosition(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
1237
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1238
|
+
if (!node || argc < 1) return JS_NewInt32(ctx, 0);
|
|
1239
|
+
auto* other = unwrap_node(ctx, argv[0]);
|
|
1240
|
+
if (!other) return JS_NewInt32(ctx, 0);
|
|
1241
|
+
return JS_NewInt32(ctx, get_doc(ctx)->compareDocumentPosition(node, other));
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1025
1244
|
} // namespace
|
|
1026
1245
|
|
|
1027
1246
|
void ElementBindings::install(JSContext* ctx) {
|
|
@@ -1048,6 +1267,8 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1048
1267
|
JS_SetPropertyStr(ctx, node_proto, "replaceWith", JS_NewCFunction(ctx, js_el_replaceWith, "replaceWith", 0));
|
|
1049
1268
|
JS_SetPropertyStr(ctx, node_proto, "isSameNode", JS_NewCFunction(ctx, js_el_isSameNode, "isSameNode", 1));
|
|
1050
1269
|
JS_SetPropertyStr(ctx, node_proto, "isEqualNode", JS_NewCFunction(ctx, js_el_isEqualNode, "isEqualNode", 1));
|
|
1270
|
+
JS_SetPropertyStr(ctx, node_proto, "normalize", JS_NewCFunction(ctx, js_el_normalize, "normalize", 0));
|
|
1271
|
+
JS_SetPropertyStr(ctx, node_proto, "compareDocumentPosition", JS_NewCFunction(ctx, js_el_compareDocumentPosition, "compareDocumentPosition", 1));
|
|
1051
1272
|
|
|
1052
1273
|
define_prop(ctx, node_proto, "nodeType", js_el_get_nodeType, nullptr);
|
|
1053
1274
|
define_prop(ctx, node_proto, "nodeName", js_el_get_nodeName, nullptr);
|
|
@@ -1083,10 +1304,13 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1083
1304
|
JS_SetPropertyStr(ctx, proto, "insertAdjacentHTML", JS_NewCFunction(ctx, js_el_insertAdjacentHTML, "insertAdjacentHTML", 2));
|
|
1084
1305
|
JS_SetPropertyStr(ctx, proto, "append", JS_NewCFunction(ctx, js_el_append, "append", 0));
|
|
1085
1306
|
JS_SetPropertyStr(ctx, proto, "prepend", JS_NewCFunction(ctx, js_el_prepend, "prepend", 0));
|
|
1307
|
+
JS_SetPropertyStr(ctx, proto, "getBoundingClientRect", JS_NewCFunction(ctx, js_el_getBoundingClientRect, "getBoundingClientRect", 0));
|
|
1086
1308
|
|
|
1087
1309
|
define_prop(ctx, proto, "tagName", js_el_get_tagName, nullptr);
|
|
1088
1310
|
define_prop(ctx, proto, "id", js_el_get_id, js_el_set_id);
|
|
1089
1311
|
define_prop(ctx, proto, "className", js_el_get_className, js_el_set_className);
|
|
1312
|
+
define_prop(ctx, proto, "value", js_el_get_value, js_el_set_value);
|
|
1313
|
+
define_prop(ctx, proto, "checked", js_el_get_checked, js_el_set_checked);
|
|
1090
1314
|
define_prop(ctx, proto, "innerHTML", js_el_get_innerHTML, js_el_set_innerHTML);
|
|
1091
1315
|
define_prop(ctx, proto, "outerHTML", js_el_get_outerHTML, nullptr);
|
|
1092
1316
|
define_prop(ctx, proto, "children", js_el_get_children, nullptr);
|
|
@@ -1101,6 +1325,18 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1101
1325
|
define_prop(ctx, proto, "attributes", js_el_get_attributes, nullptr);
|
|
1102
1326
|
|
|
1103
1327
|
JS_SetClassProto(ctx, js_element_class_id, proto);
|
|
1328
|
+
|
|
1329
|
+
JSValue node_proto_ref = JS_GetClassProto(ctx, js_node_class_id);
|
|
1330
|
+
JSValue element_proto_ref = JS_GetClassProto(ctx, js_element_class_id);
|
|
1331
|
+
|
|
1332
|
+
JS_FreeValue(ctx, define_global_constructor(ctx, "Node", node_proto_ref));
|
|
1333
|
+
JSValue element_ctor = define_global_constructor(ctx, "Element", element_proto_ref);
|
|
1334
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
1335
|
+
JS_SetPropertyStr(ctx, global, "HTMLElement", element_ctor);
|
|
1336
|
+
JS_FreeValue(ctx, global);
|
|
1337
|
+
|
|
1338
|
+
JS_FreeValue(ctx, node_proto_ref);
|
|
1339
|
+
JS_FreeValue(ctx, element_proto_ref);
|
|
1104
1340
|
}
|
|
1105
1341
|
|
|
1106
1342
|
} // namespace margelo::nitro::nitrojsdom
|