Skip to content

Commit ebc1299

Browse files
committed
lib: return undefined for localStorage without file
Make localStorage return undefined and emit a warning when --localstorage-file is not provided. Mark the property as non-enumerable to avoid breaking {...globalThis}. Fixes: #60303
1 parent ce2ec3d commit ebc1299

File tree

3 files changed

+35
-16
lines changed

3 files changed

+35
-16
lines changed

lib/internal/util.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,9 +688,14 @@ function defineReplaceableLazyAttribute(target, id, keys, writable = true, check
688688
value: `set ${key}`,
689689
});
690690

691+
// Respect enumerability from the source module's property descriptor.
692+
mod ??= require(id);
693+
const descriptor = ObjectGetOwnPropertyDescriptor(mod, key);
694+
const enumerable = descriptor ? descriptor.enumerable : true;
695+
691696
ObjectDefineProperty(target, key, {
692697
__proto__: null,
693-
enumerable: true,
698+
enumerable,
694699
configurable: true,
695700
get,
696701
set: writable ? set : undefined,

lib/internal/webstorage.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ const {
33
ObjectDefineProperties,
44
} = primordials;
55
const { getOptionValue } = require('internal/options');
6-
const { lazyDOMException } = require('internal/util');
76
const { kConstructorKey, Storage } = internalBinding('webstorage');
87
const { getValidatedPath } = require('internal/fs/utils');
98
const kInMemoryPath = ':memory:';
@@ -12,26 +11,32 @@ module.exports = { Storage };
1211

1312
let lazyLocalStorage;
1413
let lazySessionStorage;
14+
let localStorageWarned = false;
15+
16+
// Check at load time if localStorage file is provided to determine enumerability.
17+
// If not provided, localStorage is non-enumerable to avoid breaking {...globalThis}.
18+
const localStorageLocation = getOptionValue('--localstorage-file');
1519

1620
ObjectDefineProperties(module.exports, {
1721
__proto__: null,
1822
localStorage: {
1923
__proto__: null,
2024
configurable: true,
21-
enumerable: true,
25+
enumerable: localStorageLocation !== '',
2226
get() {
2327
if (lazyLocalStorage === undefined) {
24-
// For consistency with the web specification, throw from the accessor
25-
// if the local storage path is not provided.
26-
const location = getOptionValue('--localstorage-file');
27-
if (location === '') {
28-
throw lazyDOMException(
29-
'Cannot initialize local storage without a `--localstorage-file` path',
30-
'SecurityError',
31-
);
28+
if (localStorageLocation === '') {
29+
if (!localStorageWarned) {
30+
localStorageWarned = true;
31+
process.emitWarning(
32+
'localStorage is not available because --localstorage-file was not provided.',
33+
'ExperimentalWarning',
34+
);
35+
}
36+
return undefined;
3237
}
3338

34-
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
39+
lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation));
3540
}
3641

3742
return lazyLocalStorage;

test/parallel/test-webstorage.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,22 @@ test('sessionStorage is not persisted', async () => {
4141
assert.strictEqual((await readdir(tmpdir.path)).length, 0);
4242
});
4343

44-
test('localStorage throws without --localstorage-file', async () => {
44+
test('localStorage returns undefined and warns without --localstorage-file', async () => {
4545
const cp = await spawnPromisified(process.execPath, [
46-
'-e', 'localStorage',
46+
'-pe', 'localStorage',
4747
]);
48-
assert.strictEqual(cp.code, 1);
48+
assert.strictEqual(cp.code, 0);
4949
assert.strictEqual(cp.signal, null);
50-
assert.match(cp.stderr, /SecurityError:/);
50+
assert.match(cp.stdout, /undefined/);
51+
assert.match(cp.stderr, /ExperimentalWarning:.*localStorage is not available/);
52+
});
53+
54+
test('localStorage is not enumerable without --localstorage-file', async () => {
55+
const cp = await spawnPromisified(process.execPath, [
56+
'-pe', 'Object.keys(globalThis).includes("localStorage")',
57+
]);
58+
assert.strictEqual(cp.code, 0);
59+
assert.match(cp.stdout, /false/);
5160
});
5261

5362
test('localStorage is not persisted if it is unused', async () => {

0 commit comments

Comments
 (0)