diff options
Diffstat (limited to 'ext/web/02_event.js')
-rw-r--r-- | ext/web/02_event.js | 2559 |
1 files changed, 1282 insertions, 1277 deletions
diff --git a/ext/web/02_event.js b/ext/web/02_event.js index c99eb8f6e..de5210d33 100644 --- a/ext/web/02_event.js +++ b/ext/web/02_event.js @@ -4,1520 +4,1525 @@ // Many parts of the DOM are not implemented in Deno, but the logic for those // parts still exists. This means you will observe a lot of strange structures // and impossible logic branches based on what Deno currently supports. -"use strict"; - -((window) => { - const core = window.Deno.core; - const ops = core.ops; - const webidl = window.__bootstrap.webidl; - const { DOMException } = window.__bootstrap.domException; - const consoleInternal = window.__bootstrap.console; - const { - ArrayPrototypeFilter, - ArrayPrototypeIncludes, - ArrayPrototypeIndexOf, - ArrayPrototypeMap, - ArrayPrototypePush, - ArrayPrototypeSlice, - ArrayPrototypeSplice, - ArrayPrototypeUnshift, - Boolean, - DateNow, - Error, - FunctionPrototypeCall, - Map, - MapPrototypeGet, - MapPrototypeSet, - ObjectCreate, - ObjectDefineProperty, - ObjectGetOwnPropertyDescriptor, - ObjectPrototypeIsPrototypeOf, - ReflectDefineProperty, - ReflectHas, - SafeArrayIterator, - StringPrototypeStartsWith, - Symbol, - SymbolFor, - SymbolToStringTag, - TypeError, - } = window.__bootstrap.primordials; - - // accessors for non runtime visible data - - function getDispatched(event) { - return Boolean(event[_dispatched]); - } - - function getPath(event) { - return event[_path] ?? []; - } - - function getStopImmediatePropagation(event) { - return Boolean(event[_stopImmediatePropagationFlag]); - } - - function setCurrentTarget( - event, - value, - ) { - event[_attributes].currentTarget = value; - } - - function setIsTrusted(event, value) { - event[_isTrusted] = value; - } - function setDispatched(event, value) { - event[_dispatched] = value; - } - - function setEventPhase(event, value) { - event[_attributes].eventPhase = value; +const core = globalThis.Deno.core; +const ops = core.ops; +import * as webidl from "internal:ext/webidl/00_webidl.js"; +import DOMException from "internal:ext/web/01_dom_exception.js"; +import { createFilteredInspectProxy } from "internal:ext/console/02_console.js"; +const primordials = globalThis.__bootstrap.primordials; +const { + ArrayPrototypeFilter, + ArrayPrototypeIncludes, + ArrayPrototypeIndexOf, + ArrayPrototypeMap, + ArrayPrototypePush, + ArrayPrototypeSlice, + ArrayPrototypeSplice, + ArrayPrototypeUnshift, + Boolean, + DateNow, + Error, + FunctionPrototypeCall, + Map, + MapPrototypeGet, + MapPrototypeSet, + ObjectCreate, + ObjectDefineProperty, + ObjectGetOwnPropertyDescriptor, + ObjectPrototypeIsPrototypeOf, + ReflectDefineProperty, + ReflectHas, + SafeArrayIterator, + StringPrototypeStartsWith, + Symbol, + SymbolFor, + SymbolToStringTag, + TypeError, +} = primordials; + +// This should be set via setGlobalThis this is required so that if even +// user deletes globalThis it is still usable +let globalThis_; + +function saveGlobalThisReference(val) { + globalThis_ = val; +} + +// accessors for non runtime visible data + +function getDispatched(event) { + return Boolean(event[_dispatched]); +} + +function getPath(event) { + return event[_path] ?? []; +} + +function getStopImmediatePropagation(event) { + return Boolean(event[_stopImmediatePropagationFlag]); +} + +function setCurrentTarget( + event, + value, +) { + event[_attributes].currentTarget = value; +} + +function setIsTrusted(event, value) { + event[_isTrusted] = value; +} + +function setDispatched(event, value) { + event[_dispatched] = value; +} + +function setEventPhase(event, value) { + event[_attributes].eventPhase = value; +} + +function setInPassiveListener(event, value) { + event[_inPassiveListener] = value; +} + +function setPath(event, value) { + event[_path] = value; +} + +function setRelatedTarget( + event, + value, +) { + event[_attributes].relatedTarget = value; +} + +function setTarget(event, value) { + event[_attributes].target = value; +} + +function setStopImmediatePropagation( + event, + value, +) { + event[_stopImmediatePropagationFlag] = value; +} + +// Type guards that widen the event type + +function hasRelatedTarget( + event, +) { + return ReflectHas(event, "relatedTarget"); +} + +const isTrusted = ObjectGetOwnPropertyDescriptor({ + get isTrusted() { + return this[_isTrusted]; + }, +}, "isTrusted").get; + +const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ + key: "bubbles", + defaultValue: false, + converter: webidl.converters.boolean, +}, { + key: "cancelable", + defaultValue: false, + converter: webidl.converters.boolean, +}, { + key: "composed", + defaultValue: false, + converter: webidl.converters.boolean, +}]); + +const _attributes = Symbol("[[attributes]]"); +const _canceledFlag = Symbol("[[canceledFlag]]"); +const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); +const _stopImmediatePropagationFlag = Symbol( + "[[stopImmediatePropagationFlag]]", +); +const _inPassiveListener = Symbol("[[inPassiveListener]]"); +const _dispatched = Symbol("[[dispatched]]"); +const _isTrusted = Symbol("[[isTrusted]]"); +const _path = Symbol("[[path]]"); +// internal. +const _skipInternalInit = Symbol("[[skipSlowInit]]"); + +class Event { + constructor(type, eventInitDict = {}) { + // TODO(lucacasonato): remove when this interface is spec aligned + this[SymbolToStringTag] = "Event"; + this[_canceledFlag] = false; + this[_stopPropagationFlag] = false; + this[_stopImmediatePropagationFlag] = false; + this[_inPassiveListener] = false; + this[_dispatched] = false; + this[_isTrusted] = false; + this[_path] = []; + + if (!eventInitDict[_skipInternalInit]) { + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'Event'", + }); + type = webidl.converters.DOMString(type, { + prefix: "Failed to construct 'Event'", + context: "Argument 1", + }); + const eventInit = eventInitConverter(eventInitDict, { + prefix: "Failed to construct 'Event'", + context: "Argument 2", + }); + this[_attributes] = { + type, + ...eventInit, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + // [LegacyUnforgeable] + ReflectDefineProperty(this, "isTrusted", { + enumerable: true, + get: isTrusted, + }); + } else { + this[_attributes] = { + type, + data: eventInitDict.data ?? null, + bubbles: eventInitDict.bubbles ?? false, + cancelable: eventInitDict.cancelable ?? false, + composed: eventInitDict.composed ?? false, + currentTarget: null, + eventPhase: Event.NONE, + target: null, + timeStamp: DateNow(), + }; + // TODO(@littledivy): Not spec compliant but performance is hurt badly + // for users of `_skipInternalInit`. + this.isTrusted = false; + } } - function setInPassiveListener(event, value) { - event[_inPassiveListener] = value; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), + keys: EVENT_PROPS, + })); } - function setPath(event, value) { - event[_path] = value; + get type() { + return this[_attributes].type; } - function setRelatedTarget( - event, - value, - ) { - event[_attributes].relatedTarget = value; + get target() { + return this[_attributes].target; } - function setTarget(event, value) { - event[_attributes].target = value; + get srcElement() { + return null; } - function setStopImmediatePropagation( - event, - value, - ) { - event[_stopImmediatePropagationFlag] = value; + set srcElement(_) { + // this member is deprecated } - // Type guards that widen the event type - - function hasRelatedTarget( - event, - ) { - return ReflectHas(event, "relatedTarget"); + get currentTarget() { + return this[_attributes].currentTarget; } - const isTrusted = ObjectGetOwnPropertyDescriptor({ - get isTrusted() { - return this[_isTrusted]; - }, - }, "isTrusted").get; - - const eventInitConverter = webidl.createDictionaryConverter("EventInit", [{ - key: "bubbles", - defaultValue: false, - converter: webidl.converters.boolean, - }, { - key: "cancelable", - defaultValue: false, - converter: webidl.converters.boolean, - }, { - key: "composed", - defaultValue: false, - converter: webidl.converters.boolean, - }]); - - const _attributes = Symbol("[[attributes]]"); - const _canceledFlag = Symbol("[[canceledFlag]]"); - const _stopPropagationFlag = Symbol("[[stopPropagationFlag]]"); - const _stopImmediatePropagationFlag = Symbol( - "[[stopImmediatePropagationFlag]]", - ); - const _inPassiveListener = Symbol("[[inPassiveListener]]"); - const _dispatched = Symbol("[[dispatched]]"); - const _isTrusted = Symbol("[[isTrusted]]"); - const _path = Symbol("[[path]]"); - // internal. - const _skipInternalInit = Symbol("[[skipSlowInit]]"); - - class Event { - constructor(type, eventInitDict = {}) { - // TODO(lucacasonato): remove when this interface is spec aligned - this[SymbolToStringTag] = "Event"; - this[_canceledFlag] = false; - this[_stopPropagationFlag] = false; - this[_stopImmediatePropagationFlag] = false; - this[_inPassiveListener] = false; - this[_dispatched] = false; - this[_isTrusted] = false; - this[_path] = []; - - if (!eventInitDict[_skipInternalInit]) { - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'Event'", - }); - type = webidl.converters.DOMString(type, { - prefix: "Failed to construct 'Event'", - context: "Argument 1", - }); - const eventInit = eventInitConverter(eventInitDict, { - prefix: "Failed to construct 'Event'", - context: "Argument 2", - }); - this[_attributes] = { - type, - ...eventInit, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: DateNow(), - }; - // [LegacyUnforgeable] - ReflectDefineProperty(this, "isTrusted", { - enumerable: true, - get: isTrusted, - }); - } else { - this[_attributes] = { - type, - data: eventInitDict.data ?? null, - bubbles: eventInitDict.bubbles ?? false, - cancelable: eventInitDict.cancelable ?? false, - composed: eventInitDict.composed ?? false, - currentTarget: null, - eventPhase: Event.NONE, - target: null, - timeStamp: DateNow(), - }; - // TODO(@littledivy): Not spec compliant but performance is hurt badly - // for users of `_skipInternalInit`. - this.isTrusted = false; - } + composedPath() { + const path = this[_path]; + if (path.length === 0) { + return []; } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(Event.prototype, this), - keys: EVENT_PROPS, - })); + if (!this.currentTarget) { + throw new Error("assertion error"); } + const composedPath = [ + { + item: this.currentTarget, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }, + ]; - get type() { - return this[_attributes].type; - } + let currentTargetIndex = 0; + let currentTargetHiddenSubtreeLevel = 0; - get target() { - return this[_attributes].target; - } + for (let index = path.length - 1; index >= 0; index--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - get srcElement() { - return null; - } + if (rootOfClosedTree) { + currentTargetHiddenSubtreeLevel++; + } - set srcElement(_) { - // this member is deprecated - } + if (item === this.currentTarget) { + currentTargetIndex = index; + break; + } - get currentTarget() { - return this[_attributes].currentTarget; + if (slotInClosedTree) { + currentTargetHiddenSubtreeLevel--; + } } - composedPath() { - const path = this[_path]; - if (path.length === 0) { - return []; - } + let currentHiddenLevel = currentTargetHiddenSubtreeLevel; + let maxHiddenLevel = currentTargetHiddenSubtreeLevel; + + for (let i = currentTargetIndex - 1; i >= 0; i--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - if (!this.currentTarget) { - throw new Error("assertion error"); + if (rootOfClosedTree) { + currentHiddenLevel++; } - const composedPath = [ - { - item: this.currentTarget, + + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypeUnshift(composedPath, { + item, itemInShadowTree: false, relatedTarget: null, rootOfClosedTree: false, slotInClosedTree: false, target: null, touchTargetList: [], - }, - ]; - - let currentTargetIndex = 0; - let currentTargetHiddenSubtreeLevel = 0; - - for (let index = path.length - 1; index >= 0; index--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - - if (rootOfClosedTree) { - currentTargetHiddenSubtreeLevel++; - } - - if (item === this.currentTarget) { - currentTargetIndex = index; - break; - } - - if (slotInClosedTree) { - currentTargetHiddenSubtreeLevel--; - } + }); } - let currentHiddenLevel = currentTargetHiddenSubtreeLevel; - let maxHiddenLevel = currentTargetHiddenSubtreeLevel; - - for (let i = currentTargetIndex - 1; i >= 0; i--) { - const { item, rootOfClosedTree, slotInClosedTree } = path[i]; - - if (rootOfClosedTree) { - currentHiddenLevel++; - } - - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypeUnshift(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } - - if (slotInClosedTree) { - currentHiddenLevel--; + if (slotInClosedTree) { + currentHiddenLevel--; - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; } } + } - currentHiddenLevel = currentTargetHiddenSubtreeLevel; - maxHiddenLevel = currentTargetHiddenSubtreeLevel; + currentHiddenLevel = currentTargetHiddenSubtreeLevel; + maxHiddenLevel = currentTargetHiddenSubtreeLevel; - for (let index = currentTargetIndex + 1; index < path.length; index++) { - const { item, rootOfClosedTree, slotInClosedTree } = path[index]; + for (let index = currentTargetIndex + 1; index < path.length; index++) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; - if (slotInClosedTree) { - currentHiddenLevel++; - } + if (slotInClosedTree) { + currentHiddenLevel++; + } - if (currentHiddenLevel <= maxHiddenLevel) { - ArrayPrototypePush(composedPath, { - item, - itemInShadowTree: false, - relatedTarget: null, - rootOfClosedTree: false, - slotInClosedTree: false, - target: null, - touchTargetList: [], - }); - } + if (currentHiddenLevel <= maxHiddenLevel) { + ArrayPrototypePush(composedPath, { + item, + itemInShadowTree: false, + relatedTarget: null, + rootOfClosedTree: false, + slotInClosedTree: false, + target: null, + touchTargetList: [], + }); + } - if (rootOfClosedTree) { - currentHiddenLevel--; + if (rootOfClosedTree) { + currentHiddenLevel--; - if (currentHiddenLevel < maxHiddenLevel) { - maxHiddenLevel = currentHiddenLevel; - } + if (currentHiddenLevel < maxHiddenLevel) { + maxHiddenLevel = currentHiddenLevel; } } - return ArrayPrototypeMap(composedPath, (p) => p.item); - } - - get NONE() { - return Event.NONE; - } - - get CAPTURING_PHASE() { - return Event.CAPTURING_PHASE; - } - - get AT_TARGET() { - return Event.AT_TARGET; - } - - get BUBBLING_PHASE() { - return Event.BUBBLING_PHASE; - } - - static get NONE() { - return 0; } + return ArrayPrototypeMap(composedPath, (p) => p.item); + } - static get CAPTURING_PHASE() { - return 1; - } + get NONE() { + return Event.NONE; + } - static get AT_TARGET() { - return 2; - } + get CAPTURING_PHASE() { + return Event.CAPTURING_PHASE; + } - static get BUBBLING_PHASE() { - return 3; - } + get AT_TARGET() { + return Event.AT_TARGET; + } - get eventPhase() { - return this[_attributes].eventPhase; - } + get BUBBLING_PHASE() { + return Event.BUBBLING_PHASE; + } - stopPropagation() { - this[_stopPropagationFlag] = true; - } + static get NONE() { + return 0; + } - get cancelBubble() { - return this[_stopPropagationFlag]; - } + static get CAPTURING_PHASE() { + return 1; + } - set cancelBubble(value) { - this[_stopPropagationFlag] = webidl.converters.boolean(value); - } + static get AT_TARGET() { + return 2; + } - stopImmediatePropagation() { - this[_stopPropagationFlag] = true; - this[_stopImmediatePropagationFlag] = true; - } + static get BUBBLING_PHASE() { + return 3; + } - get bubbles() { - return this[_attributes].bubbles; - } + get eventPhase() { + return this[_attributes].eventPhase; + } - get cancelable() { - return this[_attributes].cancelable; - } + stopPropagation() { + this[_stopPropagationFlag] = true; + } - get returnValue() { - return !this[_canceledFlag]; - } + get cancelBubble() { + return this[_stopPropagationFlag]; + } - set returnValue(value) { - if (!webidl.converters.boolean(value)) { - this[_canceledFlag] = true; - } - } + set cancelBubble(value) { + this[_stopPropagationFlag] = webidl.converters.boolean(value); + } - preventDefault() { - if (this[_attributes].cancelable && !this[_inPassiveListener]) { - this[_canceledFlag] = true; - } - } + stopImmediatePropagation() { + this[_stopPropagationFlag] = true; + this[_stopImmediatePropagationFlag] = true; + } - get defaultPrevented() { - return this[_canceledFlag]; - } + get bubbles() { + return this[_attributes].bubbles; + } - get composed() { - return this[_attributes].composed; - } + get cancelable() { + return this[_attributes].cancelable; + } - get initialized() { - return true; - } + get returnValue() { + return !this[_canceledFlag]; + } - get timeStamp() { - return this[_attributes].timeStamp; + set returnValue(value) { + if (!webidl.converters.boolean(value)) { + this[_canceledFlag] = true; } } - function defineEnumerableProps( - Ctor, - props, - ) { - for (let i = 0; i < props.length; ++i) { - const prop = props[i]; - ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); + preventDefault() { + if (this[_attributes].cancelable && !this[_inPassiveListener]) { + this[_canceledFlag] = true; } } - const EVENT_PROPS = [ - "bubbles", - "cancelable", - "composed", - "currentTarget", - "defaultPrevented", - "eventPhase", - "srcElement", - "target", - "returnValue", - "timeStamp", - "type", - ]; - - defineEnumerableProps(Event, EVENT_PROPS); - - // This is currently the only node type we are using, so instead of implementing - // the whole of the Node interface at the moment, this just gives us the one - // value to power the standards based logic - const DOCUMENT_FRAGMENT_NODE = 11; - - // DOM Logic Helper functions and type guards - - /** Get the parent node, for event targets that have a parent. - * - * Ref: https://dom.spec.whatwg.org/#get-the-parent */ - function getParent(eventTarget) { - return isNode(eventTarget) ? eventTarget.parentNode : null; + get defaultPrevented() { + return this[_canceledFlag]; } - function getRoot(eventTarget) { - return isNode(eventTarget) - ? eventTarget.getRootNode({ composed: true }) - : null; + get composed() { + return this[_attributes].composed; } - function isNode( - eventTarget, - ) { - return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); + get initialized() { + return true; } - // https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor - function isShadowInclusiveAncestor( - ancestor, - node, - ) { - while (isNode(node)) { - if (node === ancestor) { - return true; - } - - if (isShadowRoot(node)) { - node = node && getHost(node); - } else { - node = getParent(node); - } - } - - return false; + get timeStamp() { + return this[_attributes].timeStamp; } - - function isShadowRoot(nodeImpl) { - return Boolean( - nodeImpl && - isNode(nodeImpl) && - nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && - getHost(nodeImpl) != null, - ); +} + +function defineEnumerableProps( + Ctor, + props, +) { + for (let i = 0; i < props.length; ++i) { + const prop = props[i]; + ReflectDefineProperty(Ctor.prototype, prop, { enumerable: true }); } +} + +const EVENT_PROPS = [ + "bubbles", + "cancelable", + "composed", + "currentTarget", + "defaultPrevented", + "eventPhase", + "srcElement", + "target", + "returnValue", + "timeStamp", + "type", +]; + +defineEnumerableProps(Event, EVENT_PROPS); + +// This is currently the only node type we are using, so instead of implementing +// the whole of the Node interface at the moment, this just gives us the one +// value to power the standards based logic +const DOCUMENT_FRAGMENT_NODE = 11; + +// DOM Logic Helper functions and type guards + +/** Get the parent node, for event targets that have a parent. + * + * Ref: https://dom.spec.whatwg.org/#get-the-parent */ +function getParent(eventTarget) { + return isNode(eventTarget) ? eventTarget.parentNode : null; +} + +function getRoot(eventTarget) { + return isNode(eventTarget) + ? eventTarget.getRootNode({ composed: true }) + : null; +} + +function isNode( + eventTarget, +) { + return Boolean(eventTarget && ReflectHas(eventTarget, "nodeType")); +} + +// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor +function isShadowInclusiveAncestor( + ancestor, + node, +) { + while (isNode(node)) { + if (node === ancestor) { + return true; + } - function isSlotable( - nodeImpl, - ) { - return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); + if (isShadowRoot(node)) { + node = node && getHost(node); + } else { + node = getParent(node); + } } - // DOM Logic functions + return false; +} - /** Append a path item to an event's path. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-path-append - */ - function appendToEventPath( - eventImpl, - target, - targetOverride, +function isShadowRoot(nodeImpl) { + return Boolean( + nodeImpl && + isNode(nodeImpl) && + nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && + getHost(nodeImpl) != null, + ); +} + +function isSlotable( + nodeImpl, +) { + return Boolean(isNode(nodeImpl) && ReflectHas(nodeImpl, "assignedSlot")); +} + +// DOM Logic functions + +/** Append a path item to an event's path. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-path-append + */ +function appendToEventPath( + eventImpl, + target, + targetOverride, + relatedTarget, + touchTargets, + slotInClosedTree, +) { + const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); + const rootOfClosedTree = isShadowRoot(target) && + getMode(target) === "closed"; + + ArrayPrototypePush(getPath(eventImpl), { + item: target, + itemInShadowTree, + target: targetOverride, relatedTarget, - touchTargets, + touchTargetList: touchTargets, + rootOfClosedTree, slotInClosedTree, - ) { - const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); - const rootOfClosedTree = isShadowRoot(target) && - getMode(target) === "closed"; - - ArrayPrototypePush(getPath(eventImpl), { - item: target, - itemInShadowTree, - target: targetOverride, + }); +} + +function dispatch( + targetImpl, + eventImpl, + targetOverride, +) { + let clearTargets = false; + let activationTarget = null; + + setDispatched(eventImpl, true); + + targetOverride = targetOverride ?? targetImpl; + const eventRelatedTarget = hasRelatedTarget(eventImpl) + ? eventImpl.relatedTarget + : null; + let relatedTarget = retarget(eventRelatedTarget, targetImpl); + + if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { + const touchTargets = []; + + appendToEventPath( + eventImpl, + targetImpl, + targetOverride, relatedTarget, - touchTargetList: touchTargets, - rootOfClosedTree, - slotInClosedTree, - }); - } + touchTargets, + false, + ); - function dispatch( - targetImpl, - eventImpl, - targetOverride, - ) { - let clearTargets = false; - let activationTarget = null; + const isActivationEvent = eventImpl.type === "click"; - setDispatched(eventImpl, true); + if (isActivationEvent && getHasActivationBehavior(targetImpl)) { + activationTarget = targetImpl; + } - targetOverride = targetOverride ?? targetImpl; - const eventRelatedTarget = hasRelatedTarget(eventImpl) - ? eventImpl.relatedTarget + let slotInClosedTree = false; + let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) + ? targetImpl : null; - let relatedTarget = retarget(eventRelatedTarget, targetImpl); - - if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { - const touchTargets = []; - - appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false, - ); - - const isActivationEvent = eventImpl.type === "click"; + let parent = getParent(targetImpl); - if (isActivationEvent && getHasActivationBehavior(targetImpl)) { - activationTarget = targetImpl; - } - - let slotInClosedTree = false; - let slotable = isSlotable(targetImpl) && getAssignedSlot(targetImpl) - ? targetImpl - : null; - let parent = getParent(targetImpl); - - // Populate event path - // https://dom.spec.whatwg.org/#event-path - while (parent !== null) { - if (slotable !== null) { - slotable = null; - - const parentRoot = getRoot(parent); - if ( - isShadowRoot(parentRoot) && - parentRoot && - getMode(parentRoot) === "closed" - ) { - slotInClosedTree = true; - } - } - - relatedTarget = retarget(eventRelatedTarget, parent); + // Populate event path + // https://dom.spec.whatwg.org/#event-path + while (parent !== null) { + if (slotable !== null) { + slotable = null; + const parentRoot = getRoot(parent); if ( - isNode(parent) && - isShadowInclusiveAncestor(getRoot(targetImpl), parent) + isShadowRoot(parentRoot) && + parentRoot && + getMode(parentRoot) === "closed" ) { - appendToEventPath( - eventImpl, - parent, - null, - relatedTarget, - touchTargets, - slotInClosedTree, - ); - } else if (parent === relatedTarget) { - parent = null; - } else { - targetImpl = parent; - - if ( - isActivationEvent && - activationTarget === null && - getHasActivationBehavior(targetImpl) - ) { - activationTarget = targetImpl; - } - - appendToEventPath( - eventImpl, - parent, - targetImpl, - relatedTarget, - touchTargets, - slotInClosedTree, - ); - } - - if (parent !== null) { - parent = getParent(parent); - } - - slotInClosedTree = false; - } - - let clearTargetsTupleIndex = -1; - const path = getPath(eventImpl); - for ( - let i = path.length - 1; - i >= 0 && clearTargetsTupleIndex === -1; - i-- - ) { - if (path[i].target !== null) { - clearTargetsTupleIndex = i; - } - } - const clearTargetsTuple = path[clearTargetsTupleIndex]; - - clearTargets = (isNode(clearTargetsTuple.target) && - isShadowRoot(getRoot(clearTargetsTuple.target))) || - (isNode(clearTargetsTuple.relatedTarget) && - isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - - setEventPhase(eventImpl, Event.CAPTURING_PHASE); - - for (let i = path.length - 1; i >= 0; --i) { - const tuple = path[i]; - - if (tuple.target === null) { - invokeEventListeners(tuple, eventImpl); + slotInClosedTree = true; } } - for (let i = 0; i < path.length; i++) { - const tuple = path[i]; + relatedTarget = retarget(eventRelatedTarget, parent); - if (tuple.target !== null) { - setEventPhase(eventImpl, Event.AT_TARGET); - } else { - setEventPhase(eventImpl, Event.BUBBLING_PHASE); - } + if ( + isNode(parent) && + isShadowInclusiveAncestor(getRoot(targetImpl), parent) + ) { + appendToEventPath( + eventImpl, + parent, + null, + relatedTarget, + touchTargets, + slotInClosedTree, + ); + } else if (parent === relatedTarget) { + parent = null; + } else { + targetImpl = parent; if ( - (eventImpl.eventPhase === Event.BUBBLING_PHASE && - eventImpl.bubbles) || - eventImpl.eventPhase === Event.AT_TARGET + isActivationEvent && + activationTarget === null && + getHasActivationBehavior(targetImpl) ) { - invokeEventListeners(tuple, eventImpl); + activationTarget = targetImpl; } + + appendToEventPath( + eventImpl, + parent, + targetImpl, + relatedTarget, + touchTargets, + slotInClosedTree, + ); } - } - setEventPhase(eventImpl, Event.NONE); - setCurrentTarget(eventImpl, null); - setPath(eventImpl, []); - setDispatched(eventImpl, false); - eventImpl.cancelBubble = false; - setStopImmediatePropagation(eventImpl, false); + if (parent !== null) { + parent = getParent(parent); + } - if (clearTargets) { - setTarget(eventImpl, null); - setRelatedTarget(eventImpl, null); + slotInClosedTree = false; } - // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented - // if (activationTarget !== null) { - // if (!eventImpl.defaultPrevented) { - // activationTarget._activationBehavior(); - // } - // } + let clearTargetsTupleIndex = -1; + const path = getPath(eventImpl); + for ( + let i = path.length - 1; + i >= 0 && clearTargetsTupleIndex === -1; + i-- + ) { + if (path[i].target !== null) { + clearTargetsTupleIndex = i; + } + } + const clearTargetsTuple = path[clearTargetsTupleIndex]; - return !eventImpl.defaultPrevented; - } + clearTargets = (isNode(clearTargetsTuple.target) && + isShadowRoot(getRoot(clearTargetsTuple.target))) || + (isNode(clearTargetsTuple.relatedTarget) && + isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - /** Inner invoking of the event listeners where the resolved listeners are - * called. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ - function innerInvokeEventListeners( - eventImpl, - targetListeners, - ) { - let found = false; + setEventPhase(eventImpl, Event.CAPTURING_PHASE); - const { type } = eventImpl; + for (let i = path.length - 1; i >= 0; --i) { + const tuple = path[i]; - if (!targetListeners || !targetListeners[type]) { - return found; + if (tuple.target === null) { + invokeEventListeners(tuple, eventImpl); + } } - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = ArrayPrototypeSlice(targetListeners[type]); + for (let i = 0; i < path.length; i++) { + const tuple = path[i]; - for (let i = 0; i < handlers.length; i++) { - const listener = handlers[i]; - - let capture, once, passive; - if (typeof listener.options === "boolean") { - capture = listener.options; - once = false; - passive = false; + if (tuple.target !== null) { + setEventPhase(eventImpl, Event.AT_TARGET); } else { - capture = listener.options.capture; - once = listener.options.once; - passive = listener.options.passive; - } - - // Check if the event listener has been removed since the listeners has been cloned. - if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { - continue; + setEventPhase(eventImpl, Event.BUBBLING_PHASE); } - found = true; - if ( - (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || - (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + (eventImpl.eventPhase === Event.BUBBLING_PHASE && + eventImpl.bubbles) || + eventImpl.eventPhase === Event.AT_TARGET ) { - continue; - } - - if (once) { - ArrayPrototypeSplice( - targetListeners[type], - ArrayPrototypeIndexOf(targetListeners[type], listener), - 1, - ); - } - - if (passive) { - setInPassiveListener(eventImpl, true); - } - - if (typeof listener.callback === "object") { - if (typeof listener.callback.handleEvent === "function") { - listener.callback.handleEvent(eventImpl); - } - } else { - FunctionPrototypeCall( - listener.callback, - eventImpl.currentTarget, - eventImpl, - ); + invokeEventListeners(tuple, eventImpl); } + } + } - setInPassiveListener(eventImpl, false); + setEventPhase(eventImpl, Event.NONE); + setCurrentTarget(eventImpl, null); + setPath(eventImpl, []); + setDispatched(eventImpl, false); + eventImpl.cancelBubble = false; + setStopImmediatePropagation(eventImpl, false); - if (getStopImmediatePropagation(eventImpl)) { - return found; - } - } + if (clearTargets) { + setTarget(eventImpl, null); + setRelatedTarget(eventImpl, null); + } + // TODO(bartlomieju): invoke activation targets if HTML nodes will be implemented + // if (activationTarget !== null) { + // if (!eventImpl.defaultPrevented) { + // activationTarget._activationBehavior(); + // } + // } + + return !eventImpl.defaultPrevented; +} + +/** Inner invoking of the event listeners where the resolved listeners are + * called. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ +function innerInvokeEventListeners( + eventImpl, + targetListeners, +) { + let found = false; + + const { type } = eventImpl; + + if (!targetListeners || !targetListeners[type]) { return found; } - /** Invokes the listeners on a given event path with the supplied event. - * - * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ - function invokeEventListeners(tuple, eventImpl) { - const path = getPath(eventImpl); - const tupleIndex = ArrayPrototypeIndexOf(path, tuple); - for (let i = tupleIndex; i >= 0; i--) { - const t = path[i]; - if (t.target) { - setTarget(eventImpl, t.target); - break; - } - } + // Copy event listeners before iterating since the list can be modified during the iteration. + const handlers = ArrayPrototypeSlice(targetListeners[type]); - setRelatedTarget(eventImpl, tuple.relatedTarget); + for (let i = 0; i < handlers.length; i++) { + const listener = handlers[i]; - if (eventImpl.cancelBubble) { - return; + let capture, once, passive; + if (typeof listener.options === "boolean") { + capture = listener.options; + once = false; + passive = false; + } else { + capture = listener.options.capture; + once = listener.options.once; + passive = listener.options.passive; } - setCurrentTarget(eventImpl, tuple.item); - - try { - innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); - } catch (error) { - reportException(error); + // Check if the event listener has been removed since the listeners has been cloned. + if (!ArrayPrototypeIncludes(targetListeners[type], listener)) { + continue; } - } - function normalizeEventHandlerOptions( - options, - ) { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - }; - } else { - return options; - } - } + found = true; - /** Retarget the target following the spec logic. - * - * Ref: https://dom.spec.whatwg.org/#retarget */ - function retarget(a, b) { - while (true) { - if (!isNode(a)) { - return a; - } + if ( + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + ) { + continue; + } - const aRoot = a.getRootNode(); + if (once) { + ArrayPrototypeSplice( + targetListeners[type], + ArrayPrototypeIndexOf(targetListeners[type], listener), + 1, + ); + } - if (aRoot) { - if ( - !isShadowRoot(aRoot) || - (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) - ) { - return a; - } + if (passive) { + setInPassiveListener(eventImpl, true); + } - a = getHost(aRoot); + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); } + } else { + FunctionPrototypeCall( + listener.callback, + eventImpl.currentTarget, + eventImpl, + ); } - } - // Accessors for non-public data + setInPassiveListener(eventImpl, false); - const eventTargetData = Symbol(); - - function setEventTargetData(target) { - target[eventTargetData] = getDefaultTargetData(); - } - - function getAssignedSlot(target) { - return Boolean(target?.[eventTargetData]?.assignedSlot); + if (getStopImmediatePropagation(eventImpl)) { + return found; + } } - function getHasActivationBehavior(target) { - return Boolean(target?.[eventTargetData]?.hasActivationBehavior); + return found; +} + +/** Invokes the listeners on a given event path with the supplied event. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ +function invokeEventListeners(tuple, eventImpl) { + const path = getPath(eventImpl); + const tupleIndex = ArrayPrototypeIndexOf(path, tuple); + for (let i = tupleIndex; i >= 0; i--) { + const t = path[i]; + if (t.target) { + setTarget(eventImpl, t.target); + break; + } } - function getHost(target) { - return target?.[eventTargetData]?.host ?? null; - } + setRelatedTarget(eventImpl, tuple.relatedTarget); - function getListeners(target) { - return target?.[eventTargetData]?.listeners ?? {}; + if (eventImpl.cancelBubble) { + return; } - function getMode(target) { - return target?.[eventTargetData]?.mode ?? null; - } + setCurrentTarget(eventImpl, tuple.item); - function listenerCount(target, type) { - return getListeners(target)?.[type]?.length ?? 0; + try { + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); + } catch (error) { + reportException(error); } +} - function getDefaultTargetData() { +function normalizeEventHandlerOptions( + options, +) { + if (typeof options === "boolean" || typeof options === "undefined") { return { - assignedSlot: false, - hasActivationBehavior: false, - host: null, - listeners: ObjectCreate(null), - mode: "", + capture: Boolean(options), }; + } else { + return options; } +} - // This is lazy loaded because there is a circular dependency with AbortSignal. - let addEventListenerOptionsConverter; - - function lazyAddEventListenerOptionsConverter() { - addEventListenerOptionsConverter ??= webidl.createDictionaryConverter( - "AddEventListenerOptions", - [ - { - key: "capture", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "passive", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "once", - defaultValue: false, - converter: webidl.converters.boolean, - }, - { - key: "signal", - converter: webidl.converters.AbortSignal, - }, - ], - ); - } - - webidl.converters.AddEventListenerOptions = (V, opts) => { - if (webidl.type(V) !== "Object" || V === null) { - V = { capture: Boolean(V) }; - } - - lazyAddEventListenerOptionsConverter(); - return addEventListenerOptionsConverter(V, opts); - }; - - class EventTarget { - constructor() { - this[eventTargetData] = getDefaultTargetData(); - this[webidl.brand] = webidl.brand; +/** Retarget the target following the spec logic. + * + * Ref: https://dom.spec.whatwg.org/#retarget */ +function retarget(a, b) { + while (true) { + if (!isNode(a)) { + return a; } - addEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; + const aRoot = a.getRootNode(); - webidl.requiredArguments(arguments.length, 2, { - prefix, - }); + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { + return a; + } - options = webidl.converters.AddEventListenerOptions(options, { - prefix, - context: "Argument 3", - }); + a = getHost(aRoot); + } + } +} - if (callback === null) { - return; - } +// Accessors for non-public data - const { listeners } = self[eventTargetData]; +const eventTargetData = Symbol(); - if (!(ReflectHas(listeners, type))) { - listeners[type] = []; - } +function setEventTargetData(target) { + target[eventTargetData] = getDefaultTargetData(); +} - const listenerList = listeners[type]; - for (let i = 0; i < listenerList.length; ++i) { - const listener = listenerList[i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - return; - } - } - if (options?.signal) { - const signal = options?.signal; - if (signal.aborted) { - // If signal is not null and its aborted flag is set, then return. - return; - } else { - // If listener’s signal is not null, then add the following abort - // abort steps to it: Remove an event listener. - signal.addEventListener("abort", () => { - self.removeEventListener(type, callback, options); - }); - } - } +function getAssignedSlot(target) { + return Boolean(target?.[eventTargetData]?.assignedSlot); +} - ArrayPrototypePush(listeners[type], { callback, options }); - } +function getHasActivationBehavior(target) { + return Boolean(target?.[eventTargetData]?.hasActivationBehavior); +} - removeEventListener( - type, - callback, - options, - ) { - const self = this ?? globalThis; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 2, { - prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", - }); +function getHost(target) { + return target?.[eventTargetData]?.host ?? null; +} - const { listeners } = self[eventTargetData]; - if (callback !== null && ReflectHas(listeners, type)) { - listeners[type] = ArrayPrototypeFilter( - listeners[type], - (listener) => listener.callback !== callback, - ); - } else if (callback === null || !listeners[type]) { - return; - } +function getListeners(target) { + return target?.[eventTargetData]?.listeners ?? {}; +} - options = normalizeEventHandlerOptions(options); +function getMode(target) { + return target?.[eventTargetData]?.mode ?? null; +} - for (let i = 0; i < listeners[type].length; ++i) { - const listener = listeners[type][i]; - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - ArrayPrototypeSplice(listeners[type], i, 1); - break; - } - } - } +function listenerCount(target, type) { + return getListeners(target)?.[type]?.length ?? 0; +} - dispatchEvent(event) { - // If `this` is not present, then fallback to global scope. We don't use - // `globalThis` directly here, because it could be deleted by user. - // Instead use saved reference to global scope when the script was - // executed. - const self = this ?? window; - webidl.assertBranded(self, EventTargetPrototype); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", - }); +function getDefaultTargetData() { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: ObjectCreate(null), + mode: "", + }; +} - const { listeners } = self[eventTargetData]; - if (!ReflectHas(listeners, event.type)) { - setTarget(event, this); - return true; - } +// This is lazy loaded because there is a circular dependency with AbortSignal. +let addEventListenerOptionsConverter; - if (getDispatched(event)) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } +function lazyAddEventListenerOptionsConverter() { + addEventListenerOptionsConverter ??= webidl.createDictionaryConverter( + "AddEventListenerOptions", + [ + { + key: "capture", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "passive", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "once", + defaultValue: false, + converter: webidl.converters.boolean, + }, + { + key: "signal", + converter: webidl.converters.AbortSignal, + }, + ], + ); +} - if (event.eventPhase !== Event.NONE) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } +webidl.converters.AddEventListenerOptions = (V, opts) => { + if (webidl.type(V) !== "Object" || V === null) { + V = { capture: Boolean(V) }; + } - return dispatch(self, event); - } + lazyAddEventListenerOptionsConverter(); + return addEventListenerOptionsConverter(V, opts); +}; - getParent(_event) { - return null; - } +class EventTarget { + constructor() { + this[eventTargetData] = getDefaultTargetData(); + this[webidl.brand] = webidl.brand; } - webidl.configurePrototype(EventTarget); - const EventTargetPrototype = EventTarget.prototype; + addEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + const prefix = "Failed to execute 'addEventListener' on 'EventTarget'"; - defineEnumerableProps(EventTarget, [ - "addEventListener", - "removeEventListener", - "dispatchEvent", - ]); + webidl.requiredArguments(arguments.length, 2, { + prefix, + }); - class ErrorEvent extends Event { - #message = ""; - #filename = ""; - #lineno = ""; - #colno = ""; - #error = ""; + options = webidl.converters.AddEventListenerOptions(options, { + prefix, + context: "Argument 3", + }); - get message() { - return this.#message; - } - get filename() { - return this.#filename; + if (callback === null) { + return; } - get lineno() { - return this.#lineno; + + const { listeners } = self[eventTargetData]; + + if (!(ReflectHas(listeners, type))) { + listeners[type] = []; } - get colno() { - return this.#colno; + + const listenerList = listeners[type]; + for (let i = 0; i < listenerList.length; ++i) { + const listener = listenerList[i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + return; + } } - get error() { - return this.#error; + if (options?.signal) { + const signal = options?.signal; + if (signal.aborted) { + // If signal is not null and its aborted flag is set, then return. + return; + } else { + // If listener’s signal is not null, then add the following abort + // abort steps to it: Remove an event listener. + signal.addEventListener("abort", () => { + self.removeEventListener(type, callback, options); + }); + } } - constructor( - type, - { - bubbles, - cancelable, - composed, - message = "", - filename = "", - lineno = 0, - colno = 0, - error, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + ArrayPrototypePush(listeners[type], { callback, options }); + } - this.#message = message; - this.#filename = filename; - this.#lineno = lineno; - this.#colno = colno; - this.#error = error; - } + removeEventListener( + type, + callback, + options, + ) { + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 2, { + prefix: "Failed to execute 'removeEventListener' on 'EventTarget'", + }); - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "message", - "filename", - "lineno", - "colno", - "error", - ], - })); + const { listeners } = self[eventTargetData]; + if (callback !== null && ReflectHas(listeners, type)) { + listeners[type] = ArrayPrototypeFilter( + listeners[type], + (listener) => listener.callback !== callback, + ); + } else if (callback === null || !listeners[type]) { + return; } - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ErrorEvent"; - } + options = normalizeEventHandlerOptions(options); - defineEnumerableProps(ErrorEvent, [ - "message", - "filename", - "lineno", - "colno", - "error", - ]); + for (let i = 0; i < listeners[type].length; ++i) { + const listener = listeners[type][i]; + if ( + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback + ) { + ArrayPrototypeSplice(listeners[type], i, 1); + break; + } + } + } - class CloseEvent extends Event { - #wasClean = ""; - #code = ""; - #reason = ""; + dispatchEvent(event) { + // If `this` is not present, then fallback to global scope. We don't use + // `globalThis` directly here, because it could be deleted by user. + // Instead use saved reference to global scope when the script was + // executed. + const self = this ?? globalThis_; + webidl.assertBranded(self, EventTargetPrototype); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to execute 'dispatchEvent' on 'EventTarget'", + }); - get wasClean() { - return this.#wasClean; + const { listeners } = self[eventTargetData]; + if (!ReflectHas(listeners, event.type)) { + setTarget(event, this); + return true; } - get code() { - return this.#code; + + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - get reason() { - return this.#reason; + + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - constructor(type, { + return dispatch(self, event); + } + + getParent(_event) { + return null; + } +} + +webidl.configurePrototype(EventTarget); +const EventTargetPrototype = EventTarget.prototype; + +defineEnumerableProps(EventTarget, [ + "addEventListener", + "removeEventListener", + "dispatchEvent", +]); + +class ErrorEvent extends Event { + #message = ""; + #filename = ""; + #lineno = ""; + #colno = ""; + #error = ""; + + get message() { + return this.#message; + } + get filename() { + return this.#filename; + } + get lineno() { + return this.#lineno; + } + get colno() { + return this.#colno; + } + get error() { + return this.#error; + } + + constructor( + type, + { bubbles, cancelable, composed, - wasClean = false, - code = 0, - reason = "", - } = {}) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); + message = "", + filename = "", + lineno = 0, + colno = 0, + error, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - this.#wasClean = wasClean; - this.#code = code; - this.#reason = reason; - } + this.#message = message; + this.#filename = filename; + this.#lineno = lineno; + this.#colno = colno; + this.#error = error; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "wasClean", - "code", - "reason", - ], - })); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "message", + "filename", + "lineno", + "colno", + "error", + ], + })); } - class MessageEvent extends Event { - get source() { - return null; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ErrorEvent"; +} + +defineEnumerableProps(ErrorEvent, [ + "message", + "filename", + "lineno", + "colno", + "error", +]); + +class CloseEvent extends Event { + #wasClean = ""; + #code = ""; + #reason = ""; + + get wasClean() { + return this.#wasClean; + } + get code() { + return this.#code; + } + get reason() { + return this.#reason; + } - constructor(type, eventInitDict) { - super(type, { - bubbles: eventInitDict?.bubbles ?? false, - cancelable: eventInitDict?.cancelable ?? false, - composed: eventInitDict?.composed ?? false, - [_skipInternalInit]: eventInitDict?.[_skipInternalInit], - }); + constructor(type, { + bubbles, + cancelable, + composed, + wasClean = false, + code = 0, + reason = "", + } = {}) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - this.data = eventInitDict?.data ?? null; - this.ports = eventInitDict?.ports ?? []; - this.origin = eventInitDict?.origin ?? ""; - this.lastEventId = eventInitDict?.lastEventId ?? ""; - } + this.#wasClean = wasClean; + this.#code = code; + this.#reason = reason; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "data", - "origin", - "lastEventId", - ], - })); - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CloseEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "wasClean", + "code", + "reason", + ], + })); + } +} - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CloseEvent"; +class MessageEvent extends Event { + get source() { + return null; } - class CustomEvent extends Event { - #detail = null; + constructor(type, eventInitDict) { + super(type, { + bubbles: eventInitDict?.bubbles ?? false, + cancelable: eventInitDict?.cancelable ?? false, + composed: eventInitDict?.composed ?? false, + [_skipInternalInit]: eventInitDict?.[_skipInternalInit], + }); - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); - webidl.requiredArguments(arguments.length, 1, { - prefix: "Failed to construct 'CustomEvent'", - }); - const { detail } = eventInitDict; - this.#detail = detail; - } + this.data = eventInitDict?.data ?? null; + this.ports = eventInitDict?.ports ?? []; + this.origin = eventInitDict?.origin ?? ""; + this.lastEventId = eventInitDict?.lastEventId ?? ""; + } - get detail() { - return this.#detail; - } + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(MessageEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "data", + "origin", + "lastEventId", + ], + })); + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "detail", - ], - })); - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CloseEvent"; +} - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "CustomEvent"; +class CustomEvent extends Event { + #detail = null; + + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + webidl.requiredArguments(arguments.length, 1, { + prefix: "Failed to construct 'CustomEvent'", + }); + const { detail } = eventInitDict; + this.#detail = detail; } - ReflectDefineProperty(CustomEvent.prototype, "detail", { - enumerable: true, - }); + get detail() { + return this.#detail; + } - // ProgressEvent could also be used in other DOM progress event emits. - // Current use is for FileReader. - class ProgressEvent extends Event { - constructor(type, eventInitDict = {}) { - super(type, eventInitDict); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(CustomEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "detail", + ], + })); + } - this.lengthComputable = eventInitDict?.lengthComputable ?? false; - this.loaded = eventInitDict?.loaded ?? 0; - this.total = eventInitDict?.total ?? 0; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "CustomEvent"; +} - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "lengthComputable", - "loaded", - "total", - ], - })); - } +ReflectDefineProperty(CustomEvent.prototype, "detail", { + enumerable: true, +}); - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "ProgressEvent"; +// ProgressEvent could also be used in other DOM progress event emits. +// Current use is for FileReader. +class ProgressEvent extends Event { + constructor(type, eventInitDict = {}) { + super(type, eventInitDict); + + this.lengthComputable = eventInitDict?.lengthComputable ?? false; + this.loaded = eventInitDict?.loaded ?? 0; + this.total = eventInitDict?.total ?? 0; } - class PromiseRejectionEvent extends Event { - #promise = null; - #reason = null; + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf(ProgressEvent.prototype, this), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "lengthComputable", + "loaded", + "total", + ], + })); + } - get promise() { - return this.#promise; - } - get reason() { - return this.#reason; - } + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "ProgressEvent"; +} - constructor( - type, - { - bubbles, - cancelable, - composed, - promise, - reason, - } = {}, - ) { - super(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed, - }); +class PromiseRejectionEvent extends Event { + #promise = null; + #reason = null; - this.#promise = promise; - this.#reason = reason; - } + get promise() { + return this.#promise; + } + get reason() { + return this.#reason; + } - [SymbolFor("Deno.privateCustomInspect")](inspect) { - return inspect(consoleInternal.createFilteredInspectProxy({ - object: this, - evaluate: ObjectPrototypeIsPrototypeOf( - PromiseRejectionEvent.prototype, - this, - ), - keys: [ - ...new SafeArrayIterator(EVENT_PROPS), - "promise", - "reason", - ], - })); - } + constructor( + type, + { + bubbles, + cancelable, + composed, + promise, + reason, + } = {}, + ) { + super(type, { + bubbles: bubbles, + cancelable: cancelable, + composed: composed, + }); - // TODO(lucacasonato): remove when this interface is spec aligned - [SymbolToStringTag] = "PromiseRejectionEvent"; + this.#promise = promise; + this.#reason = reason; } - defineEnumerableProps(PromiseRejectionEvent, [ - "promise", - "reason", - ]); + [SymbolFor("Deno.privateCustomInspect")](inspect) { + return inspect(createFilteredInspectProxy({ + object: this, + evaluate: ObjectPrototypeIsPrototypeOf( + PromiseRejectionEvent.prototype, + this, + ), + keys: [ + ...new SafeArrayIterator(EVENT_PROPS), + "promise", + "reason", + ], + })); + } - const _eventHandlers = Symbol("eventHandlers"); + // TODO(lucacasonato): remove when this interface is spec aligned + [SymbolToStringTag] = "PromiseRejectionEvent"; +} - function makeWrappedHandler(handler, isSpecialErrorEventHandler) { - function wrappedHandler(evt) { - if (typeof wrappedHandler.handler !== "function") { - return; - } +defineEnumerableProps(PromiseRejectionEvent, [ + "promise", + "reason", +]); - if ( - isSpecialErrorEventHandler && - ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, evt) && - evt.type === "error" - ) { - const ret = FunctionPrototypeCall( - wrappedHandler.handler, - this, - evt.message, - evt.filename, - evt.lineno, - evt.colno, - evt.error, - ); - if (ret === true) { - evt.preventDefault(); - } - return; - } +const _eventHandlers = Symbol("eventHandlers"); - return FunctionPrototypeCall(wrappedHandler.handler, this, evt); +function makeWrappedHandler(handler, isSpecialErrorEventHandler) { + function wrappedHandler(evt) { + if (typeof wrappedHandler.handler !== "function") { + return; } - wrappedHandler.handler = handler; - return wrappedHandler; - } - - // `init` is an optional function that will be called the first time that the - // event handler property is set. It will be called with the object on which - // the property is set as its argument. - // `isSpecialErrorEventHandler` can be set to true to opt into the special - // behavior of event handlers for the "error" event in a global scope. - function defineEventHandler( - emitter, - name, - init = undefined, - isSpecialErrorEventHandler = false, - ) { - // HTML specification section 8.1.7.1 - ObjectDefineProperty(emitter, `on${name}`, { - get() { - if (!this[_eventHandlers]) { - return null; - } - return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; - }, - set(value) { - // All three Web IDL event handler types are nullable callback functions - // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning - // anything other than an object is treated as null. - if (typeof value !== "object" && typeof value !== "function") { - value = null; - } + if ( + isSpecialErrorEventHandler && + ObjectPrototypeIsPrototypeOf(ErrorEvent.prototype, evt) && + evt.type === "error" + ) { + const ret = FunctionPrototypeCall( + wrappedHandler.handler, + this, + evt.message, + evt.filename, + evt.lineno, + evt.colno, + evt.error, + ); + if (ret === true) { + evt.preventDefault(); + } + return; + } - if (!this[_eventHandlers]) { - this[_eventHandlers] = new Map(); - } - let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); - if (handlerWrapper) { - handlerWrapper.handler = value; - } else if (value !== null) { - handlerWrapper = makeWrappedHandler( - value, - isSpecialErrorEventHandler, - ); - this.addEventListener(name, handlerWrapper); - init?.(this); - } - MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); - }, - configurable: true, - enumerable: true, - }); + return FunctionPrototypeCall(wrappedHandler.handler, this, evt); } + wrappedHandler.handler = handler; + return wrappedHandler; +} + +// `init` is an optional function that will be called the first time that the +// event handler property is set. It will be called with the object on which +// the property is set as its argument. +// `isSpecialErrorEventHandler` can be set to true to opt into the special +// behavior of event handlers for the "error" event in a global scope. +function defineEventHandler( + emitter, + name, + init = undefined, + isSpecialErrorEventHandler = false, +) { + // HTML specification section 8.1.7.1 + ObjectDefineProperty(emitter, `on${name}`, { + get() { + if (!this[_eventHandlers]) { + return null; + } - let reportExceptionStackedCalls = 0; - - // https://html.spec.whatwg.org/#report-the-exception - function reportException(error) { - reportExceptionStackedCalls++; - const jsError = core.destructureError(error); - const message = jsError.exceptionMessage; - let filename = ""; - let lineno = 0; - let colno = 0; - if (jsError.frames.length > 0) { - filename = jsError.frames[0].fileName; - lineno = jsError.frames[0].lineNumber; - colno = jsError.frames[0].columnNumber; - } else { - const jsError = core.destructureError(new Error()); - const frames = jsError.frames; - for (let i = 0; i < frames.length; ++i) { - const frame = frames[i]; - if ( - typeof frame.fileName == "string" && - !StringPrototypeStartsWith(frame.fileName, "internal:") - ) { - filename = frame.fileName; - lineno = frame.lineNumber; - colno = frame.columnNumber; - break; - } + return MapPrototypeGet(this[_eventHandlers], name)?.handler ?? null; + }, + set(value) { + // All three Web IDL event handler types are nullable callback functions + // with the [LegacyTreatNonObjectAsNull] extended attribute, meaning + // anything other than an object is treated as null. + if (typeof value !== "object" && typeof value !== "function") { + value = null; } - } - const event = new ErrorEvent("error", { - cancelable: true, - message, - filename, - lineno, - colno, - error, - }); - // Avoid recursing `reportException()` via error handlers more than once. - if (reportExceptionStackedCalls > 1 || window.dispatchEvent(event)) { - ops.op_dispatch_exception(error); - } - reportExceptionStackedCalls--; - } - function checkThis(thisArg) { - if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) { - throw new TypeError("Illegal invocation"); + if (!this[_eventHandlers]) { + this[_eventHandlers] = new Map(); + } + let handlerWrapper = MapPrototypeGet(this[_eventHandlers], name); + if (handlerWrapper) { + handlerWrapper.handler = value; + } else if (value !== null) { + handlerWrapper = makeWrappedHandler( + value, + isSpecialErrorEventHandler, + ); + this.addEventListener(name, handlerWrapper); + init?.(this); + } + MapPrototypeSet(this[_eventHandlers], name, handlerWrapper); + }, + configurable: true, + enumerable: true, + }); +} + +let reportExceptionStackedCalls = 0; + +// https://html.spec.whatwg.org/#report-the-exception +function reportException(error) { + reportExceptionStackedCalls++; + const jsError = core.destructureError(error); + const message = jsError.exceptionMessage; + let filename = ""; + let lineno = 0; + let colno = 0; + if (jsError.frames.length > 0) { + filename = jsError.frames[0].fileName; + lineno = jsError.frames[0].lineNumber; + colno = jsError.frames[0].columnNumber; + } else { + const jsError = core.destructureError(new Error()); + const frames = jsError.frames; + for (let i = 0; i < frames.length; ++i) { + const frame = frames[i]; + if ( + typeof frame.fileName == "string" && + !StringPrototypeStartsWith(frame.fileName, "internal:") + ) { + filename = frame.fileName; + lineno = frame.lineNumber; + colno = frame.columnNumber; + break; + } } } - - // https://html.spec.whatwg.org/#dom-reporterror - function reportError(error) { - checkThis(this); - const prefix = "Failed to call 'reportError'"; - webidl.requiredArguments(arguments.length, 1, { prefix }); - reportException(error); + const event = new ErrorEvent("error", { + cancelable: true, + message, + filename, + lineno, + colno, + error, + }); + // Avoid recursing `reportException()` via error handlers more than once. + if (reportExceptionStackedCalls > 1 || globalThis_.dispatchEvent(event)) { + ops.op_dispatch_exception(error); } + reportExceptionStackedCalls--; +} - window.__bootstrap.eventTarget = { - EventTarget, - setEventTargetData, - listenerCount, - }; - window.__bootstrap.event = { - reportException, - setIsTrusted, - setTarget, - defineEventHandler, - _skipInternalInit, - Event, - ErrorEvent, - CloseEvent, - MessageEvent, - CustomEvent, - ProgressEvent, - PromiseRejectionEvent, - reportError, - }; -})(this); +function checkThis(thisArg) { + if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis_) { + throw new TypeError("Illegal invocation"); + } +} + +// https://html.spec.whatwg.org/#dom-reporterror +function reportError(error) { + checkThis(this); + const prefix = "Failed to call 'reportError'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + reportException(error); +} + +export { + _skipInternalInit, + CloseEvent, + CustomEvent, + defineEventHandler, + ErrorEvent, + Event, + EventTarget, + listenerCount, + MessageEvent, + ProgressEvent, + PromiseRejectionEvent, + reportError, + reportException, + saveGlobalThisReference, + setEventTargetData, + setIsTrusted, + setTarget, +}; |