mirror of
https://github.com/zebrajr/node.git
synced 2025-12-06 00:20:08 +01:00
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:
parent
340cb6887d
commit
3d09e579d3
9
.github/workflows/tools.yml
vendored
9
.github/workflows/tools.yml
vendored
|
|
@ -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
14
deps/sqlite/BUILD.gn
vendored
Normal 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
24
deps/sqlite/sqlite.gyp
vendored
Normal 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
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
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
22
deps/sqlite/unofficial.gni
vendored
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
.
|
.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
55
lib/internal/webstorage.js
Normal file
55
lib/internal/webstorage.js
Normal 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;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
16
node.gyp
16
node.gyp
|
|
@ -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' ],
|
||||||
|
|
|
||||||
|
|
@ -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") \
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) \
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
706
src/node_webstorage.cc
Normal 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
58
src/node_webstorage.h
Normal 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_
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
test/fixtures/wpt/README.md
vendored
1
test/fixtures/wpt/README.md
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
55
test/fixtures/wpt/html/anonymous-iframe/resources/common.js
vendored
Normal file
55
test/fixtures/wpt/html/anonymous-iframe/resources/common.js
vendored
Normal 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;
|
||||||
|
}
|
||||||
134
test/fixtures/wpt/html/cross-origin-embedder-policy/credentialless/resources/common.js
vendored
Normal file
134
test/fixtures/wpt/html/cross-origin-embedder-policy/credentialless/resources/common.js
vendored
Normal 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];
|
||||||
|
},
|
||||||
|
};
|
||||||
4
test/fixtures/wpt/versions.json
vendored
4
test/fixtures/wpt/versions.json
vendored
|
|
@ -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
5
test/fixtures/wpt/webstorage/META.yml
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
spec: https://html.spec.whatwg.org/multipage/webstorage.html
|
||||||
|
suggested_reviewers:
|
||||||
|
- siusin
|
||||||
|
- inexorabletash
|
||||||
|
- jdm
|
||||||
4
test/fixtures/wpt/webstorage/README.md
vendored
Normal file
4
test/fixtures/wpt/webstorage/README.md
vendored
Normal 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`.
|
||||||
57
test/fixtures/wpt/webstorage/defineProperty.window.js
vendored
Normal file
57
test/fixtures/wpt/webstorage/defineProperty.window.js
vendored
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
20
test/fixtures/wpt/webstorage/document-domain.html
vendored
Normal file
20
test/fixtures/wpt/webstorage/document-domain.html
vendored
Normal 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>
|
||||||
54
test/fixtures/wpt/webstorage/eventTestHarness.js
vendored
Normal file
54
test/fixtures/wpt/webstorage/eventTestHarness.js
vendored
Normal 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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
15
test/fixtures/wpt/webstorage/event_basic.html
vendored
Normal file
15
test/fixtures/wpt/webstorage/event_basic.html
vendored
Normal 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>
|
||||||
|
|
||||||
128
test/fixtures/wpt/webstorage/event_basic.js
vendored
Normal file
128
test/fixtures/wpt/webstorage/event_basic.js
vendored
Normal 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.");
|
||||||
|
});
|
||||||
15
test/fixtures/wpt/webstorage/event_body_attribute.html
vendored
Normal file
15
test/fixtures/wpt/webstorage/event_body_attribute.html
vendored
Normal 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>
|
||||||
|
|
||||||
116
test/fixtures/wpt/webstorage/event_body_attribute.js
vendored
Normal file
116
test/fixtures/wpt/webstorage/event_body_attribute.js
vendored
Normal 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.");
|
||||||
|
});
|
||||||
15
test/fixtures/wpt/webstorage/event_case_sensitive.html
vendored
Normal file
15
test/fixtures/wpt/webstorage/event_case_sensitive.html
vendored
Normal 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>
|
||||||
|
|
||||||
52
test/fixtures/wpt/webstorage/event_case_sensitive.js
vendored
Normal file
52
test/fixtures/wpt/webstorage/event_case_sensitive.js
vendored
Normal 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.");
|
||||||
|
});
|
||||||
77
test/fixtures/wpt/webstorage/event_constructor.window.js
vendored
Normal file
77
test/fixtures/wpt/webstorage/event_constructor.window.js
vendored
Normal 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');
|
||||||
60
test/fixtures/wpt/webstorage/event_initstorageevent.window.js
vendored
Normal file
60
test/fixtures/wpt/webstorage/event_initstorageevent.window.js
vendored
Normal 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');
|
||||||
38
test/fixtures/wpt/webstorage/event_local_key.html
vendored
Normal file
38
test/fixtures/wpt/webstorage/event_local_key.html
vendored
Normal 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>
|
||||||
38
test/fixtures/wpt/webstorage/event_local_newvalue.html
vendored
Normal file
38
test/fixtures/wpt/webstorage/event_local_newvalue.html
vendored
Normal 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>
|
||||||
38
test/fixtures/wpt/webstorage/event_local_oldvalue.html
vendored
Normal file
38
test/fixtures/wpt/webstorage/event_local_oldvalue.html
vendored
Normal 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>
|
||||||
45
test/fixtures/wpt/webstorage/event_local_removeitem.html
vendored
Normal file
45
test/fixtures/wpt/webstorage/event_local_removeitem.html
vendored
Normal 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>
|
||||||
39
test/fixtures/wpt/webstorage/event_local_storagearea.html
vendored
Normal file
39
test/fixtures/wpt/webstorage/event_local_storagearea.html
vendored
Normal 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>
|
||||||
43
test/fixtures/wpt/webstorage/event_local_url.html
vendored
Normal file
43
test/fixtures/wpt/webstorage/event_local_url.html
vendored
Normal 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>
|
||||||
111
test/fixtures/wpt/webstorage/event_no_duplicates.html
vendored
Normal file
111
test/fixtures/wpt/webstorage/event_no_duplicates.html
vendored
Normal 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>
|
||||||
|
|
||||||
38
test/fixtures/wpt/webstorage/event_session_key.html
vendored
Normal file
38
test/fixtures/wpt/webstorage/event_session_key.html
vendored
Normal 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>
|
||||||
40
test/fixtures/wpt/webstorage/event_session_newvalue.html
vendored
Normal file
40
test/fixtures/wpt/webstorage/event_session_newvalue.html
vendored
Normal 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>
|
||||||
38
test/fixtures/wpt/webstorage/event_session_oldvalue.html
vendored
Normal file
38
test/fixtures/wpt/webstorage/event_session_oldvalue.html
vendored
Normal 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>
|
||||||
44
test/fixtures/wpt/webstorage/event_session_removeitem.html
vendored
Normal file
44
test/fixtures/wpt/webstorage/event_session_removeitem.html
vendored
Normal 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>
|
||||||
40
test/fixtures/wpt/webstorage/event_session_storagearea.html
vendored
Normal file
40
test/fixtures/wpt/webstorage/event_session_storagearea.html
vendored
Normal 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>
|
||||||
43
test/fixtures/wpt/webstorage/event_session_url.html
vendored
Normal file
43
test/fixtures/wpt/webstorage/event_session_url.html
vendored
Normal 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>
|
||||||
15
test/fixtures/wpt/webstorage/event_setattribute.html
vendored
Normal file
15
test/fixtures/wpt/webstorage/event_setattribute.html
vendored
Normal 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>
|
||||||
|
|
||||||
115
test/fixtures/wpt/webstorage/event_setattribute.js
vendored
Normal file
115
test/fixtures/wpt/webstorage/event_setattribute.js
vendored
Normal 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.");
|
||||||
|
});
|
||||||
|
|
@ -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>
|
||||||
61
test/fixtures/wpt/webstorage/localstorage-basic-partitioned.tentative.sub.html
vendored
Normal file
61
test/fixtures/wpt/webstorage/localstorage-basic-partitioned.tentative.sub.html
vendored
Normal 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>
|
||||||
27
test/fixtures/wpt/webstorage/localstorage-cross-origin-iframe.tentative.https.window.js
vendored
Normal file
27
test/fixtures/wpt/webstorage/localstorage-cross-origin-iframe.tentative.https.window.js
vendored
Normal 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");
|
||||||
17
test/fixtures/wpt/webstorage/missing_arguments.window.js
vendored
Normal file
17
test/fixtures/wpt/webstorage/missing_arguments.window.js
vendored
Normal 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) + ".");
|
||||||
|
});
|
||||||
16
test/fixtures/wpt/webstorage/resources/event_basic.html
vendored
Normal file
16
test/fixtures/wpt/webstorage/resources/event_basic.html
vendored
Normal 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>
|
||||||
14
test/fixtures/wpt/webstorage/resources/event_body_handler.html
vendored
Normal file
14
test/fixtures/wpt/webstorage/resources/event_body_handler.html
vendored
Normal 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>
|
||||||
15
test/fixtures/wpt/webstorage/resources/event_setattribute_handler.html
vendored
Normal file
15
test/fixtures/wpt/webstorage/resources/event_setattribute_handler.html
vendored
Normal 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>
|
||||||
18
test/fixtures/wpt/webstorage/resources/local_change_item_iframe.html
vendored
Normal file
18
test/fixtures/wpt/webstorage/resources/local_change_item_iframe.html
vendored
Normal 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>
|
||||||
17
test/fixtures/wpt/webstorage/resources/local_set_item_clear_iframe.html
vendored
Normal file
17
test/fixtures/wpt/webstorage/resources/local_set_item_clear_iframe.html
vendored
Normal 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>
|
||||||
16
test/fixtures/wpt/webstorage/resources/local_set_item_iframe.html
vendored
Normal file
16
test/fixtures/wpt/webstorage/resources/local_set_item_iframe.html
vendored
Normal 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>
|
||||||
11
test/fixtures/wpt/webstorage/resources/local_set_item_remove_iframe.html
vendored
Normal file
11
test/fixtures/wpt/webstorage/resources/local_set_item_remove_iframe.html
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
parent.step(function() {
|
||||||
|
localStorage.setItem("name", "user1");
|
||||||
|
localStorage.removeItem('name');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
41
test/fixtures/wpt/webstorage/resources/localstorage-about-blank-partitioned-iframe.html
vendored
Normal file
41
test/fixtures/wpt/webstorage/resources/localstorage-about-blank-partitioned-iframe.html
vendored
Normal 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>
|
||||||
37
test/fixtures/wpt/webstorage/resources/localstorage-about-blank-partitioned-win-open.html
vendored
Normal file
37
test/fixtures/wpt/webstorage/resources/localstorage-about-blank-partitioned-win-open.html
vendored
Normal 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>
|
||||||
20
test/fixtures/wpt/webstorage/resources/partitioning-utils.js
vendored
Normal file
20
test/fixtures/wpt/webstorage/resources/partitioning-utils.js
vendored
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
44
test/fixtures/wpt/webstorage/resources/sessionStorage-about-blank-partitioned-iframe.html
vendored
Normal file
44
test/fixtures/wpt/webstorage/resources/sessionStorage-about-blank-partitioned-iframe.html
vendored
Normal 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>
|
||||||
18
test/fixtures/wpt/webstorage/resources/session_change_item_iframe.html
vendored
Normal file
18
test/fixtures/wpt/webstorage/resources/session_change_item_iframe.html
vendored
Normal 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>
|
||||||
17
test/fixtures/wpt/webstorage/resources/session_set_item_clear_iframe.html
vendored
Normal file
17
test/fixtures/wpt/webstorage/resources/session_set_item_clear_iframe.html
vendored
Normal 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>
|
||||||
16
test/fixtures/wpt/webstorage/resources/session_set_item_iframe.html
vendored
Normal file
16
test/fixtures/wpt/webstorage/resources/session_set_item_iframe.html
vendored
Normal 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>
|
||||||
11
test/fixtures/wpt/webstorage/resources/session_set_item_remove_iframe.html
vendored
Normal file
11
test/fixtures/wpt/webstorage/resources/session_set_item_remove_iframe.html
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
parent.step(function() {
|
||||||
|
sessionStorage.setItem("name", "user1");
|
||||||
|
sessionStorage.removeItem('name');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
36
test/fixtures/wpt/webstorage/resources/storage_local_window_open_second.html
vendored
Normal file
36
test/fixtures/wpt/webstorage/resources/storage_local_window_open_second.html
vendored
Normal 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>
|
||||||
34
test/fixtures/wpt/webstorage/resources/storage_session_window_noopener_second.html
vendored
Normal file
34
test/fixtures/wpt/webstorage/resources/storage_session_window_noopener_second.html
vendored
Normal 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>
|
||||||
41
test/fixtures/wpt/webstorage/resources/storage_session_window_open_second.html
vendored
Normal file
41
test/fixtures/wpt/webstorage/resources/storage_session_window_open_second.html
vendored
Normal 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>
|
||||||
73
test/fixtures/wpt/webstorage/sessionStorage-basic-partitioned.tentative.sub.html
vendored
Normal file
73
test/fixtures/wpt/webstorage/sessionStorage-basic-partitioned.tentative.sub.html
vendored
Normal 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>
|
||||||
102
test/fixtures/wpt/webstorage/set.window.js
vendored
Normal file
102
test/fixtures/wpt/webstorage/set.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
16
test/fixtures/wpt/webstorage/storage_builtins.window.js
vendored
Normal file
16
test/fixtures/wpt/webstorage/storage_builtins.window.js
vendored
Normal 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);
|
||||||
|
});
|
||||||
16
test/fixtures/wpt/webstorage/storage_clear.window.js
vendored
Normal file
16
test/fixtures/wpt/webstorage/storage_clear.window.js
vendored
Normal 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);
|
||||||
|
});
|
||||||
55
test/fixtures/wpt/webstorage/storage_enumerate.window.js
vendored
Normal file
55
test/fixtures/wpt/webstorage/storage_enumerate.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
37
test/fixtures/wpt/webstorage/storage_functions_not_overwritten.window.js
vendored
Normal file
37
test/fixtures/wpt/webstorage/storage_functions_not_overwritten.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
34
test/fixtures/wpt/webstorage/storage_getitem.window.js
vendored
Normal file
34
test/fixtures/wpt/webstorage/storage_getitem.window.js
vendored
Normal 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 + ".");
|
||||||
|
});
|
||||||
22
test/fixtures/wpt/webstorage/storage_in.window.js
vendored
Normal file
22
test/fixtures/wpt/webstorage/storage_in.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
28
test/fixtures/wpt/webstorage/storage_indexing.window.js
vendored
Normal file
28
test/fixtures/wpt/webstorage/storage_indexing.window.js
vendored
Normal 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);
|
||||||
|
});
|
||||||
51
test/fixtures/wpt/webstorage/storage_key.window.js
vendored
Normal file
51
test/fixtures/wpt/webstorage/storage_key.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
10
test/fixtures/wpt/webstorage/storage_key_empty_string.window.js
vendored
Normal file
10
test/fixtures/wpt/webstorage/storage_key_empty_string.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
23
test/fixtures/wpt/webstorage/storage_length.window.js
vendored
Normal file
23
test/fixtures/wpt/webstorage/storage_length.window.js
vendored
Normal 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)");
|
||||||
|
});
|
||||||
40
test/fixtures/wpt/webstorage/storage_local-manual.html
vendored
Normal file
40
test/fixtures/wpt/webstorage/storage_local-manual.html
vendored
Normal 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>
|
||||||
16
test/fixtures/wpt/webstorage/storage_local_setitem_quotaexceedederr.window.js
vendored
Normal file
16
test/fixtures/wpt/webstorage/storage_local_setitem_quotaexceedederr.window.js
vendored
Normal 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");
|
||||||
16
test/fixtures/wpt/webstorage/storage_local_window_open.window.js
vendored
Normal file
16
test/fixtures/wpt/webstorage/storage_local_window_open.window.js
vendored
Normal 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");
|
||||||
44
test/fixtures/wpt/webstorage/storage_removeitem.window.js
vendored
Normal file
44
test/fixtures/wpt/webstorage/storage_removeitem.window.js
vendored
Normal 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)");
|
||||||
|
});
|
||||||
39
test/fixtures/wpt/webstorage/storage_session-manual.html
vendored
Normal file
39
test/fixtures/wpt/webstorage/storage_session-manual.html
vendored
Normal 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>
|
||||||
16
test/fixtures/wpt/webstorage/storage_session_setitem_quotaexceedederr.window.js
vendored
Normal file
16
test/fixtures/wpt/webstorage/storage_session_setitem_quotaexceedederr.window.js
vendored
Normal 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");
|
||||||
21
test/fixtures/wpt/webstorage/storage_session_window_noopener.window.js
vendored
Normal file
21
test/fixtures/wpt/webstorage/storage_session_window_noopener.window.js
vendored
Normal 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");
|
||||||
17
test/fixtures/wpt/webstorage/storage_session_window_open.window.js
vendored
Normal file
17
test/fixtures/wpt/webstorage/storage_session_window_open.window.js
vendored
Normal 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");
|
||||||
26
test/fixtures/wpt/webstorage/storage_session_window_reopen.window.js
vendored
Normal file
26
test/fixtures/wpt/webstorage/storage_session_window_reopen.window.js
vendored
Normal 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");
|
||||||
21
test/fixtures/wpt/webstorage/storage_set_value_enumerate.window.js
vendored
Normal file
21
test/fixtures/wpt/webstorage/storage_set_value_enumerate.window.js
vendored
Normal 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");
|
||||||
|
});
|
||||||
|
|
||||||
215
test/fixtures/wpt/webstorage/storage_setitem.window.js
vendored
Normal file
215
test/fixtures/wpt/webstorage/storage_setitem.window.js
vendored
Normal 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
Loading…
Reference in New Issue
Block a user