@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
|
@@ -34,9 +34,17 @@ JSValue js_Event_constructor(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
|
34
34
|
else { cancelable = JS_ToBool(ctx, c) > 0; JS_FreeValue(ctx, c); }
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
bool composed = false;
|
|
38
|
+
if (argc >= 2 && JS_IsObject(argv[1])) {
|
|
39
|
+
JSValue c = JS_GetPropertyStr(ctx, argv[1], "composed");
|
|
40
|
+
if (JS_IsException(c)) { JS_FreeValue(ctx, JS_GetException(ctx)); }
|
|
41
|
+
else { composed = JS_ToBool(ctx, c) > 0; JS_FreeValue(ctx, c); }
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
|
|
38
45
|
JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, bubbles));
|
|
39
46
|
JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, cancelable));
|
|
47
|
+
JS_SetPropertyStr(ctx, obj, "composed", JS_NewBool(ctx, composed));
|
|
40
48
|
return obj;
|
|
41
49
|
}
|
|
42
50
|
|
|
@@ -64,10 +72,103 @@ JSValue js_CustomEvent_constructor(JSContext* ctx, JSValue, int argc, JSValue* a
|
|
|
64
72
|
else { cancelable = JS_ToBool(ctx, c) > 0; JS_FreeValue(ctx, c); }
|
|
65
73
|
}
|
|
66
74
|
|
|
75
|
+
bool composed = false;
|
|
76
|
+
if (argc >= 2 && JS_IsObject(argv[1])) {
|
|
77
|
+
JSValue c = JS_GetPropertyStr(ctx, argv[1], "composed");
|
|
78
|
+
if (JS_IsException(c)) { JS_FreeValue(ctx, JS_GetException(ctx)); }
|
|
79
|
+
else { composed = JS_ToBool(ctx, c) > 0; JS_FreeValue(ctx, c); }
|
|
80
|
+
}
|
|
81
|
+
|
|
67
82
|
JS_SetPropertyStr(ctx, obj, "detail", detail);
|
|
68
83
|
JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, bubbles));
|
|
69
84
|
JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, cancelable));
|
|
85
|
+
JS_SetPropertyStr(ctx, obj, "composed", JS_NewBool(ctx, composed));
|
|
86
|
+
JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
|
|
87
|
+
return obj;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Reads a numeric/string init-dict field, falling back to `def` if the field
|
|
91
|
+
// is absent or the dict itself wasn't passed — shared by KeyboardEvent and
|
|
92
|
+
// MouseEvent, whose constructors otherwise differ only in which fields they copy.
|
|
93
|
+
double get_num_init_field(JSContext* ctx, JSValue init, const char* name, double def) {
|
|
94
|
+
if (!JS_IsObject(init)) return def;
|
|
95
|
+
JSValue v = JS_GetPropertyStr(ctx, init, name);
|
|
96
|
+
if (JS_IsException(v)) { JS_FreeValue(ctx, JS_GetException(ctx)); return def; }
|
|
97
|
+
if (JS_IsUndefined(v)) { JS_FreeValue(ctx, v); return def; }
|
|
98
|
+
double d = def;
|
|
99
|
+
JS_ToFloat64(ctx, &d, v);
|
|
100
|
+
JS_FreeValue(ctx, v);
|
|
101
|
+
return d;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// get_bool_prop() (DOMBindingsInternal.hpp) assumes a real object receiver;
|
|
105
|
+
// init dicts are optional, so this guards the JS_IsObject check first.
|
|
106
|
+
bool get_bool_init_field(JSContext* ctx, JSValue init, const char* name) {
|
|
107
|
+
if (!JS_IsObject(init)) return false;
|
|
108
|
+
return get_bool_prop(ctx, init, name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
std::string get_str_init_field(JSContext* ctx, JSValue init, const char* name, const char* def) {
|
|
112
|
+
if (!JS_IsObject(init)) return def;
|
|
113
|
+
JSValue v = JS_GetPropertyStr(ctx, init, name);
|
|
114
|
+
if (JS_IsException(v)) { JS_FreeValue(ctx, JS_GetException(ctx)); return def; }
|
|
115
|
+
if (JS_IsUndefined(v)) { JS_FreeValue(ctx, v); return def; }
|
|
116
|
+
const char* s = JS_ToCString(ctx, v);
|
|
117
|
+
std::string result = s ? s : def;
|
|
118
|
+
if (s) JS_FreeCString(ctx, s);
|
|
119
|
+
JS_FreeValue(ctx, v);
|
|
120
|
+
return result;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
JSValue js_KeyboardEvent_constructor(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
124
|
+
JSValue obj = JS_NewObjectClass(ctx, js_event_class_id);
|
|
125
|
+
const char* type_str = (argc >= 1) ? JS_ToCString(ctx, argv[0]) : nullptr;
|
|
126
|
+
JS_SetPropertyStr(ctx, obj, "type", JS_NewString(ctx, type_str ? type_str : ""));
|
|
127
|
+
if (type_str) JS_FreeCString(ctx, type_str);
|
|
128
|
+
|
|
129
|
+
JSValue init = (argc >= 2) ? argv[1] : JS_UNDEFINED;
|
|
130
|
+
JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, get_bool_init_field(ctx, init, "bubbles")));
|
|
131
|
+
JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, get_bool_init_field(ctx, init, "cancelable")));
|
|
132
|
+
JS_SetPropertyStr(ctx, obj, "composed", JS_NewBool(ctx, get_bool_init_field(ctx, init, "composed")));
|
|
133
|
+
JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
|
|
134
|
+
|
|
135
|
+
std::string key = get_str_init_field(ctx, init, "key", "");
|
|
136
|
+
std::string code = get_str_init_field(ctx, init, "code", "");
|
|
137
|
+
JS_SetPropertyStr(ctx, obj, "key", JS_NewStringLen(ctx, key.c_str(), key.size()));
|
|
138
|
+
JS_SetPropertyStr(ctx, obj, "code", JS_NewStringLen(ctx, code.c_str(), code.size()));
|
|
139
|
+
JS_SetPropertyStr(ctx, obj, "location", JS_NewInt32(ctx, (int32_t)get_num_init_field(ctx, init, "location", 0)));
|
|
140
|
+
JS_SetPropertyStr(ctx, obj, "repeat", JS_NewBool(ctx, get_bool_init_field(ctx, init, "repeat")));
|
|
141
|
+
JS_SetPropertyStr(ctx, obj, "ctrlKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "ctrlKey")));
|
|
142
|
+
JS_SetPropertyStr(ctx, obj, "shiftKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "shiftKey")));
|
|
143
|
+
JS_SetPropertyStr(ctx, obj, "altKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "altKey")));
|
|
144
|
+
JS_SetPropertyStr(ctx, obj, "metaKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "metaKey")));
|
|
145
|
+
return obj;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
JSValue js_MouseEvent_constructor(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
149
|
+
JSValue obj = JS_NewObjectClass(ctx, js_event_class_id);
|
|
150
|
+
const char* type_str = (argc >= 1) ? JS_ToCString(ctx, argv[0]) : nullptr;
|
|
151
|
+
JS_SetPropertyStr(ctx, obj, "type", JS_NewString(ctx, type_str ? type_str : ""));
|
|
152
|
+
if (type_str) JS_FreeCString(ctx, type_str);
|
|
153
|
+
|
|
154
|
+
JSValue init = (argc >= 2) ? argv[1] : JS_UNDEFINED;
|
|
155
|
+
JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, get_bool_init_field(ctx, init, "bubbles")));
|
|
156
|
+
JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, get_bool_init_field(ctx, init, "cancelable")));
|
|
157
|
+
JS_SetPropertyStr(ctx, obj, "composed", JS_NewBool(ctx, get_bool_init_field(ctx, init, "composed")));
|
|
70
158
|
JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
|
|
159
|
+
|
|
160
|
+
JS_SetPropertyStr(ctx, obj, "clientX", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "clientX", 0)));
|
|
161
|
+
JS_SetPropertyStr(ctx, obj, "clientY", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "clientY", 0)));
|
|
162
|
+
JS_SetPropertyStr(ctx, obj, "screenX", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "screenX", 0)));
|
|
163
|
+
JS_SetPropertyStr(ctx, obj, "screenY", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "screenY", 0)));
|
|
164
|
+
JS_SetPropertyStr(ctx, obj, "pageX", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "pageX", 0)));
|
|
165
|
+
JS_SetPropertyStr(ctx, obj, "pageY", JS_NewFloat64(ctx, get_num_init_field(ctx, init, "pageY", 0)));
|
|
166
|
+
JS_SetPropertyStr(ctx, obj, "button", JS_NewInt32(ctx, (int32_t)get_num_init_field(ctx, init, "button", 0)));
|
|
167
|
+
JS_SetPropertyStr(ctx, obj, "buttons", JS_NewInt32(ctx, (int32_t)get_num_init_field(ctx, init, "buttons", 0)));
|
|
168
|
+
JS_SetPropertyStr(ctx, obj, "ctrlKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "ctrlKey")));
|
|
169
|
+
JS_SetPropertyStr(ctx, obj, "shiftKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "shiftKey")));
|
|
170
|
+
JS_SetPropertyStr(ctx, obj, "altKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "altKey")));
|
|
171
|
+
JS_SetPropertyStr(ctx, obj, "metaKey", JS_NewBool(ctx, get_bool_init_field(ctx, init, "metaKey")));
|
|
71
172
|
return obj;
|
|
72
173
|
}
|
|
73
174
|
|
|
@@ -90,6 +191,18 @@ JSValue js_event_stopImmediatePropagation(JSContext* ctx, JSValue this_val, int,
|
|
|
90
191
|
return JS_UNDEFINED;
|
|
91
192
|
}
|
|
92
193
|
|
|
194
|
+
// __composedPath is populated by dispatch_event_on_target() right before
|
|
195
|
+
// listener invocation begins; an event that was never dispatched (just
|
|
196
|
+
// constructed) has none, matching the spec's "not currently dispatched" case.
|
|
197
|
+
JSValue js_event_composedPath(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
198
|
+
JSValue path = JS_GetPropertyStr(ctx, this_val, "__composedPath");
|
|
199
|
+
if (JS_IsUndefined(path) || JS_IsException(path)) {
|
|
200
|
+
if (JS_IsException(path)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
201
|
+
return JS_NewArray(ctx);
|
|
202
|
+
}
|
|
203
|
+
return path;
|
|
204
|
+
}
|
|
205
|
+
|
|
93
206
|
// ── addEventListener / removeEventListener (element) ──────────────────────────
|
|
94
207
|
|
|
95
208
|
JSValue js_el_addEventListener(JSContext* ctx, JSValue this_val, int argc, JSValue* argv) {
|
|
@@ -106,7 +219,7 @@ JSValue js_el_addEventListener(JSContext* ctx, JSValue this_val, int argc, JSVal
|
|
|
106
219
|
|
|
107
220
|
void* node = lxb_dom_interface_node(el);
|
|
108
221
|
for (auto& l : rctx->listeners) {
|
|
109
|
-
if (l.node == node && l.event_type == event_type) {
|
|
222
|
+
if (l.node == node && l.event_type == event_type && !l.is_handler_property) {
|
|
110
223
|
JSValue* stored = static_cast<JSValue*>(l.callback);
|
|
111
224
|
if (JS_VALUE_GET_TAG(*stored) == JS_VALUE_GET_TAG(argv[1]) &&
|
|
112
225
|
JS_VALUE_GET_PTR(*stored) == JS_VALUE_GET_PTR(argv[1]))
|
|
@@ -136,7 +249,7 @@ JSValue js_el_removeEventListener(JSContext* ctx, JSValue this_val, int argc, JS
|
|
|
136
249
|
|
|
137
250
|
void* node = lxb_dom_interface_node(el);
|
|
138
251
|
for (auto it = rctx->listeners.begin(); it != rctx->listeners.end(); ) {
|
|
139
|
-
if (it->node == node && it->event_type == event_type) {
|
|
252
|
+
if (it->node == node && it->event_type == event_type && !it->is_handler_property) {
|
|
140
253
|
JSValue* stored_cb = static_cast<JSValue*>(it->callback);
|
|
141
254
|
if (JS_VALUE_GET_TAG(*stored_cb) == JS_VALUE_GET_TAG(argv[1]) &&
|
|
142
255
|
JS_VALUE_GET_PTR(*stored_cb) == JS_VALUE_GET_PTR(argv[1])) {
|
|
@@ -171,6 +284,7 @@ JSValue dispatch_event_on_target(JSContext* ctx, RuntimeContext* rctx, JSValue e
|
|
|
171
284
|
JS_FreeCString(ctx, type_str);
|
|
172
285
|
|
|
173
286
|
bool bubbles = get_bool_prop(ctx, event_obj, "bubbles");
|
|
287
|
+
bool composed = get_bool_prop(ctx, event_obj, "composed");
|
|
174
288
|
|
|
175
289
|
lxb_dom_node_t* doc_node = nullptr;
|
|
176
290
|
if (rctx->document) {
|
|
@@ -184,7 +298,46 @@ JSValue dispatch_event_on_target(JSContext* ctx, RuntimeContext* rctx, JSValue e
|
|
|
184
298
|
std::vector<lxb_dom_node_t*> chain;
|
|
185
299
|
chain.push_back(target_node);
|
|
186
300
|
if (bubbles) {
|
|
187
|
-
|
|
301
|
+
// Climb ancestors; when we reach the top of a shadow tree (a registered
|
|
302
|
+
// ShadowRoot — it has no `parent` in the node tree, since Lexbor doesn't
|
|
303
|
+
// link a shadow root into its host's child list) and the event is
|
|
304
|
+
// composed, retarget through the shadow host (found by reverse-lookup in
|
|
305
|
+
// element_shadow_roots) and keep climbing into the light tree above it —
|
|
306
|
+
// this is how a composed event (e.g. 'click') escapes a shadow boundary
|
|
307
|
+
// while a non-composed one stays contained. The ShadowRoot node itself is
|
|
308
|
+
// never added to the chain/composedPath (unlike real DOM, which does
|
|
309
|
+
// include it): this sandbox's ShadowRoot wrapper only exists via
|
|
310
|
+
// ShadowRootBindings' host-keyed cache, so building one here via the
|
|
311
|
+
// generic make_element() path (used for every other chain entry) would
|
|
312
|
+
// produce a broken, non-ShadowRoot-shaped object instead. Simplification:
|
|
313
|
+
// this sandbox has no separate capture phase, so composedPath()/
|
|
314
|
+
// retargeting are only computed along the bubble chain — a non-bubbling
|
|
315
|
+
// event's composedPath() is just [target].
|
|
316
|
+
lxb_dom_node_t* p = target_node->parent;
|
|
317
|
+
while (p) {
|
|
318
|
+
void* host = nullptr;
|
|
319
|
+
for (auto& kv : rctx->element_shadow_roots) {
|
|
320
|
+
if (kv.second == static_cast<void*>(p)) { host = kv.first; break; }
|
|
321
|
+
}
|
|
322
|
+
if (host) {
|
|
323
|
+
if (!composed) break;
|
|
324
|
+
p = static_cast<lxb_dom_node_t*>(host);
|
|
325
|
+
}
|
|
326
|
+
chain.push_back(p);
|
|
327
|
+
p = p->parent;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
{
|
|
332
|
+
JSValue path_arr = JS_NewArray(ctx);
|
|
333
|
+
for (size_t i = 0; i < chain.size(); i++) {
|
|
334
|
+
lxb_dom_node_t* level = chain[i];
|
|
335
|
+
JSValue entry = (level == doc_node)
|
|
336
|
+
? ([&]() { JSValue g = JS_GetGlobalObject(ctx); JSValue d = JS_GetPropertyStr(ctx, g, "document"); JS_FreeValue(ctx, g); return d; })()
|
|
337
|
+
: make_element(ctx, level);
|
|
338
|
+
JS_SetPropertyUint32(ctx, path_arr, (uint32_t)i, entry);
|
|
339
|
+
}
|
|
340
|
+
JS_SetPropertyStr(ctx, event_obj, "__composedPath", path_arr);
|
|
188
341
|
}
|
|
189
342
|
|
|
190
343
|
for (lxb_dom_node_t* level : chain) {
|
|
@@ -204,11 +357,13 @@ JSValue dispatch_event_on_target(JSContext* ctx, RuntimeContext* rctx, JSValue e
|
|
|
204
357
|
|
|
205
358
|
std::vector<JSValue> cbs_to_fire;
|
|
206
359
|
std::vector<JSValue*> cb_ptrs;
|
|
360
|
+
std::vector<bool> cb_is_handler_property;
|
|
207
361
|
for (auto& listener : rctx->listeners) {
|
|
208
362
|
if (listener.node == level && listener.event_type == event_type) {
|
|
209
363
|
JSValue* cb = static_cast<JSValue*>(listener.callback);
|
|
210
364
|
cbs_to_fire.push_back(JS_DupValue(ctx, *cb));
|
|
211
365
|
cb_ptrs.push_back(cb);
|
|
366
|
+
cb_is_handler_property.push_back(listener.is_handler_property);
|
|
212
367
|
}
|
|
213
368
|
}
|
|
214
369
|
|
|
@@ -231,7 +386,27 @@ JSValue dispatch_event_on_target(JSContext* ctx, RuntimeContext* rctx, JSValue e
|
|
|
231
386
|
JS_FreeValue(ctx, cbs_to_fire[i]);
|
|
232
387
|
continue;
|
|
233
388
|
}
|
|
234
|
-
JSValue ret
|
|
389
|
+
JSValue ret;
|
|
390
|
+
if (cb_is_handler_property[i] && event_type == "error") {
|
|
391
|
+
JSValue message_val = JS_GetPropertyStr(ctx, event_obj, "message");
|
|
392
|
+
JSValue error_val = JS_GetPropertyStr(ctx, event_obj, "error");
|
|
393
|
+
JSValue global = JS_GetGlobalObject(ctx);
|
|
394
|
+
JSValue location_val = JS_GetPropertyStr(ctx, global, "location");
|
|
395
|
+
JSValue source_val = JS_GetPropertyStr(ctx, location_val, "href");
|
|
396
|
+
JS_FreeValue(ctx, location_val);
|
|
397
|
+
JS_FreeValue(ctx, global);
|
|
398
|
+
JSValue lineno_val = JS_NewInt32(ctx, 0);
|
|
399
|
+
JSValue colno_val = JS_NewInt32(ctx, 0);
|
|
400
|
+
JSValue args[5] = { message_val, source_val, lineno_val, colno_val, error_val };
|
|
401
|
+
ret = JS_Call(ctx, cbs_to_fire[i], current_target, 5, args);
|
|
402
|
+
JS_FreeValue(ctx, message_val);
|
|
403
|
+
JS_FreeValue(ctx, source_val);
|
|
404
|
+
JS_FreeValue(ctx, lineno_val);
|
|
405
|
+
JS_FreeValue(ctx, colno_val);
|
|
406
|
+
JS_FreeValue(ctx, error_val);
|
|
407
|
+
} else {
|
|
408
|
+
ret = JS_Call(ctx, cbs_to_fire[i], current_target, 1, &event_obj);
|
|
409
|
+
}
|
|
235
410
|
JS_FreeValue(ctx, cbs_to_fire[i]);
|
|
236
411
|
if (JS_IsException(ret)) {
|
|
237
412
|
JS_FreeValue(ctx, ret);
|
|
@@ -248,14 +423,23 @@ JSValue dispatch_event_on_target(JSContext* ctx, RuntimeContext* rctx, JSValue e
|
|
|
248
423
|
JS_FreeValue(ctx, msg_prop);
|
|
249
424
|
JS_FreeValue(ctx, ex);
|
|
250
425
|
JS_ThrowInternalError(ctx, "%s", err.c_str());
|
|
426
|
+
JS_SetPropertyStr(ctx, event_obj, "__composedPath", JS_NewArray(ctx));
|
|
251
427
|
return JS_EXCEPTION;
|
|
252
428
|
}
|
|
429
|
+
if (cb_is_handler_property[i] && JS_IsBool(ret) && JS_ToBool(ctx, ret) == 0 &&
|
|
430
|
+
get_bool_prop(ctx, event_obj, "cancelable")) {
|
|
431
|
+
JS_SetPropertyStr(ctx, event_obj, "defaultPrevented", JS_NewBool(ctx, true));
|
|
432
|
+
}
|
|
253
433
|
JS_FreeValue(ctx, ret);
|
|
254
434
|
}
|
|
255
435
|
JS_FreeValue(ctx, current_target);
|
|
256
436
|
}
|
|
257
437
|
|
|
258
438
|
bool defaultPrevented = get_bool_prop(ctx, event_obj, "defaultPrevented");
|
|
439
|
+
// Per spec, composedPath() only returns a populated path while the event is
|
|
440
|
+
// actively being dispatched — reset it so a reference to `event` saved by
|
|
441
|
+
// a listener returns [] if composedPath() is called after dispatch ends.
|
|
442
|
+
JS_SetPropertyStr(ctx, event_obj, "__composedPath", JS_NewArray(ctx));
|
|
259
443
|
return JS_NewBool(ctx, !defaultPrevented);
|
|
260
444
|
}
|
|
261
445
|
|
|
@@ -284,7 +468,7 @@ JSValue js_doc_addEventListener(JSContext* ctx, JSValue, int argc, JSValue* argv
|
|
|
284
468
|
static_cast<lxb_html_document_t*>(rctx->document->documentHtmlPtr())));
|
|
285
469
|
|
|
286
470
|
for (auto& l : rctx->listeners) {
|
|
287
|
-
if (l.node == doc_node && l.event_type == event_type) {
|
|
471
|
+
if (l.node == doc_node && l.event_type == event_type && !l.is_handler_property) {
|
|
288
472
|
JSValue* stored = static_cast<JSValue*>(l.callback);
|
|
289
473
|
if (JS_VALUE_GET_TAG(*stored) == JS_VALUE_GET_TAG(argv[1]) &&
|
|
290
474
|
JS_VALUE_GET_PTR(*stored) == JS_VALUE_GET_PTR(argv[1]))
|
|
@@ -300,6 +484,39 @@ JSValue js_doc_addEventListener(JSContext* ctx, JSValue, int argc, JSValue* argv
|
|
|
300
484
|
return JS_UNDEFINED;
|
|
301
485
|
}
|
|
302
486
|
|
|
487
|
+
JSValue js_doc_removeEventListener(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
488
|
+
auto* rctx = get_ctx(ctx);
|
|
489
|
+
if (!rctx || argc < 2 || !JS_IsFunction(ctx, argv[1])) return JS_UNDEFINED;
|
|
490
|
+
if (!rctx->document) return JS_UNDEFINED;
|
|
491
|
+
|
|
492
|
+
const char* type_str = JS_ToCString(ctx, argv[0]);
|
|
493
|
+
if (!type_str) return JS_UNDEFINED;
|
|
494
|
+
std::string event_type(type_str);
|
|
495
|
+
JS_FreeCString(ctx, type_str);
|
|
496
|
+
|
|
497
|
+
void* doc_node = lxb_dom_interface_node(
|
|
498
|
+
lxb_dom_interface_document(
|
|
499
|
+
static_cast<lxb_html_document_t*>(rctx->document->documentHtmlPtr())));
|
|
500
|
+
|
|
501
|
+
for (auto it = rctx->listeners.begin(); it != rctx->listeners.end(); ) {
|
|
502
|
+
if (it->node == doc_node && it->event_type == event_type) {
|
|
503
|
+
JSValue* stored_cb = static_cast<JSValue*>(it->callback);
|
|
504
|
+
if (JS_VALUE_GET_TAG(*stored_cb) == JS_VALUE_GET_TAG(argv[1]) &&
|
|
505
|
+
JS_VALUE_GET_PTR(*stored_cb) == JS_VALUE_GET_PTR(argv[1])) {
|
|
506
|
+
JS_FreeValue(ctx, *stored_cb);
|
|
507
|
+
delete stored_cb;
|
|
508
|
+
rctx->listeners.erase(it);
|
|
509
|
+
break; // remove first matching listener only (DOM spec)
|
|
510
|
+
} else {
|
|
511
|
+
++it;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
++it;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return JS_UNDEFINED;
|
|
518
|
+
}
|
|
519
|
+
|
|
303
520
|
JSValue js_doc_dispatchEvent(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
304
521
|
auto* rctx = get_ctx(ctx);
|
|
305
522
|
if (!rctx || argc < 1) return JS_TRUE;
|
|
@@ -311,6 +528,130 @@ JSValue js_doc_dispatchEvent(JSContext* ctx, JSValue, int argc, JSValue* argv) {
|
|
|
311
528
|
return dispatch_event_on_target(ctx, rctx, argv[0], node);
|
|
312
529
|
}
|
|
313
530
|
|
|
531
|
+
const char* kHandlerEventTypes[] = { "click", "load", "error", "unhandledrejection" };
|
|
532
|
+
|
|
533
|
+
JSValue get_handler_prop_for_node(JSContext* ctx, RuntimeContext* rctx, void* node, const std::string& event_type) {
|
|
534
|
+
if (!rctx || !node) return JS_NULL;
|
|
535
|
+
for (auto& l : rctx->listeners) {
|
|
536
|
+
if (l.node == node && l.event_type == event_type && l.is_handler_property)
|
|
537
|
+
return JS_DupValue(ctx, *static_cast<JSValue*>(l.callback));
|
|
538
|
+
}
|
|
539
|
+
return JS_NULL;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
JSValue set_handler_prop_for_node(JSContext* ctx, RuntimeContext* rctx, void* node, const std::string& event_type, JSValue val) {
|
|
543
|
+
if (!rctx || !node) return JS_UNDEFINED;
|
|
544
|
+
for (auto it = rctx->listeners.begin(); it != rctx->listeners.end(); ++it) {
|
|
545
|
+
if (it->node == node && it->event_type == event_type && it->is_handler_property) {
|
|
546
|
+
JSValue* stored = static_cast<JSValue*>(it->callback);
|
|
547
|
+
JS_FreeValue(ctx, *stored);
|
|
548
|
+
delete stored;
|
|
549
|
+
rctx->listeners.erase(it);
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (JS_IsFunction(ctx, val)) {
|
|
554
|
+
EventListener listener;
|
|
555
|
+
listener.node = node;
|
|
556
|
+
listener.event_type = event_type;
|
|
557
|
+
listener.callback = new JSValue(JS_DupValue(ctx, val));
|
|
558
|
+
listener.is_handler_property = true;
|
|
559
|
+
rctx->listeners.push_back(std::move(listener));
|
|
560
|
+
}
|
|
561
|
+
return JS_UNDEFINED;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
JSValue js_el_get_handler(JSContext* ctx, JSValue this_val, int magic) {
|
|
565
|
+
auto* rctx = get_ctx(ctx);
|
|
566
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
567
|
+
return get_handler_prop_for_node(ctx, rctx, node, kHandlerEventTypes[magic]);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
JSValue js_el_set_handler(JSContext* ctx, JSValue this_val, JSValue val, int magic) {
|
|
571
|
+
auto* rctx = get_ctx(ctx);
|
|
572
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
573
|
+
return set_handler_prop_for_node(ctx, rctx, node, kHandlerEventTypes[magic], val);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
void* doc_node_of(RuntimeContext* rctx) {
|
|
577
|
+
if (!rctx || !rctx->document) return nullptr;
|
|
578
|
+
return lxb_dom_interface_node(lxb_dom_interface_document(
|
|
579
|
+
static_cast<lxb_html_document_t*>(rctx->document->documentHtmlPtr())));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
JSValue js_doc_get_handler(JSContext* ctx, JSValue, int magic) {
|
|
583
|
+
auto* rctx = get_ctx(ctx);
|
|
584
|
+
return get_handler_prop_for_node(ctx, rctx, doc_node_of(rctx), kHandlerEventTypes[magic]);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
JSValue js_doc_set_handler(JSContext* ctx, JSValue, JSValue val, int magic) {
|
|
588
|
+
auto* rctx = get_ctx(ctx);
|
|
589
|
+
return set_handler_prop_for_node(ctx, rctx, doc_node_of(rctx), kHandlerEventTypes[magic], val);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
void define_element_handler(JSContext* ctx, JSValue obj, const char* name, int magic) {
|
|
593
|
+
JSAtom atom = JS_NewAtom(ctx, name);
|
|
594
|
+
JSValue get_fn = JS_NewCFunction2(ctx, (JSCFunction*)js_el_get_handler, name, 0, JS_CFUNC_getter_magic, magic);
|
|
595
|
+
JSValue set_fn = JS_NewCFunction2(ctx, (JSCFunction*)js_el_set_handler, name, 1, JS_CFUNC_setter_magic, magic);
|
|
596
|
+
JS_DefinePropertyGetSet(ctx, obj, atom, get_fn, set_fn, JS_PROP_CONFIGURABLE);
|
|
597
|
+
JS_FreeAtom(ctx, atom);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
void define_doc_handler(JSContext* ctx, JSValue obj, const char* name, int magic) {
|
|
601
|
+
JSAtom atom = JS_NewAtom(ctx, name);
|
|
602
|
+
JSValue get_fn = JS_NewCFunction2(ctx, (JSCFunction*)js_doc_get_handler, name, 0, JS_CFUNC_getter_magic, magic);
|
|
603
|
+
JSValue set_fn = JS_NewCFunction2(ctx, (JSCFunction*)js_doc_set_handler, name, 1, JS_CFUNC_setter_magic, magic);
|
|
604
|
+
JS_DefinePropertyGetSet(ctx, obj, atom, get_fn, set_fn, JS_PROP_CONFIGURABLE);
|
|
605
|
+
JS_FreeAtom(ctx, atom);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
JSValue make_simple_event(JSContext* ctx, const char* type, bool bubbles, bool cancelable) {
|
|
609
|
+
JSValue obj = JS_NewObjectClass(ctx, js_event_class_id);
|
|
610
|
+
JS_SetPropertyStr(ctx, obj, "type", JS_NewString(ctx, type));
|
|
611
|
+
JS_SetPropertyStr(ctx, obj, "bubbles", JS_NewBool(ctx, bubbles));
|
|
612
|
+
JS_SetPropertyStr(ctx, obj, "cancelable", JS_NewBool(ctx, cancelable));
|
|
613
|
+
JS_SetPropertyStr(ctx, obj, "defaultPrevented", JS_NewBool(ctx, false));
|
|
614
|
+
return obj;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
void fire_simple_event(JSContext* ctx, RuntimeContext* rctx, const char* type, bool bubbles, bool cancelable, lxb_dom_node_t* node) {
|
|
618
|
+
JSValue evt = make_simple_event(ctx, type, bubbles, cancelable);
|
|
619
|
+
JSValue r = dispatch_event_on_target(ctx, rctx, evt, node);
|
|
620
|
+
JS_FreeValue(ctx, evt);
|
|
621
|
+
if (JS_IsException(r)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
622
|
+
else JS_FreeValue(ctx, r);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
JSValue js_el_click(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
626
|
+
auto* rctx = get_ctx(ctx);
|
|
627
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
628
|
+
if (!rctx || !node) return JS_UNDEFINED;
|
|
629
|
+
fire_simple_event(ctx, rctx, "click", true, true, node);
|
|
630
|
+
return JS_UNDEFINED;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
JSValue js_el_focus(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
634
|
+
auto* rctx = get_ctx(ctx);
|
|
635
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
636
|
+
if (!rctx || !node || rctx->active_element == node) return JS_UNDEFINED;
|
|
637
|
+
|
|
638
|
+
void* previous = rctx->active_element;
|
|
639
|
+
rctx->active_element = node;
|
|
640
|
+
if (previous) fire_simple_event(ctx, rctx, "blur", false, false, static_cast<lxb_dom_node_t*>(previous));
|
|
641
|
+
fire_simple_event(ctx, rctx, "focus", false, false, node);
|
|
642
|
+
return JS_UNDEFINED;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
JSValue js_el_blur(JSContext* ctx, JSValue this_val, int, JSValue*) {
|
|
646
|
+
auto* rctx = get_ctx(ctx);
|
|
647
|
+
auto* node = unwrap_node(ctx, this_val);
|
|
648
|
+
if (!rctx || !node || rctx->active_element != node) return JS_UNDEFINED;
|
|
649
|
+
|
|
650
|
+
rctx->active_element = nullptr;
|
|
651
|
+
fire_simple_event(ctx, rctx, "blur", false, false, node);
|
|
652
|
+
return JS_UNDEFINED;
|
|
653
|
+
}
|
|
654
|
+
|
|
314
655
|
} // namespace
|
|
315
656
|
|
|
316
657
|
void EventBindings::install(JSContext* ctx) {
|
|
@@ -322,6 +663,7 @@ void EventBindings::install(JSContext* ctx) {
|
|
|
322
663
|
JS_SetPropertyStr(ctx, event_proto, "preventDefault", JS_NewCFunction(ctx, js_event_preventDefault, "preventDefault", 0));
|
|
323
664
|
JS_SetPropertyStr(ctx, event_proto, "stopPropagation", JS_NewCFunction(ctx, js_event_stopPropagation, "stopPropagation", 0));
|
|
324
665
|
JS_SetPropertyStr(ctx, event_proto, "stopImmediatePropagation", JS_NewCFunction(ctx, js_event_stopImmediatePropagation, "stopImmediatePropagation", 0));
|
|
666
|
+
JS_SetPropertyStr(ctx, event_proto, "composedPath", JS_NewCFunction(ctx, js_event_composedPath, "composedPath", 0));
|
|
325
667
|
JS_SetClassProto(ctx, js_event_class_id, event_proto);
|
|
326
668
|
|
|
327
669
|
// ── Event / CustomEvent constructors ───────────────────────────────────────
|
|
@@ -330,6 +672,10 @@ void EventBindings::install(JSContext* ctx) {
|
|
|
330
672
|
JS_NewCFunction2(ctx, js_Event_constructor, "Event", 1, JS_CFUNC_constructor, 0));
|
|
331
673
|
JS_SetPropertyStr(ctx, global, "CustomEvent",
|
|
332
674
|
JS_NewCFunction2(ctx, js_CustomEvent_constructor, "CustomEvent", 2, JS_CFUNC_constructor, 0));
|
|
675
|
+
JS_SetPropertyStr(ctx, global, "KeyboardEvent",
|
|
676
|
+
JS_NewCFunction2(ctx, js_KeyboardEvent_constructor, "KeyboardEvent", 2, JS_CFUNC_constructor, 0));
|
|
677
|
+
JS_SetPropertyStr(ctx, global, "MouseEvent",
|
|
678
|
+
JS_NewCFunction2(ctx, js_MouseEvent_constructor, "MouseEvent", 2, JS_CFUNC_constructor, 0));
|
|
333
679
|
|
|
334
680
|
// ── addEventListener/removeEventListener/dispatchEvent on Node + Element ──
|
|
335
681
|
// Added to the Node proto so text/comment nodes can also receive events.
|
|
@@ -337,16 +683,76 @@ void EventBindings::install(JSContext* ctx) {
|
|
|
337
683
|
JS_SetPropertyStr(ctx, node_proto, "addEventListener", JS_NewCFunction(ctx, js_el_addEventListener, "addEventListener", 2));
|
|
338
684
|
JS_SetPropertyStr(ctx, node_proto, "removeEventListener", JS_NewCFunction(ctx, js_el_removeEventListener, "removeEventListener", 2));
|
|
339
685
|
JS_SetPropertyStr(ctx, node_proto, "dispatchEvent", JS_NewCFunction(ctx, js_el_dispatchEvent, "dispatchEvent", 1));
|
|
686
|
+
|
|
687
|
+
define_element_handler(ctx, node_proto, "onclick", 0);
|
|
688
|
+
define_element_handler(ctx, node_proto, "onload", 1);
|
|
689
|
+
define_element_handler(ctx, node_proto, "onerror", 2);
|
|
340
690
|
JS_FreeValue(ctx, node_proto);
|
|
341
691
|
|
|
342
|
-
|
|
692
|
+
JSValue element_proto = JS_GetClassProto(ctx, js_element_class_id);
|
|
693
|
+
JS_SetPropertyStr(ctx, element_proto, "click", JS_NewCFunction(ctx, js_el_click, "click", 0));
|
|
694
|
+
JS_SetPropertyStr(ctx, element_proto, "focus", JS_NewCFunction(ctx, js_el_focus, "focus", 0));
|
|
695
|
+
JS_SetPropertyStr(ctx, element_proto, "blur", JS_NewCFunction(ctx, js_el_blur, "blur", 0));
|
|
696
|
+
JS_FreeValue(ctx, element_proto);
|
|
697
|
+
|
|
698
|
+
// ── addEventListener/removeEventListener/dispatchEvent on document ────────
|
|
343
699
|
// DocumentBindings::install() must have already run so globalThis.document exists.
|
|
344
700
|
JSValue doc = JS_GetPropertyStr(ctx, global, "document");
|
|
345
|
-
JS_SetPropertyStr(ctx, doc, "addEventListener",
|
|
346
|
-
JS_SetPropertyStr(ctx, doc, "
|
|
701
|
+
JS_SetPropertyStr(ctx, doc, "addEventListener", JS_NewCFunction(ctx, js_doc_addEventListener, "addEventListener", 2));
|
|
702
|
+
JS_SetPropertyStr(ctx, doc, "removeEventListener", JS_NewCFunction(ctx, js_doc_removeEventListener, "removeEventListener", 2));
|
|
703
|
+
JS_SetPropertyStr(ctx, doc, "dispatchEvent", JS_NewCFunction(ctx, js_doc_dispatchEvent, "dispatchEvent", 1));
|
|
704
|
+
|
|
705
|
+
define_doc_handler(ctx, doc, "onclick", 0);
|
|
706
|
+
define_doc_handler(ctx, doc, "onload", 1);
|
|
707
|
+
define_doc_handler(ctx, doc, "onerror", 2);
|
|
347
708
|
JS_FreeValue(ctx, doc);
|
|
348
709
|
|
|
710
|
+
// ── addEventListener/removeEventListener/dispatchEvent on window ──────────
|
|
711
|
+
// window === globalThis (see QuickJSRuntime::initialize()) and has no native
|
|
712
|
+
// node of its own to key listeners by, so js_doc_addEventListener/
|
|
713
|
+
// js_doc_removeEventListener/js_doc_dispatchEvent (which never read `this`
|
|
714
|
+
// — they always resolve the target through rctx->document) are reused
|
|
715
|
+
// verbatim. This means window.addEventListener(...) and
|
|
716
|
+
// document.addEventListener(...) share one listener list/target in this
|
|
717
|
+
// sandbox — a deliberate simplification, not a real separate Window
|
|
718
|
+
// EventTarget.
|
|
719
|
+
JS_SetPropertyStr(ctx, global, "addEventListener", JS_NewCFunction(ctx, js_doc_addEventListener, "addEventListener", 2));
|
|
720
|
+
JS_SetPropertyStr(ctx, global, "removeEventListener", JS_NewCFunction(ctx, js_doc_removeEventListener, "removeEventListener", 2));
|
|
721
|
+
JS_SetPropertyStr(ctx, global, "dispatchEvent", JS_NewCFunction(ctx, js_doc_dispatchEvent, "dispatchEvent", 1));
|
|
722
|
+
|
|
723
|
+
define_doc_handler(ctx, global, "onclick", 0);
|
|
724
|
+
define_doc_handler(ctx, global, "onload", 1);
|
|
725
|
+
define_doc_handler(ctx, global, "onerror", 2);
|
|
726
|
+
define_doc_handler(ctx, global, "onunhandledrejection", 3);
|
|
727
|
+
|
|
349
728
|
JS_FreeValue(ctx, global);
|
|
350
729
|
}
|
|
351
730
|
|
|
731
|
+
void EventBindings::dispatchErrorEvent(JSContext* ctx, const std::string& message, JSValue error_value) {
|
|
732
|
+
auto* rctx = get_ctx(ctx);
|
|
733
|
+
void* node = doc_node_of(rctx);
|
|
734
|
+
if (!rctx || !node) return;
|
|
735
|
+
|
|
736
|
+
JSValue evt = make_simple_event(ctx, "error", false, true);
|
|
737
|
+
JS_SetPropertyStr(ctx, evt, "message", JS_NewStringLen(ctx, message.data(), message.size()));
|
|
738
|
+
JS_SetPropertyStr(ctx, evt, "error", JS_DupValue(ctx, error_value));
|
|
739
|
+
JSValue r = dispatch_event_on_target(ctx, rctx, evt, static_cast<lxb_dom_node_t*>(node));
|
|
740
|
+
JS_FreeValue(ctx, evt);
|
|
741
|
+
if (JS_IsException(r)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
742
|
+
else JS_FreeValue(ctx, r);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
void EventBindings::dispatchUnhandledRejectionEvent(JSContext* ctx, JSValue reason) {
|
|
746
|
+
auto* rctx = get_ctx(ctx);
|
|
747
|
+
void* node = doc_node_of(rctx);
|
|
748
|
+
if (!rctx || !node) return;
|
|
749
|
+
|
|
750
|
+
JSValue evt = make_simple_event(ctx, "unhandledrejection", false, true);
|
|
751
|
+
JS_SetPropertyStr(ctx, evt, "reason", JS_DupValue(ctx, reason));
|
|
752
|
+
JSValue r = dispatch_event_on_target(ctx, rctx, evt, static_cast<lxb_dom_node_t*>(node));
|
|
753
|
+
JS_FreeValue(ctx, evt);
|
|
754
|
+
if (JS_IsException(r)) JS_FreeValue(ctx, JS_GetException(ctx));
|
|
755
|
+
else JS_FreeValue(ctx, r);
|
|
756
|
+
}
|
|
757
|
+
|
|
352
758
|
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -10,12 +10,15 @@
|
|
|
10
10
|
// attach addEventListener/dispatchEvent there too).
|
|
11
11
|
|
|
12
12
|
#include "quickjs.h"
|
|
13
|
+
#include <string>
|
|
13
14
|
|
|
14
15
|
namespace margelo::nitro::nitrojsdom {
|
|
15
16
|
|
|
16
17
|
class EventBindings {
|
|
17
18
|
public:
|
|
18
19
|
static void install(JSContext* ctx);
|
|
20
|
+
static void dispatchErrorEvent(JSContext* ctx, const std::string& message, JSValue error_value);
|
|
21
|
+
static void dispatchUnhandledRejectionEvent(JSContext* ctx, JSValue reason);
|
|
19
22
|
};
|
|
20
23
|
|
|
21
24
|
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#include "EventTargetBindings.hpp"
|
|
2
|
+
#include <cstring>
|
|
3
|
+
|
|
4
|
+
namespace margelo::nitro::nitrojsdom {
|
|
5
|
+
|
|
6
|
+
namespace {
|
|
7
|
+
|
|
8
|
+
const char* kEventTargetBootstrapScript = R"JS(
|
|
9
|
+
(function() {
|
|
10
|
+
function EventTarget() {
|
|
11
|
+
this._listeners = Object.create(null);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
EventTarget.prototype.addEventListener = function(type, listener, options) {
|
|
15
|
+
if (typeof listener !== 'function' && !(listener && typeof listener.handleEvent === 'function')) return;
|
|
16
|
+
type = String(type);
|
|
17
|
+
var capture = !!(options === true || (options && options.capture));
|
|
18
|
+
var once = !!(options && options.once);
|
|
19
|
+
var list = this._listeners[type] || (this._listeners[type] = []);
|
|
20
|
+
for (var i = 0; i < list.length; i++) {
|
|
21
|
+
if (list[i].listener === listener && list[i].capture === capture) return;
|
|
22
|
+
}
|
|
23
|
+
list.push({ listener: listener, capture: capture, once: once });
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
EventTarget.prototype.removeEventListener = function(type, listener, options) {
|
|
27
|
+
var list = this._listeners[String(type)];
|
|
28
|
+
if (!list) return;
|
|
29
|
+
var capture = !!(options === true || (options && options.capture));
|
|
30
|
+
for (var i = 0; i < list.length; i++) {
|
|
31
|
+
if (list[i].listener === listener && list[i].capture === capture) { list.splice(i, 1); return; }
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
EventTarget.prototype.dispatchEvent = function(event) {
|
|
36
|
+
var list = this._listeners[event.type];
|
|
37
|
+
Object.defineProperty(event, 'target', { value: this, configurable: true });
|
|
38
|
+
Object.defineProperty(event, 'currentTarget', { value: this, configurable: true });
|
|
39
|
+
try {
|
|
40
|
+
if (list) {
|
|
41
|
+
var snapshot = list.slice();
|
|
42
|
+
for (var i = 0; i < snapshot.length; i++) {
|
|
43
|
+
var entry = snapshot[i];
|
|
44
|
+
if (list.indexOf(entry) === -1) continue;
|
|
45
|
+
if (entry.once) {
|
|
46
|
+
var idx = list.indexOf(entry);
|
|
47
|
+
if (idx !== -1) list.splice(idx, 1);
|
|
48
|
+
}
|
|
49
|
+
if (typeof entry.listener === 'function') entry.listener.call(this, event);
|
|
50
|
+
else entry.listener.handleEvent(event);
|
|
51
|
+
if (event.__immediatePropagationStopped) break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} finally {
|
|
55
|
+
Object.defineProperty(event, 'currentTarget', { value: null, configurable: true });
|
|
56
|
+
}
|
|
57
|
+
return !event.defaultPrevented;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
globalThis.EventTarget = EventTarget;
|
|
61
|
+
})();
|
|
62
|
+
)JS";
|
|
63
|
+
|
|
64
|
+
} // namespace
|
|
65
|
+
|
|
66
|
+
void EventTargetBindings::install(JSContext* ctx) {
|
|
67
|
+
JSValue result = JS_Eval(ctx, kEventTargetBootstrapScript, strlen(kEventTargetBootstrapScript),
|
|
68
|
+
"<event-target-bootstrap>", JS_EVAL_TYPE_GLOBAL);
|
|
69
|
+
if (JS_IsException(result)) {
|
|
70
|
+
JS_FreeValue(ctx, JS_GetException(ctx));
|
|
71
|
+
}
|
|
72
|
+
JS_FreeValue(ctx, result);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
} // namespace margelo::nitro::nitrojsdom
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// globalThis.EventTarget — a standalone, constructible EventTarget for
|
|
4
|
+
// scripts that want addEventListener/removeEventListener/dispatchEvent
|
|
5
|
+
// pub-sub semantics without extending an Element/document/window (which
|
|
6
|
+
// already have their own dispatch paths — see EventBindings). Pure JS: its
|
|
7
|
+
// own per-instance listener map, no bubbling (a bare EventTarget has no
|
|
8
|
+
// ancestors to bubble through), reusing the Event/CustomEvent classes
|
|
9
|
+
// EventBindings already installs.
|
|
10
|
+
//
|
|
11
|
+
// Must run after EventBindings (globalThis.Event, DOMException already
|
|
12
|
+
// available for the "listener not a function" TypeError path — actually
|
|
13
|
+
// mirrors native EventTarget, which silently ignores a non-callable
|
|
14
|
+
// listener rather than throwing).
|
|
15
|
+
|
|
16
|
+
#include "quickjs.h"
|
|
17
|
+
|
|
18
|
+
namespace margelo::nitro::nitrojsdom {
|
|
19
|
+
|
|
20
|
+
struct EventTargetBindings {
|
|
21
|
+
static void install(JSContext* ctx);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
} // namespace margelo::nitro::nitrojsdom
|