@salve-software/react-native-nitro-jsdom 1.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/LICENSE +21 -0
- package/NitroJsdom.podspec +63 -0
- package/README.md +95 -0
- package/android/CMakeLists.txt +86 -0
- package/android/build.gradle +148 -0
- package/android/fix-prefab.gradle +51 -0
- package/android/gradle.properties +5 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/cpp-adapter.cpp +9 -0
- package/android/src/main/java/com/nitrojsdom/NitroJsdomPackage.kt +20 -0
- package/cpp/HybridHtmlSandbox.cpp +171 -0
- package/cpp/HybridHtmlSandbox.hpp +47 -0
- package/cpp/lexbor/LexborDocument.cpp +365 -0
- package/cpp/lexbor/LexborDocument.hpp +71 -0
- package/cpp/quickjs/DOMBindings.cpp +57 -0
- package/cpp/quickjs/DOMBindings.hpp +21 -0
- package/cpp/quickjs/DOMBindingsInternal.cpp +157 -0
- package/cpp/quickjs/DOMBindingsInternal.hpp +83 -0
- package/cpp/quickjs/MutationObservers.cpp +546 -0
- package/cpp/quickjs/MutationObservers.hpp +127 -0
- package/cpp/quickjs/QuickJSRuntime.cpp +475 -0
- package/cpp/quickjs/QuickJSRuntime.hpp +110 -0
- package/cpp/quickjs/Storage.cpp +130 -0
- package/cpp/quickjs/Storage.hpp +40 -0
- package/cpp/quickjs/bindings/ClassListBindings.cpp +241 -0
- package/cpp/quickjs/bindings/ClassListBindings.hpp +29 -0
- package/cpp/quickjs/bindings/DatasetBindings.cpp +120 -0
- package/cpp/quickjs/bindings/DatasetBindings.hpp +20 -0
- package/cpp/quickjs/bindings/DocumentBindings.cpp +155 -0
- package/cpp/quickjs/bindings/DocumentBindings.hpp +19 -0
- package/cpp/quickjs/bindings/ElementBindings.cpp +1106 -0
- package/cpp/quickjs/bindings/ElementBindings.hpp +19 -0
- package/cpp/quickjs/bindings/EventBindings.cpp +352 -0
- package/cpp/quickjs/bindings/EventBindings.hpp +21 -0
- package/cpp/quickjs/bindings/FetchBindings.cpp +347 -0
- package/cpp/quickjs/bindings/FetchBindings.hpp +16 -0
- package/cpp/quickjs/bindings/StyleBindings.cpp +245 -0
- package/cpp/quickjs/bindings/StyleBindings.hpp +22 -0
- package/cpp/quickjs/bindings/TimerBindings.cpp +94 -0
- package/cpp/quickjs/bindings/TimerBindings.hpp +16 -0
- package/cpp/quickjs/bindings/WindowBindings.cpp +211 -0
- package/cpp/quickjs/bindings/WindowBindings.hpp +16 -0
- package/ios/Bridge.h +8 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js +138 -0
- package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/index.js +17 -0
- package/lib/commonjs/classes/JSDOM/index.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/types/IFetchOptions.js +2 -0
- package/lib/commonjs/classes/JSDOM/types/IFetchOptions.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/types/IJSDOMOptions.js +6 -0
- package/lib/commonjs/classes/JSDOM/types/IJSDOMOptions.js.map +1 -0
- package/lib/commonjs/classes/JSDOM/types/index.js +2 -0
- package/lib/commonjs/classes/JSDOM/types/index.js.map +1 -0
- package/lib/commonjs/classes/index.js +17 -0
- package/lib/commonjs/classes/index.js.map +1 -0
- package/lib/commonjs/index.js +17 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/specs/HtmlSandbox.nitro.js +6 -0
- package/lib/commonjs/specs/HtmlSandbox.nitro.js.map +1 -0
- package/lib/module/classes/JSDOM/JSDOM.class.js +133 -0
- package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -0
- package/lib/module/classes/JSDOM/index.js +4 -0
- package/lib/module/classes/JSDOM/index.js.map +1 -0
- package/lib/module/classes/JSDOM/types/IFetchOptions.js +2 -0
- package/lib/module/classes/JSDOM/types/IFetchOptions.js.map +1 -0
- package/lib/module/classes/JSDOM/types/IJSDOMOptions.js +4 -0
- package/lib/module/classes/JSDOM/types/IJSDOMOptions.js.map +1 -0
- package/lib/module/classes/JSDOM/types/index.js +2 -0
- package/lib/module/classes/JSDOM/types/index.js.map +1 -0
- package/lib/module/classes/index.js +4 -0
- package/lib/module/classes/index.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/specs/HtmlSandbox.nitro.js +4 -0
- package/lib/module/specs/HtmlSandbox.nitro.js.map +1 -0
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts +74 -0
- package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/index.d.ts +3 -0
- package/lib/typescript/src/classes/JSDOM/index.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/types/IFetchOptions.d.ts +14 -0
- package/lib/typescript/src/classes/JSDOM/types/IFetchOptions.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts +57 -0
- package/lib/typescript/src/classes/JSDOM/types/IJSDOMOptions.d.ts.map +1 -0
- package/lib/typescript/src/classes/JSDOM/types/index.d.ts +3 -0
- package/lib/typescript/src/classes/JSDOM/types/index.d.ts.map +1 -0
- package/lib/typescript/src/classes/index.d.ts +2 -0
- package/lib/typescript/src/classes/index.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +13 -0
- package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -0
- package/nitro.json +26 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroJsdom+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroJsdom+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroJsdomOnLoad.cpp +49 -0
- package/nitrogen/generated/android/NitroJsdomOnLoad.hpp +34 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitrojsdom/NitroJsdomOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroJsdom+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroJsdom-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroJsdomAutolinking.mm +35 -0
- package/nitrogen/generated/ios/NitroJsdomAutolinking.swift +16 -0
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.cpp +26 -0
- package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +73 -0
- package/package.json +131 -0
- package/src/classes/JSDOM/JSDOM.class.ts +145 -0
- package/src/classes/JSDOM/index.ts +2 -0
- package/src/classes/JSDOM/types/IFetchOptions.ts +14 -0
- package/src/classes/JSDOM/types/IJSDOMOptions.ts +57 -0
- package/src/classes/JSDOM/types/index.ts +2 -0
- package/src/classes/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/specs/HtmlSandbox.nitro.ts +33 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
#include "MutationObservers.hpp"
|
|
2
|
+
#include "DOMBindingsInternal.hpp"
|
|
3
|
+
#include "QuickJSRuntime.hpp"
|
|
4
|
+
#include "quickjs.h"
|
|
5
|
+
#include <lexbor/dom/dom.h>
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
|
|
8
|
+
namespace margelo::nitro::nitrojsdom {
|
|
9
|
+
|
|
10
|
+
// ── Forward declarations ───────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
struct MutationObserverOpaque;
|
|
13
|
+
struct DispatchJob;
|
|
14
|
+
|
|
15
|
+
// ── JS Class IDs ──────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
static JSClassID js_mutation_observer_class_id = 0;
|
|
18
|
+
static JSClassID js_dispatch_carrier_class_id = 0;
|
|
19
|
+
|
|
20
|
+
static void mo_finalizer(JSRuntime*, JSValue val) {
|
|
21
|
+
auto* opaque = static_cast<MutationObserverOpaque*>(
|
|
22
|
+
JS_GetOpaque(val, js_mutation_observer_class_id));
|
|
23
|
+
delete opaque;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static void carrier_finalizer(JSRuntime*, JSValue val) {
|
|
27
|
+
auto* job = static_cast<DispatchJob*>(JS_GetOpaque(val, js_dispatch_carrier_class_id));
|
|
28
|
+
delete job;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static JSClassDef js_mutation_observer_class = { "MutationObserver", .finalizer = mo_finalizer };
|
|
32
|
+
static JSClassDef js_dispatch_carrier_class = { "_DispatchCarrier", .finalizer = carrier_finalizer };
|
|
33
|
+
|
|
34
|
+
// ── MutationObserver opaque data ──────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
struct MutationObserverOpaque {
|
|
37
|
+
MutationObservers* registry { nullptr };
|
|
38
|
+
uint32_t id { 0 };
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// ── Dispatch trampoline (passed to JS_EnqueueJob) ─────────────────────────────
|
|
42
|
+
|
|
43
|
+
// The job data: a heap-allocated pair of (registry*, observer_id).
|
|
44
|
+
// We can't store a RegisteredObserver* because the vector may reallocate.
|
|
45
|
+
struct DispatchJob {
|
|
46
|
+
MutationObservers* registry { nullptr };
|
|
47
|
+
uint32_t observer_id { 0 };
|
|
48
|
+
uint32_t generation { 0 };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
static JSValue dispatch_trampoline(JSContext* ctx, int argc, JSValue* argv) {
|
|
52
|
+
(void)argc; (void)argv;
|
|
53
|
+
// argv[0] holds a pointer to DispatchJob encoded as a carrier opaque object
|
|
54
|
+
DispatchJob* job = static_cast<DispatchJob*>(JS_GetOpaque(argv[0], js_dispatch_carrier_class_id));
|
|
55
|
+
if (!job) return JS_UNDEFINED;
|
|
56
|
+
|
|
57
|
+
MutationObservers* reg = job->registry;
|
|
58
|
+
uint32_t obs_id = job->observer_id;
|
|
59
|
+
|
|
60
|
+
// Find the observer
|
|
61
|
+
RegisteredObserver* obs = nullptr;
|
|
62
|
+
for (auto& o : reg->_observers_ref()) {
|
|
63
|
+
if (o.id == obs_id) { obs = &o; break; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!obs || obs->disconnected || obs->queue.empty() ||
|
|
67
|
+
obs->dispatch_generation != job->generation) {
|
|
68
|
+
if (obs) obs->dispatch_scheduled = false;
|
|
69
|
+
return JS_UNDEFINED;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Mark as no longer scheduled BEFORE calling callback (so re-entry works)
|
|
73
|
+
obs->dispatch_scheduled = false;
|
|
74
|
+
|
|
75
|
+
// Drain and materialize the record queue
|
|
76
|
+
auto records = std::move(obs->queue);
|
|
77
|
+
obs->queue.clear();
|
|
78
|
+
|
|
79
|
+
if (!obs->js_callback) return JS_UNDEFINED;
|
|
80
|
+
JSValue* cb = static_cast<JSValue*>(obs->js_callback);
|
|
81
|
+
|
|
82
|
+
// Build JS array of MutationRecord objects
|
|
83
|
+
JSValue arr = JS_NewArray(ctx);
|
|
84
|
+
for (size_t i = 0; i < records.size(); i++) {
|
|
85
|
+
auto& r = records[i];
|
|
86
|
+
|
|
87
|
+
// Skip records whose target node is no longer in the document
|
|
88
|
+
if (!reg->isInDocument_public(r.target)) continue;
|
|
89
|
+
|
|
90
|
+
JSValue rec_obj = JS_NewObject(ctx);
|
|
91
|
+
JS_SetPropertyStr(ctx, rec_obj, "type", JS_NewString(ctx, r.type.c_str()));
|
|
92
|
+
JS_SetPropertyStr(ctx, rec_obj, "target", make_element(ctx, r.target));
|
|
93
|
+
|
|
94
|
+
// addedNodes array
|
|
95
|
+
JSValue added = JS_NewArray(ctx);
|
|
96
|
+
for (size_t j = 0; j < r.addedNodes.size(); j++)
|
|
97
|
+
JS_SetPropertyUint32(ctx, added, (uint32_t)j, make_element(ctx, r.addedNodes[j]));
|
|
98
|
+
JS_SetPropertyStr(ctx, rec_obj, "addedNodes", added);
|
|
99
|
+
|
|
100
|
+
// removedNodes array
|
|
101
|
+
JSValue removed = JS_NewArray(ctx);
|
|
102
|
+
for (size_t j = 0; j < r.removedNodes.size(); j++)
|
|
103
|
+
JS_SetPropertyUint32(ctx, removed, (uint32_t)j, make_element(ctx, r.removedNodes[j]));
|
|
104
|
+
JS_SetPropertyStr(ctx, rec_obj, "removedNodes", removed);
|
|
105
|
+
|
|
106
|
+
JS_SetPropertyStr(ctx, rec_obj, "previousSibling",
|
|
107
|
+
r.previousSibling ? make_element(ctx, r.previousSibling) : JS_NULL);
|
|
108
|
+
JS_SetPropertyStr(ctx, rec_obj, "nextSibling",
|
|
109
|
+
r.nextSibling ? make_element(ctx, r.nextSibling) : JS_NULL);
|
|
110
|
+
JS_SetPropertyStr(ctx, rec_obj, "attributeName",
|
|
111
|
+
r.attributeName.empty() ? JS_NULL : JS_NewString(ctx, r.attributeName.c_str()));
|
|
112
|
+
JS_SetPropertyStr(ctx, rec_obj, "oldValue",
|
|
113
|
+
r.oldValue.has_value() ? JS_NewString(ctx, r.oldValue->c_str()) : JS_NULL);
|
|
114
|
+
|
|
115
|
+
JS_SetPropertyUint32(ctx, arr, (uint32_t)i, rec_obj);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Call the user callback: cb([records])
|
|
119
|
+
JSValue ret = JS_Call(ctx, *cb, JS_UNDEFINED, 1, &arr);
|
|
120
|
+
JS_FreeValue(ctx, arr);
|
|
121
|
+
|
|
122
|
+
if (JS_IsException(ret)) {
|
|
123
|
+
// Route exception as unhandled rejection via RuntimeContext pending_rejection
|
|
124
|
+
RuntimeContext* rctx = static_cast<RuntimeContext*>(JS_GetContextOpaque(ctx));
|
|
125
|
+
if (rctx) {
|
|
126
|
+
if (rctx->pending_rejection) {
|
|
127
|
+
JSValue* old = static_cast<JSValue*>(rctx->pending_rejection);
|
|
128
|
+
JS_FreeValue(ctx, *old);
|
|
129
|
+
delete old;
|
|
130
|
+
rctx->pending_rejection = nullptr;
|
|
131
|
+
}
|
|
132
|
+
JSValue ex = JS_GetException(ctx);
|
|
133
|
+
rctx->pending_rejection = new JSValue(JS_DupValue(ctx, ex));
|
|
134
|
+
JS_FreeValue(ctx, ex);
|
|
135
|
+
}
|
|
136
|
+
JS_FreeValue(ctx, ret);
|
|
137
|
+
return JS_UNDEFINED;
|
|
138
|
+
}
|
|
139
|
+
JS_FreeValue(ctx, ret);
|
|
140
|
+
return JS_UNDEFINED;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── JS MutationObserver constructor ──────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
static JSValue js_MutationObserver_constructor(JSContext* ctx, JSValue new_target,
|
|
146
|
+
int argc, JSValue* argv) {
|
|
147
|
+
(void)new_target;
|
|
148
|
+
if (argc < 1 || !JS_IsFunction(ctx, argv[0])) {
|
|
149
|
+
JS_ThrowTypeError(ctx, "MutationObserver requires a callback function");
|
|
150
|
+
return JS_EXCEPTION;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
RuntimeContext* rctx = static_cast<RuntimeContext*>(JS_GetContextOpaque(ctx));
|
|
154
|
+
if (!rctx || !rctx->mutation_observers) return JS_UNDEFINED;
|
|
155
|
+
|
|
156
|
+
// Dup the callback and store in the registry (target set later via observe())
|
|
157
|
+
void* cb = new JSValue(JS_DupValue(ctx, argv[0]));
|
|
158
|
+
uint32_t id = rctx->mutation_observers->addObserver(nullptr, ObserverInit{}, cb);
|
|
159
|
+
|
|
160
|
+
// Create JS object
|
|
161
|
+
JSValue obj = JS_NewObjectClass(ctx, js_mutation_observer_class_id);
|
|
162
|
+
auto* opaque = new MutationObserverOpaque{ rctx->mutation_observers.get(), id };
|
|
163
|
+
JS_SetOpaque(obj, opaque);
|
|
164
|
+
|
|
165
|
+
return obj;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── JS MutationObserver.observe(target, options) ──────────────────────────────
|
|
169
|
+
|
|
170
|
+
static JSValue js_MutationObserver_observe(JSContext* ctx, JSValue this_val,
|
|
171
|
+
int argc, JSValue* argv) {
|
|
172
|
+
auto* opaque = static_cast<MutationObserverOpaque*>(
|
|
173
|
+
JS_GetOpaque(this_val, js_mutation_observer_class_id));
|
|
174
|
+
if (!opaque || !opaque->registry) return JS_UNDEFINED;
|
|
175
|
+
if (argc < 1) return JS_UNDEFINED;
|
|
176
|
+
|
|
177
|
+
void* target = unwrap_node(ctx, argv[0]);
|
|
178
|
+
if (!target) return JS_UNDEFINED;
|
|
179
|
+
|
|
180
|
+
ObserverInit opts;
|
|
181
|
+
if (argc >= 2 && JS_IsObject(argv[1])) {
|
|
182
|
+
JSValue v;
|
|
183
|
+
auto getBool = [&](const char* name) -> bool {
|
|
184
|
+
v = JS_GetPropertyStr(ctx, argv[1], name);
|
|
185
|
+
bool r = JS_ToBool(ctx, v);
|
|
186
|
+
JS_FreeValue(ctx, v);
|
|
187
|
+
return r;
|
|
188
|
+
};
|
|
189
|
+
opts.childList = getBool("childList");
|
|
190
|
+
opts.attributes = getBool("attributes");
|
|
191
|
+
opts.characterData = getBool("characterData");
|
|
192
|
+
opts.subtree = getBool("subtree");
|
|
193
|
+
opts.attributeOldValue = getBool("attributeOldValue");
|
|
194
|
+
opts.characterDataOldValue = getBool("characterDataOldValue");
|
|
195
|
+
|
|
196
|
+
// attributeFilter
|
|
197
|
+
JSValue filterVal = JS_GetPropertyStr(ctx, argv[1], "attributeFilter");
|
|
198
|
+
if (JS_IsArray(ctx, filterVal)) {
|
|
199
|
+
std::vector<std::string> filters;
|
|
200
|
+
JSValue lenVal = JS_GetPropertyStr(ctx, filterVal, "length");
|
|
201
|
+
int32_t len = 0;
|
|
202
|
+
JS_ToInt32(ctx, &len, lenVal);
|
|
203
|
+
JS_FreeValue(ctx, lenVal);
|
|
204
|
+
for (int32_t i = 0; i < len; i++) {
|
|
205
|
+
JSValue item = JS_GetPropertyUint32(ctx, filterVal, (uint32_t)i);
|
|
206
|
+
const char* s = JS_ToCString(ctx, item);
|
|
207
|
+
if (s) { filters.push_back(s); JS_FreeCString(ctx, s); }
|
|
208
|
+
JS_FreeValue(ctx, item);
|
|
209
|
+
}
|
|
210
|
+
opts.attributeFilter = std::move(filters);
|
|
211
|
+
}
|
|
212
|
+
JS_FreeValue(ctx, filterVal);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if ((opts.attributeFilter.has_value() || opts.attributeOldValue) && !opts.attributes)
|
|
216
|
+
opts.attributes = true;
|
|
217
|
+
if (opts.characterDataOldValue && !opts.characterData)
|
|
218
|
+
opts.characterData = true;
|
|
219
|
+
|
|
220
|
+
if (!opts.childList && !opts.attributes && !opts.characterData) {
|
|
221
|
+
JS_ThrowTypeError(ctx,
|
|
222
|
+
"The options object must set at least one of 'attributes', 'characterData', or 'childList' to true.");
|
|
223
|
+
return JS_EXCEPTION;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Update the existing observer with the new target and options
|
|
227
|
+
opaque->registry->updateObserver(opaque->id, reinterpret_cast<lxb_dom_node_t*>(target), opts);
|
|
228
|
+
return JS_UNDEFINED;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── JS MutationObserver.disconnect() ─────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
static JSValue js_MutationObserver_disconnect(JSContext* ctx, JSValue this_val,
|
|
234
|
+
int, JSValue*) {
|
|
235
|
+
(void)ctx;
|
|
236
|
+
auto* opaque = static_cast<MutationObserverOpaque*>(
|
|
237
|
+
JS_GetOpaque(this_val, js_mutation_observer_class_id));
|
|
238
|
+
if (!opaque || !opaque->registry) return JS_UNDEFINED;
|
|
239
|
+
opaque->registry->disconnect(opaque->id);
|
|
240
|
+
return JS_UNDEFINED;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── JS MutationObserver.takeRecords() ────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
static JSValue js_MutationObserver_takeRecords(JSContext* ctx, JSValue this_val,
|
|
246
|
+
int, JSValue*) {
|
|
247
|
+
auto* opaque = static_cast<MutationObserverOpaque*>(
|
|
248
|
+
JS_GetOpaque(this_val, js_mutation_observer_class_id));
|
|
249
|
+
if (!opaque || !opaque->registry) return JS_NewArray(ctx);
|
|
250
|
+
|
|
251
|
+
auto records = opaque->registry->takeRecords(opaque->id);
|
|
252
|
+
JSValue arr = JS_NewArray(ctx);
|
|
253
|
+
for (size_t i = 0; i < records.size(); i++) {
|
|
254
|
+
auto& r = records[i];
|
|
255
|
+
JSValue rec_obj = JS_NewObject(ctx);
|
|
256
|
+
JS_SetPropertyStr(ctx, rec_obj, "type", JS_NewString(ctx, r.type.c_str()));
|
|
257
|
+
JS_SetPropertyStr(ctx, rec_obj, "target", make_element(ctx, r.target));
|
|
258
|
+
|
|
259
|
+
JSValue added = JS_NewArray(ctx);
|
|
260
|
+
for (size_t j = 0; j < r.addedNodes.size(); j++)
|
|
261
|
+
JS_SetPropertyUint32(ctx, added, (uint32_t)j, make_element(ctx, r.addedNodes[j]));
|
|
262
|
+
JS_SetPropertyStr(ctx, rec_obj, "addedNodes", added);
|
|
263
|
+
|
|
264
|
+
JSValue removed = JS_NewArray(ctx);
|
|
265
|
+
for (size_t j = 0; j < r.removedNodes.size(); j++)
|
|
266
|
+
JS_SetPropertyUint32(ctx, removed, (uint32_t)j, make_element(ctx, r.removedNodes[j]));
|
|
267
|
+
JS_SetPropertyStr(ctx, rec_obj, "removedNodes", removed);
|
|
268
|
+
|
|
269
|
+
JS_SetPropertyStr(ctx, rec_obj, "attributeName",
|
|
270
|
+
r.attributeName.empty() ? JS_NULL : JS_NewString(ctx, r.attributeName.c_str()));
|
|
271
|
+
JS_SetPropertyStr(ctx, rec_obj, "oldValue",
|
|
272
|
+
r.oldValue.has_value() ? JS_NewString(ctx, r.oldValue->c_str()) : JS_NULL);
|
|
273
|
+
|
|
274
|
+
JS_SetPropertyUint32(ctx, arr, (uint32_t)i, rec_obj);
|
|
275
|
+
}
|
|
276
|
+
return arr;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ── MutationObservers::install ────────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
void MutationObservers::install(JSContext* ctx, void* doc_root) {
|
|
282
|
+
_doc_root = doc_root;
|
|
283
|
+
|
|
284
|
+
// Register JS classes
|
|
285
|
+
JS_NewClassID(&js_mutation_observer_class_id);
|
|
286
|
+
JS_NewClass(JS_GetRuntime(ctx), js_mutation_observer_class_id, &js_mutation_observer_class);
|
|
287
|
+
JS_NewClassID(&js_dispatch_carrier_class_id);
|
|
288
|
+
JS_NewClass(JS_GetRuntime(ctx), js_dispatch_carrier_class_id, &js_dispatch_carrier_class);
|
|
289
|
+
|
|
290
|
+
// Build prototype
|
|
291
|
+
JSValue proto = JS_NewObject(ctx);
|
|
292
|
+
JS_SetPropertyStr(ctx, proto, "observe", JS_NewCFunction(ctx, js_MutationObserver_observe, "observe", 2));
|
|
293
|
+
JS_SetPropertyStr(ctx, proto, "disconnect", JS_NewCFunction(ctx, js_MutationObserver_disconnect, "disconnect", 0));
|
|
294
|
+
JS_SetPropertyStr(ctx, proto, "takeRecords", JS_NewCFunction(ctx, js_MutationObserver_takeRecords, "takeRecords", 0));
|
|
295
|
+
JS_SetClassProto(ctx, js_mutation_observer_class_id, proto);
|
|
296
|
+
|
|
297
|
+
// Register constructor on global
|
|
298
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
299
|
+
JSValue ctor = JS_NewCFunction2(ctx, js_MutationObserver_constructor,
|
|
300
|
+
"MutationObserver", 1, JS_CFUNC_constructor, 0);
|
|
301
|
+
JS_SetPropertyStr(ctx, global, "MutationObserver", ctor);
|
|
302
|
+
JS_FreeValue(ctx, global);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ── MutationObservers::addObserver ────────────────────────────────────────────
|
|
306
|
+
|
|
307
|
+
uint32_t MutationObservers::addObserver(void* target, ObserverInit options, void* js_callback) {
|
|
308
|
+
RegisteredObserver obs;
|
|
309
|
+
obs.id = _next_id++;
|
|
310
|
+
obs.target = target;
|
|
311
|
+
obs.options = std::move(options);
|
|
312
|
+
obs.js_callback = js_callback;
|
|
313
|
+
obs.dispatch_scheduled = false;
|
|
314
|
+
obs.disconnected = false;
|
|
315
|
+
_observers.push_back(std::move(obs));
|
|
316
|
+
_active_count++;
|
|
317
|
+
return _observers.back().id;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ── MutationObservers::updateObserver ────────────────────────────────────────
|
|
321
|
+
|
|
322
|
+
void MutationObservers::updateObserver(uint32_t id, void* target, ObserverInit options) {
|
|
323
|
+
for (auto& obs : _observers) {
|
|
324
|
+
if (obs.id == id) {
|
|
325
|
+
if (obs.disconnected) {
|
|
326
|
+
// Re-activating a previously disconnected observer
|
|
327
|
+
obs.disconnected = false;
|
|
328
|
+
_active_count++;
|
|
329
|
+
}
|
|
330
|
+
obs.target = target;
|
|
331
|
+
obs.options = std::move(options);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ── MutationObservers::disconnect ────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
void MutationObservers::disconnect(uint32_t observer_id) {
|
|
340
|
+
for (auto& obs : _observers) {
|
|
341
|
+
if (obs.id == observer_id) {
|
|
342
|
+
if (!obs.disconnected) {
|
|
343
|
+
obs.disconnected = true;
|
|
344
|
+
if (_active_count > 0) _active_count--;
|
|
345
|
+
}
|
|
346
|
+
obs.queue.clear();
|
|
347
|
+
obs.dispatch_scheduled = false;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ── MutationObservers::takeRecords ───────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
std::vector<MutationRecord> MutationObservers::takeRecords(uint32_t observer_id) {
|
|
356
|
+
for (auto& obs : _observers) {
|
|
357
|
+
if (obs.id == observer_id) {
|
|
358
|
+
auto records = std::move(obs.queue);
|
|
359
|
+
obs.queue.clear();
|
|
360
|
+
obs.dispatch_scheduled = false;
|
|
361
|
+
obs.dispatch_generation++;
|
|
362
|
+
return records;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return {};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ── Ancestry helpers ──────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
bool MutationObservers::isInDocument(void* node_ptr) const {
|
|
371
|
+
if (!node_ptr || !_doc_root) return false;
|
|
372
|
+
lxb_dom_node_t* node = static_cast<lxb_dom_node_t*>(node_ptr);
|
|
373
|
+
lxb_dom_node_t* walk = node;
|
|
374
|
+
while (walk) {
|
|
375
|
+
if (walk == static_cast<lxb_dom_node_t*>(_doc_root)) return true;
|
|
376
|
+
walk = walk->parent;
|
|
377
|
+
}
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
bool MutationObservers::isInDocument_public(void* node_ptr) const {
|
|
382
|
+
return isInDocument(node_ptr);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
bool MutationObservers::isAncestorOf(void* ancestor_ptr, void* node_ptr) const {
|
|
386
|
+
if (!ancestor_ptr || !node_ptr) return false;
|
|
387
|
+
lxb_dom_node_t* ancestor = static_cast<lxb_dom_node_t*>(ancestor_ptr);
|
|
388
|
+
lxb_dom_node_t* node = static_cast<lxb_dom_node_t*>(node_ptr);
|
|
389
|
+
lxb_dom_node_t* walk = node;
|
|
390
|
+
while (walk) {
|
|
391
|
+
if (walk == ancestor) return true;
|
|
392
|
+
walk = walk->parent;
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ── MutationObservers::scheduleDispatch ──────────────────────────────────────
|
|
398
|
+
|
|
399
|
+
void MutationObservers::scheduleDispatch(JSContext* ctx, RegisteredObserver& observer) {
|
|
400
|
+
if (observer.dispatch_scheduled || observer.disconnected) return;
|
|
401
|
+
observer.dispatch_scheduled = true;
|
|
402
|
+
|
|
403
|
+
JSValue carrier = JS_NewObjectClass(ctx, js_dispatch_carrier_class_id);
|
|
404
|
+
auto* job = new DispatchJob{ this, observer.id, observer.dispatch_generation };
|
|
405
|
+
JS_SetOpaque(carrier, job);
|
|
406
|
+
|
|
407
|
+
// Enqueue a job — the carrier JSValue is passed as argv[0]
|
|
408
|
+
JS_EnqueueJob(ctx, dispatch_trampoline, 1, &carrier);
|
|
409
|
+
JS_FreeValue(ctx, carrier);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ── notifyChildList ───────────────────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
void MutationObservers::notifyChildList(JSContext* ctx, void* target,
|
|
415
|
+
std::vector<void*> added,
|
|
416
|
+
std::vector<void*> removed,
|
|
417
|
+
void* prev_sibling, void* next_sibling) {
|
|
418
|
+
if (_observers.empty()) return;
|
|
419
|
+
|
|
420
|
+
for (auto& obs : _observers) {
|
|
421
|
+
if (obs.disconnected || !obs.options.childList) continue;
|
|
422
|
+
if (!obs.target) continue;
|
|
423
|
+
|
|
424
|
+
bool matches = (obs.target == target) ||
|
|
425
|
+
(obs.options.subtree && isAncestorOf(obs.target, target));
|
|
426
|
+
if (!matches) continue;
|
|
427
|
+
|
|
428
|
+
MutationRecord rec;
|
|
429
|
+
rec.type = "childList";
|
|
430
|
+
rec.target = target;
|
|
431
|
+
rec.addedNodes = added;
|
|
432
|
+
rec.removedNodes = removed;
|
|
433
|
+
rec.previousSibling = prev_sibling;
|
|
434
|
+
rec.nextSibling = next_sibling;
|
|
435
|
+
obs.queue.push_back(std::move(rec));
|
|
436
|
+
scheduleDispatch(ctx, obs);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── notifyAttribute ───────────────────────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
void MutationObservers::notifyAttribute(JSContext* ctx, void* target,
|
|
443
|
+
const std::string& attr_name,
|
|
444
|
+
std::optional<std::string> old_value) {
|
|
445
|
+
if (_observers.empty()) return;
|
|
446
|
+
|
|
447
|
+
for (auto& obs : _observers) {
|
|
448
|
+
if (obs.disconnected || !obs.options.attributes) continue;
|
|
449
|
+
if (!obs.target) continue;
|
|
450
|
+
|
|
451
|
+
bool matches = (obs.target == target) ||
|
|
452
|
+
(obs.options.subtree && isAncestorOf(obs.target, target));
|
|
453
|
+
if (!matches) continue;
|
|
454
|
+
|
|
455
|
+
// Check attributeFilter
|
|
456
|
+
if (obs.options.attributeFilter.has_value()) {
|
|
457
|
+
const auto& filters = obs.options.attributeFilter.value();
|
|
458
|
+
bool allowed = std::find(filters.begin(), filters.end(), attr_name) != filters.end();
|
|
459
|
+
if (!allowed) continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
MutationRecord rec;
|
|
463
|
+
rec.type = "attributes";
|
|
464
|
+
rec.target = target;
|
|
465
|
+
rec.attributeName = attr_name;
|
|
466
|
+
rec.oldValue = obs.options.attributeOldValue ? old_value : std::nullopt;
|
|
467
|
+
obs.queue.push_back(std::move(rec));
|
|
468
|
+
scheduleDispatch(ctx, obs);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ── notifyCharacterData ───────────────────────────────────────────────────────
|
|
473
|
+
|
|
474
|
+
void MutationObservers::notifyCharacterData(JSContext* ctx, void* target,
|
|
475
|
+
std::optional<std::string> old_value) {
|
|
476
|
+
if (_observers.empty()) return;
|
|
477
|
+
|
|
478
|
+
for (auto& obs : _observers) {
|
|
479
|
+
if (obs.disconnected || !obs.options.characterData) continue;
|
|
480
|
+
if (!obs.target) continue;
|
|
481
|
+
|
|
482
|
+
bool matches = (obs.target == target) ||
|
|
483
|
+
(obs.options.subtree && isAncestorOf(obs.target, target));
|
|
484
|
+
if (!matches) continue;
|
|
485
|
+
|
|
486
|
+
MutationRecord rec;
|
|
487
|
+
rec.type = "characterData";
|
|
488
|
+
rec.target = target;
|
|
489
|
+
rec.oldValue = obs.options.characterDataOldValue ? old_value : std::nullopt;
|
|
490
|
+
obs.queue.push_back(std::move(rec));
|
|
491
|
+
scheduleDispatch(ctx, obs);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// ── MutationObservers::disconnectDetachedTargets ──────────────────────────────
|
|
496
|
+
|
|
497
|
+
void MutationObservers::disconnectDetachedTargets(const std::vector<void*>& /*destroyed_nodes*/) {
|
|
498
|
+
if (_observers.empty()) return;
|
|
499
|
+
for (auto& obs : _observers) {
|
|
500
|
+
if (obs.disconnected || !obs.target) continue;
|
|
501
|
+
if (!isInDocument(obs.target)) {
|
|
502
|
+
obs.disconnected = true;
|
|
503
|
+
if (_active_count > 0) _active_count--;
|
|
504
|
+
obs.dispatch_scheduled = false;
|
|
505
|
+
for (auto& rec : obs.queue) {
|
|
506
|
+
rec.removedNodes.clear();
|
|
507
|
+
}
|
|
508
|
+
obs.queue.clear();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
for (auto& obs : _observers) {
|
|
512
|
+
if (obs.disconnected) continue;
|
|
513
|
+
for (auto& rec : obs.queue) {
|
|
514
|
+
if (!rec.removedNodes.empty()) {
|
|
515
|
+
rec.removedNodes.erase(
|
|
516
|
+
std::remove_if(rec.removedNodes.begin(), rec.removedNodes.end(),
|
|
517
|
+
[this](void* n) { return !isInDocument(n); }),
|
|
518
|
+
rec.removedNodes.end());
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// ── MutationObservers::clearAll ───────────────────────────────────────────────
|
|
525
|
+
|
|
526
|
+
void MutationObservers::clearAll(JSContext* ctx) {
|
|
527
|
+
for (auto& obs : _observers) {
|
|
528
|
+
if (obs.js_callback) {
|
|
529
|
+
JSValue* cb = static_cast<JSValue*>(obs.js_callback);
|
|
530
|
+
JS_FreeValue(ctx, *cb);
|
|
531
|
+
delete cb;
|
|
532
|
+
obs.js_callback = nullptr;
|
|
533
|
+
}
|
|
534
|
+
obs.queue.clear();
|
|
535
|
+
}
|
|
536
|
+
_observers.clear();
|
|
537
|
+
_active_count = 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// ── _observers_ref (used by trampoline) ──────────────────────────────────────
|
|
541
|
+
|
|
542
|
+
std::vector<RegisteredObserver>& MutationObservers::_observers_ref() {
|
|
543
|
+
return _observers;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <optional>
|
|
6
|
+
#include <memory>
|
|
7
|
+
#include <cstdint>
|
|
8
|
+
|
|
9
|
+
// Forward-declare the opaque JSContext type to avoid including quickjs.h here
|
|
10
|
+
typedef struct JSContext JSContext;
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro::nitrojsdom {
|
|
13
|
+
|
|
14
|
+
// ── MutationRecord ────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
struct MutationRecord {
|
|
17
|
+
std::string type; // "childList" | "attributes" | "characterData"
|
|
18
|
+
void* target { nullptr }; // lxb_dom_node_t*
|
|
19
|
+
std::vector<void*> addedNodes; // lxb_dom_node_t* list
|
|
20
|
+
std::vector<void*> removedNodes; // lxb_dom_node_t* list
|
|
21
|
+
void* previousSibling { nullptr }; // lxb_dom_node_t*
|
|
22
|
+
void* nextSibling { nullptr }; // lxb_dom_node_t*
|
|
23
|
+
std::string attributeName;
|
|
24
|
+
std::optional<std::string> oldValue;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// ── ObserverInit ──────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
struct ObserverInit {
|
|
30
|
+
bool childList { false };
|
|
31
|
+
bool attributes { false };
|
|
32
|
+
bool characterData { false };
|
|
33
|
+
bool subtree { false };
|
|
34
|
+
bool attributeOldValue { false };
|
|
35
|
+
bool characterDataOldValue { false };
|
|
36
|
+
std::optional<std::vector<std::string>> attributeFilter;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// ── RegisteredObserver ────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
struct RegisteredObserver {
|
|
42
|
+
uint32_t id;
|
|
43
|
+
void* target { nullptr }; // lxb_dom_node_t*
|
|
44
|
+
ObserverInit options;
|
|
45
|
+
void* js_callback { nullptr }; // JSValue* (heap-allocated, owned)
|
|
46
|
+
std::vector<MutationRecord> queue;
|
|
47
|
+
bool dispatch_scheduled { false };
|
|
48
|
+
bool disconnected { false };
|
|
49
|
+
uint32_t dispatch_generation { 0 };
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// ── MutationObservers ─────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
class MutationObservers {
|
|
55
|
+
public:
|
|
56
|
+
MutationObservers() = default;
|
|
57
|
+
~MutationObservers() = default;
|
|
58
|
+
|
|
59
|
+
// Called from DOMBindings::install to register the MutationObserver constructor
|
|
60
|
+
void install(JSContext* ctx, void* doc_root);
|
|
61
|
+
|
|
62
|
+
// Register an observer; returns its id
|
|
63
|
+
uint32_t addObserver(void* target, ObserverInit options, void* js_callback);
|
|
64
|
+
|
|
65
|
+
// Remove an observer and discard pending records
|
|
66
|
+
void disconnect(uint32_t observer_id);
|
|
67
|
+
|
|
68
|
+
// Flush and return pending records (without invoking callback)
|
|
69
|
+
std::vector<MutationRecord> takeRecords(uint32_t observer_id);
|
|
70
|
+
|
|
71
|
+
// Mutation notification hooks (called from DOMBindings after each mutation)
|
|
72
|
+
void notifyChildList(JSContext* ctx, void* target, std::vector<void*> added,
|
|
73
|
+
std::vector<void*> removed, void* prev_sibling, void* next_sibling);
|
|
74
|
+
void notifyAttribute(JSContext* ctx, void* target, const std::string& attr_name,
|
|
75
|
+
std::optional<std::string> old_value);
|
|
76
|
+
void notifyCharacterData(JSContext* ctx, void* target, std::optional<std::string> old_value);
|
|
77
|
+
|
|
78
|
+
// True when no active (non-disconnected) observers exist (fast-path check)
|
|
79
|
+
bool empty() const { return _active_count == 0; }
|
|
80
|
+
|
|
81
|
+
// True if at least one active observer requests attributeOldValue capture
|
|
82
|
+
bool hasAttributeOldValueObserver() const {
|
|
83
|
+
for (const auto& obs : _observers)
|
|
84
|
+
if (!obs.disconnected && obs.options.attributeOldValue) return true;
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// True if at least one active observer requests characterDataOldValue capture
|
|
89
|
+
bool hasCharacterDataOldValueObserver() const {
|
|
90
|
+
for (const auto& obs : _observers)
|
|
91
|
+
if (!obs.disconnected && obs.options.characterDataOldValue) return true;
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Free all held JSValue callbacks — must be called before JS_FreeContext
|
|
96
|
+
void clearAll(JSContext* ctx);
|
|
97
|
+
|
|
98
|
+
// Store the document root pointer for ancestry checks
|
|
99
|
+
void setDocumentRoot(void* doc_root) { _doc_root = doc_root; }
|
|
100
|
+
|
|
101
|
+
// Update target + options for an existing observer (called from observe())
|
|
102
|
+
void updateObserver(uint32_t id, void* target, ObserverInit options);
|
|
103
|
+
|
|
104
|
+
// Disconnect any observer whose target is in the given set of destroyed nodes
|
|
105
|
+
void disconnectDetachedTargets(const std::vector<void*>& destroyed_nodes);
|
|
106
|
+
|
|
107
|
+
// Public wrappers around private helpers (needed by dispatch_trampoline)
|
|
108
|
+
bool isInDocument_public(void* node) const;
|
|
109
|
+
std::vector<RegisteredObserver>& _observers_ref();
|
|
110
|
+
|
|
111
|
+
private:
|
|
112
|
+
std::vector<RegisteredObserver> _observers;
|
|
113
|
+
uint32_t _next_id { 1 };
|
|
114
|
+
uint32_t _active_count { 0 }; // number of non-disconnected observers
|
|
115
|
+
void* _doc_root { nullptr };
|
|
116
|
+
|
|
117
|
+
// Returns true if node is still attached to _doc_root's tree
|
|
118
|
+
bool isInDocument(void* node) const;
|
|
119
|
+
|
|
120
|
+
// Returns true if ancestor is an ancestor-or-equal of node
|
|
121
|
+
bool isAncestorOf(void* ancestor, void* node) const;
|
|
122
|
+
|
|
123
|
+
// Schedule a microtask dispatch for the observer if not already scheduled
|
|
124
|
+
void scheduleDispatch(JSContext* ctx, RegisteredObserver& observer);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
} // namespace margelo::nitro::nitrojsdom
|