Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / toolkit / components / contentprefs / src / nsContentPrefService.js
blob3e4352c9f9199e3846274d467d8264240e7aaf81
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  *
24  * Alternatively, the contents of this file may be used under the terms of
25  * either the GNU General Public License Version 2 or later (the "GPL"), or
26  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27  * in which case the provisions of the GPL or the LGPL are applicable instead
28  * of those above. If you wish to allow use of your version of this file only
29  * under the terms of either the GPL or the LGPL, and not to allow others to
30  * use your version of this file under the terms of the MPL, indicate your
31  * decision by deleting the provisions above and replace them with the notice
32  * and other provisions required by the GPL or the LGPL. If you do not delete
33  * the provisions above, a recipient may use your version of this file under
34  * the terms of any one of the MPL, the GPL or the LGPL.
35  *
36  * ***** END LICENSE BLOCK ***** */
38 const Ci = Components.interfaces;
39 const Cc = Components.classes;
40 const Cr = Components.results;
41 const Cu = Components.utils;
43 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
45 function ContentPrefService() {
46   // If this throws an exception, it causes the getService call to fail,
47   // but the next time a consumer tries to retrieve the service, we'll try
48   // to initialize the database again, which might work if the failure
49   // was due to a temporary condition (like being out of disk space).
50   this._dbInit();
52   // Observe shutdown so we can shut down the database connection.
53   this._observerSvc.addObserver(this, "xpcom-shutdown", false);
56 ContentPrefService.prototype = {
57   //**************************************************************************//
58   // XPCOM Plumbing
60   classDescription: "Content Pref Service",
61   classID:          Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
62   contractID:       "@mozilla.org/content-pref/service;1",
63   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentPrefService]),
66   //**************************************************************************//
67   // Convenience Getters
69   // Observer Service
70   __observerSvc: null,
71   get _observerSvc ContentPrefService_get__observerSvc() {
72     if (!this.__observerSvc)
73       this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
74                            getService(Ci.nsIObserverService);
75     return this.__observerSvc;
76   },
78   // Console Service
79   __consoleSvc: null,
80   get _consoleSvc ContentPrefService_get__consoleSvc() {
81     if (!this.__consoleSvc)
82       this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
83                           getService(Ci.nsIConsoleService);
84     return this.__consoleSvc;
85   },
87   // Preferences Service
88   __prefSvc: null,
89   get _prefSvc ContentPrefService_get__prefSvc() {
90     if (!this.__prefSvc)
91       this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
92                        getService(Ci.nsIPrefBranch2);
93     return this.__prefSvc;
94   },
97   //**************************************************************************//
98   // Destruction
100   _destroy: function ContentPrefService__destroy() {
101     this._observerSvc.removeObserver(this, "xpcom-shutdown");
103     // Delete references to XPCOM components to make sure we don't leak them
104     // (although we haven't observed leakage in tests).  Also delete references
105     // in _observers and _genericObservers to avoid cycles with those that
106     // refer to us and don't remove themselves from those observer pools.
107     for (var i in this) {
108       try { this[i] = null }
109       // Ignore "setting a property that has only a getter" exceptions.
110       catch(ex) {}
111     }
112   },
115   //**************************************************************************//
116   // nsIObserver
118   observe: function ContentPrefService_observe(subject, topic, data) {
119     switch (topic) {
120       case "xpcom-shutdown":
121         this._destroy();
122         break;
123     }
124   },
127   //**************************************************************************//
128   // nsIContentPrefService
130   getPref: function ContentPrefService_getPref(aURI, aName) {
131     if (aURI) {
132       var group = this.grouper.group(aURI);
133       return this._selectPref(group, aName);
134     }
136     return this._selectGlobalPref(aName);
137   },
139   setPref: function ContentPrefService_setPref(aURI, aName, aValue) {
140     // If the pref is already set to the value, there's nothing more to do.
141     var currentValue = this.getPref(aURI, aName);
142     if (typeof currentValue != "undefined") {
143       if (currentValue == aValue)
144         return;
145     }
146     else {
147       // If we are in private browsing mode, refuse to set new prefs
148       var inPrivateBrowsing = false;
149       try { // The Private Browsing service might not be available.
150         var pbs = Cc["@mozilla.org/privatebrowsing;1"].
151                   getService(Ci.nsIPrivateBrowsingService);
152         inPrivateBrowsing = pbs.privateBrowsingEnabled;
153       } catch (e) {}
154       if (inPrivateBrowsing)
155         return;
156     }
158     var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
159     var group, groupID, prefID;
160     if (aURI) {
161       group = this.grouper.group(aURI);
162       groupID = this._selectGroupID(group) || this._insertGroup(group);
163       prefID = this._selectPrefID(groupID, settingID);
164     }
165     else {
166       group = null;
167       groupID = null;
168       prefID = this._selectGlobalPrefID(settingID);
169     }
171     // Update the existing record, if any, or create a new one.
172     if (prefID)
173       this._updatePref(prefID, aValue);
174     else
175       this._insertPref(groupID, settingID, aValue);
177     for each (var observer in this._getObservers(aName)) {
178       try {
179         observer.onContentPrefSet(group, aName, aValue);
180       }
181       catch(ex) {
182         Cu.reportError(ex);
183       }
184     }
185   },
187   hasPref: function ContentPrefService_hasPref(aURI, aName) {
188     // XXX If consumers end up calling this method regularly, then we should
189     // optimize this to query the database directly.
190     return (typeof this.getPref(aURI, aName) != "undefined");
191   },
193   removePref: function ContentPrefService_removePref(aURI, aName) {
194     // If there's no old value, then there's nothing to remove.
195     if (!this.hasPref(aURI, aName))
196       return;
198     var settingID = this._selectSettingID(aName);
199     var group, groupID, prefID;
200     if (aURI) {
201       group = this.grouper.group(aURI);
202       groupID = this._selectGroupID(group);
203       prefID = this._selectPrefID(groupID, settingID);
204     }
205     else {
206       group = null;
207       groupID = null;
208       prefID = this._selectGlobalPrefID(settingID);
209     }
211     this._deletePref(prefID);
213     // Get rid of extraneous records that are no longer being used.
214     this._deleteSettingIfUnused(settingID);
215     if (groupID)
216       this._deleteGroupIfUnused(groupID);
218     for each (var observer in this._getObservers(aName)) {
219       try {
220         observer.onContentPrefRemoved(group, aName);
221       }
222       catch(ex) {
223         Cu.reportError(ex);
224       }
225     }
226   },
228   getPrefs: function ContentPrefService_getPrefs(aURI) {
229     if (aURI) {
230       var group = this.grouper.group(aURI);
231       return this._selectPrefs(group);
232     }
234     return this._selectGlobalPrefs();
235   },
237   // A hash of arrays of observers, indexed by setting name.
238   _observers: {},
240   // An array of generic observers, which observe all settings.
241   _genericObservers: [],
243   addObserver: function ContentPrefService_addObserver(aName, aObserver) {
244     var observers;
245     if (aName) {
246       if (!this._observers[aName])
247         this._observers[aName] = [];
248       observers = this._observers[aName];
249     }
250     else
251       observers = this._genericObservers;
253     if (observers.indexOf(aObserver) == -1)
254       observers.push(aObserver);
255   },
257   removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
258     var observers;
259     if (aName) {
260       if (!this._observers[aName])
261         return;
262       observers = this._observers[aName];
263     }
264     else
265       observers = this._genericObservers;
267     if (observers.indexOf(aObserver) != -1)
268       observers.splice(observers.indexOf(aObserver), 1);
269   },
271   /**
272    * Construct a list of observers to notify about a change to some setting,
273    * putting setting-specific observers before before generic ones, so observers
274    * that initialize individual settings (like the page style controller)
275    * execute before observers that display multiple settings and depend on them
276    * being initialized first (like the content prefs sidebar).
277    */
278   _getObservers: function ContentPrefService__getObservers(aName) {
279     var observers = [];
281     if (aName && this._observers[aName])
282       observers = observers.concat(this._observers[aName]);
283     observers = observers.concat(this._genericObservers);
285     return observers;
286   },
288   _grouper: null,
289   get grouper ContentPrefService_get_grouper() {
290     if (!this._grouper)
291       this._grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
292                       getService(Ci.nsIContentURIGrouper);
293     return this._grouper;
294   },
296   get DBConnection ContentPrefService_get_DBConnection() {
297     return this._dbConnection;
298   },
301   //**************************************************************************//
302   // Data Retrieval & Modification
304   __stmtSelectPref: null,
305   get _stmtSelectPref ContentPrefService_get__stmtSelectPref() {
306     if (!this.__stmtSelectPref)
307       this.__stmtSelectPref = this._dbCreateStatement(
308         "SELECT prefs.value AS value " +
309         "FROM prefs " +
310         "JOIN groups ON prefs.groupID = groups.id " +
311         "JOIN settings ON prefs.settingID = settings.id " +
312         "WHERE groups.name = :group " +
313         "AND settings.name = :setting"
314       );
316     return this.__stmtSelectPref;
317   },
319   _selectPref: function ContentPrefService__selectPref(aGroup, aSetting) {
320     var value;
322     try {
323       this._stmtSelectPref.params.group = aGroup;
324       this._stmtSelectPref.params.setting = aSetting;
326       if (this._stmtSelectPref.step())
327         value = this._stmtSelectPref.row["value"];
328     }
329     finally {
330       this._stmtSelectPref.reset();
331     }
333     return value;
334   },
336   __stmtSelectGlobalPref: null,
337   get _stmtSelectGlobalPref ContentPrefService_get__stmtSelectGlobalPref() {
338     if (!this.__stmtSelectGlobalPref)
339       this.__stmtSelectGlobalPref = this._dbCreateStatement(
340         "SELECT prefs.value AS value " +
341         "FROM prefs " +
342         "JOIN settings ON prefs.settingID = settings.id " +
343         "WHERE prefs.groupID IS NULL " +
344         "AND settings.name = :name"
345       );
347     return this.__stmtSelectGlobalPref;
348   },
350   _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName) {
351     var value;
353     try {
354       this._stmtSelectGlobalPref.params.name = aName;
356       if (this._stmtSelectGlobalPref.step())
357         value = this._stmtSelectGlobalPref.row["value"];
358     }
359     finally {
360       this._stmtSelectGlobalPref.reset();
361     }
363     return value;
364   },
366   __stmtSelectGroupID: null,
367   get _stmtSelectGroupID ContentPrefService_get__stmtSelectGroupID() {
368     if (!this.__stmtSelectGroupID)
369       this.__stmtSelectGroupID = this._dbCreateStatement(
370         "SELECT groups.id AS id " +
371         "FROM groups " +
372         "WHERE groups.name = :name "
373       );
375     return this.__stmtSelectGroupID;
376   },
378   _selectGroupID: function ContentPrefService__selectGroupID(aName) {
379     var id;
381     try {
382       this._stmtSelectGroupID.params.name = aName;
384       if (this._stmtSelectGroupID.step())
385         id = this._stmtSelectGroupID.row["id"];
386     }
387     finally {
388       this._stmtSelectGroupID.reset();
389     }
391     return id;
392   },
394   __stmtInsertGroup: null,
395   get _stmtInsertGroup ContentPrefService_get__stmtInsertGroup() {
396     if (!this.__stmtInsertGroup)
397       this.__stmtInsertGroup = this._dbCreateStatement(
398         "INSERT INTO groups (name) VALUES (:name)"
399       );
401     return this.__stmtInsertGroup;
402   },
404   _insertGroup: function ContentPrefService__insertGroup(aName) {
405     this._stmtInsertGroup.params.name = aName;
406     this._stmtInsertGroup.execute();
407     return this._dbConnection.lastInsertRowID;
408   },
410   __stmtSelectSettingID: null,
411   get _stmtSelectSettingID ContentPrefService_get__stmtSelectSettingID() {
412     if (!this.__stmtSelectSettingID)
413       this.__stmtSelectSettingID = this._dbCreateStatement(
414         "SELECT id FROM settings WHERE name = :name"
415       );
417     return this.__stmtSelectSettingID;
418   },
420   _selectSettingID: function ContentPrefService__selectSettingID(aName) {
421     var id;
423     try {
424       this._stmtSelectSettingID.params.name = aName;
426       if (this._stmtSelectSettingID.step())
427         id = this._stmtSelectSettingID.row["id"];
428     }
429     finally {
430       this._stmtSelectSettingID.reset();
431     }
433     return id;
434   },
436   __stmtInsertSetting: null,
437   get _stmtInsertSetting ContentPrefService_get__stmtInsertSetting() {
438     if (!this.__stmtInsertSetting)
439       this.__stmtInsertSetting = this._dbCreateStatement(
440         "INSERT INTO settings (name) VALUES (:name)"
441       );
443     return this.__stmtInsertSetting;
444   },
446   _insertSetting: function ContentPrefService__insertSetting(aName) {
447     this._stmtInsertSetting.params.name = aName;
448     this._stmtInsertSetting.execute();
449     return this._dbConnection.lastInsertRowID;
450   },
452   __stmtSelectPrefID: null,
453   get _stmtSelectPrefID ContentPrefService_get__stmtSelectPrefID() {
454     if (!this.__stmtSelectPrefID)
455       this.__stmtSelectPrefID = this._dbCreateStatement(
456         "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
457       );
459     return this.__stmtSelectPrefID;
460   },
462   _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
463     var id;
465     try {
466       this._stmtSelectPrefID.params.groupID = aGroupID;
467       this._stmtSelectPrefID.params.settingID = aSettingID;
469       if (this._stmtSelectPrefID.step())
470         id = this._stmtSelectPrefID.row["id"];
471     }
472     finally {
473       this._stmtSelectPrefID.reset();
474     }
476     return id;
477   },
479   __stmtSelectGlobalPrefID: null,
480   get _stmtSelectGlobalPrefID ContentPrefService_get__stmtSelectGlobalPrefID() {
481     if (!this.__stmtSelectGlobalPrefID)
482       this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
483         "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
484       );
486     return this.__stmtSelectGlobalPrefID;
487   },
489   _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
490     var id;
492     try {
493       this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
495       if (this._stmtSelectGlobalPrefID.step())
496         id = this._stmtSelectGlobalPrefID.row["id"];
497     }
498     finally {
499       this._stmtSelectGlobalPrefID.reset();
500     }
502     return id;
503   },
505   __stmtInsertPref: null,
506   get _stmtInsertPref ContentPrefService_get__stmtInsertPref() {
507     if (!this.__stmtInsertPref)
508       this.__stmtInsertPref = this._dbCreateStatement(
509         "INSERT INTO prefs (groupID, settingID, value) " +
510         "VALUES (:groupID, :settingID, :value)"
511       );
513     return this.__stmtInsertPref;
514   },
516   _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
517     this._stmtInsertPref.params.groupID = aGroupID;
518     this._stmtInsertPref.params.settingID = aSettingID;
519     this._stmtInsertPref.params.value = aValue;
520     this._stmtInsertPref.execute();
521     return this._dbConnection.lastInsertRowID;
522   },
524   __stmtUpdatePref: null,
525   get _stmtUpdatePref ContentPrefService_get__stmtUpdatePref() {
526     if (!this.__stmtUpdatePref)
527       this.__stmtUpdatePref = this._dbCreateStatement(
528         "UPDATE prefs SET value = :value WHERE id = :id"
529       );
531     return this.__stmtUpdatePref;
532   },
534   _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
535     this._stmtUpdatePref.params.id = aPrefID;
536     this._stmtUpdatePref.params.value = aValue;
537     this._stmtUpdatePref.execute();
538   },
540   __stmtDeletePref: null,
541   get _stmtDeletePref ContentPrefService_get__stmtDeletePref() {
542     if (!this.__stmtDeletePref)
543       this.__stmtDeletePref = this._dbCreateStatement(
544         "DELETE FROM prefs WHERE id = :id"
545       );
547     return this.__stmtDeletePref;
548   },
550   _deletePref: function ContentPrefService__deletePref(aPrefID) {
551     this._stmtDeletePref.params.id = aPrefID;
552     this._stmtDeletePref.execute();
553   },
555   __stmtDeleteSettingIfUnused: null,
556   get _stmtDeleteSettingIfUnused ContentPrefService_get__stmtDeleteSettingIfUnused() {
557     if (!this.__stmtDeleteSettingIfUnused)
558       this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(
559         "DELETE FROM settings WHERE id = :id " +
560         "AND id NOT IN (SELECT DISTINCT settingID FROM prefs)"
561       );
563     return this.__stmtDeleteSettingIfUnused;
564   },
566   _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
567     this._stmtDeleteSettingIfUnused.params.id = aSettingID;
568     this._stmtDeleteSettingIfUnused.execute();
569   },
571   __stmtDeleteGroupIfUnused: null,
572   get _stmtDeleteGroupIfUnused ContentPrefService_get__stmtDeleteGroupIfUnused() {
573     if (!this.__stmtDeleteGroupIfUnused)
574       this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(
575         "DELETE FROM groups WHERE id = :id " +
576         "AND id NOT IN (SELECT DISTINCT groupID FROM prefs)"
577       );
579     return this.__stmtDeleteGroupIfUnused;
580   },
582   _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
583     this._stmtDeleteGroupIfUnused.params.id = aGroupID;
584     this._stmtDeleteGroupIfUnused.execute();
585   },
587   __stmtSelectPrefs: null,
588   get _stmtSelectPrefs ContentPrefService_get__stmtSelectPrefs() {
589     if (!this.__stmtSelectPrefs)
590       this.__stmtSelectPrefs = this._dbCreateStatement(
591         "SELECT settings.name AS name, prefs.value AS value " +
592         "FROM prefs " +
593         "JOIN groups ON prefs.groupID = groups.id " +
594         "JOIN settings ON prefs.settingID = settings.id " +
595         "WHERE groups.name = :group "
596       );
598     return this.__stmtSelectPrefs;
599   },
601   _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
602     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
603                 createInstance(Ci.nsIWritablePropertyBag);
605     try {
606       this._stmtSelectPrefs.params.group = aGroup;
608       while (this._stmtSelectPrefs.step())
609         prefs.setProperty(this._stmtSelectPrefs.row["name"],
610                           this._stmtSelectPrefs.row["value"]);
611     }
612     finally {
613       this._stmtSelectPrefs.reset();
614     }
616     return prefs;
617   },
619   __stmtSelectGlobalPrefs: null,
620   get _stmtSelectGlobalPrefs ContentPrefService_get__stmtSelectGlobalPrefs() {
621     if (!this.__stmtSelectGlobalPrefs)
622       this.__stmtSelectGlobalPrefs = this._dbCreateStatement(
623         "SELECT settings.name AS name, prefs.value AS value " +
624         "FROM prefs " +
625         "JOIN settings ON prefs.settingID = settings.id " +
626         "WHERE prefs.groupID IS NULL"
627       );
629     return this.__stmtSelectGlobalPrefs;
630   },
632   _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
633     var prefs = Cc["@mozilla.org/hash-property-bag;1"].
634                 createInstance(Ci.nsIWritablePropertyBag);
636     try {
637       while (this._stmtSelectGlobalPrefs.step())
638         prefs.setProperty(this._stmtSelectGlobalPrefs.row["name"],
639                           this._stmtSelectGlobalPrefs.row["value"]);
640     }
641     finally {
642       this._stmtSelectGlobalPrefs.reset();
643     }
645     return prefs;
646   },
649   //**************************************************************************//
650   // Database Creation & Access
652   _dbVersion: 3,
654   _dbSchema: {
655     tables: {
656       groups:     "id           INTEGER PRIMARY KEY, \
657                    name         TEXT NOT NULL",
658   
659       settings:   "id           INTEGER PRIMARY KEY, \
660                    name         TEXT NOT NULL",
661   
662       prefs:      "id           INTEGER PRIMARY KEY, \
663                    groupID      INTEGER REFERENCES groups(id), \
664                    settingID    INTEGER NOT NULL REFERENCES settings(id), \
665                    value        BLOB"
666     },
667     indices: {
668       groups_idx: {
669         table: "groups",
670         columns: ["name"]
671       },
672       settings_idx: {
673         table: "settings",
674         columns: ["name"]
675       },
676       prefs_idx: {
677         table: "prefs",
678         columns: ["groupID", "settingID"]
679       }
680     }
681   },
683   _dbConnection: null,
685   _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
686     try {
687       var statement = this._dbConnection.createStatement(aSQLString);
688     }
689     catch(ex) {
690       Cu.reportError("error creating statement " + aSQLString + ": " +
691                      this._dbConnection.lastError + " - " +
692                      this._dbConnection.lastErrorString);
693       throw ex;
694     }
696     var wrappedStatement = Cc["@mozilla.org/storage/statement-wrapper;1"].
697                            createInstance(Ci.mozIStorageStatementWrapper);
698     wrappedStatement.initialize(statement);
699     return wrappedStatement;
700   },
702   // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
703   // specific migration methods) must be careful not to call any method
704   // of the service that assumes the database connection has already been
705   // initialized, since it won't be initialized until at the end of _dbInit.
707   _dbInit: function ContentPrefService__dbInit() {
708     var dirService = Cc["@mozilla.org/file/directory_service;1"].
709                      getService(Ci.nsIProperties);
710     var dbFile = dirService.get("ProfD", Ci.nsIFile);
711     dbFile.append("content-prefs.sqlite");
713     var dbService = Cc["@mozilla.org/storage/service;1"].
714                     getService(Ci.mozIStorageService);
716     var dbConnection;
718     if (!dbFile.exists())
719       dbConnection = this._dbCreate(dbService, dbFile);
720     else {
721       try {
722         dbConnection = dbService.openDatabase(dbFile);
723       }
724       // If the connection isn't ready after we open the database, that means
725       // the database has been corrupted, so we back it up and then recreate it.
726       catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
727         dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
728                                                  dbConnection);
729       }
731       // Get the version of the schema in the file.
732       var version = dbConnection.schemaVersion;
734       // Try to migrate the schema in the database to the current schema used by
735       // the service.  If migration fails, back up the database and recreate it.
736       if (version != this._dbVersion) {
737         try {
738           this._dbMigrate(dbConnection, version, this._dbVersion);
739         }
740         catch(ex) {
741           Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
742           dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
743         }
744       }
745     }
747     // Turn off disk synchronization checking to reduce disk churn and speed up
748     // operations when prefs are changed rapidly (such as when a user repeatedly
749     // changes the value of the browser zoom setting for a site).
750     //
751     // Note: this could cause database corruption if the OS crashes or machine
752     // loses power before the data gets written to disk, but this is considered
753     // a reasonable risk for the not-so-critical data stored in this database.
754     //
755     // If you really don't want to take this risk, however, just set the
756     // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
757     // (FULL synchronization), in which case mozStorageConnection::Initialize
758     // will use that value, and we won't override it here.
759     if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
760       dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
762     this._dbConnection = dbConnection;
763   },
765   _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
766     var dbConnection = aDBService.openDatabase(aDBFile);
768     try {
769       this._dbCreateSchema(dbConnection);
770       dbConnection.schemaVersion = this._dbVersion;
771     }
772     catch(ex) {
773       // If we failed to create the database (perhaps because the disk ran out
774       // of space), then remove the database file so we don't leave it in some
775       // half-created state from which we won't know how to recover.
776       dbConnection.close();
777       aDBFile.remove(false);
778       throw ex;
779     }
781     return dbConnection;
782   },
784   _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
785     this._dbCreateTables(aDBConnection);
786     this._dbCreateIndices(aDBConnection);
787   },
789   _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
790     for (let name in this._dbSchema.tables)
791       aDBConnection.createTable(name, this._dbSchema.tables[name]);
792   },
794   _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
795     for (let name in this._dbSchema.indices) {
796       let index = this._dbSchema.indices[name];
797       let statement = "CREATE INDEX IF NOT EXISTS " + name + " ON " + index.table +
798                       "(" + index.columns.join(", ") + ")";
799       aDBConnection.executeSimpleSQL(statement);
800     }
801   },
803   _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
804                                                                          aDBFile,
805                                                                          aDBConnection) {
806     aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
808     // Close the database, ignoring the "already closed" exception, if any.
809     // It'll be open if we're here because of a migration failure but closed
810     // if we're here because of database corruption.
811     try { aDBConnection.close() } catch(ex) {}
813     aDBFile.remove(false);
815     let dbConnection = this._dbCreate(aDBService, aDBFile);
817     return dbConnection;
818   },
820   _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
821     if (this["_dbMigrate" + aOldVersion + "To" + aNewVersion]) {
822       aDBConnection.beginTransaction();
823       try {
824         this["_dbMigrate" + aOldVersion + "To" + aNewVersion](aDBConnection);
825         aDBConnection.schemaVersion = aNewVersion;
826         aDBConnection.commitTransaction();
827       }
828       catch(ex) {
829         aDBConnection.rollbackTransaction();
830         throw ex;
831       }
832     }
833     else
834       throw("no migrator function from version " + aOldVersion +
835             " to version " + aNewVersion);
836   },
838   /**
839    * If the schema version is 0, that means it was never set, which means
840    * the database was somehow created without the schema being applied, perhaps
841    * because the system ran out of disk space (although we check for this
842    * in _createDB) or because some other code created the database file without
843    * applying the schema.  In any case, recover by simply reapplying the schema.
844    */
845   _dbMigrate0To3: function ContentPrefService___dbMigrate0To3(aDBConnection) {
846     this._dbCreateSchema(aDBConnection);
847   },
849   _dbMigrate1To3: function ContentPrefService___dbMigrate1To3(aDBConnection) {
850     aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
851     aDBConnection.createTable("groups", this._dbSchema.tables.groups);
852     aDBConnection.executeSimpleSQL(
853       "INSERT INTO groups (id, name) " +
854       "SELECT id, name FROM groupsOld"
855     );
857     aDBConnection.executeSimpleSQL("DROP TABLE groupers");
858     aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
860     this._dbCreateIndices(aDBConnection);
861   },
863   _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
864     this._dbCreateIndices(aDBConnection);
865   }
870 function HostnameGrouper() {}
872 HostnameGrouper.prototype = {
873   //**************************************************************************//
874   // XPCOM Plumbing
875   
876   classDescription: "Hostname Grouper",
877   classID:          Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
878   contractID:       "@mozilla.org/content-pref/hostname-grouper;1",
879   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
882   //**************************************************************************//
883   // nsIContentURIGrouper
885   group: function HostnameGrouper_group(aURI) {
886     var group;
888     try {
889       // Accessing the host property of the URI will throw an exception
890       // if the URI is of a type that doesn't have a host property.
891       // Otherwise, we manually throw an exception if the host is empty,
892       // since the effect is the same (we can't derive a group from it).
894       group = aURI.host;
895       if (!group)
896         throw("can't derive group from host; no host in URI");
897     }
898     catch(ex) {
899       // If we don't have a host, then use the entire URI (minus the query,
900       // reference, and hash, if possible) as the group.  This means that URIs
901       // like about:mozilla and about:blank will be considered separate groups,
902       // but at least they'll be grouped somehow.
903       
904       // This also means that each individual file: URL will be considered
905       // its own group.  This seems suboptimal, but so does treating the entire
906       // file: URL space as a single group (especially if folks start setting
907       // group-specific capabilities prefs).
909       // XXX Is there something better we can do here?
911       try {
912         var url = aURI.QueryInterface(Ci.nsIURL);
913         group = aURI.prePath + url.filePath;
914       }
915       catch(ex) {
916         group = aURI.spec;
917       }
918     }
920     return group;
921   }
925 //****************************************************************************//
926 // XPCOM Plumbing
928 var components = [ContentPrefService, HostnameGrouper];
929 var NSGetModule = function ContentPrefService_NSGetModule(compMgr, fileSpec) {
930   return XPCOMUtils.generateModule(components);