@salve-software/react-native-nitro-jsdom 1.1.0 → 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.
Files changed (35) hide show
  1. package/android/CMakeLists.txt +4 -0
  2. package/cpp/HybridHtmlSandbox.cpp +7 -3
  3. package/cpp/lexbor/LexborDocument.cpp +62 -0
  4. package/cpp/lexbor/LexborDocument.hpp +14 -0
  5. package/cpp/quickjs/DOMBindings.cpp +9 -1
  6. package/cpp/quickjs/DOMBindingsInternal.cpp +67 -0
  7. package/cpp/quickjs/DOMBindingsInternal.hpp +39 -0
  8. package/cpp/quickjs/QuickJSRuntime.cpp +13 -0
  9. package/cpp/quickjs/QuickJSRuntime.hpp +28 -0
  10. package/cpp/quickjs/bindings/AbortBindings.cpp +14 -0
  11. package/cpp/quickjs/bindings/BlobBindings.cpp +28 -1
  12. package/cpp/quickjs/bindings/BlobBindings.hpp +3 -3
  13. package/cpp/quickjs/bindings/CSSOMBindings.cpp +46 -0
  14. package/cpp/quickjs/bindings/CSSOMBindings.hpp +3 -1
  15. package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
  16. package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
  17. package/cpp/quickjs/bindings/DocumentBindings.cpp +166 -11
  18. package/cpp/quickjs/bindings/ElementBindings.cpp +296 -20
  19. package/cpp/quickjs/bindings/EventBindings.cpp +277 -5
  20. package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
  21. package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
  22. package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
  23. package/cpp/quickjs/bindings/FetchBindings.cpp +43 -8
  24. package/cpp/quickjs/bindings/FormBindings.cpp +223 -0
  25. package/cpp/quickjs/bindings/FormBindings.hpp +9 -5
  26. package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
  27. package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
  28. package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +45 -0
  29. package/cpp/quickjs/bindings/TimerBindings.cpp +79 -0
  30. package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
  31. package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
  32. package/cpp/quickjs/bindings/UrlBindings.cpp +9 -0
  33. package/cpp/quickjs/bindings/WindowBindings.cpp +286 -4
  34. package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
  35. package/package.json +17 -2
@@ -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;
@@ -340,7 +351,7 @@ JSValue js_el_set_textContent(JSContext* ctx, JSValue this_val, JSValue val) {
340
351
  if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
341
352
  }
342
353
  }
343
- get_doc(ctx)->setTextContentOnEl(el, str);
354
+ doc_for_node(ctx, node)->setTextContentOnEl(el, str);
344
355
  if (has_observers) {
345
356
  std::vector<void*> new_children;
346
357
  lxb_dom_node_t* child = node->first_child;
@@ -385,7 +396,7 @@ JSValue js_el_set_innerHTML(JSContext* ctx, JSValue this_val, JSValue val) {
385
396
  if (has_observers) rctx->mutation_observers->disconnectDetachedTargets(old_children);
386
397
  }
387
398
 
388
- get_doc(ctx)->setInnerHTMLOnEl(el, html);
399
+ doc_for_node(ctx, el)->setInnerHTMLOnEl(el, html);
389
400
  JS_FreeCString(ctx, html);
390
401
 
391
402
  if (has_observers) {
@@ -530,6 +541,16 @@ JSValue js_el_set_nodeValue(JSContext* ctx, JSValue this_val, JSValue val) {
530
541
  return JS_UNDEFINED;
531
542
  }
532
543
 
544
+ JSValue js_el_get_data_length(JSContext* ctx, JSValue this_val) {
545
+ auto* node = unwrap_node(ctx, this_val);
546
+ if (!node) return JS_UNDEFINED;
547
+ if (node->type != LXB_DOM_NODE_TYPE_TEXT && node->type != LXB_DOM_NODE_TYPE_COMMENT) return JS_UNDEFINED;
548
+ JSValue str_val = js_el_get_nodeValue(ctx, this_val);
549
+ JSValue len_val = JS_GetPropertyStr(ctx, str_val, "length");
550
+ JS_FreeValue(ctx, str_val);
551
+ return len_val;
552
+ }
553
+
533
554
  JSValue js_el_get_childNodes(JSContext* ctx, JSValue this_val) {
534
555
  auto* node = unwrap_node(ctx, this_val);
535
556
  if (!node) return JS_NewArray(ctx);
@@ -651,6 +672,93 @@ JSValue js_el_hasAttribute(JSContext* ctx, JSValue this_val, int argc, JSValue*
651
672
  return JS_NewBool(ctx, has);
652
673
  }
653
674
 
675
+ // ── Namespaced attribute methods (getAttributeNS/setAttributeNS/...) ─────────
676
+ // Simplification: this sandbox doesn't track a real (namespace, localName)
677
+ // identity per attribute the way createElementNS()/namespaceURI() do for
678
+ // elements — building that would mean hand-constructing lxb_dom_attr_t nodes
679
+ // with their own ns/prefix fields, a much larger and riskier lift than the
680
+ // element side. Instead, attributes are matched by local name only (the
681
+ // qualified name with any "prefix:" stripped), which is spec-accurate for
682
+ // the common real-world case (a single xlink:href-style attribute) but not
683
+ // for two attributes sharing a local name across different namespaces — a
684
+ // case that essentially never comes up in embedded-widget scripts.
685
+ lxb_dom_attr_t* find_attr_by_local_name(lxb_dom_element_t* el, const std::string& localName) {
686
+ for (lxb_dom_attr_t* attr = lxb_dom_element_first_attribute(el); attr; attr = lxb_dom_element_next_attribute(attr)) {
687
+ size_t len = 0;
688
+ const lxb_char_t* qname = lxb_dom_attr_qualified_name(attr, &len);
689
+ if (!qname) continue;
690
+ std::string q(reinterpret_cast<const char*>(qname), len);
691
+ auto colon = q.find(':');
692
+ std::string local = (colon == std::string::npos) ? q : q.substr(colon + 1);
693
+ if (local == localName) return attr;
694
+ }
695
+ return nullptr;
696
+ }
697
+
698
+ JSValue js_el_getAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
699
+ auto* el = unwrap_element(ctx, this_val);
700
+ if (!el || argc < 2) return JS_NULL;
701
+ const char* local = JS_ToCString(ctx, argv[1]);
702
+ if (!local) return JS_NULL;
703
+ auto* attr = find_attr_by_local_name(el, local);
704
+ JS_FreeCString(ctx, local);
705
+ if (!attr) return JS_NULL;
706
+ size_t len = 0;
707
+ const lxb_char_t* val = lxb_dom_attr_value(attr, &len);
708
+ return val ? JS_NewStringLen(ctx, reinterpret_cast<const char*>(val), len) : JS_NewString(ctx, "");
709
+ }
710
+
711
+ JSValue js_el_hasAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
712
+ auto* el = unwrap_element(ctx, this_val);
713
+ if (!el || argc < 2) return JS_FALSE;
714
+ const char* local = JS_ToCString(ctx, argv[1]);
715
+ if (!local) return JS_FALSE;
716
+ bool has = find_attr_by_local_name(el, local) != nullptr;
717
+ JS_FreeCString(ctx, local);
718
+ return JS_NewBool(ctx, has);
719
+ }
720
+
721
+ JSValue js_el_setAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
722
+ auto* el = unwrap_element(ctx, this_val);
723
+ if (!el || argc < 3) return JS_UNDEFINED;
724
+ const char* qualified_name = JS_ToCString(ctx, argv[1]);
725
+ const char* value = JS_ToCString(ctx, argv[2]);
726
+ if (qualified_name && value) {
727
+ std::string qn(qualified_name);
728
+ auto colon = qn.find(':');
729
+ std::string local = (colon == std::string::npos) ? qn : qn.substr(colon + 1);
730
+
731
+ auto* existing = find_attr_by_local_name(el, local);
732
+ if (existing) {
733
+ lxb_dom_attr_set_existing_value(existing,
734
+ reinterpret_cast<const lxb_char_t*>(value), strlen(value));
735
+ } else {
736
+ lxb_dom_element_set_attribute(el,
737
+ reinterpret_cast<const lxb_char_t*>(qualified_name), qn.size(),
738
+ reinterpret_cast<const lxb_char_t*>(value), strlen(value));
739
+ }
740
+ }
741
+ if (qualified_name) JS_FreeCString(ctx, qualified_name);
742
+ if (value) JS_FreeCString(ctx, value);
743
+ return JS_UNDEFINED;
744
+ }
745
+
746
+ JSValue js_el_removeAttributeNS(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
747
+ auto* el = unwrap_element(ctx, this_val);
748
+ if (!el || argc < 2) return JS_UNDEFINED;
749
+ const char* local = JS_ToCString(ctx, argv[1]);
750
+ if (local) {
751
+ auto* attr = find_attr_by_local_name(el, local);
752
+ if (attr) {
753
+ size_t qlen = 0;
754
+ const lxb_char_t* qname = lxb_dom_attr_qualified_name(attr, &qlen);
755
+ if (qname) lxb_dom_element_remove_attribute(el, qname, qlen);
756
+ }
757
+ JS_FreeCString(ctx, local);
758
+ }
759
+ return JS_UNDEFINED;
760
+ }
761
+
654
762
  // Inserts `node` via `insert_one`, expanding DocumentFragments into their
