toolkit/components/contentprefs/ContentPrefService2.jsm

branch
TOR_BUG_9701
changeset 14
925c144e1f1f
equal deleted inserted replaced
-1:000000000000 0:2157a09c9f7e
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 file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 // This file is an XPCOM component that implements nsIContentPrefService2.
6 // Although it's a JSM, it's not intended to be imported by consumers like JSMs
7 // are usually imported. It's only a JSM so that nsContentPrefService.js can
8 // easily use it. Consumers should access this component with the usual XPCOM
9 // rigmarole:
10 //
11 // Cc["@mozilla.org/content-pref/service;1"].
12 // getService(Ci.nsIContentPrefService2);
13 //
14 // That contract ID actually belongs to nsContentPrefService.js, which, when
15 // QI'ed to nsIContentPrefService2, returns an instance of this component.
16 //
17 // The plan is to eventually remove nsIContentPrefService and its
18 // implementation, nsContentPrefService.js. At such time this file can stop
19 // being a JSM, and the "_cps" parts that ContentPrefService2 relies on and
20 // NSGetFactory and all the other XPCOM initialization goop in
21 // nsContentPrefService.js can be moved here.
22 //
23 // See https://bugzilla.mozilla.org/show_bug.cgi?id=699859
24
25 let EXPORTED_SYMBOLS = [
26 "ContentPrefService2",
27 ];
28
29 const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
30
31 Cu.import("resource://gre/modules/Services.jsm");
32 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
33 Cu.import("resource://gre/modules/ContentPrefStore.jsm");
34
35 function ContentPrefService2(cps) {
36 this._cps = cps;
37 this._cache = cps._cache;
38 this._pbStore = cps._privModeStorage;
39 }
40
41 ContentPrefService2.prototype = {
42
43 getByName: function CPS2_getByName(name, context, callback) {
44 checkNameArg(name);
45 checkCallbackArg(callback, true);
46
47 // Some prefs may be in both the database and the private browsing store.
48 // Notify the caller of such prefs only once, using the values from private
49 // browsing.
50 let pbPrefs = new ContentPrefStore();
51 if (context && context.usePrivateBrowsing) {
52 for (let [sgroup, sname, val] in this._pbStore) {
53 if (sname == name) {
54 pbPrefs.set(sgroup, sname, val);
55 }
56 }
57 }
58
59 let stmt1 = this._stmt(
60 "SELECT groups.name AS grp, prefs.value AS value",
61 "FROM prefs",
62 "JOIN settings ON settings.id = prefs.settingID",
63 "JOIN groups ON groups.id = prefs.groupID",
64 "WHERE settings.name = :name"
65 );
66 stmt1.params.name = name;
67
68 let stmt2 = this._stmt(
69 "SELECT NULL AS grp, prefs.value AS value",
70 "FROM prefs",
71 "JOIN settings ON settings.id = prefs.settingID",
72 "WHERE settings.name = :name AND prefs.groupID ISNULL"
73 );
74 stmt2.params.name = name;
75
76 this._execStmts([stmt1, stmt2], {
77 onRow: function onRow(row) {
78 let grp = row.getResultByName("grp");
79 let val = row.getResultByName("value");
80 this._cache.set(grp, name, val);
81 if (!pbPrefs.has(grp, name))
82 cbHandleResult(callback, new ContentPref(grp, name, val));
83 },
84 onDone: function onDone(reason, ok, gotRow) {
85 if (ok) {
86 for (let [pbGroup, pbName, pbVal] in pbPrefs) {
87 cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
88 }
89 }
90 cbHandleCompletion(callback, reason);
91 },
92 onError: function onError(nsresult) {
93 cbHandleError(callback, nsresult);
94 }
95 });
96 },
97
98 getByDomainAndName: function CPS2_getByDomainAndName(group, name, context,
99 callback) {
100 checkGroupArg(group);
101 this._get(group, name, false, context, callback);
102 },
103
104 getBySubdomainAndName: function CPS2_getBySubdomainAndName(group, name,
105 context,
106 callback) {
107 checkGroupArg(group);
108 this._get(group, name, true, context, callback);
109 },
110
111 getGlobal: function CPS2_getGlobal(name, context, callback) {
112 this._get(null, name, false, context, callback);
113 },
114
115 _get: function CPS2__get(group, name, includeSubdomains, context, callback) {
116 group = this._parseGroup(group);
117 checkNameArg(name);
118 checkCallbackArg(callback, true);
119
120 // Some prefs may be in both the database and the private browsing store.
121 // Notify the caller of such prefs only once, using the values from private
122 // browsing.
123 let pbPrefs = new ContentPrefStore();
124 if (context && context.usePrivateBrowsing) {
125 for (let [sgroup, val] in
126 this._pbStore.match(group, name, includeSubdomains)) {
127 pbPrefs.set(sgroup, name, val);
128 }
129 }
130
131 this._execStmts([this._commonGetStmt(group, name, includeSubdomains)], {
132 onRow: function onRow(row) {
133 let grp = row.getResultByName("grp");
134 let val = row.getResultByName("value");
135 this._cache.set(grp, name, val);
136 if (!pbPrefs.has(group, name))
137 cbHandleResult(callback, new ContentPref(grp, name, val));
138 },
139 onDone: function onDone(reason, ok, gotRow) {
140 if (ok) {
141 if (!gotRow)
142 this._cache.set(group, name, undefined);
143 for (let [pbGroup, pbName, pbVal] in pbPrefs) {
144 cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
145 }
146 }
147 cbHandleCompletion(callback, reason);
148 },
149 onError: function onError(nsresult) {
150 cbHandleError(callback, nsresult);
151 }
152 });
153 },
154
155 _commonGetStmt: function CPS2__commonGetStmt(group, name, includeSubdomains) {
156 let stmt = group ?
157 this._stmtWithGroupClause(group, includeSubdomains,
158 "SELECT groups.name AS grp, prefs.value AS value",
159 "FROM prefs",
160 "JOIN settings ON settings.id = prefs.settingID",
161 "JOIN groups ON groups.id = prefs.groupID",
162 "WHERE settings.name = :name AND prefs.groupID IN ($)"
163 ) :
164 this._stmt(
165 "SELECT NULL AS grp, prefs.value AS value",
166 "FROM prefs",
167 "JOIN settings ON settings.id = prefs.settingID",
168 "WHERE settings.name = :name AND prefs.groupID ISNULL"
169 );
170 stmt.params.name = name;
171 return stmt;
172 },
173
174 _stmtWithGroupClause: function CPS2__stmtWithGroupClause(group,
175 includeSubdomains) {
176 let stmt = this._stmt(joinArgs(Array.slice(arguments, 2)).replace("$",
177 "SELECT id " +
178 "FROM groups " +
179 "WHERE name = :group OR " +
180 "(:includeSubdomains AND name LIKE :pattern ESCAPE '/')"
181 ));
182 stmt.params.group = group;
183 stmt.params.includeSubdomains = includeSubdomains || false;
184 stmt.params.pattern = "%." + stmt.escapeStringForLIKE(group, "/");
185 return stmt;
186 },
187
188 getCachedByDomainAndName: function CPS2_getCachedByDomainAndName(group,
189 name,
190 context) {
191 checkGroupArg(group);
192 let prefs = this._getCached(group, name, false, context);
193 return prefs[0] || null;
194 },
195
196 getCachedBySubdomainAndName: function CPS2_getCachedBySubdomainAndName(group,
197 name,
198 context,
199 len) {
200 checkGroupArg(group);
201 let prefs = this._getCached(group, name, true, context);
202 if (len)
203 len.value = prefs.length;
204 return prefs;
205 },
206
207 getCachedGlobal: function CPS2_getCachedGlobal(name, context) {
208 let prefs = this._getCached(null, name, false, context);
209 return prefs[0] || null;
210 },
211
212 _getCached: function CPS2__getCached(group, name, includeSubdomains,
213 context) {
214 group = this._parseGroup(group);
215 checkNameArg(name);
216
217 let storesToCheck = [this._cache];
218 if (context && context.usePrivateBrowsing)
219 storesToCheck.push(this._pbStore);
220
221 let outStore = new ContentPrefStore();
222 storesToCheck.forEach(function (store) {
223 for (let [sgroup, val] in store.match(group, name, includeSubdomains)) {
224 outStore.set(sgroup, name, val);
225 }
226 });
227
228 let prefs = [];
229 for (let [sgroup, sname, val] in outStore) {
230 prefs.push(new ContentPref(sgroup, sname, val));
231 }
232 return prefs;
233 },
234
235 set: function CPS2_set(group, name, value, context, callback) {
236 checkGroupArg(group);
237 this._set(group, name, value, context, callback);
238 },
239
240 setGlobal: function CPS2_setGlobal(name, value, context, callback) {
241 this._set(null, name, value, context, callback);
242 },
243
244 _set: function CPS2__set(group, name, value, context, callback) {
245 group = this._parseGroup(group);
246 checkNameArg(name);
247 checkValueArg(value);
248 checkCallbackArg(callback, false);
249
250 if (context && context.usePrivateBrowsing) {
251 this._pbStore.set(group, name, value);
252 this._schedule(function () {
253 cbHandleCompletion(callback, Ci.nsIContentPrefCallback2.COMPLETE_OK);
254 this._cps._notifyPrefSet(group, name, value);
255 });
256 return;
257 }
258
259 // Invalidate the cached value so consumers accessing the cache between now
260 // and when the operation finishes don't get old data.
261 this._cache.remove(group, name);
262
263 let stmts = [];
264
265 // Create the setting if it doesn't exist.
266 let stmt = this._stmt(
267 "INSERT OR IGNORE INTO settings (id, name)",
268 "VALUES((SELECT id FROM settings WHERE name = :name), :name)"
269 );
270 stmt.params.name = name;
271 stmts.push(stmt);
272
273 // Create the group if it doesn't exist.
274 if (group) {
275 stmt = this._stmt(
276 "INSERT OR IGNORE INTO groups (id, name)",
277 "VALUES((SELECT id FROM groups WHERE name = :group), :group)"
278 );
279 stmt.params.group = group;
280 stmts.push(stmt);
281 }
282
283 // Finally create or update the pref.
284 if (group) {
285 stmt = this._stmt(
286 "INSERT OR REPLACE INTO prefs (id, groupID, settingID, value)",
287 "VALUES(",
288 "(SELECT prefs.id",
289 "FROM prefs",
290 "JOIN groups ON groups.id = prefs.groupID",
291 "JOIN settings ON settings.id = prefs.settingID",
292 "WHERE groups.name = :group AND settings.name = :name),",
293 "(SELECT id FROM groups WHERE name = :group),",
294 "(SELECT id FROM settings WHERE name = :name),",
295 ":value",
296 ")"
297 );
298 stmt.params.group = group;
299 }
300 else {
301 stmt = this._stmt(
302 "INSERT OR REPLACE INTO prefs (id, groupID, settingID, value)",
303 "VALUES(",
304 "(SELECT prefs.id",
305 "FROM prefs",
306 "JOIN settings ON settings.id = prefs.settingID",
307 "WHERE prefs.groupID IS NULL AND settings.name = :name),",
308 "NULL,",
309 "(SELECT id FROM settings WHERE name = :name),",
310 ":value",
311 ")"
312 );
313 }
314 stmt.params.name = name;
315 stmt.params.value = value;
316 stmts.push(stmt);
317
318 this._execStmts(stmts, {
319 onDone: function onDone(reason, ok) {
320 if (ok)
321 this._cache.setWithCast(group, name, value);
322 cbHandleCompletion(callback, reason);
323 if (ok)
324 this._cps._notifyPrefSet(group, name, value);
325 },
326 onError: function onError(nsresult) {
327 cbHandleError(callback, nsresult);
328 }
329 });
330 },
331
332 removeByDomainAndName: function CPS2_removeByDomainAndName(group, name,
333 context,
334 callback) {
335 checkGroupArg(group);
336 this._remove(group, name, false, context, callback);
337 },
338
339 removeBySubdomainAndName: function CPS2_removeBySubdomainAndName(group, name,
340 context,
341 callback) {
342 checkGroupArg(group);
343 this._remove(group, name, true, context, callback);
344 },
345
346 removeGlobal: function CPS2_removeGlobal(name, context,callback) {
347 this._remove(null, name, false, context, callback);
348 },
349
350 _remove: function CPS2__remove(group, name, includeSubdomains, context,
351 callback) {
352 group = this._parseGroup(group);
353 checkNameArg(name);
354 checkCallbackArg(callback, false);
355
356 // Invalidate the cached values so consumers accessing the cache between now
357 // and when the operation finishes don't get old data.
358 for (let sgroup in this._cache.matchGroups(group, includeSubdomains)) {
359 this._cache.remove(sgroup, name);
360 }
361
362 let stmts = [];
363
364 // First get the matching prefs.
365 stmts.push(this._commonGetStmt(group, name, includeSubdomains));
366
367 // Delete the matching prefs.
368 let stmt = this._stmtWithGroupClause(group, includeSubdomains,
369 "DELETE FROM prefs",
370 "WHERE settingID = (SELECT id FROM settings WHERE name = :name) AND",
371 "CASE typeof(:group)",
372 "WHEN 'null' THEN prefs.groupID IS NULL",
373 "ELSE prefs.groupID IN ($)",
374 "END"
375 );
376 stmt.params.name = name;
377 stmts.push(stmt);
378
379 // Delete settings and groups that are no longer used. The NOTNULL term in
380 // the subquery of the second statment is needed because of SQLite's weird
381 // IN behavior vis-a-vis NULLs. See http://sqlite.org/lang_expr.html.
382 stmts.push(this._stmt(
383 "DELETE FROM settings",
384 "WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
385 ));
386 stmts.push(this._stmt(
387 "DELETE FROM groups WHERE id NOT IN (",
388 "SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL",
389 ")"
390 ));
391
392 let prefs = new ContentPrefStore();
393
394 this._execStmts(stmts, {
395 onRow: function onRow(row) {
396 let grp = row.getResultByName("grp");
397 prefs.set(grp, name, undefined);
398 this._cache.set(grp, name, undefined);
399 },
400 onDone: function onDone(reason, ok) {
401 if (ok) {
402 this._cache.set(group, name, undefined);
403 if (context && context.usePrivateBrowsing) {
404 for (let [sgroup, ] in
405 this._pbStore.match(group, name, includeSubdomains)) {
406 prefs.set(sgroup, name, undefined);
407 this._pbStore.remove(sgroup, name);
408 }
409 }
410 }
411 cbHandleCompletion(callback, reason);
412 if (ok) {
413 for (let [sgroup, , ] in prefs) {
414 this._cps._notifyPrefRemoved(sgroup, name);
415 }
416 }
417 },
418 onError: function onError(nsresult) {
419 cbHandleError(callback, nsresult);
420 }
421 });
422 },
423
424 removeByDomain: function CPS2_removeByDomain(group, context, callback) {
425 checkGroupArg(group);
426 this._removeByDomain(group, false, context, callback);
427 },
428
429 removeBySubdomain: function CPS2_removeBySubdomain(group, context, callback) {
430 checkGroupArg(group);
431 this._removeByDomain(group, true, context, callback);
432 },
433
434 removeAllGlobals: function CPS2_removeAllGlobals(context, callback) {
435 this._removeByDomain(null, false, context, callback);
436 },
437
438 _removeByDomain: function CPS2__removeByDomain(group, includeSubdomains,
439 context, callback) {
440 group = this._parseGroup(group);
441 checkCallbackArg(callback, false);
442
443 // Invalidate the cached values so consumers accessing the cache between now
444 // and when the operation finishes don't get old data.
445 for (let sgroup in this._cache.matchGroups(group, includeSubdomains)) {
446 this._cache.removeGroup(sgroup);
447 }
448
449 let stmts = [];
450
451 // First get the matching prefs, then delete groups and prefs that reference
452 // deleted groups.
453 if (group) {
454 stmts.push(this._stmtWithGroupClause(group, includeSubdomains,
455 "SELECT groups.name AS grp, settings.name AS name",
456 "FROM prefs",
457 "JOIN settings ON settings.id = prefs.settingID",
458 "JOIN groups ON groups.id = prefs.groupID",
459 "WHERE prefs.groupID IN ($)"
460 ));
461 stmts.push(this._stmtWithGroupClause(group, includeSubdomains,
462 "DELETE FROM groups WHERE id IN ($)"
463 ));
464 stmts.push(this._stmt(
465 "DELETE FROM prefs",
466 "WHERE groupID NOTNULL AND groupID NOT IN (SELECT id FROM groups)"
467 ));
468 }
469 else {
470 stmts.push(this._stmt(
471 "SELECT NULL AS grp, settings.name AS name",
472 "FROM prefs",
473 "JOIN settings ON settings.id = prefs.settingID",
474 "WHERE prefs.groupID IS NULL"
475 ));
476 stmts.push(this._stmt(
477 "DELETE FROM prefs WHERE groupID IS NULL"
478 ));
479 }
480
481 // Finally delete settings that are no longer referenced.
482 stmts.push(this._stmt(
483 "DELETE FROM settings",
484 "WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
485 ));
486
487 let prefs = new ContentPrefStore();
488
489 this._execStmts(stmts, {
490 onRow: function onRow(row) {
491 let grp = row.getResultByName("grp");
492 let name = row.getResultByName("name");
493 prefs.set(grp, name, undefined);
494 this._cache.set(grp, name, undefined);
495 },
496 onDone: function onDone(reason, ok) {
497 if (ok && context && context.usePrivateBrowsing) {
498 for (let [sgroup, sname, ] in this._pbStore) {
499 prefs.set(sgroup, sname, undefined);
500 this._pbStore.remove(sgroup, sname);
501 }
502 }
503 cbHandleCompletion(callback, reason);
504 if (ok) {
505 for (let [sgroup, sname, ] in prefs) {
506 this._cps._notifyPrefRemoved(sgroup, sname);
507 }
508 }
509 },
510 onError: function onError(nsresult) {
511 cbHandleError(callback, nsresult);
512 }
513 });
514 },
515
516 removeAllDomains: function CPS2_removeAllDomains(context, callback) {
517 checkCallbackArg(callback, false);
518
519 // Invalidate the cached values so consumers accessing the cache between now
520 // and when the operation finishes don't get old data.
521 this._cache.removeAllGroups();
522
523 let stmts = [];
524
525 // First get the matching prefs.
526 stmts.push(this._stmt(
527 "SELECT groups.name AS grp, settings.name AS name",
528 "FROM prefs",
529 "JOIN settings ON settings.id = prefs.settingID",
530 "JOIN groups ON groups.id = prefs.groupID"
531 ));
532
533 stmts.push(this._stmt(
534 "DELETE FROM prefs WHERE groupID NOTNULL"
535 ));
536 stmts.push(this._stmt(
537 "DELETE FROM groups"
538 ));
539 stmts.push(this._stmt(
540 "DELETE FROM settings",
541 "WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
542 ));
543
544 let prefs = new ContentPrefStore();
545
546 this._execStmts(stmts, {
547 onRow: function onRow(row) {
548 let grp = row.getResultByName("grp");
549 let name = row.getResultByName("name");
550 prefs.set(grp, name, undefined);
551 this._cache.set(grp, name, undefined);
552 },
553 onDone: function onDone(reason, ok) {
554 if (ok && context && context.usePrivateBrowsing) {
555 for (let [sgroup, sname, ] in this._pbStore) {
556 prefs.set(sgroup, sname, undefined);
557 }
558 this._pbStore.removeAllGroups();
559 }
560 cbHandleCompletion(callback, reason);
561 if (ok) {
562 for (let [sgroup, sname, ] in prefs) {
563 this._cps._notifyPrefRemoved(sgroup, sname);
564 }
565 }
566 },
567 onError: function onError(nsresult) {
568 cbHandleError(callback, nsresult);
569 }
570 });
571 },
572
573 removeByName: function CPS2_removeByName(name, context, callback) {
574 checkNameArg(name);
575 checkCallbackArg(callback, false);
576
577 // Invalidate the cached values so consumers accessing the cache between now
578 // and when the operation finishes don't get old data.
579 for (let [group, sname, ] in this._cache) {
580 if (sname == name)
581 this._cache.remove(group, name);
582 }
583
584 let stmts = [];
585
586 // First get the matching prefs. Include null if any of those prefs are
587 // global.
588 let stmt = this._stmt(
589 "SELECT groups.name AS grp",
590 "FROM prefs",
591 "JOIN settings ON settings.id = prefs.settingID",
592 "JOIN groups ON groups.id = prefs.groupID",
593 "WHERE settings.name = :name",
594 "UNION",
595 "SELECT NULL AS grp",
596 "WHERE EXISTS (",
597 "SELECT prefs.id",
598 "FROM prefs",
599 "JOIN settings ON settings.id = prefs.settingID",
600 "WHERE settings.name = :name AND prefs.groupID IS NULL",
601 ")"
602 );
603 stmt.params.name = name;
604 stmts.push(stmt);
605
606 // Delete the target settings.
607 stmt = this._stmt(
608 "DELETE FROM settings WHERE name = :name"
609 );
610 stmt.params.name = name;
611 stmts.push(stmt);
612
613 // Delete prefs and groups that are no longer used.
614 stmts.push(this._stmt(
615 "DELETE FROM prefs WHERE settingID NOT IN (SELECT id FROM settings)"
616 ));
617 stmts.push(this._stmt(
618 "DELETE FROM groups WHERE id NOT IN (",
619 "SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL",
620 ")"
621 ));
622
623 let prefs = new ContentPrefStore();
624
625 this._execStmts(stmts, {
626 onRow: function onRow(row) {
627 let grp = row.getResultByName("grp");
628 prefs.set(grp, name, undefined);
629 this._cache.set(grp, name, undefined);
630 },
631 onDone: function onDone(reason, ok) {
632 if (ok && context && context.usePrivateBrowsing) {
633 for (let [sgroup, sname, ] in this._pbStore) {
634 if (sname === name) {
635 prefs.set(sgroup, name, undefined);
636 this._pbStore.remove(sgroup, name);
637 }
638 }
639 }
640 cbHandleCompletion(callback, reason);
641 if (ok) {
642 for (let [sgroup, , ] in prefs) {
643 this._cps._notifyPrefRemoved(sgroup, name);
644 }
645 }
646 },
647 onError: function onError(nsresult) {
648 cbHandleError(callback, nsresult);
649 }
650 });
651 },
652
653 destroy: function CPS2_destroy() {
654 for each (let stmt in this._statements) {
655 stmt.finalize();
656 }
657 },
658
659 /**
660 * Returns the cached mozIStorageAsyncStatement for the given SQL. If no such
661 * statement is cached, one is created and cached.
662 *
663 * @param sql The SQL query string. If more than one string is given, then
664 * all are concatenated. The concatenation process inserts
665 * spaces where appropriate and removes unnecessary contiguous
666 * spaces. Call like _stmt("SELECT *", "FROM foo").
667 * @return The cached, possibly new, statement.
668 */
669 _stmt: function CPS2__stmt(sql /*, sql2, sql3, ... */) {
670 let sql = joinArgs(arguments);
671 if (!this._statements)
672 this._statements = {};
673 if (!this._statements[sql])
674 this._statements[sql] = this._cps._dbConnection.createAsyncStatement(sql);
675 return this._statements[sql];
676 },
677
678 /**
679 * Executes some async statements.
680 *
681 * @param stmts An array of mozIStorageAsyncStatements.
682 * @param callbacks An object with the following methods:
683 * onRow(row) (optional)
684 * Called once for each result row.
685 * row: A mozIStorageRow.
686 * onDone(reason, reasonOK, didGetRow) (required)
687 * Called when done.
688 * reason: A nsIContentPrefService2.COMPLETE_* value.
689 * reasonOK: reason == nsIContentPrefService2.COMPLETE_OK.
690 * didGetRow: True if onRow was ever called.
691 * onError(nsresult) (optional)
692 * Called on error.
693 * nsresult: The error code.
694 */
695 _execStmts: function CPS2__execStmts(stmts, callbacks) {
696 let self = this;
697 let gotRow = false;
698 this._cps._dbConnection.executeAsync(stmts, stmts.length, {
699 handleResult: function handleResult(results) {
700 try {
701 let row = null;
702 while ((row = results.getNextRow())) {
703 gotRow = true;
704 if (callbacks.onRow)
705 callbacks.onRow.call(self, row);
706 }
707 }
708 catch (err) {
709 Cu.reportError(err);
710 }
711 },
712 handleCompletion: function handleCompletion(reason) {
713 try {
714 let ok = reason == Ci.mozIStorageStatementCallback.REASON_FINISHED;
715 callbacks.onDone.call(self,
716 ok ? Ci.nsIContentPrefCallback2.COMPLETE_OK :
717 Ci.nsIContentPrefCallback2.COMPLETE_ERROR,
718 ok, gotRow);
719 }
720 catch (err) {
721 Cu.reportError(err);
722 }
723 },
724 handleError: function handleError(error) {
725 try {
726 if (callbacks.onError)
727 callbacks.onError.call(self, Cr.NS_ERROR_FAILURE);
728 }
729 catch (err) {
730 Cu.reportError(err);
731 }
732 }
733 });
734 },
735
736 /**
737 * Parses the domain (the "group", to use the database's term) from the given
738 * string.
739 *
740 * @param groupStr Assumed to be either a string or falsey.
741 * @return If groupStr is a valid URL string, returns the domain of
742 * that URL. If groupStr is some other nonempty string,
743 * returns groupStr itself. Otherwise returns null.
744 */
745 _parseGroup: function CPS2__parseGroup(groupStr) {
746 if (!groupStr)
747 return null;
748 try {
749 var groupURI = Services.io.newURI(groupStr, null, null);
750 }
751 catch (err) {
752 return groupStr;
753 }
754 return this._cps._grouper.group(groupURI);
755 },
756
757 _schedule: function CPS2__schedule(fn) {
758 Services.tm.mainThread.dispatch(fn.bind(this),
759 Ci.nsIThread.DISPATCH_NORMAL);
760 },
761
762 addObserverForName: function CPS2_addObserverForName(name, observer) {
763 this._cps._addObserver(name, observer);
764 },
765
766 removeObserverForName: function CPS2_removeObserverForName(name, observer) {
767 this._cps._removeObserver(name, observer);
768 },
769
770 extractDomain: function CPS2_extractDomain(str) {
771 return this._parseGroup(str);
772 },
773
774 /**
775 * Tests use this as a backchannel by calling it directly.
776 *
777 * @param subj This value depends on topic.
778 * @param topic The backchannel "method" name.
779 * @param data This value depends on topic.
780 */
781 observe: function CPS2_observe(subj, topic, data) {
782 switch (topic) {
783 case "test:reset":
784 let fn = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
785 this._reset(fn);
786 break;
787 case "test:db":
788 let obj = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
789 obj.value = this._cps._dbConnection;
790 break;
791 }
792 },
793
794 /**
795 * Removes all state from the service. Used by tests.
796 *
797 * @param callback A function that will be called when done.
798 */
799 _reset: function CPS2__reset(callback) {
800 this._pbStore.removeAll();
801 this._cache.removeAll();
802
803 let cps = this._cps;
804 cps._observers = {};
805 cps._genericObservers = [];
806
807 let tables = ["prefs", "groups", "settings"];
808 let stmts = tables.map(function (t) this._stmt("DELETE FROM", t), this);
809 this._execStmts(stmts, { onDone: function () callback() });
810 },
811
812 QueryInterface: function CPS2_QueryInterface(iid) {
813 let supportedIIDs = [
814 Ci.nsIContentPrefService2,
815 Ci.nsIObserver,
816 Ci.nsISupports,
817 ];
818 if (supportedIIDs.some(function (i) iid.equals(i)))
819 return this;
820 if (iid.equals(Ci.nsIContentPrefService))
821 return this._cps;
822 throw Cr.NS_ERROR_NO_INTERFACE;
823 },
824 };
825
826 function ContentPref(domain, name, value) {
827 this.domain = domain;
828 this.name = name;
829 this.value = value;
830 }
831
832 ContentPref.prototype = {
833 QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPref]),
834 };
835
836 function cbHandleResult(callback, pref) {
837 safeCallback(callback, "handleResult", [pref]);
838 }
839
840 function cbHandleCompletion(callback, reason) {
841 safeCallback(callback, "handleCompletion", [reason]);
842 }
843
844 function cbHandleError(callback, nsresult) {
845 safeCallback(callback, "handleError", [nsresult]);
846 }
847
848 function safeCallback(callbackObj, methodName, args) {
849 if (!callbackObj || typeof(callbackObj[methodName]) != "function")
850 return;
851 try {
852 callbackObj[methodName].apply(callbackObj, args);
853 }
854 catch (err) {
855 Cu.reportError(err);
856 }
857 }
858
859 function checkGroupArg(group) {
860 if (!group || typeof(group) != "string")
861 throw invalidArg("domain must be nonempty string.");
862 }
863
864 function checkNameArg(name) {
865 if (!name || typeof(name) != "string")
866 throw invalidArg("name must be nonempty string.");
867 }
868
869 function checkValueArg(value) {
870 if (value === undefined)
871 throw invalidArg("value must not be undefined.");
872 }
873
874 function checkCallbackArg(callback, required) {
875 if (callback && !(callback instanceof Ci.nsIContentPrefCallback2))
876 throw invalidArg("callback must be an nsIContentPrefCallback2.");
877 if (!callback && required)
878 throw invalidArg("callback must be given.");
879 }
880
881 function invalidArg(msg) {
882 return Components.Exception(msg, Cr.NS_ERROR_INVALID_ARG);
883 }
884
885 function joinArgs(args) {
886 return Array.join(args, " ").trim().replace(/\s{2,}/g, " ");
887 }

mercurial