deps,lib,src: add experimental web storage

This commit introduces an experimental implementation of the Web
Storage API using SQLite as the backing data store.

PR-URL: https://github.com/nodejs/node/pull/52435
Reviewed-By: Yagiz Nizipli <yagiz.nizipli@sentry.io>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ethan Arrowood <ethan@arrowood.dev>
This commit is contained in:
cjihrig 2023-09-15 10:32:14 -04:00 committed by Node.js GitHub Bot
parent 340cb6887d
commit 3d09e579d3
109 changed files with 275542 additions and 0 deletions

View File

@ -38,6 +38,7 @@ on:
- root-certificates - root-certificates
- simdjson - simdjson
- simdutf - simdutf
- sqlite
- undici - undici
- uvwasi - uvwasi
- zlib - zlib
@ -272,6 +273,14 @@ jobs:
cat temp-output cat temp-output
tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true
rm temp-output rm temp-output
- id: sqlite
subsystem: deps
label: dependencies
run: |
./tools/dep_updaters/update-sqlite.sh > temp-output
cat temp-output
tail -n1 temp-output | grep "NEW_VERSION=" >> "$GITHUB_ENV" || true
rm temp-output
- id: undici - id: undici
subsystem: deps subsystem: deps
label: dependencies label: dependencies

14
deps/sqlite/BUILD.gn vendored Normal file
View File

@ -0,0 +1,14 @@
##############################################################################
# #
# DO NOT EDIT THIS FILE! #
# #
##############################################################################
# This file is used by GN for building, which is NOT the build system used for
# building official binaries.
# Please modify the gyp files if you are making changes to build system.
import("unofficial.gni")
sqlite_gn_build("sqlite") {
}

24
deps/sqlite/sqlite.gyp vendored Normal file
View File

@ -0,0 +1,24 @@
{
'variables': {
'sqlite_sources': [
'sqlite3.c',
],
},
'targets': [
{
'target_name': 'sqlite',
'type': 'static_library',
'cflags': ['-fvisibility=hidden'],
'xcode_settings': {
'GCC_SYMBOLS_PRIVATE_EXTERN': 'YES', # -fvisibility=hidden
},
'include_dirs': ['.'],
'sources': [
'<@(sqlite_sources)',
],
'direct_dependent_settings': {
'include_dirs': ['.'],
},
},
],
}

257673
deps/sqlite/sqlite3.c vendored Normal file

File diff suppressed because it is too large Load Diff

13425
deps/sqlite/sqlite3.h vendored Normal file

File diff suppressed because it is too large Load Diff

22
deps/sqlite/unofficial.gni vendored Normal file
View File

@ -0,0 +1,22 @@
# This file is used by GN for building, which is NOT the build system used for
# building official binaries.
# Please edit the gyp files if you are making changes to build system.
# The actual configurations are put inside a template in unofficial.gni to
# prevent accidental edits from contributors.
template("sqlite_gn_build") {
config("sqlite_config") {
include_dirs = [ "." ]
}
gypi_values = exec_script("../../tools/gypi_to_gn.py",
[ rebase_path("sqlite.gyp") ],
"scope",
[ "sqlite.gyp" ])
source_set(target_name) {
forward_variables_from(invoker, "*")
public_configs = [ ":sqlite_config" ]
sources = gypi_values.sqlite_sources
}
}

View File

