@salve-software/react-native-nitro-jsdom 1.1.0 → 2.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 +6 -0
- package/cpp/HybridHtmlSandbox.cpp +14 -7
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +65 -3
- package/cpp/lexbor/LexborDocument.hpp +17 -2
- package/cpp/quickjs/DOMBindings.cpp +14 -1
- package/cpp/quickjs/DOMBindingsInternal.cpp +67 -0
- package/cpp/quickjs/DOMBindingsInternal.hpp +39 -0
- package/cpp/quickjs/QuickJSRuntime.cpp +13 -0
- package/cpp/quickjs/QuickJSRuntime.hpp +29 -0
- package/cpp/quickjs/bindings/AbortBindings.cpp +14 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +28 -1
- package/cpp/quickjs/bindings/BlobBindings.hpp +3 -3
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +96 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +3 -1
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +1 -1
- package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
- package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +174 -11
- package/cpp/quickjs/bindings/ElementBindings.cpp +516 -20
- package/cpp/quickjs/bindings/EventBindings.cpp +281 -5
- 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 +43 -8
- package/cpp/quickjs/bindings/FormBindings.cpp +294 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +9 -5
- package/cpp/quickjs/bindings/IntlBindings.cpp +352 -0
- package/cpp/quickjs/bindings/IntlBindings.hpp +29 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +45 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +79 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +43 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +295 -4
- package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
- package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp +241 -0
- package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.hpp +30 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +2 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +2 -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/classes/JSDOM/types/IJSDOMOptions.d.ts +5 -4
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.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 +2 -1
- package/src/classes/JSDOM/types/IJSDOMOptions.ts +5 -4
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
#include "DocumentBindings.hpp"
|
|
2
2
|
#include "LiveCollectionBindings.hpp"
|
|
3
|
+
#include "DOMExceptionBindings.hpp"
|
|
3
4
|
#include "../DOMBindingsInternal.hpp"
|
|
4
5
|
#include "../QuickJSRuntime.hpp"
|
|
5
6
|
#include "../../lexbor/LexborDocument.hpp"
|
|
7
|
+
#include <lexbor/html/html.h>
|
|
8
|
+
#include <lexbor/dom/dom.h>
|
|
6
9
|
#include <cstring>
|
|
7
10
|
#include <string>
|
|
8
11
|
|
|
@@ -103,6 +106,92 @@ JSValue js_doc_createTextNode(JSContext* ctx, JSValue, int argc, JSValue* argv)
|
|
|
103
106
|
return make_element(ctx, node);
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
// Implements the WHATWG "validate and extract a namespace and qualified name"
|
|
110
|
+
// algorithm (https://dom.spec.whatwg.org/#validate-and-extract): splits
|
|
111
|
+
// qualifiedName into prefix/localName and checks the namespace/prefix
|
|
112
|
+
// consistency rules, throwing the spec-mandated DOMException on failure.
|
|
113
|
+
// Full XML Name-production character validation (which characters are legal
|
|
114
|
+
// in a Name) is not implemented — only structural checks (empty name, colon
|
|
115
|
+
// placement, xml/xmlns reservations) — deliberately narrower than real
|
|
116
|
+
// jsdom, since that character-class check is a much larger, separate lift.
|
|
117
|
+
JSValue validate_and_extract_qname(JSContext* ctx, const std::string& ns,
|
|
118
|
+
const std::string& qualifiedName, bool ns_is_null,
|
|
119
|
+
std::string& out_prefix, std::string& out_local) {
|
|
120
|
+
if (qualifiedName.empty()) {
|
|
121
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
122
|
+
"The qualified name provided is empty.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
auto colon = qualifiedName.find(':');
|
|
126
|
+
auto last_colon = qualifiedName.rfind(':');
|
|
127
|
+
if (colon != last_colon) {
|
|
128
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
129
|
+
"The qualified name provided has more than one ':'.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
std::string prefix;
|
|
133
|
+
std::string local = qualifiedName;
|
|
134
|
+
if (colon != std::string::npos) {
|
|
135
|
+
prefix = qualifiedName.substr(0, colon);
|
|
136
|
+
local = qualifiedName.substr(colon + 1);
|
|
137
|
+
if (prefix.empty() || local.empty()) {
|
|
138
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
139
|
+
"The qualified name provided is malformed.");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!prefix.empty() && ns_is_null) {
|
|
144
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
145
|
+
"A namespace is required for a prefixed qualified name.");
|
|
146
|
+
}
|
|
147
|
+
if (prefix == "xml" && ns != "http://www.w3.org/XML/1998/namespace") {
|
|
148
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
149
|
+
"The 'xml' prefix requires the XML namespace.");
|
|
150
|
+
}
|
|
151
|
+
bool is_xmlns = (qualifiedName == "xmlns" || prefix == "xmlns");
|
|
152
|
+
if (is_xmlns && ns != "http://www.w3.org/2000/xmlns/") {
|
|
153
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
154
|
+
"The 'xmlns' prefix/qualified name requires the XMLNS namespace.");
|
|
155
|
+
}
|
|
156
|
+
if (!is_xmlns && ns == "http://www.w3.org/2000/xmlns/") {
|
|
157
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
158
|
+
"The XMLNS namespace requires the 'xmlns' prefix or qualified name.");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
out_prefix = prefix;
|
|
162
|
+
out_local = local;
|
|
163
|
+
return JS_UNDEFINED;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
JSValue js_doc_createElementNS(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
167
|
+
if (argc < 2) {
|
|
168
|
+
return JS_ThrowTypeError(ctx,
|
|
169
|
+
"Failed to execute 'createElementNS' on 'Document': 2 arguments required.");
|
|
170
|
+
}
|
|
171
|
+
// Per WebIDL, a nullable DOMString? argument only maps to the null
|
|
172
|
+
// namespace when explicitly `null` — `undefined` stringifies to "undefined"
|
|
173
|
+
// like any other DOMString argument, it is not treated as null.
|
|
174
|
+
bool ns_is_null = JS_IsNull(argv[0]);
|
|
175
|
+
std::string ns;
|
|
176
|
+
if (!ns_is_null) {
|
|
177
|
+
const char* ns_c = JS_ToCString(ctx, argv[0]);
|
|
178
|
+
if (!ns_c) return JS_EXCEPTION;
|
|
179
|
+
ns = ns_c;
|
|
180
|
+
JS_FreeCString(ctx, ns_c);
|
|
181
|
+
}
|
|
182
|
+
const char* qname = JS_ToCString(ctx, argv[1]);
|
|
183
|
+
if (!qname) return JS_EXCEPTION;
|
|
184
|
+
std::string qualifiedName = qname;
|
|
185
|
+
JS_FreeCString(ctx, qname);
|
|
186
|
+
|
|
187
|
+
std::string prefix, local;
|
|
188
|
+
JSValue validation_error = validate_and_extract_qname(ctx, ns, qualifiedName, ns_is_null, prefix, local);
|
|
189
|
+
if (JS_IsException(validation_error)) return validation_error;
|
|
190
|
+
|
|
191
|
+
void* el = get_doc(ctx)->createElementNS(ns, qualifiedName);
|
|
192
|
+
return make_element(ctx, el);
|
|
193
|
+
}
|
|
194
|
+
|
|
106
195
|
JSValue js_doc_createComment(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
107
196
|
if (argc < 1) return JS_NULL;
|
|
108
197
|
const char* text = JS_ToCString(ctx, argv[0]);
|
|
@@ -116,6 +205,28 @@ JSValue js_doc_createDocumentFragment(JSContext* ctx, JSValue, int, JSValue*) {
|
|
|
116
205
|
return make_element(ctx, get_doc(ctx)->createDocumentFragment());
|
|
117
206
|
}
|
|
118
207
|
|
|
208
|
+
JSValue js_doc_importNode(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
209
|
+
if (argc < 1) {
|
|
210
|
+
return JS_ThrowTypeError(ctx,
|
|
211
|
+
"Failed to execute 'importNode' on 'Document': 1 argument required, but only 0 present.");
|
|
212
|
+
}
|
|
213
|
+
auto* node = unwrap_node(ctx, argv[0]);
|
|
214
|
+
if (!node) {
|
|
215
|
+
return JS_ThrowTypeError(ctx,
|
|
216
|
+
"Failed to execute 'importNode' on 'Document': parameter 1 is not of type 'Node'.");
|
|
217
|
+
}
|
|
218
|
+
if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT) {
|
|
219
|
+
return throw_dom_exception(ctx, "NotSupportedError",
|
|
220
|
+
"Failed to execute 'importNode' on 'Document': The node provided is a document, which may not be imported.");
|
|
221
|
+
}
|
|
222
|
+
bool deep = argc >= 2 && JS_ToBool(ctx, argv[1]) > 0;
|
|
223
|
+
|
|
224
|
+
lxb_dom_document_t* target_doc = lxb_dom_interface_document(
|
|
225
|
+
static_cast<lxb_html_document_t*>(get_doc(ctx)->documentHtmlPtr()));
|
|
226
|
+
lxb_dom_node_t* imported = lxb_dom_document_import_node(target_doc, node, deep);
|
|
227
|
+
return make_element(ctx, imported);
|
|
228
|
+
}
|
|
229
|
+
|
|
119
230
|
JSValue js_doc_get_body(JSContext* ctx, JSValue) {
|
|
120
231
|
return make_element(ctx, get_doc(ctx)->body());
|
|
121
232
|
}
|
|
@@ -147,20 +258,51 @@ JSValue js_doc_get_doctype(JSContext* ctx, JSValue) {
|
|
|
147
258
|
return JS_DupValue(ctx, *static_cast<JSValue*>(rctx->doctype_wrapper));
|
|
148
259
|
}
|
|
149
260
|
|
|
150
|
-
JSValue obj =
|
|
151
|
-
std::string name = get_doc(ctx)->doctypeName(dt);
|
|
152
|
-
JS_SetPropertyStr(ctx, obj, "name", JS_NewStringLen(ctx, name.data(), name.size()));
|
|
153
|
-
std::string publicId = get_doc(ctx)->doctypePublicId(dt);
|
|
154
|
-
JS_SetPropertyStr(ctx, obj, "publicId", JS_NewStringLen(ctx, publicId.data(), publicId.size()));
|
|
155
|
-
std::string systemId = get_doc(ctx)->doctypeSystemId(dt);
|
|
156
|
-
JS_SetPropertyStr(ctx, obj, "systemId", JS_NewStringLen(ctx, systemId.data(), systemId.size()));
|
|
157
|
-
JS_SetPropertyStr(ctx, obj, "nodeType", JS_NewInt32(ctx, 10));
|
|
158
|
-
JS_SetPropertyStr(ctx, obj, "nodeName", JS_NewStringLen(ctx, name.data(), name.size()));
|
|
159
|
-
|
|
261
|
+
JSValue obj = build_doctype_object(ctx, get_doc(ctx), dt);
|
|
160
262
|
if (rctx) rctx->doctype_wrapper = new JSValue(JS_DupValue(ctx, obj));
|
|
161
263
|
return obj;
|
|
162
264
|
}
|
|
163
265
|
|
|
266
|
+
JSValue js_doc_get_activeElement(JSContext* ctx, JSValue) {
|
|
267
|
+
auto* rctx = get_ctx(ctx);
|
|
268
|
+
if (rctx && rctx->active_element) return make_element(ctx, rctx->active_element);
|
|
269
|
+
return make_element(ctx, get_doc(ctx)->body());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
JSValue js_doc_get_currentScript(JSContext* ctx, JSValue) {
|
|
273
|
+
auto* rctx = get_ctx(ctx);
|
|
274
|
+
if (rctx && rctx->current_script) return make_element(ctx, rctx->current_script);
|
|
275
|
+
return JS_NULL;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
JSValue js_doc_get_readyState(JSContext* ctx, JSValue) {
|
|
279
|
+
auto* rctx = get_ctx(ctx);
|
|
280
|
+
const std::string& state = rctx ? rctx->ready_state : "loading";
|
|
281
|
+
return JS_NewStringLen(ctx, state.data(), state.size());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// A doctype-less document renders in quirks mode in every real browser (and
|
|
285
|
+
// jsdom mirrors that), so this is a real, if approximate, signal rather than
|
|
286
|
+
// a hardcoded stub — no actual quirks-mode CSS behavior differs here since
|
|
287
|
+
// there's no layout engine to diverge.
|
|
288
|
+
JSValue js_doc_get_compatMode(JSContext* ctx, JSValue) {
|
|
289
|
+
bool has_doctype = get_doc(ctx)->doctype() != nullptr;
|
|
290
|
+
const char* mode = has_doctype ? "CSS1Compat" : "BackCompat";
|
|
291
|
+
return JS_NewString(ctx, mode);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// document uses its own object/prototype (not node_proto), so it doesn't
|
|
295
|
+
// inherit the Node-level baseURI getter — mirrors js_el_get_baseURI in
|
|
296
|
+
// ElementBindings.cpp (always location.href; no <base> element support).
|
|
297
|
+
JSValue js_doc_get_baseURI(JSContext* ctx, JSValue) {
|
|
298
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
299
|
+
JSValue location = JS_GetPropertyStr(ctx, global, "location");
|
|
300
|
+
JS_FreeValue(ctx, global);
|
|
301
|
+
JSValue href = JS_GetPropertyStr(ctx, location, "href");
|
|
302
|
+
JS_FreeValue(ctx, location);
|
|
303
|
+
return href;
|
|
304
|
+
}
|
|
305
|
+
|
|
164
306
|
const char* kDocumentTitleBootstrapScript = R"JS(
|
|
165
307
|
(function() {
|
|
166
308
|
Object.defineProperty(document, 'title', {
|
|
@@ -199,9 +341,11 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
199
341
|
JS_SetPropertyStr(ctx, doc, "getElementsByTagName", JS_NewCFunction(ctx, js_doc_getElementsByTagName, "getElementsByTagName", 1));
|
|
200
342
|
JS_SetPropertyStr(ctx, doc, "getElementsByName", JS_NewCFunction(ctx, js_doc_getElementsByName, "getElementsByName", 1));
|
|
201
343
|
JS_SetPropertyStr(ctx, doc, "createElement", JS_NewCFunction(ctx, js_doc_createElement, "createElement", 1));
|
|
344
|
+
JS_SetPropertyStr(ctx, doc, "createElementNS", JS_NewCFunction(ctx, js_doc_createElementNS, "createElementNS", 2));
|
|
202
345
|
JS_SetPropertyStr(ctx, doc, "createTextNode", JS_NewCFunction(ctx, js_doc_createTextNode, "createTextNode", 1));
|
|
203
346
|
JS_SetPropertyStr(ctx, doc, "createComment", JS_NewCFunction(ctx, js_doc_createComment, "createComment", 1));
|
|
204
347
|
JS_SetPropertyStr(ctx, doc, "createDocumentFragment", JS_NewCFunction(ctx, js_doc_createDocumentFragment, "createDocumentFragment", 0));
|
|
348
|
+
JS_SetPropertyStr(ctx, doc, "importNode", JS_NewCFunction(ctx, js_doc_importNode, "importNode", 2));
|
|
205
349
|
|
|
206
350
|
define_prop(ctx, doc, "body", js_doc_get_body, nullptr);
|
|
207
351
|
define_prop(ctx, doc, "head", js_doc_get_head, nullptr);
|
|
@@ -211,14 +355,33 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
211
355
|
define_prop(ctx, doc, "images", js_doc_get_images, nullptr);
|
|
212
356
|
define_prop(ctx, doc, "scripts", js_doc_get_scripts, nullptr);
|
|
213
357
|
define_prop(ctx, doc, "links", js_doc_get_links, nullptr);
|
|
358
|
+
define_prop(ctx, doc, "activeElement", js_doc_get_activeElement, nullptr);
|
|
359
|
+
define_prop(ctx, doc, "currentScript", js_doc_get_currentScript, nullptr);
|
|
360
|
+
define_prop(ctx, doc, "readyState", js_doc_get_readyState, nullptr);
|
|
361
|
+
define_prop(ctx, doc, "compatMode", js_doc_get_compatMode, nullptr);
|
|
362
|
+
define_prop(ctx, doc, "baseURI", js_doc_get_baseURI, nullptr);
|
|
363
|
+
|
|
364
|
+
JS_SetPropertyStr(ctx, doc, "characterSet", JS_NewString(ctx, "UTF-8"));
|
|
365
|
+
JS_SetPropertyStr(ctx, doc, "contentType", JS_NewString(ctx, "text/html"));
|
|
214
366
|
|
|
215
367
|
RuntimeContext* rctx = get_ctx(ctx);
|
|
216
368
|
bool hidden = !(rctx && rctx->pretend_to_be_visual);
|
|
217
369
|
JS_SetPropertyStr(ctx, doc, "hidden", JS_NewBool(ctx, hidden));
|
|
370
|
+
JS_SetPropertyStr(ctx, doc, "visibilityState", JS_NewString(ctx, hidden ? "hidden" : "visible"));
|
|
371
|
+
JS_SetPropertyStr(ctx, doc, "nodeType", JS_NewInt32(ctx, 9 /* DOCUMENT_NODE */));
|
|
372
|
+
JS_SetPropertyStr(ctx, doc, "nodeName", JS_NewString(ctx, "#document"));
|
|
373
|
+
JS_SetPropertyStr(ctx, doc, "ownerDocument", JS_NULL);
|
|
374
|
+
// document uses its own object/prototype (not node_proto), so it doesn't
|
|
375
|
+
// inherit js_el_get_namespaceURI — set explicitly rather than leaving this
|
|
376
|
+
// undefined, matching real jsdom (a Document node has no namespace).
|
|
377
|
+
JS_SetPropertyStr(ctx, doc, "namespaceURI", JS_NULL);
|
|
218
378
|
|
|
219
379
|
JSValue document_proto = JS_NewObject(ctx);
|
|
220
380
|
JS_SetPrototype(ctx, doc, document_proto);
|
|
221
|
-
|
|
381
|
+
define_node_type_constants(ctx, document_proto);
|
|
382
|
+
JSValue document_ctor = define_global_constructor(ctx, "Document", document_proto);
|
|
383
|
+
define_node_type_constants(ctx, document_ctor);
|
|
384
|
+
JS_FreeValue(ctx, document_ctor);
|
|
222
385
|
JS_FreeValue(ctx, document_proto);
|
|
223
386
|
|
|
224
387
|
JS_SetPropertyStr(ctx, global, "document", doc);
|