blob: 5207b8a75c911687dfa7f1d30d0913fceb718cdc [file] [log] [blame]
/* ***** 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;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
function ContentPrefService() {
// 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);
}
ContentPrefService.prototype = {
//**************************************************************************//
// XPCOM Plumbing
classDescription: "Content Pref Service",
classID: Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
contractID: "@mozilla.org/content-pref/service;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPrefService]),
//**************************************************************************//
// Convenience Getters
// Observer Service
__observerSvc: null,
get _observerSvc ContentPrefService_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 ContentPrefService_get__consoleSvc() {
if (!this.__consoleSvc)
this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
getService(Ci.nsIConsoleService);
return this.__consoleSvc;
},
// Preferences Service
__prefSvc: null,
get _prefSvc ContentPrefService_get__prefSvc() {
if (!this.__prefSvc)
this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch2);
return this.__prefSvc;
},
//**************************************************************************//
// Destruction
_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). 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;
}
},
//**************************************************************************//
// nsIContentPrefService
getPref: function ContentPrefService_getPref(aURI, aName) {
if (aURI) {
var group = this.grouper.group(aURI);
return this._selectPref(group, aName);
}
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);
}
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 ContentPrefService_get_grouper() {
if (!this._grouper)
this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
getService(Ci.nsIContentURIGrouper);
return this._grouper;
},
get DBConnection ContentPrefService_get_DBConnection() {
return this._dbConnection;
},
//**************************************************************************//
// Data Retrieval & Modification
__stmtSelectPref: null,
get _stmtSelectPref ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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 ContentPrefService_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: 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;
}
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() {
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);
}
};
function HostnameGrouper() {}
HostnameGrouper.prototype = {
//**************************************************************************//
// XPCOM Plumbing
classDescription: "Hostname Grouper",
classID: Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
contractID: "@mozilla.org/content-pref/hostname-grouper;1",
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;
}
};
//****************************************************************************//
// XPCOM Plumbing
var components = [ContentPrefService, HostnameGrouper];
var NSGetModule = function ContentPrefService_NSGetModule(compMgr, fileSpec) {
return XPCOMUtils.generateModule(components);
}