Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 23d67eb

Browse files
Zirrodomenic
authored andcommitted
Add the storageQuota option
Also fixes the default quota problem noticed in 3afbc0f#r29395675.
1 parent b4db242 commit 23d67eb

File tree

7 files changed

+97
-12
lines changed

7 files changed

+97
-12
lines changed

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ const dom = new JSDOM(``, {
4747
referrer: "https://example.com/",
4848
contentType: "text/html",
4949
userAgent: "Mellblomenator/9000",
50-
includeNodeLocations: true
50+
includeNodeLocations: true,
51+
storageQuota: 10000000
5152
});
5253
```
5354

@@ -56,6 +57,7 @@ const dom = new JSDOM(``, {
5657
- `contentType` affects the value read from `document.contentType`, and how the document is parsed: as HTML or as XML. Values that are not `"text/html"` or an [XML mime type](https://html.spec.whatwg.org/multipage/infrastructure.html#xml-mime-type) will throw. It defaults to `"text/html"`.
5758
- `userAgent` affects the value read from `navigator.userAgent`, as well as the `User-Agent` header sent while fetching subresources. It defaults to <code>\`Mozilla/5.0 (${process.platform}) AppleWebKit/537.36 (KHTML, like Gecko) jsdom/${jsdomVersion}\`</code>.
5859
- `includeNodeLocations` preserves the location info produced by the HTML parser, allowing you to retrieve it with the `nodeLocation()` method (described below). It also ensures that line numbers reported in exception stack traces for code running inside `<script>` elements are correct. It defaults to `false` to give the best performance, and cannot be used with an XML content type since our XML parser does not support location info.
60+
- `storageQuota` is the maximum size in bytes for the separate storage areas used by `localStorage` and `sessionStorage`. Attempts to store data larger than this limit will cause a `DOMException` to be thrown. By default, it is set to five megabytes per origin as suggested by the HTML specification.
5961

6062
Note that both `url` and `referrer` are canonicalized before they're used, so e.g. if you pass in `"https:example.com"`, jsdom will interpret that as if you had given `"https://example.com/"`. If you pass an unparseable URL, the call will throw. (URLs are parsed and serialized according to the [URL Standard](http://url.spec.whatwg.org/).)
6163

lib/api.js

+5
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ function transformOptions(options, encoding) {
241241
runScripts: undefined,
242242
encoding,
243243
pretendToBeVisual: false,
244+
storageQuota: 5000000,
244245

245246
// Defaults filled in later
246247
virtualConsole: undefined,
@@ -314,6 +315,10 @@ function transformOptions(options, encoding) {
314315
transformed.windowOptions.pretendToBeVisual = Boolean(options.pretendToBeVisual);
315316
}
316317

318+
if (options.storageQuota !== undefined) {
319+
transformed.windowOptions.storageQuota = Number(options.storageQuota);
320+
}
321+
317322
// concurrentNodeIterators??
318323

319324
return transformed;

lib/jsdom/browser/Window.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ function Window(options) {
142142
this._length = 0;
143143

144144
this._pretendToBeVisual = options.pretendToBeVisual;
145+
this._storageQuota = options.storageQuota;
145146

146147
// Some properties (such as localStorage and sessionStorage) share data
147148
// between windows in the same origin. This object is intended
@@ -166,13 +167,15 @@ function Window(options) {
166167
associatedWindow: this,
167168
storageArea: this._currentOriginData.localStorageArea,
168169
type: "localStorage",
169-
url: this._document.documentURI
170+
url: this._document.documentURI,
171+
storageQuota: this._storageQuota
170172
});
171173
this._sessionStorage = Storage.create([], {
172174
associatedWindow: this,
173175
storageArea: this._currentOriginData.sessionStorageArea,
174176
type: "sessionStorage",
175-
url: this._document.documentURI
177+
url: this._document.documentURI,
178+
storageQuota: this._storageQuota
176179
});
177180

178181
///// GETTERS

lib/jsdom/living/webstorage/Storage-impl.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@ const idlUtils = require("../generated/utils");
66

77
// https://html.spec.whatwg.org/multipage/webstorage.html#the-storage-interface
88
class StorageImpl {
9-
constructor(args, { associatedWindow, storageArea, url, type }) {
9+
constructor(args, { associatedWindow, storageArea, url, type, storageQuota }) {
1010
this._associatedWindow = associatedWindow;
1111
this._items = storageArea;
1212
this._url = url;
1313
this._type = type;
14-
15-
// The spec suggests a default storage quota of 5 MB
16-
this._quota = 5000;
14+
this._quota = storageQuota;
1715
}
1816

1917
_dispatchStorageEvent(key, oldValue, newValue) {
@@ -60,9 +58,12 @@ class StorageImpl {
6058

6159
// Concatenate all keys and values to measure their size against the quota
6260
let itemsConcat = key + value;
63-
this._items.forEach((v, k) => {
64-
itemsConcat += v + k;
65-
});
61+
for (const [curKey, curValue] of this._items) {
62+
// If the key already exists, skip it as it will be set to the new value instead
63+
if (key !== curKey) {
64+
itemsConcat += curKey + curValue;
65+
}
66+
}
6667
if (Buffer.byteLength(itemsConcat) > this._quota) {
6768
throw new DOMException(`The ${this._quota} byte storage quota has been exceeded.`, "QuotaExceededError");
6869
}

lib/old-api.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ exports.jsdom = function (html, options) {
126126
options.pretendToBeVisual = false;
127127
}
128128

129+
options.storageQuota = options.storageQuota || 5000000;
130+
129131
// List options explicitly to be clear which are passed through
130132
const window = new Window({
131133
parsingMode: options.parsingMode,
@@ -149,7 +151,8 @@ exports.jsdom = function (html, options) {
149151
proxy: options.proxy,
150152
userAgent: options.userAgent,
151153
runScripts: options.runScripts,
152-
pretendToBeVisual: options.pretendToBeVisual
154+
pretendToBeVisual: options.pretendToBeVisual,
155+
storageQuota: options.storageQuota
153156
});
154157

155158
const documentImpl = idlUtils.implForWrapper(window.document);

test/api/options.js

+70
Original file line numberDiff line numberDiff line change
@@ -289,4 +289,74 @@ describe("API: constructor options", () => {
289289
});
290290
});
291291
});
292+
293+
describe("storageQuota", () => {
294+
describe("not set", () => {
295+
it("should be 5000000 bytes by default", () => {
296+
const { localStorage, sessionStorage } = (new JSDOM(``, { url: "https://example.com" })).window;
297+
const dataWithinQuota = "0".repeat(4000000);
298+
299+
localStorage.setItem("foo", dataWithinQuota);
300+
sessionStorage.setItem("bar", dataWithinQuota);
301+
302+
assert.strictEqual(localStorage.foo, dataWithinQuota);
303+
assert.strictEqual(sessionStorage.bar, dataWithinQuota);
304+
305+
const dataExceedingQuota = "0".repeat(6000000);
306+
307+
assert.throws(() => localStorage.setItem("foo", dataExceedingQuota));
308+
assert.throws(() => sessionStorage.setItem("bar", dataExceedingQuota));
309+
});
310+
});
311+
312+
describe("set to 10000 bytes", () => {
313+
it("should only allow setting data within the custom quota", () => {
314+
const { localStorage, sessionStorage } = (new JSDOM(``, {
315+
url: "https://example.com",
316+
storageQuota: 10000
317+
})).window;
318+
const dataWithinQuota = "0".repeat(5);
319+
320+
localStorage.setItem("foo", dataWithinQuota);
321+
sessionStorage.setItem("bar", dataWithinQuota);
322+
323+
assert.strictEqual(localStorage.foo, dataWithinQuota);
324+
assert.strictEqual(sessionStorage.bar, dataWithinQuota);
325+
326+
const dataJustWithinQuota = "0".repeat(9995);
327+
328+
localStorage.foo = dataJustWithinQuota;
329+
sessionStorage.bar = dataJustWithinQuota;
330+
331+
assert.strictEqual(localStorage.foo, dataJustWithinQuota);
332+
assert.strictEqual(sessionStorage.bar, dataJustWithinQuota);
333+
334+
const dataExceedingQuota = "0".repeat(15000);
335+
336+
assert.throws(() => localStorage.setItem("foo", dataExceedingQuota));
337+
assert.throws(() => sessionStorage.setItem("bar", dataExceedingQuota));
338+
});
339+
});
340+
341+
describe("set to 10000000 bytes", () => {
342+
it("should only allow setting data within the custom quota", () => {
343+
const { localStorage, sessionStorage } = (new JSDOM(``, {
344+
url: "https://example.com",
345+
storageQuota: 10000000
346+
})).window;
347+
const dataWithinQuota = "0".repeat(8000000);
348+
349+
localStorage.someKey = dataWithinQuota;
350+
sessionStorage.someKey = dataWithinQuota;
351+
352+
assert.strictEqual(localStorage.someKey, dataWithinQuota);
353+
assert.strictEqual(sessionStorage.someKey, dataWithinQuota);
354+
355+
const dataExceedingQuota = "0".repeat(11000000);
356+
357+
assert.throws(() => localStorage.setItem("foo", dataExceedingQuota));
358+
assert.throws(() => sessionStorage.setItem("bar", dataExceedingQuota));
359+
});
360+
});
361+
});
292362
});

test/web-platform-tests/run-single-wpt.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ function createJSDOM(urlPrefix, testPath, expectFail) {
8383
doneErrors.push(error);
8484
}
8585
},
86-
pretendToBeVisual: true
86+
pretendToBeVisual: true,
87+
storageQuota: 100000 // Filling the default quota takes about a minute between two WPTs
8788
});
8889
});
8990

0 commit comments

Comments
 (0)