@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.
Files changed (66) hide show
  1. package/android/CMakeLists.txt +18 -0
  2. package/cpp/HybridHtmlSandbox.cpp +20 -3
  3. package/cpp/HybridHtmlSandbox.hpp +1 -1
  4. package/cpp/lexbor/LexborDocument.cpp +279 -67
  5. package/cpp/lexbor/LexborDocument.hpp +43 -10
  6. package/cpp/quickjs/DOMBindings.cpp +37 -1
  7. package/cpp/quickjs/DOMBindingsInternal.cpp +101 -2
  8. package/cpp/quickjs/DOMBindingsInternal.hpp +56 -1
  9. package/cpp/quickjs/QuickJSRuntime.cpp +32 -4
  10. package/cpp/quickjs/QuickJSRuntime.hpp +50 -1
  11. package/cpp/quickjs/bindings/AbortBindings.cpp +128 -0
  12. package/cpp/quickjs/bindings/AbortBindings.hpp +12 -0
  13. package/cpp/quickjs/bindings/BlobBindings.cpp +164 -0
  14. package/cpp/quickjs/bindings/BlobBindings.hpp +14 -0
  15. package/cpp/quickjs/bindings/CSSOMBindings.cpp +290 -0
  16. package/cpp/quickjs/bindings/CSSOMBindings.hpp +25 -0
  17. package/cpp/quickjs/bindings/ClassListBindings.cpp +3 -2
  18. package/cpp/quickjs/bindings/CookieBindings.cpp +118 -0
  19. package/cpp/quickjs/bindings/CookieBindings.hpp +23 -0
  20. package/cpp/quickjs/bindings/CustomElementsBindings.cpp +341 -0
  21. package/cpp/quickjs/bindings/CustomElementsBindings.hpp +50 -0
  22. package/cpp/quickjs/bindings/DOMExceptionBindings.cpp +111 -0
  23. package/cpp/quickjs/bindings/DOMExceptionBindings.hpp +20 -0
  24. package/cpp/quickjs/bindings/DOMParserBindings.cpp +299 -0
  25. package/cpp/quickjs/bindings/DOMParserBindings.hpp +45 -0
  26. package/cpp/quickjs/bindings/DocumentBindings.cpp +239 -4
  27. package/cpp/quickjs/bindings/ElementBindings.cpp +556 -44
  28. package/cpp/quickjs/bindings/EventBindings.cpp +414 -8
  29. package/cpp/quickjs/bindings/EventBindings.hpp +3 -0
  30. package/cpp/quickjs/bindings/EventTargetBindings.cpp +75 -0
  31. package/cpp/quickjs/bindings/EventTargetBindings.hpp +24 -0
  32. package/cpp/quickjs/bindings/FetchBindings.cpp +45 -6
  33. package/cpp/quickjs/bindings/FormBindings.cpp +365 -0
  34. package/cpp/quickjs/bindings/FormBindings.hpp +20 -0
  35. package/cpp/quickjs/bindings/LayoutStubBindings.cpp +104 -0
  36. package/cpp/quickjs/bindings/LayoutStubBindings.hpp +28 -0
  37. package/cpp/quickjs/bindings/LiveCollectionBindings.cpp +228 -0
  38. package/cpp/quickjs/bindings/LiveCollectionBindings.hpp +16 -0
  39. package/cpp/quickjs/bindings/ShadowRootBindings.cpp +171 -0
  40. package/cpp/quickjs/bindings/ShadowRootBindings.hpp +27 -0
  41. package/cpp/quickjs/bindings/SlotBindings.cpp +161 -0
  42. package/cpp/quickjs/bindings/SlotBindings.hpp +44 -0
  43. package/cpp/quickjs/bindings/TemplateBindings.cpp +42 -0
  44. package/cpp/quickjs/bindings/TemplateBindings.hpp +29 -0
  45. package/cpp/quickjs/bindings/TextEncodingBindings.cpp +117 -0
  46. package/cpp/quickjs/bindings/TextEncodingBindings.hpp +14 -0
  47. package/cpp/quickjs/bindings/TimerBindings.cpp +120 -0
  48. package/cpp/quickjs/bindings/TreeWalkerBindings.cpp +306 -0
  49. package/cpp/quickjs/bindings/TreeWalkerBindings.hpp +28 -0
  50. package/cpp/quickjs/bindings/UrlBindings.cpp +295 -0
  51. package/cpp/quickjs/bindings/UrlBindings.hpp +12 -0
  52. package/cpp/quickjs/bindings/WindowBindings.cpp +552 -0
  53. package/cpp/quickjs/bindings/WindowBindings.hpp +3 -2
  54. package/cpp/quickjs/bindings/XmlSerializerBindings.cpp +44 -0
  55. package/cpp/quickjs/bindings/XmlSerializerBindings.hpp +24 -0
  56. package/lib/commonjs/classes/JSDOM/JSDOM.class.js +1 -1
  57. package/lib/commonjs/classes/JSDOM/JSDOM.class.js.map +1 -1
  58. package/lib/module/classes/JSDOM/JSDOM.class.js +1 -1
  59. package/lib/module/classes/JSDOM/JSDOM.class.js.map +1 -1
  60. package/lib/typescript/src/classes/JSDOM/JSDOM.class.d.ts.map +1 -1
  61. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts +1 -1
  62. package/lib/typescript/src/specs/HtmlSandbox.nitro.d.ts.map +1 -1
  63. package/nitrogen/generated/shared/c++/HybridHtmlSandboxSpec.hpp +1 -1
  64. package/package.json +17 -2
  65. package/src/classes/JSDOM/JSDOM.class.ts +1 -0
  66. 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
- for (lxb_dom_node_t* p = target_node->parent; p; p = p->parent) chain.push_back(p);
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 = JS_Call(ctx, cbs_to_fire[i], current_target, 1, &event_obj);
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
- // ── addEventListener/dispatchEvent on document ─────────────────────────────
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", JS_NewCFunction(ctx, js_doc_addEventListener, "addEventListener", 2));
346
- JS_SetPropertyStr(ctx, doc, "dispatchEvent", JS_NewCFunction(ctx, js_doc_dispatchEvent, "dispatchEvent", 1));
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