diff --git a/cli/js/globals.ts b/cli/js/globals.ts index 0aed3a2523..059a70ee77 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 2027686a90..a1e834cc42 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; } -/** An event which takes place in the DOM. */ -interface Event { - /** - * 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. - */ - 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. - */ - readonly cancelable: boolean; - /** - * 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. - */ - readonly composed: boolean; - /** - * 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. - */ - readonly defaultPrevented: boolean; - /** - * 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. - */ - readonly isTrusted: boolean; - returnValue: boolean; - /** @deprecated */ - readonly srcElement: EventTarget | null; - /** - * 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. - */ - readonly timeStamp: number; - /** - * 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 - * 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. - */ - composedPath(): EventTarget[]; - initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void; - /** - * 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. - */ - 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. - */ - stopImmediatePropagation(): void; - /** - * 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; -} - interface EventInit { bubbles?: boolean; cancelable?: boolean; composed?: boolean; } -declare const Event: { - prototype: Event; - new (type: string, eventInitDict?: EventInit): Event; +/** An event which takes place in the DOM. */ +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. */ + readonly bubbles: 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. */ + readonly cancelable: boolean; + /** 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. */ + readonly composed: boolean; + /** 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 + * cancellation, and false otherwise. */ + readonly defaultPrevented: boolean; + /** 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. */ + readonly isTrusted: boolean; + /** 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. */ + readonly timeStamp: number; + /** 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 + * 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. */ + composedPath(): EventTarget[]; + /** 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. */ + 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. */ + stopImmediatePropagation(): void; + /** 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; +} /** * 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 2b8e6f50fc..6377057d31 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 edb02d2d65..0b0b1f75f4 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 0000000000..2eb7633e10 --- /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 ece4b5dba5..0c4eb4d0dd 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 05a9ed5770..ce3076e58a 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 4cff3d1d87..ba3d6746a8 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 7bdde8e28e..90480c89c0 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 a16f872b95..2f6987592d 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 418b7ea34f..ea76d2c94e 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 0000000000..e2c77d41ca --- /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 cf2a40398e..a3b43dad1f 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 191958f111..fcbca307fc 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.ts b/cli/js/web/dom_types.d.ts similarity index 63% rename from cli/js/web/dom_types.ts rename to cli/js/web/dom_types.d.ts index 94e26846fa..3ac025934c 100644 --- a/cli/js/web/dom_types.ts +++ b/cli/js/web/dom_types.d.ts @@ -23,7 +23,7 @@ export type HeadersInit = | Headers | Array<[string, string]> | Record; -export type URLSearchParamsInit = string | string[][] | Record; + type BodyInit = | Blob | BufferSource @@ -31,7 +31,9 @@ type BodyInit = | URLSearchParams | ReadableStream | string; + export type RequestInfo = Request | string; + type ReferrerPolicy = | "" | "no-referrer" @@ -39,21 +41,12 @@ type ReferrerPolicy = | "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 type EndingType = "transparent" | "native"; export interface BlobPropertyBag { type?: string; @@ -64,67 +57,16 @@ 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 { +export class URLSearchParams { + constructor( + init?: string[][] | Record | string | URLSearchParams + ); append(name: string, value: string): void; delete(name: string): void; get(name: string): string | null; @@ -137,72 +79,229 @@ export interface URLSearchParams extends DomIterable { 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 EventInit { - bubbles?: boolean; - cancelable?: boolean; +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 interface CustomEventInit extends EventInit { - detail?: any; +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; } -export enum EventPhase { - NONE = 0, - CAPTURING_PHASE = 1, - AT_TARGET = 2, - BUBBLING_PHASE = 3, +interface Slotable { + // adjusted: HTMLSlotElement -> Node + readonly assignedSlot: Node | null; } -export interface EventPath { - item: EventTarget; - itemInShadowTree: boolean; - relatedTarget: EventTarget | null; - rootOfClosedTree: boolean; - slotInClosedTree: boolean; - target: EventTarget | null; - touchTargetList: EventTarget[]; +interface ChildNode extends Node { + after(...nodes: Array): void; + before(...nodes: Array): void; + remove(): void; + replaceWith(...nodes: Array): void; } -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; +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; } -export interface CustomEvent extends Event { - readonly detail: any; - initCustomEvent( - type: string, - bubbles?: boolean, - cancelable?: boolean, - detail?: any | null +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 { @@ -225,15 +324,6 @@ interface ProgressEvent extends Event { 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; @@ -259,18 +349,17 @@ export interface AbortSignal extends EventTarget { ): void; } -export interface FormData extends DomIterable { +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; -} - -export interface FormDataConstructor { - new (): FormData; - prototype: FormData; + [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>; + entries(): IterableIterator<[string, FormDataEntryValue]>; + keys(): IterableIterator; + values(): IterableIterator; } export interface Blob { @@ -493,6 +582,7 @@ export interface WritableStreamDefaultController { error(error?: any): void; } */ + export interface QueuingStrategy { highWaterMark?: number; size?: QueuingStrategySizeCallback; @@ -502,25 +592,21 @@ export interface QueuingStrategySizeCallback { (chunk: T): number; } -export interface Headers extends DomIterable { +export class Headers { + constructor(init?: HeadersInit); 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; + entries(): IterableIterator<[string, string]>; + keys(): IterableIterator; + values(): IterableIterator; } type RequestCache = @@ -582,11 +668,6 @@ export interface ResponseInit { statusText?: string; } -export interface RequestConstructor { - new (input: RequestInfo, init?: RequestInit): Request; - prototype: Request; -} - export interface Request extends Body { readonly cache?: RequestCache; readonly credentials?: RequestCredentials; @@ -606,6 +687,11 @@ export interface Request extends Body { clone(): Request; } +export interface RequestConstructor { + new (input: RequestInfo, init?: RequestInit): Request; + prototype: Request; +} + export interface Response extends Body { readonly headers: Headers; readonly ok: boolean; @@ -618,14 +704,22 @@ export interface Response extends Body { clone(): Response; } -export interface DOMStringList { +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 interface Location { +export class Location { readonly ancestorOrigins: DOMStringList; hash: string; host: string; @@ -642,7 +736,8 @@ export interface Location { replace(url: string): void; } -export interface URL { +export class URL { + constructor(url: string, base?: string | URL); hash: string; host: string; hostname: string; @@ -657,54 +752,6 @@ export interface URL { 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; + static createObjectURL(object: any): string; + static revokeObjectURL(url: string): void; } diff --git a/cli/js/web/dom_util.ts b/cli/js/web/dom_util.ts index 40a8c618f6..d12593e8e8 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 b063efa600..b57c0f9013 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"; -function isTrusted(this: Event): boolean { - return getPrivateValue(this, eventAttributes, "isTrusted"); +/** 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[]; } -export class Event implements domTypes.Event { +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 eventData.get(this)!.isTrusted; +} + +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[] = []; + #canceledFlag = false; + #stopPropagationFlag = false; + #attributes: EventAttributes; - constructor(type: string, eventInitDict: domTypes.EventInit = {}) { + 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; + return true; } - set inPassiveListener(value: boolean) { - this._inPassiveListenerFlag = value; + get target(): EventTarget | null { + return this.#attributes.target; } - 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, { + 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: value, - target: this.target, - timeStamp: this.timeStamp, - }); - } - - get target(): domTypes.EventTarget { - return getPrivateValue(this, eventAttributes, "target"); - } - - set target(value: domTypes.EventTarget) { - eventAttributes.set(this, { - type: this.type, - bubbles: this.bubbles, - cancelable: this.cancelable, - composed: this.composed, - currentTarget: this.currentTarget, - eventPhase: this.eventPhase, - isTrusted: this.isTrusted, - relatedTarget: this.relatedTarget, target: value, timeStamp: this.timeStamp, - }); + }; } - get timeStamp(): Date { - return getPrivateValue(this, eventAttributes, "timeStamp"); + 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 605504a3a8..1a560dfbeb 100644 --- a/cli/js/web/event_target.ts +++ b/cli/js/web/event_target.ts @@ -1,115 +1,549 @@ // Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. -import * as domTypes from "./dom_types.ts"; -import { hasOwnProperty, requiredArguments } from "./util.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; +// 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 { + 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); +} + +// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor +function isShadowInclusiveAncestor( + ancestor: EventTarget | null, + node: EventTarget | null +): boolean { + while (isNode(node)) { + if (node === ancestor) { + return true; + } + + if (isShadowRoot(node)) { + node = node && getHost(node); + } else { + node = getParent(node); + } + } + + return false; +} + +function isShadowRoot(nodeImpl: EventTarget | null): boolean { + return Boolean( + nodeImpl && + isNode(nodeImpl) && + nodeImpl.nodeType === DOCUMENT_FRAGMENT_NODE && + getHost(nodeImpl) != null + ); +} + +function isSlotable( + nodeImpl: T | null +): nodeImpl is T & domTypes.Node & domTypes.Slotable { + return Boolean(isNode(nodeImpl) && "assignedSlot" in nodeImpl); +} + +// DOM Logic functions + +/** Append a path item to an event's path. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-path-append + */ +function appendToEventPath( + eventImpl: Event, + target: EventTarget, + targetOverride: EventTarget | null, + relatedTarget: EventTarget | null, + touchTargets: EventTarget[], + slotInClosedTree: boolean +): void { + const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); + const rootOfClosedTree = isShadowRoot(target) && getMode(target) === "closed"; + + getPath(eventImpl).push({ + item: target, + itemInShadowTree, + target: targetOverride, + relatedTarget, + touchTargetList: touchTargets, + rootOfClosedTree, + slotInClosedTree, + }); +} + +function dispatch( + targetImpl: EventTarget, + eventImpl: Event, + targetOverride?: EventTarget +): boolean { + let clearTargets = false; + let activationTarget: EventTarget | null = null; + + setDispatched(eventImpl, true); + + targetOverride = targetOverride ?? targetImpl; + const eventRelatedTarget = hasRelatedTarget(eventImpl) + ? eventImpl.relatedTarget + : null; + let relatedTarget = retarget(eventRelatedTarget, targetImpl); + + if (targetImpl !== relatedTarget || targetImpl === eventRelatedTarget) { + const touchTargets: EventTarget[] = []; + + appendToEventPath( + eventImpl, + targetImpl, + targetOverride, + relatedTarget, + touchTargets, + false + ); + + const isActivationEvent = eventImpl.type === "click"; + + if (isActivationEvent && getHasActivationBehavior(targetImpl)) { + activationTarget = targetImpl; + } + + let slotInClosedTree = false; + let slotable = + isSlotable(targetImpl) && getAssignedSlot(targetImpl) ? targetImpl : null; + let parent = getParent(targetImpl); + + // Populate event path + // https://dom.spec.whatwg.org/#event-path + while (parent !== null) { + if (slotable !== null) { + slotable = null; + + const parentRoot = getRoot(parent); + if ( + isShadowRoot(parentRoot) && + parentRoot && + getMode(parentRoot) === "closed" + ) { + slotInClosedTree = true; + } + } + + relatedTarget = retarget(eventRelatedTarget, parent); + + if ( + isNode(parent) && + isShadowInclusiveAncestor(getRoot(targetImpl), parent) + ) { + appendToEventPath( + eventImpl, + parent, + null, + relatedTarget, + touchTargets, + slotInClosedTree + ); + } else if (parent === relatedTarget) { + parent = null; + } else { + targetImpl = parent; + + if ( + isActivationEvent && + activationTarget === null && + getHasActivationBehavior(targetImpl) + ) { + activationTarget = targetImpl; + } + + appendToEventPath( + eventImpl, + parent, + targetImpl, + relatedTarget, + touchTargets, + slotInClosedTree + ); + } + + if (parent !== null) { + parent = getParent(parent); + } + + slotInClosedTree = false; + } + + let clearTargetsTupleIndex = -1; + const path = getPath(eventImpl); + for ( + let i = path.length - 1; + i >= 0 && clearTargetsTupleIndex === -1; + i-- + ) { + if (path[i].target !== null) { + clearTargetsTupleIndex = i; + } + } + const clearTargetsTuple = path[clearTargetsTupleIndex]; + + clearTargets = + (isNode(clearTargetsTuple.target) && + isShadowRoot(getRoot(clearTargetsTuple.target))) || + (isNode(clearTargetsTuple.relatedTarget) && + isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); + + setEventPhase(eventImpl, Event.CAPTURING_PHASE); + + for (let i = path.length - 1; i >= 0; --i) { + const tuple = path[i]; + + if (tuple.target === null) { + invokeEventListeners(tuple, eventImpl); + } + } + + for (let i = 0; i < path.length; i++) { + const tuple = path[i]; + + if (tuple.target !== null) { + setEventPhase(eventImpl, Event.AT_TARGET); + } else { + setEventPhase(eventImpl, Event.BUBBLING_PHASE); + } + + if ( + (eventImpl.eventPhase === Event.BUBBLING_PHASE && eventImpl.bubbles) || + eventImpl.eventPhase === Event.AT_TARGET + ) { + invokeEventListeners(tuple, eventImpl); + } + } + } + + setEventPhase(eventImpl, Event.NONE); + setCurrentTarget(eventImpl, null); + setPath(eventImpl, []); + setDispatched(eventImpl, false); + eventImpl.cancelBubble = false; + setStopImmediatePropagation(eventImpl, false); + + if (clearTargets) { + setTarget(eventImpl, null); + setRelatedTarget(eventImpl, null); + } + + // TODO: invoke activation targets if HTML nodes will be implemented + // if (activationTarget !== null) { + // if (!eventImpl.defaultPrevented) { + // activationTarget._activationBehavior(); + // } + // } + + return !eventImpl.defaultPrevented; +} + +/** Inner invoking of the event listeners where the resolved listeners are + * called. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke */ +function innerInvokeEventListeners( + eventImpl: Event, + targetListeners: Record +): boolean { + let found = false; + + const { type } = eventImpl; + + if (!targetListeners || !targetListeners[type]) { + return found; + } + + // Copy event listeners before iterating since the list can be modified during the iteration. + const handlers = targetListeners[type].slice(); + + for (let i = 0; i < handlers.length; i++) { + const listener = handlers[i]; + + let capture, once, passive; + if (typeof listener.options === "boolean") { + capture = listener.options; + once = false; + passive = false; + } else { + capture = listener.options.capture; + once = listener.options.once; + passive = listener.options.passive; + } + + // Check if the event listener has been removed since the listeners has been cloned. + if (!targetListeners[type].includes(listener)) { + continue; + } + + found = true; + + if ( + (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) || + (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture) + ) { + continue; + } + + if (once) { + targetListeners[type].splice(targetListeners[type].indexOf(listener), 1); + } + + if (passive) { + setInPassiveListener(eventImpl, true); + } + + if (typeof listener.callback === "object") { + if (typeof listener.callback.handleEvent === "function") { + listener.callback.handleEvent(eventImpl); + } + } else { + listener.callback.call(eventImpl.currentTarget, eventImpl); + } + + setInPassiveListener(eventImpl, false); + + if (getStopImmediatePropagation(eventImpl)) { + return found; + } + } + + return found; +} + +/** Invokes the listeners on a given event path with the supplied event. + * + * Ref: https://dom.spec.whatwg.org/#concept-event-listener-invoke */ +function invokeEventListeners(tuple: EventPath, eventImpl: Event): void { + const path = getPath(eventImpl); + const tupleIndex = path.indexOf(tuple); + for (let i = tupleIndex; i >= 0; i--) { + const t = path[i]; + if (t.target) { + setTarget(eventImpl, t.target); + break; + } + } + + setRelatedTarget(eventImpl, tuple.relatedTarget); + + if (eventImpl.cancelBubble) { + return; + } + + setCurrentTarget(eventImpl, tuple.item); + + innerInvokeEventListeners(eventImpl, getListeners(tuple.item)); +} + +function normalizeAddEventHandlerOptions( + options: boolean | AddEventListenerOptions | undefined +): AddEventListenerOptions { + if (typeof options === "boolean" || typeof options === "undefined") { + return { + capture: Boolean(options), + once: false, + passive: false, + }; + } else { + return options; + } +} + +function normalizeEventHandlerOptions( + options: boolean | EventListenerOptions | undefined +): EventListenerOptions { + if (typeof options === "boolean" || typeof options === "undefined") { + return { + capture: Boolean(options), + }; + } else { + return options; + } +} + +/** Retarget the target following the spec logic. + * + * Ref: https://dom.spec.whatwg.org/#retarget */ +function retarget(a: EventTarget | null, b: EventTarget): EventTarget | null { + while (true) { + if (!isNode(a)) { + return a; + } + + const aRoot = a.getRootNode(); + + if (aRoot) { + if ( + !isShadowRoot(aRoot) || + (isNode(b) && isShadowInclusiveAncestor(aRoot, b)) + ) { + return a; + } + + a = getHost(aRoot); + } + } +} + +// Non-public state information for an event target that needs to held onto. +// Some of the information should be moved to other entities (like Node, +// ShowRoot, UIElement, etc.). +interface EventTargetData { + assignedSlot: boolean; + hasActivationBehavior: boolean; + host: EventTarget | null; + listeners: Record; + mode: string; +} + +interface Listener { + callback: EventListenerOrEventListenerObject; + options: AddEventListenerOptions; +} + +// Accessors for non-public data + +export const eventTargetData = new WeakMap(); + +function getAssignedSlot(target: EventTarget): boolean { + return Boolean(eventTargetData.get(target as EventTarget)?.assignedSlot); +} + +function getHasActivationBehavior(target: EventTarget): boolean { + return Boolean( + eventTargetData.get(target as EventTarget)?.hasActivationBehavior + ); +} + +function getHost(target: EventTarget): EventTarget | null { + return eventTargetData.get(target as EventTarget)?.host ?? null; +} + +function getListeners(target: EventTarget): Record { + return eventTargetData.get(target as EventTarget)?.listeners ?? {}; +} + +function getMode(target: EventTarget): string | null { + return eventTargetData.get(target as EventTarget)?.mode ?? null; +} + +export function getDefaultTargetData(): Readonly { + return { + assignedSlot: false, + hasActivationBehavior: false, + host: null, + listeners: Object.create(null), + mode: "", + }; +} + +export class EventTargetImpl implements EventTarget { + constructor() { + eventTargetData.set(this, getDefaultTargetData()); + } public addEventListener( type: string, - callback: domTypes.EventListenerOrEventListenerObject | null, - options?: domTypes.AddEventListenerOptions | boolean + callback: EventListenerOrEventListenerObject | null, + options?: AddEventListenerOptions | boolean ): void { - const this_ = this || globalThis; - requiredArguments("EventTarget.addEventListener", arguments.length, 2); - const normalizedOptions: domTypes.AddEventListenerOptions = eventTargetHelpers.normalizeAddEventHandlerOptions( - options - ); - if (callback === null) { return; } - const listeners = this_[domTypes.eventTargetListeners]; + options = normalizeAddEventHandlerOptions(options); + const { listeners } = eventTargetData.get(this ?? globalThis)!; - if (!hasOwnProperty(listeners, type)) { + if (!(type in listeners)) { listeners[type] = []; } - for (let i = 0; i < listeners[type].length; ++i) { - const listener = listeners[type][i]; + for (const listener of listeners[type]) { if ( ((typeof listener.options === "boolean" && - listener.options === normalizedOptions.capture) || + listener.options === options.capture) || (typeof listener.options === "object" && - listener.options.capture === normalizedOptions.capture)) && + listener.options.capture === options.capture)) && listener.callback === callback ) { return; } } - listeners[type].push({ - callback, - options: normalizedOptions, - }); + listeners[type].push({ callback, options }); } public removeEventListener( type: string, - callback: domTypes.EventListenerOrEventListenerObject | null, - options?: domTypes.EventListenerOptions | boolean + callback: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean ): void { - const this_ = this || globalThis; - requiredArguments("EventTarget.removeEventListener", arguments.length, 2); - const listeners = this_[domTypes.eventTargetListeners]; - if (hasOwnProperty(listeners, type) && callback !== null) { + + const listeners = eventTargetData.get(this ?? globalThis)!.listeners; + if (callback !== null && type in listeners) { listeners[type] = listeners[type].filter( - (listener): boolean => listener.callback !== callback + (listener) => listener.callback !== callback ); - } - - const normalizedOptions: domTypes.EventListenerOptions = eventTargetHelpers.normalizeEventHandlerOptions( - options - ); - - if (callback === null) { - // Optimization, not in the spec. + } else if (callback === null || !listeners[type]) { return; } - if (!listeners[type]) { - return; - } + options = normalizeEventHandlerOptions(options); for (let i = 0; i < listeners[type].length; ++i) { const listener = listeners[type][i]; - if ( ((typeof listener.options === "boolean" && - listener.options === normalizedOptions.capture) || + listener.options === options.capture) || (typeof listener.options === "object" && - listener.options.capture === normalizedOptions.capture)) && + listener.options.capture === options.capture)) && listener.callback === callback ) { listeners[type].splice(i, 1); @@ -118,380 +552,37 @@ export class EventTarget implements domTypes.EventTarget { } } - public dispatchEvent(event: domTypes.Event): boolean { - const this_ = this || globalThis; - + public dispatchEvent(event: Event): boolean { requiredArguments("EventTarget.dispatchEvent", arguments.length, 1); - const listeners = this_[domTypes.eventTargetListeners]; - if (!hasOwnProperty(listeners, event.type)) { + const self = this ?? globalThis; + + const listeners = eventTargetData.get(self)!.listeners; + if (!(event.type in listeners)) { return true; } - 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"); + if (getDispatched(event)) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - 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"); + if (event.eventPhase !== Event.NONE) { + throw new DOMException("Invalid event state.", "InvalidStateError"); } - return eventTargetHelpers.dispatch(this_, event); + return dispatch(self, event); } get [Symbol.toStringTag](): string { return "EventTarget"; } + + protected getParent(_event: Event): EventTarget | null { + return null; + } } -const eventTargetHelpers = { - // https://dom.spec.whatwg.org/#concept-event-dispatch - dispatch( - targetImpl: EventTarget, - eventImpl: domTypes.Event, - targetOverride?: domTypes.EventTarget - ): boolean { - let clearTargets = false; - let activationTarget = null; - - eventImpl.dispatched = true; - - targetOverride = targetOverride || targetImpl; - let relatedTarget = retarget(eventImpl.relatedTarget, targetImpl); - - if ( - targetImpl !== relatedTarget || - targetImpl === eventImpl.relatedTarget - ) { - const touchTargets: domTypes.EventTarget[] = []; - - eventTargetHelpers.appendToEventPath( - eventImpl, - targetImpl, - targetOverride, - relatedTarget, - touchTargets, - false - ); - - const isActivationEvent = eventImpl.type === "click"; - - if (isActivationEvent && targetImpl[eventTargetHasActivationBehavior]) { - activationTarget = targetImpl; - } - - let slotInClosedTree = false; - let slotable = - isSlotable(targetImpl) && targetImpl[eventTargetAssignedSlot] - ? targetImpl - : null; - let parent = getEventTargetParent(targetImpl, eventImpl); - - // Populate event path - // https://dom.spec.whatwg.org/#event-path - while (parent !== null) { - if (slotable !== null) { - slotable = null; - - const parentRoot = getRoot(parent); - if ( - isShadowRoot(parentRoot) && - parentRoot && - parentRoot[domTypes.eventTargetMode] === "closed" - ) { - slotInClosedTree = true; - } - } - - relatedTarget = retarget(eventImpl.relatedTarget, parent); - - if ( - isNode(parent) && - isShadowInclusiveAncestor(getRoot(targetImpl), parent) - ) { - eventTargetHelpers.appendToEventPath( - eventImpl, - parent, - null, - relatedTarget, - touchTargets, - slotInClosedTree - ); - } else if (parent === relatedTarget) { - parent = null; - } else { - targetImpl = parent; - - if ( - isActivationEvent && - activationTarget === null && - targetImpl[eventTargetHasActivationBehavior] - ) { - activationTarget = targetImpl; - } - - eventTargetHelpers.appendToEventPath( - eventImpl, - parent, - targetImpl, - relatedTarget, - touchTargets, - slotInClosedTree - ); - } - - if (parent !== null) { - parent = getEventTargetParent(parent, eventImpl); - } - - slotInClosedTree = false; - } - - let clearTargetsTupleIndex = -1; - for ( - let i = eventImpl.path.length - 1; - i >= 0 && clearTargetsTupleIndex === -1; - i-- - ) { - if (eventImpl.path[i].target !== null) { - clearTargetsTupleIndex = i; - } - } - const clearTargetsTuple = eventImpl.path[clearTargetsTupleIndex]; - - clearTargets = - (isNode(clearTargetsTuple.target) && - isShadowRoot(getRoot(clearTargetsTuple.target))) || - (isNode(clearTargetsTuple.relatedTarget) && - isShadowRoot(getRoot(clearTargetsTuple.relatedTarget))); - - eventImpl.eventPhase = domTypes.EventPhase.CAPTURING_PHASE; - - for (let i = eventImpl.path.length - 1; i >= 0; --i) { - const tuple = eventImpl.path[i]; - - if (tuple.target === null) { - eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl); - } - } - - for (let i = 0; i < eventImpl.path.length; i++) { - const tuple = eventImpl.path[i]; - - if (tuple.target !== null) { - eventImpl.eventPhase = domTypes.EventPhase.AT_TARGET; - } else { - eventImpl.eventPhase = domTypes.EventPhase.BUBBLING_PHASE; - } - - if ( - (eventImpl.eventPhase === domTypes.EventPhase.BUBBLING_PHASE && - eventImpl.bubbles) || - eventImpl.eventPhase === domTypes.EventPhase.AT_TARGET - ) { - eventTargetHelpers.invokeEventListeners(targetImpl, tuple, eventImpl); - } - } - } - - eventImpl.eventPhase = domTypes.EventPhase.NONE; - - eventImpl.currentTarget = null; - eventImpl.path = []; - eventImpl.dispatched = false; - eventImpl.cancelBubble = false; - eventImpl.cancelBubbleImmediately = false; - - if (clearTargets) { - eventImpl.target = null; - eventImpl.relatedTarget = null; - } - - // TODO: invoke activation targets if HTML nodes will be implemented - // if (activationTarget !== null) { - // if (!eventImpl.defaultPrevented) { - // activationTarget._activationBehavior(); - // } - // } - - return !eventImpl.defaultPrevented; - }, - - // https://dom.spec.whatwg.org/#concept-event-listener-invoke - invokeEventListeners( - targetImpl: EventTarget, - tuple: domTypes.EventPath, - eventImpl: domTypes.Event - ): void { - const tupleIndex = eventImpl.path.indexOf(tuple); - for (let i = tupleIndex; i >= 0; i--) { - const t = eventImpl.path[i]; - if (t.target) { - eventImpl.target = t.target; - break; - } - } - - eventImpl.relatedTarget = tuple.relatedTarget; - - if (eventImpl.cancelBubble) { - return; - } - - eventImpl.currentTarget = tuple.item; - - eventTargetHelpers.innerInvokeEventListeners( - targetImpl, - eventImpl, - tuple.item[domTypes.eventTargetListeners] - ); - }, - - // https://dom.spec.whatwg.org/#concept-event-listener-inner-invoke - innerInvokeEventListeners( - targetImpl: EventTarget, - eventImpl: domTypes.Event, - targetListeners: { [type in string]: domTypes.EventTargetListener[] } - ): boolean { - let found = false; - - const { type } = eventImpl; - - if (!targetListeners || !targetListeners[type]) { - return found; - } - - // Copy event listeners before iterating since the list can be modified during the iteration. - const handlers = targetListeners[type].slice(); - - for (let i = 0; i < handlers.length; i++) { - const listener = handlers[i]; - - let capture, once, passive; - if (typeof listener.options === "boolean") { - capture = listener.options; - once = false; - passive = false; - } else { - capture = listener.options.capture; - once = listener.options.once; - passive = listener.options.passive; - } - - // Check if the event listener has been removed since the listeners has been cloned. - if (!targetListeners[type].includes(listener)) { - continue; - } - - found = true; - - if ( - (eventImpl.eventPhase === domTypes.EventPhase.CAPTURING_PHASE && - !capture) || - (eventImpl.eventPhase === domTypes.EventPhase.BUBBLING_PHASE && capture) - ) { - continue; - } - - if (once) { - targetListeners[type].splice( - targetListeners[type].indexOf(listener), - 1 - ); - } - - if (passive) { - eventImpl.inPassiveListener = true; - } - - try { - if (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); - } - - eventImpl.inPassiveListener = false; - - if (eventImpl.cancelBubbleImmediately) { - return found; - } - } - - return found; - }, - - normalizeAddEventHandlerOptions( - options: boolean | domTypes.AddEventListenerOptions | undefined - ): domTypes.AddEventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - const returnValue: domTypes.AddEventListenerOptions = { - capture: Boolean(options), - once: false, - passive: false, - }; - - return returnValue; - } else { - return options; - } - }, - - normalizeEventHandlerOptions( - options: boolean | domTypes.EventListenerOptions | undefined - ): domTypes.EventListenerOptions { - if (typeof options === "boolean" || typeof options === "undefined") { - const returnValue: domTypes.EventListenerOptions = { - capture: Boolean(options), - }; - - return returnValue; - } else { - return options; - } - }, - - // https://dom.spec.whatwg.org/#concept-event-path-append - appendToEventPath( - eventImpl: domTypes.Event, - target: domTypes.EventTarget, - targetOverride: domTypes.EventTarget | null, - relatedTarget: domTypes.EventTarget | null, - touchTargets: domTypes.EventTarget[], - slotInClosedTree: boolean - ): void { - const itemInShadowTree = isNode(target) && isShadowRoot(getRoot(target)); - const rootOfClosedTree = - isShadowRoot(target) && target[domTypes.eventTargetMode] === "closed"; - - eventImpl.path.push({ - item: target, - itemInShadowTree, - target: targetOverride, - relatedTarget, - touchTargetList: touchTargets, - rootOfClosedTree, - slotInClosedTree, - }); - }, -}; - -Reflect.defineProperty(EventTarget.prototype, "addEventListener", { - enumerable: true, -}); -Reflect.defineProperty(EventTarget.prototype, "removeEventListener", { - enumerable: true, -}); -Reflect.defineProperty(EventTarget.prototype, "dispatchEvent", { - enumerable: true, -}); +defineEnumerableProps(EventTargetImpl, [ + "addEventListener", + "removeEventListener", + "dispatchEvent", +]); diff --git a/cli/js/web/fetch.ts b/cli/js/web/fetch.ts index 26c5ff0537..112bae48f6 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 4517c2a332..42f4194031 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 e1d81393d8..1f750faa3a 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 862a4c1e49..9b59842b74 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 96edaf59e3..6a5d92a2b8 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 1d55792174..17fb8e5bd1 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 1b473b77ac..19f2594843 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 f46c798504..571ce50ede 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 d33226a9bd..5d07dba530 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 50753260d3..a003f0a176 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 7b0de22748..db0a082f42 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 98fe0f91a4..4c5b402c5f 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 4c5e3657d6..9c17db8f6c 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 090f781358..c27430db1b 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 78bb19a282..4d442d0f54 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 57ffe08fda..181edede8f 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 a6131c5d02..f231d78dc6 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 6fd498e596..b0630bf950 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 2b6a0d341d..1a6f4eb9dc 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 aad59bb8c5..0e41bdbf2e 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 2d63b4d606..32e73c4433 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 7a0abbbdb9..054c26193b 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();