@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
@@ -11,6 +11,9 @@
11
11
  #include <lexbor/html/html.h>
12
12
  #include <lexbor/dom/dom.h>
13
13
  #include <lexbor/dom/interfaces/character_data.h>
14
+ #include <lexbor/dom/interfaces/attr.h>
15
+ #include <lexbor/ns/const.h>
16
+ #include <lexbor/ns/ns.h>
14
17
  #include <cctype>
15
18
  #include <cstring>
16
19
  #include <optional>
@@ -29,10 +32,18 @@ JSValue js_el_get_tagName(JSContext* ctx, JSValue this_val) {
29
32
  auto* el = unwrap_element(ctx, this_val);
30
33
  if (!el) return JS_NewString(ctx, "");
31
34
  size_t len = 0;
32
- const lxb_char_t* name = lxb_dom_element_local_name(el, &len);
35
+ // qualified_name (prefix:localName) when the element has a namespace
36
+ // prefix (createElementNS('...', 'prefix:name')), falling back to the
37
+ // plain local name otherwise.
38
+ const lxb_char_t* name = lxb_dom_element_qualified_name(el, &len);
33
39
  if (!name || len == 0) return JS_NewString(ctx, "");
34
40
  std::string r(reinterpret_cast<const char*>(name), len);
35
- for (auto& c : r) c = (char)std::toupper((unsigned char)c);
41
+ // Only HTML-namespace elements are uppercased (per spec); SVG/MathML/custom
42
+ // namespace elements keep their (lexbor-lowercased) local name as-is.
43
+ auto* node = reinterpret_cast<lxb_dom_node_t*>(el);
44
+ if (node->ns == LXB_NS_HTML) {
45
+ for (auto& c : r) c = (char)std::toupper((unsigned char)c);
46
+ }
36
47
  return JS_NewStringLen(ctx, r.c_str(), r.size());
37
48
  }
38
49
 
@@ -223,7 +234,7 @@ JSValue js_el_set_value(JSContext* ctx, JSValue this_val, JSValue val) {
223
234
  if (has_obs) rctx->mutation_observers->disconnectDetachedTargets(old_children);
224
235
  }
225
236
 
226
- get_doc(ctx)->setTextContentOnEl(el, str);
237
+ doc_for_node(ctx, el)->setTextContentOnEl(el, str);
227
238
 
228
239
  if (has_obs) {
229
240
  std::vector<void*> new_children;
@@ -292,6 +303,107 @@ JSValue js_el_set_checked(JSContext* ctx, JSValue this_val, JSValue val) {
292
303
  return JS_UNDEFINED;
293
304
  }
294
305
 
306
+ JSValue bool_attr_get(JSContext* ctx, JSValue this_val, const char* attr, size_t attr_len) {
307
+ auto* el = unwrap_element(ctx, this_val);
308
+ if (!el) return JS_FALSE;
309
+ bool has = lxb_dom_element_has_attribute(el, reinterpret_cast<const lxb_char_t*>(attr), attr_len);
310
+ return JS_NewBool(ctx, has);
311
+ }
312
+
313
+ JSValue bool_attr_set(JSContext* ctx, JSValue this_val, JSValue val, const char* attr, size_t attr_len) {
314
+ auto* el = unwrap_element(ctx, this_val);
315
+ if (!el) return JS_UNDEFINED;
316
+ bool on = JS_ToBool(ctx, val) > 0;
317
+ auto* attr_name = reinterpret_cast<const lxb_char_t*>(attr);
318
+ bool has = lxb_dom_element_has_attribute(el, attr_name, attr_len);
319
+
320
+ auto* rctx = get_ctx(ctx);
321
+ bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
322
+ std::optional<std::string> old_val;
323
+ if (has_obs && has && rctx->mutation_observers->hasAttributeOldValueObserver()) {
324
+ size_t len = 0;
325
+ const lxb_char_t* v = lxb_dom_element_get_attribute(el, attr_name, attr_len, &len);
326
+ if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
327
+ }
328
+
329
+ if (on && !has) {
330
+ lxb_dom_element_set_attribute(el, attr_name, attr_len, reinterpret_cast<const lxb_char_t*>(""), 0);
331
+ if (has_obs) {
332
+ rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), attr, std::nullopt);
333
+ }
334
+ } else if (!on && has) {
335
+ lxb_dom_element_remove_attribute(el, attr_name, attr_len);
336
+ if (has_obs) {
337
+ rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), attr, old_val);
338
+ }
339
+ }
340
+
341
+ return JS_UNDEFINED;
342
+ }
343
+
344
+ JSValue js_el_get_disabled(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "disabled", 8); }
345
+ JSValue js_el_set_disabled(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "disabled", 8); }
346
+
347
+ JSValue js_el_get_required(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "required", 8); }
348
+ JSValue js_el_set_required(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "required", 8); }
349
+
350
+ JSValue js_el_get_readOnly(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "readonly", 8); }
351
+ JSValue js_el_set_readOnly(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "readonly", 8); }
352
+
353
+ JSValue js_el_get_multiple(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "multiple", 8); }
354
+ JSValue js_el_set_multiple(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "multiple", 8); }
355
+
356
+ JSValue js_el_get_autofocus(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "autofocus", 9); }
357
+ JSValue js_el_set_autofocus(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "autofocus", 9); }
358
+
359
+ JSValue js_el_get_selected(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "selected", 8); }
360
+ JSValue js_el_set_selected(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "selected", 8); }
361
+
362
+ JSValue js_el_get_hidden(JSContext* ctx, JSValue this_val) { return bool_attr_get(ctx, this_val, "hidden", 6); }
363
+ JSValue js_el_set_hidden(JSContext* ctx, JSValue this_val, JSValue val) { return bool_attr_set(ctx, this_val, val, "hidden", 6); }
364
+
365
+ JSValue string_attr_get(JSContext* ctx, JSValue this_val, const char* attr, size_t attr_len) {
366
+ auto* el = unwrap_element(ctx, this_val);
367
+ if (!el) return JS_NewString(ctx, "");
368
+ size_t len = 0;
369
+ const lxb_char_t* val = lxb_dom_element_get_attribute(el, reinterpret_cast<const lxb_char_t*>(attr), attr_len, &len);
370
+ return val ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len) : JS_NewString(ctx, "");
371
+ }
372
+
373
+ JSValue string_attr_set(JSContext* ctx, JSValue this_val, JSValue val, const char* attr, size_t attr_len) {
374
+ auto* el = unwrap_element(ctx, this_val);
375
+ if (!el) return JS_UNDEFINED;
376
+ const char* str = JS_ToCString(ctx, val);
377
+ if (!str) return JS_UNDEFINED;
378
+ auto* attr_name = reinterpret_cast<const lxb_char_t*>(attr);
379
+
380
+ auto* rctx = get_ctx(ctx);
381
+ bool has_obs = rctx && rctx->mutation_observers && !rctx->mutation_observers->empty();
382
+ std::optional<std::string> old_val;
383
+ if (has_obs && rctx->mutation_observers->hasAttributeOldValueObserver()) {
384
+ size_t len = 0;
385
+ const lxb_char_t* v = lxb_dom_element_get_attribute(el, attr_name, attr_len, &len);
386
+ if (v) old_val = std::string(reinterpret_cast<const char*>(v), len);
387
+ }
388
+
389
+ lxb_dom_element_set_attribute(el, attr_name, attr_len, reinterpret_cast<const lxb_char_t*>(str), strlen(str));
390
+ JS_FreeCString(ctx, str);
391
+
392
+ if (has_obs) {
393
+ rctx->mutation_observers->notifyAttribute(ctx, lxb_dom_interface_node(el), attr, old_val);
394
+ }
395
+ return JS_UNDEFINED;
396
+ }
397
+
398
+ JSValue js_el_get_title(JSContext* ctx, JSValue this_val) { return string_attr_get(ctx, this_val, "title", 5); }
399
+ JSValue js_el_set_title(JSContext* ctx, JSValue this_val, JSValue val) { return string_attr_set(ctx, this_val, val, "title", 5); }
400
+
401
+ JSValue js_el_get_lang(JSContext* ctx, JSValue this_val) { return string_attr_get(ctx, this_val, "lang", 4); }
402
+ JSValue js_el_set_lang(JSContext* ctx, JSValue this_val, JSValue val) { return string_attr_set(ctx, this_val, val, "lang", 4); }
403
+
404
+ JSValue js_el_get_dir(JSContext* ctx, JSValue this_val) { return string_attr_get(ctx, this_val, "dir", 3); }
405
+ JSValue js_el_set_dir(JSContext* ctx, JSValue this_val, JSValue val) { return string_attr_set(ctx, this_val, val, "dir", 3); }
406
+
295
407
  JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val) {
296
408
  lxb_dom_node_t* node = unwrap_node(ctx, this_val);
297
409
  if (!node) return JS_NewString(ctx, "");
@@ -340,7 +452,7 @@ JSValue js_el_set_textContent(JSContext* ctx, JSValue this_val, JSValue val) {
340
452
  if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
341
453
  }
342
454
  }
