diff --git a/benchmark/webstorage/getItem.js b/benchmark/webstorage/getItem.js index c383f91ee0d75b..f92ee0318ff67e 100644 --- a/benchmark/webstorage/getItem.js +++ b/benchmark/webstorage/getItem.js @@ -12,7 +12,7 @@ function nextLocalStorage() { } const options = { - flags: ['--experimental-webstorage', `--localstorage-file=${nextLocalStorage()}`], + flags: [`--localstorage-file=${nextLocalStorage()}`], }; const bench = common.createBenchmark(main, { diff --git a/benchmark/webstorage/removeItem.js b/benchmark/webstorage/removeItem.js index 330f17bdf35293..0de686d45a3d2b 100644 --- a/benchmark/webstorage/removeItem.js +++ b/benchmark/webstorage/removeItem.js @@ -12,7 +12,7 @@ function nextLocalStorage() { } const options = { - flags: ['--experimental-webstorage', `--localstorage-file=${nextLocalStorage()}`], + flags: [`--localstorage-file=${nextLocalStorage()}`], }; const bench = common.createBenchmark(main, { diff --git a/benchmark/webstorage/setItem.js b/benchmark/webstorage/setItem.js index e6ef2f866de05a..5d88a39ef52ac1 100644 --- a/benchmark/webstorage/setItem.js +++ b/benchmark/webstorage/setItem.js @@ -12,7 +12,7 @@ function nextLocalStorage() { } const options = { - flags: ['--experimental-webstorage', `--localstorage-file=${nextLocalStorage()}`], + flags: [`--localstorage-file=${nextLocalStorage()}`], }; const bench = common.createBenchmark(main, { diff --git a/doc/api/cli.md b/doc/api/cli.md index 9775bbf267e204..5650649a48b172 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -1269,14 +1269,6 @@ changes: Enable experimental WebAssembly System Interface (WASI) support. -### `--experimental-webstorage` - - - -Enable experimental [`Web Storage`][] support. - ### `--experimental-worker-inspection` + +Disable [`Web Storage`][] support. + ### `--node-memory-debug` -> Stability: 1.0 - Early development. - A browser-compatible implementation of [`localStorage`][]. Data is stored unencrypted in the file specified by the [`--localstorage-file`][] CLI flag. The maximum amount of data that can be stored is 10 MB. Any modification of this data outside of the Web Storage API is not supported. -Enable this API with the [`--experimental-webstorage`][] CLI flag. +Disable this API with the [`--no-webstorage`][] (or its alias `--no-experimental-webstorage`) CLI flag. `localStorage` data is not stored per user or per request when used in the context of a server, it is shared across all users and requests. @@ -1104,9 +1102,10 @@ added: v22.4.0 --> > Stability: 1.0 - Early development. Enable this API with the -> [`--experimental-webstorage`][] CLI flag. +> \[`--experimental-webstorage`]\[] CLI flag. -A browser-compatible implementation of {Storage}. +A browser-compatible implementation of {Storage}. Disable this API with the +[`--no-webstorage`][] (or its alias `--no-experimental-webstorage`) CLI flag. ## `structuredClone(value[, options])` @@ -1317,10 +1316,10 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][]. [RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt [Web Crypto API]: webcrypto.md [`--experimental-eventsource`]: cli.md#--experimental-eventsource -[`--experimental-webstorage`]: cli.md#--experimental-webstorage [`--localstorage-file`]: cli.md#--localstorage-filefile [`--no-experimental-global-navigator`]: cli.md#--no-experimental-global-navigator [`--no-experimental-websocket`]: cli.md#--no-experimental-websocket +[`--no-webstorage`]: cli.md#--no-webstorage [`ByteLengthQueuingStrategy`]: webstreams.md#class-bytelengthqueuingstrategy [`CompressionStream`]: webstreams.md#class-compressionstream [`CountQueuingStrategy`]: webstreams.md#class-countqueuingstrategy diff --git a/doc/node-config-schema.json b/doc/node-config-schema.json index bffae1fc617917..e5104a3e16b516 100644 --- a/doc/node-config-schema.json +++ b/doc/node-config-schema.json @@ -186,9 +186,6 @@ "experimental-websocket": { "type": "boolean" }, - "experimental-webstorage": { - "type": "boolean" - }, "extra-info-on-fatal-exception": { "type": "boolean" }, @@ -594,6 +591,9 @@ "watch-preserve-output": { "type": "boolean" }, + "webstorage": { + "type": "boolean" + }, "zero-fill-buffers": { "type": "boolean" } diff --git a/doc/node.1 b/doc/node.1 index 691a5c914859b2..110706933dbaa7 100644 --- a/doc/node.1 +++ b/doc/node.1 @@ -204,8 +204,8 @@ Enable experimental support for the EventSource Web API. .It Fl -no-experimental-websocket Disable experimental support for the WebSocket API. . -.It Fl -experimental-webstorage -Enable experimental support for the Web Storage API. +.It Fl -no-webstorage +Disable webstorage. . .It Fl -no-experimental-repl-await Disable top-level await keyword support in REPL. diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js index fc7a0e206928da..912b4601a06616 100644 --- a/lib/internal/process/pre_execution.js +++ b/lib/internal/process/pre_execution.js @@ -365,7 +365,7 @@ function setupQuic() { function setupWebStorage() { if (getEmbedderOptions().noBrowserGlobals || - !getOptionValue('--experimental-webstorage')) { + !getOptionValue('--webstorage')) { return; } diff --git a/lib/internal/webstorage.js b/lib/internal/webstorage.js index 7f58299a1e835f..28b5df3f3da835 100644 --- a/lib/internal/webstorage.js +++ b/lib/internal/webstorage.js @@ -1,16 +1,13 @@ 'use strict'; const { ObjectDefineProperties, + Proxy, } = primordials; -const { ERR_INVALID_ARG_VALUE } = require('internal/errors').codes; const { getOptionValue } = require('internal/options'); -const { emitExperimentalWarning } = require('internal/util'); const { kConstructorKey, Storage } = internalBinding('webstorage'); const { getValidatedPath } = require('internal/fs/utils'); const kInMemoryPath = ':memory:'; -emitExperimentalWarning('Web Storage'); - module.exports = { Storage }; let lazyLocalStorage; @@ -27,12 +24,31 @@ ObjectDefineProperties(module.exports, { const location = getOptionValue('--localstorage-file'); if (location === '') { - throw new ERR_INVALID_ARG_VALUE('--localstorage-file', - location, - 'is an invalid localStorage 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; + } - lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location)); + return false; + }, + }; + + lazyLocalStorage = new Proxy({}, handler); + } else { + lazyLocalStorage = new Storage(kConstructorKey, getValidatedPath(location)); + } } return lazyLocalStorage; diff --git a/src/node_options.cc b/src/node_options.cc index c98be3257978ca..f29929a996456f 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -559,10 +559,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { NoOp{}, #endif kAllowedInEnvvar); - AddOption("--experimental-webstorage", - "experimental Web Storage API", - &EnvironmentOptions::experimental_webstorage, - kAllowedInEnvvar); + AddOption("--webstorage", + "Web Storage API", + &EnvironmentOptions::webstorage, + kAllowedInEnvvar, + true); + AddAlias("--experimental-webstorage", "--webstorage"); AddOption("--localstorage-file", "file used to persist localStorage data", &EnvironmentOptions::localstorage_file, diff --git a/src/node_options.h b/src/node_options.h index 4d6b4103ce6e24..3858547227bb9f 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -127,7 +127,7 @@ class EnvironmentOptions : public Options { bool experimental_fetch = true; bool experimental_websocket = true; bool experimental_sqlite = true; - bool experimental_webstorage = false; + bool webstorage = true; #ifndef OPENSSL_NO_QUIC bool experimental_quic = false; #endif diff --git a/test/parallel/test-global.js b/test/parallel/test-global.js index 835bcc75a83e3b..8aba5c333a2ac3 100644 --- a/test/parallel/test-global.js +++ b/test/parallel/test-global.js @@ -59,6 +59,8 @@ for (const moduleName of builtinModules) { 'fetch', 'crypto', 'navigator', + 'localStorage', + 'sessionStorage', ]; assert.deepStrictEqual(new Set(Object.keys(globalThis)), new Set(expected)); expected.forEach((value) => { diff --git a/test/parallel/test-webstorage.js b/test/parallel/test-webstorage.js index 9f9070df430f10..bc48c9643773b5 100644 --- a/test/parallel/test-webstorage.js +++ b/test/parallel/test-webstorage.js @@ -1,4 +1,5 @@ 'use strict'; + const { skipIfSQLiteMissing, spawnPromisified } = require('../common'); skipIfSQLiteMissing(); const tmpdir = require('../common/tmpdir'); @@ -14,35 +15,9 @@ function nextLocalStorage() { return join(tmpdir.path, `${++cnt}.localstorage`); } -test('disabled without --experimental-webstorage', async () => { - for (const api of ['Storage', 'localStorage', 'sessionStorage']) { - const cp = await spawnPromisified(process.execPath, ['-e', api]); - - assert.strictEqual(cp.code, 1); - assert.strictEqual(cp.signal, null); - assert.strictEqual(cp.stdout, ''); - assert(cp.stderr.includes(`ReferenceError: ${api} is not defined`)); - } -}); - -test('emits a warning when used', async () => { - for (const api of ['Storage', 'localStorage', 'sessionStorage']) { - const cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', - '--localstorage-file', nextLocalStorage(), - '-e', api, - ]); - - assert.strictEqual(cp.code, 0); - assert.strictEqual(cp.signal, null); - assert.strictEqual(cp.stdout, ''); - assert.match(cp.stderr, /ExperimentalWarning: Web Storage/); - } -}); - test('Storage instances cannot be created in userland', async () => { const cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '-e', 'new globalThis.Storage()', + '-e', 'new globalThis.Storage()', ]); assert.strictEqual(cp.code, 1); @@ -53,33 +28,30 @@ test('Storage instances cannot be created in userland', async () => { test('sessionStorage is not persisted', async () => { let cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '-pe', 'sessionStorage.foo = "barbaz"', + '-pe', 'sessionStorage.foo = "barbaz"', ]); assert.strictEqual(cp.code, 0); assert.match(cp.stdout, /barbaz/); cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '-pe', 'sessionStorage.foo', + '-pe', 'sessionStorage.foo', ]); assert.strictEqual(cp.code, 0); assert.match(cp.stdout, /undefined/); 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, [ - '--experimental-webstorage', - '-pe', 'localStorage === globalThis.localStorage', + '-pe', 'localStorage.length', ]); - assert.strictEqual(cp.code, 1); + assert.strictEqual(cp.code, 0); assert.strictEqual(cp.signal, null); - assert.strictEqual(cp.stdout, ''); - assert.match(cp.stderr, /The argument '--localstorage-file' is an invalid localStorage location/); + assert.match(cp.stderr, /Warning: `--localstorage-file` was provided without a valid path/); }); test('localStorage is not persisted if it is unused', async () => { const cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '--localstorage-file', nextLocalStorage(), '-pe', 'localStorage === globalThis.localStorage', ]); @@ -91,7 +63,6 @@ test('localStorage is not persisted if it is unused', async () => { test('localStorage is persisted if it is used', async () => { const localStorageFile = nextLocalStorage(); let cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '--localstorage-file', localStorageFile, '-pe', 'localStorage.foo = "barbaz"', ]); @@ -102,7 +73,6 @@ test('localStorage is persisted if it is used', async () => { assert.match(entries[0], /\d+\.localstorage/); cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '--localstorage-file', localStorageFile, '-pe', 'localStorage.foo', ]); @@ -117,7 +87,6 @@ describe('webstorage quota for localStorage and sessionStorage', () => { test('localStorage can store and retrieve a max of 10 MB quota', async () => { const localStorageFile = nextLocalStorage(); const cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', '--localstorage-file', localStorageFile, // Each character is 2 bytes '-pe', ` @@ -133,7 +102,6 @@ describe('webstorage quota for localStorage and sessionStorage', () => { test('sessionStorage can store a max of 10 MB quota', async () => { const cp = await spawnPromisified(process.execPath, [ - '--experimental-webstorage', // Each character is 2 bytes '-pe', `sessionStorage['a'.repeat(${MAX_STORAGE_SIZE} / 2)] = ''; console.error('filled'); @@ -145,3 +113,20 @@ describe('webstorage quota for localStorage and sessionStorage', () => { assert.match(cp.stderr, /QuotaExceededError/); }); }); + +test('disabled with --no-webstorage', async () => { + for (const api of ['Storage', 'localStorage', 'sessionStorage']) { + const cp = await spawnPromisified(process.execPath, [ + '--no-webstorage', + '--localstorage-file', + './test/fixtures/localstoragefile-global-test', + '-e', + api, + ]); + + assert.strictEqual(cp.code, 1); + assert.strictEqual(cp.signal, null); + assert.strictEqual(cp.stdout, ''); + assert(cp.stderr.includes(`ReferenceError: ${api} is not defined`)); + } +}); diff --git a/test/wpt/test-webstorage.js b/test/wpt/test-webstorage.js index 490fac8ed7e232..a677659f4b04f4 100644 --- a/test/wpt/test-webstorage.js +++ b/test/wpt/test-webstorage.js @@ -9,7 +9,6 @@ const runner = new WPTRunner('webstorage', { concurrency: 1 }); tmpdir.refresh(); runner.setFlags([ - '--experimental-webstorage', '--localstorage-file', join(tmpdir.path, 'wpt-tests.localstorage'), ]); runner.setInitScript(`