@salve-software/react-native-nitro-jsdom 1.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/LICENSE +21 -0
- package/NitroJsdom.podspec +63 -0
- package/README.md +95 -0
- package/android/CMakeLists.txt +86 -0
- package/android/build.gradle +148 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/nitrojsdom/NitroJsdomPackage.kt +20 -0
- package/cpp/HybridHtmlSandbox.cpp +171 -0
- package/cpp/HybridHtmlSandbox.hpp +47 -0
- package/cpp/lexbor/LexborDocument.cpp +365 -0
- package/cpp/lexbor/LexborDocument.hpp +71 -0
- package/cpp/quickjs/DOMBindings.cpp +57 -0
- package/cpp/quickjs/DOMBindings.hpp +21 -0
- package/cpp/quickjs/DOMBindingsInternal.cpp +157 -0
- package/cpp/quickjs/DOMBindingsInternal.hpp +83 -0
- package/cpp/quickjs/MutationObservers.cpp +546 -0
- package/cpp/quickjs/MutationObservers.hpp +127 -0
- package/cpp/quickjs/QuickJSRuntime.cpp +475 -0
- package/cpp/quickjs/QuickJSRuntime.hpp +110 -0
- package/cpp/quickjs/Storage.cpp +130 -0
- package/cpp/quickjs/Storage.hpp +40 -0
- package/cpp/quickjs/bindings/ClassListBindings.cpp +241 -0
- package/cpp/quickjs/bindings/ClassListBindings.hpp +29 -0
- package/cpp/quickjs/bindings/DatasetBindings.cpp +120 -0
- package/cpp/quickjs/bindings/DatasetBindings.hpp +20 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +155 -0
- package/cpp/quickjs/bindings/DocumentBindings.hpp +19 -0
- package/cpp/quickjs/bindings/ElementBindings.cpp +1106 -0
- package/cpp/quickjs/bindings/ElementBindings.hpp +19 -0
- package/cpp/quickjs/bindings/EventBindings.cpp +352 -0
- package/cpp/quickjs/bindings/EventBindings.hpp +21 -0
- package/cpp/quickjs/bindings/FetchBindings.cpp +347 -0
- package/cpp/quickjs/bindings/FetchBindings.hpp +16 -0
- package/cpp/quickjs/bindings/StyleBindings.cpp +245 -0
- package/cpp/quickjs/bindings/StyleBindings.hpp +22 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +94 -0
- package/cpp/quickjs/bindings/TimerBindings.hpp +16 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +211 -0
- package/cpp/quickjs/bindings/WindowBindings.hpp +16 -0
- package/ios/Bridge.h +8 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +138 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/index.js +17 -0
- package/lib/commonjs/classes/JSDOM/index.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/types/IFetchOptions.js +2 -0
- package/lib/commonjs/classes/JSDOM/types/IFetchOptions.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/types/IJSDOMOptions.js +6 -0
- package/lib/commonjs/classes/JSDOM/types/IJSDOMOptions.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/types/index.js +2 -0
- package/lib/commonjs/classes/JSDOM/types/index.js.map +1 -0
- package/lib/commonjs/classes/index.js +17 -0
- package/lib/commonjs/classes/index.js.map +1 -0
- package/lib/commonjs/index.js +17 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/HtmlSandbox.nitro.js +6 -0
- package/lib/commonjs/specs/HtmlSandbox.nitro.js.map +1 -0
- package/lib/module/classes/JSDOM/JSDOM.class.js +133 -0
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -0
- package/lib/module/classes/JSDOM/index.js +4 -0
- package/lib/module/classes/JSDOM/index.js.map +1 -0
- package/lib/module/classes/JSDOM/types/IFetchOptions.js +2 -0
- package/lib/module/classes/JSDOM/types/IFetchOptions.js.map +1 -0
- package/lib/module/classes/JSDOM/types/IJSDOMOptions.js +4 -0
- package/lib/module/classes/JSDOM/types/IJSDOMOptions.js.map +1 -0
- package/lib/module/classes/JSDOM/types/index.js +2 -0
- package/lib/module/classes/JSDOM/types/index.js.map +1 -0
- package/lib/module/classes/index.js +4 -0
- package/lib/module/classes/index.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/specs/HtmlSandbox.nitro.js +4 -0
- package/lib/module/specs/HtmlSandbox.nitro.js.map +1 -0
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts +74 -0
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/index.d.ts +3 -0
- package/lib/typescript/src/classes/JSDOM/index.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/types/IFetchOptions.d.ts +14 -0
- package/lib/typescript/src/classes/JSDOM/types/IFetchOptions.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts +57 -0
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/types/index.d.ts +3 -0
- package/lib/typescript/src/classes/JSDOM/types/index.d.ts.map +1 -0
- package/lib/typescript/src/classes/index.d.ts +2 -0
- package/lib/typescript/src/classes/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +13 -0
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -0
- package/nitro.json +26 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroJsdom+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroJsdom+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroJsdomOnLoad.cpp +49 -0
- package/nitrogen/generated/android/NitroJsdomOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrojsdom/NitroJsdomOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroJsdom+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroJsdomAutolinking.mm +35 -0
- package/nitrogen/generated/ios/NitroJsdomAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +73 -0
- package/package.json +131 -0
- package/src/classes/JSDOM/JSDOM.class.ts +145 -0
- package/src/classes/JSDOM/index.ts +2 -0
- package/src/classes/JSDOM/types/IFetchOptions.ts +14 -0
- package/src/classes/JSDOM/types/IJSDOMOptions.ts +57 -0
- package/src/classes/JSDOM/types/index.ts +2 -0
- package/src/classes/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +33 -0
|
@@ -0,0 +1,1106 @@
|
|
|
1
|
+
#include "ElementBindings.hpp"
|
|
2
|
+
#include "ClassListBindings.hpp"
|
|
3
|
+
#include "DatasetBindings.hpp"
|
|
4
|
+
#include "StyleBindings.hpp"
|
|
5
|
+
#include "../DOMBindingsInternal.hpp"
|
|
6
|
+
#include "../QuickJSRuntime.hpp"
|
|
7
|
+
#include "../MutationObservers.hpp"
|
|
8
|
+
#include "../../lexbor/LexborDocument.hpp"
|
|
9
|
+
#include <lexbor/html/html.h>
|
|
10
|
+
#include <lexbor/dom/dom.h>
|
|
11
|
+
#include <lexbor/dom/interfaces/character_data.h>
|
|
12
|
+
#include <cctype>
|
|
13
|
+
#include <cstring>
|
|
14
|
+
#include <optional>
|
|
15
|
+
#include <string>
|
|
16
|
+
|
|
17
|
+
namespace margelo::nitro::nitrojsdom {
|
|
18
|
+
|
|
19
|
+
namespace {
|
|
20
|
+
|
|
21
|
+
JSClassDef js_element_class = { "Element", .finalizer = nullptr };
|
|
22
|
+
JSClassDef js_node_class = { "Node", .finalizer = nullptr };
|
|
23
|
+
|
|
24
|
+
// ── Element property getters/setters ─────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
JSValue js_el_get_tagName(JSContext* ctx, JSValue this_val) {
|
|
27
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
28
|
+
if (!el) return JS_NewString(ctx, "");
|
|
29
|
+
size_t len = 0;
|
|
30
|
+
const lxb_char_t* name = lxb_dom_element_local_name(el, &len);
|
|
31
|
+
if (!name || len == 0) return JS_NewString(ctx, "");
|
|
32
|
+
std::string r(reinterpret_cast<const char*>(name), len);
|
|
33
|
+
for (auto& c : r) c = (char)std::toupper((unsigned char)c);
|
|
34
|
+
return JS_NewStringLen(ctx, r.c_str(), r.size());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
JSValue js_el_get_id(JSContext* ctx, JSValue this_val) {
|
|
38
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
39
|
+
if (!el) return JS_NewString(ctx, "");
|
|
40
|
+
size_t len = 0;
|
|
41
|
+
const lxb_char_t* val = lxb_dom_element_get_attribute(el,
|
|
42
|
+
reinterpret_cast<const lxb_char_t*>("id"), 2, &len);
|
|
43
|
+
return val ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len) : JS_NewString(ctx, "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
JSValue js_el_set_id(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
47
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
48
|
+
if (!el) return JS_UNDEFINED;
|
|
49
|
+
const char* str = JS_ToCString(ctx, val);
|
|
50
|
+
if (str) {
|
|
51
|
+
auto* rctx = get_ctx(ctx);
|
|
52
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
53
|
+
|
|
54
|
+
std::optional<std::string> old_val;
|
|
55
|
+
if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
56
|
+
size_t len = 0;
|
|
57
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el,
|
|
58
|
+
reinterpret_cast<const lxb_char_t*>("id"), 2, &len);
|
|
59
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
lxb_dom_element_set_attribute(el,
|
|
63
|
+
reinterpret_cast<const lxb_char_t*>("id"), 2,
|
|
64
|
+
reinterpret_cast<const lxb_char_t*>(str), strlen(str));
|
|
65
|
+
JS_FreeCString(ctx, str);
|
|
66
|
+
|
|
67
|
+
if (has_obs) {
|
|
68
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el),
|
|
69
|
+
"id", old_val);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return JS_UNDEFINED;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
JSValue js_el_get_className(JSContext* ctx, JSValue this_val) {
|
|
76
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
77
|
+
if (!el) return JS_NewString(ctx, "");
|
|
78
|
+
return JS_NewString(ctx, get_class_attr(el).c_str());
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
JSValue js_el_set_className(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
82
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
83
|
+
if (!el) return JS_UNDEFINED;
|
|
84
|
+
const char* str = JS_ToCString(ctx, val);
|
|
85
|
+
if (str) {
|
|
86
|
+
auto* rctx = get_ctx(ctx);
|
|
87
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
88
|
+
std::optional<std::string> old_val;
|
|
89
|
+
if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver())
|
|
90
|
+
old_val = get_class_attr(el);
|
|
91
|
+
|
|
92
|
+
set_class_attr(el, str);
|
|
93
|
+
JS_FreeCString(ctx, str);
|
|
94
|
+
|
|
95
|
+
if (has_obs) {
|
|
96
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el),
|
|
97
|
+
"class", old_val);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return JS_UNDEFINED;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val) {
|
|
104
|
+
lxb_dom_node_t* node = unwrap_node(ctx, this_val);
|
|
105
|
+
if (!node) return JS_NewString(ctx, "");
|
|
106
|
+
size_t len = 0;
|
|
107
|
+
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
108
|
+
if (!text) return JS_NewString(ctx, "");
|
|
109
|
+
JSValue result = JS_NewStringLen(ctx, reinterpret_cast<char*>(text), len);
|
|
110
|
+
lxb_dom_document_destroy_text(node->owner_document, text);
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
JSValue js_el_set_textContent(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
115
|
+
lxb_dom_node_t* node = unwrap_node(ctx, this_val);
|
|
116
|
+
if (!node) return JS_UNDEFINED;
|
|
117
|
+
const char* str = JS_ToCString(ctx, val);
|
|
118
|
+
if (!str) return JS_UNDEFINED;
|
|
119
|
+
|
|
120
|
+
auto* rctx = get_ctx(ctx);
|
|
121
|
+
bool has_observers = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
122
|
+
|
|
123
|
+
if (node->type == LXB_DOM_NODE_TYPE_TEXT ||
|
|
124
|
+
node->type == LXB_DOM_NODE_TYPE_COMMENT) {
|
|
125
|
+
std::optional<std::string> old_val;
|
|
126
|
+
if (has_observers && rctx->mutation_observers->hasCharacterDataOldValueObserver()) {
|
|
127
|
+
auto* cd = reinterpret_cast<lxb_dom_character_data_t*>(node);
|
|
128
|
+
old_val = std::string(reinterpret_cast<const char*>(cd->data.data), cd->data.length);
|
|
129
|
+
}
|
|
130
|
+
auto* cd = reinterpret_cast<lxb_dom_character_data_t*>(node);
|
|
131
|
+
lxb_dom_character_data_replace(cd, reinterpret_cast<const lxb_char_t*>(str), strlen(str), 0, cd->data.length);
|
|
132
|
+
if (has_observers) {
|
|
133
|
+
rctx->mutation_observers->notifyCharacterData(ctx, node, old_val);
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
auto* el = reinterpret_cast<lxb_dom_element_t*>(node);
|
|
137
|
+
// Element node: textContent replaces all children — fire childList.
|
|
138
|
+
// Collect old children BEFORE destruction to disconnect their observers,
|
|
139
|
+
// but do NOT forward the raw pointers to notifyChildList: setTextContentOnEl
|
|
140
|
+
// frees those nodes, so passing them into the async dispatch job would be
|
|
141
|
+
// a use-after-free. We report removed={} (nodes already gone by dispatch time).
|
|
142
|
+
{
|
|
143
|
+
std::vector<void*> old_children;
|
|
144
|
+
lxb_dom_node_t* child = node->first_child;
|
|
145
|
+
while (child) { old_children.push_back(child); child = child->next; }
|
|
146
|
+
if (!old_children.empty()) {
|
|
147
|
+
invalidate_node_cache_batch(ctx, rctx, old_children);
|
|
148
|
+
if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
get_doc(ctx)->setTextContentOnEl(el, str);
|
|
152
|
+
if (has_observers) {
|
|
153
|
+
std::vector<void*> new_children;
|
|
154
|
+
lxb_dom_node_t* child = node->first_child;
|
|
155
|
+
while (child) { new_children.push_back(child); child = child->next; }
|
|
156
|
+
rctx->mutation_observers->notifyChildList(ctx, node,
|
|
157
|
+
new_children, {}, nullptr, nullptr);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
JS_FreeCString(ctx, str);
|
|
162
|
+
return JS_UNDEFINED;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
JSValue js_el_get_innerHTML(JSContext* ctx, JSValue this_val) {
|
|
166
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
167
|
+
if (!el) return JS_NewString(ctx, "");
|
|
168
|
+
std::string result;
|
|
169
|
+
lxb_dom_node_t* child = lxb_dom_interface_node(el)->first_child;
|
|
170
|
+
while (child) { result += serialize_node(child); child = child->next; }
|
|
171
|
+
return JS_NewString(ctx, result.c_str());
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
JSValue js_el_set_innerHTML(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
175
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
176
|
+
if (!el) return JS_UNDEFINED;
|
|
177
|
+
const char* html = JS_ToCString(ctx, val);
|
|
178
|
+
if (!html) return JS_UNDEFINED;
|
|
179
|
+
|
|
180
|
+
auto* rctx = get_ctx(ctx);
|
|
181
|
+
bool has_observers = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
182
|
+
|
|
183
|
+
lxb_dom_node_t* parent = lxb_dom_interface_node(el);
|
|
184
|
+
|
|
185
|
+
std::vector<void*> old_children;
|
|
186
|
+
{
|
|
187
|
+
lxb_dom_node_t* child = parent->first_child;
|
|
188
|
+
while (child) { old_children.push_back(child); child = child->next; }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!old_children.empty()) {
|
|
192
|
+
invalidate_node_cache_batch(ctx, rctx, old_children);
|
|
193
|
+
if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
get_doc(ctx)->setInnerHTMLOnEl(el, html);
|
|
197
|
+
JS_FreeCString(ctx, html);
|
|
198
|
+
|
|
199
|
+
if (has_observers) {
|
|
200
|
+
std::vector<void*> new_children;
|
|
201
|
+
lxb_dom_node_t* child = parent->first_child;
|
|
202
|
+
while (child) { new_children.push_back(child); child = child->next; }
|
|
203
|
+
rctx->mutation_observers->notifyChildList(ctx, parent,
|
|
204
|
+
new_children, old_children, nullptr, nullptr);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return JS_UNDEFINED;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
JSValue js_el_get_outerHTML(JSContext* ctx, JSValue this_val) {
|
|
211
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
212
|
+
if (!el) return JS_NewString(ctx, "");
|
|
213
|
+
return JS_NewString(ctx, serialize_node(lxb_dom_interface_node(el)).c_str());
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
JSValue js_el_get_parentElement(JSContext* ctx, JSValue this_val) {
|
|
217
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
218
|
+
if (!node) return JS_NULL;
|
|
219
|
+
lxb_dom_node_t* parent = node->parent;
|
|
220
|
+
if (!parent || parent->type != LXB_DOM_NODE_TYPE_ELEMENT) return JS_NULL;
|
|
221
|
+
return make_element(ctx, lxb_dom_interface_element(parent));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
JSValue js_el_get_children(JSContext* ctx, JSValue this_val) {
|
|
225
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
226
|
+
JSValue arr = JS_NewArray(ctx);
|
|
227
|
+
if (!el) return arr;
|
|
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;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
JSValue js_el_get_firstElementChild(JSContext* ctx, JSValue this_val) {
|
|
239
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
240
|
+
if (!el) return JS_NULL;
|
|
241
|
+
lxb_dom_node_t* n = lxb_dom_interface_node(el)->first_child;
|
|
242
|
+
while (n && n->type != LXB_DOM_NODE_TYPE_ELEMENT) n = n->next;
|
|
243
|
+
return n ? make_element(ctx, lxb_dom_interface_element(n)) : JS_NULL;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
JSValue js_el_get_lastElementChild(JSContext* ctx, JSValue this_val) {
|
|
247
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
248
|
+
if (!el) return JS_NULL;
|
|
249
|
+
lxb_dom_node_t* n = lxb_dom_interface_node(el)->last_child;
|
|
250
|
+
while (n && n->type != LXB_DOM_NODE_TYPE_ELEMENT) n = n->prev;
|
|
251
|
+
return n ? make_element(ctx, lxb_dom_interface_element(n)) : JS_NULL;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
JSValue js_el_get_nextElementSibling(JSContext* ctx, JSValue this_val) {
|
|
255
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
256
|
+
if (!el) return JS_NULL;
|
|
257
|
+
lxb_dom_node_t* n = lxb_dom_interface_node(el)->next;
|
|
258
|
+
while (n && n->type != LXB_DOM_NODE_TYPE_ELEMENT) n = n->next;
|
|
259
|
+
return n ? make_element(ctx, lxb_dom_interface_element(n)) : JS_NULL;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
JSValue js_el_get_prevElementSibling(JSContext* ctx, JSValue this_val) {
|
|
263
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
264
|
+
if (!el) return JS_NULL;
|
|
265
|
+
lxb_dom_node_t* n = lxb_dom_interface_node(el)->prev;
|
|
266
|
+
while (n && n->type != LXB_DOM_NODE_TYPE_ELEMENT) n = n->prev;
|
|
267
|
+
return n ? make_element(ctx, lxb_dom_interface_element(n)) : JS_NULL;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
JSValue js_el_get_classList(JSContext* ctx, JSValue this_val) {
|
|
271
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
272
|
+
if (!el) return JS_NULL;
|
|
273
|
+
return ClassListBindings::make(ctx, el);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
JSValue js_el_get_dataset(JSContext* ctx, JSValue this_val) {
|
|
277
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
278
|
+
if (!el) return JS_NULL;
|
|
279
|
+
return DatasetBindings::make(ctx, el);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
JSValue js_el_get_style(JSContext* ctx, JSValue this_val) {
|
|
283
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
284
|
+
if (!el) return JS_NULL;
|
|
285
|
+
return StyleBindings::make(ctx, el);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
JSValue js_el_get_childElementCount(JSContext* ctx, JSValue this_val) {
|
|
289
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
290
|
+
if (!el) return JS_NewInt32(ctx, 0);
|
|
291
|
+
int count = 0;
|
|
292
|
+
lxb_dom_node_t* child = lxb_dom_interface_node(el)->first_child;
|
|
293
|
+
while (child) { if (child->type == LXB_DOM_NODE_TYPE_ELEMENT) count++; child = child->next; }
|
|
294
|
+
return JS_NewInt32(ctx, count);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ── Generic Node traversal getters ────────────────────────────────────────────
|
|
298
|
+
// These operate on any lxb_dom_node_t-derived pointer (element, text, comment,
|
|
299
|
+
// document), since every such struct starts with an lxb_dom_node_t member.
|
|
300
|
+
|
|
301
|
+
JSValue js_el_get_nodeType(JSContext* ctx, JSValue this_val) {
|
|
302
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
303
|
+
if (!node) return JS_NewInt32(ctx, 0);
|
|
304
|
+
return JS_NewInt32(ctx, (int32_t)node->type);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
JSValue js_el_get_nodeName(JSContext* ctx, JSValue this_val) {
|
|
308
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
309
|
+
if (!node) return JS_NewString(ctx, "");
|
|
310
|
+
switch (node->type) {
|
|
311
|
+
case LXB_DOM_NODE_TYPE_ELEMENT: return js_el_get_tagName(ctx, this_val);
|
|
312
|
+
case LXB_DOM_NODE_TYPE_TEXT: return JS_NewString(ctx, "#text");
|
|
313
|
+
case LXB_DOM_NODE_TYPE_COMMENT: return JS_NewString(ctx, "#comment");
|
|
314
|
+
case LXB_DOM_NODE_TYPE_DOCUMENT: return JS_NewString(ctx, "#document");
|
|
315
|
+
case LXB_DOM_NODE_TYPE_DOCUMENT_FRAGMENT: return JS_NewString(ctx, "#document-fragment");
|
|
316
|
+
default: return JS_NewString(ctx, "");
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
JSValue js_el_get_nodeValue(JSContext* ctx, JSValue this_val) {
|
|
321
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
322
|
+
if (!node) return JS_NULL;
|
|
323
|
+
if (node->type != LXB_DOM_NODE_TYPE_TEXT && node->type != LXB_DOM_NODE_TYPE_COMMENT) return JS_NULL;
|
|
324
|
+
auto* cd = reinterpret_cast<lxb_dom_character_data_t*>(node);
|
|
325
|
+
return JS_NewStringLen(ctx, reinterpret_cast<const char*>(cd->data.data), cd->data.length);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
JSValue js_el_set_nodeValue(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
329
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
330
|
+
if (!node) return JS_UNDEFINED;
|
|
331
|
+
if (node->type != LXB_DOM_NODE_TYPE_TEXT && node->type != LXB_DOM_NODE_TYPE_COMMENT) return JS_UNDEFINED;
|
|
332
|
+
const char* str = JS_ToCString(ctx, val);
|
|
333
|
+
if (!str) return JS_UNDEFINED;
|
|
334
|
+
|
|
335
|
+
auto* rctx = get_ctx(ctx);
|
|
336
|
+
bool has_observers = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
337
|
+
std::optional<std::string> old_val;
|
|
338
|
+
auto* cd = reinterpret_cast<lxb_dom_character_data_t*>(node);
|
|
339
|
+
if (has_observers && rctx->mutation_observers->hasCharacterDataOldValueObserver())
|
|
340
|
+
old_val = std::string(reinterpret_cast<const char*>(cd->data.data), cd->data.length);
|
|
341
|
+
|
|
342
|
+
lxb_dom_character_data_replace(cd, reinterpret_cast<const lxb_char_t*>(str), strlen(str), 0, cd->data.length);
|
|
343
|
+
JS_FreeCString(ctx, str);
|
|
344
|
+
|
|
345
|
+
if (has_observers) rctx->mutation_observers->notifyCharacterData(ctx, node, old_val);
|
|
346
|
+
return JS_UNDEFINED;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
JSValue js_el_get_childNodes(JSContext* ctx, JSValue this_val) {
|
|
350
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
351
|
+
JSValue arr = JS_NewArray(ctx);
|
|
352
|
+
if (!node) return arr;
|
|
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;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
JSValue js_el_get_firstChild(JSContext* ctx, JSValue this_val) {
|
|
360
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
361
|
+
if (!node) return JS_NULL;
|
|
362
|
+
return make_element(ctx, node->first_child);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
JSValue js_el_get_lastChild(JSContext* ctx, JSValue this_val) {
|
|
366
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
367
|
+
if (!node) return JS_NULL;
|
|
368
|
+
return make_element(ctx, node->last_child);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
JSValue js_el_get_nextSibling(JSContext* ctx, JSValue this_val) {
|
|
372
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
373
|
+
if (!node) return JS_NULL;
|
|
374
|
+
return make_element(ctx, node->next);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
JSValue js_el_get_previousSibling(JSContext* ctx, JSValue this_val) {
|
|
378
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
379
|
+
if (!node) return JS_NULL;
|
|
380
|
+
return make_element(ctx, node->prev);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
JSValue js_el_get_parentNode(JSContext* ctx, JSValue this_val) {
|
|
384
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
385
|
+
if (!node) return JS_NULL;
|
|
386
|
+
return make_element(ctx, node->parent);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── Element methods ───────────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
JSValue js_el_getAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
392
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
393
|
+
if (!el || argc < 1) return JS_NULL;
|
|
394
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
395
|
+
if (!name) return JS_NULL;
|
|
396
|
+
size_t val_len = 0;
|
|
397
|
+
const lxb_char_t* val = lxb_dom_element_get_attribute(el,
|
|
398
|
+
reinterpret_cast<const lxb_char_t*>(name), strlen(name), &val_len);
|
|
399
|
+
JS_FreeCString(ctx, name);
|
|
400
|
+
return val ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), val_len) : JS_NULL;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
JSValue js_el_setAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
404
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
405
|
+
if (!el || argc < 2) return JS_UNDEFINED;
|
|
406
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
407
|
+
const char* value = JS_ToCString(ctx, argv[1]);
|
|
408
|
+
if (name && value) {
|
|
409
|
+
auto* rctx = get_ctx(ctx);
|
|
410
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
411
|
+
|
|
412
|
+
std::optional<std::string> old_val;
|
|
413
|
+
if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
414
|
+
size_t len = 0;
|
|
415
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el,
|
|
416
|
+
reinterpret_cast<const lxb_char_t*>(name), strlen(name), &len);
|
|
417
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
lxb_dom_element_set_attribute(el,
|
|
421
|
+
reinterpret_cast<const lxb_char_t*>(name), strlen(name),
|
|
422
|
+
reinterpret_cast<const lxb_char_t*>(value), strlen(value));
|
|
423
|
+
|
|
424
|
+
if (has_obs) {
|
|
425
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el),
|
|
426
|
+
std::string(name), old_val);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (name) JS_FreeCString(ctx, name);
|
|
430
|
+
if (value) JS_FreeCString(ctx, value);
|
|
431
|
+
return JS_UNDEFINED;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
JSValue js_el_removeAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
435
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
436
|
+
if (!el || argc < 1) return JS_UNDEFINED;
|
|
437
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
438
|
+
if (name) {
|
|
439
|
+
auto* rctx = get_ctx(ctx);
|
|
440
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
441
|
+
|
|
442
|
+
std::optional<std::string> old_val;
|
|
443
|
+
if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
444
|
+
size_t len = 0;
|
|
445
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el,
|
|
446
|
+
reinterpret_cast<const lxb_char_t*>(name), strlen(name), &len);
|
|
447
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
lxb_dom_element_remove_attribute(el,
|
|
451
|
+
reinterpret_cast<const lxb_char_t*>(name), strlen(name));
|
|
452
|
+
|
|
453
|
+
if (has_obs) {
|
|
454
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el),
|
|
455
|
+
std::string(name), old_val);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
JS_FreeCString(ctx, name);
|
|
459
|
+
}
|
|
460
|
+
return JS_UNDEFINED;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
JSValue js_el_hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
464
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
465
|
+
if (!el || argc < 1) return JS_FALSE;
|
|
466
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
467
|
+
if (!name) return JS_FALSE;
|
|
468
|
+
bool has = lxb_dom_element_has_attribute(el,
|
|
469
|
+
reinterpret_cast<const lxb_char_t*>(name), strlen(name));
|
|
470
|
+
JS_FreeCString(ctx, name);
|
|
471
|
+
return JS_NewBool(ctx, has);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Inserts `node` via `insert_one`, expanding DocumentFragments into their
|
|
475
|
+
// children first (per the DOM's "insert a node" algorithm — a fragment is
|
|
476
|
+
// never itself part of the resulting tree, only its children are moved in).
|
|
477
|
+
template <typename InsertOneFn>
|
|
478
|
+
std::vector<lxb_dom_node_t*> expand_and_insert(lxb_dom_node_t* node, InsertOneFn insert_one) {
|
|
479
|
+
std::vector<lxb_dom_node_t*> inserted;
|
|
480
|
+
if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT_FRAGMENT) {
|
|
481
|
+
while (node->first_child) {
|
|
482
|
+
lxb_dom_node_t* child = node->first_child;
|
|
483
|
+
lxb_dom_node_remove(child);
|
|
484
|
+
insert_one(child);
|
|
485
|
+
inserted.push_back(child);
|
|
486
|
+
}
|
|
487
|
+
} else {
|
|
488
|
+
insert_one(node);
|
|
489
|
+
inserted.push_back(node);
|
|
490
|
+
}
|
|
491
|
+
return inserted;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
JSValue js_el_appendChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
495
|
+
auto* parent_node = unwrap_node(ctx, this_val);
|
|
496
|
+
if (!parent_node || argc < 1) return JS_NULL;
|
|
497
|
+
auto* child_node = unwrap_node(ctx, argv[0]);
|
|
498
|
+
if (!child_node) return JS_NULL;
|
|
499
|
+
|
|
500
|
+
auto inserted = expand_and_insert(child_node, [&](lxb_dom_node_t* n) {
|
|
501
|
+
lxb_dom_node_insert_child(parent_node, n);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
auto* rctx = get_ctx(ctx);
|
|
505
|
+
if (!inserted.empty() && rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
506
|
+
std::vector<void*> insertedVoid(inserted.begin(), inserted.end());
|
|
507
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node,
|
|
508
|
+
insertedVoid, {}, inserted.front()->prev, nullptr);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return JS_DupValue(ctx, argv[0]);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
JSValue js_el_removeChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
515
|
+
auto* parent_node = unwrap_node(ctx, this_val);
|
|
516
|
+
if (!parent_node || argc < 1) return JS_NULL;
|
|
517
|
+
auto* cn = unwrap_node(ctx, argv[0]);
|
|
518
|
+
if (!cn) return JS_NULL;
|
|
519
|
+
if (cn->parent == parent_node) {
|
|
520
|
+
lxb_dom_node_t* prev_sib = cn->prev;
|
|
521
|
+
lxb_dom_node_t* next_sib = cn->next;
|
|
522
|
+
invalidate_node_cache(ctx, get_ctx(ctx), cn);
|
|
523
|
+
lxb_dom_node_remove(cn);
|
|
524
|
+
|
|
525
|
+
auto* rctx = get_ctx(ctx);
|
|
526
|
+
if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
527
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node,
|
|
528
|
+
{}, { cn }, prev_sib, next_sib);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return JS_DupValue(ctx, argv[0]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
JSValue js_el_insertBefore(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
535
|
+
if (argc < 1) return JS_NULL;
|
|
536
|
+
auto* new_node = unwrap_node(ctx, argv[0]);
|
|
537
|
+
if (!new_node) return JS_NULL;
|
|
538
|
+
|
|
539
|
+
lxb_dom_node_t* parent_node = nullptr;
|
|
540
|
+
std::vector<lxb_dom_node_t*> inserted;
|
|
541
|
+
|
|
542
|
+
if (argc < 2 || JS_IsNull(argv[1])) {
|
|
543
|
+
parent_node = unwrap_node(ctx, this_val);
|
|
544
|
+
if (parent_node) {
|
|
545
|
+
inserted = expand_and_insert(new_node, [&](lxb_dom_node_t* n) {
|
|
546
|
+
lxb_dom_node_insert_child(parent_node, n);
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
auto* ref = unwrap_node(ctx, argv[1]);
|
|
551
|
+
if (ref) {
|
|
552
|
+
lxb_dom_node_t* receiver_node = unwrap_node(ctx, this_val);
|
|
553
|
+
if (!receiver_node || ref->parent != receiver_node) {
|
|
554
|
+
JS_ThrowTypeError(ctx, "NotFoundError: reference node is not a child of this node");
|
|
555
|
+
return JS_EXCEPTION;
|
|
556
|
+
}
|
|
557
|
+
parent_node = receiver_node;
|
|
558
|
+
inserted = expand_and_insert(new_node, [&](lxb_dom_node_t* n) {
|
|
559
|
+
lxb_dom_node_insert_before(ref, n);
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (parent_node && !inserted.empty()) {
|
|
565
|
+
auto* rctx = get_ctx(ctx);
|
|
566
|
+
if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
567
|
+
std::vector<void*> insertedVoid(inserted.begin(), inserted.end());
|
|
568
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node,
|
|
569
|
+
insertedVoid, {}, inserted.front()->prev, inserted.back()->next);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
return JS_DupValue(ctx, argv[0]);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
JSValue js_el_remove(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
577
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
578
|
+
if (el) {
|
|
579
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
580
|
+
lxb_dom_node_t* parent_node = node->parent;
|
|
581
|
+
lxb_dom_node_t* prev_sib = node->prev;
|
|
582
|
+
lxb_dom_node_t* next_sib = node->next;
|
|
583
|
+
lxb_dom_node_remove(node);
|
|
584
|
+
|
|
585
|
+
if (parent_node) {
|
|
586
|
+
auto* rctx = get_ctx(ctx);
|
|
587
|
+
if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
588
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node,
|
|
589
|
+
{}, { node }, prev_sib, next_sib);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return JS_UNDEFINED;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
JSValue js_el_cloneNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
597
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
598
|
+
if (!node) return JS_NULL;
|
|
599
|
+
bool deep = argc >= 1 && JS_ToBool(ctx, argv[0]) > 0;
|
|
600
|
+
lxb_dom_node_t* clone = lxb_dom_node_clone(node, deep);
|
|
601
|
+
return make_element(ctx, clone);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
JSValue js_el_contains(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
605
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
606
|
+
if (!node || argc < 1) return JS_FALSE;
|
|
607
|
+
auto* other = unwrap_node(ctx, argv[0]);
|
|
608
|
+
if (!other) return JS_FALSE;
|
|
609
|
+
for (lxb_dom_node_t* n = other; n; n = n->parent) {
|
|
610
|
+
if (n == node) return JS_TRUE;
|
|
611
|
+
}
|
|
612
|
+
return JS_FALSE;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
JSValue js_el_replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
616
|
+
auto* parent_node = unwrap_node(ctx, this_val);
|
|
617
|
+
if (!parent_node || argc < 2) return JS_NULL;
|
|
618
|
+
auto* new_node = unwrap_node(ctx, argv[0]);
|
|
619
|
+
auto* old_node = unwrap_node(ctx, argv[1]);
|
|
620
|
+
if (!new_node || !old_node) return JS_NULL;
|
|
621
|
+
if (old_node->parent != parent_node) {
|
|
622
|
+
JS_ThrowTypeError(ctx, "NotFoundError: the node to be replaced is not a child of this node");
|
|
623
|
+
return JS_EXCEPTION;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
lxb_dom_node_t* prev_sib = old_node->prev;
|
|
627
|
+
lxb_dom_node_t* next_sib = old_node->next;
|
|
628
|
+
|
|
629
|
+
auto inserted = expand_and_insert(new_node, [&](lxb_dom_node_t* n) {
|
|
630
|
+
lxb_dom_node_insert_before(old_node, n);
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
auto* rctx = get_ctx(ctx);
|
|
634
|
+
invalidate_node_cache(ctx, rctx, old_node);
|
|
635
|
+
lxb_dom_node_remove(old_node);
|
|
636
|
+
|
|
637
|
+
if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
638
|
+
std::vector<void*> insertedVoid(inserted.begin(), inserted.end());
|
|
639
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node,
|
|
640
|
+
insertedVoid, { old_node }, prev_sib, next_sib);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return JS_DupValue(ctx, argv[1]); // returns the replaced (old) child, per spec
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
lxb_dom_node_t* js_to_node_or_text(JSContext* ctx, JSValue val) {
|
|
647
|
+
auto* node = unwrap_node(ctx, val);
|
|
648
|
+
if (node) return node;
|
|
649
|
+
const char* str = JS_ToCString(ctx, val);
|
|
650
|
+
if (!str) return nullptr;
|
|
651
|
+
void* text = get_doc(ctx)->createTextNode(str);
|
|
652
|
+
JS_FreeCString(ctx, str);
|
|
653
|
+
return static_cast<lxb_dom_node_t*>(text);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
JSValue js_el_before(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
657
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
658
|
+
if (!node || !node->parent) return JS_UNDEFINED;
|
|
659
|
+
lxb_dom_node_t* parent = node->parent;
|
|
660
|
+
|
|
661
|
+
std::vector<void*> inserted;
|
|
662
|
+
for (int i = 0; i < argc; i++) {
|
|
663
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
664
|
+
if (!n) continue;
|
|
665
|
+
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
|
|
666
|
+
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
auto* rctx = get_ctx(ctx);
|
|
670
|
+
if (!inserted.empty() && rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
671
|
+
rctx->mutation_observers->notifyChildList(ctx, parent, inserted, {},
|
|
672
|
+
static_cast<lxb_dom_node_t*>(inserted.front())->prev, node);
|
|
673
|
+
}
|
|
674
|
+
return JS_UNDEFINED;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
JSValue js_el_after(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
678
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
679
|
+
if (!node || !node->parent) return JS_UNDEFINED;
|
|
680
|
+
lxb_dom_node_t* parent = node->parent;
|
|
681
|
+
lxb_dom_node_t* ref = node->next;
|
|
682
|
+
|
|
683
|
+
std::vector<void*> inserted;
|
|
684
|
+
for (int i = 0; i < argc; i++) {
|
|
685
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
686
|
+
if (!n) continue;
|
|
687
|
+
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
|
|
688
|
+
if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent, one);
|
|
689
|
+
});
|
|
690
|
+
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
auto* rctx = get_ctx(ctx);
|
|
694
|
+
if (!inserted.empty() && rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
695
|
+
rctx->mutation_observers->notifyChildList(ctx, parent, inserted, {}, node, ref);
|
|
696
|
+
}
|
|
697
|
+
return JS_UNDEFINED;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
JSValue js_el_replaceWith(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
701
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
702
|
+
if (!node || !node->parent) return JS_UNDEFINED;
|
|
703
|
+
lxb_dom_node_t* parent = node->parent;
|
|
704
|
+
lxb_dom_node_t* prev_sib = node->prev;
|
|
705
|
+
lxb_dom_node_t* next_sib = node->next;
|
|
706
|
+
|
|
707
|
+
std::vector<void*> inserted;
|
|
708
|
+
for (int i = 0; i < argc; i++) {
|
|
709
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
710
|
+
if (!n) continue;
|
|
711
|
+
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
|
|
712
|
+
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
auto* rctx = get_ctx(ctx);
|
|
716
|
+
invalidate_node_cache(ctx, rctx, node);
|
|
717
|
+
lxb_dom_node_remove(node);
|
|
718
|
+
|
|
719
|
+
if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
720
|
+
rctx->mutation_observers->notifyChildList(ctx, parent, inserted, { node }, prev_sib, next_sib);
|
|
721
|
+
}
|
|
722
|
+
return JS_UNDEFINED;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
JSValue js_el_append(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
726
|
+
auto* parent_node = unwrap_node(ctx, this_val);
|
|
727
|
+
if (!parent_node) return JS_UNDEFINED;
|
|
728
|
+
|
|
729
|
+
std::vector<void*> inserted;
|
|
730
|
+
for (int i = 0; i < argc; i++) {
|
|
731
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
732
|
+
if (!n) continue;
|
|
733
|
+
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_child(parent_node, one); });
|
|
734
|
+
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
auto* rctx = get_ctx(ctx);
|
|
738
|
+
if (!inserted.empty() && rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
739
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node, inserted, {},
|
|
740
|
+
static_cast<lxb_dom_node_t*>(inserted.front())->prev, nullptr);
|
|
741
|
+
}
|
|
742
|
+
return JS_UNDEFINED;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
JSValue js_el_prepend(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
746
|
+
auto* parent_node = unwrap_node(ctx, this_val);
|
|
747
|
+
if (!parent_node) return JS_UNDEFINED;
|
|
748
|
+
lxb_dom_node_t* ref = parent_node->first_child;
|
|
749
|
+
|
|
750
|
+
std::vector<void*> inserted;
|
|
751
|
+
for (int i = 0; i < argc; i++) {
|
|
752
|
+
lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
|
|
753
|
+
if (!n) continue;
|
|
754
|
+
auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
|
|
755
|
+
if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent_node, one);
|
|
756
|
+
});
|
|
757
|
+
inserted.insert(inserted.end(), batch.begin(), batch.end());
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
auto* rctx = get_ctx(ctx);
|
|
761
|
+
if (!inserted.empty() && rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
762
|
+
rctx->mutation_observers->notifyChildList(ctx, parent_node, inserted, {}, nullptr, ref);
|
|
763
|
+
}
|
|
764
|
+
return JS_UNDEFINED;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
JSValue js_el_closest(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
768
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
769
|
+
if (!el || argc < 1) return JS_NULL;
|
|
770
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
771
|
+
if (!sel) return JS_NULL;
|
|
772
|
+
|
|
773
|
+
JSValue result = JS_NULL;
|
|
774
|
+
for (lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
775
|
+
node && node->type == LXB_DOM_NODE_TYPE_ELEMENT;
|
|
776
|
+
node = node->parent) {
|
|
777
|
+
if (get_doc(ctx)->matchesSelector(lxb_dom_interface_element(node), sel)) {
|
|
778
|
+
result = make_element(ctx, node);
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
JS_FreeCString(ctx, sel);
|
|
783
|
+
return result;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
787
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
788
|
+
if (!el || argc < 2) return JS_UNDEFINED;
|
|
789
|
+
const char* position = JS_ToCString(ctx, argv[0]);
|
|
790
|
+
const char* html = JS_ToCString(ctx, argv[1]);
|
|
791
|
+
if (!position || !html) {
|
|
792
|
+
if (position) JS_FreeCString(ctx, position);
|
|
793
|
+
if (html) JS_FreeCString(ctx, html);
|
|
794
|
+
return JS_UNDEFINED;
|
|
795
|
+
}
|
|
796
|
+
std::string pos(position);
|
|
797
|
+
JS_FreeCString(ctx, position);
|
|
798
|
+
|
|
799
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
800
|
+
auto parsed = get_doc(ctx)->parseFragmentNodes(el, html);
|
|
801
|
+
JS_FreeCString(ctx, html);
|
|
802
|
+
if (parsed.empty()) return JS_UNDEFINED;
|
|
803
|
+
|
|
804
|
+
lxb_dom_node_t* parent = nullptr;
|
|
805
|
+
lxb_dom_node_t* prev_sib = nullptr;
|
|
806
|
+
lxb_dom_node_t* next_sib = nullptr;
|
|
807
|
+
|
|
808
|
+
if (pos == "beforebegin") {
|
|
809
|
+
parent = node->parent;
|
|
810
|
+
if (!parent) return JS_UNDEFINED;
|
|
811
|
+
prev_sib = node->prev;
|
|
812
|
+
for (void* n : parsed) lxb_dom_node_insert_before(node, static_cast<lxb_dom_node_t*>(n));
|
|
813
|
+
next_sib = node;
|
|
814
|
+
} else if (pos == "afterbegin") {
|
|
815
|
+
parent = node;
|
|
816
|
+
lxb_dom_node_t* ref = node->first_child;
|
|
817
|
+
for (void* n : parsed) {
|
|
818
|
+
auto* dn = static_cast<lxb_dom_node_t*>(n);
|
|
819
|
+
if (ref) lxb_dom_node_insert_before(ref, dn); else lxb_dom_node_insert_child(node, dn);
|
|
820
|
+
}
|
|
821
|
+
next_sib = ref;
|
|
822
|
+
} else if (pos == "beforeend") {
|
|
823
|
+
parent = node;
|
|
824
|
+
prev_sib = node->last_child;
|
|
825
|
+
for (void* n : parsed) lxb_dom_node_insert_child(node, static_cast<lxb_dom_node_t*>(n));
|
|
826
|
+
} else if (pos == "afterend") {
|
|
827
|
+
parent = node->parent;
|
|
828
|
+
if (!parent) return JS_UNDEFINED;
|
|
829
|
+
prev_sib = node;
|
|
830
|
+
lxb_dom_node_t* ref = node->next;
|
|
831
|
+
for (void* n : parsed) {
|
|
832
|
+
auto* dn = static_cast<lxb_dom_node_t*>(n);
|
|
833
|
+
if (ref) lxb_dom_node_insert_before(ref, dn); else lxb_dom_node_insert_child(parent, dn);
|
|
834
|
+
}
|
|
835
|
+
next_sib = ref;
|
|
836
|
+
} else {
|
|
837
|
+
for (void* n : parsed) lxb_dom_node_destroy_deep(static_cast<lxb_dom_node_t*>(n));
|
|
838
|
+
JS_ThrowTypeError(ctx, "SyntaxError: invalid insertAdjacentHTML position '%s'", pos.c_str());
|
|
839
|
+
return JS_EXCEPTION;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
auto* rctx = get_ctx(ctx);
|
|
843
|
+
if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
|
|
844
|
+
rctx->mutation_observers->notifyChildList(ctx, parent, parsed, {}, prev_sib, next_sib);
|
|
845
|
+
}
|
|
846
|
+
return JS_UNDEFINED;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
JSValue js_el_matches(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
850
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
851
|
+
if (!el || argc < 1) return JS_FALSE;
|
|
852
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
853
|
+
if (!sel) return JS_FALSE;
|
|
854
|
+
bool result = get_doc(ctx)->matchesSelector(el, sel);
|
|
855
|
+
JS_FreeCString(ctx, sel);
|
|
856
|
+
return JS_NewBool(ctx, result);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
JSValue js_el_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
860
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
861
|
+
if (!el || argc < 1) return JS_NULL;
|
|
862
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
863
|
+
if (!sel) return JS_NULL;
|
|
864
|
+
void* found = get_doc(ctx)->querySelectorFromEl(el, sel);
|
|
865
|
+
JS_FreeCString(ctx, sel);
|
|
866
|
+
return make_element(ctx, found);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
JSValue js_el_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
870
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
871
|
+
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
872
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
873
|
+
if (!sel) return JS_NewArray(ctx);
|
|
874
|
+
auto results = get_doc(ctx)->querySelectorAllFromEl(el, sel);
|
|
875
|
+
JS_FreeCString(ctx, sel);
|
|
876
|
+
return make_element_array(ctx, results);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
JSValue js_el_getElementsByClassName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
880
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
881
|
+
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
882
|
+
const char* names = JS_ToCString(ctx, argv[0]);
|
|
883
|
+
if (!names) return JS_NewArray(ctx);
|
|
884
|
+
auto results = get_doc(ctx)->querySelectorAllFromEl(el, classNames_to_selector(names));
|
|
885
|
+
JS_FreeCString(ctx, names);
|
|
886
|
+
return make_element_array(ctx, results);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
JSValue js_el_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
890
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
891
|
+
if (!el || argc < 1) return JS_NewArray(ctx);
|
|
892
|
+
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
893
|
+
if (!tag) return JS_NewArray(ctx);
|
|
894
|
+
auto results = get_doc(ctx)->querySelectorAllFromEl(el, tag);
|
|
895
|
+
JS_FreeCString(ctx, tag);
|
|
896
|
+
return make_element_array(ctx, results);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
JSValue js_el_getAttributeNames(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
900
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
901
|
+
JSValue arr = JS_NewArray(ctx);
|
|
902
|
+
if (!el) return arr;
|
|
903
|
+
uint32_t idx = 0;
|
|
904
|
+
for (auto* attr = lxb_dom_element_first_attribute(el); attr; attr = lxb_dom_element_next_attribute(attr)) {
|
|
905
|
+
size_t len = 0;
|
|
906
|
+
const lxb_char_t* name = lxb_dom_attr_qualified_name(attr, &len);
|
|
907
|
+
JS_SetPropertyUint32(ctx, arr, idx++, JS_NewStringLen(ctx, reinterpret_cast<const char*>(name), len));
|
|
908
|
+
}
|
|
909
|
+
return arr;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// NamedNodeMap-like: a snapshot array of {name, value} pairs rather than a
|
|
913
|
+
// live spec-accurate NamedNodeMap, so it's plain-Array iterable for free.
|
|
914
|
+
JSValue js_el_get_attributes(JSContext* ctx, JSValue this_val) {
|
|
915
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
916
|
+
JSValue arr = JS_NewArray(ctx);
|
|
917
|
+
if (!el) return arr;
|
|
918
|
+
uint32_t idx = 0;
|
|
919
|
+
for (auto* attr = lxb_dom_element_first_attribute(el); attr; attr = lxb_dom_element_next_attribute(attr)) {
|
|
920
|
+
size_t nlen = 0, vlen = 0;
|
|
921
|
+
const lxb_char_t* name = lxb_dom_attr_qualified_name(attr, &nlen);
|
|
922
|
+
const lxb_char_t* value = lxb_dom_attr_value(attr, &vlen);
|
|
923
|
+
JSValue entry = JS_NewObject(ctx);
|
|
924
|
+
JS_SetPropertyStr(ctx, entry, "name", JS_NewStringLen(ctx, reinterpret_cast<const char*>(name), nlen));
|
|
925
|
+
JS_SetPropertyStr(ctx, entry, "value",
|
|
926
|
+
value ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(value), vlen) : JS_NewString(ctx, ""));
|
|
927
|
+
JS_SetPropertyUint32(ctx, arr, idx++, entry);
|
|
928
|
+
}
|
|
929
|
+
return arr;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
JSValue js_el_toggleAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
933
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
934
|
+
if (!el || argc < 1) return JS_FALSE;
|
|
935
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
936
|
+
if (!name) return JS_FALSE;
|
|
937
|
+
size_t name_len = strlen(name);
|
|
938
|
+
auto* attr_name = reinterpret_cast<const lxb_char_t*>(name);
|
|
939
|
+
|
|
940
|
+
bool has = lxb_dom_element_has_attribute(el, attr_name, name_len);
|
|
941
|
+
bool force_present = (argc >= 2 && !JS_IsUndefined(argv[1])) ? JS_ToBool(ctx, argv[1]) > 0 : !has;
|
|
942
|
+
|
|
943
|
+
auto* rctx = get_ctx(ctx);
|
|
944
|
+
bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
|
|
945
|
+
std::optional<std::string> old_val;
|
|
946
|
+
if (has_obs && has && rctx->mutation_observers->hasAttributeOldValueObserver()) {
|
|
947
|
+
size_t len = 0;
|
|
948
|
+
const lxb_char_t* v = lxb_dom_element_get_attribute(el, attr_name, name_len, &len);
|
|
949
|
+
if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
bool changed = false;
|
|
953
|
+
if (force_present && !has) {
|
|
954
|
+
lxb_dom_element_set_attribute(el, attr_name, name_len, reinterpret_cast<const lxb_char_t*>(""), 0);
|
|
955
|
+
changed = true;
|
|
956
|
+
} else if (!force_present && has) {
|
|
957
|
+
lxb_dom_element_remove_attribute(el, attr_name, name_len);
|
|
958
|
+
changed = true;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (changed && has_obs) {
|
|
962
|
+
rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), std::string(name), old_val);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
JS_FreeCString(ctx, name);
|
|
966
|
+
return JS_NewBool(ctx, force_present);
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
JSValue js_el_isSameNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
970
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
971
|
+
if (!node || argc < 1) return JS_FALSE;
|
|
972
|
+
return JS_NewBool(ctx, unwrap_node(ctx, argv[0]) == node);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
bool nodes_equal(lxb_dom_node_t* a, lxb_dom_node_t* b) {
|
|
976
|
+
if (a->type != b->type) return false;
|
|
977
|
+
|
|
978
|
+
if (a->type == LXB_DOM_NODE_TYPE_ELEMENT) {
|
|
979
|
+
auto* ea = lxb_dom_interface_element(a);
|
|
980
|
+
auto* eb = lxb_dom_interface_element(b);
|
|
981
|
+
|
|
982
|
+
size_t la = 0, lb = 0;
|
|
983
|
+
const lxb_char_t* na = lxb_dom_element_local_name(ea, &la);
|
|
984
|
+
const lxb_char_t* nb = lxb_dom_element_local_name(eb, &lb);
|
|
985
|
+
if (la != lb || memcmp(na, nb, la) != 0) return false;
|
|
986
|
+
|
|
987
|
+
size_t count_a = 0, count_b = 0;
|
|
988
|
+
for (auto* at = lxb_dom_element_first_attribute(ea); at; at = lxb_dom_element_next_attribute(at)) count_a++;
|
|
989
|
+
for (auto* at = lxb_dom_element_first_attribute(eb); at; at = lxb_dom_element_next_attribute(at)) count_b++;
|
|
990
|
+
if (count_a != count_b) return false;
|
|
991
|
+
|
|
992
|
+
for (auto* at = lxb_dom_element_first_attribute(ea); at; at = lxb_dom_element_next_attribute(at)) {
|
|
993
|
+
size_t nlen = 0, vlen = 0;
|
|
994
|
+
const lxb_char_t* name = lxb_dom_attr_qualified_name(at, &nlen);
|
|
995
|
+
const lxb_char_t* val = lxb_dom_attr_value(at, &vlen);
|
|
996
|
+
size_t blen = 0;
|
|
997
|
+
const lxb_char_t* bval = lxb_dom_element_get_attribute(eb, name, nlen, &blen);
|
|
998
|
+
if (!bval || vlen != blen || (vlen > 0 && memcmp(val, bval, vlen) != 0)) return false;
|
|
999
|
+
}
|
|
1000
|
+
} else if (a->type == LXB_DOM_NODE_TYPE_TEXT || a->type == LXB_DOM_NODE_TYPE_COMMENT) {
|
|
1001
|
+
auto* ca = reinterpret_cast<lxb_dom_character_data_t*>(a);
|
|
1002
|
+
auto* cb = reinterpret_cast<lxb_dom_character_data_t*>(b);
|
|
1003
|
+
if (ca->data.length != cb->data.length) return false;
|
|
1004
|
+
if (ca->data.length > 0 && memcmp(ca->data.data, cb->data.data, ca->data.length) != 0) return false;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
lxb_dom_node_t* ca = a->first_child;
|
|
1008
|
+
lxb_dom_node_t* cb = b->first_child;
|
|
1009
|
+
while (ca && cb) {
|
|
1010
|
+
if (!nodes_equal(ca, cb)) return false;
|
|
1011
|
+
ca = ca->next;
|
|
1012
|
+
cb = cb->next;
|
|
1013
|
+
}
|
|
1014
|
+
return ca == nullptr && cb == nullptr;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
JSValue js_el_isEqualNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
1018
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
1019
|
+
if (!node || argc < 1) return JS_FALSE;
|
|
1020
|
+
auto* other = unwrap_node(ctx, argv[0]);
|
|
1021
|
+
if (!other) return JS_FALSE;
|
|
1022
|
+
return JS_NewBool(ctx, nodes_equal(node, other));
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
} // namespace
|
|
1026
|
+
|
|
1027
|
+
void ElementBindings::install(JSContext* ctx) {
|
|
1028
|
+
if (js_element_class_id == 0) JS_NewClassID(&js_element_class_id);
|
|
1029
|
+
if (js_node_class_id == 0) JS_NewClassID(&js_node_class_id);
|
|
1030
|
+
JS_NewClass(JS_GetRuntime(ctx), js_element_class_id, &js_element_class);
|
|
1031
|
+
JS_NewClass(JS_GetRuntime(ctx), js_node_class_id, &js_node_class);
|
|
1032
|
+
|
|
1033
|
+
ClassListBindings::install(ctx);
|
|
1034
|
+
DatasetBindings::install(ctx);
|
|
1035
|
+
StyleBindings::install(ctx);
|
|
1036
|
+
|
|
1037
|
+
// ── Node proto: shared traversal + CharacterData methods ─────────────────
|
|
1038
|
+
JSValue node_proto = JS_NewObject(ctx);
|
|
1039
|
+
|
|
1040
|
+
JS_SetPropertyStr(ctx, node_proto, "appendChild", JS_NewCFunction(ctx, js_el_appendChild, "appendChild", 1));
|
|
1041
|
+
JS_SetPropertyStr(ctx, node_proto, "removeChild", JS_NewCFunction(ctx, js_el_removeChild, "removeChild", 1));
|
|
1042
|
+
JS_SetPropertyStr(ctx, node_proto, "insertBefore", JS_NewCFunction(ctx, js_el_insertBefore, "insertBefore", 2));
|
|
1043
|
+
JS_SetPropertyStr(ctx, node_proto, "replaceChild", JS_NewCFunction(ctx, js_el_replaceChild, "replaceChild", 2));
|
|
1044
|
+
JS_SetPropertyStr(ctx, node_proto, "cloneNode", JS_NewCFunction(ctx, js_el_cloneNode, "cloneNode", 1));
|
|
1045
|
+
JS_SetPropertyStr(ctx, node_proto, "contains", JS_NewCFunction(ctx, js_el_contains, "contains", 1));
|
|
1046
|
+
JS_SetPropertyStr(ctx, node_proto, "before", JS_NewCFunction(ctx, js_el_before, "before", 0));
|
|
1047
|
+
JS_SetPropertyStr(ctx, node_proto, "after", JS_NewCFunction(ctx, js_el_after, "after", 0));
|
|
1048
|
+
JS_SetPropertyStr(ctx, node_proto, "replaceWith", JS_NewCFunction(ctx, js_el_replaceWith, "replaceWith", 0));
|
|
1049
|
+
JS_SetPropertyStr(ctx, node_proto, "isSameNode", JS_NewCFunction(ctx, js_el_isSameNode, "isSameNode", 1));
|
|
1050
|
+
JS_SetPropertyStr(ctx, node_proto, "isEqualNode", JS_NewCFunction(ctx, js_el_isEqualNode, "isEqualNode", 1));
|
|
1051
|
+
|
|
1052
|
+
define_prop(ctx, node_proto, "nodeType", js_el_get_nodeType, nullptr);
|
|
1053
|
+
define_prop(ctx, node_proto, "nodeName", js_el_get_nodeName, nullptr);
|
|
1054
|
+
define_prop(ctx, node_proto, "nodeValue", js_el_get_nodeValue, js_el_set_nodeValue);
|
|
1055
|
+
define_prop(ctx, node_proto, "textContent", js_el_get_textContent, js_el_set_textContent);
|
|
1056
|
+
define_prop(ctx, node_proto, "childNodes", js_el_get_childNodes, nullptr);
|
|
1057
|
+
define_prop(ctx, node_proto, "firstChild", js_el_get_firstChild, nullptr);
|
|
1058
|
+
define_prop(ctx, node_proto, "lastChild", js_el_get_lastChild, nullptr);
|
|
1059
|
+
define_prop(ctx, node_proto, "nextSibling", js_el_get_nextSibling, nullptr);
|
|
1060
|
+
define_prop(ctx, node_proto, "previousSibling", js_el_get_previousSibling, nullptr);
|
|
1061
|
+
define_prop(ctx, node_proto, "parentNode", js_el_get_parentNode, nullptr);
|
|
1062
|
+
define_prop(ctx, node_proto, "parentElement", js_el_get_parentElement, nullptr);
|
|
1063
|
+
|
|
1064
|
+
JS_SetClassProto(ctx, js_node_class_id, JS_DupValue(ctx, node_proto));
|
|
1065
|
+
|
|
1066
|
+
// ── Element proto: inherits Node, adds Element-specific APIs ─────────────
|
|
1067
|
+
JSValue proto = JS_NewObjectProto(ctx, node_proto);
|
|
1068
|
+
JS_FreeValue(ctx, node_proto);
|
|
1069
|
+
|
|
1070
|
+
JS_SetPropertyStr(ctx, proto, "getAttribute", JS_NewCFunction(ctx, js_el_getAttribute, "getAttribute", 1));
|
|
1071
|
+
JS_SetPropertyStr(ctx, proto, "setAttribute", JS_NewCFunction(ctx, js_el_setAttribute, "setAttribute", 2));
|
|
1072
|
+
JS_SetPropertyStr(ctx, proto, "removeAttribute", JS_NewCFunction(ctx, js_el_removeAttribute, "removeAttribute", 1));
|
|
1073
|
+
JS_SetPropertyStr(ctx, proto, "hasAttribute", JS_NewCFunction(ctx, js_el_hasAttribute, "hasAttribute", 1));
|
|
1074
|
+
JS_SetPropertyStr(ctx, proto, "toggleAttribute", JS_NewCFunction(ctx, js_el_toggleAttribute, "toggleAttribute", 2));
|
|
1075
|
+
JS_SetPropertyStr(ctx, proto, "getAttributeNames", JS_NewCFunction(ctx, js_el_getAttributeNames, "getAttributeNames", 0));
|
|
1076
|
+
JS_SetPropertyStr(ctx, proto, "remove", JS_NewCFunction(ctx, js_el_remove, "remove", 0));
|
|
1077
|
+
JS_SetPropertyStr(ctx, proto, "matches", JS_NewCFunction(ctx, js_el_matches, "matches", 1));
|
|
1078
|
+
JS_SetPropertyStr(ctx, proto, "querySelector", JS_NewCFunction(ctx, js_el_querySelector, "querySelector", 1));
|
|
1079
|
+
JS_SetPropertyStr(ctx, proto, "querySelectorAll", JS_NewCFunction(ctx, js_el_querySelectorAll, "querySelectorAll", 1));
|
|
1080
|
+
JS_SetPropertyStr(ctx, proto, "getElementsByClassName", JS_NewCFunction(ctx, js_el_getElementsByClassName, "getElementsByClassName", 1));
|
|
1081
|
+
JS_SetPropertyStr(ctx, proto, "getElementsByTagName", JS_NewCFunction(ctx, js_el_getElementsByTagName, "getElementsByTagName", 1));
|
|
1082
|
+
JS_SetPropertyStr(ctx, proto, "closest", JS_NewCFunction(ctx, js_el_closest, "closest", 1));
|
|
1083
|
+
JS_SetPropertyStr(ctx, proto, "insertAdjacentHTML", JS_NewCFunction(ctx, js_el_insertAdjacentHTML, "insertAdjacentHTML", 2));
|
|
1084
|
+
JS_SetPropertyStr(ctx, proto, "append", JS_NewCFunction(ctx, js_el_append, "append", 0));
|
|
1085
|
+
JS_SetPropertyStr(ctx, proto, "prepend", JS_NewCFunction(ctx, js_el_prepend, "prepend", 0));
|
|
1086
|
+
|
|
1087
|
+
define_prop(ctx, proto, "tagName", js_el_get_tagName, nullptr);
|
|
1088
|
+
define_prop(ctx, proto, "id", js_el_get_id, js_el_set_id);
|
|
1089
|
+
define_prop(ctx, proto, "className", js_el_get_className, js_el_set_className);
|
|
1090
|
+
define_prop(ctx, proto, "innerHTML", js_el_get_innerHTML, js_el_set_innerHTML);
|
|
1091
|
+
define_prop(ctx, proto, "outerHTML", js_el_get_outerHTML, nullptr);
|
|
1092
|
+
define_prop(ctx, proto, "children", js_el_get_children, nullptr);
|
|
1093
|
+
define_prop(ctx, proto, "firstElementChild", js_el_get_firstElementChild, nullptr);
|
|
1094
|
+
define_prop(ctx, proto, "lastElementChild", js_el_get_lastElementChild, nullptr);
|
|
1095
|
+
define_prop(ctx, proto, "nextElementSibling", js_el_get_nextElementSibling, nullptr);
|
|
1096
|
+
define_prop(ctx, proto, "previousElementSibling", js_el_get_prevElementSibling, nullptr);
|
|
1097
|
+
define_prop(ctx, proto, "classList", js_el_get_classList, nullptr);
|
|
1098
|
+
define_prop(ctx, proto, "dataset", js_el_get_dataset, nullptr);
|
|
1099
|
+
define_prop(ctx, proto, "style", js_el_get_style, nullptr);
|
|
1100
|
+
define_prop(ctx, proto, "childElementCount", js_el_get_childElementCount, nullptr);
|
|
1101
|
+
define_prop(ctx, proto, "attributes", js_el_get_attributes, nullptr);
|
|
1102
|
+
|
|
1103
|
+
JS_SetClassProto(ctx, js_element_class_id, proto);
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
} // namespace margelo::nitro::nitrojsdom
|