mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-27 02:43:07 +00:00
1407 lines
44 KiB
JavaScript
1407 lines
44 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 Ci = Components.interfaces;
|
|
const Cc = Components.classes;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
const CACHE_MAX_GROUP_ENTRIES = 100;
|
|
|
|
|
|
// We have a whitelist for getting/setting. This is because
|
|
// there are potential privacy issues with a compromised
|
|
// content process checking the user's content preferences
|
|
// and using that to discover all the websites visited, etc.
|
|
// Also there are both potential race conditions (if two processes
|
|
// set more than one value in succession, and the values
|
|
// only make sense together), as well as security issues, if
|
|
// a compromised content process can send arbitrary setPref
|
|
// messages. The whitelist contains only those settings that
|
|
// are not at risk for either.
|
|
// We currently whitelist saving/reading the last directory of file
|
|
// uploads, and the last current spellchecker dictionary which are so far
|
|
// the only need we have identified.
|
|
const REMOTE_WHITELIST = [
|
|
"browser.upload.lastDir",
|
|
"spellcheck.lang",
|
|
];
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
/**
|
|
* Remotes the service. All the remoting/electrolysis code is in here,
|
|
* so the regular service code below remains uncluttered and maintainable.
|
|
*/
|
|
function electrolify(service) {
|
|
// FIXME: For now, use the wrappedJSObject hack, until bug
|
|
// 593407 which will clean that up.
|
|
// Note that we also use this in the xpcshell tests, separately.
|
|
service.wrappedJSObject = service;
|
|
|
|
var appInfo = Cc["@mozilla.org/xre/app-info;1"];
|
|
if (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType ==
|
|
Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
|
|
// Parent process
|
|
|
|
service.messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"].
|
|
getService(Ci.nsIMessageBroadcaster);
|
|
|
|
// Setup listener for child messages. We don't need to call
|
|
// addMessageListener as the wakeup service will do that for us.
|
|
service.receiveMessage = function(aMessage) {
|
|
var json = aMessage.json;
|
|
|
|
if (REMOTE_WHITELIST.indexOf(json.name) == -1)
|
|
return { succeeded: false };
|
|
|
|
switch (aMessage.name) {
|
|
case "ContentPref:getPref":
|
|
return { succeeded: true,
|
|
value: service.getPref(json.group, json.name, json.value) };
|
|
|
|
case "ContentPref:setPref":
|
|
service.setPref(json.group, json.name, json.value);
|
|
return { succeeded: true };
|
|
}
|
|
};
|
|
} else {
|
|
// Child process
|
|
|
|
service._dbInit = function(){}; // No local DB
|
|
|
|
service.messageManager = Cc["@mozilla.org/childprocessmessagemanager;1"].
|
|
getService(Ci.nsISyncMessageSender);
|
|
|
|
// Child method remoting
|
|
[
|
|
['getPref', ['group', 'name'], ['_parseGroupParam']],
|
|
['setPref', ['group', 'name', 'value'], ['_parseGroupParam']],
|
|
].forEach(function(data) {
|
|
var method = data[0];
|
|
var params = data[1];
|
|
var parsers = data[2];
|
|
service[method] = function __remoted__() {
|
|
var json = {};
|
|
for (var i = 0; i < params.length; i++) {
|
|
if (params[i]) {
|
|
json[params[i]] = arguments[i];
|
|
if (parsers[i])
|
|
json[params[i]] = this[parsers[i]](json[params[i]]);
|
|
}
|
|
}
|
|
var ret = service.messageManager.sendSyncMessage('ContentPref:' + method, json)[0];
|
|
if (!ret.succeeded)
|
|
throw "ContentPrefs remoting failed to pass whitelist";
|
|
return ret.value;
|
|
};
|
|
});
|
|
|
|
// Listen to preference change notifications from the parent and notify
|
|
// observers in the child process according to the change
|
|
service.messageManager.addMessageListener("ContentPref:notifyPrefSet",
|
|
function(aMessage) {
|
|
var json = aMessage.json;
|
|
service._notifyPrefSet(json.group, json.name, json.value);
|
|
});
|
|
service.messageManager.addMessageListener("ContentPref:notifyPrefRemoved",
|
|
function(aMessage) {
|
|
var json = aMessage.json;
|
|
service._notifyPrefRemoved(json.group, json.name);
|
|
});
|
|
}
|
|
}
|
|
|
|
function ContentPrefService() {
|
|
electrolify(this);
|
|
|
|
// If this throws an exception, it causes the getService call to fail,
|
|
// but the next time a consumer tries to retrieve the service, we'll try
|
|
// to initialize the database again, which might work if the failure
|
|
// was due to a temporary condition (like being out of disk space).
|
|
this._dbInit();
|
|
|
|
this._observerSvc.addObserver(this, "last-pb-context-exited", false);
|
|
|
|
// Observe shutdown so we can shut down the database connection.
|
|
this._observerSvc.addObserver(this, "xpcom-shutdown", false);
|
|
}
|
|
|
|
Cu.import("resource://gre/modules/ContentPrefStore.jsm");
|
|
const cache = new ContentPrefStore();
|
|
cache.set = function CPS_cache_set(group, name, val) {
|
|
Object.getPrototypeOf(this).set.apply(this, arguments);
|
|
let groupCount = Object.keys(this._groups).length;
|
|
if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
|
|
// Clean half of the entries
|
|
for (let [group, name, ] in this) {
|
|
this.remove(group, name);
|
|
groupCount--;
|
|
if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
const privModeStorage = new ContentPrefStore();
|
|
|
|
ContentPrefService.prototype = {
|
|
//**************************************************************************//
|
|
// XPCOM Plumbing
|
|
|
|
classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),
|
|
|
|
QueryInterface: function CPS_QueryInterface(iid) {
|
|
let supportedIIDs = [
|
|
Ci.nsIContentPrefService,
|
|
Ci.nsIFrameMessageListener,
|
|
Ci.nsISupports,
|
|
];
|
|
if (supportedIIDs.some(function (i) iid.equals(i)))
|
|
return this;
|
|
if (iid.equals(Ci.nsIContentPrefService2)) {
|
|
if (!this._contentPrefService2) {
|
|
let s = {};
|
|
Cu.import("resource://gre/modules/ContentPrefService2.jsm", s);
|
|
this._contentPrefService2 = new s.ContentPrefService2(this);
|
|
}
|
|
return this._contentPrefService2;
|
|
}
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
},
|
|
|
|
//**************************************************************************//
|
|
// Convenience Getters
|
|
|
|
// Observer Service
|
|
__observerSvc: null,
|
|
get _observerSvc() {
|
|
if (!this.__observerSvc)
|
|
this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
|
|
getService(Ci.nsIObserverService);
|
|
return this.__observerSvc;
|
|
},
|
|
|
|
// Console Service
|
|
__consoleSvc: null,
|
|
get _consoleSvc() {
|
|
if (!this.__consoleSvc)
|
|
this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
|
|
getService(Ci.nsIConsoleService);
|
|
return this.__consoleSvc;
|
|
},
|
|
|
|
// Preferences Service
|
|
__prefSvc: null,
|
|
get _prefSvc() {
|
|
if (!this.__prefSvc)
|
|
this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
|
|
getService(Ci.nsIPrefBranch);
|
|
return this.__prefSvc;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Destruction
|
|
|
|
_destroy: function ContentPrefService__destroy() {
|
|
this._observerSvc.removeObserver(this, "xpcom-shutdown");
|
|
this._observerSvc.removeObserver(this, "last-pb-context-exited");
|
|
|
|
// Finalize statements which may have been used asynchronously.
|
|
// FIXME(696499): put them in an object cache like other components.
|
|
if (this.__stmtSelectPrefID) {
|
|
this.__stmtSelectPrefID.finalize();
|
|
this.__stmtSelectPrefID = null;
|
|
}
|
|
if (this.__stmtSelectGlobalPrefID) {
|
|
this.__stmtSelectGlobalPrefID.finalize();
|
|
this.__stmtSelectGlobalPrefID = null;
|
|
}
|
|
if (this.__stmtInsertPref) {
|
|
this.__stmtInsertPref.finalize();
|
|
this.__stmtInsertPref = null;
|
|
}
|
|
if (this.__stmtInsertGroup) {
|
|
this.__stmtInsertGroup.finalize();
|
|
this.__stmtInsertGroup = null;
|
|
}
|
|
if (this.__stmtInsertSetting) {
|
|
this.__stmtInsertSetting.finalize();
|
|
this.__stmtInsertSetting = null;
|
|
}
|
|
if (this.__stmtSelectGroupID) {
|
|
this.__stmtSelectGroupID.finalize();
|
|
this.__stmtSelectGroupID = null;
|
|
}
|
|
if (this.__stmtSelectSettingID) {
|
|
this.__stmtSelectSettingID.finalize();
|
|
this.__stmtSelectSettingID = null;
|
|
}
|
|
if (this.__stmtSelectPref) {
|
|
this.__stmtSelectPref.finalize();
|
|
this.__stmtSelectPref = null;
|
|
}
|
|
if (this.__stmtSelectGlobalPref) {
|
|
this.__stmtSelectGlobalPref.finalize();
|
|
this.__stmtSelectGlobalPref = null;
|
|
}
|
|
if (this.__stmtSelectPrefsByName) {
|
|
this.__stmtSelectPrefsByName.finalize();
|
|
this.__stmtSelectPrefsByName = null;
|
|
}
|
|
if (this.__stmtDeleteSettingIfUnused) {
|
|
this.__stmtDeleteSettingIfUnused.finalize();
|
|
this.__stmtDeleteSettingIfUnused = null;
|
|
}
|
|
if(this.__stmtSelectPrefs) {
|
|
this.__stmtSelectPrefs.finalize();
|
|
this.__stmtSelectPrefs = null;
|
|
}
|
|
if(this.__stmtDeleteGroupIfUnused) {
|
|
this.__stmtDeleteGroupIfUnused.finalize();
|
|
this.__stmtDeleteGroupIfUnused = null;
|
|
}
|
|
if (this.__stmtDeletePref) {
|
|
this.__stmtDeletePref.finalize();
|
|
this.__stmtDeletePref = null;
|
|
}
|
|
if (this.__stmtUpdatePref) {
|
|
this.__stmtUpdatePref.finalize();
|
|
this.__stmtUpdatePref = null;
|
|
}
|
|
|
|
if (this._contentPrefService2)
|
|
this._contentPrefService2.destroy();
|
|
|
|
this._dbConnection.asyncClose();
|
|
|
|
// Delete references to XPCOM components to make sure we don't leak them
|
|
// (although we haven't observed leakage in tests). Also delete references
|
|
// in _observers and _genericObservers to avoid cycles with those that
|
|
// refer to us and don't remove themselves from those observer pools.
|
|
for (var i in this) {
|
|
try { this[i] = null }
|
|
// Ignore "setting a property that has only a getter" exceptions.
|
|
catch(ex) {}
|
|
}
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// nsIObserver
|
|
|
|
observe: function ContentPrefService_observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case "xpcom-shutdown":
|
|
this._destroy();
|
|
break;
|
|
case "last-pb-context-exited":
|
|
this._privModeStorage.removeAll();
|
|
break;
|
|
}
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// in-memory cache and private-browsing stores
|
|
|
|
_cache: cache,
|
|
_privModeStorage: privModeStorage,
|
|
|
|
//**************************************************************************//
|
|
// nsIContentPrefService
|
|
|
|
getPref: function ContentPrefService_getPref(aGroup, aName, aContext, aCallback) {
|
|
if (!aName)
|
|
throw Components.Exception("aName cannot be null or an empty string",
|
|
Cr.NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
var group = this._parseGroupParam(aGroup);
|
|
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
if (this._privModeStorage.has(group, aName)) {
|
|
let value = this._privModeStorage.get(group, aName);
|
|
if (aCallback) {
|
|
this._scheduleCallback(function(){aCallback.onResult(value);});
|
|
return;
|
|
}
|
|
return value;
|
|
}
|
|
// if we don't have a pref specific to this private mode browsing
|
|
// session, to try to get one from normal mode
|
|
}
|
|
|
|
if (group == null)
|
|
return this._selectGlobalPref(aName, aCallback);
|
|
return this._selectPref(group, aName, aCallback);
|
|
},
|
|
|
|
setPref: function ContentPrefService_setPref(aGroup, aName, aValue, aContext) {
|
|
// If the pref is already set to the value, there's nothing more to do.
|
|
var currentValue = this.getPref(aGroup, aName, aContext);
|
|
if (typeof currentValue != "undefined") {
|
|
if (currentValue == aValue)
|
|
return;
|
|
}
|
|
|
|
var group = this._parseGroupParam(aGroup);
|
|
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
this._privModeStorage.setWithCast(group, aName, aValue);
|
|
this._broadcastPrefSet(group, aName, aValue);
|
|
return;
|
|
}
|
|
|
|
var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
|
|
var groupID, prefID;
|
|
if (group == null) {
|
|
groupID = null;
|
|
prefID = this._selectGlobalPrefID(settingID);
|
|
}
|
|
else {
|
|
groupID = this._selectGroupID(group) || this._insertGroup(group);
|
|
prefID = this._selectPrefID(groupID, settingID);
|
|
}
|
|
|
|
// Update the existing record, if any, or create a new one.
|
|
if (prefID)
|
|
this._updatePref(prefID, aValue);
|
|
else
|
|
this._insertPref(groupID, settingID, aValue);
|
|
|
|
this._cache.setWithCast(group, aName, aValue);
|
|
this._broadcastPrefSet(group, aName, aValue);
|
|
},
|
|
|
|
hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) {
|
|
// XXX If consumers end up calling this method regularly, then we should
|
|
// optimize this to query the database directly.
|
|
return (typeof this.getPref(aGroup, aName, aContext) != "undefined");
|
|
},
|
|
|
|
hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName, aContext) {
|
|
if (!aName)
|
|
throw Components.Exception("aName cannot be null or an empty string",
|
|
Cr.NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
let group = this._parseGroupParam(aGroup);
|
|
let storage = aContext && aContext.usePrivateBrowsing ? this._privModeStorage: this._cache;
|
|
return storage.has(group, aName);
|
|
},
|
|
|
|
removePref: function ContentPrefService_removePref(aGroup, aName, aContext) {
|
|
// If there's no old value, then there's nothing to remove.
|
|
if (!this.hasPref(aGroup, aName, aContext))
|
|
return;
|
|
|
|
var group = this._parseGroupParam(aGroup);
|
|
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
this._privModeStorage.remove(group, aName);
|
|
this._broadcastPrefRemoved(group, aName);
|
|
return;
|
|
}
|
|
|
|
var settingID = this._selectSettingID(aName);
|
|
var groupID, prefID;
|
|
if (group == null) {
|
|
groupID = null;
|
|
prefID = this._selectGlobalPrefID(settingID);
|
|
}
|
|
else {
|
|
groupID = this._selectGroupID(group);
|
|
prefID = this._selectPrefID(groupID, settingID);
|
|
}
|
|
|
|
this._deletePref(prefID);
|
|
|
|
// Get rid of extraneous records that are no longer being used.
|
|
this._deleteSettingIfUnused(settingID);
|
|
if (groupID)
|
|
this._deleteGroupIfUnused(groupID);
|
|
|
|
this._cache.remove(group, aName);
|
|
this._broadcastPrefRemoved(group, aName);
|
|
},
|
|
|
|
removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) {
|
|
// will not delete global preferences
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
// keep only global prefs
|
|
this._privModeStorage.removeGrouped();
|
|
}
|
|
this._cache.removeGrouped();
|
|
this._dbConnection.beginTransaction();
|
|
try {
|
|
this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
|
|
this._dbConnection.executeSimpleSQL("DELETE FROM groups");
|
|
this._dbConnection.executeSimpleSQL(
|
|
"DELETE FROM settings " +
|
|
"WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
|
|
);
|
|
this._dbConnection.commitTransaction();
|
|
}
|
|
catch(ex) {
|
|
this._dbConnection.rollbackTransaction();
|
|
throw ex;
|
|
}
|
|
},
|
|
|
|
removePrefsByName: function ContentPrefService_removePrefsByName(aName, aContext) {
|
|
if (!aName)
|
|
throw Components.Exception("aName cannot be null or an empty string",
|
|
Cr.NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
for (let [group, name, ] in this._privModeStorage) {
|
|
if (name === aName) {
|
|
this._privModeStorage.remove(group, aName);
|
|
this._broadcastPrefRemoved(group, aName);
|
|
}
|
|
}
|
|
}
|
|
|
|
var settingID = this._selectSettingID(aName);
|
|
if (!settingID)
|
|
return;
|
|
|
|
var selectGroupsStmt = this._dbCreateStatement(
|
|
"SELECT groups.id AS groupID, groups.name AS groupName " +
|
|
"FROM prefs " +
|
|
"JOIN groups ON prefs.groupID = groups.id " +
|
|
"WHERE prefs.settingID = :setting "
|
|
);
|
|
|
|
var groupNames = [];
|
|
var groupIDs = [];
|
|
try {
|
|
selectGroupsStmt.params.setting = settingID;
|
|
|
|
while (selectGroupsStmt.executeStep()) {
|
|
groupIDs.push(selectGroupsStmt.row["groupID"]);
|
|
groupNames.push(selectGroupsStmt.row["groupName"]);
|
|
}
|
|
}
|
|
finally {
|
|
selectGroupsStmt.reset();
|
|
}
|
|
|
|
if (this.hasPref(null, aName)) {
|
|
groupNames.push(null);
|
|
}
|
|
|
|
this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
|
|
this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);
|
|
|
|
for (var i = 0; i < groupNames.length; i++) {
|
|
this._cache.remove(groupNames[i], aName);
|
|
if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
|
|
this._deleteGroupIfUnused(groupIDs[i]);
|
|
if (!aContext || !aContext.usePrivateBrowsing) {
|
|
this._broadcastPrefRemoved(groupNames[i], aName);
|
|
}
|
|
}
|
|
},
|
|
|
|
getPrefs: function ContentPrefService_getPrefs(aGroup, aContext) {
|
|
var group = this._parseGroupParam(aGroup);
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
let prefs = Cc["@mozilla.org/hash-property-bag;1"].
|
|
createInstance(Ci.nsIWritablePropertyBag);
|
|
for (let [sgroup, sname, sval] in this._privModeStorage) {
|
|
if (sgroup === group)
|
|
prefs.setProperty(sname, sval);
|
|
}
|
|
return prefs;
|
|
}
|
|
|
|
if (group == null)
|
|
return this._selectGlobalPrefs();
|
|
return this._selectPrefs(group);
|
|
},
|
|
|
|
getPrefsByName: function ContentPrefService_getPrefsByName(aName, aContext) {
|
|
if (!aName)
|
|
throw Components.Exception("aName cannot be null or an empty string",
|
|
Cr.NS_ERROR_ILLEGAL_VALUE);
|
|
|
|
if (aContext && aContext.usePrivateBrowsing) {
|
|
let prefs = Cc["@mozilla.org/hash-property-bag;1"].
|
|
createInstance(Ci.nsIWritablePropertyBag);
|
|
for (let [sgroup, sname, sval] in this._privModeStorage) {
|
|
if (sname === aName)
|
|
prefs.setProperty(sgroup, sval);
|
|
}
|
|
return prefs;
|
|
}
|
|
|
|
return this._selectPrefsByName(aName);
|
|
},
|
|
|
|
// A hash of arrays of observers, indexed by setting name.
|
|
_observers: {},
|
|
|
|
// An array of generic observers, which observe all settings.
|
|
_genericObservers: [],
|
|
|
|
addObserver: function ContentPrefService_addObserver(aName, aObserver) {
|
|
var observers;
|
|
if (aName) {
|
|
if (!this._observers[aName])
|
|
this._observers[aName] = [];
|
|
observers = this._observers[aName];
|
|
}
|
|
else
|
|
observers = this._genericObservers;
|
|
|
|
if (observers.indexOf(aObserver) == -1)
|
|
observers.push(aObserver);
|
|
},
|
|
|
|
removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
|
|
var observers;
|
|
if (aName) {
|
|
if (!this._observers[aName])
|
|
return;
|
|
observers = this._observers[aName];
|
|
}
|
|
else
|
|
observers = this._genericObservers;
|
|
|
|
if (observers.indexOf(aObserver) != -1)
|
|
observers.splice(observers.indexOf(aObserver), 1);
|
|
},
|
|
|
|
/**
|
|
* Construct a list of observers to notify about a change to some setting,
|
|
* putting setting-specific observers before before generic ones, so observers
|
|
* that initialize individual settings (like the page style controller)
|
|
* execute before observers that display multiple settings and depend on them
|
|
* being initialized first (like the content prefs sidebar).
|
|
*/
|
|
_getObservers: function ContentPrefService__getObservers(aName) {
|
|
var observers = [];
|
|
|
|
if (aName && this._observers[aName])
|
|
observers = observers.concat(this._observers[aName]);
|
|
observers = observers.concat(this._genericObservers);
|
|
|
|
return observers;
|
|
},
|
|
|
|
/**
|
|
* Notify all observers about the removal of a preference.
|
|
*/
|
|
_notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName) {
|
|
for each (var observer in this._getObservers(aName)) {
|
|
try {
|
|
observer.onContentPrefRemoved(aGroup, aName);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Notify all observers about a preference change.
|
|
*/
|
|
_notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue) {
|
|
for each (var observer in this._getObservers(aName)) {
|
|
try {
|
|
observer.onContentPrefSet(aGroup, aName, aValue);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Notify all observers in the current process about the removal of a
|
|
* preference and send a message to all other processes so that they can in
|
|
* turn notify their observers about the change. This is meant to be called
|
|
* only in the parent process. Only whitelisted preferences are broadcast to
|
|
* the child processes.
|
|
*/
|
|
_broadcastPrefRemoved: function ContentPrefService__broadcastPrefRemoved(aGroup, aName) {
|
|
this._notifyPrefRemoved(aGroup, aName);
|
|
|
|
if (REMOTE_WHITELIST.indexOf(aName) != -1) {
|
|
this.messageManager.broadcastAsyncMessage('ContentPref:notifyPrefRemoved',
|
|
{ "group": aGroup, "name": aName } );
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Notify all observers in the current process about a preference change and
|
|
* send a message to all other processes so that they can in turn notify
|
|
* their observers about the change. This is meant to be called only in the
|
|
* parent process. Only whitelisted preferences are broadcast to the child
|
|
* processes.
|
|
*/
|
|
_broadcastPrefSet: function ContentPrefService__broadcastPrefSet(aGroup, aName, aValue) {
|
|
this._notifyPrefSet(aGroup, aName, aValue);
|
|
|
|
if (REMOTE_WHITELIST.indexOf(aName) != -1) {
|
|
this.messageManager.broadcastAsyncMessage('ContentPref:notifyPrefSet',
|
|
{ "group": aGroup, "name": aName, "value": aValue } );
|
|
}
|
|
},
|
|
|
|
_grouper: null,
|
|
get grouper() {
|
|
if (!this._grouper)
|
|
this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
|
|
getService(Ci.nsIContentURIGrouper);
|
|
return this._grouper;
|
|
},
|
|
|
|
get DBConnection() {
|
|
return this._dbConnection;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Data Retrieval & Modification
|
|
|
|
__stmtSelectPref: null,
|
|
get _stmtSelectPref() {
|
|
if (!this.__stmtSelectPref)
|
|
this.__stmtSelectPref = this._dbCreateStatement(
|
|
"SELECT prefs.value AS value " +
|
|
"FROM prefs " +
|
|
"JOIN groups ON prefs.groupID = groups.id " +
|
|
"JOIN settings ON prefs.settingID = settings.id " +
|
|
"WHERE groups.name = :group " +
|
|
"AND settings.name = :setting"
|
|
);
|
|
|
|
return this.__stmtSelectPref;
|
|
},
|
|
|
|
_scheduleCallback: function(func) {
|
|
let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
|
|
tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
|
|
},
|
|
|
|
_selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
|
|
let value = undefined;
|
|
if (this._cache.has(aGroup, aSetting)) {
|
|
value = this._cache.get(aGroup, aSetting);
|
|
if (aCallback) {
|
|
this._scheduleCallback(function(){aCallback.onResult(value);});
|
|
return;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
try {
|
|
this._stmtSelectPref.params.group = aGroup;
|
|
this._stmtSelectPref.params.setting = aSetting;
|
|
|
|
if (aCallback) {
|
|
let cache = this._cache;
|
|
new AsyncStatement(this._stmtSelectPref).execute({onResult: function(aResult) {
|
|
cache.set(aGroup, aSetting, aResult);
|
|
aCallback.onResult(aResult);
|
|
}});
|
|
}
|
|
else {
|
|
if (this._stmtSelectPref.executeStep()) {
|
|
value = this._stmtSelectPref.row["value"];
|
|
}
|
|
this._cache.set(aGroup, aSetting, value);
|
|
}
|
|
}
|
|
finally {
|
|
this._stmtSelectPref.reset();
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
__stmtSelectGlobalPref: null,
|
|
get _stmtSelectGlobalPref() {
|
|
if (!this.__stmtSelectGlobalPref)
|
|
this.__stmtSelectGlobalPref = this._dbCreateStatement(
|
|
"SELECT prefs.value AS value " +
|
|
"FROM prefs " +
|
|
"JOIN settings ON prefs.settingID = settings.id " +
|
|
"WHERE prefs.groupID IS NULL " +
|
|
"AND settings.name = :name"
|
|
);
|
|
|
|
return this.__stmtSelectGlobalPref;
|
|
},
|
|
|
|
_selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
|
|
let value = undefined;
|
|
if (this._cache.has(null, aName)) {
|
|
value = this._cache.get(null, aName);
|
|
if (aCallback) {
|
|
this._scheduleCallback(function(){aCallback.onResult(value);});
|
|
return;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
try {
|
|
this._stmtSelectGlobalPref.params.name = aName;
|
|
|
|
if (aCallback) {
|
|
let cache = this._cache;
|
|
new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult: function(aResult) {
|
|
cache.set(null, aName, aResult);
|
|
aCallback.onResult(aResult);
|
|
}});
|
|
}
|
|
else {
|
|
if (this._stmtSelectGlobalPref.executeStep()) {
|
|
value = this._stmtSelectGlobalPref.row["value"];
|
|
}
|
|
this._cache.set(null, aName, value);
|
|
}
|
|
}
|
|
finally {
|
|
this._stmtSelectGlobalPref.reset();
|
|
}
|
|
|
|
return value;
|
|
},
|
|
|
|
__stmtSelectGroupID: null,
|
|
get _stmtSelectGroupID() {
|
|
if (!this.__stmtSelectGroupID)
|
|
this.__stmtSelectGroupID = this._dbCreateStatement(
|
|
"SELECT groups.id AS id " +
|
|
"FROM groups " +
|
|
"WHERE groups.name = :name "
|
|
);
|
|
|
|
return this.__stmtSelectGroupID;
|
|
},
|
|
|
|
_selectGroupID: function ContentPrefService__selectGroupID(aName) {
|
|
var id;
|
|
|
|
try {
|
|
this._stmtSelectGroupID.params.name = aName;
|
|
|
|
if (this._stmtSelectGroupID.executeStep())
|
|
id = this._stmtSelectGroupID.row["id"];
|
|
}
|
|
finally {
|
|
this._stmtSelectGroupID.reset();
|
|
}
|
|
|
|
return id;
|
|
},
|
|
|
|
__stmtInsertGroup: null,
|
|
get _stmtInsertGroup() {
|
|
if (!this.__stmtInsertGroup)
|
|
this.__stmtInsertGroup = this._dbCreateStatement(
|
|
"INSERT INTO groups (name) VALUES (:name)"
|
|
);
|
|
|
|
return this.__stmtInsertGroup;
|
|
},
|
|
|
|
_insertGroup: function ContentPrefService__insertGroup(aName) {
|
|
this._stmtInsertGroup.params.name = aName;
|
|
this._stmtInsertGroup.execute();
|
|
return this._dbConnection.lastInsertRowID;
|
|
},
|
|
|
|
__stmtSelectSettingID: null,
|
|
get _stmtSelectSettingID() {
|
|
if (!this.__stmtSelectSettingID)
|
|
this.__stmtSelectSettingID = this._dbCreateStatement(
|
|
"SELECT id FROM settings WHERE name = :name"
|
|
);
|
|
|
|
return this.__stmtSelectSettingID;
|
|
},
|
|
|
|
_selectSettingID: function ContentPrefService__selectSettingID(aName) {
|
|
var id;
|
|
|
|
try {
|
|
this._stmtSelectSettingID.params.name = aName;
|
|
|
|
if (this._stmtSelectSettingID.executeStep())
|
|
id = this._stmtSelectSettingID.row["id"];
|
|
}
|
|
finally {
|
|
this._stmtSelectSettingID.reset();
|
|
}
|
|
|
|
return id;
|
|
},
|
|
|
|
__stmtInsertSetting: null,
|
|
get _stmtInsertSetting() {
|
|
if (!this.__stmtInsertSetting)
|
|
this.__stmtInsertSetting = this._dbCreateStatement(
|
|
"INSERT INTO settings (name) VALUES (:name)"
|
|
);
|
|
|
|
return this.__stmtInsertSetting;
|
|
},
|
|
|
|
_insertSetting: function ContentPrefService__insertSetting(aName) {
|
|
this._stmtInsertSetting.params.name = aName;
|
|
this._stmtInsertSetting.execute();
|
|
return this._dbConnection.lastInsertRowID;
|
|
},
|
|
|
|
__stmtSelectPrefID: null,
|
|
get _stmtSelectPrefID() {
|
|
if (!this.__stmtSelectPrefID)
|
|
this.__stmtSelectPrefID = this._dbCreateStatement(
|
|
"SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
|
|
);
|
|
|
|
return this.__stmtSelectPrefID;
|
|
},
|
|
|
|
_selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
|
|
var id;
|
|
|
|
try {
|
|
this._stmtSelectPrefID.params.groupID = aGroupID;
|
|
this._stmtSelectPrefID.params.settingID = aSettingID;
|
|
|
|
if (this._stmtSelectPrefID.executeStep())
|
|
id = this._stmtSelectPrefID.row["id"];
|
|
}
|
|
finally {
|
|
this._stmtSelectPrefID.reset();
|
|
}
|
|
|
|
return id;
|
|
},
|
|
|
|
__stmtSelectGlobalPrefID: null,
|
|
get _stmtSelectGlobalPrefID() {
|
|
if (!this.__stmtSelectGlobalPrefID)
|
|
this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
|
|
"SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
|
|
);
|
|
|
|
return this.__stmtSelectGlobalPrefID;
|
|
},
|
|
|
|
_selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
|
|
var id;
|
|
|
|
try {
|
|
this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
|
|
|
|
if (this._stmtSelectGlobalPrefID.executeStep())
|
|
id = this._stmtSelectGlobalPrefID.row["id"];
|
|
}
|
|
finally {
|
|
this._stmtSelectGlobalPrefID.reset();
|
|
}
|
|
|
|
return id;
|
|
},
|
|
|
|
__stmtInsertPref: null,
|
|
get _stmtInsertPref() {
|
|
if (!this.__stmtInsertPref)
|
|
this.__stmtInsertPref = this._dbCreateStatement(
|
|
"INSERT INTO prefs (groupID, settingID, value) " +
|
|
"VALUES (:groupID, :settingID, :value)"
|
|
);
|
|
|
|
return this.__stmtInsertPref;
|
|
},
|
|
|
|
_insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
|
|
this._stmtInsertPref.params.groupID = aGroupID;
|
|
this._stmtInsertPref.params.settingID = aSettingID;
|
|
this._stmtInsertPref.params.value = aValue;
|
|
this._stmtInsertPref.execute();
|
|
return this._dbConnection.lastInsertRowID;
|
|
},
|
|
|
|
__stmtUpdatePref: null,
|
|
get _stmtUpdatePref() {
|
|
if (!this.__stmtUpdatePref)
|
|
this.__stmtUpdatePref = this._dbCreateStatement(
|
|
"UPDATE prefs SET value = :value WHERE id = :id"
|
|
);
|
|
|
|
return this.__stmtUpdatePref;
|
|
},
|
|
|
|
_updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
|
|
this._stmtUpdatePref.params.id = aPrefID;
|
|
this._stmtUpdatePref.params.value = aValue;
|
|
this._stmtUpdatePref.execute();
|
|
},
|
|
|
|
__stmtDeletePref: null,
|
|
get _stmtDeletePref() {
|
|
if (!this.__stmtDeletePref)
|
|
this.__stmtDeletePref = this._dbCreateStatement(
|
|
"DELETE FROM prefs WHERE id = :id"
|
|
);
|
|
|
|
return this.__stmtDeletePref;
|
|
},
|
|
|
|
_deletePref: function ContentPrefService__deletePref(aPrefID) {
|
|
this._stmtDeletePref.params.id = aPrefID;
|
|
this._stmtDeletePref.execute();
|
|
},
|
|
|
|
__stmtDeleteSettingIfUnused: null,
|
|
get _stmtDeleteSettingIfUnused() {
|
|
if (!this.__stmtDeleteSettingIfUnused)
|
|
this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(
|
|
"DELETE FROM settings WHERE id = :id " +
|
|
"AND id NOT IN (SELECT DISTINCT settingID FROM prefs)"
|
|
);
|
|
|
|
return this.__stmtDeleteSettingIfUnused;
|
|
},
|
|
|
|
_deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
|
|
this._stmtDeleteSettingIfUnused.params.id = aSettingID;
|
|
this._stmtDeleteSettingIfUnused.execute();
|
|
},
|
|
|
|
__stmtDeleteGroupIfUnused: null,
|
|
get _stmtDeleteGroupIfUnused() {
|
|
if (!this.__stmtDeleteGroupIfUnused)
|
|
this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(
|
|
"DELETE FROM groups WHERE id = :id " +
|
|
"AND id NOT IN (SELECT DISTINCT groupID FROM prefs)"
|
|
);
|
|
|
|
return this.__stmtDeleteGroupIfUnused;
|
|
},
|
|
|
|
_deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
|
|
this._stmtDeleteGroupIfUnused.params.id = aGroupID;
|
|
this._stmtDeleteGroupIfUnused.execute();
|
|
},
|
|
|
|
__stmtSelectPrefs: null,
|
|
get _stmtSelectPrefs() {
|
|
if (!this.__stmtSelectPrefs)
|
|
this.__stmtSelectPrefs = this._dbCreateStatement(
|
|
"SELECT settings.name AS name, prefs.value AS value " +
|
|
"FROM prefs " +
|
|
"JOIN groups ON prefs.groupID = groups.id " +
|
|
"JOIN settings ON prefs.settingID = settings.id " +
|
|
"WHERE groups.name = :group "
|
|
);
|
|
|
|
return this.__stmtSelectPrefs;
|
|
},
|
|
|
|
_selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
|
|
var prefs = Cc["@mozilla.org/hash-property-bag;1"].
|
|
createInstance(Ci.nsIWritablePropertyBag);
|
|
|
|
try {
|
|
this._stmtSelectPrefs.params.group = aGroup;
|
|
|
|
while (this._stmtSelectPrefs.executeStep())
|
|
prefs.setProperty(this._stmtSelectPrefs.row["name"],
|
|
this._stmtSelectPrefs.row["value"]);
|
|
}
|
|
finally {
|
|
this._stmtSelectPrefs.reset();
|
|
}
|
|
|
|
return prefs;
|
|
},
|
|
|
|
__stmtSelectGlobalPrefs: null,
|
|
get _stmtSelectGlobalPrefs() {
|
|
if (!this.__stmtSelectGlobalPrefs)
|
|
this.__stmtSelectGlobalPrefs = this._dbCreateStatement(
|
|
"SELECT settings.name AS name, prefs.value AS value " +
|
|
"FROM prefs " +
|
|
"JOIN settings ON prefs.settingID = settings.id " +
|
|
"WHERE prefs.groupID IS NULL"
|
|
);
|
|
|
|
return this.__stmtSelectGlobalPrefs;
|
|
},
|
|
|
|
_selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
|
|
var prefs = Cc["@mozilla.org/hash-property-bag;1"].
|
|
createInstance(Ci.nsIWritablePropertyBag);
|
|
|
|
try {
|
|
while (this._stmtSelectGlobalPrefs.executeStep())
|
|
prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
|
|
this._stmtSelectGlobalPrefs.row["value"]);
|
|
}
|
|
finally {
|
|
this._stmtSelectGlobalPrefs.reset();
|
|
}
|
|
|
|
return prefs;
|
|
},
|
|
|
|
__stmtSelectPrefsByName: null,
|
|
get _stmtSelectPrefsByName() {
|
|
if (!this.__stmtSelectPrefsByName)
|
|
this.__stmtSelectPrefsByName = this._dbCreateStatement(
|
|
"SELECT groups.name AS groupName, prefs.value AS value " +
|
|
"FROM prefs " +
|
|
"JOIN groups ON prefs.groupID = groups.id " +
|
|
"JOIN settings ON prefs.settingID = settings.id " +
|
|
"WHERE settings.name = :setting "
|
|
);
|
|
|
|
return this.__stmtSelectPrefsByName;
|
|
},
|
|
|
|
_selectPrefsByName: function ContentPrefService__selectPrefsByName(aName) {
|
|
var prefs = Cc["@mozilla.org/hash-property-bag;1"].
|
|
createInstance(Ci.nsIWritablePropertyBag);
|
|
|
|
try {
|
|
this._stmtSelectPrefsByName.params.setting = aName;
|
|
|
|
while (this._stmtSelectPrefsByName.executeStep())
|
|
prefs.setProperty(this._stmtSelectPrefsByName.row["groupName"],
|
|
this._stmtSelectPrefsByName.row["value"]);
|
|
}
|
|
finally {
|
|
this._stmtSelectPrefsByName.reset();
|
|
}
|
|
|
|
var global = this._selectGlobalPref(aName);
|
|
if (typeof global != "undefined") {
|
|
prefs.setProperty(null, global);
|
|
}
|
|
|
|
return prefs;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Database Creation & Access
|
|
|
|
_dbVersion: 3,
|
|
|
|
_dbSchema: {
|
|
tables: {
|
|
groups: "id INTEGER PRIMARY KEY, \
|
|
name TEXT NOT NULL",
|
|
|
|
settings: "id INTEGER PRIMARY KEY, \
|
|
name TEXT NOT NULL",
|
|
|
|
prefs: "id INTEGER PRIMARY KEY, \
|
|
groupID INTEGER REFERENCES groups(id), \
|
|
settingID INTEGER NOT NULL REFERENCES settings(id), \
|
|
value BLOB"
|
|
},
|
|
indices: {
|
|
groups_idx: {
|
|
table: "groups",
|
|
columns: ["name"]
|
|
},
|
|
settings_idx: {
|
|
table: "settings",
|
|
columns: ["name"]
|
|
},
|
|
prefs_idx: {
|
|
table: "prefs",
|
|
columns: ["groupID", "settingID"]
|
|
}
|
|
}
|
|
},
|
|
|
|
_dbConnection: null,
|
|
|
|
_dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
|
|
try {
|
|
var statement = this._dbConnection.createStatement(aSQLString);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError("error creating statement " + aSQLString + ": " +
|
|
this._dbConnection.lastError + " - " +
|
|
this._dbConnection.lastErrorString);
|
|
throw ex;
|
|
}
|
|
|
|
return statement;
|
|
},
|
|
|
|
// _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
|
|
// specific migration methods) must be careful not to call any method
|
|
// of the service that assumes the database connection has already been
|
|
// initialized, since it won't be initialized until at the end of _dbInit.
|
|
|
|
_dbInit: function ContentPrefService__dbInit() {
|
|
var dirService = Cc["@mozilla.org/file/directory_service;1"].
|
|
getService(Ci.nsIProperties);
|
|
var dbFile = dirService.get("ProfD", Ci.nsIFile);
|
|
dbFile.append("content-prefs.sqlite");
|
|
|
|
var dbService = Cc["@mozilla.org/storage/service;1"].
|
|
getService(Ci.mozIStorageService);
|
|
|
|
var dbConnection;
|
|
|
|
if (!dbFile.exists())
|
|
dbConnection = this._dbCreate(dbService, dbFile);
|
|
else {
|
|
try {
|
|
dbConnection = dbService.openDatabase(dbFile);
|
|
}
|
|
// If the connection isn't ready after we open the database, that means
|
|
// the database has been corrupted, so we back it up and then recreate it.
|
|
catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
|
|
dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
|
|
dbConnection);
|
|
}
|
|
|
|
// Get the version of the schema in the file.
|
|
var version = dbConnection.schemaVersion;
|
|
|
|
// Try to migrate the schema in the database to the current schema used by
|
|
// the service. If migration fails, back up the database and recreate it.
|
|
if (version != this._dbVersion) {
|
|
try {
|
|
this._dbMigrate(dbConnection, version, this._dbVersion);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
|
|
dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Turn off disk synchronization checking to reduce disk churn and speed up
|
|
// operations when prefs are changed rapidly (such as when a user repeatedly
|
|
// changes the value of the browser zoom setting for a site).
|
|
//
|
|
// Note: this could cause database corruption if the OS crashes or machine
|
|
// loses power before the data gets written to disk, but this is considered
|
|
// a reasonable risk for the not-so-critical data stored in this database.
|
|
//
|
|
// If you really don't want to take this risk, however, just set the
|
|
// toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
|
|
// (FULL synchronization), in which case mozStorageConnection::Initialize
|
|
// will use that value, and we won't override it here.
|
|
if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
|
|
dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
|
|
|
|
this._dbConnection = dbConnection;
|
|
},
|
|
|
|
_dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
|
|
var dbConnection = aDBService.openDatabase(aDBFile);
|
|
|
|
try {
|
|
this._dbCreateSchema(dbConnection);
|
|
dbConnection.schemaVersion = this._dbVersion;
|
|
}
|
|
catch(ex) {
|
|
// If we failed to create the database (perhaps because the disk ran out
|
|
// of space), then remove the database file so we don't leave it in some
|
|
// half-created state from which we won't know how to recover.
|
|
dbConnection.close();
|
|
aDBFile.remove(false);
|
|
throw ex;
|
|
}
|
|
|
|
return dbConnection;
|
|
},
|
|
|
|
_dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
|
|
this._dbCreateTables(aDBConnection);
|
|
this._dbCreateIndices(aDBConnection);
|
|
},
|
|
|
|
_dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
|
|
for (let name in this._dbSchema.tables)
|
|
aDBConnection.createTable(name, this._dbSchema.tables[name]);
|
|
},
|
|
|
|
_dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
|
|
for (let name in this._dbSchema.indices) {
|
|
let index = this._dbSchema.indices[name];
|
|
let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
|
|
"(" + index.columns.join(", ") + ")";
|
|
aDBConnection.executeSimpleSQL(statement);
|
|
}
|
|
},
|
|
|
|
_dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
|
|
aDBFile,
|
|
aDBConnection) {
|
|
aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
|
|
|
|
// Close the database, ignoring the "already closed" exception, if any.
|
|
// It'll be open if we're here because of a migration failure but closed
|
|
// if we're here because of database corruption.
|
|
try { aDBConnection.close() } catch(ex) {}
|
|
|
|
aDBFile.remove(false);
|
|
|
|
let dbConnection = this._dbCreate(aDBService, aDBFile);
|
|
|
|
return dbConnection;
|
|
},
|
|
|
|
_dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
|
|
if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
|
|
aDBConnection.beginTransaction();
|
|
try {
|
|
this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
|
|
aDBConnection.schemaVersion = aNewVersion;
|
|
aDBConnection.commitTransaction();
|
|
}
|
|
catch(ex) {
|
|
aDBConnection.rollbackTransaction();
|
|
throw ex;
|
|
}
|
|
}
|
|
else
|
|
throw("no migrator function from version " + aOldVersion +
|
|
" to version " + aNewVersion);
|
|
},
|
|
|
|
/**
|
|
* If the schema version is 0, that means it was never set, which means
|
|
* the database was somehow created without the schema being applied, perhaps
|
|
* because the system ran out of disk space (although we check for this
|
|
* in _createDB) or because some other code created the database file without
|
|
* applying the schema. In any case, recover by simply reapplying the schema.
|
|
*/
|
|
_dbMigrate0To3: function ContentPrefService___dbMigrate0To3(aDBConnection) {
|
|
this._dbCreateSchema(aDBConnection);
|
|
},
|
|
|
|
_dbMigrate1To3: function ContentPrefService___dbMigrate1To3(aDBConnection) {
|
|
aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
|
|
aDBConnection.createTable("groups", this._dbSchema.tables.groups);
|
|
aDBConnection.executeSimpleSQL(
|
|
"INSERT INTO groups (id, name) " +
|
|
"SELECT id, name FROM groupsOld"
|
|
);
|
|
|
|
aDBConnection.executeSimpleSQL("DROP TABLE groupers");
|
|
aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
|
|
|
|
this._dbCreateIndices(aDBConnection);
|
|
},
|
|
|
|
_dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
|
|
this._dbCreateIndices(aDBConnection);
|
|
},
|
|
|
|
_parseGroupParam: function ContentPrefService__parseGroupParam(aGroup) {
|
|
if (aGroup == null)
|
|
return null;
|
|
if (aGroup.constructor.name == "String")
|
|
return aGroup.toString();
|
|
if (aGroup instanceof Ci.nsIURI)
|
|
return this.grouper.group(aGroup);
|
|
|
|
throw Components.Exception("aGroup is not a string, nsIURI or null",
|
|
Cr.NS_ERROR_ILLEGAL_VALUE);
|
|
},
|
|
};
|
|
|
|
|
|
function HostnameGrouper() {}
|
|
|
|
HostnameGrouper.prototype = {
|
|
//**************************************************************************//
|
|
// XPCOM Plumbing
|
|
|
|
classID: Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
|
|
|
|
//**************************************************************************//
|
|
// nsIContentURIGrouper
|
|
|
|
group: function HostnameGrouper_group(aURI) {
|
|
var group;
|
|
|
|
try {
|
|
// Accessing the host property of the URI will throw an exception
|
|
// if the URI is of a type that doesn't have a host property.
|
|
// Otherwise, we manually throw an exception if the host is empty,
|
|
// since the effect is the same (we can't derive a group from it).
|
|
|
|
group = aURI.host;
|
|
if (!group)
|
|
throw("can't derive group from host; no host in URI");
|
|
}
|
|
catch(ex) {
|
|
// If we don't have a host, then use the entire URI (minus the query,
|
|
// reference, and hash, if possible) as the group. This means that URIs
|
|
// like about:mozilla and about:blank will be considered separate groups,
|
|
// but at least they'll be grouped somehow.
|
|
|
|
// This also means that each individual file: URL will be considered
|
|
// its own group. This seems suboptimal, but so does treating the entire
|
|
// file: URL space as a single group (especially if folks start setting
|
|
// group-specific capabilities prefs).
|
|
|
|
// XXX Is there something better we can do here?
|
|
|
|
try {
|
|
var url = aURI.QueryInterface(Ci.nsIURL);
|
|
group = aURI.prePath + url.filePath;
|
|
}
|
|
catch(ex) {
|
|
group = aURI.spec;
|
|
}
|
|
}
|
|
|
|
return group;
|
|
}
|
|
};
|
|
|
|
function AsyncStatement(aStatement) {
|
|
this.stmt = aStatement;
|
|
}
|
|
|
|
AsyncStatement.prototype = {
|
|
execute: function AsyncStmt_execute(aCallback) {
|
|
let stmt = this.stmt;
|
|
stmt.executeAsync({
|
|
_callback: aCallback,
|
|
_hadResult: false,
|
|
handleResult: function(aResult) {
|
|
this._hadResult = true;
|
|
if (this._callback) {
|
|
let row = aResult.getNextRow();
|
|
this._callback.onResult(row.getResultByName("value"));
|
|
}
|
|
},
|
|
handleCompletion: function(aReason) {
|
|
if (!this._hadResult && this._callback &&
|
|
aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED)
|
|
this._callback.onResult(undefined);
|
|
},
|
|
handleError: function(aError) {}
|
|
});
|
|
}
|
|
};
|
|
|
|
//****************************************************************************//
|
|
// XPCOM Plumbing
|
|
|
|
var components = [ContentPrefService, HostnameGrouper];
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
|