1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-31 11:34:15 -05:00

add EventTarget implementation (#2377)

This commit is contained in:
Adam Conrad 2019-05-27 13:20:34 +00:00 committed by Ryan Dahl
parent 73ac5f89f0
commit 9fd4096235
7 changed files with 759 additions and 50 deletions

View file

@ -78,6 +78,7 @@ ts_sources = [
"../js/dispatch.ts",
"../js/dispatch_minimal.ts",
"../js/dom_types.ts",
"../js/dom_util.ts",
"../js/errors.ts",
"../js/event.ts",
"../js/event_target.ts",

View file

@ -41,9 +41,6 @@ type ReferrerPolicy =
| "unsafe-url";
export type BlobPart = BufferSource | Blob | string;
export type FormDataEntryValue = DomFile | string;
export type EventListenerOrEventListenerObject =
| EventListener
| EventListenerObject;
export interface DomIterable<K, V> {
keys(): IterableIterator<K>;
@ -67,16 +64,27 @@ interface AbortSignalEventMap {
abort: ProgressEvent;
}
// https://dom.spec.whatwg.org/#node
export enum NodeType {
ELEMENT_NODE = 1,
TEXT_NODE = 3,
DOCUMENT_FRAGMENT_NODE = 11
}
export interface EventTarget {
host: EventTarget | null;
listeners: { [type in string]: EventListener[] };
mode: string;
nodeType: NodeType;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject | null,
callback: (event: Event) => void | null,
options?: boolean | AddEventListenerOptions
): void;
dispatchEvent(evt: Event): boolean;
dispatchEvent(event: Event): boolean;
removeEventListener(
type: string,
listener?: EventListenerOrEventListenerObject | null,
callback?: (event: Event) => void | null,
options?: EventListenerOptions | boolean
): void;
}
@ -135,7 +143,9 @@ export interface URLSearchParams {
}
export interface EventListener {
(evt: Event): void;
handleEvent(event: Event): void;
readonly callback: (event: Event) => void | null;
readonly options: boolean | AddEventListenerOptions;
}
export interface EventInit {
@ -167,11 +177,11 @@ export interface EventPath {
export interface Event {
readonly type: string;
readonly target: EventTarget | null;
readonly currentTarget: EventTarget | null;
target: EventTarget | null;
currentTarget: EventTarget | null;
composedPath(): EventPath[];
readonly eventPhase: number;
eventPhase: number;
stopPropagation(): void;
stopImmediatePropagation(): void;
@ -182,8 +192,16 @@ export interface Event {
readonly defaultPrevented: boolean;
readonly composed: boolean;
readonly isTrusted: boolean;
isTrusted: boolean;
readonly timeStamp: Date;
dispatched: boolean;
readonly initialized: boolean;
inPassiveListener: boolean;
cancelBubble: boolean;
cancelBubbleImmediately: boolean;
path: EventPath[];
relatedTarget: EventTarget | null;
}
export interface CustomEvent extends Event {
@ -217,12 +235,12 @@ interface ProgressEvent extends Event {
}
export interface EventListenerOptions {
capture?: boolean;
capture: boolean;
}
export interface AddEventListenerOptions extends EventListenerOptions {
once?: boolean;
passive?: boolean;
once: boolean;
passive: boolean;
}
interface AbortSignal extends EventTarget {
@ -235,7 +253,7 @@ interface AbortSignal extends EventTarget {
): void;
addEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
listener: EventListener,
options?: boolean | AddEventListenerOptions
): void;
removeEventListener<K extends keyof AbortSignalEventMap>(
@ -245,7 +263,7 @@ interface AbortSignal extends EventTarget {
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
listener: EventListener,
options?: boolean | EventListenerOptions
): void;
}
@ -257,10 +275,6 @@ export interface ReadableStream {
tee(): [ReadableStream, ReadableStream];
}
export interface EventListenerObject {
handleEvent(evt: Event): void;
}
export interface ReadableStreamReader {
cancel(): Promise<void>;
read(): Promise<any>;

83
js/dom_util.ts Normal file
View file

@ -0,0 +1,83 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Utility functions for DOM nodes
import * as domTypes from "./dom_types";
export function isNode(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean(nodeImpl && "nodeType" in nodeImpl);
}
export function isShadowRoot(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean(
nodeImpl &&
nodeImpl.nodeType === domTypes.NodeType.DOCUMENT_FRAGMENT_NODE &&
"host" in nodeImpl
);
}
export function isSlotable(nodeImpl: domTypes.EventTarget | null): boolean {
return Boolean(
nodeImpl &&
(nodeImpl.nodeType === domTypes.NodeType.ELEMENT_NODE ||
nodeImpl.nodeType === domTypes.NodeType.TEXT_NODE)
);
}
// https://dom.spec.whatwg.org/#node-trees
// const domSymbolTree = Symbol("DOM Symbol Tree");
// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor
export function isShadowInclusiveAncestor(
ancestor: domTypes.EventTarget | null,
node: domTypes.EventTarget | null
): boolean {
while (isNode(node)) {
if (node === ancestor) {
return true;
}
if (isShadowRoot(node)) {
node = node && node.host;
} else {
node = null; // domSymbolTree.parent(node);
}
}
return false;
}
export function getRoot(
node: domTypes.EventTarget | null
): domTypes.EventTarget | null {
let root = node;
// for (const ancestor of domSymbolTree.ancestorsIterator(node)) {
// root = ancestor;
// }
return root;
}
// https://dom.spec.whatwg.org/#retarget
export function retarget(
a: domTypes.EventTarget | null,
b: domTypes.EventTarget
): domTypes.EventTarget | null {
while (true) {
if (!isNode(a)) {
return a;
}
const aRoot = getRoot(a);
if (aRoot) {
if (
!isShadowRoot(aRoot) ||
(isNode(b) && isShadowInclusiveAncestor(aRoot, b))
) {
return a;
}
a = aRoot.host;
}
}
}

View file

@ -21,7 +21,7 @@ export class EventInit implements domTypes.EventInit {
export class Event implements domTypes.Event {
// Each event has the following associated flags
private _canceledFlag = false;
private dispatchedFlag = false;
private _dispatchedFlag = false;
private _initializedFlag = false;
private _inPassiveListenerFlag = false;
private _stopImmediatePropagationFlag = false;
@ -42,6 +42,7 @@ export class Event implements domTypes.Event {
currentTarget: null,
eventPhase: domTypes.EventPhase.NONE,
isTrusted: false,
relatedTarget: null,
target: null,
timeStamp: Date.now()
});
@ -55,10 +56,18 @@ export class Event implements domTypes.Event {
return this._stopPropagationFlag;
}
set cancelBubble(value: boolean) {
this._stopPropagationFlag = value;
}
get cancelBubbleImmediately(): boolean {
return this._stopImmediatePropagationFlag;
}
set cancelBubbleImmediately(value: boolean) {
this._stopImmediatePropagationFlag = value;
}
get cancelable(): boolean {
return getPrivateValue(this, eventAttributes, "cancelable");
}
@ -71,30 +80,125 @@ export class Event implements domTypes.Event {
return getPrivateValue(this, eventAttributes, "currentTarget");
}
set currentTarget(value: domTypes.EventTarget) {
eventAttributes.set(this, {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: value,
eventPhase: this.eventPhase,
isTrusted: this.isTrusted,
relatedTarget: this.relatedTarget,
target: this.target,
timeStamp: this.timeStamp
});
}
get defaultPrevented(): boolean {
return this._canceledFlag;
}
get dispatched(): boolean {
return this.dispatchedFlag;
return this._dispatchedFlag;
}
set dispatched(value: boolean) {
this._dispatchedFlag = value;
}
get eventPhase(): number {
return getPrivateValue(this, eventAttributes, "eventPhase");
}
set eventPhase(value: number) {
eventAttributes.set(this, {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: value,
isTrusted: this.isTrusted,
relatedTarget: this.relatedTarget,
target: this.target,
timeStamp: this.timeStamp
});
}
get initialized(): boolean {
return this._initializedFlag;
}
set inPassiveListener(value: boolean) {
this._inPassiveListenerFlag = value;
}
get isTrusted(): boolean {
return getPrivateValue(this, eventAttributes, "isTrusted");
}
set isTrusted(value: boolean) {
eventAttributes.set(this, {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: this.eventPhase,
isTrusted: value,
relatedTarget: this.relatedTarget,
target: this.target,
timeStamp: this.timeStamp
});
}
get path(): domTypes.EventPath[] {
return this._path;
}
set path(value: domTypes.EventPath[]) {
this._path = value;
}
get relatedTarget(): domTypes.EventTarget {
return getPrivateValue(this, eventAttributes, "relatedTarget");
}
set relatedTarget(value: domTypes.EventTarget) {
eventAttributes.set(this, {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: this.eventPhase,
isTrusted: this.isTrusted,
relatedTarget: value,
target: this.target,
timeStamp: this.timeStamp
});
}
get target(): domTypes.EventTarget {
return getPrivateValue(this, eventAttributes, "target");
}
set target(value: domTypes.EventTarget) {
eventAttributes.set(this, {
type: this.type,
bubbles: this.bubbles,
cancelable: this.cancelable,
composed: this.composed,
currentTarget: this.currentTarget,
eventPhase: this.eventPhase,
isTrusted: this.isTrusted,
relatedTarget: this.relatedTarget,
target: value,
timeStamp: this.timeStamp
});
}
get timeStamp(): Date {
return getPrivateValue(this, eventAttributes, "timeStamp");
}
@ -257,6 +361,7 @@ Reflect.defineProperty(Event.prototype, "currentTarget", { enumerable: true });
Reflect.defineProperty(Event.prototype, "defaultPrevented", {
enumerable: true
});
Reflect.defineProperty(Event.prototype, "dispatched", { enumerable: true });
Reflect.defineProperty(Event.prototype, "eventPhase", { enumerable: true });
Reflect.defineProperty(Event.prototype, "isTrusted", { enumerable: true });
Reflect.defineProperty(Event.prototype, "target", { enumerable: true });

View file

@ -1,40 +1,184 @@
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
import * as domTypes from "./dom_types";
import { requiredArguments, hasOwnProperty } from "./util";
import { DenoError, ErrorKind } from "./errors";
import { hasOwnProperty, requiredArguments } from "./util";
import {
getRoot,
isNode,
isShadowRoot,
isShadowInclusiveAncestor,
isSlotable,
retarget
} from "./dom_util";
// 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 class EventListenerOptions implements domTypes.EventListenerOptions {
_capture = false;
constructor({ capture = false } = {}) {
this._capture = capture;
}
get capture(): boolean {
return this._capture;
}
}
export class AddEventListenerOptions extends EventListenerOptions
implements domTypes.AddEventListenerOptions {
_passive = false;
_once = false;
constructor({ capture = false, passive = false, once = false } = {}) {
super({ capture });
this._passive = passive;
this._once = once;
}
get passive(): boolean {
return this._passive;
}
get once(): boolean {
return this._once;
}
}
export class EventListener implements domTypes.EventListener {
allEvents: domTypes.Event[] = [];
atEvents: domTypes.Event[] = [];
bubbledEvents: domTypes.Event[] = [];
capturedEvents: domTypes.Event[] = [];
private _callback: (event: domTypes.Event) => void | null;
private _options: boolean | domTypes.AddEventListenerOptions = false;
constructor(
callback: (event: domTypes.Event) => void | null,
options: boolean | domTypes.AddEventListenerOptions
) {
this._callback = callback;
this._options = options;
}
public handleEvent(event: domTypes.Event): void {
this.allEvents.push(event);
switch (event.eventPhase) {
case domTypes.EventPhase.CAPTURING_PHASE:
this.capturedEvents.push(event);
break;
case domTypes.EventPhase.AT_TARGET:
this.atEvents.push(event);
break;
case domTypes.EventPhase.BUBBLING_PHASE:
this.bubbledEvents.push(event);
break;
default:
throw new Error("Unspecified event phase");
}
this._callback(event);
}
get callback(): (event: domTypes.Event) => void | null {
return this._callback;
}
get options(): domTypes.AddEventListenerOptions | boolean {
return this._options;
}
}
/* TODO: This is an incomplete implementation to provide functionality
* for Event. A proper spec is still required for a proper Web API.
*/
export class EventTarget implements domTypes.EventTarget {
public listeners: {
[type in string]: domTypes.EventListenerOrEventListenerObject[]
} = {};
public host: domTypes.EventTarget | null = null;
public listeners: { [type in string]: domTypes.EventListener[] } = {};
public mode = "";
public nodeType: domTypes.NodeType = domTypes.NodeType.DOCUMENT_FRAGMENT_NODE;
private _assignedSlot = false;
private _hasActivationBehavior = false;
public addEventListener(
type: string,
listener: domTypes.EventListenerOrEventListenerObject | null,
_options?: boolean | domTypes.AddEventListenerOptions
callback: (event: domTypes.Event) => void | null,
options?: domTypes.AddEventListenerOptions | boolean
): void {
requiredArguments("EventTarget.addEventListener", arguments.length, 2);
const normalizedOptions: domTypes.AddEventListenerOptions = this._normalizeAddEventHandlerOptions(
options
);
if (callback === null) {
return;
}
if (!hasOwnProperty(this.listeners, type)) {
this.listeners[type] = [];
}
if (listener !== null) {
this.listeners[type].push(listener);
for (let i = 0; i < this.listeners[type].length; ++i) {
const listener = this.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;
}
}
this.listeners[type].push(new EventListener(callback, normalizedOptions));
}
public removeEventListener(
type: string,
callback: domTypes.EventListenerOrEventListenerObject | null,
_options?: domTypes.EventListenerOptions | boolean
callback: (event: domTypes.Event) => void | null,
options?: domTypes.EventListenerOptions | boolean
): void {
requiredArguments("EventTarget.removeEventListener", arguments.length, 2);
if (hasOwnProperty(this.listeners, type) && callback !== null) {
this.listeners[type] = this.listeners[type].filter(
(listener): boolean => listener !== callback
(listener): boolean => listener.callback !== callback
);
}
const normalizedOptions: domTypes.EventListenerOptions = this._normalizeEventHandlerOptions(
options
);
if (callback === null) {
// Optimization, not in the spec.
return;
}
if (!this.listeners[type]) {
return;
}
for (let i = 0; i < this.listeners[type].length; ++i) {
const listener = this.listeners[type][i];
if (
((typeof listener.options === "boolean" &&
listener.options === normalizedOptions.capture) ||
(typeof listener.options === "object" &&
listener.options.capture === normalizedOptions.capture)) &&
listener.callback === callback
) {
this.listeners[type].splice(i, 1);
break;
}
}
}
public dispatchEvent(event: domTypes.Event): boolean {
@ -42,19 +186,378 @@ export class EventTarget implements domTypes.EventTarget {
if (!hasOwnProperty(this.listeners, event.type)) {
return true;
}
const stack = this.listeners[event.type].slice();
for (const stackElement of stack) {
if ((stackElement as domTypes.EventListenerObject).handleEvent) {
(stackElement as domTypes.EventListenerObject).handleEvent(event);
} else {
(stackElement as domTypes.EventListener).call(this, event);
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"
);
}
event.isTrusted = false;
return this._dispatch(event);
}
// https://dom.spec.whatwg.org/#concept-event-dispatch
_dispatch(
eventImpl: domTypes.Event,
targetOverride?: domTypes.EventTarget
): boolean {
let targetImpl = this;
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[] = [];
this._appendToEventPath(
eventImpl,
targetImpl,
targetOverride,
relatedTarget,
touchTargets,
false
);
const isActivationEvent = eventImpl.type === "click";
if (isActivationEvent && targetImpl._hasActivationBehavior) {
activationTarget = targetImpl;
}
let slotInClosedTree = false;
let slotable =
isSlotable(targetImpl) && targetImpl._assignedSlot ? 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.mode === "closed"
) {
slotInClosedTree = true;
}
}
relatedTarget = retarget(eventImpl.relatedTarget, parent);
if (
isNode(parent) &&
isShadowInclusiveAncestor(getRoot(targetImpl), parent)
) {
this._appendToEventPath(
eventImpl,
parent,
null,
relatedTarget,
touchTargets,
slotInClosedTree
);
} else if (parent === relatedTarget) {
parent = null;
} else {
targetImpl = parent;
if (
isActivationEvent &&
activationTarget === null &&
targetImpl._hasActivationBehavior
) {
activationTarget = targetImpl;
}
this._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) {
this._invokeEventListeners(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
) {
this._invokeEventListeners(tuple, eventImpl);
}
}
}
return !event.defaultPrevented;
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(
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;
this._innerInvokeEventListeners(eventImpl, tuple.item.listeners);
}
// https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke
_innerInvokeEventListeners(
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 && typeof listener.handleEvent === "function") {
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.mode === "closed";
eventImpl.path.push({
item: target,
itemInShadowTree,
target: targetOverride,
relatedTarget,
touchTargetList: touchTargets,
rootOfClosedTree,
slotInClosedTree
});
}
get [Symbol.toStringTag](): string {
return "EventTarget";
}
}
/** Built-in objects providing `get` methods for our
* interceptable JavaScript operations.
*/
Reflect.defineProperty(EventTarget.prototype, "host", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "listeners", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "mode", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "nodeType", {
enumerable: true,
writable: true
});
Reflect.defineProperty(EventTarget.prototype, "addEventListener", {
enumerable: true
});
Reflect.defineProperty(EventTarget.prototype, "removeEventListener", {
enumerable: true
});
Reflect.defineProperty(EventTarget.prototype, "dispatchEvent", {
enumerable: true
});

View file

@ -14,10 +14,10 @@ test(function constructedEventTargetCanBeUsedAsExpected(): void {
const event = new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
function listener(e): void {
const listener = (e): void => {
assertEquals(e, event);
++callCount;
}
};
target.addEventListener("foo", listener);
@ -47,9 +47,9 @@ test(function anEventTargetCanBeSubclassed(): void {
new Event("foo", { bubbles: true, cancelable: false });
let callCount = 0;
function listener(): void {
const listener = (): void => {
++callCount;
}
};
target.on("foo", listener);
assertEquals(callCount, 0);
@ -70,10 +70,10 @@ test(function constructedEventTargetUseObjectPrototype(): void {
const event = new Event("toString", { bubbles: true, cancelable: false });
let callCount = 0;
function listener(e): void {
const listener = (e): void => {
assertEquals(e, event);
++callCount;
}
};
target.addEventListener("toString", listener);
@ -102,7 +102,8 @@ test(function dispatchEventShouldNotThrowError(): void {
bubbles: true,
cancelable: false
});
target.addEventListener("hasOwnProperty", (): void => {});
const listener = (): void => {};
target.addEventListener("hasOwnProperty", listener);
target.dispatchEvent(event);
} catch {
hasThrown = true;

View file

@ -95,6 +95,8 @@ window.EventInit = event.EventInit;
export type EventInit = event.EventInit;
window.Event = event.Event;
export type Event = event.Event;
window.EventListener = eventTarget.EventListener;
export type EventListener = eventTarget.EventListener;
window.EventTarget = eventTarget.EventTarget;
export type EventTarget = eventTarget.EventTarget;
window.URL = url.URL;