343
- get_doc(ctx)->setTextContentOnEl(el, str);
455
+ doc_for_node(ctx, node)->setTextContentOnEl(el, str);
344
456
  if (has_observers) {
345
457
  std::vector<void*> new_children;
346
458
  lxb_dom_node_t* child = node->first_child;
@@ -385,7 +497,7 @@ JSValue js_el_set_innerHTML(JSContext* ctx, JSValue this_val, JSValue val) {
385
497
  if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
386
498
  }
387
499
 
388
- get_doc(ctx)->setInnerHTMLOnEl(el, html);
500
+ doc_for_node(ctx, el)->setInnerHTMLOnEl(el, html);
389
501
  JS_FreeCString(ctx, html);
390
502
 
391
503
  if (has_observers) {
@@ -530,6 +642,16 @@ JSValue js_el_set_nodeValue(JSContext* ctx, JSValue this_val, JSValue val) {
530
642
  return JS_UNDEFINED;
531
643
  }
532
644
 
645
+ JSValue js_el_get_data_length(JSContext* ctx, JSValue this_val) {
646
+ auto* node = unwrap_node(ctx, this_val);
647
+ if (!node) return JS_UNDEFINED;
648
+ if (node->type != LXB_DOM_NODE_TYPE_TEXT && node->type != LXB_DOM_NODE_TYPE_COMMENT) return JS_UNDEFINED;
649
+ JSValue str_val = js_el_get_nodeValue(ctx, this_val);
650
+ JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
651
+ JS_FreeValue(ctx, str_val);
652
+ return len_val;
653
+ }
654
+
533
655
  JSValue js_el_get_childNodes(JSContext* ctx, JSValue this_val) {
534
656
  auto* node = unwrap_node(ctx, this_val);
535
657
  if (!node) return JS_NewArray(ctx);
@@ -651,6 +773,93 @@ JSValue js_el_hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue*
651
773
  return JS_NewBool(ctx, has);
652
774
  }
653
775
 
776
+ // ── Namespaced attribute methods (getAttributeNS/setAttributeNS/...) ─────────
777
+ // Simplification: this sandbox doesn't track a real (namespace, localName)
778
+ // identity per attribute the way createElementNS()/namespaceURI() do for
779
+ // elements — building that would mean hand-constructing lxb_dom_attr_t nodes
780
+ // with their own ns/prefix fields, a much larger and riskier lift than the
781
+ // element side. Instead, attributes are matched by local name only (the
782
+ // qualified name with any "prefix:" stripped), which is spec-accurate for
783
+ // the common real-world case (a single xlink:href-style attribute) but not
784
+ // for two attributes sharing a local name across different namespaces — a
785
+ // case that essentially never comes up in embedded-widget scripts.
786
+ lxb_dom_attr_t* find_attr_by_local_name(lxb_dom_element_t* el, const std::string& localName) {
787
+ for (lxb_dom_attr_t* attr = lxb_dom_element_first_attribute(el); attr; attr = lxb_dom_element_next_attribute(attr)) {
788
+ size_t len = 0;
789
+ const lxb_char_t* qname = lxb_dom_attr_qualified_name(attr, &len);
790
+ if (!qname) continue;
791
+ std::string q(reinterpret_cast<const char*>(qname), len);
792
+ auto colon = q.find(':');
793
+ std::string local = (colon == std::string::npos) ? q : q.substr(colon + 1);
794
+ if (local == localName) return attr;
795
+ }
796
+ return nullptr;
797
+ }
798
+
799
+ JSValue js_el_getAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
800
+ auto* el = unwrap_element(ctx, this_val);
801
+ if (!el || argc < 2) return JS_NULL;
802
+ const char* local = JS_ToCString(ctx, argv[1]);
803
+ if (!local) return JS_NULL;
804
+ auto* attr = find_attr_by_local_name(el, local);
805
+ JS_FreeCString(ctx, local);
806
+ if (!attr) return JS_NULL;
807
+ size_t len = 0;
808
+ const lxb_char_t* val = lxb_dom_attr_value(attr, &len);
809
+ return val ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len) : JS_NewString(ctx, "");
810
+ }
811
+
812
+ JSValue js_el_hasAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
813
+ auto* el = unwrap_element(ctx, this_val);
814
+ if (!el || argc < 2) return JS_FALSE;
815
+ const char* local = JS_ToCString(ctx, argv[1]);
816
+ if (!local) return JS_FALSE;
817
+ bool has = find_attr_by_local_name(el, local) != nullptr;
818
+ JS_FreeCString(ctx, local);
819
+ return JS_NewBool(ctx, has);
820
+ }
821
+
822
+ JSValue js_el_setAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
823
+ auto* el = unwrap_element(ctx, this_val);
824
+ if (!el || argc < 3) return JS_UNDEFINED;
825
+ const char* qualified_name = JS_ToCString(ctx, argv[1]);
826
+ const char* value = JS_ToCString(ctx, argv[2]);
827
+ if (qualified_name && value) {
828
+ std::string qn(qualified_name);
829
+ auto colon = qn.find(':');
830
+ std::string local = (colon == std::string::npos) ? qn : qn.substr(colon + 1);
831
+
832
+ auto* existing = find_attr_by_local_name(el, local);
833
+ if (existing) {
834
+ lxb_dom_attr_set_existing_value(existing,
835
+ reinterpret_cast<const lxb_char_t*>(value), strlen(value));
836
+ } else {
837
+ lxb_dom_element_set_attribute(el,
838
+ reinterpret_cast<const lxb_char_t*>(qualified_name), qn.size(),
839
+ reinterpret_cast<const lxb_char_t*>(value), strlen(value));
840
+ }
841
+ }
842
+ if (qualified_name) JS_FreeCString(ctx, qualified_name);
843
+ if (value) JS_FreeCString(ctx, value);
844
+ return JS_UNDEFINED;
845
+ }
846
+
847
+ JSValue js_el_removeAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
848
+ auto* el = unwrap_element(ctx, this_val);
849
+ if (!el || argc < 2) return JS_UNDEFINED;
850
+ const char* local = JS_ToCString(ctx, argv[1]);
851
+ if (local) {
852
+ auto* attr = find_attr_by_local_name(el, local);
853
+ if (attr) {
854
+ size_t qlen = 0;
855
+ const lxb_char_t* qname = lxb_dom_attr_qualified_name(attr, &qlen);
856
+ if (qname) lxb_dom_element_remove_attribute(el, qname, qlen);
857
+ }
858
+ JS_FreeCString(ctx, local);
859
+ }
860
+ return JS_UNDEFINED;
861
+ }
862
+
654
863
  // Inserts `node` via `insert_one`, expanding DocumentFragments into their
