diff options
-rw-r--r-- | Cargo.lock | 147 | ||||
-rw-r--r-- | cli/build.rs | 6 | ||||
-rw-r--r-- | cli/dts/lib.deno.window.d.ts | 5 | ||||
-rw-r--r-- | cli/main.rs | 11 | ||||
-rw-r--r-- | cli/standalone.rs | 1 | ||||
-rw-r--r-- | cli/tsc.rs | 2 | ||||
-rw-r--r-- | extensions/webstorage/01_webstorage.js | 190 | ||||
-rw-r--r-- | extensions/webstorage/Cargo.toml | 19 | ||||
-rw-r--r-- | extensions/webstorage/README.md | 5 | ||||
-rw-r--r-- | extensions/webstorage/lib.deno_webstorage.d.ts | 42 | ||||
-rw-r--r-- | extensions/webstorage/lib.rs | 316 | ||||
-rw-r--r-- | runtime/Cargo.toml | 2 | ||||
-rw-r--r-- | runtime/build.rs | 1 | ||||
-rw-r--r-- | runtime/errors.rs | 2 | ||||
-rw-r--r-- | runtime/examples/hello_runtime.rs | 1 | ||||
-rw-r--r-- | runtime/js/99_main.js | 24 | ||||
-rw-r--r-- | runtime/lib.rs | 1 | ||||
-rw-r--r-- | runtime/worker.rs | 3 | ||||
-rwxr-xr-x | tools/wpt.ts | 26 | ||||
-rw-r--r-- | tools/wpt/expectation.json | 60 |
20 files changed, 819 insertions, 45 deletions
diff --git a/Cargo.lock b/Cargo.lock index afedc80aa..5627224ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,9 +37,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] @@ -114,9 +114,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72c1f1154e234325b50864a349b9c8e56939e266a4c307c0f159812df2f9537" +checksum = "5443ccbb270374a2b1055fc72da40e1f237809cd6bb0e97e66d264cd138473a6" dependencies = [ "brotli", "flate2", @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +checksum = "4feb231f0d4d6af81aed15928e58ecf5816aa62a2393e2c82f46973e92a9a278" dependencies = [ "autocfg", "cfg-if 1.0.0", @@ -697,6 +697,7 @@ dependencies = [ "deno_webgpu", "deno_webidl", "deno_websocket", + "deno_webstorage", "dlopen", "encoding_rs", "filetime", @@ -788,6 +789,15 @@ dependencies = [ ] [[package]] +name = "deno_webstorage" +version = "0.1.0" +dependencies = [ + "deno_core", + "rusqlite", + "serde", +] + +[[package]] name = "derive_more" version = "0.99.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1014,6 +1024,18 @@ dependencies = [ ] [[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] name = "fancy-regex" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1483,9 +1505,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc018e188373e2777d0ef2467ebff62a08e66c3f5857b23c8fbec3018210dc00" +checksum = "825343c4eef0b63f541f8903f395dc5beb362a979b5799a84062527ef1e37726" dependencies = [ "bytes", "fnv", @@ -1510,6 +1532,15 @@ dependencies = [ ] [[package]] +name = "hashlink" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d99cf782f0dc4372d26846bec3de7804ceb5df083c2d4462c0b8d2330e894fa8" +dependencies = [ + "hashbrown", +] + +[[package]] name = "heck" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1787,9 +1818,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" [[package]] name = "libloading" @@ -1802,6 +1833,17 @@ dependencies = [ ] [[package]] +name = "libsqlite3-sys" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19cb1effde5f834799ac5e5ef0e40d45027cd74f271b1de786ba8abb30e2164d" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "linked-hash-map" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1809,9 +1851,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" +checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" dependencies = [ "scopeguard", ] @@ -1908,9 +1950,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" [[package]] name = "metal" @@ -2254,6 +2296,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] +name = "pkg-config" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" + +[[package]] name = "pmutil" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2327,7 +2375,7 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" dependencies = [ - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -2505,9 +2553,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041" +checksum = "85dd92e586f7355c633911e11f77f3d12f04b1b1bd76a198bd34ae3af8341ef2" dependencies = [ "bitflags", ] @@ -2526,9 +2574,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.23" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "relative-path" @@ -2625,6 +2673,21 @@ dependencies = [ ] [[package]] +name = "rusqlite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc783b7ddae608338003bac1fa00b6786a75a9675fbd8e87243ecfdea3f6ed2" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "memchr", + "smallvec", +] + +[[package]] name = "rustc_version" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -3010,9 +3073,9 @@ dependencies = [ [[package]] name = "swc_bundler" -version = "0.32.5" +version = "0.32.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3fd9db5796f48c7db74c045838c77981aed155c47e8ecf7dca2f7c47408e70" +checksum = "2c724be1aea9b099b11b2d57ab59e25ed8428c03d9daa8fdded8fa81d17b6d56" dependencies = [ "ahash 0.7.2", "anyhow", @@ -3076,9 +3139,9 @@ dependencies = [ [[package]] name = "swc_ecma_codegen" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bffd587bf624fc3ad732433704cc4ff415967ed9feed26accf2db9130237e09" +checksum = "4f62df17607eee6c488fdd24d192911db862b2af68a6ea72e166bb699f8a417a" dependencies = [ "bitflags", "num-bigint", @@ -3132,7 +3195,7 @@ dependencies = [ "swc_common", "swc_ecma_ast", "swc_ecma_visit", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -3152,7 +3215,7 @@ dependencies = [ "swc_ecma_transforms_typescript", "swc_ecma_utils", "swc_ecma_visit", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -3176,9 +3239,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_optimization" -version = "0.15.5" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f187623203e84095337ea16b499ef6768819c3f2f7e3cdae24022135282dd3d" +checksum = "70ae2bd352c1365d1c8f02c5016f729de6e1fb2fe03afd5c37a896468d1f20a4" dependencies = [ "dashmap", "fxhash", @@ -3217,9 +3280,9 @@ dependencies = [ [[package]] name = "swc_ecma_transforms_react" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3cfd8e900a1f1e26ea705f4614717b3deceec40d4b2fd4b1dc65a2b90de066" +checksum = "b13d80190e922c2f83edec85c40372cbc0dd14d15973447471c0885ca408ce78" dependencies = [ "base64 0.13.0", "dashmap", @@ -3267,7 +3330,7 @@ dependencies = [ "swc_common", "swc_ecma_ast", "swc_ecma_visit", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -3285,9 +3348,9 @@ dependencies = [ [[package]] name = "swc_ecmascript" -version = "0.31.3" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45286c7e1199a0c9ef13951535f5c6f777228d26df2fcf2d2d81ef11d0f22e40" +checksum = "9148743bf5d6dcc482fd4db219968b216bff61a475c7838e0f60e00886cfa2b4" dependencies = [ "swc_ecma_ast", "swc_ecma_codegen", @@ -3365,7 +3428,7 @@ checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" dependencies = [ "proc-macro2 1.0.26", "quote 1.0.9", - "unicode-xid 0.2.1", + "unicode-xid 0.2.2", ] [[package]] @@ -3640,9 +3703,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f" +checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -3651,9 +3714,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f" +checksum = "a9ff14f98b1a4b289c6248a023c1c2fa1491062964e9fed67ab29c4e4da4a052" dependencies = [ "lazy_static", ] @@ -3852,9 +3915,9 @@ checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "untrusted" @@ -3898,6 +3961,12 @@ dependencies = [ ] [[package]] +name = "vcpkg" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d" + +[[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/cli/build.rs b/cli/build.rs index eb8a71c8c..116ce8167 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -16,6 +16,7 @@ use deno_runtime::deno_url; use deno_runtime::deno_web; use deno_runtime::deno_webgpu; use deno_runtime::deno_websocket; +use deno_runtime::deno_webstorage; use regex::Regex; use std::collections::HashMap; use std::env; @@ -71,6 +72,7 @@ fn create_compiler_snapshot( op_crate_libs.insert("deno.fetch", deno_fetch::get_declaration()); op_crate_libs.insert("deno.webgpu", deno_webgpu::get_declaration()); op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration()); + op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration()); op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration()); // ensure we invalidate the build properly. @@ -291,6 +293,10 @@ fn main() { deno_websocket::get_declaration().display() ); println!( + "cargo:rustc-env=DENO_WEBSTORAGE_LIB_PATH={}", + deno_webstorage::get_declaration().display() + ); + println!( "cargo:rustc-env=DENO_CRYPTO_LIB_PATH={}", deno_crypto::get_declaration().display() ); diff --git a/cli/dts/lib.deno.window.d.ts b/cli/dts/lib.deno.window.d.ts index b7eca1e24..3c985e641 100644 --- a/cli/dts/lib.deno.window.d.ts +++ b/cli/dts/lib.deno.window.d.ts @@ -4,6 +4,7 @@ /// <reference lib="deno.ns" /> /// <reference lib="deno.shared_globals" /> /// <reference lib="deno.webgpu" /> +/// <reference lib="deno.webstorage" /> /// <reference lib="esnext" /> declare class Window extends EventTarget { @@ -22,12 +23,16 @@ declare class Window extends EventTarget { navigator: Navigator; Location: typeof Location; location: Location; + localStorage: Storage; + sessionStorage: Storage; } declare var window: Window & typeof globalThis; declare var self: Window & typeof globalThis; declare var onload: ((this: Window, ev: Event) => any) | null; declare var onunload: ((this: Window, ev: Event) => any) | null; +declare var localStorage: Storage; +declare var sessionStorage: Storage; declare class Navigator { constructor(); diff --git a/cli/main.rs b/cli/main.rs index d20462c96..43c1cd0b2 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -204,6 +204,14 @@ pub fn create_main_worker( no_color: !colors::use_color(), get_error_class_fn: Some(&crate::errors::get_error_class_name), location: program_state.flags.location.clone(), + location_data_dir: program_state.flags.location.clone().map(|loc| { + program_state + .dir + .root + .clone() + .join("location_data") + .join(checksum::gen(&[loc.to_string().as_bytes()])) + }), blob_url_store: program_state.blob_url_store.clone(), }; @@ -295,7 +303,7 @@ fn print_cache_info( pub fn get_types(unstable: bool) -> String { let mut types = format!( - "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}", + "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}", crate::tsc::DENO_NS_LIB, crate::tsc::DENO_CONSOLE_LIB, crate::tsc::DENO_URL_LIB, @@ -304,6 +312,7 @@ pub fn get_types(unstable: bool) -> String { crate::tsc::DENO_FETCH_LIB, crate::tsc::DENO_WEBGPU_LIB, crate::tsc::DENO_WEBSOCKET_LIB, + crate::tsc::DENO_WEBSTORAGE_LIB, crate::tsc::DENO_CRYPTO_LIB, crate::tsc::SHARED_GLOBALS_LIB, crate::tsc::WINDOW_LIB, diff --git a/cli/standalone.rs b/cli/standalone.rs index 83879a163..e0b131eb8 100644 --- a/cli/standalone.rs +++ b/cli/standalone.rs @@ -191,6 +191,7 @@ pub async fn run( no_color: !colors::use_color(), get_error_class_fn: Some(&get_error_class_name), location: metadata.location, + location_data_dir: None, blob_url_store, }; let mut worker = diff --git a/cli/tsc.rs b/cli/tsc.rs index e2fc80676..7abae5ca1 100644 --- a/cli/tsc.rs +++ b/cli/tsc.rs @@ -37,6 +37,8 @@ pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH")); pub static DENO_WEBGPU_LIB: &str = include_str!(env!("DENO_WEBGPU_LIB_PATH")); pub static DENO_WEBSOCKET_LIB: &str = include_str!(env!("DENO_WEBSOCKET_LIB_PATH")); +pub static DENO_WEBSTORAGE_LIB: &str = + include_str!(env!("DENO_WEBSTORAGE_LIB_PATH")); pub static DENO_CRYPTO_LIB: &str = include_str!(env!("DENO_CRYPTO_LIB_PATH")); pub static SHARED_GLOBALS_LIB: &str = include_str!("dts/lib.deno.shared_globals.d.ts"); diff --git a/extensions/webstorage/01_webstorage.js b/extensions/webstorage/01_webstorage.js new file mode 100644 index 000000000..a11d44068 --- /dev/null +++ b/extensions/webstorage/01_webstorage.js @@ -0,0 +1,190 @@ +((window) => { + const core = window.Deno.core; + const webidl = window.__bootstrap.webidl; + + const _rid = Symbol("[[rid]]"); + + class Storage { + [_rid]; + + constructor() { + webidl.illegalConstructor(); + } + + get length() { + webidl.assertBranded(this, Storage); + return core.opSync("op_webstorage_length", this[_rid]); + } + + key(index) { + webidl.assertBranded(this, Storage); + const prefix = "Failed to execute 'key' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + index = webidl.converters["unsigned long"](index, { + prefix, + context: "Argument 1", + }); + + return core.opSync("op_webstorage_key", { + rid: this[_rid], + index, + }); + } + + setItem(key, value) { + webidl.assertBranded(this, Storage); + const prefix = "Failed to execute 'setItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 2, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + value = webidl.converters.DOMString(value, { + prefix, + context: "Argument 2", + }); + + core.opSync("op_webstorage_set", { + rid: this[_rid], + keyName: key, + keyValue: value, + }); + } + + getItem(key) { + webidl.assertBranded(this, Storage); + const prefix = "Failed to execute 'getItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + + return core.opSync("op_webstorage_get", { + rid: this[_rid], + keyName: key, + }); + } + + removeItem(key) { + webidl.assertBranded(this, Storage); + const prefix = "Failed to execute 'removeItem' on 'Storage'"; + webidl.requiredArguments(arguments.length, 1, { prefix }); + key = webidl.converters.DOMString(key, { + prefix, + context: "Argument 1", + }); + + core.opSync("op_webstorage_remove", { + rid: this[_rid], + keyName: key, + }); + } + + clear() { + webidl.assertBranded(this, Storage); + core.opSync("op_webstorage_clear", this[_rid]); + } + } + + function createStorage(persistent) { + if (persistent) window.location; + + const rid = core.opSync("op_webstorage_open", persistent); + + const storage = webidl.createBranded(Storage); + storage[_rid] = rid; + + const proxy = new Proxy(storage, { + deleteProperty(target, key) { + if (typeof key == "symbol") { + delete target[key]; + } else { + target.removeItem(key); + } + return true; + }, + defineProperty(target, key, descriptor) { + if (typeof key == "symbol") { + Object.defineProperty(target, key, descriptor); + } else { + target.setItem(key, descriptor.value); + } + return true; + }, + get(target, key) { + if (typeof key == "symbol") return target[key]; + if (key in target) { + return Reflect.get(...arguments); + } else { + return target.getItem(key) ?? undefined; + } + }, + set(target, key, value) { + if (typeof key == "symbol") { + Object.defineProperty(target, key, { + value, + configurable: true, + }); + } else { + target.setItem(key, value); + } + return true; + }, + has(target, p) { + return (typeof target.getItem(p)) === "string"; + }, + ownKeys() { + return core.opSync("op_webstorage_iterate_keys", rid); + }, + getOwnPropertyDescriptor(target, key) { + if (arguments.length === 1) { + return undefined; + } + if (key in target) { + return undefined; + } + const value = target.getItem(key); + if (value === null) { + return undefined; + } + return { + value, + enumerable: true, + configurable: true, + writable: true, + }; + }, + }); + + proxy[Symbol.for("Deno.customInspect")] = function (inspect) { + return `${this.constructor.name} ${ + inspect({ + length: this.length, + ...Object.fromEntries(Object.entries(proxy)), + }) + }`; + }; + + return proxy; + } + + let localStorage; + let sessionStorage; + + window.__bootstrap.webStorage = { + localStorage() { + if (!localStorage) { + localStorage = createStorage(true); + } + return localStorage; + }, + sessionStorage() { + if (!sessionStorage) { + sessionStorage = createStorage(false); + } + return sessionStorage; + }, + Storage, + }; +})(this); diff --git a/extensions/webstorage/Cargo.toml b/extensions/webstorage/Cargo.toml new file mode 100644 index 000000000..acfaf6a16 --- /dev/null +++ b/extensions/webstorage/Cargo.toml @@ -0,0 +1,19 @@ +# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +[package] +name = "deno_webstorage" +version = "0.1.0" +edition = "2018" +description = "Implementation of WebStorage API for Deno" +authors = ["the Deno authors"] +license = "MIT" +readme = "README.md" +repository = "https://github.com/denoland/deno" + +[lib] +path = "lib.rs" + +[dependencies] +deno_core = { version = "0.86.0", path = "../../core" } +rusqlite = { version = "0.25.0", features = ["unlock_notify", "bundled"] } +serde = { version = "1.0.125", features = ["derive"] } diff --git a/extensions/webstorage/README.md b/extensions/webstorage/README.md new file mode 100644 index 000000000..a0f8a0613 --- /dev/null +++ b/extensions/webstorage/README.md @@ -0,0 +1,5 @@ +# deno_webstorage + +This op crate implements the WebStorage spec in Deno. + +Spec: https://html.spec.whatwg.org/multipage/webstorage.html diff --git a/extensions/webstorage/lib.deno_webstorage.d.ts b/extensions/webstorage/lib.deno_webstorage.d.ts new file mode 100644 index 000000000..bf438e005 --- /dev/null +++ b/extensions/webstorage/lib.deno_webstorage.d.ts @@ -0,0 +1,42 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +// deno-lint-ignore-file no-explicit-any + +/// <reference no-default-lib="true" /> +/// <reference lib="esnext" /> + +/** This Web Storage API interface provides access to a particular domain's session or local storage. It allows, for example, the addition, modification, or deletion of stored data items. */ +interface Storage { + /** + * Returns the number of key/value pairs currently present in the list associated with the object. + */ + readonly length: number; + /** + * Empties the list associated with the object of all key/value pairs, if there are any. + */ + clear(): void; + /** + * Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object. + */ + getItem(key: string): string | null; + /** + * Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object. + */ + key(index: number): string | null; + /** + * Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists. + */ + removeItem(key: string): void; + /** + * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously. + * + * Throws a "QuotaExceededError" DOMException exception if the new value couldn't be set. (Setting could fail if, e.g., the user has disabled storage for the site, or if the quota has been exceeded.) + */ + setItem(key: string, value: string): void; + [name: string]: any; +} + +declare var Storage: { + prototype: Storage; + new (): Storage; +}; diff --git a/extensions/webstorage/lib.rs b/extensions/webstorage/lib.rs new file mode 100644 index 000000000..90ae0598a --- /dev/null +++ b/extensions/webstorage/lib.rs @@ -0,0 +1,316 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +use deno_core::error::bad_resource_id; +use deno_core::error::AnyError; +use deno_core::include_js_files; +use deno_core::op_sync; +use deno_core::Extension; +use deno_core::OpState; +use deno_core::Resource; +use deno_core::ZeroCopyBuf; +use rusqlite::params; +use rusqlite::Connection; +use rusqlite::OptionalExtension; +use serde::Deserialize; +use std::borrow::Cow; +use std::fmt; +use std::path::PathBuf; + +#[derive(Clone)] +struct LocationDataDir(PathBuf); + +pub fn init(location_data_dir: Option<PathBuf>) -> Extension { + Extension::builder() + .js(include_js_files!( + prefix "deno:extensions/webstorage", + "01_webstorage.js", + )) + .ops(vec![ + ("op_webstorage_open", op_sync(op_webstorage_open)), + ("op_webstorage_length", op_sync(op_webstorage_length)), + ("op_webstorage_key", op_sync(op_webstorage_key)), + ("op_webstorage_set", op_sync(op_webstorage_set)), + ("op_webstorage_get", op_sync(op_webstorage_get)), + ("op_webstorage_remove", op_sync(op_webstorage_remove)), + ("op_webstorage_clear", op_sync(op_webstorage_clear)), + ( + "op_webstorage_iterate_keys", + op_sync(op_webstorage_iterate_keys), + ), + ]) + .state(move |state| { + if let Some(location_data_dir) = location_data_dir.clone() { + state.put(LocationDataDir(location_data_dir)); + } + Ok(()) + }) + .build() +} + +pub fn get_declaration() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_webstorage.d.ts") +} + +struct WebStorageConnectionResource(Connection); + +impl Resource for WebStorageConnectionResource { + fn name(&self) -> Cow<str> { + "webStorage".into() + } +} + +pub fn op_webstorage_open( + state: &mut OpState, + persistent: bool, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<u32, AnyError> { + let connection = if persistent { + let path = state.try_borrow::<LocationDataDir>().ok_or_else(|| { + DomExceptionNotSupportedError::new( + "LocalStorage is not supported in this context.", + ) + })?; + std::fs::create_dir_all(&path.0)?; + Connection::open(path.0.join("local_storage"))? + } else { + Connection::open_in_memory()? + }; + + connection.execute( + "CREATE TABLE IF NOT EXISTS data (key VARCHAR UNIQUE, value VARCHAR)", + params![], + )?; + + let rid = state + .resource_table + .add(WebStorageConnectionResource(connection)); + Ok(rid) +} + +pub fn op_webstorage_length( + state: &mut OpState, + rid: u32, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<u32, AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(rid) + .ok_or_else(bad_resource_id)?; + + let mut stmt = resource.0.prepare("SELECT COUNT(*) FROM data")?; + + let length: u32 = stmt.query_row(params![], |row| row.get(0))?; + + Ok(length) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct KeyArgs { + rid: u32, + index: u32, +} + +pub fn op_webstorage_key( + state: &mut OpState, + args: KeyArgs, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<Option<String>, AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(args.rid) + .ok_or_else(bad_resource_id)?; + + let mut stmt = resource + .0 + .prepare("SELECT key FROM data LIMIT 1 OFFSET ?")?; + + let key: Option<String> = stmt + .query_row(params![args.index], |row| row.get(0)) + .optional()?; + + Ok(key) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SetArgs { + rid: u32, + key_name: String, + key_value: String, +} + +pub fn op_webstorage_set( + state: &mut OpState, + args: SetArgs, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<(), AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(args.rid) + .ok_or_else(bad_resource_id)?; + + let mut stmt = resource + .0 + .prepare("SELECT SUM(pgsize) FROM dbstat WHERE name = 'data'")?; + let size: u32 = stmt.query_row(params![], |row| row.get(0))?; + + if size >= 5000000 { + return Err( + DomExceptionQuotaExceededError::new("Exceeded maximum storage size") + .into(), + ); + } + + resource.0.execute( + "INSERT OR REPLACE INTO data (key, value) VALUES (?, ?)", + params![args.key_name, args.key_value], + )?; + + Ok(()) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GetArgs { + rid: u32, + key_name: String, +} + +pub fn op_webstorage_get( + state: &mut OpState, + args: GetArgs, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<Option<String>, AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(args.rid) + .ok_or_else(bad_resource_id)?; + + let mut stmt = resource.0.prepare("SELECT value FROM data WHERE key = ?")?; + + let val = stmt + .query_row(params![args.key_name], |row| row.get(0)) + .optional()?; + + Ok(val) +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RemoveArgs { + rid: u32, + key_name: String, +} + +pub fn op_webstorage_remove( + state: &mut OpState, + args: RemoveArgs, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<(), AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(args.rid) + .ok_or_else(bad_resource_id)?; + + resource + .0 + .execute("DELETE FROM data WHERE key = ?", params![args.key_name])?; + + Ok(()) +} + +pub fn op_webstorage_clear( + state: &mut OpState, + rid: u32, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<(), AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(rid) + .ok_or_else(bad_resource_id)?; + + resource.0.execute("DROP TABLE data", params![])?; + resource.0.execute( + "CREATE TABLE data (key VARCHAR UNIQUE, value VARCHAR)", + params![], + )?; + + Ok(()) +} + +pub fn op_webstorage_iterate_keys( + state: &mut OpState, + rid: u32, + _zero_copy: Option<ZeroCopyBuf>, +) -> Result<Vec<String>, AnyError> { + let resource = state + .resource_table + .get::<WebStorageConnectionResource>(rid) + .ok_or_else(bad_resource_id)?; + + let mut stmt = resource.0.prepare("SELECT key FROM data")?; + + let keys = stmt + .query_map(params![], |row| row.get::<_, String>(0))? + .map(|r| r.unwrap()) + .collect(); + + Ok(keys) +} + +#[derive(Debug)] +pub struct DomExceptionQuotaExceededError { + pub msg: String, +} + +impl DomExceptionQuotaExceededError { + pub fn new(msg: &str) -> Self { + DomExceptionQuotaExceededError { + msg: msg.to_string(), + } + } +} + +impl fmt::Display for DomExceptionQuotaExceededError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(&self.msg) + } +} + +impl std::error::Error for DomExceptionQuotaExceededError {} + +pub fn get_quota_exceeded_error_class_name( + e: &AnyError, +) -> Option<&'static str> { + e.downcast_ref::<DomExceptionQuotaExceededError>() + .map(|_| "DOMExceptionQuotaExceededError") +} + +#[derive(Debug)] +pub struct DomExceptionNotSupportedError { + pub msg: String, +} + +impl DomExceptionNotSupportedError { + pub fn new(msg: &str) -> Self { + DomExceptionNotSupportedError { + msg: msg.to_string(), + } + } +} + +impl fmt::Display for DomExceptionNotSupportedError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.pad(&self.msg) + } +} + +impl std::error::Error for DomExceptionNotSupportedError {} + +pub fn get_not_supported_error_class_name( + e: &AnyError, +) -> Option<&'static str> { + e.downcast_ref::<DomExceptionNotSupportedError>() + .map(|_| "DOMExceptionNotSupportedError") +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 8acd61f7e..4cefa23ee 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -29,6 +29,7 @@ deno_web = { path = "../extensions/web", version = "0.35.0" } deno_webgpu = { path = "../extensions/webgpu", version = "0.6.0" } deno_webidl = { path = "../extensions/webidl", version = "0.5.0" } deno_websocket = { path = "../extensions/websocket", version = "0.10.0" } +deno_webstorage = { path = "../extensions/webstorage", version = "0.1.0" } [target.'cfg(windows)'.build-dependencies] winres = "0.1.11" @@ -46,6 +47,7 @@ deno_web = { path = "../extensions/web", version = "0.35.0" } deno_webgpu = { path = "../extensions/webgpu", version = "0.6.0" } deno_webidl = { path = "../extensions/webidl", version = "0.5.0" } deno_websocket = { path = "../extensions/websocket", version = "0.10.0" } +deno_webstorage = { path = "../extensions/webstorage", version = "0.1.0" } atty = "0.2.14" bytes = "1" diff --git a/runtime/build.rs b/runtime/build.rs index b6a9da582..e1cae7195 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -48,6 +48,7 @@ fn create_runtime_snapshot(snapshot_path: &Path, files: Vec<PathBuf>) { "".to_owned(), None, ), + deno_webstorage::init(None), deno_crypto::init(None), deno_webgpu::init(false), deno_timers::init::<deno_timers::NoTimersPermission>(), diff --git a/runtime/errors.rs b/runtime/errors.rs index f9ef947a3..c5e93d65b 100644 --- a/runtime/errors.rs +++ b/runtime/errors.rs @@ -157,6 +157,8 @@ fn get_nix_error_class(error: &nix::Error) -> &'static str { pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { deno_core::error::get_custom_error_class(e) .or_else(|| deno_webgpu::error::get_error_class_name(e)) + .or_else(|| deno_webstorage::get_quota_exceeded_error_class_name(e)) + .or_else(|| deno_webstorage::get_not_supported_error_class_name(e)) .or_else(|| { e.downcast_ref::<dlopen::Error>() .map(get_dlopen_error_class) diff --git a/runtime/examples/hello_runtime.rs b/runtime/examples/hello_runtime.rs index 9c3cbd67e..80a258c17 100644 --- a/runtime/examples/hello_runtime.rs +++ b/runtime/examples/hello_runtime.rs @@ -40,6 +40,7 @@ async fn main() -> Result<(), AnyError> { no_color: false, get_error_class_fn: Some(&get_error_class_name), location: None, + location_data_dir: None, blob_url_store: BlobUrlStore::default(), }; diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index fee7cd2d7..d2926bb1f 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -28,6 +28,7 @@ delete Object.prototype.__proto__; const fileReader = window.__bootstrap.fileReader; const webgpu = window.__bootstrap.webgpu; const webSocket = window.__bootstrap.webSocket; + const webStorage = window.__bootstrap.webStorage; const file = window.__bootstrap.file; const formData = window.__bootstrap.formData; const fetch = window.__bootstrap.fetch; @@ -190,6 +191,18 @@ delete Object.prototype.__proto__; return new DOMException(msg, "OperationError"); }, ); + core.registerErrorBuilder( + "DOMExceptionQuotaExceededError", + function DOMExceptionQuotaExceededError(msg) { + return new DOMException(msg, "QuotaExceededError"); + }, + ); + core.registerErrorBuilder( + "DOMExceptionNotSupportedError", + function DOMExceptionNotSupportedError(msg) { + return new DOMException(msg, "NotSupported"); + }, + ); } class Navigator { @@ -351,6 +364,17 @@ delete Object.prototype.__proto__; alert: util.writable(prompt.alert), confirm: util.writable(prompt.confirm), prompt: util.writable(prompt.prompt), + localStorage: { + configurable: true, + enumerable: true, + get: webStorage.localStorage, + }, + sessionStorage: { + configurable: true, + enumerable: true, + get: webStorage.sessionStorage, + }, + Storage: util.nonEnumerable(webStorage.Storage), }; const workerRuntimeGlobalProperties = { diff --git a/runtime/lib.rs b/runtime/lib.rs index 61b8fc77e..d45a72727 100644 --- a/runtime/lib.rs +++ b/runtime/lib.rs @@ -10,6 +10,7 @@ pub use deno_web; pub use deno_webgpu; pub use deno_webidl; pub use deno_websocket; +pub use deno_webstorage; pub mod colors; pub mod errors; diff --git a/runtime/worker.rs b/runtime/worker.rs index b4c27b4f4..ab54e2153 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -69,6 +69,7 @@ pub struct WorkerOptions { pub no_color: bool, pub get_error_class_fn: Option<GetErrorClassFn>, pub location: Option<Url>, + pub location_data_dir: Option<std::path::PathBuf>, pub blob_url_store: BlobUrlStore, } @@ -104,6 +105,7 @@ impl MainWorker { options.user_agent.clone(), options.ca_data.clone(), ), + deno_webstorage::init(options.location_data_dir.clone()), deno_crypto::init(options.seed), deno_webgpu::init(options.unstable), deno_timers::init::<Permissions>(), @@ -291,6 +293,7 @@ mod tests { no_color: true, get_error_class_fn: None, location: None, + location_data_dir: None, blob_url_store: BlobUrlStore::default(), }; diff --git a/tools/wpt.ts b/tools/wpt.ts index e7da43e64..13f8b6467 100755 --- a/tools/wpt.ts +++ b/tools/wpt.ts @@ -34,6 +34,10 @@ import { red, yellow, } from "https://deno.land/std@0.84.0/fmt/colors.ts"; +import { + writeAll, + writeAllSync, +} from "https://deno.land/std@0.95.0/io/util.ts"; import { saveExpectation } from "./wpt/utils.ts"; const command = Deno.args[0]; @@ -104,7 +108,7 @@ async function setup() { throw err; } }); - await Deno.writeAll( + await writeAll( file, new TextEncoder().encode( "\n\n# Configured for Web Platform Tests (Deno)\n" + entries, @@ -150,7 +154,7 @@ async function run() { rest.length == 0 ? undefined : rest, expectation, ); - assertAllExpectationsHaveTests(expectation, tests); + assertAllExpectationsHaveTests(expectation, tests, rest); console.log(`Going to run ${tests.length} test files.`); const results = await runWithTestUtil(false, async () => { @@ -182,6 +186,7 @@ async function run() { function assertAllExpectationsHaveTests( expectation: Expectation, testsToRun: TestToRun[], + filter?: string[], ): void { const tests = new Set(testsToRun.map((t) => t.path)); const missingTests: string[] = []; @@ -189,6 +194,12 @@ function assertAllExpectationsHaveTests( function walk(parentExpectation: Expectation, parent: string) { for (const key in parentExpectation) { const path = `${parent}/${key}`; + if ( + filter && + !filter.find((filter) => path.substring(1).startsWith(filter)) + ) { + continue; + } const expectation = parentExpectation[key]; if (typeof expectation == "boolean" || Array.isArray(expectation)) { if (!tests.has(path)) { @@ -422,7 +433,7 @@ function analyzeTestResult( function reportVariation(result: TestResult, expectation: boolean | string[]) { if (result.status !== 0) { console.log(`test stderr:`); - Deno.writeAllSync(Deno.stdout, new TextEncoder().encode(result.stderr)); + writeAllSync(Deno.stdout, new TextEncoder().encode(result.stderr)); const expectFail = expectation === false; console.log( @@ -508,7 +519,7 @@ function createReportTestCase(expectation: boolean | string[]) { break; } - console.log(simpleMessage); + writeAllSync(Deno.stdout, new TextEncoder().encode(simpleMessage + "\n")); }; } @@ -536,7 +547,12 @@ function discoverTestsToRun( ) { if (!path) continue; const url = new URL(path, "http://web-platform.test:8000"); - if (!url.pathname.endsWith(".any.html")) continue; + if ( + !url.pathname.endsWith(".any.html") && + !url.pathname.endsWith(".window.html") + ) { + continue; + } const finalPath = url.pathname + url.search; const split = finalPath.split("/"); diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 983f73e53..afdc0cb1f 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -1064,6 +1064,66 @@ } } }, + "webstorage": { + "defineProperty.window.html": true, + "set.window.html": true, + "storage_enumerate.window.html": true, + "storage_in.window.html": true, + "event_constructor.window.html": false, + "event_initstorageevent.window.html": false, + "missing_arguments.window.html": true, + "storage_builtins.window.html": true, + "storage_clear.window.html": true, + "storage_functions_not_overwritten.window.html": true, + "storage_getitem.window.html": true, + "storage_indexing.window.html": true, + "storage_key.window.html": true, + "storage_key_empty_string.window.html": true, + "storage_length.window.html": true, + "storage_local_setitem_quotaexceedederr.window.html": true, + "storage_local_window_open.window.html": false, + "storage_removeitem.window.html": true, + "storage_session_setitem_quotaexceedederr.window.html": true, + "storage_session_window_noopener.window.html": false, + "storage_session_window_open.window.html": false, + "storage_set_value_enumerate.window.html": true, + "storage_setitem.window.html": [ + "localStorage[\"\ud800\"]", + "localStorage[] = \"\ud800\"", + "localStorage[\"\udbff\"]", + "localStorage[] = \"\udbff\"", + "localStorage[\"\udc00\"]", + "localStorage[] = \"\udc00\"", + "localStorage[\"\udfff\"]", + "localStorage[] = \"\udfff\"", + "localStorage[\"\\ufffd\"]", + "localStorage[] = \"\\ufffd\"", + "localStorage[\"\ud83ca\"]", + "localStorage[] = \"\ud83ca\"", + "localStorage[\"a\udf4d\"]", + "localStorage[] = \"a\udf4d\"", + "sessionStorage[\"\ud800\"]", + "sessionStorage[] = \"\ud800\"", + "sessionStorage[\"\udbff\"]", + "sessionStorage[] = \"\udbff\"", + "sessionStorage[\"\udc00\"]", + "sessionStorage[] = \"\udc00\"", + "sessionStorage[\"\udfff\"]", + "sessionStorage[] = \"\udfff\"", + "sessionStorage[\"\\ufffd\"]", + "sessionStorage[] = \"\\ufffd\"", + "sessionStorage[\"\ud83ca\"]", + "sessionStorage[] = \"\ud83ca\"", + "sessionStorage[\"a\udf4d\"]", + "sessionStorage[] = \"a\udf4d\"" + ], + "storage_string_conversion.window.html": true, + "storage_supported_property_names.window.html": true, + "symbol-props.window.html": [ + "localStorage: defineProperty not configurable", + "sessionStorage: defineProperty not configurable" + ] + }, "xhr": { "formdata": { "append.any.html": true, |