mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 12:19:12 +00:00
Merge deno_cli_snapshots into deno_cli (#3064)
This commit is contained in:
parent
9049213867
commit
b81e5db17a
148 changed files with 38 additions and 83 deletions
503
cli/js/event_target.ts
Normal file
503
cli/js/event_target.ts
Normal file
|
@ -0,0 +1,503 @@
|
|||
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
|
||||
import * as domTypes from "./dom_types.ts";
|
||||
import { DenoError, ErrorKind } from "./errors.ts";
|
||||
import { hasOwnProperty, requiredArguments } from "./util.ts";
|
||||
import {
|
||||
getRoot,
|
||||
isNode,
|
||||
isShadowRoot,
|
||||
isShadowInclusiveAncestor,
|
||||
isSlotable,
|
||||
retarget
|
||||
} from "./dom_util.ts";
|
||||
import { window } from "./window.ts";
|
||||
|
||||
// https://dom.spec.whatwg.org/#get-the-parent
|
||||
// Note: Nodes, shadow roots, and documents override this algorithm so we set it to null.
|
||||
function getEventTargetParent(
|
||||
_eventTarget: domTypes.EventTarget,
|
||||
_event: domTypes.Event
|
||||
): null {
|
||||
return null;
|
||||
}
|
||||
|
||||
export const eventTargetAssignedSlot: unique symbol = Symbol();
|
||||
export const eventTargetHasActivationBehavior: unique symbol = Symbol();
|
||||
|
||||
export class EventTarget implements domTypes.EventTarget {
|
||||
public [domTypes.eventTargetHost]: domTypes.EventTarget | null = null;
|
||||
public [domTypes.eventTargetListeners]: {
|
||||
[type in string]: domTypes.EventListener[]
|
||||
} = {};
|
||||
public [domTypes.eventTargetMode] = "";
|
||||
public [domTypes.eventTargetNodeType]: domTypes.NodeType =
|
||||
domTypes.NodeType.DOCUMENT_FRAGMENT_NODE;
|
||||
private [eventTargetAssignedSlot] = false;
|
||||
private [eventTargetHasActivationBehavior] = false;
|
||||
|
||||
public addEventListener(
|
||||
type: string,
|
||||
callback: (event: domTypes.Event) => void | null,
|
||||
options?: domTypes.AddEventListenerOptions | boolean
|
||||
): void {
|
||||
const this_ = this || window;
|
||||
|
||||
requiredArguments("EventTarget.addEventListener", arguments.length, 2);
|
||||
const normalizedOptions: domTypes.AddEventListenerOptions = eventTargetHelpers.normalizeAddEventHandlerOptions(
|
||||
options
|
||||
);
|
||||
|
||||
if (callback === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const listeners = this_[domTypes.eventTargetListeners];
|
||||
|
||||
if (!hasOwnProperty(listeners, type)) {
|
||||
listeners[type] = [];
|
||||
}
|
||||
|
||||
for (let i = 0; i < listeners[type].length; ++i) {
|
||||
const listener = listeners[type][i];
|
||||
if (
|
||||
((typeof listener.options === "boolean" &&
|
||||
listener.options === normalizedOptions.capture) ||
|
||||
(typeof listener.options === "object" &&
|
||||
listener.options.capture === normalizedOptions.capture)) &&
|
||||
listener.callback === callback
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const eventTarget = this;
|
||||
listeners[type].push({
|
||||
callback,
|
||||
options: normalizedOptions,
|
||||
handleEvent(event: domTypes.Event): void {
|
||||
this.callback.call(eventTarget, event);
|
||||
}
|
||||
} as domTypes.EventListener);
|
||||
}
|
||||
|
||||
public removeEventListener(
|
||||
type: string,
|
||||
callback: (event: domTypes.Event) => void | null,
|
||||
options?: domTypes.EventListenerOptions | boolean
|
||||
): void {
|
||||
const this_ = this || window;
|
||||
|
||||
requiredArguments("EventTarget.removeEventListener", arguments.length, 2);
|
||||
const listeners = this_[domTypes.eventTargetListeners];
|
||||
if (hasOwnProperty(listeners, type) && callback !== null) {
|
||||
listeners[type] = listeners[type].filter(
|
||||
(listener): boolean => listener.callback !== callback
|
||||
);
|
||||
}
|
||||
|
||||
const normalizedOptions: domTypes.EventListenerOptions = eventTargetHelpers.normalizeEventHandlerOptions(
|
||||
options
|
||||
);
|
||||
|
||||
if (callback === null) {
|
||||
// Optimization, not in the spec.
|
||||
return;
|
||||
}
|
||||
|
||||
if (!listeners[type]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0; i < listeners[type].length; ++i) {
|
||||
const listener = listeners[type][i];
|
||||
|
||||
if (
|
||||
((typeof listener.options === "boolean" &&
|
||||
listener.options === normalizedOptions.capture) ||
|
||||
(typeof listener.options === "object" &&
|
||||
listener.options.capture === normalizedOptions.capture)) &&
|
||||
listener.callback === callback
|
||||
) {
|
||||
listeners[type].splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispatchEvent(event: domTypes.Event): boolean {
|
||||
const this_ = this || window;
|
||||
|
||||
requiredArguments("EventTarget.dispatchEvent", arguments.length, 1);
|
||||
const listeners = this_[domTypes.eventTargetListeners];
|
||||
if (!hasOwnProperty(listeners, event.type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event.dispatched || !event.initialized) {
|
||||
throw new DenoError(
|
||||
ErrorKind.InvalidData,
|
||||
"Tried to dispatch an uninitialized event"
|
||||
);
|
||||
}
|
||||
|
||||
if (event.eventPhase !== domTypes.EventPhase.NONE) {
|
||||
throw new DenoError(
|
||||
ErrorKind.InvalidData,
|
||||
"Tried to dispatch a dispatching event"
|
||||
);
|
||||
}
|
||||
|
||||
return eventTargetHelpers.dispatch(this_, event);
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag](): string {
|
||||
return "EventTarget";
|
||||
}
|
||||
}
|
||||
|
||||
const eventTargetHelpers = {
|
||||
// https://dom.spec.whatwg.org/#concept-event-dispatch
|
||||
dispatch(
|
||||
targetImpl: EventTarget,
|
||||
eventImpl: domTypes.Event,
|
||||
targetOverride?: domTypes.EventTarget
|
||||
): boolean {
|
||||
let clearTargets = false;
|
||||
let activationTarget = null;
|
||||
|
||||
eventImpl.dispatched = true;
|
||||
|
||||
targetOverride = targetOverride || targetImpl;
|
||||
let relatedTarget = retarget(eventImpl.relatedTarget, targetImpl);
|
||||
|
||||
if (
|
||||
targetImpl !== relatedTarget ||
|
||||
targetImpl === eventImpl.relatedTarget
|
||||
) {
|
||||
const touchTargets: domTypes.EventTarget[] = [];
|
||||
|
||||
eventTargetHelpers.appendToEventPath(
|
||||
eventImpl,
|
||||
targetImpl,
|
||||
targetOverride,
|
||||
relatedTarget,
|
||||
touchTargets,
|
||||
false
|
||||
);
|
||||
|
||||
const isActivationEvent = eventImpl.type === "click";
|
||||
|
||||
if (isActivationEvent && targetImpl[eventTargetHasActivationBehavior]) {
|
||||
activationTarget = targetImpl;
|
||||
}
|
||||
|
||||
let slotInClosedTree = false;
|
||||
let slotable =
|
||||
isSlotable(targetImpl) && targetImpl[eventTargetAssignedSlot]
|
||||
? targetImpl
|
||||
: null;
|
||||
let parent = getEventTargetParent(targetImpl, eventImpl);
|
||||
|
||||
// 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 &&
|
||||
parentRoot[domTypes.eventTargetMode] === "closed"
|
||||
) {
|
||||
slotInClosedTree = true;
|
||||
}
|
||||
}
|
||||
|
||||
relatedTarget = retarget(eventImpl.relatedTarget, parent);
|
||||
|
||||
if (
|
||||
isNode(parent) &&
|
||||
isShadowInclusiveAncestor(getRoot(targetImpl), parent)
|
||||
) {
|
||||
eventTargetHelpers.appendToEventPath(
|
||||
eventImpl,
|
||||
parent,
|
||||
null,
|
||||
relatedTarget,
|
||||
touchTargets,
|
||||
slotInClosedTree
|
||||
);
|
||||
} else if (parent === relatedTarget) {
|
||||
parent = null;
|
||||
} else {
|
||||
targetImpl = parent;
|
||||
|
||||
if (
|
||||
isActivationEvent &&
|
||||
activationTarget === null &&
|
||||
targetImpl[eventTargetHasActivationBehavior]
|
||||
) {
|
||||
activationTarget = targetImpl;
|
||||
}
|
||||
|
||||
eventTargetHelpers.appendToEventPath(
|
||||
eventImpl,
|
||||
parent,
|
||||
targetImpl,
|
||||
relatedTarget,
|
||||
touchTargets,
|
||||
slotInClosedTree
|
||||
);
|
||||
}
|
||||
|
||||
if (parent !== null) {
|
||||
parent = getEventTargetParent(parent, eventImpl);
|
||||
}
|
||||
|
||||
slotInClosedTree = false;
|
||||
}
|
||||
|
||||
let clearTargetsTupleIndex = -1;
|
||||
for (
|
||||
let i = eventImpl.path.length - 1;
|
||||
i >= 0 && clearTargetsTupleIndex === -1;
|
||||
i--
|
||||
) {
|
||||
if (eventImpl.path[i].target !== null) {
|
||||
clearTargetsTupleIndex = i;
|
||||
}
|
||||
}
|
||||
const clearTargetsTuple = eventImpl.path[clearTargetsTupleIndex];
|
||||
|
||||
clearTargets =
|
||||
(isNode(clearTargetsTuple.target) &&
|
||||
isShadowRoot(getRoot(clearTargetsTuple.target))) ||
|
||||
(isNode(clearTargetsTuple.relatedTarget) &&
|
||||
isShadowRoot(getRoot(clearTargetsTuple.relatedTarget)));
|
||||
|
||||
eventImpl.eventPhase = domTypes.EventPhase.CAPTURING_PHASE;
|
||||
|
||||
for (let i = eventImpl.path.length - 1; i >= 0; --i) {
|
||||
const tuple = eventImpl.path[i];
|
||||
|
||||
if (tuple.target === null) {
|
||||
eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < eventImpl.path.length; i++) {
|
||||
const tuple = eventImpl.path[i];
|
||||
|
||||
if (tuple.target !== null) {
|
||||
eventImpl.eventPhase = domTypes.EventPhase.AT_TARGET;
|
||||
} else {
|
||||
eventImpl.eventPhase = domTypes.EventPhase.BUBBLING_PHASE;
|
||||
}
|
||||
|
||||
if (
|
||||
(eventImpl.eventPhase === domTypes.EventPhase.BUBBLING_PHASE &&
|
||||
eventImpl.bubbles) ||
|
||||
eventImpl.eventPhase === domTypes.EventPhase.AT_TARGET
|
||||
) {
|
||||
eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
eventImpl.eventPhase = domTypes.EventPhase.NONE;
|
||||
|
||||
eventImpl.currentTarget = null;
|
||||
eventImpl.path = [];
|
||||
eventImpl.dispatched = false;
|
||||
eventImpl.cancelBubble = false;
|
||||
eventImpl.cancelBubbleImmediately = false;
|
||||
|
||||
if (clearTargets) {
|
||||
eventImpl.target = null;
|
||||
eventImpl.relatedTarget = null;
|
||||
}
|
||||
|
||||
// TODO: invoke activation targets if HTML nodes will be implemented
|
||||
// if (activationTarget !== null) {
|
||||
// if (!eventImpl.defaultPrevented) {
|
||||
// activationTarget._activationBehavior();
|
||||
// }
|
||||
// }
|
||||
|
||||
return !eventImpl.defaultPrevented;
|
||||
},
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-event-listener-invoke
|
||||
invokeEventListeners(
|
||||
targetImpl: EventTarget,
|
||||
tuple: domTypes.EventPath,
|
||||
eventImpl: domTypes.Event
|
||||
): void {
|
||||
const tupleIndex = eventImpl.path.indexOf(tuple);
|
||||
for (let i = tupleIndex; i >= 0; i--) {
|
||||
const t = eventImpl.path[i];
|
||||
if (t.target) {
|
||||
eventImpl.target = t.target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
eventImpl.relatedTarget = tuple.relatedTarget;
|
||||
|
||||
if (eventImpl.cancelBubble) {
|
||||
return;
|
||||
}
|
||||
|
||||
eventImpl.currentTarget = tuple.item;
|
||||
|
||||
eventTargetHelpers.innerInvokeEventListeners(
|
||||
targetImpl,
|
||||
eventImpl,
|
||||
tuple.item[domTypes.eventTargetListeners]
|
||||
);
|
||||
},
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
|
||||
innerInvokeEventListeners(
|
||||
targetImpl: EventTarget,
|
||||
eventImpl: domTypes.Event,
|
||||
targetListeners: { [type in string]: domTypes.EventListener[] }
|
||||
): 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 === domTypes.EventPhase.CAPTURING_PHASE &&
|
||||
!capture) ||
|
||||
(eventImpl.eventPhase === domTypes.EventPhase.BUBBLING_PHASE && capture)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (once) {
|
||||
targetListeners[type].splice(
|
||||
targetListeners[type].indexOf(listener),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
if (passive) {
|
||||
eventImpl.inPassiveListener = true;
|
||||
}
|
||||
|
||||
try {
|
||||
if (listener.callback) {
|
||||
listener.handleEvent(eventImpl);
|
||||
}
|
||||
} catch (error) {
|
||||
throw new DenoError(ErrorKind.Interrupted, error.message);
|
||||
}
|
||||
|
||||
eventImpl.inPassiveListener = false;
|
||||
|
||||
if (eventImpl.cancelBubbleImmediately) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
},
|
||||
|
||||
normalizeAddEventHandlerOptions(
|
||||
options: boolean | domTypes.AddEventListenerOptions | undefined
|
||||
): domTypes.AddEventListenerOptions {
|
||||
if (typeof options === "boolean" || typeof options === "undefined") {
|
||||
const returnValue: domTypes.AddEventListenerOptions = {
|
||||
capture: Boolean(options),
|
||||
once: false,
|
||||
passive: false
|
||||
};
|
||||
|
||||
return returnValue;
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
},
|
||||
|
||||
normalizeEventHandlerOptions(
|
||||
options: boolean | domTypes.EventListenerOptions | undefined
|
||||
): domTypes.EventListenerOptions {
|
||||
if (typeof options === "boolean" || typeof options === "undefined") {
|
||||
const returnValue: domTypes.EventListenerOptions = {
|
||||
capture: Boolean(options)
|
||||
};
|
||||
|
||||
return returnValue;
|
||||
} else {
|
||||
return options;
|
||||
}
|
||||
},
|
||||
|
||||
// https://dom.spec.whatwg.org/#concept-event-path-append
|
||||
appendToEventPath(
|
||||
eventImpl: domTypes.Event,
|
||||
target: domTypes.EventTarget,
|
||||
targetOverride: domTypes.EventTarget | null,
|
||||
relatedTarget: domTypes.EventTarget | null,
|
||||
touchTargets: domTypes.EventTarget[],
|
||||
slotInClosedTree: boolean
|
||||
): void {
|
||||
const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target));
|
||||
const rootOfClosedTree =
|
||||
isShadowRoot(target) && target[domTypes.eventTargetMode] === "closed";
|
||||
|
||||
eventImpl.path.push({
|
||||
item: target,
|
||||
itemInShadowTree,
|
||||
target: targetOverride,
|
||||
relatedTarget,
|
||||
touchTargetList: touchTargets,
|
||||
rootOfClosedTree,
|
||||
slotInClosedTree
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** Built-in objects providing `get` methods for our
|
||||
* interceptable JavaScript operations.
|
||||
*/
|
||||
Reflect.defineProperty(EventTarget.prototype, "addEventListener", {
|
||||
enumerable: true
|
||||
});
|
||||
Reflect.defineProperty(EventTarget.prototype, "removeEventListener", {
|
||||
enumerable: true
|
||||
});
|
||||
Reflect.defineProperty(EventTarget.prototype, "dispatchEvent", {
|
||||
enumerable: true
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue