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