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.

     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/. */
     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
    25 let EXPORTED_SYMBOLS = [
    26   "ContentPrefService2",
    27 ];
    29 const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
    31 Cu.import("resource://gre/modules/Services.jsm");
    32 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    33 Cu.import("resource://gre/modules/ContentPrefStore.jsm");
    35 function ContentPrefService2(cps) {
    36   this._cps = cps;
    37   this._cache = cps._cache;
    38   this._pbStore = cps._privModeStorage;
    39 }
    41 ContentPrefService2.prototype = {
    43   getByName: function CPS2_getByName(name, context, callback) {
    44     checkNameArg(name);
    45     checkCallbackArg(callback, true);
    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     }
    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;
    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;
    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   },
    98   getByDomainAndName: function CPS2_getByDomainAndName(group, name, context,
    99                                                        callback) {
   100     checkGroupArg(group);
   101     this._get(group, name, false, context, callback);
   102   },
   104   getBySubdomainAndName: function CPS2_getBySubdomainAndName(group, name,
   105                                                              context,
   106                                                              callback) {
   107     checkGroupArg(group);
   108     this._get(group, name, true, context, callback);
   109   },
   111   getGlobal: function CPS2_getGlobal(name, context, callback) {
   112     this._get(null, name, false, context, callback);
   113   },
   115   _get: function CPS2__get(group, name, includeSubdomains, context, callback) {
   116     group = this._parseGroup(group);
   117     checkNameArg(name);
   118     checkCallbackArg(callback, true);
   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     }
   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   },
   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   },
   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   },
   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   },
   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   },
   207   getCachedGlobal: function CPS2_getCachedGlobal(name, context) {
   208     let prefs = this._getCached(null, name, false, context);
   209     return prefs[0] || null;
   210   },
   212   _getCached: function CPS2__getCached(group, name, includeSubdomains,
   213                                        context) {
   214     group = this._parseGroup(group);
   215     checkNameArg(name);
   217     let storesToCheck = [this._cache];
   218     if (context && context.usePrivateBrowsing)
   219       storesToCheck.push(this._pbStore);
   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     });
   228     let prefs = [];
   229     for (let [sgroup, sname, val] in outStore) {
   230       prefs.push(new ContentPref(sgroup, sname, val));
   231     }
   232     return prefs;
   233   },
   235   set: function CPS2_set(group, name, value, context, callback) {
   236     checkGroupArg(group);
   237     this._set(group, name, value, context, callback);
   238   },
   240   setGlobal: function CPS2_setGlobal(name, value, context, callback) {
   241     this._set(null, name, value, context, callback);
   242   },
   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);
   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     }
   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);
   263     let stmts = [];
   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);
   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     }
   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);
   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   },
   332   removeByDomainAndName: function CPS2_removeByDomainAndName(group, name,
   333                                                              context,
   334                                                              callback) {
   335     checkGroupArg(group);
   336     this._remove(group, name, false, context, callback);
   337   },
   339   removeBySubdomainAndName: function CPS2_removeBySubdomainAndName(group, name,
   340                                                                    context,
   341                                                                    callback) {
   342     checkGroupArg(group);
   343     this._remove(group, name, true, context, callback);
   344   },
   346   removeGlobal: function CPS2_removeGlobal(name, context,callback) {
   347     this._remove(null, name, false, context, callback);
   348   },
   350   _remove: function CPS2__remove(group, name, includeSubdomains, context,
   351                                  callback) {
   352     group = this._parseGroup(group);
   353     checkNameArg(name);
   354     checkCallbackArg(callback, false);
   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     }
   362     let stmts = [];
   364     // First get the matching prefs.
   365     stmts.push(this._commonGetStmt(group, name, includeSubdomains));
   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);
   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     ));
   392     let prefs = new ContentPrefStore();
   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   },
   424   removeByDomain: function CPS2_removeByDomain(group, context, callback) {
   425     checkGroupArg(group);
   426     this._removeByDomain(group, false, context, callback);
   427   },
   429   removeBySubdomain: function CPS2_removeBySubdomain(group, context, callback) {
   430     checkGroupArg(group);
   431     this._removeByDomain(group, true, context, callback);
   432   },
   434   removeAllGlobals: function CPS2_removeAllGlobals(context, callback) {
   435     this._removeByDomain(null, false, context, callback);
   436   },
   438   _removeByDomain: function CPS2__removeByDomain(group, includeSubdomains,
   439                                                  context, callback) {
   440     group = this._parseGroup(group);
   441     checkCallbackArg(callback, false);
   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     }
   449     let stmts = [];
   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     }
   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     ));
   487     let prefs = new ContentPrefStore();
   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   },
   516   removeAllDomains: function CPS2_removeAllDomains(context, callback) {
   517     checkCallbackArg(callback, false);
   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();
   523     let stmts = [];
   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     ));
   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     ));
   544     let prefs = new ContentPrefStore();
   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   },
   573   removeByName: function CPS2_removeByName(name, context, callback) {
   574     checkNameArg(name);
   575     checkCallbackArg(callback, false);
   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     }
   584     let stmts = [];
   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);
   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);
   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     ));
   623     let prefs = new ContentPrefStore();
   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   },
   653   destroy: function CPS2_destroy() {
   654     for each (let stmt in this._statements) {
   655       stmt.finalize();
   656     }
   657   },
   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   },
   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   },
   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   },
   757   _schedule: function CPS2__schedule(fn) {
   758     Services.tm.mainThread.dispatch(fn.bind(this),
   759                                     Ci.nsIThread.DISPATCH_NORMAL);
   760   },
   762   addObserverForName: function CPS2_addObserverForName(name, observer) {
   763     this._cps._addObserver(name, observer);
   764   },
   766   removeObserverForName: function CPS2_removeObserverForName(name, observer) {
   767     this._cps._removeObserver(name, observer);
   768   },
   770   extractDomain: function CPS2_extractDomain(str) {
   771     return this._parseGroup(str);
   772   },
   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   },
   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();
   803     let cps = this._cps;
   804     cps._observers = {};
   805     cps._genericObservers = [];
   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   },
   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 };
   826 function ContentPref(domain, name, value) {
   827   this.domain = domain;
   828   this.name = name;
   829   this.value = value;
   830 }
   832 ContentPref.prototype = {
   833   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPref]),
   834 };
   836 function cbHandleResult(callback, pref) {
   837   safeCallback(callback, "handleResult", [pref]);
   838 }
   840 function cbHandleCompletion(callback, reason) {
   841   safeCallback(callback, "handleCompletion", [reason]);
   842 }
   844 function cbHandleError(callback, nsresult) {
   845   safeCallback(callback, "handleError", [nsresult]);
   846 }
   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 }
   859 function checkGroupArg(group) {
   860   if (!group || typeof(group) != "string")
   861     throw invalidArg("domain must be nonempty string.");
   862 }
   864 function checkNameArg(name) {
   865   if (!name || typeof(name) != "string")
   866     throw invalidArg("name must be nonempty string.");
   867 }
   869 function checkValueArg(value) {
   870   if (value === undefined)
   871     throw invalidArg("value must not be undefined.");
   872 }
   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 }
   881 function invalidArg(msg) {
   882   return Components.Exception(msg, Cr.NS_ERROR_INVALID_ARG);
   883 }
   885 function joinArgs(args) {
   886   return Array.join(args, " ").trim().replace(/\s{2,}/g, " ");
   887 }

mercurial