989 lines
31 KiB
JavaScript
Executable File
989 lines
31 KiB
JavaScript
Executable File
/* ***** BEGIN LICENSE BLOCK *****
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
*
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
* the License. You may obtain a copy of the License at
|
|
* http://www.mozilla.org/MPL/
|
|
*
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
* for the specific language governing rights and limitations under the
|
|
* License.
|
|
*
|
|
* The Original Code is Content Preferences (cpref).
|
|
*
|
|
* The Initial Developer of the Original Code is Mozilla.
|
|
* Portions created by the Initial Developer are Copyright (C) 2006
|
|
* the Initial Developer. All Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Myk Melez <myk@mozilla.org>
|
|
*
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
* the provisions above, a recipient may use your version of this file under
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
const Ci = Components.interfaces;
|
|
const Cc = Components.classes;
|
|
const Cr = Components.results;
|
|
const Cu = Components.utils;
|
|
|
|
function ContentPrefService() {}
|
|
|
|
ContentPrefService.prototype = {
|
|
//**************************************************************************//
|
|
// 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;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Initialization & Destruction
|
|
|
|
_init: function ContentPrefService__init() {
|
|
// 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();
|
|
|
|
// Observe shutdown so we can shut down the database connection.
|
|
this._observerSvc.addObserver(this, "xpcom-shutdown", false);
|
|
},
|
|
|
|
_destroy: function ContentPrefService__destroy() {
|
|
this._observerSvc.removeObserver(this, "xpcom-shutdown");
|
|
|
|
// Delete references to XPCOM components to make sure we don't leak them
|
|
// (although we haven't observed leakage in tests).
|
|
this.__observerSvc = null;
|
|
this.__consoleSvc = null;
|
|
this._grouper = null;
|
|
this.__stmtSelectPref = null;
|
|
this.__stmtSelectGlobalPref = null;
|
|
this.__stmtSelectGroupID = null;
|
|
this.__stmtInsertGroup = null;
|
|
this.__stmtSelectSettingID = null;
|
|
this.__stmtInsertSetting = null;
|
|
this.__stmtSelectPrefID = null;
|
|
this.__stmtSelectGlobalPrefID = null;
|
|
this.__stmtInsertPref = null;
|
|
this.__stmtUpdatePref = null;
|
|
this.__stmtDeletePref = null;
|
|
this.__stmtDeleteSettingIfUnused = null;
|
|
this.__stmtDeleteGroupIfUnused = null;
|
|
this.__stmtSelectPrefs = null;
|
|
this.__stmtSelectGlobalPrefs = null;
|
|
this._dbConnection = null;
|
|
|
|
// Delete references to observers to avoid cycles with those that refer
|
|
// to us and don't remove themselves from the observer pool.
|
|
this._observers = {};
|
|
this._genericObservers = [];
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// nsISupports
|
|
|
|
_interfaces: [Ci.nsIContentPrefService, Ci.nsISupports],
|
|
|
|
QueryInterface: function ContentPrefService_QueryInterface(aIID) {
|
|
if (!this._interfaces.some( function(v) { return aIID.equals(v) } ))
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
return this;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// nsIObserver
|
|
|
|
observe: function ContentPrefService_observe(subject, topic, data) {
|
|
switch (topic) {
|
|
case "xpcom-shutdown":
|
|
this._destroy();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// nsIContentPrefService
|
|
|
|
getPref: function ContentPrefService_getPref(aURI, aName) {
|
|
if (aURI) {
|
|
var group = this.grouper.group(aURI);
|
|
return this._selectPref(group, aName);
|
|
}
|
|
else
|
|
return this._selectGlobalPref(aName);
|
|
},
|
|
|
|
setPref: function ContentPrefService_setPref(aURI, aName, aValue) {
|
|
// If the pref is already set to the value, there's nothing more to do.
|
|
var currentValue = this.getPref(aURI, aName);
|
|
if (typeof currentValue != "undefined" && currentValue == aValue)
|
|
return;
|
|
|
|
var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
|
|
var group, groupID, prefID;
|
|
if (aURI) {
|
|
group = this.grouper.group(aURI);
|
|
groupID = this._selectGroupID(group) || this._insertGroup(group);
|
|
prefID = this._selectPrefID(groupID, settingID);
|
|
}
|
|
else {
|
|
group = null;
|
|
groupID = null;
|
|
prefID = this._selectGlobalPrefID(settingID);
|
|
}
|
|
|
|
// Update the existing record, if any, or create a new one.
|
|
if (prefID)
|
|
this._updatePref(prefID, aValue);
|
|
else
|
|
this._insertPref(groupID, settingID, aValue);
|
|
|
|
for each (var observer in this._getObservers(aName)) {
|
|
try {
|
|
observer.onContentPrefSet(group, aName, aValue);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
}
|
|
},
|
|
|
|
hasPref: function ContentPrefService_hasPref(aURI, aName) {
|
|
// XXX If consumers end up calling this method regularly, then we should
|
|
// optimize this to query the database directly.
|
|
return (typeof this.getPref(aURI, aName) != "undefined");
|
|
},
|
|
|
|
removePref: function ContentPrefService_removePref(aURI, aName) {
|
|
// If there's no old value, then there's nothing to remove.
|
|
if (!this.hasPref(aURI, aName))
|
|
return;
|
|
|
|
var settingID = this._selectSettingID(aName);
|
|
var group, groupID, prefID;
|
|
if (aURI) {
|
|
group = this.grouper.group(aURI);
|
|
groupID = this._selectGroupID(group);
|
|
prefID = this._selectPrefID(groupID, settingID);
|
|
}
|
|
else {
|
|
group = null;
|
|
groupID = null;
|
|
prefID = this._selectGlobalPrefID(settingID);
|
|
}
|
|
|
|
this._deletePref(prefID);
|
|
|
|
// Get rid of extraneous records that are no longer being used.
|
|
this._deleteSettingIfUnused(settingID);
|
|
if (groupID)
|
|
this._deleteGroupIfUnused(groupID);
|
|
|
|
for each (var observer in this._getObservers(aName)) {
|
|
try {
|
|
observer.onContentPrefRemoved(group, aName);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError(ex);
|
|
}
|
|
}
|
|
},
|
|
|
|
getPrefs: function ContentPrefService_getPrefs(aURI) {
|
|
if (aURI) {
|
|
var group = this.grouper.group(aURI);
|
|
return this._selectPrefs(group);
|
|
}
|
|
else
|
|
return this._selectGlobalPrefs();
|
|
},
|
|
|
|
// 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;
|
|
},
|
|
|
|
_grouper: null,
|
|
get grouper() {
|
|
if (!this._grouper)
|
|
this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
|
|
getService(Ci.nsIContentURIGrouper);
|
|
return this._grouper;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// 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;
|
|
},
|
|
|
|
_selectPref: function ContentPrefService__selectPref(aGroup, aSetting) {
|
|
var value;
|
|
|
|
try {
|
|
this._stmtSelectPref.params.group = aGroup;
|
|
this._stmtSelectPref.params.setting = aSetting;
|
|
|
|
if (this._stmtSelectPref.step())
|
|
value = this._stmtSelectPref.row["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) {
|
|
var value;
|
|
|
|
try {
|
|
this._stmtSelectGlobalPref.params.name = aName;
|
|
|
|
if (this._stmtSelectGlobalPref.step())
|
|
value = this._stmtSelectGlobalPref.row["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.step())
|
|
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.step())
|
|
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.step())
|
|
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.step())
|
|
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.step())
|
|
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.step())
|
|
prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
|
|
this._stmtSelectGlobalPrefs.row["value"]);
|
|
}
|
|
finally {
|
|
this._stmtSelectGlobalPrefs.reset();
|
|
}
|
|
|
|
return prefs;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Database Creation & Access
|
|
|
|
_dbVersion: 2,
|
|
|
|
_dbSchema: {
|
|
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"
|
|
},
|
|
|
|
_dbConnection: null,
|
|
|
|
_dbCreateStatement: function(aSQLString) {
|
|
try {
|
|
var statement = this._dbConnection.createStatement(aSQLString);
|
|
}
|
|
catch(ex) {
|
|
Cu.reportError("error creating statement " + aSQLString + ": " +
|
|
this._dbConnection.lastError + " - " +
|
|
this._dbConnection.lastErrorString);
|
|
throw ex;
|
|
}
|
|
|
|
var wrappedStatement = Cc["@mozilla.org/storage/statement-wrapper;1"].
|
|
createInstance(Ci.mozIStorageStatementWrapper);
|
|
wrappedStatement.initialize(statement);
|
|
return wrappedStatement;
|
|
},
|
|
|
|
// _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() {
|
|
this._log("initializing database");
|
|
|
|
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()) {
|
|
this._log("no database file; creating database");
|
|
dbConnection = this._dbCreate(dbService, dbFile);
|
|
}
|
|
else {
|
|
try {
|
|
dbConnection = dbService.openDatabase(dbFile);
|
|
|
|
// Get the version of the database in the file.
|
|
var statement, version;
|
|
try {
|
|
statement = dbConnection.createStatement("PRAGMA user_version");
|
|
statement.executeStep();
|
|
version = statement.getInt32(0);
|
|
}
|
|
finally {
|
|
statement.reset();
|
|
}
|
|
|
|
if (version != this._dbVersion) {
|
|
this._log("database: v" + version + ", application: v" +
|
|
this._dbVersion + "; migrating database");
|
|
this._dbMigrate(dbConnection, version, this._dbVersion);
|
|
}
|
|
}
|
|
catch (ex) {
|
|
// If the database file is corrupted, I'm not sure whether we should
|
|
// just delete the corrupted file or back it up. For now I'm just
|
|
// deleting it, but here's some code that backs it up (but doesn't limit
|
|
// the number of backups, which is probably necessary, thus I'm not
|
|
// using this code):
|
|
//var backup = this._dbFile.clone();
|
|
//backup.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
|
|
//backup.remove(false);
|
|
//this._dbFile.moveTo(null, backup.leafName);
|
|
if (ex.result == Cr.NS_ERROR_FILE_CORRUPTED) {
|
|
this._log("database file corrupted; recreating database");
|
|
// Remove the corrupted file, then recreate it.
|
|
dbFile.remove(false);
|
|
dbConnection = this._dbCreate(dbService, dbFile);
|
|
}
|
|
else
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
this._dbConnection = dbConnection;
|
|
},
|
|
|
|
_dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
|
|
var dbConnection = aDBService.openDatabase(aDBFile);
|
|
for (var table in this._dbSchema)
|
|
dbConnection.createTable(table, this._dbSchema[table]);
|
|
dbConnection.executeSimpleSQL("PRAGMA user_version = " + this._dbVersion);
|
|
return dbConnection;
|
|
},
|
|
|
|
_dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
|
|
if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
|
|
this._log("migrating database from v" + aOldVersion + " to v" + aNewVersion);
|
|
aDBConnection.beginTransaction();
|
|
try {
|
|
this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
|
|
aDBConnection.commitTransaction();
|
|
}
|
|
catch(ex) {
|
|
this._log("migration failed: " + ex + "; DB error code " +
|
|
aDBConnection.lastError + ": " + aDBConnection.lastErrorString +
|
|
"; rolling back transaction");
|
|
aDBConnection.rollbackTransaction();
|
|
throw ex;
|
|
}
|
|
}
|
|
else
|
|
throw("can't migrate database from v" + aOldVersion +
|
|
" to v" + aNewVersion + ": no migrator function");
|
|
},
|
|
|
|
_dbMigrate0To2: function ContentPrefService___dbMigrate0To2(aDBConnection) {
|
|
this._log("migrating sites to the groups table");
|
|
aDBConnection.createTable("groups", this._dbSchema.groups);
|
|
aDBConnection.executeSimpleSQL(
|
|
"INSERT INTO groups (id, name) SELECT id, name FROM sites"
|
|
);
|
|
|
|
this._log("migrating keys to the settings table");
|
|
aDBConnection.createTable("settings", this._dbSchema.settings);
|
|
aDBConnection.executeSimpleSQL(
|
|
"INSERT INTO settings (id, name) SELECT id, name FROM keys"
|
|
);
|
|
|
|
this._log("migrating prefs from the old prefs table to the new one");
|
|
aDBConnection.executeSimpleSQL("ALTER TABLE prefs RENAME TO prefsOld");
|
|
aDBConnection.createTable("prefs", this._dbSchema.prefs);
|
|
aDBConnection.executeSimpleSQL(
|
|
"INSERT INTO prefs (id, groupID, settingID, value) " +
|
|
"SELECT id, site_id, key_id, value FROM prefsOld"
|
|
);
|
|
|
|
// Drop obsolete tables.
|
|
this._log("dropping obsolete tables");
|
|
aDBConnection.executeSimpleSQL("DROP TABLE prefsOld");
|
|
aDBConnection.executeSimpleSQL("DROP TABLE keys");
|
|
aDBConnection.executeSimpleSQL("DROP TABLE sites");
|
|
|
|
aDBConnection.executeSimpleSQL("PRAGMA user_version = " + this._dbVersion);
|
|
},
|
|
|
|
_dbMigrate1To2: function ContentPrefService___dbMigrate1To2(aDBConnection) {
|
|
this._log("migrating groups from the old groups table to the new one");
|
|
aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
|
|
aDBConnection.createTable("groups", this._dbSchema.groups);
|
|
aDBConnection.executeSimpleSQL(
|
|
"INSERT INTO groups (id, name) " +
|
|
"SELECT id, name FROM groupsOld"
|
|
);
|
|
|
|
this._log("dropping obsolete tables");
|
|
aDBConnection.executeSimpleSQL("DROP TABLE groupers");
|
|
aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
|
|
|
|
aDBConnection.executeSimpleSQL("PRAGMA user_version = " + this._dbVersion);
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// Utilities
|
|
|
|
/**
|
|
* Get an app pref or a default value if the pref doesn't exist.
|
|
*
|
|
* @param aPrefName
|
|
* @param aDefaultValue
|
|
* @returns the pref's value or the default (if it is missing)
|
|
*/
|
|
_getAppPref: function ContentPrefService__getAppPref(aPrefName, aDefaultValue) {
|
|
try {
|
|
var prefBranch = Cc["@mozilla.org/preferences-service;1"].
|
|
getService(Ci.nsIPrefBranch);
|
|
switch (prefBranch.getPrefType(aPrefName)) {
|
|
case prefBranch.PREF_STRING:
|
|
return prefBranch.getCharPref(aPrefName);
|
|
case prefBranch.PREF_INT:
|
|
return prefBranch.getIntPref(aPrefName);
|
|
case prefBranch.PREF_BOOL:
|
|
return prefBranch.getBoolPref(aPrefName);
|
|
}
|
|
}
|
|
catch (ex) { /* return the default value */ }
|
|
|
|
return aDefaultValue;
|
|
},
|
|
|
|
_log: function ContentPrefService__log(aMessage) {
|
|
if (!this._getAppPref("browser.preferences.content.log", false))
|
|
return;
|
|
|
|
aMessage = "*** ContentPrefService: " + aMessage;
|
|
dump(aMessage + "\n");
|
|
this._consoleSvc.logStringMessage(aMessage);
|
|
}
|
|
};
|
|
|
|
|
|
function HostnameGrouper() {}
|
|
|
|
HostnameGrouper.prototype = {
|
|
|
|
//**************************************************************************//
|
|
// nsISupports
|
|
|
|
_interfaces: [Ci.nsIContentURIGrouper, Ci.nsISupports],
|
|
|
|
QueryInterface: function HostnameGrouper_QueryInterface(aIID) {
|
|
if (!this._interfaces.some( function(v) { return aIID.equals(v) } ))
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
return this;
|
|
},
|
|
|
|
|
|
//**************************************************************************//
|
|
// 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;
|
|
}
|
|
};
|
|
|
|
|
|
var gModule = {
|
|
registerSelf: function(aComponentManager, aFileSpec, aLocation, aType) {
|
|
aComponentManager = aComponentManager.QueryInterface(Ci.nsIComponentRegistrar);
|
|
|
|
for (var key in this._objects) {
|
|
var obj = this._objects[key];
|
|
aComponentManager.registerFactoryLocation(obj.CID,
|
|
obj.className,
|
|
obj.contractID,
|
|
aFileSpec,
|
|
aLocation,
|
|
aType);
|
|
}
|
|
},
|
|
|
|
unregisterSelf: function(aComponentManager, aFileSpec, aLocation) {},
|
|
|
|
getClassObject: function(aComponentManager, aCID, aIID) {
|
|
if (!aIID.equals(Components.interfaces.nsIFactory))
|
|
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
for (var key in this._objects) {
|
|
if (aCID.equals(this._objects[key].CID))
|
|
return this._objects[key].factory;
|
|
}
|
|
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
},
|
|
|
|
_objects: {
|
|
service: {
|
|
CID : Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
|
|
contractID : "@mozilla.org/content-pref/service;1",
|
|
className : "Content Pref Service",
|
|
factory : ContentPrefServiceFactory = {
|
|
createInstance: function(aOuter, aIID) {
|
|
if (aOuter != null)
|
|
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
|
var service = new ContentPrefService();
|
|
service._init();
|
|
return service.QueryInterface(aIID);
|
|
}
|
|
}
|
|
},
|
|
grouper: {
|
|
CID : Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
|
|
contractID : "@mozilla.org/content-pref/hostname-grouper;1",
|
|
className : "Hostname Grouper",
|
|
factory : HostnameGrouperFactory = {
|
|
createInstance: function(aOuter, aIID) {
|
|
if (aOuter != null)
|
|
throw Components.results.NS_ERROR_NO_AGGREGATION;
|
|
var grouper = new HostnameGrouper();
|
|
return grouper.QueryInterface(aIID);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
canUnload: function(aComponentManager) {
|
|
return true;
|
|
}
|
|
};
|
|
|
|
function NSGetModule(aComponentManager, aFileSpec) {
|
|
return gModule;
|
|
}
|