Skip to content

Commit

Permalink
Change storageQuota to operate on code units, not bytes
Browse files Browse the repository at this point in the history
Test runs in browsers on CI revealed that the buffer shim used in browsers is very slow at calculating byte length. Even in Node, it takes an unnecessarily-long amount of time. Instead, we can just set the quota to be derived from the string length, since that's the native format of the data. This is much less expensive to compute.
  • Loading branch information
domenic committed Jul 26, 2018
1 parent 23d67eb commit 500a209
Show file tree
Hide file tree
Showing 3 changed files with 11 additions and 11 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -57,7 +57,7 @@ const dom = new JSDOM(``, {
- `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"`.
- `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>.
- `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.
- `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.
- `storageQuota` is the maximum size in code units 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 5,000,000 code units per origin, as inspired by the HTML specification.

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/).)

Expand Down
10 changes: 5 additions & 5 deletions lib/jsdom/living/webstorage/Storage-impl.js
Expand Up @@ -56,16 +56,16 @@ class StorageImpl {
return;
}

// Concatenate all keys and values to measure their size against the quota
let itemsConcat = key + value;
// Concatenate all keys and values to measure their length against the quota
let itemsTotalLength = key.length + value.length;
for (const [curKey, curValue] of this._items) {
// If the key already exists, skip it as it will be set to the new value instead
if (key !== curKey) {
itemsConcat += curKey + curValue;
itemsTotalLength += curKey.length + curValue.length;
}
}
if (Buffer.byteLength(itemsConcat) > this._quota) {
throw new DOMException(`The ${this._quota} byte storage quota has been exceeded.`, "QuotaExceededError");
if (itemsTotalLength > this._quota) {
throw new DOMException(`The ${this._quota}-code unit storage quota has been exceeded.`, "QuotaExceededError");
}

setTimeout(this._dispatchStorageEvent.bind(this), 0, key, oldValue, value);
Expand Down
10 changes: 5 additions & 5 deletions test/api/options.js
Expand Up @@ -292,7 +292,7 @@ describe("API: constructor options", () => {

describe("storageQuota", () => {
describe("not set", () => {
it("should be 5000000 bytes by default", () => {
it("should be 5000000 code units by default", () => {
const { localStorage, sessionStorage } = (new JSDOM(``, { url: "https://example.com" })).window;
const dataWithinQuota = "0".repeat(4000000);

Expand All @@ -309,7 +309,7 @@ describe("API: constructor options", () => {
});
});

describe("set to 10000 bytes", () => {
describe("set to 10000 code units", () => {
it("should only allow setting data within the custom quota", () => {
const { localStorage, sessionStorage } = (new JSDOM(``, {
url: "https://example.com",
Expand Down Expand Up @@ -338,21 +338,21 @@ describe("API: constructor options", () => {
});
});

describe("set to 10000000 bytes", () => {
describe("set to 10000000 code units", () => {
it("should only allow setting data within the custom quota", () => {
const { localStorage, sessionStorage } = (new JSDOM(``, {
url: "https://example.com",
storageQuota: 10000000
})).window;
const dataWithinQuota = "0".repeat(8000000);
const dataWithinQuota = "0000000000".repeat(800000);

localStorage.someKey = dataWithinQuota;
sessionStorage.someKey = dataWithinQuota;

assert.strictEqual(localStorage.someKey, dataWithinQuota);
assert.strictEqual(sessionStorage.someKey, dataWithinQuota);

const dataExceedingQuota = "0".repeat(11000000);
const dataExceedingQuota = "0000000000".repeat(1100000);

assert.throws(() => localStorage.setItem("foo", dataExceedingQuota));
assert.throws(() => sessionStorage.setItem("bar", dataExceedingQuota));
Expand Down

0 comments on commit 500a209

Please sign in to comment.