Skip to content

Commit b328bf7

Browse files
committed
lib,src: implement QuotaExceededError as DOMException-derived interface
Implement QuotaExceededError as a DOMException-derived interface per the WebIDL specification update. QuotaExceededError is now a proper constructor exposed as a global [Exposed=*] interface that extends DOMException with optional `quota` and `requested` attributes (both nullable doubles, defaulting to null). The constructor validates that quota and requested are finite, non-negative, and that requested is not less than quota when both are provided. QuotaExceededError is [Serializable] and supports structuredClone, preserving the quota and requested values across the serialization boundary. Callers updated: - crypto.getRandomValues() now throws a QuotaExceededError instance - WebStorage (C++) now constructs QuotaExceededError directly Refs: https://redirect.github.com/whatwg/webidl/pull/1465 Fixes: #58987 PR-URL: #62293 Reviewed-By: Mattias Buelens <[email protected]> Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: Michaël Zasso <[email protected]> Reviewed-By: Colin Ihrig <[email protected]>
1 parent 4ee467f commit b328bf7

File tree

15 files changed

+376
-75
lines changed

15 files changed

+376
-75
lines changed

doc/api/globals.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,6 +976,14 @@ DataHandler.prototype.load = async function load(key) {
976976
};
977977
```
978978

979+
## Class: `QuotaExceededError`
980+
981+
<!-- YAML
982+
added: REPLACEME
983+
-->
984+
985+
The WHATWG {QuotaExceededError} class. Extends {DOMException}.
986+
979987
## Class: `ReadableByteStreamController`
980988

981989
<!-- YAML

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export default [
130130
Float16Array: 'readonly',
131131
FormData: 'readonly',
132132
navigator: 'readonly',
133+
QuotaExceededError: 'readonly',
133134
ReadableStream: 'readonly',
134135
ReadableStreamDefaultReader: 'readonly',
135136
ReadableStreamBYOBReader: 'readonly',

lib/eslint.config_partial.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const noRestrictedSyntax = [
2323
message: "`btoa` supports only latin-1 charset, use Buffer.from(str).toString('base64') instead",
2424
},
2525
{
26-
selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError)$/])',
26+
selector: 'NewExpression[callee.name=/Error$/]:not([callee.name=/^(AssertionError|NghttpError|AbortError|NodeAggregateError|QuotaExceededError)$/])',
2727
message: "Use an error exported by 'internal/errors' instead.",
2828
},
2929
{
@@ -122,6 +122,10 @@ export default [
122122
name: 'DOMException',
123123
message: "Use lazy function `const { lazyDOMExceptionClass } = require('internal/util');` instead of the global.",
124124
},
125+
{
126+
name: 'QuotaExceededError',
127+
message: "Use `internalBinding('messaging').QuotaExceededError` instead of the global.",
128+
},
125129
{
126130
name: 'ErrorEvent',
127131
message: "Use `const { ErrorEvent } = require('internal/deps/undici/undici');` instead of the global.",

lib/internal/bootstrap/web/exposed-wildcard.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ exposeInterface(globalThis, 'URL', URL);
2929
exposeInterface(globalThis, 'URLSearchParams', URLSearchParams);
3030
exposeLazyDOMExceptionProperty(globalThis);
3131

32+
// https://webidl.spec.whatwg.org/#quotaexceedederror
33+
exposeLazyInterfaces(globalThis, 'internal/worker/clone_dom_exception', [
34+
'QuotaExceededError',
35+
]);
36+
3237
// https://dom.spec.whatwg.org/#interface-abortcontroller
3338
// Lazy ones.
3439
exposeLazyInterfaces(globalThis, 'internal/abort_controller', [

lib/internal/crypto/random.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ function getRandomValues(data) {
328328
'TypeMismatchError');
329329
}
330330
if (data.byteLength > 65536) {
331-
throw lazyDOMException(
332-
'The requested length exceeds 65,536 bytes',
333-
'QuotaExceededError');
331+
const { QuotaExceededError } = internalBinding('messaging');
332+
throw new QuotaExceededError(
333+
'The requested length exceeds 65,536 bytes');
334334
}
335335
randomFillSync(data, 0);
336336
return data;

lib/internal/per_context/domexception.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
const {
44
Error,
55
ErrorPrototype,
6+
NumberIsFinite,
7+
NumberIsNaN,
68
ObjectDefineProperties,
79
ObjectDefineProperty,
810
ObjectSetPrototypeOf,
11+
RangeError,
912
SafeMap,
1013
SafeSet,
1114
SafeWeakMap,
@@ -203,3 +206,91 @@ for (const { 0: name, 1: codeName, 2: value } of [
203206
}
204207

205208
exports.DOMException = DOMException;
209+
210+
// https://webidl.spec.whatwg.org/#quotaexceedederror
211+
class QuotaExceededError extends DOMException {
212+
#quota;
213+
#requested;
214+
215+
constructor(message = '', options = { __proto__: null }) {
216+
super(message, 'QuotaExceededError');
217+
this[transfer_mode_private_symbol] = kCloneable;
218+
219+
let quota = null;
220+
let requested = null;
221+
222+
if (options !== null && options !== undefined) {
223+
if ('quota' in options) {
224+
quota = +options.quota;
225+
if (!NumberIsFinite(quota)) {
226+
// eslint-disable-next-line no-restricted-syntax
227+
throw new TypeError(
228+
`Cannot convert options.quota to a double: the value is ${NumberIsNaN(quota) ? 'NaN' : 'Infinity'}`,
229+
);
230+
}
231+
if (quota < 0) {
232+
// eslint-disable-next-line no-restricted-syntax
233+
throw new RangeError('options.quota must not be negative');
234+
}
235+
}
236+
237+
if ('requested' in options) {
238+
requested = +options.requested;
239+
if (!NumberIsFinite(requested)) {
240+
// eslint-disable-next-line no-restricted-syntax
241+
throw new TypeError(
242+
`Cannot convert options.requested to a double: the value is ${NumberIsNaN(requested) ? 'NaN' : 'Infinity'}`,
243+
);
244+
}
245+
if (requested < 0) {
246+
// eslint-disable-next-line no-restricted-syntax
247+
throw new RangeError('options.requested must not be negative');
248+
}
249+
}
250+
}
251+
252+
if (quota !== null && requested !== null && requested < quota) {
253+
// eslint-disable-next-line no-restricted-syntax
254+
throw new RangeError('options.requested must not be less than options.quota');
255+
}
256+
257+
this.#quota = quota;
258+
this.#requested = requested;
259+
}
260+
261+
[messaging_clone_symbol]() {
262+
const domExceptionClone = DOMExceptionPrototype[messaging_clone_symbol].call(this);
263+
domExceptionClone.data.quota = this.#quota;
264+
domExceptionClone.data.requested = this.#requested;
265+
domExceptionClone.deserializeInfo = 'internal/worker/clone_dom_exception:QuotaExceededError';
266+
return domExceptionClone;
267+
}
268+
269+
[messaging_deserialize_symbol](data) {
270+
DOMExceptionPrototype[messaging_deserialize_symbol].call(this, data);
271+
this.#quota = data.quota;
272+
this.#requested = data.requested;
273+
}
274+
275+
get quota() {
276+
if (!(#quota in this)) {
277+
throwInvalidThisError(TypeError, 'QuotaExceededError');
278+
}
279+
return this.#quota;
280+
}
281+
282+
get requested() {
283+
if (!(#requested in this)) {
284+
throwInvalidThisError(TypeError, 'QuotaExceededError');
285+
}
286+
return this.#requested;
287+
}
288+
}
289+
290+
ObjectDefineProperties(QuotaExceededError.prototype, {
291+
[SymbolToStringTag]: { __proto__: null, configurable: true, value: 'QuotaExceededError' },
292+
quota: { __proto__: null, enumerable: true, configurable: true },
293+
requested: { __proto__: null, enumerable: true, configurable: true },
294+
});
295+
296+
exports.QuotaExceededError = QuotaExceededError;
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
'use strict';
22

3-
// Delegate to the actual DOMException implementation.
3+
// Delegate to the actual DOMException/QuotaExceededError implementation.
4+
const messaging = internalBinding('messaging');
45
module.exports = {
5-
DOMException: internalBinding('messaging').DOMException,
6+
DOMException: messaging.DOMException,
7+
QuotaExceededError: messaging.QuotaExceededError,
68
};

src/node_messaging.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,21 @@ static void CreatePerContextProperties(Local<Object> target,
17621762
domexception)
17631763
.Check();
17641764
}
1765+
{
1766+
Local<Object> per_context_bindings;
1767+
Local<Value> quota_exceeded_error_val;
1768+
if (GetPerContextExports(context).ToLocal(&per_context_bindings) &&
1769+
per_context_bindings
1770+
->Get(context,
1771+
FIXED_ONE_BYTE_STRING(env->isolate(), "QuotaExceededError"))
1772+
.ToLocal(&quota_exceeded_error_val)) {
1773+
target
1774+
->Set(context,
1775+
FIXED_ONE_BYTE_STRING(env->isolate(), "QuotaExceededError"),
1776+
quota_exceeded_error_val)
1777+
.Check();
1778+
}
1779+
}
17651780
}
17661781

17671782
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {

src/node_webstorage.cc

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,23 @@ using v8::Value;
5757

5858
static void ThrowQuotaExceededException(Local<Context> context) {
5959
Isolate* isolate = Isolate::GetCurrent();
60-
auto dom_exception_str = FIXED_ONE_BYTE_STRING(isolate, "DOMException");
61-
auto err_name = FIXED_ONE_BYTE_STRING(isolate, "QuotaExceededError");
60+
auto quota_exceeded_str =
61+
FIXED_ONE_BYTE_STRING(isolate, "QuotaExceededError");
6262
auto err_message =
6363
FIXED_ONE_BYTE_STRING(isolate, "Setting the value exceeded the quota");
6464
Local<Object> per_context_bindings;
65-
Local<Value> domexception_ctor_val;
65+
Local<Value> quota_exceeded_ctor_val;
6666
if (!GetPerContextExports(context).ToLocal(&per_context_bindings) ||
67-
!per_context_bindings->Get(context, dom_exception_str)
68-
.ToLocal(&domexception_ctor_val)) {
67+
!per_context_bindings->Get(context, quota_exceeded_str)
68+
.ToLocal(&quota_exceeded_ctor_val)) {
6969
return;
7070
}
71-
CHECK(domexception_ctor_val->IsFunction());
72-
Local<Function> domexception_ctor = domexception_ctor_val.As<Function>();
73-
Local<Value> argv[] = {err_message, err_name};
71+
CHECK(quota_exceeded_ctor_val->IsFunction());
72+
Local<Function> quota_exceeded_ctor = quota_exceeded_ctor_val.As<Function>();
73+
Local<Value> argv[] = {err_message};
7474
Local<Value> exception;
7575

76-
if (!domexception_ctor->NewInstance(context, arraysize(argv), argv)
76+
if (!quota_exceeded_ctor->NewInstance(context, arraysize(argv), argv)
7777
.ToLocal(&exception)) {
7878
return;
7979
}

test/common/globals.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ intrinsics.add('console');
7979

8080
const webIdlExposedWildcard = new Set([
8181
'DOMException',
82+
'QuotaExceededError',
8283
'TextEncoder',
8384
'TextDecoder',
8485
'AbortController',

0 commit comments

Comments
 (0)