@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,228 @@
|
|
|
1
|
+
#include "LiveCollectionBindings.hpp"
|
|
2
|
+
#include "../DOMBindingsInternal.hpp"
|
|
3
|
+
#include "../../lexbor/LexborDocument.hpp"
|
|
4
|
+
#include <lexbor/dom/dom.h>
|
|
5
|
+
#include <cctype>
|
|
6
|
+
#include <cstdlib>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
namespace margelo::nitro::nitrojsdom {
|
|
12
|
+
|
|
13
|
+
namespace {
|
|
14
|
+
|
|
15
|
+
JSClassID js_live_collection_class_id = 0;
|
|
16
|
+
|
|
17
|
+
enum class LiveKind { CHILDREN, CHILD_NODES, SELECTOR };
|
|
18
|
+
|
|
19
|
+
struct LiveCollectionData {
|
|
20
|
+
void* owner;
|
|
21
|
+
LiveKind kind;
|
|
22
|
+
std::string selector;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
LiveCollectionData* unwrap_live(JSContext*, JSValue val) {
|
|
26
|
+
return static_cast<LiveCollectionData*>(JS_GetOpaque(val, js_live_collection_class_id));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
std::vector<void*> compute_results(JSContext* ctx, LiveCollectionData* data) {
|
|
30
|
+
switch (data->kind) {
|
|
31
|
+
case LiveKind::CHILDREN: {
|
|
32
|
+
std::vector<void*> out;
|
|
33
|
+
auto* node = static_cast<lxb_dom_node_t*>(data->owner);
|
|
34
|
+
for (auto* c = node->first_child; c; c = c->next)
|
|
35
|
+
if (c->type == LXB_DOM_NODE_TYPE_ELEMENT) out.push_back(c);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
case LiveKind::CHILD_NODES: {
|
|
39
|
+
std::vector<void*> out;
|
|
40
|
+
auto* node = static_cast<lxb_dom_node_t*>(data->owner);
|
|
41
|
+
for (auto* c = node->first_child; c; c = c->next) out.push_back(c);
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
case LiveKind::SELECTOR:
|
|
45
|
+
if (data->owner) return get_doc(ctx)->querySelectorAllFromEl(data->owner, data->selector);
|
|
46
|
+
return get_doc(ctx)->querySelectorAll_el(data->selector);
|
|
47
|
+
}
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
bool prop_is_index(const std::string& key, size_t& out_idx) {
|
|
52
|
+
if (key.empty()) return false;
|
|
53
|
+
for (char c : key) if (!std::isdigit(static_cast<unsigned char>(c))) return false;
|
|
54
|
+
out_idx = static_cast<size_t>(std::strtoul(key.c_str(), nullptr, 10));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
int js_livecoll_get_own_property(JSContext* ctx, JSPropertyDescriptor* desc, JSValue obj, JSAtom prop) {
|
|
59
|
+
auto* data = unwrap_live(ctx, obj);
|
|
60
|
+
if (!data) return 0;
|
|
61
|
+
|
|
62
|
+
JSValue key_val = JS_AtomToString(ctx, prop);
|
|
63
|
+
const char* key_c = JS_ToCString(ctx, key_val);
|
|
64
|
+
std::string key = key_c ? key_c : "";
|
|
65
|
+
if (key_c) JS_FreeCString(ctx, key_c);
|
|
66
|
+
JS_FreeValue(ctx, key_val);
|
|
67
|
+
|
|
68
|
+
if (key == "length") {
|
|
69
|
+
if (desc) {
|
|
70
|
+
desc->flags = JS_PROP_ENUMERABLE;
|
|
71
|
+
desc->value = JS_NewInt32(ctx, static_cast<int32_t>(compute_results(ctx, data).size()));
|
|
72
|
+
desc->getter = JS_UNDEFINED;
|
|
73
|
+
desc->setter = JS_UNDEFINED;
|
|
74
|
+
}
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
size_t idx;
|
|
79
|
+
if (prop_is_index(key, idx)) {
|
|
80
|
+
auto results = compute_results(ctx, data);
|
|
81
|
+
if (idx < results.size()) {
|
|
82
|
+
if (desc) {
|
|
83
|
+
desc->flags = JS_PROP_ENUMERABLE;
|
|
84
|
+
desc->value = make_element(ctx, results[idx]);
|
|
85
|
+
desc->getter = JS_UNDEFINED;
|
|
86
|
+
desc->setter = JS_UNDEFINED;
|
|
87
|
+
}
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
int js_livecoll_get_own_property_names(JSContext* ctx, JSPropertyEnum** ptab, uint32_t* plen, JSValue obj) {
|
|
96
|
+
*ptab = nullptr;
|
|
97
|
+
*plen = 0;
|
|
98
|
+
auto* data = unwrap_live(ctx, obj);
|
|
99
|
+
if (!data) return 0;
|
|
100
|
+
|
|
101
|
+
auto results = compute_results(ctx, data);
|
|
102
|
+
auto* tab = static_cast<JSPropertyEnum*>(js_malloc(ctx, sizeof(JSPropertyEnum) * (results.size() + 1)));
|
|
103
|
+
if (!tab) return -1;
|
|
104
|
+
|
|
105
|
+
for (size_t i = 0; i < results.size(); i++) {
|
|
106
|
+
tab[i].is_enumerable = 1;
|
|
107
|
+
tab[i].atom = JS_NewAtom(ctx, std::to_string(i).c_str());
|
|
108
|
+
}
|
|
109
|
+
tab[results.size()].is_enumerable = 0;
|
|
110
|
+
tab[results.size()].atom = JS_NewAtom(ctx, "length");
|
|
111
|
+
|
|
112
|
+
*ptab = tab;
|
|
113
|
+
*plen = static_cast<uint32_t>(results.size() + 1);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
JSClassExoticMethods js_livecoll_exotic = {
|
|
118
|
+
.get_own_property = js_livecoll_get_own_property,
|
|
119
|
+
.get_own_property_names = js_livecoll_get_own_property_names,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
void js_livecoll_finalizer(JSRuntime*, JSValue val) {
|
|
123
|
+
auto* data = static_cast<LiveCollectionData*>(JS_GetOpaque(val, js_live_collection_class_id));
|
|
124
|
+
delete data;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
JSClassDef js_livecoll_class = { "LiveCollection", .finalizer = js_livecoll_finalizer, .exotic = &js_livecoll_exotic };
|
|
128
|
+
|
|
129
|
+
const char* kIteratorBootstrapScript = R"JS(
|
|
130
|
+
(function() {
|
|
131
|
+
var proto = globalThis.__LiveCollectionProto;
|
|
132
|
+
proto[Symbol.iterator] = function() {
|
|
133
|
+
var i = 0;
|
|
134
|
+
var self = this;
|
|
135
|
+
return {
|
|
136
|
+
next: function() {
|
|
137
|
+
return i < self.length ? { value: self[i++], done: false } : { value: undefined, done: true };
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
proto.forEach = function(callback, thisArg) {
|
|
143
|
+
for (var i = 0; i < this.length; i++) callback.call(thisArg, this[i], i, this);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
function arrayLikeIterator(self, mapEntry) {
|
|
147
|
+
var i = 0;
|
|
148
|
+
return {
|
|
149
|
+
next: function() {
|
|
150
|
+
if (i >= self.length) return { value: undefined, done: true };
|
|
151
|
+
var value = mapEntry(i, self[i]);
|
|
152
|
+
i++;
|
|
153
|
+
return { value: value, done: false };
|
|
154
|
+
},
|
|
155
|
+
[Symbol.iterator]: function() { return this; },
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
proto.entries = function() {
|
|
160
|
+
return arrayLikeIterator(this, function(i, v) { return [i, v]; });
|
|
161
|
+
};
|
|
162
|
+
proto.keys = function() {
|
|
163
|
+
return arrayLikeIterator(this, function(i) { return i; });
|
|
164
|
+
};
|
|
165
|
+
proto.values = function() {
|
|
166
|
+
return arrayLikeIterator(this, function(i, v) { return v; });
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
proto.item = function(index) {
|
|
170
|
+
return index >= 0 && index < this.length ? this[index] : null;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// namedItem() is spec'd on HTMLCollection only (matches by id, then by
|
|
174
|
+
// name), not NodeList — but both share this one LiveCollection class (see
|
|
175
|
+
// the forEach() comment above), so this stays a no-op-safe lookup for
|
|
176
|
+
// NodeLists of non-Elements (text/comment nodes have no getAttribute).
|
|
177
|
+
proto.namedItem = function(name) {
|
|
178
|
+
for (var i = 0; i < this.length; i++) {
|
|
179
|
+
var item = this[i];
|
|
180
|
+
if (typeof item.getAttribute !== 'function') continue;
|
|
181
|
+
if (item.getAttribute('id') === name || item.getAttribute('name') === name) return item;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
delete globalThis.__LiveCollectionProto;
|
|
187
|
+
})();
|
|
188
|
+
)JS";
|
|
189
|
+
|
|
190
|
+
JSValue make(JSContext* ctx, void* owner, LiveKind kind, const std::string& selector) {
|
|
191
|
+
auto* data = new LiveCollectionData{ owner, kind, selector };
|
|
192
|
+
JSValue obj = JS_NewObjectClass(ctx, js_live_collection_class_id);
|
|
193
|
+
JS_SetOpaque(obj, data);
|
|
194
|
+
return obj;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
} // namespace
|
|
198
|
+
|
|
199
|
+
void LiveCollectionBindings::install(JSContext* ctx) {
|
|
200
|
+
if (js_live_collection_class_id == 0) JS_NewClassID(&js_live_collection_class_id);
|
|
201
|
+
JS_NewClass(JS_GetRuntime(ctx), js_live_collection_class_id, &js_livecoll_class);
|
|
202
|
+
|
|
203
|
+
JSValue proto = JS_NewObject(ctx);
|
|
204
|
+
JS_SetClassProto(ctx, js_live_collection_class_id, JS_DupValue(ctx, proto));
|
|
205
|
+
|
|
206
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
207
|
+
JS_SetPropertyStr(ctx, global, "__LiveCollectionProto", proto);
|
|
208
|
+
JS_FreeValue(ctx, global);
|
|
209
|
+
|
|
210
|
+
JSValue result = JS_Eval(ctx, kIteratorBootstrapScript, strlen(kIteratorBootstrapScript),
|
|
211
|
+
"<live-collection-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
212
|
+
if (JS_IsException(result)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
213
|
+
JS_FreeValue(ctx, result);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
JSValue LiveCollectionBindings::makeChildren(JSContext* ctx, void* element) {
|
|
217
|
+
return make(ctx, element, LiveKind::CHILDREN, "");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
JSValue LiveCollectionBindings::makeChildNodes(JSContext* ctx, void* node) {
|
|
221
|
+
return make(ctx, node, LiveKind::CHILD_NODES, "");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
JSValue LiveCollectionBindings::makeBySelector(JSContext* ctx, void* owner_or_null, const std::string& selector) {
|
|
225
|
+
return make(ctx, owner_or_null, LiveKind::SELECTOR, selector);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
#include <string>
|
|
5
|
+
|
|
6
|
+
namespace margelo::nitro::nitrojsdom {
|
|
7
|
+
|
|
8
|
+
class LiveCollectionBindings {
|
|
9
|
+
public:
|
|
10
|
+
static void install(JSContext* ctx);
|
|
11
|
+
static JSValue makeChildren(JSContext* ctx, void* element);
|
|
12
|
+
static JSValue makeChildNodes(JSContext* ctx, void* node);
|
|
13
|
+
static JSValue makeBySelector(JSContext* ctx, void* owner_or_null, const std::string& selector);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#include "ShadowRootBindings.hpp"
|
|
2
|
+
#include "DOMExceptionBindings.hpp"
|
|
3
|
+
#include "../DOMBindingsInternal.hpp"
|
|
4
|
+
#include "../QuickJSRuntime.hpp"
|
|
5
|
+
#include "../../lexbor/LexborDocument.hpp"
|
|
6
|
+
#include <lexbor/html/html.h>
|
|
7
|
+
#include <lexbor/dom/dom.h>
|
|
8
|
+
#include <cstring>
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro::nitrojsdom {
|
|
11
|
+
|
|
12
|
+
namespace {
|
|
13
|
+
|
|
14
|
+
JSClassDef js_shadow_root_class = { "ShadowRoot", .finalizer = nullptr };
|
|
15
|
+
|
|
16
|
+
lxb_dom_shadow_root_t* unwrap_shadow_root(JSValue val) {
|
|
17
|
+
return static_cast<lxb_dom_shadow_root_t*>(JS_GetOpaque(val, js_shadow_root_class_id));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
JSValue make_shadow_root(JSContext* ctx, void* shadow_ptr) {
|
|
21
|
+
if (!shadow_ptr) return JS_NULL;
|
|
22
|
+
|
|
23
|
+
RuntimeContext* rctx = get_ctx(ctx);
|
|
24
|
+
if (rctx) {
|
|
25
|
+
auto it = rctx->node_wrapper_cache.find(shadow_ptr);
|
|
26
|
+
if (it != rctx->node_wrapper_cache.end()) {
|
|
27
|
+
return JS_DupValue(ctx, *static_cast<JSValue*>(it->second));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
JSValue obj = JS_NewObjectClass(ctx, js_shadow_root_class_id);
|
|
32
|
+
JS_SetOpaque(obj, shadow_ptr);
|
|
33
|
+
if (rctx) rctx->node_wrapper_cache[shadow_ptr] = new JSValue(JS_DupValue(ctx, obj));
|
|
34
|
+
return obj;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
JSValue js_shadow_get_host(JSContext* ctx, JSValue this_val) {
|
|
38
|
+
auto* shadow = unwrap_shadow_root(this_val);
|
|
39
|
+
if (!shadow) return JS_NULL;
|
|
40
|
+
return make_element(ctx, shadow->host);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
JSValue js_shadow_get_mode(JSContext* ctx, JSValue this_val) {
|
|
44
|
+
auto* shadow = unwrap_shadow_root(this_val);
|
|
45
|
+
if (!shadow) return JS_NewString(ctx, "open");
|
|
46
|
+
return JS_NewString(ctx, shadow->mode == LXB_DOM_SHADOW_ROOT_MODE_CLOSED ? "closed" : "open");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
JSValue js_shadow_get_innerHTML(JSContext* ctx, JSValue this_val) {
|
|
50
|
+
auto* shadow = unwrap_shadow_root(this_val);
|
|
51
|
+
if (!shadow) return JS_NewString(ctx, "");
|
|
52
|
+
std::string result;
|
|
53
|
+
lxb_dom_node_t* child = lxb_dom_interface_node(shadow)->first_child;
|
|
54
|
+
while (child) { result += serialize_node(child); child = child->next; }
|
|
55
|
+
return JS_NewString(ctx, result.c_str());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
JSValue js_shadow_set_innerHTML(JSContext* ctx, JSValue this_val, JSValue val) {
|
|
59
|
+
auto* shadow = unwrap_shadow_root(this_val);
|
|
60
|
+
if (!shadow) return JS_UNDEFINED;
|
|
61
|
+
const char* html = JS_ToCString(ctx, val);
|
|
62
|
+
if (!html) return JS_UNDEFINED;
|
|
63
|
+
|
|
64
|
+
lxb_dom_node_t* node = lxb_dom_interface_node(shadow);
|
|
65
|
+
auto* rctx = get_ctx(ctx);
|
|
66
|
+
std::vector<void*> old_children;
|
|
67
|
+
lxb_dom_node_t* child = node->first_child;
|
|
68
|
+
while (child) { old_children.push_back(child); child = child->next; }
|
|
69
|
+
if (!old_children.empty()) invalidate_node_cache_deep_batch(ctx, rctx, old_children);
|
|
70
|
+
|
|
71
|
+
get_doc(ctx)->setInnerHTMLOnShadowRoot(shadow, shadow->host, html);
|
|
72
|
+
JS_FreeCString(ctx, html);
|
|
73
|
+
return JS_UNDEFINED;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
JSValue js_shadow_querySelector(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
77
|
+
auto* shadow = unwrap_shadow_root(this_val);
|
|
78
|
+
if (!shadow || argc < 1) return JS_NULL;
|
|
79
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
80
|
+
if (!sel) return JS_NULL;
|
|
81
|
+
void* found = get_doc(ctx)->querySelectorFromEl(shadow, sel);
|
|
82
|
+
JS_FreeCString(ctx, sel);
|
|
83
|
+
return make_element(ctx, found);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
JSValue js_shadow_querySelectorAll(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
87
|
+
auto* shadow = unwrap_shadow_root(this_val);
|
|
88
|
+
if (!shadow || argc < 1) return JS_NewArray(ctx);
|
|
89
|
+
const char* sel = JS_ToCString(ctx, argv[0]);
|
|
90
|
+
if (!sel) return JS_NewArray(ctx);
|
|
91
|
+
auto found = get_doc(ctx)->querySelectorAllFromEl(shadow, sel);
|
|
92
|
+
JS_FreeCString(ctx, sel);
|
|
93
|
+
return make_element_array(ctx, found);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Element.prototype.attachShadow() / .shadowRoot ────────────────────────────
|
|
97
|
+
|
|
98
|
+
JSValue js_el_attachShadow(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
99
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
100
|
+
if (!el) return JS_UNDEFINED;
|
|
101
|
+
|
|
102
|
+
if (argc < 1 || !JS_IsObject(argv[0])) {
|
|
103
|
+
return JS_ThrowTypeError(ctx, "attachShadow() requires an options object with a 'mode' property");
|
|
104
|
+
}
|
|
105
|
+
JSValue mode_val = JS_GetPropertyStr(ctx, argv[0], "mode");
|
|
106
|
+
if (JS_IsException(mode_val)) return JS_EXCEPTION;
|
|
107
|
+
const char* mode_str = JS_ToCString(ctx, mode_val);
|
|
108
|
+
JS_FreeValue(ctx, mode_val);
|
|
109
|
+
if (!mode_str) return JS_EXCEPTION;
|
|
110
|
+
bool is_open = strcmp(mode_str, "open") == 0;
|
|
111
|
+
bool is_closed = strcmp(mode_str, "closed") == 0;
|
|
112
|
+
JS_FreeCString(ctx, mode_str);
|
|
113
|
+
if (!is_open && !is_closed) {
|
|
114
|
+
return JS_ThrowTypeError(ctx, "attachShadow() options.mode must be 'open' or 'closed'");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
auto* rctx = get_ctx(ctx);
|
|
118
|
+
if (rctx && rctx->element_shadow_roots.count(el)) {
|
|
119
|
+
return throw_dom_exception(ctx, "NotSupportedError", "This element already has an attached shadow root.");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
void* shadow = get_doc(ctx)->createShadowRoot(el, is_closed ? 1 : 0);
|
|
123
|
+
if (!shadow) return JS_NULL;
|
|
124
|
+
if (rctx) rctx->element_shadow_roots[el] = shadow;
|
|
125
|
+
|
|
126
|
+
return make_shadow_root(ctx, shadow);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
JSValue js_el_get_shadowRoot(JSContext* ctx, JSValue this_val) {
|
|
130
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
131
|
+
if (!el) return JS_NULL;
|
|
132
|
+
auto* rctx = get_ctx(ctx);
|
|
133
|
+
if (!rctx) return JS_NULL;
|
|
134
|
+
auto it = rctx->element_shadow_roots.find(el);
|
|
135
|
+
if (it == rctx->element_shadow_roots.end()) return JS_NULL;
|
|
136
|
+
auto* shadow = static_cast<lxb_dom_shadow_root_t*>(it->second);
|
|
137
|
+
if (shadow->mode == LXB_DOM_SHADOW_ROOT_MODE_CLOSED) return JS_NULL;
|
|
138
|
+
return make_shadow_root(ctx, shadow);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
} // namespace
|
|
142
|
+
|
|
143
|
+
void ShadowRootBindings::install(JSContext* ctx) {
|
|
144
|
+
if (js_shadow_root_class_id == 0) JS_NewClassID(&js_shadow_root_class_id);
|
|
145
|
+
JS_NewClass(JS_GetRuntime(ctx), js_shadow_root_class_id, &js_shadow_root_class);
|
|
146
|
+
|
|
147
|
+
JSValue node_proto = JS_GetClassProto(ctx, js_node_class_id);
|
|
148
|
+
JSValue proto = JS_NewObjectProto(ctx, node_proto);
|
|
149
|
+
JS_FreeValue(ctx, node_proto);
|
|
150
|
+
|
|
151
|
+
define_prop(ctx, proto, "host", js_shadow_get_host, nullptr);
|
|
152
|
+
define_prop(ctx, proto, "mode", js_shadow_get_mode, nullptr);
|
|
153
|
+
define_prop(ctx, proto, "innerHTML", js_shadow_get_innerHTML, js_shadow_set_innerHTML);
|
|
154
|
+
JS_SetPropertyStr(ctx, proto, "querySelector",
|
|
155
|
+
JS_NewCFunction(ctx, js_shadow_querySelector, "querySelector", 1));
|
|
156
|
+
JS_SetPropertyStr(ctx, proto, "querySelectorAll",
|
|
157
|
+
JS_NewCFunction(ctx, js_shadow_querySelectorAll, "querySelectorAll", 1));
|
|
158
|
+
|
|
159
|
+
JS_SetClassProto(ctx, js_shadow_root_class_id, proto);
|
|
160
|
+
JSValue shadow_proto_ref = JS_GetClassProto(ctx, js_shadow_root_class_id);
|
|
161
|
+
JS_FreeValue(ctx, define_global_constructor(ctx, "ShadowRoot", shadow_proto_ref));
|
|
162
|
+
JS_FreeValue(ctx, shadow_proto_ref);
|
|
163
|
+
|
|
164
|
+
JSValue element_proto = JS_GetClassProto(ctx, js_element_class_id);
|
|
165
|
+
JS_SetPropertyStr(ctx, element_proto, "attachShadow",
|
|
166
|
+
JS_NewCFunction(ctx, js_el_attachShadow, "attachShadow", 1));
|
|
167
|
+
define_prop(ctx, element_proto, "shadowRoot", js_el_get_shadowRoot, nullptr);
|
|
168
|
+
JS_FreeValue(ctx, element_proto);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::nitrojsdom {
|
|
6
|
+
|
|
7
|
+
// Registers the ShadowRoot class (host/mode/innerHTML/querySelector(All),
|
|
8
|
+
// plus Node-proto methods like appendChild/childNodes via prototype chain)
|
|
9
|
+
// and Element.prototype.attachShadow()/shadowRoot.
|
|
10
|
+
//
|
|
11
|
+
// A ShadowRoot wraps a real lxb_dom_shadow_root_t*, which embeds a
|
|
12
|
+
// lxb_dom_document_fragment_t as its first member — so it shares the
|
|
13
|
+
// document fragment's node-tree layout, and generic Node methods work on it
|
|
14
|
+
// unmodified. Lexbor has no attachShadow-equivalent linking a host element
|
|
15
|
+
// to its shadow root, so that association is tracked in
|
|
16
|
+
// RuntimeContext::element_shadow_roots.
|
|
17
|
+
//
|
|
18
|
+
// No slotting/encapsulation/event retargeting — this models just enough of
|
|
19
|
+
// Shadow DOM for scripts that create/query/populate a scoped subtree.
|
|
20
|
+
//
|
|
21
|
+
// Must run after ElementBindings (globalThis.Element, js_node_class_id's
|
|
22
|
+
// proto) and DOMExceptionBindings (NotSupportedError on double-attach).
|
|
23
|
+
struct ShadowRootBindings {
|
|
24
|
+
static void install(JSContext* ctx);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#include "SlotBindings.hpp"
|
|
2
|
+
#include <cstring>
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::nitrojsdom {
|
|
5
|
+
|
|
6
|
+
namespace {
|
|
7
|
+
|
|
8
|
+
const char* kSlotBootstrapScript = R"JS(
|
|
9
|
+
(function() {
|
|
10
|
+
function isSlotElement(el) {
|
|
11
|
+
return !!el && el.tagName === 'SLOT';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Elements can carry a slot="..." attribute; anything else (text nodes,
|
|
15
|
+
// elements without the attribute) is assigned to the default slot ("").
|
|
16
|
+
function effectiveSlotName(node) {
|
|
17
|
+
if (node && node.nodeType === 1 && typeof node.getAttribute === 'function') {
|
|
18
|
+
return node.getAttribute('slot') || '';
|
|
19
|
+
}
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Walks up from a node living INSIDE a shadow tree (e.g. a <slot>) to find
|
|
24
|
+
// its containing ShadowRoot.
|
|
25
|
+
function containingShadowRoot(node) {
|
|
26
|
+
var n = node ? node.parentNode : null;
|
|
27
|
+
while (n) {
|
|
28
|
+
if (n instanceof ShadowRoot) return n;
|
|
29
|
+
n = n.parentNode;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// For a light-DOM child of some shadow host, returns that host's shadow
|
|
35
|
+
// root (or null if its parent has none / it's closed-mode).
|
|
36
|
+
function hostShadowRootFor(node) {
|
|
37
|
+
var parent = node ? node.parentNode : null;
|
|
38
|
+
return (parent && parent.shadowRoot) ? parent.shadowRoot : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function slotName(slotEl) {
|
|
42
|
+
return slotEl.getAttribute('name') || '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Nodes assigned to `slotEl`: light-DOM children of the shadow root's host
|
|
46
|
+
// whose effective slot name matches this slot's name, in host child order.
|
|
47
|
+
function computeAssigned(slotEl) {
|
|
48
|
+
var root = containingShadowRoot(slotEl);
|
|
49
|
+
if (!root) return [];
|
|
50
|
+
var host = root.host;
|
|
51
|
+
if (!host) return [];
|
|
52
|
+
var name = slotName(slotEl);
|
|
53
|
+
var result = [];
|
|
54
|
+
var child = host.firstChild;
|
|
55
|
+
while (child) {
|
|
56
|
+
if (effectiveSlotName(child) === name) result.push(child);
|
|
57
|
+
child = child.nextSibling;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Element.prototype.assignedNodes = function(options) {
|
|
63
|
+
if (!isSlotElement(this)) return [];
|
|
64
|
+
var assigned = computeAssigned(this);
|
|
65
|
+
if (assigned.length === 0 && options && options.flatten) {
|
|
66
|
+
var fallback = [];
|
|
67
|
+
var c = this.firstChild;
|
|
68
|
+
while (c) { fallback.push(c); c = c.nextSibling; }
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
71
|
+
return assigned;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
Element.prototype.assignedElements = function(options) {
|
|
75
|
+
return this.assignedNodes(options).filter(function(n) { return n.nodeType === 1; });
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ── slotchange (best-effort — see SlotBindings.hpp for exact triggers) ────
|
|
79
|
+
|
|
80
|
+
function sameAssignment(a, b) {
|
|
81
|
+
if (a.length !== b.length) return false;
|
|
82
|
+
for (var i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function notifySlotChange(root) {
|
|
87
|
+
if (!root || typeof root.querySelectorAll !== 'function') return;
|
|
88
|
+
var slots = root.querySelectorAll('slot');
|
|
89
|
+
for (var i = 0; i < slots.length; i++) {
|
|
90
|
+
var slot = slots[i];
|
|
91
|
+
var next = computeAssigned(slot);
|
|
92
|
+
var prev = slot.__slotAssigned || [];
|
|
93
|
+
if (!sameAssignment(prev, next)) {
|
|
94
|
+
slot.__slotAssigned = next;
|
|
95
|
+
slot.dispatchEvent(new Event('slotchange', { bubbles: true }));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var origAppendChild = Node.prototype.appendChild;
|
|
101
|
+
Node.prototype.appendChild = function(child) {
|
|
102
|
+
var result = origAppendChild.call(this, child);
|
|
103
|
+
if (this.shadowRoot) notifySlotChange(this.shadowRoot);
|
|
104
|
+
return result;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
var origRemoveChild = Node.prototype.removeChild;
|
|
108
|
+
Node.prototype.removeChild = function(child) {
|
|
109
|
+
var result = origRemoveChild.call(this, child);
|
|
110
|
+
if (this.shadowRoot) notifySlotChange(this.shadowRoot);
|
|
111
|
+
return result;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
var origSetAttribute = Element.prototype.setAttribute;
|
|
115
|
+
Element.prototype.setAttribute = function(name, value) {
|
|
116
|
+
var result = origSetAttribute.call(this, name, value);
|
|
117
|
+
if (String(name).toLowerCase() === 'slot') {
|
|
118
|
+
var root = hostShadowRootFor(this);
|
|
119
|
+
if (root) notifySlotChange(root);
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
var origRemoveAttribute = Element.prototype.removeAttribute;
|
|
125
|
+
Element.prototype.removeAttribute = function(name) {
|
|
126
|
+
var result = origRemoveAttribute.call(this, name);
|
|
127
|
+
if (String(name).toLowerCase() === 'slot') {
|
|
128
|
+
var root = hostShadowRootFor(this);
|
|
129
|
+
if (root) notifySlotChange(root);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
var shadowInnerHTMLDesc = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'innerHTML');
|
|
135
|
+
if (shadowInnerHTMLDesc && shadowInnerHTMLDesc.set) {
|
|
136
|
+
var origShadowInnerHTMLSet = shadowInnerHTMLDesc.set;
|
|
137
|
+
Object.defineProperty(ShadowRoot.prototype, 'innerHTML', {
|
|
138
|
+
get: shadowInnerHTMLDesc.get,
|
|
139
|
+
set: function(html) {
|
|
140
|
+
origShadowInnerHTMLSet.call(this, html);
|
|
141
|
+
notifySlotChange(this);
|
|
142
|
+
},
|
|
143
|
+
enumerable: shadowInnerHTMLDesc.enumerable,
|
|
144
|
+
configurable: true,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
)JS";
|
|
149
|
+
|
|
150
|
+
} // namespace
|
|
151
|
+
|
|
152
|
+
void SlotBindings::install(JSContext* ctx) {
|
|
153
|
+
JSValue result = JS_Eval(ctx, kSlotBootstrapScript, strlen(kSlotBootstrapScript),
|
|
154
|
+
"<slot-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
155
|
+
if (JS_IsException(result)) {
|
|
156
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
157
|
+
}
|
|
158
|
+
JS_FreeValue(ctx, result);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "quickjs.h"
|
|
4
|
+
|
|
5
|
+
namespace margelo::nitro::nitrojsdom {
|
|
6
|
+
|
|
7
|
+
// Registers <slot> assignment: Element.prototype.assignedNodes/
|
|
8
|
+
// assignedElements (tag-checked for "slot"), and a best-effort `slotchange`
|
|
9
|
+
// event. Pure JS, built entirely on top of already-exposed primitives
|
|
10
|
+
// (querySelectorAll, childNodes/parentNode, Element.prototype.shadowRoot,
|
|
11
|
+
// setAttribute/removeAttribute, ShadowRoot.prototype.innerHTML) via
|
|
12
|
+
// monkey-patching — Lexbor itself has no slot concept at all.
|
|
13
|
+
//
|
|
14
|
+
// Assignment algorithm: a light-DOM child of a shadow host is assigned to
|
|
15
|
+
// the <slot> (inside that host's shadow tree) whose `name` matches the
|
|
16
|
+
// child's `slot` attribute (or the unnamed/default slot, if the child has
|
|
17
|
+
// none), computed on demand from the live tree — never cached/stale.
|
|
18
|
+
//
|
|
19
|
+
// Scope/limitations (documented rather than silently wrong):
|
|
20
|
+
// - Does not implement the "first slot with a given name wins" tie-break for
|
|
21
|
+
// shadow trees with duplicate slot names — assumes one slot per name.
|
|
22
|
+
// - assignedNodes({flatten}) falls back to the slot's own light-DOM children
|
|
23
|
+
// (its fallback content) when it has no real assignment; it does not
|
|
24
|
+
// recursively flatten nested slots-inside-slots.
|
|
25
|
+
// - slotchange fires (best-effort, comparing each slot's previous vs.
|
|
26
|
+
// recomputed assignment) on: host.appendChild()/removeChild(), a
|
|
27
|
+
// light-DOM child's `slot` attribute changing via setAttribute/
|
|
28
|
+
// removeAttribute, and ShadowRoot.innerHTML assignment (covers newly
|
|
29
|
+
// added <slot> elements getting their first, non-empty assignment — the
|
|
30
|
+
// common attachShadow-then-populate pattern). NOT covered: before/after/
|
|
31
|
+
// replaceWith/append/prepend on the host, or mutating the shadow tree
|
|
32
|
+
// itself via appendChild/removeChild on the ShadowRoot directly.
|
|
33
|
+
// - Only reaches shadow roots visible via the public (mode-agnostic-for-us)
|
|
34
|
+
// Element.prototype.shadowRoot getter, so a *closed*-mode shadow host
|
|
35
|
+
// won't trigger slotchange from its light-DOM child mutations (the getter
|
|
36
|
+
// returns null for closed mode by spec) — assignedNodes()/assignedElements()
|
|
37
|
+
// on the <slot> itself are unaffected and still compute correctly.
|
|
38
|
+
//
|
|
39
|
+
// Must run after ElementBindings, EventBindings, and ShadowRootBindings.
|
|
40
|
+
struct SlotBindings {
|
|
41
|
+
static void install(JSContext* ctx);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#include "TemplateBindings.hpp"
|
|
2
|
+
#include "../DOMBindingsInternal.hpp"
|
|
3
|
+
#include "../../lexbor/LexborDocument.hpp"
|
|
4
|
+
#include <lexbor/dom/dom.h>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <cctype>
|
|
7
|
+
|
|
8
|
+
namespace margelo::nitro::nitrojsdom {
|
|
9
|
+
|
|
10
|
+
namespace {
|
|
11
|
+
|
|
12
|
+
bool is_template_element(lxb_dom_element_t* el) {
|
|
13
|
+
size_t len = 0;
|
|
14
|
+
const lxb_char_t* name = lxb_dom_element_local_name(el, &len);
|
|
15
|
+
if (!name || len != 8) return false;
|
|
16
|
+
for (size_t i = 0; i < len; i++) {
|
|
17
|
+
if (std::tolower(name[i]) != "template"[i]) return false;
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
JSValue js_el_get_content(JSContext* ctx, JSValue this_val) {
|
|
23
|
+
auto* el = unwrap_element(ctx, this_val);
|
|
24
|
+
if (!el || !is_template_element(el)) return JS_UNDEFINED;
|
|
25
|
+
return make_element(ctx, get_doc(ctx)->templateContent(el));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
} // namespace
|
|
29
|
+
|
|
30
|
+
void TemplateBindings::install(JSContext* ctx) {
|
|
31
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
32
|
+
JSValue element_ctor = JS_GetPropertyStr(ctx, global, "Element");
|
|
33
|
+
JSValue element_proto = JS_GetPropertyStr(ctx, element_ctor, "prototype");
|
|
34
|
+
|
|
35
|
+
define_prop(ctx, element_proto, "content", js_el_get_content, nullptr);
|
|
36
|
+
|
|
37
|
+
JS_FreeValue(ctx, element_proto);
|
|
38
|
+
JS_FreeValue(ctx, element_ctor);
|
|
39
|
+
JS_FreeValue(ctx, global);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
} // namespace margelo::nitro::nitrojsdom
|