@salve-software/react-native-nitro-jsdom 1.0.0 → 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.
- package/README.md +1 -1
- package/android/CMakeLists.txt +14 -0
- package/cpp/HybridHtmlSandbox.cpp +16 -3
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +217 -67
- package/cpp/lexbor/LexborDocument.hpp +29 -10
- package/cpp/quickjs/DOMBindings.cpp +28 -0
- package/cpp/quickjs/DOMBindingsInternal.cpp +34 -2
- package/cpp/quickjs/DOMBindingsInternal.hpp +17 -1
- package/cpp/quickjs/QuickJSRuntime.cpp +19 -4
- package/cpp/quickjs/QuickJSRuntime.hpp +22 -1
- package/cpp/quickjs/bindings/AbortBindings.cpp +114 -0
- package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +137 -0
- package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +244 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +23 -0
- package/cpp/quickjs/bindings/ClassListBindings.cpp +3 -2
- package/cpp/quickjs/bindings/CookieBindings.cpp +118 -0
- package/cpp/quickjs/bindings/CookieBindings.hpp +23 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +341 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.hpp +50 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.cpp +111 -0
- package/cpp/quickjs/bindings/DOMExceptionBindings.hpp +20 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +84 -4
- package/cpp/quickjs/bindings/ElementBindings.cpp +264 -28
- package/cpp/quickjs/bindings/EventBindings.cpp +137 -3
- package/cpp/quickjs/bindings/FetchBindings.cpp +5 -1
- package/cpp/quickjs/bindings/FormBindings.cpp +142 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +16 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +183 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.hpp +16 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.cpp +171 -0
- package/cpp/quickjs/bindings/ShadowRootBindings.hpp +27 -0
- package/cpp/quickjs/bindings/SlotBindings.cpp +161 -0
- package/cpp/quickjs/bindings/SlotBindings.hpp +44 -0
- package/cpp/quickjs/bindings/TemplateBindings.cpp +42 -0
- package/cpp/quickjs/bindings/TemplateBindings.hpp +29 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.cpp +117 -0
- package/cpp/quickjs/bindings/TextEncodingBindings.hpp +14 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +41 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +286 -0
- package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +270 -0
- package/cpp/quickjs/bindings/XmlSerializerBindings.cpp +44 -0
- package/cpp/quickjs/bindings/XmlSerializerBindings.hpp +24 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
- package/package.json +2 -2
- package/src/classes/JSDOM/JSDOM.class.ts +1 -0
- 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",
|
|
346
|
-
JS_SetPropertyStr(ctx, doc, "
|
|
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
|