@ -1123,6 +1123,14 @@ added: v12.3.0
Enable experimental WebAssembly module support. Enable experimental WebAssembly module support.
### `--experimental-webstorage`
<!-- YAML
added: REPLACEME
-->
Enable experimental [`Web Storage`][] support.
### `--force-context-aware` ### `--force-context-aware`
<!-- YAML <!-- YAML
@ -1491,6 +1499,17 @@ Disable [runtime allocation of executable memory][jitless]. This may be
required on some platforms for security reasons. It can also reduce attack required on some platforms for security reasons. It can also reduce attack
surface on other platforms, but the performance impact may be severe. surface on other platforms, but the performance impact may be severe.
### `--localstorage-file=file`
<!-- YAML
added: REPLACEME
-->
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.
### `--max-http-header-size=size` ### `--max-http-header-size=size`
<!-- YAML <!-- YAML
@ -2833,6 +2852,7 @@ one is included in the list below.
* `--experimental-vm-modules` * `--experimental-vm-modules`
* `--experimental-wasi-unstable-preview1` * `--experimental-wasi-unstable-preview1`
* `--experimental-wasm-modules` * `--experimental-wasm-modules`
* `--experimental-webstorage`
* `--force-context-aware` * `--force-context-aware`
* `--force-fips` * `--force-fips`
* `--force-node-api-uncaught-exceptions-policy` * `--force-node-api-uncaught-exceptions-policy`
@ -2849,6 +2869,7 @@ one is included in the list below.
* `--inspect-publish-uid` * `--inspect-publish-uid`
* `--inspect-wait` * `--inspect-wait`
* `--inspect` * `--inspect`
* `--localstorage-file`
* `--max-http-header-size` * `--max-http-header-size`
* `--napi-modules` * `--napi-modules`
* `--network-family-autoselection-attempt-timeout` * `--network-family-autoselection-attempt-timeout`
@ -3376,6 +3397,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
[`NODE_OPTIONS`]: #node_optionsoptions [`NODE_OPTIONS`]: #node_optionsoptions
[`NO_COLOR`]: https://no-color.org [`NO_COLOR`]: https://no-color.org
[`SlowBuffer`]: buffer.md#class-slowbuffer [`SlowBuffer`]: buffer.md#class-slowbuffer
[`Web Storage`]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API
[`WebSocket`]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket [`WebSocket`]: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket
[`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328 [`YoungGenerationSizeFromSemiSpaceSize`]: https://chromium.googlesource.com/v8/v8.git/+/refs/tags/10.3.129/src/heap/heap.cc#328
[`dns.lookup()`]: dns.md#dnslookuphostname-options-callback [`dns.lookup()`]: dns.md#dnslookuphostname-options-callback

View File

@ -582,6 +582,19 @@ changes:
A browser-compatible implementation of {Headers}. A browser-compatible implementation of {Headers}.
## `localStorage`
<!-- YAML
added: REPLACEME
-->
> 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.
Any modification of this data outside of the Web Storage API is not supported.
Enable this API with the [`--experimental-webstorage`][] CLI flag.
## `MessageChannel` ## `MessageChannel`
<!-- YAML <!-- YAML
@ -950,6 +963,19 @@ changes:
A browser-compatible implementation of {Request}. A browser-compatible implementation of {Request}.
## `sessionStorage`
<!-- YAML
added: REPLACEME
-->
> Stability: 1.0 - Early development.
A browser-compatible implementation of [`sessionStorage`][]. Data is stored in
memory, with a storage quota of 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.
## `setImmediate(callback[, ...args])` ## `setImmediate(callback[, ...args])`
<!-- YAML <!-- YAML
@ -980,6 +1006,17 @@ added: v0.0.1
[`setTimeout`][] is described in the [timers][] section. [`setTimeout`][] is described in the [timers][] section.
## Class: `Storage`
<!-- YAML
added: REPLACEME
-->
> Stability: 1.0 - Early development.
A browser-compatible implementation of [`Storage`][]. Enable this API with the
[`--experimental-webstorage`][] CLI flag.
## `structuredClone(value[, options])` ## `structuredClone(value[, options])`
<!-- YAML <!-- YAML
@ -1168,6 +1205,8 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object [Navigator API]: https://html.spec.whatwg.org/multipage/system-state.html#the-navigator-object
[RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt [RFC 5646]: https://www.rfc-editor.org/rfc/rfc5646.txt
[Web Crypto API]: webcrypto.md [Web Crypto API]: webcrypto.md
[`--experimental-webstorage`]: cli.md#--experimental-webstorage
[`--localstorage-file`]: cli.md#--localstorage-filefile
[`--no-experimental-global-navigator`]: cli.md#--no-experimental-global-navigator [`--no-experimental-global-navigator`]: cli.md#--no-experimental-global-navigator
[`--no-experimental-websocket`]: cli.md#--no-experimental-websocket [`--no-experimental-websocket`]: cli.md#--no-experimental-websocket
[`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController [`AbortController`]: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
@ -1194,6 +1233,7 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[`ReadableStreamDefaultController`]: webstreams.md#class-readablestreamdefaultcontroller [`ReadableStreamDefaultController`]: webstreams.md#class-readablestreamdefaultcontroller
[`ReadableStreamDefaultReader`]: webstreams.md#class-readablestreamdefaultreader [`ReadableStreamDefaultReader`]: webstreams.md#class-readablestreamdefaultreader
[`ReadableStream`]: webstreams.md#class-readablestream [`ReadableStream`]: webstreams.md#class-readablestream
[`Storage`]: https://developer.mozilla.org/en-US/docs/Web/API/Storage
[`TextDecoderStream`]: webstreams.md#class-textdecoderstream [`TextDecoderStream`]: webstreams.md#class-textdecoderstream
[`TextDecoder`]: util.md#class-utiltextdecoder [`TextDecoder`]: util.md#class-utiltextdecoder
[`TextEncoderStream`]: webstreams.md#class-textencoderstream [`TextEncoderStream`]: webstreams.md#class-textencoderstream
@ -1218,11 +1258,13 @@ A browser-compatible implementation of [`WritableStreamDefaultWriter`][].
[`exports`]: modules.md#exports [`exports`]: modules.md#exports
[`fetch()`]: https://developer.mozilla.org/en-US/docs/Web/API/fetch [`fetch()`]: https://developer.mozilla.org/en-US/docs/Web/API/fetch
[`globalThis`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis [`globalThis`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
[`localStorage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
[`module`]: modules.md#module [`module`]: modules.md#module
[`perf_hooks.performance`]: perf_hooks.md#perf_hooksperformance [`perf_hooks.performance`]: perf_hooks.md#perf_hooksperformance
[`process.nextTick()`]: process.md#processnexttickcallback-args [`process.nextTick()`]: process.md#processnexttickcallback-args
[`process` object]: process.md#process [`process` object]: process.md#process
[`require()`]: modules.md#requireid [`require()`]: modules.md#requireid
[`sessionStorage`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
[`setImmediate`]: timers.md#setimmediatecallback-args [`setImmediate`]: timers.md#setimmediatecallback-args
[`setInterval`]: timers.md#setintervalcallback-delay-args [`setInterval`]: timers.md#setintervalcallback-delay-args
[`setTimeout`]: timers.md#settimeoutcallback-delay-args [`setTimeout`]: timers.md#settimeoutcallback-delay-args

View File

@ -3970,6 +3970,7 @@ Will generate an object similar to:
openssl: '3.0.13+quic', openssl: '3.0.13+quic',
simdjson: '3.8.0', simdjson: '3.8.0',
simdutf: '5.2.4', simdutf: '5.2.4',
sqlite: '3.46.0',
tz: '2024a', tz: '2024a',
undici: '6.13.0', undici: '6.13.0',
unicode: '15.1', unicode: '15.1',

View File

@ -29,6 +29,7 @@ This a list of all the dependencies:
* [postject][] * [postject][]
* [simdjson][] * [simdjson][]
* [simdutf][] * [simdutf][]
* [sqlite][]
* [undici][] * [undici][]
* [uvwasi][] * [uvwasi][]
* [V8][] * [V8][]
@ -290,6 +291,11 @@ a C++ library for fast JSON parsing.
The [simdutf](https://github.com/simdutf/simdutf) dependency is The [simdutf](https://github.com/simdutf/simdutf) dependency is
a C++ library for fast UTF-8 decoding and encoding. a C++ library for fast UTF-8 decoding and encoding.
### sqlite
The [sqlite](https://github.com/sqlite/sqlite) dependency is
an embedded SQL database engine written in C.
### undici ### undici
The [undici](https://github.com/nodejs/undici) dependency is an HTTP/1.1 client, The [undici](https://github.com/nodejs/undici) dependency is an HTTP/1.1 client,
@ -345,6 +351,7 @@ performance improvements not currently available in standard zlib.
[postject]: #postject [postject]: #postject
[simdjson]: #simdjson [simdjson]: #simdjson
[simdutf]: #simdutf [simdutf]: #simdutf
[sqlite]: #sqlite
[undici]: #undici [undici]: #undici
[update-openssl-action]: ../../../.github/workflows/update-openssl.yml [update-openssl-action]: ../../../.github/workflows/update-openssl.yml
[uvwasi]: #uvwasi [uvwasi]: #uvwasi

View File

@ -200,6 +200,9 @@ Enable experimental support for the EventSource Web API.
.It Fl -no-experimental-websocket .It Fl -no-experimental-websocket
Disable experimental support for the WebSocket API. Disable experimental support for the WebSocket API.
. .
.It Fl -experimental-webstorage
Enable experimental support for the Web Storage API.
.
.It Fl -no-experimental-repl-await .It Fl -no-experimental-repl-await
Disable top-level await keyword support in REPL. Disable top-level await keyword support in REPL.
. .
@ -313,6 +316,9 @@ other platforms, but the performance impact may be severe.
This flag is inherited from V8 and is subject to change upstream. It may This flag is inherited from V8 and is subject to change upstream. It may
disappear in a non-semver-major release. disappear in a non-semver-major release.
. .
.It Fl -localstorage-file Ns = Ns Ar file
The file used to store localStorage data.
.
.It Fl -max-http-header-size Ns = Ns Ar size .It Fl -max-http-header-size Ns = Ns Ar size
Specify the maximum size of HTTP headers in bytes. Defaults to 16 KiB. Specify the maximum size of HTTP headers in bytes. Defaults to 16 KiB.
. .

View File

@ -100,6 +100,7 @@ function prepareExecution(options) {
setupInspectorHooks(); setupInspectorHooks();
setupNavigator(); setupNavigator();
setupWarningHandler(); setupWarningHandler();
setupWebStorage();
setupWebsocket(); setupWebsocket();
setupEventsource(); setupEventsource();
setupCodeCoverage(); setupCodeCoverage();
@ -328,6 +329,19 @@ function setupNavigator() {
defineReplaceableLazyAttribute(globalThis, 'internal/navigator', ['navigator'], false); defineReplaceableLazyAttribute(globalThis, 'internal/navigator', ['navigator'], false);
} }
function setupWebStorage() {
if (getEmbedderOptions().noBrowserGlobals ||
!getOptionValue('--experimental-webstorage')) {
return;
}
// https://html.spec.whatwg.org/multipage/webstorage.html#webstorage
exposeLazyInterfaces(globalThis, 'internal/webstorage', ['Storage']);
defineReplaceableLazyAttribute(globalThis, 'internal/webstorage', [
'localStorage', 'sessionStorage',
]);
}
function setupCodeCoverage() { function setupCodeCoverage() {
// Resolve the coverage directory to an absolute path, and // Resolve the coverage directory to an absolute path, and
// overwrite process.env so that the original path gets passed // overwrite process.env so that the original path gets passed

View File

@ -0,0 +1,55 @@
'use strict';
const {
ObjectDefineProperties,
} = 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 { resolve, toNamespacedPath } = require('path');
const { getValidatedPath } = require('internal/fs/utils');
const kInMemoryPath = ':memory:';
emitExperimentalWarning('Web Storage');
module.exports = { Storage };
let lazyLocalStorage;
let lazySessionStorage;
ObjectDefineProperties(module.exports, {
__proto__: null,
localStorage: {
__proto__: null,
configurable: true,
enumerable: true,
get() {
if (lazyLocalStorage === undefined) {
let location = getOptionValue('--localstorage-file');
if (location === '') {
throw new ERR_INVALID_ARG_VALUE('--localstorage-file',
location,
'is an invalid localStorage location');
}
location = toNamespacedPath(resolve(getValidatedPath(location)));
lazyLocalStorage = new Storage(kConstructorKey, location);
}
return lazyLocalStorage;
},
},
sessionStorage: {
__proto__: null,
configurable: true,
enumerable: true,
get() {
if (lazySessionStorage === undefined) {
lazySessionStorage = new Storage(kConstructorKey, kInMemoryPath);
}
return lazySessionStorage;
},
},
});

View File

@ -146,6 +146,7 @@
'src/node_wasi.cc', 'src/node_wasi.cc',
'src/node_wasm_web_api.cc', 'src/node_wasm_web_api.cc',
'src/node_watchdog.cc', 'src/node_watchdog.cc',
'src/node_webstorage.cc',
'src/node_worker.cc', 'src/node_worker.cc',
'src/node_zlib.cc', 'src/node_zlib.cc',
'src/path.cc', 'src/path.cc',
@ -272,6 +273,7 @@
'src/node_v8_platform-inl.h', 'src/node_v8_platform-inl.h',
'src/node_wasi.h', 'src/node_wasi.h',
'src/node_watchdog.h', 'src/node_watchdog.h',
'src/node_webstorage.h',
'src/node_worker.h', 'src/node_worker.h',
'src/path.h', 'src/path.h',
'src/permission/child_process_permission.h', 'src/permission/child_process_permission.h',
@ -544,6 +546,7 @@
'dependencies': [ 'dependencies': [
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
], ],
'msvs_settings': { 'msvs_settings': {
@ -838,6 +841,7 @@
'dependencies': [ 'dependencies': [
'deps/googletest/googletest.gyp:gtest_prod', 'deps/googletest/googletest.gyp:gtest_prod',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
'deps/simdjson/simdjson.gyp:simdjson', 'deps/simdjson/simdjson.gyp:simdjson',
'deps/simdutf/simdutf.gyp:simdutf', 'deps/simdutf/simdutf.gyp:simdutf',
'deps/ada/ada.gyp:ada', 'deps/ada/ada.gyp:ada',
@ -1022,6 +1026,7 @@
'dependencies': [ 'dependencies': [
'<(node_lib_target_name)', '<(node_lib_target_name)',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
], ],
'includes': [ 'includes': [
@ -1033,6 +1038,7 @@
'deps/v8/include', 'deps/v8/include',
'deps/cares/include', 'deps/cares/include',
'deps/uv/include', 'deps/uv/include',
'deps/sqlite',
'test/cctest', 'test/cctest',
], ],
@ -1065,6 +1071,7 @@
'dependencies': [ 'dependencies': [
'<(node_lib_target_name)', '<(node_lib_target_name)',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
'deps/uvwasi/uvwasi.gyp:uvwasi', 'deps/uvwasi/uvwasi.gyp:uvwasi',
], ],
'includes': [ 'includes': [
@ -1075,6 +1082,7 @@
'tools/msvs/genfiles', 'tools/msvs/genfiles',
'deps/v8/include', 'deps/v8/include',
'deps/cares/include', 'deps/cares/include',
'deps/sqlite',
'deps/uv/include', 'deps/uv/include',
'deps/uvwasi/include', 'deps/uvwasi/include',
'test/cctest', 'test/cctest',
@ -1109,6 +1117,7 @@
'<(node_lib_target_name)', '<(node_lib_target_name)',
'deps/googletest/googletest.gyp:gtest_prod', 'deps/googletest/googletest.gyp:gtest_prod',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
'deps/uvwasi/uvwasi.gyp:uvwasi', 'deps/uvwasi/uvwasi.gyp:uvwasi',
'deps/ada/ada.gyp:ada', 'deps/ada/ada.gyp:ada',
], ],
@ -1120,6 +1129,7 @@
'tools/msvs/genfiles', 'tools/msvs/genfiles',
'deps/v8/include', 'deps/v8/include',
'deps/cares/include', 'deps/cares/include',
'deps/sqlite',
'deps/uv/include', 'deps/uv/include',
'deps/uvwasi/include', 'deps/uvwasi/include',
'test/cctest', 'test/cctest',
@ -1156,6 +1166,7 @@
'deps/googletest/googletest.gyp:gtest', 'deps/googletest/googletest.gyp:gtest',
'deps/googletest/googletest.gyp:gtest_main', 'deps/googletest/googletest.gyp:gtest_main',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
'deps/simdjson/simdjson.gyp:simdjson', 'deps/simdjson/simdjson.gyp:simdjson',
'deps/simdutf/simdutf.gyp:simdutf', 'deps/simdutf/simdutf.gyp:simdutf',
'deps/ada/ada.gyp:ada', 'deps/ada/ada.gyp:ada',
@ -1171,6 +1182,7 @@
'deps/v8/include', 'deps/v8/include',
'deps/cares/include', 'deps/cares/include',
'deps/uv/include', 'deps/uv/include',
'deps/sqlite',
'test/cctest', 'test/cctest',
], ],
@ -1232,6 +1244,7 @@
'dependencies': [ 'dependencies': [
'<(node_lib_target_name)', '<(node_lib_target_name)',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
'deps/ada/ada.gyp:ada', 'deps/ada/ada.gyp:ada',
], ],
@ -1246,6 +1259,7 @@
'deps/v8/include', 'deps/v8/include',
'deps/cares/include', 'deps/cares/include',
'deps/uv/include', 'deps/uv/include',
'deps/sqlite',
'test/embedding', 'test/embedding',
], ],
@ -1345,6 +1359,7 @@
'dependencies': [ 'dependencies': [
'<(node_lib_target_name)', '<(node_lib_target_name)',
'deps/histogram/histogram.gyp:histogram', 'deps/histogram/histogram.gyp:histogram',
'deps/sqlite/sqlite.gyp:sqlite',
'deps/ada/ada.gyp:ada', 'deps/ada/ada.gyp:ada',
'deps/simdjson/simdjson.gyp:simdjson', 'deps/simdjson/simdjson.gyp:simdjson',
'deps/simdutf/simdutf.gyp:simdutf', 'deps/simdutf/simdutf.gyp:simdutf',
@ -1360,6 +1375,7 @@
'deps/v8/include', 'deps/v8/include',
'deps/cares/include', 'deps/cares/include',
'deps/uv/include', 'deps/uv/include',
'deps/sqlite',
], ],
'defines': [ 'NODE_WANT_INTERNALS=1' ], 'defines': [ 'NODE_WANT_INTERNALS=1' ],

View File

@ -43,6 +43,7 @@
#define PER_ISOLATE_SYMBOL_PROPERTIES(V) \ #define PER_ISOLATE_SYMBOL_PROPERTIES(V) \
V(fs_use_promises_symbol, "fs_use_promises_symbol") \ V(fs_use_promises_symbol, "fs_use_promises_symbol") \
V(async_id_symbol, "async_id_symbol") \ V(async_id_symbol, "async_id_symbol") \
V(constructor_key_symbol, "constructor_key_symbol") \
V(handle_onclose_symbol, "handle_onclose") \ V(handle_onclose_symbol, "handle_onclose") \
V(no_message_symbol, "no_message_symbol") \ V(no_message_symbol, "no_message_symbol") \
V(messaging_deserialize_symbol, "messaging_deserialize_symbol") \ V(messaging_deserialize_symbol, "messaging_deserialize_symbol") \

View File

@ -85,6 +85,7 @@
V(wasi) \ V(wasi) \
V(wasm_web_api) \ V(wasm_web_api) \
V(watchdog) \ V(watchdog) \
V(webstorage) \
V(worker) \ V(worker) \
V(zlib) V(zlib)

View File

@ -10,6 +10,7 @@
#include "node.h" #include "node.h"
#include "simdjson.h" #include "simdjson.h"
#include "simdutf.h" #include "simdutf.h"
#include "sqlite3.h"
#include "undici_version.h" #include "undici_version.h"
#include "util.h" #include "util.h"
#include "uv.h" #include "uv.h"
@ -131,6 +132,7 @@ Metadata::Versions::Versions() {
simdjson = SIMDJSON_VERSION; simdjson = SIMDJSON_VERSION;
simdutf = SIMDUTF_VERSION; simdutf = SIMDUTF_VERSION;
sqlite = SQLITE_VERSION;
ada = ADA_VERSION; ada = ADA_VERSION;
} }

View File

@ -48,6 +48,7 @@ namespace node {
V(acorn) \ V(acorn) \
V(simdjson) \ V(simdjson) \
V(simdutf) \ V(simdutf) \
V(sqlite) \
V(ada) \ V(ada) \
NODE_VERSIONS_KEY_UNDICI(V) \ NODE_VERSIONS_KEY_UNDICI(V) \
V(cjs_module_lexer) \ V(cjs_module_lexer) \

View File

@ -410,6 +410,14 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
kAllowedInEnvvar, kAllowedInEnvvar,
true); true);
AddOption("--experimental-global-customevent", "", NoOp{}, kAllowedInEnvvar); AddOption("--experimental-global-customevent", "", NoOp{}, kAllowedInEnvvar);
AddOption("--experimental-webstorage",
"experimental Web Storage API",
&EnvironmentOptions::experimental_webstorage,
kAllowedInEnvvar);
AddOption("--localstorage-file",
"file used to persist localStorage data",
&EnvironmentOptions::localstorage_file,
kAllowedInEnvvar);
AddOption("--experimental-global-navigator", AddOption("--experimental-global-navigator",
"expose experimental Navigator API on the global scope", "expose experimental Navigator API on the global scope",
&EnvironmentOptions::experimental_global_navigator, &EnvironmentOptions::experimental_global_navigator,

View File

@ -118,6 +118,8 @@ class EnvironmentOptions : public Options {
bool experimental_eventsource = false; bool experimental_eventsource = false;
bool experimental_fetch = true; bool experimental_fetch = true;
bool experimental_websocket = true; bool experimental_websocket = true;
bool experimental_webstorage = false;
std::string localstorage_file;
bool experimental_global_navigator = true; bool experimental_global_navigator = true;
bool experimental_global_web_crypto = true; bool experimental_global_web_crypto = true;
bool experimental_https_modules = false; bool experimental_https_modules = false;

706
src/node_webstorage.cc Normal file
View File

@ -0,0 +1,706 @@
#include "node_webstorage.h"
#include "base_object-inl.h"
#include "debug_utils-inl.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "node.h"
#include "node_errors.h"
#include "node_mem-inl.h"
#include "sqlite3.h"
#include "util-inl.h"
namespace node {
namespace webstorage {
using v8::Array;
using v8::Boolean;
using v8::Context;
using v8::DontDelete;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::IndexedPropertyHandlerConfiguration;
using v8::Integer;
using v8::Intercepted;
using v8::Isolate;
using v8::Local;
using v8::Map;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Name;
using v8::NamedPropertyHandlerConfiguration;
using v8::Null;
using v8::Object;
using v8::PropertyAttribute;
using v8::PropertyCallbackInfo;
using v8::PropertyDescriptor;
using v8::PropertyHandlerFlags;
using v8::String;
using v8::Uint32;
using v8::Value;
#define THROW_SQLITE_ERROR(env, r) \
node::THROW_ERR_INVALID_STATE((env), sqlite3_errstr((r)))
#define CHECK_ERROR_OR_THROW(env, expr, expected, ret) \
do { \
int r_ = (expr); \
if (r_ != (expected)) { \
THROW_SQLITE_ERROR((env), r_); \
return (ret); \
} \
} while (0)
static void ThrowQuotaExceededException(Local<Context> context) {
Isolate* isolate = context->GetIsolate();
auto dom_exception_str = FIXED_ONE_BYTE_STRING(isolate, "DOMException");
auto err_name = FIXED_ONE_BYTE_STRING(isolate, "QuotaExceededError");
auto err_message =
FIXED_ONE_BYTE_STRING(isolate, "Setting the value exceeded the quota");
Local<Object> per_context_bindings;
Local<Value> domexception_ctor_val;
if (!GetPerContextExports(context).ToLocal(&per_context_bindings) ||
!per_context_bindings->Get(context, dom_exception_str)
.ToLocal(&domexception_ctor_val)) {
return;
}
CHECK(domexception_ctor_val->IsFunction());
Local<Function> domexception_ctor = domexception_ctor_val.As<Function>();
Local<Value> argv[] = {err_message, err_name};
Local<Value> exception;
if (!domexception_ctor->NewInstance(context, arraysize(argv), argv)
.ToLocal(&exception)) {
return;
}
isolate->ThrowException(exception);
}
Storage::Storage(Environment* env, Local<Object> object, Local<String> location)
: BaseObject(env, object) {
MakeWeak();
node::Utf8Value utf8_location(env->isolate(), location);
symbols_.Reset(env->isolate(), Map::New(env->isolate()));
db_ = nullptr;
location_ = utf8_location.ToString();
}
Storage::~Storage() {
db_ = nullptr;
}
void Storage::MemoryInfo(MemoryTracker* tracker) const {
tracker->TrackField("symbols", symbols_);
tracker->TrackField("location", location_);
}
bool Storage::Open() {
static const int kCurrentSchemaVersion = 1;
static const char get_schema_version_sql[] =
"SELECT schema_version FROM nodejs_webstorage_state";
static const char init_sql_v0[] =
"PRAGMA encoding = 'UTF-16le';"
"PRAGMA busy_timeout = 3000;"
"PRAGMA journal_mode = WAL;"
"PRAGMA synchronous = NORMAL;"
"PRAGMA temp_store = memory;"
"PRAGMA optimize;"
""
"CREATE TABLE IF NOT EXISTS nodejs_webstorage("
" key BLOB NOT NULL,"
" value BLOB NOT NULL,"
" PRIMARY KEY(key)"
") STRICT;"
""
"CREATE TABLE IF NOT EXISTS nodejs_webstorage_state("
// max_size is 10MB. This can be made configurable in the future.
" max_size INTEGER NOT NULL DEFAULT 10485760,"
" total_size INTEGER NOT NULL,"
" schema_version INTEGER NOT NULL DEFAULT 0,"
" single_row_ INTEGER NOT NULL DEFAULT 1 CHECK(single_row_ = 1),"
" PRIMARY KEY(single_row_)"
") STRICT;"
""
"CREATE TRIGGER IF NOT EXISTS nodejs_quota_insert "
"AFTER INSERT ON nodejs_webstorage "
"FOR EACH ROW "
"BEGIN "
" UPDATE nodejs_webstorage_state"
" SET total_size = total_size + OCTET_LENGTH(NEW.key) +"
" OCTET_LENGTH(NEW.value);"
" SELECT RAISE(ABORT, 'QuotaExceeded') WHERE EXISTS ("
" SELECT 1 FROM nodejs_webstorage_state WHERE total_size > max_size"
" );"
"END;"
""
"CREATE TRIGGER IF NOT EXISTS nodejs_quota_update "
"AFTER UPDATE ON nodejs_webstorage "
"FOR EACH ROW "
"BEGIN "
" UPDATE nodejs_webstorage_state"
" SET total_size = total_size + "
" ((OCTET_LENGTH(NEW.key) + OCTET_LENGTH(NEW.value)) -"
" (OCTET_LENGTH(OLD.key) + OCTET_LENGTH(OLD.value)));"
" SELECT RAISE(ABORT, 'QuotaExceeded') WHERE EXISTS ("
" SELECT 1 FROM nodejs_webstorage_state WHERE total_size > max_size"
" );"
"END;"
""
"CREATE TRIGGER IF NOT EXISTS nodejs_quota_delete "
"AFTER DELETE ON nodejs_webstorage "
"FOR EACH ROW "
"BEGIN "
" UPDATE nodejs_webstorage_state"
" SET total_size = total_size - (OCTET_LENGTH(OLD.key) +"
" OCTET_LENGTH(OLD.value));"
"END;"
""
"INSERT OR IGNORE INTO nodejs_webstorage_state (total_size) VALUES (0);";
sqlite3* db = db_.get();
if (db != nullptr) {
return true;
}
int r = sqlite3_open(location_.c_str(), &db);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
r = sqlite3_exec(db, init_sql_v0, 0, 0, nullptr);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
// Get the current schema version, used to determine schema migrations.
sqlite3_stmt* s = nullptr;
r = sqlite3_prepare_v2(db, get_schema_version_sql, -1, &s, 0);
r = sqlite3_exec(db, init_sql_v0, 0, 0, nullptr);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
auto stmt = stmt_unique_ptr(s);
CHECK_ERROR_OR_THROW(env(), sqlite3_step(stmt.get()), SQLITE_ROW, false);
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_INTEGER);
int schema_version = sqlite3_column_int(stmt.get(), 0);
stmt = nullptr; // Force finalization.
if (schema_version > kCurrentSchemaVersion) {
node::THROW_ERR_INVALID_STATE(
env(), "localStorage was created with a newer version of Node.js");
return false;
}
if (schema_version < kCurrentSchemaVersion) {
// Run any migrations and update the schema version.
std::string set_user_version_sql =
"UPDATE nodejs_webstorage_state SET schema_version = " +
std::to_string(kCurrentSchemaVersion) + ";";
r = sqlite3_exec(db, set_user_version_sql.c_str(), 0, 0, nullptr);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
}
db_ = conn_unique_ptr(db);
return true;
}
void Storage::New(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
Realm* realm = Realm::GetCurrent(args);
if (!args[0]->StrictEquals(realm->isolate_data()->constructor_key_symbol())) {
THROW_ERR_ILLEGAL_CONSTRUCTOR(env);
return;
}
CHECK(args.IsConstructCall());
CHECK(args[1]->IsString());
new Storage(env, args.This(), args[1].As<String>());
}
void Storage::Clear() {
if (!Open()) {
return;
}
static const char sql[] = "DELETE FROM nodejs_webstorage";
sqlite3_stmt* s = nullptr;
CHECK_ERROR_OR_THROW(
env(), sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0), SQLITE_OK, void());
auto stmt = stmt_unique_ptr(s);
CHECK_ERROR_OR_THROW(env(), sqlite3_step(stmt.get()), SQLITE_DONE, void());
}
Local<Array> Storage::Enumerate() {
if (!Open()) {
return Local<Array>();
}
static const char sql[] = "SELECT key FROM nodejs_webstorage";
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local<Array>());
auto stmt = stmt_unique_ptr(s);
std::vector<Local<Value>> values;
while ((r = sqlite3_step(stmt.get())) == SQLITE_ROW) {
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB);
auto size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t);
values.emplace_back(
String::NewFromTwoByte(env()->isolate(),
reinterpret_cast<const uint16_t*>(
sqlite3_column_blob(stmt.get(), 0)),
v8::NewStringType::kNormal,
size)
.ToLocalChecked());
}
CHECK_ERROR_OR_THROW(env(), r, SQLITE_DONE, Local<Array>());
return Array::New(env()->isolate(), values.data(), values.size());
}
Local<Value> Storage::Length() {
if (!Open()) {
return Local<Value>();
}
static const char sql[] = "SELECT count(*) FROM nodejs_webstorage";
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local<Value>());
auto stmt = stmt_unique_ptr(s);
CHECK_ERROR_OR_THROW(
env(), sqlite3_step(stmt.get()), SQLITE_ROW, Local<Value>());
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_INTEGER);
int result = sqlite3_column_int(stmt.get(), 0);
return Integer::New(env()->isolate(), result);
}
Local<Value> Storage::Load(Local<Name> key) {
if (key->IsSymbol()) {
auto symbol_map = symbols_.Get(env()->isolate());
MaybeLocal<Value> result = symbol_map->Get(env()->context(), key);
return result.FromMaybe(Local<Value>());
}
if (!Open()) {
return Local<Value>();
}
static const char sql[] =
"SELECT value FROM nodejs_webstorage WHERE key = ? LIMIT 1";
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local<Value>());
auto stmt = stmt_unique_ptr(s);
node::TwoByteValue utf16key(env()->isolate(), key);
auto key_size = utf16key.length() * sizeof(uint16_t);
r = sqlite3_bind_blob(stmt.get(), 1, utf16key.out(), key_size, SQLITE_STATIC);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local<Value>());
auto value = Local<Value>();
r = sqlite3_step(stmt.get());
if (r == SQLITE_ROW) {
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB);
auto size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t);
value = String::NewFromTwoByte(env()->isolate(),
reinterpret_cast<const uint16_t*>(
sqlite3_column_blob(stmt.get(), 0)),
v8::NewStringType::kNormal,
size)
.ToLocalChecked();
} else if (r != SQLITE_DONE) {
THROW_SQLITE_ERROR(env(), r);
}
return value;
}
Local<Value> Storage::LoadKey(const int index) {
if (!Open()) {
return Local<Value>();
}
static const char sql[] =
"SELECT key FROM nodejs_webstorage LIMIT 1 OFFSET ?";
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local<Value>());
auto stmt = stmt_unique_ptr(s);
r = sqlite3_bind_int(stmt.get(), 1, index);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, Local<Value>());
auto value = Local<Value>();
r = sqlite3_step(stmt.get());
if (r == SQLITE_ROW) {
CHECK(sqlite3_column_type(stmt.get(), 0) == SQLITE_BLOB);
auto size = sqlite3_column_bytes(stmt.get(), 0) / sizeof(uint16_t);
value = String::NewFromTwoByte(env()->isolate(),
reinterpret_cast<const uint16_t*>(
sqlite3_column_blob(stmt.get(), 0)),
v8::NewStringType::kNormal,
size)
.ToLocalChecked();
} else if (r != SQLITE_DONE) {
THROW_SQLITE_ERROR(env(), r);
}
return value;
}
bool Storage::Remove(Local<Name> key) {
if (key->IsSymbol()) {
auto symbol_map = symbols_.Get(env()->isolate());
Maybe<bool> result = symbol_map->Delete(env()->context(), key);
return !result.IsNothing();
}
if (!Open()) {
return false;
}
static const char sql[] = "DELETE FROM nodejs_webstorage WHERE key = ?";
sqlite3_stmt* s = nullptr;
int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
auto stmt = stmt_unique_ptr(s);
node::TwoByteValue utf16key(env()->isolate(), key);
auto key_size = utf16key.length() * sizeof(uint16_t);
r = sqlite3_bind_blob(stmt.get(), 1, utf16key.out(), key_size, SQLITE_STATIC);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
CHECK_ERROR_OR_THROW(env(), sqlite3_step(stmt.get()), SQLITE_DONE, false);
return true;
}
bool Storage::Store(Local<Name> key, Local<Value> value) {
if (key->IsSymbol()) {
auto symbol_map = symbols_.Get(env()->isolate());
MaybeLocal<Map> result = symbol_map->Set(env()->context(), key, value);
return !result.IsEmpty();
}
Local<String> val;
if (!value->ToString(env()->context()).ToLocal(&val)) {
return false;
}
if (!Open()) {
return false;
}
static const char sql[] =
"INSERT INTO nodejs_webstorage (key, value) VALUES (?, ?)"
" ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value"
" WHERE EXCLUDED.key = key";
sqlite3_stmt* s = nullptr;
node::TwoByteValue utf16key(env()->isolate(), key);
node::TwoByteValue utf16val(env()->isolate(), val);
int r = sqlite3_prepare_v2(db_.get(), sql, -1, &s, 0);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
auto stmt = stmt_unique_ptr(s);
auto key_size = utf16key.length() * sizeof(uint16_t);
r = sqlite3_bind_blob(stmt.get(), 1, utf16key.out(), key_size, SQLITE_STATIC);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
auto val_size = utf16val.length() * sizeof(uint16_t);
r = sqlite3_bind_blob(stmt.get(), 2, utf16val.out(), val_size, SQLITE_STATIC);
CHECK_ERROR_OR_THROW(env(), r, SQLITE_OK, false);
r = sqlite3_step(stmt.get());
if (r == SQLITE_CONSTRAINT) {
ThrowQuotaExceededException(env()->context());
return false;
}
CHECK_ERROR_OR_THROW(env(), r, SQLITE_DONE, false);
return true;
}
static Local<Name> Uint32ToName(Local<Context> context, uint32_t index) {
return Uint32::New(context->GetIsolate(), index)
->ToString(context)
.ToLocalChecked();
}
static void Clear(const FunctionCallbackInfo<Value>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder());
storage->Clear();
}
static void GetItem(const FunctionCallbackInfo<Value>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder());
Environment* env = Environment::GetCurrent(info);
if (info.Length() < 1) {
return THROW_ERR_MISSING_ARGS(
env, "Failed to execute 'getItem' on 'Storage': 1 argument required");
}
Local<String> prop;
if (!info[0]->ToString(env->context()).ToLocal(&prop)) {
return;
}
Local<Value> result = storage->Load(prop);
if (result.IsEmpty()) {
info.GetReturnValue().Set(Null(env->isolate()));
} else {
info.GetReturnValue().Set(result);
}
}
static void Key(const FunctionCallbackInfo<Value>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder());
Environment* env = Environment::GetCurrent(info);
int index;
if (info.Length() < 1) {
return THROW_ERR_MISSING_ARGS(
env, "Failed to execute 'key' on 'Storage': 1 argument required");
}
if (!info[0]->Int32Value(env->context()).To(&index)) {
return;
}
if (index < 0) {
info.GetReturnValue().Set(Null(env->isolate()));
return;
}
Local<Value> result = storage->LoadKey(index);
if (result.IsEmpty()) {
info.GetReturnValue().Set(Null(env->isolate()));
} else {
info.GetReturnValue().Set(result);
}
}
static void RemoveItem(const FunctionCallbackInfo<Value>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder());
Environment* env = Environment::GetCurrent(info);
Local<String> prop;
if (info.Length() < 1) {
return THROW_ERR_MISSING_ARGS(
env,
"Failed to execute 'removeItem' on 'Storage': 1 argument required");
}
if (!info[0]->ToString(env->context()).ToLocal(&prop)) {
return;
}
storage->Remove(prop);
}
static void SetItem(const FunctionCallbackInfo<Value>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder());
Environment* env = Environment::GetCurrent(info);
if (info.Length() < 2) {
return THROW_ERR_MISSING_ARGS(
env, "Failed to execute 'setItem' on 'Storage': 2 arguments required");
}
Local<String> prop;
if (!info[0]->ToString(env->context()).ToLocal(&prop)) {
return;
}
storage->Store(prop, info[1]);
}
template <typename T>
static bool ShouldIntercept(Local<Name> property,
const PropertyCallbackInfo<T>& info) {
Environment* env = Environment::GetCurrent(info);
Local<Value> proto = info.Holder()->GetPrototype();
if (proto->IsObject()) {
bool has_prop;
if (!proto.As<Object>()->Has(env->context(), property).To(&has_prop)) {
return false;
}
if (has_prop) {
return false;
}
}
return true;
}
static Intercepted StorageGetter(Local<Name> property,
const PropertyCallbackInfo<Value>& info) {
if (!ShouldIntercept(property, info)) {
return Intercepted::kNo;
}
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder(), Intercepted::kNo);
Local<Value> result = storage->Load(property);
if (result.IsEmpty()) {
info.GetReturnValue().SetUndefined();
} else {
info.GetReturnValue().Set(result);
}
return Intercepted::kYes;
}
static Intercepted StorageSetter(Local<Name> property,
Local<Value> value,
const PropertyCallbackInfo<void>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder(), Intercepted::kNo);
if (storage->Store(property, value)) {
info.GetReturnValue().Set(value);
}
return Intercepted::kYes;
}
static Intercepted StorageQuery(Local<Name> property,
const PropertyCallbackInfo<Integer>& info) {
if (!ShouldIntercept(property, info)) {
return Intercepted::kNo;
}
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder(), Intercepted::kNo);
Local<Value> result = storage->Load(property);
if (result.IsEmpty()) {
return Intercepted::kNo;
}
info.GetReturnValue().Set(0);
return Intercepted::kYes;
}
static Intercepted StorageDeleter(Local<Name> property,
const PropertyCallbackInfo<Boolean>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder(), Intercepted::kNo);
if (storage->Remove(property)) {
info.GetReturnValue().Set(true);
}
return Intercepted::kYes;
}
static void StorageEnumerator(const PropertyCallbackInfo<Array>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder());
info.GetReturnValue().Set(storage->Enumerate());
}
static Intercepted StorageDefiner(Local<Name> property,
const PropertyDescriptor& desc,
const PropertyCallbackInfo<void>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.Holder(), Intercepted::kNo);
if (desc.has_value()) {
return StorageSetter(property, desc.value(), info);
}
return Intercepted::kYes;
}
static Intercepted IndexedGetter(uint32_t index,
const PropertyCallbackInfo<Value>& info) {
Environment* env = Environment::GetCurrent(info);
return StorageGetter(Uint32ToName(env->context(), index), info);
}
static Intercepted IndexedSetter(uint32_t index,
Local<Value> value,
const PropertyCallbackInfo<void>& info) {
Environment* env = Environment::GetCurrent(info);
return StorageSetter(Uint32ToName(env->context(), index), value, info);
}
static Intercepted IndexedQuery(uint32_t index,
const PropertyCallbackInfo<Integer>& info) {
Environment* env = Environment::GetCurrent(info);
return StorageQuery(Uint32ToName(env->context(), index), info);
}
static Intercepted IndexedDeleter(uint32_t index,
const PropertyCallbackInfo<Boolean>& info) {
Environment* env = Environment::GetCurrent(info);
return StorageDeleter(Uint32ToName(env->context(), index), info);
}
static Intercepted IndexedDefiner(uint32_t index,
const PropertyDescriptor& desc,
const PropertyCallbackInfo<void>& info) {
Environment* env = Environment::GetCurrent(info);
return StorageDefiner(Uint32ToName(env->context(), index), desc, info);
}
static void StorageLengthGetter(const FunctionCallbackInfo<Value>& info) {
Storage* storage;
ASSIGN_OR_RETURN_UNWRAP(&storage, info.This());
info.GetReturnValue().Set(storage->Length());
}
static void Initialize(Local<Object> target,
Local<Value> unused,
Local<Context> context,
void* priv) {
Environment* env = Environment::GetCurrent(context);
Isolate* isolate = env->isolate();
auto ctor_tmpl = NewFunctionTemplate(isolate, Storage::New);
auto inst_tmpl = ctor_tmpl->InstanceTemplate();
inst_tmpl->SetInternalFieldCount(Storage::kInternalFieldCount);
inst_tmpl->SetHandler(NamedPropertyHandlerConfiguration(
StorageGetter,
StorageSetter,
StorageQuery,
StorageDeleter,
StorageEnumerator,
StorageDefiner,
nullptr,
Local<Value>(),
PropertyHandlerFlags::kHasNoSideEffect));
inst_tmpl->SetHandler(IndexedPropertyHandlerConfiguration(
IndexedGetter,
IndexedSetter,
IndexedQuery,
IndexedDeleter,
nullptr,
IndexedDefiner,
nullptr,
Local<Value>(),
PropertyHandlerFlags::kHasNoSideEffect));
Local<FunctionTemplate> length_getter =
FunctionTemplate::New(isolate, StorageLengthGetter);
ctor_tmpl->PrototypeTemplate()->SetAccessorProperty(
FIXED_ONE_BYTE_STRING(isolate, "length"),
length_getter,
Local<FunctionTemplate>(),
DontDelete);
SetProtoMethod(isolate, ctor_tmpl, "clear", Clear);
SetProtoMethodNoSideEffect(isolate, ctor_tmpl, "getItem", GetItem);
SetProtoMethodNoSideEffect(isolate, ctor_tmpl, "key", Key);
SetProtoMethod(isolate, ctor_tmpl, "removeItem", RemoveItem);
SetProtoMethod(isolate, ctor_tmpl, "setItem", SetItem);
SetConstructorFunction(context, target, "Storage", ctor_tmpl);
auto symbol = env->isolate_data()->constructor_key_symbol();
target
->DefineOwnProperty(context,
FIXED_ONE_BYTE_STRING(isolate, "kConstructorKey"),
symbol,
PropertyAttribute::ReadOnly)
.Check();
}
} // namespace webstorage
} // namespace node
NODE_BINDING_CONTEXT_AWARE_INTERNAL(webstorage, node::webstorage::Initialize)

58
src/node_webstorage.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef SRC_NODE_WEBSTORAGE_H_
#define SRC_NODE_WEBSTORAGE_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "base_object.h"
#include "node_mem.h"
#include "sqlite3.h"
#include "util.h"
namespace node {
namespace webstorage {
struct conn_deleter {
void operator()(sqlite3* conn) const noexcept {
CHECK_EQ(sqlite3_close(conn), SQLITE_OK);
}
};
using conn_unique_ptr = std::unique_ptr<sqlite3, conn_deleter>;
struct stmt_deleter {
void operator()(sqlite3_stmt* stmt) const noexcept { sqlite3_finalize(stmt); }
};
using stmt_unique_ptr = std::unique_ptr<sqlite3_stmt, stmt_deleter>;
class Storage : public BaseObject {
public:
Storage(Environment* env,
v8::Local<v8::Object> object,
v8::Local<v8::String> location);
void MemoryInfo(MemoryTracker* tracker) const override;
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
void Clear();
v8::Local<v8::Array> Enumerate();
v8::Local<v8::Value> Length();
v8::Local<v8::Value> Load(v8::Local<v8::Name> key);
v8::Local<v8::Value> LoadKey(const int index);
bool Remove(v8::Local<v8::Name> key);
bool Store(v8::Local<v8::Name> key, v8::Local<v8::Value> value);
SET_MEMORY_INFO_NAME(Storage)
SET_SELF_SIZE(Storage)
private:
bool Open();
~Storage() override;
std::string location_;
conn_unique_ptr db_;
v8::Global<v8::Map> symbols_;
};
} // namespace webstorage
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_NODE_WEBSTORAGE_H_

View File

@ -366,6 +366,14 @@ if (global.ReadableStream) {
); );
} }
if (global.Storage) {
knownGlobals.push(
global.localStorage,
global.sessionStorage,
global.Storage,
);
}
function allowGlobals(...allowlist) { function allowGlobals(...allowlist) {
knownGlobals = knownGlobals.concat(allowlist); knownGlobals = knownGlobals.concat(allowlist);
} }

View File

@ -35,6 +35,7 @@ Last update:
- WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/5e042cbc4e/WebCryptoAPI - WebCryptoAPI: https://github.com/web-platform-tests/wpt/tree/5e042cbc4e/WebCryptoAPI
- webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions - webidl/ecmascript-binding/es-exceptions: https://github.com/web-platform-tests/wpt/tree/a370aad338/webidl/ecmascript-binding/es-exceptions
- webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/e97fac4791/webmessaging/broadcastchannel - webmessaging/broadcastchannel: https://github.com/web-platform-tests/wpt/tree/e97fac4791/webmessaging/broadcastchannel
- webstorage: https://github.com/web-platform-tests/wpt/tree/9dafa89214/webstorage
[Web Platform Tests]: https://github.com/web-platform-tests/wpt [Web Platform Tests]: https://github.com/web-platform-tests/wpt
[`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/main/docs/git-node.md#git-node-wpt [`git node wpt`]: https://github.com/nodejs/node-core-utils/blob/main/docs/git-node.md#git-node-wpt

View File

@ -0,0 +1,55 @@
// Create a credentialless iframe. The new document will execute any scripts
// sent toward the token it returns.
const newIframeCredentialless = (child_origin, opt_headers) => {
opt_headers ||= "";
const sub_document_token = token();
let iframe = document.createElement('iframe');
iframe.src = child_origin + executor_path + opt_headers +
`&uuid=${sub_document_token}`;
iframe.credentialless = true;
document.body.appendChild(iframe);
return sub_document_token;
};
// Create a normal iframe. The new document will execute any scripts sent
// toward the token it returns.
const newIframe = (child_origin) => {
const sub_document_token = token();
let iframe = document.createElement('iframe');
iframe.src = child_origin + executor_path + `&uuid=${sub_document_token}`;
iframe.credentialless = false
document.body.appendChild(iframe);
return sub_document_token;
};
// Create a popup. The new document will execute any scripts sent toward the
// token it returns.
const newPopup = (test, origin) => {
const popup_token = token();
const popup = window.open(origin + executor_path + `&uuid=${popup_token}`);
test.add_cleanup(() => popup.close());
return popup_token;
}
// Create a fenced frame. The new document will execute any scripts sent
// toward the token it returns.
const newFencedFrame = async (child_origin) => {
const support_loading_mode_fenced_frame =
"|header(Supports-Loading-Mode,fenced-frame)";
const sub_document_token = token();
const url = child_origin + executor_path +
support_loading_mode_fenced_frame +
`&uuid=${sub_document_token}`;
const urn = await generateURNFromFledge(url, []);
attachFencedFrame(urn);
return sub_document_token;
};
const importScript = (url) => {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = url;
const loaded = new Promise(resolve => script.onload = resolve);
document.body.appendChild(script);
return loaded;
}

View File

@ -0,0 +1,134 @@
const executor_path = '/common/dispatcher/executor.html?pipe=';
const executor_worker_path = '/common/dispatcher/executor-worker.js?pipe=';
const executor_service_worker_path = '/common/dispatcher/executor-service-worker.js?pipe=';
// COEP
const coep_none =
'|header(Cross-Origin-Embedder-Policy,none)';
const coep_credentialless =
'|header(Cross-Origin-Embedder-Policy,credentialless)';
const coep_require_corp =
'|header(Cross-Origin-Embedder-Policy,require-corp)';
// COEP-Report-Only
const coep_report_only_credentialless =
'|header(Cross-Origin-Embedder-Policy-Report-Only,credentialless)';
// COOP
const coop_same_origin =
'|header(Cross-Origin-Opener-Policy,same-origin)';
// CORP
const corp_cross_origin =
'|header(Cross-Origin-Resource-Policy,cross-origin)';
const cookie_same_site_none = ';SameSite=None;Secure';
// Test using the modern async/await primitives are easier to read/write.
// However they run sequentially, contrary to async_test. This is the parallel
// version, to avoid timing out.
let promise_test_parallel = (promise, description) => {
async_test(test => {
promise(test)
.then(() => test.done())
.catch(test.step_func(error => { throw error; }));
}, description);
};
// Add a cookie |cookie_key|=|cookie_value| on an |origin|.
// Note: cookies visibility depends on the path of the document. Those are set
// from a document from: /html/cross-origin-embedder-policy/credentialless/. So
// the cookie is visible to every path underneath.
const setCookie = async (origin, cookie_key, cookie_value) => {
const popup_token = token();
const popup_url = origin + executor_path + `&uuid=${popup_token}`;
const popup = window.open(popup_url);
const reply_token = token();
send(popup_token, `
document.cookie = "${cookie_key}=${cookie_value}";
send("${reply_token}", "done");
`);
assert_equals(await receive(reply_token), "done");
popup.close();
}
let parseCookies = function(headers_json) {
if (!headers_json["cookie"])
return {};
return headers_json["cookie"]
.split(';')
.map(v => v.split('='))
.reduce((acc, v) => {
acc[v[0].trim()] = v[1].trim();
return acc;
}, {});
}
// Open a new window with a given |origin|, loaded with COEP:credentialless. The
// new document will execute any scripts sent toward the token it returns.
const newCredentiallessWindow = (origin) => {
const main_document_token = token();
const url = origin + executor_path + coep_credentialless +
`&uuid=${main_document_token}`;
const context = window.open(url);
add_completion_callback(() => w.close());
return main_document_token;
};
// Create a new iframe, loaded with COEP:credentialless.
// The new document will execute any scripts sent toward the token it returns.
const newCredentiallessIframe = (parent_token, child_origin) => {
const sub_document_token = token();
const iframe_url = child_origin + executor_path + coep_credentialless +
`&uuid=${sub_document_token}`;
send(parent_token, `
let iframe = document.createElement("iframe");
iframe.src = "${iframe_url}";
document.body.appendChild(iframe);
`)
return sub_document_token;
};
// A common interface for building the 4 type of execution contexts:
// It outputs: [
// - The token to communicate with the environment.
// - A promise resolved when the environment encounters an error.
// ]
const environments = {
document: headers => {
const tok = token();
const url = window.origin + executor_path + headers + `&uuid=${tok}`;
const context = window.open(url);
add_completion_callback(() => context.close());
return [tok, new Promise(resolve => {})];
},
dedicated_worker: headers => {
const tok = token();
const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
const context = new Worker(url);
return [tok, new Promise(resolve => context.onerror = resolve)];
},
shared_worker: headers => {
const tok = token();
const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
const context = new SharedWorker(url);
return [tok, new Promise(resolve => context.onerror = resolve)];
},
service_worker: headers => {
const tok = token();
const url = window.origin + executor_worker_path + headers + `&uuid=${tok}`;
const scope = url; // Generate a one-time scope for service worker.
const error = new Promise(resolve => {
navigator.serviceWorker.register(url, {scope: scope})
.then(registration => {
add_completion_callback(() => registration.unregister());
}, /* catch */ resolve);
});
return [tok, error];
},
};

View File

@ -98,5 +98,9 @@
"webmessaging/broadcastchannel": { "webmessaging/broadcastchannel": {
"commit": "e97fac4791931fb7455ba3fad759d362c7108b09", "commit": "e97fac4791931fb7455ba3fad759d362c7108b09",
"path": "webmessaging/broadcastchannel" "path": "webmessaging/broadcastchannel"
},
"webstorage": {
"commit": "9dafa892146c4b5b1f604a39b3cf8677f8f70d44",
"path": "webstorage"
} }
} }

5
test/fixtures/wpt/webstorage/META.yml vendored Normal file
View File

@ -0,0 +1,5 @@
spec: https://html.spec.whatwg.org/multipage/webstorage.html
suggested_reviewers:
- siusin
- inexorabletash
- jdm

View File

@ -0,0 +1,4 @@
These are the storage (`localStorage`, `sessionStorage`) tests for the
[Web storage chapter of the HTML Standard](https://html.spec.whatwg.org/multipage/webstorage.html).
IDL is covered by `/html/dom/idlharness.https.html`.

View File

@ -0,0 +1,57 @@
["localStorage", "sessionStorage"].forEach(function(name) {
[9, "x"].forEach(function(key) {
test(function() {
var desc = {
value: "value",
};
var storage = window[name];
storage.clear();
assert_equals(storage[key], undefined);
assert_equals(storage.getItem(key), null);
assert_equals(Object.defineProperty(storage, key, desc), storage);
assert_equals(storage[key], "value");
assert_equals(storage.getItem(key), "value");
}, "Defining data property for key " + key + " on " + name);
test(function() {
var desc1 = {
value: "value",
};
var desc2 = {
value: "new value",
};
var storage = window[name];
storage.clear();
assert_equals(storage[key], undefined);
assert_equals(storage.getItem(key), null);
assert_equals(Object.defineProperty(storage, key, desc1), storage);
assert_equals(storage[key], "value");
assert_equals(storage.getItem(key), "value");
assert_equals(Object.defineProperty(storage, key, desc2), storage);
assert_equals(storage[key], "new value");
assert_equals(storage.getItem(key), "new value");
}, "Defining data property for key " + key + " on " + name + " twice");
test(function() {
var desc = {
value: {
toString: function() { return "value"; }
},
};
var storage = window[name];
storage.clear();
assert_equals(storage[key], undefined);
assert_equals(storage.getItem(key), null);
assert_equals(Object.defineProperty(storage, key, desc), storage);
assert_equals(storage[key], "value");
assert_equals(storage.getItem(key), "value");
}, "Defining data property with toString for key " + key + " on " + name);
});
});

View File

@ -0,0 +1,20 @@
<!doctype html>
<title>localStorage and document.domain</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<iframe></iframe>
<script>
async_test(function(t) {
frames[0].addEventListener("storage", function(e) {
t.step(function() {
localStorage.clear()
t.done()
})
})
frames[0].document.domain = document.domain
localStorage.setItem("test", "test")
})
</script>

View File

@ -0,0 +1,54 @@
storageEventList = [];
iframe = document.createElement("IFRAME");
document.body.appendChild(iframe);
function runAfterNStorageEvents(callback, expectedNumEvents)
{
countStorageEvents(callback, expectedNumEvents, 0)
}
function countStorageEvents(callback, expectedNumEvents, times)
{
function onTimeout()
{
var currentCount = storageEventList.length;
if (currentCount == expectedNumEvents) {
callback();
} else if (currentCount > expectedNumEvents) {
msg = "got at least " + currentCount + ", expected only " + expectedNumEvents + " events";
callback(msg);
} else if (times > 50) {
msg = "Timeout: only got " + currentCount + ", expected " + expectedNumEvents + " events";
callback(msg);
} else {
countStorageEvents(callback, expectedNumEvents, times+1);
}
}
setTimeout(onTimeout, 20);
}
function clearStorage(storageName, callback)
{
if (window[storageName].length === 0) {
storageEventList = [];
setTimeout(callback, 0);
} else {
window[storageName].clear();
runAfterNStorageEvents(function() {
storageEventList = [];
callback();
}, 1);
}
}
function testStorages(testCallback)
{
testCallback("sessionStorage");
var hit = false;
add_result_callback(function() {
if (!hit) {
hit = true;
testCallback("localStorage");
}
});
}

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta name="timeout" content="long">
<title>WebStorage Test: StorageEvent - window.onstorage</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script src="eventTestHarness.js"></script>
<script src="event_basic.js"></script>
</body>
</html>

View File

@ -0,0 +1,128 @@
testStorages(function(storageString) {
async_test(function(t) {
assert_true(storageString in window, storageString + " exist");
var storage = window[storageString];
t.add_cleanup(function() { storage.clear() });
clearStorage(storageString, t.step_func(loadiframe));
assert_equals(storage.length, 0, "storage.length");
function loadiframe(msg)
{
iframe.onload = t.step_func(step1);
iframe.src = "resources/event_basic.html";
}
function step1(msg)
{
storage.setItem('FOO', 'BAR');
runAfterNStorageEvents(t.step_func(step2), 1);
}
function step2(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 1);
assert_equals(storageEventList[0].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[0].key, "FOO");
assert_equals(storageEventList[0].oldValue, null);
assert_equals(storageEventList[0].newValue, "BAR");
storage.setItem('FU', 'BAR');
storage.setItem('a', '1');
storage.setItem('b', '2');
storage.setItem('b', '3');
runAfterNStorageEvents(t.step_func(step3), 5);
}
function step3(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 5);
assert_equals(storageEventList[1].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[1].key, "FU");
assert_equals(storageEventList[1].oldValue, null);
assert_equals(storageEventList[1].newValue, "BAR");
assert_equals(storageEventList[2].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[2].key, "a");
assert_equals(storageEventList[2].oldValue, null);
assert_equals(storageEventList[2].newValue, "1");
assert_equals(storageEventList[3].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[3].key, "b");
assert_equals(storageEventList[3].oldValue, null);
assert_equals(storageEventList[3].newValue, "2");
assert_equals(storageEventList[4].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[4].key, "b");
assert_equals(storageEventList[4].oldValue, "2");
assert_equals(storageEventList[4].newValue, "3");
storage.removeItem('FOO');
runAfterNStorageEvents(t.step_func(step4), 6);
}
function step4(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 6);
assert_equals(storageEventList[5].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[5].key, "FOO");
assert_equals(storageEventList[5].oldValue, "BAR");
assert_equals(storageEventList[5].newValue, null);
storage.removeItem('FU');
runAfterNStorageEvents(t.step_func(step5), 7);
}
function step5(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 7);
assert_equals(storageEventList[6].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[6].key, "FU");
assert_equals(storageEventList[6].oldValue, "BAR");
assert_equals(storageEventList[6].newValue, null);
storage.clear();
runAfterNStorageEvents(t.step_func(step6), 8);
}
function step6(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 8);
assert_equals(storageEventList[7].storageAreaString, storageString,
"Storage event came from wrong storage type.");
assert_equals(storageEventList[7].key, null);
assert_equals(storageEventList[7].oldValue, null);
assert_equals(storageEventList[7].newValue, null);
t.done();
}
}, storageString + " mutations fire StorageEvents that are caught by the event listener set via window.onstorage.");
});

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta name="timeout" content="long">
<title>WebStorage Test: StorageEvent - set onstorage as body attribute</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script src="eventTestHarness.js"></script>
<script src="event_body_attribute.js"></script>
</body>
</html>

