Skip to content

Commit

Permalink
add EventTarget implementation (denoland#2377)
Browse files Browse the repository at this point in the history
  • Loading branch information
acconrad authored and ry committed May 27, 2019
1 parent 73ac5f8 commit 9fd4096
Show file tree
Hide file tree
Showing 7 changed files with 758 additions and 49 deletions.
1 change: 1 addition & 0 deletions cli/BUILD.gn
Expand Up @@ -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",
Expand Down
54 changes: 34 additions & 20 deletions js/dom_types.ts
Expand Up @@ -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>;
Expand All @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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>(
Expand All @@ -245,7 +263,7 @@ interface AbortSignal extends EventTarget {
): void;
removeEventListener(
type: string,
listener: EventListenerOrEventListenerObject,
listener: EventListener,
options?: boolean | EventListenerOptions
): void;
}
Expand All @@ -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>;
Expand Down
83 changes: 83 additions & 0 deletions js/dom_util.ts
@@ -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;
}
}
}
109 changes: 107 additions & 2 deletions js/event.ts
Expand Up @@ -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;
Expand All @@ -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()
});
Expand All @@ -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");
}
Expand All @@ -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");
}
Expand Down Expand Up @@ -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 });
Expand Down

0 comments on commit 9fd4096

Please sign in to comment.