@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
|
@@ -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
|
|
|
@@ -107,6 +126,40 @@ LexborDocument* get_doc(JSContext* ctx) {
|
|
|
107
126
|
return rctx ? rctx->document : nullptr;
|
|
108
127
|
}
|
|
109
128
|
|
|
129
|
+
LexborDocument* doc_for_node(JSContext* ctx, void* node) {
|
|
130
|
+
auto* rctx = get_ctx(ctx);
|
|
131
|
+
if (!rctx) return nullptr;
|
|
132
|
+
if (node) {
|
|
133
|
+
auto* dom_node = static_cast<lxb_dom_node_t*>(node);
|
|
134
|
+
auto it = rctx->document_registry.find(dom_node->owner_document);
|
|
135
|
+
if (it != rctx->document_registry.end()) return it->second;
|
|
136
|
+
}
|
|
137
|
+
return rctx->document;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
void register_document(JSContext* ctx, LexborDocument* doc) {
|
|
141
|
+
auto* rctx = get_ctx(ctx);
|
|
142
|
+
if (!rctx || !doc) return;
|
|
143
|
+
auto* html_doc = static_cast<lxb_html_document_t*>(doc->documentHtmlPtr());
|
|
144
|
+
if (!html_doc) return;
|
|
145
|
+
void* dom_doc = lxb_dom_interface_document(html_doc);
|
|
146
|
+
rctx->document_registry[dom_doc] = doc;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
void register_document_wrapper(JSContext* ctx, LexborDocument* doc, JSValue wrapper) {
|
|
150
|
+
auto* rctx = get_ctx(ctx);
|
|
151
|
+
if (!rctx || !doc) return;
|
|
152
|
+
rctx->document_wrappers[doc] = new JSValue(JS_DupValue(ctx, wrapper));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
JSValue get_document_wrapper(JSContext* ctx, LexborDocument* doc) {
|
|
156
|
+
auto* rctx = get_ctx(ctx);
|
|
157
|
+
if (!rctx || !doc) return JS_UNDEFINED;
|
|
158
|
+
auto it = rctx->document_wrappers.find(doc);
|
|
159
|
+
if (it == rctx->document_wrappers.end()) return JS_UNDEFINED;
|
|
160
|
+
return JS_DupValue(ctx, *static_cast<JSValue*>(it->second));
|
|
161
|
+
}
|
|
162
|
+
|
|
110
163
|
void define_prop(JSContext* ctx, JSValue obj, const char* name, GetterFn getter, SetterFn setter) {
|
|
111
164
|
JSAtom atom = JS_NewAtom(ctx, name);
|
|
112
165
|
JSValue get_fn = JS_NewCFunction2(ctx, (JSCFunction*)getter, name, 0, JS_CFUNC_getter, 0);
|
|
@@ -117,6 +170,52 @@ void define_prop(JSContext* ctx, JSValue obj, const char* name, GetterFn getter,
|
|
|
117
170
|
JS_FreeAtom(ctx, atom);
|
|
118
171
|
}
|
|
119
172
|
|
|
173
|
+
void define_node_type_constants(JSContext* ctx, JSValue obj) {
|
|
174
|
+
static const struct { const char* name; int32_t value; } kNodeTypeConstants[] = {
|
|
175
|
+
{ "ELEMENT_NODE", 1 },
|
|
176
|
+
{ "ATTRIBUTE_NODE", 2 },
|
|
177
|
+
{ "TEXT_NODE", 3 },
|
|
178
|
+
{ "CDATA_SECTION_NODE", 4 },
|
|
179
|
+
{ "ENTITY_REFERENCE_NODE", 5 },
|
|
180
|
+
{ "ENTITY_NODE", 6 },
|
|
181
|
+
{ "PROCESSING_INSTRUCTION_NODE", 7 },
|
|
182
|
+
{ "COMMENT_NODE", 8 },
|
|
183
|
+
{ "DOCUMENT_NODE", 9 },
|
|
184
|
+
{ "DOCUMENT_TYPE_NODE", 10 },
|
|
185
|
+
{ "DOCUMENT_FRAGMENT_NODE", 11 },
|
|
186
|
+
{ "NOTATION_NODE", 12 },
|
|
187
|
+
};
|
|
188
|
+
for (const auto& c : kNodeTypeConstants) {
|
|
189
|
+
JS_DefinePropertyValueStr(ctx, obj, c.name, JS_NewInt32(ctx, c.value), JS_PROP_ENUMERABLE);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
JSValue build_doctype_object(JSContext* ctx, LexborDocument* doc, void* doctype) {
|
|
194
|
+
JSValue obj = JS_NewObject(ctx);
|
|
195
|
+
std::string name = doc->doctypeName(doctype);
|
|
196
|
+
JS_SetPropertyStr(ctx, obj, "name", JS_NewStringLen(ctx, name.data(), name.size()));
|
|
197
|
+
std::string publicId = doc->doctypePublicId(doctype);
|
|
198
|
+
JS_SetPropertyStr(ctx, obj, "publicId", JS_NewStringLen(ctx, publicId.data(), publicId.size()));
|
|
199
|
+
std::string systemId = doc->doctypeSystemId(doctype);
|
|
200
|
+
JS_SetPropertyStr(ctx, obj, "systemId", JS_NewStringLen(ctx, systemId.data(), systemId.size()));
|
|
201
|
+
JS_SetPropertyStr(ctx, obj, "nodeType", JS_NewInt32(ctx, 10));
|
|
202
|
+
JS_SetPropertyStr(ctx, obj, "nodeName", JS_NewStringLen(ctx, name.data(), name.size()));
|
|
203
|
+
return obj;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
JSValue js_illegal_constructor(JSContext* ctx, JSValue, int, JSValue*) {
|
|
207
|
+
return JS_ThrowTypeError(ctx, "Illegal constructor");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
JSValue define_global_constructor(JSContext* ctx, const char* name, JSValue proto) {
|
|
211
|
+
JSValue ctor = JS_NewCFunction2(ctx, js_illegal_constructor, name, 0, JS_CFUNC_constructor, 0);
|
|
212
|
+
JS_SetConstructor(ctx, ctor, proto);
|
|
213
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
214
|
+
JS_SetPropertyStr(ctx, global, name, JS_DupValue(ctx, ctor));
|
|
215
|
+
JS_FreeValue(ctx, global);
|
|
216
|
+
return ctor;
|
|
217
|
+
}
|
|
218
|
+
|
|
120
219
|
bool get_bool_prop(JSContext* ctx, JSValue obj, const char* name) {
|
|
121
220
|
JSValue v = JS_GetPropertyStr(ctx, obj, name);
|
|
122
221
|
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);
|
|
@@ -51,12 +56,46 @@ std::string serialize_node(lxb_dom_node_t* node);
|
|
|
51
56
|
RuntimeContext* get_ctx(JSContext* ctx);
|
|
52
57
|
LexborDocument* get_doc(JSContext* ctx);
|
|
53
58
|
|
|
59
|
+
// Resolves the LexborDocument that owns `node`'s tree — the sandbox's
|
|
60
|
+
// primary document, or a secondary document created via
|
|
61
|
+
// DOMParser.parseFromString()/document.implementation.createHTMLDocument()
|
|
62
|
+
// (see DOMParserBindings). Any binding that *creates* a new node relative to
|
|
63
|
+
// an existing one (textContent/innerHTML setters, insertAdjacentHTML,
|
|
64
|
+
// before/after/replaceWith/append/prepend's string-to-text-node coercion,
|
|
65
|
+
// matches()/closest()) must resolve the document this way instead of using
|
|
66
|
+
// get_doc(ctx) directly — otherwise a node from a secondary document would
|
|
67
|
+
// have children created in the *primary* document's memory arena, which is
|
|
68
|
+
// undefined behavior once either document is destroyed. Falls back to the
|
|
69
|
+
// primary document if `node`'s owner isn't registered (shouldn't happen).
|
|
70
|
+
LexborDocument* doc_for_node(JSContext* ctx, void* node);
|
|
71
|
+
|
|
72
|
+
// Registers `doc` in the runtime's document registry so doc_for_node() can
|
|
73
|
+
// resolve nodes created inside it back to `doc`. Call once, right after the
|
|
74
|
+
// document is constructed/parsed.
|
|
75
|
+
void register_document(JSContext* ctx, LexborDocument* doc);
|
|
76
|
+
|
|
77
|
+
// Registers `wrapper` (the JS Document object returned to script) as the
|
|
78
|
+
// canonical JS object for `doc`, so get_document_wrapper() can hand back the
|
|
79
|
+
// same reference later (e.g. from node.ownerDocument) instead of building a
|
|
80
|
+
// new, unequal one. Call once, alongside register_document().
|
|
81
|
+
void register_document_wrapper(JSContext* ctx, LexborDocument* doc, JSValue wrapper);
|
|
82
|
+
|
|
83
|
+
// Returns the JSValue registered via register_document_wrapper() for `doc`,
|
|
84
|
+
// or JS_UNDEFINED if none was registered.
|
|
85
|
+
JSValue get_document_wrapper(JSContext* ctx, LexborDocument* doc);
|
|
86
|
+
|
|
54
87
|
// ── Node wrapper cache ────────────────────────────────────────────────────────
|
|
55
88
|
|
|
56
89
|
void invalidate_node_cache(JSContext* ctx, RuntimeContext* rctx, void* node);
|
|
57
90
|
void invalidate_node_cache_batch(JSContext* ctx, RuntimeContext* rctx, const std::vector<void*>& nodes);
|
|
58
91
|
void clear_node_cache(JSContext* ctx, RuntimeContext* rctx);
|
|
59
92
|
|
|
93
|
+
// Same as invalidate_node_cache_batch, but also invalidates every descendant
|
|
94
|
+
// of each given node. Use before destroying a subtree with
|
|
95
|
+
// lxb_dom_node_destroy_deep(): that frees descendants too, and a JS wrapper
|
|
96
|
+
// left pointing at a freed descendant is a use-after-free waiting to happen.
|
|
97
|
+
void invalidate_node_cache_deep_batch(JSContext* ctx, RuntimeContext* rctx, const std::vector<void*>& roots);
|
|
98
|
+
|
|
60
99
|
// ── Accessor property helper ──────────────────────────────────────────────────
|
|
61
100
|
|
|
62
101
|
using GetterFn = JSValue (*)(JSContext*, JSValue);
|
|
@@ -64,6 +103,22 @@ using SetterFn = JSValue (*)(JSContext*, JSValue, JSValue);
|
|
|
64
103
|
|
|
65
104
|
void define_prop(JSContext* ctx, JSValue obj, const char* name, GetterFn getter, SetterFn setter = nullptr);
|
|
66
105
|
|
|
106
|
+
// Sets the WHATWG DOM Node.ELEMENT_NODE-style numeric constants (ELEMENT_NODE,
|
|
107
|
+
// TEXT_NODE, COMMENT_NODE, ...) as read-only enumerable properties on `obj`.
|
|
108
|
+
// Values match lxb_dom_node_type_t, which already mirrors the DOM spec numbering.
|
|
109
|
+
void define_node_type_constants(JSContext* ctx, JSValue obj);
|
|
110
|
+
|
|
111
|
+
// Builds the plain {name, publicId, systemId, nodeType, nodeName} object used
|
|
112
|
+
// to represent a document's DocumentType node (see doc->doctype()). Shared by
|
|
113
|
+
// DocumentBindings (which layers its own identity cache on top) and
|
|
114
|
+
// DOMParserBindings (which doesn't cache — see its call site).
|
|
115
|
+
JSValue build_doctype_object(JSContext* ctx, LexborDocument* doc, void* doctype);
|
|
116
|
+
|
|
117
|
+
// ── Global constructor helper (instanceof support) ────────────────────────────
|
|
118
|
+
|
|
119
|
+
JSValue js_illegal_constructor(JSContext* ctx, JSValue this_val, int argc, JSValue* argv);
|
|
120
|
+
JSValue define_global_constructor(JSContext* ctx, const char* name, JSValue proto);
|
|
121
|
+
|
|
67
122
|
// ── Misc shared helpers ───────────────────────────────────────────────────────
|
|
68
123
|
|
|
69
124
|
// Reads a boolean-ish property off any JS object (used by Event dispatch to read
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#include "DOMBindings.hpp"
|
|
3
3
|
#include "DOMBindingsInternal.hpp"
|
|
4
4
|
#include "MutationObservers.hpp"
|
|
5
|
+
#include "bindings/EventBindings.hpp"
|
|
5
6
|
#include "../lexbor/LexborDocument.hpp"
|
|
6
7
|
#include "quickjs.h"
|
|
7
8
|
#include <stdexcept>
|
|
@@ -90,6 +91,22 @@ QuickJSRuntime::~QuickJSRuntime() {
|
|
|
90
91
|
|
|
91
92
|
// Free node wrapper cache
|
|
92
93
|
clear_node_cache(ctx, _ctxState.get());
|
|
94
|
+
|
|
95
|
+
// Free the cached document.doctype wrapper
|
|
96
|
+
if (_ctxState->doctype_wrapper) {
|
|
97
|
+
JSValue* stored = static_cast<JSValue*>(_ctxState->doctype_wrapper);
|
|
98
|
+
JS_FreeValue(ctx, *stored);
|
|
99
|
+
delete stored;
|
|
100
|
+
_ctxState->doctype_wrapper = nullptr;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Free cached secondary-document wrappers
|
|
104
|
+
for (auto& kv : _ctxState->document_wrappers) {
|
|
105
|
+
JSValue* stored = static_cast<JSValue*>(kv.second);
|
|
106
|
+
JS_FreeValue(ctx, *stored);
|
|
107
|
+
delete stored;
|
|
108
|
+
}
|
|
109
|
+
_ctxState->document_wrappers.clear();
|
|
93
110
|
}
|
|
94
111
|
|
|
95
112
|
if (_context) JS_FreeContext(reinterpret_cast<JSContext*>(_context));
|
|
@@ -137,7 +154,7 @@ void QuickJSRuntime::cleanupTimers() {
|
|
|
137
154
|
|
|
138
155
|
// ── initialize ─────────────────────────────────────────────────────────────────
|
|
139
156
|
|
|
140
|
-
void QuickJSRuntime::initialize(const std::string& url) {
|
|
157
|
+
void QuickJSRuntime::initialize(const std::string& url, bool pretendToBeVisual) {
|
|
141
158
|
JSRuntime* rt = JS_NewRuntime();
|
|
142
159
|
if (!rt) throw std::runtime_error("QuickJS: failed to create runtime");
|
|
143
160
|
JSContext* ctx = JS_NewContext(rt);
|
|
@@ -151,6 +168,7 @@ void QuickJSRuntime::initialize(const std::string& url) {
|
|
|
151
168
|
// Create RuntimeContext and attach to QuickJS context opaque
|
|
152
169
|
_ctxState = std::make_unique<RuntimeContext>();
|
|
153
170
|
_ctxState->runtime = this;
|
|
171
|
+
_ctxState->pretend_to_be_visual = pretendToBeVisual;
|
|
154
172
|
_ctxState->mutation_observers = std::make_unique<MutationObservers>();
|
|
155
173
|
JS_SetContextOpaque(ctx, _ctxState.get());
|
|
156
174
|
|
|
@@ -234,6 +252,7 @@ void QuickJSRuntime::drainEventLoop() {
|
|
|
234
252
|
}
|
|
235
253
|
JS_FreeValue(ctx, str_val);
|
|
236
254
|
}
|
|
255
|
+
EventBindings::dispatchErrorEvent(ctx, err, ex);
|
|
237
256
|
JS_FreeValue(ctx, ex);
|
|
238
257
|
throw std::runtime_error(err);
|
|
239
258
|
}
|
|
@@ -257,6 +276,7 @@ void QuickJSRuntime::drainEventLoop() {
|
|
|
257
276
|
}
|
|
258
277
|
JS_FreeValue(ctx, str_val);
|
|
259
278
|
}
|
|
279
|
+
EventBindings::dispatchUnhandledRejectionEvent(ctx, *stored);
|
|
260
280
|
JS_FreeValue(ctx, *stored);
|
|
261
281
|
delete stored;
|
|
262
282
|
_ctxState->pending_rejection = nullptr;
|
|
@@ -385,6 +405,7 @@ void QuickJSRuntime::fireTimer(Timer* t) {
|
|
|
385
405
|
}
|
|
386
406
|
JS_FreeValue(ctx, str_val);
|
|
387
407
|
}
|
|
408
|
+
EventBindings::dispatchErrorEvent(ctx, err, ex);
|
|
388
409
|
JS_FreeValue(ctx, ex);
|
|
389
410
|
throw std::runtime_error(err);
|
|
390
411
|
}
|
|
@@ -396,10 +417,16 @@ void QuickJSRuntime::fireTimer(Timer* t) {
|
|
|
396
417
|
std::string QuickJSRuntime::evaluate(const std::string& script) {
|
|
397
418
|
JSRuntime* rt = reinterpret_cast<JSRuntime*>(_runtime);
|
|
398
419
|
JSContext* ctx = reinterpret_cast<JSContext*>(_context);
|
|
399
|
-
std::lock_guard<std::mutex> lock(_mutex);
|
|
400
420
|
|
|
401
|
-
|
|
402
|
-
|
|
421
|
+
std::unique_lock<std::mutex> lock(_mutex, std::try_to_lock);
|
|
422
|
+
if (!lock.owns_lock()) {
|
|
423
|
+
throw std::runtime_error(
|
|
424
|
+
"QuickJSRuntime: evaluate() called reentrantly. A callback (onFetch/onAlert/onConfirm/onPrompt) "
|
|
425
|
+
"attempted to call dom.evaluate() again while an evaluate() call was already in progress on this "
|
|
426
|
+
"instance. QuickJS is not re-entrant."
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
|
|
403
430
|
JS_UpdateStackTop(rt);
|
|
404
431
|
|
|
405
432
|
JSValue result = JS_Eval(ctx, script.data(), script.size(), "<eval>", JS_EVAL_TYPE_GLOBAL);
|
|
@@ -425,6 +452,7 @@ std::string QuickJSRuntime::evaluate(const std::string& script) {
|
|
|
425
452
|
JS_FreeValue(ctx, str_val);
|
|
426
453
|
}
|
|
427
454
|
|
|
455
|
+
EventBindings::dispatchErrorEvent(ctx, err, ex);
|
|
428
456
|
JS_FreeValue(ctx, ex);
|
|
429
457
|
QJS_LOG("QuickJS exception: %s", err.c_str());
|
|
430
458
|
throw std::runtime_error(err);
|
|
@@ -39,6 +39,7 @@ struct EventListener {
|
|
|
39
39
|
void* node; // lxb_dom_node_t*
|
|
40
40
|
std::string event_type;
|
|
41
41
|
void* callback; // JSValue* (heap-allocated, owned)
|
|
42
|
+
bool is_handler_property { false };
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
// ── RuntimeContext ─────────────────────────────────────────────────────────────
|
|
@@ -66,10 +67,58 @@ struct RuntimeContext {
|
|
|
66
67
|
Storage local_storage;
|
|
67
68
|
Storage session_storage;
|
|
68
69
|
|
|
70
|
+
// In-memory document.cookie jar (name -> value). No real navigation/origin
|
|
71
|
+
// model exists in this sandbox, so cookie attributes (expires/path/domain/
|
|
72
|
+
// secure/samesite) are parsed off the setter's input and discarded rather
|
|
73
|
+
// than enforced — see CookieBindings.
|
|
74
|
+
Storage cookie_jar;
|
|
75
|
+
|
|
76
|
+
bool pretend_to_be_visual { false };
|
|
77
|
+
double time_origin_ms { 0 };
|
|
78
|
+
|
|
79
|
+
void* active_element { nullptr };
|
|
80
|
+
std::string ready_state { "loading" };
|
|
81
|
+
|
|
69
82
|
// node pointer → heap-allocated JSValue* (DupValue'd strong ref)
|
|
70
83
|
// Ensures the same native node always returns the same JS wrapper object.
|
|
71
84
|
std::unordered_map<void*, void*> node_wrapper_cache;
|
|
72
85
|
|
|
86
|
+
// document.doctype's plain-object wrapper (heap-allocated JSValue*,
|
|
87
|
+
// DupValue'd strong ref) — not routed through node_wrapper_cache since
|
|
88
|
+
// it isn't keyed by a native node pointer (see DocumentBindings.cpp).
|
|
89
|
+
// Built lazily on first access and reused after that, so repeated
|
|
90
|
+
// `document.doctype` reads return the same JS object (identity-stable).
|
|
91
|
+
void* doctype_wrapper { nullptr };
|
|
92
|
+
|
|
93
|
+
// host element pointer (lxb_dom_element_t*) → its shadow root
|
|
94
|
+
// (lxb_dom_shadow_root_t*). Lexbor's element struct has no built-in
|
|
95
|
+
// back-pointer to an attached shadow root, so we track it ourselves.
|
|
96
|
+
std::unordered_map<void*, void*> element_shadow_roots;
|
|
97
|
+
|
|
98
|
+
// Secondary documents created via DOMParser.parseFromString() /
|
|
99
|
+
// document.implementation.createHTMLDocument() (see DOMParserBindings).
|
|
100
|
+
// Owned here rather than by their JS wrapper's finalizer: QuickJS class
|
|
101
|
+
// finalizers only receive a JSRuntime*, not the JSContext needed to safely
|
|
102
|
+
// touch document_registry below, so they're freed together with the rest
|
|
103
|
+
// of the sandbox instead of individually via GC.
|
|
104
|
+
std::vector<std::unique_ptr<LexborDocument>> extra_documents;
|
|
105
|
+
|
|
106
|
+
// Raw lexbor document pointer (lxb_dom_document_t*) -> the LexborDocument
|
|
107
|
+
// wrapper that owns it (primary sandbox document, or one of
|
|
108
|
+
// extra_documents). Lets node-scoped bindings that create new nodes
|
|
109
|
+
// (textContent/innerHTML setters, insertAdjacentHTML, matches()/closest())
|
|
110
|
+
// resolve the *correct* owning document instead of assuming the sandbox's
|
|
111
|
+
// primary `document` — which would otherwise create nodes in the wrong
|
|
112
|
+
// document's memory arena when called on a DOMParser-produced element.
|
|
113
|
+
// See doc_for_node() in DOMBindingsInternal.
|
|
114
|
+
std::unordered_map<void*, LexborDocument*> document_registry;
|
|
115
|
+
|
|
116
|
+
// LexborDocument* -> its JS wrapper (JSValue*, heap-allocated, owned).
|
|
117
|
+
// Lets node.ownerDocument return the same JS object DOMParser.
|
|
118
|
+
// parseFromString()/createHTMLDocument() already handed the caller,
|
|
119
|
+
// instead of building a second, unequal wrapper for the same document.
|
|
120
|
+
std::unordered_map<LexborDocument*, void*> document_wrappers;
|
|
121
|
+
|
|
73
122
|
~RuntimeContext() = default;
|
|
74
123
|
};
|
|
75
124
|
|
|
@@ -80,7 +129,7 @@ public:
|
|
|
80
129
|
QuickJSRuntime();
|
|
81
130
|
~QuickJSRuntime();
|
|
82
131
|
|
|
83
|
-
void initialize(const std::string& url);
|
|
132
|
+
void initialize(const std::string& url, bool pretendToBeVisual);
|
|
84
133
|
void bindDocument(LexborDocument* document);
|
|
85
134
|
std::string evaluate(const std::string& script);
|
|
86
135
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#include "AbortBindings.hpp"
|
|
2
|
+
#include <cstring>
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::nitrojsdom {
|
|
5
|
+
|
|
6
|
+
namespace {
|
|
7
|
+
|
|
8
|
+
const char* kAbortBootstrapScript = R"JS(
|
|
9
|
+
(function() {
|
|
10
|
+
function makeAbortError() {
|
|
11
|
+
var e = new Error('signal is aborted without reason');
|
|
12
|
+
e.name = 'AbortError';
|
|
13
|
+
return e;
|
|
14
|
+
}
|
|
15
|
+
function makeTimeoutError() {
|
|
16
|
+
var e = new Error('signal timed out');
|
|
17
|
+
e.name = 'TimeoutError';
|
|
18
|
+
return e;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function AbortSignal() {
|
|
22
|
+
this.aborted = false;
|
|
23
|
+
this.reason = undefined;
|
|
24
|
+
this._listeners = [];
|
|
25
|
+
this._onabort = null;
|
|
26
|
+
this._onabortListener = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Object.defineProperty(AbortSignal.prototype, 'onabort', {
|
|
30
|
+
get: function() { return this._onabort; },
|
|
31
|
+
set: function(fn) {
|
|
32
|
+
if (this._onabortListener) {
|
|
33
|
+
var idx = this._listeners.indexOf(this._onabortListener);
|
|
34
|
+
if (idx !== -1) this._listeners.splice(idx, 1);
|
|
35
|
+
}
|
|
36
|
+
this._onabort = typeof fn === 'function' ? fn : null;
|
|
37
|
+
if (this._onabort) {
|
|
38
|
+
var self = this;
|
|
39
|
+
this._onabortListener = function(evt) { self._onabort(evt); };
|
|
40
|
+
this._listeners.push(this._onabortListener);
|
|
41
|
+
} else {
|
|
42
|
+
this._onabortListener = null;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
enumerable: true,
|
|
46
|
+
configurable: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
AbortSignal.prototype.addEventListener = function(type, cb) {
|
|
50
|
+
if (type !== 'abort' || typeof cb !== 'function') return;
|
|
51
|
+
if (this._listeners.indexOf(cb) === -1) this._listeners.push(cb);
|
|
52
|
+
};
|
|
53
|
+
AbortSignal.prototype.removeEventListener = function(type, cb) {
|
|
54
|
+
if (type !== 'abort') return;
|
|
55
|
+
var idx = this._listeners.indexOf(cb);
|
|
56
|
+
if (idx !== -1) this._listeners.splice(idx, 1);
|
|
57
|
+
};
|
|
58
|
+
AbortSignal.prototype.dispatchEvent = function(evt) {
|
|
59
|
+
if (evt && evt.type === 'abort') this._fire();
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
AbortSignal.prototype.throwIfAborted = function() {
|
|
63
|
+
if (this.aborted) throw this.reason;
|
|
64
|
+
};
|
|
65
|
+
AbortSignal.prototype._fire = function() {
|
|
66
|
+
var evt = new Event('abort');
|
|
67
|
+
evt.target = this;
|
|
68
|
+
this._listeners.slice().forEach(function(cb) {
|
|
69
|
+
try { cb(evt); } catch (e) {}
|
|
70
|
+
});
|
|
71
|
+
};
|
|
72
|
+
AbortSignal.prototype._abort = function(reason) {
|
|
73
|
+
if (this.aborted) return;
|
|
74
|
+
this.aborted = true;
|
|
75
|
+
this.reason = reason !== undefined ? reason : makeAbortError();
|
|
76
|
+
this._fire();
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
AbortSignal.abort = function(reason) {
|
|
80
|
+
var s = new AbortSignal();
|
|
81
|
+
s._abort(reason);
|
|
82
|
+
return s;
|
|
83
|
+
};
|
|
84
|
+
AbortSignal.timeout = function(ms) {
|
|
85
|
+
var s = new AbortSignal();
|
|
86
|
+
setTimeout(function() { s._abort(makeTimeoutError()); }, ms);
|
|
87
|
+
return s;
|
|
88
|
+
};
|
|
89
|
+
AbortSignal.any = function(signals) {
|
|
90
|
+
var s = new AbortSignal();
|
|
91
|
+
var list = [];
|
|
92
|
+
for (var i = 0; i < signals.length; i++) list.push(signals[i]);
|
|
93
|
+
for (var j = 0; j < list.length; j++) {
|
|
94
|
+
if (list[j].aborted) { s._abort(list[j].reason); break; }
|
|
95
|
+
}
|
|
96
|
+
if (!s.aborted) {
|
|
97
|
+
list.forEach(function(signal) {
|
|
98
|
+
signal.addEventListener('abort', function() { s._abort(signal.reason); });
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return s;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
globalThis.AbortSignal = AbortSignal;
|
|
105
|
+
|
|
106
|
+
function AbortController() {
|
|
107
|
+
this.signal = new AbortSignal();
|
|
108
|
+
}
|
|
109
|
+
AbortController.prototype.abort = function(reason) {
|
|
110
|
+
this.signal._abort(reason);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
globalThis.AbortController = AbortController;
|
|
114
|
+
})();
|
|
115
|
+
)JS";
|
|
116
|
+
|
|
117
|
+
} // namespace
|
|
118
|
+
|
|
119
|
+
void AbortBindings::install(JSContext* ctx) {
|
|
120
|
+
JSValue result = JS_Eval(ctx, kAbortBootstrapScript, strlen(kAbortBootstrapScript),
|
|
121
|
+
"<abort-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
122
|
+
if (JS_IsException(result)) {
|
|
123
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
124
|
+
}
|
|
125
|
+
JS_FreeValue(ctx, result);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
} // namespace margelo::nitro::nitrojsdom
|