View File

@ -0,0 +1,116 @@
testStorages(function(storageString) {
async_test(function(t) {
assert_true(storageString in window, storageString + " exist");
var storage = window[storageString];
t.add_cleanup(function() { storage.clear() });
clearStorage(storageString, t.step_func(step0));
assert_equals(storage.length, 0, "storage.length");
function step0(msg)
{
iframe.onload = t.step_func(step1);
// Null out the existing handler eventTestHarness.js set up;
// otherwise this test won't be testing much of anything useful.
iframe.contentWindow.onstorage = null;
iframe.src = "resources/event_body_handler.html";
}
function step1(msg)
{
storageEventList = new Array();
storage.setItem('FOO', 'BAR');
runAfterNStorageEvents(t.step_func(step2), 1);
}
function step2(msg)
{
if (msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 1);
assert_equals(storageEventList[0].key, "FOO");
assert_equals(storageEventList[0].oldValue, null);
assert_equals(storageEventList[0].newValue, "BAR");
storage.setItem('FU', 'BAR');
storage.setItem('a', '1');
storage.setItem('b', '2');
storage.setItem('b', '3');
runAfterNStorageEvents(t.step_func(step3), 5);
}
function step3(msg)
{
if (msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 5);
assert_equals(storageEventList[1].key, "FU");
assert_equals(storageEventList[1].oldValue, null);
assert_equals(storageEventList[1].newValue, "BAR");
assert_equals(storageEventList[2].key, "a");
assert_equals(storageEventList[2].oldValue, null);
assert_equals(storageEventList[2].newValue, "1");
assert_equals(storageEventList[3].key, "b");
assert_equals(storageEventList[3].oldValue, null);
assert_equals(storageEventList[3].newValue, "2");
assert_equals(storageEventList[4].key, "b");
assert_equals(storageEventList[4].oldValue, "2");
assert_equals(storageEventList[4].newValue, "3");
storage.removeItem('FOO');
runAfterNStorageEvents(t.step_func(step4), 6);
}
function step4(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 6);
assert_equals(storageEventList[5].key, "FOO");
assert_equals(storageEventList[5].oldValue, "BAR");
assert_equals(storageEventList[5].newValue, null);
storage.removeItem('FU');
runAfterNStorageEvents(t.step_func(step5), 7);
}
function step5(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 7);
assert_equals(storageEventList[6].key, "FU");
assert_equals(storageEventList[6].oldValue, "BAR");
assert_equals(storageEventList[6].newValue, null);
storage.clear();
runAfterNStorageEvents(t.step_func(step6), 8);
}
function step6(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 8);
assert_equals(storageEventList[7].key, null);
assert_equals(storageEventList[7].oldValue, null);
assert_equals(storageEventList[7].newValue, null);
t.done();
}
}, storageString + " mutations fire StorageEvents that are caught by the event listener specified as an attribute on the body.");
});

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta name="timeout" content="long">
<title>WebStorage Test: StorageEvent - the case of value changed</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script src="eventTestHarness.js"></script>
<script src="event_case_sensitive.js"></script>
</body>
</html>

View File

@ -0,0 +1,52 @@
testStorages(function(storageString) {
async_test(function(t) {
assert_true(storageString in window, storageString + " exist");
var storage = window[storageString];
t.add_cleanup(function() { storage.clear() });
clearStorage(storageString, t.step_func(loadiframe));
assert_equals(storage.length, 0, "storage.length");
function loadiframe(msg)
{
iframe.onload = t.step_func(step0);
iframe.src = "resources/event_basic.html";
}
function step0(msg)
{
storage.foo = "test";
runAfterNStorageEvents(t.step_func(step1), 1);
}
function step1(msg)
{
storageEventList = new Array();
storage.foo = "test";
runAfterNStorageEvents(t.step_func(step2), 0);
}
function step2(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 0);
storage.foo = "TEST";
runAfterNStorageEvents(t.step_func(step3), 1);
}
function step3(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 1);
t.done();
}
}, storageString + " storage events fire even when only the case of the value changes.");
});

View File

@ -0,0 +1,77 @@
test(function() {
assert_throws_js(
TypeError,
() => StorageEvent(""),
"Calling StorageEvent constructor without 'new' must throw"
);
}, "StorageEvent constructor called as normal function");
test(function() {
assert_throws_js(TypeError, () => new StorageEvent());
// should be redundant, but .length can be wrong with custom bindings
assert_equals(StorageEvent.length, 1, 'StorageEvent.length');
}, 'constructor with no arguments');
test(function() {
var event = new StorageEvent('type');
assert_equals(event.type, 'type', 'type');
assert_equals(event.key, null, 'key');
assert_equals(event.oldValue, null, 'oldValue');
assert_equals(event.newValue, null, 'newValue');
assert_equals(event.url, '', 'url');
assert_equals(event.storageArea, null, 'storageArea');
}, 'constructor with just type argument');
test(function() {
assert_not_equals(localStorage, null, 'localStorage'); // precondition
var event = new StorageEvent('storage', {
bubbles: true,
cancelable: true,
key: 'key',
oldValue: 'oldValue',
newValue: 'newValue',
url: 'url', // not an absolute URL to ensure it isn't resolved
storageArea: localStorage
});
assert_equals(event.type, 'storage', 'type');
assert_equals(event.bubbles, true, 'bubbles');
assert_equals(event.cancelable, true, 'cancelable');
assert_equals(event.key, 'key', 'key');
assert_equals(event.oldValue, 'oldValue', 'oldValue');
assert_equals(event.newValue, 'newValue', 'newValue');
assert_equals(event.url, 'url', 'url');
assert_equals(event.storageArea, localStorage, 'storageArea');
}, 'constructor with sensible type argument and members');
test(function() {
var event = new StorageEvent(null, {
key: null,
oldValue: null,
newValue: null,
url: null,
storageArea: null
});
assert_equals(event.type, 'null', 'type');
assert_equals(event.key, null, 'key');
assert_equals(event.oldValue, null, 'oldValue');
assert_equals(event.newValue, null, 'newValue');
assert_equals(event.url, 'null', 'url');
assert_equals(event.storageArea, null, 'storageArea');
}, 'constructor with null type argument and members');
test(function() {
var event = new StorageEvent(undefined, {
key: undefined,
oldValue: undefined,
newValue: undefined,
url: undefined,
storageArea: undefined
});
assert_equals(event.type, 'undefined', 'type');
assert_equals(event.key, null, 'key');
assert_equals(event.oldValue, null, 'oldValue');
assert_equals(event.newValue, null, 'newValue');
assert_equals(event.url, '', 'url'); // not 'undefined'!
assert_equals(event.storageArea, null, 'storageArea');
}, 'constructor with undefined type argument and members');

