GeezerVison is showing github.com — lib: return undefined for localStorage without file · nodejs/node@fa70327
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/process/pre_execution.js +19-1Lines changed: 19 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -404,8 +404,26 @@ function setupWebStorage() { | 404 | 404 |
| 405 | 405 | // https://html.spec.whatwg.org/multipage/webstorage.html#webstorage | 406 | 406 | exposeLazyInterfaces(globalThis, 'internal/webstorage', ['Storage']); | | 407 | + | | 408 | + // localStorage is non-enumerable when --localstorage-file is not provided | | 409 | + // to avoid breaking {...globalThis} operations. | | 410 | + const localStorageFile = getOptionValue('--localstorage-file'); | | 411 | + let lazyLocalStorage; | | 412 | + ObjectDefineProperty(globalThis, 'localStorage', { | | 413 | + __proto__: null, | | 414 | + enumerable: localStorageFile !== '', | | 415 | + configurable: true, | | 416 | + get() { | | 417 | + lazyLocalStorage ??= require('internal/webstorage').localStorage; | | 418 | + return lazyLocalStorage; | | 419 | + }, | | 420 | + set(value) { | | 421 | + lazyLocalStorage = value; | | 422 | + }, | | 423 | + }); | | 424 | + | 407 | 425 | defineReplaceableLazyAttribute(globalThis, 'internal/webstorage', [ | 408 | | - 'localStorage', 'sessionStorage', | | 426 | + 'sessionStorage', | 409 | 427 | ]); | 410 | 428 | } | 411 | 429 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/webstorage.js +16-11Lines changed: 16 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | ObjectDefineProperties, | 4 | 4 | } = primordials; | 5 | 5 | const { getOptionValue } = require('internal/options'); | 6 | | -const { lazyDOMException } = require('internal/util'); | 7 | 6 | const { kConstructorKey, Storage } = internalBinding('webstorage'); | 8 | 7 | const { getValidatedPath } = require('internal/fs/utils'); | 9 | 8 | const kInMemoryPath = ':memory:'; | @@ -12,26 +11,32 @@ module.exports = { Storage }; | 12 | 11 |
| 13 | 12 | let lazyLocalStorage; | 14 | 13 | 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'); | 15 | 19 |
| 16 | 20 | ObjectDefineProperties(module.exports, { | 17 | 21 | __proto__: null, | 18 | 22 | localStorage: { | 19 | 23 | __proto__: null, | 20 | 24 | configurable: true, | 21 | | - enumerable: true, | | 25 | + enumerable: localStorageLocation !== '', | 22 | 26 | get() { | 23 | 27 | 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; | 32 | 37 | } | 33 | 38 |
| 34 | | - lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location)); | | 39 | + lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(localStorageLocation)); | 35 | 40 | } | 36 | 41 |
| 37 | 42 | return lazyLocalStorage; | |
Collapse file Copy file name to clipboardExpand all lines: test/common/index.js +4-5Lines changed: 4 additions & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -71,11 +71,10 @@ const hasSQLite = Boolean(process.versions.sqlite); | 71 | 71 | const hasQuic = hasCrypto && !!process.features.quic; | 72 | 72 |
| 73 | 73 | const hasLocalStorage = (() => { | 74 | | - try { | 75 | | - return hasSQLite && globalThis.localStorage !== undefined; | 76 | | - } catch { | 77 | | - return false; | 78 | | - } | | 74 | + // Check enumerable property to avoid triggering the getter which emits a warning. | | 75 | + // localStorage is enumerable only when --localstorage-file is provided. | | 76 | + const desc = Object.getOwnPropertyDescriptor(globalThis, 'localStorage'); | | 77 | + return hasSQLite && desc?.enumerable === true; | 79 | 78 | })(); | 80 | 79 |
| 81 | 80 | /** | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-global.js +6-1Lines changed: 6 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -61,7 +61,12 @@ for (const moduleName of builtinModules) { | 61 | 61 | 'navigator', | 62 | 62 | ]; | 63 | 63 | if (common.hasSQLite) { | 64 | | - expected.push('localStorage', 'sessionStorage'); | | 64 | + // sessionStorage is always enumerable when SQLite is available. | | 65 | + // localStorage is only enumerable when --localstorage-file is provided. | | 66 | + expected.push('sessionStorage'); | | 67 | + if (common.hasLocalStorage) { | | 68 | + expected.push('localStorage'); | | 69 | + } | 65 | 70 | } | 66 | 71 | assert.deepStrictEqual(new Set(Object.keys(globalThis)), new Set(expected)); | 67 | 72 | expected.forEach((value) => { | |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-webstorage.js +13-4Lines changed: 13 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -41,13 +41,22 @@ test('sessionStorage is not persisted', async () => { | 41 | 41 | assert.strictEqual((await readdir(tmpdir.path)).length, 0); | 42 | 42 | }); | 43 | 43 |
| 44 | | -test('localStorage throws without --localstorage-file', async () => { | | 44 | +test('localStorage returns undefined and warns without --localstorage-file', async () => { | 45 | 45 | const cp = await spawnPromisified(process.execPath, [ | 46 | | - '-e', 'localStorage', | | 46 | + '-pe', 'localStorage', | 47 | 47 | ]); | 48 | | - assert.strictEqual(cp.code, 1); | | 48 | + assert.strictEqual(cp.code, 0); | 49 | 49 | 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/); | 51 | 60 | }); | 52 | 61 |
| 53 | 62 | test('localStorage is not persisted if it is unused', async () => { | |
|