From e470f31d328884303e56f2f15a4121b8ffe097a6 Mon Sep 17 00:00:00 2001 From: Adam Conrad <422184+acconrad@users.noreply.github.com> Date: Wed, 23 Jan 2019 12:20:53 +0000 Subject: [PATCH] Web API: CustomEvent (#1505) --- BUILD.gn | 1 + js/custom_event.ts | 60 +++++++++++++++++++++++++++++++++++++++++ js/custom_event_test.ts | 21 +++++++++++++++ js/dom_types.ts | 17 ++++++++++++ js/event.ts | 25 +++++++++++++++++ js/event_target_test.ts | 4 +-- js/globals.ts | 5 ++++ js/unit_tests.ts | 1 + 8 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 js/custom_event.ts create mode 100644 js/custom_event_test.ts diff --git a/BUILD.gn b/BUILD.gn index 46b0d314a820e..405bc5ca37243 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -56,6 +56,7 @@ ts_sources = [ "js/compiler.ts", "js/console.ts", "js/copy_file.ts", + "js/custom_event.ts", "js/deno.ts", "js/dir.ts", "js/dispatch.ts", diff --git a/js/custom_event.ts b/js/custom_event.ts new file mode 100644 index 0000000000000..dd1c33d58f178 --- /dev/null +++ b/js/custom_event.ts @@ -0,0 +1,60 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import * as domTypes from "./dom_types"; +import * as event from "./event"; +import { getPrivateValue } from "./util"; + +// WeakMaps are recommended for private attributes (see MDN link below) +// tslint:disable-next-line:max-line-length +// https://developer.mozilla.org/en-US/docs/Archive/Add-ons/Add-on_SDK/Guides/Contributor_s_Guide/Private_Properties#Using_WeakMaps +export const customEventAttributes = new WeakMap(); + +export class CustomEventInit extends event.EventInit + implements domTypes.CustomEventInit { + // tslint:disable-next-line:no-any + detail: any; + + constructor({ + bubbles = false, + cancelable = false, + composed = false, + detail = null + }: domTypes.CustomEventInit) { + super({ bubbles, cancelable, composed }); + this.detail = detail; + } +} + +export class CustomEvent extends event.Event implements domTypes.CustomEvent { + constructor( + type: string, + customEventInitDict: domTypes.CustomEventInit = {} + ) { + super(type, customEventInitDict); + const { detail = null } = customEventInitDict; + customEventAttributes.set(this, { detail }); + } + + // tslint:disable-next-line:no-any + get detail(): any { + return getPrivateValue(this, customEventAttributes, "detail"); + } + + initCustomEvent( + type: string, + bubbles?: boolean, + cancelable?: boolean, + // tslint:disable-next-line:no-any + detail?: any + ) { + if (this.dispatched) { + return; + } + + customEventAttributes.set(this, { detail }); + } +} + +/** Built-in objects providing `get` methods for our + * interceptable JavaScript operations. + */ +Reflect.defineProperty(CustomEvent.prototype, "detail", { enumerable: true }); diff --git a/js/custom_event_test.ts b/js/custom_event_test.ts new file mode 100644 index 0000000000000..4a95727800c4a --- /dev/null +++ b/js/custom_event_test.ts @@ -0,0 +1,21 @@ +// Copyright 2018 the Deno authors. All rights reserved. MIT license. +import { test, assertEqual } from "./test_util.ts"; + +test(function customEventInitializedWithDetail() { + const type = "touchstart"; + const detail = { message: "hello" }; + const customEventDict = new CustomEventInit({ + bubbles: true, + cancelable: true, + detail + }); + const event = new CustomEvent(type, customEventDict); + + assertEqual(event.bubbles, true); + assertEqual(event.cancelable, true); + assertEqual(event.currentTarget, null); + assertEqual(event.detail, detail); + assertEqual(event.isTrusted, false); + assertEqual(event.target, null); + assertEqual(event.type, type); +}); diff --git a/js/dom_types.ts b/js/dom_types.ts index 068ae9cfd3629..0c6814dae6134 100644 --- a/js/dom_types.ts +++ b/js/dom_types.ts @@ -144,6 +144,11 @@ export interface EventInit { composed?: boolean; } +export interface CustomEventInit extends EventInit { + // tslint:disable-next-line:no-any + detail?: any; +} + export enum EventPhase { NONE = 0, CAPTURING_PHASE = 1, @@ -182,6 +187,18 @@ export interface Event { readonly timeStamp: Date; } +export interface CustomEvent extends Event { + // tslint:disable-next-line:no-any + readonly detail: any; + initCustomEvent( + type: string, + bubbles?: boolean, + cancelable?: boolean, + // tslint:disable-next-line:no-any + detail?: any | null + ): void; +} + /* TODO(ry) Re-expose this interface. There is currently some interference * between deno's File and this one. */ diff --git a/js/event.ts b/js/event.ts index b0d16ff0a8086..1cc711b32fae1 100644 --- a/js/event.ts +++ b/js/event.ts @@ -22,6 +22,8 @@ export class EventInit implements domTypes.EventInit { export class Event implements domTypes.Event { // Each event has the following associated flags private _canceledFlag = false; + private _dispatchedFlag = false; + private _initializedFlag = false; private _inPassiveListenerFlag = false; private _stopImmediatePropagationFlag = false; private _stopPropagationFlag = false; @@ -30,6 +32,7 @@ export class Event implements domTypes.Event { private _path: domTypes.EventPath[] = []; constructor(type: string, eventInitDict: domTypes.EventInit = {}) { + this._initializedFlag = true; eventAttributes.set(this, { type, bubbles: eventInitDict.bubbles || false, @@ -71,14 +74,36 @@ export class Event implements domTypes.Event { return this._canceledFlag; } + get dispatched(): boolean { + return this._dispatchedFlag; + } + get eventPhase(): number { return getPrivateValue(this, eventAttributes, "eventPhase"); } + get initialized(): boolean { + return this._initializedFlag; + } + get isTrusted(): boolean { return getPrivateValue(this, eventAttributes, "isTrusted"); } + set isTrusted(value) { + eventAttributes.set(this, { + type: this.type, + bubbles: this.bubbles, + cancelable: this.cancelable, + composed: this.composed, + currentTarget: this.currentTarget, + eventPhase: this.eventPhase, + isTrusted: value, + target: this.target, + timeStamp: this.timeStamp + }); + } + get target(): domTypes.EventTarget { return getPrivateValue(this, eventAttributes, "target"); } diff --git a/js/event_target_test.ts b/js/event_target_test.ts index 6065c875adf76..c6477f2c75204 100644 --- a/js/event_target_test.ts +++ b/js/event_target_test.ts @@ -34,8 +34,8 @@ test(function constructedEventTargetCanBeUsedAsExpected() { test(function anEventTargetCanBeSubclassed() { class NicerEventTarget extends EventTarget { - on(type, listener?, options?) { - this.addEventListener(type, listener, options); + on(type, callback?, options?) { + this.addEventListener(type, callback, options); } off(type, callback?, options?) { diff --git a/js/globals.ts b/js/globals.ts index edf7fae8f7c30..706d6cd158455 100644 --- a/js/globals.ts +++ b/js/globals.ts @@ -9,6 +9,7 @@ // can be expressed as a namespace in the type library. import * as blob from "./blob"; import * as consoleTypes from "./console"; +import * as customEvent from "./custom_event"; import * as domTypes from "./dom_types"; import * as event from "./event"; import * as eventTarget from "./event_target"; @@ -64,6 +65,10 @@ export type Blob = blob.DenoBlob; // window.File = file.DenoFile; // export type File = file.DenoFile; +window.CustomEventInit = customEvent.CustomEventInit; +export type CustomEventInit = customEvent.CustomEventInit; +window.CustomEvent = customEvent.CustomEvent; +export type CustomEvent = customEvent.CustomEvent; window.EventInit = event.EventInit; export type EventInit = event.EventInit; window.Event = event.Event; diff --git a/js/unit_tests.ts b/js/unit_tests.ts index 51027c3874a01..68fa20d074edc 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -9,6 +9,7 @@ import "./chmod_test.ts"; import "./compiler_test.ts"; import "./console_test.ts"; import "./copy_file_test.ts"; +import "./custom_event_test.ts"; import "./dir_test.ts"; import "./event_test.ts"; import "./event_target_test.ts";