655
864
  // children first (per the DOM's "insert a node" algorithm — a fragment is
656
865
  // never itself part of the resulting tree, only its children are moved in).
@@ -821,12 +1030,15 @@ JSValue js_el_replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue*
821
1030
  return JS_DupValue(ctx, argv[1]); // returns the replaced (old) child, per spec
822
1031
  }
823
1032
 
824
- lxb_dom_node_t* js_to_node_or_text(JSContext* ctx, JSValue val) {
1033
+ // `context_node` supplies the owning document for a string argument coerced
1034
+ // into a new text node (see doc_for_node()) — irrelevant when `val` is
1035
+ // already a node.
1036
+ lxb_dom_node_t* js_to_node_or_text(JSContext* ctx, JSValue val, lxb_dom_node_t* context_node) {
825
1037
  auto* node = unwrap_node(ctx, val);
826
1038
  if (node) return node;
827
1039
  const char* str = JS_ToCString(ctx, val);
828
1040
  if (!str) return nullptr;
829
- void* text = get_doc(ctx)->createTextNode(str);
1041
+ void* text = doc_for_node(ctx, context_node)->createTextNode(str);
830
1042
  JS_FreeCString(ctx, str);
831
1043
  return static_cast<lxb_dom_node_t*>(text);
832
1044
  }
@@ -838,7 +1050,7 @@ JSValue js_el_before(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
838
1050
 
839
1051
  std::vector<void*> inserted;
840
1052
  for (int i = 0; i < argc; i++) {
841
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1053
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
842
1054
  if (!n) continue;
843
1055
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
844
1056
  inserted.insert(inserted.end(), batch.begin(), batch.end());
@@ -860,7 +1072,7 @@ JSValue js_el_after(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
860
1072
 
861
1073
  std::vector<void*> inserted;
862
1074
  for (int i = 0; i < argc; i++) {
863
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1075
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
864
1076
  if (!n) continue;
865
1077
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
866
1078
  if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent, one);
@@ -884,7 +1096,7 @@ JSValue js_el_replaceWith(JSContext* ctx, JSValue this_val, int argc, JSValue* a
884
1096
 
885
1097
  std::vector<void*> inserted;
886
1098
  for (int i = 0; i < argc; i++) {
887
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1099
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
888
1100
  if (!n) continue;
889
1101
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
890
1102
  inserted.insert(inserted.end(), batch.begin(), batch.end());
@@ -906,7 +1118,7 @@ JSValue js_el_append(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
906
1118
 
907
1119
  std::vector<void*> inserted;
908
1120
  for (int i = 0; i < argc; i++) {
909
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1121
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], parent_node);
910
1122
  if (!n) continue;
911
1123
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_child(parent_node, one); });
912
1124
  inserted.insert(inserted.end(), batch.begin(), batch.end());
@@ -927,7 +1139,7 @@ JSValue js_el_prepend(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
927
1139
 
928
1140
  std::vector<void*> inserted;
929
1141
  for (int i = 0; i < argc; i++) {
930
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1142
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], parent_node);
931
1143
  if (!n) continue;
932
1144
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
933
1145
  if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent_node, one);
@@ -948,11 +1160,12 @@ JSValue js_el_closest(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
948
1160
  const char* sel = JS_ToCString(ctx, argv[0]);
949
1161
  if (!sel) return JS_NULL;
950
1162
 
1163
+ auto* doc = doc_for_node(ctx, lxb_dom_interface_node(el));
951
1164
  JSValue result = JS_NULL;
952
1165
  for (lxb_dom_node_t* node = lxb_dom_interface_node(el);
953
1166
  node && node->type == LXB_DOM_NODE_TYPE_ELEMENT;
954
1167
  node = node->parent) {
955
- if (get_doc(ctx)->matchesSelector(lxb_dom_interface_element(node), sel)) {
1168
+ if (doc->matchesSelector(lxb_dom_interface_element(node), sel)) {
956
1169
  result = make_element(ctx, node);
957
1170
  break;
958
1171
  }
@@ -975,7 +1188,7 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
975
1188
  JS_FreeCString(ctx, position);
976
1189
 
977
1190
  lxb_dom_node_t* node = lxb_dom_interface_node(el);
978
- auto parsed = get_doc(ctx)->parseFragmentNodes(el, html);
1191
+ auto parsed = doc_for_node(ctx, node)->parseFragmentNodes(el, html);
979
1192
  JS_FreeCString(ctx, html);
980
1193
  if (parsed.empty()) return JS_UNDEFINED;
981
1194
 
@@ -1023,12 +1236,119 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
1023
1236
  return JS_UNDEFINED;
1024
1237
  }
1025
1238
 
1239
+ JSValue js_el_insertAdjacentElement(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1240
+ auto* el = unwrap_element(ctx, this_val);
1241
+ if (!el || argc < 2) return JS_NULL;
1242
+ const char* position = JS_ToCString(ctx, argv[0]);
1243
+ if (!position) return JS_NULL;
1244
+ std::string pos(position);
1245
+ JS_FreeCString(ctx, position);
1246
+
1247
+ auto* inserted_el = unwrap_element(ctx, argv[1]);
1248
+ if (!inserted_el) return JS_NULL;
1249
+ lxb_dom_node_t* inserted = lxb_dom_interface_node(inserted_el);
1250
+
1251
+ lxb_dom_node_t* node = lxb_dom_interface_node(el);
1252
+ lxb_dom_node_t* parent = nullptr;
1253
+ lxb_dom_node_t* prev_sib = nullptr;
1254
+ lxb_dom_node_t* next_sib = nullptr;
1255
+
1256
+ if (pos == "beforebegin") {
1257
+ parent = node->parent;
1258
+ if (!parent) return JS_NULL;
1259
+ prev_sib = node->prev;
1260
+ lxb_dom_node_insert_before(node, inserted);
1261
+ next_sib = node;
1262
+ } else if (pos == "afterbegin") {
1263
+ parent = node;
1264
+ lxb_dom_node_t* ref = node->first_child;
1265
+ if (ref) lxb_dom_node_insert_before(ref, inserted); else lxb_dom_node_insert_child(node, inserted);
1266
+ next_sib = ref;
1267
+ } else if (pos == "beforeend") {
1268
+ parent = node;
1269
+ prev_sib = node->last_child;
1270
+ lxb_dom_node_insert_child(node, inserted);
1271
+ } else if (pos == "afterend") {
1272
+ parent = node->parent;
1273
+ if (!parent) return JS_NULL;
1274
+ prev_sib = node;
1275
+ lxb_dom_node_t* ref = node->next;
1276
+ if (ref) lxb_dom_node_insert_before(ref, inserted); else lxb_dom_node_insert_child(parent, inserted);
1277
+ next_sib = ref;
1278
+ } else {
1279
+ return throw_dom_exception(ctx, "SyntaxError", ("invalid insertAdjacentElement position '" + pos + "'").c_str());
1280
+ }
1281
+
1282
+ auto* rctx = get_ctx(ctx);
1283
+ if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
1284
+ rctx->mutation_observers->notifyChildList(ctx, parent, {inserted}, {}, prev_sib, next_sib);
1285
+ }
1286
+ return JS_DupValue(ctx, argv[1]);
1287
+ }
1288
+
1289
+ JSValue js_el_insertAdjacentText(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1290
+ auto* el = unwrap_element(ctx, this_val);
1291
+ if (!el || argc < 2) return JS_UNDEFINED;
1292
+ const char* position = JS_ToCString(ctx, argv[0]);
1293
+ const char* text = JS_ToCString(ctx, argv[1]);
1294
+ if (!position || !text) {
1295
+ if (position) JS_FreeCString(ctx, position);
1296
+ if (text) JS_FreeCString(ctx, text);
1297
+ return JS_UNDEFINED;
1298
+ }
1299
+ std::string pos(position);
1300
+ JS_FreeCString(ctx, position);
1301
+
1302
+ lxb_dom_node_t* node = lxb_dom_interface_node(el);
1303
+ void* text_node_v = doc_for_node(ctx, node)->createTextNode(text);
1304
+ JS_FreeCString(ctx, text);
1305
+ if (!text_node_v) return JS_UNDEFINED;
1306
+ auto* text_node = static_cast<lxb_dom_node_t*>(text_node_v);
1307
+
1308
+ lxb_dom_node_t* parent = nullptr;
1309
+ lxb_dom_node_t* prev_sib = nullptr;
1310
+ lxb_dom_node_t* next_sib = nullptr;
1311
+
1312
+ if (pos == "beforebegin") {
1313
+ parent = node->parent;
1314
+ if (!parent) { lxb_dom_node_destroy_deep(text_node); return JS_UNDEFINED; }
1315
+ prev_sib = node->prev;
1316
+ lxb_dom_node_insert_before(node, text_node);
1317
+ next_sib = node;
1318
+ } else if (pos == "afterbegin") {
1319
+ parent = node;
1320
+ lxb_dom_node_t* ref = node->first_child;
1321
+ if (ref) lxb_dom_node_insert_before(ref, text_node); else lxb_dom_node_insert_child(node, text_node);
1322
+ next_sib = ref;
1323
+ } else if (pos == "beforeend") {
1324
+ parent = node;
1325
+ prev_sib = node->last_child;
1326
+ lxb_dom_node_insert_child(node, text_node);
1327
+ } else if (pos == "afterend") {
1328
+ parent = node->parent;
1329
+ if (!parent) { lxb_dom_node_destroy_deep(text_node); return JS_UNDEFINED; }
1330
+ prev_sib = node;
1331
+ lxb_dom_node_t* ref = node->next;
1332
+ if (ref) lxb_dom_node_insert_before(ref, text_node); else lxb_dom_node_insert_child(parent, text_node);
1333
+ next_sib = ref;
1334
+ } else {
1335
+ lxb_dom_node_destroy_deep(text_node);
1336
+ return throw_dom_exception(ctx, "SyntaxError", ("invalid insertAdjacentText position '" + pos + "'").c_str());
1337
+ }
1338
+
1339
+ auto* rctx = get_ctx(ctx);
1340
+ if (rctx && rctx->mutation_observers && !rctx->mutation_observers->empty()) {
1341
+ rctx->mutation_observers->notifyChildList(ctx, parent, {text_node}, {}, prev_sib, next_sib);
1342
+ }
1343
+ return JS_UNDEFINED;
1344
+ }
1345
+
1026
1346
  JSValue js_el_matches(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1027
1347
  auto* el = unwrap_element(ctx, this_val);
1028
1348
  if (!el || argc < 1) return JS_FALSE;
1029
1349
  const char* sel = JS_ToCString(ctx, argv[0]);
1030
1350
  if (!sel) return JS_FALSE;
1031
- bool result = get_doc(ctx)->matchesSelector(el, sel);
1351
+ bool result = doc_for_node(ctx, el)->matchesSelector(el, sel);
1032
1352
  JS_FreeCString(ctx, sel);
1033
1353
  return JS_NewBool(ctx, result);
1034
1354
  }
@@ -1038,7 +1358,7 @@ JSValue js_el_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue*
1038
1358
  if (!el || argc < 1) return JS_NULL;
1039
1359
  const char* sel = JS_ToCString(ctx, argv[0]);
1040
1360
  if (!sel) return JS_NULL;
1041
- void* found = get_doc(ctx)->querySelectorFromEl(el, sel);
1361
+ void* found = doc_for_node(ctx, el)->querySelectorFromEl(el, sel);
1042
1362
  JS_FreeCString(ctx, sel);
1043
1363
  return make_element(ctx, found);
1044
1364
  }
@@ -1048,7 +1368,7 @@ JSValue js_el_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSVal
1048
1368
  if (!el || argc < 1) return JS_NewArray(ctx);
1049
1369
  const char* sel = JS_ToCString(ctx, argv[0]);
1050
1370
  if (!sel) return JS_NewArray(ctx);
1051
- auto results = get_doc(ctx)->querySelectorAllFromEl(el, sel);
1371
+ auto results = doc_for_node(ctx, el)->querySelectorAllFromEl(el, sel);
1052
1372
  JS_FreeCString(ctx, sel);
1053
1373
  return make_element_array(ctx, results);
1054
1374
  }
@@ -1154,6 +1474,14 @@ JSValue js_el_getBoundingClientRect(JSContext* ctx, JSValue this_val, int, JSVal
1154
1474
  return rect;
1155
1475
  }
1156
1476
 
1477
+ JSValue js_el_getClientRects(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1478
+ auto* el = unwrap_element(ctx, this_val);
1479
+ if (!el) return JS_NewArray(ctx);
1480
+ JSValue arr = JS_NewArray(ctx);
1481
+ JS_SetPropertyUint32(ctx, arr, 0, js_el_getBoundingClientRect(ctx, this_val, argc, argv));
1482
+ return arr;
1483
+ }
1484
+
1157
1485
  JSValue js_el_isSameNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1158
1486
  auto* node = unwrap_node(ctx, this_val);
1159
1487
  if (!node || argc < 1) return JS_FALSE;
@@ -1229,7 +1557,7 @@ JSValue js_el_normalize(JSContext* ctx, JSValue this_val, int, JSValue*) {
1229
1557
  invalidate_node_cache_batch(ctx, get_ctx(ctx), text_nodes);
1230
1558
  }
1231
1559
 
1232
- get_doc(ctx)->normalize(node);
1560
+ doc_for_node(ctx, node)->normalize(node);
1233
1561
  return JS_UNDEFINED;
1234
1562
  }
1235
1563
 
@@ -1238,7 +1566,75 @@ JSValue js_el_compareDocumentPosition(JSContext* ctx, JSValue this_val, int argc
1238
1566
  if (!node || argc < 1) return JS_NewInt32(ctx, 0);
1239
1567
  auto* other = unwrap_node(ctx, argv[0]);
1240
1568
  if (!other) return JS_NewInt32(ctx, 0);
1241
- return JS_NewInt32(ctx, get_doc(ctx)->compareDocumentPosition(node, other));
1569
+ return JS_NewInt32(ctx, doc_for_node(ctx, node)->compareDocumentPosition(node, other));
1570
+ }
1571
+
1572
+ JSValue js_canonicalize_root_node(JSContext* ctx, JSValue, int argc, JSValue* argv) {
1573
+ if (argc < 1) return JS_UNDEFINED;
1574
+ auto* node = unwrap_node(ctx, argv[0]);
1575
+ if (node && node->type == LXB_DOM_NODE_TYPE_DOCUMENT && doc_for_node(ctx, node) == get_doc(ctx)) {
1576
+ JSValue global = JS_GetGlobalObject(ctx);
1577
+ JSValue doc_val = JS_GetPropertyStr(ctx, global, "document");
1578
+ JS_FreeValue(ctx, global);
1579
+ return doc_val;
1580
+ }
1581
+ return JS_DupValue(ctx, argv[0]);
1582
+ }
1583
+
1584
+ JSValue js_el_get_ownerDocument(JSContext* ctx, JSValue this_val) {
1585
+ auto* node = unwrap_node(ctx, this_val);
1586
+ if (!node) return JS_NULL;
1587
+ if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT) return JS_NULL;
1588
+
1589
+ LexborDocument* owner = doc_for_node(ctx, node);
1590
+ if (owner == get_doc(ctx)) {
1591
+ JSValue global = JS_GetGlobalObject(ctx);
1592
+ JSValue doc_val = JS_GetPropertyStr(ctx, global, "document");
1593
+ JS_FreeValue(ctx, global);
1594
+ return doc_val;
1595
+ }
1596
+
1597
+ JSValue wrapper = get_document_wrapper(ctx, owner);
1598
+ if (!JS_IsUndefined(wrapper)) return wrapper;
1599
+
1600
+ void* html_doc = owner->documentHtmlPtr();
1601
+ void* doc_node = lxb_dom_interface_node(
1602
+ lxb_dom_interface_document(static_cast<lxb_html_document_t*>(html_doc)));
1603
+ return make_element(ctx, doc_node);
1604
+ }
1605
+
1606
+ JSValue js_el_get_namespaceURI(JSContext* ctx, JSValue this_val) {
1607
+ auto* node = unwrap_node(ctx, this_val);
1608
+ if (!node) return JS_NULL;
1609
+ LexborDocument* owner = doc_for_node(ctx, node);
1610
+ std::string uri = owner ? owner->namespaceURI(node) : "";
1611
+ if (uri.empty()) return JS_NULL;
1612
+ return JS_NewStringLen(ctx, uri.data(), uri.size());
1613
+ }
1614
+
1615
+ // Element.prefix — the namespace prefix set via createElementNS('ns', 'prefix:local'),
1616
+ // or null for an element with no prefix (the overwhelmingly common case).
1617
+ JSValue js_el_get_prefix(JSContext* ctx, JSValue this_val) {
1618
+ auto* el = unwrap_element(ctx, this_val);
1619
+ if (!el) return JS_NULL;
1620
+ auto* node = reinterpret_cast<lxb_dom_node_t*>(el);
1621
+ if (node->prefix == 0) return JS_NULL;
1622
+ const lxb_ns_prefix_data_t* data = lxb_ns_prefix_data_by_id(node->owner_document->prefix, node->prefix);
1623
+ if (!data) return JS_NULL;
1624
+ const lxb_char_t* str = lexbor_hash_entry_str(&data->entry);
1625
+ if (!str) return JS_NULL;
1626
+ return JS_NewStringLen(ctx, reinterpret_cast<const char*>(str), data->entry.length);
1627
+ }
1628
+
1629
+ // Node.baseURI — always the document's URL in this sandbox (no <base> element
1630
+ // support/per-node override), so this just forwards to location.href.
1631
+ JSValue js_el_get_baseURI(JSContext* ctx, JSValue) {
1632
+ JSValue global = JS_GetGlobalObject(ctx);
1633
+ JSValue location = JS_GetPropertyStr(ctx, global, "location");
1634
+ JS_FreeValue(ctx, global);
1635
+ JSValue href = JS_GetPropertyStr(ctx, location, "href");
1636
+ JS_FreeValue(ctx, location);
1637
+ return href;
1242
1638
  }
