1 /* See license.txt for terms of usage */ 2 3 // ********************************************************************************************* // 4 // Constants 5 6 const Cc = Components.classes; 7 const Ci = Components.interfaces; 8 const Cr = Components.results; 9 const Cu = Components.utils; 10 11 const dirService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties); 12 13 // https://developer.mozilla.org/en/Using_JavaScript_code_modules 14 var EXPORTED_SYMBOLS = ["Storage", "StorageService", "TextService"]; 15 16 Cu.import("resource://firebug/fbtrace.js"); 17 Cu.import("resource://gre/modules/FileUtils.jsm"); 18 19 // ********************************************************************************************* // 20 // Implementation 21 22 /** 23 * http://dev.w3.org/html5/webstorage/#storage-0 24 * interface Storage { 25 * readonly attribute unsigned long length; 26 * getter DOMString key(in unsigned long index); 27 * getter any getItem(in DOMString key); 28 * setter creator void setItem(in DOMString key, in any data); 29 * deleter void removeItem(in DOMString key); 30 * void clear(); 31 * }; 32 */ 33 function Storage(leafName) 34 { 35 this.leafName = leafName; 36 this.objectTable = {}; 37 } 38 39 Storage.prototype = 40 { 41 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 42 // Read 43 44 get length() 45 { 46 return this.key(-1); 47 }, 48 49 key: function(index) 50 { 51 var i = 0; 52 for (var p in this.objectTable) 53 { 54 if (i === index) 55 return p; 56 i++; 57 } 58 return (index < 0 ? i : null); 59 }, 60 61 getItem: function(key) 62 { 63 return this.objectTable[key]; 64 }, 65 66 getKeys: function() 67 { 68 var keys = []; 69 70 for (var p in this.objectTable) 71 if (this.objectTable.hasOwnProperty(p)) 72 keys.push(p); 73 74 return keys; 75 }, 76 77 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 78 // Write 79 80 setItem: function(key, data) 81 { 82 this.objectTable[key] = data; 83 StorageService.setStorage(this); 84 }, 85 86 removeItem: function(key) 87 { 88 delete this.objectTable[key]; 89 StorageService.setStorage(this); 90 }, 91 92 clear: function(now) 93 { 94 this.objectTable = {}; 95 StorageService.setStorage(this, now); 96 } 97 }; 98 99 // ********************************************************************************************* // 100 101 /** 102 * var store = StorageService.getStorage(leafName); 103 * store.setItem("foo", bar); // writes to disk 104 */ 105 var StorageService = 106 { 107 getStorage: function(leafName) 108 { 109 var store = new Storage(leafName); 110 111 try 112 { 113 var obj = ObjectPersister.readObject(leafName); 114 if (obj) 115 store.objectTable = obj; 116 } 117 catch (err) 118 { 119 if (FBTrace.DBG_ERRORS || FBTrace.DBG_STORAGE) 120 { 121 FBTrace.sysout("StorageService.getStorage; EXCEPTION for " + leafName + 122 ": " + err, err); 123 } 124 } 125 126 return store; 127 }, 128 129 setStorage: function(store, now) 130 { 131 if (!store || !store.leafName || !store.objectTable) 132 throw new Error("StorageService.setStorage requires Storage Object argument"); 133 134 if (now) 135 ObjectPersister.writeNow(store.leafName, store.objectTable); 136 else 137 ObjectPersister.writeObject(store.leafName, store.objectTable); 138 }, 139 140 removeStorage: function(leafName) 141 { 142 ObjectPersister.deleteObject(leafname); 143 } 144 }; 145 146 // ********************************************************************************************* // 147 // Implementation 148 149 /** 150 * @class Represents an internal Firebug persistence service. 151 */ 152 var ObjectPersister = 153 { 154 readObject: function(leafName) 155 { 156 if (FBTrace.DBG_STORAGE) 157 FBTrace.sysout("ObjectPersister read from leafName "+leafName); 158 159 var file = FileUtils.getFile("ProfD", ["firebug", leafName]); 160 161 if (!file.exists()) 162 { 163 file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0666); 164 if (FBTrace.DBG_STORAGE) 165 FBTrace.sysout("ObjectPersister.readTextFromFile file created " + file.path); 166 return; 167 } 168 169 var obj = this.readObjectFromFile(file); 170 return obj; 171 }, 172 173 readObjectFromFile: function(file) 174 { 175 var text = ObjectPersister.readTextFromFile(file); 176 if (!text) 177 return null; 178 179 var obj = JSON.parse(text); 180 if (!obj) 181 return; 182 183 if (FBTrace.DBG_STORAGE) 184 FBTrace.sysout("PersistedObject loaded from " + file.path+" got text "+text, obj); 185 186 return obj; 187 }, 188 189 readTextFromFile: function(file) 190 { 191 try 192 { 193 var inputStream = Cc["@mozilla.org/network/file-input-stream;1"] 194 .createInstance(Ci.nsIFileInputStream); 195 var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"] 196 .createInstance(Ci.nsIConverterInputStream); 197 198 // Initialize input stream. 199 inputStream.init(file, 0x01 | 0x08, 0666, 0); // read, create 200 cstream.init(inputStream, "UTF-8", 0, 0); 201 202 // Load json. 203 var json = ""; 204 var data = {}; 205 while (cstream.readString(-1, data) != 0) 206 json += data.value; 207 208 inputStream.close(); 209 210 return json; 211 } 212 catch (err) 213 { 214 if (FBTrace.DBG_ERRORS || FBTrace.DBG_STORAGE) 215 FBTrace.sysout("ObjectPersister.initialize; EXCEPTION", err); 216 } 217 }, 218 219 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // 220 221 // Batch the writes for each event loop 222 writeDelay: 250, 223 224 writeObject: function(leafName, obj) 225 { 226 if (this.isPrivateBrowsing()) 227 throw new Error("No storage is written while in private browsing mode"); 228 229 if (ObjectPersister.flushTimeout) 230 return; 231 232 ObjectPersister.flushTimeout = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 233 234 var writeOnTimeout = ObjectPersister.getWriteOnTimeout(leafName, obj); 235 ObjectPersister.flushTimeout.init(writeOnTimeout, ObjectPersister.writeDelay, 236 Ci.nsITimer.TYPE_ONE_SHOT); 237 }, 238 239 getWriteOnTimeout: function(leafName, obj) 240 { 241 var writerClosure = 242 { 243 leafName: leafName, 244 obj: obj, 245 observe: function(timer, topic, data) 246 { 247 ObjectPersister.writeNow(writerClosure.leafName, writerClosure.obj); 248 delete ObjectPersister.flushTimeout; 249 } 250 }; 251 return writerClosure; 252 }, 253 254 writeNow: function(leafName, obj) 255 { 256 try 257 { 258 // Convert data to JSON. 259 var jsonString = JSON.stringify(obj); 260 var file = FileUtils.getFile("ProfD", ["firebug", leafName]); 261 ObjectPersister.writeTextToFile(file, jsonString); 262 } 263 catch(exc) 264 { 265 if (FBTrace.DBG_ERRORS || FBTrace.DBG_STORAGE) 266 FBTrace.sysout("ObjectPersister.writeNow; EXCEPTION for " + leafName + ": " + 267 exc, {exception: exc, obj: obj}); 268 } 269 }, 270 271 writeTextToFile: function(file, string) 272 { 273 try 274 { 275 // Initialize output stream. 276 var outputStream = Cc["@mozilla.org/network/file-output-stream;1"] 277 .createInstance(Ci.nsIFileOutputStream); 278 outputStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); // write, create, truncate 279 280 // Store JSON 281 outputStream.write(string, string.length); 282 outputStream.close(); 283 284 if (FBTrace.DBG_STORAGE) 285 FBTrace.sysout("ObjectPersister.writeNow to " + file.path, string); 286 287 return file.path; 288 } 289 catch (err) 290 { 291 if (FBTrace.DBG_ERRORS || FBTrace.DBG_STORAGE) 292 FBTrace.sysout("ObjectPersister.writeTextToFile; EXCEPTION for " + file.path + 293 ": "+err, {exception: err, string: string}); 294 } 295 }, 296 297 isPrivateBrowsing: function() 298 { 299 try 300 { 301 // Unfortunatelly the "firebug/chrome/privacy" module can't be used 302 // since this scope is JavaScript code module. 303 // xxxHonza: storageService should be converted into AMD (but it's used 304 // in firebug-service, which is also JS code module). 305 var pbs = Components.classes["@mozilla.org/privatebrowsing;1"] 306 .getService(Components.interfaces.nsIPrivateBrowsingService); 307 return pbs.privateBrowsingEnabled; 308 } 309 catch (e) 310 { 311 } 312 313 return false; 314 } 315 }; 316 317 // ********************************************************************************************* // 318 319 var TextService = 320 { 321 readText: ObjectPersister.readTextFromFile, 322 writeText: ObjectPersister.writeTextToFile 323 }; 324 325 // ********************************************************************************************* // 326