@salve-software/react-native-nitro-jsdom 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +18 -0
- package/cpp/HybridHtmlSandbox.cpp +20 -3
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +279 -67
- package/cpp/lexbor/LexborDocument.hpp +43 -10
- package/cpp/quickjs/DOMBindings.cpp +37 -1
- package/cpp/quickjs/DOMBindingsInternal.cpp +101 -2
- package/cpp/quickjs/DOMBindingsInternal.hpp +56 -1
- package/cpp/quickjs/QuickJSRuntime.cpp +32 -4
- package/cpp/quickjs/QuickJSRuntime.hpp +50 -1
- package/cpp/quickjs/bindings/AbortBindings.cpp +128 -0
- package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +164 -0
- package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +290 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +25 -0
- package/cpp/quickjs/bindings/ClassListBindings.cpp +3 -2
- package/cpp/quickjs/bindings/CookieBindings.cpp +118 -0
- package/cpp/quickjs/bindings/CookieBindings.hpp +23 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +341 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.hpp +50 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.cpp +111 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.hpp +20 -0
- package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
- package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +239 -4
- package/cpp/quickjs/bindings/ElementBindings.cpp +556 -44
- package/cpp/quickjs/bindings/EventBindings.cpp +414 -8
- package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
- package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
- package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
- package/cpp/quickjs/bindings/FetchBindings.cpp +45 -6
- package/cpp/quickjs/bindings/FormBindings.cpp +365 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +20 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +228 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.hpp +16 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.cpp +171 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.hpp +27 -0
- package/cpp/quickjs/bindings/SlotBindings.cpp +161 -0
- package/cpp/quickjs/bindings/SlotBindings.hpp +44 -0
- package/cpp/quickjs/bindings/TemplateBindings.cpp +42 -0
- package/cpp/quickjs/bindings/TemplateBindings.hpp +29 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.cpp +117 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.hpp +14 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +120 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +295 -0
- package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +552 -0
- package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
- package/cpp/quickjs/bindings/XmlSerializerBindings.cpp +44 -0
- package/cpp/quickjs/bindings/XmlSerializerBindings.hpp +24 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
- package/package.json +17 -2
- package/src/classes/JSDOM/JSDOM.class.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
package/android/CMakeLists.txt
CHANGED
|
@@ -48,13 +48,31 @@ add_library(${PACKAGE_NAME} SHARED
|
|
|
48
48
|
../cpp/quickjs/Storage.cpp
|
|
49
49
|
../cpp/quickjs/bindings/ClassListBindings.cpp
|
|
50
50
|
../cpp/quickjs/bindings/DatasetBindings.cpp
|
|
51
|
+
../cpp/quickjs/bindings/DOMExceptionBindings.cpp
|
|
51
52
|
../cpp/quickjs/bindings/StyleBindings.cpp
|
|
52
53
|
../cpp/quickjs/bindings/ElementBindings.cpp
|
|
53
54
|
../cpp/quickjs/bindings/DocumentBindings.cpp
|
|
55
|
+
../cpp/quickjs/bindings/DOMParserBindings.cpp
|
|
54
56
|
../cpp/quickjs/bindings/EventBindings.cpp
|
|
55
57
|
../cpp/quickjs/bindings/TimerBindings.cpp
|
|
56
58
|
../cpp/quickjs/bindings/WindowBindings.cpp
|
|
57
59
|
../cpp/quickjs/bindings/FetchBindings.cpp
|
|
60
|
+
../cpp/quickjs/bindings/LiveCollectionBindings.cpp
|
|
61
|
+
../cpp/quickjs/bindings/UrlBindings.cpp
|
|
62
|
+
../cpp/quickjs/bindings/AbortBindings.cpp
|
|
63
|
+
../cpp/quickjs/bindings/TextEncodingBindings.cpp
|
|
64
|
+
../cpp/quickjs/bindings/FormBindings.cpp
|
|
65
|
+
../cpp/quickjs/bindings/BlobBindings.cpp
|
|
66
|
+
../cpp/quickjs/bindings/CSSOMBindings.cpp
|
|
67
|
+
../cpp/quickjs/bindings/ShadowRootBindings.cpp
|
|
68
|
+
../cpp/quickjs/bindings/CustomElementsBindings.cpp
|
|
69
|
+
../cpp/quickjs/bindings/CookieBindings.cpp
|
|
70
|
+
../cpp/quickjs/bindings/TemplateBindings.cpp
|
|
71
|
+
../cpp/quickjs/bindings/SlotBindings.cpp
|
|
72
|
+
../cpp/quickjs/bindings/XmlSerializerBindings.cpp
|
|
73
|
+
../cpp/quickjs/bindings/LayoutStubBindings.cpp
|
|
74
|
+
../cpp/quickjs/bindings/TreeWalkerBindings.cpp
|
|
75
|
+
../cpp/quickjs/bindings/EventTargetBindings.cpp
|
|
58
76
|
${QUICKJS_SOURCES}
|
|
59
77
|
)
|
|
60
78
|
|
|
@@ -34,20 +34,37 @@ std::string jsonQuote(const std::string& s) {
|
|
|
34
34
|
}
|
|
35
35
|
} // namespace
|
|
36
36
|
|
|
37
|
-
void HybridHtmlSandbox::initialize(const std::string& html, bool runScripts, const std::string& url) {
|
|
37
|
+
void HybridHtmlSandbox::initialize(const std::string& html, bool runScripts, const std::string& url, bool pretendToBeVisual) {
|
|
38
38
|
_document = std::make_unique<LexborDocument>();
|
|
39
39
|
_document->parse(html);
|
|
40
40
|
|
|
41
41
|
_runtime = std::make_unique<QuickJSRuntime>();
|
|
42
|
-
_runtime->initialize(url);
|
|
42
|
+
_runtime->initialize(url, pretendToBeVisual);
|
|
43
43
|
_runtime->bindDocument(_document.get());
|
|
44
44
|
|
|
45
45
|
if (runScripts) {
|
|
46
46
|
for (const auto& script : _document->getScriptContents()) {
|
|
47
|
-
|
|
47
|
+
try {
|
|
48
|
+
_runtime->evaluate(script);
|
|
49
|
+
} catch (...) { }
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// Fire once parsing (and any inline <script> execution above) is done, so
|
|
54
|
+
// scripts that do document.addEventListener('DOMContentLoaded'/'load', ...)
|
|
55
|
+
// or window.addEventListener(...) — the common embedded-widget bootstrap
|
|
56
|
+
// pattern — see them. There's no real subresource loading in this sandbox,
|
|
57
|
+
// so both fire back-to-back with nothing in between.
|
|
58
|
+
_runtime->contextState()->ready_state = "interactive";
|
|
59
|
+
try {
|
|
60
|
+
_runtime->evaluate("document.dispatchEvent(new Event('DOMContentLoaded', { bubbles: true }));");
|
|
61
|
+
} catch (...) { }
|
|
62
|
+
|
|
63
|
+
_runtime->contextState()->ready_state = "complete";
|
|
64
|
+
try {
|
|
65
|
+
_runtime->evaluate("document.dispatchEvent(new Event('load'));");
|
|
66
|
+
} catch (...) { }
|
|
67
|
+
|
|
51
68
|
_initialized = true;
|
|
52
69
|
}
|
|
53
70
|
|
|
@@ -18,7 +18,7 @@ public:
|
|
|
18
18
|
HybridHtmlSandbox() : HybridObject(TAG), HybridHtmlSandboxSpec() {}
|
|
19
19
|
~HybridHtmlSandbox() override = default;
|
|
20
20
|
|
|
21
|
-
void initialize(const std::string& html, bool runScripts, const std::string& url) override;
|
|
21
|
+
void initialize(const std::string& html, bool runScripts, const std::string& url, bool pretendToBeVisual) override;
|
|
22
22
|
|
|
23
23
|
std::shared_ptr<Promise<std::string>> evaluate(const std::string& script) override;
|
|
24
24
|
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
#include "LexborDocument.hpp"
|
|
2
2
|
#include <lexbor/html/html.h>
|
|
3
|
+
#include <lexbor/html/interfaces/template_element.h>
|
|
3
4
|
#include <lexbor/css/css.h>
|
|
4
5
|
#include <lexbor/selectors/selectors.h>
|
|
5
6
|
#include <lexbor/dom/dom.h>
|
|
7
|
+
#include <lexbor/dom/interfaces/character_data.h>
|
|
8
|
+
#include <lexbor/dom/interfaces/document_type.h>
|
|
9
|
+
#include <lexbor/dom/interfaces/element.h>
|
|
10
|
+
#include <lexbor/ns/ns.h>
|
|
11
|
+
#include <lexbor/tag/tag.h>
|
|
6
12
|
#include <stdexcept>
|
|
13
|
+
#include <cctype>
|
|
14
|
+
#include <cstring>
|
|
15
|
+
#include <unordered_set>
|
|
16
|
+
#include <vector>
|
|
17
|
+
#include <algorithm>
|
|
7
18
|
|
|
8
19
|
namespace margelo::nitro::nitrojsdom {
|
|
9
20
|
|
|
@@ -22,6 +33,26 @@ static std::string serializeNode(lxb_dom_node_t* node) {
|
|
|
22
33
|
return result;
|
|
23
34
|
}
|
|
24
35
|
|
|
36
|
+
bool is_javascript_mime_type(const std::string& type) {
|
|
37
|
+
if (type.empty()) return true;
|
|
38
|
+
std::string t;
|
|
39
|
+
t.reserve(type.size());
|
|
40
|
+
for (char c : type) t += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
41
|
+
size_t start = t.find_first_not_of(" \t\n\r\f");
|
|
42
|
+
if (start == std::string::npos) return true;
|
|
43
|
+
size_t end = t.find_last_not_of(" \t\n\r\f");
|
|
44
|
+
t = t.substr(start, end - start + 1);
|
|
45
|
+
|
|
46
|
+
static const std::unordered_set<std::string> kJsTypes = {
|
|
47
|
+
"text/javascript", "application/javascript", "application/ecmascript",
|
|
48
|
+
"application/x-ecmascript", "application/x-javascript", "text/ecmascript",
|
|
49
|
+
"text/javascript1.0", "text/javascript1.1", "text/javascript1.2",
|
|
50
|
+
"text/javascript1.3", "text/javascript1.4", "text/javascript1.5",
|
|
51
|
+
"text/jscript", "text/livescript", "text/x-ecmascript", "text/x-javascript",
|
|
52
|
+
};
|
|
53
|
+
return kJsTypes.count(t) > 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
25
56
|
struct FindFirstCtx { lxb_dom_element_t* result = nullptr; };
|
|
26
57
|
|
|
27
58
|
static lxb_status_t findFirstCallback(lxb_dom_node_t* node, lxb_css_selector_specificity_t, void* ctx) {
|
|
@@ -29,11 +60,6 @@ static lxb_status_t findFirstCallback(lxb_dom_node_t* node, lxb_css_selector_spe
|
|
|
29
60
|
return LXB_STATUS_STOP;
|
|
30
61
|
}
|
|
31
62
|
|
|
32
|
-
static lxb_status_t findAllStringCallback(lxb_dom_node_t* node, lxb_css_selector_specificity_t, void* ctx) {
|
|
33
|
-
static_cast<std::vector<std::string>*>(ctx)->push_back(serializeNode(node));
|
|
34
|
-
return LXB_STATUS_OK;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
63
|
static lxb_status_t findAllPtrCallback(lxb_dom_node_t* node, lxb_css_selector_specificity_t, void* ctx) {
|
|
38
64
|
static_cast<std::vector<void*>*>(ctx)->push_back(lxb_dom_interface_element(node));
|
|
39
65
|
return LXB_STATUS_OK;
|
|
@@ -134,14 +160,14 @@ std::vector<void*> LexborDocument::querySelectorAll_el(const std::string& sel) c
|
|
|
134
160
|
|
|
135
161
|
void* LexborDocument::querySelectorFromEl(void* element, const std::string& sel) const {
|
|
136
162
|
if (!element) return nullptr;
|
|
137
|
-
return findFirstFrom(
|
|
163
|
+
return findFirstFrom(static_cast<lxb_dom_node_t*>(element), sel);
|
|
138
164
|
}
|
|
139
165
|
|
|
140
166
|
std::vector<void*> LexborDocument::querySelectorAllFromEl(void* element, const std::string& sel) const {
|
|
141
167
|
if (!_document || !element) return {};
|
|
142
168
|
auto* parser = static_cast<lxb_css_parser_t*>(_cssParser);
|
|
143
169
|
auto* selectors = static_cast<lxb_selectors_t*>(_selectors);
|
|
144
|
-
auto* node =
|
|
170
|
+
auto* node = static_cast<lxb_dom_node_t*>(element);
|
|
145
171
|
|
|
146
172
|
lxb_css_selector_list_t* list = lxb_css_selectors_parse(parser,
|
|
147
173
|
reinterpret_cast<const lxb_char_t*>(sel.data()), sel.size());
|
|
@@ -179,7 +205,15 @@ std::vector<std::string> LexborDocument::getScriptContents() const {
|
|
|
179
205
|
contents.reserve(scriptEls.size());
|
|
180
206
|
|
|
181
207
|
for (void* el : scriptEls) {
|
|
182
|
-
auto*
|
|
208
|
+
auto* element = static_cast<lxb_dom_element_t*>(el);
|
|
209
|
+
|
|
210
|
+
size_t type_len = 0;
|
|
211
|
+
const lxb_char_t* type_attr = lxb_dom_element_get_attribute(element,
|
|
212
|
+
reinterpret_cast<const lxb_char_t*>("type"), 4, &type_len);
|
|
213
|
+
std::string type = type_attr ? std::string(reinterpret_cast<const char*>(type_attr), type_len) : "";
|
|
214
|
+
if (!is_javascript_mime_type(type)) continue;
|
|
215
|
+
|
|
216
|
+
auto* node = lxb_dom_interface_node(element);
|
|
183
217
|
size_t len = 0;
|
|
184
218
|
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
185
219
|
if (text && len > 0) {
|
|
@@ -199,6 +233,65 @@ void* LexborDocument::createElement(const std::string& tag) {
|
|
|
199
233
|
reinterpret_cast<const lxb_char_t*>(tag.data()), tag.size(), nullptr);
|
|
200
234
|
}
|
|
201
235
|
|
|
236
|
+
void* LexborDocument::createElementNS(const std::string& nsUri, const std::string& qualifiedName) {
|
|
237
|
+
if (!_document) return nullptr;
|
|
238
|
+
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
239
|
+
|
|
240
|
+
std::string prefix;
|
|
241
|
+
std::string localName = qualifiedName;
|
|
242
|
+
auto colon = qualifiedName.find(':');
|
|
243
|
+
if (colon != std::string::npos) {
|
|
244
|
+
prefix = qualifiedName.substr(0, colon);
|
|
245
|
+
localName = qualifiedName.substr(colon + 1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
bool known_tag = lxb_tag_data_by_name(dom_doc->tags,
|
|
249
|
+
reinterpret_cast<const lxb_char_t*>(localName.data()), localName.size()) != nullptr;
|
|
250
|
+
|
|
251
|
+
if (!known_tag) {
|
|
252
|
+
return lxb_dom_element_create(dom_doc,
|
|
253
|
+
reinterpret_cast<const lxb_char_t*>(localName.data()), localName.size(),
|
|
254
|
+
nsUri.empty() ? nullptr : reinterpret_cast<const lxb_char_t*>(nsUri.data()), nsUri.size(),
|
|
255
|
+
prefix.empty() ? nullptr : reinterpret_cast<const lxb_char_t*>(prefix.data()), prefix.size(),
|
|
256
|
+
nullptr, 0, true);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Known tag + arbitrary ns crashes Lexbor's HTML interface dispatch, so
|
|
260
|
+
// resolve the ns id via a throwaway unknown-tag element first (safe path).
|
|
261
|
+
static const char* kNsProbeTag = "x-nitrojsdom-ns-probe";
|
|
262
|
+
lxb_dom_element_t* probe = lxb_dom_element_create(dom_doc,
|
|
263
|
+
reinterpret_cast<const lxb_char_t*>(kNsProbeTag), strlen(kNsProbeTag),
|
|
264
|
+
nsUri.empty() ? nullptr : reinterpret_cast<const lxb_char_t*>(nsUri.data()), nsUri.size(),
|
|
265
|
+
nullptr, 0, nullptr, 0, true);
|
|
266
|
+
if (!probe) return nullptr;
|
|
267
|
+
lxb_ns_id_t resolved_ns = probe->node.ns;
|
|
268
|
+
lxb_dom_element_destroy(probe);
|
|
269
|
+
|
|
270
|
+
lxb_dom_element_t* element = lxb_dom_document_create_element(dom_doc,
|
|
271
|
+
reinterpret_cast<const lxb_char_t*>(localName.data()), localName.size(), nullptr);
|
|
272
|
+
if (!element) return nullptr;
|
|
273
|
+
element->node.ns = resolved_ns;
|
|
274
|
+
|
|
275
|
+
if (!prefix.empty()) {
|
|
276
|
+
const lxb_ns_prefix_data_t* ns_prefix = lxb_ns_prefix_append(dom_doc->prefix,
|
|
277
|
+
reinterpret_cast<const lxb_char_t*>(prefix.data()), prefix.size());
|
|
278
|
+
if (ns_prefix) element->node.prefix = ns_prefix->prefix_id;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return element;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
std::string LexborDocument::namespaceURI(void* node) const {
|
|
285
|
+
if (!_document || !node) return "";
|
|
286
|
+
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
287
|
+
auto* n = static_cast<lxb_dom_node_t*>(node);
|
|
288
|
+
if (n->ns == LXB_NS__UNDEF) return "";
|
|
289
|
+
size_t len = 0;
|
|
290
|
+
const lxb_char_t* uri = lxb_ns_by_id(dom_doc->ns, static_cast<lxb_ns_id_t>(n->ns), &len);
|
|
291
|
+
if (!uri || len == 0) return "";
|
|
292
|
+
return std::string(reinterpret_cast<const char*>(uri), len);
|
|
293
|
+
}
|
|
294
|
+
|
|
202
295
|
void* LexborDocument::createTextNode(const std::string& text) {
|
|
203
296
|
if (!_document) return nullptr;
|
|
204
297
|
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
@@ -219,6 +312,16 @@ void* LexborDocument::createDocumentFragment() {
|
|
|
219
312
|
return lxb_dom_document_create_document_fragment(dom_doc);
|
|
220
313
|
}
|
|
221
314
|
|
|
315
|
+
void* LexborDocument::createShadowRoot(void* hostElement, int mode) {
|
|
316
|
+
if (!_document || !hostElement) return nullptr;
|
|
317
|
+
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
318
|
+
auto* shadow = lxb_dom_shadow_root_interface_create(dom_doc);
|
|
319
|
+
if (!shadow) return nullptr;
|
|
320
|
+
shadow->host = static_cast<lxb_dom_element_t*>(hostElement);
|
|
321
|
+
shadow->mode = static_cast<lxb_dom_shadow_root_mode_t>(mode);
|
|
322
|
+
return shadow;
|
|
323
|
+
}
|
|
324
|
+
|
|
222
325
|
// ── Element content ──────────────────────────────────────────────────────────
|
|
223
326
|
|
|
224
327
|
void LexborDocument::setTextContentOnEl(void* element, const std::string& text) {
|
|
@@ -242,10 +345,40 @@ void LexborDocument::setTextContentOnEl(void* element, const std::string& text)
|
|
|
242
345
|
}
|
|
243
346
|
}
|
|
244
347
|
|
|
348
|
+
void LexborDocument::setInnerHTMLOnShadowRoot(void* shadowRoot, void* hostElement, const std::string& html) {
|
|
349
|
+
auto* host = static_cast<lxb_dom_element_t*>(hostElement);
|
|
350
|
+
auto* doc = static_cast<lxb_html_document_t*>(_document);
|
|
351
|
+
auto* root = static_cast<lxb_dom_node_t*>(shadowRoot);
|
|
352
|
+
|
|
353
|
+
lxb_dom_node_t* frag = lxb_html_document_parse_fragment(doc, host,
|
|
354
|
+
reinterpret_cast<const lxb_char_t*>(html.data()), html.size());
|
|
355
|
+
if (!frag) return;
|
|
356
|
+
|
|
357
|
+
while (root->first_child) lxb_dom_node_destroy_deep(root->first_child);
|
|
358
|
+
|
|
359
|
+
while (frag->first_child) {
|
|
360
|
+
lxb_dom_node_t* child = frag->first_child;
|
|
361
|
+
lxb_dom_node_remove(child);
|
|
362
|
+
lxb_dom_node_insert_child(root, child);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
lxb_dom_node_destroy(frag);
|
|
366
|
+
}
|
|
367
|
+
|
|
245
368
|
void LexborDocument::setInnerHTMLOnEl(void* element, const std::string& html) {
|
|
246
369
|
auto* el = static_cast<lxb_dom_element_t*>(element);
|
|
247
370
|
auto* doc = static_cast<lxb_html_document_t*>(_document);
|
|
248
|
-
|
|
371
|
+
|
|
372
|
+
// <template>.innerHTML populates .content (a separate inert fragment), not
|
|
373
|
+
// the template element's own light-DOM children — matches the parser's own
|
|
374
|
+
// "in template" insertion mode behavior for parsed <template> markup.
|
|
375
|
+
size_t tag_len = 0;
|
|
376
|
+
const lxb_char_t* tag_name = lxb_dom_element_local_name(el, &tag_len);
|
|
377
|
+
bool is_template = tag_len == 8 && memcmp(tag_name, "template", 8) == 0;
|
|
378
|
+
|
|
379
|
+
lxb_dom_node_t* root = is_template
|
|
380
|
+
? lxb_dom_interface_node(reinterpret_cast<lxb_html_template_element_t*>(el)->content)
|
|
381
|
+
: lxb_dom_interface_node(el);
|
|
249
382
|
|
|
250
383
|
lxb_dom_node_t* frag = lxb_html_document_parse_fragment(doc, el,
|
|
251
384
|
reinterpret_cast<const lxb_char_t*>(html.data()), html.size());
|
|
@@ -290,76 +423,155 @@ bool LexborDocument::matchesSelector(void* element, const std::string& sel) cons
|
|
|
290
423
|
return false;
|
|
291
424
|
}
|
|
292
425
|
|
|
293
|
-
// ──
|
|
426
|
+
// ── <template> ────────────────────────────────────────────────────────────
|
|
294
427
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return
|
|
428
|
+
void* LexborDocument::templateContent(void* templateEl) const {
|
|
429
|
+
if (!templateEl) return nullptr;
|
|
430
|
+
auto* tpl = reinterpret_cast<lxb_html_template_element_t*>(templateEl);
|
|
431
|
+
return tpl->content;
|
|
299
432
|
}
|
|
300
433
|
|
|
301
|
-
|
|
302
|
-
if (!_document) return {};
|
|
303
|
-
auto* parser = static_cast<lxb_css_parser_t*>(_cssParser);
|
|
304
|
-
auto* selectors = static_cast<lxb_selectors_t*>(_selectors);
|
|
305
|
-
auto* doc = static_cast<lxb_html_document_t*>(_document);
|
|
434
|
+
// ── Doctype ──────────────────────────────────────────────────────────────────
|
|
306
435
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
lxb_selectors_find(selectors, lxb_dom_interface_node(doc), list, findAllStringCallback, &results);
|
|
312
|
-
lxb_css_selector_list_destroy_memory(list);
|
|
313
|
-
}
|
|
314
|
-
return results;
|
|
436
|
+
void* LexborDocument::doctype() const {
|
|
437
|
+
if (!_document) return nullptr;
|
|
438
|
+
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
439
|
+
return dom_doc->doctype;
|
|
315
440
|
}
|
|
316
441
|
|
|
317
|
-
std::
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (!val) return std::nullopt;
|
|
324
|
-
return std::string(reinterpret_cast<const char*>(val), val_len);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
void LexborDocument::setAttribute(const std::string& selector, const std::string& attr, const std::string& value) {
|
|
328
|
-
auto* el = static_cast<lxb_dom_element_t*>(findFirst(selector));
|
|
329
|
-
if (!el) return;
|
|
330
|
-
lxb_dom_element_set_attribute(el,
|
|
331
|
-
reinterpret_cast<const lxb_char_t*>(attr.data()), attr.size(),
|
|
332
|
-
reinterpret_cast<const lxb_char_t*>(value.data()), value.size());
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
std::optional<std::string> LexborDocument::getTextContent(const std::string& selector) const {
|
|
336
|
-
auto* el = static_cast<lxb_dom_element_t*>(findFirst(selector));
|
|
337
|
-
if (!el) return std::nullopt;
|
|
338
|
-
lxb_dom_node_t* node = lxb_dom_interface_node(el);
|
|
339
|
-
size_t len;
|
|
340
|
-
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
341
|
-
if (!text) return std::nullopt;
|
|
342
|
-
std::string result(reinterpret_cast<char*>(text), len);
|
|
343
|
-
lxb_dom_document_destroy_text(node->owner_document, text);
|
|
344
|
-
return result;
|
|
442
|
+
std::string LexborDocument::doctypeName(void* voidDoctype) const {
|
|
443
|
+
if (!voidDoctype) return "";
|
|
444
|
+
size_t len = 0;
|
|
445
|
+
const lxb_char_t* name = lxb_dom_document_type_name(
|
|
446
|
+
static_cast<lxb_dom_document_type_t*>(voidDoctype), &len);
|
|
447
|
+
return name ? std::string(reinterpret_cast<const char*>(name), len) : "";
|
|
345
448
|
}
|
|
346
449
|
|
|
347
|
-
std::
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
450
|
+
std::string LexborDocument::doctypePublicId(void* voidDoctype) const {
|
|
451
|
+
if (!voidDoctype) return "";
|
|
452
|
+
size_t len = 0;
|
|
453
|
+
const lxb_char_t* id = lxb_dom_document_type_public_id(
|
|
454
|
+
static_cast<lxb_dom_document_type_t*>(voidDoctype), &len);
|
|
455
|
+
return id ? std::string(reinterpret_cast<const char*>(id), len) : "";
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
std::string LexborDocument::doctypeSystemId(void* voidDoctype) const {
|
|
459
|
+
if (!voidDoctype) return "";
|
|
460
|
+
size_t len = 0;
|
|
461
|
+
const lxb_char_t* id = lxb_dom_document_type_system_id(
|
|
462
|
+
static_cast<lxb_dom_document_type_t*>(voidDoctype), &len);
|
|
463
|
+
return id ? std::string(reinterpret_cast<const char*>(id), len) : "";
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ── Node.normalize() ─────────────────────────────────────────────────────────
|
|
467
|
+
|
|
468
|
+
namespace {
|
|
469
|
+
|
|
470
|
+
void normalize_children(lxb_dom_node_t* parent) {
|
|
471
|
+
lxb_dom_node_t* child = parent->first_child;
|
|
352
472
|
while (child) {
|
|
353
|
-
|
|
354
|
-
|
|
473
|
+
lxb_dom_node_t* next = child->next;
|
|
474
|
+
|
|
475
|
+
if (child->type == LXB_DOM_NODE_TYPE_TEXT) {
|
|
476
|
+
auto* cd = reinterpret_cast<lxb_dom_character_data_t*>(child);
|
|
477
|
+
std::string merged(reinterpret_cast<const char*>(cd->data.data), cd->data.length);
|
|
478
|
+
size_t old_len = cd->data.length;
|
|
479
|
+
|
|
480
|
+
lxb_dom_node_t* sibling = next;
|
|
481
|
+
while (sibling && sibling->type == LXB_DOM_NODE_TYPE_TEXT) {
|
|
482
|
+
auto* scd = reinterpret_cast<lxb_dom_character_data_t*>(sibling);
|
|
483
|
+
merged.append(reinterpret_cast<const char*>(scd->data.data), scd->data.length);
|
|
484
|
+
lxb_dom_node_t* dead = sibling;
|
|
485
|
+
sibling = sibling->next;
|
|
486
|
+
lxb_dom_node_remove(dead);
|
|
487
|
+
lxb_dom_node_destroy(dead);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (merged.empty()) {
|
|
491
|
+
lxb_dom_node_remove(child);
|
|
492
|
+
lxb_dom_node_destroy(child);
|
|
493
|
+
} else {
|
|
494
|
+
lxb_dom_character_data_replace(cd,
|
|
495
|
+
reinterpret_cast<const lxb_char_t*>(merged.data()), merged.size(),
|
|
496
|
+
0, old_len);
|
|
497
|
+
}
|
|
498
|
+
next = sibling;
|
|
499
|
+
} else if (child->first_child) {
|
|
500
|
+
normalize_children(child);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
child = next;
|
|
355
504
|
}
|
|
356
|
-
return result;
|
|
357
505
|
}
|
|
358
506
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
507
|
+
} // namespace
|
|
508
|
+
|
|
509
|
+
void LexborDocument::normalize(void* voidNode) {
|
|
510
|
+
auto* node = static_cast<lxb_dom_node_t*>(voidNode);
|
|
511
|
+
if (!node) return;
|
|
512
|
+
normalize_children(node);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// ── Node.compareDocumentPosition() ──────────────────────────────────────────
|
|
516
|
+
|
|
517
|
+
namespace {
|
|
518
|
+
|
|
519
|
+
constexpr int kDocumentPositionDisconnected = 0x01;
|
|
520
|
+
constexpr int kDocumentPositionPreceding = 0x02;
|
|
521
|
+
constexpr int kDocumentPositionFollowing = 0x04;
|
|
522
|
+
constexpr int kDocumentPositionContains = 0x08;
|
|
523
|
+
constexpr int kDocumentPositionContainedBy = 0x10;
|
|
524
|
+
constexpr int kDocumentPositionImplementationSpecific = 0x20;
|
|
525
|
+
|
|
526
|
+
lxb_dom_node_t* root_of(lxb_dom_node_t* n) {
|
|
527
|
+
while (n->parent) n = n->parent;
|
|
528
|
+
return n;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
bool is_ancestor(lxb_dom_node_t* ancestor, lxb_dom_node_t* node) {
|
|
532
|
+
for (auto* p = node->parent; p; p = p->parent) {
|
|
533
|
+
if (p == ancestor) return true;
|
|
534
|
+
}
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
} // namespace
|
|
539
|
+
|
|
540
|
+
int LexborDocument::compareDocumentPosition(void* voidA, void* voidB) const {
|
|
541
|
+
auto* a = static_cast<lxb_dom_node_t*>(voidA);
|
|
542
|
+
auto* b = static_cast<lxb_dom_node_t*>(voidB);
|
|
543
|
+
if (!a || !b || a == b) return 0;
|
|
544
|
+
|
|
545
|
+
if (root_of(a) != root_of(b)) {
|
|
546
|
+
return kDocumentPositionDisconnected | kDocumentPositionImplementationSpecific
|
|
547
|
+
| kDocumentPositionFollowing;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// b is a descendant of a
|
|
551
|
+
if (is_ancestor(a, b)) return kDocumentPositionContainedBy | kDocumentPositionFollowing;
|
|
552
|
+
// a is a descendant of b
|
|
553
|
+
if (is_ancestor(b, a)) return kDocumentPositionContains | kDocumentPositionPreceding;
|
|
554
|
+
|
|
555
|
+
std::vector<lxb_dom_node_t*> chainA, chainB;
|
|
556
|
+
for (auto* p = a; p; p = p->parent) chainA.push_back(p);
|
|
557
|
+
for (auto* p = b; p; p = p->parent) chainB.push_back(p);
|
|
558
|
+
std::reverse(chainA.begin(), chainA.end());
|
|
559
|
+
std::reverse(chainB.begin(), chainB.end());
|
|
560
|
+
|
|
561
|
+
size_t i = 0;
|
|
562
|
+
while (i < chainA.size() && i < chainB.size() && chainA[i] == chainB[i]) i++;
|
|
563
|
+
// chainA[i-1] == chainB[i-1] is the lowest common ancestor (guaranteed to
|
|
564
|
+
// exist since both nodes share the same root and neither contains the other).
|
|
565
|
+
lxb_dom_node_t* lca = chainA[i - 1];
|
|
566
|
+
lxb_dom_node_t* branchA = chainA[i];
|
|
567
|
+
lxb_dom_node_t* branchB = chainB[i];
|
|
568
|
+
|
|
569
|
+
for (lxb_dom_node_t* c = lca->first_child; c; c = c->next) {
|
|
570
|
+
if (c == branchA) return kDocumentPositionFollowing;
|
|
571
|
+
if (c == branchB) return kDocumentPositionPreceding;
|
|
572
|
+
}
|
|
573
|
+
return kDocumentPositionDisconnected | kDocumentPositionImplementationSpecific
|
|
574
|
+
| kDocumentPositionFollowing;
|
|
363
575
|
}
|
|
364
576
|
|
|
365
577
|
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <string>
|
|
4
|
-
#include <optional>
|
|
5
4
|
#include <vector>
|
|
6
5
|
|
|
7
6
|
namespace margelo::nitro::nitrojsdom {
|
|
@@ -39,9 +38,52 @@ public:
|
|
|
39
38
|
void* createComment(const std::string& text);
|
|
40
39
|
void* createDocumentFragment();
|
|
41
40
|
|
|
41
|
+
// ── Namespaces (createElementNS / Element.namespaceURI) ───────────────────
|
|
42
|
+
// qualifiedName may be "prefix:localName" or a bare "localName". nsUri may be
|
|
43
|
+
// empty for the null namespace. Lexbor registers arbitrary namespace URIs
|
|
44
|
+
// dynamically (lxb_ns_append), so any URI works, not just HTML/SVG/MathML.
|
|
45
|
+
// Note: lxb_tag_append_lower lowercases local_name internally, so
|
|
46
|
+
// mixed-case foreign tag/attribute names (e.g. SVG's "viewBox",
|
|
47
|
+
// "linearGradient") come back lowercased — a lexbor limitation, not
|
|
48
|
+
// something this binding can work around.
|
|
49
|
+
void* createElementNS(const std::string& nsUri, const std::string& qualifiedName);
|
|
50
|
+
|
|
51
|
+
// Returns the namespace URI for a node, or "" if the node has no namespace
|
|
52
|
+
// (LXB_NS__UNDEF) — callers should map "" to JS null.
|
|
53
|
+
std::string namespaceURI(void* node) const;
|
|
54
|
+
|
|
55
|
+
// ── Shadow DOM ─────────────────────────────────────────────────────────────
|
|
56
|
+
// mode: 0 = open, 1 = closed (matches lxb_dom_shadow_root_mode_t).
|
|
57
|
+
void* createShadowRoot(void* hostElement, int mode);
|
|
58
|
+
|
|
59
|
+
// ── <template> ─────────────────────────────────────────────────────────────
|
|
60
|
+
// Returns the lxb_dom_document_fragment_t* backing a <template> element's
|
|
61
|
+
// `content`. Lexbor allocates this at element-creation time (both for parsed
|
|
62
|
+
// and createElement()'d templates), so this is a plain field read.
|
|
63
|
+
void* templateContent(void* templateEl) const;
|
|
64
|
+
|
|
65
|
+
// ── Doctype ────────────────────────────────────────────────────────────────
|
|
66
|
+
// Returns the lxb_dom_document_type_t* for this document, or nullptr if the
|
|
67
|
+
// parsed HTML had no doctype.
|
|
68
|
+
void* doctype() const;
|
|
69
|
+
std::string doctypeName(void* doctype) const;
|
|
70
|
+
std::string doctypePublicId(void* doctype) const;
|
|
71
|
+
std::string doctypeSystemId(void* doctype) const;
|
|
72
|
+
|
|
73
|
+
// ── Node.normalize() ───────────────────────────────────────────────────────
|
|
74
|
+
// Merges adjacent Text node siblings and removes empty ones, recursively
|
|
75
|
+
// through node's subtree (node itself is not replaced, only its descendants).
|
|
76
|
+
void normalize(void* node);
|
|
77
|
+
|
|
78
|
+
// ── Node.compareDocumentPosition() ────────────────────────────────────────
|
|
79
|
+
// Returns the DOM DOCUMENT_POSITION_* bitmask describing nodeB's position
|
|
80
|
+
// relative to nodeA (mirrors `nodeA.compareDocumentPosition(nodeB)`).
|
|
81
|
+
int compareDocumentPosition(void* nodeA, void* nodeB) const;
|
|
82
|
+
|
|
42
83
|
// ── Element content ───────────────────────────────────────────────────────
|
|
43
84
|
void setTextContentOnEl(void* element, const std::string& text);
|
|
44
85
|
void setInnerHTMLOnEl(void* element, const std::string& html);
|
|
86
|
+
void setInnerHTMLOnShadowRoot(void* shadowRoot, void* hostElement, const std::string& html);
|
|
45
87
|
|
|
46
88
|
// Like setInnerHTMLOnEl, but returns the detached parsed nodes instead of
|
|
47
89
|
// replacing contextElement's children — used by insertAdjacentHTML.
|
|
@@ -50,15 +92,6 @@ public:
|
|
|
50
92
|
// ── Selector matching ─────────────────────────────────────────────────────
|
|
51
93
|
bool matchesSelector(void* element, const std::string& sel) const;
|
|
52
94
|
|
|
53
|
-
// ── Legacy selector-based API (kept for backward compat) ─────────────────
|
|
54
|
-
std::optional<std::string> querySelector(const std::string& selector) const;
|
|
55
|
-
std::vector<std::string> querySelectorAll(const std::string& selector) const;
|
|
56
|
-
std::optional<std::string> getAttribute(const std::string& selector, const std::string& attr) const;
|
|
57
|
-
void setAttribute(const std::string& selector, const std::string& attr, const std::string& value);
|
|
58
|
-
std::optional<std::string> getTextContent(const std::string& selector) const;
|
|
59
|
-
std::optional<std::string> getInnerHTML(const std::string& selector) const;
|
|
60
|
-
void setInnerHTML(const std::string& selector, const std::string& html);
|
|
61
|
-
|
|
62
95
|
private:
|
|
63
96
|
void* _document { nullptr };
|
|
64
97
|
void* _cssParser { nullptr };
|
|
@@ -4,12 +4,30 @@
|
|
|
4
4
|
#include "Storage.hpp"
|
|
5
5
|
#include "QuickJSRuntime.hpp"
|
|
6
6
|
#include "../lexbor/LexborDocument.hpp"
|
|
7
|
+
#include "bindings/DOMExceptionBindings.hpp"
|
|
7
8
|
#include "bindings/ElementBindings.hpp"
|
|
8
9
|
#include "bindings/DocumentBindings.hpp"
|
|
10
|
+
#include "bindings/DOMParserBindings.hpp"
|
|
9
11
|
#include "bindings/EventBindings.hpp"
|
|
10
12
|
#include "bindings/TimerBindings.hpp"
|
|
11
13
|
#include "bindings/WindowBindings.hpp"
|
|
12
14
|
#include "bindings/FetchBindings.hpp"
|
|
15
|
+
#include "bindings/LiveCollectionBindings.hpp"
|
|
16
|
+
#include "bindings/UrlBindings.hpp"
|
|
17
|
+
#include "bindings/AbortBindings.hpp"
|
|
18
|
+
#include "bindings/TextEncodingBindings.hpp"
|
|
19
|
+
#include "bindings/FormBindings.hpp"
|
|
20
|
+
#include "bindings/BlobBindings.hpp"
|
|
21
|
+
#include "bindings/CSSOMBindings.hpp"
|
|
22
|
+
#include "bindings/ShadowRootBindings.hpp"
|
|
23
|
+
#include "bindings/CustomElementsBindings.hpp"
|
|
24
|
+
#include "bindings/CookieBindings.hpp"
|
|
25
|
+
#include "bindings/TemplateBindings.hpp"
|
|
26
|
+
#include "bindings/SlotBindings.hpp"
|
|
27
|
+
#include "bindings/XmlSerializerBindings.hpp"
|
|
28
|
+
#include "bindings/LayoutStubBindings.hpp"
|
|
29
|
+
#include "bindings/TreeWalkerBindings.hpp"
|
|
30
|
+
#include "bindings/EventTargetBindings.hpp"
|
|
13
31
|
#include <lexbor/html/html.h>
|
|
14
32
|
#include <lexbor/dom/dom.h>
|
|
15
33
|
|
|
@@ -22,14 +40,32 @@ namespace margelo::nitro::nitrojsdom {
|
|
|
22
40
|
|
|
23
41
|
void DOMBindings::install(QuickJSRuntime* runtime, LexborDocument* document) {
|
|
24
42
|
JSContext* ctx = static_cast<JSContext*>(runtime->context());
|
|
25
|
-
(
|
|
43
|
+
register_document(ctx, document); // so doc_for_node() resolves nodes back to the primary document
|
|
26
44
|
|
|
45
|
+
DOMExceptionBindings::install(ctx); // no dependencies; other modules throw DOMException instances
|
|
46
|
+
LiveCollectionBindings::install(ctx);
|
|
27
47
|
ElementBindings::install(ctx); // registers the Element class + proto
|
|
48
|
+
ShadowRootBindings::install(ctx); // needs Element's proto + js_node_class_id's proto
|
|
28
49
|
DocumentBindings::install(ctx); // creates globalThis.document
|
|
50
|
+
DOMParserBindings::install(ctx); // adds document.implementation + globalThis.DOMParser; needs globalThis.document
|
|
51
|
+
CookieBindings::install(ctx); // needs globalThis.document to exist
|
|
52
|
+
TemplateBindings::install(ctx); // needs Element's proto
|
|
53
|
+
CustomElementsBindings::install(ctx); // needs Element/ShadowRoot protos + globalThis.document
|
|
54
|
+
CSSOMBindings::install(ctx); // needs Element's proto + globalThis.document to exist
|
|
29
55
|
EventBindings::install(ctx); // needs Element's proto + globalThis.document to exist
|
|
30
56
|
TimerBindings::install(ctx);
|
|
31
57
|
WindowBindings::install(ctx);
|
|
58
|
+
UrlBindings::install(ctx);
|
|
59
|
+
AbortBindings::install(ctx);
|
|
60
|
+
TextEncodingBindings::install(ctx);
|
|
61
|
+
BlobBindings::install(ctx); // uses TextEncoder/TextDecoder + btoa, so must run after both
|
|
32
62
|
FetchBindings::install(ctx); // XHR bootstrap uses `new Event(...)`, so must run after EventBindings
|
|
63
|
+
FormBindings::install(ctx); // uses globalThis.Element + globalThis.Event, so must run after both
|
|
64
|
+
SlotBindings::install(ctx); // uses Event/dispatchEvent + Element/ShadowRoot protos, so must run after EventBindings/ShadowRootBindings
|
|
65
|
+
XmlSerializerBindings::install(ctx); // pure serialization, no ordering requirement beyond ElementBindings
|
|
66
|
+
LayoutStubBindings::install(ctx); // needs Element's proto + globalThis.document to exist
|
|
67
|
+
TreeWalkerBindings::install(ctx); // needs Element's proto (Node traversal props) + globalThis.document to exist
|
|
68
|
+
EventTargetBindings::install(ctx); // no dependencies
|
|
33
69
|
|
|
34
70
|
// ── localStorage / sessionStorage ──────────────────────────────────────────
|
|
35
71
|
{
|