“use strict”; const DOMException = require(“../../web-idl/DOMException”); const reportException = require(“../helpers/runtime-script-errors”); const domSymbolTree = require(“../helpers/internal-constants”).domSymbolTree; const idlUtils = require(“../generated/utils”);
const EventImpl = require(“./Event-impl”).implementation; const Event = require(“../generated/Event”).interface;
class EventTargetImpl {
constructor() { this._eventListeners = Object.create(null); } addEventListener(type, callback, options) { // webidl2js currently can't handle neither optional arguments nor callback interfaces if (callback === undefined || callback === null) { callback = null; } else if (typeof callback !== "object" && typeof callback !== "function") { throw new TypeError("Only undefined, null, an object, or a function are allowed for the callback parameter"); } options = normalizeEventHandlerOptions(options, ["capture", "once"]); if (callback === null) { return; } if (!this._eventListeners[type]) { this._eventListeners[type] = []; } for (let i = 0; i < this._eventListeners[type].length; ++i) { const listener = this._eventListeners[type][i]; if (listener.options.capture === options.capture && listener.callback === callback) { return; } } this._eventListeners[type].push({ callback, options }); } removeEventListener(type, callback, options) { if (callback === undefined || callback === null) { callback = null; } else if (typeof callback !== "object" && typeof callback !== "function") { throw new TypeError("Only undefined, null, an object, or a function are allowed for the callback parameter"); } options = normalizeEventHandlerOptions(options, ["capture"]); if (callback === null) { // Optimization, not in the spec. return; } if (!this._eventListeners[type]) { return; } for (let i = 0; i < this._eventListeners[type].length; ++i) { const listener = this._eventListeners[type][i]; if (listener.callback === callback && listener.options.capture === options.capture) { this._eventListeners[type].splice(i, 1); break; } } } dispatchEvent(eventImpl) { if (!(eventImpl instanceof EventImpl)) { throw new TypeError("Argument to dispatchEvent must be an Event"); } if (eventImpl._dispatchFlag || !eventImpl._initializedFlag) { throw new DOMException(DOMException.INVALID_STATE_ERR, "Tried to dispatch an uninitialized event"); } if (eventImpl.eventPhase !== Event.NONE) { throw new DOMException(DOMException.INVALID_STATE_ERR, "Tried to dispatch a dispatching event"); } eventImpl.isTrusted = false; return this._dispatch(eventImpl); } _dispatch(eventImpl, targetOverride) { eventImpl._dispatchFlag = true; eventImpl.target = targetOverride || this; const eventPath = []; let targetParent = domSymbolTree.parent(eventImpl.target); let target = eventImpl.target; while (targetParent) { eventPath.push(targetParent); target = targetParent; targetParent = domSymbolTree.parent(targetParent); } if (eventImpl.type !== "load" && target._defaultView) { // https://html.spec.whatwg.org/#events-and-the-window-object eventPath.push(idlUtils.implForWrapper(target._defaultView)); } eventImpl.eventPhase = Event.CAPTURING_PHASE; for (let i = eventPath.length - 1; i >= 0; --i) { if (eventImpl._stopPropagationFlag) { break; } const object = eventPath[i]; const objectImpl = idlUtils.implForWrapper(object) || object; // window :( const eventListeners = objectImpl._eventListeners[eventImpl.type]; invokeEventListeners(eventListeners, object, eventImpl); } eventImpl.eventPhase = Event.AT_TARGET; if (!eventImpl._stopPropagationFlag) { invokeInlineListeners(eventImpl.target, eventImpl); if (this._eventListeners[eventImpl.type]) { const eventListeners = this._eventListeners[eventImpl.type]; invokeEventListeners(eventListeners, eventImpl.target, eventImpl); } } if (eventImpl.bubbles) { eventImpl.eventPhase = Event.BUBBLING_PHASE; for (let i = 0; i < eventPath.length; ++i) { if (eventImpl._stopPropagationFlag) { break; } const object = eventPath[i]; const objectImpl = idlUtils.implForWrapper(object) || object; // window :( const eventListeners = objectImpl._eventListeners[eventImpl.type]; invokeInlineListeners(object, eventImpl); invokeEventListeners(eventListeners, object, eventImpl); } } eventImpl._dispatchFlag = false; eventImpl._stopPropagationFlag = false; eventImpl._stopImmediatePropagationFlag = false; eventImpl.eventPhase = Event.NONE; eventImpl.currentTarget = null; return !eventImpl._canceledFlag; }
}
module.exports = {
implementation: EventTargetImpl
};
function invokeInlineListeners(object, event) {
const wrapper = idlUtils.wrapperForImpl(object); const inlineListener = getListenerForInlineEventHandler(wrapper, event.type); if (inlineListener) { const document = object._ownerDocument || (wrapper && (wrapper._document || wrapper._ownerDocument)); // Will be falsy for windows that have closed if (document && (!object.nodeName || document.implementation._hasFeature("ProcessExternalResources", "script"))) { invokeEventListeners([{ callback: inlineListener, options: normalizeEventHandlerOptions(false, ["capture", "once"]) }], object, event); } }
}
function invokeEventListeners(listeners, target, eventImpl) {
const wrapper = idlUtils.wrapperForImpl(target); const document = target._ownerDocument || (wrapper && (wrapper._document || wrapper._ownerDocument)); // Will be falsy for windows that have closed if (!document) { return; } // workaround for events emitted on window (window-proxy) // the wrapper is the root window instance, but we only want to expose the vm proxy at all times if (wrapper._document) { target = idlUtils.implForWrapper(wrapper._document)._defaultView; } eventImpl.currentTarget = target; if (!listeners) { return; } const handlers = listeners.slice(); for (let i = 0; i < handlers.length; ++i) { if (eventImpl._stopImmediatePropagationFlag) { return; } const listener = handlers[i]; const capture = listener.options.capture; const once = listener.options.once; // const passive = listener.options.passive; if (listeners.indexOf(listener) === -1 || (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture)) { continue; } if (once) { listeners.splice(listeners.indexOf(listener), 1); } try { if (typeof listener.callback === "object") { if (typeof listener.callback.handleEvent === "function") { listener.callback.handleEvent(idlUtils.wrapperForImpl(eventImpl)); } } else { listener.callback.call(idlUtils.wrapperForImpl(eventImpl.currentTarget), idlUtils.wrapperForImpl(eventImpl)); } } catch (e) { let window = null; if (wrapper && wrapper._document) { // Triggered by Window window = wrapper; } else if (target._ownerDocument) { // Triggered by most webidl2js'ed instances window = target._ownerDocument._defaultView; } else if (wrapper._ownerDocument) { // Currently triggered by XHR and some other non-webidl2js things window = wrapper._ownerDocument._defaultView; } if (window) { reportException(window, e); } // Errors in window-less documents just get swallowed... can you think of anything better? } }
}
const wrappedListener = Symbol(“inline event listener wrapper”);
/**
* Normalize the event listeners options argument in order to get always a valid options object * @param {Object} options - user defined options * @param {Array} defaultBoolKeys - boolean properties that should belong to the options object * @returns {Object} object containing at least the "defaultBoolKeys" */
function normalizeEventHandlerOptions(options, defaultBoolKeys) {
const returnValue = {}; // no need to go further here if (typeof options === "boolean" || options === null || typeof options === "undefined") { returnValue.capture = Boolean(options); return returnValue; } // non objects options so we typecast its value as "capture" value if (typeof options !== "object") { returnValue.capture = Boolean(options); // at this point we don't need to loop the "capture" key anymore defaultBoolKeys = defaultBoolKeys.filter(k => k !== "capture"); } for (const key of defaultBoolKeys) { returnValue[key] = Boolean(options[key]); } return returnValue;
}
function getListenerForInlineEventHandler(target, type) {
const callback = target["on" + type]; if (!callback) { // TODO event handlers: only check null return null; } if (!callback[wrappedListener]) { // https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm callback[wrappedListener] = function (E) { const isWindowError = E.constructor.name === "ErrorEvent" && type === "error"; // TODO branding let returnValue; if (isWindowError) { returnValue = callback.call(E.currentTarget, E.message, E.filename, E.lineno, E.colno, E.error); } else { returnValue = callback.call(E.currentTarget, E); } if (type === "mouseover" || isWindowError) { if (returnValue === true) { E.preventDefault(); } } else if (returnValue === false) { E.preventDefault(); } }; } return callback[wrappedListener];
}