@salve-software/react-native-nitro-jsdom 1.0.1 → 1.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 (56) hide show
  1. package/android/CMakeLists.txt +14 -0
  2. package/cpp/HybridHtmlSandbox.cpp +16 -3
  3. package/cpp/HybridHtmlSandbox.hpp +1 -1
  4. package/cpp/lexbor/LexborDocument.cpp +217 -67
  5. package/cpp/lexbor/LexborDocument.hpp +29 -10
  6. package/cpp/quickjs/DOMBindings.cpp +28 -0
  7. package/cpp/quickjs/DOMBindingsInternal.cpp +34 -2
  8. package/cpp/quickjs/DOMBindingsInternal.hpp +17 -1
  9. package/cpp/quickjs/QuickJSRuntime.cpp +19 -4
  10. package/cpp/quickjs/QuickJSRuntime.hpp +22 -1
  11. package/cpp/quickjs/bindings/AbortBindings.cpp +114 -0
  12. package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
  13. package/cpp/quickjs/bindings/BlobBindings.cpp +137 -0
  14. package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
  15. package/cpp/quickjs/bindings/CSSOMBindings.cpp +244 -0
  16. package/cpp/quickjs/bindings/CSSOMBindings.hpp +23 -0
  17. package/cpp/quickjs/bindings/ClassListBindings.cpp +3 -2
  18. package/cpp/quickjs/bindings/CookieBindings.cpp +118 -0
  19. package/cpp/quickjs/bindings/CookieBindings.hpp +23 -0
  20. package/cpp/quickjs/bindings/CustomElementsBindings.cpp +341 -0
  21. package/cpp/quickjs/bindings/CustomElementsBindings.hpp +50 -0
  22. package/cpp/quickjs/bindings/DOMExceptionBindings.cpp +111 -0
  23. package/cpp/quickjs/bindings/DOMExceptionBindings.hpp +20 -0
  24. package/cpp/quickjs/bindings/DocumentBindings.cpp +84 -4
  25. package/cpp/quickjs/bindings/ElementBindings.cpp +264 -28
  26. package/cpp/quickjs/bindings/EventBindings.cpp +137 -3
  27. package/cpp/quickjs/bindings/FetchBindings.cpp +5 -1
  28. package/cpp/quickjs/bindings/FormBindings.cpp +142 -0
  29. package/cpp/quickjs/bindings/FormBindings.hpp +16 -0
  30. package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +183 -0
  31. package/cpp/quickjs/bindings/LiveCollectionBindings.hpp +16 -0
  32. package/cpp/quickjs/bindings/ShadowRootBindings.cpp +171 -0
  33. package/cpp/quickjs/bindings/ShadowRootBindings.hpp +27 -0
  34. package/cpp/quickjs/bindings/SlotBindings.cpp +161 -0
  35. package/cpp/quickjs/bindings/SlotBindings.hpp +44 -0
  36. package/cpp/quickjs/bindings/TemplateBindings.cpp +42 -0
  37. package/cpp/quickjs/bindings/TemplateBindings.hpp +29 -0
  38. package/cpp/quickjs/bindings/TextEncodingBindings.cpp +117 -0
  39. package/cpp/quickjs/bindings/TextEncodingBindings.hpp +14 -0
  40. package/cpp/quickjs/bindings/TimerBindings.cpp +41 -0
  41. package/cpp/quickjs/bindings/UrlBindings.cpp +286 -0
  42. package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
  43. package/cpp/quickjs/bindings/WindowBindings.cpp +270 -0
  44. package/cpp/quickjs/bindings/XmlSerializerBindings.cpp +44 -0
  45. package/cpp/quickjs/bindings/XmlSerializerBindings.hpp +24 -0
  46. package/lib/commonjs/classes/JSDOM/JSDOM.class.js +1 -1
  47. package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
  48. package/lib/module/classes/JSDOM/JSDOM.class.js +1 -1
  49. package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
  50. package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
  51. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
  52. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
  53. package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
  54. package/package.json +1 -1
  55. package/src/classes/JSDOM/JSDOM.class.ts +1 -0
  56. package/src/specs/HtmlSandbox.nitro.ts +1 -1
