@salve-software/react-native-nitro-jsdom 1.0.1 → 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.
- package/android/CMakeLists.txt +18 -0
- package/cpp/HybridHtmlSandbox.cpp +20 -3
- package/cpp/HybridHtmlSandbox.hpp +1 -1
- package/cpp/lexbor/LexborDocument.cpp +279 -67
- package/cpp/lexbor/LexborDocument.hpp +43 -10
- package/cpp/quickjs/DOMBindings.cpp +37 -1
- package/cpp/quickjs/DOMBindingsInternal.cpp +101 -2
- package/cpp/quickjs/DOMBindingsInternal.hpp +56 -1
- package/cpp/quickjs/QuickJSRuntime.cpp +32 -4
- package/cpp/quickjs/QuickJSRuntime.hpp +50 -1
- package/cpp/quickjs/bindings/AbortBindings.cpp +128 -0
- package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
- package/cpp/quickjs/bindings/BlobBindings.cpp +164 -0
- package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
- package/cpp/quickjs/bindings/CSSOMBindings.cpp +290 -0
- package/cpp/quickjs/bindings/CSSOMBindings.hpp +25 -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/DOMParserBindings.cpp +299 -0
- package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +239 -4
- package/cpp/quickjs/bindings/ElementBindings.cpp +556 -44
- package/cpp/quickjs/bindings/EventBindings.cpp +414 -8
- package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
- package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
- package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
- package/cpp/quickjs/bindings/FetchBindings.cpp +45 -6
- package/cpp/quickjs/bindings/FormBindings.cpp +365 -0
- package/cpp/quickjs/bindings/FormBindings.hpp +20 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
- package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
- package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +228 -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 +120 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
- package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
- package/cpp/quickjs/bindings/UrlBindings.cpp +295 -0
- package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +552 -0
- package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
- 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 +17 -2
- package/src/classes/JSDOM/JSDOM.class.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +1 -1
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#include "WindowBindings.hpp"
|
|
2
|
+
#include "DOMExceptionBindings.hpp"
|
|
3
|
+
#include "EventBindings.hpp"
|
|
2
4
|
#include "../DOMBindingsInternal.hpp"
|
|
3
5
|
#include "../QuickJSRuntime.hpp"
|
|
6
|
+
#include <cstdlib>
|
|
4
7
|
#include <cstring>
|
|
5
8
|
#include <optional>
|
|
6
9
|
#include <string>
|
|
@@ -10,6 +13,162 @@ namespace margelo::nitro::nitrojsdom {
|
|
|
10
13
|
|
|
11
14
|
namespace {
|
|
12
15
|
|
|
16
|
+
// ── atob / btoa ────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const char kBase64Alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
19
|
+
|
|
20
|
+
int8_t base64_decode_char(unsigned char c) {
|
|
21
|
+
if (c >= 'A' && c <= 'Z') return c - 'A';
|
|
22
|
+
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
|
23
|
+
if (c >= '0' && c <= '9') return c - '0' + 52;
|
|
24
|
+
if (c == '+') return 62;
|
|
25
|
+
if (c == '/') return 63;
|
|
26
|
+
return -1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
bool is_ascii_whitespace(char c) {
|
|
30
|
+
return c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
JSValue js_window_btoa(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
34
|
+
if (argc < 1) return JS_ThrowTypeError(ctx, "btoa() requires 1 argument");
|
|
35
|
+
JSValue str_val = JS_ToString(ctx, argv[0]);
|
|
36
|
+
if (JS_IsException(str_val)) return JS_EXCEPTION;
|
|
37
|
+
size_t len = 0;
|
|
38
|
+
const char* s = JS_ToCStringLen(ctx, &len, str_val);
|
|
39
|
+
if (!s) { JS_FreeValue(ctx, str_val); return JS_EXCEPTION; }
|
|
40
|
+
|
|
41
|
+
// JS_ToCStringLen yields UTF-8 (length-aware, so embedded NULs survive);
|
|
42
|
+
// btoa operates on Latin1 code units, so decode the UTF-8 back into code
|
|
43
|
+
// points and reject anything outside 0x00-0xFF.
|
|
44
|
+
std::vector<uint8_t> bytes;
|
|
45
|
+
bytes.reserve(len);
|
|
46
|
+
bool out_of_range = false;
|
|
47
|
+
for (size_t i = 0; i < len && !out_of_range;) {
|
|
48
|
+
unsigned char c = static_cast<unsigned char>(s[i]);
|
|
49
|
+
uint32_t codepoint;
|
|
50
|
+
size_t seq_len;
|
|
51
|
+
if (c < 0x80) { codepoint = c; seq_len = 1; }
|
|
52
|
+
else if ((c & 0xE0) == 0xC0 && i + 1 < len) { codepoint = c & 0x1F; seq_len = 2; }
|
|
53
|
+
else if ((c & 0xF0) == 0xE0 && i + 2 < len) { codepoint = c & 0x0F; seq_len = 3; }
|
|
54
|
+
else { out_of_range = true; break; }
|
|
55
|
+
for (size_t k = 1; k < seq_len; k++) {
|
|
56
|
+
codepoint = (codepoint << 6) | (static_cast<unsigned char>(s[i + k]) & 0x3F);
|
|
57
|
+
}
|
|
58
|
+
if (codepoint > 0xFF) { out_of_range = true; break; }
|
|
59
|
+
bytes.push_back(static_cast<uint8_t>(codepoint));
|
|
60
|
+
i += seq_len;
|
|
61
|
+
}
|
|
62
|
+
JS_FreeCString(ctx, s);
|
|
63
|
+
JS_FreeValue(ctx, str_val);
|
|
64
|
+
|
|
65
|
+
if (out_of_range) {
|
|
66
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
67
|
+
"The string to be encoded contains characters outside of the Latin1 range.");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
std::string out;
|
|
71
|
+
out.reserve(((bytes.size() + 2) / 3) * 4);
|
|
72
|
+
size_t i = 0;
|
|
73
|
+
while (i + 3 <= bytes.size()) {
|
|
74
|
+
uint32_t n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
|
|
75
|
+
out += kBase64Alphabet[(n >> 18) & 0x3F];
|
|
76
|
+
out += kBase64Alphabet[(n >> 12) & 0x3F];
|
|
77
|
+
out += kBase64Alphabet[(n >> 6) & 0x3F];
|
|
78
|
+
out += kBase64Alphabet[n & 0x3F];
|
|
79
|
+
i += 3;
|
|
80
|
+
}
|
|
81
|
+
size_t remaining = bytes.size() - i;
|
|
82
|
+
if (remaining == 1) {
|
|
83
|
+
uint32_t n = bytes[i] << 16;
|
|
84
|
+
out += kBase64Alphabet[(n >> 18) & 0x3F];
|
|
85
|
+
out += kBase64Alphabet[(n >> 12) & 0x3F];
|
|
86
|
+
out += "==";
|
|
87
|
+
} else if (remaining == 2) {
|
|
88
|
+
uint32_t n = (bytes[i] << 16) | (bytes[i + 1] << 8);
|
|
89
|
+
out += kBase64Alphabet[(n >> 18) & 0x3F];
|
|
90
|
+
out += kBase64Alphabet[(n >> 12) & 0x3F];
|
|
91
|
+
out += kBase64Alphabet[(n >> 6) & 0x3F];
|
|
92
|
+
out += "=";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return JS_NewStringLen(ctx, out.c_str(), out.size());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
JSValue js_window_atob(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
99
|
+
if (argc < 1) return JS_ThrowTypeError(ctx, "atob() requires 1 argument");
|
|
100
|
+
JSValue str_val = JS_ToString(ctx, argv[0]);
|
|
101
|
+
if (JS_IsException(str_val)) return JS_EXCEPTION;
|
|
102
|
+
size_t len = 0;
|
|
103
|
+
const char* s = JS_ToCStringLen(ctx, &len, str_val);
|
|
104
|
+
if (!s) { JS_FreeValue(ctx, str_val); return JS_EXCEPTION; }
|
|
105
|
+
|
|
106
|
+
std::string data;
|
|
107
|
+
data.reserve(len);
|
|
108
|
+
for (size_t i = 0; i < len; i++) {
|
|
109
|
+
if (!is_ascii_whitespace(s[i])) data += s[i];
|
|
110
|
+
}
|
|
111
|
+
JS_FreeCString(ctx, s);
|
|
112
|
+
JS_FreeValue(ctx, str_val);
|
|
113
|
+
|
|
114
|
+
if (data.size() % 4 == 0) {
|
|
115
|
+
if (data.size() >= 2 && data[data.size() - 1] == '=' && data[data.size() - 2] == '=') {
|
|
116
|
+
data.resize(data.size() - 2);
|
|
117
|
+
} else if (!data.empty() && data.back() == '=') {
|
|
118
|
+
data.resize(data.size() - 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (data.size() % 4 == 1) {
|
|
123
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
124
|
+
"The string to be decoded is not correctly encoded.");
|
|
125
|
+
}
|
|
126
|
+
for (char c : data) {
|
|
127
|
+
if (base64_decode_char(static_cast<unsigned char>(c)) < 0) {
|
|
128
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
129
|
+
"The string to be decoded is not correctly encoded.");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
std::string out;
|
|
134
|
+
out.reserve((data.size() / 4) * 3 + 3);
|
|
135
|
+
size_t i = 0;
|
|
136
|
+
while (i + 4 <= data.size()) {
|
|
137
|
+
uint32_t n = (base64_decode_char(data[i]) << 18) | (base64_decode_char(data[i + 1]) << 12) |
|
|
138
|
+
(base64_decode_char(data[i + 2]) << 6) | base64_decode_char(data[i + 3]);
|
|
139
|
+
out += static_cast<char>((n >> 16) & 0xFF);
|
|
140
|
+
out += static_cast<char>((n >> 8) & 0xFF);
|
|
141
|
+
out += static_cast<char>(n & 0xFF);
|
|
142
|
+
i += 4;
|
|
143
|
+
}
|
|
144
|
+
size_t remaining = data.size() - i;
|
|
145
|
+
if (remaining == 2) {
|
|
146
|
+
uint32_t n = (base64_decode_char(data[i]) << 18) | (base64_decode_char(data[i + 1]) << 12);
|
|
147
|
+
out += static_cast<char>((n >> 16) & 0xFF);
|
|
148
|
+
} else if (remaining == 3) {
|
|
149
|
+
uint32_t n = (base64_decode_char(data[i]) << 18) | (base64_decode_char(data[i + 1]) << 12) |
|
|
150
|
+
(base64_decode_char(data[i + 2]) << 6);
|
|
151
|
+
out += static_cast<char>((n >> 16) & 0xFF);
|
|
152
|
+
out += static_cast<char>((n >> 8) & 0xFF);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// atob's result is a "binary string" — each output char code is one decoded
|
|
156
|
+
// byte (0x00-0xFF). JS_NewStringLen expects UTF-8 input, so each byte must
|
|
157
|
+
// be re-encoded as the UTF-8 sequence for that same code point (1 byte for
|
|
158
|
+
// 0x00-0x7F, 2 bytes for 0x80-0xFF) to round-trip through QuickJS correctly.
|
|
159
|
+
std::string utf8;
|
|
160
|
+
utf8.reserve(out.size() * 2);
|
|
161
|
+
for (unsigned char byte : out) {
|
|
162
|
+
if (byte < 0x80) {
|
|
163
|
+
utf8 += static_cast<char>(byte);
|
|
164
|
+
} else {
|
|
165
|
+
utf8 += static_cast<char>(0xC0 | (byte >> 6));
|
|
166
|
+
utf8 += static_cast<char>(0x80 | (byte & 0x3F));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return JS_NewStringLen(ctx, utf8.c_str(), utf8.size());
|
|
170
|
+
}
|
|
171
|
+
|
|
13
172
|
// ── console ────────────────────────────────────────────────────────────────
|
|
14
173
|
|
|
15
174
|
JSValue js_console_method(JSContext* ctx, JSValue, int argc, JSValue* argv, int magic) {
|
|
@@ -33,6 +192,23 @@ JSValue js_console_method(JSContext* ctx, JSValue, int argc, JSValue* argv, int
|
|
|
33
192
|
return JS_UNDEFINED;
|
|
34
193
|
}
|
|
35
194
|
|
|
195
|
+
JSValue js_window_reportError(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
196
|
+
JSValue error_value = argc >= 1 ? argv[0] : JS_UNDEFINED;
|
|
197
|
+
std::string message = "Uncaught";
|
|
198
|
+
if (argc >= 1) {
|
|
199
|
+
JSValue msg_prop = JS_GetPropertyStr(ctx, argv[0], "message");
|
|
200
|
+
if (!JS_IsException(msg_prop) && !JS_IsUndefined(msg_prop)) {
|
|
201
|
+
const char* s = JS_ToCString(ctx, msg_prop);
|
|
202
|
+
if (s) { message = s; JS_FreeCString(ctx, s); }
|
|
203
|
+
} else if (JS_IsException(msg_prop)) {
|
|
204
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
205
|
+
}
|
|
206
|
+
JS_FreeValue(ctx, msg_prop);
|
|
207
|
+
}
|
|
208
|
+
EventBindings::dispatchErrorEvent(ctx, message, error_value);
|
|
209
|
+
return JS_UNDEFINED;
|
|
210
|
+
}
|
|
211
|
+
|
|
36
212
|
// ── window.alert / confirm / prompt ───────────────────────────────────────────
|
|
37
213
|
|
|
38
214
|
JSValue js_window_alert(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
@@ -181,6 +357,329 @@ const char* kLocationBootstrapScript = R"JS(
|
|
|
181
357
|
})();
|
|
182
358
|
)JS";
|
|
183
359
|
|
|
360
|
+
// ── crypto.getRandomValues ────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
JSValue js_native_random_bytes(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
363
|
+
int32_t count = 0;
|
|
364
|
+
if (argc >= 1) JS_ToInt32(ctx, &count, argv[0]);
|
|
365
|
+
if (count < 0) count = 0;
|
|
366
|
+
|
|
367
|
+
std::vector<uint8_t> bytes(static_cast<size_t>(count));
|
|
368
|
+
if (count > 0) arc4random_buf(bytes.data(), bytes.size());
|
|
369
|
+
|
|
370
|
+
JSValue arr = JS_NewArray(ctx);
|
|
371
|
+
for (int32_t i = 0; i < count; i++) {
|
|
372
|
+
JS_SetPropertyUint32(ctx, arr, static_cast<uint32_t>(i), JS_NewInt32(ctx, bytes[static_cast<size_t>(i)]));
|
|
373
|
+
}
|
|
374
|
+
return arr;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const char* kCryptoBootstrapScript = R"JS(
|
|
378
|
+
(function() {
|
|
379
|
+
globalThis.crypto = globalThis.crypto || {};
|
|
380
|
+
globalThis.crypto.getRandomValues = function(typedArray) {
|
|
381
|
+
if (!typedArray || typeof typedArray.byteLength !== 'number' || typedArray instanceof BigInt64Array || typedArray instanceof BigUint64Array) {
|
|
382
|
+
throw new TypeError('crypto.getRandomValues() requires an integer TypedArray argument');
|
|
383
|
+
}
|
|
384
|
+
if (typedArray.byteLength > 65536) {
|
|
385
|
+
var err = new Error('crypto.getRandomValues() can only fill up to 65536 bytes at a time');
|
|
386
|
+
err.name = 'QuotaExceededError';
|
|
387
|
+
throw err;
|
|
388
|
+
}
|
|
389
|
+
var bytes = __nativeRandomBytes(typedArray.byteLength);
|
|
390
|
+
var view = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength);
|
|
391
|
+
for (var i = 0; i < bytes.length; i++) view[i] = bytes[i];
|
|
392
|
+
return typedArray;
|
|
393
|
+
};
|
|
394
|
+
globalThis.crypto.randomUUID = function() {
|
|
395
|
+
var bytes = __nativeRandomBytes(16);
|
|
396
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
397
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
398
|
+
var hex = [];
|
|
399
|
+
for (var i = 0; i < 16; i++) {
|
|
400
|
+
var h = bytes[i].toString(16);
|
|
401
|
+
hex.push(h.length === 1 ? '0' + h : h);
|
|
402
|
+
}
|
|
403
|
+
return hex.slice(0, 4).join('') + '-' + hex.slice(4, 6).join('') + '-' +
|
|
404
|
+
hex.slice(6, 8).join('') + '-' + hex.slice(8, 10).join('') + '-' + hex.slice(10, 16).join('');
|
|
405
|
+
};
|
|
406
|
+
})();
|
|
407
|
+
)JS";
|
|
408
|
+
|
|
409
|
+
// ── console ergonomics (group/table/assert/trace/count) ──────────────────────
|
|
410
|
+
// Layered in pure JS on top of the native log/warn/error/info/debug methods
|
|
411
|
+
// above rather than adding new native levels — none of these need anything
|
|
412
|
+
// the onConsole callback can't already receive as stringified args.
|
|
413
|
+
|
|
414
|
+
const char* kConsoleExtrasBootstrapScript = R"JS(
|
|
415
|
+
(function() {
|
|
416
|
+
var counts = Object.create(null);
|
|
417
|
+
|
|
418
|
+
console.group = function() {
|
|
419
|
+
console.log.apply(console, arguments);
|
|
420
|
+
};
|
|
421
|
+
console.groupCollapsed = console.group;
|
|
422
|
+
console.groupEnd = function() {};
|
|
423
|
+
|
|
424
|
+
console.trace = function() {
|
|
425
|
+
var args = Array.prototype.slice.call(arguments);
|
|
426
|
+
args.unshift('Trace:');
|
|
427
|
+
console.log.apply(console, args);
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
console.assert = function(condition) {
|
|
431
|
+
if (condition) return;
|
|
432
|
+
var args = Array.prototype.slice.call(arguments, 1);
|
|
433
|
+
args.unshift('Assertion failed:');
|
|
434
|
+
console.error.apply(console, args);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
console.table = function(data) {
|
|
438
|
+
try {
|
|
439
|
+
console.log(JSON.stringify(data));
|
|
440
|
+
} catch (e) {
|
|
441
|
+
console.log(String(data));
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
console.count = function(label) {
|
|
446
|
+
label = label === undefined ? 'default' : String(label);
|
|
447
|
+
counts[label] = (counts[label] || 0) + 1;
|
|
448
|
+
console.log(label + ': ' + counts[label]);
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
console.countReset = function(label) {
|
|
452
|
+
label = label === undefined ? 'default' : String(label);
|
|
453
|
+
counts[label] = 0;
|
|
454
|
+
};
|
|
455
|
+
})();
|
|
456
|
+
)JS";
|
|
457
|
+
|
|
458
|
+
// ── navigator ──────────────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
const char* kNavigatorBootstrapScript = R"JS(
|
|
461
|
+
(function() {
|
|
462
|
+
var navigator = {
|
|
463
|
+
userAgent: 'Mozilla/5.0 (ReactNative) react-native-nitro-jsdom',
|
|
464
|
+
platform: 'ReactNative',
|
|
465
|
+
language: 'en-US',
|
|
466
|
+
languages: ['en-US'],
|
|
467
|
+
onLine: true,
|
|
468
|
+
cookieEnabled: false,
|
|
469
|
+
hardwareConcurrency: 1,
|
|
470
|
+
};
|
|
471
|
+
navigator.sendBeacon = function(url, data) {
|
|
472
|
+
try {
|
|
473
|
+
fetch(url, { method: 'POST', body: data }).catch(function() {});
|
|
474
|
+
} catch (e) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
return true;
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// jsdom itself doesn't implement the Clipboard API at all (no OS clipboard
|
|
481
|
+
// to back it, same reasoning as window.history/getSelection). This is an
|
|
482
|
+
// in-memory stand-in purely so 'copy discount code'-style embedded widgets
|
|
483
|
+
// that call navigator.clipboard.writeText() directly don't throw — reads
|
|
484
|
+
// back whatever was last written, nothing more.
|
|
485
|
+
var clipboardText = '';
|
|
486
|
+
navigator.clipboard = {
|
|
487
|
+
writeText: function(text) {
|
|
488
|
+
clipboardText = String(text);
|
|
489
|
+
return Promise.resolve();
|
|
490
|
+
},
|
|
491
|
+
readText: function() {
|
|
492
|
+
return Promise.resolve(clipboardText);
|
|
493
|
+
},
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
globalThis.navigator = navigator;
|
|
497
|
+
})();
|
|
498
|
+
)JS";
|
|
499
|
+
|
|
500
|
+
// ── structuredClone ────────────────────────────────────────────────────────
|
|
501
|
+
|
|
502
|
+
const char* kStructuredCloneBootstrapScript = R"JS(
|
|
503
|
+
(function() {
|
|
504
|
+
globalThis.structuredClone = function(value) {
|
|
505
|
+
var seen = new Map();
|
|
506
|
+
function clone(v) {
|
|
507
|
+
if (v === null || typeof v !== 'object') {
|
|
508
|
+
if (typeof v === 'function' || typeof v === 'symbol') {
|
|
509
|
+
throw new DOMException(String(v) + ' could not be cloned.', 'DataCloneError');
|
|
510
|
+
}
|
|
511
|
+
return v;
|
|
512
|
+
}
|
|
513
|
+
if (seen.has(v)) return seen.get(v);
|
|
514
|
+
if (v instanceof Date) return new Date(v.getTime());
|
|
515
|
+
if (v instanceof RegExp) return new RegExp(v.source, v.flags);
|
|
516
|
+
if (Array.isArray(v)) {
|
|
517
|
+
var arr = [];
|
|
518
|
+
seen.set(v, arr);
|
|
519
|
+
for (var i = 0; i < v.length; i++) arr.push(clone(v[i]));
|
|
520
|
+
return arr;
|
|
521
|
+
}
|
|
522
|
+
if (v instanceof Map) {
|
|
523
|
+
var m = new Map();
|
|
524
|
+
seen.set(v, m);
|
|
525
|
+
v.forEach(function(val, key) { m.set(clone(key), clone(val)); });
|
|
526
|
+
return m;
|
|
527
|
+
}
|
|
528
|
+
if (v instanceof Set) {
|
|
529
|
+
var s = new Set();
|
|
530
|
+
seen.set(v, s);
|
|
531
|
+
v.forEach(function(val) { s.add(clone(val)); });
|
|
532
|
+
return s;
|
|
533
|
+
}
|
|
534
|
+
if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView && ArrayBuffer.isView(v)) {
|
|
535
|
+
return new v.constructor(v);
|
|
536
|
+
}
|
|
537
|
+
if (typeof ArrayBuffer !== 'undefined' && v instanceof ArrayBuffer) {
|
|
538
|
+
return v.slice(0);
|
|
539
|
+
}
|
|
540
|
+
var proto = Object.getPrototypeOf(v);
|
|
541
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
542
|
+
throw new DOMException('The object could not be cloned.', 'DataCloneError');
|
|
543
|
+
}
|
|
544
|
+
var out = {};
|
|
545
|
+
seen.set(v, out);
|
|
546
|
+
for (var key in v) {
|
|
547
|
+
if (Object.prototype.hasOwnProperty.call(v, key)) out[key] = clone(v[key]);
|
|
548
|
+
}
|
|
549
|
+
return out;
|
|
550
|
+
}
|
|
551
|
+
return clone(value);
|
|
552
|
+
};
|
|
553
|
+
})();
|
|
554
|
+
)JS";
|
|
555
|
+
|
|
556
|
+
// ── window.history ────────────────────────────────────────────────────────
|
|
557
|
+
// No real page navigation exists in this sandbox, so History tracks its own
|
|
558
|
+
// in-memory entry stack rather than session history. pushState/replaceState
|
|
559
|
+
// never fire popstate (per spec); go/back/forward do, since those are the
|
|
560
|
+
// events CMS-widget routers actually listen for. `title` is accepted but
|
|
561
|
+
// ignored (same as jsdom — nothing renders a document title). State is
|
|
562
|
+
// structuredClone()'d on write (per spec — matches pushState/replaceState's
|
|
563
|
+
// documented "serializable" semantics), so callers mutating their object
|
|
564
|
+
// after the call can't leak into a stored entry. `go()`'s delta is truncated
|
|
565
|
+
// to an integer so a fractional/NaN input can't produce a non-integer
|
|
566
|
+
// `_index`.
|
|
567
|
+
|
|
568
|
+
const char* kHistoryBootstrapScript = R"JS(
|
|
569
|
+
(function() {
|
|
570
|
+
function History() {
|
|
571
|
+
this._entries = [null];
|
|
572
|
+
this._index = 0;
|
|
573
|
+
}
|
|
574
|
+
Object.defineProperty(History.prototype, 'length', {
|
|
575
|
+
get: function() { return this._entries.length; },
|
|
576
|
+
enumerable: true,
|
|
577
|
+
});
|
|
578
|
+
Object.defineProperty(History.prototype, 'state', {
|
|
579
|
+
get: function() { return this._entries[this._index]; },
|
|
580
|
+
enumerable: true,
|
|
581
|
+
});
|
|
582
|
+
function cloneState(state) {
|
|
583
|
+
return state === undefined ? null : structuredClone(state);
|
|
584
|
+
}
|
|
585
|
+
History.prototype.pushState = function(state, _title, url) {
|
|
586
|
+
this._entries = this._entries.slice(0, this._index + 1);
|
|
587
|
+
this._entries.push(cloneState(state));
|
|
588
|
+
this._index++;
|
|
589
|
+
if (url !== undefined && url !== null) location.href = url;
|
|
590
|
+
};
|
|
591
|
+
History.prototype.replaceState = function(state, _title, url) {
|
|
592
|
+
this._entries[this._index] = cloneState(state);
|
|
593
|
+
if (url !== undefined && url !== null) location.href = url;
|
|
594
|
+
};
|
|
595
|
+
History.prototype.go = function(delta) {
|
|
596
|
+
delta = delta === undefined ? 0 : Math.trunc(Number(delta) || 0);
|
|
597
|
+
if (delta === 0) return;
|
|
598
|
+
var next = this._index + delta;
|
|
599
|
+
if (next < 0 || next > this._entries.length - 1) return;
|
|
600
|
+
this._index = next;
|
|
601
|
+
var ev = new Event('popstate');
|
|
602
|
+
ev.state = this._entries[this._index];
|
|
603
|
+
dispatchEvent(ev);
|
|
604
|
+
};
|
|
605
|
+
History.prototype.back = function() { this.go(-1); };
|
|
606
|
+
History.prototype.forward = function() { this.go(1); };
|
|
607
|
+
|
|
608
|
+
globalThis.History = History;
|
|
609
|
+
globalThis.history = new History();
|
|
610
|
+
})();
|
|
611
|
+
)JS";
|
|
612
|
+
|
|
613
|
+
// ── window.getSelection ──────────────────────────────────────────────────
|
|
614
|
+
// No layout engine backs this sandbox, so there is nothing to select — this
|
|
615
|
+
// mirrors matchMedia's stance: an always-empty Selection rather than a
|
|
616
|
+
// missing global, so defensive `window.getSelection().toString()` checks in
|
|
617
|
+
// third-party scripts don't crash the sandbox.
|
|
618
|
+
|
|
619
|
+
const char* kSelectionBootstrapScript = R"JS(
|
|
620
|
+
(function() {
|
|
621
|
+
function Selection() {}
|
|
622
|
+
Object.defineProperty(Selection.prototype, 'rangeCount', { get: function() { return 0; }, enumerable: true });
|
|
623
|
+
Object.defineProperty(Selection.prototype, 'isCollapsed', { get: function() { return true; }, enumerable: true });
|
|
624
|
+
Object.defineProperty(Selection.prototype, 'anchorNode', { get: function() { return null; }, enumerable: true });
|
|
625
|
+
Object.defineProperty(Selection.prototype, 'anchorOffset', { get: function() { return 0; }, enumerable: true });
|
|
626
|
+
Object.defineProperty(Selection.prototype, 'focusNode', { get: function() { return null; }, enumerable: true });
|
|
627
|
+
Object.defineProperty(Selection.prototype, 'focusOffset', { get: function() { return 0; }, enumerable: true });
|
|
628
|
+
Object.defineProperty(Selection.prototype, 'type', { get: function() { return 'None'; }, enumerable: true });
|
|
629
|
+
Selection.prototype.toString = function() { return ''; };
|
|
630
|
+
Selection.prototype.removeAllRanges = function() {};
|
|
631
|
+
Selection.prototype.collapse = function() {};
|
|
632
|
+
Selection.prototype.collapseToStart = function() {};
|
|
633
|
+
Selection.prototype.collapseToEnd = function() {};
|
|
634
|
+
Selection.prototype.selectAllChildren = function() {};
|
|
635
|
+
Selection.prototype.addRange = function() {};
|
|
636
|
+
Selection.prototype.containsNode = function() { return false; };
|
|
637
|
+
Selection.prototype.getRangeAt = function(index) {
|
|
638
|
+
throw new DOMException(
|
|
639
|
+
"Failed to execute 'getRangeAt' on 'Selection': " + index + ' is not a valid index.', 'IndexSizeError');
|
|
640
|
+
};
|
|
641
|
+
Selection.prototype.empty = Selection.prototype.removeAllRanges;
|
|
642
|
+
|
|
643
|
+
var selectionInstance = new Selection();
|
|
644
|
+
globalThis.Selection = Selection;
|
|
645
|
+
globalThis.getSelection = function() { return selectionInstance; };
|
|
646
|
+
})();
|
|
647
|
+
)JS";
|
|
648
|
+
|
|
649
|
+
// ── matchMedia ─────────────────────────────────────────────────────────────
|
|
650
|
+
// No layout engine backs this sandbox, so every query reports "no match" —
|
|
651
|
+
// mirrors jsdom's own stance that layout-dependent APIs return inert defaults
|
|
652
|
+
// rather than throwing.
|
|
653
|
+
|
|
654
|
+
const char* kMatchMediaBootstrapScript = R"JS(
|
|
655
|
+
(function() {
|
|
656
|
+
function MediaQueryList(media) {
|
|
657
|
+
this.media = String(media === undefined ? '' : media);
|
|
658
|
+
this.matches = false;
|
|
659
|
+
this.onchange = null;
|
|
660
|
+
this._listeners = [];
|
|
661
|
+
}
|
|
662
|
+
MediaQueryList.prototype.addListener = function(cb) {
|
|
663
|
+
if (typeof cb === 'function' && this._listeners.indexOf(cb) === -1) this._listeners.push(cb);
|
|
664
|
+
};
|
|
665
|
+
MediaQueryList.prototype.removeListener = function(cb) {
|
|
666
|
+
var idx = this._listeners.indexOf(cb);
|
|
667
|
+
if (idx !== -1) this._listeners.splice(idx, 1);
|
|
668
|
+
};
|
|
669
|
+
MediaQueryList.prototype.addEventListener = function(type, cb) {
|
|
670
|
+
if (type === 'change') this.addListener(cb);
|
|
671
|
+
};
|
|
672
|
+
MediaQueryList.prototype.removeEventListener = function(type, cb) {
|
|
673
|
+
if (type === 'change') this.removeListener(cb);
|
|
674
|
+
};
|
|
675
|
+
MediaQueryList.prototype.dispatchEvent = function() { return true; };
|
|
676
|
+
|
|
677
|
+
globalThis.matchMedia = function(query) {
|
|
678
|
+
return new MediaQueryList(query);
|
|
679
|
+
};
|
|
680
|
+
})();
|
|
681
|
+
)JS";
|
|
682
|
+
|
|
184
683
|
} // namespace
|
|
185
684
|
|
|
186
685
|
void WindowBindings::install(JSContext* ctx) {
|
|
@@ -189,6 +688,9 @@ void WindowBindings::install(JSContext* ctx) {
|
|
|
189
688
|
JS_SetPropertyStr(ctx, global, "alert", JS_NewCFunction(ctx, js_window_alert, "alert", 1));
|
|
190
689
|
JS_SetPropertyStr(ctx, global, "confirm", JS_NewCFunction(ctx, js_window_confirm, "confirm", 1));
|
|
191
690
|
JS_SetPropertyStr(ctx, global, "prompt", JS_NewCFunction(ctx, js_window_prompt, "prompt", 2));
|
|
691
|
+
JS_SetPropertyStr(ctx, global, "reportError", JS_NewCFunction(ctx, js_window_reportError, "reportError", 1));
|
|
692
|
+
JS_SetPropertyStr(ctx, global, "atob", JS_NewCFunction(ctx, js_window_atob, "atob", 1));
|
|
693
|
+
JS_SetPropertyStr(ctx, global, "btoa", JS_NewCFunction(ctx, js_window_btoa, "btoa", 1));
|
|
192
694
|
|
|
193
695
|
JSValue console_obj = JS_NewObject(ctx);
|
|
194
696
|
JS_SetPropertyStr(ctx, console_obj, "log", JS_NewCFunctionMagic(ctx, js_console_method, "log", 0, JS_CFUNC_generic_magic, 0));
|
|
@@ -197,6 +699,7 @@ void WindowBindings::install(JSContext* ctx) {
|
|
|
197
699
|
JS_SetPropertyStr(ctx, console_obj, "info", JS_NewCFunctionMagic(ctx, js_console_method, "info", 0, JS_CFUNC_generic_magic, 3));
|
|
198
700
|
JS_SetPropertyStr(ctx, console_obj, "debug", JS_NewCFunctionMagic(ctx, js_console_method, "debug", 0, JS_CFUNC_generic_magic, 4));
|
|
199
701
|
JS_SetPropertyStr(ctx, global, "console", console_obj);
|
|
702
|
+
JS_SetPropertyStr(ctx, global, "__nativeRandomBytes", JS_NewCFunction(ctx, js_native_random_bytes, "__nativeRandomBytes", 1));
|
|
200
703
|
|
|
201
704
|
JS_FreeValue(ctx, global);
|
|
202
705
|
|
|
@@ -206,6 +709,55 @@ void WindowBindings::install(JSContext* ctx) {
|
|
|
206
709
|
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
207
710
|
}
|
|
208
711
|
JS_FreeValue(ctx, location_result);
|
|
712
|
+
|
|
713
|
+
JSValue history_result = JS_Eval(ctx, kHistoryBootstrapScript, strlen(kHistoryBootstrapScript),
|
|
714
|
+
"<history-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
715
|
+
if (JS_IsException(history_result)) {
|
|
716
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
717
|
+
}
|
|
718
|
+
JS_FreeValue(ctx, history_result);
|
|
719
|
+
|
|
720
|
+
JSValue selection_result = JS_Eval(ctx, kSelectionBootstrapScript, strlen(kSelectionBootstrapScript),
|
|
721
|
+
"<selection-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
722
|
+
if (JS_IsException(selection_result)) {
|
|
723
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
724
|
+
}
|
|
725
|
+
JS_FreeValue(ctx, selection_result);
|
|
726
|
+
|
|
727
|
+
JSValue console_extras_result = JS_Eval(ctx, kConsoleExtrasBootstrapScript, strlen(kConsoleExtrasBootstrapScript),
|
|
728
|
+
"<console-extras-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
729
|
+
if (JS_IsException(console_extras_result)) {
|
|
730
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
731
|
+
}
|
|
732
|
+
JS_FreeValue(ctx, console_extras_result);
|
|
733
|
+
|
|
734
|
+
JSValue crypto_result = JS_Eval(ctx, kCryptoBootstrapScript, strlen(kCryptoBootstrapScript),
|
|
735
|
+
"<crypto-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
736
|
+
if (JS_IsException(crypto_result)) {
|
|
737
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
738
|
+
}
|
|
739
|
+
JS_FreeValue(ctx, crypto_result);
|
|
740
|
+
|
|
741
|
+
JSValue navigator_result = JS_Eval(ctx, kNavigatorBootstrapScript, strlen(kNavigatorBootstrapScript),
|
|
742
|
+
"<navigator-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
743
|
+
if (JS_IsException(navigator_result)) {
|
|
744
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
745
|
+
}
|
|
746
|
+
JS_FreeValue(ctx, navigator_result);
|
|
747
|
+
|
|
748
|
+
JSValue match_media_result = JS_Eval(ctx, kMatchMediaBootstrapScript, strlen(kMatchMediaBootstrapScript),
|
|
749
|
+
"<match-media-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
750
|
+
if (JS_IsException(match_media_result)) {
|
|
751
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
752
|
+
}
|
|
753
|
+
JS_FreeValue(ctx, match_media_result);
|
|
754
|
+
|
|
755
|
+
JSValue structured_clone_result = JS_Eval(ctx, kStructuredCloneBootstrapScript, strlen(kStructuredCloneBootstrapScript),
|
|
756
|
+
"<structured-clone-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
757
|
+
if (JS_IsException(structured_clone_result)) {
|
|
758
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
759
|
+
}
|
|
760
|
+
JS_FreeValue(ctx, structured_clone_result);
|
|
209
761
|
}
|
|
210
762
|
|
|
211
763
|
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
3
|
// window.alert/confirm/prompt (bridged to onAlert/onConfirm/onPrompt native
|
|
4
|
-
// callbacks), console.log/warn/error/info/debug, and the window.location
|
|
5
|
-
//
|
|
4
|
+
// callbacks), console.log/warn/error/info/debug, and the window.location,
|
|
5
|
+
// window.history, window.getSelection, crypto, navigator, matchMedia, and
|
|
6
|
+
// structuredClone bootstrap scripts.
|
|
6
7
|
|
|
7
8
|
#include "quickjs.h"
|
|
8
9
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#include "XmlSerializerBindings.hpp"
|
|
2
|
+
#include "../DOMBindingsInternal.hpp"
|
|
3
|
+
#include <string>
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::nitrojsdom {
|
|
6
|
+
|
|
7
|
+
namespace {
|
|
8
|
+
|
|
9
|
+
JSClassID js_xml_serializer_class_id = 0;
|
|
10
|
+
JSClassDef js_xml_serializer_class = { "XMLSerializer", .finalizer = nullptr };
|
|
11
|
+
|
|
12
|
+
JSValue js_xmlserializer_constructor(JSContext* ctx, JSValue, int, JSValue*) {
|
|
13
|
+
return JS_NewObjectClass(ctx, js_xml_serializer_class_id);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
JSValue js_xmlserializer_serializeToString(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
17
|
+
if (argc < 1) return JS_NewString(ctx, "");
|
|
18
|
+
auto* node = unwrap_node(ctx, argv[0]);
|
|
19
|
+
if (!node) return JS_NewString(ctx, "");
|
|
20
|
+
std::string result = serialize_node(node);
|
|
21
|
+
return JS_NewStringLen(ctx, result.data(), result.size());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
} // namespace
|
|
25
|
+
|
|
26
|
+
void XmlSerializerBindings::install(JSContext* ctx) {
|
|
27
|
+
if (js_xml_serializer_class_id == 0) JS_NewClassID(&js_xml_serializer_class_id);
|
|
28
|
+
JS_NewClass(JS_GetRuntime(ctx), js_xml_serializer_class_id, &js_xml_serializer_class);
|
|
29
|
+
|
|
30
|
+
JSValue proto = JS_NewObject(ctx);
|
|
31
|
+
JS_SetPropertyStr(ctx, proto, "serializeToString",
|
|
32
|
+
JS_NewCFunction(ctx, js_xmlserializer_serializeToString, "serializeToString", 1));
|
|
33
|
+
JS_SetClassProto(ctx, js_xml_serializer_class_id, JS_DupValue(ctx, proto));
|
|
34
|
+
|
|
35
|
+
JSValue ctor = JS_NewCFunction2(ctx, js_xmlserializer_constructor, "XMLSerializer", 0, JS_CFUNC_constructor, 0);
|
|
36
|
+
JS_SetConstructor(ctx, ctor, proto);
|
|
37
|
+
JS_FreeValue(ctx, proto);
|
|
38
|
+
|
|
39
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
40
|
+
JS_SetPropertyStr(ctx, global, "XMLSerializer", ctor);
|
|
41
|
+
JS_FreeValue(ctx, global);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::nitrojsdom {
|
|
6
|
+
|
|
7
|
+
// Registers globalThis.XMLSerializer with serializeToString(node), backed by
|
|
8
|
+
// the same serialize_node() helper Element.prototype.outerHTML already uses
|
|
9
|
+
// (DOMBindingsInternal.hpp) — serializes the given node and its subtree.
|
|
10
|
+
//
|
|
11
|
+
// Scope: only serializeToString(node) is implemented (DOMParser.parseFromString
|
|
12
|
+
// is out of scope — see docs/overview.md's v0.10 notes, it would need a real
|
|
13
|
+
// second Document). `node` must be a wrapped Element/Node/ShadowRoot (i.e. not
|
|
14
|
+
// `document` itself, which is a plain JS object with no native node behind
|
|
15
|
+
// it) — pass document.documentElement instead, same as any other subtree
|
|
16
|
+
// serialization in this sandbox.
|
|
17
|
+
//
|
|
18
|
+
// Must run after ElementBindings (unwrap_node/serialize_node need
|
|
19
|
+
// js_element_class_id/js_node_class_id to exist).
|
|
20
|
+
struct XmlSerializerBindings {
|
|
21
|
+
static void install(JSContext* ctx);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -39,7 +39,7 @@ class JSDOM {
|
|
|
39
39
|
*/
|
|
40
40
|
static create(html, options) {
|
|
41
41
|
const sandbox = _reactNativeNitroModules.NitroModules.createHybridObject('HtmlSandbox');
|
|
42
|
-
sandbox.initialize(html, options?.runScripts ?? true, options?.url ?? 'about:blank');
|
|
42
|
+
sandbox.initialize(html, options?.runScripts ?? true, options?.url ?? 'about:blank', options?.pretendToBeVisual ?? false);
|
|
43
43
|
if (options?.onConsole) {
|
|
44
44
|
const cb = options.onConsole;
|
|
45
45
|
sandbox.setConsoleCallback((level, args) => cb(level, args));
|