902 lines, 480 LOC, 343 covered (71%)
38 | 1 | /* ***** BEGIN LICENSE BLOCK ***** |
2 | * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
|
3 | * |
|
4 | * The contents of this file are subject to the Mozilla Public License Version |
|
5 | * 1.1 (the "License"); you may not use this file except in compliance with |
|
6 | * the License. You may obtain a copy of the License at |
|
7 | * http://www.mozilla.org/MPL/ |
|
8 | * |
|
9 | * Software distributed under the License is distributed on an "AS IS" basis, |
|
10 | * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
|
11 | * for the specific language governing rights and limitations under the |
|
12 | * License. |
|
13 | * |
|
14 | * The Original Code is Bookmarks Sync. |
|
15 | * |
|
16 | * The Initial Developer of the Original Code is Mozilla. |
|
17 | * Portions created by the Initial Developer are Copyright (C) 2007 |
|
18 | * the Initial Developer. All Rights Reserved. |
|
19 | * |
|
20 | * Contributor(s): |
|
21 | * Dan Mills <thunder@mozilla.com> |
|
22 | * |
|
23 | * Alternatively, the contents of this file may be used under the terms of |
|
24 | * either the GNU General Public License Version 2 or later (the "GPL"), or |
|
25 | * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
|
26 | * in which case the provisions of the GPL or the LGPL are applicable instead |
|
27 | * of those above. If you wish to allow use of your version of this file only |
|
28 | * under the terms of either the GPL or the LGPL, and not to allow others to |
|
29 | * use your version of this file under the terms of the MPL, indicate your |
|
30 | * decision by deleting the provisions above and replace them with the notice |
|
31 | * and other provisions required by the GPL or the LGPL. If you do not delete |
|
32 | * the provisions above, a recipient may use your version of this file under |
|
33 | * the terms of any one of the MPL, the GPL or the LGPL. |
|
34 | * |
|
35 | * ***** END LICENSE BLOCK ***** */ |
|
36 | ||
494 | 37 | const EXPORTED_SYMBOLS = ['Utils', 'Svc', 'Str']; |
38 | ||
152 | 39 | const Cc = Components.classes; |
152 | 40 | const Ci = Components.interfaces; |
152 | 41 | const Cr = Components.results; |
152 | 42 | const Cu = Components.utils; |
43 | ||
190 | 44 | Cu.import("resource://weave/ext/Preferences.js"); |
190 | 45 | Cu.import("resource://weave/ext/Observers.js"); |
190 | 46 | Cu.import("resource://weave/ext/StringBundle.js"); |
190 | 47 | Cu.import("resource://weave/constants.js"); |
190 | 48 | Cu.import("resource://weave/log4moz.js"); |
49 | ||
50 | /* |
|
51 | * Utility functions |
|
52 | */ |
|
53 | ||
76 | 54 | let Utils = { |
55 | /** |
|
56 | * Wrap a function to catch all exceptions and log them |
|
57 | * |
|
58 | * @usage MyObj._catch = Utils.catch; |
|
59 | * MyObj.foo = function() { this._catch(func)(); } |
|
60 | */ |
|
78 | 61 | catch: function Utils_catch(func) { |
4 | 62 | let thisArg = this; |
6 | 63 | return function WrappedCatch() { |
2 | 64 | try { |
9 | 65 | return func.call(thisArg); |
66 | } |
|
3 | 67 | catch(ex) { |
13 | 68 | thisArg._log.debug("Exception: " + Utils.exceptionStr(ex)); |
1 | 69 | } |
70 | }; |
|
71 | }, |
|
72 | ||
73 | /** |
|
74 | * Wrap a function to call lock before calling the function then unlock. |
|
75 | * |
|
76 | * @usage MyObj._lock = Utils.lock; |
|
77 | * MyObj.foo = function() { this._lock(func)(); } |
|
78 | */ |
|
79 | 79 | lock: function Utils_lock(func) { |
6 | 80 | let thisArg = this; |
9 | 81 | return function WrappedLock() { |
15 | 82 | if (!thisArg.lock()) |
2 | 83 | throw "Could not acquire lock"; |
84 | ||
2 | 85 | try { |
11 | 86 | return func.call(thisArg); |
87 | } |
|
2 | 88 | finally { |
10 | 89 | thisArg.unlock(); |
90 | } |
|
91 | }; |
|
92 | }, |
|
93 | ||
94 | /** |
|
95 | * Wrap functions to notify when it starts and finishes executing or if it got |
|
96 | * an error. The message is a combination of a provided prefix and local name |
|
97 | * with the current state and the subject is the provided subject. |
|
98 | * |
|
99 | * @usage function MyObj() { this._notify = Utils.notify("prefix:"); } |
|
100 | * MyObj.foo = function() { this._notify(name, subject, func)(); } |
|
101 | */ |
|
121 | 102 | notify: function Utils_notify(prefix) { |
97 | 103 | return function NotifyMaker(name, subject, func) { |
14 | 104 | let thisArg = this; |
28 | 105 | let notify = function(state) { |
112 | 106 | let mesg = prefix + name + ":" + state; |
112 | 107 | thisArg._log.trace("Event: " + mesg); |
98 | 108 | Observers.notify(mesg, subject); |
109 | }; |
|
110 | ||
21 | 111 | return function WrappedNotify() { |
14 | 112 | try { |
28 | 113 | notify("start"); |
34 | 114 | let ret = func.call(thisArg); |
24 | 115 | notify("finish"); |
24 | 116 | return ret; |
117 | } |
|
3 | 118 | catch(ex) { |
4 | 119 | notify("error"); |
2 | 120 | throw ex; |
121 | } |
|
122 | }; |
|
123 | }; |
|
124 | }, |
|
125 | ||
83 | 126 | batchSync: function batchSync(service, engineType) { |
15 | 127 | return function batchedSync() { |
2 | 128 | let engine = this; |
2 | 129 | let batchEx = null; |
130 | ||
131 | // Try running sync in batch mode |
|
43 | 132 | Svc[service].runInBatchMode({ |
4 | 133 | runBatched: function wrappedSync() { |
1 | 134 | try { |
6 | 135 | engineType.prototype._sync.call(engine); |
136 | } |
|
3 | 137 | catch(ex) { |
5 | 138 | batchEx = ex; |
1 | 139 | } |
140 | } |
|
3 | 141 | }, null); |
142 | ||
143 | // Expose the exception if something inside the batch failed |
|
3 | 144 | if (batchEx!= null) |
2 | 145 | throw batchEx; |
146 | }; |
|
147 | }, |
|
148 | ||
149 | // Generates a brand-new globally unique identifier (GUID). |
|
1076 | 150 | makeGUID: function makeGUID() { |
151 | // 70 characters that are not-escaped URL-friendly |
|
152 | const code = |
|
2000 | 153 | "!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; |
154 | ||
2000 | 155 | let guid = ""; |
2000 | 156 | let num = 0; |
2000 | 157 | let val; |
158 | ||
159 | // Generate ten 70-value characters for a 70^10 (~61.29-bit) GUID |
|
58000 | 160 | for (let i = 0; i < 10; i++) { |
161 | // Refresh the number source after using it a few times |
|
68000 | 162 | if (i == 0 || i == 5) |
8000 | 163 | num = Math.random(); |
164 | ||
165 | // Figure out which code to use for the next GUID character |
|
40000 | 166 | num *= 70; |
50000 | 167 | val = Math.floor(num); |
60000 | 168 | guid += code[val]; |
40000 | 169 | num -= val; |
170 | } |
|
171 | ||
2000 | 172 | return guid; |
173 | }, |
|
174 | ||
140 | 175 | anno: function anno(id, anno, val, expire) { |
176 | // Figure out if we have a bookmark or page |
|
571 | 177 | let annoFunc = (typeof id == "number" ? "Item" : "Page") + "Annotation"; |
178 | ||
179 | // Convert to a nsIURI if necessary |
|
256 | 180 | if (typeof id == "string") |
15 | 181 | id = Utils.makeURI(id); |
182 | ||
192 | 183 | if (id == null) |
184 | throw "Null id for anno! (invalid uri)"; |
|
185 | ||
128 | 186 | switch (arguments.length) { |
187 | case 2: |
|
188 | // Get the annotation with 2 args |
|
485 | 189 | return Svc.Annos["get" + annoFunc](id, anno); |
190 | case 3: |
|
24 | 191 | expire = "NEVER"; |
192 | // Fallthrough! |
|
193 | case 4: |
|
194 | // Convert to actual EXPIRE value |
|
84 | 195 | expire = Svc.Annos["EXPIRE_" + expire]; |
196 | ||
197 | // Set the annotation with 3 or 4 args |
|
156 | 198 | return Svc.Annos["set" + annoFunc](id, anno, val, 0, expire); |
199 | } |
|
200 | }, |
|
201 | ||
266 | 202 | ensureOneOpen: let (windows = {}) function ensureOneOpen(window) { |
203 | // Close the other window if it exists |
|
204 | let url = window.location.href; |
|
205 | let other = windows[url]; |
|
206 | if (other != null) |
|
207 | other.close(); |
|
208 | ||
209 | // Save the new window for future closure |
|
210 | windows[url] = window; |
|
211 | ||
212 | // Actively clean up when the window is closed |
|
213 | window.addEventListener("unload", function() windows[url] = null, false); |
|
214 | }, |
|
215 | ||
216 | // Returns a nsILocalFile representing a file relative to the |
|
217 | // current user's profile directory. If the argument is a string, |
|
218 | // it should be a string with unix-style slashes for directory names |
|
219 | // (these slashes are automatically converted to platform-specific |
|
220 | // path separators). |
|
221 | // |
|
222 | // Alternatively, if the argument is an object, it should contain |
|
223 | // the following attributes: |
|
224 | // |
|
225 | // path: the path to the file, relative to the current user's |
|
226 | // profile dir. |
|
227 | // |
|
228 | // autoCreate: whether or not the file should be created if it |
|
229 | // doesn't already exist. |
|
108 | 230 | getProfileFile: function getProfileFile(arg) { |
128 | 231 | if (typeof arg == "string") |
145 | 232 | arg = {path: arg}; |
233 | ||
192 | 234 | let pathParts = arg.path.split("/"); |
256 | 235 | let file = Svc.Directory.get("ProfD", Ci.nsIFile); |
192 | 236 | file.QueryInterface(Ci.nsILocalFile); |
864 | 237 | for (let i = 0; i < pathParts.length; i++) |
672 | 238 | file.append(pathParts[i]); |
108 | 239 | if (arg.autoCreate && !file.exists()) |
240 | file.create(file.NORMAL_FILE_TYPE, PERMS_FILE); |
|
64 | 241 | return file; |
242 | }, |
|
243 | ||
244 | /** |
|
245 | * Add a simple getter/setter to an object that defers access of a property |
|
246 | * to an inner property. |
|
247 | * |
|
248 | * @param obj |
|
249 | * Object to add properties to defer in its prototype |
|
250 | * @param defer |
|
251 | * Hash property of obj to defer to (dot split each level) |
|
252 | * @param prop |
|
253 | * Property name to defer (or an array of property names) |
|
254 | */ |
|
531 | 255 | deferGetSet: function Utils_deferGetSet(obj, defer, prop) { |
2275 | 256 | if (Utils.isArray(prop)) |
3110 | 257 | return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop)); |
258 | ||
259 | // Split the defer into each dot part for each level to dereference |
|
1805 | 260 | let parts = defer.split("."); |
6051655 | 261 | let deref = function(base) Utils.deref(base, parts); |
262 | ||
722 | 263 | let prot = obj.prototype; |
264 | ||
265 | // Create a getter if it doesn't exist yet |
|
2166 | 266 | if (!prot.__lookupGetter__(prop)) |
5838497 | 267 | prot.__defineGetter__(prop, function() deref(this)[prop]); |
268 | ||
269 | // Create a setter if it doesn't exist yet |
|
2166 | 270 | if (!prot.__lookupSetter__(prop)) |
247513 | 271 | prot.__defineSetter__(prop, function(val) deref(this)[prop] = val); |
272 | }, |
|
273 | ||
274 | /** |
|
275 | * Dereference an array of properties starting from a base object |
|
276 | * |
|
277 | * @param base |
|
278 | * Base object to start dereferencing |
|
279 | * @param props |
|
280 | * Array of properties to dereference (one for each level) |
|
281 | */ |
|
4322173 | 282 | deref: function Utils_deref(base, props) props.reduce(function(curr, prop) |
6050941 | 283 | curr[prop], base), |
284 | ||
285 | /** |
|
286 | * Determine if some value is an array |
|
287 | * |
|
288 | * @param val |
|
289 | * Value to check (can be null, undefined, etc.) |
|
290 | * @return True if it's an array; false otherwise |
|
291 | */ |
|
4936 | 292 | isArray: function Utils_isArray(val) val != null && typeof val == "object" && |
976 | 293 | val.constructor.name == "Array", |
294 | ||
295 | // lazy load objects from a constructor on first access. It will |
|
296 | // work with the global object ('this' in the global context). |
|
195 | 297 | lazy: function Weave_lazy(dest, prop, ctr) { |
476 | 298 | delete dest[prop]; |
1428 | 299 | dest.__defineGetter__(prop, Utils.lazyCb(dest, prop, ctr)); |
300 | }, |
|
195 | 301 | lazyCb: function Weave_lazyCb(dest, prop, ctr) { |
357 | 302 | return function() { |
476 | 303 | delete dest[prop]; |
595 | 304 | dest[prop] = new ctr(); |
476 | 305 | return dest[prop]; |
306 | }; |
|
307 | }, |
|
308 | ||
309 | // like lazy, but rather than new'ing the 3rd arg we use its return value |
|
376 | 310 | lazy2: function Weave_lazy2(dest, prop, fn) { |
1200 | 311 | delete dest[prop]; |
3600 | 312 | dest.__defineGetter__(prop, Utils.lazyCb2(dest, prop, fn)); |
313 | }, |
|
376 | 314 | lazyCb2: function Weave_lazyCb2(dest, prop, fn) { |
618 | 315 | return function() { |
72 | 316 | delete dest[prop]; |
106 | 317 | return dest[prop] = fn(); |
318 | }; |
|
319 | }, |
|
320 | ||
993 | 321 | lazySvc: function Weave_lazySvc(dest, prop, cid, iface) { |
1891 | 322 | let getter = function() { |
228 | 323 | delete dest[prop]; |
114 | 324 | let svc = null; |
325 | ||
326 | // Use the platform's service if it exists |
|
387 | 327 | if (cid in Cc && iface in Ci) |
510 | 328 | svc = Cc[cid].getService(Ci[iface]); |
6 | 329 | else { |
62 | 330 | svc = FakeSvc[cid]; |
331 | ||
36 | 332 | let log = Log4Moz.repository.getLogger("Service.Util"); |
18 | 333 | if (svc == null) |
9 | 334 | log.warn("Component " + cid + " doesn't exist on this platform."); |
335 | else |
|
41 | 336 | log.debug("Using a fake svc object for " + cid); |
337 | } |
|
338 | ||
285 | 339 | return dest[prop] = svc; |
340 | }; |
|
6419 | 341 | dest.__defineGetter__(prop, getter); |
342 | }, |
|
343 | ||
190 | 344 | lazyStrings: function Weave_lazyStrings(name) { |
570 | 345 | let bundle = "chrome://weave/locale/" + name + ".properties"; |
233 | 346 | return function() new StringBundle(bundle); |
347 | }, |
|
348 | ||
633 | 349 | deepEquals: function eq(a, b) { |
350 | // If they're triple equals, then it must be equals! |
|
2228 | 351 | if (a === b) |
318 | 352 | return true; |
353 | ||
354 | // If they weren't equal, they must be objects to be different |
|
3039 | 355 | if (typeof a != "object" || typeof b != "object") |
526 | 356 | return false; |
357 | ||
358 | // But null objects won't have properties to compare |
|
1059 | 359 | if (a === null || b === null) |
28 | 360 | return false; |
361 | ||
362 | // Make sure all of a's keys have a matching value in b |
|
938 | 363 | for (let k in a) |
2497 | 364 | if (!eq(a[k], b[k])) |
984 | 365 | return false; |
366 | ||
367 | // Do the same for b's keys but skip those that we already checked |
|
658 | 368 | for (let k in b) |
1144 | 369 | if (!(k in a) && !eq(a[k], b[k])) |
660 | 370 | return false; |
371 | ||
146 | 372 | return true; |
373 | }, |
|
374 | ||
76 | 375 | deepCopy: function Weave_deepCopy(thing, noSort) { |
376 | if (typeof(thing) != "object" || thing == null) |
|
377 | return thing; |
|
378 | let ret; |
|
379 | ||
380 | if (Utils.isArray(thing)) { |
|
381 | ret = []; |
|
382 | for (let i = 0; i < thing.length; i++) |
|
383 | ret.push(Utils.deepCopy(thing[i], noSort)); |
|
384 | ||
385 | } else { |
|
386 | ret = {}; |
|
387 | let props = [p for (p in thing)]; |
|
388 | if (!noSort) |
|
389 | props = props.sort(); |
|
390 | props.forEach(function(k) ret[k] = Utils.deepCopy(thing[k], noSort)); |
|
391 | } |
|
392 | ||
393 | return ret; |
|
394 | }, |
|
395 | ||
396 | // Works on frames or exceptions, munges file:// URIs to shorten the paths |
|
397 | // FIXME: filename munging is sort of hackish, might be confusing if |
|
398 | // there are multiple extensions with similar filenames |
|
88 | 399 | formatFrame: function Utils_formatFrame(frame) { |
24 | 400 | let tmp = "<file:unknown>"; |
401 | ||
36 | 402 | let file = frame.filename || frame.fileName; |
24 | 403 | if (file) |
72 | 404 | tmp = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1"); |
405 | ||
24 | 406 | if (frame.lineNumber) |
72 | 407 | tmp += ":" + frame.lineNumber; |
24 | 408 | if (frame.name) |
54 | 409 | tmp = frame.name + "()@" + tmp; |
410 | ||
24 | 411 | return tmp; |
412 | }, |
|
413 | ||
81 | 414 | exceptionStr: function Weave_exceptionStr(e) { |
24 | 415 | let message = e.message ? e.message : e; |
45 | 416 | return message + " " + Utils.stackTrace(e); |
417 | }, |
|
418 | ||
79 | 419 | stackTraceFromFrame: function Weave_stackTraceFromFrame(frame) { |
6 | 420 | let output = []; |
57 | 421 | while (frame) { |
60 | 422 | let str = Utils.formatFrame(frame); |
24 | 423 | if (str) |
60 | 424 | output.push(str); |
36 | 425 | frame = frame.caller; |
426 | } |
|
15 | 427 | return output.join(" < "); |
428 | }, |
|
429 | ||
83 | 430 | stackTrace: function Weave_stackTrace(e) { |
431 | // Wrapped nsIException |
|
14 | 432 | if (e.location) |
21 | 433 | return "Stack trace: " + Utils.stackTraceFromFrame(e.location); |
434 | ||
435 | // Standard JS exception |
|
8 | 436 | if (e.stack) |
30 | 437 | return "JS Stack trace: " + e.stack.trim().replace(/\n/g, " < "). |
15 | 438 | replace(/@(?:chrome|file):.*?([^\/\.]+\.\w+:)/g, "@$1"); |
439 | ||
2 | 440 | return "No traceback available"; |
441 | }, |
|
442 | ||
76 | 443 | checkStatus: function Weave_checkStatus(code, msg, ranges) { |
444 | if (!ranges) |
|
445 | ranges = [[200,300]]; |
|
446 | ||
447 | for (let i = 0; i < ranges.length; i++) { |
|
448 | var rng = ranges[i]; |
|
449 | if (typeof(rng) == "object" && code >= rng[0] && code < rng[1]) |
|
450 | return true; |
|
451 | else if (typeof(rng) == "number" && code == rng) { |
|
452 | return true; |
|
453 | } |
|
454 | } |
|
455 | ||
456 | if (msg) { |
|
457 | let log = Log4Moz.repository.getLogger("Service.Util"); |
|
458 | log.error(msg + " Error code: " + code); |
|
459 | } |
|
460 | ||
461 | return false; |
|
462 | }, |
|
463 | ||
76 | 464 | ensureStatus: function Weave_ensureStatus(args) { |
465 | if (!Utils.checkStatus.apply(Utils, arguments)) |
|
466 | throw 'checkStatus failed'; |
|
467 | }, |
|
468 | ||
103 | 469 | digest: function digest(message, hasher) { |
81 | 470 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]. |
108 | 471 | createInstance(Ci.nsIScriptableUnicodeConverter); |
81 | 472 | converter.charset = "UTF-8"; |
473 | ||
189 | 474 | let data = converter.convertToByteArray(message, {}); |
189 | 475 | hasher.update(data, data.length); |
476 | ||
477 | // Convert each hashed byte into 2-hex strings then combine them |
|
12312 | 478 | return [("0" + byte.charCodeAt().toString(16)).slice(-2) for each (byte in |
1971 | 479 | hasher.finish(false))].join(""); |
480 | }, |
|
481 | ||
76 | 482 | sha1: function sha1(message) { |
483 | let hasher = Cc["@mozilla.org/security/hash;1"]. |
|
484 | createInstance(Ci.nsICryptoHash); |
|
485 | hasher.init(hasher.SHA1); |
|
486 | return Utils.digest(message, hasher); |
|
487 | }, |
|
488 | ||
489 | /** |
|
490 | * Generate a sha256 HMAC for a string message and a given nsIKeyObject |
|
491 | */ |
|
103 | 492 | sha256HMAC: function sha256HMAC(message, key) { |
81 | 493 | let hasher = Cc["@mozilla.org/security/hmac;1"]. |
108 | 494 | createInstance(Ci.nsICryptoHMAC); |
162 | 495 | hasher.init(hasher.SHA256, key); |
162 | 496 | return Utils.digest(message, hasher); |
497 | }, |
|
498 | ||
7605 | 499 | makeURI: function Weave_makeURI(URIString) { |
22587 | 500 | if (!URIString) |
2 | 501 | return null; |
7528 | 502 | try { |
60221 | 503 | return Svc.IO.newURI(URIString, null, null); |
9 | 504 | } catch (e) { |
18 | 505 | let log = Log4Moz.repository.getLogger("Service.Util"); |
30 | 506 | log.debug("Could not create URI: " + Utils.exceptionStr(e)); |
12 | 507 | return null; |
508 | } |
|
509 | }, |
|
510 | ||
76 | 511 | makeURL: function Weave_makeURL(URIString) { |
512 | let url = Utils.makeURI(URIString); |
|
513 | url.QueryInterface(Ci.nsIURL); |
|
514 | return url; |
|
515 | }, |
|
516 | ||
76 | 517 | xpath: function Weave_xpath(xmlDoc, xpathString) { |
518 | let root = xmlDoc.ownerDocument == null ? |
|
519 | xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement; |
|
520 | let nsResolver = xmlDoc.createNSResolver(root); |
|
521 | ||
522 | return xmlDoc.evaluate(xpathString, xmlDoc, nsResolver, |
|
523 | Ci.nsIDOMXPathResult.ANY_TYPE, null); |
|
524 | }, |
|
525 | ||
76 | 526 | getTmp: function Weave_getTmp(name) { |
527 | let tmp = Svc.Directory.get("ProfD", Ci.nsIFile); |
|
528 | tmp.QueryInterface(Ci.nsILocalFile); |
|
529 | ||
530 | tmp.append("weave"); |
|
531 | tmp.append("tmp"); |
|
532 | if (!tmp.exists()) |
|
533 | tmp.create(tmp.DIRECTORY_TYPE, PERMS_DIRECTORY); |
|
534 | ||
535 | if (name) |
|
536 | tmp.append(name); |
|
537 | ||
538 | return tmp; |
|
539 | }, |
|
540 | ||
541 | /** |
|
542 | * Load a json object from disk |
|
543 | * |
|
544 | * @param filePath |
|
545 | * Json file path load from weave/[filePath].json |
|
546 | * @param that |
|
547 | * Object to use for logging and "this" for callback |
|
548 | * @param callback |
|
549 | * Function to process json object as its first parameter |
|
550 | */ |
|
156 | 551 | jsonLoad: function Utils_jsonLoad(filePath, that, callback) { |
400 | 552 | filePath = "weave/" + filePath + ".json"; |
160 | 553 | if (that._log) |
640 | 554 | that._log.trace("Loading json from disk: " + filePath); |
555 | ||
400 | 556 | let file = Utils.getProfileFile(filePath); |
400 | 557 | if (!file.exists()) |
138 | 558 | return; |
559 | ||
22 | 560 | try { |
110 | 561 | let [is] = Utils.open(file, "<"); |
55 | 562 | let json = Utils.readStream(is); |
44 | 563 | is.close(); |
121 | 564 | callback.call(that, JSON.parse(json)); |
565 | } |
|
566 | catch (ex) { |
|
567 | if (that._log) |
|
568 | that._log.debug("Failed to load json: " + Utils.exceptionStr(ex)); |
|
11 | 569 | } |
570 | }, |
|
571 | ||
572 | /** |
|
573 | * Save a json-able object to disk |
|
574 | * |
|
575 | * @param filePath |
|
576 | * Json file path save to weave/[filePath].json |
|
577 | * @param that |
|
578 | * Object to use for logging and "this" for callback |
|
579 | * @param callback |
|
580 | * Function to provide json-able object to save. If this isn't a |
|
581 | * function, it'll be used as the object to make a json string. |
|
582 | */ |
|
117 | 583 | jsonSave: function Utils_jsonSave(filePath, that, callback) { |
205 | 584 | filePath = "weave/" + filePath + ".json"; |
82 | 585 | if (that._log) |
328 | 586 | that._log.trace("Saving json to disk: " + filePath); |
587 | ||
410 | 588 | let file = Utils.getProfileFile({ autoCreate: true, path: filePath }); |
246 | 589 | let json = typeof callback == "function" ? callback.call(that) : callback; |
205 | 590 | let out = JSON.stringify(json); |
410 | 591 | let [fos] = Utils.open(file, ">"); |
205 | 592 | fos.writeString(out); |
205 | 593 | fos.close(); |
594 | }, |
|
595 | ||
596 | /** |
|
597 | * Return a timer that is scheduled to call the callback after waiting the |
|
598 | * provided time or as soon as possible. The timer will be set as a property |
|
599 | * of the provided object with the given timer name. |
|
600 | */ |
|
3056 | 601 | delay: function delay(callback, wait, thisObj, name) { |
602 | // Default to running right away |
|
8940 | 603 | wait = wait || 0; |
604 | ||
605 | // Use a dummy object if one wasn't provided |
|
8940 | 606 | thisObj = thisObj || {}; |
607 | ||
608 | // Delay an existing timer if it exists |
|
31796 | 609 | if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) { |
14070 | 610 | thisObj[name].delay = wait; |
5628 | 611 | return; |
612 | } |
|
613 | ||
614 | // Create a special timer that we can add extra properties |
|
498 | 615 | let timer = {}; |
1328 | 616 | timer.__proto__ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
617 | ||
618 | // Provide an easy way to clear out the timer |
|
659 | 619 | timer.clear = function() { |
644 | 620 | thisObj[name] = null; |
805 | 621 | timer.cancel(); |
622 | }; |
|
623 | ||
624 | // Initialize the timer with a smart callback |
|
498 | 625 | timer.initWithCallback({ |
504 | 626 | notify: function notify() { |
627 | // Clear out the timer once it's been triggered |
|
24 | 628 | timer.clear(); |
42 | 629 | callback.call(thisObj, timer); |
630 | } |
|
664 | 631 | }, wait, timer.TYPE_ONE_SHOT); |
632 | ||
830 | 633 | return thisObj[name] = timer; |
634 | }, |
|
635 | ||
86 | 636 | open: function open(pathOrFile, mode, perms) { |
40 | 637 | let stream, file; |
638 | ||
50 | 639 | if (pathOrFile instanceof Ci.nsIFile) { |
30 | 640 | file = pathOrFile; |
641 | } else { |
|
642 | file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); |
|
643 | dump("PATH IS" + pathOrFile + "\n"); |
|
644 | file.initWithPath(pathOrFile); |
|
645 | } |
|
646 | ||
30 | 647 | if (!perms) |
20 | 648 | perms = PERMS_FILE; |
649 | ||
20 | 650 | switch(mode) { |
7 | 651 | case "<": { |
35 | 652 | if (!file.exists()) |
653 | throw "Cannot open file for reading, file does not exist"; |
|
21 | 654 | let fis = Cc["@mozilla.org/network/file-input-stream;1"]. |
28 | 655 | createInstance(Ci.nsIFileInputStream); |
56 | 656 | fis.init(file, MODE_RDONLY, perms, 0); |
21 | 657 | stream = Cc["@mozilla.org/intl/converter-input-stream;1"]. |
28 | 658 | createInstance(Ci.nsIConverterInputStream); |
35 | 659 | stream.init(fis, "UTF-8", 4096, |
42 | 660 | Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); |
7 | 661 | } break; |
662 | ||
3 | 663 | case ">": { |
9 | 664 | let fos = Cc["@mozilla.org/network/file-output-stream;1"]. |
12 | 665 | createInstance(Ci.nsIFileOutputStream); |
36 | 666 | fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, perms, 0); |
9 | 667 | stream = Cc["@mozilla.org/intl/converter-output-stream;1"] |
12 | 668 | .createInstance(Ci.nsIConverterOutputStream); |
27 | 669 | stream.init(fos, "UTF-8", 4096, 0x0000); |
3 | 670 | } break; |
671 | ||
672 | case ">>": { |
|
673 | let fos = Cc["@mozilla.org/network/file-output-stream;1"]. |
|
674 | createInstance(Ci.nsIFileOutputStream); |
|
675 | fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_APPEND, perms, 0); |
|
676 | stream = Cc["@mozilla.org/intl/converter-output-stream;1"] |
|
677 | .createInstance(Ci.nsIConverterOutputStream); |
|
678 | stream.init(fos, "UTF-8", 4096, 0x0000); |
|
679 | } break; |
|
680 | ||
681 | default: |
|
682 | throw "Illegal mode to open(): " + mode; |
|
683 | } |
|
684 | ||
40 | 685 | return [stream, file]; |
686 | }, |
|
687 | ||
688 | /** |
|
689 | * Open/reshow a window/dialog based on its name and type. |
|
690 | * |
|
691 | * @param name |
|
692 | * Name of the window/dialog to reshow if already open |
|
693 | * @param type |
|
694 | * Opening behavior: "Window" or "Dialog" |
|
695 | * @param args |
|
696 | * More arguments go here depending on the type |
|
697 | */ |
|
76 | 698 | _openWin: function Utils__openWin(name, type /*, args... */) { |
699 | // Just re-show the window if it's already open |
|
700 | let openedWindow = Svc.WinMediator.getMostRecentWindow("Weave:" + name); |
|
701 | if (openedWindow) { |
|
702 | openedWindow.focus(); |
|
703 | return; |
|
704 | } |
|
705 | ||
706 | // Open up the window/dialog! |
|
707 | let win = Svc.WinWatcher; |
|
708 | if (type == "Dialog") |
|
709 | win = win.activeWindow; |
|
710 | win["open" + type].apply(win, Array.slice(arguments, 2)); |
|
711 | }, |
|
712 | ||
76 | 713 | _openChromeWindow: function Utils_openCWindow(name, uri, options, args) { |
714 | Utils.openWindow(name, "chrome://weave/content/" + uri, options, args); |
|
715 | }, |
|
716 | ||
76 | 717 | openWindow: function Utils_openWindow(name, uri, options, args) { |
718 | Utils._openWin(name, "Window", null, uri, "", |
|
719 | options || "centerscreen,chrome,dialog,resizable=yes", args); |
|
720 | }, |
|
721 | ||
76 | 722 | openDialog: function Utils_openDialog(name, uri, options, args) { |
723 | Utils._openWin(name, "Dialog", "chrome://weave/content/" + uri, "", |
|
724 | options || "centerscreen,chrome,dialog,modal,resizable=no", args); |
|
725 | }, |
|
726 | ||
76 | 727 | openGenericDialog: function Utils_openGenericDialog(type) { |
728 | this._genericDialogType = type; |
|
729 | this.openDialog("ChangeSomething", "generic-change.xul"); |
|
730 | }, |
|
731 | ||
76 | 732 | getIcon: function(iconUri, defaultIcon) { |
733 | try { |
|
734 | let iconURI = Utils.makeURI(iconUri); |
|
735 | return Svc.Favicon.getFaviconLinkForIcon(iconURI).spec; |
|
736 | } |
|
737 | catch(ex) {} |
|
738 | ||
739 | // Just give the provided default icon or the system's default |
|
740 | return defaultIcon || Svc.Favicon.defaultFavicon.spec; |
|
741 | }, |
|
742 | ||
76 | 743 | getErrorString: function Utils_getErrorString(error, args) { |
744 | try { |
|
38 | 745 | return Str.errors.get(error, args || null); |
746 | } catch (e) {} |
|
747 | ||
748 | // basically returns "Unknown Error" |
|
749 | return Str.errors.get("error.reason.unknown"); |
|
750 | }, |
|
751 | ||
752 | // assumes an nsIConverterInputStream |
|
83 | 753 | readStream: function Weave_readStream(is) { |
35 | 754 | let ret = "", str = {}; |
112 | 755 | while (is.readString(4096, str) != 0) { |
28 | 756 | ret += str.value; |
757 | } |
|
14 | 758 | return ret; |
759 | }, |
|
760 | ||
761 | /** |
|
762 | * Create an array like the first but without elements of the second |
|
763 | */ |
|
78 | 764 | arraySub: function arraySub(minuend, subtrahend) { |
9882 | 765 | return minuend.filter(function(i) subtrahend.indexOf(i) == -1); |
766 | }, |
|
767 | ||
148 | 768 | bind2: function Async_bind2(object, method) { |
9272 | 769 | return function innerBind() { return method.apply(object, arguments); }; |
770 | }, |
|
771 | ||
76 | 772 | mpLocked: function mpLocked() { |
773 | let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]. |
|
774 | getService(Ci.nsIPKCS11ModuleDB); |
|
775 | let sdrSlot = modules.findSlotByName(""); |
|
776 | let status = sdrSlot.status; |
|
777 | let slots = Ci.nsIPKCS11Slot; |
|
778 | ||
779 | if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN) |
|
780 | return false; |
|
781 | ||
782 | if (status == slots.SLOT_NOT_LOGGED_IN) |
|
783 | return true; |
|
784 | ||
785 | // something wacky happened, pretend MP is locked |
|
786 | return true; |
|
787 | }, |
|
788 | ||
76 | 789 | __prefs: null, |
270 | 790 | get prefs() { |
354 | 791 | if (!this.__prefs) { |
24 | 792 | this.__prefs = Cc["@mozilla.org/preferences-service;1"] |
24 | 793 | .getService(Ci.nsIPrefService); |
42 | 794 | this.__prefs = this.__prefs.getBranch(PREFS_BRANCH); |
42 | 795 | this.__prefs.QueryInterface(Ci.nsIPrefBranch2); |
796 | } |
|
236 | 797 | return this.__prefs; |
798 | } |
|
799 | }; |
|
800 | ||
38 | 801 | let FakeSvc = { |
802 | // Private Browsing |
|
38 | 803 | "@mozilla.org/privatebrowsing;1": { |
76 | 804 | autoStarted: false, |
152 | 805 | privateBrowsingEnabled: false |
806 | }, |
|
807 | // Session Restore |
|
38 | 808 | "@mozilla.org/browser/sessionstore;1": { |
76 | 809 | setTabValue: function(tab, key, value) { |
810 | if (!tab.__SS_extdata) |
|
811 | tab.__SS_extdata = {}; |
|
812 | tab.__SS_extData[key] = value; |
|
813 | }, |
|
152 | 814 | getBrowserState: function() { |
815 | // Fennec should have only one window. Not more, not less. |
|
816 | let state = { windows: [{ tabs: [] }] }; |
|
817 | let window = Svc.WinMediator.getMostRecentWindow("navigator:browser"); |
|
818 | ||
819 | // Extract various pieces of tab data |
|
820 | window.Browser._tabs.forEach(function(tab) { |
|
821 | let tabState = { entries: [{}] }; |
|
822 | let browser = tab.browser; |
|
823 | ||
824 | // Cases when we want to skip the tab. Could come up if we get |
|
825 | // state as a tab is opening or closing. |
|
826 | if (!browser || !browser.currentURI || !browser.sessionHistory) |
|
827 | return; |
|
828 | ||
829 | let history = browser.sessionHistory; |
|
830 | if (history.count > 0) { |
|
831 | // We're only grabbing the current history entry for now. |
|
832 | let entry = history.getEntryAtIndex(history.index, false); |
|
833 | tabState.entries[0].url = entry.URI.spec; |
|
834 | // Like SessionStore really does it... |
|
835 | if (entry.title && entry.title != entry.url) |
|
836 | tabState.entries[0].title = entry.title; |
|
837 | } |
|
838 | // index is 1-based |
|
839 | tabState.index = 1; |
|
840 | ||
841 | // Get the image for the tab. Fennec doesn't quite work the same |
|
842 | // way as Firefox, so we'll just get this from the browser object. |
|
843 | tabState.attributes = { image: browser.mIconURL }; |
|
844 | ||
845 | // Collect the extdata |
|
846 | if (tab.__SS_extdata) { |
|
847 | tabState.extData = {}; |
|
848 | for (let key in tab.__SS_extdata) |
|
849 | tabState.extData[key] = tab.__SS_extdata[key]; |
|
850 | } |
|
851 | ||
852 | // Add the tab to the window |
|
853 | state.windows[0].tabs.push(tabState); |
|
854 | }); |
|
855 | return JSON.stringify(state); |
|
856 | } |
|
857 | }, |
|
858 | // A fake service only used for testing |
|
38 | 859 | "@labs.mozilla.com/Fake/Thing;1": { |
228 | 860 | isFake: true |
861 | } |
|
862 | }; |
|
863 | ||
864 | // Use the binary WeaveCrypto (;1) if the js-ctypes version (;2) fails to load |
|
865 | // by adding an alias on FakeSvc from ;2 to ;1 |
|
152 | 866 | Utils.lazySvc(FakeSvc, "@labs.mozilla.com/Weave/Crypto;2", |
152 | 867 | "@labs.mozilla.com/Weave/Crypto;1", "IWeaveCrypto"); |
868 | ||
869 | /* |
|
870 | * Commonly-used services |
|
871 | */ |
|
114 | 872 | let Svc = {}; |
228 | 873 | Svc.Prefs = new Preferences(PREFS_BRANCH); |
152 | 874 | Svc.Obs = Observers; |
570 | 875 | [["Annos", "@mozilla.org/browser/annotation-service;1", "nsIAnnotationService"], |
494 | 876 | ["AppInfo", "@mozilla.org/xre/app-info;1", "nsIXULAppInfo"], |
494 | 877 | ["Bookmark", "@mozilla.org/browser/nav-bookmarks-service;1", "nsINavBookmarksService"], |
494 | 878 | ["Crypto", "@labs.mozilla.com/Weave/Crypto;2", "IWeaveCrypto"], |
494 | 879 | ["Directory", "@mozilla.org/file/directory_service;1", "nsIProperties"], |
494 | 880 | ["Env", "@mozilla.org/process/environment;1", "nsIEnvironment"], |
494 | 881 | ["Favicon", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"], |
494 | 882 | ["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"], |
494 | 883 | ["History", "@mozilla.org/browser/nav-history-service;1", "nsPIPlacesDatabase"], |
494 | 884 | ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"], |
494 | 885 | ["IO", "@mozilla.org/network/io-service;1", "nsIIOService"], |
494 | 886 | ["KeyFactory", "@mozilla.org/security/keyobjectfactory;1", "nsIKeyObjectFactory"], |
494 | 887 | ["Login", "@mozilla.org/login-manager;1", "nsILoginManager"], |
494 | 888 | ["Memory", "@mozilla.org/xpcom/memory-service;1", "nsIMemory"], |
494 | 889 | ["Private", "@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService"], |
494 | 890 | ["Profiles", "@mozilla.org/toolkit/profile-service;1", "nsIToolkitProfileService"], |
494 | 891 | ["Prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"], |
494 | 892 | ["Script", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"], |
494 | 893 | ["SysInfo", "@mozilla.org/system-info;1", "nsIPropertyBag2"], |
494 | 894 | ["Version", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"], |
494 | 895 | ["WinMediator", "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"], |
494 | 896 | ["WinWatcher", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"], |
532 | 897 | ["Session", "@mozilla.org/browser/sessionstore;1", "nsISessionStore"], |
13224 | 898 | ].forEach(function(lazy) Utils.lazySvc(Svc, lazy[0], lazy[1], lazy[2])); |
899 | ||
114 | 900 | let Str = {}; |
456 | 901 | ["engines", "errors", "sync"] |
1406 | 902 | .forEach(function(lazy) Utils.lazy2(Str, lazy, Utils.lazyStrings(lazy))); |