Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c68dd4c
src: unflag --experimental-webstorage by default
danielmbrasil Mar 28, 2025
c26b9a5
lib: return undefined for localStorage when location is invalid
danielmbrasil Mar 28, 2025
8c7f883
test: remove --experimental-webstorage flag from tests
danielmbrasil Apr 2, 2025
2103157
lib: use process.emitWarning instead of emitWarning
danielmbrasil Apr 2, 2025
c9cda8f
benchmark: remove --experimental-webstorage
danielmbrasil Apr 4, 2025
8c6d177
lib: remove unused import
danielmbrasil Apr 4, 2025
ecc92a0
lib: reword warning for --localstorage-file to fix no-regex-spaces lint
danielmbrasil Apr 7, 2025
1fe9092
test: add localStorage and sessionStorage to globalThis test
danielmbrasil Apr 7, 2025
6764302
doc: add --no-experimental-webstorage docs
danielmbrasil Apr 7, 2025
5cbf0ff
test: improve localStorage mock to fix failing test case
danielmbrasil Apr 7, 2025
047d40f
doc: fix ordering in doc/api/globals.md
danielmbrasil Apr 7, 2025
a6648c5
test: set getter name to 'get localStorage' in mock for compatibility
danielmbrasil Apr 7, 2025
c6693ea
test: use Flags comment option to set a localstorage file
danielmbrasil May 26, 2025
96a7054
test: include test for new --no-experimental-webstorage flag
danielmbrasil May 26, 2025
923d469
test: add --localstorage-file flag to --no-experimental-webstorage test
danielmbrasil May 27, 2025
fab489d
test: fix linter issue
danielmbrasil May 27, 2025
67874b1
lib: add experimental warning back in
danielmbrasil Jun 6, 2025
494a23d
doc: mark localStorage as stable
danielmbrasil Jun 10, 2025
39048d9
src: add --webstorage alias
danielmbrasil Jun 11, 2025
eff1a70
lib: remove experimental warning
danielmbrasil Jun 11, 2025
b17e1f5
doc: add --webstorage alias to docs
danielmbrasil Jun 17, 2025
07f19ba
test: fix tests
danielmbrasil Jun 17, 2025
7433905
doc: fix lint error
danielmbrasil Jun 17, 2025
3a4e2b5
lib: delay localstorage warning with proxy
danielmbrasil Jun 24, 2025
a6ce8fd
test: remove --locastorage-file flag from tests
danielmbrasil Jun 24, 2025
ac15bcf
src,lib: rename experimental_experimental option
danielmbrasil Jun 24, 2025
b9b8906
doc: update docs
danielmbrasil Jun 24, 2025
e7b8440
lib: fix linter errors
danielmbrasil Jun 24, 2025
8e1972c
test: remove useless change
danielmbrasil Jun 24, 2025
0ba26af
doc: update node-config-schema.json
danielmbrasil Jun 24, 2025
abe8491
doc: fix markdown error
danielmbrasil Jul 9, 2025
4b152b1
doc: fix failing tests
danielmbrasil Aug 1, 2025
890a794
lib: emit localstorage-file warning only once
danielmbrasil Sep 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmark/webstorage/getItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function nextLocalStorage() {
}

const options = {
flags: ['--experimental-webstorage', `--localstorage-file=${nextLocalStorage()}`],
flags: [`--localstorage-file=${nextLocalStorage()}`],
};

const bench = common.createBenchmark(main, {
Expand Down
2 changes: 1 addition & 1 deletion benchmark/webstorage/removeItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function nextLocalStorage() {
}

const options = {
flags: ['--experimental-webstorage', `--localstorage-file=${nextLocalStorage()}`],
flags: [`--localstorage-file=${nextLocalStorage()}`],
};

const bench = common.createBenchmark(main, {
Expand Down
2 changes: 1 addition & 1 deletion benchmark/webstorage/setItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function nextLocalStorage() {
}

const options = {
flags: ['--experimental-webstorage', `--localstorage-file=${nextLocalStorage()}`],
flags: [`--localstorage-file=${nextLocalStorage()}`],
};

const bench = common.createBenchmark(main, {
Expand Down
23 changes: 12 additions & 11 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1269,14 +1269,6 @@ changes:

Enable experimental WebAssembly System Interface (WASI) support.

### `--experimental-webstorage`

<!-- YAML
added: v22.4.0
-->

Enable experimental [`Web Storage`][] support.

### `--experimental-worker-inspection`

<!-- YAML
Expand Down Expand Up @@ -1717,8 +1709,8 @@ added: v22.4.0

The file used to store `localStorage` data. If the file does not exist, it is
created the first time `localStorage` is accessed. The same file may be shared
between multiple Node.js processes concurrently. This flag is a no-op unless
Node.js is started with the `--experimental-webstorage` flag.
between multiple Node.js processes concurrently. This flag is a no-op if
Node.js is started with the `--no-webstorage` (or `--no-experimental-webstorage`) flag.

### `--max-http-header-size=size`

Expand Down Expand Up @@ -1943,6 +1935,14 @@ added: v6.0.0

Silence all process warnings (including deprecations).

### `--no-webstorage`

<!-- YAML
added: REPLACEME
-->

Disable [`Web Storage`][] support.

### `--node-memory-debug`

<!-- YAML
Expand Down Expand Up @@ -3461,7 +3461,6 @@ one is included in the list below.
* `--experimental-transform-types`
* `--experimental-vm-modules`
* `--experimental-wasi-unstable-preview1`
* `--experimental-webstorage`
* `--force-context-aware`
* `--force-fips`
* `--force-node-api-uncaught-exceptions-policy`
Expand Down Expand Up @@ -3495,11 +3494,13 @@ one is included in the list below.
* `--no-experimental-sqlite`
* `--no-experimental-strip-types`
* `--no-experimental-websocket`
* `--no-experimental-webstorage`
* `--no-extra-info-on-fatal-exception`
* `--no-force-async-hooks-checks`
* `--no-global-search-paths`
* `--no-network-family-autoselection`
* `--no-warnings`
* `--no-webstorage`
* `--node-memory-debug`
* `--openssl-config`
* `--openssl-legacy-provider`
Expand Down
11 changes: 5 additions & 6 deletions doc/api/globals.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,13 +636,11 @@ A browser-compatible implementation of {Headers}.
added: v22.4.0
-->

> 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.

Expand Down Expand Up @@ -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])`

Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions doc/node-config-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,6 @@
"experimental-websocket": {
"type": "boolean"
},
"experimental-webstorage": {
"type": "boolean"
},
"extra-info-on-fatal-exception": {
"type": "boolean"
},
Expand Down Expand Up @@ -594,6 +591,9 @@
"watch-preserve-output": {
"type": "boolean"
},
"webstorage": {
"type": "boolean"
},
"zero-fill-buffers": {
"type": "boolean"
}
Expand Down
4 changes: 2 additions & 2 deletions doc/node.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/process/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ function setupQuic() {

function setupWebStorage() {
if (getEmbedderOptions().noBrowserGlobals ||
!getOptionValue('--experimental-webstorage')) {
!getOptionValue('--webstorage')) {
return;
}

Expand Down
34 changes: 25 additions & 9 deletions lib/internal/webstorage.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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));
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if we are making the localStorage global by default, this check should be deferred. We can already see by some of the test changes here that having this check in the getter for localStorage will cause spurious warnings to be emitted even in code that never intentionally touches localStorage. This warning should only be emitted when someone actually tries to intentionally use localStorage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. That should've been done now. I've removed the flags from unrelated tests and deferred the warning using a proxy.

}

return lazyLocalStorage;
Expand Down
10 changes: 6 additions & 4 deletions src/node_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions test/parallel/test-global.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
65 changes: 25 additions & 40 deletions test/parallel/test-webstorage.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';

const { skipIfSQLiteMissing, spawnPromisified } = require('../common');
skipIfSQLiteMissing();
const tmpdir = require('../common/tmpdir');
Expand All @@ -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);
Expand All @@ -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',
]);
Expand All @@ -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"',
]);
Expand All @@ -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',
]);
Expand All @@ -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', `
Expand All @@ -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');
Expand All @@ -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`));
}
});
Loading
Loading