summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/resources/storage/local-and-session-storage.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/actors/resources/storage/local-and-session-storage.js')
-rw-r--r--devtools/server/actors/resources/storage/local-and-session-storage.js200
1 files changed, 200 insertions, 0 deletions
diff --git a/devtools/server/actors/resources/storage/local-and-session-storage.js b/devtools/server/actors/resources/storage/local-and-session-storage.js
new file mode 100644
index 0000000000..ba0f006d22
--- /dev/null
+++ b/devtools/server/actors/resources/storage/local-and-session-storage.js
@@ -0,0 +1,200 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ BaseStorageActor,
+ DEFAULT_VALUE,
+} = require("resource://devtools/server/actors/resources/storage/index.js");
+const {
+ LongStringActor,
+} = require("resource://devtools/server/actors/string.js");
+
+class LocalOrSessionStorageActor extends BaseStorageActor {
+ constructor(storageActor, typeName) {
+ super(storageActor, typeName);
+
+ Services.obs.addObserver(this, "dom-storage2-changed");
+ Services.obs.addObserver(this, "dom-private-storage2-changed");
+ }
+
+ destroy() {
+ if (this.isDestroyed()) {
+ return;
+ }
+ Services.obs.removeObserver(this, "dom-storage2-changed");
+ Services.obs.removeObserver(this, "dom-private-storage2-changed");
+
+ super.destroy();
+ }
+
+ getNamesForHost(host) {
+ const storage = this.hostVsStores.get(host);
+ return storage ? Object.keys(storage) : [];
+ }
+
+ getValuesForHost(host, name) {
+ const storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return [];
+ }
+ if (name) {
+ const value = storage ? storage.getItem(name) : null;
+ return [{ name, value }];
+ }
+ if (!storage) {
+ return [];
+ }
+
+ // local and session storage cannot be iterated over using Object.keys()
+ // because it skips keys that are duplicated on the prototype
+ // e.g. "key", "getKeys" so we need to gather the real keys using the
+ // storage.key() function.
+ const storageArray = [];
+ for (let i = 0; i < storage.length; i++) {
+ const key = storage.key(i);
+ storageArray.push({
+ name: key,
+ value: storage.getItem(key),
+ });
+ }
+ return storageArray;
+ }
+
+ // We need to override this method as populateStoresForHost expect the window object
+ populateStoresForHosts() {
+ this.hostVsStores = new Map();
+ for (const window of this.windows) {
+ const host = this.getHostName(window.location);
+ if (host) {
+ this.populateStoresForHost(host, window);
+ }
+ }
+ }
+
+ populateStoresForHost(host, window) {
+ try {
+ this.hostVsStores.set(host, window[this.typeName]);
+ } catch (ex) {
+ console.warn(
+ `Failed to enumerate ${this.typeName} for host ${host}: ${ex}`
+ );
+ }
+ }
+
+ async getFields() {
+ return [
+ { name: "name", editable: true },
+ { name: "value", editable: true },
+ ];
+ }
+
+ async addItem(guid, host) {
+ const storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return;
+ }
+ storage.setItem(guid, DEFAULT_VALUE);
+ }
+
+ /**
+ * Edit localStorage or sessionStorage fields.
+ *
+ * @param {Object} data
+ * See editCookie() for format details.
+ */
+ async editItem({ host, field, oldValue, items }) {
+ const storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return;
+ }
+
+ if (field === "name") {
+ storage.removeItem(oldValue);
+ }
+
+ storage.setItem(items.name, items.value);
+ }
+
+ async removeItem(host, name) {
+ const storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return;
+ }
+ storage.removeItem(name);
+ }
+
+ async removeAll(host) {
+ const storage = this.hostVsStores.get(host);
+ if (!storage) {
+ return;
+ }
+ storage.clear();
+ }
+
+ observe(subject, topic, data) {
+ if (
+ (topic != "dom-storage2-changed" &&
+ topic != "dom-private-storage2-changed") ||
+ data != this.typeName
+ ) {
+ return null;
+ }
+
+ const host = this.getSchemaAndHost(subject.url);
+
+ if (!this.hostVsStores.has(host)) {
+ return null;
+ }
+
+ let action = "changed";
+ if (subject.key == null) {
+ return this.storageActor.update("cleared", this.typeName, [host]);
+ } else if (subject.oldValue == null) {
+ action = "added";
+ } else if (subject.newValue == null) {
+ action = "deleted";
+ }
+ const updateData = {};
+ updateData[host] = [subject.key];
+ return this.storageActor.update(action, this.typeName, updateData);
+ }
+
+ /**
+ * Given a url, correctly determine its protocol + hostname part.
+ */
+ getSchemaAndHost(url) {
+ const uri = Services.io.newURI(url);
+ if (!uri.host) {
+ return uri.spec;
+ }
+ return uri.scheme + "://" + uri.hostPort;
+ }
+
+ toStoreObject(item) {
+ if (!item) {
+ return null;
+ }
+
+ return {
+ name: item.name,
+ value: new LongStringActor(this.conn, item.value || ""),
+ };
+ }
+}
+
+class LocalStorageActor extends LocalOrSessionStorageActor {
+ constructor(storageActor) {
+ super(storageActor, "localStorage");
+ }
+}
+exports.LocalStorageActor = LocalStorageActor;
+
+class SessionStorageActor extends LocalOrSessionStorageActor {
+ constructor(storageActor) {
+ super(storageActor, "sessionStorage");
+ }
+}
+exports.SessionStorageActor = SessionStorageActor;