1243
1639
 
1244
1640
  } // namespace
@@ -1273,7 +1669,12 @@ void ElementBindings::install(JSContext* ctx) {
1273
1669
  define_prop(ctx, node_proto, "nodeType", js_el_get_nodeType, nullptr);
1274
1670
  define_prop(ctx, node_proto, "nodeName", js_el_get_nodeName, nullptr);
1275
1671
  define_prop(ctx, node_proto, "nodeValue", js_el_get_nodeValue, js_el_set_nodeValue);
1672
+ define_prop(ctx, node_proto, "data", js_el_get_nodeValue, js_el_set_nodeValue);
1673
+ define_prop(ctx, node_proto, "length", js_el_get_data_length, nullptr);
1276
1674
  define_prop(ctx, node_proto, "textContent", js_el_get_textContent, js_el_set_textContent);
1675
+ define_prop(ctx, node_proto, "ownerDocument", js_el_get_ownerDocument, nullptr);
1676
+ define_prop(ctx, node_proto, "namespaceURI", js_el_get_namespaceURI, nullptr);
1677
+ define_prop(ctx, node_proto, "baseURI", js_el_get_baseURI, nullptr);
1277
1678
  define_prop(ctx, node_proto, "childNodes", js_el_get_childNodes, nullptr);
1278
1679
  define_prop(ctx, node_proto, "firstChild", js_el_get_firstChild, nullptr);
1279
1680
  define_prop(ctx, node_proto, "lastChild", js_el_get_lastChild, nullptr);
@@ -1282,6 +1683,8 @@ void ElementBindings::install(JSContext* ctx) {
1282
1683
  define_prop(ctx, node_proto, "parentNode", js_el_get_parentNode, nullptr);
1283
1684
  define_prop(ctx, node_proto, "parentElement", js_el_get_parentElement, nullptr);
1284
1685
 
1686
+ define_node_type_constants(ctx, node_proto);
1687
+
1285
1688
  JS_SetClassProto(ctx, js_node_class_id, JS_DupValue(ctx, node_proto));
1286
1689
 
1287
1690
  // ── Element proto: inherits Node, adds Element-specific APIs ─────────────
@@ -1292,27 +1695,47 @@ void ElementBindings::install(JSContext* ctx) {
1292
1695
  JS_SetPropertyStr(ctx, proto, "setAttribute", JS_NewCFunction(ctx, js_el_setAttribute, "setAttribute", 2));
1293
1696
  JS_SetPropertyStr(ctx, proto, "removeAttribute", JS_NewCFunction(ctx, js_el_removeAttribute, "removeAttribute", 1));
1294
1697
  JS_SetPropertyStr(ctx, proto, "hasAttribute", JS_NewCFunction(ctx, js_el_hasAttribute, "hasAttribute", 1));
1698
+ JS_SetPropertyStr(ctx, proto, "getAttributeNS", JS_NewCFunction(ctx, js_el_getAttributeNS, "getAttributeNS", 2));
1699
+ JS_SetPropertyStr(ctx, proto, "setAttributeNS", JS_NewCFunction(ctx, js_el_setAttributeNS, "setAttributeNS", 3));
1700
+ JS_SetPropertyStr(ctx, proto, "removeAttributeNS", JS_NewCFunction(ctx, js_el_removeAttributeNS, "removeAttributeNS", 2));
1701
+ JS_SetPropertyStr(ctx, proto, "hasAttributeNS", JS_NewCFunction(ctx, js_el_hasAttributeNS, "hasAttributeNS", 2));
1295
1702
  JS_SetPropertyStr(ctx, proto, "toggleAttribute", JS_NewCFunction(ctx, js_el_toggleAttribute, "toggleAttribute", 2));
1296
1703
  JS_SetPropertyStr(ctx, proto, "getAttributeNames", JS_NewCFunction(ctx, js_el_getAttributeNames, "getAttributeNames", 0));
1297
1704
  JS_SetPropertyStr(ctx, proto, "remove", JS_NewCFunction(ctx, js_el_remove, "remove", 0));
1298
1705
  JS_SetPropertyStr(ctx, proto, "matches", JS_NewCFunction(ctx, js_el_matches, "matches", 1));
1706
+ JS_SetPropertyStr(ctx, proto, "webkitMatchesSelector", JS_NewCFunction(ctx, js_el_matches, "webkitMatchesSelector", 1));
1299
1707
  JS_SetPropertyStr(ctx, proto, "querySelector", JS_NewCFunction(ctx, js_el_querySelector, "querySelector", 1));
1300
1708
  JS_SetPropertyStr(ctx, proto, "querySelectorAll", JS_NewCFunction(ctx, js_el_querySelectorAll, "querySelectorAll", 1));
1301
1709
  JS_SetPropertyStr(ctx, proto, "getElementsByClassName", JS_NewCFunction(ctx, js_el_getElementsByClassName, "getElementsByClassName", 1));
1302
1710
  JS_SetPropertyStr(ctx, proto, "getElementsByTagName", JS_NewCFunction(ctx, js_el_getElementsByTagName, "getElementsByTagName", 1));
1303
1711
  JS_SetPropertyStr(ctx, proto, "closest", JS_NewCFunction(ctx, js_el_closest, "closest", 1));
1304
1712
  JS_SetPropertyStr(ctx, proto, "insertAdjacentHTML", JS_NewCFunction(ctx, js_el_insertAdjacentHTML, "insertAdjacentHTML", 2));
1713
+ JS_SetPropertyStr(ctx, proto, "insertAdjacentElement", JS_NewCFunction(ctx, js_el_insertAdjacentElement, "insertAdjacentElement", 2));
1714
+ JS_SetPropertyStr(ctx, proto, "insertAdjacentText", JS_NewCFunction(ctx, js_el_insertAdjacentText, "insertAdjacentText", 2));
1305
1715
  JS_SetPropertyStr(ctx, proto, "append", JS_NewCFunction(ctx, js_el_append, "append", 0));
1306
1716
  JS_SetPropertyStr(ctx, proto, "prepend", JS_NewCFunction(ctx, js_el_prepend, "prepend", 0));
1307
1717
  JS_SetPropertyStr(ctx, proto, "getBoundingClientRect", JS_NewCFunction(ctx, js_el_getBoundingClientRect, "getBoundingClientRect", 0));
1718
+ JS_SetPropertyStr(ctx, proto, "getClientRects", JS_NewCFunction(ctx, js_el_getClientRects, "getClientRects", 0));
1308
1719
 
1309
1720
  define_prop(ctx, proto, "tagName", js_el_get_tagName, nullptr);
1721
+ define_prop(ctx, proto, "prefix", js_el_get_prefix, nullptr);
1310
1722
  define_prop(ctx, proto, "id", js_el_get_id, js_el_set_id);
1311
1723
  define_prop(ctx, proto, "className", js_el_get_className, js_el_set_className);
1312
1724
  define_prop(ctx, proto, "value", js_el_get_value, js_el_set_value);
1313
1725
  define_prop(ctx, proto, "checked", js_el_get_checked, js_el_set_checked);
1726
+ define_prop(ctx, proto, "disabled", js_el_get_disabled, js_el_set_disabled);
1727
+ define_prop(ctx, proto, "required", js_el_get_required, js_el_set_required);
1728
+ define_prop(ctx, proto, "readOnly", js_el_get_readOnly, js_el_set_readOnly);
1729
+ define_prop(ctx, proto, "multiple", js_el_get_multiple, js_el_set_multiple);
1730
+ define_prop(ctx, proto, "autofocus", js_el_get_autofocus, js_el_set_autofocus);
1731
+ define_prop(ctx, proto, "selected", js_el_get_selected, js_el_set_selected);
1732
+ define_prop(ctx, proto, "hidden", js_el_get_hidden, js_el_set_hidden);
1733
+ define_prop(ctx, proto, "title", js_el_get_title, js_el_set_title);
1734
+ define_prop(ctx, proto, "lang", js_el_get_lang, js_el_set_lang);
1735
+ define_prop(ctx, proto, "dir", js_el_get_dir, js_el_set_dir);
1314
1736
  define_prop(ctx, proto, "innerHTML", js_el_get_innerHTML, js_el_set_innerHTML);
1315
1737
  define_prop(ctx, proto, "outerHTML", js_el_get_outerHTML, nullptr);
1738
+ define_prop(ctx, proto, "innerText", js_el_get_textContent, js_el_set_textContent);
1316
1739
  define_prop(ctx, proto, "children", js_el_get_children, nullptr);
1317
1740
  define_prop(ctx, proto, "firstElementChild", js_el_get_firstElementChild, nullptr);
1318
1741
  define_prop(ctx, proto, "lastElementChild", js_el_get_lastElementChild, nullptr);
@@ -1329,14 +1752,87 @@ void ElementBindings::install(JSContext* ctx) {
1329
1752
  JSValue node_proto_ref = JS_GetClassProto(ctx, js_node_class_id);
1330
1753
  JSValue element_proto_ref = JS_GetClassProto(ctx, js_element_class_id);
1331
1754
 
1332
- JS_FreeValue(ctx, define_global_constructor(ctx, "Node", node_proto_ref));
1755
+ JSValue node_ctor = define_global_constructor(ctx, "Node", node_proto_ref);
1756
+ define_node_type_constants(ctx, node_ctor);
1757
+ JS_FreeValue(ctx, node_ctor);
1333
1758
  JSValue element_ctor = define_global_constructor(ctx, "Element", element_proto_ref);
1334
1759
  JSValue global = JS_GetGlobalObject(ctx);
1335
1760
  JS_SetPropertyStr(ctx, global, "HTMLElement", element_ctor);
1761
+ JS_SetPropertyStr(ctx, global, "__nativeCanonicalizeRootNode",
1762
+ JS_NewCFunction(ctx, js_canonicalize_root_node, "__nativeCanonicalizeRootNode", 1));
1336
1763
  JS_FreeValue(ctx, global);
1337
1764
 
1338
1765
  JS_FreeValue(ctx, node_proto_ref);
1339
1766
  JS_FreeValue(ctx, element_proto_ref);
1767
+
1768
+ static const char* kGetRootNodeScript = R"JS(
1769
+ (function() {
1770
+ Node.prototype.getRootNode = function(options) {
1771
+ var composed = !!(options && options.composed);
1772
+ var node = this;
1773
+ while (true) {
1774
+ var parent = node.parentNode;
1775
+ if (parent) { node = parent; continue; }
1776
+ if (composed && node instanceof ShadowRoot) { node = node.host; continue; }
1777
+ return __nativeCanonicalizeRootNode(node);
1778
+ }
1779
+ };
1780
+
1781
+ Object.defineProperty(Node.prototype, 'isConnected', {
1782
+ get: function() { return this.getRootNode() === document; },
1783
+ enumerable: true,
1784
+ configurable: true,
1785
+ });
1786
+
1787
+ function locateNamespace(node, prefix) {
1788
+ if (!node) return null;
1789
+ if (node.nodeType === 1) {
1790
+ if (node.namespaceURI !== null && node.prefix === prefix) return node.namespaceURI;
1791
+ var attrs = node.attributes;
1792
+ for (var i = 0; i < attrs.length; i++) {
1793
+ var a = attrs[i];
1794
+ if (prefix === null && a.name === 'xmlns') return a.value || null;
1795
+ if (prefix !== null && a.name === 'xmlns:' + prefix) return a.value || null;
1796
+ }
1797
+ return locateNamespace(node.parentElement, prefix);
1798
+ }
1799
+ if (node.nodeType === 9) {
1800
+ return node.documentElement ? locateNamespace(node.documentElement, prefix) : null;
1801
+ }
1802
+ if (node.nodeType === 10 || node.nodeType === 11) return null;
1803
+ return locateNamespace(node.parentElement, prefix);
1804
+ }
1805
+
1806
+ Node.prototype.lookupNamespaceURI = function(prefix) {
1807
+ if (prefix === undefined || prefix === '') prefix = null;
1808
+ return locateNamespace(this, prefix);
1809
+ };
1810
+
1811
+ Node.prototype.isDefaultNamespace = function(namespace) {
1812
+ if (namespace === undefined || namespace === '') namespace = null;
1813
+ return this.lookupNamespaceURI(null) === namespace;
1814
+ };
1815
+
1816
+ Node.prototype.lookupPrefix = function(namespace) {
1817
+ if (!namespace) return null;
1818
+ var node = this.nodeType === 9 ? this.documentElement : this;
1819
+ while (node && node.nodeType === 1) {
1820
+ if (node.namespaceURI === namespace && node.prefix !== null) return node.prefix;
1821
+ var attrs = node.attributes;
1822
+ for (var i = 0; i < attrs.length; i++) {
1823
+ var a = attrs[i];
1824
+ if (a.name.indexOf('xmlns:') === 0 && a.value === namespace) return a.name.slice(6);
1825
+ }
1826
+ node = node.parentElement;
1827
+ }
1828
+ return null;
1829
+ };
1830
+ })();
1831
+ )JS";
1832
+ JSValue root_node_result = JS_Eval(ctx, kGetRootNodeScript, strlen(kGetRootNodeScript),
1833
+ "<get-root-node-bootstrap>", JS_EVAL_TYPE_GLOBAL);
1834
+ if (JS_IsException(root_node_result)) JS_FreeValue(ctx, JS_GetException(ctx));
1835
+ JS_FreeValue(ctx, root_node_result);
1340
1836
  }
1341
1837
 
1342
1838
  } // namespace margelo::nitro::nitrojsdom