1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/common/utils.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,651 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = ["CommonUtils"]; 1.11 + 1.12 +Cu.import("resource://gre/modules/Promise.jsm"); 1.13 +Cu.import("resource://gre/modules/Services.jsm"); 1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.15 +Cu.import("resource://gre/modules/osfile.jsm") 1.16 +Cu.import("resource://gre/modules/Log.jsm"); 1.17 + 1.18 +this.CommonUtils = { 1.19 + /* 1.20 + * Set manipulation methods. These should be lifted into toolkit, or added to 1.21 + * `Set` itself. 1.22 + */ 1.23 + 1.24 + /** 1.25 + * Return elements of `a` or `b`. 1.26 + */ 1.27 + union: function (a, b) { 1.28 + let out = new Set(a); 1.29 + for (let x of b) { 1.30 + out.add(x); 1.31 + } 1.32 + return out; 1.33 + }, 1.34 + 1.35 + /** 1.36 + * Return elements of `a` that are not present in `b`. 1.37 + */ 1.38 + difference: function (a, b) { 1.39 + let out = new Set(a); 1.40 + for (let x of b) { 1.41 + out.delete(x); 1.42 + } 1.43 + return out; 1.44 + }, 1.45 + 1.46 + /** 1.47 + * Return elements of `a` that are also in `b`. 1.48 + */ 1.49 + intersection: function (a, b) { 1.50 + let out = new Set(); 1.51 + for (let x of a) { 1.52 + if (b.has(x)) { 1.53 + out.add(x); 1.54 + } 1.55 + } 1.56 + return out; 1.57 + }, 1.58 + 1.59 + /** 1.60 + * Return true if `a` and `b` are the same size, and 1.61 + * every element of `a` is in `b`. 1.62 + */ 1.63 + setEqual: function (a, b) { 1.64 + if (a.size != b.size) { 1.65 + return false; 1.66 + } 1.67 + for (let x of a) { 1.68 + if (!b.has(x)) { 1.69 + return false; 1.70 + } 1.71 + } 1.72 + return true; 1.73 + }, 1.74 + 1.75 + // Import these from Log.jsm for backward compatibility 1.76 + exceptionStr: Log.exceptionStr, 1.77 + stackTrace: Log.stackTrace, 1.78 + 1.79 + /** 1.80 + * Encode byte string as base64URL (RFC 4648). 1.81 + * 1.82 + * @param bytes 1.83 + * (string) Raw byte string to encode. 1.84 + * @param pad 1.85 + * (bool) Whether to include padding characters (=). Defaults 1.86 + * to true for historical reasons. 1.87 + */ 1.88 + encodeBase64URL: function encodeBase64URL(bytes, pad=true) { 1.89 + let s = btoa(bytes).replace("+", "-", "g").replace("/", "_", "g"); 1.90 + 1.91 + if (!pad) { 1.92 + s = s.replace("=", "", "g"); 1.93 + } 1.94 + 1.95 + return s; 1.96 + }, 1.97 + 1.98 + /** 1.99 + * Create a nsIURI instance from a string. 1.100 + */ 1.101 + makeURI: function makeURI(URIString) { 1.102 + if (!URIString) 1.103 + return null; 1.104 + try { 1.105 + return Services.io.newURI(URIString, null, null); 1.106 + } catch (e) { 1.107 + let log = Log.repository.getLogger("Common.Utils"); 1.108 + log.debug("Could not create URI: " + CommonUtils.exceptionStr(e)); 1.109 + return null; 1.110 + } 1.111 + }, 1.112 + 1.113 + /** 1.114 + * Execute a function on the next event loop tick. 1.115 + * 1.116 + * @param callback 1.117 + * Function to invoke. 1.118 + * @param thisObj [optional] 1.119 + * Object to bind the callback to. 1.120 + */ 1.121 + nextTick: function nextTick(callback, thisObj) { 1.122 + if (thisObj) { 1.123 + callback = callback.bind(thisObj); 1.124 + } 1.125 + Services.tm.currentThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL); 1.126 + }, 1.127 + 1.128 + /** 1.129 + * Return a promise resolving on some later tick. 1.130 + * 1.131 + * This a wrapper around Promise.resolve() that prevents stack 1.132 + * accumulation and prevents callers from accidentally relying on 1.133 + * same-tick promise resolution. 1.134 + */ 1.135 + laterTickResolvingPromise: function (value, prototype) { 1.136 + let deferred = Promise.defer(prototype); 1.137 + this.nextTick(deferred.resolve.bind(deferred, value)); 1.138 + return deferred.promise; 1.139 + }, 1.140 + 1.141 + /** 1.142 + * Spin the event loop and return once the next tick is executed. 1.143 + * 1.144 + * This is an evil function and should not be used in production code. It 1.145 + * exists in this module for ease-of-use. 1.146 + */ 1.147 + waitForNextTick: function waitForNextTick() { 1.148 + let cb = Async.makeSyncCallback(); 1.149 + this.nextTick(cb); 1.150 + Async.waitForSyncCallback(cb); 1.151 + 1.152 + return; 1.153 + }, 1.154 + 1.155 + /** 1.156 + * Return a timer that is scheduled to call the callback after waiting the 1.157 + * provided time or as soon as possible. The timer will be set as a property 1.158 + * of the provided object with the given timer name. 1.159 + */ 1.160 + namedTimer: function namedTimer(callback, wait, thisObj, name) { 1.161 + if (!thisObj || !name) { 1.162 + throw "You must provide both an object and a property name for the timer!"; 1.163 + } 1.164 + 1.165 + // Delay an existing timer if it exists 1.166 + if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) { 1.167 + thisObj[name].delay = wait; 1.168 + return; 1.169 + } 1.170 + 1.171 + // Create a special timer that we can add extra properties 1.172 + let timer = {}; 1.173 + timer.__proto__ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.174 + 1.175 + // Provide an easy way to clear out the timer 1.176 + timer.clear = function() { 1.177 + thisObj[name] = null; 1.178 + timer.cancel(); 1.179 + }; 1.180 + 1.181 + // Initialize the timer with a smart callback 1.182 + timer.initWithCallback({ 1.183 + notify: function notify() { 1.184 + // Clear out the timer once it's been triggered 1.185 + timer.clear(); 1.186 + callback.call(thisObj, timer); 1.187 + } 1.188 + }, wait, timer.TYPE_ONE_SHOT); 1.189 + 1.190 + return thisObj[name] = timer; 1.191 + }, 1.192 + 1.193 + encodeUTF8: function encodeUTF8(str) { 1.194 + try { 1.195 + str = this._utf8Converter.ConvertFromUnicode(str); 1.196 + return str + this._utf8Converter.Finish(); 1.197 + } catch (ex) { 1.198 + return null; 1.199 + } 1.200 + }, 1.201 + 1.202 + decodeUTF8: function decodeUTF8(str) { 1.203 + try { 1.204 + str = this._utf8Converter.ConvertToUnicode(str); 1.205 + return str + this._utf8Converter.Finish(); 1.206 + } catch (ex) { 1.207 + return null; 1.208 + } 1.209 + }, 1.210 + 1.211 + byteArrayToString: function byteArrayToString(bytes) { 1.212 + return [String.fromCharCode(byte) for each (byte in bytes)].join(""); 1.213 + }, 1.214 + 1.215 + stringToByteArray: function stringToByteArray(bytesString) { 1.216 + return [String.charCodeAt(byte) for each (byte in bytesString)]; 1.217 + }, 1.218 + 1.219 + bytesAsHex: function bytesAsHex(bytes) { 1.220 + return [("0" + bytes.charCodeAt(byte).toString(16)).slice(-2) 1.221 + for (byte in bytes)].join(""); 1.222 + }, 1.223 + 1.224 + stringAsHex: function stringAsHex(str) { 1.225 + return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str)); 1.226 + }, 1.227 + 1.228 + stringToBytes: function stringToBytes(str) { 1.229 + return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str)); 1.230 + }, 1.231 + 1.232 + hexToBytes: function hexToBytes(str) { 1.233 + let bytes = []; 1.234 + for (let i = 0; i < str.length - 1; i += 2) { 1.235 + bytes.push(parseInt(str.substr(i, 2), 16)); 1.236 + } 1.237 + return String.fromCharCode.apply(String, bytes); 1.238 + }, 1.239 + 1.240 + hexAsString: function hexAsString(hex) { 1.241 + return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex)); 1.242 + }, 1.243 + 1.244 + /** 1.245 + * Base32 encode (RFC 4648) a string 1.246 + */ 1.247 + encodeBase32: function encodeBase32(bytes) { 1.248 + const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 1.249 + let quanta = Math.floor(bytes.length / 5); 1.250 + let leftover = bytes.length % 5; 1.251 + 1.252 + // Pad the last quantum with zeros so the length is a multiple of 5. 1.253 + if (leftover) { 1.254 + quanta += 1; 1.255 + for (let i = leftover; i < 5; i++) 1.256 + bytes += "\0"; 1.257 + } 1.258 + 1.259 + // Chop the string into quanta of 5 bytes (40 bits). Each quantum 1.260 + // is turned into 8 characters from the 32 character base. 1.261 + let ret = ""; 1.262 + for (let i = 0; i < bytes.length; i += 5) { 1.263 + let c = [byte.charCodeAt() for each (byte in bytes.slice(i, i + 5))]; 1.264 + ret += key[c[0] >> 3] 1.265 + + key[((c[0] << 2) & 0x1f) | (c[1] >> 6)] 1.266 + + key[(c[1] >> 1) & 0x1f] 1.267 + + key[((c[1] << 4) & 0x1f) | (c[2] >> 4)] 1.268 + + key[((c[2] << 1) & 0x1f) | (c[3] >> 7)] 1.269 + + key[(c[3] >> 2) & 0x1f] 1.270 + + key[((c[3] << 3) & 0x1f) | (c[4] >> 5)] 1.271 + + key[c[4] & 0x1f]; 1.272 + } 1.273 + 1.274 + switch (leftover) { 1.275 + case 1: 1.276 + return ret.slice(0, -6) + "======"; 1.277 + case 2: 1.278 + return ret.slice(0, -4) + "===="; 1.279 + case 3: 1.280 + return ret.slice(0, -3) + "==="; 1.281 + case 4: 1.282 + return ret.slice(0, -1) + "="; 1.283 + default: 1.284 + return ret; 1.285 + } 1.286 + }, 1.287 + 1.288 + /** 1.289 + * Base32 decode (RFC 4648) a string. 1.290 + */ 1.291 + decodeBase32: function decodeBase32(str) { 1.292 + const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; 1.293 + 1.294 + let padChar = str.indexOf("="); 1.295 + let chars = (padChar == -1) ? str.length : padChar; 1.296 + let bytes = Math.floor(chars * 5 / 8); 1.297 + let blocks = Math.ceil(chars / 8); 1.298 + 1.299 + // Process a chunk of 5 bytes / 8 characters. 1.300 + // The processing of this is known in advance, 1.301 + // so avoid arithmetic! 1.302 + function processBlock(ret, cOffset, rOffset) { 1.303 + let c, val; 1.304 + 1.305 + // N.B., this relies on 1.306 + // undefined | foo == foo. 1.307 + function accumulate(val) { 1.308 + ret[rOffset] |= val; 1.309 + } 1.310 + 1.311 + function advance() { 1.312 + c = str[cOffset++]; 1.313 + if (!c || c == "" || c == "=") // Easier than range checking. 1.314 + throw "Done"; // Will be caught far away. 1.315 + val = key.indexOf(c); 1.316 + if (val == -1) 1.317 + throw "Unknown character in base32: " + c; 1.318 + } 1.319 + 1.320 + // Handle a left shift, restricted to bytes. 1.321 + function left(octet, shift) 1.322 + (octet << shift) & 0xff; 1.323 + 1.324 + advance(); 1.325 + accumulate(left(val, 3)); 1.326 + advance(); 1.327 + accumulate(val >> 2); 1.328 + ++rOffset; 1.329 + accumulate(left(val, 6)); 1.330 + advance(); 1.331 + accumulate(left(val, 1)); 1.332 + advance(); 1.333 + accumulate(val >> 4); 1.334 + ++rOffset; 1.335 + accumulate(left(val, 4)); 1.336 + advance(); 1.337 + accumulate(val >> 1); 1.338 + ++rOffset; 1.339 + accumulate(left(val, 7)); 1.340 + advance(); 1.341 + accumulate(left(val, 2)); 1.342 + advance(); 1.343 + accumulate(val >> 3); 1.344 + ++rOffset; 1.345 + accumulate(left(val, 5)); 1.346 + advance(); 1.347 + accumulate(val); 1.348 + ++rOffset; 1.349 + } 1.350 + 1.351 + // Our output. Define to be explicit (and maybe the compiler will be smart). 1.352 + let ret = new Array(bytes); 1.353 + let i = 0; 1.354 + let cOff = 0; 1.355 + let rOff = 0; 1.356 + 1.357 + for (; i < blocks; ++i) { 1.358 + try { 1.359 + processBlock(ret, cOff, rOff); 1.360 + } catch (ex) { 1.361 + // Handle the detection of padding. 1.362 + if (ex == "Done") 1.363 + break; 1.364 + throw ex; 1.365 + } 1.366 + cOff += 8; 1.367 + rOff += 5; 1.368 + } 1.369 + 1.370 + // Slice in case our shift overflowed to the right. 1.371 + return CommonUtils.byteArrayToString(ret.slice(0, bytes)); 1.372 + }, 1.373 + 1.374 + /** 1.375 + * Trim excess padding from a Base64 string and atob(). 1.376 + * 1.377 + * See bug 562431 comment 4. 1.378 + */ 1.379 + safeAtoB: function safeAtoB(b64) { 1.380 + let len = b64.length; 1.381 + let over = len % 4; 1.382 + return over ? atob(b64.substr(0, len - over)) : atob(b64); 1.383 + }, 1.384 + 1.385 + /** 1.386 + * Parses a JSON file from disk using OS.File and promises. 1.387 + * 1.388 + * @param path the file to read. Will be passed to `OS.File.read()`. 1.389 + * @return a promise that resolves to the JSON contents of the named file. 1.390 + */ 1.391 + readJSON: function(path) { 1.392 + return OS.File.read(path, { encoding: "utf-8" }).then((data) => { 1.393 + return JSON.parse(data); 1.394 + }); 1.395 + }, 1.396 + 1.397 + /** 1.398 + * Write a JSON object to the named file using OS.File and promises. 1.399 + * 1.400 + * @param contents a JS object. Will be serialized. 1.401 + * @param path the path of the file to write. 1.402 + * @return a promise, as produced by OS.File.writeAtomic. 1.403 + */ 1.404 + writeJSON: function(contents, path) { 1.405 + let encoder = new TextEncoder(); 1.406 + let array = encoder.encode(JSON.stringify(contents)); 1.407 + return OS.File.writeAtomic(path, array, {tmpPath: path + ".tmp"}); 1.408 + }, 1.409 + 1.410 + 1.411 + /** 1.412 + * Ensure that the specified value is defined in integer milliseconds since 1.413 + * UNIX epoch. 1.414 + * 1.415 + * This throws an error if the value is not an integer, is negative, or looks 1.416 + * like seconds, not milliseconds. 1.417 + * 1.418 + * If the value is null or 0, no exception is raised. 1.419 + * 1.420 + * @param value 1.421 + * Value to validate. 1.422 + */ 1.423 + ensureMillisecondsTimestamp: function ensureMillisecondsTimestamp(value) { 1.424 + if (!value) { 1.425 + return; 1.426 + } 1.427 + 1.428 + if (!/^[0-9]+$/.test(value)) { 1.429 + throw new Error("Timestamp value is not a positive integer: " + value); 1.430 + } 1.431 + 1.432 + let intValue = parseInt(value, 10); 1.433 + 1.434 + if (!intValue) { 1.435 + return; 1.436 + } 1.437 + 1.438 + // Catch what looks like seconds, not milliseconds. 1.439 + if (intValue < 10000000000) { 1.440 + throw new Error("Timestamp appears to be in seconds: " + intValue); 1.441 + } 1.442 + }, 1.443 + 1.444 + /** 1.445 + * Read bytes from an nsIInputStream into a string. 1.446 + * 1.447 + * @param stream 1.448 + * (nsIInputStream) Stream to read from. 1.449 + * @param count 1.450 + * (number) Integer number of bytes to read. If not defined, or 1.451 + * 0, all available input is read. 1.452 + */ 1.453 + readBytesFromInputStream: function readBytesFromInputStream(stream, count) { 1.454 + let BinaryInputStream = Components.Constructor( 1.455 + "@mozilla.org/binaryinputstream;1", 1.456 + "nsIBinaryInputStream", 1.457 + "setInputStream"); 1.458 + if (!count) { 1.459 + count = stream.available(); 1.460 + } 1.461 + 1.462 + return new BinaryInputStream(stream).readBytes(count); 1.463 + }, 1.464 + 1.465 + /** 1.466 + * Generate a new UUID using nsIUUIDGenerator. 1.467 + * 1.468 + * Example value: "1e00a2e2-1570-443e-bf5e-000354124234" 1.469 + * 1.470 + * @return string A hex-formatted UUID string. 1.471 + */ 1.472 + generateUUID: function generateUUID() { 1.473 + let uuid = Cc["@mozilla.org/uuid-generator;1"] 1.474 + .getService(Ci.nsIUUIDGenerator) 1.475 + .generateUUID() 1.476 + .toString(); 1.477 + 1.478 + return uuid.substring(1, uuid.length - 1); 1.479 + }, 1.480 + 1.481 + /** 1.482 + * Obtain an epoch value from a preference. 1.483 + * 1.484 + * This reads a string preference and returns an integer. The string 1.485 + * preference is expected to contain the integer milliseconds since epoch. 1.486 + * For best results, only read preferences that have been saved with 1.487 + * setDatePref(). 1.488 + * 1.489 + * We need to store times as strings because integer preferences are only 1.490 + * 32 bits and likely overflow most dates. 1.491 + * 1.492 + * If the pref contains a non-integer value, the specified default value will 1.493 + * be returned. 1.494 + * 1.495 + * @param branch 1.496 + * (Preferences) Branch from which to retrieve preference. 1.497 + * @param pref 1.498 + * (string) The preference to read from. 1.499 + * @param def 1.500 + * (Number) The default value to use if the preference is not defined. 1.501 + * @param log 1.502 + * (Log.Logger) Logger to write warnings to. 1.503 + */ 1.504 + getEpochPref: function getEpochPref(branch, pref, def=0, log=null) { 1.505 + if (!Number.isInteger(def)) { 1.506 + throw new Error("Default value is not a number: " + def); 1.507 + } 1.508 + 1.509 + let valueStr = branch.get(pref, null); 1.510 + 1.511 + if (valueStr !== null) { 1.512 + let valueInt = parseInt(valueStr, 10); 1.513 + if (Number.isNaN(valueInt)) { 1.514 + if (log) { 1.515 + log.warn("Preference value is not an integer. Using default. " + 1.516 + pref + "=" + valueStr + " -> " + def); 1.517 + } 1.518 + 1.519 + return def; 1.520 + } 1.521 + 1.522 + return valueInt; 1.523 + } 1.524 + 1.525 + return def; 1.526 + }, 1.527 + 1.528 + /** 1.529 + * Obtain a Date from a preference. 1.530 + * 1.531 + * This is a wrapper around getEpochPref. It converts the value to a Date 1.532 + * instance and performs simple range checking. 1.533 + * 1.534 + * The range checking ensures the date is newer than the oldestYear 1.535 + * parameter. 1.536 + * 1.537 + * @param branch 1.538 + * (Preferences) Branch from which to read preference. 1.539 + * @param pref 1.540 + * (string) The preference from which to read. 1.541 + * @param def 1.542 + * (Number) The default value (in milliseconds) if the preference is 1.543 + * not defined or invalid. 1.544 + * @param log 1.545 + * (Log.Logger) Logger to write warnings to. 1.546 + * @param oldestYear 1.547 + * (Number) Oldest year to accept in read values. 1.548 + */ 1.549 + getDatePref: function getDatePref(branch, pref, def=0, log=null, 1.550 + oldestYear=2010) { 1.551 + 1.552 + let valueInt = this.getEpochPref(branch, pref, def, log); 1.553 + let date = new Date(valueInt); 1.554 + 1.555 + if (valueInt == def || date.getFullYear() >= oldestYear) { 1.556 + return date; 1.557 + } 1.558 + 1.559 + if (log) { 1.560 + log.warn("Unexpected old date seen in pref. Returning default: " + 1.561 + pref + "=" + date + " -> " + def); 1.562 + } 1.563 + 1.564 + return new Date(def); 1.565 + }, 1.566 + 1.567 + /** 1.568 + * Store a Date in a preference. 1.569 + * 1.570 + * This is the opposite of getDatePref(). The same notes apply. 1.571 + * 1.572 + * If the range check fails, an Error will be thrown instead of a default 1.573 + * value silently being used. 1.574 + * 1.575 + * @param branch 1.576 + * (Preference) Branch from which to read preference. 1.577 + * @param pref 1.578 + * (string) Name of preference to write to. 1.579 + * @param date 1.580 + * (Date) The value to save. 1.581 + * @param oldestYear 1.582 + * (Number) The oldest year to accept for values. 1.583 + */ 1.584 + setDatePref: function setDatePref(branch, pref, date, oldestYear=2010) { 1.585 + if (date.getFullYear() < oldestYear) { 1.586 + throw new Error("Trying to set " + pref + " to a very old time: " + 1.587 + date + ". The current time is " + new Date() + 1.588 + ". Is the system clock wrong?"); 1.589 + } 1.590 + 1.591 + branch.set(pref, "" + date.getTime()); 1.592 + }, 1.593 + 1.594 + /** 1.595 + * Convert a string between two encodings. 1.596 + * 1.597 + * Output is only guaranteed if the input stream is composed of octets. If 1.598 + * the input string has characters with values larger than 255, data loss 1.599 + * will occur. 1.600 + * 1.601 + * The returned string is guaranteed to consist of character codes no greater 1.602 + * than 255. 1.603 + * 1.604 + * @param s 1.605 + * (string) The source string to convert. 1.606 + * @param source 1.607 + * (string) The current encoding of the string. 1.608 + * @param dest 1.609 + * (string) The target encoding of the string. 1.610 + * 1.611 + * @return string 1.612 + */ 1.613 + convertString: function convertString(s, source, dest) { 1.614 + if (!s) { 1.615 + throw new Error("Input string must be defined."); 1.616 + } 1.617 + 1.618 + let is = Cc["@mozilla.org/io/string-input-stream;1"] 1.619 + .createInstance(Ci.nsIStringInputStream); 1.620 + is.setData(s, s.length); 1.621 + 1.622 + let listener = Cc["@mozilla.org/network/stream-loader;1"] 1.623 + .createInstance(Ci.nsIStreamLoader); 1.624 + 1.625 + let result; 1.626 + 1.627 + listener.init({ 1.628 + onStreamComplete: function onStreamComplete(loader, context, status, 1.629 + length, data) { 1.630 + result = String.fromCharCode.apply(this, data); 1.631 + }, 1.632 + }); 1.633 + 1.634 + let converter = this._converterService.asyncConvertData(source, dest, 1.635 + listener, null); 1.636 + converter.onStartRequest(null, null); 1.637 + converter.onDataAvailable(null, null, is, 0, s.length); 1.638 + converter.onStopRequest(null, null, null); 1.639 + 1.640 + return result; 1.641 + }, 1.642 +}; 1.643 + 1.644 +XPCOMUtils.defineLazyGetter(CommonUtils, "_utf8Converter", function() { 1.645 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.646 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.647 + converter.charset = "UTF-8"; 1.648 + return converter; 1.649 +}); 1.650 + 1.651 +XPCOMUtils.defineLazyGetter(CommonUtils, "_converterService", function() { 1.652 + return Cc["@mozilla.org/streamConverters;1"] 1.653 + .getService(Ci.nsIStreamConverterService); 1.654 +});