home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 February / PCWorld_2008-02_cd.bin / temacd / songbird / Songbird_0.4_windows-i686.exe / xulrunner / components / nsContentPrefService.js < prev    next >
Text File  |  2007-11-30  |  28KB  |  871 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.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const Ci = Components.interfaces;
  38. const Cc = Components.classes;
  39. const Cr = Components.results;
  40. const Cu = Components.utils;
  41.  
  42. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  43.  
  44. function ContentPrefService() {
  45.   // If this throws an exception, it causes the getService call to fail,
  46.   // but the next time a consumer tries to retrieve the service, we'll try
  47.   // to initialize the database again, which might work if the failure
  48.   // was due to a temporary condition (like being out of disk space).
  49.   this._dbInit();
  50.  
  51.   // Observe shutdown so we can shut down the database connection.
  52.   this._observerSvc.addObserver(this, "xpcom-shutdown", false);
  53. }
  54.  
  55. ContentPrefService.prototype = {
  56.   //**************************************************************************//
  57.   // XPCOM Plumbing
  58.  
  59.   classDescription: "Content Pref Service",
  60.   classID:          Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
  61.   contractID:       "@mozilla.org/content-pref/service;1",
  62.   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentPrefService]),
  63.  
  64.  
  65.   //**************************************************************************//
  66.   // Convenience Getters
  67.  
  68.   // Observer Service
  69.   __observerSvc: null,
  70.   get _observerSvc ContentPrefService_get__observerSvc() {
  71.     if (!this.__observerSvc)
  72.       this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
  73.                            getService(Ci.nsIObserverService);
  74.     return this.__observerSvc;
  75.   },
  76.  
  77.   // Console Service
  78.   __consoleSvc: null,
  79.   get _consoleSvc ContentPrefService_get__consoleSvc() {
  80.     if (!this.__consoleSvc)
  81.       this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
  82.                           getService(Ci.nsIConsoleService);
  83.     return this.__consoleSvc;
  84.   },
  85.  
  86.  
  87.   //**************************************************************************//
  88.   // Destruction
  89.  
  90.   _destroy: function ContentPrefService__destroy() {
  91.     this._observerSvc.removeObserver(this, "xpcom-shutdown");
  92.  
  93.     // Delete references to XPCOM components to make sure we don't leak them
  94.     // (although we haven't observed leakage in tests).  Also delete references
  95.     // in _observers and _genericObservers to avoid cycles with those that
  96.     // refer to us and don't remove themselves from those observer pools.
  97.     for (var i in this) {
  98.       try { this[i] = null }
  99.       // Ignore "setting a property that has only a getter" exceptions.
  100.       catch(ex) {}
  101.     }
  102.   },
  103.  
  104.  
  105.   //**************************************************************************//
  106.   // nsIObserver
  107.  
  108.   observe: function ContentPrefService_observe(subject, topic, data) {
  109.     switch (topic) {
  110.       case "xpcom-shutdown":
  111.         this._destroy();
  112.         break;
  113.     }
  114.   },
  115.  
  116.  
  117.   //**************************************************************************//
  118.   // nsIContentPrefService
  119.  
  120.   getPref: function ContentPrefService_getPref(aURI, aName) {
  121.     if (aURI) {
  122.       var group = this.grouper.group(aURI);
  123.       return this._selectPref(group, aName);
  124.     }
  125.  
  126.     return this._selectGlobalPref(aName);
  127.   },
  128.  
  129.   setPref: function ContentPrefService_setPref(aURI, aName, aValue) {
  130.     // If the pref is already set to the value, there's nothing more to do.
  131.     var currentValue = this.getPref(aURI, aName);
  132.     if (typeof currentValue != "undefined" && currentValue == aValue)
  133.       return;
  134.  
  135.     var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
  136.     var group, groupID, prefID;
  137.     if (aURI) {
  138.       group = this.grouper.group(aURI);
  139.       groupID = this._selectGroupID(group) || this._insertGroup(group);
  140.       prefID = this._selectPrefID(groupID, settingID);
  141.     }
  142.     else {
  143.       group = null;
  144.       groupID = null;
  145.       prefID = this._selectGlobalPrefID(settingID);
  146.     }
  147.  
  148.     // Update the existing record, if any, or create a new one.
  149.     if (prefID)
  150.       this._updatePref(prefID, aValue);
  151.     else
  152.       this._insertPref(groupID, settingID, aValue);
  153.  
  154.     for each (var observer in this._getObservers(aName)) {
  155.       try {
  156.         observer.onContentPrefSet(group, aName, aValue);
  157.       }
  158.       catch(ex) {
  159.         Cu.reportError(ex);
  160.       }
  161.     }
  162.   },
  163.  
  164.   hasPref: function ContentPrefService_hasPref(aURI, aName) {
  165.     // XXX If consumers end up calling this method regularly, then we should
  166.     // optimize this to query the database directly.
  167.     return (typeof this.getPref(aURI, aName) != "undefined");
  168.   },
  169.  
  170.   removePref: function ContentPrefService_removePref(aURI, aName) {
  171.     // If there's no old value, then there's nothing to remove.
  172.     if (!this.hasPref(aURI, aName))
  173.       return;
  174.  
  175.     var settingID = this._selectSettingID(aName);
  176.     var group, groupID, prefID;
  177.     if (aURI) {
  178.       group = this.grouper.group(aURI);
  179.       groupID = this._selectGroupID(group);
  180.       prefID = this._selectPrefID(groupID, settingID);
  181.     }
  182.     else {
  183.       group = null;
  184.       groupID = null;
  185.       prefID = this._selectGlobalPrefID(settingID);
  186.     }
  187.  
  188.     this._deletePref(prefID);
  189.  
  190.     // Get rid of extraneous records that are no longer being used.
  191.     this._deleteSettingIfUnused(settingID);
  192.     if (groupID)
  193.       this._deleteGroupIfUnused(groupID);
  194.  
  195.     for each (var observer in this._getObservers(aName)) {
  196.       try {
  197.         observer.onContentPrefRemoved(group, aName);
  198.       }
  199.       catch(ex) {
  200.         Cu.reportError(ex);
  201.       }
  202.     }
  203.   },
  204.  
  205.   getPrefs: function ContentPrefService_getPrefs(aURI) {
  206.     if (aURI) {
  207.       var group = this.grouper.group(aURI);
  208.       return this._selectPrefs(group);
  209.     }
  210.  
  211.     return this._selectGlobalPrefs();
  212.   },
  213.  
  214.   // A hash of arrays of observers, indexed by setting name.
  215.   _observers: {},
  216.  
  217.   // An array of generic observers, which observe all settings.
  218.   _genericObservers: [],
  219.  
  220.   addObserver: function ContentPrefService_addObserver(aName, aObserver) {
  221.     var observers;
  222.     if (aName) {
  223.       if (!this._observers[aName])
  224.         this._observers[aName] = [];
  225.       observers = this._observers[aName];
  226.     }
  227.     else
  228.       observers = this._genericObservers;
  229.  
  230.     if (observers.indexOf(aObserver) == -1)
  231.       observers.push(aObserver);
  232.   },
  233.  
  234.   removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
  235.     var observers;
  236.     if (aName) {
  237.       if (!this._observers[aName])
  238.         return;
  239.       observers = this._observers[aName];
  240.     }
  241.     else
  242.       observers = this._genericObservers;
  243.  
  244.     if (observers.indexOf(aObserver) != -1)
  245.       observers.splice(observers.indexOf(aObserver), 1);
  246.   },
  247.  
  248.   /**
  249.    * Construct a list of observers to notify about a change to some setting,
  250.    * putting setting-specific observers before before generic ones, so observers
  251.    * that initialize individual settings (like the page style controller)
  252.    * execute before observers that display multiple settings and depend on them
  253.    * being initialized first (like the content prefs sidebar).
  254.    */
  255.   _getObservers: function ContentPrefService__getObservers(aName) {
  256.     var observers = [];
  257.  
  258.     if (aName && this._observers[aName])
  259.       observers = observers.concat(this._observers[aName]);
  260.     observers = observers.concat(this._genericObservers);
  261.  
  262.     return observers;
  263.   },
  264.  
  265.   _grouper: null,
  266.   get grouper ContentPrefService_get_grouper() {
  267.     if (!this._grouper)
  268.       this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
  269.                       getService(Ci.nsIContentURIGrouper);
  270.     return this._grouper;
  271.   },
  272.  
  273.  
  274.   //**************************************************************************//
  275.   // Data Retrieval & Modification
  276.  
  277.   __stmtSelectPref: null,
  278.   get _stmtSelectPref ContentPrefService_get__stmtSelectPref() {
  279.     if (!this.__stmtSelectPref)
  280.       this.__stmtSelectPref = this._dbCreateStatement(
  281.         "SELECT prefs.value AS value " +
  282.         "FROM prefs " +
  283.         "JOIN groups ON prefs.groupID = groups.id " +
  284.         "JOIN settings ON prefs.settingID = settings.id " +
  285.         "WHERE groups.name = :group " +
  286.         "AND settings.name = :setting"
  287.       );
  288.  
  289.     return this.__stmtSelectPref;
  290.   },
  291.  
  292.   _selectPref: function ContentPrefService__selectPref(aGroup, aSetting) {
  293.     var value;
  294.  
  295.     try {
  296.       this._stmtSelectPref.params.group = aGroup;
  297.       this._stmtSelectPref.params.setting = aSetting;
  298.  
  299.       if (this._stmtSelectPref.step())
  300.         value = this._stmtSelectPref.row["value"];
  301.     }
  302.     finally {
  303.       this._stmtSelectPref.reset();
  304.     }
  305.  
  306.     return value;
  307.   },
  308.  
  309.   __stmtSelectGlobalPref: null,
  310.   get _stmtSelectGlobalPref ContentPrefService_get__stmtSelectGlobalPref() {
  311.     if (!this.__stmtSelectGlobalPref)
  312.       this.__stmtSelectGlobalPref = this._dbCreateStatement(
  313.         "SELECT prefs.value AS value " +
  314.         "FROM prefs " +
  315.         "JOIN settings ON prefs.settingID = settings.id " +
  316.         "WHERE prefs.groupID IS NULL " +
  317.         "AND settings.name = :name"
  318.       );
  319.  
  320.     return this.__stmtSelectGlobalPref;
  321.   },
  322.  
  323.   _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName) {
  324.     var value;
  325.  
  326.     try {
  327.       this._stmtSelectGlobalPref.params.name = aName;
  328.  
  329.       if (this._stmtSelectGlobalPref.step())
  330.         value = this._stmtSelectGlobalPref.row["value"];
  331.     }
  332.     finally {
  333.       this._stmtSelectGlobalPref.reset();
  334.     }
  335.  
  336.     return value;
  337.   },
  338.  
  339.   __stmtSelectGroupID: null,
  340.   get _stmtSelectGroupID ContentPrefService_get__stmtSelectGroupID() {
  341.     if (!this.__stmtSelectGroupID)
  342.       this.__stmtSelectGroupID = this._dbCreateStatement(
  343.         "SELECT groups.id AS id " +
  344.         "FROM groups " +
  345.         "WHERE groups.name = :name "
  346.       );
  347.  
  348.     return this.__stmtSelectGroupID;
  349.   },
  350.  
  351.   _selectGroupID: function ContentPrefService__selectGroupID(aName) {
  352.     var id;
  353.  
  354.     try {
  355.       this._stmtSelectGroupID.params.name = aName;
  356.  
  357.       if (this._stmtSelectGroupID.step())
  358.         id = this._stmtSelectGroupID.row["id"];
  359.     }
  360.     finally {
  361.       this._stmtSelectGroupID.reset();
  362.     }
  363.  
  364.     return id;
  365.   },
  366.  
  367.   __stmtInsertGroup: null,
  368.   get _stmtInsertGroup ContentPrefService_get__stmtInsertGroup() {
  369.     if (!this.__stmtInsertGroup)
  370.       this.__stmtInsertGroup = this._dbCreateStatement(
  371.         "INSERT INTO groups (name) VALUES (:name)"
  372.       );
  373.  
  374.     return this.__stmtInsertGroup;
  375.   },
  376.  
  377.   _insertGroup: function ContentPrefService__insertGroup(aName) {
  378.     this._stmtInsertGroup.params.name = aName;
  379.     this._stmtInsertGroup.execute();
  380.     return this._dbConnection.lastInsertRowID;
  381.   },
  382.  
  383.   __stmtSelectSettingID: null,
  384.   get _stmtSelectSettingID ContentPrefService_get__stmtSelectSettingID() {
  385.     if (!this.__stmtSelectSettingID)
  386.       this.__stmtSelectSettingID = this._dbCreateStatement(
  387.         "SELECT id FROM settings WHERE name = :name"
  388.       );
  389.  
  390.     return this.__stmtSelectSettingID;
  391.   },
  392.  
  393.   _selectSettingID: function ContentPrefService__selectSettingID(aName) {
  394.     var id;
  395.  
  396.     try {
  397.       this._stmtSelectSettingID.params.name = aName;
  398.  
  399.       if (this._stmtSelectSettingID.step())
  400.         id = this._stmtSelectSettingID.row["id"];
  401.     }
  402.     finally {
  403.       this._stmtSelectSettingID.reset();
  404.     }
  405.  
  406.     return id;
  407.   },
  408.  
  409.   __stmtInsertSetting: null,
  410.   get _stmtInsertSetting ContentPrefService_get__stmtInsertSetting() {
  411.     if (!this.__stmtInsertSetting)
  412.       this.__stmtInsertSetting = this._dbCreateStatement(
  413.         "INSERT INTO settings (name) VALUES (:name)"
  414.       );
  415.  
  416.     return this.__stmtInsertSetting;
  417.   },
  418.  
  419.   _insertSetting: function ContentPrefService__insertSetting(aName) {
  420.     this._stmtInsertSetting.params.name = aName;
  421.     this._stmtInsertSetting.execute();
  422.     return this._dbConnection.lastInsertRowID;
  423.   },
  424.  
  425.   __stmtSelectPrefID: null,
  426.   get _stmtSelectPrefID ContentPrefService_get__stmtSelectPrefID() {
  427.     if (!this.__stmtSelectPrefID)
  428.       this.__stmtSelectPrefID = this._dbCreateStatement(
  429.         "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
  430.       );
  431.  
  432.     return this.__stmtSelectPrefID;
  433.   },
  434.  
  435.   _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
  436.     var id;
  437.  
  438.     try {
  439.       this._stmtSelectPrefID.params.groupID = aGroupID;
  440.       this._stmtSelectPrefID.params.settingID = aSettingID;
  441.  
  442.       if (this._stmtSelectPrefID.step())
  443.         id = this._stmtSelectPrefID.row["id"];
  444.     }
  445.     finally {
  446.       this._stmtSelectPrefID.reset();
  447.     }
  448.  
  449.     return id;
  450.   },
  451.  
  452.   __stmtSelectGlobalPrefID: null,
  453.   get _stmtSelectGlobalPrefID ContentPrefService_get__stmtSelectGlobalPrefID() {
  454.     if (!this.__stmtSelectGlobalPrefID)
  455.       this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
  456.         "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
  457.       );
  458.  
  459.     return this.__stmtSelectGlobalPrefID;
  460.   },
  461.  
  462.   _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
  463.     var id;
  464.  
  465.     try {
  466.       this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
  467.  
  468.       if (this._stmtSelectGlobalPrefID.step())
  469.         id = this._stmtSelectGlobalPrefID.row["id"];
  470.     }
  471.     finally {
  472.       this._stmtSelectGlobalPrefID.reset();
  473.     }
  474.  
  475.     return id;
  476.   },
  477.  
  478.   __stmtInsertPref: null,
  479.   get _stmtInsertPref ContentPrefService_get__stmtInsertPref() {
  480.     if (!this.__stmtInsertPref)
  481.       this.__stmtInsertPref = this._dbCreateStatement(
  482.         "INSERT INTO prefs (groupID, settingID, value) " +
  483.         "VALUES (:groupID, :settingID, :value)"
  484.       );
  485.  
  486.     return this.__stmtInsertPref;
  487.   },
  488.  
  489.   _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
  490.     this._stmtInsertPref.params.groupID = aGroupID;
  491.     this._stmtInsertPref.params.settingID = aSettingID;
  492.     this._stmtInsertPref.params.value = aValue;
  493.     this._stmtInsertPref.execute();
  494.     return this._dbConnection.lastInsertRowID;
  495.   },
  496.  
  497.   __stmtUpdatePref: null,
  498.   get _stmtUpdatePref ContentPrefService_get__stmtUpdatePref() {
  499.     if (!this.__stmtUpdatePref)
  500.       this.__stmtUpdatePref = this._dbCreateStatement(
  501.         "UPDATE prefs SET value = :value WHERE id = :id"
  502.       );
  503.  
  504.     return this.__stmtUpdatePref;
  505.   },
  506.  
  507.   _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
  508.     this._stmtUpdatePref.params.id = aPrefID;
  509.     this._stmtUpdatePref.params.value = aValue;
  510.     this._stmtUpdatePref.execute();
  511.   },
  512.  
  513.   __stmtDeletePref: null,
  514.   get _stmtDeletePref ContentPrefService_get__stmtDeletePref() {
  515.     if (!this.__stmtDeletePref)
  516.       this.__stmtDeletePref = this._dbCreateStatement(
  517.         "DELETE FROM prefs WHERE id = :id"
  518.       );
  519.  
  520.     return this.__stmtDeletePref;
  521.   },
  522.  
  523.   _deletePref: function ContentPrefService__deletePref(aPrefID) {
  524.     this._stmtDeletePref.params.id = aPrefID;
  525.     this._stmtDeletePref.execute();
  526.   },
  527.  
  528.   __stmtDeleteSettingIfUnused: null,
  529.   get _stmtDeleteSettingIfUnused ContentPrefService_get__stmtDeleteSettingIfUnused() {
  530.     if (!this.__stmtDeleteSettingIfUnused)
  531.       this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(
  532.         "DELETE FROM settings WHERE id = :id " +
  533.         "AND id NOT IN (SELECT DISTINCT settingID FROM prefs)"
  534.       );
  535.  
  536.     return this.__stmtDeleteSettingIfUnused;
  537.   },
  538.  
  539.   _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
  540.     this._stmtDeleteSettingIfUnused.params.id = aSettingID;
  541.     this._stmtDeleteSettingIfUnused.execute();
  542.   },
  543.  
  544.   __stmtDeleteGroupIfUnused: null,
  545.   get _stmtDeleteGroupIfUnused ContentPrefService_get__stmtDeleteGroupIfUnused() {
  546.     if (!this.__stmtDeleteGroupIfUnused)
  547.       this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(
  548.         "DELETE FROM groups WHERE id = :id " +
  549.         "AND id NOT IN (SELECT DISTINCT groupID FROM prefs)"
  550.       );
  551.  
  552.     return this.__stmtDeleteGroupIfUnused;
  553.   },
  554.  
  555.   _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
  556.     this._stmtDeleteGroupIfUnused.params.id = aGroupID;
  557.     this._stmtDeleteGroupIfUnused.execute();
  558.   },
  559.  
  560.   __stmtSelectPrefs: null,
  561.   get _stmtSelectPrefs ContentPrefService_get__stmtSelectPrefs() {
  562.     if (!this.__stmtSelectPrefs)
  563.       this.__stmtSelectPrefs = this._dbCreateStatement(
  564.         "SELECT settings.name AS name, prefs.value AS value " +
  565.         "FROM prefs " +
  566.         "JOIN groups ON prefs.groupID = groups.id " +
  567.         "JOIN settings ON prefs.settingID = settings.id " +
  568.         "WHERE groups.name = :group "
  569.       );
  570.  
  571.     return this.__stmtSelectPrefs;
  572.   },
  573.  
  574.   _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
  575.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  576.                 createInstance(Ci.nsIWritablePropertyBag);
  577.  
  578.     try {
  579.       this._stmtSelectPrefs.params.group = aGroup;
  580.  
  581.       while (this._stmtSelectPrefs.step())
  582.         prefs.setProperty(this._stmtSelectPrefs.row["name"],
  583.                           this._stmtSelectPrefs.row["value"]);
  584.     }
  585.     finally {
  586.       this._stmtSelectPrefs.reset();
  587.     }
  588.  
  589.     return prefs;
  590.   },
  591.  
  592.   __stmtSelectGlobalPrefs: null,
  593.   get _stmtSelectGlobalPrefs ContentPrefService_get__stmtSelectGlobalPrefs() {
  594.     if (!this.__stmtSelectGlobalPrefs)
  595.       this.__stmtSelectGlobalPrefs = this._dbCreateStatement(
  596.         "SELECT settings.name AS name, prefs.value AS value " +
  597.         "FROM prefs " +
  598.         "JOIN settings ON prefs.settingID = settings.id " +
  599.         "WHERE prefs.groupID IS NULL"
  600.       );
  601.  
  602.     return this.__stmtSelectGlobalPrefs;
  603.   },
  604.  
  605.   _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
  606.     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
  607.                 createInstance(Ci.nsIWritablePropertyBag);
  608.  
  609.     try {
  610.       while (this._stmtSelectGlobalPrefs.step())
  611.         prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
  612.                           this._stmtSelectGlobalPrefs.row["value"]);
  613.     }
  614.     finally {
  615.       this._stmtSelectGlobalPrefs.reset();
  616.     }
  617.  
  618.     return prefs;
  619.   },
  620.  
  621.  
  622.   //**************************************************************************//
  623.   // Database Creation & Access
  624.  
  625.   _dbVersion: 3,
  626.  
  627.   _dbSchema: {
  628.     tables: {
  629.       groups:     "id           INTEGER PRIMARY KEY, \
  630.                    name         TEXT NOT NULL",
  631.   
  632.       settings:   "id           INTEGER PRIMARY KEY, \
  633.                    name         TEXT NOT NULL",
  634.   
  635.       prefs:      "id           INTEGER PRIMARY KEY, \
  636.                    groupID      INTEGER REFERENCES groups(id), \
  637.                    settingID    INTEGER NOT NULL REFERENCES settings(id), \
  638.                    value        BLOB"
  639.     },
  640.     indices: {
  641.       groups_idx: {
  642.         table: "groups",
  643.         columns: ["name"]
  644.       },
  645.       settings_idx: {
  646.         table: "settings",
  647.         columns: ["name"]
  648.       },
  649.       prefs_idx: {
  650.         table: "prefs",
  651.         columns: ["groupID", "settingID"]
  652.       }
  653.     }
  654.   },
  655.  
  656.   _dbConnection: null,
  657.  
  658.   _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
  659.     try {
  660.       var statement = this._dbConnection.createStatement(aSQLString);
  661.     }
  662.     catch(ex) {
  663.       Cu.reportError("error creating statement " + aSQLString + ": " +
  664.                      this._dbConnection.lastError + " - " +
  665.                      this._dbConnection.lastErrorString);
  666.       throw ex;
  667.     }
  668.  
  669.     var wrappedStatement = Cc["@mozilla.org/storage/statement-wrapper;1"].
  670.                            createInstance(Ci.mozIStorageStatementWrapper);
  671.     wrappedStatement.initialize(statement);
  672.     return wrappedStatement;
  673.   },
  674.  
  675.   // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
  676.   // specific migration methods) must be careful not to call any method
  677.   // of the service that assumes the database connection has already been
  678.   // initialized, since it won't be initialized until at the end of _dbInit.
  679.  
  680.   _dbInit: function ContentPrefService__dbInit() {
  681.     var dirService = Cc["@mozilla.org/file/directory_service;1"].
  682.                      getService(Ci.nsIProperties);
  683.     var dbFile = dirService.get("ProfD", Ci.nsIFile);
  684.     dbFile.append("content-prefs.sqlite");
  685.  
  686.     var dbService = Cc["@mozilla.org/storage/service;1"].
  687.                     getService(Ci.mozIStorageService);
  688.  
  689.     var dbConnection;
  690.  
  691.     if (!dbFile.exists())
  692.       dbConnection = this._dbCreate(dbService, dbFile);
  693.     else {
  694.       try {
  695.         dbConnection = dbService.openDatabase(dbFile);
  696.  
  697.         // Get the version of the database in the file.
  698.         var version = dbConnection.schemaVersion;
  699.  
  700.         if (version != this._dbVersion)
  701.           this._dbMigrate(dbConnection, version, this._dbVersion);
  702.       }
  703.       catch (ex) {
  704.         // If the database file is corrupted, I'm not sure whether we should
  705.         // just delete the corrupted file or back it up.  For now I'm just
  706.         // deleting it, but here's some code that backs it up (but doesn't limit
  707.         // the number of backups, which is probably necessary, thus I'm not
  708.         // using this code):
  709.         //var backup = this._dbFile.clone();
  710.         //backup.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  711.         //backup.remove(false);
  712.         //this._dbFile.moveTo(null, backup.leafName);
  713.         if (ex.result == Cr.NS_ERROR_FILE_CORRUPTED) {
  714.           // Remove the corrupted file, then recreate it.
  715.           dbFile.remove(false);
  716.           dbConnection = this._dbCreate(dbService, dbFile);
  717.         }
  718.         else
  719.           throw ex;
  720.       }
  721.     }
  722.  
  723.     this._dbConnection = dbConnection;
  724.   },
  725.  
  726.   _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
  727.     var dbConnection = aDBService.openDatabase(aDBFile);
  728.     for (let name in this._dbSchema.tables)
  729.       dbConnection.createTable(name, this._dbSchema.tables[name]);
  730.     this._dbCreateIndices(dbConnection);
  731.     dbConnection.schemaVersion = this._dbVersion;
  732.     return dbConnection;
  733.   },
  734.  
  735.   _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
  736.     for (let name in this._dbSchema.indices) {
  737.       let index = this._dbSchema.indices[name];
  738.       let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
  739.                       "(" + index.columns.join(", ") + ")";
  740.       aDBConnection.executeSimpleSQL(statement);
  741.     }
  742.   },
  743.  
  744.   _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
  745.     if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
  746.       aDBConnection.beginTransaction();
  747.       try {
  748.         this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
  749.         aDBConnection.schemaVersion = aNewVersion;
  750.         aDBConnection.commitTransaction();
  751.       }
  752.       catch(ex) {
  753.         aDBConnection.rollbackTransaction();
  754.         throw ex;
  755.       }
  756.     }
  757.     else
  758.       throw("can't migrate database from v" + aOldVersion +
  759.             " to v" + aNewVersion + ": no migrator function");
  760.   },
  761.  
  762.   _dbMigrate0To3: function ContentPrefService___dbMigrate0To3(aDBConnection) {
  763.     aDBConnection.createTable("groups", this._dbSchema.tables.groups);
  764.     aDBConnection.executeSimpleSQL(
  765.       "INSERT INTO groups (id, name) SELECT id, name FROM sites"
  766.     );
  767.  
  768.     aDBConnection.createTable("settings", this._dbSchema.tables.settings);
  769.     aDBConnection.executeSimpleSQL(
  770.       "INSERT INTO settings (id, name) SELECT id, name FROM keys"
  771.     );
  772.  
  773.     aDBConnection.executeSimpleSQL("ALTER TABLE prefs RENAME TO prefsOld");
  774.     aDBConnection.createTable("prefs", this._dbSchema.tables.prefs);
  775.     aDBConnection.executeSimpleSQL(
  776.       "INSERT INTO prefs (id, groupID, settingID, value) " +
  777.       "SELECT id, site_id, key_id, value FROM prefsOld"
  778.     );
  779.  
  780.     // Drop obsolete tables.
  781.     aDBConnection.executeSimpleSQL("DROP TABLE prefsOld");
  782.     aDBConnection.executeSimpleSQL("DROP TABLE keys");
  783.     aDBConnection.executeSimpleSQL("DROP TABLE sites");
  784.  
  785.     this._dbCreateIndices(aDBConnection);
  786.   },
  787.  
  788.   _dbMigrate1To3: function ContentPrefService___dbMigrate1To3(aDBConnection) {
  789.     aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
  790.     aDBConnection.createTable("groups", this._dbSchema.tables.groups);
  791.     aDBConnection.executeSimpleSQL(
  792.       "INSERT INTO groups (id, name) " +
  793.       "SELECT id, name FROM groupsOld"
  794.     );
  795.  
  796.     aDBConnection.executeSimpleSQL("DROP TABLE groupers");
  797.     aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
  798.  
  799.     this._dbCreateIndices(aDBConnection);
  800.   },
  801.  
  802.   _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
  803.     this._dbCreateIndices(aDBConnection);
  804.   }
  805.  
  806. };
  807.  
  808.  
  809. function HostnameGrouper() {}
  810.  
  811. HostnameGrouper.prototype = {
  812.   //**************************************************************************//
  813.   // XPCOM Plumbing
  814.   
  815.   classDescription: "Hostname Grouper",
  816.   classID:          Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
  817.   contractID:       "@mozilla.org/content-pref/hostname-grouper;1",
  818.   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
  819.  
  820.  
  821.   //**************************************************************************//
  822.   // nsIContentURIGrouper
  823.  
  824.   group: function HostnameGrouper_group(aURI) {
  825.     var group;
  826.  
  827.     try {
  828.       // Accessing the host property of the URI will throw an exception
  829.       // if the URI is of a type that doesn't have a host property.
  830.       // Otherwise, we manually throw an exception if the host is empty,
  831.       // since the effect is the same (we can't derive a group from it).
  832.  
  833.       group = aURI.host;
  834.       if (!group)
  835.         throw("can't derive group from host; no host in URI");
  836.     }
  837.     catch(ex) {
  838.       // If we don't have a host, then use the entire URI (minus the query,
  839.       // reference, and hash, if possible) as the group.  This means that URIs
  840.       // like about:mozilla and about:blank will be considered separate groups,
  841.       // but at least they'll be grouped somehow.
  842.       
  843.       // This also means that each individual file: URL will be considered
  844.       // its own group.  This seems suboptimal, but so does treating the entire
  845.       // file: URL space as a single group (especially if folks start setting
  846.       // group-specific capabilities prefs).
  847.  
  848.       // XXX Is there something better we can do here?
  849.  
  850.       try {
  851.         var url = aURI.QueryInterface(Ci.nsIURL);
  852.         group = aURI.prePath + url.filePath;
  853.       }
  854.       catch(ex) {
  855.         group = aURI.spec;
  856.       }
  857.     }
  858.  
  859.     return group;
  860.   }
  861. };
  862.  
  863.  
  864. //****************************************************************************//
  865. // XPCOM Plumbing
  866.  
  867. var components = [ContentPrefService, HostnameGrouper];
  868. var NSGetModule = function ContentPrefService_NSGetModule(compMgr, fileSpec) {
  869.   return XPCOMUtils.generateModule(components);
  870. }
  871.