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