1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-25 15:29:32 -05:00

feat(ext/web): add AbortSignal.any() (#21087)

Fixes #18944
This commit is contained in:
Kenta Moriuchi 2023-11-13 09:04:11 +09:00 committed by Yoshiya Hinosawa
parent bfe9c9e1ba
commit c72f5fced0
No known key found for this signature in database
GPG key ID: 9017DB4559488785
4 changed files with 170 additions and 43 deletions

View file

@ -10,6 +10,7 @@
/// <reference lib="esnext" />
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { assert } from "ext:deno_web/00_infra.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import {
byteUpperCase,
@ -356,21 +357,19 @@ class Request {
request.clientRid = init.client?.rid ?? null;
}
// 27.
// 28.
this[_request] = request;
// 28.
this[_signal] = abortSignal.newSignal();
// 29.
if (signal !== null) {
abortSignal.follow(this[_signal], signal);
}
const signals = signal !== null ? [signal] : [];
// 30.
this[_signal] = abortSignal.createDependentAbortSignal(signals, prefix);
// 31.
this[_headers] = headersFromHeaderList(request.headerList, "request");
// 32.
// 33.
if (init.headers || ObjectKeys(init).length > 0) {
const headerList = headerListFromHeaders(this[_headers]);
const headers = init.headers ?? ArrayPrototypeSlice(
@ -384,13 +383,13 @@ class Request {
fillHeaders(this[_headers], headers);
}
// 33.
// 34.
let inputBody = null;
if (ObjectPrototypeIsPrototypeOf(RequestPrototype, input)) {
inputBody = input[_body];
}
// 34.
// 35.
if (
(request.method === "GET" || request.method === "HEAD") &&
((init.body !== undefined && init.body !== null) ||
@ -399,10 +398,10 @@ class Request {
throw new TypeError("Request with GET/HEAD method cannot have body.");
}
// 35.
// 36.
let initBody = null;
// 36.
// 37.
if (init.body !== undefined && init.body !== null) {
const res = extractBody(init.body);
initBody = res.body;
@ -411,13 +410,13 @@ class Request {
}
}
// 37.
// 38.
const inputOrInitBody = initBody ?? inputBody;
// 39.
// 40.
let finalBody = inputOrInitBody;
// 40.
// 41.
if (initBody === null && inputBody !== null) {
if (input[_body] && input[_body].unusable()) {
throw new TypeError("Input request's body is unusable.");
@ -425,7 +424,7 @@ class Request {
finalBody = inputBody.createProxy();
}
// 41.
// 42.
request.body = finalBody;
}
@ -464,20 +463,22 @@ class Request {
}
clone() {
const prefix = "Failed to call 'Request.clone'";
webidl.assertBranded(this, RequestPrototype);
if (this[_body] && this[_body].unusable()) {
throw new TypeError("Body is unusable.");
}
const newReq = cloneInnerRequest(this[_request]);
const newSignal = abortSignal.newSignal();
const clonedReq = cloneInnerRequest(this[_request]);
if (this[_signal]) {
abortSignal.follow(newSignal, this[_signal]);
}
assert(this[_signal] !== null);
const clonedSignal = abortSignal.createDependentAbortSignal(
[this[_signal]],
prefix,
);
return fromInnerRequest(
newReq,
newSignal,
clonedReq,
clonedSignal,
guardFromHeaders(this[_headers]),
);
}

View file

@ -4,6 +4,7 @@
/// <reference path="../../core/internal.d.ts" />
import * as webidl from "ext:deno_webidl/00_webidl.js";
import { assert } from "ext:deno_web/00_infra.js";
import {
defineEventHandler,
Event,
@ -13,27 +14,76 @@ import {
} from "ext:deno_web/02_event.js";
const primordials = globalThis.__bootstrap.primordials;
const {
ArrayPrototypeEvery,
ArrayPrototypePush,
SafeArrayIterator,
SafeSet,
SafeSetIterator,
SafeWeakRef,
SafeWeakSet,
SetPrototypeAdd,
SetPrototypeDelete,
Symbol,
TypeError,
WeakRefPrototypeDeref,
WeakSetPrototypeAdd,
WeakSetPrototypeHas,
} = primordials;
import { refTimer, setTimeout, unrefTimer } from "ext:deno_web/02_timers.js";
// Since WeakSet is not a iterable, WeakRefSet class is provided to store and
// iterate objects.
// To create an AsyncIterable using GeneratorFunction in the internal code,
// there are many primordial considerations, so we simply implement the
// toArray method.
class WeakRefSet {
#weakSet = new SafeWeakSet();
#refs = [];
add(value) {
if (WeakSetPrototypeHas(this.#weakSet, value)) {
return;
}
WeakSetPrototypeAdd(this.#weakSet, value);
ArrayPrototypePush(this.#refs, new SafeWeakRef(value));
}
has(value) {
return WeakSetPrototypeHas(this.#weakSet, value);
}
toArray() {
const ret = [];
for (let i = 0; i < this.#refs.length; ++i) {
const value = WeakRefPrototypeDeref(this.#refs[i]);
if (value !== undefined) {
ArrayPrototypePush(ret, value);
}
}
return ret;
}
}
const add = Symbol("[[add]]");
const signalAbort = Symbol("[[signalAbort]]");
const remove = Symbol("[[remove]]");
const abortReason = Symbol("[[abortReason]]");
const abortAlgos = Symbol("[[abortAlgos]]");
const dependent = Symbol("[[dependent]]");
const sourceSignals = Symbol("[[sourceSignals]]");
const dependentSignals = Symbol("[[dependentSignals]]");
const signal = Symbol("[[signal]]");
const timerId = Symbol("[[timerId]]");
const illegalConstructorKey = Symbol("illegalConstructorKey");
class AbortSignal extends EventTarget {
static any(signals) {
const prefix = "Failed to call 'AbortSignal.any'";
webidl.requiredArguments(arguments.length, 1, prefix);
return createDependentAbortSignal(signals, prefix);
}
static abort(reason = undefined) {
if (reason !== undefined) {
reason = webidl.converters.any(reason);
@ -73,9 +123,7 @@ class AbortSignal extends EventTarget {
if (this.aborted) {
return;
}
if (this[abortAlgos] === null) {
this[abortAlgos] = new SafeSet();
}
this[abortAlgos] ??= new SafeSet();
SetPrototypeAdd(this[abortAlgos], algorithm);
}
@ -91,12 +139,20 @@ class AbortSignal extends EventTarget {
const event = new Event("abort");
setIsTrusted(event, true);
this.dispatchEvent(event);
super.dispatchEvent(event);
if (algos !== null) {
for (const algorithm of new SafeSetIterator(algos)) {
algorithm();
}
}
if (this[dependentSignals] !== null) {
const dependentSignalArray = this[dependentSignals].toArray();
for (let i = 0; i < dependentSignalArray.length; ++i) {
const dependentSignal = dependentSignalArray[i];
dependentSignal[signalAbort](reason);
}
}
}
[remove](algorithm) {
@ -104,12 +160,15 @@ class AbortSignal extends EventTarget {
}
constructor(key = null) {
if (key != illegalConstructorKey) {
if (key !== illegalConstructorKey) {
throw new TypeError("Illegal constructor.");
}
super();
this[abortReason] = undefined;
this[abortAlgos] = null;
this[dependent] = false;
this[sourceSignals] = null;
this[dependentSignals] = null;
this[timerId] = null;
this[webidl.brand] = webidl.brand;
}
@ -138,15 +197,45 @@ class AbortSignal extends EventTarget {
// ops which would block the event loop.
addEventListener(...args) {
super.addEventListener(...new SafeArrayIterator(args));
if (this[timerId] !== null && listenerCount(this, "abort") > 0) {
refTimer(this[timerId]);
if (listenerCount(this, "abort") > 0) {
if (this[timerId] !== null) {
refTimer(this[timerId]);
} else if (this[sourceSignals] !== null) {
const sourceSignalArray = this[sourceSignals].toArray();
for (let i = 0; i < sourceSignalArray.length; ++i) {
const sourceSignal = sourceSignalArray[i];
if (sourceSignal[timerId] !== null) {
refTimer(sourceSignal[timerId]);
}
}
}
}
}
removeEventListener(...args) {
super.removeEventListener(...new SafeArrayIterator(args));
if (this[timerId] !== null && listenerCount(this, "abort") === 0) {
unrefTimer(this[timerId]);
if (listenerCount(this, "abort") === 0) {
if (this[timerId] !== null) {
unrefTimer(this[timerId]);
} else if (this[sourceSignals] !== null) {
const sourceSignalArray = this[sourceSignals].toArray();
for (let i = 0; i < sourceSignalArray.length; ++i) {
const sourceSignal = sourceSignalArray[i];
if (sourceSignal[timerId] !== null) {
// Check that all dependent signals of the timer signal do not have listeners
if (
ArrayPrototypeEvery(
sourceSignal[dependentSignals].toArray(),
(dependentSignal) =>
dependentSignal === this ||
listenerCount(dependentSignal, "abort") === 0,
)
) {
unrefTimer(sourceSignal[timerId]);
}
}
}
}
}
}
}
@ -176,24 +265,59 @@ class AbortController {
webidl.configureInterface(AbortController);
const AbortControllerPrototype = AbortController.prototype;
webidl.converters["AbortSignal"] = webidl.createInterfaceConverter(
webidl.converters.AbortSignal = webidl.createInterfaceConverter(
"AbortSignal",
AbortSignal.prototype,
);
webidl.converters["sequence<AbortSignal>"] = webidl.createSequenceConverter(
webidl.converters.AbortSignal,
);
function newSignal() {
return new AbortSignal(illegalConstructorKey);
}
function follow(followingSignal, parentSignal) {
if (followingSignal.aborted) {
return;
function createDependentAbortSignal(signals, prefix) {
signals = webidl.converters["sequence<AbortSignal>"](
signals,
prefix,
"Argument 1",
);
const resultSignal = new AbortSignal(illegalConstructorKey);
for (let i = 0; i < signals.length; ++i) {
const signal = signals[i];
if (signal[abortReason] !== undefined) {
resultSignal[abortReason] = signal[abortReason];
return resultSignal;
}
}
if (parentSignal.aborted) {
followingSignal[signalAbort](parentSignal.reason);
} else {
parentSignal[add](() => followingSignal[signalAbort](parentSignal.reason));
resultSignal[dependent] = true;
resultSignal[sourceSignals] = new WeakRefSet();
for (let i = 0; i < signals.length; ++i) {
const signal = signals[i];
if (!signal[dependent]) {
signal[dependentSignals] ??= new WeakRefSet();
resultSignal[sourceSignals].add(signal);
signal[dependentSignals].add(resultSignal);
} else {
const sourceSignalArray = signal[sourceSignals].toArray();
for (let j = 0; j < sourceSignalArray.length; ++j) {
const sourceSignal = sourceSignalArray[j];
assert(sourceSignal[abortReason] === undefined);
assert(!sourceSignal[dependent]);
if (resultSignal[sourceSignals].has(sourceSignal)) {
continue;
}
resultSignal[sourceSignals].add(sourceSignal);
sourceSignal[dependentSignals].add(resultSignal);
}
}
}
return resultSignal;
}
export {
@ -201,7 +325,7 @@ export {
AbortSignal,
AbortSignalPrototype,
add,
follow,
createDependentAbortSignal,
newSignal,
remove,
signalAbort,

View file

@ -442,6 +442,7 @@ declare var AbortSignal: {
readonly prototype: AbortSignal;
new (): never;
abort(reason?: any): AbortSignal;
any(signals: AbortSignal[]): AbortSignal;
timeout(milliseconds: number): AbortSignal;
};

View file

@ -2302,7 +2302,9 @@
"AbortSignal.any.html": true,
"AbortSignal.any.worker.html": true,
"event.any.html": true,
"event.any.worker.html": true
"event.any.worker.html": true,
"abort-signal-any.any.html": true,
"abort-signal-any.any.worker.html": true
},
"events": {
"AddEventListenerOptions-once.any.html": true,
@ -2364,7 +2366,6 @@
"EventTarget interface: operation addEventListener(DOMString, EventListener?, optional (AddEventListenerOptions or boolean))",
"EventTarget interface: operation removeEventListener(DOMString, EventListener?, optional (EventListenerOptions or boolean))",
"AbortController interface: operation abort(optional any)",
"AbortSignal interface: operation any(sequence<AbortSignal>)",
"AbortSignal interface: attribute onabort",
"NodeList interface: existence and properties of interface object",
"NodeList interface object length",