diff options
Diffstat (limited to 'cli/js/web/event_target.ts')
-rw-r--r-- | cli/js/web/event_target.ts | 588 |
1 files changed, 0 insertions, 588 deletions
diff --git a/cli/js/web/event_target.ts b/cli/js/web/event_target.ts deleted file mode 100644 index 82935dd9c..000000000 --- a/cli/js/web/event_target.ts +++ /dev/null @@ -1,588 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -// This module follows most of the WHATWG Living Standard for the DOM logic. -// 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. - -import { DOMExceptionImpl as DOMException } from "./dom_exception.ts"; -import type * as domTypes from "./dom_types.d.ts"; -import { - EventImpl as Event, - EventPath, - getDispatched, - getPath, - getStopImmediatePropagation, - hasRelatedTarget, - setCurrentTarget, - setDispatched, - setEventPhase, - setInPassiveListener, - setPath, - setRelatedTarget, - setStopImmediatePropagation, - setTarget, -} from "./event.ts"; -import { defineEnumerableProps, requiredArguments } from "./util.ts"; - -// 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: EventTarget): EventTarget | null { - return isNode(eventTarget) ? eventTarget.parentNode : null; -} - -function getRoot(eventTarget: EventTarget): EventTarget | null { - return isNode(eventTarget) - ? eventTarget.getRootNode({ composed: true }) - : null; -} - -function isNode<T extends EventTarget>( - eventTarget: T | null, -): eventTarget is T & domTypes.Node { - return Boolean(eventTarget && "nodeType" in eventTarget); -} - -// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor -function isShadowInclusiveAncestor( - ancestor: EventTarget | null, - node: EventTarget | null, -): boolean { - while (isNode(node)) { - if (node === ancestor) { - return true; - } - - if (isShadowRoot(node)) { - node = node && getHost(node); - } else { - node = getParent(node); - } - } - - return false; -} - -function isShadowRoot(nodeImpl: EventTarget | null): boolean { - return Boolean( - nodeImpl && - isNode(nodeImpl) && - nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && - getHost(nodeImpl) != null, - ); -} - -function isSlotable<T extends EventTarget>( - nodeImpl: T | null, -): nodeImpl is T & domTypes.Node & domTypes.Slotable { - return Boolean(isNode(nodeImpl) && "assignedSlot" in nodeImpl); -} - -// 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: Event, - target: EventTarget, - targetOverride: EventTarget | null, - relatedTarget: EventTarget | null, - touchTargets: EventTarget[], - slotInClosedTree: boolean, -): void { - const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); - const rootOfClosedTree = isShadowRoot(target) && getMode(target) === "closed"; - - getPath(eventImpl).push({ - item: target, - itemInShadowTree, - target: targetOverride, - relatedTarget, - touchTargetList: touchTargets, - rootOfClosedTree, - slotInClosedTree, - }); -} - -function dispatch( - targetImpl: EventTarget, - eventImpl: Event, - targetOverride?: EventTarget, -): boolean { - let clearTargets = false; - let activationTarget: EventTarget | null = 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: EventTarget[] = []; - - appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false, - ); - - const isActivationEvent = eventImpl.type === "click"; - - 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); - - if ( - isNode(parent) && - isShadowInclusiveAncestor(getRoot(targetImpl), parent) - ) { - 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); - } - } - - for (let i = 0; i < path.length; i++) { - const tuple = path[i]; - - if (tuple.target !== null) { - setEventPhase(eventImpl, Event.AT_TARGET); - } else { - setEventPhase(eventImpl, Event.BUBBLING_PHASE); - } - - if ( - (eventImpl.eventPhase === Event.BUBBLING_PHASE && eventImpl.bubbles) || - eventImpl.eventPhase === Event.AT_TARGET - ) { - invokeEventListeners(tuple, eventImpl); - } - } - } - - setEventPhase(eventImpl, Event.NONE); - setCurrentTarget(eventImpl, null); - setPath(eventImpl, []); - setDispatched(eventImpl, false); - eventImpl.cancelBubble = false; - setStopImmediatePropagation(eventImpl, false); - - if (clearTargets) { - setTarget(eventImpl, null); - setRelatedTarget(eventImpl, null); - } - - // TODO: 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: Event, - targetListeners: Record<string, Listener[]>, -): boolean { - let found = false; - - const { type } = eventImpl; - - if (!targetListeners || !targetListeners[type]) { - return found; - } - - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = targetListeners[type].slice(); - - 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; - } 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 (!targetListeners[type].includes(listener)) { - continue; - } - - found = true; - - if ( - (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || - (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) - ) { - continue; - } - - if (once) { - targetListeners[type].splice(targetListeners[type].indexOf(listener), 1); - } - - if (passive) { - setInPassiveListener(eventImpl, true); - } - - if (typeof listener.callback === "object") { - if (typeof listener.callback.handleEvent === "function") { - listener.callback.handleEvent(eventImpl); - } - } else { - listener.callback.call(eventImpl.currentTarget, eventImpl); - } - - setInPassiveListener(eventImpl, false); - - if (getStopImmediatePropagation(eventImpl)) { - return found; - } - } - - 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: EventPath, eventImpl: Event): void { - const path = getPath(eventImpl); - const tupleIndex = path.indexOf(tuple); - for (let i = tupleIndex; i >= 0; i--) { - const t = path[i]; - if (t.target) { - setTarget(eventImpl, t.target); - break; - } - } - - setRelatedTarget(eventImpl, tuple.relatedTarget); - - if (eventImpl.cancelBubble) { - return; - } - - setCurrentTarget(eventImpl, tuple.item); - - innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); -} - -function normalizeAddEventHandlerOptions( - options: boolean | AddEventListenerOptions | undefined, -): AddEventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - once: false, - passive: false, - }; - } else { - return options; - } -} - -function normalizeEventHandlerOptions( - options: boolean | EventListenerOptions | undefined, -): EventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - return { - capture: Boolean(options), - }; - } else { - return options; - } -} - -/** Retarget the target following the spec logic. - * - * Ref: https://dom.spec.whatwg.org/#retarget */ -function retarget(a: EventTarget | null, b: EventTarget): EventTarget | null { - while (true) { - if (!isNode(a)) { - return a; - } - - const aRoot = a.getRootNode(); - - if (aRoot) { - if ( - !isShadowRoot(aRoot) || - (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) - ) { - return a; - } - - a = getHost(aRoot); - } - } -} - -// Non-public state information for an event target that needs to held onto. -// Some of the information should be moved to other entities (like Node, -// ShowRoot, UIElement, etc.). -interface EventTargetData { - assignedSlot: boolean; - hasActivationBehavior: boolean; - host: EventTarget | null; - listeners: Record<string, Listener[]>; - mode: string; -} - -interface Listener { - callback: EventListenerOrEventListenerObject; - options: AddEventListenerOptions; -} - -// Accessors for non-public data - -export const eventTargetData = new WeakMap<EventTarget, EventTargetData>(); - -function getAssignedSlot(target: EventTarget): boolean { - return Boolean(eventTargetData.get(target as EventTarget)?.assignedSlot); -} - -function getHasActivationBehavior(target: EventTarget): boolean { - return Boolean( - eventTargetData.get(target as EventTarget)?.hasActivationBehavior, - ); -} - -function getHost(target: EventTarget): EventTarget | null { - return eventTargetData.get(target as EventTarget)?.host ?? null; -} - -function getListeners(target: EventTarget): Record<string, Listener[]> { - return eventTargetData.get(target as EventTarget)?.listeners ?? {}; -} - -function getMode(target: EventTarget): string | null { - return eventTargetData.get(target as EventTarget)?.mode ?? null; -} - -export function getDefaultTargetData(): Readonly<EventTargetData> { - return { - assignedSlot: false, - hasActivationBehavior: false, - host: null, - listeners: Object.create(null), - mode: "", - }; -} - -export class EventTargetImpl implements EventTarget { - constructor() { - eventTargetData.set(this, getDefaultTargetData()); - } - - public addEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: AddEventListenerOptions | boolean, - ): void { - requiredArguments("EventTarget.addEventListener", arguments.length, 2); - if (callback === null) { - return; - } - - options = normalizeAddEventHandlerOptions(options); - const { listeners } = eventTargetData.get(this ?? globalThis)!; - - if (!(type in listeners)) { - listeners[type] = []; - } - - for (const listener of listeners[type]) { - if ( - ((typeof listener.options === "boolean" && - listener.options === options.capture) || - (typeof listener.options === "object" && - listener.options.capture === options.capture)) && - listener.callback === callback - ) { - return; - } - } - - listeners[type].push({ callback, options }); - } - - public removeEventListener( - type: string, - callback: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean, - ): void { - requiredArguments("EventTarget.removeEventListener", arguments.length, 2); - - const listeners = eventTargetData.get(this ?? globalThis)!.listeners; - if (callback !== null && type in listeners) { - listeners[type] = listeners[type].filter( - (listener) => listener.callback !== callback, - ); - } else if (callback === null || !listeners[type]) { - return; - } - - options = normalizeEventHandlerOptions(options); - - 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 - ) { - listeners[type].splice(i, 1); - break; - } - } - } - - public dispatchEvent(event: Event): boolean { - requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); - const self = this ?? globalThis; - - const listeners = eventTargetData.get(self)!.listeners; - if (!(event.type in listeners)) { - return true; - } - - if (getDispatched(event)) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } - - if (event.eventPhase !== Event.NONE) { - throw new DOMException("Invalid event state.", "InvalidStateError"); - } - - return dispatch(self, event); - } - - get [Symbol.toStringTag](): string { - return "EventTarget"; - } - - protected getParent(_event: Event): EventTarget | null { - return null; - } -} - -defineEnumerableProps(EventTargetImpl, [ - "addEventListener", - "removeEventListener", - "dispatchEvent", -]); |