services/sync/modules/util.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 this.EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"];
     7 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
     9 Cu.import("resource://gre/modules/Log.jsm");
    10 Cu.import("resource://services-common/observers.js");
    11 Cu.import("resource://services-common/stringbundle.js");
    12 Cu.import("resource://services-common/utils.js");
    13 Cu.import("resource://services-common/async.js", this);
    14 Cu.import("resource://services-crypto/utils.js");
    15 Cu.import("resource://services-sync/constants.js");
    16 Cu.import("resource://gre/modules/Preferences.jsm");
    17 Cu.import("resource://gre/modules/Services.jsm", this);
    18 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    19 Cu.import("resource://gre/modules/osfile.jsm", this);
    20 Cu.import("resource://gre/modules/Task.jsm", this);
    22 /*
    23  * Utility functions
    24  */
    26 this.Utils = {
    27   // Alias in functions from CommonUtils. These previously were defined here.
    28   // In the ideal world, references to these would be removed.
    29   nextTick: CommonUtils.nextTick,
    30   namedTimer: CommonUtils.namedTimer,
    31   exceptionStr: CommonUtils.exceptionStr,
    32   stackTrace: CommonUtils.stackTrace,
    33   makeURI: CommonUtils.makeURI,
    34   encodeUTF8: CommonUtils.encodeUTF8,
    35   decodeUTF8: CommonUtils.decodeUTF8,
    36   safeAtoB: CommonUtils.safeAtoB,
    37   byteArrayToString: CommonUtils.byteArrayToString,
    38   bytesAsHex: CommonUtils.bytesAsHex,
    39   hexToBytes: CommonUtils.hexToBytes,
    40   encodeBase32: CommonUtils.encodeBase32,
    41   decodeBase32: CommonUtils.decodeBase32,
    43   // Aliases from CryptoUtils.
    44   generateRandomBytes: CryptoUtils.generateRandomBytes,
    45   computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
    46   digestUTF8: CryptoUtils.digestUTF8,
    47   digestBytes: CryptoUtils.digestBytes,
    48   sha1: CryptoUtils.sha1,
    49   sha1Base32: CryptoUtils.sha1Base32,
    50   makeHMACKey: CryptoUtils.makeHMACKey,
    51   makeHMACHasher: CryptoUtils.makeHMACHasher,
    52   hkdfExpand: CryptoUtils.hkdfExpand,
    53   pbkdf2Generate: CryptoUtils.pbkdf2Generate,
    54   deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
    55   getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
    57   /**
    58    * Wrap a function to catch all exceptions and log them
    59    *
    60    * @usage MyObj._catch = Utils.catch;
    61    *        MyObj.foo = function() { this._catch(func)(); }
    62    *
    63    * Optionally pass a function which will be called if an
    64    * exception occurs.
    65    */
    66   catch: function Utils_catch(func, exceptionCallback) {
    67     let thisArg = this;
    68     return function WrappedCatch() {
    69       try {
    70         return func.call(thisArg);
    71       }
    72       catch(ex) {
    73         thisArg._log.debug("Exception: " + Utils.exceptionStr(ex));
    74         if (exceptionCallback) {
    75           return exceptionCallback.call(thisArg, ex);
    76         }
    77         return null;
    78       }
    79     };
    80   },
    82   /**
    83    * Wrap a function to call lock before calling the function then unlock.
    84    *
    85    * @usage MyObj._lock = Utils.lock;
    86    *        MyObj.foo = function() { this._lock(func)(); }
    87    */
    88   lock: function lock(label, func) {
    89     let thisArg = this;
    90     return function WrappedLock() {
    91       if (!thisArg.lock()) {
    92         throw "Could not acquire lock. Label: \"" + label + "\".";
    93       }
    95       try {
    96         return func.call(thisArg);
    97       }
    98       finally {
    99         thisArg.unlock();
   100       }
   101     };
   102   },
   104   isLockException: function isLockException(ex) {
   105     return ex && ex.indexOf && ex.indexOf("Could not acquire lock.") == 0;
   106   },
   108   /**
   109    * Wrap functions to notify when it starts and finishes executing or if it
   110    * threw an error.
   111    *
   112    * The message is a combination of a provided prefix, the local name, and
   113    * the event. Possible events are: "start", "finish", "error". The subject
   114    * is the function's return value on "finish" or the caught exception on
   115    * "error". The data argument is the predefined data value.
   116    *
   117    * Example:
   118    *
   119    * @usage function MyObj(name) {
   120    *          this.name = name;
   121    *          this._notify = Utils.notify("obj:");
   122    *        }
   123    *        MyObj.prototype = {
   124    *          foo: function() this._notify("func", "data-arg", function () {
   125    *            //...
   126    *          }(),
   127    *        };
   128    */
   129   notify: function Utils_notify(prefix) {
   130     return function NotifyMaker(name, data, func) {
   131       let thisArg = this;
   132       let notify = function(state, subject) {
   133         let mesg = prefix + name + ":" + state;
   134         thisArg._log.trace("Event: " + mesg);
   135         Observers.notify(mesg, subject, data);
   136       };
   138       return function WrappedNotify() {
   139         try {
   140           notify("start", null);
   141           let ret = func.call(thisArg);
   142           notify("finish", ret);
   143           return ret;
   144         }
   145         catch(ex) {
   146           notify("error", ex);
   147           throw ex;
   148         }
   149       };
   150     };
   151   },
   153   runInTransaction: function(db, callback, thisObj) {
   154     let hasTransaction = false;
   155     try {
   156       db.beginTransaction();
   157       hasTransaction = true;
   158     } catch(e) { /* om nom nom exceptions */ }
   160     try {
   161       return callback.call(thisObj);
   162     } finally {
   163       if (hasTransaction) {
   164         db.commitTransaction();
   165       }
   166     }
   167   },
   169   /**
   170    * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
   171    * That makes them 12 characters long with 72 bits of entropy.
   172    */
   173   makeGUID: function makeGUID() {
   174     return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
   175   },
   177   _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
   178   checkGUID: function checkGUID(guid) {
   179     return !!guid && this._base64url_regex.test(guid);
   180   },
   182   /**
   183    * Add a simple getter/setter to an object that defers access of a property
   184    * to an inner property.
   185    *
   186    * @param obj
   187    *        Object to add properties to defer in its prototype
   188    * @param defer
   189    *        Property of obj to defer to
   190    * @param prop
   191    *        Property name to defer (or an array of property names)
   192    */
   193   deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
   194     if (Array.isArray(prop))
   195       return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop));
   197     let prot = obj.prototype;
   199     // Create a getter if it doesn't exist yet
   200     if (!prot.__lookupGetter__(prop)) {
   201       prot.__defineGetter__(prop, function () {
   202         return this[defer][prop];
   203       });
   204     }
   206     // Create a setter if it doesn't exist yet
   207     if (!prot.__lookupSetter__(prop)) {
   208       prot.__defineSetter__(prop, function (val) {
   209         this[defer][prop] = val;
   210       });
   211     }
   212   },
   214   lazyStrings: function Weave_lazyStrings(name) {
   215     let bundle = "chrome://weave/locale/services/" + name + ".properties";
   216     return function() new StringBundle(bundle);
   217   },
   219   deepEquals: function eq(a, b) {
   220     // If they're triple equals, then it must be equals!
   221     if (a === b)
   222       return true;
   224     // If they weren't equal, they must be objects to be different
   225     if (typeof a != "object" || typeof b != "object")
   226       return false;
   228     // But null objects won't have properties to compare
   229     if (a === null || b === null)
   230       return false;
   232     // Make sure all of a's keys have a matching value in b
   233     for (let k in a)
   234       if (!eq(a[k], b[k]))
   235         return false;
   237     // Do the same for b's keys but skip those that we already checked
   238     for (let k in b)
   239       if (!(k in a) && !eq(a[k], b[k]))
   240         return false;
   242     return true;
   243   },
   245   // Generator and discriminator for HMAC exceptions.
   246   // Split these out in case we want to make them richer in future, and to
   247   // avoid inevitable confusion if the message changes.
   248   throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
   249     throw "Record SHA256 HMAC mismatch: should be " + shouldBe + ", is " + is;
   250   },
   252   isHMACMismatch: function isHMACMismatch(ex) {
   253     const hmacFail = "Record SHA256 HMAC mismatch: ";
   254     return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
   255   },
   257   /**
   258    * Turn RFC 4648 base32 into our own user-friendly version.
   259    *   ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
   260    * becomes
   261    *   abcdefghijk8mn9pqrstuvwxyz234567
   262    */
   263   base32ToFriendly: function base32ToFriendly(input) {
   264     return input.toLowerCase()
   265                 .replace("l", '8', "g")
   266                 .replace("o", '9', "g");
   267   },
   269   base32FromFriendly: function base32FromFriendly(input) {
   270     return input.toUpperCase()
   271                 .replace("8", 'L', "g")
   272                 .replace("9", 'O', "g");
   273   },
   275   /**
   276    * Key manipulation.
   277    */
   279   // Return an octet string in friendly base32 *with no trailing =*.
   280   encodeKeyBase32: function encodeKeyBase32(keyData) {
   281     return Utils.base32ToFriendly(
   282              Utils.encodeBase32(keyData))
   283            .slice(0, SYNC_KEY_ENCODED_LENGTH);
   284   },
   286   decodeKeyBase32: function decodeKeyBase32(encoded) {
   287     return Utils.decodeBase32(
   288              Utils.base32FromFriendly(
   289                Utils.normalizePassphrase(encoded)))
   290            .slice(0, SYNC_KEY_DECODED_LENGTH);
   291   },
   293   base64Key: function base64Key(keyData) {
   294     return btoa(keyData);
   295   },
   297   /**
   298    * N.B., salt should be base64 encoded, even though we have to decode
   299    * it later!
   300    */
   301   derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
   302     let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
   303                                                 forceJS);
   304     return Utils.encodeKeyBase32(k);
   305   },
   307   /**
   308    * N.B., salt should be base64 encoded, even though we have to decode
   309    * it later!
   310    */
   311   deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
   312     let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
   313                                                 forceJS);
   314     return Utils.base64Key(k);
   315   },
   317   /**
   318    * Take a base64-encoded 128-bit AES key, returning it as five groups of five
   319    * uppercase alphanumeric characters, separated by hyphens.
   320    * A.K.A. base64-to-base32 encoding.
   321    */
   322   presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
   323     return Utils.encodeKeyBase32(atob(encodedKey));
   324   },
   326   /**
   327    * Load a JSON file from disk in the profile directory.
   328    *
   329    * @param filePath
   330    *        JSON file path load from profile. Loaded file will be
   331    *        <profile>/<filePath>.json. i.e. Do not specify the ".json"
   332    *        extension.
   333    * @param that
   334    *        Object to use for logging and "this" for callback.
   335    * @param callback
   336    *        Function to process json object as its first argument. If the file
   337    *        could not be loaded, the first argument will be undefined.
   338    */
   339   jsonLoad: Task.async(function*(filePath, that, callback) {
   340     let path = OS.Path.join(OS.Constants.Path.profileDir, "weave", filePath + ".json");
   342     if (that._log) {
   343       that._log.trace("Loading json from disk: " + filePath);
   344     }
   346     let json;
   348     try {
   349       json = yield CommonUtils.readJSON(path);
   350     } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
   351       // Ignore non-existent files.
   352     } catch (e) {
   353       if (that._log) {
   354         that._log.debug("Failed to load json: " +
   355                         CommonUtils.exceptionStr(e));
   356       }
   357     }
   359     callback.call(that, json);
   360   }),
   362   /**
   363    * Save a json-able object to disk in the profile directory.
   364    *
   365    * @param filePath
   366    *        JSON file path save to <filePath>.json
   367    * @param that
   368    *        Object to use for logging and "this" for callback
   369    * @param obj
   370    *        Function to provide json-able object to save. If this isn't a
   371    *        function, it'll be used as the object to make a json string.
   372    * @param callback
   373    *        Function called when the write has been performed. Optional.
   374    *        The first argument will be a Components.results error
   375    *        constant on error or null if no error was encountered (and
   376    *        the file saved successfully).
   377    */
   378   jsonSave: Task.async(function*(filePath, that, obj, callback) {
   379     let path = OS.Path.join(OS.Constants.Path.profileDir, "weave",
   380                             ...(filePath + ".json").split("/"));
   381     let dir = OS.Path.dirname(path);
   382     let error = null;
   384     try {
   385       yield OS.File.makeDir(dir, { from: OS.Constants.Path.profileDir });
   387       if (that._log) {
   388         that._log.trace("Saving json to disk: " + path);
   389       }
   391       let json = typeof obj == "function" ? obj.call(that) : obj;
   393       yield CommonUtils.writeJSON(json, path);
   394     } catch (e) {
   395       error = e
   396     }
   398     if (typeof callback == "function") {
   399       callback.call(that, error);
   400     }
   401   }),
   403   getErrorString: function Utils_getErrorString(error, args) {
   404     try {
   405       return Str.errors.get(error, args || null);
   406     } catch (e) {}
   408     // basically returns "Unknown Error"
   409     return Str.errors.get("error.reason.unknown");
   410   },
   412   /**
   413    * Generate 26 characters.
   414    */
   415   generatePassphrase: function generatePassphrase() {
   416     // Note that this is a different base32 alphabet to the one we use for
   417     // other tasks. It's lowercase, uses different letters, and needs to be
   418     // decoded with decodeKeyBase32, not just decodeBase32.
   419     return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16));
   420   },
   422   /**
   423    * The following are the methods supported for UI use:
   424    *
   425    * * isPassphrase:
   426    *     determines whether a string is either a normalized or presentable
   427    *     passphrase.
   428    * * hyphenatePassphrase:
   429    *     present a normalized passphrase for display. This might actually
   430    *     perform work beyond just hyphenation; sorry.
   431    * * hyphenatePartialPassphrase:
   432    *     present a fragment of a normalized passphrase for display.
   433    * * normalizePassphrase:
   434    *     take a presentable passphrase and reduce it to a normalized
   435    *     representation for storage. normalizePassphrase can safely be called
   436    *     on normalized input.
   437    * * normalizeAccount:
   438    *     take user input for account/username, cleaning up appropriately.
   439    */
   441   isPassphrase: function(s) {
   442     if (s) {
   443       return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
   444     }
   445     return false;
   446   },
   448   /**
   449    * Hyphenate a passphrase (26 characters) into groups.
   450    * abbbbccccddddeeeeffffggggh
   451    * =>
   452    * a-bbbbc-cccdd-ddeee-effff-ggggh
   453    */
   454   hyphenatePassphrase: function hyphenatePassphrase(passphrase) {
   455     // For now, these are the same.
   456     return Utils.hyphenatePartialPassphrase(passphrase, true);
   457   },
   459   hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) {
   460     if (!passphrase)
   461       return null;
   463     // Get the raw data input. Just base32.
   464     let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, "");
   466     // This is the neatest way to do this.
   467     if ((data.length == 1) && !omitTrailingDash)
   468       return data + "-";
   470     // Hyphenate it.
   471     let y = data.substr(0,1);
   472     let z = data.substr(1).replace(/(.{1,5})/g, "-$1");
   474     // Correct length? We're done.
   475     if ((z.length == 30) || omitTrailingDash)
   476       return y + z;
   478     // Add a trailing dash if appropriate.
   479     return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH);
   480   },
   482   normalizePassphrase: function normalizePassphrase(pp) {
   483     // Short var name... have you seen the lines below?!
   484     // Allow leading and trailing whitespace.
   485     pp = pp.trim().toLowerCase();
   487     // 20-char sync key.
   488     if (pp.length == 23 &&
   489         [5, 11, 17].every(function(i) pp[i] == '-')) {
   491       return pp.slice(0, 5) + pp.slice(6, 11)
   492              + pp.slice(12, 17) + pp.slice(18, 23);
   493     }
   495     // "Modern" 26-char key.
   496     if (pp.length == 31 &&
   497         [1, 7, 13, 19, 25].every(function(i) pp[i] == '-')) {
   499       return pp.slice(0, 1) + pp.slice(2, 7)
   500              + pp.slice(8, 13) + pp.slice(14, 19)
   501              + pp.slice(20, 25) + pp.slice(26, 31);
   502     }
   504     // Something else -- just return.
   505     return pp;
   506   },
   508   normalizeAccount: function normalizeAccount(acc) {
   509     return acc.trim();
   510   },
   512   /**
   513    * Create an array like the first but without elements of the second. Reuse
   514    * arrays if possible.
   515    */
   516   arraySub: function arraySub(minuend, subtrahend) {
   517     if (!minuend.length || !subtrahend.length)
   518       return minuend;
   519     return minuend.filter(function(i) subtrahend.indexOf(i) == -1);
   520   },
   522   /**
   523    * Build the union of two arrays. Reuse arrays if possible.
   524    */
   525   arrayUnion: function arrayUnion(foo, bar) {
   526     if (!foo.length)
   527       return bar;
   528     if (!bar.length)
   529       return foo;
   530     return foo.concat(Utils.arraySub(bar, foo));
   531   },
   533   bind2: function Async_bind2(object, method) {
   534     return function innerBind() { return method.apply(object, arguments); };
   535   },
   537   /**
   538    * Is there a master password configured, regardless of current lock state?
   539    */
   540   mpEnabled: function mpEnabled() {
   541     let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
   542                     .getService(Ci.nsIPKCS11ModuleDB);
   543     let sdrSlot = modules.findSlotByName("");
   544     let status  = sdrSlot.status;
   545     let slots = Ci.nsIPKCS11Slot;
   547     return status != slots.SLOT_UNINITIALIZED && status != slots.SLOT_READY;
   548   },
   550   /**
   551    * Is there a master password configured and currently locked?
   552    */
   553   mpLocked: function mpLocked() {
   554     let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
   555                     .getService(Ci.nsIPKCS11ModuleDB);
   556     let sdrSlot = modules.findSlotByName("");
   557     let status  = sdrSlot.status;
   558     let slots = Ci.nsIPKCS11Slot;
   560     if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN
   561                                    || status == slots.SLOT_UNINITIALIZED)
   562       return false;
   564     if (status == slots.SLOT_NOT_LOGGED_IN)
   565       return true;
   567     // something wacky happened, pretend MP is locked
   568     return true;
   569   },
   571   // If Master Password is enabled and locked, present a dialog to unlock it.
   572   // Return whether the system is unlocked.
   573   ensureMPUnlocked: function ensureMPUnlocked() {
   574     if (!Utils.mpLocked()) {
   575       return true;
   576     }
   577     let sdr = Cc["@mozilla.org/security/sdr;1"]
   578                 .getService(Ci.nsISecretDecoderRing);
   579     try {
   580       sdr.encryptString("bacon");
   581       return true;
   582     } catch(e) {}
   583     return false;
   584   },
   586   /**
   587    * Return a value for a backoff interval.  Maximum is eight hours, unless
   588    * Status.backoffInterval is higher.
   589    *
   590    */
   591   calculateBackoff: function calculateBackoff(attempts, baseInterval,
   592                                               statusInterval) {
   593     let backoffInterval = attempts *
   594                           (Math.floor(Math.random() * baseInterval) +
   595                            baseInterval);
   596     return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
   597                     statusInterval);
   598   },
   600   /**
   601    * Return a set of hostnames (including the protocol) which may have
   602    * credentials for sync itself stored in the login manager.
   603    *
   604    * In general, these hosts will not have their passwords synced, will be
   605    * reset when we drop sync credentials, etc.
   606    */
   607   getSyncCredentialsHosts: function() {
   608     // This is somewhat expensive and the result static, so we cache the result.
   609     if (this._syncCredentialsHosts) {
   610       return this._syncCredentialsHosts;
   611     }
   612     let result = new Set();
   613     // the legacy sync host.
   614     result.add(PWDMGR_HOST);
   615     // The FxA hosts - these almost certainly all have the same hostname, but
   616     // better safe than sorry...
   617     for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
   618                           "identity.fxaccounts.remote.signup.uri",
   619                           "identity.fxaccounts.remote.signin.uri",
   620                           "identity.fxaccounts.settings.uri"]) {
   621       let prefVal;
   622       try {
   623         prefVal = Services.prefs.getCharPref(prefName);
   624       } catch (_) {
   625         continue;
   626       }
   627       let uri = Services.io.newURI(prefVal, null, null);
   628       result.add(uri.prePath);
   629     }
   630     return this._syncCredentialsHosts = result;
   631   },
   632 };
   634 XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
   635   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   636                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   637   converter.charset = "UTF-8";
   638   return converter;
   639 });
   641 /*
   642  * Commonly-used services
   643  */
   644 this.Svc = {};
   645 Svc.Prefs = new Preferences(PREFS_BRANCH);
   646 Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true});
   647 Svc.Obs = Observers;
   649 let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
   650   "@mozilla.org/suite/sessionstore;1" :
   651   "@mozilla.org/browser/sessionstore;1";
   653 [
   654  ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
   655  ["Session", _sessionCID, "nsISessionStore"]
   656 ].forEach(function([name, contract, iface]) {
   657   XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
   658 });
   660 XPCOMUtils.defineLazyModuleGetter(Svc, "FormHistory", "resource://gre/modules/FormHistory.jsm");
   662 Svc.__defineGetter__("Crypto", function() {
   663   let cryptoSvc;
   664   let ns = {};
   665   Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
   666   cryptoSvc = new ns.WeaveCrypto();
   667   delete Svc.Crypto;
   668   return Svc.Crypto = cryptoSvc;
   669 });
   671 this.Str = {};
   672 ["errors", "sync"].forEach(function(lazy) {
   673   XPCOMUtils.defineLazyGetter(Str, lazy, Utils.lazyStrings(lazy));
   674 });
   676 Svc.Obs.add("xpcom-shutdown", function () {
   677   for (let name in Svc)
   678     delete Svc[name];
   679 });

mercurial