node/test/parallel/test-webstorage.js
Mohamed Sayed fda667a6c8
src: fix crash when reading length on Storage.prototype
Signed-off-by: Mohamed Sayed <k@3zrv.com>
PR-URL: https://github.com/nodejs/node/pull/63529
Fixes: https://github.com/nodejs/node/issues/63514
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
2026-05-27 08:13:58 +00:00

148 lines
4.8 KiB
JavaScript

'use strict';
const { skipIfSQLiteMissing, spawnPromisified } = require('../common');
skipIfSQLiteMissing();
const tmpdir = require('../common/tmpdir');
const assert = require('node:assert');
const { join } = require('node:path');
const { readdir } = require('node:fs/promises');
const { test, describe } = require('node:test');
let cnt = 0;
tmpdir.refresh();
function nextLocalStorage() {
return join(tmpdir.path, `${++cnt}.localstorage`);
}
test('Storage instances cannot be created in userland', async () => {
const cp = await spawnPromisified(process.execPath, [
'-e', 'new globalThis.Storage()',
]);
assert.strictEqual(cp.code, 1);
assert.strictEqual(cp.signal, null);
assert.strictEqual(cp.stdout, '');
assert.match(cp.stderr, /Error: Illegal constructor/);
});
test('calling "length" getter on invalid this throws', async () => {
assert.throws(() => Storage.prototype.length, TypeError);
const { get } = Object.getOwnPropertyDescriptor(Storage.prototype, 'length');
for (const thisArg of [null, undefined, 1n, -0, NaN, true, false, '', [], {}, Symbol()]) {
assert.throws(() => get.call(thisArg), TypeError);
}
});
test('sessionStorage is not persisted', async () => {
let cp = await spawnPromisified(process.execPath, [
'-pe', 'sessionStorage.foo = "barbaz"',
]);
assert.strictEqual(cp.code, 0);
assert.match(cp.stdout, /barbaz/);
cp = await spawnPromisified(process.execPath, [
'-pe', 'sessionStorage.foo',
]);
assert.strictEqual(cp.code, 0);
assert.match(cp.stdout, /undefined/);
assert.strictEqual((await readdir(tmpdir.path)).length, 0);
});
test('localStorage returns undefined and warns without --localstorage-file', async () => {
const cp = await spawnPromisified(process.execPath, [
'-pe', 'localStorage',
]);
assert.strictEqual(cp.code, 0);
assert.strictEqual(cp.signal, null);
assert.match(cp.stdout, /undefined/);
assert.match(cp.stderr, /ExperimentalWarning:.*localStorage is not available/);
});
test('localStorage is not enumerable without --localstorage-file', async () => {
const cp = await spawnPromisified(process.execPath, [
'-pe', 'Object.keys(globalThis).includes("localStorage")',
]);
assert.strictEqual(cp.code, 0);
assert.match(cp.stdout, /false/);
});
test('localStorage is not persisted if it is unused', async () => {
const cp = await spawnPromisified(process.execPath, [
'--localstorage-file', nextLocalStorage(),
'-pe', 'localStorage === globalThis.localStorage',
]);
assert.strictEqual(cp.code, 0);
assert.match(cp.stdout, /true/);
assert.strictEqual((await readdir(tmpdir.path)).length, 0);
});
test('localStorage is persisted if it is used', async () => {
const localStorageFile = nextLocalStorage();
let cp = await spawnPromisified(process.execPath, [
'--localstorage-file', localStorageFile,
'-pe', 'localStorage.foo = "barbaz"',
]);
assert.strictEqual(cp.code, 0);
assert.match(cp.stdout, /barbaz/);
const entries = await readdir(tmpdir.path);
assert.strictEqual(entries.length, 1);
assert.match(entries[0], /\d+\.localstorage/);
cp = await spawnPromisified(process.execPath, [
'--localstorage-file', localStorageFile,
'-pe', 'localStorage.foo',
]);
assert.strictEqual(cp.code, 0);
assert.match(cp.stdout, /barbaz/);
});
describe('webstorage quota for localStorage and sessionStorage', () => {
const MAX_STORAGE_SIZE = 10 * 1024 * 1024;
for (const storage of ['localStorage', 'sessionStorage']) {
test(`${storage} can store a max of 10 MB quota`, async () => {
const args = [];
if (storage === 'localStorage') {
args.push('--localstorage-file', nextLocalStorage());
}
// Each character is 2 bytes
args.push('-e', `
const assert = require('assert');
${storage}['a'.repeat(${MAX_STORAGE_SIZE} / 2)] = '';
assert.throws(
() => { ${storage}.anything = 'should fail'; },
(err) => {
assert.strictEqual(err.name, 'QuotaExceededError');
assert.strictEqual(err.code, 22);
assert(err instanceof DOMException);
assert(err instanceof QuotaExceededError);
assert.strictEqual(err.quota, null);
assert.strictEqual(err.requested, null);
return true;
},
);
`);
const cp = await spawnPromisified(process.execPath, args);
assert.strictEqual(cp.code, 0);
});
}
});
test('disabled with --no-webstorage', async () => {
for (const api of ['Storage', 'localStorage', 'sessionStorage']) {
const cp = await spawnPromisified(process.execPath, [
'--no-webstorage',
'--localstorage-file',
'./test/fixtures/localstoragefile-global-test',
'-e',
api,
]);
assert.strictEqual(cp.code, 1);
assert.strictEqual(cp.signal, null);
assert.strictEqual(cp.stdout, '');
assert(cp.stderr.includes(`ReferenceError: ${api} is not defined`));
}
});