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/1465Fixes: #58987 PR-URL: #62293 Reviewed-By: Mattias Buelens <mattias@buelens.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 4ee467f commit b328bf7

15 files changed

Lines changed: 376 additions & 75 deletions

‎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-
throwlazyDOMException(
332-
'The requested length exceeds 65,536 bytes',
333-
'QuotaExceededError');
331+
const{ QuotaExceededError }=internalBinding('messaging');
332+
thrownewQuotaExceededError(
333+
'The requested length exceeds 65,536 bytes');
334334
}
335335
randomFillSync(data,0);
336336
returndata;

‎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+
classQuotaExceededErrorextendsDOMException{
212+
#quota;
213+
#requested;
214+
215+
constructor(message='',options={__proto__: null}){
216+
super(message,'QuotaExceededError');
217+
this[transfer_mode_private_symbol]=kCloneable;
218+
219+
letquota=null;
220+
letrequested=null;
221+
222+
if(options!==null&&options!==undefined){
223+
if('quota'inoptions){
224+
quota=+options.quota;
225+
if(!NumberIsFinite(quota)){
226+
// eslint-disable-next-line no-restricted-syntax
227+
thrownewTypeError(
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+
thrownewRangeError('options.quota must not be negative');
234+
}
235+
}
236+
237+
if('requested'inoptions){
238+
requested=+options.requested;
239+
if(!NumberIsFinite(requested)){
240+
// eslint-disable-next-line no-restricted-syntax
241+
thrownewTypeError(
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+
thrownewRangeError('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+
thrownewRangeError('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+
constdomExceptionClone=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+
returndomExceptionClone;
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+
getquota(){
276+
if(!(#quota inthis)){
277+
throwInvalidThisError(TypeError,'QuotaExceededError');
278+
}
279+
returnthis.#quota;
280+
}
281+
282+
getrequested(){
283+
if(!(#requested inthis)){
284+
throwInvalidThisError(TypeError,'QuotaExceededError');
285+
}
286+
returnthis.#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+
constmessaging=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
staticvoidRegisterExternalReferences(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
staticvoidThrowQuotaExceededException(Local<Context> context) {
5959
Isolate* isolate = Isolate::GetCurrent();
60-
autodom_exception_str = FIXED_ONE_BYTE_STRING(isolate, "DOMException");
61-
auto err_name =FIXED_ONE_BYTE_STRING(isolate, "QuotaExceededError");
60+
autoquota_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
constwebIdlExposedWildcard=newSet([
8181
'DOMException',
82+
'QuotaExceededError',
8283
'TextEncoder',
8384
'TextDecoder',
8485
'AbortController',

0 commit comments

Comments
 (0)