@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
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#include "DOMParserBindings.hpp"
|
|
2
|
+
#include "../DOMBindingsInternal.hpp"
|
|
3
|
+
#include "../QuickJSRuntime.hpp"
|
|
4
|
+
#include "../../lexbor/LexborDocument.hpp"
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace margelo::nitro::nitrojsdom {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
JSClassID js_parsed_document_class_id = 0;
|
|
13
|
+
JSClassDef js_parsed_document_class = { "Document", .finalizer = nullptr };
|
|
14
|
+
|
|
15
|
+
LexborDocument* unwrap_parsed_doc(JSContext* ctx, JSValue val) {
|
|
16
|
+
return static_cast<LexborDocument*>(JS_GetOpaque(val, js_parsed_document_class_id));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
JSValue make_parsed_document(JSContext* ctx, LexborDocument* doc) {
|
|
20
|
+
register_document(ctx, doc);
|
|
21
|
+
JSValue obj = JS_NewObjectClass(ctx, js_parsed_document_class_id);
|
|
22
|
+
JS_SetOpaque(obj, doc);
|
|
23
|
+
register_document_wrapper(ctx, doc, obj);
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── Document-scoped query/creation methods (mirror DocumentBindings.cpp's
|
|
28
|
+
// js_doc_* functions, but resolve `this`'s own LexborDocument instead of
|
|
29
|
+
// always reading the sandbox's primary get_doc(ctx)) ────────────────────────
|
|
30
|
+
|
|
31
|
+
JSValue js_pdoc_getElementById(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
32
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
33
|
+
if (!doc || argc < 1) return JS_NULL;
|
|
34
|
+
const char* id = JS_ToCString(ctx, argv[0]);
|
|
35
|
+
if (!id) return JS_NULL;
|
|
36
|
+
void* el = doc->getElementById(id);
|
|
37
|
+
JS_FreeCString(ctx, id);
|
|
38
|
+
return make_element(ctx, el);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
JSValue js_pdoc_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
42
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
43
|
+
if (!doc || argc < 1) return JS_NULL;
|
|
44
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
45
|
+
if (!sel) return JS_NULL;
|
|
46
|
+
void* el = doc->querySelector_el(sel);
|
|
47
|
+
JS_FreeCString(ctx, sel);
|
|
48
|
+
return make_element(ctx, el);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
JSValue js_pdoc_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
52
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
53
|
+
if (!doc || argc < 1) return JS_NewArray(ctx);
|
|
54
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
55
|
+
if (!sel) return JS_NewArray(ctx);
|
|
56
|
+
auto results = doc->querySelectorAll_el(sel);
|
|
57
|
+
JS_FreeCString(ctx, sel);
|
|
58
|
+
return make_element_array(ctx, results);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Static snapshot, not a live HTMLCollection — see header comment.
|
|
62
|
+
JSValue js_pdoc_getElementsByClassName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
63
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
64
|
+
if (!doc || argc < 1) return JS_NewArray(ctx);
|
|
65
|
+
const char* names = JS_ToCString(ctx, argv[0]);
|
|
66
|
+
if (!names) return JS_NewArray(ctx);
|
|
67
|
+
auto results = doc->querySelectorAll_el(classNames_to_selector(names));
|
|
68
|
+
JS_FreeCString(ctx, names);
|
|
69
|
+
return make_element_array(ctx, results);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Static snapshot, not a live HTMLCollection — see header comment.
|
|
73
|
+
JSValue js_pdoc_getElementsByTagName(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
74
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
75
|
+
if (!doc || argc < 1) return JS_NewArray(ctx);
|
|
76
|
+
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
77
|
+
if (!tag) return JS_NewArray(ctx);
|
|
78
|
+
auto results = doc->querySelectorAll_el(tag);
|
|
79
|
+
JS_FreeCString(ctx, tag);
|
|
80
|
+
return make_element_array(ctx, results);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
JSValue js_pdoc_createElement(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
84
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
85
|
+
if (!doc || argc < 1) return JS_NULL;
|
|
86
|
+
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
87
|
+
if (!tag) return JS_NULL;
|
|
88
|
+
void* el = doc->createElement(tag);
|
|
89
|
+
JS_FreeCString(ctx, tag);
|
|
90
|
+
return make_element(ctx, el);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
JSValue js_pdoc_createTextNode(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
94
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
95
|
+
if (!doc || argc < 1) return JS_NULL;
|
|
96
|
+
const char* text = JS_ToCString(ctx, argv[0]);
|
|
97
|
+
if (!text) return JS_NULL;
|
|
98
|
+
void* node = doc->createTextNode(text);
|
|
99
|
+
JS_FreeCString(ctx, text);
|
|
100
|
+
return make_element(ctx, node);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
JSValue js_pdoc_createComment(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
104
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
105
|
+
if (!doc || argc < 1) return JS_NULL;
|
|
106
|
+
const char* text = JS_ToCString(ctx, argv[0]);
|
|
107
|
+
if (!text) return JS_NULL;
|
|
108
|
+
void* node = doc->createComment(text);
|
|
109
|
+
JS_FreeCString(ctx, text);
|
|
110
|
+
return make_element(ctx, node);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
JSValue js_pdoc_createDocumentFragment(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
114
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
115
|
+
if (!doc) return JS_NULL;
|
|
116
|
+
return make_element(ctx, doc->createDocumentFragment());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
JSValue js_pdoc_get_body(JSContext* ctx, JSValue this_val) {
|
|
120
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
121
|
+
return doc ? make_element(ctx, doc->body()) : JS_NULL;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
JSValue js_pdoc_get_head(JSContext* ctx, JSValue this_val) {
|
|
125
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
126
|
+
return doc ? make_element(ctx, doc->head()) : JS_NULL;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
JSValue js_pdoc_get_documentElement(JSContext* ctx, JSValue this_val) {
|
|
130
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
131
|
+
return doc ? make_element(ctx, doc->documentElement()) : JS_NULL;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Unlike document.doctype (DocumentBindings.cpp), this isn't cached: a
|
|
135
|
+
// secondary document is far less likely to have `.doctype` read repeatedly,
|
|
136
|
+
// so a fresh plain object per access keeps this file simpler.
|
|
137
|
+
JSValue js_pdoc_get_doctype(JSContext* ctx, JSValue this_val) {
|
|
138
|
+
auto* doc = unwrap_parsed_doc(ctx, this_val);
|
|
139
|
+
if (!doc) return JS_NULL;
|
|
140
|
+
void* dt = doc->doctype();
|
|
141
|
+
if (!dt) return JS_NULL;
|
|
142
|
+
return build_doctype_object(ctx, doc, dt);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ── DOMParser.parseFromString() / document.implementation.createHTMLDocument() ─
|
|
146
|
+
|
|
147
|
+
JSValue js_native_parse_from_string(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
148
|
+
if (argc < 2) return JS_ThrowTypeError(ctx, "parseFromString requires 2 arguments");
|
|
149
|
+
const char* html = JS_ToCString(ctx, argv[0]);
|
|
150
|
+
if (!html) return JS_EXCEPTION;
|
|
151
|
+
const char* type = JS_ToCString(ctx, argv[1]);
|
|
152
|
+
if (!type) { JS_FreeCString(ctx, html); return JS_EXCEPTION; }
|
|
153
|
+
|
|
154
|
+
static const char* kSupportedTypes[] = {
|
|
155
|
+
"text/html", "text/xml", "application/xml", "application/xhtml+xml", "image/svg+xml",
|
|
156
|
+
};
|
|
157
|
+
bool supported = false;
|
|
158
|
+
for (const char* t : kSupportedTypes) {
|
|
159
|
+
if (std::strcmp(type, t) == 0) { supported = true; break; }
|
|
160
|
+
}
|
|
161
|
+
if (!supported) {
|
|
162
|
+
std::string msg = "Invalid DOMParser type: " + std::string(type);
|
|
163
|
+
JS_FreeCString(ctx, html);
|
|
164
|
+
JS_FreeCString(ctx, type);
|
|
165
|
+
return JS_ThrowTypeError(ctx, "%s", msg.c_str());
|
|
166
|
+
}
|
|
167
|
+
// Every supported type is parsed with the HTML parser regardless of MIME
|
|
168
|
+
// type — this sandbox has no separate XML parser wired up. Good enough for
|
|
169
|
+
// the common case (parsing HTML/XHTML/SVG markup); genuine XML-specific
|
|
170
|
+
// syntax (e.g. CDATA sections, processing instructions) is not modeled.
|
|
171
|
+
JS_FreeCString(ctx, type);
|
|
172
|
+
|
|
173
|
+
auto* doc = new LexborDocument();
|
|
174
|
+
doc->parse(html);
|
|
175
|
+
JS_FreeCString(ctx, html);
|
|
176
|
+
|
|
177
|
+
auto* rctx = get_ctx(ctx);
|
|
178
|
+
JSValue result = make_parsed_document(ctx, doc);
|
|
179
|
+
if (rctx) rctx->extra_documents.emplace_back(doc);
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
JSValue js_native_create_html_document(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
184
|
+
std::string title;
|
|
185
|
+
if (argc >= 1 && !JS_IsUndefined(argv[0])) {
|
|
186
|
+
const char* t = JS_ToCString(ctx, argv[0]);
|
|
187
|
+
if (t) { title = t; JS_FreeCString(ctx, t); }
|
|
188
|
+
}
|
|
189
|
+
std::string escaped;
|
|
190
|
+
escaped.reserve(title.size());
|
|
191
|
+
for (char c : title) {
|
|
192
|
+
if (c == '<') escaped += "<";
|
|
193
|
+
else if (c == '>') escaped += ">";
|
|
194
|
+
else if (c == '&') escaped += "&";
|
|
195
|
+
else escaped += c;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
auto* doc = new LexborDocument();
|
|
199
|
+
doc->parse("<!doctype html><html><head><title>" + escaped + "</title></head><body></body></html>");
|
|
200
|
+
|
|
201
|
+
auto* rctx = get_ctx(ctx);
|
|
202
|
+
JSValue result = make_parsed_document(ctx, doc);
|
|
203
|
+
if (rctx) rctx->extra_documents.emplace_back(doc);
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const char* kDOMParserBootstrapScript = R"JS(
|
|
208
|
+
(function() {
|
|
209
|
+
function DOMParser() {}
|
|
210
|
+
DOMParser.prototype.parseFromString = function(str, type) {
|
|
211
|
+
return __nativeParseFromString(String(str), String(type));
|
|
212
|
+
};
|
|
213
|
+
globalThis.DOMParser = DOMParser;
|
|
214
|
+
|
|
215
|
+
document.implementation = {
|
|
216
|
+
createHTMLDocument: function(title) {
|
|
217
|
+
return __nativeCreateHTMLDocument(title === undefined ? undefined : String(title));
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
function definePDocTitle(proto) {
|
|
222
|
+
Object.defineProperty(proto, 'title', {
|
|
223
|
+
configurable: true,
|
|
224
|
+
get: function() {
|
|
225
|
+
var head = this.head;
|
|
226
|
+
if (!head) return '';
|
|
227
|
+
var titleEl = head.querySelector('title');
|
|
228
|
+
return titleEl ? titleEl.textContent : '';
|
|
229
|
+
},
|
|
230
|
+
set: function(value) {
|
|
231
|
+
var head = this.head;
|
|
232
|
+
if (!head) return;
|
|
233
|
+
var titleEl = head.querySelector('title');
|
|
234
|
+
if (!titleEl) {
|
|
235
|
+
titleEl = this.createElement('title');
|
|
236
|
+
head.appendChild(titleEl);
|
|
237
|
+
}
|
|
238
|
+
titleEl.textContent = String(value);
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
definePDocTitle(globalThis.__parsedDocumentProto);
|
|
243
|
+
delete globalThis.__parsedDocumentProto;
|
|
244
|
+
})();
|
|
245
|
+
)JS";
|
|
246
|
+
|
|
247
|
+
} // namespace
|
|
248
|
+
|
|
249
|
+
void DOMParserBindings::install(JSContext* ctx) {
|
|
250
|
+
JS_NewClassID(&js_parsed_document_class_id);
|
|
251
|
+
JS_NewClass(JS_GetRuntime(ctx), js_parsed_document_class_id, &js_parsed_document_class);
|
|
252
|
+
|
|
253
|
+
JSValue proto = JS_NewObject(ctx);
|
|
254
|
+
JS_SetPropertyStr(ctx, proto, "getElementById", JS_NewCFunction(ctx, js_pdoc_getElementById, "getElementById", 1));
|
|
255
|
+
JS_SetPropertyStr(ctx, proto, "querySelector", JS_NewCFunction(ctx, js_pdoc_querySelector, "querySelector", 1));
|
|
256
|
+
JS_SetPropertyStr(ctx, proto, "querySelectorAll", JS_NewCFunction(ctx, js_pdoc_querySelectorAll, "querySelectorAll", 1));
|
|
257
|
+
JS_SetPropertyStr(ctx, proto, "getElementsByClassName", JS_NewCFunction(ctx, js_pdoc_getElementsByClassName, "getElementsByClassName", 1));
|
|
258
|
+
JS_SetPropertyStr(ctx, proto, "getElementsByTagName", JS_NewCFunction(ctx, js_pdoc_getElementsByTagName, "getElementsByTagName", 1));
|
|
259
|
+
JS_SetPropertyStr(ctx, proto, "createElement", JS_NewCFunction(ctx, js_pdoc_createElement, "createElement", 1));
|
|
260
|
+
JS_SetPropertyStr(ctx, proto, "createTextNode", JS_NewCFunction(ctx, js_pdoc_createTextNode, "createTextNode", 1));
|
|
261
|
+
JS_SetPropertyStr(ctx, proto, "createComment", JS_NewCFunction(ctx, js_pdoc_createComment, "createComment", 1));
|
|
262
|
+
JS_SetPropertyStr(ctx, proto, "createDocumentFragment", JS_NewCFunction(ctx, js_pdoc_createDocumentFragment, "createDocumentFragment", 0));
|
|
263
|
+
|
|
264
|
+
define_prop(ctx, proto, "body", js_pdoc_get_body, nullptr);
|
|
265
|
+
define_prop(ctx, proto, "head", js_pdoc_get_head, nullptr);
|
|
266
|
+
define_prop(ctx, proto, "documentElement", js_pdoc_get_documentElement, nullptr);
|
|
267
|
+
define_prop(ctx, proto, "doctype", js_pdoc_get_doctype, nullptr);
|
|
268
|
+
|
|
269
|
+
JS_SetPropertyStr(ctx, proto, "nodeType", JS_NewInt32(ctx, 9 /* DOCUMENT_NODE */));
|
|
270
|
+
JS_SetPropertyStr(ctx, proto, "nodeName", JS_NewString(ctx, "#document"));
|
|
271
|
+
JS_SetPropertyStr(ctx, proto, "ownerDocument", JS_NULL);
|
|
272
|
+
define_node_type_constants(ctx, proto);
|
|
273
|
+
|
|
274
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
275
|
+
|
|
276
|
+
// Chain onto Document.prototype (set up by DocumentBindings.cpp) purely so
|
|
277
|
+
// `parsedDoc instanceof Document` holds, matching real jsdom/browsers.
|
|
278
|
+
JSValue document_ctor = JS_GetPropertyStr(ctx, global, "Document");
|
|
279
|
+
JSValue document_proto_val = JS_GetPropertyStr(ctx, document_ctor, "prototype");
|
|
280
|
+
JS_SetPrototype(ctx, proto, document_proto_val);
|
|
281
|
+
JS_FreeValue(ctx, document_proto_val);
|
|
282
|
+
JS_FreeValue(ctx, document_ctor);
|
|
283
|
+
|
|
284
|
+
JS_SetClassProto(ctx, js_parsed_document_class_id, JS_DupValue(ctx, proto));
|
|
285
|
+
|
|
286
|
+
JS_SetPropertyStr(ctx, global, "__parsedDocumentProto", proto);
|
|
287
|
+
JS_SetPropertyStr(ctx, global, "__nativeParseFromString", JS_NewCFunction(ctx, js_native_parse_from_string, "__nativeParseFromString", 2));
|
|
288
|
+
JS_SetPropertyStr(ctx, global, "__nativeCreateHTMLDocument", JS_NewCFunction(ctx, js_native_create_html_document, "__nativeCreateHTMLDocument", 1));
|
|
289
|
+
JS_FreeValue(ctx, global);
|
|
290
|
+
|
|
291
|
+
JSValue result = JS_Eval(ctx, kDOMParserBootstrapScript, strlen(kDOMParserBootstrapScript),
|
|
292
|
+
"<domparser-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
293
|
+
if (JS_IsException(result)) {
|
|
294
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
295
|
+
}
|
|
296
|
+
JS_FreeValue(ctx, result);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Registers globalThis.DOMParser (`.parseFromString()`) and
|
|
4
|
+
// document.implementation.createHTMLDocument(), each of which produces a
|
|
5
|
+
// genuinely separate, inert Document backed by its own LexborDocument
|
|
6
|
+
// instance (see doc_for_node()/register_document() in DOMBindingsInternal —
|
|
7
|
+
// that's what makes mutating a second document safe rather than corrupting
|
|
8
|
+
// the primary document's memory arena).
|
|
9
|
+
//
|
|
10
|
+
// Scope, matching real-world DOMParser usage (parsing a fetched/embedded
|
|
11
|
+
// HTML string and reading it back, occasionally assembling a few new nodes):
|
|
12
|
+
// - Full read/query support: querySelector(All), getElementById,
|
|
13
|
+
// getElementsBy{ClassName,TagName} (as static snapshots, not live
|
|
14
|
+
// HTMLCollections — see below), all generic Node traversal/attribute
|
|
15
|
+
// methods (those are shared with the primary document's Element/Node
|
|
16
|
+
// prototypes and don't need document-specific wiring).
|
|
17
|
+
// - createElement/createTextNode/createComment/createDocumentFragment,
|
|
18
|
+
// appendChild/removeChild/insertBefore/replaceChild, and the
|
|
19
|
+
// textContent/innerHTML setters/insertAdjacentHTML/matches()/closest()
|
|
20
|
+
// inherited from Element.prototype — all resolve the correct owning
|
|
21
|
+
// document dynamically, so building up a secondary document is fully
|
|
22
|
+
// safe, not just read-only.
|
|
23
|
+
// - title get/set (mirrors document.title's head/<title> shim).
|
|
24
|
+
// Not supported on secondary documents (their bindings are hardwired to the
|
|
25
|
+
// primary document — see ShadowRootBindings/CustomElementsBindings/
|
|
26
|
+
// TemplateBindings/LiveCollectionBindings): Shadow DOM (attachShadow),
|
|
27
|
+
// Custom Elements, <template>.content, MutationObserver, live
|
|
28
|
+
// HTMLCollections (.forms/.images/.scripts/.links, and
|
|
29
|
+
// getElementsBy{ClassName,TagName} return static arrays here instead).
|
|
30
|
+
// Scripts inside parsed HTML never execute (per spec, these documents are
|
|
31
|
+
// inert) — no script pipeline is wired to them.
|
|
32
|
+
//
|
|
33
|
+
// Must run after ElementBindings (shares its Element/Node classes/protos)
|
|
34
|
+
// and DocumentBindings (adds .implementation onto globalThis.document).
|
|
35
|
+
|
|
36
|
+
#include "quickjs.h"
|
|
37
|
+
|
|
38
|
+
namespace margelo::nitro::nitrojsdom {
|
|
39
|
+
|
|
40
|
+
class DOMParserBindings {
|
|
41
|
+
public:
|
|
42
|
+
static void install(JSContext* ctx);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#include "DocumentBindings.hpp"
|
|
2
|
+
#include "LiveCollectionBindings.hpp"
|
|
3
|
+
#include "DOMExceptionBindings.hpp"
|
|
2
4
|
#include "../DOMBindingsInternal.hpp"
|
|
5
|
+
#include "../QuickJSRuntime.hpp"
|
|
3
6
|
#include "../../lexbor/LexborDocument.hpp"
|
|
7
|
+
#include <lexbor/html/html.h>
|
|
8
|
+
#include <lexbor/dom/dom.h>
|
|
4
9
|
#include <cstring>
|
|
10
|
+
#include <string>
|
|
5
11
|
|
|
6
12
|
namespace margelo::nitro::nitrojsdom {
|
|
7
13
|
|
|
@@ -38,18 +44,47 @@ JSValue js_doc_getElementsByClassName(JSContext* ctx, JSValue, int argc, JSValue
|
|
|
38
44
|
if (argc < 1) return JS_NewArray(ctx);
|
|
39
45
|
const char* names = JS_ToCString(ctx, argv[0]);
|
|
40
46
|
if (!names) return JS_NewArray(ctx);
|
|
41
|
-
|
|
47
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, nullptr, classNames_to_selector(names));
|
|
42
48
|
JS_FreeCString(ctx, names);
|
|
43
|
-
return
|
|
49
|
+
return result;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
JSValue js_doc_getElementsByTagName(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
47
53
|
if (argc < 1) return JS_NewArray(ctx);
|
|
48
54
|
const char* tag = JS_ToCString(ctx, argv[0]);
|
|
49
55
|
if (!tag) return JS_NewArray(ctx);
|
|
50
|
-
|
|
56
|
+
JSValue result = LiveCollectionBindings::makeBySelector(ctx, nullptr, tag);
|
|
51
57
|
JS_FreeCString(ctx, tag);
|
|
52
|
-
return
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
JSValue js_doc_getElementsByName(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
62
|
+
if (argc < 1) return JS_NewArray(ctx);
|
|
63
|
+
const char* name = JS_ToCString(ctx, argv[0]);
|
|
64
|
+
if (!name) return JS_NewArray(ctx);
|
|
65
|
+
std::string selector = "[name=\"" + std::string(name) + "\"]";
|
|
66
|
+
JS_FreeCString(ctx, name);
|
|
67
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, selector);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// document.forms/.images/.scripts/.links — live HTMLCollections backed by the
|
|
71
|
+
// same LiveCollectionBindings::makeBySelector primitive that already backs
|
|
72
|
+
// getElementsByClassName/getElementsByTagName. .links matches jsdom: any <a>
|
|
73
|
+
// or <area> that actually has an href attribute (bare <a> anchors don't count).
|
|
74
|
+
JSValue js_doc_get_forms(JSContext* ctx, JSValue) {
|
|
75
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "form");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
JSValue js_doc_get_images(JSContext* ctx, JSValue) {
|
|
79
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "img");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
JSValue js_doc_get_scripts(JSContext* ctx, JSValue) {
|
|
83
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "script");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
JSValue js_doc_get_links(JSContext* ctx, JSValue) {
|
|
87
|
+
return LiveCollectionBindings::makeBySelector(ctx, nullptr, "a[href], area[href]");
|
|
53
88
|
}
|
|
54
89
|
|
|
55
90
|
JSValue js_doc_createElement(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
@@ -71,6 +106,92 @@ JSValue js_doc_createTextNode(JSContext* ctx, JSValue, int argc, JSValue* argv)
|
|
|
71
106
|
return make_element(ctx, node);
|
|
72
107
|
}
|
|
73
108
|
|
|
109
|
+
// Implements the WHATWG "validate and extract a namespace and qualified name"
|
|
110
|
+
// algorithm (https://dom.spec.whatwg.org/#validate-and-extract): splits
|
|
111
|
+
// qualifiedName into prefix/localName and checks the namespace/prefix
|
|
112
|
+
// consistency rules, throwing the spec-mandated DOMException on failure.
|
|
113
|
+
// Full XML Name-production character validation (which characters are legal
|
|
114
|
+
// in a Name) is not implemented — only structural checks (empty name, colon
|
|
115
|
+
// placement, xml/xmlns reservations) — deliberately narrower than real
|
|
116
|
+
// jsdom, since that character-class check is a much larger, separate lift.
|
|
117
|
+
JSValue validate_and_extract_qname(JSContext* ctx, const std::string& ns,
|
|
118
|
+
const std::string& qualifiedName, bool ns_is_null,
|
|
119
|
+
std::string& out_prefix, std::string& out_local) {
|
|
120
|
+
if (qualifiedName.empty()) {
|
|
121
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
122
|
+
"The qualified name provided is empty.");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
auto colon = qualifiedName.find(':');
|
|
126
|
+
auto last_colon = qualifiedName.rfind(':');
|
|
127
|
+
if (colon != last_colon) {
|
|
128
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
129
|
+
"The qualified name provided has more than one ':'.");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
std::string prefix;
|
|
133
|
+
std::string local = qualifiedName;
|
|
134
|
+
if (colon != std::string::npos) {
|
|
135
|
+
prefix = qualifiedName.substr(0, colon);
|
|
136
|
+
local = qualifiedName.substr(colon + 1);
|
|
137
|
+
if (prefix.empty() || local.empty()) {
|
|
138
|
+
return throw_dom_exception(ctx, "InvalidCharacterError",
|
|
139
|
+
"The qualified name provided is malformed.");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!prefix.empty() && ns_is_null) {
|
|
144
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
145
|
+
"A namespace is required for a prefixed qualified name.");
|
|
146
|
+
}
|
|
147
|
+
if (prefix == "xml" && ns != "http://www.w3.org/XML/1998/namespace") {
|
|
148
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
149
|
+
"The 'xml' prefix requires the XML namespace.");
|
|
150
|
+
}
|
|
151
|
+
bool is_xmlns = (qualifiedName == "xmlns" || prefix == "xmlns");
|
|
152
|
+
if (is_xmlns && ns != "http://www.w3.org/2000/xmlns/") {
|
|
153
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
154
|
+
"The 'xmlns' prefix/qualified name requires the XMLNS namespace.");
|
|
155
|
+
}
|
|
156
|
+
if (!is_xmlns && ns == "http://www.w3.org/2000/xmlns/") {
|
|
157
|
+
return throw_dom_exception(ctx, "NamespaceError",
|
|
158
|
+
"The XMLNS namespace requires the 'xmlns' prefix or qualified name.");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
out_prefix = prefix;
|
|
162
|
+
out_local = local;
|
|
163
|
+
return JS_UNDEFINED;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
JSValue js_doc_createElementNS(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
167
|
+
if (argc < 2) {
|
|
168
|
+
return JS_ThrowTypeError(ctx,
|
|
169
|
+
"Failed to execute 'createElementNS' on 'Document': 2 arguments required.");
|
|
170
|
+
}
|
|
171
|
+
// Per WebIDL, a nullable DOMString? argument only maps to the null
|
|
172
|
+
// namespace when explicitly `null` — `undefined` stringifies to "undefined"
|
|
173
|
+
// like any other DOMString argument, it is not treated as null.
|
|
174
|
+
bool ns_is_null = JS_IsNull(argv[0]);
|
|
175
|
+
std::string ns;
|
|
176
|
+
if (!ns_is_null) {
|
|
177
|
+
const char* ns_c = JS_ToCString(ctx, argv[0]);
|
|
178
|
+
if (!ns_c) return JS_EXCEPTION;
|
|
179
|
+
ns = ns_c;
|
|
180
|
+
JS_FreeCString(ctx, ns_c);
|
|
181
|
+
}
|
|
182
|
+
const char* qname = JS_ToCString(ctx, argv[1]);
|
|
183
|
+
if (!qname) return JS_EXCEPTION;
|
|
184
|
+
std::string qualifiedName = qname;
|
|
185
|
+
JS_FreeCString(ctx, qname);
|
|
186
|
+
|
|
187
|
+
std::string prefix, local;
|
|
188
|
+
JSValue validation_error = validate_and_extract_qname(ctx, ns, qualifiedName, ns_is_null, prefix, local);
|
|
189
|
+
if (JS_IsException(validation_error)) return validation_error;
|
|
190
|
+
|
|
191
|
+
void* el = get_doc(ctx)->createElementNS(ns, qualifiedName);
|
|
192
|
+
return make_element(ctx, el);
|
|
193
|
+
}
|
|
194
|
+
|
|
74
195
|
JSValue js_doc_createComment(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
75
196
|
if (argc < 1) return JS_NULL;
|
|
76
197
|
const char* text = JS_ToCString(ctx, argv[0]);
|
|
@@ -84,6 +205,28 @@ JSValue js_doc_createDocumentFragment(JSContext* ctx, JSValue, int, JSValue*) {
|
|
|
84
205
|
return make_element(ctx, get_doc(ctx)->createDocumentFragment());
|
|
85
206
|
}
|
|
86
207
|
|
|
208
|
+
JSValue js_doc_importNode(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
209
|
+
if (argc < 1) {
|
|
210
|
+
return JS_ThrowTypeError(ctx,
|
|
211
|
+
"Failed to execute 'importNode' on 'Document': 1 argument required, but only 0 present.");
|
|
212
|
+
}
|
|
213
|
+
auto* node = unwrap_node(ctx, argv[0]);
|
|
214
|
+
if (!node) {
|
|
215
|
+
return JS_ThrowTypeError(ctx,
|
|
216
|
+
"Failed to execute 'importNode' on 'Document': parameter 1 is not of type 'Node'.");
|
|
217
|
+
}
|
|
218
|
+
if (node->type == LXB_DOM_NODE_TYPE_DOCUMENT) {
|
|
219
|
+
return throw_dom_exception(ctx, "NotSupportedError",
|
|
220
|
+
"Failed to execute 'importNode' on 'Document': The node provided is a document, which may not be imported.");
|
|
221
|
+
}
|
|
222
|
+
bool deep = argc >= 2 && JS_ToBool(ctx, argv[1]) > 0;
|
|
223
|
+
|
|
224
|
+
lxb_dom_document_t* target_doc = lxb_dom_interface_document(
|
|
225
|
+
static_cast<lxb_html_document_t*>(get_doc(ctx)->documentHtmlPtr()));
|
|
226
|
+
lxb_dom_node_t* imported = lxb_dom_document_import_node(target_doc, node, deep);
|
|
227
|
+
return make_element(ctx, imported);
|
|
228
|
+
}
|
|
229
|
+
|
|
87
230
|
JSValue js_doc_get_body(JSContext* ctx, JSValue) {
|
|
88
231
|
return make_element(ctx, get_doc(ctx)->body());
|
|
89
232
|
}
|
|
@@ -96,6 +239,64 @@ JSValue js_doc_get_documentElement(JSContext* ctx, JSValue) {
|
|
|
96
239
|
return make_element(ctx, get_doc(ctx)->documentElement());
|
|
97
240
|
}
|
|
98
241
|
|
|
242
|
+
// document.doctype is exposed as a plain {name, publicId, systemId, nodeType,
|
|
243
|
+
// nodeName} object rather than routed through make_element()/the generic
|
|
244
|
+
// Node wrapper: DocumentType's `name` is an interned lxb_dom_attr_id_t (needs
|
|
245
|
+
// LexborDocument::doctypeName(), not the generic nodeName getter), and it has
|
|
246
|
+
// no other Node behavior real-world embedded scripts rely on (no children,
|
|
247
|
+
// not appendChild-able). Since it's a plain object rather than a cached
|
|
248
|
+
// node-pointer-keyed wrapper, the constructed object is cached directly on
|
|
249
|
+
// RuntimeContext (built once, on first access) so repeated `document.doctype`
|
|
250
|
+
// reads return the same JS object — the document itself never gets
|
|
251
|
+
// re-parsed mid-instance, so the cache never needs invalidation.
|
|
252
|
+
JSValue js_doc_get_doctype(JSContext* ctx, JSValue) {
|
|
253
|
+
void* dt = get_doc(ctx)->doctype();
|
|
254
|
+
if (!dt) return JS_NULL;
|
|
255
|
+
|
|
256
|
+
auto* rctx = get_ctx(ctx);
|
|
257
|
+
if (rctx && rctx->doctype_wrapper) {
|
|
258
|
+
return JS_DupValue(ctx, *static_cast<JSValue*>(rctx->doctype_wrapper));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
JSValue obj = build_doctype_object(ctx, get_doc(ctx), dt);
|
|
262
|
+
if (rctx) rctx->doctype_wrapper = new JSValue(JS_DupValue(ctx, obj));
|
|
263
|
+
return obj;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
JSValue js_doc_get_activeElement(JSContext* ctx, JSValue) {
|
|
267
|
+
auto* rctx = get_ctx(ctx);
|
|
268
|
+
if (rctx && rctx->active_element) return make_element(ctx, rctx->active_element);
|
|
269
|
+
return make_element(ctx, get_doc(ctx)->body());
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
JSValue js_doc_get_readyState(JSContext* ctx, JSValue) {
|
|
273
|
+
auto* rctx = get_ctx(ctx);
|
|
274
|
+
const std::string& state = rctx ? rctx->ready_state : "loading";
|
|
275
|
+
return JS_NewStringLen(ctx, state.data(), state.size());
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// A doctype-less document renders in quirks mode in every real browser (and
|
|
279
|
+
// jsdom mirrors that), so this is a real, if approximate, signal rather than
|
|
280
|
+
// a hardcoded stub — no actual quirks-mode CSS behavior differs here since
|
|
281
|
+
// there's no layout engine to diverge.
|
|
282
|
+
JSValue js_doc_get_compatMode(JSContext* ctx, JSValue) {
|
|
283
|
+
bool has_doctype = get_doc(ctx)->doctype() != nullptr;
|
|
284
|
+
const char* mode = has_doctype ? "CSS1Compat" : "BackCompat";
|
|
285
|
+
return JS_NewString(ctx, mode);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// document uses its own object/prototype (not node_proto), so it doesn't
|
|
289
|
+
// inherit the Node-level baseURI getter — mirrors js_el_get_baseURI in
|
|
290
|
+
// ElementBindings.cpp (always location.href; no <base> element support).
|
|
291
|
+
JSValue js_doc_get_baseURI(JSContext* ctx, JSValue) {
|
|
292
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
293
|
+
JSValue location = JS_GetPropertyStr(ctx, global, "location");
|
|
294
|
+
JS_FreeValue(ctx, global);
|
|
295
|
+
JSValue href = JS_GetPropertyStr(ctx, location, "href");
|
|
296
|
+
JS_FreeValue(ctx, location);
|
|
297
|
+
return href;
|
|
298
|
+
}
|
|
299
|
+
|
|
99
300
|
const char* kDocumentTitleBootstrapScript = R"JS(
|
|
100
301
|
(function() {
|
|
101
302
|
Object.defineProperty(document, 'title', {
|
|
@@ -132,14 +333,48 @@ void DocumentBindings::install(JSContext* ctx) {
|
|
|
132
333
|
JS_SetPropertyStr(ctx, doc, "querySelectorAll", JS_NewCFunction(ctx, js_doc_querySelectorAll, "querySelectorAll", 1));
|
|
133
334
|
JS_SetPropertyStr(ctx, doc, "getElementsByClassName", JS_NewCFunction(ctx, js_doc_getElementsByClassName, "getElementsByClassName", 1));
|
|
134
335
|
JS_SetPropertyStr(ctx, doc, "getElementsByTagName", JS_NewCFunction(ctx, js_doc_getElementsByTagName, "getElementsByTagName", 1));
|
|
336
|
+
JS_SetPropertyStr(ctx, doc, "getElementsByName", JS_NewCFunction(ctx, js_doc_getElementsByName, "getElementsByName", 1));
|
|
135
337
|
JS_SetPropertyStr(ctx, doc, "createElement", JS_NewCFunction(ctx, js_doc_createElement, "createElement", 1));
|
|
338
|
+
JS_SetPropertyStr(ctx, doc, "createElementNS", JS_NewCFunction(ctx, js_doc_createElementNS, "createElementNS", 2));
|
|
136
339
|
JS_SetPropertyStr(ctx, doc, "createTextNode", JS_NewCFunction(ctx, js_doc_createTextNode, "createTextNode", 1));
|
|
137
340
|
JS_SetPropertyStr(ctx, doc, "createComment", JS_NewCFunction(ctx, js_doc_createComment, "createComment", 1));
|
|
138
341
|
JS_SetPropertyStr(ctx, doc, "createDocumentFragment", JS_NewCFunction(ctx, js_doc_createDocumentFragment, "createDocumentFragment", 0));
|
|
342
|
+
JS_SetPropertyStr(ctx, doc, "importNode", JS_NewCFunction(ctx, js_doc_importNode, "importNode", 2));
|
|
139
343
|
|
|
140
344
|
define_prop(ctx, doc, "body", js_doc_get_body, nullptr);
|
|
141
345
|
define_prop(ctx, doc, "head", js_doc_get_head, nullptr);
|
|
142
346
|
define_prop(ctx, doc, "documentElement", js_doc_get_documentElement, nullptr);
|
|
347
|
+
define_prop(ctx, doc, "doctype", js_doc_get_doctype, nullptr);
|
|
348
|
+
define_prop(ctx, doc, "forms", js_doc_get_forms, nullptr);
|
|
349
|
+
define_prop(ctx, doc, "images", js_doc_get_images, nullptr);
|
|
350
|
+
define_prop(ctx, doc, "scripts", js_doc_get_scripts, nullptr);
|
|
351
|
+
define_prop(ctx, doc, "links", js_doc_get_links, nullptr);
|
|
352
|
+
define_prop(ctx, doc, "activeElement", js_doc_get_activeElement, nullptr);
|
|
353
|
+
define_prop(ctx, doc, "readyState", js_doc_get_readyState, nullptr);
|
|
354
|
+
define_prop(ctx, doc, "compatMode", js_doc_get_compatMode, nullptr);
|
|
355
|
+
define_prop(ctx, doc, "baseURI", js_doc_get_baseURI, nullptr);
|
|
356
|
+
|
|
357
|
+
JS_SetPropertyStr(ctx, doc, "characterSet", JS_NewString(ctx, "UTF-8"));
|
|
358
|
+
JS_SetPropertyStr(ctx, doc, "contentType", JS_NewString(ctx, "text/html"));
|
|
359
|
+
|
|
360
|
+
RuntimeContext* rctx = get_ctx(ctx);
|
|
361
|
+
bool hidden = !(rctx && rctx->pretend_to_be_visual);
|
|
362
|
+
JS_SetPropertyStr(ctx, doc, "hidden", JS_NewBool(ctx, hidden));
|
|
363
|
+
JS_SetPropertyStr(ctx, doc, "nodeType", JS_NewInt32(ctx, 9 /* DOCUMENT_NODE */));
|
|
364
|
+
JS_SetPropertyStr(ctx, doc, "nodeName", JS_NewString(ctx, "#document"));
|
|
365
|
+
JS_SetPropertyStr(ctx, doc, "ownerDocument", JS_NULL);
|
|
366
|
+
// document uses its own object/prototype (not node_proto), so it doesn't
|
|
367
|
+
// inherit js_el_get_namespaceURI — set explicitly rather than leaving this
|
|
368
|
+
// undefined, matching real jsdom (a Document node has no namespace).
|
|
369
|
+
JS_SetPropertyStr(ctx, doc, "namespaceURI", JS_NULL);
|
|
370
|
+
|
|
371
|
+
JSValue document_proto = JS_NewObject(ctx);
|
|
372
|
+
JS_SetPrototype(ctx, doc, document_proto);
|
|
373
|
+
define_node_type_constants(ctx, document_proto);
|
|
374
|
+
JSValue document_ctor = define_global_constructor(ctx, "Document", document_proto);
|
|
375
|
+
define_node_type_constants(ctx, document_ctor);
|
|
376
|
+
JS_FreeValue(ctx, document_ctor);
|
|
377
|
+
JS_FreeValue(ctx, document_proto);
|
|
143
378
|
|
|
144
379
|
JS_SetPropertyStr(ctx, global, "document", doc);
|
|
145
380
|
JS_FreeValue(ctx, global);
|