655
763
  // children first (per the DOM's "insert a node" algorithm — a fragment is
656
764
  // never itself part of the resulting tree, only its children are moved in).
@@ -821,12 +929,15 @@ JSValue js_el_replaceChild(JSContext* ctx, JSValue this_val, int argc, JSValue*
821
929
  return JS_DupValue(ctx, argv[1]); // returns the replaced (old) child, per spec
822
930
  }
823
931
 
824
- lxb_dom_node_t* js_to_node_or_text(JSContext* ctx, JSValue val) {
932
+ // `context_node` supplies the owning document for a string argument coerced
933
+ // into a new text node (see doc_for_node()) — irrelevant when `val` is
934
+ // already a node.
935
+ lxb_dom_node_t* js_to_node_or_text(JSContext* ctx, JSValue val, lxb_dom_node_t* context_node) {
825
936
  auto* node = unwrap_node(ctx, val);
826
937
  if (node) return node;
827
938
  const char* str = JS_ToCString(ctx, val);
828
939
  if (!str) return nullptr;
829
- void* text = get_doc(ctx)->createTextNode(str);
940
+ void* text = doc_for_node(ctx, context_node)->createTextNode(str);
830
941
  JS_FreeCString(ctx, str);
831
942
  return static_cast<lxb_dom_node_t*>(text);
832
943
  }
@@ -838,7 +949,7 @@ JSValue js_el_before(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
838
949
 
839
950
  std::vector<void*> inserted;
840
951
  for (int i = 0; i < argc; i++) {
841
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
952
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
842
953
  if (!n) continue;
843
954
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
844
955
  inserted.insert(inserted.end(), batch.begin(), batch.end());
@@ -860,7 +971,7 @@ JSValue js_el_after(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
860
971
 
861
972
  std::vector<void*> inserted;
862
973
  for (int i = 0; i < argc; i++) {
863
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
974
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
864
975
  if (!n) continue;
865
976
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
866
977
  if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent, one);
@@ -884,7 +995,7 @@ JSValue js_el_replaceWith(JSContext* ctx, JSValue this_val, int argc, JSValue* a
884
995
 
885
996
  std::vector<void*> inserted;
886
997
  for (int i = 0; i < argc; i++) {
887
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
998
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], node);
888
999
  if (!n) continue;
889
1000
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_before(node, one); });
890
1001
  inserted.insert(inserted.end(), batch.begin(), batch.end());
@@ -906,7 +1017,7 @@ JSValue js_el_append(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
906
1017
 
907
1018
  std::vector<void*> inserted;
908
1019
  for (int i = 0; i < argc; i++) {
909
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1020
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], parent_node);
910
1021
  if (!n) continue;
911
1022
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) { lxb_dom_node_insert_child(parent_node, one); });
912
1023
  inserted.insert(inserted.end(), batch.begin(), batch.end());
@@ -927,7 +1038,7 @@ JSValue js_el_prepend(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
927
1038
 
928
1039
  std::vector<void*> inserted;
929
1040
  for (int i = 0; i < argc; i++) {
930
- lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i]);
1041
+ lxb_dom_node_t* n = js_to_node_or_text(ctx, argv[i], parent_node);
931
1042
  if (!n) continue;
