Commit 409a7961 authored by Hiroshige Hayashizaki's avatar Hiroshige Hayashizaki Committed by Commit Bot

[Layered API] Implement std:async-local-storage behind a flag

This CL imports the async-local-storage implementation as-is
except for renaming to index.js.

Original author:
    Domenic Denicola (domenic@chromium.org)
https://github.com/domenic/async-local-storage

All code were written and contributed by Googlers
who are Chromium Contributors.

Tentatively this CL imports the files under core/script/resources
until a long-term plan for repository location etc. is settled.

This is a still experimental feature behind a flag, and its
API interface and implementation can be changed significantly
before shipped.

This CL also imports a test with some modifications.

Change-Id: I99944c75240ab2f1d114aa85f5c8ffac1f5e9b98
Reviewed-on: https://chromium-review.googlesource.com/1033713
Commit-Queue: Hiroshige Hayashizaki <hiroshige@chromium.org>
Reviewed-by: default avatarKinuko Yasuda <kinuko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555967}
parent 889ab33a
<!DOCTYPE html>
<meta charset="utf-8">
<title>Async local storage storage export smoke test</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script type="module">
import { storage } from "std:async-local-storage";
test(() => {
const { backingStore } = storage;
assert_array_equals(Object.keys(backingStore), ["database", "store", "version"]);
assert_own_property(backingStore, "database");
assert_own_property(backingStore, "store");
assert_own_property(backingStore, "version");
assert_equals(Object.getPrototypeOf(backingStore), Object.prototype);
assert_equals(backingStore.database, "async-local-storage:default");
assert_equals(backingStore.store, "store");
assert_equals(backingStore.version, 1);
}, "backingStore returns the correct object");
promise_test(async (t) => {
t.add_cleanup(async () => {
await storage.clear();
});
assert_equals(await storage.set("key", "value"), undefined);
assert_equals(await storage.get("key"), "value");
assert_equals(await storage.has("key"), true);
assert_array_equals(await storage.keys(), ["key"]);
assert_array_equals(await storage.values(), ["value"]);
const entries = await storage.entries();
assert_true(Array.isArray(entries));
assert_equals(entries.length, 1);
assert_array_equals(entries[0], ["key", "value"]);
assert_equals(await storage.delete("key"), undefined);
assert_equals(await storage.get("key"), undefined);
assert_equals(await storage.has("key"), false);
}, "storage methods work, at least for one entry with string key and value");
</script>
......@@ -50,6 +50,7 @@
<!-- Layered API scripts. Should be consistent with kLayeredAPIResources
in renderer/core/script/layered_api.cc -->
<include name="IDR_LAYERED_API_ASYNC_LOCAL_STORAGE_INDEX_JS" file="../renderer/core/script/resources/layered_api/async-local-storage/index.js" type="BINDATA" skip_minify="true"/>
<include name="IDR_LAYERED_API_BLANK_INDEX_JS" file="../renderer/core/script/resources/layered_api/blank/index.js" type="BINDATA" skip_minify="true"/>
<include name="IDR_LAYERED_API_VIRTUAL_LIST_INDEX_JS" file="../renderer/core/script/resources/layered_api/virtual-list/index.js" type="BINDATA" skip_minify="true"/>
<include name="IDR_LAYERED_API_VIRTUAL_LIST_LAYOUTS_LAYOUT_1D_BASE_JS" file="../renderer/core/script/resources/layered_api/virtual-list/layouts/layout-1d-base.js" type="BINDATA" skip_minify="true"/>
......
......@@ -26,6 +26,9 @@ struct LayeredAPIResource {
const LayeredAPIResource kLayeredAPIResources[] = {
{"blank/index.js", IDR_LAYERED_API_BLANK_INDEX_JS},
{"async-local-storage/index.js",
IDR_LAYERED_API_ASYNC_LOCAL_STORAGE_INDEX_JS},
{"virtual-list/index.js", IDR_LAYERED_API_VIRTUAL_LIST_INDEX_JS},
{"virtual-list/layouts/layout-1d-base.js",
IDR_LAYERED_API_VIRTUAL_LIST_LAYOUTS_LAYOUT_1D_BASE_JS},
......
Name: async-local-storage Layered API
URL: https://github.com/valdrinkoshi/virtual-list
Version: 4bfe22c2d424e3451acfcf809585336f389f6397
Security Critical: no
Description:
Temporarily, the files under this directory are authored by Chromium Authors
on a github repository, and then imported to Chromium repository directly here,
until a long-term Layered API development plan is settled.
Local Modifications:
None (except for renaming to index.js)
// TODOs/spec-noncompliances:
// - Susceptible to tampering of built-in prototypes and globals. We want to work on tooling to ameliorate that.
// - Uses symbols for information hiding but those are interceptable and forgeable. Need private fields/methods.
const databaseName = Symbol("[[DatabaseName]]");
const databasePromise = Symbol("[[DatabasePromise]]");
export class StorageArea {
constructor(name) {
this[databasePromise] = null;
this[databaseName] = "async-local-storage:" + `${name}`;
}
async set(key, value) {
throwForDisallowedKey(key);
return performDatabaseOperation(this, "readwrite", (transaction, store) => {
store.put(value, key);
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onabort = () => reject(transaction.error);
transaction.onerror = () => reject(transaction.error);
});
});
}
async get(key) {
throwForDisallowedKey(key);
return performDatabaseOperation(this, "readonly", (transaction, store) => {
const request = store.get(key);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
}
async has(key) {
throwForDisallowedKey(key);
return performDatabaseOperation(this, "readonly", (transaction, store) => {
const request = store.count(key);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result === 0 ? false : true);
request.onerror = () => reject(request.error);
});
});
}
async delete(key) {
throwForDisallowedKey(key);
return performDatabaseOperation(this, "readwrite", (transaction, store) => {
store.delete(key);
return new Promise((resolve, reject) => {
transaction.oncomplete = () => resolve();
transaction.onabort = () => reject(transaction.error);
transaction.onerror = () => reject(transaction.error);
});
});
}
async clear() {
if (!(databasePromise in this)) {
return Promise.reject(new TypeError("Invalid this value"));
}
if (this[databasePromise] !== null) {
return this[databasePromise].then(
() => {
this[databasePromise] = null;
return deleteDatabase(this[databaseName]);
},
() => {
this[databasePromise] = null;
return deleteDatabase(this[databaseName]);
}
);
}
return deleteDatabase(this[databaseName]);
}
async keys() {
return performDatabaseOperation(this, "readonly", (transaction, store) => {
const request = store.getAllKeys(undefined);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
}
async values() {
return performDatabaseOperation(this, "readonly", (transaction, store) => {
const request = store.getAll(undefined);
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
});
}
async entries() {
return performDatabaseOperation(this, "readonly", (transaction, store) => {
const keysRequest = store.getAllKeys(undefined);
const valuesRequest = store.getAll(undefined);
return new Promise((resolve, reject) => {
keysRequest.onerror = () => reject(keysRequest.error);
valuesRequest.onerror = () => reject(valuesRequest.error);
valuesRequest.onsuccess = () => {
resolve(zip(keysRequest.result, valuesRequest.result));
};
});
});
}
get backingStore() {
if (!(databasePromise in this)) {
throw new TypeError("Invalid this value");
}
return {
database: this[databaseName],
store: "store",
version: 1
};
}
}
export const storage = new StorageArea("default");
function performDatabaseOperation(area, mode, steps) {
if (!(databasePromise in area)) {
return Promise.reject(new TypeError("Invalid this value"));
}
if (area[databasePromise] === null) {
initializeDatabasePromise(area);
}
return area[databasePromise].then(database => {
const transaction = database.transaction("store", mode);
const store = transaction.objectStore("store");
return steps(transaction, store);
});
}
function initializeDatabasePromise(area) {
area[databasePromise] = new Promise((resolve, reject) => {
const request = self.indexedDB.open(area[databaseName], 1);
request.onsuccess = () => {
const database = request.result;
database.onclose = () => area[databasePromise] = null;
database.onversionchange = () => {
database.close();
area[databasePromise] = null;
}
resolve(database);
};
request.onerror = () => reject(request.error);
request.onupgradeneeded = () => {
try {
request.result.createObjectStore("store");
} catch (e) {
reject(e);
}
};
});
}
function isAllowedAsAKey(value) {
if (typeof value === "number" || typeof value === "string") {
return true;
}
if (Array.isArray(value)) {
return true;
}
if (isDate(value)) {
return true;
}
if (ArrayBuffer.isView(value)) {
return true;
}
if (isArrayBuffer(value)) {
return true;
}
return false;
}
function isDate(value) {
try {
Date.prototype.getTime.call(value);
return true;
} catch (e) { // TODO: remove useless binding
return false;
}
}
const byteLengthGetter = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, "byteLength").get;
function isArrayBuffer(value) {
try {
byteLengthGetter.call(value);
return true;
} catch (e) { // TODO: remove useless binding
return false;
}
}
function throwForDisallowedKey(key) {
if (!isAllowedAsAKey(key)) {
throw new DOMException("The given value is not allowed as a key", "DataError");
}
}
function zip(a, b) {
const result = [];
for (let i = 0; i < a.length; ++i) {
result.push([a[i], b[i]]);
}
return result;
}
function deleteDatabase(name) {
return new Promise((resolve, reject) => {
const request = self.indexedDB.deleteDatabase(name);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment