| /* ***** 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); |
| } |