This commit is contained in:
Kenta Moriuchi 2025-09-18 16:57:50 +02:00 committed by GitHub
commit f9a07a80ec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 3073 additions and 1748 deletions

View file

@ -278,7 +278,8 @@ class Request {
if (signal === false) { if (signal === false) {
const signal = newSignal(); const signal = newSignal();
this[_signalCache] = signal; this[_signalCache] = signal;
signal[signalAbort]( signalAbort(
signal,
new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"), new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"),
); );
return signal; return signal;
@ -289,7 +290,8 @@ class Request {
const signal = newSignal(); const signal = newSignal();
this[_signalCache] = signal; this[_signalCache] = signal;
this[_request].onCancel?.(() => { this[_request].onCancel?.(() => {
signal[signalAbort]( signalAbort(
signal,
new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"), new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"),
); );
}); });
@ -615,7 +617,8 @@ const MESSAGE_REQUEST_CANCELLED = "The request has been cancelled.";
function abortRequest(request) { function abortRequest(request) {
if (request[_signalCache] !== undefined) { if (request[_signalCache] !== undefined) {
request[_signal][signalAbort]( signalAbort(
request[_signal],
new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"), new DOMException(MESSAGE_REQUEST_CANCELLED, "AbortError"),
); );
} else { } else {

View file

@ -57,7 +57,10 @@ import {
redirectStatus, redirectStatus,
toInnerResponse, toInnerResponse,
} from "ext:deno_fetch/23_response.js"; } from "ext:deno_fetch/23_response.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import {
addSignalAlgorithm,
removeSignalAlgorithm,
} from "ext:deno_web/03_abort_signal.js";
import { import {
builtinTracer, builtinTracer,
ContextManager, ContextManager,
@ -107,7 +110,7 @@ function createResponseBodyStream(responseBodyRid, terminator) {
} }
// TODO(lucacasonato): clean up registration // TODO(lucacasonato): clean up registration
terminator[abortSignal.add](onAbort); addSignalAlgorithm(terminator, onAbort);
return readable; return readable;
} }
@ -125,7 +128,7 @@ async function mainFetch(req, recursive, terminator) {
} }
const body = new InnerBody(req.blobUrlEntry.stream()); const body = new InnerBody(req.blobUrlEntry.stream());
terminator[abortSignal.add](() => body.error(terminator.reason)); addSignalAlgorithm(terminator, () => body.error(terminator.reason));
processUrlList(req.urlList, req.urlListProcessed); processUrlList(req.urlList, req.urlListProcessed);
return { return {
@ -186,7 +189,7 @@ async function mainFetch(req, recursive, terminator) {
core.tryClose(cancelHandleRid); core.tryClose(cancelHandleRid);
} }
} }
terminator[abortSignal.add](onAbort); addSignalAlgorithm(terminator, onAbort);
let resp; let resp;
try { try {
resp = await opFetchSend(requestRid); resp = await opFetchSend(requestRid);
@ -403,13 +406,13 @@ function fetch(input, init = { __proto__: null }) {
// 9. // 9.
let locallyAborted = false; let locallyAborted = false;
// 10. // 10.
function onabort() { function onAbort() {
locallyAborted = true; locallyAborted = true;
reject( reject(
abortFetch(request, responseObject, requestObject.signal.reason), abortFetch(request, responseObject, requestObject.signal.reason),
); );
} }
requestObject.signal[abortSignal.add](onabort); addSignalAlgorithm(requestObject, onAbort);
if (!requestObject.headers.has("Accept")) { if (!requestObject.headers.has("Accept")) {
ArrayPrototypePush(request.headerList, ["Accept", "*/*"]); ArrayPrototypePush(request.headerList, ["Accept", "*/*"]);
@ -435,7 +438,7 @@ function fetch(input, init = { __proto__: null }) {
requestObject.signal.reason, requestObject.signal.reason,
), ),
); );
requestObject.signal[abortSignal.remove](onabort); removeSignalAlgorithm(requestObject, onAbort);
return; return;
} }
// 12.3. // 12.3.
@ -444,7 +447,7 @@ function fetch(input, init = { __proto__: null }) {
"Fetch failed: " + (response.error ?? "unknown error"), "Fetch failed: " + (response.error ?? "unknown error"),
); );
reject(err); reject(err);
requestObject.signal[abortSignal.remove](onabort); removeSignalAlgorithm(requestObject, onAbort);
return; return;
} }
responseObject = fromInnerResponse(response, "immutable"); responseObject = fromInnerResponse(response, "immutable");
@ -454,12 +457,12 @@ function fetch(input, init = { __proto__: null }) {
} }
resolve(responseObject); resolve(responseObject);
requestObject.signal[abortSignal.remove](onabort); removeSignalAlgorithm(requestObject, onAbort);
}, },
), ),
(err) => { (err) => {
reject(err); reject(err);
requestObject.signal[abortSignal.remove](onabort); removeSignalAlgorithm(requestObject, onAbort);
}, },
); );
}); });

View file

