mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 06:35:42 +00:00
337 lines
9.4 KiB
JavaScript
337 lines
9.4 KiB
JavaScript
/* 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/. */
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
// Enables logging and shorter save intervals.
|
|
const debugMode = false;
|
|
|
|
// Delay when a change is made to when the file is saved.
|
|
// 30 seconds normally, or 3 seconds for testing
|
|
const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000;
|
|
|
|
const XULSTORE_CONTRACTID = "@mozilla.org/xul/xulstore;1";
|
|
const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");
|
|
const STOREDB_FILENAME = "xulstore.json";
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/Task.jsm");
|
|
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
|
|
|
function XULStore() {
|
|
if (!Services.appinfo.inSafeMode)
|
|
this.load();
|
|
}
|
|
|
|
XULStore.prototype = {
|
|
classID: XULSTORE_CID,
|
|
classInfo: XPCOMUtils.generateCI({classID: XULSTORE_CID,
|
|
contractID: XULSTORE_CONTRACTID,
|
|
classDescription: "XULStore",
|
|
interfaces: [Ci.nsIXULStore]}),
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsIXULStore,
|
|
Ci.nsISupportsWeakReference]),
|
|
_xpcom_factory: XPCOMUtils.generateSingletonFactory(XULStore),
|
|
|
|
/* ---------- private members ---------- */
|
|
|
|
/*
|
|
* The format of _data is _data[docuri][elementid][attribute]. For example:
|
|
* {
|
|
* "chrome://blah/foo.xul" : {
|
|
* "main-window" : { aaa : 1, bbb : "c" },
|
|
* "barColumn" : { ddd : 9, eee : "f" },
|
|
* },
|
|
*
|
|
* "chrome://foopy/b.xul" : { ... },
|
|
* ...
|
|
* }
|
|
*/
|
|
_data: {},
|
|
_storeFile: null,
|
|
_needsSaving: false,
|
|
_saveAllowed: true,
|
|
_writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
|
|
|
|
load: function () {
|
|
Services.obs.addObserver(this, "profile-before-change", true);
|
|
|
|
this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
|
this._storeFile.append(STOREDB_FILENAME);
|
|
|
|
if (!this._storeFile.exists()) {
|
|
this.import();
|
|
} else {
|
|
this.readFile();
|
|
}
|
|
},
|
|
|
|
observe: function(subject, topic, data) {
|
|
this.writeFile();
|
|
if (topic == "profile-before-change") {
|
|
this._saveAllowed = false;
|
|
}
|
|
},
|
|
|
|
/*
|
|
* Internal function for logging debug messages to the Error Console window
|
|
*/
|
|
log: function (message) {
|
|
if (!debugMode)
|
|
return;
|
|
dump("XULStore: " + message + "\n");
|
|
Services.console.logStringMessage("XULStore: " + message);
|
|
},
|
|
|
|
import: function() {
|
|
let localStoreFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
|
|
|
|
localStoreFile.append("localstore.rdf");
|
|
if (!localStoreFile.exists()) {
|
|
return;
|
|
}
|
|
|
|
const RDF = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
|
|
const persistKey = RDF.GetResource("http://home.netscape.com/NC-rdf#persist");
|
|
|
|
this.log("Import localstore from " + localStoreFile.path);
|
|
|
|
let localStoreURI = Services.io.newFileURI(localStoreFile).spec;
|
|
let localStore = RDF.GetDataSourceBlocking(localStoreURI);
|
|
let resources = localStore.GetAllResources();
|
|
|
|
while (resources.hasMoreElements()) {
|
|
let resource = resources.getNext().QueryInterface(Ci.nsIRDFResource);
|
|
let uri;
|
|
|
|
try {
|
|
uri = NetUtil.newURI(resource.ValueUTF8);
|
|
} catch(ex) {
|
|
continue; // skip invalid uris
|
|
}
|
|
|
|
// If this has a ref, then this is an attribute reference. Otherwise,
|
|
// this is a document reference.
|
|
if (!uri.hasRef)
|
|
continue;
|
|
|
|
// Verify that there the persist key is connected up.
|
|
let docURI = uri.specIgnoringRef;
|
|
|
|
if (!localStore.HasAssertion(RDF.GetResource(docURI), persistKey, resource, true))
|
|
continue;
|
|
|
|
let id = uri.ref;
|
|
let attrs = localStore.ArcLabelsOut(resource);
|
|
|
|
while (attrs.hasMoreElements()) {
|
|
let attr = attrs.getNext().QueryInterface(Ci.nsIRDFResource);
|
|
let value = localStore.GetTarget(resource, attr, true);
|
|
|
|
if (value instanceof Ci.nsIRDFLiteral) {
|
|
this.setValue(docURI, id, attr.ValueUTF8, value.Value);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
readFile: function() {
|
|
const MODE_RDONLY = 0x01;
|
|
const FILE_PERMS = 0o600;
|
|
|
|
let stream = Cc["@mozilla.org/network/file-input-stream;1"].
|
|
createInstance(Ci.nsIFileInputStream);
|
|
let json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
|
|
try {
|
|
stream.init(this._storeFile, MODE_RDONLY, FILE_PERMS, 0);
|
|
this._data = json.decodeFromStream(stream, stream.available());
|
|
} catch(e) {
|
|
this.log("Error reading JSON: " + e);
|
|
// Ignore problem, we'll just continue on with an empty dataset.
|
|
} finally {
|
|
stream.close();
|
|
}
|
|
},
|
|
|
|
writeFile: Task.async(function* () {
|
|
if (!this._needsSaving)
|
|
return;
|
|
|
|
this._needsSaving = false;
|
|
|
|
this.log("Writing to xulstore.json");
|
|
|
|
try {
|
|
let data = JSON.stringify(this._data);
|
|
let encoder = new TextEncoder();
|
|
|
|
data = encoder.encode(data);
|
|
yield OS.File.writeAtomic(this._storeFile.path, data,
|
|
{ tmpPath: this._storeFile.path + ".tmp" });
|
|
} catch (e) {
|
|
this.log("Failed to write xulstore.json: " + e);
|
|
throw e;
|
|
}
|
|
}),
|
|
|
|
markAsChanged: function() {
|
|
if (this._needsSaving || !this._storeFile)
|
|
return;
|
|
|
|
// Don't write the file more than once every 30 seconds.
|
|
this._needsSaving = true;
|
|
this._writeTimer.init(this, WRITE_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
|
|
},
|
|
|
|
/* ---------- interface implementation ---------- */
|
|
|
|
setValue: function (docURI, id, attr, value) {
|
|
this.log("Saving " + attr + "=" + value + " for id=" + id + ", doc=" + docURI);
|
|
|
|
if (!this._saveAllowed) {
|
|
Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");
|
|
return;
|
|
}
|
|
|
|
// bug 319846 -- don't save really long attributes or values.
|
|
if (id.length > 512 || attr.length > 512) {
|
|
throw Components.Exception("id or attribute name too long", Cr.NS_ERROR_ILLEGAL_VALUE);
|
|
}
|
|
|
|
if (value.length > 4096) {
|
|
Services.console.logStringMessage("XULStore: Warning, truncating long attribute value")
|
|
value = value.substr(0, 4096);
|
|
}
|
|
|
|
let obj = this._data;
|
|
if (!(docURI in obj)) {
|
|
obj[docURI] = {};
|
|
}
|
|
obj = obj[docURI];
|
|
if (!(id in obj)) {
|
|
obj[id] = {};
|
|
}
|
|
obj = obj[id];
|
|
|
|
// Don't set the value if it is already set to avoid saving the file.
|
|
if (attr in obj && obj[attr] == value)
|
|
return;
|
|
|
|
obj[attr] = value; //IE, this._data[docURI][id][attr] = value;
|
|
|
|
this.markAsChanged();
|
|
},
|
|
|
|
hasValue: function (docURI, id, attr) {
|
|
this.log("has store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);
|
|
|
|
let ids = this._data[docURI];
|
|
if (ids) {
|
|
let attrs = ids[id];
|
|
if (attrs) {
|
|
return attr in attrs;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
getValue: function (docURI, id, attr) {
|
|
this.log("get store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);
|
|
|
|
let ids = this._data[docURI];
|
|
if (ids) {
|
|
let attrs = ids[id];
|
|
if (attrs) {
|
|
return attrs[attr] || "";
|
|
}
|
|
}
|
|
|
|
return "";
|
|
},
|
|
|
|
removeValue: function (docURI, id, attr) {
|
|
this.log("remove store value for id=" + id + ", attr=" + attr + ", doc=" + docURI);
|
|
|
|
if (!this._saveAllowed) {
|
|
Services.console.logStringMessage("XULStore: Changes after profile-before-change are ignored!");
|
|
return;
|
|
}
|
|
|
|
let ids = this._data[docURI];
|
|
if (ids) {
|
|
let attrs = ids[id];
|
|
if (attrs && attr in attrs) {
|
|
delete attrs[attr];
|
|
|
|
if (Object.getOwnPropertyNames(attrs).length == 0) {
|
|
delete ids[id];
|
|
|
|
if (Object.getOwnPropertyNames(ids).length == 0) {
|
|
delete this._data[docURI];
|
|
}
|
|
}
|
|
|
|
this.markAsChanged();
|
|
}
|
|
}
|
|
},
|
|
|
|
getIDsEnumerator: function (docURI) {
|
|
this.log("Getting ID enumerator for doc=" + docURI);
|
|
|
|
if (!(docURI in this._data))
|
|
return new nsStringEnumerator([]);
|
|
|
|
let result = [];
|
|
let ids = this._data[docURI];
|
|
if (ids) {
|
|
for (let id in this._data[docURI]) {
|
|
result.push(id);
|
|
}
|
|
}
|
|
|
|
return new nsStringEnumerator(result);
|
|
},
|
|
|
|
getAttributeEnumerator: function (docURI, id) {
|
|
this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI);
|
|
|
|
if (!(docURI in this._data) || !(id in this._data[docURI]))
|
|
return new nsStringEnumerator([]);
|
|
|
|
let attrs = [];
|
|
for (let attr in this._data[docURI][id]) {
|
|
attrs.push(attr);
|
|
}
|
|
|
|
return new nsStringEnumerator(attrs);
|
|
}
|
|
};
|
|
|
|
function nsStringEnumerator(items) {
|
|
this._items = items;
|
|
}
|
|
|
|
nsStringEnumerator.prototype = {
|
|
QueryInterface : XPCOMUtils.generateQI([Ci.nsIStringEnumerator]),
|
|
_nextIndex : 0,
|
|
hasMore: function() {
|
|
return this._nextIndex < this._items.length;
|
|
},
|
|
getNext : function() {
|
|
if (!this.hasMore())
|
|
throw Cr.NS_ERROR_NOT_AVAILABLE;
|
|
return this._items[this._nextIndex++];
|
|
},
|
|
};
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([XULStore]);
|