@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.
Files changed (54) hide show
  1. package/android/CMakeLists.txt +6 -0
  2. package/cpp/HybridHtmlSandbox.cpp +14 -7
  3. package/cpp/HybridHtmlSandbox.hpp +1 -1
  4. package/cpp/lexbor/LexborDocument.cpp +65 -3
  5. package/cpp/lexbor/LexborDocument.hpp +17 -2
  6. package/cpp/quickjs/DOMBindings.cpp +14 -1
  7. package/cpp/quickjs/DOMBindingsInternal.cpp +67 -0
  8. package/cpp/quickjs/DOMBindingsInternal.hpp +39 -0
  9. package/cpp/quickjs/QuickJSRuntime.cpp +13 -0
  10. package/cpp/quickjs/QuickJSRuntime.hpp +29 -0
  11. package/cpp/quickjs/bindings/AbortBindings.cpp +14 -0
  12. package/cpp/quickjs/bindings/BlobBindings.cpp +28 -1
  13. package/cpp/quickjs/bindings/BlobBindings.hpp +3 -3
  14. package/cpp/quickjs/bindings/CSSOMBindings.cpp +96 -0
  15. package/cpp/quickjs/bindings/CSSOMBindings.hpp +3 -1
  16. package/cpp/quickjs/bindings/CustomElementsBindings.cpp +1 -1
  17. package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
  18. package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
  19. package/cpp/quickjs/bindings/DocumentBindings.cpp +174 -11
  20. package/cpp/quickjs/bindings/ElementBindings.cpp +516 -20
  21. package/cpp/quickjs/bindings/EventBindings.cpp +281 -5
  22. package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
  23. package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
  24. package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
  25. package/cpp/quickjs/bindings/FetchBindings.cpp +43 -8
  26. package/cpp/quickjs/bindings/FormBindings.cpp +294 -0
  27. package/cpp/quickjs/bindings/FormBindings.hpp +9 -5
  28. package/cpp/quickjs/bindings/IntlBindings.cpp +352 -0
  29. package/cpp/quickjs/bindings/IntlBindings.hpp +29 -0
  30. package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
  31. package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
  32. package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +45 -0
  33. package/cpp/quickjs/bindings/TimerBindings.cpp +79 -0
  34. package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
  35. package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
  36. package/cpp/quickjs/bindings/UrlBindings.cpp +43 -0
  37. package/cpp/quickjs/bindings/WindowBindings.cpp +295 -4
  38. package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
  39. package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp +241 -0
  40. package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.hpp +30 -0
  41. package/lib/commonjs/classes/JSDOM/JSDOM.class.js +2 -1
  42. package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
  43. package/lib/module/classes/JSDOM/JSDOM.class.js +2 -1
  44. package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
  45. package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
  46. package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts +5 -4
  47. package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts.map +1 -1
  48. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
  49. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
  50. package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
  51. package/package.json +17 -2
  52. package/src/classes/JSDOM/JSDOM.class.ts +2 -1
  53. package/src/classes/JSDOM/types/IJSDOMOptions.ts +5 -4
  54. package/src/specs/HtmlSandbox.nitro.ts +1 -1
@@ -201,6 +201,56 @@ const char* kCSSOMBootstrapScript = R"JS(
201
201
  globalThis.CSSStyleRule = CSSStyleRule;
202
202
  globalThis.CSSStyleSheet = CSSStyleSheet;
203
203
 
