/* 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;