Skip to content

Commit ff69a75

Browse files
ExE-Bossdomenic
authored andcommitted
Convert JSDOM to use callback functions
This involves updating to webidl2js 16.2.0, which brings along other changes and improvements.
1 parent 19df6bc commit ff69a75

19 files changed

Lines changed: 180 additions & 65 deletions

lib/jsdom/browser/Window.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ const { installInterfaces } = require("../living/interfaces");
88
const { define, mixin } = require("../utils");
99
const Element = require("../living/generated/Element");
1010
const EventTarget = require("../living/generated/EventTarget");
11+
const EventHandlerNonNull = require("../living/generated/EventHandlerNonNull");
12+
const OnBeforeUnloadEventHandlerNonNull = require("../living/generated/OnBeforeUnloadEventHandlerNonNull");
13+
const OnErrorEventHandlerNonNull = require("../living/generated/OnErrorEventHandlerNonNull");
1114
const PageTransitionEvent = require("../living/generated/PageTransitionEvent");
1215
const namedPropertiesWindow = require("../living/named-properties-window");
1316
const postMessage = require("../living/post-message");
@@ -34,6 +37,59 @@ const jsGlobals = require("./js-globals.json");
3437
const GlobalEventHandlersImpl = require("../living/nodes/GlobalEventHandlers-impl").implementation;
3538
const WindowEventHandlersImpl = require("../living/nodes/WindowEventHandlers-impl").implementation;
3639

40+
const events = new Set([
41+
// GlobalEventHandlers
42+
"abort", "autocomplete",
43+
"autocompleteerror", "blur",
44+
"cancel", "canplay", "canplaythrough",
45+
"change", "click",
46+
"close", "contextmenu",
47+
"cuechange", "dblclick",
48+
"drag", "dragend",
49+
"dragenter",
50+
"dragleave", "dragover",
51+
"dragstart", "drop",
52+
"durationchange", "emptied",
53+
"ended", "focus",
54+
"input", "invalid",
55+
"keydown", "keypress",
56+
"keyup", "load", "loadeddata",
57+
"loadedmetadata", "loadstart",
58+
"mousedown", "mouseenter",
59+
"mouseleave", "mousemove",
60+
"mouseout", "mouseover",
61+
"mouseup", "wheel",
62+
"pause", "play",
63+
"playing", "progress",
64+
"ratechange", "reset",
65+
"resize", "scroll",
66+
"securitypolicyviolation",
67+
"seeked", "seeking",
68+
"select", "sort", "stalled",
69+
"submit", "suspend",
70+
"timeupdate", "toggle",
71+
"volumechange", "waiting",
72+
73+
// WindowEventHandlers
74+
"afterprint",
75+
"beforeprint",
76+
"hashchange",
77+
"languagechange",
78+
"message",
79+
"messageerror",
80+
"offline",
81+
"online",
82+
"pagehide",
83+
"pageshow",
84+
"popstate",
85+
"rejectionhandled",
86+
"storage",
87+
"unhandledrejection",
88+
"unload"
89+
90+
// "error" and "beforeunload" are added separately
91+
]);
92+
3793
exports.createWindow = function (options) {
3894
return new Window(options);
3995
};
@@ -100,6 +156,53 @@ function setupWindow(windowInstance, { runScripts }) {
100156
mixin(windowInstance, GlobalEventHandlersImpl.prototype);
101157
windowInstance._initGlobalEvents();
102158

159+
// The getters are already obtained from the above mixins.
160+
// eslint-disable-next-line accessor-pairs
161+
Object.defineProperty(windowInstance, "onbeforeunload", {
162+
set(V) {
163+
if (!idlUtils.isObject(V)) {
164+
V = null;
165+
} else {
166+
V = OnBeforeUnloadEventHandlerNonNull.convert(V, {
167+
context: "Failed to set the 'onbeforeunload' property on 'Window': The provided value"
168+
});
169+
}
170+
this._setEventHandlerFor("beforeunload", V);
171+
}
172+
});
173+
174+
// The getters are already obtained from the above mixins.
175+
// eslint-disable-next-line accessor-pairs
176+
Object.defineProperty(windowInstance, "onerror", {
177+
set(V) {
178+
if (!idlUtils.isObject(V)) {
179+
V = null;
180+
} else {
181+
V = OnErrorEventHandlerNonNull.convert(V, {
182+
context: "Failed to set the 'onerror' property on 'Window': The provided value"
183+
});
184+
}
185+
this._setEventHandlerFor("error", V);
186+
}
187+
});
188+
189+
for (const event of events) {
190+
// The getters are already obtained from the above mixins.
191+
// eslint-disable-next-line accessor-pairs
192+
Object.defineProperty(windowInstance, `on${event}`, {
193+
set(V) {
194+
if (!idlUtils.isObject(V)) {
195+
V = null;
196+
} else {
197+
V = EventHandlerNonNull.convert(V, {
198+
context: `Failed to set the 'on${event}' property on 'Window': The provided value`
199+
});
200+
}
201+
this._setEventHandlerFor(event, V);
202+
}
203+
});
204+
}
205+
103206
windowInstance._globalObject = windowInstance;
104207
}
105208

