From dfe528198d363ebc883da84dc816bce112ecd24b Mon Sep 17 00:00:00 2001
From: crowlKats <13135287+crowlKats@users.noreply.github.com>
Date: Mon, 10 May 2021 12:02:47 +0200
Subject: feat: add WebStorage API (#7819)
This commit introduces localStorage and sessionStorage.
---
extensions/webstorage/01_webstorage.js | 190 +++++++++++++++
extensions/webstorage/Cargo.toml | 19 ++
extensions/webstorage/README.md | 5 +
extensions/webstorage/lib.deno_webstorage.d.ts | 42 ++++
extensions/webstorage/lib.rs | 316 +++++++++++++++++++++++++
5 files changed, 572 insertions(+)
create mode 100644 extensions/webstorage/01_webstorage.js
create mode 100644 extensions/webstorage/Cargo.toml
create mode 100644 extensions/webstorage/README.md
create mode 100644 extensions/webstorage/lib.deno_webstorage.d.ts
create mode 100644 extensions/webstorage/lib.rs
(limited to 'extensions')
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
+
+///
+///
+
+/** 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) -> 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 {
+ "webStorage".into()
+ }
+}
+
+pub fn op_webstorage_open(
+ state: &mut OpState,
+ persistent: bool,
+ _zero_copy: Option,
+) -> Result {
+ let connection = if persistent {
+ let path = state.try_borrow::().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,
+) -> Result {
+ let resource = state
+ .resource_table
+ .get::(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,
+) -> Result