@ -92,7 +92,10 @@ const {
} = primordials; } = primordials;
import { read, readSync, write, writeSync } from "ext:deno_io/12_io.js"; import { read, readSync, write, writeSync } from "ext:deno_io/12_io.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import {
addSignalAlgorithm,
removeSignalAlgorithm,
} from "ext:deno_web/03_abort_signal.js";
import { import {
readableStreamForRid, readableStreamForRid,
ReadableStreamPrototype, ReadableStreamPrototype,
@ -747,7 +750,7 @@ async function readFile(path, options) {
options.signal.throwIfAborted(); options.signal.throwIfAborted();
cancelRid = createCancelHandle(); cancelRid = createCancelHandle();
abortHandler = () => core.tryClose(cancelRid); abortHandler = () => core.tryClose(cancelRid);
options.signal[abortSignal.add](abortHandler); addSignalAlgorithm(options.signal, abortHandler);
} }
try { try {
@ -758,7 +761,7 @@ async function readFile(path, options) {
return read; return read;
} finally { } finally {
if (options?.signal) { if (options?.signal) {
options.signal[abortSignal.remove](abortHandler); removeSignalAlgorithm(options.signal, abortHandler);
// always throw the abort error when aborted // always throw the abort error when aborted
options.signal.throwIfAborted(); options.signal.throwIfAborted();
@ -777,7 +780,7 @@ async function readTextFile(path, options) {
options.signal.throwIfAborted(); options.signal.throwIfAborted();
cancelRid = createCancelHandle(); cancelRid = createCancelHandle();
abortHandler = () => core.tryClose(cancelRid); abortHandler = () => core.tryClose(cancelRid);
options.signal[abortSignal.add](abortHandler); addSignalAlgorithm(options.signal, abortHandler);
} }
try { try {
@ -788,7 +791,7 @@ async function readTextFile(path, options) {
return read; return read;
} finally { } finally {
if (options?.signal) { if (options?.signal) {
options.signal[abortSignal.remove](abortHandler); removeSignalAlgorithm(options.signal, abortHandler);
// always throw the abort error when aborted // always throw the abort error when aborted
options.signal.throwIfAborted(); options.signal.throwIfAborted();
@ -823,7 +826,7 @@ async function writeFile(
options.signal.throwIfAborted(); options.signal.throwIfAborted();
cancelRid = createCancelHandle(); cancelRid = createCancelHandle();
abortHandler = () => core.tryClose(cancelRid); abortHandler = () => core.tryClose(cancelRid);
options.signal[abortSignal.add](abortHandler); addSignalAlgorithm(options.signal, abortHandler);
} }
try { try {
if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) { if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, data)) {
@ -851,7 +854,7 @@ async function writeFile(
} }
} finally { } finally {
if (options.signal) { if (options.signal) {
options.signal[abortSignal.remove](abortHandler); removeSignalAlgorithm(options.signal, abortHandler);
// always throw the abort error when aborted // always throw the abort error when aborted
options.signal.throwIfAborted(); options.signal.throwIfAborted();

View file

@ -64,7 +64,10 @@ import {
readableStreamForRidUnrefableUnref, readableStreamForRidUnrefableUnref,
writableStreamForRid, writableStreamForRid,
} from "ext:deno_web/06_streams.js"; } from "ext:deno_web/06_streams.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import {
addSignalAlgorithm,
removeSignalAlgorithm,
} from "ext:deno_web/03_abort_signal.js";
import { SymbolDispose } from "ext:deno_web/00_infra.js"; import { SymbolDispose } from "ext:deno_web/00_infra.js";
async function write(rid, data) { async function write(rid, data) {
@ -78,7 +81,7 @@ async function resolveDns(query, recordType, options) {
options.signal.throwIfAborted(); options.signal.throwIfAborted();
cancelRid = createCancelHandle(); cancelRid = createCancelHandle();
abortHandler = () => core.tryClose(cancelRid); abortHandler = () => core.tryClose(cancelRid);
options.signal[abortSignal.add](abortHandler); addSignalAlgorithm(options.signal, abortHandler);
} }
try { try {
@ -91,7 +94,7 @@ async function resolveDns(query, recordType, options) {
return ArrayPrototypeMap(res, (recordWithTtl) => recordWithTtl.data); return ArrayPrototypeMap(res, (recordWithTtl) => recordWithTtl.data);
} finally { } finally {
if (options?.signal) { if (options?.signal) {
options.signal[abortSignal.remove](abortHandler); removeSignalAlgorithm(options.signal, abortHandler);
// always throw the abort error when aborted // always throw the abort error when aborted
options.signal.throwIfAborted(); options.signal.throwIfAborted();
@ -692,7 +695,7 @@ async function connect(args) {
args.signal.throwIfAborted(); args.signal.throwIfAborted();
cancelRid = createCancelHandle(); cancelRid = createCancelHandle();
abortHandler = () => core.tryClose(cancelRid); abortHandler = () => core.tryClose(cancelRid);
args.signal[abortSignal.add](abortHandler); addSignalAlgorithm(args.signal, abortHandler);
} }
const port = validatePort(args.port); const port = validatePort(args.port);
@ -712,7 +715,7 @@ async function connect(args) {
return new TcpConn(rid, remoteAddr, localAddr); return new TcpConn(rid, remoteAddr, localAddr);
} finally { } finally {
if (args?.signal) { if (args?.signal) {
args.signal[abortSignal.remove](abortHandler); removeSignalAlgorithm(args.signal, abortHandler);
args.signal.throwIfAborted(); args.signal.throwIfAborted();
} }
} }

View file

@ -72,10 +72,7 @@ import {
} from "ext:deno_node/internal/validators.mjs"; } from "ext:deno_node/internal/validators.mjs";
import { spliceOne } from "ext:deno_node/_utils.ts"; import { spliceOne } from "ext:deno_node/_utils.ts";
import { nextTick } from "ext:deno_node/_process/process.ts"; import { nextTick } from "ext:deno_node/_process/process.ts";
import { import { getListeners } from "ext:deno_web/02_event.js";
eventTargetData,
kResistStopImmediatePropagation,
} from "ext:deno_web/02_event.js";
export { addAbortListener } from "./internal/events/abort_listener.mjs"; export { addAbortListener } from "./internal/events/abort_listener.mjs";
@ -867,9 +864,7 @@ export function getEventListeners(emitterOrTarget, type) {
return emitterOrTarget.listeners(type); return emitterOrTarget.listeners(type);
} }
if (emitterOrTarget instanceof EventTarget) { if (emitterOrTarget instanceof EventTarget) {
return emitterOrTarget[eventTargetData]?.listeners?.[type]?.map(( return getListeners(emitterOrTarget, type);
listener,
) => listener.callback) || [];
} }
throw new ERR_INVALID_ARG_TYPE( throw new ERR_INVALID_ARG_TYPE(
"emitter", "emitter",
@ -934,7 +929,7 @@ export async function once(emitter, name, options = kEmptyObject) {
signal, signal,
"abort", "abort",
abortListener, abortListener,
{ once: true, [kResistStopImmediatePropagation]: true }, { once: true, [SymbolFor("Deno.stopImmediatePropagation")]: true },
); );
} }
}); });

View file

@ -4,7 +4,10 @@
import { primordials } from "ext:core/mod.js"; import { primordials } from "ext:core/mod.js";
const { queueMicrotask } = primordials; const { queueMicrotask } = primordials;
import { SymbolDispose } from "ext:deno_web/00_infra.js"; import { SymbolDispose } from "ext:deno_web/00_infra.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import {
addSignalAlgorithm,
removeSignalAlgorithm,
} from "ext:deno_web/03_abort_signal.js";
import { validateAbortSignal, validateFunction } from "../validators.mjs"; import { validateAbortSignal, validateFunction } from "../validators.mjs";
import { codes } from "../errors.ts"; import { codes } from "../errors.ts";
const { ERR_INVALID_ARG_TYPE } = codes; const { ERR_INVALID_ARG_TYPE } = codes;
@ -29,9 +32,9 @@ function addAbortListener(signal, listener) {
removeEventListener?.(); removeEventListener?.();
listener({ target: signal }); listener({ target: signal });
}; };
signal[abortSignal.add](handler); addSignalAlgorithm(signal, handler);
removeEventListener = () => { removeEventListener = () => {
signal[abortSignal.remove](handler); removeSignalAlgorithm(signal, handler);
}; };
} }
return { return {

View file

@ -50,7 +50,7 @@ import {
validateString, validateString,
} from "ext:deno_node/internal/validators.mjs"; } from "ext:deno_node/internal/validators.mjs";
import { parseArgs } from "ext:deno_node/internal/util/parse_args/parse_args.js"; import { parseArgs } from "ext:deno_node/internal/util/parse_args/parse_args.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import { addSignalAlgorithm } from "ext:deno_web/03_abort_signal.js";
import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts"; import { ERR_INVALID_ARG_TYPE } from "ext:deno_node/internal/errors.ts";
export { export {
@ -246,7 +246,7 @@ export async function aborted(
return PromiseResolve(); return PromiseResolve();
} }
const abortPromise = PromiseWithResolvers(); const abortPromise = PromiseWithResolvers();
signal[abortSignal.add](abortPromise.resolve); addSignalAlgorithm(signal, abortPromise.resolve);
return abortPromise.promise; return abortPromise.promise;
} }

View file

@ -32,7 +32,10 @@ import {
SymbolAsyncDispose, SymbolAsyncDispose,
} from "ext:deno_web/00_infra.js"; } from "ext:deno_web/00_infra.js";
import { packageData } from "ext:deno_fetch/22_body.js"; import { packageData } from "ext:deno_fetch/22_body.js";
import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import {
addSignalAlgorithm,
removeSignalAlgorithm,
} from "ext:deno_web/03_abort_signal.js";
import { import {
ReadableStream, ReadableStream,
readableStreamCollectIntoUint8Array, readableStreamCollectIntoUint8Array,
@ -314,11 +317,15 @@ class ChildProcess {
// Ignore the error for https://github.com/denoland/deno/issues/27112 // Ignore the error for https://github.com/denoland/deno/issues/27112
} }
}; };
signal?.[abortSignal.add](onAbort); if (signal != null) {
addSignalAlgorithm(signal, onAbort);
}
const waitPromise = op_spawn_wait(this.#rid); const waitPromise = op_spawn_wait(this.#rid);
this.#waitPromise = waitPromise; this.#waitPromise = waitPromise;
this.#status = PromisePrototypeThen(waitPromise, (res) => { this.#status = PromisePrototypeThen(waitPromise, (res) => {
signal?.[abortSignal.remove](onAbort); if (signal != null) {
removeSignalAlgorithm(signal, onAbort);
}
this.#waitComplete = true; this.#waitComplete = true;
return res; return res;
}); });

