diff --git a/cli/tsc/dts/lib.deno_web.d.ts b/cli/tsc/dts/lib.deno_web.d.ts index 3546141d5f779a..db4061eaacff15 100644 --- a/cli/tsc/dts/lib.deno_web.d.ts +++ b/cli/tsc/dts/lib.deno_web.d.ts @@ -69,6 +69,31 @@ declare var DOMException: { readonly DATA_CLONE_ERR: 25; }; +/** @category Platform */ +interface QuotaExceededErrorOptions { + quota?: number; + requested?: number; +} + +/** + * Represents an error when a quota has been exceeded. + * + * @category Platform + */ +interface QuotaExceededError extends DOMException { + readonly quota: number | null; + readonly requested: number | null; +} + +/** @category Platform */ +declare var QuotaExceededError: { + readonly prototype: QuotaExceededError; + new ( + message?: string, + options?: QuotaExceededErrorOptions, + ): QuotaExceededError; +}; + /** @category Events */ interface EventInit { bubbles?: boolean; diff --git a/cli/tsc/mod.rs b/cli/tsc/mod.rs index e670c3e7e06d0c..7bdd727e364a73 100644 --- a/cli/tsc/mod.rs +++ b/cli/tsc/mod.rs @@ -1344,6 +1344,8 @@ pub static TYPES_NODE_IGNORABLE_NAMES: &[&str] = &[ "PerformanceMeasure", "QueuingStrategy", "QueuingStrategySize", + "QuotaExceededError", + "QuotaExceededErrorOptions", "ReadableByteStreamController", "ReadableStream", "ReadableStreamBYOBReader", diff --git a/ext/web/01_dom_exception.js b/ext/web/01_dom_exception.js index f50ebac2e37122..358762807e37c9 100644 --- a/ext/web/01_dom_exception.js +++ b/ext/web/01_dom_exception.js @@ -19,6 +19,7 @@ const { ObjectSetPrototypeOf, ReflectConstruct, ReflectHas, + RangeError, Symbol, SymbolFor, } = primordials; @@ -243,4 +244,72 @@ core.registerCloneableResource("DOMException", (data) => { return ex; }); -export { DOMException, DOMExceptionPrototype }; +const _quota = Symbol("quota"); +const _requested = Symbol("requested"); + +// Defined in WebIDL 4.3.1. +// https://webidl.spec.whatwg.org/#quotaexceedederror +class QuotaExceededError extends DOMException { + [_quota]; + [_requested]; + + constructor(message = "", options = { __proto__: null }) { + super(message, "QuotaExceededError"); + + if (options !== null && typeof options === "object") { + if (ObjectHasOwn(options, "quota")) { + const quota = webidl.converters["unrestricted double"]( + options.quota, + "Failed to construct 'QuotaExceededError'", + "'quota' member of QuotaExceededErrorOptions", + ); + if (quota < 0) { + throw new RangeError( + "Failed to construct 'QuotaExceededError': quota must not be negative", + ); + } + this[_quota] = quota; + } else { + this[_quota] = null; + } + if (ObjectHasOwn(options, "requested")) { + const requested = webidl.converters["unrestricted double"]( + options.requested, + "Failed to construct 'QuotaExceededError'", + "'requested' member of QuotaExceededErrorOptions", + ); + if (requested < 0) { + throw new RangeError( + "Failed to construct 'QuotaExceededError': requested must not be negative", + ); + } + this[_requested] = requested; + } else { + this[_requested] = null; + } + } else { + this[_quota] = null; + this[_requested] = null; + } + } + + get quota() { + webidl.assertBranded(this, QuotaExceededErrorPrototype); + return this[_quota]; + } + + get requested() { + webidl.assertBranded(this, QuotaExceededErrorPrototype); + return this[_requested]; + } +} + +webidl.configureInterface(QuotaExceededError); +const QuotaExceededErrorPrototype = QuotaExceededError.prototype; + +export { + DOMException, + DOMExceptionPrototype, + QuotaExceededError, + QuotaExceededErrorPrototype, +}; diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index e4e5bb2689cb93..f0bcd08f85159a 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -49,6 +49,7 @@ declare module "ext:deno_web/00_infra.js" { declare module "ext:deno_web/01_dom_exception.js" { const DOMException: DOMException; + const QuotaExceededError: QuotaExceededError; } declare module "ext:deno_web/01_mimesniff.js" { diff --git a/runtime/js/98_global_scope_shared.js b/runtime/js/98_global_scope_shared.js index fda4da9fca7acd..98d2ef1df8506d 100644 --- a/runtime/js/98_global_scope_shared.js +++ b/runtime/js/98_global_scope_shared.js @@ -28,7 +28,10 @@ import * as fetch from "ext:deno_fetch/26_fetch.js"; import * as eventSource from "ext:deno_fetch/27_eventsource.js"; import * as messagePort from "ext:deno_web/13_message_port.js"; import * as webidl from "ext:deno_webidl/00_webidl.js"; -import { DOMException } from "ext:deno_web/01_dom_exception.js"; +import { + DOMException, + QuotaExceededError, +} from "ext:deno_web/01_dom_exception.js"; import * as abortSignal from "ext:deno_web/03_abort_signal.js"; import * as imageData from "ext:deno_web/16_image_data.js"; import process from "node:process"; @@ -66,6 +69,7 @@ const windowOrWorkerGlobalScope = { CustomEvent: core.propNonEnumerable(event.CustomEvent), DecompressionStream: core.propNonEnumerable(compression.DecompressionStream), DOMException: core.propNonEnumerable(DOMException), + QuotaExceededError: core.propNonEnumerable(QuotaExceededError), ErrorEvent: core.propNonEnumerable(event.ErrorEvent), Event: core.propNonEnumerable(event.Event), EventTarget: core.propNonEnumerable(event.EventTarget), diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index a17750dd2df681..98918fbb77caa5 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -76,7 +76,10 @@ import { } from "ext:runtime/90_deno_ns.js"; import { errors } from "ext:runtime/01_errors.js"; import * as webidl from "ext:deno_webidl/00_webidl.js"; -import { DOMException } from "ext:deno_web/01_dom_exception.js"; +import { + DOMException, + QuotaExceededError, +} from "ext:deno_web/01_dom_exception.js"; import { unstableForWindowOrWorkerGlobalScope, windowOrWorkerGlobalScope, @@ -351,7 +354,7 @@ core.registerErrorBuilder( core.registerErrorBuilder( "DOMExceptionQuotaExceededError", function DOMExceptionQuotaExceededError(msg) { - return new DOMException(msg, "QuotaExceededError"); + return new QuotaExceededError(msg); }, ); core.registerErrorBuilder( diff --git a/tests/unit/dom_exception_test.ts b/tests/unit/dom_exception_test.ts index 82138d78428cab..901f479023a842 100644 --- a/tests/unit/dom_exception_test.ts +++ b/tests/unit/dom_exception_test.ts @@ -5,6 +5,7 @@ import { assertEquals, assertNotEquals, assertStringIncludes, + assertThrows, } from "./test_util.ts"; Deno.test(function customInspectFunction() { @@ -30,3 +31,81 @@ Deno.test(function hasStackAccessor() { assert(typeof desc.get === "function"); assert(typeof desc.set === "function"); }); + +// QuotaExceededError still maps to code 22 in the DOMException names table +Deno.test(function domExceptionQuotaExceededErrorCode() { + const e = new DOMException("test", "QuotaExceededError"); + assertEquals(e.code, 22); + assertEquals(e.name, "QuotaExceededError"); +}); + +// QuotaExceededError as a DOMException derived interface +Deno.test(function quotaExceededErrorBasic() { + const e = new QuotaExceededError("quota exceeded"); + assert(e instanceof QuotaExceededError); + assert(e instanceof DOMException); + assert(e instanceof Error); + assertEquals(e.name, "QuotaExceededError"); + assertEquals(e.message, "quota exceeded"); + assertEquals(e.code, 22); + assertEquals(e.quota, null); + assertEquals(e.requested, null); +}); + +Deno.test(function quotaExceededErrorWithOptions() { + const e = new QuotaExceededError("too much", { quota: 100, requested: 200 }); + assertEquals(e.name, "QuotaExceededError"); + assertEquals(e.message, "too much"); + assertEquals(e.code, 22); + assertEquals(e.quota, 100); + assertEquals(e.requested, 200); +}); + +Deno.test(function quotaExceededErrorDefaultMessage() { + const e = new QuotaExceededError(); + assertEquals(e.message, ""); + assertEquals(e.name, "QuotaExceededError"); + assertEquals(e.code, 22); + assertEquals(e.quota, null); + assertEquals(e.requested, null); +}); + +Deno.test(function quotaExceededErrorPartialOptions() { + const e1 = new QuotaExceededError("msg", { quota: 50 }); + assertEquals(e1.quota, 50); + assertEquals(e1.requested, null); + + const e2 = new QuotaExceededError("msg", { requested: 75 }); + assertEquals(e2.quota, null); + assertEquals(e2.requested, 75); +}); + +Deno.test(function quotaExceededErrorNegativeQuotaThrows() { + assertThrows( + () => new QuotaExceededError("msg", { quota: -1 }), + RangeError, + ); +}); + +Deno.test(function quotaExceededErrorNegativeRequestedThrows() { + assertThrows( + () => new QuotaExceededError("msg", { requested: -1 }), + RangeError, + ); +}); + +Deno.test(function quotaExceededErrorLegacyCodeConstant() { + assertEquals(DOMException.QUOTA_EXCEEDED_ERR, 22); +}); + +Deno.test(function quotaExceededErrorConstructorName() { + const e = new QuotaExceededError("test"); + assertEquals(e.constructor, QuotaExceededError); + assertEquals(e.constructor.name, "QuotaExceededError"); +}); + +Deno.test(function quotaExceededErrorHasStack() { + const e = new QuotaExceededError("test"); + assert(typeof e.stack === "string"); + assertStringIncludes(e.stack, "QuotaExceededError"); +}); diff --git a/tests/wpt/runner/expectations/WebCryptoAPI.json b/tests/wpt/runner/expectations/WebCryptoAPI.json index f1e7eab91e2e36..071d1e1560a1f2 100644 --- a/tests/wpt/runner/expectations/WebCryptoAPI.json +++ b/tests/wpt/runner/expectations/WebCryptoAPI.json @@ -1,6 +1,30 @@ { - "getRandomValues.any.html": true, - "getRandomValues.any.worker.html": true, + "getRandomValues.any.html": { + "expectedFailures": [ + "Large length: Int8Array", + "Large length: Int16Array", + "Large length: Int32Array", + "Large length: BigInt64Array", + "Large length: Uint8Array", + "Large length: Uint8ClampedArray", + "Large length: Uint16Array", + "Large length: Uint32Array", + "Large length: BigUint64Array" + ] + }, + "getRandomValues.any.worker.html": { + "expectedFailures": [ + "Large length: Int8Array", + "Large length: Int16Array", + "Large length: Int32Array", + "Large length: BigInt64Array", + "Large length: Uint8Array", + "Large length: Uint8ClampedArray", + "Large length: Uint16Array", + "Large length: Uint32Array", + "Large length: BigUint64Array" + ] + }, "derive_bits_keys": { "ecdh_bits.https.any.html": { "expectedFailures": [ diff --git a/tests/wpt/runner/expectations/fetch.json b/tests/wpt/runner/expectations/fetch.json index 0ec0ca0eac3083..97a4ad2d03be8f 100644 --- a/tests/wpt/runner/expectations/fetch.json +++ b/tests/wpt/runner/expectations/fetch.json @@ -2589,11 +2589,11 @@ "", "", "\\n \\n ", - "Fetch: /images/gre\\ten-1x1.png?img=<", "Fetch: /images/green-1x1.png?<\\n=block", "Fetch: /images/gre\\nen-1x1.png?img=<", "Fetch: /images/gre\\ren-1x1.png?img=<", "Fetch: /images/green-1x1.png?<\\r=block", + "Fetch: /images/gre\\ten-1x1.png?img=<", "Fetch: /images/green-1x1.png?<\\t=block" ] }, diff --git a/tests/wpt/runner/expectations/webstorage.json b/tests/wpt/runner/expectations/webstorage.json index a43f337a3ebde5..bb27a6c6abe5a1 100644 --- a/tests/wpt/runner/expectations/webstorage.json +++ b/tests/wpt/runner/expectations/webstorage.json @@ -14,9 +14,9 @@ "storage_key.window.html": true, "storage_key_empty_string.window.html": true, "storage_length.window.html": true, - "storage_local_setitem_quotaexceedederr.window.html": true, + "storage_local_setitem_quotaexceedederr.window.html": false, "storage_removeitem.window.html": true, - "storage_session_setitem_quotaexceedederr.window.html": true, + "storage_session_setitem_quotaexceedederr.window.html": false, "storage_set_value_enumerate.window.html": true, "storage_setitem.window.html": { "expectedFailures": [ diff --git a/tools/update_types_node.ts b/tools/update_types_node.ts index 5fa53f2dbbd3b7..f988614f86089b 100755 --- a/tools/update_types_node.ts +++ b/tools/update_types_node.ts @@ -330,6 +330,7 @@ function handleVarDecl(decl: VariableDeclaration) { case "PerformanceEntry": case "PerformanceMark": case "PerformanceMeasure": + case "QuotaExceededError": case "ReadableByteStreamController": case "ReadableStream": case "ReadableStreamBYOBReader":