Skip to content

Commit fa70327

Browse files
authored
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 PR-URL: #61333 Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
1 parent 7698480 commit fa70327

5 files changed

Lines changed: 58 additions & 22 deletions

File tree

‎lib/internal/process/pre_execution.js‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,26 @@ function setupWebStorage() {
404404

405405
// https://html.spec.whatwg.org/multipage/webstorage.html#webstorage
406406
exposeLazyInterfaces(globalThis,'internal/webstorage',['Storage']);
407+
408+
// localStorage is non-enumerable when --localstorage-file is not provided
409+
// to avoid breaking {...globalThis} operations.
410+
constlocalStorageFile=getOptionValue('--localstorage-file');
411+
letlazyLocalStorage;
412+
ObjectDefineProperty(globalThis,'localStorage',{
413+
__proto__: null,
414+
enumerable: localStorageFile!=='',
415+
configurable: true,
416+
get(){
417+
lazyLocalStorage??=require('internal/webstorage').localStorage;
418+
returnlazyLocalStorage;
419+
},
420+
set(value){
421+
lazyLocalStorage=value;
422+
},
423+
});
424+
407425
defineReplaceableLazyAttribute(globalThis,'internal/webstorage',[
408-
'localStorage','sessionStorage',
426+
'sessionStorage',
409427
]);
410428
}
411429

‎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
constkInMemoryPath=':memory:';
@@ -12,26 +11,32 @@ module.exports = { Storage };
1211

1312
letlazyLocalStorage;
1413
letlazySessionStorage;
14+
letlocalStorageWarned=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+
constlocalStorageLocation=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-
constlocation=getOptionValue('--localstorage-file');
27-
if(location===''){
28-
throwlazyDOMException(
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+
returnundefined;
3237
}
3338

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

3742
returnlazyLocalStorage;

‎test/common/index.js‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,10 @@ const hasSQLite = Boolean(process.versions.sqlite);
7171
consthasQuic=hasCrypto&&!!process.features.quic;
7272

7373
consthasLocalStorage=(()=>{
74-
try{
75-
returnhasSQLite&&globalThis.localStorage!==undefined;
76-
}catch{
77-
returnfalse;
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+
constdesc=Object.getOwnPropertyDescriptor(globalThis,'localStorage');
77+
returnhasSQLite&&desc?.enumerable===true;
7978
})();
8079

8180
/**

‎test/parallel/test-global.js‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,12 @@ for (const moduleName of builtinModules) {
6161
'navigator',
6262
];
6363
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+
}
6570
}
6671
assert.deepStrictEqual(newSet(Object.keys(globalThis)),newSet(expected));
6772
expected.forEach((value)=>{

‎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((awaitreaddir(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
constcp=awaitspawnPromisified(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:.*localStorageisnotavailable/);
52+
});
53+
54+
test('localStorage is not enumerable without --localstorage-file',async()=>{
55+
constcp=awaitspawnPromisified(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)