File diff suppressed because it is too large Load diff

View file

@ -6,107 +6,40 @@
import { core, primordials } from "ext:core/mod.js"; import { core, primordials } from "ext:core/mod.js";
const { const {
ArrayPrototypeEvery, ArrayPrototypeEvery,
ArrayPrototypePush,
FunctionPrototypeApply, FunctionPrototypeApply,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
SafeSet, ObjectDefineProperties,
SafeSetIterator, ObjectDefineProperty,
SafeWeakRef,
SafeWeakSet,
SetPrototypeAdd,
SetPrototypeDelete,
Symbol, Symbol,
SymbolFor, SymbolFor,
TypeError,
WeakRefPrototypeDeref,
WeakSetPrototypeAdd,
WeakSetPrototypeHas,
} = primordials; } = primordials;
import {
AbortController,
AbortSignal,
op_event_add_abort_algorithm,
op_event_create_abort_signal,
op_event_create_dependent_abort_signal,
op_event_get_dependent_signals,
op_event_get_source_signals,
op_event_remove_abort_algorithm,
op_event_signal_abort,
} from "ext:core/ops";
import * as webidl from "ext:deno_webidl/00_webidl.js"; import * as webidl from "ext:deno_webidl/00_webidl.js";
import { assert } from "./00_infra.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { DOMException } from "./01_dom_exception.js";
import { import {
defineEventHandler, defineEventHandler,
Event,
EventTarget, EventTarget,
listenerCount, getListenerCount,
setIsTrusted,
} from "./02_event.js"; } from "./02_event.js";
import { clearTimeout, refTimer, unrefTimer } from "./02_timers.js"; import { clearTimeout, refTimer, unrefTimer } from "./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 runAbortSteps = Symbol("[[runAbortSteps]]");
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 timerId = Symbol("[[timerId]]");
const illegalConstructorKey = Symbol("illegalConstructorKey"); ObjectDefineProperty(AbortSignal, "timeout", {
__proto__: null,
class AbortSignal extends EventTarget { value: function timeout(millis) {
[abortReason] = undefined;
[abortAlgos] = null;
[dependent] = false;
[sourceSignals] = null;
[dependentSignals] = null;
[timerId] = null;
[webidl.brand] = webidl.brand;
static any(signals) {
const prefix = "Failed to execute 'AbortSignal.any'";
webidl.requiredArguments(arguments.length, 1, prefix);
return createDependentAbortSignal(signals, prefix);
}
static abort(reason = undefined) {
if (reason !== undefined) {
reason = webidl.converters.any(reason);
}
const signal = new AbortSignal(illegalConstructorKey);
signal[signalAbort](reason);
return signal;
}
static timeout(millis) {
const prefix = "Failed to execute 'AbortSignal.timeout'"; const prefix = "Failed to execute 'AbortSignal.timeout'";
webidl.requiredArguments(arguments.length, 1, prefix); webidl.requiredArguments(arguments.length, 1, prefix);
millis = webidl.converters["unsigned long long"]( millis = webidl.converters["unsigned long long"](
@ -118,7 +51,7 @@ class AbortSignal extends EventTarget {
}, },
); );
const signal = new AbortSignal(illegalConstructorKey); const signal = op_event_create_abort_signal();
signal[timerId] = core.queueSystemTimer( signal[timerId] = core.queueSystemTimer(
undefined, undefined,
false, false,
@ -126,197 +59,129 @@ class AbortSignal extends EventTarget {
() => { () => {
clearTimeout(signal[timerId]); clearTimeout(signal[timerId]);
signal[timerId] = null; signal[timerId] = null;
signal[signalAbort]( op_event_signal_abort(
signal,
new DOMException("Signal timed out.", "TimeoutError"), new DOMException("Signal timed out.", "TimeoutError"),
); );
}, },
); );
unrefTimer(signal[timerId]); unrefTimer(signal[timerId]);
return signal; return signal;
} },
configurable: true,
enumerable: true,
writable: true,
});
[add](algorithm) { const addEventListener_ = EventTarget.prototype.addEventListener;
if (this.aborted) { const removeEventListener_ = EventTarget.prototype.removeEventListener;
return;
}
this[abortAlgos] ??= new SafeSet();
SetPrototypeAdd(this[abortAlgos], algorithm);
}
[signalAbort]( // `addEventListener` and `removeEventListener` have to be overridden in
reason = new DOMException("The signal has been aborted", "AbortError"), // order to have the timer block the event loop while there are listeners.
) { // `[add]` and `[remove]` don't ref and unref the timer because they can
if (this.aborted) { // only be used by Deno internals, which use it to essentially cancel async
return; // ops which would block the event loop.
} ObjectDefineProperties(AbortSignal.prototype, {
this[abortReason] = reason; addEventListener: {
__proto__: null,
const dependentSignalsToAbort = []; value: function addEventListener() {
if (this[dependentSignals] !== null) { FunctionPrototypeApply(addEventListener_, this, arguments);
const dependentSignalArray = this[dependentSignals].toArray(); if (getListenerCount(this, "abort") > 0) {
for (let i = 0; i < dependentSignalArray.length; ++i) { if (this[timerId] !== null) {
const dependentSignal = dependentSignalArray[i]; refTimer(this[timerId]);
if (dependentSignal[abortReason] === undefined) { } else {
dependentSignal[abortReason] = this[abortReason]; const sourceSignals = op_event_get_source_signals(this);
ArrayPrototypePush(dependentSignalsToAbort, dependentSignal); for (let i = 0; i < sourceSignals.length; ++i) {
} const sourceSignal = sourceSignals[i];
} if (sourceSignal[timerId] !== null) {
} refTimer(sourceSignal[timerId]);
this[runAbortSteps]();
if (dependentSignalsToAbort.length !== 0) {
for (let i = 0; i < dependentSignalsToAbort.length; ++i) {
const dependentSignal = dependentSignalsToAbort[i];
dependentSignal[runAbortSteps]();
}
}
}
[runAbortSteps]() {
const algos = this[abortAlgos];
this[abortAlgos] = null;
if (algos !== null) {
for (const algorithm of new SafeSetIterator(algos)) {
algorithm();
}
}
if (listenerCount(this, "abort") > 0) {
const event = new Event("abort");
setIsTrusted(event, true);
super.dispatchEvent(event);
}
}
[remove](algorithm) {
this[abortAlgos] && SetPrototypeDelete(this[abortAlgos], algorithm);
}
constructor(key = null) {
if (key !== illegalConstructorKey) {
throw new TypeError("Illegal constructor");
}
super();
}
get aborted() {
webidl.assertBranded(this, AbortSignalPrototype);
return this[abortReason] !== undefined;
}
get reason() {
webidl.assertBranded(this, AbortSignalPrototype);
return this[abortReason];
}
throwIfAborted() {
webidl.assertBranded(this, AbortSignalPrototype);
if (this[abortReason] !== undefined) {
throw this[abortReason];
}
}
// `addEventListener` and `removeEventListener` have to be overridden in
// order to have the timer block the event loop while there are listeners.
// `[add]` and `[remove]` don't ref and unref the timer because they can
// only be used by Deno internals, which use it to essentially cancel async
// ops which would block the event loop.
addEventListener() {
FunctionPrototypeApply(super.addEventListener, this, arguments);
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() {
FunctionPrototypeApply(super.removeEventListener, this, arguments);
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]);
} }
} }
} }
} }
} },
} configurable: true,
enumerable: true,
writable: true,
},
removeEventListener: {
__proto__: null,
value: function removeEventListener() {
FunctionPrototypeApply(removeEventListener_, this, arguments);
if (getListenerCount(this, "abort") === 0) {
if (this[timerId] !== null) {
unrefTimer(this[timerId]);
} else {
const sourceSignals = op_event_get_source_signals(this);
for (let i = 0; i < sourceSignals.length; ++i) {
const sourceSignal = sourceSignals[i];
if (sourceSignal[timerId] !== null) {
// Check that all dependent signals of the timer signal do not have listeners
if (
ArrayPrototypeEvery(
op_event_get_dependent_signals(sourceSignal),
(dependentSignal) =>
dependentSignal === this ||
getListenerCount(dependentSignal, "abort") === 0,
)
) {
unrefTimer(sourceSignal[timerId]);
}
}
}
}
}
},
configurable: true,
enumerable: true,
writable: true,
},
[SymbolFor("Deno.privateCustomInspect")]: {
__proto__: null,
value(inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(AbortSignalPrototype, this),
keys: [
"aborted",
"reason",
"onabort",
],
}),
inspectOptions,
);
},
},
});
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
return inspect(
createFilteredInspectProxy({
object: this,
evaluate: ObjectPrototypeIsPrototypeOf(AbortSignalPrototype, this),
keys: [
"aborted",
"reason",
"onabort",
],
}),
inspectOptions,
);
}
}
defineEventHandler(AbortSignal.prototype, "abort"); defineEventHandler(AbortSignal.prototype, "abort");
webidl.configureInterface(AbortSignal); webidl.configureInterface(AbortSignal);
const AbortSignalPrototype = AbortSignal.prototype; const AbortSignalPrototype = AbortSignal.prototype;
class AbortController { ObjectDefineProperty(
[signal] = new AbortSignal(illegalConstructorKey); AbortController.prototype,
SymbolFor("Deno.privateCustomInspect"),
constructor() { {
this[webidl.brand] = webidl.brand; __proto__: null,
} value(inspect, inspectOptions) {
return inspect(
get signal() { createFilteredInspectProxy({
webidl.assertBranded(this, AbortControllerPrototype); object: this,
return this[signal]; evaluate: ObjectPrototypeIsPrototypeOf(
} AbortControllerPrototype,
this,
abort(reason) { ),
webidl.assertBranded(this, AbortControllerPrototype); keys: [
this[signal][signalAbort](reason); "signal",
} ],
}),
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) { inspectOptions,
return inspect( );
createFilteredInspectProxy({ },
object: this, },
evaluate: ObjectPrototypeIsPrototypeOf(AbortControllerPrototype, this), );
keys: [
"signal",
],
}),
inspectOptions,
);
}
}
webidl.configureInterface(AbortController); webidl.configureInterface(AbortController);
const AbortControllerPrototype = AbortController.prototype; const AbortControllerPrototype = AbortController.prototype;
@ -329,61 +194,54 @@ webidl.converters["sequence<AbortSignal>"] = webidl.createSequenceConverter(
webidl.converters.AbortSignal, webidl.converters.AbortSignal,
); );
/**
* @returns {AbortSignal}
*/
function newSignal() { function newSignal() {
return new AbortSignal(illegalConstructorKey); return op_event_create_abort_signal();
} }
/**
* @param {AbortSignal[]} signals
* @param {string} prefix
* @returns {AbortSignal}
*/
function createDependentAbortSignal(signals, prefix) { function createDependentAbortSignal(signals, prefix) {
signals = webidl.converters["sequence<AbortSignal>"]( return op_event_create_dependent_abort_signal(signals, prefix);
signals, }
prefix,
"Argument 1",
);
const resultSignal = new AbortSignal(illegalConstructorKey); /**
for (let i = 0; i < signals.length; ++i) { * @param {AbortSignal} signal
const signal = signals[i]; * @param {() => void} algorithm
if (signal[abortReason] !== undefined) { */
resultSignal[abortReason] = signal[abortReason]; function addSignalAlgorithm(signal, algorithm) {
return resultSignal; op_event_add_abort_algorithm(signal, algorithm);
} }
}
resultSignal[dependent] = true; /**
resultSignal[sourceSignals] = new WeakRefSet(); * @param {AbortSignal} signal
for (let i = 0; i < signals.length; ++i) { * @param {() => void} algorithm
const signal = signals[i]; */
if (!signal[dependent]) { function removeSignalAlgorithm(signal, algorithm) {
signal[dependentSignals] ??= new WeakRefSet(); op_event_remove_abort_algorithm(signal, algorithm);
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; * @param {AbortSignal} signal
} * @param {any} reason
resultSignal[sourceSignals].add(sourceSignal); */
sourceSignal[dependentSignals].add(resultSignal); function signalAbort(signal, reason) {
} op_event_signal_abort(signal, reason);
}
}
return resultSignal;
} }
export { export {
AbortController, AbortController,
AbortSignal, AbortSignal,
AbortSignalPrototype, AbortSignalPrototype,
add, addSignalAlgorithm,
createDependentAbortSignal, createDependentAbortSignal,
newSignal, newSignal,
remove, removeSignalAlgorithm,
signalAbort, signalAbort,
timerId, timerId,
}; };