932
1043
  auto batch = expand_and_insert(n, [&](lxb_dom_node_t* one) {
933
1044
  if (ref) lxb_dom_node_insert_before(ref, one); else lxb_dom_node_insert_child(parent_node, one);
@@ -948,11 +1059,12 @@ JSValue js_el_closest(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
948
1059
  const char* sel = JS_ToCString(ctx, argv[0]);
949
1060
  if (!sel) return JS_NULL;
950
1061
 
1062
+ auto* doc = doc_for_node(ctx, lxb_dom_interface_node(el));
951
1063
  JSValue result = JS_NULL;
952
1064
  for (lxb_dom_node_t* node = lxb_dom_interface_node(el);
953
1065
  node && node->type == LXB_DOM_NODE_TYPE_ELEMENT;
954
1066
  node = node->parent) {
955
- if (get_doc(ctx)->matchesSelector(lxb_dom_interface_element(node), sel)) {
1067
+ if (doc->matchesSelector(lxb_dom_interface_element(node), sel)) {
956
1068
  result = make_element(ctx, node);
957
1069
  break;
958
1070
  }
@@ -975,7 +1087,7 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
975
1087
  JS_FreeCString(ctx, position);
976
1088
 
977
1089
  lxb_dom_node_t* node = lxb_dom_interface_node(el);
978
- auto parsed = get_doc(ctx)->parseFragmentNodes(el, html);
1090
+ auto parsed = doc_for_node(ctx, node)->parseFragmentNodes(el, html);
979
1091
  JS_FreeCString(ctx, html);
980
1092
  if (parsed.empty()) return JS_UNDEFINED;
981
1093
 
@@ -1028,7 +1140,7 @@ JSValue js_el_matches(JSContext* ctx, JSValue this_val, int argc, JSValue* argv)
1028
1140
  if (!el || argc < 1) return JS_FALSE;
1029
1141
  const char* sel = JS_ToCString(ctx, argv[0]);
1030
1142
  if (!sel) return JS_FALSE;
1031
- bool result = get_doc(ctx)->matchesSelector(el, sel);
1143
+ bool result = doc_for_node(ctx, el)->matchesSelector(el, sel);
1032
1144
  JS_FreeCString(ctx, sel);
1033
1145
  return JS_NewBool(ctx, result);
1034
1146
  }
@@ -1038,7 +1150,7 @@ JSValue js_el_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue*
1038
1150
  if (!el || argc < 1) return JS_NULL;
1039
1151
  const char* sel = JS_ToCString(ctx, argv[0]);
1040
1152
  if (!sel) return JS_NULL;
1041
- void* found = get_doc(ctx)->querySelectorFromEl(el, sel);
1153
+ void* found = doc_for_node(ctx, el)->querySelectorFromEl(el, sel);
1042
1154
  JS_FreeCString(ctx, sel);
1043
1155
  return make_element(ctx, found);
1044
1156
  }
@@ -1048,7 +1160,7 @@ JSValue js_el_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSVal
1048
1160
  if (!el || argc < 1) return JS_NewArray(ctx);
1049
1161
  const char* sel = JS_ToCString(ctx, argv[0]);
1050
1162
  if (!sel) return JS_NewArray(ctx);
1051
- auto results = get_doc(ctx)->querySelectorAllFromEl(el, sel);
1163
+ auto results = doc_for_node(ctx, el)->querySelectorAllFromEl(el, sel);
1052
1164
  JS_FreeCString(ctx, sel);
1053
1165
  return make_element_array(ctx, results);
1054
1166
  }
@@ -1154,6 +1266,14 @@ JSValue js_el_getBoundingClientRect(JSContext* ctx, JSValue this_val, int, JSVal
1154
1266
  return rect;
1155
1267
  }
1156
1268
 
1269
+ JSValue js_el_getClientRects(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1270
+ auto* el = unwrap_element(ctx, this_val);
1271
+ if (!el) return JS_NewArray(ctx);
1272
+ JSValue arr = JS_NewArray(ctx);
1273
+ JS_SetPropertyUint32(ctx, arr, 0, js_el_getBoundingClientRect(ctx, this_val, argc, argv));
1274
+ return arr;
1275
+ }
1276
+
1157
1277
  JSValue js_el_isSameNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
1158
1278
  auto* node = unwrap_node(ctx, this_val);
1159
1279
  if (!node || argc < 1) return JS_FALSE;
@@ -1229,7 +1349,7 @@ JSValue js_el_normalize(JSContext* ctx, JSValue this_val, int, JSValue*) {
1229
1349
  invalidate_node_cache_batch(ctx, get_ctx(ctx), text_nodes);
1230
1350
  }
1231
1351
 
1232
- get_doc(ctx)->normalize(node);
1352
+ doc_for_node(ctx, node)->normalize(node);
1233
1353
  return JS_UNDEFINED;
1234
1354
  }
1235
1355
 
@@ -1238,7 +1358,75 @@ JSValue js_el_compareDocumentPosition(JSContext* ctx, JSValue this_val, int argc
1238
1358
  if (!node || argc < 1) return JS_NewInt32(ctx, 0);
1239
1359
  auto* other = unwrap_node(ctx, argv[0]);
1240
1360
  if (!other) return JS_NewInt32(ctx, 0);
1241
- return JS_NewInt32(ctx, get_doc(ctx)->compareDocumentPosition(node, other));
1361
+ return JS_NewInt32(ctx, doc_for_node(ctx, node)->compareDocumentPosition(node, other));
1362
+ }
1363
+
1364
+ JSValue js_canonicalize_root_node(JSContext* ctx, JSValue, int argc, JSValue* argv) {
1365
+ if (argc < 1) return JS_UNDEFINED;
1366
+ auto* node = unwrap_node(ctx, argv[0]);
1367
+ if (node && node->type == LXB_DOM_NODE_TYPE_DOCUMENT && doc_for_node(ctx, node) == get_doc(ctx)) {
1368
+ JSValue global = JS_GetGlobalObject(ctx);
1369
+ JSValue doc_val = JS_GetPropertyStr(ctx, global, "document");
1370
+ JS_FreeValue(ctx, global);
1371
+ return doc_val;
1372
+ }
1373
+ return JS_DupValue(ctx, argv[0]);
1374
+ }
1375
+
1376
+ JSValue js_el_get_ownerDocument(JSContext* ctx, JSValue this_val) {
1377
+ auto* node = unwrap_node(ctx, this_val);
1378
+ if (!node) return JS_NULL;
1379
+ if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT) return JS_NULL;
1380
+
1381
+ LexborDocument* owner = doc_for_node(ctx, node);
1382
+ if (owner == get_doc(ctx)) {
1383
+ JSValue global = JS_GetGlobalObject(ctx);
1384
+ JSValue doc_val = JS_GetPropertyStr(ctx, global, "document");
1385
+ JS_FreeValue(ctx, global);
1386
+ return doc_val;
1387
+ }
1388
+
1389
+ JSValue wrapper = get_document_wrapper(ctx, owner);
1390
+ if (!JS_IsUndefined(wrapper)) return wrapper;
1391
+
1392
+ void* html_doc = owner->documentHtmlPtr();
1393
+ void* doc_node = lxb_dom_interface_node(
1394
+ lxb_dom_interface_document(static_cast<lxb_html_document_t*>(html_doc)));
1395
+ return make_element(ctx, doc_node);
1396
+ }
1397
+
1398
+ JSValue js_el_get_namespaceURI(JSContext* ctx, JSValue this_val) {
1399
+ auto* node = unwrap_node(ctx, this_val);
1400
+ if (!node) return JS_NULL;
1401
+ LexborDocument* owner = doc_for_node(ctx, node);
1402
+ std::string uri = owner ? owner->namespaceURI(node) : "";
1403
+ if (uri.empty()) return JS_NULL;
1404
+ return JS_NewStringLen(ctx, uri.data(), uri.size());
1405
+ }
1406
+
1407
+ // Element.prefix — the namespace prefix set via createElementNS('ns', 'prefix:local'),
1408
+ // or null for an element with no prefix (the overwhelmingly common case).
1409
+ JSValue js_el_get_prefix(JSContext* ctx, JSValue this_val) {
1410
+ auto* el = unwrap_element(ctx, this_val);
1411
+ if (!el) return JS_NULL;
1412
+ auto* node = reinterpret_cast<lxb_dom_node_t*>(el);
1413
+ if (node->prefix == 0) return JS_NULL;
1414
+ const lxb_ns_prefix_data_t* data = lxb_ns_prefix_data_by_id(node->owner_document->prefix, node->prefix);
1415
+ if (!data) return JS_NULL;
1416
+ const lxb_char_t* str = lexbor_hash_entry_str(&data->entry);
1417
+ if (!str) return JS_NULL;
1418
+ return JS_NewStringLen(ctx, reinterpret_cast<const char*>(str), data->entry.length);
1419
+ }
1420
+
1421
+ // Node.baseURI — always the document's URL in this sandbox (no <base> element
1422
+ // support/per-node override), so this just forwards to location.href.
1423
+ JSValue js_el_get_baseURI(JSContext* ctx, JSValue) {
1424
+ JSValue global = JS_GetGlobalObject(ctx);
1425
+ JSValue location = JS_GetPropertyStr(ctx, global, "location");
1426
+ JS_FreeValue(ctx, global);
1427
+ JSValue href = JS_GetPropertyStr(ctx, location, "href");
1428
+ JS_FreeValue(ctx, location);
1429
+ return href;
1242
1430
  }
