summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/js/globals.ts4
-rw-r--r--cli/js/lib.deno.shared_globals.d.ts16
-rw-r--r--cli/js/tests/abort_controller_test.ts56
-rw-r--r--cli/js/tests/unit_tests.ts1
-rw-r--r--cli/js/web/abort_controller.ts23
-rw-r--r--cli/js/web/abort_signal.ts58
6 files changed, 154 insertions, 4 deletions
diff --git a/cli/js/globals.ts b/cli/js/globals.ts
index 1c34e7297..ec314bf68 100644
--- a/cli/js/globals.ts
+++ b/cli/js/globals.ts
@@ -2,6 +2,8 @@
import "./lib.deno.shared_globals.d.ts";
+import * as abortController from "./web/abort_controller.ts";
+import * as abortSignal from "./web/abort_signal.ts";
import * as blob from "./web/blob.ts";
import * as consoleTypes from "./web/console.ts";
import * as promiseTypes from "./web/promise.ts";
@@ -207,6 +209,8 @@ export const windowOrWorkerGlobalScopeMethods = {
// Other properties shared between WindowScope and WorkerGlobalScope
export const windowOrWorkerGlobalScopeProperties = {
console: writable(new consoleTypes.Console(core.print)),
+ AbortController: nonEnumerable(abortController.AbortControllerImpl),
+ AbortSignal: nonEnumerable(abortSignal.AbortSignalImpl),
Blob: nonEnumerable(blob.DenoBlob),
File: nonEnumerable(domFile.DomFileImpl),
CustomEvent: nonEnumerable(customEvent.CustomEventImpl),
diff --git a/cli/js/lib.deno.shared_globals.d.ts b/cli/js/lib.deno.shared_globals.d.ts
index f84a056db..fd9f3691d 100644
--- a/cli/js/lib.deno.shared_globals.d.ts
+++ b/cli/js/lib.deno.shared_globals.d.ts
@@ -1256,6 +1256,16 @@ declare class CustomEvent<T = any> extends Event {
readonly detail: T;
}
+/** A controller object that allows you to abort one or more DOM requests as and
+ * when desired. */
+declare class AbortController {
+ /** Returns the AbortSignal object associated with this object. */
+ readonly signal: AbortSignal;
+ /** Invoking this method will set this object's AbortSignal's aborted flag and
+ * signal to any observers that the associated activity is to be aborted. */
+ abort(): void;
+}
+
interface AbortSignalEventMap {
abort: Event;
}
@@ -1263,10 +1273,8 @@ interface AbortSignalEventMap {
/** A signal object that allows you to communicate with a DOM request (such as a
* Fetch) and abort it if required via an AbortController object. */
interface AbortSignal extends EventTarget {
- /**
- * Returns true if this AbortSignal's AbortController has signaled to abort,
- * and false otherwise.
- */
+ /** Returns true if this AbortSignal's AbortController has signaled to abort,
+ * and false otherwise. */
readonly aborted: boolean;
onabort: ((this: AbortSignal, ev: Event) => any) | null;
addEventListener<K extends keyof AbortSignalEventMap>(
diff --git a/cli/js/tests/abort_controller_test.ts b/cli/js/tests/abort_controller_test.ts
new file mode 100644
index 000000000..ecc1abb88
--- /dev/null
+++ b/cli/js/tests/abort_controller_test.ts
@@ -0,0 +1,56 @@
+import { unitTest, assert, assertEquals } from "./test_util.ts";
+
+unitTest(function basicAbortController() {
+ const controller = new AbortController();
+ assert(controller);
+ const { signal } = controller;
+ assert(signal);
+ assertEquals(signal.aborted, false);
+ controller.abort();
+ assertEquals(signal.aborted, true);
+});
+
+unitTest(function signalCallsOnabort() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = false;
+ signal.onabort = (evt): void => {
+ assert(evt);
+ assertEquals(evt.type, "abort");
+ called = true;
+ };
+ controller.abort();
+ assert(called);
+});
+
+unitTest(function signalEventListener() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = false;
+ signal.addEventListener("abort", function (ev) {
+ assert(this === signal);
+ assertEquals(ev.type, "abort");
+ called = true;
+ });
+ controller.abort();
+ assert(called);
+});
+
+unitTest(function onlyAbortsOnce() {
+ const controller = new AbortController();
+ const { signal } = controller;
+ let called = 0;
+ signal.addEventListener("abort", () => called++);
+ signal.onabort = (): void => {
+ called++;
+ };
+ controller.abort();
+ assertEquals(called, 2);
+ controller.abort();
+ assertEquals(called, 2);
+});
+
+unitTest(function controllerHasProperToString() {
+ const actual = Object.prototype.toString.call(new AbortController());
+ assertEquals(actual, "[object AbortController]");
+});
diff --git a/cli/js/tests/unit_tests.ts b/cli/js/tests/unit_tests.ts
index ba3d6746a..7d7ec4821 100644
--- a/cli/js/tests/unit_tests.ts
+++ b/cli/js/tests/unit_tests.ts
@@ -4,6 +4,7 @@
//
// Test runner automatically spawns subprocesses for each required permissions combination.
+import "./abort_controller_test.ts";
import "./blob_test.ts";
import "./body_test.ts";
import "./buffer_test.ts";
diff --git a/cli/js/web/abort_controller.ts b/cli/js/web/abort_controller.ts
new file mode 100644
index 000000000..5b0a3af3c
--- /dev/null
+++ b/cli/js/web/abort_controller.ts
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { AbortSignalImpl, signalAbort } from "./abort_signal.ts";
+
+export class AbortControllerImpl implements AbortController {
+ #signal = new AbortSignalImpl();
+
+ get signal(): AbortSignal {
+ return this.#signal;
+ }
+
+ abort(): void {
+ this.#signal[signalAbort]();
+ }
+
+ get [Symbol.toStringTag](): string {
+ return "AbortController";
+ }
+}
+
+Object.defineProperty(AbortControllerImpl, "name", {
+ value: "AbortController",
+ configurable: true,
+});
diff --git a/cli/js/web/abort_signal.ts b/cli/js/web/abort_signal.ts
new file mode 100644
index 000000000..b741d6534
--- /dev/null
+++ b/cli/js/web/abort_signal.ts
@@ -0,0 +1,58 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+import { EventImpl } from "./event.ts";
+import { EventTargetImpl } from "./event_target.ts";
+
+export const add = Symbol("add");
+export const signalAbort = Symbol("signalAbort");
+export const remove = Symbol("remove");
+
+export class AbortSignalImpl extends EventTargetImpl implements AbortSignal {
+ #aborted?: boolean;
+ #abortAlgorithms = new Set<() => void>();
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ onabort: ((this: AbortSignal, ev: Event) => any) | null = null;
+
+ [add](algorithm: () => void): void {
+ this.#abortAlgorithms.add(algorithm);
+ }
+
+ [signalAbort](): void {
+ if (this.#aborted) {
+ return;
+ }
+ this.#aborted = true;
+ for (const algorithm of this.#abortAlgorithms) {
+ algorithm();
+ }
+ this.#abortAlgorithms.clear();
+ this.dispatchEvent(new EventImpl("abort"));
+ }
+
+ [remove](algorithm: () => void): void {
+ this.#abortAlgorithms.delete(algorithm);
+ }
+
+ constructor() {
+ super();
+ this.addEventListener("abort", (evt: Event) => {
+ const { onabort } = this;
+ if (typeof onabort === "function") {
+ onabort.call(this, evt);
+ }
+ });
+ }
+
+ get aborted(): boolean {
+ return Boolean(this.#aborted);
+ }
+
+ get [Symbol.toStringTag](): string {
+ return "AbortSignal";
+ }
+}
+
+Object.defineProperty(AbortSignalImpl, "name", {
+ value: "AbortSignal",
+ configurable: true,
+});