@salve-software/react-native-nitro-jsdom 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/CMakeLists.txt +14 -0
- package/cpp/HybridHtmlSandbox.cpp +16 -3
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +217 -67
- package/cpp/lexbor/LexborDocument.hpp +29 -10
- package/cpp/quickjs/DOMBindings.cpp +28 -0
- package/cpp/quickjs/DOMBindingsInternal.cpp +34 -2
- package/cpp/quickjs/DOMBindingsInternal.hpp +17 -1
- package/cpp/quickjs/QuickJSRuntime.cpp +19 -4
- package/cpp/quickjs/QuickJSRuntime.hpp +22 -1
- package/cpp/quickjs/bindings/AbortBindings.cpp +114 -0
- package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +137 -0
- package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +244 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +23 -0
- package/cpp/quickjs/bindings/ClassListBindings.cpp +3 -2
- package/cpp/quickjs/bindings/CookieBindings.cpp +118 -0
- package/cpp/quickjs/bindings/CookieBindings.hpp +23 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +341 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.hpp +50 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.cpp +111 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.hpp +20 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +84 -4
- package/cpp/quickjs/bindings/ElementBindings.cpp +264 -28
- package/cpp/quickjs/bindings/EventBindings.cpp +137 -3
- package/cpp/quickjs/bindings/FetchBindings.cpp +5 -1
- package/cpp/quickjs/bindings/FormBindings.cpp +142 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +16 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +183 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.hpp +16 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.cpp +171 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.hpp +27 -0
- package/cpp/quickjs/bindings/SlotBindings.cpp +161 -0
- package/cpp/quickjs/bindings/SlotBindings.hpp +44 -0
- package/cpp/quickjs/bindings/TemplateBindings.cpp +42 -0
- package/cpp/quickjs/bindings/TemplateBindings.hpp +29 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.cpp +117 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.hpp +14 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +41 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +286 -0
- package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +270 -0
- package/cpp/quickjs/bindings/XmlSerializerBindings.cpp +44 -0
- package/cpp/quickjs/bindings/XmlSerializerBindings.hpp +24 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
- package/package.json +1 -1
- package/src/classes/JSDOM/JSDOM.class.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
package/android/CMakeLists.txt
CHANGED
|
@@ -48,6 +48,7 @@ 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,6 +56,19 @@ add_library(${PACKAGE_NAME} SHARED
|
|
|
55
56
|
../cpp/quickjs/bindings/TimerBindings.cpp
|
|
56
57
|
../cpp/quickjs/bindings/WindowBindings.cpp
|
|
57
58
|
../cpp/quickjs/bindings/FetchBindings.cpp
|
|
59
|
+
../cpp/quickjs/bindings/LiveCollectionBindings.cpp
|
|
60
|
+
../cpp/quickjs/bindings/UrlBindings.cpp
|
|
61
|
+
../cpp/quickjs/bindings/AbortBindings.cpp
|
|
62
|
+
../cpp/quickjs/bindings/TextEncodingBindings.cpp
|
|
63
|
+
../cpp/quickjs/bindings/FormBindings.cpp
|
|
64
|
+
../cpp/quickjs/bindings/BlobBindings.cpp
|
|
65
|
+
../cpp/quickjs/bindings/CSSOMBindings.cpp
|
|
66
|
+
../cpp/quickjs/bindings/ShadowRootBindings.cpp
|
|
67
|
+
../cpp/quickjs/bindings/CustomElementsBindings.cpp
|
|
68
|
+
../cpp/quickjs/bindings/CookieBindings.cpp
|
|
69
|
+
../cpp/quickjs/bindings/TemplateBindings.cpp
|
|
70
|
+
../cpp/quickjs/bindings/SlotBindings.cpp
|
|
71
|
+
../cpp/quickjs/bindings/XmlSerializerBindings.cpp
|
|
58
72
|
${QUICKJS_SOURCES}
|
|
59
73
|
)
|
|
60
74
|
|
|
@@ -34,20 +34,33 @@ 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
|
+
try {
|
|
59
|
+
_runtime->evaluate(
|
|
60
|
+
"document.dispatchEvent(new Event('DOMContentLoaded', { bubbles: true, cancelable: true }));"
|
|
61
|
+
"document.dispatchEvent(new Event('load'));");
|
|
62
|
+
} catch (...) { }
|
|
63
|
+
|
|
51
64
|
_initialized = true;
|
|
52
65
|
}
|
|
53
66
|
|
|
@@ -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,17 @@
|
|
|
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>
|
|
6
9
|
#include <stdexcept>
|
|
10
|
+
#include <cctype>
|
|
11
|
+
#include <cstring>
|
|
12
|
+
#include <unordered_set>
|
|
13
|
+
#include <vector>
|
|
14
|
+
#include <algorithm>
|
|
7
15
|
|
|
8
16
|
namespace margelo::nitro::nitrojsdom {
|
|
9
17
|
|
|
@@ -22,6 +30,26 @@ static std::string serializeNode(lxb_dom_node_t* node) {
|
|
|
22
30
|
return result;
|
|
23
31
|
}
|
|
24
32
|
|
|
33
|
+
bool is_javascript_mime_type(const std::string& type) {
|
|
34
|
+
if (type.empty()) return true;
|
|
35
|
+
std::string t;
|
|
36
|
+
t.reserve(type.size());
|
|
37
|
+
for (char c : type) t += static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
|
38
|
+
size_t start = t.find_first_not_of(" \t\n\r\f");
|
|
39
|
+
if (start == std::string::npos) return true;
|
|
40
|
+
size_t end = t.find_last_not_of(" \t\n\r\f");
|
|
41
|
+
t = t.substr(start, end - start + 1);
|
|
42
|
+
|
|
43
|
+
static const std::unordered_set<std::string> kJsTypes = {
|
|
44
|
+
"text/javascript", "application/javascript", "application/ecmascript",
|
|
45
|
+
"application/x-ecmascript", "application/x-javascript", "text/ecmascript",
|
|
46
|
+
"text/javascript1.0", "text/javascript1.1", "text/javascript1.2",
|
|
47
|
+
"text/javascript1.3", "text/javascript1.4", "text/javascript1.5",
|
|
48
|
+
"text/jscript", "text/livescript", "text/x-ecmascript", "text/x-javascript",
|
|
49
|
+
};
|
|
50
|
+
return kJsTypes.count(t) > 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
struct FindFirstCtx { lxb_dom_element_t* result = nullptr; };
|
|
26
54
|
|
|
27
55
|
static lxb_status_t findFirstCallback(lxb_dom_node_t* node, lxb_css_selector_specificity_t, void* ctx) {
|
|
@@ -29,11 +57,6 @@ static lxb_status_t findFirstCallback(lxb_dom_node_t* node, lxb_css_selector_spe
|
|
|
29
57
|
return LXB_STATUS_STOP;
|
|
30
58
|
}
|
|
31
59
|
|
|
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
60
|
static lxb_status_t findAllPtrCallback(lxb_dom_node_t* node, lxb_css_selector_specificity_t, void* ctx) {
|
|
38
61
|
static_cast<std::vector<void*>*>(ctx)->push_back(lxb_dom_interface_element(node));
|
|
39
62
|
return LXB_STATUS_OK;
|
|
@@ -134,14 +157,14 @@ std::vector<void*> LexborDocument::querySelectorAll_el(const std::string& sel) c
|
|
|
134
157
|
|
|
135
158
|
void* LexborDocument::querySelectorFromEl(void* element, const std::string& sel) const {
|
|
136
159
|
if (!element) return nullptr;
|
|
137
|
-
return findFirstFrom(
|
|
160
|
+
return findFirstFrom(static_cast<lxb_dom_node_t*>(element), sel);
|
|
138
161
|
}
|
|
139
162
|
|
|
140
163
|
std::vector<void*> LexborDocument::querySelectorAllFromEl(void* element, const std::string& sel) const {
|
|
141
164
|
if (!_document || !element) return {};
|
|
142
165
|
auto* parser = static_cast<lxb_css_parser_t*>(_cssParser);
|
|
143
166
|
auto* selectors = static_cast<lxb_selectors_t*>(_selectors);
|
|
144
|
-
auto* node =
|
|
167
|
+
auto* node = static_cast<lxb_dom_node_t*>(element);
|
|
145
168
|
|
|
146
169
|
lxb_css_selector_list_t* list = lxb_css_selectors_parse(parser,
|
|
147
170
|
reinterpret_cast<const lxb_char_t*>(sel.data()), sel.size());
|
|
@@ -179,7 +202,15 @@ std::vector<std::string> LexborDocument::getScriptContents() const {
|
|
|
179
202
|
contents.reserve(scriptEls.size());
|
|
180
203
|
|
|
181
204
|
for (void* el : scriptEls) {
|
|
182
|
-
auto*
|
|
205
|
+
auto* element = static_cast<lxb_dom_element_t*>(el);
|
|
206
|
+
|
|
207
|
+
size_t type_len = 0;
|
|
208
|
+
const lxb_char_t* type_attr = lxb_dom_element_get_attribute(element,
|
|
209
|
+
reinterpret_cast<const lxb_char_t*>("type"), 4, &type_len);
|
|
210
|
+
std::string type = type_attr ? std::string(reinterpret_cast<const char*>(type_attr), type_len) : "";
|
|
211
|
+
if (!is_javascript_mime_type(type)) continue;
|
|
212
|
+
|
|
213
|
+
auto* node = lxb_dom_interface_node(element);
|
|
183
214
|
size_t len = 0;
|
|
184
215
|
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
185
216
|
if (text && len > 0) {
|
|
@@ -219,6 +250,16 @@ void* LexborDocument::createDocumentFragment() {
|
|
|
219
250
|
return lxb_dom_document_create_document_fragment(dom_doc);
|
|
220
251
|
}
|
|
221
252
|
|
|
253
|
+
void* LexborDocument::createShadowRoot(void* hostElement, int mode) {
|
|
254
|
+
if (!_document || !hostElement) return nullptr;
|
|
255
|
+
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
256
|
+
auto* shadow = lxb_dom_shadow_root_interface_create(dom_doc);
|
|
257
|
+
if (!shadow) return nullptr;
|
|
258
|
+
shadow->host = static_cast<lxb_dom_element_t*>(hostElement);
|
|
259
|
+
shadow->mode = static_cast<lxb_dom_shadow_root_mode_t>(mode);
|
|
260
|
+
return shadow;
|
|
261
|
+
}
|
|
262
|
+
|
|
222
263
|
// ── Element content ──────────────────────────────────────────────────────────
|
|
223
264
|
|
|
224
265
|
void LexborDocument::setTextContentOnEl(void* element, const std::string& text) {
|
|
@@ -242,10 +283,40 @@ void LexborDocument::setTextContentOnEl(void* element, const std::string& text)
|
|
|
242
283
|
}
|
|
243
284
|
}
|
|
244
285
|
|
|
286
|
+
void LexborDocument::setInnerHTMLOnShadowRoot(void* shadowRoot, void* hostElement, const std::string& html) {
|
|
287
|
+
auto* host = static_cast<lxb_dom_element_t*>(hostElement);
|
|
288
|
+
auto* doc = static_cast<lxb_html_document_t*>(_document);
|
|
289
|
+
auto* root = static_cast<lxb_dom_node_t*>(shadowRoot);
|
|
290
|
+
|
|
291
|
+
lxb_dom_node_t* frag = lxb_html_document_parse_fragment(doc, host,
|
|
292
|
+
reinterpret_cast<const lxb_char_t*>(html.data()), html.size());
|
|
293
|
+
if (!frag) return;
|
|
294
|
+
|
|
295
|
+
while (root->first_child) lxb_dom_node_destroy_deep(root->first_child);
|
|
296
|
+
|
|
297
|
+
while (frag->first_child) {
|
|
298
|
+
lxb_dom_node_t* child = frag->first_child;
|
|
299
|
+
lxb_dom_node_remove(child);
|
|
300
|
+
lxb_dom_node_insert_child(root, child);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
lxb_dom_node_destroy(frag);
|
|
304
|
+
}
|
|
305
|
+
|
|
245
306
|
void LexborDocument::setInnerHTMLOnEl(void* element, const std::string& html) {
|
|
246
307
|
auto* el = static_cast<lxb_dom_element_t*>(element);
|
|
247
308
|
auto* doc = static_cast<lxb_html_document_t*>(_document);
|
|
248
|
-
|
|
309
|
+
|
|
310
|
+
// <template>.innerHTML populates .content (a separate inert fragment), not
|
|
311
|
+
// the template element's own light-DOM children — matches the parser's own
|
|
312
|
+
// "in template" insertion mode behavior for parsed <template> markup.
|
|
313
|
+
size_t tag_len = 0;
|
|
314
|
+
const lxb_char_t* tag_name = lxb_dom_element_local_name(el, &tag_len);
|
|
315
|
+
bool is_template = tag_len == 8 && memcmp(tag_name, "template", 8) == 0;
|
|
316
|
+
|
|
317
|
+
lxb_dom_node_t* root = is_template
|
|
318
|
+
? lxb_dom_interface_node(reinterpret_cast<lxb_html_template_element_t*>(el)->content)
|
|
319
|
+
: lxb_dom_interface_node(el);
|
|
249
320
|
|
|
250
321
|
lxb_dom_node_t* frag = lxb_html_document_parse_fragment(doc, el,
|
|
251
322
|
reinterpret_cast<const lxb_char_t*>(html.data()), html.size());
|
|
@@ -290,76 +361,155 @@ bool LexborDocument::matchesSelector(void* element, const std::string& sel) cons
|
|
|
290
361
|
return false;
|
|
291
362
|
}
|
|
292
363
|
|
|
293
|
-
// ──
|
|
364
|
+
// ── <template> ────────────────────────────────────────────────────────────
|
|
294
365
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
return
|
|
366
|
+
void* LexborDocument::templateContent(void* templateEl) const {
|
|
367
|
+
if (!templateEl) return nullptr;
|
|
368
|
+
auto* tpl = reinterpret_cast<lxb_html_template_element_t*>(templateEl);
|
|
369
|
+
return tpl->content;
|
|
299
370
|
}
|
|
300
371
|
|
|
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);
|
|
372
|
+
// ── Doctype ──────────────────────────────────────────────────────────────────
|
|
306
373
|
|
|
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;
|
|
374
|
+
void* LexborDocument::doctype() const {
|
|
375
|
+
if (!_document) return nullptr;
|
|
376
|
+
auto* dom_doc = lxb_dom_interface_document(static_cast<lxb_html_document_t*>(_document));
|
|
377
|
+
return dom_doc->doctype;
|
|
315
378
|
}
|
|
316
379
|
|
|
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;
|
|
380
|
+
std::string LexborDocument::doctypeName(void* voidDoctype) const {
|
|
381
|
+
if (!voidDoctype) return "";
|
|
382
|
+
size_t len = 0;
|
|
383
|
+
const lxb_char_t* name = lxb_dom_document_type_name(
|
|
384
|
+
static_cast<lxb_dom_document_type_t*>(voidDoctype), &len);
|
|
385
|
+
return name ? std::string(reinterpret_cast<const char*>(name), len) : "";
|
|
345
386
|
}
|
|
346
387
|
|
|
347
|
-
std::
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
388
|
+
std::string LexborDocument::doctypePublicId(void* voidDoctype) const {
|
|
389
|
+
if (!voidDoctype) return "";
|
|
390
|
+
size_t len = 0;
|
|
391
|
+
const lxb_char_t* id = lxb_dom_document_type_public_id(
|
|
392
|
+
static_cast<lxb_dom_document_type_t*>(voidDoctype), &len);
|
|
393
|
+
return id ? std::string(reinterpret_cast<const char*>(id), len) : "";
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
std::string LexborDocument::doctypeSystemId(void* voidDoctype) const {
|
|
397
|
+
if (!voidDoctype) return "";
|
|
398
|
+
size_t len = 0;
|
|
399
|
+
const lxb_char_t* id = lxb_dom_document_type_system_id(
|
|
400
|
+
static_cast<lxb_dom_document_type_t*>(voidDoctype), &len);
|
|
401
|
+
return id ? std::string(reinterpret_cast<const char*>(id), len) : "";
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// ── Node.normalize() ─────────────────────────────────────────────────────────
|
|
405
|
+
|
|
406
|
+
namespace {
|
|
407
|
+
|
|
408
|
+
void normalize_children(lxb_dom_node_t* parent) {
|
|
409
|
+
lxb_dom_node_t* child = parent->first_child;
|
|
352
410
|
while (child) {
|
|
353
|
-
|
|
354
|
-
|
|
411
|
+
lxb_dom_node_t* next = child->next;
|
|
412
|
+
|
|
413
|
+
if (child->type == LXB_DOM_NODE_TYPE_TEXT) {
|
|
414
|
+
auto* cd = reinterpret_cast<lxb_dom_character_data_t*>(child);
|
|
415
|
+
std::string merged(reinterpret_cast<const char*>(cd->data.data), cd->data.length);
|
|
416
|
+
size_t old_len = cd->data.length;
|
|
417
|
+
|
|
418
|
+
lxb_dom_node_t* sibling = next;
|
|
419
|
+
while (sibling && sibling->type == LXB_DOM_NODE_TYPE_TEXT) {
|
|
420
|
+
auto* scd = reinterpret_cast<lxb_dom_character_data_t*>(sibling);
|
|
421
|
+
merged.append(reinterpret_cast<const char*>(scd->data.data), scd->data.length);
|
|
422
|
+
lxb_dom_node_t* dead = sibling;
|
|
423
|
+
sibling = sibling->next;
|
|
424
|
+
lxb_dom_node_remove(dead);
|
|
425
|
+
lxb_dom_node_destroy(dead);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (merged.empty()) {
|
|
429
|
+
lxb_dom_node_remove(child);
|
|
430
|
+
lxb_dom_node_destroy(child);
|
|
431
|
+
} else {
|
|
432
|
+
lxb_dom_character_data_replace(cd,
|
|
433
|
+
reinterpret_cast<const lxb_char_t*>(merged.data()), merged.size(),
|
|
434
|
+
0, old_len);
|
|
435
|
+
}
|
|
436
|
+
next = sibling;
|
|
437
|
+
} else if (child->first_child) {
|
|
438
|
+
normalize_children(child);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
child = next;
|
|
355
442
|
}
|
|
356
|
-
return result;
|
|
357
443
|
}
|
|
358
444
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
445
|
+
} // namespace
|
|
446
|
+
|
|
447
|
+
void LexborDocument::normalize(void* voidNode) {
|
|
448
|
+
auto* node = static_cast<lxb_dom_node_t*>(voidNode);
|
|
449
|
+
if (!node) return;
|
|
450
|
+
normalize_children(node);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ── Node.compareDocumentPosition() ──────────────────────────────────────────
|
|
454
|
+
|
|
455
|
+
namespace {
|
|
456
|
+
|
|
457
|
+
constexpr int kDocumentPositionDisconnected = 0x01;
|
|
458
|
+
constexpr int kDocumentPositionPreceding = 0x02;
|
|
459
|
+
constexpr int kDocumentPositionFollowing = 0x04;
|
|
460
|
+
constexpr int kDocumentPositionContains = 0x08;
|
|
461
|
+
constexpr int kDocumentPositionContainedBy = 0x10;
|
|
462
|
+
constexpr int kDocumentPositionImplementationSpecific = 0x20;
|
|
463
|
+
|
|
464
|
+
lxb_dom_node_t* root_of(lxb_dom_node_t* n) {
|
|
465
|
+
while (n->parent) n = n->parent;
|
|
466
|
+
return n;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
bool is_ancestor(lxb_dom_node_t* ancestor, lxb_dom_node_t* node) {
|
|
470
|
+
for (auto* p = node->parent; p; p = p->parent) {
|
|
471
|
+
if (p == ancestor) return true;
|
|
472
|
+
}
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
} // namespace
|
|
477
|
+
|
|
478
|
+
int LexborDocument::compareDocumentPosition(void* voidA, void* voidB) const {
|
|
479
|
+
auto* a = static_cast<lxb_dom_node_t*>(voidA);
|
|
480
|
+
auto* b = static_cast<lxb_dom_node_t*>(voidB);
|
|
481
|
+
if (!a || !b || a == b) return 0;
|
|
482
|
+
|
|
483
|
+
if (root_of(a) != root_of(b)) {
|
|
484
|
+
return kDocumentPositionDisconnected | kDocumentPositionImplementationSpecific
|
|
485
|
+
| kDocumentPositionFollowing;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// b is a descendant of a
|
|
489
|
+
if (is_ancestor(a, b)) return kDocumentPositionContainedBy | kDocumentPositionFollowing;
|
|
490
|
+
// a is a descendant of b
|
|
491
|
+
if (is_ancestor(b, a)) return kDocumentPositionContains | kDocumentPositionPreceding;
|
|
492
|
+
|
|
493
|
+
std::vector<lxb_dom_node_t*> chainA, chainB;
|
|
494
|
+
for (auto* p = a; p; p = p->parent) chainA.push_back(p);
|
|
495
|
+
for (auto* p = b; p; p = p->parent) chainB.push_back(p);
|
|
496
|
+
std::reverse(chainA.begin(), chainA.end());
|
|
497
|
+
std::reverse(chainB.begin(), chainB.end());
|
|
498
|
+
|
|
499
|
+
size_t i = 0;
|
|
500
|
+
while (i < chainA.size() && i < chainB.size() && chainA[i] == chainB[i]) i++;
|
|
501
|
+
// chainA[i-1] == chainB[i-1] is the lowest common ancestor (guaranteed to
|
|
502
|
+
// exist since both nodes share the same root and neither contains the other).
|
|
503
|
+
lxb_dom_node_t* lca = chainA[i - 1];
|
|
504
|
+
lxb_dom_node_t* branchA = chainA[i];
|
|
505
|
+
lxb_dom_node_t* branchB = chainB[i];
|
|
506
|
+
|
|
507
|
+
for (lxb_dom_node_t* c = lca->first_child; c; c = c->next) {
|
|
508
|
+
if (c == branchA) return kDocumentPositionFollowing;
|
|
509
|
+
if (c == branchB) return kDocumentPositionPreceding;
|
|
510
|
+
}
|
|
511
|
+
return kDocumentPositionDisconnected | kDocumentPositionImplementationSpecific
|
|
512
|
+
| kDocumentPositionFollowing;
|
|
363
513
|
}
|
|
364
514
|
|
|
365
515
|
} // 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,38 @@ public:
|
|
|
39
38
|
void* createComment(const std::string& text);
|
|
40
39
|
void* createDocumentFragment();
|
|
41
40
|
|
|
41
|
+
// ── Shadow DOM ─────────────────────────────────────────────────────────────
|
|
42
|
+
// mode: 0 = open, 1 = closed (matches lxb_dom_shadow_root_mode_t).
|
|
43
|
+
void* createShadowRoot(void* hostElement, int mode);
|
|
44
|
+
|
|
45
|
+
// ── <template> ─────────────────────────────────────────────────────────────
|
|
46
|
+
// Returns the lxb_dom_document_fragment_t* backing a <template> element's
|
|
47
|
+
// `content`. Lexbor allocates this at element-creation time (both for parsed
|
|
48
|
+
// and createElement()'d templates), so this is a plain field read.
|
|
49
|
+
void* templateContent(void* templateEl) const;
|
|
50
|
+
|
|
51
|
+
// ── Doctype ────────────────────────────────────────────────────────────────
|
|
52
|
+
// Returns the lxb_dom_document_type_t* for this document, or nullptr if the
|
|
53
|
+
// parsed HTML had no doctype.
|
|
54
|
+
void* doctype() const;
|
|
55
|
+
std::string doctypeName(void* doctype) const;
|
|
56
|
+
std::string doctypePublicId(void* doctype) const;
|
|
57
|
+
std::string doctypeSystemId(void* doctype) const;
|
|
58
|
+
|
|
59
|
+
// ── Node.normalize() ───────────────────────────────────────────────────────
|
|
60
|
+
// Merges adjacent Text node siblings and removes empty ones, recursively
|
|
61
|
+
// through node's subtree (node itself is not replaced, only its descendants).
|
|
62
|
+
void normalize(void* node);
|
|
63
|
+
|
|
64
|
+
// ── Node.compareDocumentPosition() ────────────────────────────────────────
|
|
65
|
+
// Returns the DOM DOCUMENT_POSITION_* bitmask describing nodeB's position
|
|
66
|
+
// relative to nodeA (mirrors `nodeA.compareDocumentPosition(nodeB)`).
|
|
67
|
+
int compareDocumentPosition(void* nodeA, void* nodeB) const;
|
|
68
|
+
|
|
42
69
|
// ── Element content ───────────────────────────────────────────────────────
|
|
43
70
|
void setTextContentOnEl(void* element, const std::string& text);
|
|
44
71
|
void setInnerHTMLOnEl(void* element, const std::string& html);
|
|
72
|
+
void setInnerHTMLOnShadowRoot(void* shadowRoot, void* hostElement, const std::string& html);
|
|
45
73
|
|
|
46
74
|
// Like setInnerHTMLOnEl, but returns the detached parsed nodes instead of
|
|
47
75
|
// replacing contextElement's children — used by insertAdjacentHTML.
|
|
@@ -50,15 +78,6 @@ public:
|
|
|
50
78
|
// ── Selector matching ─────────────────────────────────────────────────────
|
|
51
79
|
bool matchesSelector(void* element, const std::string& sel) const;
|
|
52
80
|
|
|
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
81
|
private:
|
|
63
82
|
void* _document { nullptr };
|
|
64
83
|
void* _cssParser { nullptr };
|
|
@@ -4,12 +4,26 @@
|
|
|
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"
|
|
9
10
|
#include "bindings/EventBindings.hpp"
|
|
10
11
|
#include "bindings/TimerBindings.hpp"
|
|
11
12
|
#include "bindings/WindowBindings.hpp"
|
|
12
13
|
#include "bindings/FetchBindings.hpp"
|
|
14
|
+
#include "bindings/LiveCollectionBindings.hpp"
|
|
15
|
+
#include "bindings/UrlBindings.hpp"
|
|
16
|
+
#include "bindings/AbortBindings.hpp"
|
|
17
|
+
#include "bindings/TextEncodingBindings.hpp"
|
|
18
|
+
#include "bindings/FormBindings.hpp"
|
|
19
|
+
#include "bindings/BlobBindings.hpp"
|
|
20
|
+
#include "bindings/CSSOMBindings.hpp"
|
|
21
|
+
#include "bindings/ShadowRootBindings.hpp"
|
|
22
|
+
#include "bindings/CustomElementsBindings.hpp"
|
|
23
|
+
#include "bindings/CookieBindings.hpp"
|
|
24
|
+
#include "bindings/TemplateBindings.hpp"
|
|
25
|
+
#include "bindings/SlotBindings.hpp"
|
|
26
|
+
#include "bindings/XmlSerializerBindings.hpp"
|
|
13
27
|
#include <lexbor/html/html.h>
|
|
14
28
|
#include <lexbor/dom/dom.h>
|
|
15
29
|
|
|
@@ -24,12 +38,26 @@ void DOMBindings::install(QuickJSRuntime* runtime, LexborDocument* document) {
|
|
|
24
38
|
JSContext* ctx = static_cast<JSContext*>(runtime->context());
|
|
25
39
|
(void)document;
|
|
26
40
|
|
|
41
|
+
DOMExceptionBindings::install(ctx); // no dependencies; other modules throw DOMException instances
|
|
42
|
+
LiveCollectionBindings::install(ctx);
|
|
27
43
|
ElementBindings::install(ctx); // registers the Element class + proto
|
|
44
|
+
ShadowRootBindings::install(ctx); // needs Element's proto + js_node_class_id's proto
|
|
28
45
|
DocumentBindings::install(ctx); // creates globalThis.document
|
|
46
|
+
CookieBindings::install(ctx); // needs globalThis.document to exist
|
|
47
|
+
TemplateBindings::install(ctx); // needs Element's proto
|
|
48
|
+
CustomElementsBindings::install(ctx); // needs Element/ShadowRoot protos + globalThis.document
|
|
49
|
+
CSSOMBindings::install(ctx); // needs Element's proto + globalThis.document to exist
|
|
29
50
|
EventBindings::install(ctx); // needs Element's proto + globalThis.document to exist
|
|
30
51
|
TimerBindings::install(ctx);
|
|
31
52
|
WindowBindings::install(ctx);
|
|
53
|
+
UrlBindings::install(ctx);
|
|
54
|
+
AbortBindings::install(ctx);
|
|
55
|
+
TextEncodingBindings::install(ctx);
|
|
56
|
+
BlobBindings::install(ctx); // uses TextEncoder/TextDecoder + btoa, so must run after both
|
|
32
57
|
FetchBindings::install(ctx); // XHR bootstrap uses `new Event(...)`, so must run after EventBindings
|
|
58
|
+
FormBindings::install(ctx); // uses globalThis.Element + globalThis.Event, so must run after both
|
|
59
|
+
SlotBindings::install(ctx); // uses Event/dispatchEvent + Element/ShadowRoot protos, so must run after EventBindings/ShadowRootBindings
|
|
60
|
+
XmlSerializerBindings::install(ctx); // pure serialization, no ordering requirement beyond ElementBindings
|
|
33
61
|
|
|
34
62
|
// ── localStorage / sessionStorage ──────────────────────────────────────────
|
|
35
63
|
{
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
namespace margelo::nitro::nitrojsdom {
|
|
10
10
|
|
|
11
|
-
JSClassID js_element_class_id
|
|
12
|
-
JSClassID js_node_class_id
|
|
11
|
+
JSClassID js_element_class_id = 0;
|
|
12
|
+
JSClassID js_node_class_id = 0;
|
|
13
|
+
JSClassID js_shadow_root_class_id = 0;
|
|
13
14
|
|
|
14
15
|
namespace {
|
|
15
16
|
JSClassDef js_element_class = { "Element", .finalizer = nullptr };
|
|
@@ -59,6 +60,22 @@ void invalidate_node_cache_batch(JSContext* ctx, RuntimeContext* rctx, const std
|
|
|
59
60
|
for (void* n : nodes) invalidate_node_cache(ctx, rctx, n);
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
namespace {
|
|
64
|
+
void invalidate_node_cache_deep(JSContext* ctx, RuntimeContext* rctx, void* node_ptr) {
|
|
65
|
+
if (!node_ptr) return;
|
|
66
|
+
invalidate_node_cache(ctx, rctx, node_ptr);
|
|
67
|
+
for (lxb_dom_node_t* child = static_cast<lxb_dom_node_t*>(node_ptr)->first_child;
|
|
68
|
+
child; child = child->next) {
|
|
69
|
+
invalidate_node_cache_deep(ctx, rctx, child);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} // namespace
|
|
73
|
+
|
|
74
|
+
void invalidate_node_cache_deep_batch(JSContext* ctx, RuntimeContext* rctx, const std::vector<void*>& roots) {
|
|
75
|
+
if (!rctx) return;
|
|
76
|
+
for (void* root : roots) invalidate_node_cache_deep(ctx, rctx, root);
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
void clear_node_cache(JSContext* ctx, RuntimeContext* rctx) {
|
|
63
80
|
if (!rctx) return;
|
|
64
81
|
for (auto& kv : rctx->node_wrapper_cache) {
|
|
@@ -85,6 +102,8 @@ lxb_dom_node_t* unwrap_node(JSContext* ctx, JSValue val) {
|
|
|
85
102
|
void* p = JS_GetOpaque(val, js_element_class_id);
|
|
86
103
|
if (p) return static_cast<lxb_dom_node_t*>(p);
|
|
87
104
|
p = JS_GetOpaque(val, js_node_class_id);
|
|
105
|
+
if (p) return static_cast<lxb_dom_node_t*>(p);
|
|
106
|
+
p = JS_GetOpaque(val, js_shadow_root_class_id);
|
|
88
107
|
return static_cast<lxb_dom_node_t*>(p);
|
|
89
108
|
}
|
|
90
109
|
|
|
@@ -117,6 +136,19 @@ void define_prop(JSContext* ctx, JSValue obj, const char* name, GetterFn getter,
|
|
|
117
136
|
JS_FreeAtom(ctx, atom);
|
|
118
137
|
}
|
|
119
138
|
|
|
139
|
+
JSValue js_illegal_constructor(JSContext* ctx, JSValue, int, JSValue*) {
|
|
140
|
+
return JS_ThrowTypeError(ctx, "Illegal constructor");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
JSValue define_global_constructor(JSContext* ctx, const char* name, JSValue proto) {
|
|
144
|
+
JSValue ctor = JS_NewCFunction2(ctx, js_illegal_constructor, name, 0, JS_CFUNC_constructor, 0);
|
|
145
|
+
JS_SetConstructor(ctx, ctor, proto);
|
|
146
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
147
|
+
JS_SetPropertyStr(ctx, global, name, JS_DupValue(ctx, ctor));
|
|
148
|
+
JS_FreeValue(ctx, global);
|
|
149
|
+
return ctor;
|
|
150
|
+
}
|
|
151
|
+
|
|
120
152
|
bool get_bool_prop(JSContext* ctx, JSValue obj, const char* name) {
|
|
121
153
|
JSValue v = JS_GetPropertyStr(ctx, obj, name);
|
|
122
154
|
bool b = JS_ToBool(ctx, v) > 0;
|
|
@@ -34,11 +34,16 @@ class LexborDocument;
|
|
|
34
34
|
// node class (Node traversal only). This prevents non-element nodes from
|
|
35
35
|
// reaching lxb_dom_element_* calls.
|
|
36
36
|
//
|
|
37
|
+
// js_shadow_root_class_id wraps lxb_dom_shadow_root_t* (see ShadowRootBindings).
|
|
38
|
+
//
|
|
37
39
|
// unwrap_element() returns non-null only for element-class objects.
|
|
38
|
-
// unwrap_node() returns non-null for
|
|
40
|
+
// unwrap_node() returns non-null for any of the three (shared Node methods
|
|
41
|
+
// like appendChild/childNodes work the same way on a
|
|
42
|
+
// ShadowRoot, since it embeds a document fragment's layout).
|
|
39
43
|
|
|
40
44
|
extern JSClassID js_element_class_id;
|
|
41
45
|
extern JSClassID js_node_class_id;
|
|
46
|
+
extern JSClassID js_shadow_root_class_id;
|
|
42
47
|
|
|
43
48
|
JSValue make_element(JSContext* ctx, void* node_or_el);
|
|
44
49
|
JSValue make_element_array(JSContext* ctx, const std::vector<void*>& elements);
|
|
@@ -57,6 +62,12 @@ void invalidate_node_cache(JSContext* ctx, RuntimeContext* rctx, void* node);
|
|
|
57
62
|
void invalidate_node_cache_batch(JSContext* ctx, RuntimeContext* rctx, const std::vector<void*>& nodes);
|
|
58
63
|
void clear_node_cache(JSContext* ctx, RuntimeContext* rctx);
|
|
59
64
|
|
|
65
|
+
// Same as invalidate_node_cache_batch, but also invalidates every descendant
|
|
66
|
+
// of each given node. Use before destroying a subtree with
|
|
67
|
+
// lxb_dom_node_destroy_deep(): that frees descendants too, and a JS wrapper
|
|
68
|
+
// left pointing at a freed descendant is a use-after-free waiting to happen.
|
|
69
|
+
void invalidate_node_cache_deep_batch(JSContext* ctx, RuntimeContext* rctx, const std::vector<void*>& roots);
|
|
70
|
+
|
|
60
71
|
// ── Accessor property helper ──────────────────────────────────────────────────
|
|
61
72
|
|
|
62
73
|
using GetterFn = JSValue (*)(JSContext*, JSValue);
|
|
@@ -64,6 +75,11 @@ using SetterFn = JSValue (*)(JSContext*, JSValue, JSValue);
|
|
|
64
75
|
|
|
65
76
|
void define_prop(JSContext* ctx, JSValue obj, const char* name, GetterFn getter, SetterFn setter = nullptr);
|
|
66
77
|
|
|
78
|
+
// ── Global constructor helper (instanceof support) ────────────────────────────
|
|
79
|
+
|
|
80
|
+
JSValue js_illegal_constructor(JSContext* ctx, JSValue this_val, int argc, JSValue* argv);
|
|
81
|
+
JSValue define_global_constructor(JSContext* ctx, const char* name, JSValue proto);
|
|
82
|
+
|
|
67
83
|
// ── Misc shared helpers ───────────────────────────────────────────────────────
|
|
68
84
|
|
|
69
85
|
// Reads a boolean-ish property off any JS object (used by Event dispatch to read
|