toolkit/components/contentprefs/nsContentPrefService.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial