-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Properly implement event handler properties/attributes
This exposes getters/setters for all the on[event] properties on the prototype, thus closing #1354. It also slots them into the correct place in the attribute list upon setting, thus closing #696. Along the way, it generally makes all the event handler processing per-spec; we pass many more web platform tests in this area.
- Loading branch information
Showing
15 changed files
with
356 additions
and
279 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
"use strict"; | ||
|
||
const vm = require("vm"); | ||
const conversions = require("webidl-conversions"); | ||
const idlUtils = require("../generated/utils"); | ||
const ErrorEvent = require("../generated/ErrorEvent"); | ||
const reportException = require("./runtime-script-errors"); | ||
|
||
exports.appendHandler = function appendHandler(el, eventName) { | ||
el.addEventListener(eventName, event => { | ||
// https://html.spec.whatwg.org/#the-event-handler-processing-algorithm | ||
event = idlUtils.implForWrapper(event); | ||
|
||
const callback = el["on" + eventName]; | ||
if (callback === null) { | ||
return; | ||
} | ||
|
||
const specialError = ErrorEvent.isImpl(event) && event.type === "error" && | ||
event.currentTarget.constructor.name === "Window"; | ||
|
||
let returnValue = null; | ||
const thisValue = idlUtils.tryWrapperForImpl(event.currentTarget); | ||
if (specialError) { | ||
returnValue = callback.call( | ||
thisValue, event.message, | ||
event.filename, event.lineno, event.colno, event.error | ||
); | ||
} else { | ||
const eventWrapper = idlUtils.wrapperForImpl(event); | ||
returnValue = callback.call(thisValue, eventWrapper); | ||
} | ||
|
||
if (event.type === "beforeunload") { // TODO: we don't implement BeforeUnloadEvent so we can't brand-check here | ||
// Perform conversion which in the spec is done by the event handler return type being DOMString? | ||
returnValue = returnValue === undefined || returnValue === null ? null : conversions.DOMString(returnValue); | ||
|
||
if (returnValue !== null) { | ||
event._canceledFlag = true; | ||
if (event.returnValue === "") { | ||
event.returnValue = returnValue; | ||
} | ||
} | ||
} else if (specialError) { | ||
if (returnValue === true) { | ||
event._canceledFlag = true; | ||
} | ||
} else if (returnValue === false) { | ||
event._canceledFlag = true; | ||
} | ||
}); | ||
}; | ||
|
||
// https://html.spec.whatwg.org/#event-handler-idl-attributes | ||
exports.createEventAccessor = function createEventAccessor(obj, event) { | ||
Object.defineProperty(obj, "on" + event, { | ||
configurable: true, | ||
get() { // https://html.spec.whatwg.org/#getting-the-current-value-of-the-event-handler | ||
const value = this._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.body; | ||
|
||
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; | ||
} | ||
|
||
// Note: the with (window) { } is not necessary in Node, but is necessary in a browserified environment. | ||
|
||
let fn; | ||
const createFunction = vm.isContext(document._global) ? document.defaultView._globalProxy.Function : 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) { | ||
${wrapperBody} | ||
}; }`)(window); | ||
} 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); | ||
} | ||
|
||
this._setEventHandlerFor(event, fn); | ||
} | ||
return this._getEventHandlerFor(event); | ||
}, | ||
set(val) { | ||
val = eventHandlerArgCoercion(val); | ||
this._setEventHandlerFor(event, val); | ||
} | ||
}); | ||
}; | ||
|
||
function typeIsObject(v) { | ||
return (typeof v === "object" && v !== null) || typeof v === "function"; | ||
} | ||
|
||
// Implements: | ||
// [TreatNonObjectAsNull] | ||
// callback EventHandlerNonNull = any (Event event); | ||
// typedef EventHandlerNonNull? EventHandler; | ||
// Also implements the part of https://heycam.github.io/webidl/#es-invoking-callback-functions which treats | ||
// non-callable callback functions as callback functions that return undefined. | ||
// TODO: replace with webidl2js typechecking when it has sufficient callback support | ||
function eventHandlerArgCoercion(val) { | ||
if (!typeIsObject(val)) { | ||
return null; | ||
} | ||
|
||
if (val === null || val === undefined) { | ||
return null; | ||
} | ||
|
||
if (typeof val !== "function") { | ||
return () => {}; | ||
} | ||
|
||
return val; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.