diff --git a/lib/internal/webstorage.js b/lib/internal/webstorage.js index 28b5df3f3da835..989ccb2dd40eea 100644 --- a/lib/internal/webstorage.js +++ b/lib/internal/webstorage.js @@ -1,9 +1,9 @@ 'use strict'; const { ObjectDefineProperties, - Proxy, } = primordials; const { getOptionValue } = require('internal/options'); +const { lazyDOMException } = require('internal/util'); const { kConstructorKey, Storage } = internalBinding('webstorage'); const { getValidatedPath } = require('internal/fs/utils'); const kInMemoryPath = ':memory:'; @@ -21,34 +21,17 @@ ObjectDefineProperties(module.exports, { enumerable: true, get() { if (lazyLocalStorage === undefined) { + // For consistency with the web specification, throw from the accessor + // if the local storage path is not provided. const location = getOptionValue('--localstorage-file'); - if (location === '') { - let warningEmitted = false; - const handler = { - __proto__: null, - get(target, prop) { - if (!warningEmitted) { - process.emitWarning('`--localstorage-file` was provided without a valid path'); - warningEmitted = true; - } - - return undefined; - }, - set(target, prop, value) { - if (!warningEmitted) { - process.emitWarning('`--localstorage-file` was provided without a valid path'); - warningEmitted = true; - } - - return false; - }, - }; - - lazyLocalStorage = new Proxy({}, handler); - } else { - lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location)); + throw lazyDOMException( + 'Cannot initialize local storage without a `--localstorage-file` path', + 'SecurityError', + ); } + + lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location)); } return lazyLocalStorage; diff --git a/test/common/index.js b/test/common/index.js index 83166076e16405..4c7d61514e5285 100755 --- a/test/common/index.js +++ b/test/common/index.js @@ -59,6 +59,14 @@ const hasSQLite = Boolean(process.versions.sqlite); const hasQuic = hasCrypto && !!process.features.quic; +const hasLocalStorage = (() => { + try { + return hasSQLite && globalThis.localStorage !== undefined; + } catch { + return false; + } +})(); + /** * Parse test metadata from the specified file. * @param {string} filename - The name of the file to parse. @@ -350,7 +358,6 @@ const knownGlobals = new Set([ 'CompressionStream', 'DecompressionStream', 'Storage', - 'localStorage', 'sessionStorage', ].forEach((i) => { if (globalThis[i] !== undefined) { @@ -365,6 +372,10 @@ if (hasCrypto) { knownGlobals.add(globalThis.SubtleCrypto); } +if (hasLocalStorage) { + knownGlobals.add(globalThis.localStorage); +} + const { Worker } = require('node:worker_threads'); knownGlobals.add(Worker); @@ -389,6 +400,11 @@ if (process.env.NODE_TEST_KNOWN_GLOBALS !== '0') { if (val === 'crypto' && !hasCrypto) { continue; } + // globalThis.localStorage is a getter that throws if Node.js was + // executed without a --localstorage-file path. + if (val === 'localStorage' && !hasLocalStorage) { + continue; + } if (!knownGlobals.has(globalThis[val])) { leaked.push(val); } @@ -933,6 +949,7 @@ const common = { hasQuic, hasInspector, hasSQLite, + hasLocalStorage, invalidArgTypeHelper, isAlive, isASan, diff --git a/test/common/index.mjs b/test/common/index.mjs index b535f38ae32673..29030aeb591ba2 100644 --- a/test/common/index.mjs +++ b/test/common/index.mjs @@ -19,6 +19,7 @@ const { hasQuic, hasInspector, hasSQLite, + hasLocalStorage, hasIntl, hasIPv6, isAIX, @@ -71,6 +72,7 @@ export { hasQuic, hasInspector, hasSQLite, + hasLocalStorage, hasIntl, hasIPv6, isAIX, diff --git a/test/parallel/test-assert-checktag.js b/test/parallel/test-assert-checktag.js index b86a1bde7f096d..b1c3660ab4a693 100644 --- a/test/parallel/test-assert-checktag.js +++ b/test/parallel/test-assert-checktag.js @@ -1,5 +1,5 @@ 'use strict'; -const { hasCrypto } = require('../common'); +const { hasCrypto, hasLocalStorage } = require('../common'); const { test } = require('node:test'); const assert = require('assert'); @@ -12,7 +12,7 @@ const assert = require('assert'); if (process.stdout.isTTY) process.env.NODE_DISABLE_COLORS = '1'; -test('', { skip: !hasCrypto }, () => { +test({ skip: !hasCrypto || !hasLocalStorage }, () => { // See https://github.com/nodejs/node/issues/10258 { const date = new Date('2016'); diff --git a/test/parallel/test-webstorage.js b/test/parallel/test-webstorage.js index bc48c9643773b5..cc5f0366f7116e 100644 --- a/test/parallel/test-webstorage.js +++ b/test/parallel/test-webstorage.js @@ -41,13 +41,13 @@ test('sessionStorage is not persisted', async () => { assert.strictEqual((await readdir(tmpdir.path)).length, 0); }); -test('localStorage emits a warning when used without --localstorage-file ', async () => { +test('localStorage throws without --localstorage-file', async () => { const cp = await spawnPromisified(process.execPath, [ - '-pe', 'localStorage.length', + '-e', 'localStorage', ]); - assert.strictEqual(cp.code, 0); + assert.strictEqual(cp.code, 1); assert.strictEqual(cp.signal, null); - assert.match(cp.stderr, /Warning: `--localstorage-file` was provided without a valid path/); + assert.match(cp.stderr, /SecurityError:/); }); test('localStorage is not persisted if it is unused', async () => {