204
+ function cssEscape(value) {
205
+ var string = String(value);
206
+ var length = string.length;
207
+ var index = -1;
208
+ var result = '';
209
+ var firstCodeUnit = string.charCodeAt(0);
210
+
211
+ if (length === 1 && firstCodeUnit === 0x002D) {
212
+ return '\\' + string;
213
+ }
214
+
215
+ while (++index < length) {
216
+ var codeUnit = string.charCodeAt(index);
217
+ if (codeUnit === 0x0000) {
218
+ result += '\uFFFD';
219
+ continue;
220
+ }
221
+ if (
222
+ (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit === 0x007F ||
223
+ (index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
224
+ (index === 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit === 0x002D)
225
+ ) {
226
+ result += '\\' + codeUnit.toString(16) + ' ';
227
+ continue;
228
+ }
229
+ if (
230
+ codeUnit >= 0x0080 || codeUnit === 0x002D || codeUnit === 0x005F ||
231
+ (codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
232
+ (codeUnit >= 0x0041 && codeUnit <= 0x005A) ||
233
+ (codeUnit >= 0x0061 && codeUnit <= 0x007A)
234
+ ) {
235
+ result += string.charAt(index);
236
+ continue;
237
+ }
238
+ result += '\\' + string.charAt(index);
239
+ }
240
+ return result;
241
+ }
242
+
243
+ globalThis.CSS = {
244
+ escape: cssEscape,
245
+ supports: function(propertyOrCondition, value) {
246
+ if (arguments.length >= 2) {
247
+ return typeof propertyOrCondition === 'string' && propertyOrCondition.length > 0 &&
248
+ value !== undefined && String(value).length > 0;
249
+ }
250
+ return typeof propertyOrCondition === 'string' && propertyOrCondition.trim().length > 0;
251
+ },
252
+ };
253
+
204
254
  Object.defineProperty(Element.prototype, 'sheet', {
205
255
  get: function() {
206
256
  if (this.tagName !== 'STYLE') return null;
@@ -227,6 +277,52 @@ const char* kCSSOMBootstrapScript = R"JS(
227
277
  },
228
278
  configurable: true,
229
279
  });
280
+
281
+ // ── getComputedStyle ──────────────────────────────────────────────────
282
+ // No layout/cascade engine backs this sandbox — there is no stylesheet
283
+ // specificity resolution or inheritance, only the element's own inline
284
+ // `style` attribute. `display` falls back to a small user-agent-stylesheet
285
+ // approximation (block/inline/inline-block/none by tag name, or 'none' for
286
+ // a `hidden` attribute) since "is this element visible" is the one
287
+ // getComputedStyle check real-world embedded scripts actually make;
288
+ // `visibility`/`opacity` fall back to their initial values. Every other
289
+ // unset property returns '', not a computed initial value.
290
+ var kBlockTags = { ADDRESS: 1, ARTICLE: 1, ASIDE: 1, BLOCKQUOTE: 1, DETAILS: 1, DIALOG: 1, DD: 1, DIV: 1,
291
+ DL: 1, DT: 1, FIELDSET: 1, FIGCAPTION: 1, FIGURE: 1, FOOTER: 1, FORM: 1, H1: 1, H2: 1, H3: 1, H4: 1,
292
+ H5: 1, H6: 1, HEADER: 1, HGROUP: 1, HR: 1, LI: 1, MAIN: 1, NAV: 1, OL: 1, P: 1, PRE: 1, SECTION: 1,
293
+ TABLE: 1, UL: 1, HTML: 1, BODY: 1 };
294
+ var kNoneTags = { SCRIPT: 1, STYLE: 1, HEAD: 1, TITLE: 1, META: 1, LINK: 1, TEMPLATE: 1 };
295
+ var kInlineBlockTags = { IMG: 1, BUTTON: 1, INPUT: 1, SELECT: 1, TEXTAREA: 1 };
296
+
297
+ function defaultDisplay(tagName) {
298
+ if (kNoneTags[tagName]) return 'none';
299
+ if (kInlineBlockTags[tagName]) return 'inline-block';
300
+ if (kBlockTags[tagName]) return 'block';
301
+ return 'inline';
302
+ }
303
+
304
+ function resolveProperty(el, kebabName) {
305
+ var v = el.style.getPropertyValue(kebabName);
306
+ if (v) return v;
307
+ if (kebabName === 'display') return el.hasAttribute('hidden') ? 'none' : defaultDisplay(el.tagName);
308
+ if (kebabName === 'visibility') return 'visible';
309
+ if (kebabName === 'opacity') return '1';
310
+ return '';
311
+ }
312
+
313
+ globalThis.getComputedStyle = function(el) {
314
+ if (!el || typeof el.tagName !== 'string') {
315
+ throw new TypeError("Failed to execute 'getComputedStyle': parameter 1 is not of type 'Element'.");
316
+ }
317
+ return new Proxy({}, {
318
+ get: function(_target, prop) {
319
+ if (prop === 'getPropertyValue') return function(name) { return resolveProperty(el, String(name)); };
320
+ if (prop === 'getPropertyPriority') return function() { return ''; };
321
+ if (typeof prop !== 'string') return undefined;
322
+ return resolveProperty(el, camelToKebab(prop));
323
+ },
324
+ });
325
+ };
230
326
  })();
231
327
  )JS";
