UNPKG

jsdom-se

Version:

jsdom fork for silent errors - A JavaScript implementation of the DOM and HTML standards

245 lines (203 loc) 7.51 kB
"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 Event = require("../generated/Event").interface; class EventTargetImpl { constructor() { this._eventListeners = Object.create(null); } addEventListener(type, callback, capture) { // webidl2js currently can't handle neither optional arguments nor callback interfaces if (callback === undefined || callback === null) { callback = null; } else if (typeof callback === "object") { callback = callback.handleEvent; } else if (typeof callback !== "function") { throw new TypeError("Only undefined, null, an object, or a function are allowed for the callback parameter"); } 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.capture === capture && listener.callback === callback) { return; } } this._eventListeners[type].push({ callback, capture }); } removeEventListener(type, callback, capture) { if (callback === undefined || callback === null) { callback = null; } else if (typeof callback === "object") { callback = callback.handleEvent; } else if (typeof callback !== "function") { throw new TypeError("Only undefined, null, an object, or a function are allowed for the callback parameter"); } 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.capture === capture) { this._eventListeners[type].splice(i, 1); break; } } } dispatchEvent(event) { if (!(event instanceof Event)) { throw new TypeError("Argument to dispatchEvent must be an Event"); } const eventImpl = idlUtils.implForWrapper(event); if (eventImpl._dispatchFlag || !eventImpl._initializedFlag) { throw new DOMException(DOMException.INVALID_STATE_ERR, "Tried to dispatch an uninitialized event"); } if (event.eventPhase !== event.NONE) { throw new DOMException(DOMException.INVALID_STATE_ERR, "Tried to dispatch a dispatching event"); } eventImpl.isTrusted = false; return this._dispatch(event); } _dispatch(event, targetOverride) { const eventImpl = idlUtils.implForWrapper(event); eventImpl._dispatchFlag = true; eventImpl.target = targetOverride || idlUtils.wrapperForImpl(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 (event.type !== "load" && target._defaultView) { // https://html.spec.whatwg.org/#events-and-the-window-object eventPath.push(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); const eventListeners = objectImpl._eventListeners[event.type]; invokeEventListeners(eventListeners, object, event); } eventImpl.eventPhase = Event.AT_TARGET; if (!eventImpl._stopPropagationFlag) { invokeInlineListeners(eventImpl.target, event); if (this._eventListeners[event.type]) { const eventListeners = this._eventListeners[event.type]; invokeEventListeners(eventListeners, eventImpl.target, event); } } if (event.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); const eventListeners = objectImpl._eventListeners[event.type]; invokeInlineListeners(object, event); invokeEventListeners(eventListeners, object, event); } } eventImpl._dispatchFlag = false; eventImpl.eventPhase = Event.NONE; eventImpl.currentTarget = null; return !eventImpl._canceledFlag; } } module.exports = { implementation: EventTargetImpl }; function invokeInlineListeners(object, event) { const inlineListener = getListenerForInlineEventHandler(object, event.type); if (inlineListener) { const document = object._ownerDocument || object._document; // Will be falsy for windows that have closed if (document && (!object.nodeName || document.implementation._hasFeature("ProcessExternalResources", "script"))) { invokeEventListeners([{ callback: inlineListener }], object, event); } } } function invokeEventListeners(listeners, target, event) { const document = target._ownerDocument || target._document; // Will be falsy for windows that have closed if (!document) { return; } const eventImpl = idlUtils.implForWrapper(event); 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]; if (listeners.indexOf(listener) === -1 || (event.eventPhase === Event.CAPTURING_PHASE && !listener.capture) || (event.eventPhase === Event.BUBBLING_PHASE && listener.capture)) { continue; } try { listener.callback.call(eventImpl.currentTarget, event); } catch (e) { let window = null; if (target._document) { window = target; } else if (target._ownerDocument) { window = target._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"); 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) { E.preventDefault(); } } else if (!returnValue) { E.preventDefault(); } }; } return callback[wrappedListener]; }