1243
1431
 
1244
1432
  } // namespace
@@ -1273,7 +1461,12 @@ void ElementBindings::install(JSContext* ctx) {
1273
1461
  define_prop(ctx, node_proto, "nodeType", js_el_get_nodeType, nullptr);
1274
1462
  define_prop(ctx, node_proto, "nodeName", js_el_get_nodeName, nullptr);
1275
1463
  define_prop(ctx, node_proto, "nodeValue", js_el_get_nodeValue, js_el_set_nodeValue);
1464
+ define_prop(ctx, node_proto, "data", js_el_get_nodeValue, js_el_set_nodeValue);
1465
+ define_prop(ctx, node_proto, "length", js_el_get_data_length, nullptr);
1276
1466
  define_prop(ctx, node_proto, "textContent", js_el_get_textContent, js_el_set_textContent);
1467
+ define_prop(ctx, node_proto, "ownerDocument", js_el_get_ownerDocument, nullptr);
1468
+ define_prop(ctx, node_proto, "namespaceURI", js_el_get_namespaceURI, nullptr);
1469
+ define_prop(ctx, node_proto, "baseURI", js_el_get_baseURI, nullptr);
1277
1470
  define_prop(ctx, node_proto, "childNodes", js_el_get_childNodes, nullptr);
1278
1471
  define_prop(ctx, node_proto, "firstChild", js_el_get_firstChild, nullptr);
1279
1472
  define_prop(ctx, node_proto, "lastChild", js_el_get_lastChild, nullptr);
@@ -1282,6 +1475,8 @@ void ElementBindings::install(JSContext* ctx) {
1282
1475
  define_prop(ctx, node_proto, "parentNode", js_el_get_parentNode, nullptr);
1283
1476
  define_prop(ctx, node_proto, "parentElement", js_el_get_parentElement, nullptr);
1284
1477
 
1478
+ define_node_type_constants(ctx, node_proto);
1479
+
1285
1480
  JS_SetClassProto(ctx, js_node_class_id, JS_DupValue(ctx, node_proto));
1286
1481
 
1287
1482
  // ── Element proto: inherits Node, adds Element-specific APIs ─────────────
@@ -1292,10 +1487,15 @@ void ElementBindings::install(JSContext* ctx) {
1292
1487
  JS_SetPropertyStr(ctx, proto, "setAttribute", JS_NewCFunction(ctx, js_el_setAttribute, "setAttribute", 2));
1293
1488
  JS_SetPropertyStr(ctx, proto, "removeAttribute", JS_NewCFunction(ctx, js_el_removeAttribute, "removeAttribute", 1));
1294
1489
  JS_SetPropertyStr(ctx, proto, "hasAttribute", JS_NewCFunction(ctx, js_el_hasAttribute, "hasAttribute", 1));
1490
+ JS_SetPropertyStr(ctx, proto, "getAttributeNS", JS_NewCFunction(ctx, js_el_getAttributeNS, "getAttributeNS", 2));
1491
+ JS_SetPropertyStr(ctx, proto, "setAttributeNS", JS_NewCFunction(ctx, js_el_setAttributeNS, "setAttributeNS", 3));
1492
+ JS_SetPropertyStr(ctx, proto, "removeAttributeNS", JS_NewCFunction(ctx, js_el_removeAttributeNS, "removeAttributeNS", 2));
1493
+ JS_SetPropertyStr(ctx, proto, "hasAttributeNS", JS_NewCFunction(ctx, js_el_hasAttributeNS, "hasAttributeNS", 2));
1295
1494
  JS_SetPropertyStr(ctx, proto, "toggleAttribute", JS_NewCFunction(ctx, js_el_toggleAttribute, "toggleAttribute", 2));
1296
1495
  JS_SetPropertyStr(ctx, proto, "getAttributeNames", JS_NewCFunction(ctx, js_el_getAttributeNames, "getAttributeNames", 0));
1297
1496
  JS_SetPropertyStr(ctx, proto, "remove", JS_NewCFunction(ctx, js_el_remove, "remove", 0));
1298
1497
  JS_SetPropertyStr(ctx, proto, "matches", JS_NewCFunction(ctx, js_el_matches, "matches", 1));
1498
+ JS_SetPropertyStr(ctx, proto, "webkitMatchesSelector", JS_NewCFunction(ctx, js_el_matches, "webkitMatchesSelector", 1));
1299
1499
  JS_SetPropertyStr(ctx, proto, "querySelector", JS_NewCFunction(ctx, js_el_querySelector, "querySelector", 1));
1300
1500
  JS_SetPropertyStr(ctx, proto, "querySelectorAll", JS_NewCFunction(ctx, js_el_querySelectorAll, "querySelectorAll", 1));
1301
1501
  JS_SetPropertyStr(ctx, proto, "getElementsByClassName", JS_NewCFunction(ctx, js_el_getElementsByClassName, "getElementsByClassName", 1));
@@ -1305,14 +1505,17 @@ void ElementBindings::install(JSContext* ctx) {
1305
1505
  JS_SetPropertyStr(ctx, proto, "append", JS_NewCFunction(ctx, js_el_append, "append", 0));
1306
1506
  JS_SetPropertyStr(ctx, proto, "prepend", JS_NewCFunction(ctx, js_el_prepend, "prepend", 0));
1307
1507
  JS_SetPropertyStr(ctx, proto, "getBoundingClientRect", JS_NewCFunction(ctx, js_el_getBoundingClientRect, "getBoundingClientRect", 0));
1508
+ JS_SetPropertyStr(ctx, proto, "getClientRects", JS_NewCFunction(ctx, js_el_getClientRects, "getClientRects", 0));
1308
1509
 
1309
1510
  define_prop(ctx, proto, "tagName", js_el_get_tagName, nullptr);
1511
+ define_prop(ctx, proto, "prefix", js_el_get_prefix, nullptr);
1310
1512
  define_prop(ctx, proto, "id", js_el_get_id, js_el_set_id);
1311
1513
  define_prop(ctx, proto, "className", js_el_get_className, js_el_set_className);
1312
1514
  define_prop(ctx, proto, "value", js_el_get_value, js_el_set_value);
1313
1515
  define_prop(ctx, proto, "checked", js_el_get_checked, js_el_set_checked);
1314
1516
  define_prop(ctx, proto, "innerHTML", js_el_get_innerHTML, js_el_set_innerHTML);
1315
1517
  define_prop(ctx, proto, "outerHTML", js_el_get_outerHTML, nullptr);
1518
+ define_prop(ctx, proto, "innerText", js_el_get_textContent, js_el_set_textContent);
1316
1519
  define_prop(ctx, proto, "children", js_el_get_children, nullptr);
1317
1520
  define_prop(ctx, proto, "firstElementChild", js_el_get_firstElementChild, nullptr);
1318
1521
  define_prop(ctx, proto, "lastElementChild", js_el_get_lastElementChild, nullptr);
@@ -1329,14 +1532,87 @@ void ElementBindings::install(JSContext* ctx) {
1329
1532
  JSValue node_proto_ref = JS_GetClassProto(ctx, js_node_class_id);
1330
1533
  JSValue element_proto_ref = JS_GetClassProto(ctx, js_element_class_id);
1331
1534
 
1332
- JS_FreeValue(ctx, define_global_constructor(ctx, "Node", node_proto_ref));
1535
+ JSValue node_ctor = define_global_constructor(ctx, "Node", node_proto_ref);
1536
+ define_node_type_constants(ctx, node_ctor);
1537
+ JS_FreeValue(ctx, node_ctor);
1333
1538
  JSValue element_ctor = define_global_constructor(ctx, "Element", element_proto_ref);
1334
1539
  JSValue global = JS_GetGlobalObject(ctx);
1335
1540
  JS_SetPropertyStr(ctx, global, "HTMLElement", element_ctor);
1541
+ JS_SetPropertyStr(ctx, global, "__nativeCanonicalizeRootNode",
1542
+ JS_NewCFunction(ctx, js_canonicalize_root_node, "__nativeCanonicalizeRootNode", 1));
1336
1543
  JS_FreeValue(ctx, global);
1337
1544
 
1338
1545
  JS_FreeValue(ctx, node_proto_ref);
1339
1546
  JS_FreeValue(ctx, element_proto_ref);
1547
+
1548
+ static const char* kGetRootNodeScript = R"JS(
1549
+ (function() {
1550
+ Node.prototype.getRootNode = function(options) {
1551
+ var composed = !!(options && options.composed);
1552
+ var node = this;
1553
+ while (true) {
1554
+ var parent = node.parentNode;
1555
+ if (parent) { node = parent; continue; }
1556
+ if (composed && node.host) { node = node.host; continue; }
1557
+ return __nativeCanonicalizeRootNode(node);
1558
+ }
1559
+ };
1560
+
1561
+ Object.defineProperty(Node.prototype, 'isConnected', {
1562
+ get: function() { return this.getRootNode() === document; },
1563
+ enumerable: true,
1564
+ configurable: true,
1565
+ });
1566
+
1567
+ function locateNamespace(node, prefix) {
1568
+ if (!node) return null;
1569
+ if (node.nodeType === 1) {
1570
+ if (node.namespaceURI !== null && node.prefix === prefix) return node.namespaceURI;
1571
+ var attrs = node.attributes;
1572
+ for (var i = 0; i < attrs.length; i++) {
1573
+ var a = attrs[i];
1574
+ if (prefix === null && a.name === 'xmlns') return a.value || null;
1575
+ if (prefix !== null && a.name === 'xmlns:' + prefix) return a.value || null;
1576
+ }
1577
+ return locateNamespace(node.parentElement, prefix);
1578
+ }
1579
+ if (node.nodeType === 9) {
1580
+ return node.documentElement ? locateNamespace(node.documentElement, prefix) : null;
1581
+ }
1582
+ if (node.nodeType === 10 || node.nodeType === 11) return null;
1583
+ return locateNamespace(node.parentElement, prefix);
1584
+ }
1585
+
1586
+ Node.prototype.lookupNamespaceURI = function(prefix) {
1587
+ if (prefix === undefined || prefix === '') prefix = null;
1588
+ return locateNamespace(this, prefix);
1589
+ };
1590
+
1591
+ Node.prototype.isDefaultNamespace = function(namespace) {
1592
+ if (namespace === undefined || namespace === '') namespace = null;
1593
+ return this.lookupNamespaceURI(null) === namespace;
1594
+ };
1595
+
1596
+ Node.prototype.lookupPrefix = function(namespace) {
1597
+ if (!namespace) return null;
1598
+ var node = this.nodeType === 9 ? this.documentElement : this;
1599
+ while (node && node.nodeType === 1) {
1600
+ if (node.namespaceURI === namespace && node.prefix !== null) return node.prefix;
1601
+ var attrs = node.attributes;
1602
+ for (var i = 0; i < attrs.length; i++) {
1603
+ var a = attrs[i];
1604
+ if (a.name.indexOf('xmlns:') === 0 && a.value === namespace) return a.name.slice(6);
1605
+ }
1606
+ node = node.parentElement;
1607
+ }
1608
+ return null;
1609
+ };
1610
+ })();
1611
+ )JS";
1612
+ JSValue root_node_result = JS_Eval(ctx, kGetRootNodeScript, strlen(kGetRootNodeScript),
1613
+ "<get-root-node-bootstrap>", JS_EVAL_TYPE_GLOBAL);
1614
+ if (JS_IsException(root_node_result)) JS_FreeValue(ctx, JS_GetException(ctx));
1615
+ JS_FreeValue(ctx, root_node_result);
1340
1616
  }
1341
1617
 
1342
1618
  } // namespace margelo::nitro::nitrojsdom