diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2020-03-05 08:36:13 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-05 08:36:13 -0500 |
commit | c850b258b4e2487b0456a95cd6b65c169ffb9de2 (patch) | |
tree | d631f6995c048b0c7cddb600d4f33297922ba5b1 | |
parent | 54a1688868810af0ef16f5c186dfb839f2fce23f (diff) |
Support async function and EventListenerObject as listeners (#4240)
-rw-r--r-- | cli/js/dom_types.ts | 36 | ||||
-rw-r--r-- | cli/js/event_target.ts | 25 | ||||
-rw-r--r-- | cli/js/event_target_test.ts | 79 | ||||
-rw-r--r-- | cli/js/globals.ts | 2 | ||||
-rw-r--r-- | cli/js/lib.deno.shared_globals.d.ts | 51 |
5 files changed, 140 insertions, 53 deletions
diff --git a/cli/js/dom_types.ts b/cli/js/dom_types.ts index 1e6c622c8..cdd681615 100644 --- a/cli/js/dom_types.ts +++ b/cli/js/dom_types.ts @@ -76,20 +76,44 @@ 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]: EventListener[] }; + [eventTargetListeners]: { [type in string]: EventTargetListener[] }; [eventTargetMode]: string; [eventTargetNodeType]: NodeType; addEventListener( type: string, - callback: (event: Event) => void | null, + listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions ): void; dispatchEvent(event: Event): boolean; removeEventListener( type: string, - callback?: (event: Event) => void | null, + listener: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean ): void; } @@ -147,12 +171,6 @@ export interface URLSearchParams extends DomIterable<string, string> { ): void; } -export interface EventListener { - handleEvent(event: Event): void; - readonly callback: (event: Event) => void | null; - readonly options: boolean | AddEventListenerOptions; -} - export interface EventInit { bubbles?: boolean; cancelable?: boolean; diff --git a/cli/js/event_target.ts b/cli/js/event_target.ts index daa73eb23..03557526a 100644 --- a/cli/js/event_target.ts +++ b/cli/js/event_target.ts @@ -25,7 +25,7 @@ export const eventTargetHasActivationBehavior: unique symbol = Symbol(); export class EventTarget implements domTypes.EventTarget { public [domTypes.eventTargetHost]: domTypes.EventTarget | null = null; public [domTypes.eventTargetListeners]: { - [type in string]: domTypes.EventListener[]; + [type in string]: domTypes.EventTargetListener[]; } = {}; public [domTypes.eventTargetMode] = ""; public [domTypes.eventTargetNodeType]: domTypes.NodeType = @@ -35,7 +35,7 @@ export class EventTarget implements domTypes.EventTarget { public addEventListener( type: string, - callback: (event: domTypes.Event) => void | null, + callback: domTypes.EventListenerOrEventListenerObject | null, options?: domTypes.AddEventListenerOptions | boolean ): void { const this_ = this || globalThis; @@ -68,20 +68,15 @@ export class EventTarget implements domTypes.EventTarget { } } - // eslint-disable-next-line @typescript-eslint/no-this-alias - const eventTarget = this; listeners[type].push({ callback, - options: normalizedOptions, - handleEvent(event: domTypes.Event): void { - this.callback.call(eventTarget, event); - } - } as domTypes.EventListener); + options: normalizedOptions + }); } public removeEventListener( type: string, - callback: (event: domTypes.Event) => void | null, + callback: domTypes.EventListenerOrEventListenerObject | null, options?: domTypes.EventListenerOptions | boolean ): void { const this_ = this || globalThis; @@ -359,7 +354,7 @@ const eventTargetHelpers = { innerInvokeEventListeners( targetImpl: EventTarget, eventImpl: domTypes.Event, - targetListeners: { [type in string]: domTypes.EventListener[] } + targetListeners: { [type in string]: domTypes.EventTargetListener[] } ): boolean { let found = false; @@ -413,7 +408,13 @@ const eventTargetHelpers = { } try { - listener.handleEvent(eventImpl); + 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?) diff --git a/cli/js/event_target_test.ts b/cli/js/event_target_test.ts index af7895081..376c96dd0 100644 --- a/cli/js/event_target_test.ts +++ b/cli/js/event_target_test.ts @@ -39,7 +39,7 @@ unitTest(function anEventTargetCanBeSubclassed(): void { class NicerEventTarget extends EventTarget { on( type: string, - callback: (e: Event) => void | null, + callback: ((e: Event) => void) | null, options?: __domTypes.AddEventListenerOptions ): void { this.addEventListener(type, callback, options); @@ -47,7 +47,7 @@ unitTest(function anEventTargetCanBeSubclassed(): void { off( type: string, - callback: (e: Event) => void | null, + callback: ((e: Event) => void) | null, options?: __domTypes.EventListenerOptions ): void { this.removeEventListener(type, callback, options); @@ -154,3 +154,78 @@ unitTest(function eventTargetThisShouldDefaultToWindow(): void { dispatchEvent(event); assertEquals(n, 1); }); + +test(function eventTargetShouldAcceptEventListenerObject(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = { + handleEvent(e: Event): void { + assertEquals(e, event); + ++callCount; + } + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); + +test(function eventTargetShouldAcceptAsyncFunction(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = async (e: Event): Promise<void> => { + assertEquals(e, event); + ++callCount; + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); +}); + +test( + function eventTargetShouldAcceptAsyncFunctionForEventListenerObject(): void { + const target = new EventTarget(); + const event = new Event("foo", { bubbles: true, cancelable: false }); + let callCount = 0; + + const listener = { + async handleEvent(e: Event): Promise<void> { + assertEquals(e, event); + ++callCount; + } + }; + + target.addEventListener("foo", listener); + + target.dispatchEvent(event); + assertEquals(callCount, 1); + + target.dispatchEvent(event); + assertEquals(callCount, 2); + + target.removeEventListener("foo", listener); + target.dispatchEvent(event); + assertEquals(callCount, 2); + } +); diff --git a/cli/js/globals.ts b/cli/js/globals.ts index fd2082e40..0e3ae8fd8 100644 --- a/cli/js/globals.ts +++ b/cli/js/globals.ts @@ -104,7 +104,7 @@ declare global { /* eslint-disable no-var */ var addEventListener: ( type: string, - callback: (event: domTypes.Event) => void | null, + callback: domTypes.EventListenerOrEventListenerObject | null, options?: boolean | domTypes.AddEventListenerOptions | undefined ) => void; var queueMicrotask: (callback: () => void) => void; diff --git a/cli/js/lib.deno.shared_globals.d.ts b/cli/js/lib.deno.shared_globals.d.ts index fef155f3d..9ec045a8e 100644 --- a/cli/js/lib.deno.shared_globals.d.ts +++ b/cli/js/lib.deno.shared_globals.d.ts @@ -42,13 +42,13 @@ declare interface WindowOrWorkerGlobalScope { addEventListener: ( type: string, - callback: (event: __domTypes.Event) => void | null, + callback: __domTypes.EventListenerOrEventListenerObject | null, options?: boolean | __domTypes.AddEventListenerOptions | undefined ) => void; dispatchEvent: (event: __domTypes.Event) => boolean; removeEventListener: ( type: string, - callback: (event: __domTypes.Event) => void | null, + callback: __domTypes.EventListenerOrEventListenerObject | null, options?: boolean | __domTypes.EventListenerOptions | undefined ) => void; } @@ -240,7 +240,7 @@ declare const CustomEventInit: typeof __customEvent.CustomEventInit; declare const CustomEvent: typeof __customEvent.CustomEvent; declare const EventInit: typeof __event.EventInit; declare const Event: typeof __event.Event; -declare const EventListener: typeof __eventTarget.EventListener; +declare const EventListener: __domTypes.EventListener; declare const EventTarget: typeof __eventTarget.EventTarget; declare const URL: typeof __url.URL; declare const URLSearchParams: typeof __urlSearchParams.URLSearchParams; @@ -256,13 +256,13 @@ declare const Worker: typeof __workers.WorkerImpl; declare const addEventListener: ( type: string, - callback: (event: __domTypes.Event) => void | null, + callback: __domTypes.EventListenerOrEventListenerObject | null, options?: boolean | __domTypes.AddEventListenerOptions | undefined ) => void; declare const dispatchEvent: (event: __domTypes.Event) => boolean; declare const removeEventListener: ( type: string, - callback: (event: __domTypes.Event) => void | null, + callback: __domTypes.EventListenerOrEventListenerObject | null, options?: boolean | __domTypes.EventListenerOptions | undefined ) => void; @@ -346,6 +346,19 @@ declare namespace __domTypes { export const eventTargetListeners: unique symbol; export const eventTargetMode: unique symbol; export const eventTargetNodeType: unique symbol; + export interface EventListener { + (evt: Event): void | Promise<void>; + } + export interface EventListenerObject { + handleEvent(evt: Event): void | Promise<void>; + } + export type EventListenerOrEventListenerObject = + | EventListener + | EventListenerObject; + export interface EventTargetListener { + callback: EventListenerOrEventListenerObject; + options: AddEventListenerOptions; + } export interface EventTarget { [eventTargetHost]: EventTarget | null; [eventTargetListeners]: { [type in string]: EventListener[] }; @@ -353,13 +366,13 @@ declare namespace __domTypes { [eventTargetNodeType]: NodeType; addEventListener( type: string, - callback: (event: Event) => void | null, + callback: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions ): void; dispatchEvent(event: Event): boolean; removeEventListener( type: string, - callback?: (event: Event) => void | null, + callback?: EventListenerOrEventListenerObject | null, options?: EventListenerOptions | boolean ): void; } @@ -414,11 +427,6 @@ declare namespace __domTypes { thisArg?: any ): void; } - export interface EventListener { - handleEvent(event: Event): void; - readonly callback: (event: Event) => void | null; - readonly options: boolean | AddEventListenerOptions; - } export interface EventInit { bubbles?: boolean; cancelable?: boolean; @@ -1095,21 +1103,6 @@ declare namespace __eventTarget { readonly passive: boolean; readonly once: boolean; } - export class EventListener implements __domTypes.EventListener { - allEvents: __domTypes.Event[]; - atEvents: __domTypes.Event[]; - bubbledEvents: __domTypes.Event[]; - capturedEvents: __domTypes.Event[]; - private _callback; - private _options; - constructor( - callback: (event: __domTypes.Event) => void | null, - options: boolean | __domTypes.AddEventListenerOptions - ); - handleEvent(event: __domTypes.Event): void; - readonly callback: (event: __domTypes.Event) => void | null; - readonly options: __domTypes.AddEventListenerOptions | boolean; - } export const eventTargetAssignedSlot: unique symbol; export const eventTargetHasActivationBehavior: unique symbol; export class EventTarget implements __domTypes.EventTarget { @@ -1123,12 +1116,12 @@ declare namespace __eventTarget { private [eventTargetHasActivationBehavior]; addEventListener( type: string, - callback: (event: __domTypes.Event) => void | null, + callback: __domTypes.EventListenerOrEventListenerObject | null, options?: __domTypes.AddEventListenerOptions | boolean ): void; removeEventListener( type: string, - callback: (event: __domTypes.Event) => void | null, + callback: __domTypes.EventListenerOrEventListenerObject | null, options?: __domTypes.EventListenerOptions | boolean ): void; dispatchEvent(event: __domTypes.Event): boolean; |