michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"]; michael@0: michael@0: const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://services-common/observers.js"); michael@0: Cu.import("resource://services-common/stringbundle.js"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://services-common/async.js", this); michael@0: Cu.import("resource://services-crypto/utils.js"); michael@0: Cu.import("resource://services-sync/constants.js"); michael@0: Cu.import("resource://gre/modules/Preferences.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm", this); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); michael@0: Cu.import("resource://gre/modules/osfile.jsm", this); michael@0: Cu.import("resource://gre/modules/Task.jsm", this); michael@0: michael@0: /* michael@0: * Utility functions michael@0: */ michael@0: michael@0: this.Utils = { michael@0: // Alias in functions from CommonUtils. These previously were defined here. michael@0: // In the ideal world, references to these would be removed. michael@0: nextTick: CommonUtils.nextTick, michael@0: namedTimer: CommonUtils.namedTimer, michael@0: exceptionStr: CommonUtils.exceptionStr, michael@0: stackTrace: CommonUtils.stackTrace, michael@0: makeURI: CommonUtils.makeURI, michael@0: encodeUTF8: CommonUtils.encodeUTF8, michael@0: decodeUTF8: CommonUtils.decodeUTF8, michael@0: safeAtoB: CommonUtils.safeAtoB, michael@0: byteArrayToString: CommonUtils.byteArrayToString, michael@0: bytesAsHex: CommonUtils.bytesAsHex, michael@0: hexToBytes: CommonUtils.hexToBytes, michael@0: encodeBase32: CommonUtils.encodeBase32, michael@0: decodeBase32: CommonUtils.decodeBase32, michael@0: michael@0: // Aliases from CryptoUtils. michael@0: generateRandomBytes: CryptoUtils.generateRandomBytes, michael@0: computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1, michael@0: digestUTF8: CryptoUtils.digestUTF8, michael@0: digestBytes: CryptoUtils.digestBytes, michael@0: sha1: CryptoUtils.sha1, michael@0: sha1Base32: CryptoUtils.sha1Base32, michael@0: makeHMACKey: CryptoUtils.makeHMACKey, michael@0: makeHMACHasher: CryptoUtils.makeHMACHasher, michael@0: hkdfExpand: CryptoUtils.hkdfExpand, michael@0: pbkdf2Generate: CryptoUtils.pbkdf2Generate, michael@0: deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase, michael@0: getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header, michael@0: michael@0: /** michael@0: * Wrap a function to catch all exceptions and log them michael@0: * michael@0: * @usage MyObj._catch = Utils.catch; michael@0: * MyObj.foo = function() { this._catch(func)(); } michael@0: * michael@0: * Optionally pass a function which will be called if an michael@0: * exception occurs. michael@0: */ michael@0: catch: function Utils_catch(func, exceptionCallback) { michael@0: let thisArg = this; michael@0: return function WrappedCatch() { michael@0: try { michael@0: return func.call(thisArg); michael@0: } michael@0: catch(ex) { michael@0: thisArg._log.debug("Exception: " + Utils.exceptionStr(ex)); michael@0: if (exceptionCallback) { michael@0: return exceptionCallback.call(thisArg, ex); michael@0: } michael@0: return null; michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: /** michael@0: * Wrap a function to call lock before calling the function then unlock. michael@0: * michael@0: * @usage MyObj._lock = Utils.lock; michael@0: * MyObj.foo = function() { this._lock(func)(); } michael@0: */ michael@0: lock: function lock(label, func) { michael@0: let thisArg = this; michael@0: return function WrappedLock() { michael@0: if (!thisArg.lock()) { michael@0: throw "Could not acquire lock. Label: \"" + label + "\"."; michael@0: } michael@0: michael@0: try { michael@0: return func.call(thisArg); michael@0: } michael@0: finally { michael@0: thisArg.unlock(); michael@0: } michael@0: }; michael@0: }, michael@0: michael@0: isLockException: function isLockException(ex) { michael@0: return ex && ex.indexOf && ex.indexOf("Could not acquire lock.") == 0; michael@0: }, michael@0: michael@0: /** michael@0: * Wrap functions to notify when it starts and finishes executing or if it michael@0: * threw an error. michael@0: * michael@0: * The message is a combination of a provided prefix, the local name, and michael@0: * the event. Possible events are: "start", "finish", "error". The subject michael@0: * is the function's return value on "finish" or the caught exception on michael@0: * "error". The data argument is the predefined data value. michael@0: * michael@0: * Example: michael@0: * michael@0: * @usage function MyObj(name) { michael@0: * this.name = name; michael@0: * this._notify = Utils.notify("obj:"); michael@0: * } michael@0: * MyObj.prototype = { michael@0: * foo: function() this._notify("func", "data-arg", function () { michael@0: * //... michael@0: * }(), michael@0: * }; michael@0: */ michael@0: notify: function Utils_notify(prefix) { michael@0: return function NotifyMaker(name, data, func) { michael@0: let thisArg = this; michael@0: let notify = function(state, subject) { michael@0: let mesg = prefix + name + ":" + state; michael@0: thisArg._log.trace("Event: " + mesg); michael@0: Observers.notify(mesg, subject, data); michael@0: }; michael@0: michael@0: return function WrappedNotify() { michael@0: try { michael@0: notify("start", null); michael@0: let ret = func.call(thisArg); michael@0: notify("finish", ret); michael@0: return ret; michael@0: } michael@0: catch(ex) { michael@0: notify("error", ex); michael@0: throw ex; michael@0: } michael@0: }; michael@0: }; michael@0: }, michael@0: michael@0: runInTransaction: function(db, callback, thisObj) { michael@0: let hasTransaction = false; michael@0: try { michael@0: db.beginTransaction(); michael@0: hasTransaction = true; michael@0: } catch(e) { /* om nom nom exceptions */ } michael@0: michael@0: try { michael@0: return callback.call(thisObj); michael@0: } finally { michael@0: if (hasTransaction) { michael@0: db.commitTransaction(); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * GUIDs are 9 random bytes encoded with base64url (RFC 4648). michael@0: * That makes them 12 characters long with 72 bits of entropy. michael@0: */ michael@0: makeGUID: function makeGUID() { michael@0: return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9)); michael@0: }, michael@0: michael@0: _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i, michael@0: checkGUID: function checkGUID(guid) { michael@0: return !!guid && this._base64url_regex.test(guid); michael@0: }, michael@0: michael@0: /** michael@0: * Add a simple getter/setter to an object that defers access of a property michael@0: * to an inner property. michael@0: * michael@0: * @param obj michael@0: * Object to add properties to defer in its prototype michael@0: * @param defer michael@0: * Property of obj to defer to michael@0: * @param prop michael@0: * Property name to defer (or an array of property names) michael@0: */ michael@0: deferGetSet: function Utils_deferGetSet(obj, defer, prop) { michael@0: if (Array.isArray(prop)) michael@0: return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop)); michael@0: michael@0: let prot = obj.prototype; michael@0: michael@0: // Create a getter if it doesn't exist yet michael@0: if (!prot.__lookupGetter__(prop)) { michael@0: prot.__defineGetter__(prop, function () { michael@0: return this[defer][prop]; michael@0: }); michael@0: } michael@0: michael@0: // Create a setter if it doesn't exist yet michael@0: if (!prot.__lookupSetter__(prop)) { michael@0: prot.__defineSetter__(prop, function (val) { michael@0: this[defer][prop] = val; michael@0: }); michael@0: } michael@0: }, michael@0: michael@0: lazyStrings: function Weave_lazyStrings(name) { michael@0: let bundle = "chrome://weave/locale/services/" + name + ".properties"; michael@0: return function() new StringBundle(bundle); michael@0: }, michael@0: michael@0: deepEquals: function eq(a, b) { michael@0: // If they're triple equals, then it must be equals! michael@0: if (a === b) michael@0: return true; michael@0: michael@0: // If they weren't equal, they must be objects to be different michael@0: if (typeof a != "object" || typeof b != "object") michael@0: return false; michael@0: michael@0: // But null objects won't have properties to compare michael@0: if (a === null || b === null) michael@0: return false; michael@0: michael@0: // Make sure all of a's keys have a matching value in b michael@0: for (let k in a) michael@0: if (!eq(a[k], b[k])) michael@0: return false; michael@0: michael@0: // Do the same for b's keys but skip those that we already checked michael@0: for (let k in b) michael@0: if (!(k in a) && !eq(a[k], b[k])) michael@0: return false; michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: // Generator and discriminator for HMAC exceptions. michael@0: // Split these out in case we want to make them richer in future, and to michael@0: // avoid inevitable confusion if the message changes. michael@0: throwHMACMismatch: function throwHMACMismatch(shouldBe, is) { michael@0: throw "Record SHA256 HMAC mismatch: should be " + shouldBe + ", is " + is; michael@0: }, michael@0: michael@0: isHMACMismatch: function isHMACMismatch(ex) { michael@0: const hmacFail = "Record SHA256 HMAC mismatch: "; michael@0: return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0); michael@0: }, michael@0: michael@0: /** michael@0: * Turn RFC 4648 base32 into our own user-friendly version. michael@0: * ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 michael@0: * becomes michael@0: * abcdefghijk8mn9pqrstuvwxyz234567 michael@0: */ michael@0: base32ToFriendly: function base32ToFriendly(input) { michael@0: return input.toLowerCase() michael@0: .replace("l", '8', "g") michael@0: .replace("o", '9', "g"); michael@0: }, michael@0: michael@0: base32FromFriendly: function base32FromFriendly(input) { michael@0: return input.toUpperCase() michael@0: .replace("8", 'L', "g") michael@0: .replace("9", 'O', "g"); michael@0: }, michael@0: michael@0: /** michael@0: * Key manipulation. michael@0: */ michael@0: michael@0: // Return an octet string in friendly base32 *with no trailing =*. michael@0: encodeKeyBase32: function encodeKeyBase32(keyData) { michael@0: return Utils.base32ToFriendly( michael@0: Utils.encodeBase32(keyData)) michael@0: .slice(0, SYNC_KEY_ENCODED_LENGTH); michael@0: }, michael@0: michael@0: decodeKeyBase32: function decodeKeyBase32(encoded) { michael@0: return Utils.decodeBase32( michael@0: Utils.base32FromFriendly( michael@0: Utils.normalizePassphrase(encoded))) michael@0: .slice(0, SYNC_KEY_DECODED_LENGTH); michael@0: }, michael@0: michael@0: base64Key: function base64Key(keyData) { michael@0: return btoa(keyData); michael@0: }, michael@0: michael@0: /** michael@0: * N.B., salt should be base64 encoded, even though we have to decode michael@0: * it later! michael@0: */ michael@0: derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) { michael@0: let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength, michael@0: forceJS); michael@0: return Utils.encodeKeyBase32(k); michael@0: }, michael@0: michael@0: /** michael@0: * N.B., salt should be base64 encoded, even though we have to decode michael@0: * it later! michael@0: */ michael@0: deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) { michael@0: let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength, michael@0: forceJS); michael@0: return Utils.base64Key(k); michael@0: }, michael@0: michael@0: /** michael@0: * Take a base64-encoded 128-bit AES key, returning it as five groups of five michael@0: * uppercase alphanumeric characters, separated by hyphens. michael@0: * A.K.A. base64-to-base32 encoding. michael@0: */ michael@0: presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) { michael@0: return Utils.encodeKeyBase32(atob(encodedKey)); michael@0: }, michael@0: michael@0: /** michael@0: * Load a JSON file from disk in the profile directory. michael@0: * michael@0: * @param filePath michael@0: * JSON file path load from profile. Loaded file will be michael@0: * /.json. i.e. Do not specify the ".json" michael@0: * extension. michael@0: * @param that michael@0: * Object to use for logging and "this" for callback. michael@0: * @param callback michael@0: * Function to process json object as its first argument. If the file michael@0: * could not be loaded, the first argument will be undefined. michael@0: */ michael@0: jsonLoad: Task.async(function*(filePath, that, callback) { michael@0: let path = OS.Path.join(OS.Constants.Path.profileDir, "weave", filePath + ".json"); michael@0: michael@0: if (that._log) { michael@0: that._log.trace("Loading json from disk: " + filePath); michael@0: } michael@0: michael@0: let json; michael@0: michael@0: try { michael@0: json = yield CommonUtils.readJSON(path); michael@0: } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) { michael@0: // Ignore non-existent files. michael@0: } catch (e) { michael@0: if (that._log) { michael@0: that._log.debug("Failed to load json: " + michael@0: CommonUtils.exceptionStr(e)); michael@0: } michael@0: } michael@0: michael@0: callback.call(that, json); michael@0: }), michael@0: michael@0: /** michael@0: * Save a json-able object to disk in the profile directory. michael@0: * michael@0: * @param filePath michael@0: * JSON file path save to .json michael@0: * @param that michael@0: * Object to use for logging and "this" for callback michael@0: * @param obj michael@0: * Function to provide json-able object to save. If this isn't a michael@0: * function, it'll be used as the object to make a json string. michael@0: * @param callback michael@0: * Function called when the write has been performed. Optional. michael@0: * The first argument will be a Components.results error michael@0: * constant on error or null if no error was encountered (and michael@0: * the file saved successfully). michael@0: */ michael@0: jsonSave: Task.async(function*(filePath, that, obj, callback) { michael@0: let path = OS.Path.join(OS.Constants.Path.profileDir, "weave", michael@0: ...(filePath + ".json").split("/")); michael@0: let dir = OS.Path.dirname(path); michael@0: let error = null; michael@0: michael@0: try { michael@0: yield OS.File.makeDir(dir, { from: OS.Constants.Path.profileDir }); michael@0: michael@0: if (that._log) { michael@0: that._log.trace("Saving json to disk: " + path); michael@0: } michael@0: michael@0: let json = typeof obj == "function" ? obj.call(that) : obj; michael@0: michael@0: yield CommonUtils.writeJSON(json, path); michael@0: } catch (e) { michael@0: error = e michael@0: } michael@0: michael@0: if (typeof callback == "function") { michael@0: callback.call(that, error); michael@0: } michael@0: }), michael@0: michael@0: getErrorString: function Utils_getErrorString(error, args) { michael@0: try { michael@0: return Str.errors.get(error, args || null); michael@0: } catch (e) {} michael@0: michael@0: // basically returns "Unknown Error" michael@0: return Str.errors.get("error.reason.unknown"); michael@0: }, michael@0: michael@0: /** michael@0: * Generate 26 characters. michael@0: */ michael@0: generatePassphrase: function generatePassphrase() { michael@0: // Note that this is a different base32 alphabet to the one we use for michael@0: // other tasks. It's lowercase, uses different letters, and needs to be michael@0: // decoded with decodeKeyBase32, not just decodeBase32. michael@0: return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16)); michael@0: }, michael@0: michael@0: /** michael@0: * The following are the methods supported for UI use: michael@0: * michael@0: * * isPassphrase: michael@0: * determines whether a string is either a normalized or presentable michael@0: * passphrase. michael@0: * * hyphenatePassphrase: michael@0: * present a normalized passphrase for display. This might actually michael@0: * perform work beyond just hyphenation; sorry. michael@0: * * hyphenatePartialPassphrase: michael@0: * present a fragment of a normalized passphrase for display. michael@0: * * normalizePassphrase: michael@0: * take a presentable passphrase and reduce it to a normalized michael@0: * representation for storage. normalizePassphrase can safely be called michael@0: * on normalized input. michael@0: * * normalizeAccount: michael@0: * take user input for account/username, cleaning up appropriately. michael@0: */ michael@0: michael@0: isPassphrase: function(s) { michael@0: if (s) { michael@0: return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s)); michael@0: } michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Hyphenate a passphrase (26 characters) into groups. michael@0: * abbbbccccddddeeeeffffggggh michael@0: * => michael@0: * a-bbbbc-cccdd-ddeee-effff-ggggh michael@0: */ michael@0: hyphenatePassphrase: function hyphenatePassphrase(passphrase) { michael@0: // For now, these are the same. michael@0: return Utils.hyphenatePartialPassphrase(passphrase, true); michael@0: }, michael@0: michael@0: hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) { michael@0: if (!passphrase) michael@0: return null; michael@0: michael@0: // Get the raw data input. Just base32. michael@0: let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, ""); michael@0: michael@0: // This is the neatest way to do this. michael@0: if ((data.length == 1) && !omitTrailingDash) michael@0: return data + "-"; michael@0: michael@0: // Hyphenate it. michael@0: let y = data.substr(0,1); michael@0: let z = data.substr(1).replace(/(.{1,5})/g, "-$1"); michael@0: michael@0: // Correct length? We're done. michael@0: if ((z.length == 30) || omitTrailingDash) michael@0: return y + z; michael@0: michael@0: // Add a trailing dash if appropriate. michael@0: return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH); michael@0: }, michael@0: michael@0: normalizePassphrase: function normalizePassphrase(pp) { michael@0: // Short var name... have you seen the lines below?! michael@0: // Allow leading and trailing whitespace. michael@0: pp = pp.trim().toLowerCase(); michael@0: michael@0: // 20-char sync key. michael@0: if (pp.length == 23 && michael@0: [5, 11, 17].every(function(i) pp[i] == '-')) { michael@0: michael@0: return pp.slice(0, 5) + pp.slice(6, 11) michael@0: + pp.slice(12, 17) + pp.slice(18, 23); michael@0: } michael@0: michael@0: // "Modern" 26-char key. michael@0: if (pp.length == 31 && michael@0: [1, 7, 13, 19, 25].every(function(i) pp[i] == '-')) { michael@0: michael@0: return pp.slice(0, 1) + pp.slice(2, 7) michael@0: + pp.slice(8, 13) + pp.slice(14, 19) michael@0: + pp.slice(20, 25) + pp.slice(26, 31); michael@0: } michael@0: michael@0: // Something else -- just return. michael@0: return pp; michael@0: }, michael@0: michael@0: normalizeAccount: function normalizeAccount(acc) { michael@0: return acc.trim(); michael@0: }, michael@0: michael@0: /** michael@0: * Create an array like the first but without elements of the second. Reuse michael@0: * arrays if possible. michael@0: */ michael@0: arraySub: function arraySub(minuend, subtrahend) { michael@0: if (!minuend.length || !subtrahend.length) michael@0: return minuend; michael@0: return minuend.filter(function(i) subtrahend.indexOf(i) == -1); michael@0: }, michael@0: michael@0: /** michael@0: * Build the union of two arrays. Reuse arrays if possible. michael@0: */ michael@0: arrayUnion: function arrayUnion(foo, bar) { michael@0: if (!foo.length) michael@0: return bar; michael@0: if (!bar.length) michael@0: return foo; michael@0: return foo.concat(Utils.arraySub(bar, foo)); michael@0: }, michael@0: michael@0: bind2: function Async_bind2(object, method) { michael@0: return function innerBind() { return method.apply(object, arguments); }; michael@0: }, michael@0: michael@0: /** michael@0: * Is there a master password configured, regardless of current lock state? michael@0: */ michael@0: mpEnabled: function mpEnabled() { michael@0: let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"] michael@0: .getService(Ci.nsIPKCS11ModuleDB); michael@0: let sdrSlot = modules.findSlotByName(""); michael@0: let status = sdrSlot.status; michael@0: let slots = Ci.nsIPKCS11Slot; michael@0: michael@0: return status != slots.SLOT_UNINITIALIZED && status != slots.SLOT_READY; michael@0: }, michael@0: michael@0: /** michael@0: * Is there a master password configured and currently locked? michael@0: */ michael@0: mpLocked: function mpLocked() { michael@0: let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"] michael@0: .getService(Ci.nsIPKCS11ModuleDB); michael@0: let sdrSlot = modules.findSlotByName(""); michael@0: let status = sdrSlot.status; michael@0: let slots = Ci.nsIPKCS11Slot; michael@0: michael@0: if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN michael@0: || status == slots.SLOT_UNINITIALIZED) michael@0: return false; michael@0: michael@0: if (status == slots.SLOT_NOT_LOGGED_IN) michael@0: return true; michael@0: michael@0: // something wacky happened, pretend MP is locked michael@0: return true; michael@0: }, michael@0: michael@0: // If Master Password is enabled and locked, present a dialog to unlock it. michael@0: // Return whether the system is unlocked. michael@0: ensureMPUnlocked: function ensureMPUnlocked() { michael@0: if (!Utils.mpLocked()) { michael@0: return true; michael@0: } michael@0: let sdr = Cc["@mozilla.org/security/sdr;1"] michael@0: .getService(Ci.nsISecretDecoderRing); michael@0: try { michael@0: sdr.encryptString("bacon"); michael@0: return true; michael@0: } catch(e) {} michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Return a value for a backoff interval. Maximum is eight hours, unless michael@0: * Status.backoffInterval is higher. michael@0: * michael@0: */ michael@0: calculateBackoff: function calculateBackoff(attempts, baseInterval, michael@0: statusInterval) { michael@0: let backoffInterval = attempts * michael@0: (Math.floor(Math.random() * baseInterval) + michael@0: baseInterval); michael@0: return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL), michael@0: statusInterval); michael@0: }, michael@0: michael@0: /** michael@0: * Return a set of hostnames (including the protocol) which may have michael@0: * credentials for sync itself stored in the login manager. michael@0: * michael@0: * In general, these hosts will not have their passwords synced, will be michael@0: * reset when we drop sync credentials, etc. michael@0: */ michael@0: getSyncCredentialsHosts: function() { michael@0: // This is somewhat expensive and the result static, so we cache the result. michael@0: if (this._syncCredentialsHosts) { michael@0: return this._syncCredentialsHosts; michael@0: } michael@0: let result = new Set(); michael@0: // the legacy sync host. michael@0: result.add(PWDMGR_HOST); michael@0: // The FxA hosts - these almost certainly all have the same hostname, but michael@0: // better safe than sorry... michael@0: for (let prefName of ["identity.fxaccounts.remote.force_auth.uri", michael@0: "identity.fxaccounts.remote.signup.uri", michael@0: "identity.fxaccounts.remote.signin.uri", michael@0: "identity.fxaccounts.settings.uri"]) { michael@0: let prefVal; michael@0: try { michael@0: prefVal = Services.prefs.getCharPref(prefName); michael@0: } catch (_) { michael@0: continue; michael@0: } michael@0: let uri = Services.io.newURI(prefVal, null, null); michael@0: result.add(uri.prePath); michael@0: } michael@0: return this._syncCredentialsHosts = result; michael@0: }, michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() { michael@0: let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: return converter; michael@0: }); michael@0: michael@0: /* michael@0: * Commonly-used services michael@0: */ michael@0: this.Svc = {}; michael@0: Svc.Prefs = new Preferences(PREFS_BRANCH); michael@0: Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true}); michael@0: Svc.Obs = Observers; michael@0: michael@0: let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ? michael@0: "@mozilla.org/suite/sessionstore;1" : michael@0: "@mozilla.org/browser/sessionstore;1"; michael@0: michael@0: [ michael@0: ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"], michael@0: ["Session", _sessionCID, "nsISessionStore"] michael@0: ].forEach(function([name, contract, iface]) { michael@0: XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface); michael@0: }); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(Svc, "FormHistory", "resource://gre/modules/FormHistory.jsm"); michael@0: michael@0: Svc.__defineGetter__("Crypto", function() { michael@0: let cryptoSvc; michael@0: let ns = {}; michael@0: Cu.import("resource://services-crypto/WeaveCrypto.js", ns); michael@0: cryptoSvc = new ns.WeaveCrypto(); michael@0: delete Svc.Crypto; michael@0: return Svc.Crypto = cryptoSvc; michael@0: }); michael@0: michael@0: this.Str = {}; michael@0: ["errors", "sync"].forEach(function(lazy) { michael@0: XPCOMUtils.defineLazyGetter(Str, lazy, Utils.lazyStrings(lazy)); michael@0: }); michael@0: michael@0: Svc.Obs.add("xpcom-shutdown", function () { michael@0: for (let name in Svc) michael@0: delete Svc[name]; michael@0: });