From fc4819e1e0624b9377913932202bc31f5a25bab4 Mon Sep 17 00:00:00 2001 From: Kitson Kelly Date: Sun, 12 Apr 2020 01:42:02 +1000 Subject: refactor: Event and EventTarget implementations (#4707) Refactors Event and EventTarget so that they better encapsulate their non-public data as well as are more forward compatible with things like DOM Nodes. Moves `dom_types.ts` -> `dom_types.d.ts` which was always the intention, it was a legacy of when we used to build the types from the code and the limitations of the compiler. There was a lot of cruft in `dom_types` which shouldn't have been there, and mis-alignment to the DOM standards. This generally has been eliminated, though we still have some minor differences from the DOM (like the removal of some deprecated methods/properties). Adds `DOMException`. Strictly it shouldn't inherit from `Error`, but most browsers provide a stack trace when one is thrown, so the behaviour in Deno actually better matches the browser. `Event` still doesn't log to console like it does in the browser. I wanted to get this raised and that could be an enhancement later on (it currently doesn't either). --- cli/js/globals.ts | 46 +- cli/js/lib.deno.shared_globals.d.ts | 192 ++--- cli/js/lib.deno.window.d.ts | 20 +- cli/js/runtime_main.ts | 13 +- cli/js/tests/dom_exception_test.ts | 9 + cli/js/tests/event_target_test.ts | 12 - cli/js/tests/event_test.ts | 2 - cli/js/tests/unit_tests.ts | 1 + cli/js/web/blob.ts | 2 +- cli/js/web/body.ts | 2 +- cli/js/web/custom_event.ts | 40 +- cli/js/web/dom_exception.ts | 14 + cli/js/web/dom_file.ts | 2 +- cli/js/web/dom_iterable.ts | 16 +- cli/js/web/dom_types.d.ts | 757 +++++++++++++++++ cli/js/web/dom_types.ts | 710 ---------------- cli/js/web/dom_util.ts | 88 +- cli/js/web/event.ts | 351 +++++--- cli/js/web/event_target.ts | 901 ++++++++++++--------- cli/js/web/fetch.ts | 2 +- cli/js/web/form_data.ts | 2 +- cli/js/web/headers.ts | 2 +- cli/js/web/location.ts | 2 +- cli/js/web/request.ts | 2 +- cli/js/web/streams/pipe-to.ts | 2 +- .../web/streams/readable-byte-stream-controller.ts | 2 +- cli/js/web/streams/readable-internals.ts | 2 +- .../streams/readable-stream-default-controller.ts | 5 +- cli/js/web/streams/readable-stream.ts | 2 +- cli/js/web/streams/shared-internals.ts | 2 +- cli/js/web/streams/strategies.ts | 2 +- cli/js/web/streams/transform-internals.ts | 2 +- cli/js/web/streams/transform-stream.ts | 2 +- cli/js/web/streams/writable-internals.ts | 2 +- .../streams/writable-stream-default-controller.ts | 2 +- cli/js/web/streams/writable-stream.ts | 2 +- cli/js/web/text_encoding.ts | 4 +- cli/js/web/url.ts | 2 +- cli/js/web/url_search_params.ts | 2 +- cli/js/web/util.ts | 32 +- cli/js/web/workers.ts | 4 +- 41 files changed, 1672 insertions(+), 1587 deletions(-) create mode 100644 cli/js/tests/dom_exception_test.ts create mode 100644 cli/js/web/dom_exception.ts create mode 100644 cli/js/web/dom_types.d.ts delete mode 100644 cli/js/web/dom_types.ts (limited to 'cli') diff --git a/cli/js/globals.ts b/cli/js/globals.ts index 0aed3a252..059a70ee7 100644 --- a/cli/js/globals.ts +++ b/cli/js/globals.ts @@ -1,10 +1,12 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import "./lib.deno.shared_globals.d.ts"; + import * as blob from "./web/blob.ts"; import * as consoleTypes from "./web/console.ts"; import * as promiseTypes from "./web/promise.ts"; import * as customEvent from "./web/custom_event.ts"; -import * as domTypes from "./web/dom_types.ts"; +import * as domException from "./web/dom_exception.ts"; import * as domFile from "./web/dom_file.ts"; import * as event from "./web/event.ts"; import * as eventTarget from "./web/event_target.ts"; @@ -123,21 +125,13 @@ declare global { // Only `var` variables show up in the `globalThis` type when doing a global // scope augmentation. /* eslint-disable no-var */ - var addEventListener: ( - type: string, - callback: domTypes.EventListenerOrEventListenerObject | null, - options?: boolean | domTypes.AddEventListenerOptions | undefined - ) => void; - var queueMicrotask: (callback: () => void) => void; - var console: consoleTypes.Console; - var location: domTypes.Location; // Assigned to `window` global - main runtime var Deno: { core: DenoCore; }; - var onload: ((e: domTypes.Event) => void) | undefined; - var onunload: ((e: domTypes.Event) => void) | undefined; + var onload: ((e: Event) => void) | undefined; + var onunload: ((e: Event) => void) | undefined; var bootstrapMainRuntime: (() => void) | undefined; // Assigned to `self` global - worker runtime and compiler @@ -150,7 +144,7 @@ declare global { source: string, lineno: number, colno: number, - e: domTypes.Event + e: Event ) => boolean | void) | undefined; @@ -163,9 +157,6 @@ declare global { // Assigned to `self` global - compiler var bootstrapTsCompilerRuntime: (() => void) | undefined; var bootstrapWasmCompilerRuntime: (() => void) | undefined; - - var performance: performanceUtil.Performance; - var setTimeout: typeof timers.setTimeout; /* eslint-enable */ } @@ -218,9 +209,10 @@ export const windowOrWorkerGlobalScopeProperties = { console: writable(new consoleTypes.Console(core.print)), Blob: nonEnumerable(blob.DenoBlob), File: nonEnumerable(domFile.DomFileImpl), - CustomEvent: nonEnumerable(customEvent.CustomEvent), - Event: nonEnumerable(event.Event), - EventTarget: nonEnumerable(eventTarget.EventTarget), + CustomEvent: nonEnumerable(customEvent.CustomEventImpl), + DOMException: nonEnumerable(domException.DOMExceptionImpl), + Event: nonEnumerable(event.EventImpl), + EventTarget: nonEnumerable(eventTarget.EventTargetImpl), URL: nonEnumerable(url.URL), URLSearchParams: nonEnumerable(urlSearchParams.URLSearchParams), Headers: nonEnumerable(headers.Headers), @@ -234,19 +226,17 @@ export const windowOrWorkerGlobalScopeProperties = { Worker: nonEnumerable(workers.WorkerImpl), }; -export const eventTargetProperties = { - [domTypes.eventTargetHost]: nonEnumerable(null), - [domTypes.eventTargetListeners]: nonEnumerable({}), - [domTypes.eventTargetMode]: nonEnumerable(""), - [domTypes.eventTargetNodeType]: nonEnumerable(0), - [eventTarget.eventTargetAssignedSlot]: nonEnumerable(false), - [eventTarget.eventTargetHasActivationBehavior]: nonEnumerable(false), +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function setEventTargetData(value: any): void { + eventTarget.eventTargetData.set(value, eventTarget.getDefaultTargetData()); +} +export const eventTargetProperties = { addEventListener: readOnly( - eventTarget.EventTarget.prototype.addEventListener + eventTarget.EventTargetImpl.prototype.addEventListener ), - dispatchEvent: readOnly(eventTarget.EventTarget.prototype.dispatchEvent), + dispatchEvent: readOnly(eventTarget.EventTargetImpl.prototype.dispatchEvent), removeEventListener: readOnly( - eventTarget.EventTarget.prototype.removeEventListener + eventTarget.EventTargetImpl.prototype.removeEventListener ), }; diff --git a/cli/js/lib.deno.shared_globals.d.ts b/cli/js/lib.deno.shared_globals.d.ts index 2027686a9..a1e834cc4 100644 --- a/cli/js/lib.deno.shared_globals.d.ts +++ b/cli/js/lib.deno.shared_globals.d.ts @@ -1,12 +1,8 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, no-var */ /// -// TODO: we need to remove this, but Fetch::Response::Body implements Reader -// which requires Deno.EOF, and we shouldn't be leaking that, but https_proxy -// at the least requires the Reader interface on Body, which it shouldn't -/// /// // This follows the WebIDL at: https://webassembly.github.io/spec/js-api/ @@ -184,6 +180,7 @@ declare function setTimeout( delay?: number, ...args: unknown[] ): number; + /** Repeatedly calls a function , with a fixed time delay between each call. */ declare function setInterval( cb: (...args: unknown[]) => void, @@ -194,8 +191,8 @@ declare function clearTimeout(id?: number): void; declare function clearInterval(id?: number): void; declare function queueMicrotask(func: Function): void; -declare const console: Console; -declare const location: Location; +declare var console: Console; +declare var location: Location; declare function addEventListener( type: string, @@ -315,6 +312,12 @@ interface DOMStringList { [index: number]: string; } +declare class DOMException extends Error { + constructor(message?: string, name?: string); + readonly name: string; + readonly message: string; +} + /** The location (URL) of the object it is linked to. Changes done on it are * reflected on the object it relates to. Both the Document and Window * interface have such a linked Location, accessible via Document.location and @@ -1060,122 +1063,81 @@ declare namespace performance { export function now(): number; } +interface EventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; +} + /** An event which takes place in the DOM. */ -interface Event { - /** - * Returns true or false depending on how event was initialized. True if +declare class Event { + constructor(type: string, eventInitDict?: EventInit); + /** Returns true or false depending on how event was initialized. True if * event goes through its target's ancestors in reverse tree order, and - * false otherwise. - */ + * false otherwise. */ readonly bubbles: boolean; - - // TODO(ry) Remove cancelBubbleImmediately - non-standard extension. - cancelBubbleImmediately: boolean; - cancelBubble: boolean; - /** - * Returns true or false depending on how event was initialized. Its return - * value does not always carry meaning, but true can indicate that part of - * the operation during which event was dispatched, can be canceled by - * invoking the preventDefault() method. - */ + /** Returns true or false depending on how event was initialized. Its return + * value does not always carry meaning, but true can indicate that part of the + * operation during which event was dispatched, can be canceled by invoking + * the preventDefault() method. */ readonly cancelable: boolean; - /** - * Returns true or false depending on how event was initialized. True if + /** Returns true or false depending on how event was initialized. True if * event invokes listeners past a ShadowRoot node that is the root of its - * target, and false otherwise. - */ + * target, and false otherwise. */ readonly composed: boolean; - /** - * Returns the object whose event listener's callback is currently being - * invoked. - */ + /** Returns the object whose event listener's callback is currently being + * invoked. */ readonly currentTarget: EventTarget | null; - /** - * Returns true if preventDefault() was invoked successfully to indicate - * cancelation, and false otherwise. - */ + /** Returns true if preventDefault() was invoked successfully to indicate + * cancellation, and false otherwise. */ readonly defaultPrevented: boolean; - /** - * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, - * AT_TARGET, and BUBBLING_PHASE. - */ + /** Returns the event's phase, which is one of NONE, CAPTURING_PHASE, + * AT_TARGET, and BUBBLING_PHASE. */ readonly eventPhase: number; - /** - * Returns true if event was dispatched by the user agent, and false - * otherwise. - */ + /** Returns true if event was dispatched by the user agent, and false + * otherwise. */ readonly isTrusted: boolean; - returnValue: boolean; - /** @deprecated */ - readonly srcElement: EventTarget | null; - /** - * Returns the object to which event is dispatched (its target). - */ + /** Returns the object to which event is dispatched (its target). */ readonly target: EventTarget | null; - /** - * Returns the event's timestamp as the number of milliseconds measured - * relative to the time origin. - */ + /** Returns the event's timestamp as the number of milliseconds measured + * relative to the time origin. */ readonly timeStamp: number; - /** - * Returns the type of event, e.g. "click", "hashchange", or "submit". - */ + /** Returns the type of event, e.g. "click", "hashchange", or "submit". */ readonly type: string; - /** - * Returns the invocation target objects of event's path (objects on which + /** Returns the invocation target objects of event's path (objects on which * listeners will be invoked), except for any nodes in shadow trees of which * the shadow root's mode is "closed" that are not reachable from event's - * currentTarget. - */ + * currentTarget. */ composedPath(): EventTarget[]; - initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void; - /** - * If invoked when the cancelable attribute value is true, and while + /** If invoked when the cancelable attribute value is true, and while * executing a listener for the event with passive set to false, signals to * the operation that caused event to be dispatched that it needs to be - * canceled. - */ + * canceled. */ preventDefault(): void; - /** - * Invoking this method prevents event from reaching any registered event - * listeners after the current one finishes running and, when dispatched in - * a tree, also prevents event from reaching any other objects. - */ + /** Invoking this method prevents event from reaching any registered event + * listeners after the current one finishes running and, when dispatched in a + * tree, also prevents event from reaching any other objects. */ stopImmediatePropagation(): void; - /** - * When dispatched in a tree, invoking this method prevents event from - * reaching any objects other than the current object. - */ + /** When dispatched in a tree, invoking this method prevents event from + * reaching any objects other than the current object. */ stopPropagation(): void; readonly AT_TARGET: number; readonly BUBBLING_PHASE: number; readonly CAPTURING_PHASE: number; readonly NONE: number; + static readonly AT_TARGET: number; + static readonly BUBBLING_PHASE: number; + static readonly CAPTURING_PHASE: number; + static readonly NONE: number; } -interface EventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; -} - -declare const Event: { - prototype: Event; - new (type: string, eventInitDict?: EventInit): Event; - readonly AT_TARGET: number; - readonly BUBBLING_PHASE: number; - readonly CAPTURING_PHASE: number; - readonly NONE: number; -}; - /** * EventTarget is a DOM interface implemented by objects that can receive events * and may have listeners for them. */ -interface EventTarget { - /** - * Appends an event listener for events whose type attribute value is type. +declare class EventTarget { + /** Appends an event listener for events whose type attribute value is type. * The callback argument sets the callback that will be invoked when the event * is dispatched. * @@ -1197,41 +1159,32 @@ interface EventTarget { * invoked once after which the event listener will be removed. * * The event listener is appended to target's event listener list and is not - * appended if it has the same type, callback, and capture. - */ + * appended if it has the same type, callback, and capture. */ addEventListener( type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions ): void; - /** - * Dispatches a synthetic event event to target and returns true if either + /** Dispatches a synthetic event event to target and returns true if either * event's cancelable attribute value is false or its preventDefault() method - * was not invoked, and false otherwise. - */ + * was not invoked, and false otherwise. */ dispatchEvent(event: Event): boolean; - /** - * Removes the event listener in target's event listener list with the same - * type, callback, and options. - */ + /** Removes the event listener in target's event listener list with the same + * type, callback, and options. */ removeEventListener( type: string, callback: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean ): void; + [Symbol.toStringTag]: string; } -declare const EventTarget: { - prototype: EventTarget; - new (): EventTarget; -}; - interface EventListener { - (evt: Event): void; + (evt: Event): void | Promise; } interface EventListenerObject { - handleEvent(evt: Event): void; + handleEvent(evt: Event): void | Promise; } declare type EventListenerOrEventListenerObject = @@ -1257,27 +1210,16 @@ interface ProgressEvent extends Event { readonly total: number; } -interface CustomEvent extends Event { - /** - * Returns any custom data event was created with. Typically used for synthetic events. - */ - readonly detail: T; - initCustomEvent( - typeArg: string, - canBubbleArg: boolean, - cancelableArg: boolean, - detailArg: T - ): void; -} - interface CustomEventInit extends EventInit { detail?: T; } -declare const CustomEvent: { - prototype: CustomEvent; - new (typeArg: string, eventInitDict?: CustomEventInit): CustomEvent; -}; +declare class CustomEvent extends Event { + constructor(typeArg: string, eventInitDict?: CustomEventInit); + /** Returns any custom data event was created with. Typically used for + * synthetic events. */ + readonly detail: T; +} interface AbortSignalEventMap { abort: Event; diff --git a/cli/js/lib.deno.window.d.ts b/cli/js/lib.deno.window.d.ts index 2b8e6f50f..6377057d3 100644 --- a/cli/js/lib.deno.window.d.ts +++ b/cli/js/lib.deno.window.d.ts @@ -1,28 +1,28 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/no-explicit-any */ /// /// /// /// -declare interface Window { - window: Window & typeof globalThis; - self: Window & typeof globalThis; - onload: Function | undefined; - onunload: Function | undefined; +declare interface Window extends EventTarget { + readonly window: Window & typeof globalThis; + readonly self: Window & typeof globalThis; + onload: ((this: Window, ev: Event) => any) | null; + onunload: ((this: Window, ev: Event) => any) | null; location: Location; crypto: Crypto; close: () => void; - closed: boolean; + readonly closed: boolean; Deno: typeof Deno; } declare const window: Window & typeof globalThis; declare const self: Window & typeof globalThis; -declare const onload: Function | undefined; -declare const onunload: Function | undefined; +declare const onload: ((this: Window, ev: Event) => any) | null; +declare const onunload: ((this: Window, ev: Event) => any) | null; declare const crypto: Crypto; declare interface Crypto { @@ -45,4 +45,4 @@ declare interface Crypto { ): T; } -/* eslint-enable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-interface, @typescript-eslint/no-explicit-any */ +/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/cli/js/runtime_main.ts b/cli/js/runtime_main.ts index edb02d2d6..0b0b1f75f 100644 --- a/cli/js/runtime_main.ts +++ b/cli/js/runtime_main.ts @@ -8,7 +8,6 @@ // It sets up runtime by providing globals for `WindowScope` and adds `Deno` global. import * as Deno from "./deno.ts"; -import * as domTypes from "./web/dom_types.ts"; import * as csprng from "./ops/get_random_values.ts"; import { exit } from "./ops/os.ts"; import { @@ -18,6 +17,7 @@ import { windowOrWorkerGlobalScopeMethods, windowOrWorkerGlobalScopeProperties, eventTargetProperties, + setEventTargetData, } from "./globals.ts"; import { internalObject } from "./internals.ts"; import { setSignals } from "./signals.ts"; @@ -59,9 +59,9 @@ export const mainRuntimeGlobalProperties = { self: readOnly(globalThis), crypto: readOnly(csprng), // TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope) - // it seems those two properties should be availble to workers as well - onload: writable(undefined), - onunload: writable(undefined), + // it seems those two properties should be available to workers as well + onload: writable(null), + onunload: writable(null), close: writable(windowClose), closed: getterOnly(() => windowIsClosing), }; @@ -78,15 +78,16 @@ export function bootstrapMainRuntime(): void { Object.defineProperties(globalThis, windowOrWorkerGlobalScopeProperties); Object.defineProperties(globalThis, eventTargetProperties); Object.defineProperties(globalThis, mainRuntimeGlobalProperties); + setEventTargetData(globalThis); // Registers the handler for window.onload function. - globalThis.addEventListener("load", (e: domTypes.Event): void => { + globalThis.addEventListener("load", (e) => { const { onload } = globalThis; if (typeof onload === "function") { onload(e); } }); // Registers the handler for window.onunload function. - globalThis.addEventListener("unload", (e: domTypes.Event): void => { + globalThis.addEventListener("unload", (e) => { const { onunload } = globalThis; if (typeof onunload === "function") { onunload(e); diff --git a/cli/js/tests/dom_exception_test.ts b/cli/js/tests/dom_exception_test.ts new file mode 100644 index 000000000..2eb7633e1 --- /dev/null +++ b/cli/js/tests/dom_exception_test.ts @@ -0,0 +1,9 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. +import { unitTest, assertEquals, assert } from "./test_util.ts"; + +unitTest(function testDomError() { + const de = new DOMException("foo", "bar"); + assert(de); + assertEquals(de.message, "foo"); + assertEquals(de.name, "bar"); +}); diff --git a/cli/js/tests/event_target_test.ts b/cli/js/tests/event_target_test.ts index ece4b5dba..0c4eb4d0d 100644 --- a/cli/js/tests/event_target_test.ts +++ b/cli/js/tests/event_target_test.ts @@ -35,18 +35,6 @@ unitTest(function constructedEventTargetCanBeUsedAsExpected(): void { assertEquals(callCount, 2); }); -// TODO(ry) Should AddEventListenerOptions and EventListenerOptions be exposed -// from the public API? - -interface AddEventListenerOptions extends EventListenerOptions { - once?: boolean; - passive?: boolean; -} - -interface EventListenerOptions { - capture?: boolean; -} - unitTest(function anEventTargetCanBeSubclassed(): void { class NicerEventTarget extends EventTarget { on( diff --git a/cli/js/tests/event_test.ts b/cli/js/tests/event_test.ts index 05a9ed577..ce3076e58 100644 --- a/cli/js/tests/event_test.ts +++ b/cli/js/tests/event_test.ts @@ -48,10 +48,8 @@ unitTest(function eventStopImmediatePropagationSuccess(): void { const event = new Event(type); assertEquals(event.cancelBubble, false); - assertEquals(event.cancelBubbleImmediately, false); event.stopImmediatePropagation(); assertEquals(event.cancelBubble, true); - assertEquals(event.cancelBubbleImmediately, true); }); unitTest(function eventPreventDefaultSuccess(): void { diff --git a/cli/js/tests/unit_tests.ts b/cli/js/tests/unit_tests.ts index 4cff3d1d8..ba3d6746a 100644 --- a/cli/js/tests/unit_tests.ts +++ b/cli/js/tests/unit_tests.ts @@ -16,6 +16,7 @@ import "./custom_event_test.ts"; import "./dir_test.ts"; import "./dispatch_minimal_test.ts"; import "./dispatch_json_test.ts"; +import "./dom_exception_test.ts"; import "./error_stack_test.ts"; import "./event_test.ts"; import "./event_target_test.ts"; diff --git a/cli/js/web/blob.ts b/cli/js/web/blob.ts index 7bdde8e28..90480c89c 100644 --- a/cli/js/web/blob.ts +++ b/cli/js/web/blob.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { TextDecoder, TextEncoder } from "./text_encoding.ts"; import { build } from "../build.ts"; import { ReadableStream } from "./streams/mod.ts"; diff --git a/cli/js/web/body.ts b/cli/js/web/body.ts index a16f872b9..2f6987592 100644 --- a/cli/js/web/body.ts +++ b/cli/js/web/body.ts @@ -2,7 +2,7 @@ import * as formData from "./form_data.ts"; import * as blob from "./blob.ts"; import * as encoding from "./text_encoding.ts"; import * as headers from "./headers.ts"; -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { ReadableStream } from "./streams/mod.ts"; const { Headers } = headers; diff --git a/cli/js/web/custom_event.ts b/cli/js/web/custom_event.ts index 418b7ea34..ea76d2c94 100644 --- a/cli/js/web/custom_event.ts +++ b/cli/js/web/custom_event.ts @@ -1,44 +1,28 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; -import * as event from "./event.ts"; +import { EventImpl as Event } from "./event.ts"; import { requiredArguments } from "./util.ts"; -export class CustomEvent extends event.Event implements domTypes.CustomEvent { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - #detail: any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class CustomEventImpl extends Event implements CustomEvent { + #detail: T; - constructor( - type: string, - customEventInitDict: domTypes.CustomEventInit = {} - ) { - super(type, customEventInitDict); + constructor(type: string, eventInitDict: CustomEventInit = {}) { + super(type, eventInitDict); requiredArguments("CustomEvent", arguments.length, 1); - const { detail = null } = customEventInitDict; - this.#detail = detail; + const { detail } = eventInitDict; + this.#detail = detail as T; } // eslint-disable-next-line @typescript-eslint/no-explicit-any - get detail(): any { + get detail(): T { return this.#detail; } - initCustomEvent( - _type: string, - _bubbles?: boolean, - _cancelable?: boolean, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - detail?: any - ): void { - if (this.dispatched) { - return; - } - - this.#detail = detail; - } - get [Symbol.toStringTag](): string { return "CustomEvent"; } } -Reflect.defineProperty(CustomEvent.prototype, "detail", { enumerable: true }); +Reflect.defineProperty(CustomEventImpl.prototype, "detail", { + enumerable: true, +}); diff --git a/cli/js/web/dom_exception.ts b/cli/js/web/dom_exception.ts new file mode 100644 index 000000000..e2c77d41c --- /dev/null +++ b/cli/js/web/dom_exception.ts @@ -0,0 +1,14 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +export class DOMExceptionImpl extends Error implements DOMException { + #name: string; + + constructor(message = "", name = "Error") { + super(message); + this.#name = name; + } + + get name(): string { + return this.#name; + } +} diff --git a/cli/js/web/dom_file.ts b/cli/js/web/dom_file.ts index cf2a40398..a3b43dad1 100644 --- a/cli/js/web/dom_file.ts +++ b/cli/js/web/dom_file.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import * as blob from "./blob.ts"; export class DomFileImpl extends blob.DenoBlob implements domTypes.DomFile { diff --git a/cli/js/web/dom_iterable.ts b/cli/js/web/dom_iterable.ts index 191958f11..fcbca307f 100644 --- a/cli/js/web/dom_iterable.ts +++ b/cli/js/web/dom_iterable.ts @@ -1,11 +1,21 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -// eslint-disable-next-line @typescript-eslint/no-unused-vars -import { DomIterable } from "./dom_types.ts"; import { requiredArguments } from "./util.ts"; import { exposeForTest } from "../internals.ts"; // eslint-disable-next-line @typescript-eslint/no-explicit-any -type Constructor = new (...args: any[]) => T; +export type Constructor = new (...args: any[]) => T; + +export interface DomIterable { + keys(): IterableIterator; + values(): IterableIterator; + entries(): IterableIterator<[K, V]>; + [Symbol.iterator](): IterableIterator<[K, V]>; + forEach( + callback: (value: V, key: K, parent: this) => void, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + thisArg?: any + ): void; +} export function DomIterableMixin( Base: TBase, diff --git a/cli/js/web/dom_types.d.ts b/cli/js/web/dom_types.d.ts new file mode 100644 index 000000000..3ac025934 --- /dev/null +++ b/cli/js/web/dom_types.d.ts @@ -0,0 +1,757 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +/*! **************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +*******************************************************************************/ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type BufferSource = ArrayBufferView | ArrayBuffer; + +export type HeadersInit = + | Headers + | Array<[string, string]> + | Record; + +type BodyInit = + | Blob + | BufferSource + | FormData + | URLSearchParams + | ReadableStream + | string; + +export type RequestInfo = Request | string; + +type ReferrerPolicy = + | "" + | "no-referrer" + | "no-referrer-when-downgrade" + | "origin-only" + | "origin-when-cross-origin" + | "unsafe-url"; + +export type BlobPart = BufferSource | Blob | string; + +export type FormDataEntryValue = DomFile | string; + +export type EndingType = "transparent" | "native"; + +export interface BlobPropertyBag { + type?: string; + ending?: EndingType; +} + +interface AbortSignalEventMap { + abort: ProgressEvent; +} + +export interface ProgressEventInit extends EventInit { + lengthComputable?: boolean; + loaded?: number; + total?: number; +} + +export class URLSearchParams { + constructor( + init?: string[][] | Record | string | URLSearchParams + ); + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + getAll(name: string): string[]; + has(name: string): boolean; + set(name: string, value: string): void; + sort(): void; + toString(): string; + forEach( + callbackfn: (value: string, key: string, parent: this) => void, + thisArg?: any + ): void; + [Symbol.iterator](): IterableIterator<[string, string]>; + entries(): IterableIterator<[string, string]>; + keys(): IterableIterator; + values(): IterableIterator; + static toString(): string; +} + +export interface UIEventInit extends EventInit { + detail?: number; + // adjust Window -> Node + view?: Node | null; +} + +export class UIEvent extends Event { + constructor(type: string, eventInitDict?: UIEventInit); + readonly detail: number; + // adjust Window -> Node + readonly view: Node | null; +} + +export interface FocusEventInit extends UIEventInit { + relatedTarget?: EventTarget | null; +} + +export class FocusEvent extends UIEvent { + constructor(type: string, eventInitDict?: FocusEventInit); + readonly relatedTarget: EventTarget | null; +} + +export interface EventModifierInit extends UIEventInit { + altKey?: boolean; + ctrlKey?: boolean; + metaKey?: boolean; + modifierAltGraph?: boolean; + modifierCapsLock?: boolean; + modifierFn?: boolean; + modifierFnLock?: boolean; + modifierHyper?: boolean; + modifierNumLock?: boolean; + modifierScrollLock?: boolean; + modifierSuper?: boolean; + modifierSymbol?: boolean; + modifierSymbolLock?: boolean; + shiftKey?: boolean; +} + +export interface MouseEventInit extends EventModifierInit { + button?: number; + buttons?: number; + clientX?: number; + clientY?: number; + movementX?: number; + movementY?: number; + relatedTarget?: EventTarget | null; + screenX?: number; + screenY?: number; +} + +export class MouseEvent extends UIEvent { + constructor(type: string, eventInitDict?: MouseEventInit); + readonly altKey: boolean; + readonly button: number; + readonly buttons: number; + readonly clientX: number; + readonly clientY: number; + readonly ctrlKey: boolean; + readonly metaKey: boolean; + readonly movementX: number; + readonly movementY: number; + readonly offsetX: number; + readonly offsetY: number; + readonly pageX: number; + readonly pageY: number; + readonly relatedTarget: EventTarget | null; + readonly screenX: number; + readonly screenY: number; + readonly shiftKey: boolean; + readonly x: number; + readonly y: number; + getModifierState(keyArg: string): boolean; +} + +interface GetRootNodeOptions { + composed?: boolean; +} + +export class Node extends EventTarget { + readonly baseURI: string; + readonly childNodes: NodeListOf; + readonly firstChild: ChildNode | null; + readonly isConnected: boolean; + readonly lastChild: ChildNode | null; + readonly nextSibling: ChildNode | null; + readonly nodeName: string; + readonly nodeType: number; + nodeValue: string | null; + // adjusted: Document -> Node + readonly ownerDocument: Node | null; + // adjusted: HTMLElement -> Node + readonly parentElement: Node | null; + readonly parentNode: (Node & ParentNode) | null; + readonly previousSibling: ChildNode | null; + textContent: string | null; + appendChild(newChild: T): T; + cloneNode(deep?: boolean): Node; + compareDocumentPosition(other: Node): number; + contains(other: Node | null): boolean; + getRootNode(options?: GetRootNodeOptions): Node; + hasChildNodes(): boolean; + insertBefore(newChild: T, refChild: Node | null): T; + isDefaultNamespace(namespace: string | null): boolean; + isEqualNode(otherNode: Node | null): boolean; + isSameNode(otherNode: Node | null): boolean; + lookupNamespaceURI(prefix: string | null): string | null; + lookupPrefix(namespace: string | null): string | null; + normalize(): void; + removeChild(oldChild: T): T; + replaceChild(newChild: Node, oldChild: T): T; + readonly ATTRIBUTE_NODE: number; + readonly CDATA_SECTION_NODE: number; + readonly COMMENT_NODE: number; + readonly DOCUMENT_FRAGMENT_NODE: number; + readonly DOCUMENT_NODE: number; + readonly DOCUMENT_POSITION_CONTAINED_BY: number; + readonly DOCUMENT_POSITION_CONTAINS: number; + readonly DOCUMENT_POSITION_DISCONNECTED: number; + readonly DOCUMENT_POSITION_FOLLOWING: number; + readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number; + readonly DOCUMENT_POSITION_PRECEDING: number; + readonly DOCUMENT_TYPE_NODE: number; + readonly ELEMENT_NODE: number; + readonly ENTITY_NODE: number; + readonly ENTITY_REFERENCE_NODE: number; + readonly NOTATION_NODE: number; + readonly PROCESSING_INSTRUCTION_NODE: number; + readonly TEXT_NODE: number; + static readonly ATTRIBUTE_NODE: number; + static readonly CDATA_SECTION_NODE: number; + static readonly COMMENT_NODE: number; + static readonly DOCUMENT_FRAGMENT_NODE: number; + static readonly DOCUMENT_NODE: number; + static readonly DOCUMENT_POSITION_CONTAINED_BY: number; + static readonly DOCUMENT_POSITION_CONTAINS: number; + static readonly DOCUMENT_POSITION_DISCONNECTED: number; + static readonly DOCUMENT_POSITION_FOLLOWING: number; + static readonly DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: number; + static readonly DOCUMENT_POSITION_PRECEDING: number; + static readonly DOCUMENT_TYPE_NODE: number; + static readonly ELEMENT_NODE: number; + static readonly ENTITY_NODE: number; + static readonly ENTITY_REFERENCE_NODE: number; + static readonly NOTATION_NODE: number; + static readonly PROCESSING_INSTRUCTION_NODE: number; + static readonly TEXT_NODE: number; +} + +interface Slotable { + // adjusted: HTMLSlotElement -> Node + readonly assignedSlot: Node | null; +} + +interface ChildNode extends Node { + after(...nodes: Array): void; + before(...nodes: Array): void; + remove(): void; + replaceWith(...nodes: Array): void; +} + +interface ParentNode { + readonly childElementCount: number; + // not currently supported + // readonly children: HTMLCollection; + // adjusted: Element -> Node + readonly firstElementChild: Node | null; + // adjusted: Element -> Node + readonly lastElementChild: Node | null; + append(...nodes: Array): void; + prepend(...nodes: Array): void; + // not currently supported + // querySelector( + // selectors: K, + // ): HTMLElementTagNameMap[K] | null; + // querySelector( + // selectors: K, + // ): SVGElementTagNameMap[K] | null; + // querySelector(selectors: string): E | null; + // querySelectorAll( + // selectors: K, + // ): NodeListOf; + // querySelectorAll( + // selectors: K, + // ): NodeListOf; + // querySelectorAll( + // selectors: string, + // ): NodeListOf; +} + +interface NodeList { + readonly length: number; + item(index: number): Node | null; + forEach( + callbackfn: (value: Node, key: number, parent: NodeList) => void, + thisArg?: any + ): void; + [index: number]: Node; + [Symbol.iterator](): IterableIterator; + entries(): IterableIterator<[number, Node]>; + keys(): IterableIterator; + values(): IterableIterator; +} + +interface NodeListOf extends NodeList { + length: number; + item(index: number): TNode; + forEach( + callbackfn: (value: TNode, key: number, parent: NodeListOf) => void, + thisArg?: any + ): void; + [index: number]: TNode; + [Symbol.iterator](): IterableIterator; + entries(): IterableIterator<[number, TNode]>; + keys(): IterableIterator; + values(): IterableIterator; +} + +export interface DomFile extends Blob { + readonly lastModified: number; + readonly name: string; +} + +export interface DomFileConstructor { + new (bits: BlobPart[], filename: string, options?: FilePropertyBag): DomFile; + prototype: DomFile; +} + +export interface FilePropertyBag extends BlobPropertyBag { + lastModified?: number; +} + +interface ProgressEvent extends Event { + readonly lengthComputable: boolean; + readonly loaded: number; + readonly total: number; +} + +export interface AbortSignal extends EventTarget { + readonly aborted: boolean; + onabort: ((this: AbortSignal, ev: ProgressEvent) => any) | null; + addEventListener( + type: K, + listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, + options?: boolean | AddEventListenerOptions + ): void; + addEventListener( + type: string, + listener: EventListener, + options?: boolean | AddEventListenerOptions + ): void; + removeEventListener( + type: K, + listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: string, + listener: EventListener, + options?: boolean | EventListenerOptions + ): void; +} + +export class FormData { + append(name: string, value: string | Blob, fileName?: string): void; + delete(name: string): void; + get(name: string): FormDataEntryValue | null; + getAll(name: string): FormDataEntryValue[]; + has(name: string): boolean; + set(name: string, value: string | Blob, fileName?: string): void; + [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>; + entries(): IterableIterator<[string, FormDataEntryValue]>; + keys(): IterableIterator; + values(): IterableIterator; +} + +export interface Blob { + readonly size: number; + readonly type: string; + slice(start?: number, end?: number, contentType?: string): Blob; + stream(): ReadableStream; + text(): Promise; + arrayBuffer(): Promise; +} + +export interface Body { + readonly body: ReadableStream | null; + readonly bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + text(): Promise; +} + +export interface ReadableStreamReadDoneResult { + done: true; + value?: T; +} + +export interface ReadableStreamReadValueResult { + done: false; + value: T; +} + +export type ReadableStreamReadResult = + | ReadableStreamReadValueResult + | ReadableStreamReadDoneResult; + +export interface ReadableStreamDefaultReader { + readonly closed: Promise; + cancel(reason?: any): Promise; + read(): Promise>; + releaseLock(): void; +} + +export interface PipeOptions { + preventAbort?: boolean; + preventCancel?: boolean; + preventClose?: boolean; + signal?: AbortSignal; +} + +export interface UnderlyingSource { + cancel?: ReadableStreamErrorCallback; + pull?: ReadableStreamDefaultControllerCallback; + start?: ReadableStreamDefaultControllerCallback; + type?: undefined; +} +export interface ReadableStreamErrorCallback { + (reason: any): void | PromiseLike; +} + +export interface ReadableStreamDefaultControllerCallback { + (controller: ReadableStreamDefaultController): void | PromiseLike; +} + +export interface ReadableStreamConstructor { + new (source?: UnderlyingSource): ReadableStream; +} + +export interface ReadableStream { + readonly locked: boolean; + cancel(reason?: any): Promise; + getReader(options: { mode: "byob" }): ReadableStreamBYOBReader; + getReader(): ReadableStreamDefaultReader; + /* disabled for now + pipeThrough( + { + writable, + readable + }: { + writable: WritableStream; + readable: ReadableStream; + }, + options?: PipeOptions + ): ReadableStream; + pipeTo(dest: WritableStream, options?: PipeOptions): Promise; + */ + tee(): [ReadableStream, ReadableStream]; +} + +export interface ReadableStreamBYOBReader { + readonly closed: Promise; + cancel(reason?: any): Promise; + read( + view: T + ): Promise>; + releaseLock(): void; +} + +export interface WritableStream { + readonly locked: boolean; + abort(reason?: any): Promise; + getWriter(): WritableStreamDefaultWriter; +} + +export interface WritableStreamDefaultWriter { + readonly closed: Promise; + readonly desiredSize: number | null; + readonly ready: Promise; + abort(reason?: any): Promise; + close(): Promise; + releaseLock(): void; + write(chunk: W): Promise; +} + +export interface UnderlyingSource { + cancel?: ReadableStreamErrorCallback; + pull?: ReadableStreamDefaultControllerCallback; + start?: ReadableStreamDefaultControllerCallback; + type?: undefined; +} + +export interface UnderlyingByteSource { + autoAllocateChunkSize?: number; + cancel?: ReadableStreamErrorCallback; + pull?: ReadableByteStreamControllerCallback; + start?: ReadableByteStreamControllerCallback; + type: "bytes"; +} + +export interface ReadableStreamReader { + cancel(reason: any): Promise; + read(): Promise>; + releaseLock(): void; +} + +export interface ReadableStreamErrorCallback { + (reason: any): void | PromiseLike; +} + +export interface ReadableByteStreamControllerCallback { + (controller: ReadableByteStreamController): void | PromiseLike; +} + +export interface ReadableStreamDefaultControllerCallback { + (controller: ReadableStreamDefaultController): void | PromiseLike; +} + +export interface ReadableStreamDefaultController { + readonly desiredSize: number | null; + close(): void; + enqueue(chunk: R): void; + error(error?: any): void; +} + +export interface ReadableByteStreamController { + readonly byobRequest: ReadableStreamBYOBRequest | undefined; + readonly desiredSize: number | null; + close(): void; + enqueue(chunk: ArrayBufferView): void; + error(error?: any): void; +} + +export interface ReadableStreamBYOBRequest { + readonly view: ArrayBufferView; + respond(bytesWritten: number): void; + respondWithNewView(view: ArrayBufferView): void; +} +/* TODO reenable these interfaces. These are needed to enable WritableStreams in js/streams/ +export interface WritableStream { + readonly locked: boolean; + abort(reason?: any): Promise; + getWriter(): WritableStreamDefaultWriter; +} + +TODO reenable these interfaces. These are needed to enable WritableStreams in js/streams/ +export interface UnderlyingSink { + abort?: WritableStreamErrorCallback; + close?: WritableStreamDefaultControllerCloseCallback; + start?: WritableStreamDefaultControllerStartCallback; + type?: undefined; + write?: WritableStreamDefaultControllerWriteCallback; +} + +export interface PipeOptions { + preventAbort?: boolean; + preventCancel?: boolean; + preventClose?: boolean; + signal?: AbortSignal; +} + + +export interface WritableStreamDefaultWriter { + readonly closed: Promise; + readonly desiredSize: number | null; + readonly ready: Promise; + abort(reason?: any): Promise; + close(): Promise; + releaseLock(): void; + write(chunk: W): Promise; +} + +export interface WritableStreamErrorCallback { + (reason: any): void | PromiseLike; +} + +export interface WritableStreamDefaultControllerCloseCallback { + (): void | PromiseLike; +} + +export interface WritableStreamDefaultControllerStartCallback { + (controller: WritableStreamDefaultController): void | PromiseLike; +} + +export interface WritableStreamDefaultControllerWriteCallback { + (chunk: W, controller: WritableStreamDefaultController): void | PromiseLike< + void + >; +} + +export interface WritableStreamDefaultController { + error(error?: any): void; +} +*/ + +export interface QueuingStrategy { + highWaterMark?: number; + size?: QueuingStrategySizeCallback; +} + +export interface QueuingStrategySizeCallback { + (chunk: T): number; +} + +export class Headers { + constructor(init?: HeadersInit); + append(name: string, value: string): void; + delete(name: string): void; + get(name: string): string | null; + has(name: string): boolean; + set(name: string, value: string): void; + forEach( + callbackfn: (value: string, key: string, parent: this) => void, + thisArg?: any + ): void; + [Symbol.iterator](): IterableIterator<[string, string]>; + entries(): IterableIterator<[string, string]>; + keys(): IterableIterator; + values(): IterableIterator; +} + +type RequestCache = + | "default" + | "no-store" + | "reload" + | "no-cache" + | "force-cache" + | "only-if-cached"; +type RequestCredentials = "omit" | "same-origin" | "include"; +type RequestDestination = + | "" + | "audio" + | "audioworklet" + | "document" + | "embed" + | "font" + | "image" + | "manifest" + | "object" + | "paintworklet" + | "report" + | "script" + | "sharedworker" + | "style" + | "track" + | "video" + | "worker" + | "xslt"; +type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors"; +type RequestRedirect = "follow" | "nofollow" | "error" | "manual"; +export type ResponseType = + | "basic" + | "cors" + | "default" + | "error" + | "opaque" + | "opaqueredirect"; + +export interface RequestInit { + body?: BodyInit | null; + cache?: RequestCache; + credentials?: RequestCredentials; + headers?: HeadersInit; + integrity?: string; + keepalive?: boolean; + method?: string; + mode?: RequestMode; + redirect?: RequestRedirect; + referrer?: string; + referrerPolicy?: ReferrerPolicy; + signal?: AbortSignal | null; + window?: any; +} + +export interface ResponseInit { + headers?: HeadersInit; + status?: number; + statusText?: string; +} + +export interface Request extends Body { + readonly cache?: RequestCache; + readonly credentials?: RequestCredentials; + readonly destination?: RequestDestination; + readonly headers: Headers; + readonly integrity?: string; + readonly isHistoryNavigation?: boolean; + readonly isReloadNavigation?: boolean; + readonly keepalive?: boolean; + readonly method: string; + readonly mode?: RequestMode; + readonly redirect?: RequestRedirect; + readonly referrer?: string; + readonly referrerPolicy?: ReferrerPolicy; + readonly signal?: AbortSignal; + readonly url: string; + clone(): Request; +} + +export interface RequestConstructor { + new (input: RequestInfo, init?: RequestInit): Request; + prototype: Request; +} + +export interface Response extends Body { + readonly headers: Headers; + readonly ok: boolean; + readonly redirected: boolean; + readonly status: number; + readonly statusText: string; + readonly trailer: Promise; + readonly type: ResponseType; + readonly url: string; + clone(): Response; +} + +export interface ResponseConstructor { + prototype: Response; + new (body?: BodyInit | null, init?: ResponseInit): Response; + error(): Response; + redirect(url: string, status?: number): Response; +} + +export class DOMStringList { + readonly length: number; + contains(string: string): boolean; + item(index: number): string | null; + [index: number]: string; + [Symbol.iterator](): IterableIterator; +} + +export class Location { + readonly ancestorOrigins: DOMStringList; + hash: string; + host: string; + hostname: string; + href: string; + toString(): string; + readonly origin: string; + pathname: string; + port: string; + protocol: string; + search: string; + assign(url: string): void; + reload(): void; + replace(url: string): void; +} + +export class URL { + constructor(url: string, base?: string | URL); + hash: string; + host: string; + hostname: string; + href: string; + toString(): string; + readonly origin: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + readonly searchParams: URLSearchParams; + username: string; + toJSON(): string; + static createObjectURL(object: any): string; + static revokeObjectURL(url: string): void; +} diff --git a/cli/js/web/dom_types.ts b/cli/js/web/dom_types.ts deleted file mode 100644 index 94e26846f..000000000 --- a/cli/js/web/dom_types.ts +++ /dev/null @@ -1,710 +0,0 @@ -// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. - -/*! **************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF -ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -*******************************************************************************/ - -/* eslint-disable @typescript-eslint/no-explicit-any */ - -export type BufferSource = ArrayBufferView | ArrayBuffer; - -export type HeadersInit = - | Headers - | Array<[string, string]> - | Record; -export type URLSearchParamsInit = string | string[][] | Record; -type BodyInit = - | Blob - | BufferSource - | FormData - | URLSearchParams - | ReadableStream - | string; -export type RequestInfo = Request | string; -type ReferrerPolicy = - | "" - | "no-referrer" - | "no-referrer-when-downgrade" - | "origin-only" - | "origin-when-cross-origin" - | "unsafe-url"; -export type BlobPart = BufferSource | Blob | string; -export type FormDataEntryValue = DomFile | string; - -export interface DomIterable { - keys(): IterableIterator; - values(): IterableIterator; - entries(): IterableIterator<[K, V]>; - [Symbol.iterator](): IterableIterator<[K, V]>; - forEach( - callback: (value: V, key: K, parent: this) => void, - thisArg?: any - ): void; -} - -type EndingType = "transparent" | "native"; - -export interface BlobPropertyBag { - type?: string; - ending?: EndingType; -} - -interface AbortSignalEventMap { - abort: ProgressEvent; -} - -// https://dom.spec.whatwg.org/#node -export enum NodeType { - ELEMENT_NODE = 1, - TEXT_NODE = 3, - DOCUMENT_FRAGMENT_NODE = 11, -} - -export const eventTargetHost: unique symbol = Symbol(); -export const eventTargetListeners: unique symbol = Symbol(); -export const eventTargetMode: unique symbol = Symbol(); -export const eventTargetNodeType: unique symbol = Symbol(); - -export interface EventListener { - // Different from lib.dom.d.ts. Added Promise - (evt: Event): void | Promise; -} - -export interface EventListenerObject { - // Different from lib.dom.d.ts. Added Promise - handleEvent(evt: Event): void | Promise; -} - -export type EventListenerOrEventListenerObject = - | EventListener - | EventListenerObject; - -// This is actually not part of actual DOM types, -// but an implementation specific thing on our custom EventTarget -// (due to the presence of our custom symbols) -export interface EventTargetListener { - callback: EventListenerOrEventListenerObject; - options: AddEventListenerOptions; -} - -export interface EventTarget { - // TODO: below 4 symbol props should not present on EventTarget WebIDL. - // They should be implementation specific details. - [eventTargetHost]: EventTarget | null; - [eventTargetListeners]: { [type in string]: EventTargetListener[] }; - [eventTargetMode]: string; - [eventTargetNodeType]: NodeType; - addEventListener( - type: string, - listener: EventListenerOrEventListenerObject | null, - options?: boolean | AddEventListenerOptions - ): void; - dispatchEvent(event: Event): boolean; - removeEventListener( - type: string, - listener: EventListenerOrEventListenerObject | null, - options?: EventListenerOptions | boolean - ): void; -} - -export interface ProgressEventInit extends EventInit { - lengthComputable?: boolean; - loaded?: number; - total?: number; -} - -export interface URLSearchParams extends DomIterable { - append(name: string, value: string): void; - delete(name: string): void; - get(name: string): string | null; - getAll(name: string): string[]; - has(name: string): boolean; - set(name: string, value: string): void; - sort(): void; - toString(): string; - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - thisArg?: any - ): void; -} - -export interface EventInit { - bubbles?: boolean; - cancelable?: boolean; - composed?: boolean; -} - -export interface CustomEventInit extends EventInit { - detail?: any; -} - -export enum EventPhase { - NONE = 0, - CAPTURING_PHASE = 1, - AT_TARGET = 2, - BUBBLING_PHASE = 3, -} - -export interface EventPath { - item: EventTarget; - itemInShadowTree: boolean; - relatedTarget: EventTarget | null; - rootOfClosedTree: boolean; - slotInClosedTree: boolean; - target: EventTarget | null; - touchTargetList: EventTarget[]; -} - -export interface Event { - readonly type: string; - target: EventTarget | null; - currentTarget: EventTarget | null; - composedPath(): EventPath[]; - - eventPhase: number; - - stopPropagation(): void; - stopImmediatePropagation(): void; - - readonly bubbles: boolean; - readonly cancelable: boolean; - preventDefault(): void; - readonly defaultPrevented: boolean; - readonly composed: 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 { - readonly detail: any; - initCustomEvent( - type: string, - bubbles?: boolean, - cancelable?: boolean, - detail?: any | null - ): void; -} - -export interface DomFile extends Blob { - readonly lastModified: number; - readonly name: string; -} - -export interface DomFileConstructor { - new (bits: BlobPart[], filename: string, options?: FilePropertyBag): DomFile; - prototype: DomFile; -} - -export interface FilePropertyBag extends BlobPropertyBag { - lastModified?: number; -} - -interface ProgressEvent extends Event { - readonly lengthComputable: boolean; - readonly loaded: number; - readonly total: number; -} - -export interface EventListenerOptions { - capture?: boolean; -} - -export interface AddEventListenerOptions extends EventListenerOptions { - once?: boolean; - passive?: boolean; -} - -export interface AbortSignal extends EventTarget { - readonly aborted: boolean; - onabort: ((this: AbortSignal, ev: ProgressEvent) => any) | null; - addEventListener( - type: K, - listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, - options?: boolean | AddEventListenerOptions - ): void; - addEventListener( - type: string, - listener: EventListener, - options?: boolean | AddEventListenerOptions - ): void; - removeEventListener( - type: K, - listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, - options?: boolean | EventListenerOptions - ): void; - removeEventListener( - type: string, - listener: EventListener, - options?: boolean | EventListenerOptions - ): void; -} - -export interface FormData extends DomIterable { - append(name: string, value: string | Blob, fileName?: string): void; - delete(name: string): void; - get(name: string): FormDataEntryValue | null; - getAll(name: string): FormDataEntryValue[]; - has(name: string): boolean; - set(name: string, value: string | Blob, fileName?: string): void; -} - -export interface FormDataConstructor { - new (): FormData; - prototype: FormData; -} - -export interface Blob { - readonly size: number; - readonly type: string; - slice(start?: number, end?: number, contentType?: string): Blob; - stream(): ReadableStream; - text(): Promise; - arrayBuffer(): Promise; -} - -export interface Body { - readonly body: ReadableStream | null; - readonly bodyUsed: boolean; - arrayBuffer(): Promise; - blob(): Promise; - formData(): Promise; - json(): Promise; - text(): Promise; -} - -export interface ReadableStreamReadDoneResult { - done: true; - value?: T; -} - -export interface ReadableStreamReadValueResult { - done: false; - value: T; -} - -export type ReadableStreamReadResult = - | ReadableStreamReadValueResult - | ReadableStreamReadDoneResult; - -export interface ReadableStreamDefaultReader { - readonly closed: Promise; - cancel(reason?: any): Promise; - read(): Promise>; - releaseLock(): void; -} - -export interface PipeOptions { - preventAbort?: boolean; - preventCancel?: boolean; - preventClose?: boolean; - signal?: AbortSignal; -} - -export interface UnderlyingSource { - cancel?: ReadableStreamErrorCallback; - pull?: ReadableStreamDefaultControllerCallback; - start?: ReadableStreamDefaultControllerCallback; - type?: undefined; -} -export interface ReadableStreamErrorCallback { - (reason: any): void | PromiseLike; -} - -export interface ReadableStreamDefaultControllerCallback { - (controller: ReadableStreamDefaultController): void | PromiseLike; -} - -export interface ReadableStreamConstructor { - new (source?: UnderlyingSource): ReadableStream; -} - -export interface ReadableStream { - readonly locked: boolean; - cancel(reason?: any): Promise; - getReader(options: { mode: "byob" }): ReadableStreamBYOBReader; - getReader(): ReadableStreamDefaultReader; - /* disabled for now - pipeThrough( - { - writable, - readable - }: { - writable: WritableStream; - readable: ReadableStream; - }, - options?: PipeOptions - ): ReadableStream; - pipeTo(dest: WritableStream, options?: PipeOptions): Promise; - */ - tee(): [ReadableStream, ReadableStream]; -} - -export interface ReadableStreamBYOBReader { - readonly closed: Promise; - cancel(reason?: any): Promise; - read( - view: T - ): Promise>; - releaseLock(): void; -} - -export interface WritableStream { - readonly locked: boolean; - abort(reason?: any): Promise; - getWriter(): WritableStreamDefaultWriter; -} - -export interface WritableStreamDefaultWriter { - readonly closed: Promise; - readonly desiredSize: number | null; - readonly ready: Promise; - abort(reason?: any): Promise; - close(): Promise; - releaseLock(): void; - write(chunk: W): Promise; -} - -export interface UnderlyingSource { - cancel?: ReadableStreamErrorCallback; - pull?: ReadableStreamDefaultControllerCallback; - start?: ReadableStreamDefaultControllerCallback; - type?: undefined; -} - -export interface UnderlyingByteSource { - autoAllocateChunkSize?: number; - cancel?: ReadableStreamErrorCallback; - pull?: ReadableByteStreamControllerCallback; - start?: ReadableByteStreamControllerCallback; - type: "bytes"; -} - -export interface ReadableStreamReader { - cancel(reason: any): Promise; - read(): Promise>; - releaseLock(): void; -} - -export interface ReadableStreamErrorCallback { - (reason: any): void | PromiseLike; -} - -export interface ReadableByteStreamControllerCallback { - (controller: ReadableByteStreamController): void | PromiseLike; -} - -export interface ReadableStreamDefaultControllerCallback { - (controller: ReadableStreamDefaultController): void | PromiseLike; -} - -export interface ReadableStreamDefaultController { - readonly desiredSize: number | null; - close(): void; - enqueue(chunk: R): void; - error(error?: any): void; -} - -export interface ReadableByteStreamController { - readonly byobRequest: ReadableStreamBYOBRequest | undefined; - readonly desiredSize: number | null; - close(): void; - enqueue(chunk: ArrayBufferView): void; - error(error?: any): void; -} - -export interface ReadableStreamBYOBRequest { - readonly view: ArrayBufferView; - respond(bytesWritten: number): void; - respondWithNewView(view: ArrayBufferView): void; -} -/* TODO reenable these interfaces. These are needed to enable WritableStreams in js/streams/ -export interface WritableStream { - readonly locked: boolean; - abort(reason?: any): Promise; - getWriter(): WritableStreamDefaultWriter; -} - -TODO reenable these interfaces. These are needed to enable WritableStreams in js/streams/ -export interface UnderlyingSink { - abort?: WritableStreamErrorCallback; - close?: WritableStreamDefaultControllerCloseCallback; - start?: WritableStreamDefaultControllerStartCallback; - type?: undefined; - write?: WritableStreamDefaultControllerWriteCallback; -} - -export interface PipeOptions { - preventAbort?: boolean; - preventCancel?: boolean; - preventClose?: boolean; - signal?: AbortSignal; -} - - -export interface WritableStreamDefaultWriter { - readonly closed: Promise; - readonly desiredSize: number | null; - readonly ready: Promise; - abort(reason?: any): Promise; - close(): Promise; - releaseLock(): void; - write(chunk: W): Promise; -} - -export interface WritableStreamErrorCallback { - (reason: any): void | PromiseLike; -} - -export interface WritableStreamDefaultControllerCloseCallback { - (): void | PromiseLike; -} - -export interface WritableStreamDefaultControllerStartCallback { - (controller: WritableStreamDefaultController): void | PromiseLike; -} - -export interface WritableStreamDefaultControllerWriteCallback { - (chunk: W, controller: WritableStreamDefaultController): void | PromiseLike< - void - >; -} - -export interface WritableStreamDefaultController { - error(error?: any): void; -} -*/ -export interface QueuingStrategy { - highWaterMark?: number; - size?: QueuingStrategySizeCallback; -} - -export interface QueuingStrategySizeCallback { - (chunk: T): number; -} - -export interface Headers extends DomIterable { - append(name: string, value: string): void; - delete(name: string): void; - entries(): IterableIterator<[string, string]>; - get(name: string): string | null; - has(name: string): boolean; - keys(): IterableIterator; - set(name: string, value: string): void; - values(): IterableIterator; - forEach( - callbackfn: (value: string, key: string, parent: this) => void, - thisArg?: any - ): void; - [Symbol.iterator](): IterableIterator<[string, string]>; -} - -export interface HeadersConstructor { - new (init?: HeadersInit): Headers; - prototype: Headers; -} - -type RequestCache = - | "default" - | "no-store" - | "reload" - | "no-cache" - | "force-cache" - | "only-if-cached"; -type RequestCredentials = "omit" | "same-origin" | "include"; -type RequestDestination = - | "" - | "audio" - | "audioworklet" - | "document" - | "embed" - | "font" - | "image" - | "manifest" - | "object" - | "paintworklet" - | "report" - | "script" - | "sharedworker" - | "style" - | "track" - | "video" - | "worker" - | "xslt"; -type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors"; -type RequestRedirect = "follow" | "nofollow" | "error" | "manual"; -export type ResponseType = - | "basic" - | "cors" - | "default" - | "error" - | "opaque" - | "opaqueredirect"; - -export interface RequestInit { - body?: BodyInit | null; - cache?: RequestCache; - credentials?: RequestCredentials; - headers?: HeadersInit; - integrity?: string; - keepalive?: boolean; - method?: string; - mode?: RequestMode; - redirect?: RequestRedirect; - referrer?: string; - referrerPolicy?: ReferrerPolicy; - signal?: AbortSignal | null; - window?: any; -} - -export interface ResponseInit { - headers?: HeadersInit; - status?: number; - statusText?: string; -} - -export interface RequestConstructor { - new (input: RequestInfo, init?: RequestInit): Request; - prototype: Request; -} - -export interface Request extends Body { - readonly cache?: RequestCache; - readonly credentials?: RequestCredentials; - readonly destination?: RequestDestination; - readonly headers: Headers; - readonly integrity?: string; - readonly isHistoryNavigation?: boolean; - readonly isReloadNavigation?: boolean; - readonly keepalive?: boolean; - readonly method: string; - readonly mode?: RequestMode; - readonly redirect?: RequestRedirect; - readonly referrer?: string; - readonly referrerPolicy?: ReferrerPolicy; - readonly signal?: AbortSignal; - readonly url: string; - clone(): Request; -} - -export interface Response extends Body { - readonly headers: Headers; - readonly ok: boolean; - readonly redirected: boolean; - readonly status: number; - readonly statusText: string; - readonly trailer: Promise; - readonly type: ResponseType; - readonly url: string; - clone(): Response; -} - -export interface DOMStringList { - readonly length: number; - contains(string: string): boolean; - item(index: number): string | null; - [index: number]: string; -} - -export interface Location { - readonly ancestorOrigins: DOMStringList; - hash: string; - host: string; - hostname: string; - href: string; - toString(): string; - readonly origin: string; - pathname: string; - port: string; - protocol: string; - search: string; - assign(url: string): void; - reload(): void; - replace(url: string): void; -} - -export interface URL { - hash: string; - host: string; - hostname: string; - href: string; - toString(): string; - readonly origin: string; - password: string; - pathname: string; - port: string; - protocol: string; - search: string; - readonly searchParams: URLSearchParams; - username: string; - toJSON(): string; -} - -export interface URLSearchParams { - /** - * Appends a specified key/value pair as a new search parameter. - */ - append(name: string, value: string): void; - /** - * Deletes the given search parameter, and its associated value, from the list of all search parameters. - */ - delete(name: string): void; - /** - * Returns the first value associated to the given search parameter. - */ - get(name: string): string | null; - /** - * Returns all the values association with a given search parameter. - */ - getAll(name: string): string[]; - /** - * Returns a Boolean indicating if such a search parameter exists. - */ - has(name: string): boolean; - /** - * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. - */ - set(name: string, value: string): void; - sort(): void; - /** - * Returns a string containing a query string suitable for use in a URL. Does not include the question mark. - */ - toString(): string; - forEach( - callbackfn: (value: string, key: string, parent: URLSearchParams) => void, - thisArg?: any - ): void; - - [Symbol.iterator](): IterableIterator<[string, string]>; - /** - * Returns an array of key, value pairs for every entry in the search params. - */ - entries(): IterableIterator<[string, string]>; - /** - * Returns a list of keys in the search params. - */ - keys(): IterableIterator; - /** - * Returns a list of values in the search params. - */ - values(): IterableIterator; -} diff --git a/cli/js/web/dom_util.ts b/cli/js/web/dom_util.ts index 40a8c618f..d12593e8e 100644 --- a/cli/js/web/dom_util.ts +++ b/cli/js/web/dom_util.ts @@ -1,6 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -// Utility functions for DOM nodes -import * as domTypes from "./dom_types.ts"; + +import * as domTypes from "./dom_types.d.ts"; export function getDOMStringList(arr: string[]): domTypes.DOMStringList { Object.defineProperties(arr, { @@ -16,87 +16,5 @@ export function getDOMStringList(arr: string[]): domTypes.DOMStringList { }, }, }); - return (arr as unknown) as domTypes.DOMStringList; -} - -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[domTypes.eventTargetNodeType] === - domTypes.NodeType.DOCUMENT_FRAGMENT_NODE && - nodeImpl[domTypes.eventTargetHost] != null - ); -} - -export function isSlotable(nodeImpl: domTypes.EventTarget | null): boolean { - return Boolean( - nodeImpl && - (nodeImpl[domTypes.eventTargetNodeType] === - domTypes.NodeType.ELEMENT_NODE || - nodeImpl[domTypes.eventTargetNodeType] === 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[domTypes.eventTargetHost]; - } else { - node = null; // domSymbolTree.parent(node); - } - } - - return false; -} - -export function getRoot( - node: domTypes.EventTarget | null -): domTypes.EventTarget | null { - const 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[domTypes.eventTargetHost]; - } - } + return arr as string[] & domTypes.DOMStringList; } diff --git a/cli/js/web/event.ts b/cli/js/web/event.ts index b063efa60..b57c0f901 100644 --- a/cli/js/web/event.ts +++ b/cli/js/web/event.ts @@ -1,45 +1,153 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; -import { getPrivateValue, requiredArguments } from "./util.ts"; -// WeakMaps are recommended for private attributes (see MDN link below) -// https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Add-on_SDK/Guides/Contributor_s_Guide/Private_Properties#Using_WeakMaps -export const eventAttributes = new WeakMap(); +import * as domTypes from "./dom_types.d.ts"; +import { defineEnumerableProps, requiredArguments } from "./util.ts"; +import { assert } from "../util.ts"; + +/** Stores a non-accessible view of the event path which is used internally in + * the logic for determining the path of an event. */ +export interface EventPath { + item: EventTarget; + itemInShadowTree: boolean; + relatedTarget: EventTarget | null; + rootOfClosedTree: boolean; + slotInClosedTree: boolean; + target: EventTarget | null; + touchTargetList: EventTarget[]; +} + +interface EventAttributes { + type: string; + bubbles: boolean; + cancelable: boolean; + composed: boolean; + currentTarget: EventTarget | null; + eventPhase: number; + target: EventTarget | null; + timeStamp: number; +} + +interface EventData { + dispatched: boolean; + inPassiveListener: boolean; + isTrusted: boolean; + path: EventPath[]; + stopImmediatePropagation: boolean; +} + +const eventData = new WeakMap(); + +// accessors for non runtime visible data + +export function getDispatched(event: Event): boolean { + return Boolean(eventData.get(event)?.dispatched); +} + +export function getPath(event: Event): EventPath[] { + return eventData.get(event)?.path ?? []; +} + +export function getStopImmediatePropagation(event: Event): boolean { + return Boolean(eventData.get(event)?.stopImmediatePropagation); +} + +export function setCurrentTarget( + event: Event, + value: EventTarget | null +): void { + (event as EventImpl).currentTarget = value; +} + +export function setDispatched(event: Event, value: boolean): void { + const data = eventData.get(event as Event); + if (data) { + data.dispatched = value; + } +} + +export function setEventPhase(event: Event, value: number): void { + (event as EventImpl).eventPhase = value; +} + +export function setInPassiveListener(event: Event, value: boolean): void { + const data = eventData.get(event as Event); + if (data) { + data.inPassiveListener = value; + } +} + +export function setPath(event: Event, value: EventPath[]): void { + const data = eventData.get(event as Event); + if (data) { + data.path = value; + } +} + +export function setRelatedTarget( + event: T, + value: EventTarget | null +): void { + if ("relatedTarget" in event) { + (event as T & { + relatedTarget: EventTarget | null; + }).relatedTarget = value; + } +} + +export function setTarget(event: Event, value: EventTarget | null): void { + (event as EventImpl).target = value; +} + +export function setStopImmediatePropagation( + event: Event, + value: boolean +): void { + const data = eventData.get(event as Event); + if (data) { + data.stopImmediatePropagation = value; + } +} + +// Type guards that widen the event type + +export function hasRelatedTarget( + event: Event +): event is domTypes.FocusEvent | domTypes.MouseEvent { + return "relatedTarget" in event; +} function isTrusted(this: Event): boolean { - return getPrivateValue(this, eventAttributes, "isTrusted"); + return eventData.get(this)!.isTrusted; } -export class Event implements domTypes.Event { +export class EventImpl implements Event { // The default value is `false`. // Use `defineProperty` to define on each instance, NOT on the prototype. isTrusted!: boolean; - // Each event has the following associated flags - private _canceledFlag = false; - private _dispatchedFlag = false; - private _initializedFlag = false; - private _inPassiveListenerFlag = false; - private _stopImmediatePropagationFlag = false; - private _stopPropagationFlag = false; - - // Property for objects on which listeners will be invoked - private _path: domTypes.EventPath[] = []; - - constructor(type: string, eventInitDict: domTypes.EventInit = {}) { + + #canceledFlag = false; + #stopPropagationFlag = false; + #attributes: EventAttributes; + + constructor(type: string, eventInitDict: EventInit = {}) { requiredArguments("Event", arguments.length, 1); type = String(type); - this._initializedFlag = true; - eventAttributes.set(this, { + this.#attributes = { type, - bubbles: eventInitDict.bubbles || false, - cancelable: eventInitDict.cancelable || false, - composed: eventInitDict.composed || false, + bubbles: eventInitDict.bubbles ?? false, + cancelable: eventInitDict.cancelable ?? false, + composed: eventInitDict.composed ?? false, currentTarget: null, - eventPhase: domTypes.EventPhase.NONE, - isTrusted: false, - relatedTarget: null, + eventPhase: Event.NONE, target: null, timeStamp: Date.now(), + }; + eventData.set(this, { + dispatched: false, + inPassiveListener: false, + isTrusted: false, + path: [], + stopImmediatePropagation: false, }); Reflect.defineProperty(this, "isTrusted", { enumerable: true, @@ -48,151 +156,100 @@ export class Event implements domTypes.Event { } get bubbles(): boolean { - return getPrivateValue(this, eventAttributes, "bubbles"); + return this.#attributes.bubbles; } get cancelBubble(): boolean { - return this._stopPropagationFlag; + return this.#stopPropagationFlag; } set cancelBubble(value: boolean) { - this._stopPropagationFlag = value; - } - - get cancelBubbleImmediately(): boolean { - return this._stopImmediatePropagationFlag; - } - - set cancelBubbleImmediately(value: boolean) { - this._stopImmediatePropagationFlag = value; + this.#stopPropagationFlag = value; } get cancelable(): boolean { - return getPrivateValue(this, eventAttributes, "cancelable"); + return this.#attributes.cancelable; } get composed(): boolean { - return getPrivateValue(this, eventAttributes, "composed"); + return this.#attributes.composed; } - get currentTarget(): domTypes.EventTarget { - return getPrivateValue(this, eventAttributes, "currentTarget"); + get currentTarget(): EventTarget | null { + return this.#attributes.currentTarget; } - set currentTarget(value: domTypes.EventTarget) { - eventAttributes.set(this, { + set currentTarget(value: EventTarget | null) { + this.#attributes = { 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; - } - - set dispatched(value: boolean) { - this._dispatchedFlag = value; + return this.#canceledFlag; } get eventPhase(): number { - return getPrivateValue(this, eventAttributes, "eventPhase"); + return this.#attributes.eventPhase; } set eventPhase(value: number) { - eventAttributes.set(this, { + this.#attributes = { 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 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, - }); + return true; } - get target(): domTypes.EventTarget { - return getPrivateValue(this, eventAttributes, "target"); + get target(): EventTarget | null { + return this.#attributes.target; } - set target(value: domTypes.EventTarget) { - eventAttributes.set(this, { + set target(value: EventTarget | null) { + this.#attributes = { 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"); + get timeStamp(): number { + return this.#attributes.timeStamp; } get type(): string { - return getPrivateValue(this, eventAttributes, "type"); + return this.#attributes.type; } - composedPath(): domTypes.EventPath[] { - if (this._path.length === 0) { + composedPath(): EventTarget[] { + const path = eventData.get(this)!.path; + if (path.length === 0) { return []; } - const composedPath: domTypes.EventPath[] = [ + assert(this.currentTarget); + const composedPath: EventPath[] = [ { item: this.currentTarget, itemInShadowTree: false, @@ -207,8 +264,8 @@ export class Event implements domTypes.Event { let currentTargetIndex = 0; let currentTargetHiddenSubtreeLevel = 0; - for (let index = this._path.length - 1; index >= 0; index--) { - const { item, rootOfClosedTree, slotInClosedTree } = this._path[index]; + for (let index = path.length - 1; index >= 0; index--) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; if (rootOfClosedTree) { currentTargetHiddenSubtreeLevel++; @@ -228,7 +285,7 @@ export class Event implements domTypes.Event { let maxHiddenLevel = currentTargetHiddenSubtreeLevel; for (let i = currentTargetIndex - 1; i >= 0; i--) { - const { item, rootOfClosedTree, slotInClosedTree } = this._path[i]; + const { item, rootOfClosedTree, slotInClosedTree } = path[i]; if (rootOfClosedTree) { currentHiddenLevel++; @@ -258,12 +315,8 @@ export class Event implements domTypes.Event { currentHiddenLevel = currentTargetHiddenSubtreeLevel; maxHiddenLevel = currentTargetHiddenSubtreeLevel; - for ( - let index = currentTargetIndex + 1; - index < this._path.length; - index++ - ) { - const { item, rootOfClosedTree, slotInClosedTree } = this._path[index]; + for (let index = currentTargetIndex + 1; index < path.length; index++) { + const { item, rootOfClosedTree, slotInClosedTree } = path[index]; if (slotInClosedTree) { currentHiddenLevel++; @@ -289,35 +342,65 @@ export class Event implements domTypes.Event { } } } - - return composedPath; + return composedPath.map((p) => p.item); } preventDefault(): void { - if (this.cancelable && !this._inPassiveListenerFlag) { - this._canceledFlag = true; + if (this.cancelable && !eventData.get(this)!.inPassiveListener) { + this.#canceledFlag = true; } } stopPropagation(): void { - this._stopPropagationFlag = true; + this.#stopPropagationFlag = true; } stopImmediatePropagation(): void { - this._stopPropagationFlag = true; - this._stopImmediatePropagationFlag = true; + this.#stopPropagationFlag = true; + eventData.get(this)!.stopImmediatePropagation = true; + } + + get NONE(): number { + return Event.NONE; + } + + get CAPTURING_PHASE(): number { + return Event.CAPTURING_PHASE; + } + + get AT_TARGET(): number { + return Event.AT_TARGET; + } + + get BUBBLING_PHASE(): number { + return Event.BUBBLING_PHASE; + } + + static get NONE(): number { + return 0; + } + + static get CAPTURING_PHASE(): number { + return 1; + } + + static get AT_TARGET(): number { + return 2; + } + + static get BUBBLING_PHASE(): number { + return 3; } } -Reflect.defineProperty(Event.prototype, "bubbles", { enumerable: true }); -Reflect.defineProperty(Event.prototype, "cancelable", { enumerable: true }); -Reflect.defineProperty(Event.prototype, "composed", { enumerable: true }); -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, "target", { enumerable: true }); -Reflect.defineProperty(Event.prototype, "timeStamp", { enumerable: true }); -Reflect.defineProperty(Event.prototype, "type", { enumerable: true }); +defineEnumerableProps(EventImpl, [ + "bubbles", + "cancelable", + "composed", + "currentTarget", + "defaultPrevented", + "eventPhase", + "target", + "timeStamp", + "type", +]); diff --git a/cli/js/web/event_target.ts b/cli/js/web/event_target.ts index 605504a3a..1a560dfbe 100644 --- a/cli/js/web/event_target.ts +++ b/cli/js/web/event_target.ts @@ -1,497 +1,588 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; -import { hasOwnProperty, requiredArguments } from "./util.ts"; + +// 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 * as domTypes from "./dom_types.d.ts"; import { - getRoot, - isNode, - isShadowRoot, - isShadowInclusiveAncestor, - isSlotable, - retarget, -} from "./dom_util.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; + 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; } -export const eventTargetAssignedSlot: unique symbol = Symbol(); -export const eventTargetHasActivationBehavior: unique symbol = Symbol(); +function getRoot(eventTarget: EventTarget): EventTarget | null { + return isNode(eventTarget) + ? eventTarget.getRootNode({ composed: true }) + : null; +} -export class EventTarget implements domTypes.EventTarget { - public [domTypes.eventTargetHost]: domTypes.EventTarget | null = null; - public [domTypes.eventTargetListeners]: { - [type in string]: domTypes.EventTargetListener[]; - } = {}; - public [domTypes.eventTargetMode] = ""; - public [domTypes.eventTargetNodeType]: domTypes.NodeType = - domTypes.NodeType.DOCUMENT_FRAGMENT_NODE; - private [eventTargetAssignedSlot] = false; - private [eventTargetHasActivationBehavior] = false; +function isNode( + eventTarget: T | null +): eventTarget is T & domTypes.Node { + return Boolean(eventTarget && "nodeType" in eventTarget); +} - public addEventListener( - type: string, - callback: domTypes.EventListenerOrEventListenerObject | null, - options?: domTypes.AddEventListenerOptions | boolean - ): void { - const this_ = this || globalThis; +// 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; + } - requiredArguments("EventTarget.addEventListener", arguments.length, 2); - const normalizedOptions: domTypes.AddEventListenerOptions = eventTargetHelpers.normalizeAddEventHandlerOptions( - options + 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( + 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 ); - if (callback === null) { - return; + const isActivationEvent = eventImpl.type === "click"; + + if (isActivationEvent && getHasActivationBehavior(targetImpl)) { + activationTarget = targetImpl; } - const listeners = this_[domTypes.eventTargetListeners]; + let slotInClosedTree = false; + let slotable = + isSlotable(targetImpl) && getAssignedSlot(targetImpl) ? targetImpl : null; + let parent = getParent(targetImpl); - if (!hasOwnProperty(listeners, type)) { - listeners[type] = []; - } + // 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); - 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 + isNode(parent) && + isShadowInclusiveAncestor(getRoot(targetImpl), parent) ) { - return; + 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 + ); } - } - listeners[type].push({ - callback, - options: normalizedOptions, - }); - } + if (parent !== null) { + parent = getParent(parent); + } - public removeEventListener( - type: string, - callback: domTypes.EventListenerOrEventListenerObject | null, - options?: domTypes.EventListenerOptions | boolean - ): void { - const this_ = this || globalThis; + slotInClosedTree = false; + } - 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 - ); + 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]; - const normalizedOptions: domTypes.EventListenerOptions = eventTargetHelpers.normalizeEventHandlerOptions( - options - ); + clearTargets = + (isNode(clearTargetsTuple.target) && + isShadowRoot(getRoot(clearTargetsTuple.target))) || + (isNode(clearTargetsTuple.relatedTarget) && + isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - if (callback === null) { - // Optimization, not in the spec. - return; - } + setEventPhase(eventImpl, Event.CAPTURING_PHASE); - if (!listeners[type]) { - return; + 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 < listeners[type].length; ++i) { - const listener = listeners[type][i]; + 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 ( - ((typeof listener.options === "boolean" && - listener.options === normalizedOptions.capture) || - (typeof listener.options === "object" && - listener.options.capture === normalizedOptions.capture)) && - listener.callback === callback + (eventImpl.eventPhase === Event.BUBBLING_PHASE && eventImpl.bubbles) || + eventImpl.eventPhase === Event.AT_TARGET ) { - listeners[type].splice(i, 1); - break; + invokeEventListeners(tuple, eventImpl); } } } - public dispatchEvent(event: domTypes.Event): boolean { - const this_ = this || globalThis; + setEventPhase(eventImpl, Event.NONE); + setCurrentTarget(eventImpl, null); + setPath(eventImpl, []); + setDispatched(eventImpl, false); + eventImpl.cancelBubble = false; + setStopImmediatePropagation(eventImpl, false); - requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); - const listeners = this_[domTypes.eventTargetListeners]; - if (!hasOwnProperty(listeners, event.type)) { - return true; - } + if (clearTargets) { + setTarget(eventImpl, null); + setRelatedTarget(eventImpl, null); + } - if (event.dispatched || !event.initialized) { - // TODO(bartlomieju): very likely that different error - // should be thrown here (DOMException?) - throw new TypeError("Tried to dispatch an uninitialized event"); - } + // TODO: invoke activation targets if HTML nodes will be implemented + // if (activationTarget !== null) { + // if (!eventImpl.defaultPrevented) { + // activationTarget._activationBehavior(); + // } + // } - if (event.eventPhase !== domTypes.EventPhase.NONE) { - // TODO(bartlomieju): very likely that different error - // should be thrown here (DOMException?) - throw new TypeError("Tried to dispatch a dispatching event"); - } + return !eventImpl.defaultPrevented; +} - return eventTargetHelpers.dispatch(this_, event); - } +/** 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 +): boolean { + let found = false; - get [Symbol.toStringTag](): string { - return "EventTarget"; + const { type } = eventImpl; + + if (!targetListeners || !targetListeners[type]) { + return found; } -} -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; + // 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; + } - eventImpl.dispatched = true; + // Check if the event listener has been removed since the listeners has been cloned. + if (!targetListeners[type].includes(listener)) { + continue; + } - targetOverride = targetOverride || targetImpl; - let relatedTarget = retarget(eventImpl.relatedTarget, targetImpl); + found = true; if ( - targetImpl !== relatedTarget || - targetImpl === eventImpl.relatedTarget + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) ) { - const touchTargets: domTypes.EventTarget[] = []; - - eventTargetHelpers.appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false - ); - - const isActivationEvent = eventImpl.type === "click"; + continue; + } - if (isActivationEvent && targetImpl[eventTargetHasActivationBehavior]) { - activationTarget = targetImpl; - } + if (once) { + targetListeners[type].splice(targetListeners[type].indexOf(listener), 1); + } - 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; - } - } + if (passive) { + setInPassiveListener(eventImpl, true); + } - relatedTarget = retarget(eventImpl.relatedTarget, parent); + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); + } + } else { + listener.callback.call(eventImpl.currentTarget, eventImpl); + } - 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 - ); - } + setInPassiveListener(eventImpl, false); - if (parent !== null) { - parent = getEventTargetParent(parent, eventImpl); - } + if (getStopImmediatePropagation(eventImpl)) { + return found; + } + } - slotInClosedTree = false; - } + return found; +} - 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]; +/** 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; + } + } - clearTargets = - (isNode(clearTargetsTuple.target) && - isShadowRoot(getRoot(clearTargetsTuple.target))) || - (isNode(clearTargetsTuple.relatedTarget) && - isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); + setRelatedTarget(eventImpl, tuple.relatedTarget); - eventImpl.eventPhase = domTypes.EventPhase.CAPTURING_PHASE; + if (eventImpl.cancelBubble) { + return; + } - for (let i = eventImpl.path.length - 1; i >= 0; --i) { - const tuple = eventImpl.path[i]; + setCurrentTarget(eventImpl, tuple.item); - if (tuple.target === null) { - eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl); - } - } + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); +} - for (let i = 0; i < eventImpl.path.length; i++) { - const tuple = eventImpl.path[i]; +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; + } +} - if (tuple.target !== null) { - eventImpl.eventPhase = domTypes.EventPhase.AT_TARGET; - } else { - eventImpl.eventPhase = domTypes.EventPhase.BUBBLING_PHASE; - } +function normalizeEventHandlerOptions( + options: boolean | EventListenerOptions | undefined +): EventListenerOptions { + if (typeof options === "boolean" || typeof options === "undefined") { + return { + capture: Boolean(options), + }; + } else { + return options; + } +} - if ( - (eventImpl.eventPhase === domTypes.EventPhase.BUBBLING_PHASE && - eventImpl.bubbles) || - eventImpl.eventPhase === domTypes.EventPhase.AT_TARGET - ) { - eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl); - } - } +/** 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; } - eventImpl.eventPhase = domTypes.EventPhase.NONE; + const aRoot = a.getRootNode(); - eventImpl.currentTarget = null; - eventImpl.path = []; - eventImpl.dispatched = false; - eventImpl.cancelBubble = false; - eventImpl.cancelBubbleImmediately = false; + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { + return a; + } - if (clearTargets) { - eventImpl.target = null; - eventImpl.relatedTarget = null; + a = getHost(aRoot); } + } +} - // 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; - } - } +// 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; + mode: string; +} - eventImpl.relatedTarget = tuple.relatedTarget; +interface Listener { + callback: EventListenerOrEventListenerObject; + options: AddEventListenerOptions; +} - if (eventImpl.cancelBubble) { - return; - } +// Accessors for non-public data - eventImpl.currentTarget = tuple.item; +export const eventTargetData = new WeakMap(); - eventTargetHelpers.innerInvokeEventListeners( - targetImpl, - eventImpl, - tuple.item[domTypes.eventTargetListeners] - ); - }, +function getAssignedSlot(target: EventTarget): boolean { + return Boolean(eventTargetData.get(target as EventTarget)?.assignedSlot); +} - // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke - innerInvokeEventListeners( - targetImpl: EventTarget, - eventImpl: domTypes.Event, - targetListeners: { [type in string]: domTypes.EventTargetListener[] } - ): boolean { - let found = false; +function getHasActivationBehavior(target: EventTarget): boolean { + return Boolean( + eventTargetData.get(target as EventTarget)?.hasActivationBehavior + ); +} - const { type } = eventImpl; +function getHost(target: EventTarget): EventTarget | null { + return eventTargetData.get(target as EventTarget)?.host ?? null; +} - if (!targetListeners || !targetListeners[type]) { - return found; - } +function getListeners(target: EventTarget): Record { + return eventTargetData.get(target as EventTarget)?.listeners ?? {}; +} - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = targetListeners[type].slice(); +function getMode(target: EventTarget): string | null { + return eventTargetData.get(target as EventTarget)?.mode ?? null; +} - for (let i = 0; i < handlers.length; i++) { - const listener = handlers[i]; +export function getDefaultTargetData(): Readonly { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: Object.create(null), + mode: "", + }; +} - 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; - } +export class EventTargetImpl implements EventTarget { + constructor() { + eventTargetData.set(this, getDefaultTargetData()); + } - // Check if the event listener has been removed since the listeners has been cloned. - if (!targetListeners[type].includes(listener)) { - continue; - } + 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)!; - found = true; + if (!(type in listeners)) { + listeners[type] = []; + } + for (const listener of listeners[type]) { if ( - (eventImpl.eventPhase === domTypes.EventPhase.CAPTURING_PHASE && - !capture) || - (eventImpl.eventPhase === domTypes.EventPhase.BUBBLING_PHASE && capture) + ((typeof listener.options === "boolean" && + listener.options === options.capture) || + (typeof listener.options === "object" && + listener.options.capture === options.capture)) && + listener.callback === callback ) { - continue; + return; } + } - if (once) { - targetListeners[type].splice( - targetListeners[type].indexOf(listener), - 1 - ); - } + listeners[type].push({ callback, options }); + } - if (passive) { - eventImpl.inPassiveListener = true; - } + public removeEventListener( + type: string, + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ): void { + requiredArguments("EventTarget.removeEventListener", arguments.length, 2); - try { - if (typeof listener.callback === "object") { - if (typeof listener.callback.handleEvent === "function") { - listener.callback.handleEvent(eventImpl); - } - } else { - listener.callback.call(eventImpl.currentTarget, eventImpl); - } - } catch (error) { - // TODO(bartlomieju): very likely that different error - // should be thrown here (DOMException?) - throw new Error(error.message); - } + 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; + } - eventImpl.inPassiveListener = false; + options = normalizeEventHandlerOptions(options); - if (eventImpl.cancelBubbleImmediately) { - return found; + 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; } } + } - 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; + 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; } - }, - normalizeEventHandlerOptions( - options: boolean | domTypes.EventListenerOptions | undefined - ): domTypes.EventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - const returnValue: domTypes.EventListenerOptions = { - capture: Boolean(options), - }; + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); + } - return returnValue; - } else { - return options; + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - }, - - // 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, - }); - }, -}; - -Reflect.defineProperty(EventTarget.prototype, "addEventListener", { - enumerable: true, -}); -Reflect.defineProperty(EventTarget.prototype, "removeEventListener", { - enumerable: true, -}); -Reflect.defineProperty(EventTarget.prototype, "dispatchEvent", { - enumerable: true, -}); + + return dispatch(self, event); + } + + get [Symbol.toStringTag](): string { + return "EventTarget"; + } + + protected getParent(_event: Event): EventTarget | null { + return null; + } +} + +defineEnumerableProps(EventTargetImpl, [ + "addEventListener", + "removeEventListener", + "dispatchEvent", +]); diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts index 26c5ff053..112bae48f 100644 --- a/cli/js/web/fetch.ts +++ b/cli/js/web/fetch.ts @@ -1,7 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { assert, createResolvable, notImplemented } from "../util.ts"; import { isTypedArray } from "./util.ts"; -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { TextDecoder, TextEncoder } from "./text_encoding.ts"; import { DenoBlob, bytesSymbol as blobBytesSymbol } from "./blob.ts"; import { Headers } from "./headers.ts"; diff --git a/cli/js/web/form_data.ts b/cli/js/web/form_data.ts index 4517c2a33..42f419403 100644 --- a/cli/js/web/form_data.ts +++ b/cli/js/web/form_data.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import * as blob from "./blob.ts"; import * as domFile from "./dom_file.ts"; import { DomIterableMixin } from "./dom_iterable.ts"; diff --git a/cli/js/web/headers.ts b/cli/js/web/headers.ts index e1d81393d..1f750faa3 100644 --- a/cli/js/web/headers.ts +++ b/cli/js/web/headers.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { DomIterableMixin } from "./dom_iterable.ts"; import { requiredArguments } from "./util.ts"; import { customInspect } from "./console.ts"; diff --git a/cli/js/web/location.ts b/cli/js/web/location.ts index 862a4c1e4..9b59842b7 100644 --- a/cli/js/web/location.ts +++ b/cli/js/web/location.ts @@ -1,7 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { URL } from "./url.ts"; import { notImplemented } from "../util.ts"; -import { DOMStringList, Location } from "./dom_types.ts"; +import { DOMStringList, Location } from "./dom_types.d.ts"; import { getDOMStringList } from "./dom_util.ts"; export class LocationImpl implements Location { diff --git a/cli/js/web/request.ts b/cli/js/web/request.ts index 96edaf59e..6a5d92a2b 100644 --- a/cli/js/web/request.ts +++ b/cli/js/web/request.ts @@ -1,7 +1,7 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import * as headers from "./headers.ts"; import * as body from "./body.ts"; -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import * as streams from "./streams/mod.ts"; const { Headers } = headers; diff --git a/cli/js/web/streams/pipe-to.ts b/cli/js/web/streams/pipe-to.ts index 1d5579217..17fb8e5bd 100644 --- a/cli/js/web/streams/pipe-to.ts +++ b/cli/js/web/streams/pipe-to.ts @@ -18,7 +18,7 @@ // import { ReadableStreamDefaultReader } from "./readable-stream-default-reader.ts"; // import { WritableStreamDefaultWriter } from "./writable-stream-default-writer.ts"; -// import { PipeOptions } from "../dom_types.ts"; +// import { PipeOptions } from "../dom_types.d.ts"; // import { Err } from "../errors.ts"; // // add a wrapper to handle falsy rejections diff --git a/cli/js/web/streams/readable-byte-stream-controller.ts b/cli/js/web/streams/readable-byte-stream-controller.ts index 1b473b77a..19f259484 100644 --- a/cli/js/web/streams/readable-byte-stream-controller.ts +++ b/cli/js/web/streams/readable-byte-stream-controller.ts @@ -9,7 +9,7 @@ import * as q from "./queue-mixin.ts"; import * as shared from "./shared-internals.ts"; import { ReadableStreamBYOBRequest } from "./readable-stream-byob-request.ts"; import { Queue } from "./queue.ts"; -import { UnderlyingByteSource } from "../dom_types.ts"; +import { UnderlyingByteSource } from "../dom_types.d.ts"; export class ReadableByteStreamController implements rs.SDReadableByteStreamController { diff --git a/cli/js/web/streams/readable-internals.ts b/cli/js/web/streams/readable-internals.ts index f46c79850..571ce50ed 100644 --- a/cli/js/web/streams/readable-internals.ts +++ b/cli/js/web/streams/readable-internals.ts @@ -11,7 +11,7 @@ import { QueuingStrategySizeCallback, UnderlyingSource, UnderlyingByteSource, -} from "../dom_types.ts"; +} from "../dom_types.d.ts"; // ReadableStreamDefaultController export const controlledReadableStream_ = Symbol("controlledReadableStream_"); diff --git a/cli/js/web/streams/readable-stream-default-controller.ts b/cli/js/web/streams/readable-stream-default-controller.ts index d33226a9b..5d07dba53 100644 --- a/cli/js/web/streams/readable-stream-default-controller.ts +++ b/cli/js/web/streams/readable-stream-default-controller.ts @@ -8,7 +8,10 @@ import * as rs from "./readable-internals.ts"; import * as shared from "./shared-internals.ts"; import * as q from "./queue-mixin.ts"; import { Queue } from "./queue.ts"; -import { QueuingStrategySizeCallback, UnderlyingSource } from "../dom_types.ts"; +import { + QueuingStrategySizeCallback, + UnderlyingSource, +} from "../dom_types.d.ts"; export class ReadableStreamDefaultController implements rs.SDReadableStreamDefaultController { diff --git a/cli/js/web/streams/readable-stream.ts b/cli/js/web/streams/readable-stream.ts index 50753260d..a003f0a17 100644 --- a/cli/js/web/streams/readable-stream.ts +++ b/cli/js/web/streams/readable-stream.ts @@ -12,7 +12,7 @@ import { QueuingStrategySizeCallback, UnderlyingSource, UnderlyingByteSource, -} from "../dom_types.ts"; +} from "../dom_types.d.ts"; import { ReadableStreamDefaultController, diff --git a/cli/js/web/streams/shared-internals.ts b/cli/js/web/streams/shared-internals.ts index 7b0de2274..db0a082f4 100644 --- a/cli/js/web/streams/shared-internals.ts +++ b/cli/js/web/streams/shared-internals.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // TODO don't disable this warning -import { AbortSignal, QueuingStrategySizeCallback } from "../dom_types.ts"; +import { AbortSignal, QueuingStrategySizeCallback } from "../dom_types.d.ts"; // common stream fields diff --git a/cli/js/web/streams/strategies.ts b/cli/js/web/streams/strategies.ts index 98fe0f91a..4c5b402c5 100644 --- a/cli/js/web/streams/strategies.ts +++ b/cli/js/web/streams/strategies.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // TODO reenable this lint here -import { QueuingStrategy } from "../dom_types.ts"; +import { QueuingStrategy } from "../dom_types.d.ts"; export class ByteLengthQueuingStrategy implements QueuingStrategy { diff --git a/cli/js/web/streams/transform-internals.ts b/cli/js/web/streams/transform-internals.ts index 4c5e3657d..9c17db8f6 100644 --- a/cli/js/web/streams/transform-internals.ts +++ b/cli/js/web/streams/transform-internals.ts @@ -19,7 +19,7 @@ // import { createReadableStream } from "./readable-stream.ts"; // import { createWritableStream } from "./writable-stream.ts"; -// import { QueuingStrategy, QueuingStrategySizeCallback } from "../dom_types.ts"; +// import { QueuingStrategy, QueuingStrategySizeCallback } from "../dom_types.d.ts"; // export const state_ = Symbol("transformState_"); // export const backpressure_ = Symbol("backpressure_"); diff --git a/cli/js/web/streams/transform-stream.ts b/cli/js/web/streams/transform-stream.ts index 090f78135..c27430db1 100644 --- a/cli/js/web/streams/transform-stream.ts +++ b/cli/js/web/streams/transform-stream.ts @@ -17,7 +17,7 @@ // import * as ts from "./transform-internals.ts"; // import * as shared from "./shared-internals.ts"; // import { TransformStreamDefaultController } from "./transform-stream-default-controller.ts"; -// import { QueuingStrategy } from "../dom_types.ts"; +// import { QueuingStrategy } from "../dom_types.d.ts"; // export class TransformStream { // [ts.backpressure_]: boolean | undefined; // Whether there was backpressure on [[readable]] the last time it was observed diff --git a/cli/js/web/streams/writable-internals.ts b/cli/js/web/streams/writable-internals.ts index 78bb19a28..4d442d0f5 100644 --- a/cli/js/web/streams/writable-internals.ts +++ b/cli/js/web/streams/writable-internals.ts @@ -15,7 +15,7 @@ // import * as shared from "./shared-internals.ts"; // import * as q from "./queue-mixin.ts"; -// import { QueuingStrategy, QueuingStrategySizeCallback } from "../dom_types.ts"; +// import { QueuingStrategy, QueuingStrategySizeCallback } from "../dom_types.d.ts"; // export const backpressure_ = Symbol("backpressure_"); // export const closeRequest_ = Symbol("closeRequest_"); diff --git a/cli/js/web/streams/writable-stream-default-controller.ts b/cli/js/web/streams/writable-stream-default-controller.ts index 57ffe08fd..181edede8 100644 --- a/cli/js/web/streams/writable-stream-default-controller.ts +++ b/cli/js/web/streams/writable-stream-default-controller.ts @@ -16,7 +16,7 @@ // import * as shared from "./shared-internals.ts"; // import * as q from "./queue-mixin.ts"; // import { Queue } from "./queue.ts"; -// import { QueuingStrategySizeCallback } from "../dom_types.ts"; +// import { QueuingStrategySizeCallback } from "../dom_types.d.ts"; // export class WritableStreamDefaultController // implements ws.WritableStreamDefaultController { diff --git a/cli/js/web/streams/writable-stream.ts b/cli/js/web/streams/writable-stream.ts index a6131c5d0..f231d78dc 100644 --- a/cli/js/web/streams/writable-stream.ts +++ b/cli/js/web/streams/writable-stream.ts @@ -16,7 +16,7 @@ // setUpWritableStreamDefaultControllerFromUnderlyingSink // } from "./writable-stream-default-controller.ts"; // import { WritableStreamDefaultWriter } from "./writable-stream-default-writer.ts"; -// import { QueuingStrategy, QueuingStrategySizeCallback } from "../dom_types.ts"; +// import { QueuingStrategy, QueuingStrategySizeCallback } from "../dom_types.d.ts"; // export class WritableStream { // [shared.state_]: ws.WritableStreamState; diff --git a/cli/js/web/text_encoding.ts b/cli/js/web/text_encoding.ts index 6fd498e59..b0630bf95 100644 --- a/cli/js/web/text_encoding.ts +++ b/cli/js/web/text_encoding.ts @@ -25,7 +25,7 @@ import * as base64 from "./base64.ts"; import { decodeUtf8 } from "./decode_utf8.ts"; -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { core } from "../core.ts"; const CONTINUE = null; @@ -348,7 +348,7 @@ encodingIndexes.set("windows-1252", [ 252, 253, 254, - 255 + 255, ]); for (const [key, index] of encodingIndexes) { decoders.set( diff --git a/cli/js/web/url.ts b/cli/js/web/url.ts index 2b6a0d341..1a6f4eb9d 100644 --- a/cli/js/web/url.ts +++ b/cli/js/web/url.ts @@ -1,6 +1,6 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. import { customInspect } from "./console.ts"; -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { urls, URLSearchParams } from "./url_search_params.ts"; import { getRandomValues } from "../ops/get_random_values.ts"; diff --git a/cli/js/web/url_search_params.ts b/cli/js/web/url_search_params.ts index aad59bb8c..0e41bdbf2 100644 --- a/cli/js/web/url_search_params.ts +++ b/cli/js/web/url_search_params.ts @@ -1,5 +1,5 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; +import * as domTypes from "./dom_types.d.ts"; import { URL, parts } from "./url.ts"; import { isIterable, requiredArguments } from "./util.ts"; diff --git a/cli/js/web/util.ts b/cli/js/web/util.ts index 2d63b4d60..32e73c443 100644 --- a/cli/js/web/util.ts +++ b/cli/js/web/util.ts @@ -11,6 +11,7 @@ export type TypedArray = | Float32Array | Float64Array; +// @internal export function isTypedArray(x: unknown): x is TypedArray { return ( x instanceof Int8Array || @@ -54,19 +55,8 @@ export function immutableDefine( }); } -// Returns values from a WeakMap to emulate private properties in JavaScript -export function getPrivateValue< - K extends object, - V extends object, - W extends keyof V ->(instance: K, weakMap: WeakMap, key: W): V[W] { - if (weakMap.has(instance)) { - return weakMap.get(instance)![key]; - } - throw new TypeError("Illegal invocation"); -} - -export function hasOwnProperty(obj: T, v: PropertyKey): boolean { +// @internal +export function hasOwnProperty(obj: unknown, v: PropertyKey): boolean { if (obj == null) { return false; } @@ -87,3 +77,19 @@ export function isIterable( typeof ((o as unknown) as Iterable<[P, K]>)[Symbol.iterator] === "function" ); } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +interface GenericConstructor { + prototype: T; +} + +/** A helper function which ensures accessors are enumerable, as they normally + * are not. */ +export function defineEnumerableProps( + Ctor: GenericConstructor, + props: string[] +): void { + for (const prop of props) { + Reflect.defineProperty(Ctor.prototype, prop, { enumerable: true }); + } +} diff --git a/cli/js/web/workers.ts b/cli/js/web/workers.ts index 7a0abbbdb..054c26193 100644 --- a/cli/js/web/workers.ts +++ b/cli/js/web/workers.ts @@ -11,8 +11,8 @@ import { TextDecoder, TextEncoder } from "./text_encoding.ts"; /* import { blobURLMap } from "./web/url.ts"; */ -import { Event } from "./event.ts"; -import { EventTarget } from "./event_target.ts"; +import { EventImpl as Event } from "./event.ts"; +import { EventTargetImpl as EventTarget } from "./event_target.ts"; const encoder = new TextEncoder(); const decoder = new TextDecoder(); -- cgit v1.2.3