|
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); |