toolkit/components/contentprefs/nsContentPrefService.js

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

mercurial