local-and-session-storage.js (4933B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 BaseStorageActor, 9 DEFAULT_VALUE, 10 } = require("resource://devtools/server/actors/resources/storage/index.js"); 11 const { 12 LongStringActor, 13 } = require("resource://devtools/server/actors/string.js"); 14 15 class LocalOrSessionStorageActor extends BaseStorageActor { 16 constructor(storageActor, typeName) { 17 super(storageActor, typeName); 18 19 Services.obs.addObserver(this, "dom-storage2-changed"); 20 Services.obs.addObserver(this, "dom-private-storage2-changed"); 21 } 22 23 destroy() { 24 if (this.isDestroyed()) { 25 return; 26 } 27 Services.obs.removeObserver(this, "dom-storage2-changed"); 28 Services.obs.removeObserver(this, "dom-private-storage2-changed"); 29 30 super.destroy(); 31 } 32 33 getNamesForHost(host) { 34 const storage = this.hostVsStores.get(host); 35 return storage ? Object.keys(storage) : []; 36 } 37 38 getValuesForHost(host, name) { 39 const storage = this.hostVsStores.get(host); 40 if (!storage) { 41 return []; 42 } 43 if (name) { 44 const value = storage ? storage.getItem(name) : null; 45 return [{ name, value }]; 46 } 47 if (!storage) { 48 return []; 49 } 50 51 // local and session storage cannot be iterated over using Object.keys() 52 // because it skips keys that are duplicated on the prototype 53 // e.g. "key", "getKeys" so we need to gather the real keys using the 54 // storage.key() function. 55 const storageArray = []; 56 for (let i = 0; i < storage.length; i++) { 57 const key = storage.key(i); 58 storageArray.push({ 59 name: key, 60 value: storage.getItem(key), 61 }); 62 } 63 return storageArray; 64 } 65 66 // We need to override this method as populateStoresForHost expect the window object 67 populateStoresForHosts() { 68 this.hostVsStores = new Map(); 69 for (const window of this.windows) { 70 const host = this.getHostName(window.location); 71 if (host) { 72 this.populateStoresForHost(host, window); 73 } 74 } 75 } 76 77 populateStoresForHost(host, window) { 78 try { 79 this.hostVsStores.set(host, window[this.typeName]); 80 } catch (ex) { 81 console.warn( 82 `Failed to enumerate ${this.typeName} for host ${host}: ${ex}` 83 ); 84 } 85 } 86 87 async getFields() { 88 return [ 89 { name: "name", editable: true }, 90 { name: "value", editable: true }, 91 ]; 92 } 93 94 async addItem(guid, host) { 95 const storage = this.hostVsStores.get(host); 96 if (!storage) { 97 return; 98 } 99 storage.setItem(guid, DEFAULT_VALUE); 100 } 101 102 /** 103 * Edit localStorage or sessionStorage fields. 104 * 105 * @param {object} data 106 * See editCookie() for format details. 107 */ 108 async editItem({ host, field, oldValue, items }) { 109 const storage = this.hostVsStores.get(host); 110 if (!storage) { 111 return; 112 } 113 114 if (field === "name") { 115 storage.removeItem(oldValue); 116 } 117 118 storage.setItem(items.name, items.value); 119 } 120 121 async removeItem(host, name) { 122 const storage = this.hostVsStores.get(host); 123 if (!storage) { 124 return; 125 } 126 storage.removeItem(name); 127 } 128 129 async removeAll(host) { 130 const storage = this.hostVsStores.get(host); 131 if (!storage) { 132 return; 133 } 134 storage.clear(); 135 } 136 137 observe(subject, topic, data) { 138 if ( 139 (topic != "dom-storage2-changed" && 140 topic != "dom-private-storage2-changed") || 141 data != this.typeName 142 ) { 143 return null; 144 } 145 146 const host = this.getSchemaAndHost(subject.url); 147 148 if (!this.hostVsStores.has(host)) { 149 return null; 150 } 151 152 let action = "changed"; 153 if (subject.key == null) { 154 return this.storageActor.update("cleared", this.typeName, [host]); 155 } else if (subject.oldValue == null) { 156 action = "added"; 157 } else if (subject.newValue == null) { 158 action = "deleted"; 159 } 160 const updateData = {}; 161 updateData[host] = [subject.key]; 162 return this.storageActor.update(action, this.typeName, updateData); 163 } 164 165 /** 166 * Given a url, correctly determine its protocol + hostname part. 167 */ 168 getSchemaAndHost(url) { 169 const uri = Services.io.newURI(url); 170 if (!uri.host) { 171 return uri.spec; 172 } 173 return uri.scheme + "://" + uri.hostPort; 174 } 175 176 toStoreObject(item) { 177 if (!item) { 178 return null; 179 } 180 181 return { 182 name: item.name, 183 value: new LongStringActor(this.conn, item.value || ""), 184 }; 185 } 186 } 187 188 class LocalStorageActor extends LocalOrSessionStorageActor { 189 constructor(storageActor) { 190 super(storageActor, "localStorage"); 191 } 192 } 193 exports.LocalStorageActor = LocalStorageActor; 194 195 class SessionStorageActor extends LocalOrSessionStorageActor { 196 constructor(storageActor) { 197 super(storageActor, "sessionStorage"); 198 } 199 } 200 exports.SessionStorageActor = SessionStorageActor;