View File

@ -0,0 +1,60 @@
test(() => {
const event = new StorageEvent('storage');
assert_throws_js(TypeError, () => event.initStorageEvent());
// should be redundant, but .length can be wrong with custom bindings
assert_equals(event.initStorageEvent.length, 1, 'event.initStorageEvent.length');
}, 'initStorageEvent with 0 arguments');
test(() => {
const event = new StorageEvent('storage');
event.initStorageEvent('type');
assert_equals(event.type, 'type', 'event.type');
assert_equals(event.bubbles, false, 'event.bubbles');
assert_equals(event.cancelable, false, 'event.cancelable');
assert_equals(event.key, null, 'event.key');
assert_equals(event.oldValue, null, 'event.oldValue');
assert_equals(event.newValue, null, 'event.newValue');
assert_equals(event.url, '', 'event.url');
assert_equals(event.storageArea, null, 'event.storageArea');
}, 'initStorageEvent with 1 argument');
test(() => {
assert_not_equals(localStorage, null, 'localStorage'); // precondition
const event = new StorageEvent('storage');
event.initStorageEvent('type', true, true, 'key', 'oldValue', 'newValue', 'url', localStorage);
assert_equals(event.type, 'type', 'event.type');
assert_equals(event.bubbles, true, 'event.bubbles');
assert_equals(event.cancelable, true, 'event.cancelable');
assert_equals(event.key, 'key', 'event.key');
assert_equals(event.oldValue, 'oldValue', 'event.oldValue');
assert_equals(event.newValue, 'newValue', 'event.newValue');
assert_equals(event.url, 'url', 'event.url');
assert_equals(event.storageArea, localStorage, 'event.storageArea');
}, 'initStorageEvent with 8 sensible arguments');
test(() => {
const event = new StorageEvent('storage');
event.initStorageEvent(null, null, null, null, null, null, null, null);
assert_equals(event.type, 'null', 'event.type');
assert_equals(event.bubbles, false, 'event.bubbles');
assert_equals(event.cancelable, false, 'event.cancelable');
assert_equals(event.key, null, 'event.key');
assert_equals(event.oldValue, null, 'event.oldValue');
assert_equals(event.newValue, null, 'event.newValue');
assert_equals(event.url, 'null', 'event.url');
assert_equals(event.storageArea, null, 'event.storageArea');
}, 'initStorageEvent with 8 null arguments');
test(() => {
const event = new StorageEvent('storage');
event.initStorageEvent(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
assert_equals(event.type, 'undefined', 'event.type');
assert_equals(event.bubbles, false, 'event.bubbles');
assert_equals(event.cancelable, false, 'event.cancelable');
assert_equals(event.key, null, 'event.key');
assert_equals(event.oldValue, null, 'event.oldValue');
assert_equals(event.newValue, null, 'event.newValue');
assert_equals(event.url, '', 'event.url');
assert_equals(event.storageArea, null, 'event.storageArea');
}, 'initStorageEvent with 8 undefined arguments');

View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: localStorage event - key</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_local_key</h1>
<div id="log"></div>
<script>
async_test(function(t) {
localStorage.clear();
t.add_cleanup(function() { localStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
var expected = ['name', null]
function onStorageEvent(event) {
assert_equals(event.key, expected.shift());
if (!expected.length) {
t.done();
}
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/local_set_item_clear_iframe.html');
document.body.appendChild(el);
}, "key property test of local event - Local event is fired due to an invocation of the setItem(), clear() methods.");
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: localStorage event - newValue</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_local_newValue</h1>
<div id="log"></div>
<script>
async_test(function(t) {
localStorage.clear();
t.add_cleanup(function() { localStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
var expected = ['user1', 'user2', null]
function onStorageEvent(event) {
assert_equals(event.newValue, expected.shift());
if (!expected.length) {
t.done();
}
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/local_change_item_iframe.html');
document.body.appendChild(el);
}, "newValue property test of local event - Local event is fired due to an invocation of the setItem(), clear() methods.");
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: localStorage event - oldValue</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_local_oldValue</h1>
<div id="log"></div>
<script>
async_test(function(t) {
localStorage.clear();
t.add_cleanup(function() { localStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
var expected = [null, 'user1', null]
function onStorageEvent(event) {
assert_equals(event.oldValue, expected.shift());
if (!expected.length) {
t.done();
}
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/local_change_item_iframe.html');
document.body.appendChild(el);
}, "oldValue property test of local event - Local event is fired due to an invocation of the setItem(), clear() methods.");
</script>
</body>
</html>

View File

@ -0,0 +1,45 @@
<!DOCTYPE HTML>
<meta charset="utf-8">
<title>Web Storage Test: event - localStorage removeItem</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
async_test(function(t) {
localStorage.clear();
t.add_cleanup(function() { localStorage.clear() });
self.step = function(f) { t.step(f); };
var event_index = 0;
window.addEventListener('storage', t.step_func(function(event) {
switch(++event_index) {
case 1:
assert_equals(event.key, "name", "set key");
assert_equals(event.oldValue, null, "set oldValue");
assert_equals(event.newValue, "user1", "set newValue");
assert_equals(event.url, el.contentDocument.documentURI, "set url");
assert_equals(event.storageArea, localStorage, "set storageArea");
break;
case 2:
assert_equals(event.key, "name", "remove key");
assert_equals(event.oldValue, "user1", "remove oldValue");
assert_equals(event.newValue, null, "remove newValue");
assert_equals(event.url, el.contentDocument.documentURI, "remove url");
assert_equals(event.storageArea, localStorage, "remove storageArea");
t.done();
break;
}
}), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/local_set_item_remove_iframe.html');
document.body.appendChild(el);
}, "key property test of local event");
</script>

View File

@ -0,0 +1,39 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: localStorage event - storageArea</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_local_storageArea</h1>
<div id="log"></div>
<script>
async_test(function(t) {
localStorage.clear();
t.add_cleanup(function() { localStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
function onStorageEvent(event) {
assert_equals(event.storageArea.length, 1);
var key = event.storageArea.key(0);
var value = event.storageArea.getItem(key);
assert_equals(key, "name");
assert_equals(value, "user1");
t.done();
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/local_set_item_iframe.html');
document.body.appendChild(el);
}, "storageArea property test of local event - Local event is fired due to an invocation of the setItem() method.");
</script>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: localStorage event - url</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_local_url</h1>
<div id="log"></div>
<script>
async_test(function(t) {
localStorage.clear();
t.add_cleanup(function() { localStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
function onStorageEvent(event) {
var url = window.location.href;
var pos = url.lastIndexOf("/");
if (pos != -1) {
url = url.substr(0, pos + 1);
url = url + "resources/local_set_item_iframe.html";
}
assert_equals(event.url, url);
t.done();
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/local_set_item_iframe.html');
document.body.appendChild(el);
}, "url property test of local event - Local event is fired due to an invocation of the setItem() method.");
</script>
</body>
</html>

View File

@ -0,0 +1,111 @@
<!DOCTYPE HTML>
<html>
<title>WebStorage Test: StorageEvent - only if something changes</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
const iframe = document.createElement('iframe');
function tests(storageName) {
test(t => assert_true(storageName in window), storageName + ' exists');
const storage = window[storageName];
const iframeStorage = iframe.contentWindow[storageName];
add_completion_callback(() => {
storage.clear();
});
promise_test(t => {
const w = new EventWatcher(t, iframe.contentWindow, 'storage');
// Random key to make sure we don't conflict with any cruft leftover from
// earlier runs. Any synchronization would be really hard with localStorage
// limited guarantees.
const testKey = Math.random().toString(36).slice(2);
storage.setItem(testKey, 'foo');
storage.setItem(testKey, 'foo');
storage.setItem(testKey, 'bar');
return w.wait_for('storage')
.then(e => {
assert_equals(e.storageArea, iframeStorage);
assert_equals(e.key, testKey);
assert_equals(e.newValue, 'foo');
return w.wait_for('storage');
})
.then(e => {
assert_equals(e.storageArea, iframeStorage);
assert_equals(e.key, testKey);
assert_equals(e.oldValue, 'foo');
assert_equals(e.newValue, 'bar');
});
}, 'Setting to same value does not trigger event for ' + storageName);
promise_test(t => {
const w = new EventWatcher(t, iframe.contentWindow, 'storage');
// Random key to make sure we don't conflict with any cruft leftover from
// earlier runs. Any synchronization would be really hard with localStorage
// limited guarantees.
const testKey1 = Math.random().toString(36).slice(2);
const testKey2 = Math.random().toString(36).slice(2);
storage.removeItem(testKey1);
storage.setItem(testKey2, 'foo');
return w.wait_for('storage')
.then(e => {
assert_equals(e.storageArea, iframeStorage);
assert_equals(e.key, testKey2);
assert_equals(e.newValue, 'foo');
});
}, 'Deleting non-existent key does not trigger event for ' + storageName);
promise_test(t => {
const w = new EventWatcher(t, iframe.contentWindow, 'storage');
// Random key to make sure we don't conflict with any cruft leftover from
// earlier runs. Any synchronization would be really hard with localStorage
// limited guarantees.
const testKey = Math.random().toString(36).slice(2);
storage.setItem(testKey, 'foo');
storage.clear();
storage.clear();
storage.setItem(testKey, 'bar');
return w.wait_for('storage')
.then(e => {
assert_equals(e.storageArea, iframeStorage);
assert_equals(e.key, testKey);
assert_equals(e.newValue, 'foo');
return w.wait_for('storage');
})
.then(e => {
assert_equals(e.storageArea, iframeStorage);
assert_equals(e.key, null);
assert_equals(e.oldValue, null);
assert_equals(e.newValue, null);
return w.wait_for('storage');
})
.then(e => {
assert_equals(e.storageArea, iframeStorage);
assert_equals(e.key, testKey);
assert_equals(e.oldValue, null);
assert_equals(e.newValue, 'bar');
});
}, 'Clearing empty storage does not trigger event for ' + storageName);
}
iframe.src = "resources/event_basic.html";
iframe.onload = () => {
tests('sessionStorage');
tests('localStorage');
};
document.body.appendChild(iframe);
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage event - key</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_session_key</h1>
<div id="log"></div>
<script>
async_test(function(t) {
sessionStorage.clear();
t.add_cleanup(function() { sessionStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
var expected = ['name', null]
function onStorageEvent(event) {
assert_equals(event.key, expected.shift());
if (!expected.length) {
t.done();
}
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/session_set_item_clear_iframe.html');
document.body.appendChild(el);
}, "key property test of session event - Session event is fired due to an invocation of the setItem(), clear() methods.");
</script>
</body>
</html>

View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage event - newValue</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_session_newValue</h1>
<div id="log"></div>
<script>
async_test(function(t) {
sessionStorage.clear();
t.add_cleanup(function() { sessionStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
var expected = ['user1', 'user2', null]
function onStorageEvent(event) {
t.step(function() {
assert_equals(event.newValue, expected.shift());
});
if (!expected.length) {
t.done();
}
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/session_change_item_iframe.html');
document.body.appendChild(el);
}, "newvalue property test of session event - Session event is fired due to an invocation of the setItem(), clear() methods.");
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage event - oldValue</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_session_oldValue</h1>
<div id="log"></div>
<script>
async_test(function(t) {
sessionStorage.clear();
t.add_cleanup(function() { sessionStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
var expected = [null, 'user1', null]
function onStorageEvent(event) {
assert_equals(event.oldValue, expected.shift());
if (!expected.length) {
t.done();
}
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/session_change_item_iframe.html');
document.body.appendChild(el);
}, "oldvalue property test of session event - Session event is fired due to an invocation of the setItem(), clear() methods.");
</script>
</body>
</html>

View File

@ -0,0 +1,44 @@
<!DOCTYPE HTML>
<meta charset="utf-8">
<title>Web Storage Test: event - sessionStorage removeItem</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<script>
async_test(function(t) {
sessionStorage.clear();
t.add_cleanup(function() { sessionStorage.clear() });
self.step = function(f) { t.step(f); };
var event_index = 0;
window.addEventListener('storage', t.step_func(function(event) {
switch(++event_index) {
case 1:
assert_equals(event.key, "name", "set key");
assert_equals(event.oldValue, null, "set oldValue");
assert_equals(event.newValue, "user1", "set newValue");
assert_equals(event.url, el.contentDocument.documentURI, "set url");
assert_equals(event.storageArea, sessionStorage, "set storageArea");
break;
case 2:
assert_equals(event.key, "name", "remove key");
assert_equals(event.oldValue, "user1", "remove oldValue");
assert_equals(event.newValue, null, "remove newValue");
assert_equals(event.url, el.contentDocument.documentURI, "remove url");
assert_equals(event.storageArea, sessionStorage, "remove storageArea");
t.done();
break;
}
}), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/session_set_item_remove_iframe.html');
document.body.appendChild(el);
}, "key property test of session event");
</script>

View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage event - storageArea</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_session_storageArea</h1>
<div id="log"></div>
<script>
async_test(function(t) {
sessionStorage.clear();
t.add_cleanup(function() { sessionStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
function onStorageEvent(event) {
assert_equals(event.storageArea.length, 1);
var key = event.storageArea.key(0);
var value = event.storageArea.getItem(key);
assert_equals(key, "name");
assert_equals(value, "user1");
t.done();
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/session_set_item_iframe.html');
document.body.appendChild(el);
}, "storageArea property test of session event - session event is fired due to an invocation of the setItem() method.");
</script>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage event - url</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<h1>event_session_url</h1>
<div id="log"></div>
<script>
async_test(function(t) {
sessionStorage.clear();
t.add_cleanup(function() { sessionStorage.clear() });
self.fail = t.step_func(function(msg) {
assert_unreached(msg);
t.done();
});
function onStorageEvent(event) {
var url = window.location.href;
var pos = url.lastIndexOf("/");
if (pos != -1) {
url = url.substr(0, pos + 1);
url = url + "resources/session_set_item_iframe.html";
}
assert_equals(event.url, url);
t.done();
}
window.addEventListener('storage', t.step_func(onStorageEvent), false);
var el = document.createElement("iframe");
el.setAttribute('id', 'ifrm');
el.setAttribute('src', 'resources/session_set_item_iframe.html');
document.body.appendChild(el);
}, "url property test of session event - Session event is fired due to an invocation of the setItem() method.");
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head>
<meta name="timeout" content="long">
<title>WebStorage Test: StorageEvent - attached setAttribute</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<script src="eventTestHarness.js"></script>
<script src="event_setattribute.js"></script>
</body>
</html>

View File

@ -0,0 +1,115 @@
testStorages(function(storageString) {
async_test(function(t) {
assert_true(storageString in window, storageString + " exist");
var storage = window[storageString];
t.add_cleanup(function() { storage.clear() });
clearStorage(storageString, t.step_func(step0));
assert_equals(storage.length, 0, "storage.length");
function step0(msg)
{
iframe.onload = t.step_func(step1);
// Null out the existing handler eventTestHarness.js set up;
// otherwise this test won't be testing much of anything useful.
iframe.contentWindow.onstorage = null;
iframe.src = "resources/event_setattribute_handler.html";
}
function step1(msg)
{
storage.setItem('FOO', 'BAR');
runAfterNStorageEvents(t.step_func(step2), 1);
}
function step2(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 1);
assert_equals(storageEventList[0].key, "FOO");
assert_equals(storageEventList[0].oldValue, null);
assert_equals(storageEventList[0].newValue, "BAR");
storage.setItem('FU', 'BAR');
storage.setItem('a', '1');
storage.setItem('b', '2');
storage.setItem('b', '3');
runAfterNStorageEvents(t.step_func(step3), 5);
}
function step3(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 5);
assert_equals(storageEventList[1].key, "FU");
assert_equals(storageEventList[1].oldValue, null);
assert_equals(storageEventList[1].newValue, "BAR");
assert_equals(storageEventList[2].key, "a");
assert_equals(storageEventList[2].oldValue, null);
assert_equals(storageEventList[2].newValue, "1");
assert_equals(storageEventList[3].key, "b");
assert_equals(storageEventList[3].oldValue, null);
assert_equals(storageEventList[3].newValue, "2");
assert_equals(storageEventList[4].key, "b");
assert_equals(storageEventList[4].oldValue, "2");
assert_equals(storageEventList[4].newValue, "3");
storage.removeItem('FOO');
runAfterNStorageEvents(t.step_func(step4), 6);
}
function step4(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 6);
assert_equals(storageEventList[5].key, "FOO");
assert_equals(storageEventList[5].oldValue, "BAR");
assert_equals(storageEventList[5].newValue, null);
storage.removeItem('FU');
runAfterNStorageEvents(t.step_func(step5), 7);
}
function step5(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 7);
assert_equals(storageEventList[6].key, "FU");
assert_equals(storageEventList[6].oldValue, "BAR");
assert_equals(storageEventList[6].newValue, null);
storage.clear();
runAfterNStorageEvents(t.step_func(step6), 8);
}
function step6(msg)
{
if(msg != undefined) {
assert_unreached(msg);
}
assert_equals(storageEventList.length, 8);
assert_equals(storageEventList[7].key, null);
assert_equals(storageEventList[7].oldValue, null);
assert_equals(storageEventList[7].newValue, null);
t.done();
}
}, storageString + " mutations fire StorageEvents that are caught by the event listener attached via setattribute.");
});

View File

@ -0,0 +1,75 @@
<!doctype html>
<meta charset=utf-8>
<title>localStorage: about:blank partitioning</title>
<meta name=help href="https://privacycg.github.io/storage-partitioning/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/get-host-info.sub.js"></script>
<script src="/webstorage/resources/partitioning-utils.js"></script>
<body>
<script>
const path =
"webstorage/resources/localstorage-about-blank-partitioned-win-open.html";
const crossSiteURL = `${get_host_info().HTTP_NOTSAMESITE_ORIGIN}/${path}`;
const sameSiteURL = `${get_host_info().HTTP_ORIGIN}/${path}`;
let firstPartyID = getOrCreateID("userID3");
let crossSiteIframeID;
let sameSiteIframeID;
let crossSiteIframe;
let crossSiteIframeAboutBlankID;
let frameMessageCount = 0;
promise_test(async t => {
localStorage.clear();
// Step 1. Add a cross-site iframe
return addIframePromise(crossSiteURL).then(async crossSiteIframe => {
return new Promise(resolve => {
window.addEventListener("message", async e => {
const payload = {
command: "open about:blank window"
}
if (e.data.message === "window loaded") {
// Step 2. cross-site iframe is loaded, capture reference to its ID
crossSiteIframeID = e.data.userID;
// Step 3. Ask the cross-site iframe to create an about:blank window
crossSiteIframe.contentWindow.postMessage(payload, e.origin);
}
if (e.data.message === "about:blank frame ID") {
// Step 4. capture reference to 3P iframe's about:blank window ID
crossSiteIframeAboutBlankID = e.data.userID;
crossSiteIframe.contentWindow.postMessage(
{command: "close about:blank window"}, "*");
}
if (e.data.message === "about:blank window closed") {
resolve({crossSiteIframeID, crossSiteIframeAboutBlankID});
}
});
}).then(ids => {
const {
crossSiteIframeID,
crossSiteIframeAboutBlankID
} = ids;
// Step 5. Assert some things
for (let id in ids) {
assert_true(id !== undefined, "id is not undefined");
}
// Note: we use assert_true, rather than assert_equals becuase we're
// setting random numbers as IDs - this would mean expectations
// files wouldn't work as intended.
assert_true(crossSiteIframeAboutBlankID !== crossSiteIframeID,
"about:blank window opened by 3P iframe does not inherit 3P iframe's StorageKey");
assert_true(firstPartyID !== crossSiteIframeAboutBlankID,
"about:blank window open by 3P iframe does not inherit 1P StorageKey");
localStorage.clear();
})
});
}, "StorageKey: test 3P about:blank window opened from a 3P iframe");
</script>
</body>

View File

@ -0,0 +1,61 @@
<!doctype html>
<meta charset=utf-8>
<title>localStorage: partitioned storage test</title>
<meta name=help href="https://privacycg.github.io/storage-partitioning/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<iframe id="shared-iframe" src="http://{{host}}:{{ports[http][0]}}/webstorage/resources/localstorage-about-blank-partitioned-iframe.html"></iframe>
<body>
<script>
// Here's the set-up for this test:
// Step 1. (window) set up listeners for main window.
// Step 2. (window) set up load listener for same-site iframe.
// Step 3. (same-site iframe) loads, send it a message to createOrGet a "userID".
// Step 4. (same-site iframe) receives the message, creates the "userID".
// Step 5. (window) receives "storage got set" message from same-site iframe.
// Step 6. (window) opens cross-site window w/ shared (same-site to us currently) iframe.
// Step 7. (cross-site iframe) loads, sends back the userID key from the iframe.
// Step 8. (window) asserts that the IDs should be different, as they should have a different StorageKey.
const altOrigin = "http://{{hosts[alt][]}}:{{ports[http][0]}}";
async_test(t => {
let crossSiteWindow;
let crossSiteID;
let sameSiteID;
const iframe = document.getElementById("shared-iframe");
iframe.addEventListener("load", t.step_func(e => {
const payload = {
command: "create ID",
key: "userID",
};
iframe.contentWindow.postMessage(payload, iframe.origin);
}), {once: true});
window.addEventListener("message", t.step_func(e => {
if (e.data.message === "ID created") {
sameSiteID = e.data.userID;
assert_true(typeof sameSiteID === "string");
if (location.origin !== altOrigin) {
crossSiteWindow = window.open(`${altOrigin}/webstorage/localstorage-basic-partitioned.tentative.sub.html`, "", "noopener=false");
t.add_cleanup(() => crossSiteWindow.close());
}
}
if (e.data.message === "cross-site window iframe loaded") {
crossSiteID = e.data.userID;
t.step(() => {
assert_true(typeof crossSiteID === "string");
assert_true(sameSiteID !== crossSiteID, "IDs pulled from two partitioned iframes are different.")
});
// clean up after ourselves.
iframe.contentWindow.localStorage.clear();
crossSiteWindow.postMessage({command: "clearStorage"}, altOrigin);
t.done();
};
}));
}, "Simple test for partitioned localStorage");
</script>
</body>

View File

@ -0,0 +1,27 @@
// META: script=/common/get-host-info.sub.js
// META: script=/common/utils.js
// META: script=/common/dispatcher/dispatcher.js
// META: script=/html/cross-origin-embedder-policy/credentialless/resources/common.js
// META: script=/html/anonymous-iframe/resources/common.js
promise_test(async test => {
const same_origin= get_host_info().HTTPS_ORIGIN;
const cross_origin = get_host_info().HTTPS_REMOTE_ORIGIN;
const reply_token = token();
for(iframe of [
newIframe(same_origin),
newIframe(cross_origin),
]) {
send(iframe, `
try {
let c = window.localStorage;
send("${reply_token}","OK");
} catch (exception) {
send("${reply_token}","ERROR");
}
`);
}
assert_equals(await receive(reply_token), "OK");
assert_equals(await receive(reply_token), "OK");
}, "LocalStorage should be accessible on both same_origin and cross_origin iframes");

View File

@ -0,0 +1,17 @@
var tests = [
function() { localStorage.key(); },
function() { localStorage.getItem(); },
function() { localStorage.setItem(); },
function() { localStorage.setItem("a"); },
function() { localStorage.removeItem(); },
function() { sessionStorage.key(); },
function() { sessionStorage.getItem(); },
function() { sessionStorage.setItem(); },
function() { sessionStorage.setItem("a"); },
function() { sessionStorage.removeItem(); },
];
tests.forEach(function(fun) {
test(function() {
assert_throws_js(TypeError, fun);
}, "Should throw TypeError for " + format_value(fun) + ".");
});

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<head>
<script>
function handleStorageEvent(e) {
if (window.sessionStorage === e.storageArea)
e.storageAreaString = "sessionStorage";
else if (window.localStorage === e.storageArea)
e.storageAreaString = "localStorage";
window.parent.storageEventList.push(e);
}
</script>
</head>
<body onstorage="handleStorageEvent(event);">
</body>
</html>

View File

@ -0,0 +1,14 @@
<!DOCTYPE HTML>
<html>
<head>
<script>
function handleStorageEvent(e) {
window.parent.storageEventList.push(e);
}
</script>
</head>
<body onstorage="handleStorageEvent(event);">
</body>
</html>

View File

@ -0,0 +1,15 @@
<!DOCTYPE HTML>
<html>
<head></head>
<body>
<script>
function handleStorageEvent(e) {
window.parent.storageEventList.push(e);
}
document.body.setAttribute("onstorage", "handleStorageEvent(event);");
</script>
</body>
</html>

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
if (('localStorage' in window) && window.localStorage !== null){
try {
localStorage.setItem("name", "user1");
localStorage.setItem("name", "user2");
} catch (e) {
parent.fail("setItem method is failed.");
}
localStorage.clear();
} else {
parent.fail("localStorage is not supported.");
}
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
if (('localStorage' in window) && window.localStorage !== null){
try {
localStorage.setItem("name", "user1");
} catch (e) {
parent.fail("setItem method is failed.");
}
localStorage.clear();
} else {
parent.fail("localStorage is not supported.");
}
</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
if (('localStorage' in window) && window.localStorage !== null){
try {
localStorage.setItem("name", "user1");
} catch (e) {
parent.fail("setItem method is failed.");
}
} else {
parent.fail("localStorage is not supported.");
}
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
parent.step(function() {
localStorage.setItem("name", "user1");
localStorage.removeItem('name');
});
</script>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!doctype html>
<meta charset="utf-8">
<script>
function getOrCreateID(key) {
if (!localStorage.getItem(key)) {
const newID = +new Date() + "-" + Math.random();
localStorage.setItem(key, newID);
}
return localStorage.getItem(key);
}
window.addEventListener("load", () => {
// if we have an opener, we know that we are loaded inside a cross-site
// iframe (because we opened it ourselves).
if (parent.opener) {
const payload = {
message: "cross-site window iframe loaded",
userID: getOrCreateID("userID"),
}
parent.opener.postMessage(payload, parent.opener.origin);
}
});
window.addEventListener("message", (e) => {
if (e.data.command == "create ID") {
getOrCreateID(e.data.key);
// storage is set, call back to window.
const payload = {
message: "ID created",
userID: localStorage.getItem("userID"),
}
e.source.postMessage(payload, e.source.origin);
}
if (e.data.command == "clearStorage") {
localStorage.clear();
}
});
</script>

View File

@ -0,0 +1,37 @@
<!doctype html>
<meta charset="utf-8">
<script src="./partitioning-utils.js"></script>
<script>
window.addEventListener("load", () => {
localStorage.clear();
const userID = getOrCreateID("userID4");
const payload = {
message: "window loaded",
userID,
}
let win = window.opener ? window.opener : window.parent;
win.postMessage(payload, "*");
});
window.addEventListener("message", e => {
let win = window.opener ? parent.window.opener : window.parent;
if (e.data.command == "open about:blank window") {
window.blankWindow = window.open("about:blank");
const payload = {
message: "about:blank frame ID",
userID: window.blankWindow?.localStorage["userID4"],
}
let win = window.opener ? parent.window.opener : window.parent;
win.postMessage(payload, "*");
}
if (e.data.command == "close about:blank window") {
window.blankWindow.close();
win.postMessage({message: "about:blank window closed"}, "*");
}
});
</script>

View File

@ -0,0 +1,20 @@
function getOrCreateID(key) {
if (!localStorage.getItem(key)) {
const newID = +new Date() + "-" + Math.random();
localStorage.setItem(key, newID);
}
return localStorage.getItem(key);
}
function addIframePromise(url) {
return new Promise(resolve => {
const iframe = document.createElement("iframe");
iframe.style.display = "none";
iframe.src = url;
iframe.addEventListener("load", (e) => {
resolve(iframe);
}, {once: true});
document.body.appendChild(iframe);
});
}

View File

@ -0,0 +1,44 @@
<!doctype html>
<meta charset="utf-8">
<script>
function getOrCreateID(key) {
if (!sessionStorage.getItem(key)) {
const newID = new Date() + "-" + Math.random();
sessionStorage.setItem(key, newID);
}
return sessionStorage.getItem(key);
}
window.addEventListener("load", () => {
// In this testing set-up, only cross-site iframes will have an opener.
if (parent.opener) {
const payload = {
message: "cross-site window iframe loaded",
userID: getOrCreateID("userID"),
}
// Once the cross-site iframe has loaded, we send a message back to
// the main window with the ID from sessionStorage.
parent.opener.postMessage(payload, parent.opener.origin);
}
});
window.addEventListener("message", (e) => {
if (e.data.command == "create ID") {
// e.data.key is equivalent to "userID"
getOrCreateID(e.data.key);
const payload = {
message: "ID created",
userID: sessionStorage.getItem("userID"),
}
// Return the ID from sessionStorage to the main window.
e.source.postMessage(payload, e.source.origin);
}
// Additional functionality for clean-up at the end of the test.
if (e.data.command == "clearStorage") {
sessionStorage.clear();
}
});
</script>

View File

@ -0,0 +1,18 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
if (('sessionStorage' in window) && window.sessionStorage !== null){
try {
sessionStorage.setItem("name", "user1");
sessionStorage.setItem("name", "user2");
} catch (e) {
parent.fail("setItem method is failed.");
}
sessionStorage.clear();
} else {
parent.fail("sessionStorage is not supported.");
}
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
if (('sessionStorage' in window) && window.sessionStorage !== null){
try {
sessionStorage.setItem('name', 'user1');
} catch (e) {
parent.fail('setItem method is failed.');
}
sessionStorage.clear();
} else {
parent.fail('sessionStorage is not supported.');
}
</script>
</body>
</html>

View File

@ -0,0 +1,16 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
if (('sessionStorage' in window) && window.sessionStorage !== null){
try {
sessionStorage.setItem('name', 'user1');
} catch (e) {
parent.fail('setItem method is failed.');
}
} else {
parent.fail('sessionStorage is not supported.');
}
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
<!DOCTYPE HTML>
<html>
<body>
<script>
parent.step(function() {
sessionStorage.setItem("name", "user1");
sessionStorage.removeItem('name');
});
</script>
</body>
</html>

View File

@ -0,0 +1,36 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: localStorage - second page</title>
</head>
<body>
<script>
var storage = window.localStorage;
var assertions = [];
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR",
message: "storage.getItem('FOO')"
});
storage.setItem("FOO", "BAR-NEWWINDOW");
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR-NEWWINDOW",
message: "value for FOO after changing"
});
assertions.push({
actual: window.opener.localStorage.getItem("FOO"),
expected: "BAR-NEWWINDOW",
message: "value for FOO in my opening window"
});
window.opener.postMessage(assertions, '*');
</script>
</body>
</html>

View File

@ -0,0 +1,34 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage - second page</title>
</head>
<body>
<script>
var storage = window.sessionStorage;
var assertions = [];
assertions.push({
actual: storage.getItem("FOO"),
expected: null,
message: "storage.getItem('FOO')"
});
storage.setItem("FOO", "BAR-NEWWINDOW");
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR-NEWWINDOW",
message: "value for FOO after changing"
});
let channel = new BroadcastChannel('storage_session_window_noopener');
channel.postMessage(assertions, '*');
window.close();
</script>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<title>WebStorage Test: sessionStorage - second page</title>
</head>
<body>
<script>
var storage = window.sessionStorage;
var assertions = [];
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR",
message: "storage.getItem('FOO')"
});
storage.setItem("FOO", "BAR-NEWWINDOW");
assertions.push({
actual: storage.getItem("FOO"),
expected: "BAR-NEWWINDOW",
message: "value for FOO after changing"
});
assertions.push({
actual: window.opener.sessionStorage.getItem("FOO"),
expected: "BAR",
message: "value for FOO in my opening window"
});
assertions.push({
actual: storage.getItem("BAZ"),
expected: null,
message: "value for BAZ set after window.open(), is not set in new window"
});
window.opener.postMessage(assertions, '*');
</script>
</body>
</html>

View File

@ -0,0 +1,73 @@
<!doctype html>
<meta charset=utf-8>
<title>sessionStorage: partitioned storage test</title>
<meta name=help href="https://privacycg.github.io/storage-partitioning/">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<iframe id="shared-iframe" src="http://{{host}}:{{ports[http][0]}}/webstorage/resources/sessionStorage-about-blank-partitioned-iframe.html"></iframe>
<body>
<script>
// Here's the set-up for this test:
// Step 1. (main window) set up messaging and same-site iframe load listeners.
// Step 2. (same-site iframe) loads, requests sessionStorage for "userID".
// Step 3. (same-site iframe) receives the message, gets or allocates sessionStorage,
// and returns the generated ID to the main frame.
// Step 4. (main window) receives "storage got set" message from same-site iframe.
// Step 5. (main window) opens a new cross-site window with the shared-iframe inside.
// Step 6. (cross-site iframe) loads, requests sessionStorage for "userID", gets or
// allocates that sessionStorage, and returns the generated ID to the main frame.
// Step 7. (main window) asserts that the generated IDs should be different, as
// they should have a different StorageKey.
const altOrigin = "http://{{hosts[alt][]}}:{{ports[http][0]}}";
async_test(t => {
let crossSiteWindow;
let crossSiteID;
let sameSiteID;
// Retrieve the iframe we created in the HTML above.
const iframe = document.getElementById("shared-iframe");
// Once the iframe loads, we request sessionStorage.
iframe.addEventListener("load", t.step_func(e => {
const payload = {
command: "create ID",
key: "userID",
};
iframe.contentWindow.postMessage(payload, iframe.origin);
}), {once: true});
window.addEventListener("message", t.step_func(e => {
// Once we get or allocate the sessionStorage, we expect the iframe
// to message us back with the generated ID.
if (e.data.message === "ID created") {
sameSiteID = e.data.userID;
assert_true(typeof sameSiteID === "string");
// Now that same-site storage has been secured, we need to open a
// new cross-site window that contains our shared-iframe to repeat
// the process in a cross-site environment.
if (location.origin !== altOrigin) {
crossSiteWindow = window.open(`${altOrigin}/webstorage/sessionStorage-basic-partitioned.tentative.sub.html`, "", "noopener=false");
t.add_cleanup(() => crossSiteWindow.close());
}
}
// We expect that once the cross-site iframe requests sessionStorage,
// it will message us back with the generated ID.
if (e.data.message === "cross-site window iframe loaded") {
crossSiteID = e.data.userID;
t.step(() => {
// Same and cross-site iframes should have different generated IDs.
assert_true(typeof crossSiteID === "string");
assert_true(sameSiteID !== crossSiteID, "IDs pulled from two partitioned iframes are different.")
});
// Clear storage state to clean up after the test.
iframe.contentWindow.sessionStorage.clear();
crossSiteWindow.postMessage({command: "clearStorage"}, altOrigin);
t.done();
};
}));
}, "Simple test for partitioned sessionStorage");
</script>
</body>

View File

@ -0,0 +1,102 @@
["localStorage", "sessionStorage"].forEach(function(name) {
[9, "x"].forEach(function(key) {
test(function() {
var expected = "value for " + this.name;
var value = expected;
var storage = window[name];
storage.clear();
assert_equals(storage[key], undefined);
assert_equals(storage.getItem(key), null);
assert_equals(storage[key] = value, value);
assert_equals(storage[key], expected);
assert_equals(storage.getItem(key), expected);
}, "Setting property for key " + key + " on " + name);
test(function() {
var expected = "value for " + this.name;
var value = {
toString: function() { return expected; }
};
var storage = window[name];
storage.clear();
assert_equals(storage[key], undefined);
assert_equals(storage.getItem(key), null);
assert_equals(storage[key] = value, value);
assert_equals(storage[key], expected);
assert_equals(storage.getItem(key), expected);
}, "Setting property with toString for key " + key + " on " + name);
test(function() {
var proto = "proto for " + this.name;
Storage.prototype[key] = proto;
this.add_cleanup(function() { delete Storage.prototype[key]; });
var value = "value for " + this.name;
var storage = window[name];
storage.clear();
assert_equals(storage[key], proto);
assert_equals(storage.getItem(key), null);
assert_equals(storage[key] = value, value);
// Hidden because no [LegacyOverrideBuiltIns].
assert_equals(storage[key], proto);
assert_equals(Object.getOwnPropertyDescriptor(storage, key), undefined);
assert_equals(storage.getItem(key), value);
}, "Setting property for key " + key + " on " + name + " with data property on prototype");
test(function() {
var proto = "proto for " + this.name;
Storage.prototype[key] = proto;
this.add_cleanup(function() { delete Storage.prototype[key]; });
var value = "value for " + this.name;
var existing = "existing for " + this.name;
var storage = window[name];
storage.clear();
storage.setItem(key, existing);
// Hidden because no [LegacyOverrideBuiltIns].
assert_equals(storage[key], proto);
assert_equals(Object.getOwnPropertyDescriptor(storage, key), undefined);
assert_equals(storage.getItem(key), existing);
assert_equals(storage[key] = value, value);
assert_equals(storage[key], proto);
assert_equals(Object.getOwnPropertyDescriptor(storage, key), undefined);
assert_equals(storage.getItem(key), value);
}, "Setting property for key " + key + " on " + name + " with data property on prototype and existing item");
test(function() {
var storage = window[name];
storage.clear();
var proto = "proto getter for " + this.name;
Object.defineProperty(Storage.prototype, key, {
"get": function() { return proto; },
"set": this.unreached_func("Should not call [[Set]] on prototype"),
"configurable": true,
});
this.add_cleanup(function() {
delete Storage.prototype[key];
delete storage[key];
assert_false(key in storage);
});
var value = "value for " + this.name;
assert_equals(storage[key], proto);
assert_equals(storage.getItem(key), null);
assert_equals(storage[key] = value, value);
// Property is hidden because no [LegacyOverrideBuiltIns].
assert_equals(storage[key], proto);
assert_equals(Object.getOwnPropertyDescriptor(storage, key), undefined);
assert_equals(storage.getItem(key), value);
}, "Setting property for key " + key + " on " + name + " with accessor property on prototype");
});
});

View File

@ -0,0 +1,16 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
assert_equals(storage.length, 0, "storage.length");
var builtins = ["key", "getItem", "setItem", "removeItem", "clear"];
var origBuiltins = builtins.map(function(b) { return Storage.prototype[b]; });
assert_array_equals(builtins.map(function(b) { return storage[b]; }), origBuiltins, "a");
builtins.forEach(function(b) { storage[b] = b; });
assert_array_equals(builtins.map(function(b) { return storage[b]; }), origBuiltins, "b");
assert_array_equals(builtins.map(function(b) { return storage.getItem(b); }), builtins, "c");
assert_equals(storage.length, builtins.length, "storage.length");
}, "Builtins in " + name);
});

View File

@ -0,0 +1,16 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("name", "user1");
assert_equals(storage.getItem("name"), "user1");
assert_equals(storage.name, "user1");
assert_equals(storage.length, 1);
storage.clear();
assert_equals(storage.getItem("name"), null, "storage.getItem('name')");
assert_equals(storage.name, undefined, "storage.name");
assert_equals(storage.length, 0, "storage.length");
}, "Clear in " + name);
});

View File

@ -0,0 +1,55 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
assert_true(name in window, name + " exist");
var storage = window[name];
storage.clear();
Storage.prototype.prototypeTestKey = "prototypeTestValue";
storage.foo = "bar";
storage.fu = "baz";
storage.batman = "bin suparman";
storage.bar = "foo";
storage.alpha = "beta";
storage.zeta = "gamma";
const enumeratedArray = Object.keys(storage);
enumeratedArray.sort(); // Storage order is implementation-defined.
const expectArray = ["alpha", "bar", "batman", "foo", "fu", "zeta"];
assert_array_equals(enumeratedArray, expectArray);
// 'prototypeTestKey' is not an actual storage key, it is just a
// property set on Storage's prototype object.
assert_equals(storage.length, 6);
assert_equals(storage.getItem("prototypeTestKey"), null);
assert_equals(storage.prototypeTestKey, "prototypeTestValue");
}, name + ": enumerate a Storage object and get only the keys as a result and the built-in properties of the Storage object should be ignored");
test(function() {
const storage = window[name];
storage.clear();
storage.setItem("foo", "bar");
storage.baz = "quux";
storage.setItem(0, "alpha");
storage[42] = "beta";
for (let prop in storage) {
if (!storage.hasOwnProperty(prop))
continue;
const desc = Object.getOwnPropertyDescriptor(storage, prop);
assert_true(desc.configurable);
assert_true(desc.enumerable);
assert_true(desc.writable);
}
const keys = Object.keys(storage);
keys.sort(); // Storage order is implementation-defined.
assert_array_equals(keys, ["0", "42", "baz", "foo"]);
const values = Object.values(storage);
values.sort(); // Storage order is implementation-defined.
assert_array_equals(values, ["alpha", "bar", "beta", "quux"]);
}, name + ": test enumeration of numeric and non-numeric keys");
});

View File

@ -0,0 +1,37 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
runTest();
function doWedgeThySelf() {
storage.setItem("clear", "almost");
storage.setItem("key", "too");
storage.setItem("getItem", "funny");
storage.setItem("removeItem", "to");
storage.setItem("length", "be");
storage.setItem("setItem", "true");
}
function runTest() {
doWedgeThySelf();
assert_equals(storage.getItem('clear'), "almost");
assert_equals(storage.getItem('key'), "too");
assert_equals(storage.getItem('getItem'), "funny");
assert_equals(storage.getItem('removeItem'), "to");
assert_equals(storage.getItem('length'), "be");
assert_equals(storage.getItem('setItem'), "true");
// Test to see if an exception is thrown for any of the built in
// functions.
storage.setItem("test", "123");
storage.key(0);
storage.getItem("test");
storage.removeItem("test");
storage.clear();
assert_equals(storage.length, 0);
}
}, name + " should be not rendered unusable by setting a key with the same name as a storage function such that the function is hidden");
});

View File

@ -0,0 +1,34 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("name", "x");
storage.setItem("undefined", "foo");
storage.setItem("null", "bar");
storage.setItem("", "baz");
test(function() {
assert_equals(storage.length, 4);
}, "All items should be added to " + name + ".");
test(function() {
assert_equals(storage["unknown"], undefined, "storage['unknown']")
assert_equals(storage["name"], "x", "storage['name']")
assert_equals(storage["undefined"], "foo", "storage['undefined']")
assert_equals(storage["null"], "bar", "storage['null']")
assert_equals(storage[undefined], "foo", "storage[undefined]")
assert_equals(storage[null], "bar", "storage[null]")
assert_equals(storage[""], "baz", "storage['']")
}, "Named access to " + name + " should be correct");
test(function() {
assert_equals(storage.getItem("unknown"), null, "storage.getItem('unknown')")
assert_equals(storage.getItem("name"), "x", "storage.getItem('name')")
assert_equals(storage.getItem("undefined"), "foo", "storage.getItem('undefined')")
assert_equals(storage.getItem("null"), "bar", "storage.getItem('null')")
assert_equals(storage.getItem(undefined), "foo", "storage.getItem(undefined)")
assert_equals(storage.getItem(null), "bar", "storage.getItem(null)")
assert_equals(storage.getItem(""), "baz", "storage.getItem('')")
}, name + ".getItem should be correct")
}, "Get value by getIten(key) and named access in " + name + ".");
});