@@ -71,6 +71,89 @@ JSValue js_CustomEvent_constructor(JSContext* ctx, JSValue, int argc, JSValue* a
71
71
  return obj;
72
72
  }
73
73
 
74
+ // Reads a numeric/string init-dict field, falling back to `def` if the field
75
+ // is absent or the dict itself wasn't passed — shared by KeyboardEvent and
76
+ // MouseEvent, whose constructors otherwise differ only in which fields they copy.
77
+ double get_num_init_field(JSContext* ctx, JSValue init, const char* name, double def) {
78
+ if (!JS_IsObject(init)) return def;
79
+ JSValue v = JS_GetPropertyStr(ctx, init, name);
80
+ if (JS_IsException(v)) { JS_FreeValue(ctx, JS_GetException(ctx)); return def; }
81
+ if (JS_IsUndefined(v)) { JS_FreeValue(ctx, v); return def; }
82
+ double d = def;
83
+ JS_ToFloat64(ctx, &d, v);
84
+ JS_FreeValue(ctx, v);
85
+ return d;
86
+ }
87
+
88
+ // get_bool_prop() (DOMBindingsInternal.hpp) assumes a real object receiver;
89
+ // init dicts are optional, so this guards the JS_IsObject check first.
90
+ bool get_bool_init_field(JSContext* ctx, JSValue init, const char* name) {
91
+ if (!JS_IsObject(init)) return false;
92
+ return get_bool_prop(ctx, init, name);
93
+ }
94
+
95
+ std::string get_str_init_field(JSContext* ctx, JSValue init, const char* name, const char* def) {
96
+ if (!JS_IsObject(init)) return def;
97
+ JSValue v = JS_GetPropertyStr(ctx, init, name);
98
+ if (JS_IsException(v)) { JS_FreeValue(ctx, JS_GetException(ctx)); return def; }
99
+ if (JS_IsUndefined(v)) { JS_FreeValue(ctx, v); return def; }
100
+ const char* s = JS_ToCString(ctx, v);
101
+ std::string result = s ? s : def;
102
+ if (s) JS_FreeCString(ctx, s);
103
+ JS_FreeValue(ctx, v);
104
+ return result;
105
+ }
106
+
107
+ JSValue js_KeyboardEvent_constructor(JSContext* ctx, JSValue, int argc, JSValue* argv) {
108
+ JSValue obj = JS_NewObjectClass(ctx, js_event_class_id);
109
+ const char* type_str = (argc >= 1) ? JS_ToCString(ctx, argv[0]) : nullptr;
110
+ JS_SetPropertyStr(ctx, obj, "type", JS_NewString(ctx, type_str ? type_str : ""));
111
+ if (type_str) JS_FreeCString(ctx, type_str);
112
+
113
+ JSValue init = (argc >= 2) ? argv[1] : JS_UNDEFINED;
114
+ JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, get_bool_init_field(ctx, init, "bubbles")));
115
+ JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, get_bool_init_field(ctx, init, "cancelable")));
116
+ JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
117
+
118
+ std::string key = get_str_init_field(ctx, init, "key", "");
119
+ std::string code = get_str_init_field(ctx, init, "code", "");
120
+ JS_SetPropertyStr(ctx, obj, "key", JS_NewStringLen(ctx, key.c_str(), key.size()));
121
+ JS_SetPropertyStr(ctx, obj, "code", JS_NewStringLen(ctx, code.c_str(), code.size()));
122
+ JS_SetPropertyStr(ctx, obj, "location", JS_NewInt32(ctx, (int32_t)get_num_init_field(ctx, init, "location", 0)));
123
+ JS_SetPropertyStr(ctx, obj, "repeat", JS_NewBool(ctx, get_bool_init_field(ctx, init, "repeat")));
124
+ JS_SetPropertyStr(ctx, obj, "ctrlKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "ctrlKey")));
125
+ JS_SetPropertyStr(ctx, obj, "shiftKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "shiftKey")));
126
+ JS_SetPropertyStr(ctx, obj, "altKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "altKey")));
127
+ JS_SetPropertyStr(ctx, obj, "metaKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "metaKey")));
128
+ return obj;
129
+ }
130
+
131
+ JSValue js_MouseEvent_constructor(JSContext* ctx, JSValue, int argc, JSValue* argv) {
132
+ JSValue obj = JS_NewObjectClass(ctx, js_event_class_id);
133
+ const char* type_str = (argc >= 1) ? JS_ToCString(ctx, argv[0]) : nullptr;
134
+ JS_SetPropertyStr(ctx, obj, "type", JS_NewString(ctx, type_str ? type_str : ""));
135
+ if (type_str) JS_FreeCString(ctx, type_str);
136
+
137
+ JSValue init = (argc >= 2) ? argv[1] : JS_UNDEFINED;
138
+ JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, get_bool_init_field(ctx, init, "bubbles")));
139
+ JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, get_bool_init_field(ctx, init, "cancelable")));
140
+ JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
141
+
142
+ JS_SetPropertyStr(ctx, obj, "clientX", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "clientX", 0)));
143
+ JS_SetPropertyStr(ctx, obj, "clientY", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "clientY", 0)));
144
+ JS_SetPropertyStr(ctx, obj, "screenX", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "screenX", 0)));
145
+ JS_SetPropertyStr(ctx, obj, "screenY", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "screenY", 0)));
146
+ JS_SetPropertyStr(ctx, obj, "pageX", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "pageX", 0)));
147
+ JS_SetPropertyStr(ctx, obj, "pageY", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "pageY", 0)));
148
+ JS_SetPropertyStr(ctx, obj, "button", JS_NewInt32(ctx, (int32_t)get_num_init_field(ctx, init, "button", 0)));
149
+ JS_SetPropertyStr(ctx, obj, "buttons", JS_NewInt32(ctx, (int32_t)get_num_init_field(ctx, init, "buttons", 0)));
150
+ JS_SetPropertyStr(ctx, obj, "ctrlKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "ctrlKey")));
151
+ JS_SetPropertyStr(ctx, obj, "shiftKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "shiftKey")));
152
+ JS_SetPropertyStr(ctx, obj, "altKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "altKey")));
153
+ JS_SetPropertyStr(ctx, obj, "metaKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "metaKey")));
154
+ return obj;
155
+ }
156
+
74
157
  // ── Event prototype methods ───────────────────────────────────────────────────
75
158
 
76
159
  JSValue js_event_preventDefault(JSContext* ctx, JSValue this_val, int, JSValue*) {
@@ -300,6 +383,39 @@ JSValue js_doc_addEventListener(JSContext* ctx, JSValue, int argc, JSValue* argv
300
383
  return JS_UNDEFINED;
301
384
  }
302
385
 
386
+ JSValue js_doc_removeEventListener(JSContext* ctx, JSValue, int argc, JSValue* argv) {
387
+ auto* rctx = get_ctx(ctx);
388
+ if (!rctx || argc < 2 || !JS_IsFunction(ctx, argv[1])) return JS_UNDEFINED;
389
+ if (!rctx->document) return JS_UNDEFINED;
390
+
391
+ const char* type_str = JS_ToCString(ctx, argv[0]);
392
+ if (!type_str) return JS_UNDEFINED;
393
+ std::string event_type(type_str);
394
+ JS_FreeCString(ctx, type_str);
395
+
396
+ void* doc_node = lxb_dom_interface_node(
397
+ lxb_dom_interface_document(
398
+ static_cast<lxb_html_document_t*>(rctx->document->documentHtmlPtr())));
399
+
400
+ for (auto it = rctx->listeners.begin(); it != rctx->listeners.end(); ) {
401
+ if (it->node == doc_node && it->event_type == event_type) {
402
+ JSValue* stored_cb = static_cast<JSValue*>(it->callback);
403
+ if (JS_VALUE_GET_TAG(*stored_cb) == JS_VALUE_GET_TAG(argv[1]) &&
404
+ JS_VALUE_GET_PTR(*stored_cb) == JS_VALUE_GET_PTR(argv[1])) {
405
+ JS_FreeValue(ctx, *stored_cb);
406
+ delete stored_cb;
407
+ rctx->listeners.erase(it);
408
+ break; // remove first matching listener only (DOM spec)
409
+ } else {
410
+ ++it;
411
+ }
412
+ } else {
413
+ ++it;
414
+ }
415
+ }
416
+ return JS_UNDEFINED;
417
+ }
418
+
303
419
  JSValue js_doc_dispatchEvent(JSContext* ctx, JSValue, int argc, JSValue* argv) {
304
420
  auto* rctx = get_ctx(ctx);
305
421
  if (!rctx || argc < 1) return JS_TRUE;
@@ -330,6 +446,10 @@ void EventBindings::install(JSContext* ctx) {
330
446
  JS_NewCFunction2(ctx, js_Event_constructor, "Event", 1, JS_CFUNC_constructor, 0));
331
447
  JS_SetPropertyStr(ctx, global, "CustomEvent",
332
448
  JS_NewCFunction2(ctx, js_CustomEvent_constructor, "CustomEvent", 2, JS_CFUNC_constructor, 0));
449
+ JS_SetPropertyStr(ctx, global, "KeyboardEvent",
450
+ JS_NewCFunction2(ctx, js_KeyboardEvent_constructor, "KeyboardEvent", 2, JS_CFUNC_constructor, 0));
451
+ JS_SetPropertyStr(ctx, global, "MouseEvent",
452
+ JS_NewCFunction2(ctx, js_MouseEvent_constructor, "MouseEvent", 2, JS_CFUNC_constructor, 0));
333
453
 
334
454
  // ── addEventListener/removeEventListener/dispatchEvent on Node + Element ──
335
455
  // Added to the Node proto so text/comment nodes can also receive events.
@@ -339,13 +459,27 @@ void EventBindings::install(JSContext* ctx) {
339
459
  JS_SetPropertyStr(ctx, node_proto, "dispatchEvent", JS_NewCFunction(ctx, js_el_dispatchEvent, "dispatchEvent", 1));
340
460
  JS_FreeValue(ctx, node_proto);
341
461
 
342
- // ── addEventListener/dispatchEvent on document ─────────────────────────────
462
+ // ── addEventListener/removeEventListener/dispatchEvent on document ────────
343
463
  // DocumentBindings::install() must have already run so globalThis.document exists.
344
464
  JSValue doc = JS_GetPropertyStr(ctx, global, "document");
345
- JS_SetPropertyStr(ctx, doc, "addEventListener", JS_NewCFunction(ctx, js_doc_addEventListener, "addEventListener", 2));
346
- JS_SetPropertyStr(ctx, doc, "dispatchEvent", JS_NewCFunction(ctx, js_doc_dispatchEvent, "dispatchEvent", 1));
465
+ JS_SetPropertyStr(ctx, doc, "addEventListener", JS_NewCFunction(ctx, js_doc_addEventListener, "addEventListener", 2));
466
+ JS_SetPropertyStr(ctx, doc, "removeEventListener", JS_NewCFunction(ctx, js_doc_removeEventListener, "removeEventListener", 2));
467
+ JS_SetPropertyStr(ctx, doc, "dispatchEvent", JS_NewCFunction(ctx, js_doc_dispatchEvent, "dispatchEvent", 1));
347
468
  JS_FreeValue(ctx, doc);
348
469
 
470
+ // ── addEventListener/removeEventListener/dispatchEvent on window ──────────
471
+ // window === globalThis (see QuickJSRuntime::initialize()) and has no native
472
+ // node of its own to key listeners by, so js_doc_addEventListener/
473
+ // js_doc_removeEventListener/js_doc_dispatchEvent (which never read `this`
474
+ // — they always resolve the target through rctx->document) are reused
475
+ // verbatim. This means window.addEventListener(...) and
476
+ // document.addEventListener(...) share one listener list/target in this
477
+ // sandbox — a deliberate simplification, not a real separate Window
478
+ // EventTarget.
479
+ JS_SetPropertyStr(ctx, global, "addEventListener", JS_NewCFunction(ctx, js_doc_addEventListener, "addEventListener", 2));
480
+ JS_SetPropertyStr(ctx, global, "removeEventListener", JS_NewCFunction(ctx, js_doc_removeEventListener, "removeEventListener", 2));
481
+ JS_SetPropertyStr(ctx, global, "dispatchEvent", JS_NewCFunction(ctx, js_doc_dispatchEvent, "dispatchEvent", 1));
482
+
349
483
  JS_FreeValue(ctx, global);
350
484
  }
351
485
 
@@ -141,8 +141,12 @@ const char* kFetchBootstrapScript = R"JS(
141
141
  globalThis.fetch = function(input, init) {
142
142
  return new Promise(function(resolve, reject) {
143
143
  try {
144
- var url = typeof input === 'string' ? input : (input && input.url);
145
144
  init = init || {};
145
+ if (init.signal && init.signal.aborted) {
146
+ reject(init.signal.reason !== undefined ? init.signal.reason : new Error('The operation was aborted'));
147
+ return;
148
+ }
149
+ var url = typeof input === 'string' ? input : (input && input.url);
146
150
  var method = (init.method || 'GET').toUpperCase();
147
151
  var headers = normalizeHeaders(init.headers);
148
152
  var headersJson = JSON.stringify(headers);
@@ -0,0 +1,142 @@
1
+ #include "FormBindings.hpp"
2
+ #include <cstring>
3
+
4
+ namespace margelo::nitro::nitrojsdom {
5
+
6
+ namespace {
7
+
8
+ const char* kFormBootstrapScript = R"JS(
9
+ (function() {
10
+ function FormData(form) {
11
+ this._entries = [];
12
+ if (form) {
13
+ var els = form.querySelectorAll('input, textarea, select');
14
+ for (var i = 0; i < els.length; i++) {
15
+ var el = els[i];
16
+ var name = el.getAttribute('name');
17
+ if (!name) continue;
18
+ var type = (el.getAttribute('type') || '').toLowerCase();
19
+ if ((type === 'checkbox' || type === 'radio') && !el.checked) continue;
20
+ this._entries.push([name, el.value]);
21
+ }
22
+ }
23
+ }
24
+
25
+ FormData.prototype.append = function(name, value) {
26
+ this._entries.push([String(name), String(value)]);
27
+ };
28
+ FormData.prototype.set = function(name, value) {
29
+ name = String(name);
30
+ value = String(value);
31
+ var replaced = false;
32
+ var next = [];
33
+ for (var i = 0; i < this._entries.length; i++) {
34
+ var entry = this._entries[i];
35
+ if (entry[0] !== name) { next.push(entry); continue; }
36
+ if (!replaced) { next.push([name, value]); replaced = true; }
37
+ }
38
+ if (!replaced) next.push([name, value]);
39
+ this._entries = next;
40
+ };
41
+ FormData.prototype.get = function(name) {
42
+ name = String(name);
43
+ for (var i = 0; i < this._entries.length; i++) {
44
+ if (this._entries[i][0] === name) return this._entries[i][1];
45
+ }
46
+ return null;
47
+ };
48
+ FormData.prototype.getAll = function(name) {
49
+ name = String(name);
50
+ var result = [];
51
+ for (var i = 0; i < this._entries.length; i++) {
52
+ if (this._entries[i][0] === name) result.push(this._entries[i][1]);
53
+ }
54
+ return result;
55
+ };
56
+ FormData.prototype.has = function(name) {
57
+ name = String(name);
58
+ for (var i = 0; i < this._entries.length; i++) {
59
+ if (this._entries[i][0] === name) return true;
60
+ }
61
+ return false;
62
+ };
63
+ FormData.prototype.delete = function(name) {
64
+ name = String(name);
65
+ var next = [];
66
+ for (var i = 0; i < this._entries.length; i++) {
67
+ if (this._entries[i][0] !== name) next.push(this._entries[i]);
68
+ }
69
+ this._entries = next;
70
+ };
71
+ FormData.prototype.forEach = function(callback, thisArg) {
72
+ for (var i = 0; i < this._entries.length; i++) {
73
+ callback.call(thisArg, this._entries[i][1], this._entries[i][0], this);
74
+ }
75
+ };
76
+ FormData.prototype.entries = function() {
77
+ var entries = this._entries;
78
+ var i = 0;
79
+ var iter = {
80
+ next: function() {
81
+ if (i >= entries.length) return { done: true, value: undefined };
82
+ return { done: false, value: entries[i++].slice() };
83
+ },
84
+ };
85
+ iter[Symbol.iterator] = function() { return iter; };
86
+ return iter;
87
+ };
88
+ FormData.prototype.keys = function() {
89
+ var it = this.entries();
90
+ var keysIter = {
91
+ next: function() {
92
+ var r = it.next();
93
+ return r.done ? r : { done: false, value: r.value[0] };
94
+ },
95
+ };
96
+ keysIter[Symbol.iterator] = function() { return keysIter; };
97
+ return keysIter;
98
+ };
99
+ FormData.prototype.values = function() {
100
+ var it = this.entries();
101
+ var valuesIter = {
102
+ next: function() {
103
+ var r = it.next();
104
+ return r.done ? r : { done: false, value: r.value[1] };
105
+ },
106
+ };
107
+ valuesIter[Symbol.iterator] = function() { return valuesIter; };
108
+ return valuesIter;
109
+ };
110
+ FormData.prototype[Symbol.iterator] = function() { return this.entries(); };
111
+
112
+ globalThis.FormData = FormData;
113
+
114
+ // ── form.requestSubmit() / form.submit() ──────────────────────────────────
115
+ function isFormElement(el) {
116
+ return !!el && el.tagName === 'FORM';
117
+ }
118
+ Element.prototype.requestSubmit = function(submitter) {
119
+ if (!isFormElement(this)) return;
120
+ var evt = new Event('submit', { bubbles: true, cancelable: true });
121
+ evt.submitter = submitter || null;
122
+ this.dispatchEvent(evt);
123
+ };
124
+ Element.prototype.submit = function() {
125
+ // Per spec, submit() bypasses the "submit" event and constraint validation.
126
+ // There is no real navigation in this sandbox, so this is intentionally inert.
127
+ };
128
+ })();
129
+ )JS";
130
+
131
+ } // namespace
132
+
133
+ void FormBindings::install(JSContext* ctx) {
134
+ JSValue result = JS_Eval(ctx, kFormBootstrapScript, strlen(kFormBootstrapScript),
135
+ "<form-bootstrap>", JS_EVAL_TYPE_GLOBAL);
136
+ if (JS_IsException(result)) {
137
+ JS_FreeValue(ctx, JS_GetException(ctx));
138
+ }
139
+ JS_FreeValue(ctx, result);
140
+ }
141
+
142
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ #include "quickjs.h"
4
+
5
+ namespace margelo::nitro::nitrojsdom {
6
+
7
+ // Registers globalThis.FormData and Element.prototype.requestSubmit/submit
8
+ // (tag-checked for "form" internally, mirroring ElementBindings' value/checked
9
+ // pattern). Pure JS on top of the already-exposed querySelectorAll/Event
10
+ // primitives — no native code needed. Must run after ElementBindings (for
11
+ // globalThis.Element) and EventBindings (for globalThis.Event).
12
+ struct FormBindings {
13
+ static void install(JSContext* ctx);
14
+ };
15
+
16
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,183 @@
1
+ #include "LiveCollectionBindings.hpp"
2
+ #include "../DOMBindingsInternal.hpp"
3
+ #include "../../lexbor/LexborDocument.hpp"
4
+ #include <lexbor/dom/dom.h>
5
+ #include <cctype>
6
+ #include <cstdlib>
7
+ #include <cstring>
8
+ #include <string>
9
+ #include <vector>
10
+
11
+ namespace margelo::nitro::nitrojsdom {
12
+
13
+ namespace {
14
+
15
+ JSClassID js_live_collection_class_id = 0;
16
+
17
+ enum class LiveKind { CHILDREN, CHILD_NODES, SELECTOR };
18
+
19
+ struct LiveCollectionData {
20
+ void* owner;
21
+ LiveKind kind;
22
+ std::string selector;
23
+ };
24
+
25
+ LiveCollectionData* unwrap_live(JSContext*, JSValue val) {
26
+ return static_cast<LiveCollectionData*>(JS_GetOpaque(val, js_live_collection_class_id));
27
+ }
28
+
29
+ std::vector<void*> compute_results(JSContext* ctx, LiveCollectionData* data) {
30
+ switch (data->kind) {
31
+ case LiveKind::CHILDREN: {
32
+ std::vector<void*> out;
33
+ auto* node = static_cast<lxb_dom_node_t*>(data->owner);
34
+ for (auto* c = node->first_child; c; c = c->next)
35
+ if (c->type == LXB_DOM_NODE_TYPE_ELEMENT) out.push_back(c);
36
+ return out;
37
+ }
38
+ case LiveKind::CHILD_NODES: {
39
+ std::vector<void*> out;
40
+ auto* node = static_cast<lxb_dom_node_t*>(data->owner);
41
+ for (auto* c = node->first_child; c; c = c->next) out.push_back(c);
42
+ return out;
43
+ }
44
+ case LiveKind::SELECTOR:
45
+ if (data->owner) return get_doc(ctx)->querySelectorAllFromEl(data->owner, data->selector);
46
+ return get_doc(ctx)->querySelectorAll_el(data->selector);
47
+ }
48
+ return {};
49
+ }
50
+
51
+ bool prop_is_index(const std::string& key, size_t& out_idx) {
52
+ if (key.empty()) return false;
53
+ for (char c : key) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
54
+ out_idx = static_cast<size_t>(std::strtoul(key.c_str(), nullptr, 10));
55
+ return true;
56
+ }
57
+
58
+ int js_livecoll_get_own_property(JSContext* ctx, JSPropertyDescriptor* desc, JSValue obj, JSAtom prop) {
59
+ auto* data = unwrap_live(ctx, obj);
60
+ if (!data) return 0;
61
+
62
+ JSValue key_val = JS_AtomToString(ctx, prop);
63
+ const char* key_c = JS_ToCString(ctx, key_val);
64
+ std::string key = key_c ? key_c : "";
65
+ if (key_c) JS_FreeCString(ctx, key_c);
66
+ JS_FreeValue(ctx, key_val);
67
+
68
+ if (key == "length") {
69
+ if (desc) {
70
+ desc->flags = JS_PROP_ENUMERABLE;
71
+ desc->value = JS_NewInt32(ctx, static_cast<int32_t>(compute_results(ctx, data).size()));
72
+ desc->getter = JS_UNDEFINED;
73
+ desc->setter = JS_UNDEFINED;
74
+ }
75
+ return 1;
76
+ }
77
+
78
+ size_t idx;
79
+ if (prop_is_index(key, idx)) {
80
+ auto results = compute_results(ctx, data);
81
+ if (idx < results.size()) {
82
+ if (desc) {
83
+ desc->flags = JS_PROP_ENUMERABLE;
84
+ desc->value = make_element(ctx, results[idx]);
85
+ desc->getter = JS_UNDEFINED;
86
+ desc->setter = JS_UNDEFINED;
87
+ }
88
+ return 1;
89
+ }
90
+ }
91
+
92
+ return 0;
93
+ }
94
+
95
+ int js_livecoll_get_own_property_names(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValue obj) {
96
+ *ptab = nullptr;
97
+ *plen = 0;
98
+ auto* data = unwrap_live(ctx, obj);
99
+ if (!data) return 0;
100
+
101
+ auto results = compute_results(ctx, data);
102
+ auto* tab = static_cast<JSPropertyEnum*>(js_malloc(ctx, sizeof(JSPropertyEnum) * (results.size() + 1)));
103
+ if (!tab) return -1;
104
+
105
+ for (size_t i = 0; i < results.size(); i++) {
106
+ tab[i].is_enumerable = 1;
107
+ tab[i].atom = JS_NewAtom(ctx, std::to_string(i).c_str());
108
+ }
109
+ tab[results.size()].is_enumerable = 0;
110
+ tab[results.size()].atom = JS_NewAtom(ctx, "length");
111
+
112
+ *ptab = tab;
113
+ *plen = static_cast<uint32_t>(results.size() + 1);
114
+ return 0;
115
+ }
116
+
117
+ JSClassExoticMethods js_livecoll_exotic = {
118
+ .get_own_property = js_livecoll_get_own_property,
119
+ .get_own_property_names = js_livecoll_get_own_property_names,
120
+ };
121
+
122
+ void js_livecoll_finalizer(JSRuntime*, JSValue val) {
123
+ auto* data = static_cast<LiveCollectionData*>(JS_GetOpaque(val, js_live_collection_class_id));
124
+ delete data;
125
+ }
126
+
127
+ JSClassDef js_livecoll_class = { "LiveCollection", .finalizer = js_livecoll_finalizer, .exotic = &js_livecoll_exotic };
128
+
129
+ const char* kIteratorBootstrapScript = R"JS(
130
+ (function() {
131
+ var proto = globalThis.__LiveCollectionProto;
132
+ proto[Symbol.iterator] = function() {
133
+ var i = 0;
134
+ var self = this;
135
+ return {
136
+ next: function() {
137
+ return i < self.length ? { value: self[i++], done: false } : { value: undefined, done: true };
138
+ }
139
+ };
140
+ };
141
+ delete globalThis.__LiveCollectionProto;
142
+ })();
143
+ )JS";
144
+
145
+ JSValue make(JSContext* ctx, void* owner, LiveKind kind, const std::string& selector) {
146
+ auto* data = new LiveCollectionData{ owner, kind, selector };
147
+ JSValue obj = JS_NewObjectClass(ctx, js_live_collection_class_id);
148
+ JS_SetOpaque(obj, data);
149
+ return obj;
150
+ }
151
+
152
+ } // namespace
153
+
154
+ void LiveCollectionBindings::install(JSContext* ctx) {
155
+ if (js_live_collection_class_id == 0) JS_NewClassID(&js_live_collection_class_id);
156
+ JS_NewClass(JS_GetRuntime(ctx), js_live_collection_class_id, &js_livecoll_class);
157
+
158
+ JSValue proto = JS_NewObject(ctx);
159
+ JS_SetClassProto(ctx, js_live_collection_class_id, JS_DupValue(ctx, proto));
160
+
161
+ JSValue global = JS_GetGlobalObject(ctx);
162
+ JS_SetPropertyStr(ctx, global, "__LiveCollectionProto", proto);
163
+ JS_FreeValue(ctx, global);
164
+
165
+ JSValue result = JS_Eval(ctx, kIteratorBootstrapScript, strlen(kIteratorBootstrapScript),
166
+ "<live-collection-bootstrap>", JS_EVAL_TYPE_GLOBAL);
167
+ if (JS_IsException(result)) JS_FreeValue(ctx, JS_GetException(ctx));
168
+ JS_FreeValue(ctx, result);
169
+ }
170
+
171
+ JSValue LiveCollectionBindings::makeChildren(JSContext* ctx, void* element) {
172
+ return make(ctx, element, LiveKind::CHILDREN, "");
173
+ }
174
+
175
+ JSValue LiveCollectionBindings::makeChildNodes(JSContext* ctx, void* node) {
176
+ return make(ctx, node, LiveKind::CHILD_NODES, "");
177
+ }
178
+
179
+ JSValue LiveCollectionBindings::makeBySelector(JSContext* ctx, void* owner_or_null, const std::string& selector) {
180
+ return make(ctx, owner_or_null, LiveKind::SELECTOR, selector);
181
+ }
182
+
183
+ } // namespace margelo::nitro::nitrojsdom
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ #include "quickjs.h"
4
+ #include <string>
5
+
6
+ namespace margelo::nitro::nitrojsdom {
7
+
8
+ class LiveCollectionBindings {
9
+ public:
10
+ static void install(JSContext* ctx);
11
+ static JSValue makeChildren(JSContext* ctx, void* element);
12
+ static JSValue makeChildNodes(JSContext* ctx, void* node);
13
+ static JSValue makeBySelector(JSContext* ctx, void* owner_or_null, const std::string& selector);
14
+ };
15
+
16
+ } // namespace margelo::nitro::nitrojsdom