UNPKG

happy-dom

Version:

Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.

309 lines 13.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const PropertySymbol = __importStar(require("../PropertySymbol.cjs")); const EventPhaseEnum_js_1 = __importDefault(require("./EventPhaseEnum.cjs")); const WindowBrowserContext_js_1 = __importDefault(require("../window/WindowBrowserContext.cjs")); const BrowserErrorCaptureEnum_js_1 = __importDefault(require("../browser/enums/BrowserErrorCaptureEnum.cjs")); /** * Handles events. */ class EventTarget { [PropertySymbol.listeners] = { capturing: new Map(), bubbling: new Map() }; [PropertySymbol.listenerOptions] = { capturing: new Map(), bubbling: new Map() }; /** * Return a default description for the EventTarget class. */ get [Symbol.toStringTag]() { return 'EventTarget'; } /** * Adds an event listener. * * @param type Event type. * @param listener Listener. * @param options An object that specifies characteristics about the event listener.(currently only once) * @param options.once * @param options.signal An AbortSignal. The listener will be removed when the given AbortSignal object's abort() method is called. */ addEventListener(type, listener, options) { options = typeof options === 'boolean' ? { capture: options } : options || {}; const eventPhase = options.capture ? 'capturing' : 'bubbling'; let listeners = this[PropertySymbol.listeners][eventPhase].get(type); let listenerOptions = this[PropertySymbol.listenerOptions][eventPhase].get(type); if (!listeners) { listeners = []; listenerOptions = []; this[PropertySymbol.listeners][eventPhase].set(type, listeners); this[PropertySymbol.listenerOptions][eventPhase].set(type, listenerOptions); } if (listeners.includes(listener)) { return; } listeners.push(listener); listenerOptions.push(options); if (options.signal && !options.signal.aborted) { options.signal.addEventListener('abort', () => { this.removeEventListener(type, listener); }); } } /** * Adds an event listener. * * @param type Event type. * @param listener Listener. */ removeEventListener(type, listener) { const bubblingListeners = this[PropertySymbol.listeners].bubbling.get(type); if (bubblingListeners) { const index = bubblingListeners.indexOf(listener); if (index !== -1) { bubblingListeners.splice(index, 1); this[PropertySymbol.listenerOptions].bubbling.get(type).splice(index, 1); return; } } const capturingListeners = this[PropertySymbol.listeners].capturing.get(type); if (capturingListeners) { const index = capturingListeners.indexOf(listener); if (index !== -1) { capturingListeners.splice(index, 1); this[PropertySymbol.listenerOptions].capturing.get(type).splice(index, 1); } } } /** * Dispatches an event. * * @see https://www.w3.org/TR/DOM-Level-3-Events/#event-flow * @see https://www.quirksmode.org/js/events_order.html#link4 * @param event Event. * @returns The return value is false if event is cancelable and at least one of the event handlers which handled this event called Event.preventDefault(). */ dispatchEvent(event) { // The "load" event is a special case. It should not bubble up to the window from the document. if (!event[PropertySymbol.dispatching] && (event[PropertySymbol.type] !== 'load' || !event[PropertySymbol.target])) { event[PropertySymbol.dispatching] = true; event[PropertySymbol.target] = this[PropertySymbol.proxy] || this; this.#goThroughDispatchEventPhases(event); event[PropertySymbol.dispatching] = false; return !(event[PropertySymbol.cancelable] && event[PropertySymbol.defaultPrevented]); } this.#callDispatchEventListeners(event); return !(event[PropertySymbol.cancelable] && event[PropertySymbol.defaultPrevented]); } /** * Adds an event listener. * * TODO: * Was used by with IE8- and Opera. React believed Happy DOM was a legacy browser and used them, but that is no longer the case, so we should remove this method after that this is verified. * * @deprecated * @param type Event type. * @param listener Listener. */ attachEvent(type, listener) { this.addEventListener(type.replace('on', ''), listener); } /** * Removes an event listener. * * TODO: * Was used by IE8- and Opera. React believed Happy DOM was a legacy browser and used them, but that is no longer the case, so we should remove this method after that this is verified. * * @deprecated * @param type Event type. * @param listener Listener. */ detachEvent(type, listener) { this.removeEventListener(type.replace('on', ''), listener); } /** * Goes through dispatch event phases. * * @param event Event. */ #goThroughDispatchEventPhases(event) { const composedPath = event.composedPath(); // Capturing phase event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.capturing; for (let i = composedPath.length - 1; i >= 0; i--) { event[PropertySymbol.currentTarget] = composedPath[i]; composedPath[i].dispatchEvent(event); if (event[PropertySymbol.propagationStopped] || event[PropertySymbol.immediatePropagationStopped]) { event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.none; event[PropertySymbol.currentTarget] = null; return; } } // At target phase event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.atTarget; event[PropertySymbol.currentTarget] = this[PropertySymbol.proxy] || this; event[PropertySymbol.target].dispatchEvent(event); // Bubbling phase event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.bubbling; if (event[PropertySymbol.bubbles] && !event[PropertySymbol.propagationStopped] && !event[PropertySymbol.immediatePropagationStopped]) { for (let i = 1, max = composedPath.length; i < max; i++) { event[PropertySymbol.currentTarget] = composedPath[i]; composedPath[i].dispatchEvent(event); if (event[PropertySymbol.propagationStopped] || event[PropertySymbol.immediatePropagationStopped]) { event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.none; event[PropertySymbol.currentTarget] = null; return; } } } // None phase (done) event[PropertySymbol.eventPhase] = EventPhaseEnum_js_1.default.none; event[PropertySymbol.currentTarget] = null; } /** * Handles dispatch event listeners. * * @param event Event. */ #callDispatchEventListeners(event) { const window = this[PropertySymbol.window]; const browserSettings = window ? new WindowBrowserContext_js_1.default(window).getSettings() : null; const eventPhase = event.eventPhase === EventPhaseEnum_js_1.default.capturing ? 'capturing' : 'bubbling'; // We need to clone the arrays because the listeners may remove themselves while we are iterating. const listeners = this[PropertySymbol.listeners][eventPhase].get(event.type)?.slice(); if (listeners && listeners.length) { const listenerOptions = this[PropertySymbol.listenerOptions][eventPhase] .get(event.type) ?.slice(); for (let i = 0, max = listeners.length; i < max; i++) { const listener = listeners[i]; const options = listenerOptions[i]; if (options?.passive) { event[PropertySymbol.isInPassiveEventListener] = true; } // We can end up in a never ending loop if the listener for the error event on Window also throws an error. if (window && (this !== window || event.type !== 'error') && !browserSettings?.disableErrorCapturing && browserSettings?.errorCapture === BrowserErrorCaptureEnum_js_1.default.tryAndCatch) { if (listener.handleEvent) { let result; try { result = listener.handleEvent.call(listener, event); } catch (error) { window[PropertySymbol.dispatchError](error); } if (result instanceof Promise) { result.catch((error) => window[PropertySymbol.dispatchError](error)); } } else { let result; try { result = listener.call(this, event); } catch (error) { window[PropertySymbol.dispatchError](error); } if (result instanceof Promise) { result.catch((error) => window[PropertySymbol.dispatchError](error)); } } } else { if (listener.handleEvent) { listener.handleEvent.call(listener, event); } else { listener.call(this, event); } } event[PropertySymbol.isInPassiveEventListener] = false; if (options?.once) { // At this time, listeners and listenersOptions are cloned arrays. When the original value is deleted, // The value corresponding to the cloned array is not deleted. So we need to delete the value in the cloned array. listeners.splice(i, 1); listenerOptions.splice(i, 1); this.removeEventListener(event.type, listener); i--; max--; } if (event[PropertySymbol.immediatePropagationStopped]) { return; } } } if (event.eventPhase !== EventPhaseEnum_js_1.default.capturing) { const onEventName = 'on' + event.type.toLowerCase(); const eventListener = this[onEventName]; if (typeof eventListener === 'function') { // We can end up in a never ending loop if the listener for the error event on Window also throws an error. if (window && (this !== window || event.type !== 'error') && !browserSettings?.disableErrorCapturing && browserSettings?.errorCapture === BrowserErrorCaptureEnum_js_1.default.tryAndCatch) { let result; try { result = eventListener(event); } catch (error) { window[PropertySymbol.dispatchError](error); } if (result instanceof Promise) { result.catch((error) => window[PropertySymbol.dispatchError](error)); } } else { eventListener(event); } } } } } exports.default = EventTarget; //# sourceMappingURL=EventTarget.cjs.map