lib/jsdom/living/custom-elements/CustomElementRegistry-impl.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const { shadowIncludingInclusiveDescendantsIterator } = require("../helpers/shad
1111
const { isValidCustomElementName, tryUpgradeElement, enqueueCEUpgradeReaction } = require("../helpers/custom-elements");
1212

1313
const idlUtils = require("../generated/utils");
14+
const IDLFunction = require("../generated/Function.js");
1415
const HTMLUnknownElement = require("../generated/HTMLUnknownElement");
1516

1617
const LIFECYCLE_CALLBACKS = [
@@ -62,8 +63,9 @@ class CustomElementRegistryImpl {
6263
}
6364

6465
// https://html.spec.whatwg.org/#dom-customelementregistry-define
65-
define(name, ctor, options) {
66+
define(name, constructor, options) {
6667
const { _globalObject } = this;
68+
const ctor = constructor.objectReference;
6769

6870
if (!isConstructor(ctor)) {
6971
throw new TypeError("Constructor argument is not a constructor.");
@@ -81,7 +83,7 @@ class CustomElementRegistryImpl {
8183
]);
8284
}
8385

84-
const ctorAlreadyRegistered = this._customElementDefinitions.some(entry => entry.ctor === ctor);
86+
const ctorAlreadyRegistered = this._customElementDefinitions.some(entry => entry.objectReference === ctor);
8587
if (ctorAlreadyRegistered) {
8688
throw DOMException.create(_globalObject, [
8789
"This constructor has already been registered in the registry.",
@@ -145,7 +147,9 @@ class CustomElementRegistryImpl {
145147
const callbackValue = prototype[callbackName];
146148

147149
if (callbackValue !== undefined) {
148-
lifecycleCallbacks[callbackName] = webIDLConversions.Function(callbackValue);
150+
lifecycleCallbacks[callbackName] = IDLFunction.convert(callbackValue, {
151+
context: `The lifecycle callback "${callbackName}"`
152+
});
149153
}
150154
}
151155

@@ -177,7 +181,8 @@ class CustomElementRegistryImpl {
177181
const definition = {
178182
name,
179183
localName,
180-
ctor,
184+
constructor,
185+
objectReference: ctor,
181186
observedAttributes,
182187
lifecycleCallbacks,
183188
disableShadow,

lib/jsdom/living/custom-elements/CustomElementRegistry.webidl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[Exposed=Window]
22
interface CustomElementRegistry {
3-
[CEReactions] void define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options);
3+
[CEReactions] void define(DOMString name, CustomElementConstructor constructor, optional ElementDefinitionOptions options = {});
44
any get(DOMString name);
55
Promise<void> whenDefined(DOMString name);
66
[CEReactions] void upgrade(Node root);

lib/jsdom/living/events/CloseEvent.webidl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// https://html.spec.whatwg.org/multipage/web-sockets.html#the-closeevent-interface
22
[Exposed=(Window,Worker)]
33
interface CloseEvent : Event {
4-
constructor(DOMString type, optional CloseEventInit eventInitDict);
4+
constructor(DOMString type, optional CloseEventInit eventInitDict = {});
55

66
readonly attribute boolean wasClean;
77
readonly attribute unsigned short code;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// https://html.spec.whatwg.org/multipage/webappapis.html#eventhandlernonnull
2+
[LegacyTreatNonObjectAsNull]
3+
callback EventHandlerNonNull = any (Event event);
4+
typedef EventHandlerNonNull? EventHandler;
5+
6+
// https://html.spec.whatwg.org/multipage/webappapis.html#onerroreventhandlernonnull
7+
[LegacyTreatNonObjectAsNull]
8+
callback OnErrorEventHandlerNonNull = any (
9+
(Event or DOMString) event,
10+
optional DOMString source,
11+
optional unsigned long lineno,
12+
optional unsigned long colno,
13+
optional any error
14+
);
15+
typedef OnErrorEventHandlerNonNull? OnErrorEventHandler;
16+
17+
// https://html.spec.whatwg.org/multipage/webappapis.html#onbeforeunloadeventhandlernonnull
18+
[LegacyTreatNonObjectAsNull]
19+
callback OnBeforeUnloadEventHandlerNonNull = DOMString? (Event event);
20+
typedef OnBeforeUnloadEventHandlerNonNull? OnBeforeUnloadEventHandler;

lib/jsdom/living/helpers/create-element.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@ function createElement(
199199
} else if (definition !== null) {
200200
if (synchronousCE) {
201201
try {
202-
const C = definition.ctor;
202+
const C = definition.constructor;
203203

204-
const resultWrapper = new C();
204+
const resultWrapper = C.construct();
205205
result = implForWrapper(resultWrapper);
206206

207207
if (!result._ceState || !result._ceDefinition || result._namespaceURI !== HTML_NS) {

lib/jsdom/living/helpers/create-event-accessor.js

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"use strict";
22

3-
const conversions = require("webidl-conversions");
43
const idlUtils = require("../generated/utils");
54
const ErrorEvent = require("../generated/ErrorEvent");
5+
const EventHandlerNonNull = require("../generated/EventHandlerNonNull.js");
6+
const OnBeforeUnloadEventHandlerNonNull = require("../generated/OnBeforeUnloadEventHandlerNonNull.js");
7+
const OnErrorEventHandlerNonNull = require("../generated/OnErrorEventHandlerNonNull.js");
68
const reportException = require("./runtime-script-errors");
79

810
exports.appendHandler = function appendHandler(el, eventName) {
@@ -18,25 +20,20 @@ exports.appendHandler = function appendHandler(el, eventName) {
1820
event.currentTarget.constructor.name === "Window";
1921

2022
let returnValue = null;
21-
const thisValue = idlUtils.tryWrapperForImpl(event.currentTarget);
2223
// https://heycam.github.io/webidl/#es-invoking-callback-functions
2324
if (typeof callback === "function") {
2425
if (specialError) {
2526
returnValue = callback.call(
26-
thisValue, event.message,
27+
event.currentTarget, event.message,
2728
event.filename, event.lineno, event.colno, event.error
2829
);
2930
} else {
30-
// This will no longer be necessary once EventHandler and Window are implemented in IDL:
31-
const eventWrapper = idlUtils.wrapperForImpl(event);
32-
returnValue = callback.call(thisValue, eventWrapper);
31+
returnValue = callback.call(event.currentTarget, event);
3332
}
3433
}
3534

36-
if (event.type === "beforeunload") { // TODO: we don't implement BeforeUnloadEvent so we can't brand-check here
37-
// Perform conversion which in the spec is done by the event handler return type being DOMString?
38-
returnValue = returnValue === undefined || returnValue === null ? null : conversions.DOMString(returnValue);
39-
35+
// TODO: we don't implement BeforeUnloadEvent so we can't brand-check here
36+
if (event.type === "beforeunload") {
4037
if (returnValue !== null) {
4138
event._canceledFlag = true;
4239
if (event.returnValue === "") {
@@ -82,7 +79,9 @@ exports.createEventAccessor = function createEventAccessor(obj, event) {
8279
Object.defineProperty(obj, "on" + event, {
8380
configurable: true,
8481
enumerable: true,
85-
get() { // https://html.spec.whatwg.org/#getting-the-current-value-of-the-event-handler
82+
get() {
83+
// TODO: Extract this into a helper function
84+
// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
8685
const value = this._getEventHandlerFor(event);
8786
if (!value) {
8887
return null;
@@ -126,6 +125,8 @@ exports.createEventAccessor = function createEventAccessor(obj, event) {
126125
fn = createFunction("window", `with (window) { return function onerror(event, source, lineno, colno, error) {
127126
${wrapperBody}
128127
}; }`)(window);
128+
129+
fn = OnErrorEventHandlerNonNull.convert(fn);
129130
} else {
130131
const argNames = [];
131132
const args = [];
@@ -157,34 +158,22 @@ return function on${event}(event) {
157158
}
158159
argNames.push(wrapperBody);
159160
fn = createFunction(...argNames)(...args);
161+
162+
if (event === "beforeunload") {
163+
fn = OnBeforeUnloadEventHandlerNonNull.convert(fn);
164+
} else {
165+
fn = EventHandlerNonNull.convert(fn);
166+
}
160167
}
161168

162169
this._setEventHandlerFor(event, fn);
163170
}
164-
return this._getEventHandlerFor(event);
171+
172+
// tryWrapperForImpl() is currently required due to use in Window.js
173+
return idlUtils.tryWrapperForImpl(this._getEventHandlerFor(event));
165174
},
166175
set(val) {
167-
val = eventHandlerArgCoercion(val);
168176
this._setEventHandlerFor(event, val);
169177
}
170178
});
171179
};
172-
173-
function typeIsObject(v) {
174-
return (typeof v === "object" && v !== null) || typeof v === "function";
175-
}
176-
177-
// Implements:
178-
// [TreatNonObjectAsNull]
179-
// callback EventHandlerNonNull = any (Event event);
180-
// typedef EventHandlerNonNull? EventHandler;
181-
// Also implements the part of https://heycam.github.io/webidl/#es-invoking-callback-functions which treats
182-
// non-callable callback functions as callback functions that return undefined.
183-
// TODO: replace with webidl2js typechecking when it has sufficient callback support
184-
function eventHandlerArgCoercion(val) {
185-
if (!typeIsObject(val)) {
186-
return null;
187-
}
188-
189-
return val;
190-
}

lib/jsdom/living/helpers/custom-elements.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ function upgradeElement(definition, element) {
9494

9595
definition.constructionStack.push(element);
9696

97-
const { constructionStack, ctor: C } = definition;
97+
const { constructionStack, constructor: C } = definition;
9898

9999
let constructionError;
100100
try {
@@ -105,7 +105,7 @@ function upgradeElement(definition, element) {
105105
]);
106106
}
107107

108-
const constructionResult = new C();
108+
const constructionResult = C.construct();
109109
const constructionResultImpl = implForWrapper(constructionResult);
110110

111111
if (constructionResultImpl !== element) {

lib/jsdom/living/helpers/html-constructor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function HTMLConstructor(globalObject, constructorName, newTarget) {
1515
throw new TypeError("Invalid constructor");
1616
}
1717

18-
const definition = registry._customElementDefinitions.find(entry => entry.ctor === newTarget);
18+
const definition = registry._customElementDefinitions.find(entry => entry.objectReference === newTarget);
1919
if (definition === undefined) {
2020
throw new TypeError("Invalid constructor, the constructor is not part of the custom element registry");
2121
}

lib/jsdom/living/mutation-observer/MutationObserver.webidl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
interface MutationObserver {
44
constructor(MutationCallback callback);
55

6-
void observe(Node target, optional MutationObserverInit options);
6+
void observe(Node target, optional MutationObserverInit options = {});
77
void disconnect();
88
sequence<MutationRecord> takeRecords();
99
};

0 commit comments

Comments
 (0)