View File

@ -0,0 +1,22 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
assert_false("name" in storage);
storage["name"] = "user1";
assert_true("name" in storage);
}, "The in operator in " + name + ": property access");
test(function() {
var storage = window[name];
storage.clear();
assert_false("name" in storage);
storage.setItem("name", "user1");
assert_true("name" in storage);
assert_equals(storage.name, "user1");
storage.removeItem("name");
assert_false("name" in storage);
}, "The in operator in " + name + ": method access");
});

View File

@ -0,0 +1,28 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
storage["name"] = "user1";
storage["age"] = "42";
test(function() {
assert_equals(storage[-1], undefined);
assert_equals(storage[0], undefined);
assert_equals(storage[1], undefined);
assert_equals(storage[2], undefined);
}, "Getting number properties on " + name);
test(function() {
assert_equals(storage["-1"], undefined);
assert_equals(storage["0"], undefined);
assert_equals(storage["1"], undefined);
assert_equals(storage["2"], undefined);
}, "Getting number-valued string properties on " + name);
test(function() {
storage.setItem(1, "number");
assert_equals(storage[1], "number");
assert_equals(storage["1"], "number");
}, "Getting existing number-valued properties on " + name);
}, "Indexed getter on " + name);
});

View File

@ -0,0 +1,51 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("name", "user1");
storage.setItem("age", "20");
storage.setItem("a", "1");
storage.setItem("b", "2");
var keys = ["name", "age", "a", "b"];
function doTest(index) {
test(function() {
var key = storage.key(index);
assert_not_equals(key, null);
assert_true(keys.indexOf(key) >= 0,
"Unexpected key " + key + " found.");
}, name + ".key(" + index + ") should return the right thing.");
}
for (var i = 0; i < keys.length; ++i) {
doTest(i);
doTest(i + 0x100000000);
}
test(function() {
assert_equals(storage.key(-1), null, "storage.key(-1)");
assert_equals(storage.key(4), null, "storage.key(4)");
}, name + ".key() should return null for out-of-range arguments.");
}, name + ".key");
test(function() {
var get_keys = function(s) {
var keys = [];
for (var i = 0; i < s.length; ++i) {
keys.push(s.key(i));
}
return keys;
};
var storage = window[name];
storage.clear();
storage.setItem("name", "user1");
storage.setItem("age", "20");
storage.setItem("a", "1");
storage.setItem("b", "2");
var expected_keys = get_keys(storage);
storage.setItem("name", "user2");
assert_array_equals(get_keys(storage), expected_keys);
}, name + ".key with value changes");
});

