toolkit/components/contentprefs/ContentPrefService2.jsm

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

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

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

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

mercurial