toolkit/components/contentprefs/nsContentPrefService.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 const Ci = Components.interfaces;
     6 const Cc = Components.classes;
     7 const Cr = Components.results;
     8 const Cu = Components.utils;
    10 const CACHE_MAX_GROUP_ENTRIES = 100;
    12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    14 /**
    15  * Remotes the service. All the remoting/electrolysis code is in here,
    16  * so the regular service code below remains uncluttered and maintainable.
    17  */
    18 function electrolify(service) {
    19   // FIXME: For now, use the wrappedJSObject hack, until bug
    20   //        593407 which will clean that up.
    21   //        Note that we also use this in the xpcshell tests, separately.
    22   service.wrappedJSObject = service;
    24   var appInfo = Cc["@mozilla.org/xre/app-info;1"];
    25   if (appInfo && appInfo.getService(Ci.nsIXULRuntime).processType !=
    26       Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
    27   {
    28     // Child process
    29     service._dbInit = function(){}; // No local DB
    30   }
    31 }
    33 function ContentPrefService() {
    34   electrolify(this);
    36   // If this throws an exception, it causes the getService call to fail,
    37   // but the next time a consumer tries to retrieve the service, we'll try
    38   // to initialize the database again, which might work if the failure
    39   // was due to a temporary condition (like being out of disk space).
    40   this._dbInit();
    42   this._observerSvc.addObserver(this, "last-pb-context-exited", false);
    44   // Observe shutdown so we can shut down the database connection.
    45   this._observerSvc.addObserver(this, "xpcom-shutdown", false);
    46 }
    48 Cu.import("resource://gre/modules/ContentPrefStore.jsm");
    49 const cache = new ContentPrefStore();
    50 cache.set = function CPS_cache_set(group, name, val) {
    51   Object.getPrototypeOf(this).set.apply(this, arguments);
    52   let groupCount = Object.keys(this._groups).length;
    53   if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
    54     // Clean half of the entries
    55     for (let [group, name, ] in this) {
    56       this.remove(group, name);
    57       groupCount--;
    58       if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
    59         break;
    60     }
    61   }
    62 };
    64 const privModeStorage = new ContentPrefStore();
    66 ContentPrefService.prototype = {
    67   //**************************************************************************//
    68   // XPCOM Plumbing
    70   classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),
    72   QueryInterface: function CPS_QueryInterface(iid) {
    73     let supportedIIDs = [
    74       Ci.nsIContentPrefService,
    75       Ci.nsIFrameMessageListener,
    76       Ci.nsISupports,
    77     ];
    78     if (supportedIIDs.some(function (i) iid.equals(i)))
    79       return this;
    80     if (iid.equals(Ci.nsIContentPrefService2)) {
    81       if (!this._contentPrefService2) {
    82         let s = {};
    83         Cu.import("resource://gre/modules/ContentPrefService2.jsm", s);
    84         this._contentPrefService2 = new s.ContentPrefService2(this);
    85       }
    86       return this._contentPrefService2;
    87     }
    88     throw Cr.NS_ERROR_NO_INTERFACE;
    89   },
    91   //**************************************************************************//
    92   // Convenience Getters
    94   // Observer Service
    95   __observerSvc: null,
    96   get _observerSvc() {
    97     if (!this.__observerSvc)
    98       this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
    99                            getService(Ci.nsIObserverService);
   100     return this.__observerSvc;
   101   },
   103   // Console Service
   104   __consoleSvc: null,
   105   get _consoleSvc() {
   106     if (!this.__consoleSvc)
   107       this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
   108                           getService(Ci.nsIConsoleService);
   109     return this.__consoleSvc;
   110   },
   112   // Preferences Service
   113   __prefSvc: null,
   114   get _prefSvc() {
   115     if (!this.__prefSvc)
   116       this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
   117                        getService(Ci.nsIPrefBranch);
   118     return this.__prefSvc;
   119   },
   122   //**************************************************************************//
   123   // Destruction
   125   _destroy: function ContentPrefService__destroy() {
   126     this._observerSvc.removeObserver(this, "xpcom-shutdown");
   127     this._observerSvc.removeObserver(this, "last-pb-context-exited");
   129     // Finalize statements which may have been used asynchronously.
   130     // FIXME(696499): put them in an object cache like other components.
   131     if (this.__stmtSelectPrefID) {
   132       this.__stmtSelectPrefID.finalize();
   133       this.__stmtSelectPrefID = null;
   134     }
   135     if (this.__stmtSelectGlobalPrefID) {
   136       this.__stmtSelectGlobalPrefID.finalize();
   137       this.__stmtSelectGlobalPrefID = null;
   138     }
   139     if (this.__stmtInsertPref) {
   140       this.__stmtInsertPref.finalize();
   141       this.__stmtInsertPref = null;
   142     }
   143     if (this.__stmtInsertGroup) {
   144       this.__stmtInsertGroup.finalize();
   145       this.__stmtInsertGroup = null;
   146     }
   147     if (this.__stmtInsertSetting) {
   148       this.__stmtInsertSetting.finalize();
   149       this.__stmtInsertSetting = null;
   150     }
   151     if (this.__stmtSelectGroupID) {
   152       this.__stmtSelectGroupID.finalize();
   153       this.__stmtSelectGroupID = null;
   154     }
   155     if (this.__stmtSelectSettingID) {
   156       this.__stmtSelectSettingID.finalize();
   157       this.__stmtSelectSettingID = null;
   158     }
   159     if (this.__stmtSelectPref) {
   160       this.__stmtSelectPref.finalize();
   161       this.__stmtSelectPref = null;
   162     }
   163     if (this.__stmtSelectGlobalPref) {
   164       this.__stmtSelectGlobalPref.finalize();
   165       this.__stmtSelectGlobalPref = null;
   166     }
   167     if (this.__stmtSelectPrefsByName) {
   168       this.__stmtSelectPrefsByName.finalize();
   169       this.__stmtSelectPrefsByName = null;
   170     }
   171     if (this.__stmtDeleteSettingIfUnused) {
   172       this.__stmtDeleteSettingIfUnused.finalize();
   173       this.__stmtDeleteSettingIfUnused = null;
   174     }
   175     if(this.__stmtSelectPrefs) {
   176       this.__stmtSelectPrefs.finalize();
   177       this.__stmtSelectPrefs = null;
   178     }
   179     if(this.__stmtDeleteGroupIfUnused) {
   180       this.__stmtDeleteGroupIfUnused.finalize();
   181       this.__stmtDeleteGroupIfUnused = null;
   182     }
   183     if (this.__stmtDeletePref) {
   184       this.__stmtDeletePref.finalize();
   185       this.__stmtDeletePref = null;
   186     }
   187     if (this.__stmtUpdatePref) {
   188       this.__stmtUpdatePref.finalize();
   189       this.__stmtUpdatePref = null;
   190     }
   192     if (this._contentPrefService2)
   193       this._contentPrefService2.destroy();
   195     this._dbConnection.asyncClose();
   197     // Delete references to XPCOM components to make sure we don't leak them
   198     // (although we haven't observed leakage in tests).  Also delete references
   199     // in _observers and _genericObservers to avoid cycles with those that
   200     // refer to us and don't remove themselves from those observer pools.
   201     for (var i in this) {
   202       try { this[i] = null }
   203       // Ignore "setting a property that has only a getter" exceptions.
   204       catch(ex) {}
   205     }
   206   },
   209   //**************************************************************************//
   210   // nsIObserver
   212   observe: function ContentPrefService_observe(subject, topic, data) {
   213     switch (topic) {
   214       case "xpcom-shutdown":
   215         this._destroy();
   216         break;
   217       case "last-pb-context-exited":
   218         this._privModeStorage.removeAll();
   219         break;
   220     }
   221   },
   224   //**************************************************************************//
   225   // in-memory cache and private-browsing stores
   227   _cache: cache,
   228   _privModeStorage: privModeStorage,
   230   //**************************************************************************//
   231   // nsIContentPrefService
   233   getPref: function ContentPrefService_getPref(aGroup, aName, aContext, aCallback) {
   234     warnDeprecated();
   236     if (!aName)
   237       throw Components.Exception("aName cannot be null or an empty string",
   238                                  Cr.NS_ERROR_ILLEGAL_VALUE);
   240     var group = this._parseGroupParam(aGroup);
   242     if (aContext && aContext.usePrivateBrowsing) {
   243       if (this._privModeStorage.has(group, aName)) {
   244         let value = this._privModeStorage.get(group, aName);
   245         if (aCallback) {
   246           this._scheduleCallback(function(){aCallback.onResult(value);});
   247           return;
   248         }
   249         return value;
   250       }
   251       // if we don't have a pref specific to this private mode browsing
   252       // session, to try to get one from normal mode
   253     }
   255     if (group == null)
   256       return this._selectGlobalPref(aName, aCallback);
   257     return this._selectPref(group, aName, aCallback);
   258   },
   260   setPref: function ContentPrefService_setPref(aGroup, aName, aValue, aContext) {
   261     warnDeprecated();
   263     // If the pref is already set to the value, there's nothing more to do.
   264     var currentValue = this.getPref(aGroup, aName, aContext);
   265     if (typeof currentValue != "undefined") {
   266       if (currentValue == aValue)
   267         return;
   268     }
   270     var group = this._parseGroupParam(aGroup);
   272     if (aContext && aContext.usePrivateBrowsing) {
   273       this._privModeStorage.setWithCast(group, aName, aValue);
   274       this._notifyPrefSet(group, aName, aValue);
   275       return;
   276     }
   278     var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
   279     var groupID, prefID;
   280     if (group == null) {
   281       groupID = null;
   282       prefID = this._selectGlobalPrefID(settingID);
   283     }
   284     else {
   285       groupID = this._selectGroupID(group) || this._insertGroup(group);
   286       prefID = this._selectPrefID(groupID, settingID);
   287     }
   289     // Update the existing record, if any, or create a new one.
   290     if (prefID)
   291       this._updatePref(prefID, aValue);
   292     else
   293       this._insertPref(groupID, settingID, aValue);
   295     this._cache.setWithCast(group, aName, aValue);
   296     this._notifyPrefSet(group, aName, aValue);
   297   },
   299   hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) {
   300     warnDeprecated();
   302     // XXX If consumers end up calling this method regularly, then we should
   303     // optimize this to query the database directly.
   304     return (typeof this.getPref(aGroup, aName, aContext) != "undefined");
   305   },
   307   hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName, aContext) {
   308     warnDeprecated();
   310     if (!aName)
   311       throw Components.Exception("aName cannot be null or an empty string",
   312                                  Cr.NS_ERROR_ILLEGAL_VALUE);
   314     let group = this._parseGroupParam(aGroup);
   315     let storage = aContext && aContext.usePrivateBrowsing ? this._privModeStorage: this._cache;
   316     return storage.has(group, aName);
   317   },
   319   removePref: function ContentPrefService_removePref(aGroup, aName, aContext) {
   320     warnDeprecated();
   322     // If there's no old value, then there's nothing to remove.
   323     if (!this.hasPref(aGroup, aName, aContext))
   324       return;
   326     var group = this._parseGroupParam(aGroup);
   328     if (aContext && aContext.usePrivateBrowsing) {
   329       this._privModeStorage.remove(group, aName);
   330       this._notifyPrefRemoved(group, aName);
   331       return;
   332     }
   334     var settingID = this._selectSettingID(aName);
   335     var groupID, prefID;
   336     if (group == null) {
   337       groupID = null;
   338       prefID = this._selectGlobalPrefID(settingID);
   339     }
   340     else {
   341       groupID = this._selectGroupID(group);
   342       prefID = this._selectPrefID(groupID, settingID);
   343     }
   345     this._deletePref(prefID);
   347     // Get rid of extraneous records that are no longer being used.
   348     this._deleteSettingIfUnused(settingID);
   349     if (groupID)
   350       this._deleteGroupIfUnused(groupID);
   352     this._cache.remove(group, aName);
   353     this._notifyPrefRemoved(group, aName);
   354   },
   356   removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) {
   357     warnDeprecated();
   359     // will not delete global preferences
   360     if (aContext && aContext.usePrivateBrowsing) {
   361         // keep only global prefs
   362         this._privModeStorage.removeAllGroups();
   363     }
   364     this._cache.removeAllGroups();
   365     this._dbConnection.beginTransaction();
   366     try {
   367       this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
   368       this._dbConnection.executeSimpleSQL("DELETE FROM groups");
   369       this._dbConnection.executeSimpleSQL(
   370         "DELETE FROM settings " +
   371         "WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
   372       );
   373       this._dbConnection.commitTransaction();
   374     }
   375     catch(ex) {
   376       this._dbConnection.rollbackTransaction();
   377       throw ex;
   378     }
   379   },
   381   removePrefsByName: function ContentPrefService_removePrefsByName(aName, aContext) {
   382     warnDeprecated();
   384     if (!aName)
   385       throw Components.Exception("aName cannot be null or an empty string",
   386                                  Cr.NS_ERROR_ILLEGAL_VALUE);
   388     if (aContext && aContext.usePrivateBrowsing) {
   389       for (let [group, name, ] in this._privModeStorage) {
   390         if (name === aName) {
   391           this._privModeStorage.remove(group, aName);
   392           this._notifyPrefRemoved(group, aName);
   393         }
   394       }
   395     }
   397     var settingID = this._selectSettingID(aName);
   398     if (!settingID)
   399       return;
   401     var selectGroupsStmt = this._dbCreateStatement(
   402       "SELECT groups.id AS groupID, groups.name AS groupName " +
   403       "FROM prefs " +
   404       "JOIN groups ON prefs.groupID = groups.id " +
   405       "WHERE prefs.settingID = :setting "
   406     );
   408     var groupNames = [];
   409     var groupIDs = [];
   410     try {
   411       selectGroupsStmt.params.setting = settingID;
   413       while (selectGroupsStmt.executeStep()) {
   414         groupIDs.push(selectGroupsStmt.row["groupID"]);
   415         groupNames.push(selectGroupsStmt.row["groupName"]);
   416       }
   417     }
   418     finally {
   419       selectGroupsStmt.reset();
   420     }
   422     if (this.hasPref(null, aName)) {
   423       groupNames.push(null);
   424     }
   426     this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
   427     this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);
   429     for (var i = 0; i < groupNames.length; i++) {
   430       this._cache.remove(groupNames[i], aName);
   431       if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
   432         this._deleteGroupIfUnused(groupIDs[i]);
   433       if (!aContext || !aContext.usePrivateBrowsing) {
   434         this._notifyPrefRemoved(groupNames[i], aName);
   435       }
   436     }
   437   },
   439   getPrefs: function ContentPrefService_getPrefs(aGroup, aContext) {
   440     warnDeprecated();
   442     var group = this._parseGroupParam(aGroup);
   443     if (aContext && aContext.usePrivateBrowsing) {
   444         let prefs = Cc["@mozilla.org/hash-property-bag;1"].
   445                     createInstance(Ci.nsIWritablePropertyBag);
   446         for (let [sgroup, sname, sval] in this._privModeStorage) {
   447           if (sgroup === group)
   448             prefs.setProperty(sname, sval);
   449         }
   450         return prefs;
   451     }
   453     if (group == null)
   454       return this._selectGlobalPrefs();
   455     return this._selectPrefs(group);
   456   },
   458   getPrefsByName: function ContentPrefService_getPrefsByName(aName, aContext) {
   459     warnDeprecated();
   461     if (!aName)
   462       throw Components.Exception("aName cannot be null or an empty string",
   463                                  Cr.NS_ERROR_ILLEGAL_VALUE);
   465     if (aContext && aContext.usePrivateBrowsing) {
   466       let prefs = Cc["@mozilla.org/hash-property-bag;1"].
   467                   createInstance(Ci.nsIWritablePropertyBag);
   468       for (let [sgroup, sname, sval] in this._privModeStorage) {
   469         if (sname === aName)
   470           prefs.setProperty(sgroup, sval);
   471       }
   472       return prefs;
   473     }
   475     return this._selectPrefsByName(aName);
   476   },
   478   // A hash of arrays of observers, indexed by setting name.
   479   _observers: {},
   481   // An array of generic observers, which observe all settings.
   482   _genericObservers: [],
   484   addObserver: function ContentPrefService_addObserver(aName, aObserver) {
   485     warnDeprecated();
   486     this._addObserver.apply(this, arguments);
   487   },
   489   _addObserver: function ContentPrefService__addObserver(aName, aObserver) {
   490     var observers;
   491     if (aName) {
   492       if (!this._observers[aName])
   493         this._observers[aName] = [];
   494       observers = this._observers[aName];
   495     }
   496     else
   497       observers = this._genericObservers;
   499     if (observers.indexOf(aObserver) == -1)
   500       observers.push(aObserver);
   501   },
   503   removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
   504     warnDeprecated();
   505     this._removeObserver.apply(this, arguments);
   506   },
   508   _removeObserver: function ContentPrefService__removeObserver(aName, aObserver) {
   509     var observers;
   510     if (aName) {
   511       if (!this._observers[aName])
   512         return;
   513       observers = this._observers[aName];
   514     }
   515     else
   516       observers = this._genericObservers;
   518     if (observers.indexOf(aObserver) != -1)
   519       observers.splice(observers.indexOf(aObserver), 1);
   520   },
   522   /**
   523    * Construct a list of observers to notify about a change to some setting,
   524    * putting setting-specific observers before before generic ones, so observers
   525    * that initialize individual settings (like the page style controller)
   526    * execute before observers that display multiple settings and depend on them
   527    * being initialized first (like the content prefs sidebar).
   528    */
   529   _getObservers: function ContentPrefService__getObservers(aName) {
   530     var observers = [];
   532     if (aName && this._observers[aName])
   533       observers = observers.concat(this._observers[aName]);
   534     observers = observers.concat(this._genericObservers);
   536     return observers;
   537   },
   539   /**
   540    * Notify all observers about the removal of a preference.
   541    */
   542   _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName) {
   543     for each (var observer in this._getObservers(aName)) {
   544       try {
   545         observer.onContentPrefRemoved(aGroup, aName);
   546       }
   547       catch(ex) {
   548         Cu.reportError(ex);
   549       }
   550     }
   551   },
   553   /**
   554    * Notify all observers about a preference change.
   555    */
   556   _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue) {
   557     for each (var observer in this._getObservers(aName)) {
   558       try {
   559         observer.onContentPrefSet(aGroup, aName, aValue);
   560       }
   561       catch(ex) {
   562         Cu.reportError(ex);
   563       }
   564     }
   565   },
   567   get grouper() {
   568     warnDeprecated();
   569     return this._grouper;
   570   },
   571   __grouper: null,
   572   get _grouper() {
   573     if (!this.__grouper)
   574       this.__grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
   575                        getService(Ci.nsIContentURIGrouper);
   576     return this.__grouper;
   577   },
   579   get DBConnection() {
   580     warnDeprecated();
   581     return this._dbConnection;
   582   },
   585   //**************************************************************************//
   586   // Data Retrieval & Modification
   588   __stmtSelectPref: null,
   589   get _stmtSelectPref() {
   590     if (!this.__stmtSelectPref)
   591       this.__stmtSelectPref = this._dbCreateStatement(
   592         "SELECT prefs.value AS value " +
   593         "FROM prefs " +
   594         "JOIN groups ON prefs.groupID = groups.id " +
   595         "JOIN settings ON prefs.settingID = settings.id " +
   596         "WHERE groups.name = :group " +
   597         "AND settings.name = :setting"
   598       );
   600     return this.__stmtSelectPref;
   601   },
   603   _scheduleCallback: function(func) {
   604     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
   605     tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
   606   },
   608   _selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
   609     let value = undefined;
   610     if (this._cache.has(aGroup, aSetting)) {
   611       value = this._cache.get(aGroup, aSetting);
   612       if (aCallback) {
   613         this._scheduleCallback(function(){aCallback.onResult(value);});
   614         return;
   615       }
   616       return value;
   617     }
   619     try {
   620       this._stmtSelectPref.params.group = aGroup;
   621       this._stmtSelectPref.params.setting = aSetting;
   623       if (aCallback) {
   624         let cache = this._cache;
   625         new AsyncStatement(this._stmtSelectPref).execute({onResult: function(aResult) {
   626           cache.set(aGroup, aSetting, aResult);
   627           aCallback.onResult(aResult);
   628         }});
   629       }
   630       else {
   631         if (this._stmtSelectPref.executeStep()) {
   632           value = this._stmtSelectPref.row["value"];
   633         }
   634         this._cache.set(aGroup, aSetting, value);
   635       }
   636     }
   637     finally {
   638       this._stmtSelectPref.reset();
   639     }
   641     return value;
   642   },
   644   __stmtSelectGlobalPref: null,
   645   get _stmtSelectGlobalPref() {
   646     if (!this.__stmtSelectGlobalPref)
   647       this.__stmtSelectGlobalPref = this._dbCreateStatement(
   648         "SELECT prefs.value AS value " +
   649         "FROM prefs " +
   650         "JOIN settings ON prefs.settingID = settings.id " +
   651         "WHERE prefs.groupID IS NULL " +
   652         "AND settings.name = :name"
   653       );
   655     return this.__stmtSelectGlobalPref;
   656   },
   658   _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
   659     let value = undefined;
   660     if (this._cache.has(null, aName)) {
   661       value = this._cache.get(null, aName);
   662       if (aCallback) {
   663         this._scheduleCallback(function(){aCallback.onResult(value);});
   664         return;
   665       }
   666       return value;
   667     }
   669     try {
   670       this._stmtSelectGlobalPref.params.name = aName;
   672       if (aCallback) {
   673         let cache = this._cache;
   674         new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult: function(aResult) {
   675           cache.set(null, aName, aResult);
   676           aCallback.onResult(aResult);
   677         }});
   678       }
   679       else {
   680         if (this._stmtSelectGlobalPref.executeStep()) {
   681           value = this._stmtSelectGlobalPref.row["value"];
   682         }
   683         this._cache.set(null, aName, value);
   684       }
   685     }
   686     finally {
   687       this._stmtSelectGlobalPref.reset();
   688     }
   690     return value;
   691   },
   693   __stmtSelectGroupID: null,
   694   get _stmtSelectGroupID() {
   695     if (!this.__stmtSelectGroupID)
   696       this.__stmtSelectGroupID = this._dbCreateStatement(
   697         "SELECT groups.id AS id " +
   698         "FROM groups " +
   699         "WHERE groups.name = :name "
   700       );
   702     return this.__stmtSelectGroupID;
   703   },
   705   _selectGroupID: function ContentPrefService__selectGroupID(aName) {
   706     var id;
   708     try {
   709       this._stmtSelectGroupID.params.name = aName;
   711       if (this._stmtSelectGroupID.executeStep())
   712         id = this._stmtSelectGroupID.row["id"];
   713     }
   714     finally {
   715       this._stmtSelectGroupID.reset();
   716     }
   718     return id;
   719   },
   721   __stmtInsertGroup: null,
   722   get _stmtInsertGroup() {
   723     if (!this.__stmtInsertGroup)
   724       this.__stmtInsertGroup = this._dbCreateStatement(
   725         "INSERT INTO groups (name) VALUES (:name)"
   726       );
   728     return this.__stmtInsertGroup;
   729   },
   731   _insertGroup: function ContentPrefService__insertGroup(aName) {
   732     this._stmtInsertGroup.params.name = aName;
   733     this._stmtInsertGroup.execute();
   734     return this._dbConnection.lastInsertRowID;
   735   },
   737   __stmtSelectSettingID: null,
   738   get _stmtSelectSettingID() {
   739     if (!this.__stmtSelectSettingID)
   740       this.__stmtSelectSettingID = this._dbCreateStatement(
   741         "SELECT id FROM settings WHERE name = :name"
   742       );
   744     return this.__stmtSelectSettingID;
   745   },
   747   _selectSettingID: function ContentPrefService__selectSettingID(aName) {
   748     var id;
   750     try {
   751       this._stmtSelectSettingID.params.name = aName;
   753       if (this._stmtSelectSettingID.executeStep())
   754         id = this._stmtSelectSettingID.row["id"];
   755     }
   756     finally {
   757       this._stmtSelectSettingID.reset();
   758     }
   760     return id;
   761   },
   763   __stmtInsertSetting: null,
   764   get _stmtInsertSetting() {
   765     if (!this.__stmtInsertSetting)
   766       this.__stmtInsertSetting = this._dbCreateStatement(
   767         "INSERT INTO settings (name) VALUES (:name)"
   768       );
   770     return this.__stmtInsertSetting;
   771   },
   773   _insertSetting: function ContentPrefService__insertSetting(aName) {
   774     this._stmtInsertSetting.params.name = aName;
   775     this._stmtInsertSetting.execute();
   776     return this._dbConnection.lastInsertRowID;
   777   },
   779   __stmtSelectPrefID: null,
   780   get _stmtSelectPrefID() {
   781     if (!this.__stmtSelectPrefID)
   782       this.__stmtSelectPrefID = this._dbCreateStatement(
   783         "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
   784       );
   786     return this.__stmtSelectPrefID;
   787   },
   789   _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
   790     var id;
   792     try {
   793       this._stmtSelectPrefID.params.groupID = aGroupID;
   794       this._stmtSelectPrefID.params.settingID = aSettingID;
   796       if (this._stmtSelectPrefID.executeStep())
   797         id = this._stmtSelectPrefID.row["id"];
   798     }
   799     finally {
   800       this._stmtSelectPrefID.reset();
   801     }
   803     return id;
   804   },
   806   __stmtSelectGlobalPrefID: null,
   807   get _stmtSelectGlobalPrefID() {
   808     if (!this.__stmtSelectGlobalPrefID)
   809       this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
   810         "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
   811       );
   813     return this.__stmtSelectGlobalPrefID;
   814   },
   816   _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
   817     var id;
   819     try {
   820       this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
   822       if (this._stmtSelectGlobalPrefID.executeStep())
   823         id = this._stmtSelectGlobalPrefID.row["id"];
   824     }
   825     finally {
   826       this._stmtSelectGlobalPrefID.reset();
   827     }
   829     return id;
   830   },
   832   __stmtInsertPref: null,
   833   get _stmtInsertPref() {
   834     if (!this.__stmtInsertPref)
   835       this.__stmtInsertPref = this._dbCreateStatement(
   836         "INSERT INTO prefs (groupID, settingID, value) " +
   837         "VALUES (:groupID, :settingID, :value)"
   838       );
   840     return this.__stmtInsertPref;
   841   },
   843   _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
   844     this._stmtInsertPref.params.groupID = aGroupID;
   845     this._stmtInsertPref.params.settingID = aSettingID;
   846     this._stmtInsertPref.params.value = aValue;
   847     this._stmtInsertPref.execute();
   848     return this._dbConnection.lastInsertRowID;
   849   },
   851   __stmtUpdatePref: null,
   852   get _stmtUpdatePref() {
   853     if (!this.__stmtUpdatePref)
   854       this.__stmtUpdatePref = this._dbCreateStatement(
   855         "UPDATE prefs SET value = :value WHERE id = :id"
   856       );
   858     return this.__stmtUpdatePref;
   859   },
   861   _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
   862     this._stmtUpdatePref.params.id = aPrefID;
   863     this._stmtUpdatePref.params.value = aValue;
   864     this._stmtUpdatePref.execute();
   865   },
   867   __stmtDeletePref: null,
   868   get _stmtDeletePref() {
   869     if (!this.__stmtDeletePref)
   870       this.__stmtDeletePref = this._dbCreateStatement(
   871         "DELETE FROM prefs WHERE id = :id"
   872       );
   874     return this.__stmtDeletePref;
   875   },
   877   _deletePref: function ContentPrefService__deletePref(aPrefID) {
   878     this._stmtDeletePref.params.id = aPrefID;
   879     this._stmtDeletePref.execute();
   880   },
   882   __stmtDeleteSettingIfUnused: null,
   883   get _stmtDeleteSettingIfUnused() {
   884     if (!this.__stmtDeleteSettingIfUnused)
   885       this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(
   886         "DELETE FROM settings WHERE id = :id " +
   887         "AND id NOT IN (SELECT DISTINCT settingID FROM prefs)"
   888       );
   890     return this.__stmtDeleteSettingIfUnused;
   891   },
   893   _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
   894     this._stmtDeleteSettingIfUnused.params.id = aSettingID;
   895     this._stmtDeleteSettingIfUnused.execute();
   896   },
   898   __stmtDeleteGroupIfUnused: null,
   899   get _stmtDeleteGroupIfUnused() {
   900     if (!this.__stmtDeleteGroupIfUnused)
   901       this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(
   902         "DELETE FROM groups WHERE id = :id " +
   903         "AND id NOT IN (SELECT DISTINCT groupID FROM prefs)"
   904       );
   906     return this.__stmtDeleteGroupIfUnused;
   907   },
   909   _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
   910     this._stmtDeleteGroupIfUnused.params.id = aGroupID;
   911     this._stmtDeleteGroupIfUnused.execute();
   912   },
   914   __stmtSelectPrefs: null,
   915   get _stmtSelectPrefs() {
   916     if (!this.__stmtSelectPrefs)
   917       this.__stmtSelectPrefs = this._dbCreateStatement(
   918         "SELECT settings.name AS name, prefs.value AS value " +
   919         "FROM prefs " +
   920         "JOIN groups ON prefs.groupID = groups.id " +
   921         "JOIN settings ON prefs.settingID = settings.id " +
   922         "WHERE groups.name = :group "
   923       );
   925     return this.__stmtSelectPrefs;
   926   },
   928   _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
   929     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
   930                 createInstance(Ci.nsIWritablePropertyBag);
   932     try {
   933       this._stmtSelectPrefs.params.group = aGroup;
   935       while (this._stmtSelectPrefs.executeStep())
   936         prefs.setProperty(this._stmtSelectPrefs.row["name"],
   937                           this._stmtSelectPrefs.row["value"]);
   938     }
   939     finally {
   940       this._stmtSelectPrefs.reset();
   941     }
   943     return prefs;
   944   },
   946   __stmtSelectGlobalPrefs: null,
   947   get _stmtSelectGlobalPrefs() {
   948     if (!this.__stmtSelectGlobalPrefs)
   949       this.__stmtSelectGlobalPrefs = this._dbCreateStatement(
   950         "SELECT settings.name AS name, prefs.value AS value " +
   951         "FROM prefs " +
   952         "JOIN settings ON prefs.settingID = settings.id " +
   953         "WHERE prefs.groupID IS NULL"
   954       );
   956     return this.__stmtSelectGlobalPrefs;
   957   },
   959   _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
   960     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
   961                 createInstance(Ci.nsIWritablePropertyBag);
   963     try {
   964       while (this._stmtSelectGlobalPrefs.executeStep())
   965         prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
   966                           this._stmtSelectGlobalPrefs.row["value"]);
   967     }
   968     finally {
   969       this._stmtSelectGlobalPrefs.reset();
   970     }
   972     return prefs;
   973   },
   975   __stmtSelectPrefsByName: null,
   976   get _stmtSelectPrefsByName() {
   977     if (!this.__stmtSelectPrefsByName)
   978       this.__stmtSelectPrefsByName = this._dbCreateStatement(
   979         "SELECT groups.name AS groupName, prefs.value AS value " +
   980         "FROM prefs " +
   981         "JOIN groups ON prefs.groupID = groups.id " +
   982         "JOIN settings ON prefs.settingID = settings.id " +
   983         "WHERE settings.name = :setting "
   984       );
   986     return this.__stmtSelectPrefsByName;
   987   },
   989   _selectPrefsByName: function ContentPrefService__selectPrefsByName(aName) {
   990     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
   991                 createInstance(Ci.nsIWritablePropertyBag);
   993     try {
   994       this._stmtSelectPrefsByName.params.setting = aName;
   996       while (this._stmtSelectPrefsByName.executeStep())
   997         prefs.setProperty(this._stmtSelectPrefsByName.row["groupName"],
   998                           this._stmtSelectPrefsByName.row["value"]);
   999     }
  1000     finally {
  1001       this._stmtSelectPrefsByName.reset();
  1004     var global = this._selectGlobalPref(aName);
  1005     if (typeof global != "undefined") {
  1006       prefs.setProperty(null, global);
  1009     return prefs;
  1010   },
  1013   //**************************************************************************//
  1014   // Database Creation & Access
  1016   _dbVersion: 3,
  1018   _dbSchema: {
  1019     tables: {
  1020       groups:     "id           INTEGER PRIMARY KEY, \
  1021                    name         TEXT NOT NULL",
  1023       settings:   "id           INTEGER PRIMARY KEY, \
  1024                    name         TEXT NOT NULL",
  1026       prefs:      "id           INTEGER PRIMARY KEY, \
  1027                    groupID      INTEGER REFERENCES groups(id), \
  1028                    settingID    INTEGER NOT NULL REFERENCES settings(id), \
  1029                    value        BLOB"
  1030     },
  1031     indices: {
  1032       groups_idx: {
  1033         table: "groups",
  1034         columns: ["name"]
  1035       },
  1036       settings_idx: {
  1037         table: "settings",
  1038         columns: ["name"]
  1039       },
  1040       prefs_idx: {
  1041         table: "prefs",
  1042         columns: ["groupID", "settingID"]
  1045   },
  1047   _dbConnection: null,
  1049   _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
  1050     try {
  1051       var statement = this._dbConnection.createStatement(aSQLString);
  1053     catch(ex) {
  1054       Cu.reportError("error creating statement " + aSQLString + ": " +
  1055                      this._dbConnection.lastError + " - " +
  1056                      this._dbConnection.lastErrorString);
  1057       throw ex;
  1060     return statement;
  1061   },
  1063   // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
  1064   // specific migration methods) must be careful not to call any method
  1065   // of the service that assumes the database connection has already been
  1066   // initialized, since it won't be initialized until at the end of _dbInit.
  1068   _dbInit: function ContentPrefService__dbInit() {
  1069     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  1070                      getService(Ci.nsIProperties);
  1071     var dbFile = dirService.get("ProfD", Ci.nsIFile);
  1072     dbFile.append("content-prefs.sqlite");
  1074     var dbService = Cc["@mozilla.org/storage/service;1"].
  1075                     getService(Ci.mozIStorageService);
  1077     var dbConnection;
  1079     if (true || !dbFile.exists())
  1080       dbConnection = this._dbCreate(dbService, dbFile);
  1081     else {
  1082       try {
  1083         dbConnection = dbService.openDatabase(dbFile);
  1085       // If the connection isn't ready after we open the database, that means
  1086       // the database has been corrupted, so we back it up and then recreate it.
  1087       catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
  1088         dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
  1089                                                  dbConnection);
  1092       // Get the version of the schema in the file.
  1093       var version = dbConnection.schemaVersion;
  1095       // Try to migrate the schema in the database to the current schema used by
  1096       // the service.  If migration fails, back up the database and recreate it.
  1097       if (version != this._dbVersion) {
  1098         try {
  1099           this._dbMigrate(dbConnection, version, this._dbVersion);
  1101         catch(ex) {
  1102           Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
  1103           dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
  1108     // Turn off disk synchronization checking to reduce disk churn and speed up
  1109     // operations when prefs are changed rapidly (such as when a user repeatedly
  1110     // changes the value of the browser zoom setting for a site).
  1111     //
  1112     // Note: this could cause database corruption if the OS crashes or machine
  1113     // loses power before the data gets written to disk, but this is considered
  1114     // a reasonable risk for the not-so-critical data stored in this database.
  1115     //
  1116     // If you really don't want to take this risk, however, just set the
  1117     // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
  1118     // (FULL synchronization), in which case mozStorageConnection::Initialize
  1119     // will use that value, and we won't override it here.
  1120     if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
  1121       dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
  1123     this._dbConnection = dbConnection;
  1124   },
  1126   _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
  1127     var dbConnection = aDBService.openSpecialDatabase("memory");
  1129     try {
  1130       this._dbCreateSchema(dbConnection);
  1131       dbConnection.schemaVersion = this._dbVersion;
  1133     catch(ex) {
  1134       // If we failed to create the database (perhaps because the disk ran out
  1135       // of space), then remove the database file so we don't leave it in some
  1136       // half-created state from which we won't know how to recover.
  1137       dbConnection.close();
  1138       aDBFile.remove(false);
  1139       throw ex;
  1142     return dbConnection;
  1143   },
  1145   _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
  1146     this._dbCreateTables(aDBConnection);
  1147     this._dbCreateIndices(aDBConnection);
  1148   },
  1150   _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
  1151     for (let name in this._dbSchema.tables)
  1152       aDBConnection.createTable(name, this._dbSchema.tables[name]);
  1153   },
  1155   _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
  1156     for (let name in this._dbSchema.indices) {
  1157       let index = this._dbSchema.indices[name];
  1158       let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
  1159                       "(" + index.columns.join(", ") + ")";
  1160       aDBConnection.executeSimpleSQL(statement);
  1162   },
  1164   _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
  1165                                                                          aDBFile,
  1166                                                                          aDBConnection) {
  1167     aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
  1169     // Close the database, ignoring the "already closed" exception, if any.
  1170     // It'll be open if we're here because of a migration failure but closed
  1171     // if we're here because of database corruption.
  1172     try { aDBConnection.close() } catch(ex) {}
  1174     aDBFile.remove(false);
  1176     let dbConnection = this._dbCreate(aDBService, aDBFile);
  1178     return dbConnection;
  1179   },
  1181   _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
  1182     if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
  1183       aDBConnection.beginTransaction();
  1184       try {
  1185         this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
  1186         aDBConnection.schemaVersion = aNewVersion;
  1187         aDBConnection.commitTransaction();
  1189       catch(ex) {
  1190         aDBConnection.rollbackTransaction();
  1191         throw ex;
  1194     else
  1195       throw("no migrator function from version " + aOldVersion +
  1196             " to version " + aNewVersion);
  1197   },
  1199   /**
  1200    * If the schema version is 0, that means it was never set, which means
  1201    * the database was somehow created without the schema being applied, perhaps
  1202    * because the system ran out of disk space (although we check for this
  1203    * in _createDB) or because some other code created the database file without
  1204    * applying the schema.  In any case, recover by simply reapplying the schema.
  1205    */
  1206   _dbMigrate0To3: function ContentPrefService___dbMigrate0To3(aDBConnection) {
  1207     this._dbCreateSchema(aDBConnection);
  1208   },
  1210   _dbMigrate1To3: function ContentPrefService___dbMigrate1To3(aDBConnection) {
  1211     aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
  1212     aDBConnection.createTable("groups", this._dbSchema.tables.groups);
  1213     aDBConnection.executeSimpleSQL(
  1214       "INSERT INTO groups (id, name) " +
  1215       "SELECT id, name FROM groupsOld"
  1216     );
  1218     aDBConnection.executeSimpleSQL("DROP TABLE groupers");
  1219     aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
  1221     this._dbCreateIndices(aDBConnection);
  1222   },
  1224   _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
  1225     this._dbCreateIndices(aDBConnection);
  1226   },
  1228   _parseGroupParam: function ContentPrefService__parseGroupParam(aGroup) {
  1229     if (aGroup == null)
  1230       return null;
  1231     if (aGroup.constructor.name == "String")
  1232       return aGroup.toString();
  1233     if (aGroup instanceof Ci.nsIURI)
  1234       return this.grouper.group(aGroup);
  1236     throw Components.Exception("aGroup is not a string, nsIURI or null",
  1237                                Cr.NS_ERROR_ILLEGAL_VALUE);
  1238   },
  1239 };
  1241 function warnDeprecated() {
  1242   let Deprecated = Cu.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
  1243   Deprecated.warning("nsIContentPrefService is deprecated. Please use nsIContentPrefService2 instead.",
  1244                      "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIContentPrefService2",
  1245                      Components.stack.caller);
  1249 function HostnameGrouper() {}
  1251 HostnameGrouper.prototype = {
  1252   //**************************************************************************//
  1253   // XPCOM Plumbing
  1255   classID:          Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
  1256   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
  1258   //**************************************************************************//
  1259   // nsIContentURIGrouper
  1261   group: function HostnameGrouper_group(aURI) {
  1262     var group;
  1264     try {
  1265       // Accessing the host property of the URI will throw an exception
  1266       // if the URI is of a type that doesn't have a host property.
  1267       // Otherwise, we manually throw an exception if the host is empty,
  1268       // since the effect is the same (we can't derive a group from it).
  1270       group = aURI.host;
  1271       if (!group)
  1272         throw("can't derive group from host; no host in URI");
  1274     catch(ex) {
  1275       // If we don't have a host, then use the entire URI (minus the query,
  1276       // reference, and hash, if possible) as the group.  This means that URIs
  1277       // like about:mozilla and about:blank will be considered separate groups,
  1278       // but at least they'll be grouped somehow.
  1280       // This also means that each individual file: URL will be considered
  1281       // its own group.  This seems suboptimal, but so does treating the entire
  1282       // file: URL space as a single group (especially if folks start setting
  1283       // group-specific capabilities prefs).
  1285       // XXX Is there something better we can do here?
  1287       try {
  1288         var url = aURI.QueryInterface(Ci.nsIURL);
  1289         group = aURI.prePath + url.filePath;
  1291       catch(ex) {
  1292         group = aURI.spec;
  1296     return group;
  1298 };
  1300 function AsyncStatement(aStatement) {
  1301   this.stmt = aStatement;
  1304 AsyncStatement.prototype = {
  1305   execute: function AsyncStmt_execute(aCallback) {
  1306     let stmt = this.stmt;
  1307     stmt.executeAsync({
  1308       _callback: aCallback,
  1309       _hadResult: false,
  1310       handleResult: function(aResult) {
  1311         this._hadResult = true;
  1312         if (this._callback) {
  1313           let row = aResult.getNextRow();
  1314           this._callback.onResult(row.getResultByName("value"));
  1316       },
  1317       handleCompletion: function(aReason) {
  1318         if (!this._hadResult && this._callback &&
  1319             aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED)
  1320           this._callback.onResult(undefined);
  1321       },
  1322       handleError: function(aError) {}
  1323     });
  1325 };
  1327 //****************************************************************************//
  1328 // XPCOM Plumbing
  1330 var components = [ContentPrefService, HostnameGrouper];
  1331 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);

mercurial