home *** CD-ROM | disk | FTP | other *** search
/ Freelog 112 / FreelogNo112-NovembreDecembre2012.iso / Programmation / RJ_TextEd / RJ_TextEd_Portable.exe / components / nsContentPrefService.js < prev    next >
Text File  |  2010-01-01  |  48KB  |  1,491 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Content Preferences (cpref).
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Myk Melez <myk@mozilla.org>
  22.  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
  23.  *   Geoff Lankow <geoff@darktrojan.net>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. const Ci = Components.interfaces;
  40. const Cc = Components.classes;
  41. const Cr = Components.results;
  42. const Cu = Components.utils;
  43.  
  44. const CACHE_MAX_GROUP_ENTRIES = 100;
  45.  
  46. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  47.  
  48. /**
  49.  * Remotes the service. All the remoting/electrolysis code is in here,
  50.  * so the regular service code below remains uncluttered and maintainable.
  51.  */
  52. function electrolify(service) {
  53.   // FIXME: For now, use the wrappedJSObject hack, until bug
  54.   //        593407 which will clean that up.
  55.   //        Note that we also use this in the xpcshell tests, separately.
  56.   service.wrappedJSObject = service;
  57.  
  58.   var appInfo = Cc["@mozilla.org/xre/app-info;1"];
  59.   if (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType ==
  60.       Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
  61.     // Parent process
  62.  
  63.     // Setup listener for child messages. We don't need to call
  64.     // addMessageListener as the wakeup service will do that for us.
  65.     service.receiveMessage = function(aMessage) {
  66.       var json = aMessage.json;
  67.       // We have a whitelist for getting/setting. This is because
  68.       // there are potential privacy issues with a compromised
  69.       // content process checking the user's content preferences
  70.       // and using that to discover all the websites visited, etc.
  71.       // Also there are both potential race conditions (if two processes
  72.       // set more than one value in succession, and the values
  73.       // only make sense together), as well as security issues, if
  74.       // a compromised content process can send arbitrary setPref
  75.       // messages. The whitelist contains only those settings that
  76.       // are not at risk for either.
  77.       // We currently whitelist saving/reading the last directory of file
  78.       // uploads, and the last current spellchecker dictionary which are so far
  79.       // the only need we have identified.
  80.       const NAME_WHITELIST = ["browser.upload.lastDir", "spellcheck.lang"];
  81.       if (NAME_WHITELIST.indexOf(json.name) == -1)
  82.         return { succeeded: false };
  83.  
  84.       switch (aMessage.name) {
  85.         case "ContentPref:getPref":
  86.           return { succeeded: true,
  87.                    value: service.getPref(json.group, json.name, json.value) };
  88.  
  89.         case "ContentPref:setPref":
  90.           service.setPref(json.group, json.name, json.value);
  91.           return { succeeded: true };
  92.       }
  93.     };
  94.   } else {
  95.     // Child process
  96.  
  97.     service._dbInit = function(){}; // No local DB
  98.  
  99.     service.messageManager = Cc["@mozilla.org/childprocessmessagemanager;1"].
  100.                              getService(Ci.nsISyncMessageSender);
  101.  
  102.     // Child method remoting
  103.     [
  104.       ['getPref', ['group', 'name'], ['_parseGroupParam']],
  105.       ['setPref', ['group', 'name', 'value'], ['_parseGroupParam']],
  106.     ].forEach(function(data) {
  107.       var method = data[0];
  108.       var params = data[1];
  109.       var parsers = data[2];
  110.       service[method] = function __remoted__() {
  111.         var json = {};
  112.         for (var i = 0; i < params.length; i++) {
  113.           if (params[i]) {
  114.             json[params[i]] = arguments[i];
  115.             if (parsers[i])
  116.               json[params[i]] = this[parsers[i]](json[params[i]]);
  117.           }
  118.         }
  119.         var ret = service.messageManager.sendSyncMessage('ContentPref:' + method, json)[0];
  120.         if (!ret.succeeded)
  121.           throw "ContentPrefs remoting failed to pass whitelist";
  122.         return ret.value;
  123.       };
  124.     });
  125.   }
  126. }
  127.  
  128. function ContentPrefService() {
  129.   electrolify(this);
  130.  
  131.   // If this throws an exception, it causes the getService call to fail,
  132.   // but the next time a consumer tries to retrieve the service, we'll try
  133.   // to initialize the database again, which might work if the failure
  134.   // was due to a temporary condition (like being out of disk space).
  135.   this._dbInit();
  136.  
  137.   // detect if we are in private browsing mode
  138.   this._inPrivateBrowsing = false;
  139.   try { // The Private Browsing service might not be available.
  140.     var pbs = Cc["@mozilla.org/privatebrowsing;1"].
  141.                 getService(Ci.nsIPrivateBrowsingService);
  142.     this._inPrivateBrowsing = pbs.privateBrowsingEnabled;
  143.   } catch (e) {}
  144.   this._observerSvc.addObserver(this, "private-browsing", false);
  145.  
  146.   // Observe shutdown so we can shut down the database connection.
  147.   this._observerSvc.addObserver(this, "xpcom-shutdown", false);
  148. }
  149.  
  150. var inMemoryPrefsProto = {
  151.   getPref: function(aName, aGroup) {
  152.     aGroup = aGroup || "__GlobalPrefs__";
  153.  
  154.     if (this._prefCache[aGroup] && this._prefCache[aGroup].hasOwnProperty(aName)) {
  155.       let value = this._prefCache[aGroup][aName];
  156.       return [true, value];
  157.     }
  158.     return [false, undefined];
  159.   },
  160.  
  161.   setPref: function(aName, aValue, aGroup) {
  162.     if (typeof aValue == "boolean")
  163.       aValue = aValue ? 1 : 0;
  164.     else if (aValue === undefined)
  165.       aValue = null;
  166.  
  167.     this.cachePref(aName, aValue, aGroup);
  168.   },
  169.  
  170.   removePref: function(aName, aGroup) {
  171.     aGroup = aGroup || "__GlobalPrefs__";
  172.  
  173.     if (this._prefCache[aGroup].hasOwnProperty(aName)) {
  174.       delete this._prefCache[aGroup][aName];
  175.       if (Object.keys(this._prefCache[aGroup]).length == 0) {
  176.         // remove empty group
  177.         delete this._prefCache[aGroup];
  178.       }
  179.     }
  180.   },
  181.  
  182.   invalidate: function(aKeepGlobal) {
  183.     if (!aKeepGlobal) {
  184.       this._prefCache = {};
  185.       return;
  186.     }
  187.  
  188.     if (this._prefCache.hasOwnProperty("__GlobalPrefs__")) {
  189.       let globals = this._prefCache["__GlobalPrefs__"];
  190.       this._prefCache = {"__GlobalPrefs__": globals};
  191.     } else {
  192.       this._prefCache = {};
  193.     }
  194.   }
  195. };
  196.  
  197. ContentPrefService.prototype = {
  198.   //**************************************************************************//
  199.   // XPCOM Plumbing
  200.  
  201.   classID:          Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
  202.   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentPrefService,
  203.                                            Ci.nsIFrameMessageListener]),
  204.  
  205.  
  206.   //**************************************************************************//
  207.   // Convenience Getters
  208.  
  209.   // Observer Service
  210.   __observerSvc: null,
  211.   get _observerSvc() {
  212.     if (!this.__observerSvc)
  213.       this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
  214.                            getService(Ci.nsIObserverService);
  215.     return this.__observerSvc;
  216.   },
  217.  
  218.   // Console Service
  219.   __consoleSvc: null,
  220.   get _consoleSvc() {
  221.     if (!this.__consoleSvc)
  222.       this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
  223.                           getService(Ci.nsIConsoleService);
  224.     return this.__consoleSvc;
  225.   },
  226.  
  227.   // Preferences Service
  228.   __prefSvc: null,
  229.   get _prefSvc() {
  230.     if (!this.__prefSvc)
  231.       this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
  232.                        getService(Ci.nsIPrefBranch2);
  233.     return this.__prefSvc;
  234.   },
  235.  
  236.  
  237.   //**************************************************************************//
  238.   // Destruction
  239.  
  240.   _destroy: function ContentPrefService__destroy() {
  241.     this._observerSvc.removeObserver(this, "xpcom-shutdown");
  242.     this._observerSvc.removeObserver(this, "private-browsing");
  243.  
  244.     // Finalize statements which may have been used asynchronously.
  245.     // FIXME(696499): put them in an object cache like other components.
  246.     if (this.__stmtSelectPrefID) {
  247.       this.__stmtSelectPrefID.finalize();
  248.       this.__stmtSelectPrefID = null;
  249.     }
  250.     if (this.__stmtSelectGlobalPrefID) {
  251.       this.__stmtSelectGlobalPrefID.finalize();
  252.       this.__stmtSelectGlobalPrefID = null;
  253.     }
  254.     if (this.__stmtInsertPref) {
  255.       this.__stmtInsertPref.finalize();
  256.       this.__stmtInsertPref = null;
  257.     }
  258.     if (this.__stmtInsertGroup) {
  259.       this.__stmtInsertGroup.finalize();
  260.       this.__stmtInsertGroup = null;
  261.     }
  262.     if (this.__stmtInsertSetting) {
  263.       this.__stmtInsertSetting.finalize();
  264.       this.__stmtInsertSetting = null;
  265.     }
  266.     if (this.__stmtSelectGroupID) {
  267.       this.__stmtSelectGroupID.finalize();
  268.       this.__stmtSelectGroupID = null;
  269.     }
  270.     if (this.__stmtSelectSettingID) {
  271.       this.__stmtSelectSettingID.finalize();
  272.       this.__stmtSelectSettingID = null;
  273.     }
  274.     if (this.__stmtSelectPref) {
  275.       this.__stmtSelectPref.finalize();
  276.       this.__stmtSelectPref = null;
  277.     }
  278.     if (this.__stmtSelectGlobalPref) {
  279.       this.__stmtSelectGlobalPref.finalize();
  280.       this.__stmtSelectGlobalPref = null;
  281.     }
  282.     if (this.__stmtSelectPrefsByName) {
  283.       this.__stmtSelectPrefsByName.finalize();
  284.       this.__stmtSelectPrefsByName = null;
  285.     }
  286.     if (this.__stmtDeleteSettingIfUnused) {
  287.       this.__stmtDeleteSettingIfUnused.finalize();
  288.       this.__stmtDeleteSettingIfUnused = null;
  289.     }
  290.     if(this.__stmtSelectPrefs) {
  291.       this.__stmtSelectPrefs.finalize();
  292.       this.__stmtSelectPrefs = null;
  293.     }
  294.     if(this.__stmtDeleteGroupIfUnused) {
  295.       this.__stmtDeleteGroupIfUnused.finalize();
  296.       this.__stmtDeleteGroupIfUnused = null;
  297.     }
  298.     if (this.__stmtDeletePref) {
  299.       this.__stmtDeletePref.finalize();
  300.       this.__stmtDeletePref = null;
  301.     }
  302.     if (this.__stmtUpdatePref) {
  303.       this.__stmtUpdatePref.finalize();
  304.       this.__stmtUpdatePref = null;
  305.     }
  306.  
  307.     this._dbConnection.asyncClose();
  308.  
  309.     // Delete references to XPCOM components to make sure we don't leak them
  310.     // (although we haven't observed leakage in tests).  Also delete references
  311.     // in _observers and _genericObservers to avoid cycles with those that
  312.     // refer to us and don't remove themselves from those observer pools.
  313.     for (var i in this) {
  314.       try { this[i] = null }
  315.       // Ignore "setting a property that has only a getter" exceptions.
  316.       catch(ex) {}
  317.     }
  318.   },
  319.  
  320.  
  321.   //**************************************************************************//
  322.   // nsIObserver
  323.  
  324.   observe: function ContentPrefService_observe(subject, topic, data) {
  325.     switch (topic) {
  326.       case "xpcom-shutdown":
  327.         this._destroy();
  328.         break;
  329.       case "private-browsing":
  330.         switch (data) {
  331.           case "enter":
  332.             this._inPrivateBrowsing = true;
  333.             break;
  334.           case "exit":
  335.             this._inPrivateBrowsing = false;
  336.             this._privModeStorage.invalidate();
  337.             break;
  338.         }
  339.         break;
  340.     }
  341.   },
  342.  
  343.  
  344.   //**************************************************************************//
  345.   // Prefs cache
  346.  
  347.   _cache: Object.create(inMemoryPrefsProto, {
  348.     _prefCache: { 
  349.       value: {}, configurable: true, writable: true, enumerable: true
  350.     },
  351.  
  352.     cachePref: { value:
  353.       function(aName, aValue, aGroup) {
  354.         aGroup = aGroup || "__GlobalPrefs__";
  355.  
  356.         if (!this._prefCache[aGroup]) {
  357.           this._possiblyCleanCache();
  358.           this._prefCache[aGroup] = {};
  359.         }
  360.  
  361.         this._prefCache[aGroup][aName] = aValue;
  362.       },
  363.     },
  364.  
  365.     _possiblyCleanCache: { value:
  366.       function() {
  367.         let groupCount = Object.keys(this._prefCache).length;
  368.  
  369.         if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
  370.           // Clean half of the entries
  371.           for (let entry in this._prefCache) {
  372.             delete this._prefCache[entry];
  373.             groupCount--;
  374.  
  375.             if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
  376.               break;
  377.           }
  378.         }
  379.       }
  380.     }
  381.   }),
  382.  
  383.   //**************************************************************************//
  384.   // Private mode storage
  385.   _privModeStorage: Object.create(inMemoryPrefsProto, {
  386.     _prefCache: { 
  387.       value: {}, configurable: true, writable: true, enumerable: true
  388.     },
  389.  
  390.     cachePref: { value: 
  391.       function(aName, aValue, aGroup) {
  392.         aGroup = aGroup || "__GlobalPrefs__";
  393.  
  394.         if (!this._prefCache[aGroup]) {
  395.           this._prefCache[aGroup] = {};
  396.         }
  397.  
  398.         this._prefCache[aGroup][aName] = aValue;
  399.       }
  400.     },
  401.  
  402.     getPrefs: { value: 
  403.       function(aGroup) {
  404.         aGroup = aGroup || "__GlobalPrefs__";
  405.         if (this._prefCache[aGroup]) {
  406.           return [true, this._prefCache[aGroup]];
  407.         }
  408.         return [false, undefined];
  409.       }
  410.     },
  411.  
  412.     groupsForName: { value: 
  413.       function(aName) {
  414.         var res = [];
  415.         for (let entry in this._prefCache) {
  416.           if (this._prefCache[entry]) {
  417.             if (entry === "__GlobalPrefs__") {
  418.               entry = null;
  419.             }
  420.             res.push(entry);
  421.           }
  422.         }
  423.         return res;
  424.       }
  425.     }
  426.   }),
  427.  
  428.   //**************************************************************************//
  429.   // nsIContentPrefService
  430.  
  431.   getPref: function ContentPrefService_getPref(aGroup, aName, aCallback) {
  432.     if (!aName)
  433.       throw Components.Exception("aName cannot be null or an empty string",
  434.                                  Cr.NS_ERROR_ILLEGAL_VALUE);
  435.  
  436.     var group = this._parseGroupParam(aGroup);
  437.  
  438.     if (this._inPrivateBrowsing) {
  439.       let [haspref, value] = this._privModeStorage.getPref(aName, group);
  440.       if (haspref) {
  441.         if (aCallback) {
  442.           this._scheduleCallback(function(){aCallback.onResult(value);});
  443.           return;
  444.         }
  445.         return value;
  446.       }
  447.       // if we don't have a pref specific to this private mode browsing
  448.       // session, to try to get one from normal mode
  449.     }
  450.  
  451.     if (group == null)
  452.       return this._selectGlobalPref(aName, aCallback);
  453.     return this._selectPref(group, aName, aCallback);
  454.   },
  455.  
  456.   setPref: function ContentPrefService_setPref(aGroup, aName, aValue) {
  457.     // If the pref is already set to the value, there's nothing more to do.
  458.     var currentValue = this.getPref(aGroup, aName);
  459.     if (typeof currentValue != "undefined") {
  460.       if (currentValue == aValue)
  461.         return;
  462.     }
  463.  
  464.     var group = this._parseGroupParam(aGroup);
  465.  
  466.     if (this._inPrivateBrowsing) {
  467.       this._privModeStorage.setPref(aName, aValue, group);
  468.       this._notifyPrefSet(group, aName, aValue);
  469.       return;
  470.     }
  471.  
  472.     var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
  473.     var groupID, prefID;
  474.     if (group == null) {
  475.       groupID = null;
  476.       prefID = this._selectGlobalPrefID(settingID);
  477.     }
  478.     else {
  479.       groupID = this._selectGroupID(group) || this._insertGroup(group);
  480.       prefID = this._selectPrefID(groupID, settingID);
  481.     }
  482.  
  483.     // Update the existing record, if any, or create a new one.
  484.     if (prefID)
  485.       this._updatePref(prefID, aValue);
  486.     else
  487.       this._insertPref(groupID, settingID, aValue);
  488.  
  489.     this._cache.setPref(aName, aValue, group);
  490.  
  491.     this._notifyPrefSet(group, aName, aValue);
  492.   },
  493.  
  494.   hasPref: function ContentPrefService_hasPref(aGroup, aName) {
  495.     // XXX If consumers end up calling this method regularly, then we should
  496.     // optimize this to query the database directly.
  497.     return (typeof this.getPref(aGroup, aName) != "undefined");
  498.   },
  499.  
  500.   hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName) {
  501.     if (!aName)
  502.       throw Components.Exception("aName cannot be null or an empty string",
  503.                                  Cr.NS_ERROR_ILLEGAL_VALUE);
  504.  
  505.     let group = this._parseGroupParam(aGroup);
  506.     let storage = this._inPrivateBrowsing? this._privModeStorage: this._cache;
  507.     let [cached,] = storage.getPref(aName, group);
  508.     return cached;
  509.   },
  510.  
  511.   removePref: function ContentPrefService_removePref(aGroup, aName) {
  512.     // If there's no old value, then there's nothing to remove.
  513.     if (!this.hasPref(aGroup, aName))
  514.       return;
  515.  
  516.     var group = this._parseGroupParam(aGroup);
  517.  
  518.     if (this._inPrivateBrowsing) {
  519.       this._privModeStorage.removePref(aName, group);
  520.       this._notifyPrefRemoved(group, aName);
  521.       return;
  522.     }
  523.  
  524.     var settingID = this._selectSettingID(aName);
  525.     var groupID, prefID;
  526.     if (group == null) {
  527.       groupID = null;
  528.       prefID = this._selectGlobalPrefID(settingID);
  529.     }
  530.     else {
  531.       groupID = this._selectGroupID(group);
  532.       prefID = this._selectPrefID(groupID, settingID);
  533.     }
  534.  
  535.     this._deletePref(prefID);
  536.  
  537.     // Get rid of extraneous records that are no longer being used.
  538.     this._deleteSettingIfUnused(settingID);
  539.     if (groupID)
  540.       this._deleteGroupIfUnused(groupID);
  541.  
  542.     this._cache.removePref(aName, group);
  543.     this._notifyPrefRemoved(group, aName);
  544.   },
  545.  
  546.   removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs() {
  547.     // will not delete global preferences
  548.     if (this._inPrivateBrowsing) {
  549.         // keep only global prefs
  550.         this._privModeStorage.invalidate(true);
  551.     }
  552.     this._cache.invalidate(true);
  553.     this._dbConnection.beginTransaction();
  554.     try {
  555.       this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
  556.       this._dbConnection.executeSimpleSQL("DELETE FROM groups");
  557.       this._dbConnection.executeSimpleSQL(
  558.         "DELETE FROM settings " +
  559.         "WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
  560.       );
  561.       this._dbConnection.commitTransaction();
  562.     }
  563.     catch(ex) {
  564.       this._dbConnection.rollbackTransaction();
  565.       throw ex;
  566.     }
  567.   },
  568.  
  569.   removePrefsByName: function ContentPrefService_removePrefsByName(aName) {
  570.     if (!aName)
  571.       throw Components.Exception("aName cannot be null or an empty string",
  572.                                  Cr.NS_ERROR_ILLEGAL_VALUE);
  573.  
  574.     if (this._inPrivateBrowsing) {
  575.       let groupNames = this._privModeStorage.groupsForName(aName);
  576.       for (var i = 0; i < groupNames.length; i++) {
  577.         let groupName = groupNames[i];
  578.         this._privModeStorage.removePref(aName, groupName);
  579.         this._notifyPrefRemoved(groupName, aName);
  580.       }
  581.     }
  582.  
  583.     var settingID = this._selectSettingID(aName);
  584.     if (!settingID)
  585.       return;
  586.     
  587.     var selectGroupsStmt = this._dbCreateStatement(
  588.       "SELECT groups.id AS groupID, groups.name AS groupName " +
  589.       "FROM prefs " +
  590.       "JOIN groups ON prefs.groupID = groups.id " +
  591.       "WHERE prefs.settingID = :setting "
  592.     );
  593.     
  594.     var groupNames = [];
  595.     var groupIDs = [];
  596.     try {
  597.       selectGroupsStmt.params.setting = settingID;
  598.     
  599.       while (selectGroupsStmt.executeStep()) {
  600.         groupIDs.push(selectGroupsStmt.row["groupID"]);
  601.         groupNames.push(selectGroupsStmt.row["groupName"]);
  602.       }
  603.     }
  604.     finally {
  605.       selectGroupsStmt.reset();
  606.     }
  607.     
  608.     if (this.hasPref(null, aName)) {
  609.       groupNames.push(null);
  610.     }
  611.  
  612.     this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
  613.     this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);
  614.  
  615.     for (var i = 0; i < groupNames.length; i++) {
  616.       this._cache.removePref(aName, groupNames[i]);
  617.       if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
  618.         this._deleteGroupIfUnused(groupIDs[i]);
  619.       if (!this._inPrivateBrowsing) {
  620.         this._notifyPrefRemoved(groupNames[i], aName);
  621.       }
  622.     }
  623.   },
  624.  
  625.   getPrefs: function ContentPrefService_getPrefs(aGroup) {
  626.     var group = this._parseGroupParam(aGroup);
  627.     if (this._inPrivateBrowsing) {
  628.         let prefs = Cc["@mozilla.org/hash-property-bag;1"].
  629.                     createInstance(Ci.nsIWritablePropertyBag);
  630.         let [hasbranch,properties] = this._privModeStorage.getPrefs(group);
  631.         for (let entry in properties) {
  632.           if (properties.hasOwnProperty(entry)) {
  633.             prefs.setProperty(entry, properties[entry]);
  634.           }
  635.         }
  636.         return prefs;
  637.     }
  638.  
  639.     if (group == null)
  640.       return this._selectGlobalPrefs();
  641.     return this._selectPrefs(group);
  642.   },
  643.  
  644.   getPrefsByName: function ContentPrefService_getPrefsByName(aName) {
  645.     if (!aName)
  646.       throw Components.Exception("aName cannot be null or an empty string",
  647.                                  Cr.NS_ERROR_ILLEGAL_VALUE);
  648.  
  649.     if (this._inPrivateBrowsing) {
  650.       let prefs = Cc["@mozilla.org/hash-property-bag;1"].
  651.                   createInstance(Ci.nsIWritablePropertyBag);
  652.       let groupNames = this._privModeStorage.groupsForName(aName);
  653.       for (var i = 0; i < groupNames.length; i++) {
  654.         let groupName = groupNames[i];
  655.         prefs.setProperty(groupName,
  656.                           this._privModeStorage.getPref(aName, groupName)[1]);
  657.       }
  658.       return prefs;
  659.     }
  660.  
  661.     return this._selectPrefsByName(aName);
  662.   },
  663.  
  664.   // boolean to indicate if we are in private browsing mode
  665.   _inPrivateBrowsing: false,
  666.  
  667.   // A hash of arrays of observers, indexed by setting name.
  668.   _observers: {},
  669.  
  670.   // An array of generic observers, which observe all settings.
  671.   _genericObservers: [],
  672.  
  673.   addObserver: function ContentPrefService_addObserver(aName, aObserver) {
  674.     var observers;
  675.     if (aName) {
  676.       if (!this._observers[aName])
  677.         this._observers[aName] = [];
  678.       observers = this._observers[aName];
  679.     }
  680.     else
  681.       observers = this._genericObservers;
  682.  
  683.     if (observers.indexOf(aObserver) == -1)
  684.       observers.push(aObserver);
  685.   },
  686.  
  687.   removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
  688.     var observers;
  689.     if (aName) {
  690.       if (!this._observers[aName])
  691.         return;
  692.       observers = this._observers[aName];
  693.     }
  694.     else
  695.       observers = this._genericObservers;
  696.  
  697.     if (observers.indexOf(aObserver) != -1)
  698.       observers.splice(observers.indexOf(aObserver), 1);
  699.   },
  700.  
  701.   /**
  702.    * Construct a list of observers to notify about a change to some setting,
  703.    * putting setting-specific observers before before generic ones, so observers
  704.    * that initialize individual settings (like the page style controller)
  705.    * execute before observers that display multiple settings and depend on them
  706.    * being initialized first (like the content prefs sidebar).
  707.    */
  708.   _getObservers: function ContentPrefService__getObservers(aName) {
  709.     var observers = [];
  710.  
  711.     if (aName && this._observers[aName])
  712.       observers = observers.concat(this._observers[aName]);
  713.     observers = observers.concat(this._genericObservers);
  714.  
  715.     return observers;
  716.   },
  717.   
  718.   _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName) {
  719.     for each (var observer in this._getObservers(aName)) {
  720.       try {
  721.         observer.onContentPrefRemoved(aGroup, aName);
  722.       }
  723.       catch(ex) {
  724.         Cu.reportError(ex);
  725.       }
  726.     }
  727.   },
  728.  
  729.   _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue) {
  730.     for each (var observer in this._getObservers(aName)) {
  731.       try {
  732.         observer.onContentPrefSet(aGroup, aName, aValue);
  733.       }
  734.       catch(ex) {
  735.         Cu.reportError(ex);
  736.       }
  737.     }
  738.   },
  739.  
  740.   _grouper: null,
  741.   get grouper() {
  742.     if (!this._grouper)
  743.       this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
  744.                       getService(Ci.nsIContentURIGrouper);
  745.     return this._grouper;
  746.   },
  747.  
  748.   get DBConnection() {
  749.     return this._dbConnection;
  750.   },
  751.  
  752.  
  753.   //**************************************************************************//
  754.   // Data Retrieval & Modification
  755.  
  756.   __stmtSelectPref: null,
  757.   get _stmtSelectPref() {
  758.     if (!this.__stmtSelectPref)
  759.       this.__stmtSelectPref = this._dbCreateStatement(
  760.         "SELECT prefs.value AS value " +
  761.         "FROM prefs " +
  762.         "JOIN groups ON prefs.groupID = groups.id " +
  763.         "JOIN settings ON prefs.settingID = settings.id " +
  764.         "WHERE groups.name = :group " +
  765.         "AND settings.name = :setting"
  766.       );
  767.  
  768.     return this.__stmtSelectPref;
  769.   },
  770.  
  771.   _scheduleCallback: function(func) {
  772.     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
  773.     tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
  774.   },
  775.  
  776.   _selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
  777.     let [cached, value] = this._cache.getPref(aSetting, aGroup);
  778.     if (cached) {
  779.       if (aCallback) {
  780.         this._scheduleCallback(function(){aCallback.onResult(value);});
  781.         return;
  782.       }
  783.       return value;
  784.     }
  785.  
  786.     try {
  787.       this._stmtSelectPref.params.group = aGroup;
  788.       this._stmtSelectPref.params.setting = aSetting;
  789.  
  790.       if (aCallback) {
  791.         let cache = this._cache;
  792.         new AsyncStatement(this._stmtSelectPref).execute({onResult: function(aResult) {
  793.           cache.cachePref(aSetting, aResult, aGroup);
  794.           aCallback.onResult(aResult);
  795.         }});
  796.       }
  797.       else {
  798.         if (this._stmtSelectPref.executeStep()) {
  799.           value = this._stmtSelectPref.row["value"];
  800.         }
  801.         this._cache.cachePref(aSetting, value, aGroup);
  802.       }
  803.     }
  804.     finally {
  805.       this._stmtSelectPref.reset();
  806.     }
  807.  
  808.     return value;
  809.   },
  810.  
  811.   __stmtSelectGlobalPref: null,
  812.   get _stmtSelectGlobalPref() {
  813.     if (!this.__stmtSelectGlobalPref)
  814.       this.__stmtSelectGlobalPref = this._dbCreateStatement(
  815.         "SELECT prefs.value AS value " +
  816.         "FROM prefs " +
  817.         "JOIN settings ON prefs.settingID = settings.id " +
  818.         "WHERE prefs.groupID IS NULL " +
  819.         "AND settings.name = :name"
  820.       );
  821.  
  822.     return this.__stmtSelectGlobalPref;
  823.   },
  824.  
  825.   _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
  826.     let [cached, value] = this._cache.getPref(aName, null);
  827.     if (cached) {
  828.       if (aCallback) {
  829.         this._scheduleCallback(function(){aCallback.onResult(value);});
  830.         return;
  831.       }
  832.       return value;
  833.     }
  834.  
  835.     try {
  836.       this._stmtSelectGlobalPref.params.name = aName;
  837.  
  838.       if (aCallback) {
  839.         let cache = this._cache;
  840.         new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult: function(aResult) {
  841.           cache.cachePref(aName, aResult);
  842.           aCallback.onResult(aResult);
  843.         }});
  844.       }
  845.       else {
  846.         if (this._stmtSelectGlobalPref.executeStep()) {
  847.           value = this._stmtSelectGlobalPref.row["value"];
  848.         }
  849.         this._cache.cachePref(aName, value);
  850.       }
  851.     }
  852.     finally {
  853.       this._stmtSelectGlobalPref.reset();
  854.     }
  855.  
  856.     return value;
  857.   },
  858.  
  859.   __stmtSelectGroupID: null,
  860.   get _stmtSelectGroupID() {
  861.     if (!this.__stmtSelectGroupID)
  862.       this.__stmtSelectGroupID = this._dbCreateStatement(
  863.         "SELECT groups.id AS id " +
  864.         "FROM groups " +
  865.         "WHERE groups.name = :name "
  866.       );
  867.  
  868.     return this.__stmtSelectGroupID;
  869.   },
  870.  
  871.   _selectGroupID: function ContentPrefService__selectGroupID(aName) {
  872.     var id;
  873.  
  874.     try {
  875.       this._stmtSelectGroupID.params.name = aName;
  876.  
  877.       if (this._stmtSelectGroupID.executeStep())
  878.         id = this._stmtSelectGroupID.row["id"];
  879.     }
  880.     finally {
  881.       this._stmtSelectGroupID.reset();
  882.     }
  883.  
  884.     return id;
  885.   },
  886.  
  887.   __stmtInsertGroup: null,
  888.   get _stmtInsertGroup() {
  889.     if (!this.__stmtInsertGroup)
  890.       this.__stmtInsertGroup = this._dbCreateStatement(
  891.         "INSERT INTO groups (name) VALUES (:name)"
  892.       );
  893.  
  894.     return this.__stmtInsertGroup;
  895.   },
  896.  
  897.   _insertGroup: function ContentPrefService__insertGroup(aName) {
  898.     this._stmtInsertGroup.params.name = aName;
  899.     this._stmtInsertGroup.execute();
  900.     return this._dbConnection.lastInsertRowID;
  901.   },
  902.  
  903.   __stmtSelectSettingID: null,
  904.   get _stmtSelectSettingID() {
  905.     if (!this.__stmtSelectSettingID)
  906.       this.__stmtSelectSettingID = this._dbCreateStatement(
  907.         "SELECT id FROM settings WHERE name = :name"
  908.       );
  909.  
  910.     return this.__stmtSelectSettingID;
  911.   },
  912.  
  913.   _selectSettingID: function ContentPrefService__selectSettingID(aName) {
  914.     var id;
  915.  
  916.     try {
  917.       this._stmtSelectSettingID.params.name = aName;
  918.  
  919.       if (this._stmtSelectSettingID.executeStep())
  920.         id = this._stmtSelectSettingID.row["id"];
  921.     }
  922.     finally {
  923.       this._stmtSelectSettingID.reset();
  924.     }
  925.  
  926.     return id;
  927.   },
  928.  
  929.   __stmtInsertSetting: null,
  930.   get _stmtInsertSetting() {
  931.     if (!this.__stmtInsertSetting)
  932.       this.__stmtInsertSetting = this._dbCreateStatement(
  933.         "INSERT INTO settings (name) VALUES (:name)"
  934.       );
  935.  
  936.     return this.__stmtInsertSetting;
  937.   },
  938.  
  939.   _insertSetting: function ContentPrefService__insertSetting(aName) {
  940.     this._stmtInsertSetting.params.name = aName;
  941.     this._stmtInsertSetting.execute();
  942.     return this._dbConnection.lastInsertRowID;
  943.   },
  944.  
  945.   __stmtSelectPrefID: null,
  946.   get _stmtSelectPrefID() {
  947.     if (!this.__stmtSelectPrefID)
  948.       this.__stmtSelectPrefID = this._dbCreateStatement(
  949.         "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
  950.       );
  951.  
  952.     return this.__stmtSelectPrefID;
  953.   },
  954.  
  955.   _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
  956.     var id;
  957.  
  958.     try {
  959.       this._stmtSelectPrefID.params.groupID = aGroupID;
  960.       this._stmtSelectPrefID.params.settingID = aSettingID;
  961.  
  962.       if (this._stmtSelectPrefID.executeStep())
  963.         id = this._stmtSelectPrefID.row["id"];
  964.     }
  965.     finally {
  966.       this._stmtSelectPrefID.reset();
  967.     }
  968.  
  969.     return id;
  970.   },
  971.  
  972.   __stmtSelectGlobalPrefID: null,
  973.   get _stmtSelectGlobalPrefID() {
  974.     if (!this.__stmtSelectGlobalPrefID)
  975.       this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
  976.         "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
  977.       );
  978.  
  979.     return this.__stmtSelectGlobalPrefID;
  980.   },
  981.  
  982.   _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
  983.     var id;
  984.  
  985.     try {
  986.       this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
  987.  
  988.       if (this._stmtSelectGlobalPrefID.executeStep())
  989.         id = this._stmtSelectGlobalPrefID.row["id"];
  990.     }
  991.     finally {
  992.       this._stmtSelectGlobalPrefID.reset();
  993.     }
  994.  
  995.     return id;
  996.   },
  997.  
  998.   __stmtInsertPref: null,
  999.   get _stmtInsertPref() {
  1000.     if (!this.__stmtInsertPref)
  1001.       this.__stmtInsertPref = this._dbCreateStatement(
  1002.         "INSERT INTO prefs (groupID, settingID, value) " +
  1003.         "VALUES (:groupID, :settingID, :value)"
  1004.       );
  1005.  
  1006.     return this.__stmtInsertPref;
  1007.   },
  1008.  
  1009.   _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
  1010.     this._stmtInsertPref.params.groupID = aGroupID;
  1011.     this._stmtInsertPref.params.settingID = aSettingID;
  1012.     this._stmtInsertPref.params.value = aValue;
  1013.     this._stmtInsertPref.execute();
  1014.     return this._dbConnection.lastInsertRowID;
  1015.   },
  1016.  
  1017.   __stmtUpdatePref: null,
  1018.   get _stmtUpdatePref() {
  1019.     if (!this.__stmtUpdatePref)
  1020.       this.__stmtUpdatePref = this._dbCreateStatement(
  1021.         "UPDATE prefs SET value = :value WHERE id = :id"
  1022.       );
  1023.  
  1024.     return this.__stmtUpdatePref;
  1025.   },
  1026.  
  1027.   _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
  1028.     this._stmtUpdatePref.params.id = aPrefID;
  1029.     this._stmtUpdatePref.params.value = aValue;
  1030.     this._stmtUpdatePref.execute();
  1031.   },
  1032.  
  1033.   __stmtDeletePref: null,
  1034.   get _stmtDeletePref() {
  1035.     if (!this.__stmtDeletePref)
  1036.       this.__stmtDeletePref = this._dbCreateStatement(
  1037.         "DELETE FROM prefs WHERE id = :id"
  1038.       );
  1039.  
  1040.     return this.__stmtDeletePref;
  1041.   },
  1042.  
  1043.   _deletePref: function ContentPrefService__deletePref(aPrefID) {
  1044.     this._stmtDeletePref.params.id = aPrefID;
  1045.     this._stmtDeletePref.execute();
  1046.   },
  1047.  
  1048.   __stmtDeleteSettingIfUnused: null,
  1049.   get _stmtDeleteSettingIfUnused() {
  1050.     if (!this.__stmtDeleteSettingIfUnused)
  1051.       this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(
  1052.         "DELETE FROM settings WHERE id = :id " +
  1053.         "AND id NOT IN (SELECT DISTINCT settingID FROM prefs)"
  1054.       );
  1055.  
  1056.     return this.__stmtDeleteSettingIfUnused;
  1057.   },
  1058.  
  1059.   _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
  1060.     this._stmtDeleteSettingIfUnused.params.id = aSettingID;
  1061.     this._stmtDeleteSettingIfUnused.execute();
  1062.   },
  1063.  
  1064.   __stmtDeleteGroupIfUnused: null,
  1065.   get _stmtDeleteGroupIfUnused() {
  1066.     if (!this.__stmtDeleteGroupIfUnused)
  1067.       this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(
  1068.         "DELETE FROM groups WHERE id = :id " +
  1069.         "AND id NOT IN (SELECT DISTINCT groupID FROM prefs)"
  1070.       );
  1071.  
  1072.     return this.__stmtDeleteGroupIfUnused;
  1073.   },
  1074.  
  1075.   _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
  1076.     this._stmtDeleteGroupIfUnused.params.id = aGroupID;
  1077.     this._stmtDeleteGroupIfUnused.execute();
  1078.   },
  1079.  
  1080.   __stmtSelectPrefs: null,
  1081.   get _stmtSelectPrefs() {
  1082.     if (!this.__stmtSelectPrefs)
  1083.       this.__stmtSelectPrefs = this._dbCreateStatement(
  1084.         "SELECT settings.name AS name, prefs.value AS value " +
  1085.         "FROM prefs " +
  1086.         "JOIN groups ON prefs.groupID = groups.id " +
  1087.         "JOIN settings ON prefs.settingID = settings.id " +
  1088.         "WHERE groups.name = :group "
  1089.       );
  1090.  
  1091.     return this.__stmtSelectPrefs;
  1092.   },
  1093.  
  1094.   _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
  1095.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  1096.                 createInstance(Ci.nsIWritablePropertyBag);
  1097.  
  1098.     try {
  1099.       this._stmtSelectPrefs.params.group = aGroup;
  1100.  
  1101.       while (this._stmtSelectPrefs.executeStep())
  1102.         prefs.setProperty(this._stmtSelectPrefs.row["name"],
  1103.                           this._stmtSelectPrefs.row["value"]);
  1104.     }
  1105.     finally {
  1106.       this._stmtSelectPrefs.reset();
  1107.     }
  1108.  
  1109.     return prefs;
  1110.   },
  1111.  
  1112.   __stmtSelectGlobalPrefs: null,
  1113.   get _stmtSelectGlobalPrefs() {
  1114.     if (!this.__stmtSelectGlobalPrefs)
  1115.       this.__stmtSelectGlobalPrefs = this._dbCreateStatement(
  1116.         "SELECT settings.name AS name, prefs.value AS value " +
  1117.         "FROM prefs " +
  1118.         "JOIN settings ON prefs.settingID = settings.id " +
  1119.         "WHERE prefs.groupID IS NULL"
  1120.       );
  1121.  
  1122.     return this.__stmtSelectGlobalPrefs;
  1123.   },
  1124.  
  1125.   _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
  1126.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  1127.                 createInstance(Ci.nsIWritablePropertyBag);
  1128.  
  1129.     try {
  1130.       while (this._stmtSelectGlobalPrefs.executeStep())
  1131.         prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
  1132.                           this._stmtSelectGlobalPrefs.row["value"]);
  1133.     }
  1134.     finally {
  1135.       this._stmtSelectGlobalPrefs.reset();
  1136.     }
  1137.  
  1138.     return prefs;
  1139.   },
  1140.  
  1141.   __stmtSelectPrefsByName: null,
  1142.   get _stmtSelectPrefsByName() {
  1143.     if (!this.__stmtSelectPrefsByName)
  1144.       this.__stmtSelectPrefsByName = this._dbCreateStatement(
  1145.         "SELECT groups.name AS groupName, prefs.value AS value " +
  1146.         "FROM prefs " +
  1147.         "JOIN groups ON prefs.groupID = groups.id " +
  1148.         "JOIN settings ON prefs.settingID = settings.id " +
  1149.         "WHERE settings.name = :setting "
  1150.       );
  1151.  
  1152.     return this.__stmtSelectPrefsByName;
  1153.   },
  1154.  
  1155.   _selectPrefsByName: function ContentPrefService__selectPrefsByName(aName) {
  1156.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  1157.                 createInstance(Ci.nsIWritablePropertyBag);
  1158.  
  1159.     try {
  1160.       this._stmtSelectPrefsByName.params.setting = aName;
  1161.  
  1162.       while (this._stmtSelectPrefsByName.executeStep())
  1163.         prefs.setProperty(this._stmtSelectPrefsByName.row["groupName"],
  1164.                           this._stmtSelectPrefsByName.row["value"]);
  1165.     }
  1166.     finally {
  1167.       this._stmtSelectPrefsByName.reset();
  1168.     }
  1169.     
  1170.     var global = this._selectGlobalPref(aName);
  1171.     if (typeof global != "undefined") {
  1172.       prefs.setProperty(null, global);
  1173.     }
  1174.  
  1175.     return prefs;
  1176.   },
  1177.  
  1178.  
  1179.   //**************************************************************************//
  1180.   // Database Creation & Access
  1181.  
  1182.   _dbVersion: 3,
  1183.  
  1184.   _dbSchema: {
  1185.     tables: {
  1186.       groups:     "id           INTEGER PRIMARY KEY, \
  1187.                    name         TEXT NOT NULL",
  1188.   
  1189.       settings:   "id           INTEGER PRIMARY KEY, \
  1190.                    name         TEXT NOT NULL",
  1191.   
  1192.       prefs:      "id           INTEGER PRIMARY KEY, \
  1193.                    groupID      INTEGER REFERENCES groups(id), \
  1194.                    settingID    INTEGER NOT NULL REFERENCES settings(id), \
  1195.                    value        BLOB"
  1196.     },
  1197.     indices: {
  1198.       groups_idx: {
  1199.         table: "groups",
  1200.         columns: ["name"]
  1201.       },
  1202.       settings_idx: {
  1203.         table: "settings",
  1204.         columns: ["name"]
  1205.       },
  1206.       prefs_idx: {
  1207.         table: "prefs",
  1208.         columns: ["groupID", "settingID"]
  1209.       }
  1210.     }
  1211.   },
  1212.  
  1213.   _dbConnection: null,
  1214.  
  1215.   _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
  1216.     try {
  1217.       var statement = this._dbConnection.createStatement(aSQLString);
  1218.     }
  1219.     catch(ex) {
  1220.       Cu.reportError("error creating statement " + aSQLString + ": " +
  1221.                      this._dbConnection.lastError + " - " +
  1222.                      this._dbConnection.lastErrorString);
  1223.       throw ex;
  1224.     }
  1225.  
  1226.     return statement;
  1227.   },
  1228.  
  1229.   // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
  1230.   // specific migration methods) must be careful not to call any method
  1231.   // of the service that assumes the database connection has already been
  1232.   // initialized, since it won't be initialized until at the end of _dbInit.
  1233.  
  1234.   _dbInit: function ContentPrefService__dbInit() {
  1235.     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  1236.                      getService(Ci.nsIProperties);
  1237.     var dbFile = dirService.get("ProfD", Ci.nsIFile);
  1238.     dbFile.append("content-prefs.sqlite");
  1239.  
  1240.     var dbService = Cc["@mozilla.org/storage/service;1"].
  1241.                     getService(Ci.mozIStorageService);
  1242.  
  1243.     var dbConnection;
  1244.  
  1245.     if (!dbFile.exists())
  1246.       dbConnection = this._dbCreate(dbService, dbFile);
  1247.     else {
  1248.       try {
  1249.         dbConnection = dbService.openDatabase(dbFile);
  1250.       }
  1251.       // If the connection isn't ready after we open the database, that means
  1252.       // the database has been corrupted, so we back it up and then recreate it.
  1253.       catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
  1254.         dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
  1255.                                                  dbConnection);
  1256.       }
  1257.  
  1258.       // Get the version of the schema in the file.
  1259.       var version = dbConnection.schemaVersion;
  1260.  
  1261.       // Try to migrate the schema in the database to the current schema used by
  1262.       // the service.  If migration fails, back up the database and recreate it.
  1263.       if (version != this._dbVersion) {
  1264.         try {
  1265.           this._dbMigrate(dbConnection, version, this._dbVersion);
  1266.         }
  1267.         catch(ex) {
  1268.           Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
  1269.           dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
  1270.         }
  1271.       }
  1272.     }
  1273.  
  1274.     // Turn off disk synchronization checking to reduce disk churn and speed up
  1275.     // operations when prefs are changed rapidly (such as when a user repeatedly
  1276.     // changes the value of the browser zoom setting for a site).
  1277.     //
  1278.     // Note: this could cause database corruption if the OS crashes or machine
  1279.     // loses power before the data gets written to disk, but this is considered
  1280.     // a reasonable risk for the not-so-critical data stored in this database.
  1281.     //
  1282.     // If you really don't want to take this risk, however, just set the
  1283.     // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
  1284.     // (FULL synchronization), in which case mozStorageConnection::Initialize
  1285.     // will use that value, and we won't override it here.
  1286.     if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
  1287.       dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
  1288.  
  1289.     this._dbConnection = dbConnection;
  1290.   },
  1291.  
  1292.   _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
  1293.     var dbConnection = aDBService.openDatabase(aDBFile);
  1294.  
  1295.     try {
  1296.       this._dbCreateSchema(dbConnection);
  1297.       dbConnection.schemaVersion = this._dbVersion;
  1298.     }
  1299.     catch(ex) {
  1300.       // If we failed to create the database (perhaps because the disk ran out
  1301.       // of space), then remove the database file so we don't leave it in some
  1302.       // half-created state from which we won't know how to recover.
  1303.       dbConnection.close();
  1304.       aDBFile.remove(false);
  1305.       throw ex;
  1306.     }
  1307.  
  1308.     return dbConnection;
  1309.   },
  1310.  
  1311.   _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
  1312.     this._dbCreateTables(aDBConnection);
  1313.     this._dbCreateIndices(aDBConnection);
  1314.   },
  1315.  
  1316.   _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
  1317.     for (let name in this._dbSchema.tables)
  1318.       aDBConnection.createTable(name, this._dbSchema.tables[name]);
  1319.   },
  1320.  
  1321.   _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
  1322.     for (let name in this._dbSchema.indices) {
  1323.       let index = this._dbSchema.indices[name];
  1324.       let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
  1325.                       "(" + index.columns.join(", ") + ")";
  1326.       aDBConnection.executeSimpleSQL(statement);
  1327.     }
  1328.   },
  1329.  
  1330.   _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
  1331.                                                                          aDBFile,
  1332.                                                                          aDBConnection) {
  1333.     aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
  1334.  
  1335.     // Close the database, ignoring the "already closed" exception, if any.
  1336.     // It'll be open if we're here because of a migration failure but closed
  1337.     // if we're here because of database corruption.
  1338.     try { aDBConnection.close() } catch(ex) {}
  1339.  
  1340.     aDBFile.remove(false);
  1341.  
  1342.     let dbConnection = this._dbCreate(aDBService, aDBFile);
  1343.  
  1344.     return dbConnection;
  1345.   },
  1346.  
  1347.   _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
  1348.     if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
  1349.       aDBConnection.beginTransaction();
  1350.       try {
  1351.         this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
  1352.         aDBConnection.schemaVersion = aNewVersion;
  1353.         aDBConnection.commitTransaction();
  1354.       }
  1355.       catch(ex) {
  1356.         aDBConnection.rollbackTransaction();
  1357.         throw ex;
  1358.       }
  1359.     }
  1360.     else
  1361.       throw("no migrator function from version " + aOldVersion +
  1362.             " to version " + aNewVersion);
  1363.   },
  1364.  
  1365.   /**
  1366.    * If the schema version is 0, that means it was never set, which means
  1367.    * the database was somehow created without the schema being applied, perhaps
  1368.    * because the system ran out of disk space (although we check for this
  1369.    * in _createDB) or because some other code created the database file without
  1370.    * applying the schema.  In any case, recover by simply reapplying the schema.
  1371.    */
  1372.   _dbMigrate0To3: function ContentPrefService___dbMigrate0To3(aDBConnection) {
  1373.     this._dbCreateSchema(aDBConnection);
  1374.   },
  1375.  
  1376.   _dbMigrate1To3: function ContentPrefService___dbMigrate1To3(aDBConnection) {
  1377.     aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
  1378.     aDBConnection.createTable("groups", this._dbSchema.tables.groups);
  1379.     aDBConnection.executeSimpleSQL(
  1380.       "INSERT INTO groups (id, name) " +
  1381.       "SELECT id, name FROM groupsOld"
  1382.     );
  1383.  
  1384.     aDBConnection.executeSimpleSQL("DROP TABLE groupers");
  1385.     aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
  1386.  
  1387.     this._dbCreateIndices(aDBConnection);
  1388.   },
  1389.  
  1390.   _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
  1391.     this._dbCreateIndices(aDBConnection);
  1392.   },
  1393.  
  1394.   _parseGroupParam: function ContentPrefService__parseGroupParam(aGroup) {
  1395.     if (aGroup == null)
  1396.       return null;
  1397.     if (aGroup.constructor.name == "String")
  1398.       return aGroup.toString();
  1399.     if (aGroup instanceof Ci.nsIURI)
  1400.       return this.grouper.group(aGroup);
  1401.  
  1402.     throw Components.Exception("aGroup is not a string, nsIURI or null",
  1403.                                Cr.NS_ERROR_ILLEGAL_VALUE);
  1404.   },
  1405. };
  1406.  
  1407.  
  1408. function HostnameGrouper() {}
  1409.  
  1410. HostnameGrouper.prototype = {
  1411.   //**************************************************************************//
  1412.   // XPCOM Plumbing
  1413.   
  1414.   classID:          Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
  1415.   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
  1416.  
  1417.   //**************************************************************************//
  1418.   // nsIContentURIGrouper
  1419.  
  1420.   group: function HostnameGrouper_group(aURI) {
  1421.     var group;
  1422.  
  1423.     try {
  1424.       // Accessing the host property of the URI will throw an exception
  1425.       // if the URI is of a type that doesn't have a host property.
  1426.       // Otherwise, we manually throw an exception if the host is empty,
  1427.       // since the effect is the same (we can't derive a group from it).
  1428.  
  1429.       group = aURI.host;
  1430.       if (!group)
  1431.         throw("can't derive group from host; no host in URI");
  1432.     }
  1433.     catch(ex) {
  1434.       // If we don't have a host, then use the entire URI (minus the query,
  1435.       // reference, and hash, if possible) as the group.  This means that URIs
  1436.       // like about:mozilla and about:blank will be considered separate groups,
  1437.       // but at least they'll be grouped somehow.
  1438.       
  1439.       // This also means that each individual file: URL will be considered
  1440.       // its own group.  This seems suboptimal, but so does treating the entire
  1441.       // file: URL space as a single group (especially if folks start setting
  1442.       // group-specific capabilities prefs).
  1443.  
  1444.       // XXX Is there something better we can do here?
  1445.  
  1446.       try {
  1447.         var url = aURI.QueryInterface(Ci.nsIURL);
  1448.         group = aURI.prePath + url.filePath;
  1449.       }
  1450.       catch(ex) {
  1451.         group = aURI.spec;
  1452.       }
  1453.     }
  1454.  
  1455.     return group;
  1456.   }
  1457. };
  1458.  
  1459. function AsyncStatement(aStatement) {
  1460.   this.stmt = aStatement;
  1461. }
  1462.  
  1463. AsyncStatement.prototype = {
  1464.   execute: function AsyncStmt_execute(aCallback) {
  1465.     let stmt = this.stmt;
  1466.     stmt.executeAsync({
  1467.       _callback: aCallback,
  1468.       _hadResult: false,
  1469.       handleResult: function(aResult) {
  1470.         this._hadResult = true;
  1471.         if (this._callback) {
  1472.           let row = aResult.getNextRow();
  1473.           this._callback.onResult(row.getResultByName("value"));
  1474.         }
  1475.       },
  1476.       handleCompletion: function(aReason) {
  1477.         if (!this._hadResult && this._callback &&
  1478.             aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED)
  1479.           this._callback.onResult(undefined);
  1480.       },
  1481.       handleError: function(aError) {}
  1482.     });
  1483.   }
  1484. };
  1485.  
  1486. //****************************************************************************//
  1487. // XPCOM Plumbing
  1488.  
  1489. var components = [ContentPrefService, HostnameGrouper];
  1490. var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
  1491.