/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Bookmarks Sync. * * The Initial Developer of the Original Code is Mozilla. * Portions created by the Initial Developer are Copyright (C) 2007 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Dan Mills * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const EXPORTED_SYMBOLS = ['Utils', 'Svc', 'Str']; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/ext/Preferences.js"); Cu.import("resource://services-sync/ext/StringBundle.js"); Cu.import("resource://services-sync/ext/Sync.js"); Cu.import("resource://services-sync/log4moz.js"); /* * Utility functions */ let Utils = { /** * Execute an arbitrary number of asynchronous functions one after the * other, passing the callback arguments on to the next one. All functions * must take a callback function as their last argument. The 'this' object * will be whatever asyncChain's is. * * @usage this._chain = Utils.asyncChain; * this._chain(this.foo, this.bar, this.baz)(args, for, foo) * * This is equivalent to: * * let self = this; * self.foo(args, for, foo, function (bars, args) { * self.bar(bars, args, function (baz, params) { * self.baz(baz, params); * }); * }); */ asyncChain: function asyncChain() { let funcs = Array.slice(arguments); let thisObj = this; return function callback() { if (funcs.length) { let args = Array.slice(arguments).concat(callback); let f = funcs.shift(); f.apply(thisObj, args); } }; }, /** * Wrap a function to catch all exceptions and log them * * @usage MyObj._catch = Utils.catch; * MyObj.foo = function() { this._catch(func)(); } */ catch: function Utils_catch(func) { let thisArg = this; return function WrappedCatch() { try { return func.call(thisArg); } catch(ex) { thisArg._log.debug("Exception: " + Utils.exceptionStr(ex)); } }; }, /** * Wrap a function to call lock before calling the function then unlock. * * @usage MyObj._lock = Utils.lock; * MyObj.foo = function() { this._lock(func)(); } */ lock: function Utils_lock(func) { let thisArg = this; return function WrappedLock() { if (!thisArg.lock()) throw "Could not acquire lock"; try { return func.call(thisArg); } finally { thisArg.unlock(); } }; }, /** * Wrap functions to notify when it starts and finishes executing or if it got * an error. The message is a combination of a provided prefix and local name * with the current state and the subject is the provided subject. * * @usage function MyObj() { this._notify = Utils.notify("prefix:"); } * MyObj.foo = function() { this._notify(name, subject, func)(); } */ notify: function Utils_notify(prefix) { return function NotifyMaker(name, subject, func) { let thisArg = this; let notify = function(state) { let mesg = prefix + name + ":" + state; thisArg._log.trace("Event: " + mesg); Observers.notify(mesg, subject); }; return function WrappedNotify() { try { notify("start"); let ret = func.call(thisArg); notify("finish"); return ret; } catch(ex) { notify("error"); throw ex; } }; }; }, batchSync: function batchSync(service, engineType) { return function batchedSync() { let engine = this; let batchEx = null; // Try running sync in batch mode Svc[service].runInBatchMode({ runBatched: function wrappedSync() { try { engineType.prototype._sync.call(engine); } catch(ex) { batchEx = ex; } } }, null); // Expose the exception if something inside the batch failed if (batchEx!= null) throw batchEx; }; }, createStatement: function createStatement(db, query) { // Gecko 2.0 if (db.createAsyncStatement) return db.createAsyncStatement(query); // Gecko <2.0 return db.createStatement(query); }, queryAsync: function(query, names) { // Allow array of names, single name, and no name if (!Utils.isArray(names)) names = names == null ? [] : [names]; // Synchronously asyncExecute fetching all results by name let [exec, execCb] = Sync.withCb(query.executeAsync, query); return exec({ items: [], handleResult: function handleResult(results) { let row; while ((row = results.getNextRow()) != null) { this.items.push(names.reduce(function(item, name) { item[name] = row.getResultByName(name); return item; }, {})); } }, handleError: function handleError(error) { execCb.throw(error); }, handleCompletion: function handleCompletion(reason) { execCb(this.items); } }); }, // Generates a brand-new globally unique identifier (GUID). makeGUID: function makeGUID() { // 70 characters that are not-escaped URL-friendly const code = "!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; let guid = ""; let num = 0; let val; // Generate ten 70-value characters for a 70^10 (~61.29-bit) GUID for (let i = 0; i < 10; i++) { // Refresh the number source after using it a few times if (i == 0 || i == 5) num = Math.random(); // Figure out which code to use for the next GUID character num *= 70; val = Math.floor(num); guid += code[val]; num -= val; } return guid; }, anno: function anno(id, anno, val, expire) { // Figure out if we have a bookmark or page let annoFunc = (typeof id == "number" ? "Item" : "Page") + "Annotation"; // Convert to a nsIURI if necessary if (typeof id == "string") id = Utils.makeURI(id); if (id == null) throw "Null id for anno! (invalid uri)"; switch (arguments.length) { case 2: // Get the annotation with 2 args return Svc.Annos["get" + annoFunc](id, anno); case 3: expire = "NEVER"; // Fallthrough! case 4: // Convert to actual EXPIRE value expire = Svc.Annos["EXPIRE_" + expire]; // Set the annotation with 3 or 4 args return Svc.Annos["set" + annoFunc](id, anno, val, 0, expire); } }, ensureOneOpen: let (windows = {}) function ensureOneOpen(window) { // Close the other window if it exists let url = window.location.href; let other = windows[url]; if (other != null) other.close(); // Save the new window for future closure windows[url] = window; // Actively clean up when the window is closed window.addEventListener("unload", function() windows[url] = null, false); }, // Returns a nsILocalFile representing a file relative to the // current user's profile directory. If the argument is a string, // it should be a string with unix-style slashes for directory names // (these slashes are automatically converted to platform-specific // path separators). // // Alternatively, if the argument is an object, it should contain // the following attributes: // // path: the path to the file, relative to the current user's // profile dir. // // autoCreate: whether or not the file should be created if it // doesn't already exist. getProfileFile: function getProfileFile(arg) { if (typeof arg == "string") arg = {path: arg}; let pathParts = arg.path.split("/"); let file = Svc.Directory.get("ProfD", Ci.nsIFile); file.QueryInterface(Ci.nsILocalFile); for (let i = 0; i < pathParts.length; i++) file.append(pathParts[i]); if (arg.autoCreate && !file.exists()) file.create(file.NORMAL_FILE_TYPE, PERMS_FILE); return file; }, /** * Add a simple getter/setter to an object that defers access of a property * to an inner property. * * @param obj * Object to add properties to defer in its prototype * @param defer * Hash property of obj to defer to (dot split each level) * @param prop * Property name to defer (or an array of property names) */ deferGetSet: function Utils_deferGetSet(obj, defer, prop) { if (Utils.isArray(prop)) return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop)); // Split the defer into each dot part for each level to dereference let parts = defer.split("."); let deref = function(base) Utils.deref(base, parts); let prot = obj.prototype; // Create a getter if it doesn't exist yet if (!prot.__lookupGetter__(prop)) prot.__defineGetter__(prop, function() deref(this)[prop]); // Create a setter if it doesn't exist yet if (!prot.__lookupSetter__(prop)) prot.__defineSetter__(prop, function(val) deref(this)[prop] = val); }, /** * Dereference an array of properties starting from a base object * * @param base * Base object to start dereferencing * @param props * Array of properties to dereference (one for each level) */ deref: function Utils_deref(base, props) props.reduce(function(curr, prop) curr[prop], base), /** * Determine if some value is an array * * @param val * Value to check (can be null, undefined, etc.) * @return True if it's an array; false otherwise */ isArray: function Utils_isArray(val) val != null && typeof val == "object" && val.constructor.name == "Array", // lazy load objects from a constructor on first access. It will // work with the global object ('this' in the global context). lazy: function Weave_lazy(dest, prop, ctr) { delete dest[prop]; dest.__defineGetter__(prop, Utils.lazyCb(dest, prop, ctr)); }, lazyCb: function Weave_lazyCb(dest, prop, ctr) { return function() { delete dest[prop]; dest[prop] = new ctr(); return dest[prop]; }; }, // like lazy, but rather than new'ing the 3rd arg we use its return value lazy2: function Weave_lazy2(dest, prop, fn) { delete dest[prop]; dest.__defineGetter__(prop, Utils.lazyCb2(dest, prop, fn)); }, lazyCb2: function Weave_lazyCb2(dest, prop, fn) { return function() { delete dest[prop]; return dest[prop] = fn(); }; }, lazySvc: function Weave_lazySvc(dest, prop, cid, iface) { let getter = function() { delete dest[prop]; let svc = null; // Use the platform's service if it exists if (cid in Cc && iface in Ci) svc = Cc[cid].getService(Ci[iface]); else { svc = FakeSvc[cid]; let log = Log4Moz.repository.getLogger("Service.Util"); if (svc == null) log.warn("Component " + cid + " doesn't exist on this platform."); else log.debug("Using a fake svc object for " + cid); } return dest[prop] = svc; }; dest.__defineGetter__(prop, getter); }, lazyStrings: function Weave_lazyStrings(name) { let bundle = "chrome://weave/locale/services/" + name + ".properties"; return function() new StringBundle(bundle); }, deepEquals: function eq(a, b) { // If they're triple equals, then it must be equals! if (a === b) return true; // If they weren't equal, they must be objects to be different if (typeof a != "object" || typeof b != "object") return false; // But null objects won't have properties to compare if (a === null || b === null) return false; // Make sure all of a's keys have a matching value in b for (let k in a) if (!eq(a[k], b[k])) return false; // Do the same for b's keys but skip those that we already checked for (let k in b) if (!(k in a) && !eq(a[k], b[k])) return false; return true; }, deepCopy: function Weave_deepCopy(thing, noSort) { if (typeof(thing) != "object" || thing == null) return thing; let ret; if (Utils.isArray(thing)) { ret = []; for (let i = 0; i < thing.length; i++) ret.push(Utils.deepCopy(thing[i], noSort)); } else { ret = {}; let props = [p for (p in thing)]; if (!noSort) props = props.sort(); props.forEach(function(k) ret[k] = Utils.deepCopy(thing[k], noSort)); } return ret; }, // Works on frames or exceptions, munges file:// URIs to shorten the paths // FIXME: filename munging is sort of hackish, might be confusing if // there are multiple extensions with similar filenames formatFrame: function Utils_formatFrame(frame) { let tmp = ""; let file = frame.filename || frame.fileName; if (file) tmp = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1"); if (frame.lineNumber) tmp += ":" + frame.lineNumber; if (frame.name) tmp = frame.name + "()@" + tmp; return tmp; }, exceptionStr: function Weave_exceptionStr(e) { let message = e.message ? e.message : e; return message + " " + Utils.stackTrace(e); }, stackTraceFromFrame: function Weave_stackTraceFromFrame(frame) { let output = []; while (frame) { let str = Utils.formatFrame(frame); if (str) output.push(str); frame = frame.caller; } return output.join(" < "); }, stackTrace: function Weave_stackTrace(e) { // Wrapped nsIException if (e.location) return "Stack trace: " + Utils.stackTraceFromFrame(e.location); // Standard JS exception if (e.stack) return "JS Stack trace: " + e.stack.trim().replace(/\n/g, " < "). replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1"); return "No traceback available"; }, checkStatus: function Weave_checkStatus(code, msg, ranges) { if (!ranges) ranges = [[200,300]]; for (let i = 0; i < ranges.length; i++) { var rng = ranges[i]; if (typeof(rng) == "object" && code >= rng[0] && code < rng[1]) return true; else if (typeof(rng) == "number" && code == rng) { return true; } } if (msg) { let log = Log4Moz.repository.getLogger("Service.Util"); log.error(msg + " Error code: " + code); } return false; }, ensureStatus: function Weave_ensureStatus(args) { if (!Utils.checkStatus.apply(Utils, arguments)) throw 'checkStatus failed'; }, digest: function digest(message, hasher) { let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; let data = converter.convertToByteArray(message, {}); hasher.update(data, data.length); return hasher.finish(false); }, bytesAsHex: function bytesAsHex(bytes) { // Convert each hashed byte into 2-hex strings then combine them return [("0" + byte.charCodeAt().toString(16)).slice(-2) for each (byte in bytes)].join(""); }, _sha1: function _sha1(message) { let hasher = Cc["@mozilla.org/security/hash;1"]. createInstance(Ci.nsICryptoHash); hasher.init(hasher.SHA1); return Utils.digest(message, hasher); }, sha1: function sha1(message) { return Utils.bytesAsHex(Utils._sha1(message)); }, sha1Base32: function sha1Base32(message) { return Utils.encodeBase32(Utils._sha1(message)); }, /** * Generate a sha256 HMAC for a string message and a given nsIKeyObject */ sha256HMAC: function sha256HMAC(message, key) { let hasher = Cc["@mozilla.org/security/hmac;1"]. createInstance(Ci.nsICryptoHMAC); hasher.init(hasher.SHA256, key); return Utils.bytesAsHex(Utils.digest(message, hasher)); }, /** * Base32 encode (RFC 4648) a string */ encodeBase32: function encodeBase32(bytes) { const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; let quanta = Math.floor(bytes.length / 5); let leftover = bytes.length % 5; // Pad the last quantum with zeros so the length is a multiple of 5. if (leftover) { quanta += 1; for (let i = leftover; i < 5; i++) bytes += "\0"; } // Chop the string into quanta of 5 bytes (40 bits). Each quantum // is turned into 8 characters from the 32 character base. let ret = ""; for (let i = 0; i < bytes.length; i += 5) { let c = [byte.charCodeAt() for each (byte in bytes.slice(i, i + 5))]; ret += key[c[0] >> 3] + key[((c[0] << 2) & 0x1f) | (c[1] >> 6)] + key[(c[1] >> 1) & 0x1f] + key[((c[1] << 4) & 0x1f) | (c[2] >> 4)] + key[((c[2] << 1) & 0x1f) | (c[3] >> 7)] + key[(c[3] >> 2) & 0x1f] + key[((c[3] << 3) & 0x1f) | (c[4] >> 5)] + key[c[4] & 0x1f]; } switch (leftover) { case 1: return ret.slice(0, -6) + "======"; case 2: return ret.slice(0, -4) + "===="; case 3: return ret.slice(0, -3) + "==="; case 4: return ret.slice(0, -1) + "="; default: return ret; } }, makeURI: function Weave_makeURI(URIString) { if (!URIString) return null; try { return Svc.IO.newURI(URIString, null, null); } catch (e) { let log = Log4Moz.repository.getLogger("Service.Util"); log.debug("Could not create URI: " + Utils.exceptionStr(e)); return null; } }, makeURL: function Weave_makeURL(URIString) { let url = Utils.makeURI(URIString); url.QueryInterface(Ci.nsIURL); return url; }, xpath: function Weave_xpath(xmlDoc, xpathString) { let root = xmlDoc.ownerDocument == null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement; let nsResolver = xmlDoc.createNSResolver(root); return xmlDoc.evaluate(xpathString, xmlDoc, nsResolver, Ci.nsIDOMXPathResult.ANY_TYPE, null); }, getTmp: function Weave_getTmp(name) { let tmp = Svc.Directory.get("ProfD", Ci.nsIFile); tmp.QueryInterface(Ci.nsILocalFile); tmp.append("weave"); tmp.append("tmp"); if (!tmp.exists()) tmp.create(tmp.DIRECTORY_TYPE, PERMS_DIRECTORY); if (name) tmp.append(name); return tmp; }, /** * Load a json object from disk * * @param filePath * Json file path load from weave/[filePath].json * @param that * Object to use for logging and "this" for callback * @param callback * Function to process json object as its first parameter */ jsonLoad: function Utils_jsonLoad(filePath, that, callback) { filePath = "weave/" + filePath + ".json"; if (that._log) that._log.trace("Loading json from disk: " + filePath); let file = Utils.getProfileFile(filePath); if (!file.exists()) return; try { let [is] = Utils.open(file, "<"); let json = Utils.readStream(is); is.close(); callback.call(that, JSON.parse(json)); } catch (ex) { if (that._log) that._log.debug("Failed to load json: " + Utils.exceptionStr(ex)); } }, /** * Save a json-able object to disk * * @param filePath * Json file path save to weave/[filePath].json * @param that * Object to use for logging and "this" for callback * @param callback * Function to provide json-able object to save. If this isn't a * function, it'll be used as the object to make a json string. */ jsonSave: function Utils_jsonSave(filePath, that, callback) { filePath = "weave/" + filePath + ".json"; if (that._log) that._log.trace("Saving json to disk: " + filePath); let file = Utils.getProfileFile({ autoCreate: true, path: filePath }); let json = typeof callback == "function" ? callback.call(that) : callback; let out = JSON.stringify(json); let [fos] = Utils.open(file, ">"); fos.writeString(out); fos.close(); }, /** * Return a timer that is scheduled to call the callback after waiting the * provided time or as soon as possible. The timer will be set as a property * of the provided object with the given timer name. */ delay: function delay(callback, wait, thisObj, name) { // Default to running right away wait = wait || 0; // Use a dummy object if one wasn't provided thisObj = thisObj || {}; // Delay an existing timer if it exists if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) { thisObj[name].delay = wait; return; } // Create a special timer that we can add extra properties let timer = {}; timer.__proto__ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); // Provide an easy way to clear out the timer timer.clear = function() { thisObj[name] = null; timer.cancel(); }; // Initialize the timer with a smart callback timer.initWithCallback({ notify: function notify() { // Clear out the timer once it's been triggered timer.clear(); callback.call(thisObj, timer); } }, wait, timer.TYPE_ONE_SHOT); return thisObj[name] = timer; }, open: function open(pathOrFile, mode, perms) { let stream, file; if (pathOrFile instanceof Ci.nsIFile) { file = pathOrFile; } else { file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); dump("PATH IS" + pathOrFile + "\n"); file.initWithPath(pathOrFile); } if (!perms) perms = PERMS_FILE; switch(mode) { case "<": { if (!file.exists()) throw "Cannot open file for reading, file does not exist"; let fis = Cc["@mozilla.org/network/file-input-stream;1"]. createInstance(Ci.nsIFileInputStream); fis.init(file, MODE_RDONLY, perms, 0); stream = Cc["@mozilla.org/intl/converter-input-stream;1"]. createInstance(Ci.nsIConverterInputStream); stream.init(fis, "UTF-8", 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); } break; case ">": { let fos = Cc["@mozilla.org/network/file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, perms, 0); stream = Cc["@mozilla.org/intl/converter-output-stream;1"] .createInstance(Ci.nsIConverterOutputStream); stream.init(fos, "UTF-8", 4096, 0x0000); } break; case ">>": { let fos = Cc["@mozilla.org/network/file-output-stream;1"]. createInstance(Ci.nsIFileOutputStream); fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_APPEND, perms, 0); stream = Cc["@mozilla.org/intl/converter-output-stream;1"] .createInstance(Ci.nsIConverterOutputStream); stream.init(fos, "UTF-8", 4096, 0x0000); } break; default: throw "Illegal mode to open(): " + mode; } return [stream, file]; }, getIcon: function(iconUri, defaultIcon) { try { let iconURI = Utils.makeURI(iconUri); return Svc.Favicon.getFaviconLinkForIcon(iconURI).spec; } catch(ex) {} // Just give the provided default icon or the system's default return defaultIcon || Svc.Favicon.defaultFavicon.spec; }, getErrorString: function Utils_getErrorString(error, args) { try { return Str.errors.get(error, args || null); } catch (e) {} // basically returns "Unknown Error" return Str.errors.get("error.reason.unknown"); }, // assumes an nsIConverterInputStream readStream: function Weave_readStream(is) { let ret = "", str = {}; while (is.readString(4096, str) != 0) { ret += str.value; } return ret; }, encodeUTF8: function(str) { try { var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Ci.nsIScriptableUnicodeConverter); unicodeConverter.charset = "UTF-8"; str = unicodeConverter.ConvertFromUnicode(str); return str + unicodeConverter.Finish(); } catch(ex) { return null; } }, decodeUTF8: function(str) { try { var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Ci.nsIScriptableUnicodeConverter); unicodeConverter.charset = "UTF-8"; str = unicodeConverter.ConvertToUnicode(str); return str + unicodeConverter.Finish(); } catch(ex) { return null; } }, /** * Generate 20 random characters a-z */ generatePassphrase: function() { let rng = Cc["@mozilla.org/security/random-generator;1"] .createInstance(Ci.nsIRandomGenerator); let bytes = rng.generateRandomBytes(20); return [String.fromCharCode(97 + Math.floor(byte * 26 / 256)) for each (byte in bytes)].join(""); }, /** * Hyphenate a 20 character passphrase in 4 groups of 5. */ hyphenatePassphrase: function(passphrase) { return passphrase.slice(0, 5) + '-' + passphrase.slice(5, 10) + '-' + passphrase.slice(10, 15) + '-' + passphrase.slice(15, 20); }, /** * Remove hyphens as inserted by hyphenatePassphrase(). */ normalizePassphrase: function(pp) { if (pp.length == 23 && pp[5] == '-' && pp[11] == '-' && pp[17] == '-') return pp.slice(0, 5) + pp.slice(6, 11) + pp.slice(12, 17) + pp.slice(18, 23); return pp; }, /* * Calculate the strength of a passphrase provided by the user * according to the NIST algorithm (NIST 800-63 Appendix A.1). */ passphraseStrength: function passphraseStrength(value) { let bits = 0; // The entropy of the first character is taken to be 4 bits. if (value.length) bits = 4; // The entropy of the next 7 characters are 2 bits per character. if (value.length > 1) bits += Math.min(value.length - 1, 7) * 2; // For the 9th through the 20th character the entropy is taken to // be 1.5 bits per character. if (value.length > 8) bits += Math.min(value.length - 8, 12) * 1.5; // For characters 21 and above the entropy is taken to be 1 bit per character. if (value.length > 20) bits += value.length - 20; // Bonus of 6 bits if we find non-alphabetic characters if ([char.charCodeAt() for each (char in value.toLowerCase())] .some(function(chr) chr < 97 || chr > 122)) bits += 6; return bits; }, /** * Create an array like the first but without elements of the second */ arraySub: function arraySub(minuend, subtrahend) { return minuend.filter(function(i) subtrahend.indexOf(i) == -1); }, bind2: function Async_bind2(object, method) { return function innerBind() { return method.apply(object, arguments); }; }, mpLocked: function mpLocked() { let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]. getService(Ci.nsIPKCS11ModuleDB); let sdrSlot = modules.findSlotByName(""); let status = sdrSlot.status; let slots = Ci.nsIPKCS11Slot; if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN || status == slots.SLOT_UNINITIALIZED) return false; if (status == slots.SLOT_NOT_LOGGED_IN) return true; // something wacky happened, pretend MP is locked return true; }, __prefs: null, get prefs() { if (!this.__prefs) { this.__prefs = Cc["@mozilla.org/preferences-service;1"] .getService(Ci.nsIPrefService); this.__prefs = this.__prefs.getBranch(PREFS_BRANCH); this.__prefs.QueryInterface(Ci.nsIPrefBranch2); } return this.__prefs; } }; let FakeSvc = { // Private Browsing "@mozilla.org/privatebrowsing;1": { autoStarted: false, privateBrowsingEnabled: false }, // Session Restore "@mozilla.org/browser/sessionstore;1": { setTabValue: function(tab, key, value) { if (!tab.__SS_extdata) tab.__SS_extdata = {}; tab.__SS_extData[key] = value; }, getBrowserState: function() { // Fennec should have only one window. Not more, not less. let state = { windows: [{ tabs: [] }] }; let window = Svc.WinMediator.getMostRecentWindow("navigator:browser"); // Extract various pieces of tab data window.Browser._tabs.forEach(function(tab) { let tabState = { entries: [{}] }; let browser = tab.browser; // Cases when we want to skip the tab. Could come up if we get // state as a tab is opening or closing. if (!browser || !browser.currentURI || !browser.sessionHistory) return; let history = browser.sessionHistory; if (history.count > 0) { // We're only grabbing the current history entry for now. let entry = history.getEntryAtIndex(history.index, false); tabState.entries[0].url = entry.URI.spec; // Like SessionStore really does it... if (entry.title && entry.title != entry.url) tabState.entries[0].title = entry.title; } // index is 1-based tabState.index = 1; // Get the image for the tab. Fennec doesn't quite work the same // way as Firefox, so we'll just get this from the browser object. tabState.attributes = { image: browser.mIconURL }; // Collect the extdata if (tab.__SS_extdata) { tabState.extData = {}; for (let key in tab.__SS_extdata) tabState.extData[key] = tab.__SS_extdata[key]; } // Add the tab to the window state.windows[0].tabs.push(tabState); }); return JSON.stringify(state); } }, // A fake service only used for testing "@labs.mozilla.com/Fake/Thing;1": { isFake: true } }; /* * Commonly-used services */ let Svc = {}; Svc.Prefs = new Preferences(PREFS_BRANCH); Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true}); Svc.Obs = Observers; this.__defineGetter__("_sessionCID", function() { //sets session CID based on browser type let appInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); return appInfo.ID == SEAMONKEY_ID ? "@mozilla.org/suite/sessionstore;1" : "@mozilla.org/browser/sessionstore;1"; }); [["Annos", "@mozilla.org/browser/annotation-service;1", "nsIAnnotationService"], ["AppInfo", "@mozilla.org/xre/app-info;1", "nsIXULAppInfo"], ["Bookmark", "@mozilla.org/browser/nav-bookmarks-service;1", "nsINavBookmarksService"], ["Directory", "@mozilla.org/file/directory_service;1", "nsIProperties"], ["Env", "@mozilla.org/process/environment;1", "nsIEnvironment"], ["Favicon", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"], ["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"], ["History", "@mozilla.org/browser/nav-history-service;1", "nsPIPlacesDatabase"], ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"], ["IO", "@mozilla.org/network/io-service;1", "nsIIOService"], ["KeyFactory", "@mozilla.org/security/keyobjectfactory;1", "nsIKeyObjectFactory"], ["Login", "@mozilla.org/login-manager;1", "nsILoginManager"], ["Memory", "@mozilla.org/xpcom/memory-service;1", "nsIMemory"], ["Private", "@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService"], ["Profiles", "@mozilla.org/toolkit/profile-service;1", "nsIToolkitProfileService"], ["Prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"], ["Script", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"], ["SysInfo", "@mozilla.org/system-info;1", "nsIPropertyBag2"], ["Version", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"], ["WinMediator", "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"], ["WinWatcher", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"], ["Session", this._sessionCID, "nsISessionStore"], ].forEach(function(lazy) Utils.lazySvc(Svc, lazy[0], lazy[1], lazy[2])); Svc.__defineGetter__("Crypto", function() { let cryptoSvc; try { let ns = {}; Cu.import("resource://services-crypto/WeaveCrypto.js", ns); cryptoSvc = new ns.WeaveCrypto(); } catch (ex) { // Fallback to binary WeaveCrypto cryptoSvc = Cc["@labs.mozilla.com/Weave/Crypto;1"]. getService(Ci.IWeaveCrypto); } delete Svc.Crypto; return Svc.Crypto = cryptoSvc; }); let Str = {}; ["errors", "sync"] .forEach(function(lazy) Utils.lazy2(Str, lazy, Utils.lazyStrings(lazy))); Svc.Obs.add("xpcom-shutdown", function () { for (let name in Svc) delete Svc[name]; });