Repository: nodejs/node
Pull request: [v25.x] Revert "lib: throw from localStorage getter on missing storage path"

This reverts commit 4fbb1ab1015614258c440700373148efa4da8074.

<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: lib/internal/webstorage.js
patch: @@ -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,17 +21,34 @@ 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 === '') {
-          throw lazyDOMException(
-            'Cannot initialize local storage without a `--localstorage-file` path',
-            'SecurityError',
-          );
-        }
+          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;
+              }
 
-        lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
+              return false;
+            },
+          };
+
+          lazyLocalStorage = new Proxy({}, handler);
+        } else {
+          lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location));
+        }
       }
 
       return lazyLocalStorage;

filename: test/common/index.js
patch: @@ -59,14 +59,6 @@ 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.
@@ -359,6 +351,7 @@ const knownGlobals = new Set([
  'CompressionStream',
  'DecompressionStream',
  'Storage',
+ 'localStorage',
  'sessionStorage',
 ].forEach((i) => {
   if (globalThis[i] !== undefined) {
@@ -373,10 +366,6 @@ if (hasCrypto) {
   knownGlobals.add(globalThis.SubtleCrypto);
 }
 
-if (hasLocalStorage) {
-  knownGlobals.add(globalThis.localStorage);
-}
-
 const { Worker } = require('node:worker_threads');
 knownGlobals.add(Worker);
 
@@ -401,11 +390,6 @@ 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);
       }
@@ -956,7 +940,6 @@ const common = {
   hasQuic,
   hasInspector,
   hasSQLite,
-  hasLocalStorage,
   invalidArgTypeHelper,
   isAlive,
   isASan,

filename: test/common/index.mjs
patch: @@ -19,7 +19,6 @@ const {
   hasQuic,
   hasInspector,
   hasSQLite,
-  hasLocalStorage,
   hasIntl,
   hasIPv6,
   isAIX,
@@ -73,7 +72,6 @@ export {
   hasQuic,
   hasInspector,
   hasSQLite,
-  hasLocalStorage,
   hasIntl,
   hasIPv6,
   isAIX,

filename: test/parallel/test-assert-checktag.js
patch: @@ -1,5 +1,5 @@
 'use strict';
-const { hasCrypto, hasLocalStorage } = require('../common');
+const { hasCrypto } = 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 || !hasLocalStorage }, () => {
+test('', { skip: !hasCrypto }, () => {
   // See https://github.com/nodejs/node/issues/10258
   {
     const date = new Date('2016');

filename: test/parallel/test-webstorage.js
patch: @@ -41,13 +41,13 @@ test('sessionStorage is not persisted', async () => {
   assert.strictEqual((await readdir(tmpdir.path)).length, 0);
 });
 
-test('localStorage throws without --localstorage-file', async () => {
+test('localStorage emits a warning when used without --localstorage-file ', async () => {
   const cp = await spawnPromisified(process.execPath, [
-    '-e', 'localStorage',
+    '-pe', 'localStorage.length',
   ]);
-  assert.strictEqual(cp.code, 1);
+  assert.strictEqual(cp.code, 0);
   assert.strictEqual(cp.signal, null);
-  assert.match(cp.stderr, /SecurityError:/);
+  assert.match(cp.stderr, /Warning: `--localstorage-file` was provided without a valid path/);
 });
 
 test('localStorage is not persisted if it is unused', async () => {