refactor: Event and EventTarget implementations (#4707)

Refactors Event and EventTarget so that they better encapsulate their
non-public data as well as are more forward compatible with things like
DOM Nodes.

Moves `dom_types.ts` -> `dom_types.d.ts` which was always the intention,
it was a legacy of when we used to build the types from the code and the
limitations of the compiler.  There was a lot of cruft in `dom_types`
which shouldn't have been there, and mis-alignment to the DOM standards.
This generally has been eliminated, though we still have some minor
differences from the DOM (like the removal of some deprecated
methods/properties).

Adds `DOMException`.  Strictly it shouldn't inherit from `Error`, but
most browsers provide a stack trace when one is thrown, so the behaviour
in Deno actually better matches the browser.

`Event` still doesn't log to console like it does in the browser.  I
 wanted to get this raised and that could be an enhancement later on (it
 currently doesn't either).
This commit is contained in:
Kitson Kelly 2020-04-12 01:42:02 +10:00 committed by GitHub
parent 2b362bef85
commit fc4819e1e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1205 additions and 1120 deletions

View file

@ -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
),
};

View file

@ -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 */
/// <reference no-default-lib="true" />
// 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
/// <reference lib="deno.ns" />
/// <reference lib="esnext" />
// 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<void>;
}
interface EventListenerObject {
handleEvent(evt: Event): void;
handleEvent(evt: Event): void | Promise<void>;
}
declare type EventListenerOrEventListenerObject =
@ -1257,27 +1210,16 @@ interface ProgressEvent<T extends EventTarget = EventTarget> extends Event {
readonly total: number;
}
interface CustomEvent<T = any> 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<T = any> extends EventInit {
detail?: T;
}
declare const CustomEvent: {
prototype: CustomEvent;
new <T>(typeArg: string, eventInitDict?: CustomEventInit<T>): CustomEvent<T>;
};
declare class CustomEvent<T = any> extends Event {
constructor(typeArg: string, eventInitDict?: CustomEventInit<T>);
/** Returns any custom data event was created with. Typically used for
* synthetic events. */
readonly detail: T;
}
interface AbortSignalEventMap {
abort: Event;

View file

@ -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 */
/// <reference no-default-lib="true" />
/// <reference lib="deno.ns" />
/// <reference lib="deno.shared_globals" />
/// <reference lib="esnext" />
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 */

View file

@ -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);

View file

@ -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");
});

View file