View File

@ -0,0 +1,10 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function () {
var storage = window[name];
storage.clear();
storage.setItem("", "empty string");
assert_equals(storage.getItem(""), "empty string");
}, name + ".key with empty string");
});

View File

@ -0,0 +1,23 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
assert_equals(storage.length, 0, "storage.length")
storage["name"] = "user1";
storage["age"] = "20";
assert_equals(storage.length, 2, "storage.length")
}, name + ".length (method access)");
test(function() {
var storage = window[name];
storage.clear();
assert_equals(storage.length, 0, "storage.length")
storage.setItem("name", "user1");
storage.setItem("age", "20");
assert_equals(storage.length, 2, "storage.length")
}, name + ".length (proprty access)");
});

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>WebStorage Test: local storage</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<meta name="flags" content="interact">
<h2>Description</h2>
<p>
This test validates that store data using Local Storage which means that close the page, and re-open it, the data saved before should be loaded again.
</p>
<h2>Preconditions</h2>
<ol class="instructions">
<li>
Click the "Clear" button, refresh the page once and then check if the page shows "You have viewed this page 1 time(s)"
</li>
<li>
Close the page, re-open it and then check if the page still shows "You have viewed this page 2 time(s)"
</li>
<li>
If the above two steps are all true the test case pass, otherwise it fail
</li>
</ol>
<p>
<h2>You have viewed this page
<span id="count">an untold number of</span>
time(s).</h2>
<button type="button" onclick="javascript:localStorage.pageLoadCount = 0;"><h3>Clear</h3></button>
</p>
<script>
if (!localStorage.pageLoadCount) {
localStorage.pageLoadCount = 0;
}
localStorage.pageLoadCount = parseInt(localStorage.pageLoadCount) + 1;
document.getElementById('count').textContent = localStorage.pageLoadCount;
</script>

