Skip to content

Commit

Permalink
Refactor create‑event‑accessor.js to remove code duplication
Browse files Browse the repository at this point in the history
  • Loading branch information
ExE-Boss authored and domenic committed Mar 7, 2021
1 parent ff69a75 commit e2f7639
Show file tree
Hide file tree
Showing 2 changed files with 105 additions and 92 deletions.
22 changes: 16 additions & 6 deletions lib/jsdom/browser/Window.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const Screen = require("../living/generated/Screen");
const Storage = require("../living/generated/Storage");
const Selection = require("../living/generated/Selection");
const reportException = require("../living/helpers/runtime-script-errors");
const { getCurrentEventHandlerValue } = require("../living/helpers/create-event-accessor.js");
const { fireAnEvent } = require("../living/helpers/events");
const SessionHistory = require("../living/window/SessionHistory");
const { forEachMatchingSheetRuleOfElement, getResolvedValue, propertiesWithResolvedValueImplemented,
Expand Down Expand Up @@ -156,9 +157,12 @@ function setupWindow(windowInstance, { runScripts }) {
mixin(windowInstance, GlobalEventHandlersImpl.prototype);
windowInstance._initGlobalEvents();

// The getters are already obtained from the above mixins.
// eslint-disable-next-line accessor-pairs
Object.defineProperty(windowInstance, "onbeforeunload", {
configurable: true,
enumerable: true,
get() {
return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(this, "beforeunload"));
},
set(V) {
if (!idlUtils.isObject(V)) {
V = null;
Expand All @@ -171,9 +175,12 @@ function setupWindow(windowInstance, { runScripts }) {
}
});

// The getters are already obtained from the above mixins.
// eslint-disable-next-line accessor-pairs
Object.defineProperty(windowInstance, "onerror", {
configurable: true,
enumerable: true,
get() {
return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(this, "error"));
},
set(V) {
if (!idlUtils.isObject(V)) {
V = null;
Expand All @@ -187,9 +194,12 @@ function setupWindow(windowInstance, { runScripts }) {
});

for (const event of events) {
// The getters are already obtained from the above mixins.
// eslint-disable-next-line accessor-pairs
Object.defineProperty(windowInstance, `on${event}`, {
configurable: true,
enumerable: true,
get() {
return idlUtils.tryWrapperForImpl(getCurrentEventHandlerValue(this, event));
},
set(V) {
if (!idlUtils.isObject(V)) {
V = null;
Expand Down
175 changes: 89 additions & 86 deletions lib/jsdom/living/helpers/create-event-accessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ const OnBeforeUnloadEventHandlerNonNull = require("../generated/OnBeforeUnloadEv
const OnErrorEventHandlerNonNull = require("../generated/OnErrorEventHandlerNonNull.js");
const reportException = require("./runtime-script-errors");

exports.appendHandler = function appendHandler(el, eventName) {
exports.appendHandler = (el, eventName) => {
// tryImplForWrapper() is currently required due to use in Window.js
idlUtils.tryImplForWrapper(el).addEventListener(eventName, event => {
// https://html.spec.whatwg.org/#the-event-handler-processing-algorithm
const callback = el["on" + eventName];
const callback = exports.getCurrentEventHandlerValue(el, eventName);
if (callback === null) {
return;
}
Expand Down Expand Up @@ -74,103 +74,106 @@ exports.setupForSimpleEventAccessors = (prototype, events) => {
}
};

// https://html.spec.whatwg.org/#event-handler-idl-attributes
exports.createEventAccessor = function createEventAccessor(obj, event) {
Object.defineProperty(obj, "on" + event, {
configurable: true,
enumerable: true,
get() {
// TODO: Extract this into a helper function
// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
const value = this._getEventHandlerFor(event);
if (!value) {
return null;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#getting-the-current-value-of-the-event-handler
exports.getCurrentEventHandlerValue = (target, event) => {
const value = target._getEventHandlerFor(event);
if (!value) {
return null;
}

if (value.body !== undefined) {
let element;
let document;
if (this.constructor.name === "Window") {
element = null;
document = idlUtils.implForWrapper(this.document);
} else {
element = this;
document = element.ownerDocument;
}
const { body } = value;

const formOwner = element !== null && element.form ? element.form : null;
const window = this.constructor.name === "Window" && this._document ? this : document.defaultView;

try {
// eslint-disable-next-line no-new-func
Function(body); // properly error out on syntax errors
// Note: this won't execute body; that would require `Function(body)()`.
} catch (e) {
if (window) {
reportException(window, e);
}
this._setEventHandlerFor(event, null);
return null;
}
if (value.body !== undefined) {
let element;
let document;
if (target.constructor.name === "Window") {
element = null;
document = idlUtils.implForWrapper(target.document);
} else {
element = target;
document = element.ownerDocument;
}
const { body } = value;

const formOwner = element !== null && element.form ? element.form : null;
const window = target.constructor.name === "Window" && target._document ? target : document.defaultView;

try {
// eslint-disable-next-line no-new-func
Function(body); // properly error out on syntax errors
// Note: this won't execute body; that would require `Function(body)()`.
} catch (e) {
if (window) {
reportException(window, e);
}
target._setEventHandlerFor(event, null);
return null;
}

// Note: the with (window) { } is not necessary in Node, but is necessary in a browserified environment.
// Note: the with (window) { } is not necessary in Node, but is necessary in a browserified environment.

let fn;
const createFunction = document.defaultView.Function;
if (event === "error" && element === null) {
const wrapperBody = document ? body + `\n//# sourceURL=${document.URL}` : body;
let fn;
const createFunction = document.defaultView.Function;
if (event === "error" && element === null) {
const wrapperBody = document ? body + `\n//# sourceURL=${document.URL}` : body;

// eslint-disable-next-line no-new-func
fn = createFunction("window", `with (window) { return function onerror(event, source, lineno, colno, error) {
// eslint-disable-next-line no-new-func
fn = createFunction("window", `with (window) { return function onerror(event, source, lineno, colno, error) {
${wrapperBody}
}; }`)(window);

fn = OnErrorEventHandlerNonNull.convert(fn);
} else {
const argNames = [];
const args = [];

argNames.push("window");
args.push(window);

if (element !== null) {
argNames.push("document");
args.push(idlUtils.wrapperForImpl(document));
}
if (formOwner !== null) {
argNames.push("formOwner");
args.push(idlUtils.wrapperForImpl(formOwner));
}
if (element !== null) {
argNames.push("element");
args.push(idlUtils.wrapperForImpl(element));
}
let wrapperBody = `
fn = OnErrorEventHandlerNonNull.convert(fn);
} else {
const argNames = [];
const args = [];

argNames.push("window");
args.push(window);

if (element !== null) {
argNames.push("document");
args.push(idlUtils.wrapperForImpl(document));
}
if (formOwner !== null) {
argNames.push("formOwner");
args.push(idlUtils.wrapperForImpl(formOwner));
}
if (element !== null) {
argNames.push("element");
args.push(idlUtils.wrapperForImpl(element));
}
let wrapperBody = `
return function on${event}(event) {
${body}
};`;
for (let i = argNames.length - 1; i >= 0; --i) {
wrapperBody = `with (${argNames[i]}) { ${wrapperBody} }`;
}
if (document) {
wrapperBody += `\n//# sourceURL=${document.URL}`;
}
argNames.push(wrapperBody);
fn = createFunction(...argNames)(...args);

if (event === "beforeunload") {
fn = OnBeforeUnloadEventHandlerNonNull.convert(fn);
} else {
fn = EventHandlerNonNull.convert(fn);
}
}
for (let i = argNames.length - 1; i >= 0; --i) {
wrapperBody = `with (${argNames[i]}) { ${wrapperBody} }`;
}
if (document) {
wrapperBody += `\n//# sourceURL=${document.URL}`;
}
argNames.push(wrapperBody);
fn = createFunction(...argNames)(...args);

this._setEventHandlerFor(event, fn);
if (event === "beforeunload") {
fn = OnBeforeUnloadEventHandlerNonNull.convert(fn);
} else {
fn = EventHandlerNonNull.convert(fn);
}
}

target._setEventHandlerFor(event, fn);
}

return target._getEventHandlerFor(event);
};

// tryWrapperForImpl() is currently required due to use in Window.js
return idlUtils.tryWrapperForImpl(this._getEventHandlerFor(event));
// https://html.spec.whatwg.org/multipage/webappapis.html#event-handler-idl-attributes
// TODO: Consider replacing this with `[ReflectEvent]`
exports.createEventAccessor = (obj, event) => {
Object.defineProperty(obj, "on" + event, {
configurable: true,
enumerable: true,
get() {
return exports.getCurrentEventHandlerValue(this, event);
},
set(val) {
this._setEventHandlerFor(event, val);
Expand Down

0 comments on commit e2f7639

Please sign in to comment.