232
328
 
@@ -5,7 +5,9 @@
5
5
  namespace margelo::nitro::nitrojsdom {
6
6
 
7
7
  // Registers a minimal CSSOM: globalThis.CSSRule/CSSStyleRule/CSSStyleSheet,
8
- // Element.prototype.sheet (tag-checked for "style"), and document.styleSheets.
8
+ // Element.prototype.sheet (tag-checked for "style"), document.styleSheets,
9
+ // and globalThis.getComputedStyle (inline-style + tag-default-display only,
10
+ // no real cascade — see the bootstrap script for the exact fallback rules).
9
11
  //
10
12
  // Rules are split by a hand-rolled top-level brace scanner rather than
11
13
  // Lexbor's CSS rule/declaration AST (lxb_css_rule_t) — mirrors how
@@ -27,7 +27,7 @@ const char* kCustomElementsBootstrapScript = R"JS(
27
27
  var n = node;
28
28
  while (n) {
29
29
  if (n === docEl) return true;
30
- n = ('host' in n) ? n.host : n.parentNode;
30
+ n = (n instanceof ShadowRoot) ? n.host : n.parentNode;
31
31
  }
32
32
  return false;
33
33
  }
@@ -0,0 +1,299 @@
1
+ #include "DOMParserBindings.hpp"
2
+ #include "../DOMBindingsInternal.hpp"
3
+ #include "../QuickJSRuntime.hpp"
4
+ #include "../../lexbor/LexborDocument.hpp"
5
+ #include <cstring>
6
+ #include <string>
7
+
8
+ namespace margelo::nitro::nitrojsdom {
9
+
10
+ namespace {
11
+
12
+ JSClassID js_parsed_document_class_id = 0;
13
+ JSClassDef js_parsed_document_class = { "Document", .finalizer = nullptr };
14
+
15
+ LexborDocument* unwrap_parsed_doc(JSContext* ctx, JSValue val) {
16
+ return static_cast<LexborDocument*>(JS_GetOpaque(val, js_parsed_document_class_id));
17
+ }
18
+
19
+ JSValue make_parsed_document(JSContext* ctx, LexborDocument* doc) {
20
+ register_document(ctx, doc);
21
+ JSValue obj = JS_NewObjectClass(ctx, js_parsed_document_class_id);
22
+ JS_SetOpaque(obj, doc);
23
+ register_document_wrapper(ctx, doc, obj);
24
+ return obj;
25
+ }
26
+
27
+ // ── Document-scoped query/creation methods (mirror DocumentBindings.cpp's
28
+ // js_doc_* functions, but resolve `this`'s own LexborDocument instead of
29
+ // always reading the sandbox's primary get_doc(ctx)) ────────────────────────
30
+
31
+ JSValue js_pdoc_getElementById(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
32
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
33
+ if (!doc || argc < 1) return JS_NULL;
34
+ const char* id = JS_ToCString(ctx, argv[0]);
35
+ if (!id) return JS_NULL;
36
+ void* el = doc->getElementById(id);
37
+ JS_FreeCString(ctx, id);
38
+ return make_element(ctx, el);
39
+ }
40
+
41
+ JSValue js_pdoc_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
42
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
43
+ if (!doc || argc < 1) return JS_NULL;
44
+ const char* sel = JS_ToCString(ctx, argv[0]);
45
+ if (!sel) return JS_NULL;
46
+ void* el = doc->querySelector_el(sel);
47
+ JS_FreeCString(ctx, sel);
48
+ return make_element(ctx, el);
49
+ }
50
+
51
+ JSValue js_pdoc_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
52
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
53
+ if (!doc || argc < 1) return JS_NewArray(ctx);
54
+ const char* sel = JS_ToCString(ctx, argv[0]);
55
+ if (!sel) return JS_NewArray(ctx);
56
+ auto results = doc->querySelectorAll_el(sel);
57
+ JS_FreeCString(ctx, sel);
58
+ return make_element_array(ctx, results);
59
+ }
60
+
61
+ // Static snapshot, not a live HTMLCollection — see header comment.
62
+ JSValue js_pdoc_getElementsByClassName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
63
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
64
+ if (!doc || argc < 1) return JS_NewArray(ctx);
65
+ const char* names = JS_ToCString(ctx, argv[0]);
66
+ if (!names) return JS_NewArray(ctx);
67
+ auto results = doc->querySelectorAll_el(classNames_to_selector(names));
68
+ JS_FreeCString(ctx, names);
69
+ return make_element_array(ctx, results);
70
+ }
71
+
72
+ // Static snapshot, not a live HTMLCollection — see header comment.
73
+ JSValue js_pdoc_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
74
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
75
+ if (!doc || argc < 1) return JS_NewArray(ctx);
76
+ const char* tag = JS_ToCString(ctx, argv[0]);
77
+ if (!tag) return JS_NewArray(ctx);
78
+ auto results = doc->querySelectorAll_el(tag);
79
+ JS_FreeCString(ctx, tag);
80
+ return make_element_array(ctx, results);
81
+ }
82
+
83
+ JSValue js_pdoc_createElement(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
84
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
85
+ if (!doc || argc < 1) return JS_NULL;
86
+ const char* tag = JS_ToCString(ctx, argv[0]);
87
+ if (!tag) return JS_NULL;
88
+ void* el = doc->createElement(tag);
89
+ JS_FreeCString(ctx, tag);
90
+ return make_element(ctx, el);
91
+ }
92
+
93
+ JSValue js_pdoc_createTextNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
94
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
95
+ if (!doc || argc < 1) return JS_NULL;
96
+ const char* text = JS_ToCString(ctx, argv[0]);
97
+ if (!text) return JS_NULL;
98
+ void* node = doc->createTextNode(text);
99
+ JS_FreeCString(ctx, text);
100
+ return make_element(ctx, node);
101
+ }
102
+
103
+ JSValue js_pdoc_createComment(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
104
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
105
+ if (!doc || argc < 1) return JS_NULL;
106
+ const char* text = JS_ToCString(ctx, argv[0]);
107
+ if (!text) return JS_NULL;
108
+ void* node = doc->createComment(text);
109
+ JS_FreeCString(ctx, text);
110
+ return make_element(ctx, node);
111
+ }
112
+
113
+ JSValue js_pdoc_createDocumentFragment(JSContext* ctx, JSValue this_val, int, JSValue*) {
114
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
115
+ if (!doc) return JS_NULL;
116
+ return make_element(ctx, doc->createDocumentFragment());
117
+ }
118
+
119
+ JSValue js_pdoc_get_body(JSContext* ctx, JSValue this_val) {
120
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
121
+ return doc ? make_element(ctx, doc->body()) : JS_NULL;
122
+ }
123
+
124
+ JSValue js_pdoc_get_head(JSContext* ctx, JSValue this_val) {
125
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
126
+ return doc ? make_element(ctx, doc->head()) : JS_NULL;
127
+ }
128
+
129
+ JSValue js_pdoc_get_documentElement(JSContext* ctx, JSValue this_val) {
130
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
131
+ return doc ? make_element(ctx, doc->documentElement()) : JS_NULL;
132
+ }
133
+
134
+ // Unlike document.doctype (DocumentBindings.cpp), this isn't cached: a
135
+ // secondary document is far less likely to have `.doctype` read repeatedly,
136
+ // so a fresh plain object per access keeps this file simpler.
137
+ JSValue js_pdoc_get_doctype(JSContext* ctx, JSValue this_val) {
138
+ auto* doc = unwrap_parsed_doc(ctx, this_val);
139
+ if (!doc) return JS_NULL;
140
+ void* dt = doc->doctype();
141
+ if (!dt) return JS_NULL;
142
+ return build_doctype_object(ctx, doc, dt);
143
+ }
144
+
145
+ // ── DOMParser.parseFromString() / document.implementation.createHTMLDocument() ─
146
+
147
+ JSValue js_native_parse_from_string(JSContext* ctx, JSValue, int argc, JSValue* argv) {
148
+ if (argc < 2) return JS_ThrowTypeError(ctx, "parseFromString requires 2 arguments");
149
+ const char* html = JS_ToCString(ctx, argv[0]);
150
+ if (!html) return JS_EXCEPTION;
151
+ const char* type = JS_ToCString(ctx, argv[1]);
152
+ if (!type) { JS_FreeCString(ctx, html); return JS_EXCEPTION; }
153
+
154
+ static const char* kSupportedTypes[] = {
155
+ "text/html", "text/xml", "application/xml", "application/xhtml+xml", "image/svg+xml",
156
+ };
157
+ bool supported = false;
158
+ for (const char* t : kSupportedTypes) {
159
+ if (std::strcmp(type, t) == 0) { supported = true; break; }
160
+ }
161
+ if (!supported) {
162
+ std::string msg = "Invalid DOMParser type: " + std::string(type);
163
+ JS_FreeCString(ctx, html);
164
+ JS_FreeCString(ctx, type);
165
+ return JS_ThrowTypeError(ctx, "%s", msg.c_str());
166
+ }
167
+ // Every supported type is parsed with the HTML parser regardless of MIME
168
+ // type — this sandbox has no separate XML parser wired up. Good enough for
169
+ // the common case (parsing HTML/XHTML/SVG markup); genuine XML-specific
170
+ // syntax (e.g. CDATA sections, processing instructions) is not modeled.
171
+ JS_FreeCString(ctx, type);
172
+
173
+ auto* doc = new LexborDocument();
174
+ doc->parse(html);
175
+ JS_FreeCString(ctx, html);
176
+
177
+ auto* rctx = get_ctx(ctx);
178
+ JSValue result = make_parsed_document(ctx, doc);
179
+ if (rctx) rctx->extra_documents.emplace_back(doc);
180
+ return result;
181
+ }
182
+
183
+ JSValue js_native_create_html_document(JSContext* ctx, JSValue, int argc, JSValue* argv) {
184
+ std::string title;
185
+ if (argc >= 1 && !JS_IsUndefined(argv[0])) {
186
+ const char* t = JS_ToCString(ctx, argv[0]);
187
+ if (t) { title = t; JS_FreeCString(ctx, t); }
188
+ }
189
+ std::string escaped;
190
+ escaped.reserve(title.size());
191
+ for (char c : title) {
192
+ if (c == '<') escaped += "&lt;";
193
+ else if (c == '>') escaped += "&gt;";
194
+ else if (c == '&') escaped += "&amp;";
195
+ else escaped += c;
196
+ }
197
+
198
+ auto* doc = new LexborDocument();
199
+ doc->parse("<!doctype html><html><head><title>" + escaped + "</title></head><body></body></html>");
200
+
201
+ auto* rctx = get_ctx(ctx);
202
+ JSValue result = make_parsed_document(ctx, doc);
203
+ if (rctx) rctx->extra_documents.emplace_back(doc);
204
+ return result;
205
+ }
206
+
207
+ const char* kDOMParserBootstrapScript = R"JS(
208
+ (function() {
209
+ function DOMParser() {}
210
+ DOMParser.prototype.parseFromString = function(str, type) {
211
+ return __nativeParseFromString(String(str), String(type));
212
+ };
213
+ globalThis.DOMParser = DOMParser;
214
+
215
+ document.implementation = {
216
+ createHTMLDocument: function(title) {
217
+ return __nativeCreateHTMLDocument(title === undefined ? undefined : String(title));
218
+ },
219
+ };
220
+
221
+ function definePDocTitle(proto) {
222
+ Object.defineProperty(proto, 'title', {
223
+ configurable: true,
224
+ get: function() {
225
+ var head = this.head;
226
+ if (!head) return '';
227
+ var titleEl = head.querySelector('title');
228
+ return titleEl ? titleEl.textContent : '';
229
+ },
230
+ set: function(value) {
231
+ var head = this.head;
232
+ if (!head) return;
233
+ var titleEl = head.querySelector('title');
234
+ if (!titleEl) {
235
+ titleEl = this.createElement('title');
236
+ head.appendChild(titleEl);
237
+ }
238
+ titleEl.textContent = String(value);
239
+ },
240
+ });
241
+ }
242
+ definePDocTitle(globalThis.__parsedDocumentProto);
243
+ delete globalThis.__parsedDocumentProto;
244
+ })();
245
+ )JS";
246
+
247
+ } // namespace
248
+
249
+ void DOMParserBindings::install(JSContext* ctx) {
250
+ JS_NewClassID(&js_parsed_document_class_id);
251
+ JS_NewClass(JS_GetRuntime(ctx), js_parsed_document_class_id, &js_parsed_document_class);
252
+
253
+ JSValue proto = JS_NewObject(ctx);
254
+ JS_SetPropertyStr(ctx, proto, "getElementById", JS_NewCFunction(ctx, js_pdoc_getElementById, "getElementById", 1));
255
+ JS_SetPropertyStr(ctx, proto, "querySelector", JS_NewCFunction(ctx, js_pdoc_querySelector, "querySelector", 1));
256
+ JS_SetPropertyStr(ctx, proto, "querySelectorAll", JS_NewCFunction(ctx, js_pdoc_querySelectorAll, "querySelectorAll", 1));
257
+ JS_SetPropertyStr(ctx, proto, "getElementsByClassName", JS_NewCFunction(ctx, js_pdoc_getElementsByClassName, "getElementsByClassName", 1));
258
+ JS_SetPropertyStr(ctx, proto, "getElementsByTagName", JS_NewCFunction(ctx, js_pdoc_getElementsByTagName, "getElementsByTagName", 1));
259
+ JS_SetPropertyStr(ctx, proto, "createElement", JS_NewCFunction(ctx, js_pdoc_createElement, "createElement", 1));
260
+ JS_SetPropertyStr(ctx, proto, "createTextNode", JS_NewCFunction(ctx, js_pdoc_createTextNode, "createTextNode", 1));
261
+ JS_SetPropertyStr(ctx, proto, "createComment", JS_NewCFunction(ctx, js_pdoc_createComment, "createComment", 1));
262
+ JS_SetPropertyStr(ctx, proto, "createDocumentFragment", JS_NewCFunction(ctx, js_pdoc_createDocumentFragment, "createDocumentFragment", 0));
263
+
264
+ define_prop(ctx, proto, "body", js_pdoc_get_body, nullptr);
265
+ define_prop(ctx, proto, "head", js_pdoc_get_head, nullptr);
266
+ define_prop(ctx, proto, "documentElement", js_pdoc_get_documentElement, nullptr);
267
+ define_prop(ctx, proto, "doctype", js_pdoc_get_doctype, nullptr);
268
+
269
+ JS_SetPropertyStr(ctx, proto, "nodeType", JS_NewInt32(ctx, 9 /* DOCUMENT_NODE */));
270
+ JS_SetPropertyStr(ctx, proto, "nodeName", JS_NewString(ctx, "#document"));
271
+ JS_SetPropertyStr(ctx, proto, "ownerDocument", JS_NULL);
272
+ define_node_type_constants(ctx, proto);
273
+
274
+ JSValue global = JS_GetGlobalObject(ctx);
275
+
276
+ // Chain onto Document.prototype (set up by DocumentBindings.cpp) purely so
277
+ // `parsedDoc instanceof Document` holds, matching real jsdom/browsers.
278
+ JSValue document_ctor = JS_GetPropertyStr(ctx, global, "Document");
279
+ JSValue document_proto_val = JS_GetPropertyStr(ctx, document_ctor, "prototype");
280
+ JS_SetPrototype(ctx, proto, document_proto_val);
281
+ JS_FreeValue(ctx, document_proto_val);
282
+ JS_FreeValue(ctx, document_ctor);
283
+
284
+ JS_SetClassProto(ctx, js_parsed_document_class_id, JS_DupValue(ctx, proto));
285
+
286
+ JS_SetPropertyStr(ctx, global, "__parsedDocumentProto", proto);
287
+ JS_SetPropertyStr(ctx, global, "__nativeParseFromString", JS_NewCFunction(ctx, js_native_parse_from_string, "__nativeParseFromString", 2));
288
+ JS_SetPropertyStr(ctx, global, "__nativeCreateHTMLDocument", JS_NewCFunction(ctx, js_native_create_html_document, "__nativeCreateHTMLDocument", 1));
289
+ JS_FreeValue(ctx, global);
290
+
291
+ JSValue result = JS_Eval(ctx, kDOMParserBootstrapScript, strlen(kDOMParserBootstrapScript),
292
+ "<domparser-bootstrap>", JS_EVAL_TYPE_GLOBAL);
293
+ if (JS_IsException(result)) {
294
+ JS_FreeValue(ctx, JS_GetException(ctx));
295
+ }
296
+ JS_FreeValue(ctx, result);
297
+ }
298
+
299
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,45 @@
1
+ #pragma once
2
+
3
+ // Registers globalThis.DOMParser (`.parseFromString()`) and
4
+ // document.implementation.createHTMLDocument(), each of which produces a
5
+ // genuinely separate, inert Document backed by its own LexborDocument
6
+ // instance (see doc_for_node()/register_document() in DOMBindingsInternal —
7
+ // that's what makes mutating a second document safe rather than corrupting
8
+ // the primary document's memory arena).
9
+ //
10
+ // Scope, matching real-world DOMParser usage (parsing a fetched/embedded
11
+ // HTML string and reading it back, occasionally assembling a few new nodes):
12
+ // - Full read/query support: querySelector(All), getElementById,
13
+ // getElementsBy{ClassName,TagName} (as static snapshots, not live
14
+ // HTMLCollections — see below), all generic Node traversal/attribute
15
+ // methods (those are shared with the primary document's Element/Node
16
+ // prototypes and don't need document-specific wiring).
17
+ // - createElement/createTextNode/createComment/createDocumentFragment,
18
+ // appendChild/removeChild/insertBefore/replaceChild, and the
19
+ // textContent/innerHTML setters/insertAdjacentHTML/matches()/closest()
20
+ // inherited from Element.prototype — all resolve the correct owning
21
+ // document dynamically, so building up a secondary document is fully
22
+ // safe, not just read-only.
23
+ // - title get/set (mirrors document.title's head/<title> shim).
24
+ // Not supported on secondary documents (their bindings are hardwired to the
25
+ // primary document — see ShadowRootBindings/CustomElementsBindings/
26
+ // TemplateBindings/LiveCollectionBindings): Shadow DOM (attachShadow),
27
+ // Custom Elements, <template>.content, MutationObserver, live
28
+ // HTMLCollections (.forms/.images/.scripts/.links, and
29
+ // getElementsBy{ClassName,TagName} return static arrays here instead).
30
+ // Scripts inside parsed HTML never execute (per spec, these documents are
31
+ // inert) — no script pipeline is wired to them.
32
+ //
33
+ // Must run after ElementBindings (shares its Element/Node classes/protos)
34
+ // and DocumentBindings (adds .implementation onto globalThis.document).
35
+
36
+ #include "quickjs.h"
37
+
38
+ namespace margelo::nitro::nitrojsdom {
39
+
40
+ class DOMParserBindings {
41
+ public:
42
+ static void install(JSContext* ctx);
43
+ };
44
+
45
+ } // namespace margelo::nitro::nitrojsdom