services/common/utils.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

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
michael@0 6
michael@0 7 this.EXPORTED_SYMBOLS = ["CommonUtils"];
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/Promise.jsm");
michael@0 10 Cu.import("resource://gre/modules/Services.jsm");
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12 Cu.import("resource://gre/modules/osfile.jsm")
michael@0 13 Cu.import("resource://gre/modules/Log.jsm");
michael@0 14
michael@0 15 this.CommonUtils = {
michael@0 16 /*
michael@0 17 * Set manipulation methods. These should be lifted into toolkit, or added to
michael@0 18 * `Set` itself.
michael@0 19 */
michael@0 20
michael@0 21 /**
michael@0 22 * Return elements of `a` or `b`.
michael@0 23 */
michael@0 24 union: function (a, b) {
michael@0 25 let out = new Set(a);
michael@0 26 for (let x of b) {
michael@0 27 out.add(x);
michael@0 28 }
michael@0 29 return out;
michael@0 30 },
michael@0 31
michael@0 32 /**
michael@0 33 * Return elements of `a` that are not present in `b`.
michael@0 34 */
michael@0 35 difference: function (a, b) {
michael@0 36 let out = new Set(a);
michael@0 37 for (let x of b) {
michael@0 38 out.delete(x);
michael@0 39 }
michael@0 40 return out;
michael@0 41 },
michael@0 42
michael@0 43 /**
michael@0 44 * Return elements of `a` that are also in `b`.
michael@0 45 */
michael@0 46 intersection: function (a, b) {
michael@0 47 let out = new Set();
michael@0 48 for (let x of a) {
michael@0 49 if (b.has(x)) {
michael@0 50 out.add(x);
michael@0 51 }
michael@0 52 }
michael@0 53 return out;
michael@0 54 },
michael@0 55
michael@0 56 /**
michael@0 57 * Return true if `a` and `b` are the same size, and
michael@0 58 * every element of `a` is in `b`.
michael@0 59 */
michael@0 60 setEqual: function (a, b) {
michael@0 61 if (a.size != b.size) {
michael@0 62 return false;
michael@0 63 }
michael@0 64 for (let x of a) {
michael@0 65 if (!b.has(x)) {
michael@0 66 return false;
michael@0 67 }
michael@0 68 }
michael@0 69 return true;
michael@0 70 },
michael@0 71
michael@0 72 // Import these from Log.jsm for backward compatibility
michael@0 73 exceptionStr: Log.exceptionStr,
michael@0 74 stackTrace: Log.stackTrace,
michael@0 75
michael@0 76 /**
michael@0 77 * Encode byte string as base64URL (RFC 4648).
michael@0 78 *
michael@0 79 * @param bytes
michael@0 80 * (string) Raw byte string to encode.
michael@0 81 * @param pad
michael@0 82 * (bool) Whether to include padding characters (=). Defaults
michael@0 83 * to true for historical reasons.
michael@0 84 */
michael@0 85 encodeBase64URL: function encodeBase64URL(bytes, pad=true) {
michael@0 86 let s = btoa(bytes).replace("+", "-", "g").replace("/", "_", "g");
michael@0 87
michael@0 88 if (!pad) {
michael@0 89 s = s.replace("=", "", "g");
michael@0 90 }
michael@0 91
michael@0 92 return s;
michael@0 93 },
michael@0 94
michael@0 95 /**
michael@0 96 * Create a nsIURI instance from a string.
michael@0 97 */
michael@0 98 makeURI: function makeURI(URIString) {
michael@0 99 if (!URIString)
michael@0 100 return null;
michael@0 101 try {
michael@0 102 return Services.io.newURI(URIString, null, null);
michael@0 103 } catch (e) {
michael@0 104 let log = Log.repository.getLogger("Common.Utils");
michael@0 105 log.debug("Could not create URI: " + CommonUtils.exceptionStr(e));
michael@0 106 return null;
michael@0 107 }
michael@0 108 },
michael@0 109
michael@0 110 /**
michael@0 111 * Execute a function on the next event loop tick.
michael@0 112 *
michael@0 113 * @param callback
michael@0 114 * Function to invoke.
michael@0 115 * @param thisObj [optional]
michael@0 116 * Object to bind the callback to.
michael@0 117 */
michael@0 118 nextTick: function nextTick(callback, thisObj) {
michael@0 119 if (thisObj) {
michael@0 120 callback = callback.bind(thisObj);
michael@0 121 }
michael@0 122 Services.tm.currentThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
michael@0 123 },
michael@0 124
michael@0 125 /**
michael@0 126 * Return a promise resolving on some later tick.
michael@0 127 *
michael@0 128 * This a wrapper around Promise.resolve() that prevents stack
michael@0 129 * accumulation and prevents callers from accidentally relying on
michael@0 130 * same-tick promise resolution.
michael@0 131 */
michael@0 132 laterTickResolvingPromise: function (value, prototype) {
michael@0 133 let deferred = Promise.defer(prototype);
michael@0 134 this.nextTick(deferred.resolve.bind(deferred, value));
michael@0 135 return deferred.promise;
michael@0 136 },
michael@0 137
michael@0 138 /**
michael@0 139 * Spin the event loop and return once the next tick is executed.
michael@0 140 *
michael@0 141 * This is an evil function and should not be used in production code. It
michael@0 142 * exists in this module for ease-of-use.
michael@0 143 */
michael@0 144 waitForNextTick: function waitForNextTick() {
michael@0 145 let cb = Async.makeSyncCallback();
michael@0 146 this.nextTick(cb);
michael@0 147 Async.waitForSyncCallback(cb);
michael@0 148
michael@0 149 return;
michael@0 150 },
michael@0 151
michael@0 152 /**
michael@0 153 * Return a timer that is scheduled to call the callback after waiting the
michael@0 154 * provided time or as soon as possible. The timer will be set as a property
michael@0 155 * of the provided object with the given timer name.
michael@0 156 */
michael@0 157 namedTimer: function namedTimer(callback, wait, thisObj, name) {
michael@0 158 if (!thisObj || !name) {
michael@0 159 throw "You must provide both an object and a property name for the timer!";
michael@0 160 }
michael@0 161
michael@0 162 // Delay an existing timer if it exists
michael@0 163 if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) {
michael@0 164 thisObj[name].delay = wait;
michael@0 165 return;
michael@0 166 }
michael@0 167
michael@0 168 // Create a special timer that we can add extra properties
michael@0 169 let timer = {};
michael@0 170 timer.__proto__ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 171
michael@0 172 // Provide an easy way to clear out the timer
michael@0 173 timer.clear = function() {
michael@0 174 thisObj[name] = null;
michael@0 175 timer.cancel();
michael@0 176 };
michael@0 177
michael@0 178 // Initialize the timer with a smart callback
michael@0 179 timer.initWithCallback({
michael@0 180 notify: function notify() {
michael@0 181 // Clear out the timer once it's been triggered
michael@0 182 timer.clear();
michael@0 183 callback.call(thisObj, timer);
michael@0 184 }
michael@0 185 }, wait, timer.TYPE_ONE_SHOT);
michael@0 186
michael@0 187 return thisObj[name] = timer;
michael@0 188 },
michael@0 189
michael@0 190 encodeUTF8: function encodeUTF8(str) {
michael@0 191 try {
michael@0 192 str = this._utf8Converter.ConvertFromUnicode(str);
michael@0 193 return str + this._utf8Converter.Finish();
michael@0 194 } catch (ex) {
michael@0 195 return null;
michael@0 196 }
michael@0 197 },
michael@0 198
michael@0 199 decodeUTF8: function decodeUTF8(str) {
michael@0 200 try {
michael@0 201 str = this._utf8Converter.ConvertToUnicode(str);
michael@0 202 return str + this._utf8Converter.Finish();
michael@0 203 } catch (ex) {
michael@0 204 return null;
michael@0 205 }
michael@0 206 },
michael@0 207
michael@0 208 byteArrayToString: function byteArrayToString(bytes) {
michael@0 209 return [String.fromCharCode(byte) for each (byte in bytes)].join("");
michael@0 210 },
michael@0 211
michael@0 212 stringToByteArray: function stringToByteArray(bytesString) {
michael@0 213 return [String.charCodeAt(byte) for each (byte in bytesString)];
michael@0 214 },
michael@0 215
michael@0 216 bytesAsHex: function bytesAsHex(bytes) {
michael@0 217 return [("0" + bytes.charCodeAt(byte).toString(16)).slice(-2)
michael@0 218 for (byte in bytes)].join("");
michael@0 219 },
michael@0 220
michael@0 221 stringAsHex: function stringAsHex(str) {
michael@0 222 return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str));
michael@0 223 },
michael@0 224
michael@0 225 stringToBytes: function stringToBytes(str) {
michael@0 226 return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str));
michael@0 227 },
michael@0 228
michael@0 229 hexToBytes: function hexToBytes(str) {
michael@0 230 let bytes = [];
michael@0 231 for (let i = 0; i < str.length - 1; i += 2) {
michael@0 232 bytes.push(parseInt(str.substr(i, 2), 16));
michael@0 233 }
michael@0 234 return String.fromCharCode.apply(String, bytes);
michael@0 235 },
michael@0 236
michael@0 237 hexAsString: function hexAsString(hex) {
michael@0 238 return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
michael@0 239 },
michael@0 240
michael@0 241 /**
michael@0 242 * Base32 encode (RFC 4648) a string
michael@0 243 */
michael@0 244 encodeBase32: function encodeBase32(bytes) {
michael@0 245 const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
michael@0 246 let quanta = Math.floor(bytes.length / 5);
michael@0 247 let leftover = bytes.length % 5;
michael@0 248
michael@0 249 // Pad the last quantum with zeros so the length is a multiple of 5.
michael@0 250 if (leftover) {
michael@0 251 quanta += 1;
michael@0 252 for (let i = leftover; i < 5; i++)
michael@0 253 bytes += "\0";
michael@0 254 }
michael@0 255
michael@0 256 // Chop the string into quanta of 5 bytes (40 bits). Each quantum
michael@0 257 // is turned into 8 characters from the 32 character base.
michael@0 258 let ret = "";
michael@0 259 for (let i = 0; i < bytes.length; i += 5) {
michael@0 260 let c = [byte.charCodeAt() for each (byte in bytes.slice(i, i + 5))];
michael@0 261 ret += key[c[0] >> 3]
michael@0 262 + key[((c[0] << 2) & 0x1f) | (c[1] >> 6)]
michael@0 263 + key[(c[1] >> 1) & 0x1f]
michael@0 264 + key[((c[1] << 4) & 0x1f) | (c[2] >> 4)]
michael@0 265 + key[((c[2] << 1) & 0x1f) | (c[3] >> 7)]
michael@0 266 + key[(c[3] >> 2) & 0x1f]
michael@0 267 + key[((c[3] << 3) & 0x1f) | (c[4] >> 5)]
michael@0 268 + key[c[4] & 0x1f];
michael@0 269 }
michael@0 270
michael@0 271 switch (leftover) {
michael@0 272 case 1:
michael@0 273 return ret.slice(0, -6) + "======";
michael@0 274 case 2:
michael@0 275 return ret.slice(0, -4) + "====";
michael@0 276 case 3:
michael@0 277 return ret.slice(0, -3) + "===";
michael@0 278 case 4:
michael@0 279 return ret.slice(0, -1) + "=";
michael@0 280 default:
michael@0 281 return ret;
michael@0 282 }
michael@0 283 },
michael@0 284
michael@0 285 /**
michael@0 286 * Base32 decode (RFC 4648) a string.
michael@0 287 */
michael@0 288 decodeBase32: function decodeBase32(str) {
michael@0 289 const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
michael@0 290
michael@0 291 let padChar = str.indexOf("=");
michael@0 292 let chars = (padChar == -1) ? str.length : padChar;
michael@0 293 let bytes = Math.floor(chars * 5 / 8);
michael@0 294 let blocks = Math.ceil(chars / 8);
michael@0 295
michael@0 296 // Process a chunk of 5 bytes / 8 characters.
michael@0 297 // The processing of this is known in advance,
michael@0 298 // so avoid arithmetic!
michael@0 299 function processBlock(ret, cOffset, rOffset) {
michael@0 300 let c, val;
michael@0 301
michael@0 302 // N.B., this relies on
michael@0 303 // undefined | foo == foo.
michael@0 304 function accumulate(val) {
michael@0 305 ret[rOffset] |= val;
michael@0 306 }
michael@0 307
michael@0 308 function advance() {
michael@0 309 c = str[cOffset++];
michael@0 310 if (!c || c == "" || c == "=") // Easier than range checking.
michael@0 311 throw "Done"; // Will be caught far away.
michael@0 312 val = key.indexOf(c);
michael@0 313 if (val == -1)
michael@0 314 throw "Unknown character in base32: " + c;
michael@0 315 }
michael@0 316
michael@0 317 // Handle a left shift, restricted to bytes.
michael@0 318 function left(octet, shift)
michael@0 319 (octet << shift) & 0xff;
michael@0 320
michael@0 321 advance();
michael@0 322 accumulate(left(val, 3));
michael@0 323 advance();
michael@0 324 accumulate(val >> 2);
michael@0 325 ++rOffset;
michael@0 326 accumulate(left(val, 6));
michael@0 327 advance();
michael@0 328 accumulate(left(val, 1));
michael@0 329 advance();
michael@0 330 accumulate(val >> 4);
michael@0 331 ++rOffset;
michael@0 332 accumulate(left(val, 4));
michael@0 333 advance();
michael@0 334 accumulate(val >> 1);
michael@0 335 ++rOffset;
michael@0 336 accumulate(left(val, 7));
michael@0 337 advance();
michael@0 338 accumulate(left(val, 2));
michael@0 339 advance();
michael@0 340 accumulate(val >> 3);
michael@0 341 ++rOffset;
michael@0 342 accumulate(left(val, 5));
michael@0 343 advance();
michael@0 344 accumulate(val);
michael@0 345 ++rOffset;
michael@0 346 }
michael@0 347
michael@0 348 // Our output. Define to be explicit (and maybe the compiler will be smart).
michael@0 349 let ret = new Array(bytes);
michael@0 350 let i = 0;
michael@0 351 let cOff = 0;
michael@0 352 let rOff = 0;
michael@0 353
michael@0 354 for (; i < blocks; ++i) {
michael@0 355 try {
michael@0 356 processBlock(ret, cOff, rOff);
michael@0 357 } catch (ex) {
michael@0 358 // Handle the detection of padding.
michael@0 359 if (ex == "Done")
michael@0 360 break;
michael@0 361 throw ex;
michael@0 362 }
michael@0 363 cOff += 8;
michael@0 364 rOff += 5;
michael@0 365 }
michael@0 366
michael@0 367 // Slice in case our shift overflowed to the right.
michael@0 368 return CommonUtils.byteArrayToString(ret.slice(0, bytes));
michael@0 369 },
michael@0 370
michael@0 371 /**
michael@0 372 * Trim excess padding from a Base64 string and atob().
michael@0 373 *
michael@0 374 * See bug 562431 comment 4.
michael@0 375 */
michael@0 376 safeAtoB: function safeAtoB(b64) {
michael@0 377 let len = b64.length;
michael@0 378 let over = len % 4;
michael@0 379 return over ? atob(b64.substr(0, len - over)) : atob(b64);
michael@0 380 },
michael@0 381
michael@0 382 /**
michael@0 383 * Parses a JSON file from disk using OS.File and promises.
michael@0 384 *
michael@0 385 * @param path the file to read. Will be passed to `OS.File.read()`.
michael@0 386 * @return a promise that resolves to the JSON contents of the named file.
michael@0 387 */
michael@0 388 readJSON: function(path) {
michael@0 389 return OS.File.read(path, { encoding: "utf-8" }).then((data) => {
michael@0 390 return JSON.parse(data);
michael@0 391 });
michael@0 392 },
michael@0 393
michael@0 394 /**
michael@0 395 * Write a JSON object to the named file using OS.File and promises.
michael@0 396 *
michael@0 397 * @param contents a JS object. Will be serialized.
michael@0 398 * @param path the path of the file to write.
michael@0 399 * @return a promise, as produced by OS.File.writeAtomic.
michael@0 400 */
michael@0 401 writeJSON: function(contents, path) {
michael@0 402 let encoder = new TextEncoder();
michael@0 403 let array = encoder.encode(JSON.stringify(contents));
michael@0 404 return OS.File.writeAtomic(path, array, {tmpPath: path + ".tmp"});
michael@0 405 },
michael@0 406
michael@0 407
michael@0 408 /**
michael@0 409 * Ensure that the specified value is defined in integer milliseconds since
michael@0 410 * UNIX epoch.
michael@0 411 *
michael@0 412 * This throws an error if the value is not an integer, is negative, or looks
michael@0 413 * like seconds, not milliseconds.
michael@0 414 *
michael@0 415 * If the value is null or 0, no exception is raised.
michael@0 416 *
michael@0 417 * @param value
michael@0 418 * Value to validate.
michael@0 419 */
michael@0 420 ensureMillisecondsTimestamp: function ensureMillisecondsTimestamp(value) {
michael@0 421 if (!value) {
michael@0 422 return;
michael@0 423 }
michael@0 424
michael@0 425 if (!/^[0-9]+$/.test(value)) {
michael@0 426 throw new Error("Timestamp value is not a positive integer: " + value);
michael@0 427 }
michael@0 428
michael@0 429 let intValue = parseInt(value, 10);
michael@0 430
michael@0 431 if (!intValue) {
michael@0 432 return;
michael@0 433 }
michael@0 434
michael@0 435 // Catch what looks like seconds, not milliseconds.
michael@0 436 if (intValue < 10000000000) {
michael@0 437 throw new Error("Timestamp appears to be in seconds: " + intValue);
michael@0 438 }
michael@0 439 },
michael@0 440
michael@0 441 /**
michael@0 442 * Read bytes from an nsIInputStream into a string.
michael@0 443 *
michael@0 444 * @param stream
michael@0 445 * (nsIInputStream) Stream to read from.
michael@0 446 * @param count
michael@0 447 * (number) Integer number of bytes to read. If not defined, or
michael@0 448 * 0, all available input is read.
michael@0 449 */
michael@0 450 readBytesFromInputStream: function readBytesFromInputStream(stream, count) {
michael@0 451 let BinaryInputStream = Components.Constructor(
michael@0 452 "@mozilla.org/binaryinputstream;1",
michael@0 453 "nsIBinaryInputStream",
michael@0 454 "setInputStream");
michael@0 455 if (!count) {
michael@0 456 count = stream.available();
michael@0 457 }
michael@0 458
michael@0 459 return new BinaryInputStream(stream).readBytes(count);
michael@0 460 },
michael@0 461
michael@0 462 /**
michael@0 463 * Generate a new UUID using nsIUUIDGenerator.
michael@0 464 *
michael@0 465 * Example value: "1e00a2e2-1570-443e-bf5e-000354124234"
michael@0 466 *
michael@0 467 * @return string A hex-formatted UUID string.
michael@0 468 */
michael@0 469 generateUUID: function generateUUID() {
michael@0 470 let uuid = Cc["@mozilla.org/uuid-generator;1"]
michael@0 471 .getService(Ci.nsIUUIDGenerator)
michael@0 472 .generateUUID()
michael@0 473 .toString();
michael@0 474
michael@0 475 return uuid.substring(1, uuid.length - 1);
michael@0 476 },
michael@0 477
michael@0 478 /**
michael@0 479 * Obtain an epoch value from a preference.
michael@0 480 *
michael@0 481 * This reads a string preference and returns an integer. The string
michael@0 482 * preference is expected to contain the integer milliseconds since epoch.
michael@0 483 * For best results, only read preferences that have been saved with
michael@0 484 * setDatePref().
michael@0 485 *
michael@0 486 * We need to store times as strings because integer preferences are only
michael@0 487 * 32 bits and likely overflow most dates.
michael@0 488 *
michael@0 489 * If the pref contains a non-integer value, the specified default value will
michael@0 490 * be returned.
michael@0 491 *
michael@0 492 * @param branch
michael@0 493 * (Preferences) Branch from which to retrieve preference.
michael@0 494 * @param pref
michael@0 495 * (string) The preference to read from.
michael@0 496 * @param def
michael@0 497 * (Number) The default value to use if the preference is not defined.
michael@0 498 * @param log
michael@0 499 * (Log.Logger) Logger to write warnings to.
michael@0 500 */
michael@0 501 getEpochPref: function getEpochPref(branch, pref, def=0, log=null) {
michael@0 502 if (!Number.isInteger(def)) {
michael@0 503 throw new Error("Default value is not a number: " + def);
michael@0 504 }
michael@0 505
michael@0 506 let valueStr = branch.get(pref, null);
michael@0 507
michael@0 508 if (valueStr !== null) {
michael@0 509 let valueInt = parseInt(valueStr, 10);
michael@0 510 if (Number.isNaN(valueInt)) {
michael@0 511 if (log) {
michael@0 512 log.warn("Preference value is not an integer. Using default. " +
michael@0 513 pref + "=" + valueStr + " -> " + def);
michael@0 514 }
michael@0 515
michael@0 516 return def;
michael@0 517 }
michael@0 518
michael@0 519 return valueInt;
michael@0 520 }
michael@0 521
michael@0 522 return def;
michael@0 523 },
michael@0 524
michael@0 525 /**
michael@0 526 * Obtain a Date from a preference.
michael@0 527 *
michael@0 528 * This is a wrapper around getEpochPref. It converts the value to a Date
michael@0 529 * instance and performs simple range checking.
michael@0 530 *
michael@0 531 * The range checking ensures the date is newer than the oldestYear
michael@0 532 * parameter.
michael@0 533 *
michael@0 534 * @param branch
michael@0 535 * (Preferences) Branch from which to read preference.
michael@0 536 * @param pref
michael@0 537 * (string) The preference from which to read.
michael@0 538 * @param def
michael@0 539 * (Number) The default value (in milliseconds) if the preference is
michael@0 540 * not defined or invalid.
michael@0 541 * @param log
michael@0 542 * (Log.Logger) Logger to write warnings to.
michael@0 543 * @param oldestYear
michael@0 544 * (Number) Oldest year to accept in read values.
michael@0 545 */
michael@0 546 getDatePref: function getDatePref(branch, pref, def=0, log=null,
michael@0 547 oldestYear=2010) {
michael@0 548
michael@0 549 let valueInt = this.getEpochPref(branch, pref, def, log);
michael@0 550 let date = new Date(valueInt);
michael@0 551
michael@0 552 if (valueInt == def || date.getFullYear() >= oldestYear) {
michael@0 553 return date;
michael@0 554 }
michael@0 555
michael@0 556 if (log) {
michael@0 557 log.warn("Unexpected old date seen in pref. Returning default: " +
michael@0 558 pref + "=" + date + " -> " + def);
michael@0 559 }
michael@0 560
michael@0 561 return new Date(def);
michael@0 562 },
michael@0 563
michael@0 564 /**
michael@0 565 * Store a Date in a preference.
michael@0 566 *
michael@0 567 * This is the opposite of getDatePref(). The same notes apply.
michael@0 568 *
michael@0 569 * If the range check fails, an Error will be thrown instead of a default
michael@0 570 * value silently being used.
michael@0 571 *
michael@0 572 * @param branch
michael@0 573 * (Preference) Branch from which to read preference.
michael@0 574 * @param pref
michael@0 575 * (string) Name of preference to write to.
michael@0 576 * @param date
michael@0 577 * (Date) The value to save.
michael@0 578 * @param oldestYear
michael@0 579 * (Number) The oldest year to accept for values.
michael@0 580 */
michael@0 581 setDatePref: function setDatePref(branch, pref, date, oldestYear=2010) {
michael@0 582 if (date.getFullYear() < oldestYear) {
michael@0 583 throw new Error("Trying to set " + pref + " to a very old time: " +
michael@0 584 date + ". The current time is " + new Date() +
michael@0 585 ". Is the system clock wrong?");
michael@0 586 }
michael@0 587
michael@0 588 branch.set(pref, "" + date.getTime());
michael@0 589 },
michael@0 590
michael@0 591 /**
michael@0 592 * Convert a string between two encodings.
michael@0 593 *
michael@0 594 * Output is only guaranteed if the input stream is composed of octets. If
michael@0 595 * the input string has characters with values larger than 255, data loss
michael@0 596 * will occur.
michael@0 597 *
michael@0 598 * The returned string is guaranteed to consist of character codes no greater
michael@0 599 * than 255.
michael@0 600 *
michael@0 601 * @param s
michael@0 602 * (string) The source string to convert.
michael@0 603 * @param source
michael@0 604 * (string) The current encoding of the string.
michael@0 605 * @param dest
michael@0 606 * (string) The target encoding of the string.
michael@0 607 *
michael@0 608 * @return string
michael@0 609 */
michael@0 610 convertString: function convertString(s, source, dest) {
michael@0 611 if (!s) {
michael@0 612 throw new Error("Input string must be defined.");
michael@0 613 }
michael@0 614
michael@0 615 let is = Cc["@mozilla.org/io/string-input-stream;1"]
michael@0 616 .createInstance(Ci.nsIStringInputStream);
michael@0 617 is.setData(s, s.length);
michael@0 618
michael@0 619 let listener = Cc["@mozilla.org/network/stream-loader;1"]
michael@0 620 .createInstance(Ci.nsIStreamLoader);
michael@0 621
michael@0 622 let result;
michael@0 623
michael@0 624 listener.init({
michael@0 625 onStreamComplete: function onStreamComplete(loader, context, status,
michael@0 626 length, data) {
michael@0 627 result = String.fromCharCode.apply(this, data);
michael@0 628 },
michael@0 629 });
michael@0 630
michael@0 631 let converter = this._converterService.asyncConvertData(source, dest,
michael@0 632 listener, null);
michael@0 633 converter.onStartRequest(null, null);
michael@0 634 converter.onDataAvailable(null, null, is, 0, s.length);
michael@0 635 converter.onStopRequest(null, null, null);
michael@0 636
michael@0 637 return result;
michael@0 638 },
michael@0 639 };
michael@0 640
michael@0 641 XPCOMUtils.defineLazyGetter(CommonUtils, "_utf8Converter", function() {
michael@0 642 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 643 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 644 converter.charset = "UTF-8";
michael@0 645 return converter;
michael@0 646 });
michael@0 647
michael@0 648 XPCOMUtils.defineLazyGetter(CommonUtils, "_converterService", function() {
michael@0 649 return Cc["@mozilla.org/streamConverters;1"]
michael@0 650 .getService(Ci.nsIStreamConverterService);
michael@0 651 });

mercurial