View File

@ -0,0 +1,16 @@
test(function() {
localStorage.clear();
var index = 0;
var key = "name";
var val = "x".repeat(1024);
assert_throws_dom("QUOTA_EXCEEDED_ERR", function() {
while (true) {
index++;
localStorage.setItem("" + key + index, "" + val + index);
}
});
localStorage.clear();
}, "Throws QuotaExceededError when the quota has been exceeded");

View File

@ -0,0 +1,16 @@
async_test(function(t) {
var storage = window.localStorage;
storage.clear();
storage.setItem("FOO", "BAR");
var win = window.open("resources/storage_local_window_open_second.html");
window.addEventListener('message', t.step_func(function(e) {
e.data.forEach(t.step_func(function(assertion) {
assert_equals(assertion.actual, assertion.expected, assertion.message);
}));
win.close();
t.done();
}));
}, "A new window to make sure there is a copy of the previous window's localStorage, and that they do not diverge after a change");

View File

@ -0,0 +1,44 @@
["localStorage", "sessionStorage"].forEach(function(name) {
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("name", "user1");
assert_equals(storage.getItem("name"), "user1");
storage.removeItem("name");
storage.removeItem("unknown");
assert_equals(storage.getItem("name"), null, "storage.getItem('name')")
}, name + ".removeItem()");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("name", "user1");
assert_equals(storage.getItem("name"), "user1");
delete storage["name"];
delete storage["unknown"];
assert_equals(storage.getItem("name"), null, "storage.getItem('name')")
}, "delete " + name + "[]");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("null", "test");
assert_true("null" in storage);
storage.removeItem(null);
assert_false("null" in storage);
}, name + ".removeItem(null)");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("undefined", "test");
assert_true("undefined" in storage);
storage.removeItem(undefined);
assert_false("undefined" in storage);
}, name + ".removeItem(undefined)");
});

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>WebStorage Test: session storage</title>
<link rel="author" title="Intel" href="http://www.intel.com">
<meta name="flags" content="interact">
<h2>Description</h2>
<p>
This test validates that store the data using Session Storage which means that even if you close the page, and re-open it, the data saved before should be lost.
</p>
<ol class="instructions">
<li>
Click the "Clear" button, refresh the page once and then check if the page shows "You have viewed this page 1 time(s)"
</li>
<li>
Close the page, re-open it and then check if the page still shows "You have viewed this page 1 time(s)"
</li>
<li>
If the above two steps are all true the test case pass, otherwise it fail.<br>
</li>
</ol>
<p>
<h2>You have viewed this page
<span id="count">an untold number of</span>
time(s).</h2>
<button type="button" onclick="javascript:sessionStorage.pageLoadCount = 0;"><h3>Clear</h3></button>
</p>
<script>
if (!sessionStorage.pageLoadCount) {
sessionStorage.pageLoadCount = 0;
}
sessionStorage.pageLoadCount = parseInt(sessionStorage.pageLoadCount) + 1;
document.getElementById('count').textContent = sessionStorage.pageLoadCount;
</script>

View File

@ -0,0 +1,16 @@
test(function() {
sessionStorage.clear();
var index = 0;
var key = "name";
var val = "x".repeat(1024);
assert_throws_dom("QUOTA_EXCEEDED_ERR", function() {
while (true) {
index++;
sessionStorage.setItem("" + key + index, "" + val + index);
}
});
sessionStorage.clear();
}, "Throws QuotaExceededError when the quota has been exceeded");

View File

@ -0,0 +1,21 @@
async_test(function(t) {
var storage = window.sessionStorage;
storage.clear();
storage.setItem("FOO", "BAR");
let channel = new BroadcastChannel("storage_session_window_noopener");
channel.addEventListener("message", t.step_func(function(e) {
e.data.forEach(t.step_func(function(assertion) {
assert_equals(assertion.actual, assertion.expected, assertion.message);
}));
assert_equals(storage.getItem("FOO"), "BAR", "value for FOO in original window");
t.done();
}));
var win = window.open("resources/storage_session_window_noopener_second.html",
"_blank",
"noopener");
}, "A new noopener window to make sure there is a not copy of the previous window's sessionStorage");

View File

@ -0,0 +1,17 @@
async_test(function(t) {
var storage = window.sessionStorage;
storage.clear();
storage.setItem("FOO", "BAR");
var win = window.open("resources/storage_session_window_open_second.html");
storage.setItem("BAZ", "QUX");
window.addEventListener('message', t.step_func(function(e) {
e.data.forEach(t.step_func(function(assertion) {
assert_equals(assertion.actual, assertion.expected, assertion.message);
}));
win.close();
t.done();
}));
}, "A new window to make sure there is a copy of the previous window's sessionStorage, and that they diverge after a change");

View File

@ -0,0 +1,26 @@
test(function() {
var popup = window.open("", "sessionStorageTestWindow");
sessionStorage.setItem("FOO", "BAR");
var reopened = window.open("", "sessionStorageTestWindow");
assert_equals(
popup,
reopened,
"window.open with the same name should re-open the same window"
);
assert_equals(
sessionStorage.getItem("FOO"),
"BAR",
"local sessionStorage is correct"
);
assert_equals(
popup.sessionStorage.getItem("FOO"),
null,
"popup sessionStorage is correct"
);
popup.close();
}, "ensure that re-opening a named window doesn't copy sessionStorage");

View File

@ -0,0 +1,21 @@
var store_list = [
["key0", "value0"],
["key1", "value1"],
["key2", "value2"]
];
["localStorage", "sessionStorage"].forEach(function(name) {
test(function () {
var storage = window[name];
storage.clear();
store_list.forEach(function(item) {
storage.setItem(item[0], item[1]);
});
for (var i = 0; i < store_list.length; i++) {
var value = storage.getItem("key" + i);
assert_equals(value, "value" + i);
}
}, "enumerate a " + name + " object with the key and get the values");
});

View File

@ -0,0 +1,215 @@
["localStorage", "sessionStorage"].forEach(function(name) {
var test_error = { name: "test" };
var interesting_strs = ["\uD7FF", "\uD800", "\uDBFF", "\uDC00",
"\uDFFF", "\uE000", "\uFFFD", "\uFFFE", "\uFFFF",
"\uD83C\uDF4D", "\uD83Ca", "a\uDF4D",
"\uDBFF\uDFFF"];
for (var i = 0; i <= 0xFF; i++) {
interesting_strs.push(String.fromCharCode(i));
}
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("name", "user1");
assert_equals(storage.length, 1, "localStorage.setItem")
}, name + ".setItem()");
test(function() {
var storage = window[name];
storage.clear();
storage["name"] = "user1";
assert_true("name" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("name"), "user1");
assert_equals(storage["name"], "user1");
}, name + "[]");
test(function() {
var storage = window[name];
storage.clear();
storage["name"] = "user1";
storage["name"] = "user2";
assert_true("name" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("name"), "user2");
assert_equals(storage["name"], "user2");
}, name + "[] update");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("age", null);
assert_true("age" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("age"), "null");
assert_equals(storage["age"], "null");
}, name + ".setItem(_, null)");
test(function() {
var storage = window[name];
storage.clear();
storage["age"] = null;
assert_true("age" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("age"), "null");
assert_equals(storage["age"], "null");
}, name + "[] = null");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("age", undefined);
assert_true("age" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("age"), "undefined");
assert_equals(storage["age"], "undefined");
}, name + ".setItem(_, undefined)");
test(function() {
var storage = window[name];
storage.clear();
storage["age"] = undefined;
assert_true("age" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("age"), "undefined");
assert_equals(storage["age"], "undefined");
}, name + "[] = undefined");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("age", "10");
assert_throws_exactly(test_error, function() {
storage.setItem("age",
{ toString: function() { throw test_error; } });
});
assert_true("age" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("age"), "10");
assert_equals(storage["age"], "10");
}, name + ".setItem({ throws })");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem("age", "10");
assert_throws_exactly(test_error, function() {
storage["age"] =
{ toString: function() { throw test_error; } };
});
assert_true("age" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("age"), "10");
assert_equals(storage["age"], "10");
}, name + "[] = { throws }");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem(undefined, "test");
assert_true("undefined" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("undefined"), "test");
assert_equals(storage["undefined"], "test");
}, name + ".setItem(undefined, _)");
test(function() {
var storage = window[name];
storage.clear();
storage[undefined] = "test2";
assert_true("undefined" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("undefined"), "test2");
assert_equals(storage["undefined"], "test2");
}, name + "[undefined]");
test(function() {
var storage = window[name];
storage.clear();
storage.setItem(null, "test");
assert_true("null" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("null"), "test");
assert_equals(storage["null"], "test");
}, name + ".setItem(null, _)");
test(function() {
var storage = window[name];
storage.clear();
storage[null] = "test2";
assert_true("null" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("null"), "test2");
assert_equals(storage["null"], "test2");
}, name + "[null]");
test(function() {
var storage = window[name];
storage.clear();
storage["foo\0bar"] = "user1";
assert_true("foo\0bar" in storage);
assert_false("foo\0" in storage);
assert_false("foo\0baz" in storage);
assert_false("foo" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("foo\0bar"), "user1");
assert_equals(storage.getItem("foo\0"), null);
assert_equals(storage.getItem("foo\0baz"), null);
assert_equals(storage.getItem("foo"), null);
assert_equals(storage["foo\0bar"], "user1");
assert_equals(storage["foo\0"], undefined);
assert_equals(storage["foo\0baz"], undefined);
assert_equals(storage["foo"], undefined);
}, name + " key containing null");
test(function() {
var storage = window[name];
storage.clear();
storage["name"] = "foo\0bar";
assert_true("name" in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("name"), "foo\0bar");
assert_equals(storage["name"], "foo\0bar");
}, name + " value containing null");
for (i = 0; i < interesting_strs.length; i++) {
var str = interesting_strs[i];
test(function() {
var storage = window[name];
storage.clear();
storage[str] = "user1";
assert_true(str in storage);
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem(str), "user1");
assert_equals(storage[str], "user1");
}, name + "[" + format_value(str) + "]");
test(function() {
var storage = window[name];
storage.clear();
storage["name"] = str;
assert_equals(storage.length, 1, "storage.length")
assert_equals(storage.getItem("name"), str);
assert_equals(storage["name"], str);
}, name + "[] = " + format_value(str));
}
});

Some files were not shown because too many files have changed in this diff Show More