View file

@ -93,9 +93,9 @@ import * as webidl from "ext:deno_webidl/00_webidl.js";
import { structuredClone } from "./02_structured_clone.js"; import { structuredClone } from "./02_structured_clone.js";
import { import {
AbortSignalPrototype, AbortSignalPrototype,
add, addSignalAlgorithm,
newSignal, newSignal,
remove, removeSignalAlgorithm,
signalAbort, signalAbort,
} from "./03_abort_signal.js"; } from "./03_abort_signal.js";
@ -2747,7 +2747,7 @@ function readableStreamPipeTo(
abortAlgorithm(); abortAlgorithm();
return promise.promise; return promise.promise;
} }
signal[add](abortAlgorithm); addSignalAlgorithm(signal, abortAlgorithm);
} }
function pipeLoop() { function pipeLoop() {
@ -2949,7 +2949,7 @@ function readableStreamPipeTo(
readableStreamDefaultReaderRelease(reader); readableStreamDefaultReaderRelease(reader);
if (signal !== undefined) { if (signal !== undefined) {
signal[remove](abortAlgorithm); removeSignalAlgorithm(signal, abortAlgorithm);
} }
if (isError) { if (isError) {
promise.reject(error); promise.reject(error);
@ -4303,7 +4303,7 @@ function writableStreamAbort(stream, reason) {
if (state === "closed" || state === "errored") { if (state === "closed" || state === "errored") {
return PromiseResolve(undefined); return PromiseResolve(undefined);
} }
stream[_controller][_signal][signalAbort](reason); signalAbort(stream[_controller][_signal], reason);
if (state === "closed" || state === "errored") { if (state === "closed" || state === "errored") {
return PromiseResolve(undefined); return PromiseResolve(undefined);
} }

View file

@ -34,6 +34,7 @@ const {
import * as webidl from "ext:deno_webidl/00_webidl.js"; import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { import {
createEventTargetBranded,
defineEventHandler, defineEventHandler,
EventTarget, EventTarget,
MessageEvent, MessageEvent,
@ -111,7 +112,7 @@ export const unrefParentPort = Symbol("unrefParentPort");
* @returns {MessagePort} * @returns {MessagePort}
*/ */
function createMessagePort(id) { function createMessagePort(id) {
const port = webidl.createBranded(MessagePort); const port = createEventTargetBranded(MessagePortPrototype);
port[core.hostObjectBrand] = core.hostObjectBrand; port[core.hostObjectBrand] = core.hostObjectBrand;
setEventTargetData(port); setEventTargetData(port);
port[_id] = id; port[_id] = id;

View file

@ -7,6 +7,7 @@ const {
ArrayPrototypePush, ArrayPrototypePush,
ObjectKeys, ObjectKeys,
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
ObjectSetPrototypeOf,
ReflectHas, ReflectHas,
Symbol, Symbol,
SymbolFor, SymbolFor,
@ -19,7 +20,7 @@ const {
import * as webidl from "ext:deno_webidl/00_webidl.js"; import * as webidl from "ext:deno_webidl/00_webidl.js";
import { structuredClone } from "./02_structured_clone.js"; import { structuredClone } from "./02_structured_clone.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { EventTarget } from "./02_event.js"; import { EventTarget, createEventTargetBranded } from "./02_event.js";
import { DOMException } from "./01_dom_exception.js"; import { DOMException } from "./01_dom_exception.js";
const illegalConstructorKey = Symbol("illegalConstructorKey"); const illegalConstructorKey = Symbol("illegalConstructorKey");
@ -298,6 +299,7 @@ class PerformanceMark extends PerformanceEntry {
} }
webidl.configureInterface(PerformanceMark); webidl.configureInterface(PerformanceMark);
const PerformanceMarkPrototype = PerformanceMark.prototype; const PerformanceMarkPrototype = PerformanceMark.prototype;
class PerformanceMeasure extends PerformanceEntry { class PerformanceMeasure extends PerformanceEntry {
[_detail] = null; [_detail] = null;
@ -360,14 +362,10 @@ class PerformanceMeasure extends PerformanceEntry {
} }
webidl.configureInterface(PerformanceMeasure); webidl.configureInterface(PerformanceMeasure);
const PerformanceMeasurePrototype = PerformanceMeasure.prototype; const PerformanceMeasurePrototype = PerformanceMeasure.prototype;
class Performance extends EventTarget {
constructor(key = null) {
if (key != illegalConstructorKey) {
webidl.illegalConstructor();
}
super(); class Performance {
this[webidl.brand] = webidl.brand; constructor() {
webidl.illegalConstructor();
} }
get timeOrigin() { get timeOrigin() {
@ -600,6 +598,12 @@ class Performance extends EventTarget {
); );
} }
} }
// Prevent the execution of the EventTarget constructor and make it possible
// to initialize during bootstrap.
ObjectSetPrototypeOf(Performance, EventTarget);
ObjectSetPrototypeOf(Performance.prototype, EventTarget.prototype);
webidl.configureInterface(Performance); webidl.configureInterface(Performance);
const PerformancePrototype = Performance.prototype; const PerformancePrototype = Performance.prototype;
@ -608,7 +612,7 @@ webidl.converters["Performance"] = webidl.createInterfaceConverter(
PerformancePrototype, PerformancePrototype,
); );
const performance = new Performance(illegalConstructorKey); const performance = createEventTargetBranded(Performance.prototype);
export { export {
Performance, Performance,

2420
ext/web/event.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@
mod blob; mod blob;
mod compression; mod compression;
mod event;
mod message_port; mod message_port;
mod stream_resource; mod stream_resource;
mod timers; mod timers;
@ -36,6 +37,7 @@ use crate::blob::op_blob_read_part;
use crate::blob::op_blob_remove_part; use crate::blob::op_blob_remove_part;
use crate::blob::op_blob_revoke_object_url; use crate::blob::op_blob_revoke_object_url;
use crate::blob::op_blob_slice_part; use crate::blob::op_blob_slice_part;
use crate::event::ReportExceptionStackedCalls;
pub use crate::message_port::JsMessageData; pub use crate::message_port::JsMessageData;
pub use crate::message_port::MessagePort; pub use crate::message_port::MessagePort;
pub use crate::message_port::Transferable; pub use crate::message_port::Transferable;
@ -80,6 +82,22 @@ deno_core::extension!(deno_web,
compression::op_compression_new, compression::op_compression_new,
compression::op_compression_write, compression::op_compression_write,
compression::op_compression_finish, compression::op_compression_finish,
event::op_event_dispatch,
event::op_event_get_target_listener_count,
event::op_event_get_target_listeners,
event::op_event_set_is_trusted,
event::op_event_set_target,
event::op_event_create_empty_event_target,
event::op_event_wrap_event_target,
event::op_event_report_error,
event::op_event_report_exception,
event::op_event_create_abort_signal,
event::op_event_create_dependent_abort_signal,
event::op_event_add_abort_algorithm,
event::op_event_remove_abort_algorithm,
event::op_event_signal_abort,
event::op_event_get_source_signals,
event::op_event_get_dependent_signals,
op_now<P>, op_now<P>,
op_time_origin<P>, op_time_origin<P>,
op_defer, op_defer,
@ -92,6 +110,18 @@ deno_core::extension!(deno_web,
stream_resource::op_readable_stream_resource_close, stream_resource::op_readable_stream_resource_close,
stream_resource::op_readable_stream_resource_await_close, stream_resource::op_readable_stream_resource_await_close,
], ],
objects = [
event::Event,
event::CustomEvent,
event::ErrorEvent,
event::PromiseRejectionEvent,
event::CloseEvent,
event::MessageEvent,
event::ProgressEvent,
event::EventTarget,
event::AbortSignal,
event::AbortController,
],
esm = [ esm = [
"00_infra.js", "00_infra.js",
"01_dom_exception.js", "01_dom_exception.js",
@ -122,6 +152,7 @@ deno_core::extension!(deno_web,
if let Some(location) = options.maybe_location { if let Some(location) = options.maybe_location {
state.put(Location(location)); state.put(Location(location));
} }
state.put(ReportExceptionStackedCalls::default());
state.put(StartTime::default()); state.put(StartTime::default());
} }
); );

View file

@ -56,6 +56,7 @@ import { HTTP_TOKEN_CODE_POINT_RE } from "ext:deno_web/00_infra.js";
import { DOMException } from "ext:deno_web/01_dom_exception.js"; import { DOMException } from "ext:deno_web/01_dom_exception.js";
import { clearTimeout, setTimeout } from "ext:deno_web/02_timers.js"; import { clearTimeout, setTimeout } from "ext:deno_web/02_timers.js";
import { import {
createEventTargetBranded,
CloseEvent, CloseEvent,
defineEventHandler, defineEventHandler,
dispatch, dispatch,
@ -742,7 +743,7 @@ webidl.configureInterface(WebSocket);
const WebSocketPrototype = WebSocket.prototype; const WebSocketPrototype = WebSocket.prototype;
function createWebSocketBranded() { function createWebSocketBranded() {
const socket = webidl.createBranded(WebSocket); const socket = createEventTargetBranded(WebSocketPrototype);
socket[_rid] = undefined; socket[_rid] = undefined;
socket[_role] = undefined; socket[_role] = undefined;
socket[_readyState] = CONNECTING; socket[_readyState] = CONNECTING;

View file

@ -36,7 +36,10 @@ import * as webidl from "ext:deno_webidl/00_webidl.js";
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js"; import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
import { Deferred, writableStreamClose } from "ext:deno_web/06_streams.js"; import { Deferred, writableStreamClose } from "ext:deno_web/06_streams.js";
import { DOMException } from "ext:deno_web/01_dom_exception.js"; import { DOMException } from "ext:deno_web/01_dom_exception.js";
import { add, remove } from "ext:deno_web/03_abort_signal.js"; import {
addSignalAlgorithm,
removeSignalAlgorithm,
} from "ext:deno_web/03_abort_signal.js";
import { import {
fillHeaders, fillHeaders,
headerListFromHeaders, headerListFromHeaders,
@ -165,7 +168,9 @@ class WebSocketStream {
const abort = () => { const abort = () => {
core.close(cancelRid); core.close(cancelRid);
}; };
options.signal?.[add](abort); if (options.signal != null) {
addSignalAlgorithm(options.signal, abort);
}
PromisePrototypeThen( PromisePrototypeThen(
op_ws_create( op_ws_create(
"new WebSocketStream()", "new WebSocketStream()",
@ -175,7 +180,9 @@ class WebSocketStream {
headerListFromHeaders(headers), headerListFromHeaders(headers),
), ),
(create) => { (create) => {
options.signal?.[remove](abort); if (options.signal != null) {
removeSignalAlgorithm(options.signal, abort);
}
if (this[_earlyClose]) { if (this[_earlyClose]) {
PromisePrototypeThen( PromisePrototypeThen(
op_ws_close(create.rid), op_ws_close(create.rid),

View file

@ -719,6 +719,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
removeImportedOps(); removeImportedOps();
performance.setTimeOrigin(); performance.setTimeOrigin();
event.setEventTargetData(performance.performance);
globalThis_ = globalThis; globalThis_ = globalThis;
// Remove bootstrapping data from the global scope // Remove bootstrapping data from the global scope
@ -756,6 +757,7 @@ function bootstrapMainRuntime(runtimeOptions, warmup = false) {
core.wrapConsole(globalThis.console, core.v8Console); core.wrapConsole(globalThis.console, core.v8Console);
} }
event.setEventTargetData(globalThis);
event.defineEventHandler(globalThis, "error"); event.defineEventHandler(globalThis, "error");
event.defineEventHandler(globalThis, "load"); event.defineEventHandler(globalThis, "load");
event.defineEventHandler(globalThis, "beforeunload"); event.defineEventHandler(globalThis, "beforeunload");
@ -852,6 +854,7 @@ function bootstrapWorkerRuntime(
closeOnIdle = runtimeOptions[14]; closeOnIdle = runtimeOptions[14];
performance.setTimeOrigin(); performance.setTimeOrigin();
event.setEventTargetData(performance.performance);
globalThis_ = globalThis; globalThis_ = globalThis;
// Remove bootstrapping data from the global scope // Remove bootstrapping data from the global scope
@ -884,6 +887,7 @@ function bootstrapWorkerRuntime(
core.wrapConsole(globalThis.console, core.v8Console); core.wrapConsole(globalThis.console, core.v8Console);
event.setEventTargetData(globalThis);
event.defineEventHandler(globalThis, "message"); event.defineEventHandler(globalThis, "message");
event.defineEventHandler(globalThis, "error", undefined, true); event.defineEventHandler(globalThis, "error", undefined, true);
@ -969,13 +973,8 @@ globalThis.bootstrap = {
dispatchProcessBeforeExitEvent, dispatchProcessBeforeExitEvent,
}; };
event.setEventTargetData(globalThis);
event.saveGlobalThisReference(globalThis);
event.defineEventHandler(globalThis, "unhandledrejection"); event.defineEventHandler(globalThis, "unhandledrejection");
// Nothing listens to this, but it warms up the code paths for event dispatch
(new event.EventTarget()).dispatchEvent(new Event("warmup"));
removeImportedOps(); removeImportedOps();
// Run the warmup path through node and runtime/worker bootstrap functions // Run the warmup path through node and runtime/worker bootstrap functions

View file

@ -142,12 +142,12 @@ pub fn op_bootstrap_color_depth(state: &mut OpState) -> i32 {
} }
#[op2(fast)] #[op2(fast)]
pub fn op_bootstrap_no_color(_state: &mut OpState) -> bool { pub fn op_bootstrap_no_color() -> bool {
!deno_terminal::colors::use_color() !deno_terminal::colors::use_color()
} }
#[op2(fast)] #[op2(fast)]
pub fn op_bootstrap_stdout_no_color(_state: &mut OpState) -> bool { pub fn op_bootstrap_stdout_no_color() -> bool {
if deno_terminal::colors::force_color() { if deno_terminal::colors::force_color() {
return false; return false;
} }
@ -156,7 +156,7 @@ pub fn op_bootstrap_stdout_no_color(_state: &mut OpState) -> bool {
} }
#[op2(fast)] #[op2(fast)]
pub fn op_bootstrap_stderr_no_color(_state: &mut OpState) -> bool { pub fn op_bootstrap_stderr_no_color() -> bool {
if deno_terminal::colors::force_color() { if deno_terminal::colors::force_color() {
return false; return false;
} }