@salve-software/react-native-nitro-jsdom 2.0.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.
- package/android/CMakeLists.txt +2 -0
- package/cpp/HybridHtmlSandbox.cpp +7 -4
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +3 -3
- package/cpp/lexbor/LexborDocument.hpp +3 -2
- package/cpp/quickjs/DOMBindings.cpp +5 -0
- package/cpp/quickjs/QuickJSRuntime.hpp +1 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +50 -0
- package/cpp/quickjs/bindings/CustomElementsBindings.cpp +1 -1
- package/cpp/quickjs/bindings/DocumentBindings.cpp +8 -0
- package/cpp/quickjs/bindings/ElementBindings.cpp +221 -1
- package/cpp/quickjs/bindings/EventBindings.cpp +8 -4
- package/cpp/quickjs/bindings/FormBindings.cpp +71 -0
- package/cpp/quickjs/bindings/IntlBindings.cpp +352 -0
- package/cpp/quickjs/bindings/IntlBindings.hpp +29 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +34 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +9 -0
- package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp +241 -0
- package/cpp/quickjs/bindings/WindowNamedPropertiesBindings.hpp +30 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +2 -1
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
- package/lib/module/classes/JSDOM/JSDOM.class.js +2 -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/classes/JSDOM/types/IJSDOMOptions.d.ts +5 -4
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.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 +1 -1
- package/src/classes/JSDOM/JSDOM.class.ts +2 -1
- package/src/classes/JSDOM/types/IJSDOMOptions.ts +5 -4
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
package/android/CMakeLists.txt
CHANGED
|
@@ -61,6 +61,7 @@ add_library(${PACKAGE_NAME} SHARED
|
|
|
61
61
|
../cpp/quickjs/bindings/UrlBindings.cpp
|
|
62
62
|
../cpp/quickjs/bindings/AbortBindings.cpp
|
|
63
63
|
../cpp/quickjs/bindings/TextEncodingBindings.cpp
|
|
64
|
+
../cpp/quickjs/bindings/IntlBindings.cpp
|
|
64
65
|
../cpp/quickjs/bindings/FormBindings.cpp
|
|
65
66
|
../cpp/quickjs/bindings/BlobBindings.cpp
|
|
66
67
|
../cpp/quickjs/bindings/CSSOMBindings.cpp
|
|
@@ -73,6 +74,7 @@ add_library(${PACKAGE_NAME} SHARED
|
|
|
73
74
|
../cpp/quickjs/bindings/LayoutStubBindings.cpp
|
|
74
75
|
../cpp/quickjs/bindings/TreeWalkerBindings.cpp
|
|
75
76
|
../cpp/quickjs/bindings/EventTargetBindings.cpp
|
|
77
|
+
../cpp/quickjs/bindings/WindowNamedPropertiesBindings.cpp
|
|
76
78
|
${QUICKJS_SOURCES}
|
|
77
79
|
)
|
|
78
80
|
|
|
@@ -43,10 +43,13 @@ void HybridHtmlSandbox::initialize(const std::string& html, bool runScripts, con
|
|
|
43
43
|
_runtime->bindDocument(_document.get());
|
|
44
44
|
|
|
45
45
|
if (runScripts) {
|
|
46
|
-
|
|
46
|
+
auto* rctx = _runtime->contextState();
|
|
47
|
+
for (const auto& [scriptEl, script] : _document->getScriptContents()) {
|
|
48
|
+
rctx->current_script = scriptEl;
|
|
47
49
|
try {
|
|
48
50
|
_runtime->evaluate(script);
|
|
49
51
|
} catch (...) { }
|
|
52
|
+
rctx->current_script = nullptr;
|
|
50
53
|
}
|
|
51
54
|
}
|
|
52
55
|
|
|
@@ -111,7 +114,7 @@ void HybridHtmlSandbox::setConsoleCallback(
|
|
|
111
114
|
|
|
112
115
|
void HybridHtmlSandbox::setDialogCallbacks(
|
|
113
116
|
const std::optional<std::variant<nitro::NullType, std::function<void(const std::string&)>>>& onAlert,
|
|
114
|
-
const std::optional<std::variant<nitro::NullType, std::function<std::shared_ptr<Promise<bool
|
|
117
|
+
const std::optional<std::variant<nitro::NullType, std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<bool>>>>(const std::string&)>>>& onConfirm,
|
|
115
118
|
const std::optional<std::variant<nitro::NullType, std::function<std::shared_ptr<Promise<std::variant<nitro::NullType, std::string>>>(const std::string&, const std::optional<std::string>&)>>>& onPrompt) {
|
|
116
119
|
if (!_runtime) return;
|
|
117
120
|
|
|
@@ -129,10 +132,10 @@ void HybridHtmlSandbox::setDialogCallbacks(
|
|
|
129
132
|
if (!onConfirm.has_value() || std::holds_alternative<nitro::NullType>(onConfirm.value())) {
|
|
130
133
|
_runtime->setConfirmCallback(nullptr);
|
|
131
134
|
} else {
|
|
132
|
-
auto fn = std::get<std::function<std::shared_ptr<Promise<bool
|
|
135
|
+
auto fn = std::get<std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<bool>>>>(const std::string&)>>(onConfirm.value());
|
|
133
136
|
_runtime->setConfirmCallback([fn](const std::string& message) -> bool {
|
|
134
137
|
try {
|
|
135
|
-
return fn(message)->await().get();
|
|
138
|
+
return fn(message)->await().get()->await().get();
|
|
136
139
|
} catch (...) {
|
|
137
140
|
return false;
|
|
138
141
|
}
|
|
@@ -30,7 +30,7 @@ public:
|
|
|
30
30
|
|
|
31
31
|
void setDialogCallbacks(
|
|
32
32
|
const std::optional<std::variant<nitro::NullType, std::function<void(const std::string& /* message */)>>>& onAlert,
|
|
33
|
-
const std::optional<std::variant<nitro::NullType, std::function<std::shared_ptr<Promise<bool
|
|
33
|
+
const std::optional<std::variant<nitro::NullType, std::function<std::shared_ptr<Promise<std::shared_ptr<Promise<bool>>>>(const std::string& /* message */)>>>& onConfirm,
|
|
34
34
|
const std::optional<std::variant<nitro::NullType, std::function<std::shared_ptr<Promise<std::variant<nitro::NullType, std::string>>>(const std::string& /* message */, const std::optional<std::string>& /* defaultValue */)>>>& onPrompt
|
|
35
35
|
) override;
|
|
36
36
|
|
|
@@ -199,9 +199,9 @@ void* LexborDocument::documentElement() const {
|
|
|
199
199
|
|
|
200
200
|
// ── Script extraction ────────────────────────────────────────────────────────
|
|
201
201
|
|
|
202
|
-
std::vector<std::string
|
|
202
|
+
std::vector<std::pair<void*, std::string>> LexborDocument::getScriptContents() const {
|
|
203
203
|
auto scriptEls = querySelectorAll_el("script");
|
|
204
|
-
std::vector<std::string
|
|
204
|
+
std::vector<std::pair<void*, std::string>> contents;
|
|
205
205
|
contents.reserve(scriptEls.size());
|
|
206
206
|
|
|
207
207
|
for (void* el : scriptEls) {
|
|
@@ -217,7 +217,7 @@ std::vector<std::string> LexborDocument::getScriptContents() const {
|
|
|
217
217
|
size_t len = 0;
|
|
218
218
|
lxb_char_t* text = lxb_dom_node_text_content(node, &len);
|
|
219
219
|
if (text && len > 0) {
|
|
220
|
-
contents.emplace_back(reinterpret_cast<char*>(text), len);
|
|
220
|
+
contents.emplace_back(el, std::string(reinterpret_cast<char*>(text), len));
|
|
221
221
|
lxb_dom_document_destroy_text(node->owner_document, text);
|
|
222
222
|
}
|
|
223
223
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
#include <string>
|
|
4
|
+
#include <utility>
|
|
4
5
|
#include <vector>
|
|
5
6
|
|
|
6
7
|
namespace margelo::nitro::nitrojsdom {
|
|
@@ -29,8 +30,8 @@ public:
|
|
|
29
30
|
void* head() const;
|
|
30
31
|
void* documentElement() const;
|
|
31
32
|
|
|
32
|
-
// ── Script extraction
|
|
33
|
-
std::vector<std::string
|
|
33
|
+
// ── Script extraction — (element, text content) pairs; void* = lxb_dom_element_t* ──
|
|
34
|
+
std::vector<std::pair<void*, std::string>> getScriptContents() const;
|
|
34
35
|
|
|
35
36
|
// ── Node creation ─────────────────────────────────────────────────────────
|
|
36
37
|
void* createElement(const std::string& tag);
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
#include "bindings/UrlBindings.hpp"
|
|
17
17
|
#include "bindings/AbortBindings.hpp"
|
|
18
18
|
#include "bindings/TextEncodingBindings.hpp"
|
|
19
|
+
#include "bindings/IntlBindings.hpp"
|
|
19
20
|
#include "bindings/FormBindings.hpp"
|
|
20
21
|
#include "bindings/BlobBindings.hpp"
|
|
21
22
|
#include "bindings/CSSOMBindings.hpp"
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
#include "bindings/LayoutStubBindings.hpp"
|
|
29
30
|
#include "bindings/TreeWalkerBindings.hpp"
|
|
30
31
|
#include "bindings/EventTargetBindings.hpp"
|
|
32
|
+
#include "bindings/WindowNamedPropertiesBindings.hpp"
|
|
31
33
|
#include <lexbor/html/html.h>
|
|
32
34
|
#include <lexbor/dom/dom.h>
|
|
33
35
|
|
|
@@ -58,6 +60,7 @@ void DOMBindings::install(QuickJSRuntime* runtime, LexborDocument* document) {
|
|
|
58
60
|
UrlBindings::install(ctx);
|
|
59
61
|
AbortBindings::install(ctx);
|
|
60
62
|
TextEncodingBindings::install(ctx);
|
|
63
|
+
IntlBindings::install(ctx);
|
|
61
64
|
BlobBindings::install(ctx); // uses TextEncoder/TextDecoder + btoa, so must run after both
|
|
62
65
|
FetchBindings::install(ctx); // XHR bootstrap uses `new Event(...)`, so must run after EventBindings
|
|
63
66
|
FormBindings::install(ctx); // uses globalThis.Element + globalThis.Event, so must run after both
|
|
@@ -88,6 +91,8 @@ void DOMBindings::install(QuickJSRuntime* runtime, LexborDocument* document) {
|
|
|
88
91
|
rctx->mutation_observers->install(ctx, doc_node);
|
|
89
92
|
}
|
|
90
93
|
}
|
|
94
|
+
|
|
95
|
+
WindowNamedPropertiesBindings::install(ctx); // must run last, after localStorage/sessionStorage exist
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -201,6 +201,56 @@ const char* kCSSOMBootstrapScript = R"JS(
|
|
|
201
201
|
globalThis.CSSStyleRule = CSSStyleRule;
|
|
202
202
|
globalThis.CSSStyleSheet = CSSStyleSheet;
|
|
203
203
|
|
|
204
|
+
function cssEscape(value) {
|
|
205
|
+
var string = String(value);
|
|
206
|
+
var length = string.length;
|
|
207
|
+
var index = -1;
|
|
208
|
+
var result = '';
|
|
209
|
+
var firstCodeUnit = string.charCodeAt(0);
|
|
210
|
+
|
|
211
|
+
if (length === 1 && firstCodeUnit === 0x002D) {
|
|
212
|
+
return '\\' + string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
while (++index < length) {
|
|
216
|
+
var codeUnit = string.charCodeAt(index);
|
|
217
|
+
if (codeUnit === 0x0000) {
|
|
218
|
+
result += '\uFFFD';
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (
|
|
222
|
+
(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit === 0x007F ||
|
|
223
|
+
(index === 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
|
|
224
|
+
(index === 1 && codeUnit >= 0x0030 && codeUnit <= 0x0039 && firstCodeUnit === 0x002D)
|
|
225
|
+
) {
|
|
226
|
+
result += '\\' + codeUnit.toString(16) + ' ';
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (
|
|
230
|
+
codeUnit >= 0x0080 || codeUnit === 0x002D || codeUnit === 0x005F ||
|
|
231
|
+
(codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
|
|
232
|
+
(codeUnit >= 0x0041 && codeUnit <= 0x005A) ||
|
|
233
|
+
(codeUnit >= 0x0061 && codeUnit <= 0x007A)
|
|
234
|
+
) {
|
|
235
|
+
result += string.charAt(index);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
result += '\\' + string.charAt(index);
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
globalThis.CSS = {
|
|
244
|
+
escape: cssEscape,
|
|
245
|
+
supports: function(propertyOrCondition, value) {
|
|
246
|
+
if (arguments.length >= 2) {
|
|
247
|
+
return typeof propertyOrCondition === 'string' && propertyOrCondition.length > 0 &&
|
|
248
|
+
value !== undefined && String(value).length > 0;
|
|
249
|
+
}
|
|
250
|
+
return typeof propertyOrCondition === 'string' && propertyOrCondition.trim().length > 0;
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
|
|
204
254
|
Object.defineProperty(Element.prototype, 'sheet', {
|
|
205
255
|
get: function() {
|
|
206
256
|
if (this.tagName !== 'STYLE') return null;
|
|
@@ -269,6 +269,12 @@ JSValue js_doc_get_activeElement(JSContext* ctx, JSValue) {
|
|
|
269
269
|
return make_element(ctx, get_doc(ctx)->body());
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
+
JSValue js_doc_get_currentScript(JSContext* ctx, JSValue) {
|
|
273
|
+
auto* rctx = get_ctx(ctx);
|
|
274
|
+
if (rctx && rctx->current_script) return make_element(ctx, rctx->current_script);
|
|
275
|
+
return JS_NULL;
|
|
276
|
+
}
|
|
277
|
+
|
|
272
278
|
JSValue js_doc_get_readyState(JSContext* ctx, JSValue) {
|
|
273
279
|
auto* rctx = get_ctx(ctx);
|
|
274
280
|
const std::string& state = rctx ? rctx->ready_state : "loading";
|
|
@@ -350,6 +356,7 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
350
356
|
define_prop(ctx, doc, "scripts", js_doc_get_scripts, nullptr);
|
|
351
357
|
define_prop(ctx, doc, "links", js_doc_get_links, nullptr);
|
|
352
358
|
define_prop(ctx, doc, "activeElement", js_doc_get_activeElement, nullptr);
|
|
359
|
+
define_prop(ctx, doc, "currentScript", js_doc_get_currentScript, nullptr);
|
|
353
360
|
define_prop(ctx, doc, "readyState", js_doc_get_readyState, nullptr);
|
|
354
361
|
define_prop(ctx, doc, "compatMode", js_doc_get_compatMode, nullptr);
|
|
355
362
|
define_prop(ctx, doc, "baseURI", js_doc_get_baseURI, nullptr);
|
|
@@ -360,6 +367,7 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
360
367
|
RuntimeContext* rctx = get_ctx(ctx);
|
|
361
368
|
bool hidden = !(rctx && rctx->pretend_to_be_visual);
|
|
362
369
|
JS_SetPropertyStr(ctx, doc, "hidden", JS_NewBool(ctx, hidden));
|
|
370
|
+
JS_SetPropertyStr(ctx, doc, "visibilityState", JS_NewString(ctx, hidden ? "hidden" : "visible"));
|
|
363
371
|
JS_SetPropertyStr(ctx, doc, "nodeType", JS_NewInt32(ctx, 9 /* DOCUMENT_NODE */));
|
|
364
372
|
JS_SetPropertyStr(ctx, doc, "nodeName", JS_NewString(ctx, "#document"));
|
|
365
373
|
JS_SetPropertyStr(ctx, doc, "ownerDocument", JS_NULL);
|
|
@@ -303,6 +303,107 @@ JSValue js_el_set_checked(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
|
303
303
|
return JS_UNDEFINED;
|
|
304
304
|
}
|
|
305
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
|
+
|
|
306
407
|
JSValue js_el_get_textContent(JSContext* ctx, JSValue this_val) {
|
|
307
408
|
lxb_dom_node_t* node = unwrap_node(ctx, this_val);
|
|
308
409
|
if (!node) return JS_NewString(ctx, "");
|
|
@@ -1135,6 +1236,113 @@ JSValue js_el_insertAdjacentHTML(JSContext* ctx, JSValue this_val, int argc, JSV
|
|
|
1135
1236
|
return JS_UNDEFINED;
|
|
1136
1237
|
}
|
|
1137
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
|
+
|
|
1138
1346
|
JSValue js_el_matches(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
1139
1347
|
auto* el = unwrap_element(ctx, this_val);
|
|
1140
1348
|
if (!el || argc < 1) return JS_FALSE;
|
|
@@ -1502,6 +1710,8 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1502
1710
|
JS_SetPropertyStr(ctx, proto, "getElementsByTagName", JS_NewCFunction(ctx, js_el_getElementsByTagName, "getElementsByTagName", 1));
|
|
1503
1711
|
JS_SetPropertyStr(ctx, proto, "closest", JS_NewCFunction(ctx, js_el_closest, "closest", 1));
|
|
1504
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));
|
|
1505
1715
|
JS_SetPropertyStr(ctx, proto, "append", JS_NewCFunction(ctx, js_el_append, "append", 0));
|
|
1506
1716
|
JS_SetPropertyStr(ctx, proto, "prepend", JS_NewCFunction(ctx, js_el_prepend, "prepend", 0));
|
|
1507
1717
|
JS_SetPropertyStr(ctx, proto, "getBoundingClientRect", JS_NewCFunction(ctx, js_el_getBoundingClientRect, "getBoundingClientRect", 0));
|
|
@@ -1513,6 +1723,16 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1513
1723
|
define_prop(ctx, proto, "className", js_el_get_className, js_el_set_className);
|
|
1514
1724
|
define_prop(ctx, proto, "value", js_el_get_value, js_el_set_value);
|
|
1515
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);
|
|
1516
1736
|
define_prop(ctx, proto, "innerHTML", js_el_get_innerHTML, js_el_set_innerHTML);
|
|
1517
1737
|
define_prop(ctx, proto, "outerHTML", js_el_get_outerHTML, nullptr);
|
|
1518
1738
|
define_prop(ctx, proto, "innerText", js_el_get_textContent, js_el_set_textContent);
|
|
@@ -1553,7 +1773,7 @@ void ElementBindings::install(JSContext* ctx) {
|
|
|
1553
1773
|
while (true) {
|
|
1554
1774
|
var parent = node.parentNode;
|
|
1555
1775
|
if (parent) { node = parent; continue; }
|
|
1556
|
-
if (composed && node
|
|
1776
|
+
if (composed && node instanceof ShadowRoot) { node = node.host; continue; }
|
|
1557
1777
|
return __nativeCanonicalizeRootNode(node);
|
|
1558
1778
|
}
|
|
1559
1779
|
};
|
|
@@ -528,7 +528,7 @@ JSValue js_doc_dispatchEvent(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
|
528
528
|
return dispatch_event_on_target(ctx, rctx, argv[0], node);
|
|
529
529
|
}
|
|
530
530
|
|
|
531
|
-
const char* kHandlerEventTypes[] = { "click", "load", "error", "unhandledrejection" };
|
|
531
|
+
const char* kHandlerEventTypes[] = { "click", "load", "error", "unhandledrejection", "change", "input", "submit", "reset" };
|
|
532
532
|
|
|
533
533
|
JSValue get_handler_prop_for_node(JSContext* ctx, RuntimeContext* rctx, void* node, const std::string& event_type) {
|
|
534
534
|
if (!rctx || !node) return JS_NULL;
|
|
@@ -684,9 +684,13 @@ void EventBindings::install(JSContext* ctx) {
|
|
|
684
684
|
JS_SetPropertyStr(ctx, node_proto, "removeEventListener", JS_NewCFunction(ctx, js_el_removeEventListener, "removeEventListener", 2));
|
|
685
685
|
JS_SetPropertyStr(ctx, node_proto, "dispatchEvent", JS_NewCFunction(ctx, js_el_dispatchEvent, "dispatchEvent", 1));
|
|
686
686
|
|
|
687
|
-
define_element_handler(ctx, node_proto, "onclick",
|
|
688
|
-
define_element_handler(ctx, node_proto, "onload",
|
|
689
|
-
define_element_handler(ctx, node_proto, "onerror",
|
|
687
|
+
define_element_handler(ctx, node_proto, "onclick", 0);
|
|
688
|
+
define_element_handler(ctx, node_proto, "onload", 1);
|
|
689
|
+
define_element_handler(ctx, node_proto, "onerror", 2);
|
|
690
|
+
define_element_handler(ctx, node_proto, "onchange", 4);
|
|
691
|
+
define_element_handler(ctx, node_proto, "oninput", 5);
|
|
692
|
+
define_element_handler(ctx, node_proto, "onsubmit", 6);
|
|
693
|
+
define_element_handler(ctx, node_proto, "onreset", 7);
|
|
690
694
|
JS_FreeValue(ctx, node_proto);
|
|
691
695
|
|
|
692
696
|
JSValue element_proto = JS_GetClassProto(ctx, js_element_class_id);
|
|
@@ -125,6 +125,77 @@ const char* kFormBootstrapScript = R"JS(
|
|
|
125
125
|
// Per spec, submit() bypasses the "submit" event and constraint validation.
|
|
126
126
|
// There is no real navigation in this sandbox, so this is intentionally inert.
|
|
127
127
|
};
|
|
128
|
+
Element.prototype.reset = function() {
|
|
129
|
+
if (!isFormElement(this)) return;
|
|
130
|
+
this.dispatchEvent(new Event('reset', { bubbles: true, cancelable: true }));
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// ── select.options / .selectedIndex / .selectedOptions ────────────────────
|
|
134
|
+
function isSelectElement(el) {
|
|
135
|
+
return !!el && el.tagName === 'SELECT';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function selectOptionsArray(select) {
|
|
139
|
+
return Array.prototype.slice.call(select.querySelectorAll('option'));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
Object.defineProperty(Element.prototype, 'options', {
|
|
143
|
+
configurable: true,
|
|
144
|
+
get: function() {
|
|
145
|
+
if (!isSelectElement(this)) return undefined;
|
|
146
|
+
var select = this;
|
|
147
|
+
var opts = selectOptionsArray(select);
|
|
148
|
+
opts.item = function(index) { return this[index] !== undefined ? this[index] : null; };
|
|
149
|
+
opts.namedItem = function(name) {
|
|
150
|
+
for (var i = 0; i < this.length; i++) {
|
|
151
|
+
if (this[i].id === name || this[i].getAttribute('name') === name) return this[i];
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
};
|
|
155
|
+
opts.add = function(option, before) {
|
|
156
|
+
var refNode = null;
|
|
157
|
+
if (typeof before === 'number') refNode = this[before] || null;
|
|
158
|
+
else if (before) refNode = before;
|
|
159
|
+
if (refNode) select.insertBefore(option, refNode);
|
|
160
|
+
else select.appendChild(option);
|
|
161
|
+
};
|
|
162
|
+
opts.remove = function(index) {
|
|
163
|
+
var target = this[index];
|
|
164
|
+
if (target) select.removeChild(target);
|
|
165
|
+
};
|
|
166
|
+
return opts;
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
Object.defineProperty(Element.prototype, 'selectedIndex', {
|
|
171
|
+
configurable: true,
|
|
172
|
+
get: function() {
|
|
173
|
+
if (!isSelectElement(this)) return -1;
|
|
174
|
+
var opts = selectOptionsArray(this);
|
|
175
|
+
for (var i = 0; i < opts.length; i++) {
|
|
176
|
+
if (opts[i].selected) return i;
|
|
177
|
+
}
|
|
178
|
+
return (!this.multiple && opts.length > 0) ? 0 : -1;
|
|
179
|
+
},
|
|
180
|
+
set: function(index) {
|
|
181
|
+
if (!isSelectElement(this)) return;
|
|
182
|
+
var opts = selectOptionsArray(this);
|
|
183
|
+
for (var i = 0; i < opts.length; i++) {
|
|
184
|
+
opts[i].selected = (i === index);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
Object.defineProperty(Element.prototype, 'selectedOptions', {
|
|
190
|
+
configurable: true,
|
|
191
|
+
get: function() {
|
|
192
|
+
if (!isSelectElement(this)) return undefined;
|
|
193
|
+
var opts = selectOptionsArray(this);
|
|
194
|
+
var selected = opts.filter(function(o) { return o.selected; });
|
|
195
|
+
if (selected.length === 0 && !this.multiple && opts.length > 0) return [opts[0]];
|
|
196
|
+
return selected;
|
|
197
|
+
},
|
|
198
|
+
});
|
|
128
199
|
|
|
129
200
|
// ── Constraint Validation API (ValidityState, checkValidity, ...) ─────────
|
|
130
201
|
// Covers the subset real-world CMS forms actually hit: required, pattern,
|