@ -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(

View file

@ -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 {

View file

@ -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";

View file

@ -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";

View file

@ -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;

View file

@ -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<T = any> extends Event implements CustomEvent {
#detail: T;
constructor(
type: string,
customEventInitDict: domTypes.CustomEventInit = {}
) {
super(type, customEventInitDict);
constructor(type: string, eventInitDict: CustomEventInit<T> = {}) {
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,
});

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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<T = {}> = new (...args: any[]) => T;
export type Constructor<T = {}> = new (...args: any[]) => T;
export interface DomIterable<K, V> {
keys(): IterableIterator<K>;
values(): IterableIterator<V>;
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<K, V, TBase extends Constructor>(
Base: TBase,

View file

@ -23,7 +23,7 @@ export type HeadersInit =
| Headers
| Array<[string, string]>
| Record<string, string>;
export type URLSearchParamsInit = string | string[][] | Record<string, string>;
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<K, V> {
keys(): IterableIterator<K>;
values(): IterableIterator<V>;
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<void>
(evt: Event): void | Promise<void>;
}
export interface EventListenerObject {
// Different from lib.dom.d.ts. Added Promise<void>
handleEvent(evt: Event): void | Promise<void>;
}
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<string, string> {
export class URLSearchParams {
constructor(
init?: string[][] | Record<string, string> | 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<string, string> {
callbackfn: (value: string, key: string, parent: this) => void,
thisArg?: any
): void;
[Symbol.iterator](): IterableIterator<[string, string]>;
entries(): IterableIterator<[string, string]>;
keys(): IterableIterator<string>;
values(): IterableIterator<string>;
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<ChildNode>;
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<T extends Node>(newChild: T): T;
cloneNode(deep?: boolean): Node;
compareDocumentPosition(other: Node): number;
contains(other: Node | null): boolean;
getRootNode(options?: GetRootNodeOptions): Node;
hasChildNodes(): boolean;
insertBefore<T extends Node>(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<T extends Node>(oldChild: T): T;
replaceChild<T extends Node>(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<Node | string>): void;
before(...nodes: Array<Node | string>): void;
remove(): void;
replaceWith(...nodes: Array<Node | string>): 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<Node | string>): void;
prepend(...nodes: Array<Node | string>): void;
// not currently supported
// querySelector<K extends keyof HTMLElementTagNameMap>(
// selectors: K,
// ): HTMLElementTagNameMap[K] | null;
// querySelector<K extends keyof SVGElementTagNameMap>(
// selectors: K,
// ): SVGElementTagNameMap[K] | null;
// querySelector<E extends Element = Element>(selectors: string): E | null;
// querySelectorAll<K extends keyof HTMLElementTagNameMap>(
// selectors: K,
// ): NodeListOf<HTMLElementTagNameMap[K]>;
// querySelectorAll<K extends keyof SVGElementTagNameMap>(
// selectors: K,
// ): NodeListOf<SVGElementTagNameMap[K]>;
// querySelectorAll<E extends Element = Element>(
// selectors: string,
// ): NodeListOf<E>;
}
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<Node>;
entries(): IterableIterator<[number, Node]>;
keys(): IterableIterator<number>;
values(): IterableIterator<Node>;
}
interface NodeListOf<TNode extends Node> extends NodeList {
length: number;
item(index: number): TNode;
forEach(
callbackfn: (value: TNode, key: number, parent: NodeListOf<TNode>) => void,
thisArg?: any
): void;
[index: number]: TNode;
[Symbol.iterator](): IterableIterator<TNode>;
entries(): IterableIterator<[number, TNode]>;
keys(): IterableIterator<number>;
values(): IterableIterator<TNode>;
}
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<string, FormDataEntryValue> {
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<string>;
values(): IterableIterator<FormDataEntryValue>;
}
export interface Blob {
@ -493,6 +582,7 @@ export interface WritableStreamDefaultController {
error(error?: any): void;
}
*/
export interface QueuingStrategy<T = any> {
highWaterMark?: number;
size?: QueuingStrategySizeCallback<T>;
@ -502,25 +592,21 @@ export interface QueuingStrategySizeCallback<T = any> {
(chunk: T): number;
}
export interface Headers extends DomIterable<string, string> {
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<string>;
set(name: string, value: string): void;
values(): IterableIterator<string>;
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<string>;
values(): IterableIterator<string>;
}
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<string>;
}
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<string>;
/**
* Returns a list of values in the search params.
*/
values(): IterableIterator<string>;
static createObjectURL(object: any): string;
static revokeObjectURL(url: string): void;
}

View file

@ -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;
}

View file

@ -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<Event, EventData>();
// 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<T extends Event>(
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",
]);

File diff suppressed because it is too large Load diff

View file

@ -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";

View file

@ -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";

View file

@ -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";

View file

@ -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 {

View file

@ -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;

View file

@ -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

View file

@ -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 {

View file

@ -11,7 +11,7 @@ import {
QueuingStrategySizeCallback,
UnderlyingSource,
UnderlyingByteSource,
} from "../dom_types.ts";
} from "../dom_types.d.ts";
// ReadableStreamDefaultController
export const controlledReadableStream_ = Symbol("controlledReadableStream_");

View file

@ -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<OutputType>
implements rs.SDReadableStreamDefaultController<OutputType> {

View file

@ -12,7 +12,7 @@ import {
QueuingStrategySizeCallback,
UnderlyingSource,
UnderlyingByteSource,
} from "../dom_types.ts";
} from "../dom_types.d.ts";
import {
ReadableStreamDefaultController,

View file

@ -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

View file

@ -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<ArrayBufferView> {

View file

@ -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_");

View file

@ -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<InputType, OutputType> {
// [ts.backpressure_]: boolean | undefined; // Whether there was backpressure on [[readable]] the last time it was observed

View file

@ -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_");

View file

@ -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<InputType>
// implements ws.WritableStreamDefaultController<InputType> {

View file

@ -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<InputType> {
// [shared.state_]: ws.WritableStreamState;

View file

@ -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(

View file

@ -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";

View file

@ -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";

View file

@ -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<K, V>, key: W): V[W] {
if (weakMap.has(instance)) {
return weakMap.get(instance)![key];
}
throw new TypeError("Illegal invocation");
}
export function hasOwnProperty<T>(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<T, P extends keyof T, K extends T[P]>(
typeof ((o as unknown) as Iterable<[P, K]>)[Symbol.iterator] === "function"
);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface GenericConstructor<T = any> {
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 });
}
}

View file

@ -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();