toolkit/devtools/webconsole/utils.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/devtools/webconsole/utils.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1769 @@
     1.4 +/* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
     1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.8 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +"use strict";
    1.11 +
    1.12 +const {Cc, Ci, Cu, components} = require("chrome");
    1.13 +
    1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +
    1.16 +loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
    1.17 +loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
    1.18 +
    1.19 +// TODO: Bug 842672 - toolkit/ imports modules from browser/.
    1.20 +// Note that these are only used in JSTermHelpers, see $0 and pprint().
    1.21 +loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
    1.22 +loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
    1.23 +loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
    1.24 +loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
    1.25 +
    1.26 +// Match the function name from the result of toString() or toSource().
    1.27 +//
    1.28 +// Examples:
    1.29 +// (function foobar(a, b) { ...
    1.30 +// function foobar2(a) { ...
    1.31 +// function() { ...
    1.32 +const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
    1.33 +
    1.34 +// Match the function arguments from the result of toString() or toSource().
    1.35 +const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
    1.36 +
    1.37 +let WebConsoleUtils = {
    1.38 +  /**
    1.39 +   * Convenience function to unwrap a wrapped object.
    1.40 +   *
    1.41 +   * @param aObject the object to unwrap.
    1.42 +   * @return aObject unwrapped.
    1.43 +   */
    1.44 +  unwrap: function WCU_unwrap(aObject)
    1.45 +  {
    1.46 +    try {
    1.47 +      return XPCNativeWrapper.unwrap(aObject);
    1.48 +    }
    1.49 +    catch (ex) {
    1.50 +      return aObject;
    1.51 +    }
    1.52 +  },
    1.53 +
    1.54 +  /**
    1.55 +   * Wrap a string in an nsISupportsString object.
    1.56 +   *
    1.57 +   * @param string aString
    1.58 +   * @return nsISupportsString
    1.59 +   */
    1.60 +  supportsString: function WCU_supportsString(aString)
    1.61 +  {
    1.62 +    let str = Cc["@mozilla.org/supports-string;1"].
    1.63 +              createInstance(Ci.nsISupportsString);
    1.64 +    str.data = aString;
    1.65 +    return str;
    1.66 +  },
    1.67 +
    1.68 +  /**
    1.69 +   * Clone an object.
    1.70 +   *
    1.71 +   * @param object aObject
    1.72 +   *        The object you want cloned.
    1.73 +   * @param boolean aRecursive
    1.74 +   *        Tells if you want to dig deeper into the object, to clone
    1.75 +   *        recursively.
    1.76 +   * @param function [aFilter]
    1.77 +   *        Optional, filter function, called for every property. Three
    1.78 +   *        arguments are passed: key, value and object. Return true if the
    1.79 +   *        property should be added to the cloned object. Return false to skip
    1.80 +   *        the property.
    1.81 +   * @return object
    1.82 +   *         The cloned object.
    1.83 +   */
    1.84 +  cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter)
    1.85 +  {
    1.86 +    if (typeof aObject != "object") {
    1.87 +      return aObject;
    1.88 +    }
    1.89 +
    1.90 +    let temp;
    1.91 +
    1.92 +    if (Array.isArray(aObject)) {
    1.93 +      temp = [];
    1.94 +      Array.forEach(aObject, function(aValue, aIndex) {
    1.95 +        if (!aFilter || aFilter(aIndex, aValue, aObject)) {
    1.96 +          temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue);
    1.97 +        }
    1.98 +      });
    1.99 +    }
   1.100 +    else {
   1.101 +      temp = {};
   1.102 +      for (let key in aObject) {
   1.103 +        let value = aObject[key];
   1.104 +        if (aObject.hasOwnProperty(key) &&
   1.105 +            (!aFilter || aFilter(key, value, aObject))) {
   1.106 +          temp[key] = aRecursive ? WCU_cloneObject(value) : value;
   1.107 +        }
   1.108 +      }
   1.109 +    }
   1.110 +
   1.111 +    return temp;
   1.112 +  },
   1.113 +
   1.114 +  /**
   1.115 +   * Copies certain style attributes from one element to another.
   1.116 +   *
   1.117 +   * @param nsIDOMNode aFrom
   1.118 +   *        The target node.
   1.119 +   * @param nsIDOMNode aTo
   1.120 +   *        The destination node.
   1.121 +   */
   1.122 +  copyTextStyles: function WCU_copyTextStyles(aFrom, aTo)
   1.123 +  {
   1.124 +    let win = aFrom.ownerDocument.defaultView;
   1.125 +    let style = win.getComputedStyle(aFrom);
   1.126 +    aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
   1.127 +    aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
   1.128 +    aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
   1.129 +    aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
   1.130 +  },
   1.131 +
   1.132 +  /**
   1.133 +   * Gets the ID of the inner window of this DOM window.
   1.134 +   *
   1.135 +   * @param nsIDOMWindow aWindow
   1.136 +   * @return integer
   1.137 +   *         Inner ID for the given aWindow.
   1.138 +   */
   1.139 +  getInnerWindowId: function WCU_getInnerWindowId(aWindow)
   1.140 +  {
   1.141 +    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
   1.142 +             getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
   1.143 +  },
   1.144 +
   1.145 +  /**
   1.146 +   * Recursively gather a list of inner window ids given a
   1.147 +   * top level window.
   1.148 +   *
   1.149 +   * @param nsIDOMWindow aWindow
   1.150 +   * @return Array
   1.151 +   *         list of inner window ids.
   1.152 +   */
   1.153 +  getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow)
   1.154 +  {
   1.155 +    let innerWindowID = this.getInnerWindowId(aWindow);
   1.156 +    let ids = [innerWindowID];
   1.157 +
   1.158 +    if (aWindow.frames) {
   1.159 +      for (let i = 0; i < aWindow.frames.length; i++) {
   1.160 +        let frame = aWindow.frames[i];
   1.161 +        ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
   1.162 +      }
   1.163 +    }
   1.164 +
   1.165 +    return ids;
   1.166 +  },
   1.167 +
   1.168 +
   1.169 +  /**
   1.170 +   * Gets the ID of the outer window of this DOM window.
   1.171 +   *
   1.172 +   * @param nsIDOMWindow aWindow
   1.173 +   * @return integer
   1.174 +   *         Outer ID for the given aWindow.
   1.175 +   */
   1.176 +  getOuterWindowId: function WCU_getOuterWindowId(aWindow)
   1.177 +  {
   1.178 +    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
   1.179 +           getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
   1.180 +  },
   1.181 +
   1.182 +  /**
   1.183 +   * Abbreviates the given source URL so that it can be displayed flush-right
   1.184 +   * without being too distracting.
   1.185 +   *
   1.186 +   * @param string aSourceURL
   1.187 +   *        The source URL to shorten.
   1.188 +   * @param object [aOptions]
   1.189 +   *        Options:
   1.190 +   *        - onlyCropQuery: boolean that tells if the URL abbreviation function
   1.191 +   *        should only remove the query parameters and the hash fragment from
   1.192 +   *        the given URL.
   1.193 +   * @return string
   1.194 +   *         The abbreviated form of the source URL.
   1.195 +   */
   1.196 +  abbreviateSourceURL:
   1.197 +  function WCU_abbreviateSourceURL(aSourceURL, aOptions = {})
   1.198 +  {
   1.199 +    if (!aOptions.onlyCropQuery && aSourceURL.substr(0, 5) == "data:") {
   1.200 +      let commaIndex = aSourceURL.indexOf(",");
   1.201 +      if (commaIndex > -1) {
   1.202 +        aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
   1.203 +      }
   1.204 +    }
   1.205 +
   1.206 +    // Remove any query parameters.
   1.207 +    let hookIndex = aSourceURL.indexOf("?");
   1.208 +    if (hookIndex > -1) {
   1.209 +      aSourceURL = aSourceURL.substring(0, hookIndex);
   1.210 +    }
   1.211 +
   1.212 +    // Remove any hash fragments.
   1.213 +    let hashIndex = aSourceURL.indexOf("#");
   1.214 +    if (hashIndex > -1) {
   1.215 +      aSourceURL = aSourceURL.substring(0, hashIndex);
   1.216 +    }
   1.217 +
   1.218 +    // Remove a trailing "/".
   1.219 +    if (aSourceURL[aSourceURL.length - 1] == "/") {
   1.220 +      aSourceURL = aSourceURL.replace(/\/+$/, "");
   1.221 +    }
   1.222 +
   1.223 +    // Remove all but the last path component.
   1.224 +    if (!aOptions.onlyCropQuery) {
   1.225 +      let slashIndex = aSourceURL.lastIndexOf("/");
   1.226 +      if (slashIndex > -1) {
   1.227 +        aSourceURL = aSourceURL.substring(slashIndex + 1);
   1.228 +      }
   1.229 +    }
   1.230 +
   1.231 +    return aSourceURL;
   1.232 +  },
   1.233 +
   1.234 +  /**
   1.235 +   * Tells if the given function is native or not.
   1.236 +   *
   1.237 +   * @param function aFunction
   1.238 +   *        The function you want to check if it is native or not.
   1.239 +   * @return boolean
   1.240 +   *         True if the given function is native, false otherwise.
   1.241 +   */
   1.242 +  isNativeFunction: function WCU_isNativeFunction(aFunction)
   1.243 +  {
   1.244 +    return typeof aFunction == "function" && !("prototype" in aFunction);
   1.245 +  },
   1.246 +
   1.247 +  /**
   1.248 +   * Tells if the given property of the provided object is a non-native getter or
   1.249 +   * not.
   1.250 +   *
   1.251 +   * @param object aObject
   1.252 +   *        The object that contains the property.
   1.253 +   * @param string aProp
   1.254 +   *        The property you want to check if it is a getter or not.
   1.255 +   * @return boolean
   1.256 +   *         True if the given property is a getter, false otherwise.
   1.257 +   */
   1.258 +  isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp)
   1.259 +  {
   1.260 +    if (typeof aObject != "object") {
   1.261 +      return false;
   1.262 +    }
   1.263 +    let desc = this.getPropertyDescriptor(aObject, aProp);
   1.264 +    return desc && desc.get && !this.isNativeFunction(desc.get);
   1.265 +  },
   1.266 +
   1.267 +  /**
   1.268 +   * Get the property descriptor for the given object.
   1.269 +   *
   1.270 +   * @param object aObject
   1.271 +   *        The object that contains the property.
   1.272 +   * @param string aProp
   1.273 +   *        The property you want to get the descriptor for.
   1.274 +   * @return object
   1.275 +   *         Property descriptor.
   1.276 +   */
   1.277 +  getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp)
   1.278 +  {
   1.279 +    let desc = null;
   1.280 +    while (aObject) {
   1.281 +      try {
   1.282 +        if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) {
   1.283 +          break;
   1.284 +        }
   1.285 +      }
   1.286 +      catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
   1.287 +                    ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" ||
   1.288 +                    ex.name == "TypeError")) {
   1.289 +        // Native getters throw here. See bug 520882.
   1.290 +        // null throws TypeError.
   1.291 +      }
   1.292 +      try {
   1.293 +        aObject = Object.getPrototypeOf(aObject);
   1.294 +      }
   1.295 +      catch (ex if (ex.name == "TypeError")) {
   1.296 +        return desc;
   1.297 +      }
   1.298 +    }
   1.299 +    return desc;
   1.300 +  },
   1.301 +
   1.302 +  /**
   1.303 +   * Sort function for object properties.
   1.304 +   *
   1.305 +   * @param object a
   1.306 +   *        Property descriptor.
   1.307 +   * @param object b
   1.308 +   *        Property descriptor.
   1.309 +   * @return integer
   1.310 +   *         -1 if a.name < b.name,
   1.311 +   *         1 if a.name > b.name,
   1.312 +   *         0 otherwise.
   1.313 +   */
   1.314 +  propertiesSort: function WCU_propertiesSort(a, b)
   1.315 +  {
   1.316 +    // Convert the pair.name to a number for later sorting.
   1.317 +    let aNumber = parseFloat(a.name);
   1.318 +    let bNumber = parseFloat(b.name);
   1.319 +
   1.320 +    // Sort numbers.
   1.321 +    if (!isNaN(aNumber) && isNaN(bNumber)) {
   1.322 +      return -1;
   1.323 +    }
   1.324 +    else if (isNaN(aNumber) && !isNaN(bNumber)) {
   1.325 +      return 1;
   1.326 +    }
   1.327 +    else if (!isNaN(aNumber) && !isNaN(bNumber)) {
   1.328 +      return aNumber - bNumber;
   1.329 +    }
   1.330 +    // Sort string.
   1.331 +    else if (a.name < b.name) {
   1.332 +      return -1;
   1.333 +    }
   1.334 +    else if (a.name > b.name) {
   1.335 +      return 1;
   1.336 +    }
   1.337 +    else {
   1.338 +      return 0;
   1.339 +    }
   1.340 +  },
   1.341 +
   1.342 +  /**
   1.343 +   * Create a grip for the given value. If the value is an object,
   1.344 +   * an object wrapper will be created.
   1.345 +   *
   1.346 +   * @param mixed aValue
   1.347 +   *        The value you want to create a grip for, before sending it to the
   1.348 +   *        client.
   1.349 +   * @param function aObjectWrapper
   1.350 +   *        If the value is an object then the aObjectWrapper function is
   1.351 +   *        invoked to give us an object grip. See this.getObjectGrip().
   1.352 +   * @return mixed
   1.353 +   *         The value grip.
   1.354 +   */
   1.355 +  createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
   1.356 +  {
   1.357 +    switch (typeof aValue) {
   1.358 +      case "boolean":
   1.359 +        return aValue;
   1.360 +      case "string":
   1.361 +        return aObjectWrapper(aValue);
   1.362 +      case "number":
   1.363 +        if (aValue === Infinity) {
   1.364 +          return { type: "Infinity" };
   1.365 +        }
   1.366 +        else if (aValue === -Infinity) {
   1.367 +          return { type: "-Infinity" };
   1.368 +        }
   1.369 +        else if (Number.isNaN(aValue)) {
   1.370 +          return { type: "NaN" };
   1.371 +        }
   1.372 +        else if (!aValue && 1 / aValue === -Infinity) {
   1.373 +          return { type: "-0" };
   1.374 +        }
   1.375 +        return aValue;
   1.376 +      case "undefined":
   1.377 +        return { type: "undefined" };
   1.378 +      case "object":
   1.379 +        if (aValue === null) {
   1.380 +          return { type: "null" };
   1.381 +        }
   1.382 +      case "function":
   1.383 +        return aObjectWrapper(aValue);
   1.384 +      default:
   1.385 +        Cu.reportError("Failed to provide a grip for value of " + typeof aValue
   1.386 +                       + ": " + aValue);
   1.387 +        return null;
   1.388 +    }
   1.389 +  },
   1.390 +
   1.391 +  /**
   1.392 +   * Check if the given object is an iterator or a generator.
   1.393 +   *
   1.394 +   * @param object aObject
   1.395 +   *        The object you want to check.
   1.396 +   * @return boolean
   1.397 +   *         True if the given object is an iterator or a generator, otherwise
   1.398 +   *         false is returned.
   1.399 +   */
   1.400 +  isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject)
   1.401 +  {
   1.402 +    if (aObject === null) {
   1.403 +      return false;
   1.404 +    }
   1.405 +
   1.406 +    if (typeof aObject == "object") {
   1.407 +      if (typeof aObject.__iterator__ == "function" ||
   1.408 +          aObject.constructor && aObject.constructor.name == "Iterator") {
   1.409 +        return true;
   1.410 +      }
   1.411 +
   1.412 +      try {
   1.413 +        let str = aObject.toString();
   1.414 +        if (typeof aObject.next == "function" &&
   1.415 +            str.indexOf("[object Generator") == 0) {
   1.416 +          return true;
   1.417 +        }
   1.418 +      }
   1.419 +      catch (ex) {
   1.420 +        // window.history.next throws in the typeof check above.
   1.421 +        return false;
   1.422 +      }
   1.423 +    }
   1.424 +
   1.425 +    return false;
   1.426 +  },
   1.427 +
   1.428 +  /**
   1.429 +   * Determine if the given request mixes HTTP with HTTPS content.
   1.430 +   *
   1.431 +   * @param string aRequest
   1.432 +   *        Location of the requested content.
   1.433 +   * @param string aLocation
   1.434 +   *        Location of the current page.
   1.435 +   * @return boolean
   1.436 +   *         True if the content is mixed, false if not.
   1.437 +   */
   1.438 +  isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation)
   1.439 +  {
   1.440 +    try {
   1.441 +      let requestURI = Services.io.newURI(aRequest, null, null);
   1.442 +      let contentURI = Services.io.newURI(aLocation, null, null);
   1.443 +      return (contentURI.scheme == "https" && requestURI.scheme != "https");
   1.444 +    }
   1.445 +    catch (ex) {
   1.446 +      return false;
   1.447 +    }
   1.448 +  },
   1.449 +
   1.450 +  /**
   1.451 +   * Helper function to deduce the name of the provided function.
   1.452 +   *
   1.453 +   * @param funtion aFunction
   1.454 +   *        The function whose name will be returned.
   1.455 +   * @return string
   1.456 +   *         Function name.
   1.457 +   */
   1.458 +  getFunctionName: function WCF_getFunctionName(aFunction)
   1.459 +  {
   1.460 +    let name = null;
   1.461 +    if (aFunction.name) {
   1.462 +      name = aFunction.name;
   1.463 +    }
   1.464 +    else {
   1.465 +      let desc;
   1.466 +      try {
   1.467 +        desc = aFunction.getOwnPropertyDescriptor("displayName");
   1.468 +      }
   1.469 +      catch (ex) { }
   1.470 +      if (desc && typeof desc.value == "string") {
   1.471 +        name = desc.value;
   1.472 +      }
   1.473 +    }
   1.474 +    if (!name) {
   1.475 +      try {
   1.476 +        let str = (aFunction.toString() || aFunction.toSource()) + "";
   1.477 +        name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
   1.478 +      }
   1.479 +      catch (ex) { }
   1.480 +    }
   1.481 +    return name;
   1.482 +  },
   1.483 +
   1.484 +  /**
   1.485 +   * Get the object class name. For example, the |window| object has the Window
   1.486 +   * class name (based on [object Window]).
   1.487 +   *
   1.488 +   * @param object aObject
   1.489 +   *        The object you want to get the class name for.
   1.490 +   * @return string
   1.491 +   *         The object class name.
   1.492 +   */
   1.493 +  getObjectClassName: function WCU_getObjectClassName(aObject)
   1.494 +  {
   1.495 +    if (aObject === null) {
   1.496 +      return "null";
   1.497 +    }
   1.498 +    if (aObject === undefined) {
   1.499 +      return "undefined";
   1.500 +    }
   1.501 +
   1.502 +    let type = typeof aObject;
   1.503 +    if (type != "object") {
   1.504 +      // Grip class names should start with an uppercase letter.
   1.505 +      return type.charAt(0).toUpperCase() + type.substr(1);
   1.506 +    }
   1.507 +
   1.508 +    let className;
   1.509 +
   1.510 +    try {
   1.511 +      className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1];
   1.512 +      if (!className) {
   1.513 +        className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1];
   1.514 +      }
   1.515 +      if (!className && typeof aObject.constructor == "function") {
   1.516 +        className = this.getFunctionName(aObject.constructor);
   1.517 +      }
   1.518 +    }
   1.519 +    catch (ex) { }
   1.520 +
   1.521 +    return className;
   1.522 +  },
   1.523 +
   1.524 +  /**
   1.525 +   * Check if the given value is a grip with an actor.
   1.526 +   *
   1.527 +   * @param mixed aGrip
   1.528 +   *        Value you want to check if it is a grip with an actor.
   1.529 +   * @return boolean
   1.530 +   *         True if the given value is a grip with an actor.
   1.531 +   */
   1.532 +  isActorGrip: function WCU_isActorGrip(aGrip)
   1.533 +  {
   1.534 +    return aGrip && typeof(aGrip) == "object" && aGrip.actor;
   1.535 +  },
   1.536 +};
   1.537 +exports.Utils = WebConsoleUtils;
   1.538 +
   1.539 +//////////////////////////////////////////////////////////////////////////
   1.540 +// Localization
   1.541 +//////////////////////////////////////////////////////////////////////////
   1.542 +
   1.543 +WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
   1.544 +{
   1.545 +  this._bundleUri = aBundleURI;
   1.546 +};
   1.547 +
   1.548 +WebConsoleUtils.l10n.prototype = {
   1.549 +  _stringBundle: null,
   1.550 +
   1.551 +  get stringBundle()
   1.552 +  {
   1.553 +    if (!this._stringBundle) {
   1.554 +      this._stringBundle = Services.strings.createBundle(this._bundleUri);
   1.555 +    }
   1.556 +    return this._stringBundle;
   1.557 +  },
   1.558 +
   1.559 +  /**
   1.560 +   * Generates a formatted timestamp string for displaying in console messages.
   1.561 +   *
   1.562 +   * @param integer [aMilliseconds]
   1.563 +   *        Optional, allows you to specify the timestamp in milliseconds since
   1.564 +   *        the UNIX epoch.
   1.565 +   * @return string
   1.566 +   *         The timestamp formatted for display.
   1.567 +   */
   1.568 +  timestampString: function WCU_l10n_timestampString(aMilliseconds)
   1.569 +  {
   1.570 +    let d = new Date(aMilliseconds ? aMilliseconds : null);
   1.571 +    let hours = d.getHours(), minutes = d.getMinutes();
   1.572 +    let seconds = d.getSeconds(), milliseconds = d.getMilliseconds();
   1.573 +    let parameters = [hours, minutes, seconds, milliseconds];
   1.574 +    return this.getFormatStr("timestampFormat", parameters);
   1.575 +  },
   1.576 +
   1.577 +  /**
   1.578 +   * Retrieve a localized string.
   1.579 +   *
   1.580 +   * @param string aName
   1.581 +   *        The string name you want from the Web Console string bundle.
   1.582 +   * @return string
   1.583 +   *         The localized string.
   1.584 +   */
   1.585 +  getStr: function WCU_l10n_getStr(aName)
   1.586 +  {
   1.587 +    let result;
   1.588 +    try {
   1.589 +      result = this.stringBundle.GetStringFromName(aName);
   1.590 +    }
   1.591 +    catch (ex) {
   1.592 +      Cu.reportError("Failed to get string: " + aName);
   1.593 +      throw ex;
   1.594 +    }
   1.595 +    return result;
   1.596 +  },
   1.597 +
   1.598 +  /**
   1.599 +   * Retrieve a localized string formatted with values coming from the given
   1.600 +   * array.
   1.601 +   *
   1.602 +   * @param string aName
   1.603 +   *        The string name you want from the Web Console string bundle.
   1.604 +   * @param array aArray
   1.605 +   *        The array of values you want in the formatted string.
   1.606 +   * @return string
   1.607 +   *         The formatted local string.
   1.608 +   */
   1.609 +  getFormatStr: function WCU_l10n_getFormatStr(aName, aArray)
   1.610 +  {
   1.611 +    let result;
   1.612 +    try {
   1.613 +      result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
   1.614 +    }
   1.615 +    catch (ex) {
   1.616 +      Cu.reportError("Failed to format string: " + aName);
   1.617 +      throw ex;
   1.618 +    }
   1.619 +    return result;
   1.620 +  },
   1.621 +};
   1.622 +
   1.623 +
   1.624 +//////////////////////////////////////////////////////////////////////////
   1.625 +// JS Completer
   1.626 +//////////////////////////////////////////////////////////////////////////
   1.627 +
   1.628 +(function _JSPP(WCU) {
   1.629 +const STATE_NORMAL = 0;
   1.630 +const STATE_QUOTE = 2;
   1.631 +const STATE_DQUOTE = 3;
   1.632 +
   1.633 +const OPEN_BODY = "{[(".split("");
   1.634 +const CLOSE_BODY = "}])".split("");
   1.635 +const OPEN_CLOSE_BODY = {
   1.636 +  "{": "}",
   1.637 +  "[": "]",
   1.638 +  "(": ")",
   1.639 +};
   1.640 +
   1.641 +const MAX_COMPLETIONS = 1500;
   1.642 +
   1.643 +/**
   1.644 + * Analyses a given string to find the last statement that is interesting for
   1.645 + * later completion.
   1.646 + *
   1.647 + * @param   string aStr
   1.648 + *          A string to analyse.
   1.649 + *
   1.650 + * @returns object
   1.651 + *          If there was an error in the string detected, then a object like
   1.652 + *
   1.653 + *            { err: "ErrorMesssage" }
   1.654 + *
   1.655 + *          is returned, otherwise a object like
   1.656 + *
   1.657 + *            {
   1.658 + *              state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
   1.659 + *              startPos: index of where the last statement begins
   1.660 + *            }
   1.661 + */
   1.662 +function findCompletionBeginning(aStr)
   1.663 +{
   1.664 +  let bodyStack = [];
   1.665 +
   1.666 +  let state = STATE_NORMAL;
   1.667 +  let start = 0;
   1.668 +  let c;
   1.669 +  for (let i = 0; i < aStr.length; i++) {
   1.670 +    c = aStr[i];
   1.671 +
   1.672 +    switch (state) {
   1.673 +      // Normal JS state.
   1.674 +      case STATE_NORMAL:
   1.675 +        if (c == '"') {
   1.676 +          state = STATE_DQUOTE;
   1.677 +        }
   1.678 +        else if (c == "'") {
   1.679 +          state = STATE_QUOTE;
   1.680 +        }
   1.681 +        else if (c == ";") {
   1.682 +          start = i + 1;
   1.683 +        }
   1.684 +        else if (c == " ") {
   1.685 +          start = i + 1;
   1.686 +        }
   1.687 +        else if (OPEN_BODY.indexOf(c) != -1) {
   1.688 +          bodyStack.push({
   1.689 +            token: c,
   1.690 +            start: start
   1.691 +          });
   1.692 +          start = i + 1;
   1.693 +        }
   1.694 +        else if (CLOSE_BODY.indexOf(c) != -1) {
   1.695 +          var last = bodyStack.pop();
   1.696 +          if (!last || OPEN_CLOSE_BODY[last.token] != c) {
   1.697 +            return {
   1.698 +              err: "syntax error"
   1.699 +            };
   1.700 +          }
   1.701 +          if (c == "}") {
   1.702 +            start = i + 1;
   1.703 +          }
   1.704 +          else {
   1.705 +            start = last.start;
   1.706 +          }
   1.707 +        }
   1.708 +        break;
   1.709 +
   1.710 +      // Double quote state > " <
   1.711 +      case STATE_DQUOTE:
   1.712 +        if (c == "\\") {
   1.713 +          i++;
   1.714 +        }
   1.715 +        else if (c == "\n") {
   1.716 +          return {
   1.717 +            err: "unterminated string literal"
   1.718 +          };
   1.719 +        }
   1.720 +        else if (c == '"') {
   1.721 +          state = STATE_NORMAL;
   1.722 +        }
   1.723 +        break;
   1.724 +
   1.725 +      // Single quote state > ' <
   1.726 +      case STATE_QUOTE:
   1.727 +        if (c == "\\") {
   1.728 +          i++;
   1.729 +        }
   1.730 +        else if (c == "\n") {
   1.731 +          return {
   1.732 +            err: "unterminated string literal"
   1.733 +          };
   1.734 +        }
   1.735 +        else if (c == "'") {
   1.736 +          state = STATE_NORMAL;
   1.737 +        }
   1.738 +        break;
   1.739 +    }
   1.740 +  }
   1.741 +
   1.742 +  return {
   1.743 +    state: state,
   1.744 +    startPos: start
   1.745 +  };
   1.746 +}
   1.747 +
   1.748 +/**
   1.749 + * Provides a list of properties, that are possible matches based on the passed
   1.750 + * Debugger.Environment/Debugger.Object and inputValue.
   1.751 + *
   1.752 + * @param object aDbgObject
   1.753 + *        When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
   1.754 + *        It is null if the debugger is paused.
   1.755 + * @param object anEnvironment
   1.756 + *        When the debugger is paused this Debugger.Environment is the scope for autocompletion.
   1.757 + *        It is null if the debugger is not paused.
   1.758 + * @param string aInputValue
   1.759 + *        Value that should be completed.
   1.760 + * @param number [aCursor=aInputValue.length]
   1.761 + *        Optional offset in the input where the cursor is located. If this is
   1.762 + *        omitted then the cursor is assumed to be at the end of the input
   1.763 + *        value.
   1.764 + * @returns null or object
   1.765 + *          If no completion valued could be computed, null is returned,
   1.766 + *          otherwise a object with the following form is returned:
   1.767 + *            {
   1.768 + *              matches: [ string, string, string ],
   1.769 + *              matchProp: Last part of the inputValue that was used to find
   1.770 + *                         the matches-strings.
   1.771 + *            }
   1.772 + */
   1.773 +function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
   1.774 +{
   1.775 +  if (aCursor === undefined) {
   1.776 +    aCursor = aInputValue.length;
   1.777 +  }
   1.778 +
   1.779 +  let inputValue = aInputValue.substring(0, aCursor);
   1.780 +
   1.781 +  // Analyse the inputValue and find the beginning of the last part that
   1.782 +  // should be completed.
   1.783 +  let beginning = findCompletionBeginning(inputValue);
   1.784 +
   1.785 +  // There was an error analysing the string.
   1.786 +  if (beginning.err) {
   1.787 +    return null;
   1.788 +  }
   1.789 +
   1.790 +  // If the current state is not STATE_NORMAL, then we are inside of an string
   1.791 +  // which means that no completion is possible.
   1.792 +  if (beginning.state != STATE_NORMAL) {
   1.793 +    return null;
   1.794 +  }
   1.795 +
   1.796 +  let completionPart = inputValue.substring(beginning.startPos);
   1.797 +
   1.798 +  // Don't complete on just an empty string.
   1.799 +  if (completionPart.trim() == "") {
   1.800 +    return null;
   1.801 +  }
   1.802 +
   1.803 +  let lastDot = completionPart.lastIndexOf(".");
   1.804 +  if (lastDot > 0 &&
   1.805 +      (completionPart[0] == "'" || completionPart[0] == '"') &&
   1.806 +      completionPart[lastDot - 1] == completionPart[0]) {
   1.807 +    // We are completing a string literal.
   1.808 +    let matchProp = completionPart.slice(lastDot + 1);
   1.809 +    return getMatchedProps(String.prototype, matchProp);
   1.810 +  }
   1.811 +
   1.812 +  // We are completing a variable / a property lookup.
   1.813 +  let properties = completionPart.split(".");
   1.814 +  let matchProp = properties.pop().trimLeft();
   1.815 +  let obj = aDbgObject;
   1.816 +
   1.817 +  // The first property must be found in the environment if the debugger is
   1.818 +  // paused.
   1.819 +  if (anEnvironment) {
   1.820 +    if (properties.length == 0) {
   1.821 +      return getMatchedPropsInEnvironment(anEnvironment, matchProp);
   1.822 +    }
   1.823 +    obj = getVariableInEnvironment(anEnvironment, properties.shift());
   1.824 +  }
   1.825 +
   1.826 +  if (!isObjectUsable(obj)) {
   1.827 +    return null;
   1.828 +  }
   1.829 +
   1.830 +  // We get the rest of the properties recursively starting from the Debugger.Object
   1.831 +  // that wraps the first property
   1.832 +  for (let prop of properties) {
   1.833 +    prop = prop.trim();
   1.834 +    if (!prop) {
   1.835 +      return null;
   1.836 +    }
   1.837 +
   1.838 +    if (/\[\d+\]$/.test(prop)) {
   1.839 +      // The property to autocomplete is a member of array. For example
   1.840 +      // list[i][j]..[n]. Traverse the array to get the actual element.
   1.841 +      obj = getArrayMemberProperty(obj, prop);
   1.842 +    }
   1.843 +    else {
   1.844 +      obj = DevToolsUtils.getProperty(obj, prop);
   1.845 +    }
   1.846 +
   1.847 +    if (!isObjectUsable(obj)) {
   1.848 +      return null;
   1.849 +    }
   1.850 +  }
   1.851 +
   1.852 +  // If the final property is a primitive
   1.853 +  if (typeof obj != "object") {
   1.854 +    return getMatchedProps(obj, matchProp);
   1.855 +  }
   1.856 +
   1.857 +  return getMatchedPropsInDbgObject(obj, matchProp);
   1.858 +}
   1.859 +
   1.860 +/**
   1.861 + * Get the array member of aObj for the given aProp. For example, given
   1.862 + * aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
   1.863 + *
   1.864 + * @param object aObj
   1.865 + *        The object to operate on.
   1.866 + * @param string aProp
   1.867 + *        The property to return.
   1.868 + * @return null or Object
   1.869 + *         Returns null if the property couldn't be located. Otherwise the array
   1.870 + *         member identified by aProp.
   1.871 + */
   1.872 +function getArrayMemberProperty(aObj, aProp)
   1.873 +{
   1.874 +  // First get the array.
   1.875 +  let obj = aObj;
   1.876 +  let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
   1.877 +  obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
   1.878 +  if (!isObjectUsable(obj)) {
   1.879 +    return null;
   1.880 +  }
   1.881 +
   1.882 +  // Then traverse the list of indices to get the actual element.
   1.883 +  let result;
   1.884 +  let arrayIndicesRegex = /\[[^\]]*\]/g;
   1.885 +  while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
   1.886 +    let indexWithBrackets = result[0];
   1.887 +    let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
   1.888 +    let index = parseInt(indexAsText);
   1.889 +
   1.890 +    if (isNaN(index)) {
   1.891 +      return null;
   1.892 +    }
   1.893 +
   1.894 +    obj = DevToolsUtils.getProperty(obj, index);
   1.895 +
   1.896 +    if (!isObjectUsable(obj)) {
   1.897 +      return null;
   1.898 +    }
   1.899 +  }
   1.900 +
   1.901 +  return obj;
   1.902 +}
   1.903 +
   1.904 +/**
   1.905 + * Check if the given Debugger.Object can be used for autocomplete.
   1.906 + *
   1.907 + * @param Debugger.Object aObject
   1.908 + *        The Debugger.Object to check.
   1.909 + * @return boolean
   1.910 + *         True if further inspection into the object is possible, or false
   1.911 + *         otherwise.
   1.912 + */
   1.913 +function isObjectUsable(aObject)
   1.914 +{
   1.915 +  if (aObject == null) {
   1.916 +    return false;
   1.917 +  }
   1.918 +
   1.919 +  if (typeof aObject == "object" && aObject.class == "DeadObject") {
   1.920 +    return false;
   1.921 +  }
   1.922 +
   1.923 +  return true;
   1.924 +}
   1.925 +
   1.926 +/**
   1.927 + * @see getExactMatch_impl()
   1.928 + */
   1.929 +function getVariableInEnvironment(anEnvironment, aName)
   1.930 +{
   1.931 +  return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
   1.932 +}
   1.933 +
   1.934 +/**
   1.935 + * @see getMatchedProps_impl()
   1.936 + */
   1.937 +function getMatchedPropsInEnvironment(anEnvironment, aMatch)
   1.938 +{
   1.939 +  return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
   1.940 +}
   1.941 +
   1.942 +/**
   1.943 + * @see getMatchedProps_impl()
   1.944 + */
   1.945 +function getMatchedPropsInDbgObject(aDbgObject, aMatch)
   1.946 +{
   1.947 +  return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
   1.948 +}
   1.949 +
   1.950 +/**
   1.951 + * @see getMatchedProps_impl()
   1.952 + */
   1.953 +function getMatchedProps(aObj, aMatch)
   1.954 +{
   1.955 +  if (typeof aObj != "object") {
   1.956 +    aObj = aObj.constructor.prototype;
   1.957 +  }
   1.958 +  return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
   1.959 +}
   1.960 +
   1.961 +/**
   1.962 + * Get all properties in the given object (and its parent prototype chain) that
   1.963 + * match a given prefix.
   1.964 + *
   1.965 + * @param mixed aObj
   1.966 + *        Object whose properties we want to filter.
   1.967 + * @param string aMatch
   1.968 + *        Filter for properties that match this string.
   1.969 + * @return object
   1.970 + *         Object that contains the matchProp and the list of names.
   1.971 + */
   1.972 +function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
   1.973 +{
   1.974 +  let matches = new Set();
   1.975 +
   1.976 +  // We need to go up the prototype chain.
   1.977 +  let iter = chainIterator(aObj);
   1.978 +  for (let obj of iter) {
   1.979 +    let props = getProperties(obj);
   1.980 +    for (let prop of props) {
   1.981 +      if (prop.indexOf(aMatch) != 0) {
   1.982 +        continue;
   1.983 +      }
   1.984 +
   1.985 +      // If it is an array index, we can't take it.
   1.986 +      // This uses a trick: converting a string to a number yields NaN if
   1.987 +      // the operation failed, and NaN is not equal to itself.
   1.988 +      if (+prop != +prop) {
   1.989 +        matches.add(prop);
   1.990 +      }
   1.991 +
   1.992 +      if (matches.size > MAX_COMPLETIONS) {
   1.993 +        break;
   1.994 +      }
   1.995 +    }
   1.996 +
   1.997 +    if (matches.size > MAX_COMPLETIONS) {
   1.998 +      break;
   1.999 +    }
  1.1000 +  }
  1.1001 +
  1.1002 +  return {
  1.1003 +    matchProp: aMatch,
  1.1004 +    matches: [...matches],
  1.1005 +  };
  1.1006 +}
  1.1007 +
  1.1008 +/**
  1.1009 + * Returns a property value based on its name from the given object, by
  1.1010 + * recursively checking the object's prototype.
  1.1011 + *
  1.1012 + * @param object aObj
  1.1013 + *        An object to look the property into.
  1.1014 + * @param string aName
  1.1015 + *        The property that is looked up.
  1.1016 + * @returns object|undefined
  1.1017 + *        A Debugger.Object if the property exists in the object's prototype
  1.1018 + *        chain, undefined otherwise.
  1.1019 + */
  1.1020 +function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
  1.1021 +{
  1.1022 +  // We need to go up the prototype chain.
  1.1023 +  let iter = chainIterator(aObj);
  1.1024 +  for (let obj of iter) {
  1.1025 +    let prop = getProperty(obj, aName, aObj);
  1.1026 +    if (prop) {
  1.1027 +      return prop.value;
  1.1028 +    }
  1.1029 +  }
  1.1030 +  return undefined;
  1.1031 +}
  1.1032 +
  1.1033 +
  1.1034 +let JSObjectSupport = {
  1.1035 +  chainIterator: function(aObj)
  1.1036 +  {
  1.1037 +    while (aObj) {
  1.1038 +      yield aObj;
  1.1039 +      aObj = Object.getPrototypeOf(aObj);
  1.1040 +    }
  1.1041 +  },
  1.1042 +
  1.1043 +  getProperties: function(aObj)
  1.1044 +  {
  1.1045 +    return Object.getOwnPropertyNames(aObj);
  1.1046 +  },
  1.1047 +
  1.1048 +  getProperty: function()
  1.1049 +  {
  1.1050 +    // getProperty is unsafe with raw JS objects.
  1.1051 +    throw "Unimplemented!";
  1.1052 +  },
  1.1053 +};
  1.1054 +
  1.1055 +let DebuggerObjectSupport = {
  1.1056 +  chainIterator: function(aObj)
  1.1057 +  {
  1.1058 +    while (aObj) {
  1.1059 +      yield aObj;
  1.1060 +      aObj = aObj.proto;
  1.1061 +    }
  1.1062 +  },
  1.1063 +
  1.1064 +  getProperties: function(aObj)
  1.1065 +  {
  1.1066 +    return aObj.getOwnPropertyNames();
  1.1067 +  },
  1.1068 +
  1.1069 +  getProperty: function(aObj, aName, aRootObj)
  1.1070 +  {
  1.1071 +    // This is left unimplemented in favor to DevToolsUtils.getProperty().
  1.1072 +    throw "Unimplemented!";
  1.1073 +  },
  1.1074 +};
  1.1075 +
  1.1076 +let DebuggerEnvironmentSupport = {
  1.1077 +  chainIterator: function(aObj)
  1.1078 +  {
  1.1079 +    while (aObj) {
  1.1080 +      yield aObj;
  1.1081 +      aObj = aObj.parent;
  1.1082 +    }
  1.1083 +  },
  1.1084 +
  1.1085 +  getProperties: function(aObj)
  1.1086 +  {
  1.1087 +    return aObj.names();
  1.1088 +  },
  1.1089 +
  1.1090 +  getProperty: function(aObj, aName)
  1.1091 +  {
  1.1092 +    // TODO: we should use getVariableDescriptor() here - bug 725815.
  1.1093 +    let result = aObj.getVariable(aName);
  1.1094 +    // FIXME: Need actual UI, bug 941287.
  1.1095 +    if (result.optimizedOut || result.missingArguments) {
  1.1096 +      return null;
  1.1097 +    }
  1.1098 +    return result === undefined ? null : { value: result };
  1.1099 +  },
  1.1100 +};
  1.1101 +
  1.1102 +
  1.1103 +exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
  1.1104 +})(WebConsoleUtils);
  1.1105 +
  1.1106 +///////////////////////////////////////////////////////////////////////////////
  1.1107 +// The page errors listener
  1.1108 +///////////////////////////////////////////////////////////////////////////////
  1.1109 +
  1.1110 +/**
  1.1111 + * The nsIConsoleService listener. This is used to send all of the console
  1.1112 + * messages (JavaScript, CSS and more) to the remote Web Console instance.
  1.1113 + *
  1.1114 + * @constructor
  1.1115 + * @param nsIDOMWindow [aWindow]
  1.1116 + *        Optional - the window object for which we are created. This is used
  1.1117 + *        for filtering out messages that belong to other windows.
  1.1118 + * @param object aListener
  1.1119 + *        The listener object must have one method:
  1.1120 + *        - onConsoleServiceMessage(). This method is invoked with one argument,
  1.1121 + *        the nsIConsoleMessage, whenever a relevant message is received.
  1.1122 + */
  1.1123 +function ConsoleServiceListener(aWindow, aListener)
  1.1124 +{
  1.1125 +  this.window = aWindow;
  1.1126 +  this.listener = aListener;
  1.1127 +  if (this.window) {
  1.1128 +    this.layoutHelpers = new LayoutHelpers(this.window);
  1.1129 +  }
  1.1130 +}
  1.1131 +exports.ConsoleServiceListener = ConsoleServiceListener;
  1.1132 +
  1.1133 +ConsoleServiceListener.prototype =
  1.1134 +{
  1.1135 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
  1.1136 +
  1.1137 +  /**
  1.1138 +   * The content window for which we listen to page errors.
  1.1139 +   * @type nsIDOMWindow
  1.1140 +   */
  1.1141 +  window: null,
  1.1142 +
  1.1143 +  /**
  1.1144 +   * The listener object which is notified of messages from the console service.
  1.1145 +   * @type object
  1.1146 +   */
  1.1147 +  listener: null,
  1.1148 +
  1.1149 +  /**
  1.1150 +   * Initialize the nsIConsoleService listener.
  1.1151 +   */
  1.1152 +  init: function CSL_init()
  1.1153 +  {
  1.1154 +    Services.console.registerListener(this);
  1.1155 +  },
  1.1156 +
  1.1157 +  /**
  1.1158 +   * The nsIConsoleService observer. This method takes all the script error
  1.1159 +   * messages belonging to the current window and sends them to the remote Web
  1.1160 +   * Console instance.
  1.1161 +   *
  1.1162 +   * @param nsIConsoleMessage aMessage
  1.1163 +   *        The message object coming from the nsIConsoleService.
  1.1164 +   */
  1.1165 +  observe: function CSL_observe(aMessage)
  1.1166 +  {
  1.1167 +    if (!this.listener) {
  1.1168 +      return;
  1.1169 +    }
  1.1170 +
  1.1171 +    if (this.window) {
  1.1172 +      if (!(aMessage instanceof Ci.nsIScriptError) ||
  1.1173 +          !aMessage.outerWindowID ||
  1.1174 +          !this.isCategoryAllowed(aMessage.category)) {
  1.1175 +        return;
  1.1176 +      }
  1.1177 +
  1.1178 +      let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
  1.1179 +      if (!errorWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(errorWindow)) {
  1.1180 +        return;
  1.1181 +      }
  1.1182 +    }
  1.1183 +
  1.1184 +    this.listener.onConsoleServiceMessage(aMessage);
  1.1185 +  },
  1.1186 +
  1.1187 +  /**
  1.1188 +   * Check if the given message category is allowed to be tracked or not.
  1.1189 +   * We ignore chrome-originating errors as we only care about content.
  1.1190 +   *
  1.1191 +   * @param string aCategory
  1.1192 +   *        The message category you want to check.
  1.1193 +   * @return boolean
  1.1194 +   *         True if the category is allowed to be logged, false otherwise.
  1.1195 +   */
  1.1196 +  isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
  1.1197 +  {
  1.1198 +    if (!aCategory) {
  1.1199 +      return false;
  1.1200 +    }
  1.1201 +
  1.1202 +    switch (aCategory) {
  1.1203 +      case "XPConnect JavaScript":
  1.1204 +      case "component javascript":
  1.1205 +      case "chrome javascript":
  1.1206 +      case "chrome registration":
  1.1207 +      case "XBL":
  1.1208 +      case "XBL Prototype Handler":
  1.1209 +      case "XBL Content Sink":
  1.1210 +      case "xbl javascript":
  1.1211 +        return false;
  1.1212 +    }
  1.1213 +
  1.1214 +    return true;
  1.1215 +  },
  1.1216 +
  1.1217 +  /**
  1.1218 +   * Get the cached page errors for the current inner window and its (i)frames.
  1.1219 +   *
  1.1220 +   * @param boolean [aIncludePrivate=false]
  1.1221 +   *        Tells if you want to also retrieve messages coming from private
  1.1222 +   *        windows. Defaults to false.
  1.1223 +   * @return array
  1.1224 +   *         The array of cached messages. Each element is an nsIScriptError or
  1.1225 +   *         an nsIConsoleMessage
  1.1226 +   */
  1.1227 +  getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
  1.1228 +  {
  1.1229 +    let errors = Services.console.getMessageArray() || [];
  1.1230 +
  1.1231 +    // if !this.window, we're in a browser console. Still need to filter
  1.1232 +    // private messages.
  1.1233 +    if (!this.window) {
  1.1234 +      return errors.filter((aError) => {
  1.1235 +        if (aError instanceof Ci.nsIScriptError) {
  1.1236 +          if (!aIncludePrivate && aError.isFromPrivateWindow) {
  1.1237 +            return false;
  1.1238 +          }
  1.1239 +        }
  1.1240 +
  1.1241 +        return true;
  1.1242 +      });
  1.1243 +    }
  1.1244 +
  1.1245 +    let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
  1.1246 +
  1.1247 +    return errors.filter((aError) => {
  1.1248 +      if (aError instanceof Ci.nsIScriptError) {
  1.1249 +        if (!aIncludePrivate && aError.isFromPrivateWindow) {
  1.1250 +          return false;
  1.1251 +        }
  1.1252 +        if (ids &&
  1.1253 +            (ids.indexOf(aError.innerWindowID) == -1 ||
  1.1254 +             !this.isCategoryAllowed(aError.category))) {
  1.1255 +          return false;
  1.1256 +        }
  1.1257 +      }
  1.1258 +      else if (ids && ids[0]) {
  1.1259 +        // If this is not an nsIScriptError and we need to do window-based
  1.1260 +        // filtering we skip this message.
  1.1261 +        return false;
  1.1262 +      }
  1.1263 +
  1.1264 +      return true;
  1.1265 +    });
  1.1266 +  },
  1.1267 +
  1.1268 +  /**
  1.1269 +   * Remove the nsIConsoleService listener.
  1.1270 +   */
  1.1271 +  destroy: function CSL_destroy()
  1.1272 +  {
  1.1273 +    Services.console.unregisterListener(this);
  1.1274 +    this.listener = this.window = null;
  1.1275 +  },
  1.1276 +};
  1.1277 +
  1.1278 +
  1.1279 +///////////////////////////////////////////////////////////////////////////////
  1.1280 +// The window.console API observer
  1.1281 +///////////////////////////////////////////////////////////////////////////////
  1.1282 +
  1.1283 +/**
  1.1284 + * The window.console API observer. This allows the window.console API messages
  1.1285 + * to be sent to the remote Web Console instance.
  1.1286 + *
  1.1287 + * @constructor
  1.1288 + * @param nsIDOMWindow aWindow
  1.1289 + *        Optional - the window object for which we are created. This is used
  1.1290 + *        for filtering out messages that belong to other windows.
  1.1291 + * @param object aOwner
  1.1292 + *        The owner object must have the following methods:
  1.1293 + *        - onConsoleAPICall(). This method is invoked with one argument, the
  1.1294 + *        Console API message that comes from the observer service, whenever
  1.1295 + *        a relevant console API call is received.
  1.1296 + */
  1.1297 +function ConsoleAPIListener(aWindow, aOwner)
  1.1298 +{
  1.1299 +  this.window = aWindow;
  1.1300 +  this.owner = aOwner;
  1.1301 +  if (this.window) {
  1.1302 +    this.layoutHelpers = new LayoutHelpers(this.window);
  1.1303 +  }
  1.1304 +}
  1.1305 +exports.ConsoleAPIListener = ConsoleAPIListener;
  1.1306 +
  1.1307 +ConsoleAPIListener.prototype =
  1.1308 +{
  1.1309 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
  1.1310 +
  1.1311 +  /**
  1.1312 +   * The content window for which we listen to window.console API calls.
  1.1313 +   * @type nsIDOMWindow
  1.1314 +   */
  1.1315 +  window: null,
  1.1316 +
  1.1317 +  /**
  1.1318 +   * The owner object which is notified of window.console API calls. It must
  1.1319 +   * have a onConsoleAPICall method which is invoked with one argument: the
  1.1320 +   * console API call object that comes from the observer service.
  1.1321 +   *
  1.1322 +   * @type object
  1.1323 +   * @see WebConsoleActor
  1.1324 +   */
  1.1325 +  owner: null,
  1.1326 +
  1.1327 +  /**
  1.1328 +   * Initialize the window.console API observer.
  1.1329 +   */
  1.1330 +  init: function CAL_init()
  1.1331 +  {
  1.1332 +    // Note that the observer is process-wide. We will filter the messages as
  1.1333 +    // needed, see CAL_observe().
  1.1334 +    Services.obs.addObserver(this, "console-api-log-event", false);
  1.1335 +  },
  1.1336 +
  1.1337 +  /**
  1.1338 +   * The console API message observer. When messages are received from the
  1.1339 +   * observer service we forward them to the remote Web Console instance.
  1.1340 +   *
  1.1341 +   * @param object aMessage
  1.1342 +   *        The message object receives from the observer service.
  1.1343 +   * @param string aTopic
  1.1344 +   *        The message topic received from the observer service.
  1.1345 +   */
  1.1346 +  observe: function CAL_observe(aMessage, aTopic)
  1.1347 +  {
  1.1348 +    if (!this.owner) {
  1.1349 +      return;
  1.1350 +    }
  1.1351 +
  1.1352 +    let apiMessage = aMessage.wrappedJSObject;
  1.1353 +    if (this.window) {
  1.1354 +      let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
  1.1355 +      if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
  1.1356 +        // Not the same window!
  1.1357 +        return;
  1.1358 +      }
  1.1359 +    }
  1.1360 +
  1.1361 +    this.owner.onConsoleAPICall(apiMessage);
  1.1362 +  },
  1.1363 +
  1.1364 +  /**
  1.1365 +   * Get the cached messages for the current inner window and its (i)frames.
  1.1366 +   *
  1.1367 +   * @param boolean [aIncludePrivate=false]
  1.1368 +   *        Tells if you want to also retrieve messages coming from private
  1.1369 +   *        windows. Defaults to false.
  1.1370 +   * @return array
  1.1371 +   *         The array of cached messages.
  1.1372 +   */
  1.1373 +  getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false)
  1.1374 +  {
  1.1375 +    let messages = [];
  1.1376 +    let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
  1.1377 +                              .getService(Ci.nsIConsoleAPIStorage);
  1.1378 +
  1.1379 +    // if !this.window, we're in a browser console. Retrieve all events
  1.1380 +    // for filtering based on privacy.
  1.1381 +    if (!this.window) {
  1.1382 +      messages = ConsoleAPIStorage.getEvents();
  1.1383 +    } else {
  1.1384 +      let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
  1.1385 +      ids.forEach((id) => {
  1.1386 +        messages = messages.concat(ConsoleAPIStorage.getEvents(id));
  1.1387 +      });
  1.1388 +    }
  1.1389 +
  1.1390 +    if (aIncludePrivate) {
  1.1391 +      return messages;
  1.1392 +    }
  1.1393 +
  1.1394 +    return messages.filter((m) => !m.private);
  1.1395 +  },
  1.1396 +
  1.1397 +  /**
  1.1398 +   * Destroy the console API listener.
  1.1399 +   */
  1.1400 +  destroy: function CAL_destroy()
  1.1401 +  {
  1.1402 +    Services.obs.removeObserver(this, "console-api-log-event");
  1.1403 +    this.window = this.owner = null;
  1.1404 +  },
  1.1405 +};
  1.1406 +
  1.1407 +
  1.1408 +
  1.1409 +/**
  1.1410 + * JSTerm helper functions.
  1.1411 + *
  1.1412 + * Defines a set of functions ("helper functions") that are available from the
  1.1413 + * Web Console but not from the web page.
  1.1414 + *
  1.1415 + * A list of helper functions used by Firebug can be found here:
  1.1416 + *   http://getfirebug.com/wiki/index.php/Command_Line_API
  1.1417 + *
  1.1418 + * @param object aOwner
  1.1419 + *        The owning object.
  1.1420 + */
  1.1421 +function JSTermHelpers(aOwner)
  1.1422 +{
  1.1423 +  /**
  1.1424 +   * Find a node by ID.
  1.1425 +   *
  1.1426 +   * @param string aId
  1.1427 +   *        The ID of the element you want.
  1.1428 +   * @return nsIDOMNode or null
  1.1429 +   *         The result of calling document.querySelector(aSelector).
  1.1430 +   */
  1.1431 +  aOwner.sandbox.$ = function JSTH_$(aSelector)
  1.1432 +  {
  1.1433 +    return aOwner.window.document.querySelector(aSelector);
  1.1434 +  };
  1.1435 +
  1.1436 +  /**
  1.1437 +   * Find the nodes matching a CSS selector.
  1.1438 +   *
  1.1439 +   * @param string aSelector
  1.1440 +   *        A string that is passed to window.document.querySelectorAll.
  1.1441 +   * @return nsIDOMNodeList
  1.1442 +   *         Returns the result of document.querySelectorAll(aSelector).
  1.1443 +   */
  1.1444 +  aOwner.sandbox.$$ = function JSTH_$$(aSelector)
  1.1445 +  {
  1.1446 +    return aOwner.window.document.querySelectorAll(aSelector);
  1.1447 +  };
  1.1448 +
  1.1449 +  /**
  1.1450 +   * Runs an xPath query and returns all matched nodes.
  1.1451 +   *
  1.1452 +   * @param string aXPath
  1.1453 +   *        xPath search query to execute.
  1.1454 +   * @param [optional] nsIDOMNode aContext
  1.1455 +   *        Context to run the xPath query on. Uses window.document if not set.
  1.1456 +   * @return array of nsIDOMNode
  1.1457 +   */
  1.1458 +  aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
  1.1459 +  {
  1.1460 +    let nodes = new aOwner.window.wrappedJSObject.Array();
  1.1461 +    let doc = aOwner.window.document;
  1.1462 +    aContext = aContext || doc;
  1.1463 +
  1.1464 +    let results = doc.evaluate(aXPath, aContext, null,
  1.1465 +                               Ci.nsIDOMXPathResult.ANY_TYPE, null);
  1.1466 +    let node;
  1.1467 +    while ((node = results.iterateNext())) {
  1.1468 +      nodes.push(node);
  1.1469 +    }
  1.1470 +
  1.1471 +    return nodes;
  1.1472 +  };
  1.1473 +
  1.1474 +  /**
  1.1475 +   * Returns the currently selected object in the highlighter.
  1.1476 +   *
  1.1477 +   * TODO: this implementation crosses the client/server boundaries! This is not
  1.1478 +   * usable within a remote browser. To implement this feature correctly we need
  1.1479 +   * support for remote inspection capabilities within the Inspector as well.
  1.1480 +   * See bug 787975.
  1.1481 +   *
  1.1482 +   * @return nsIDOMElement|null
  1.1483 +   *         The DOM element currently selected in the highlighter.
  1.1484 +   */
  1.1485 +   Object.defineProperty(aOwner.sandbox, "$0", {
  1.1486 +    get: function() {
  1.1487 +      let window = aOwner.chromeWindow();
  1.1488 +      if (!window) {
  1.1489 +        return null;
  1.1490 +      }
  1.1491 +
  1.1492 +      let target = null;
  1.1493 +      try {
  1.1494 +        target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
  1.1495 +      }
  1.1496 +      catch (ex) {
  1.1497 +        // If we report this exception the user will get it in the Browser
  1.1498 +        // Console every time when she evaluates any string.
  1.1499 +      }
  1.1500 +
  1.1501 +      if (!target) {
  1.1502 +        return null;
  1.1503 +      }
  1.1504 +
  1.1505 +      let toolbox = gDevTools.getToolbox(target);
  1.1506 +      let node = toolbox && toolbox.selection ? toolbox.selection.node : null;
  1.1507 +
  1.1508 +      return node ? aOwner.makeDebuggeeValue(node) : null;
  1.1509 +    },
  1.1510 +    enumerable: true,
  1.1511 +    configurable: false
  1.1512 +  });
  1.1513 +
  1.1514 +  /**
  1.1515 +   * Clears the output of the JSTerm.
  1.1516 +   */
  1.1517 +  aOwner.sandbox.clear = function JSTH_clear()
  1.1518 +  {
  1.1519 +    aOwner.helperResult = {
  1.1520 +      type: "clearOutput",
  1.1521 +    };
  1.1522 +  };
  1.1523 +
  1.1524 +  /**
  1.1525 +   * Returns the result of Object.keys(aObject).
  1.1526 +   *
  1.1527 +   * @param object aObject
  1.1528 +   *        Object to return the property names from.
  1.1529 +   * @return array of strings
  1.1530 +   */
  1.1531 +  aOwner.sandbox.keys = function JSTH_keys(aObject)
  1.1532 +  {
  1.1533 +    return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
  1.1534 +  };
  1.1535 +
  1.1536 +  /**
  1.1537 +   * Returns the values of all properties on aObject.
  1.1538 +   *
  1.1539 +   * @param object aObject
  1.1540 +   *        Object to display the values from.
  1.1541 +   * @return array of string
  1.1542 +   */
  1.1543 +  aOwner.sandbox.values = function JSTH_values(aObject)
  1.1544 +  {
  1.1545 +    let arrValues = new aOwner.window.wrappedJSObject.Array();
  1.1546 +    let obj = WebConsoleUtils.unwrap(aObject);
  1.1547 +
  1.1548 +    for (let prop in obj) {
  1.1549 +      arrValues.push(obj[prop]);
  1.1550 +    }
  1.1551 +
  1.1552 +    return arrValues;
  1.1553 +  };
  1.1554 +
  1.1555 +  /**
  1.1556 +   * Opens a help window in MDN.
  1.1557 +   */
  1.1558 +  aOwner.sandbox.help = function JSTH_help()
  1.1559 +  {
  1.1560 +    aOwner.helperResult = { type: "help" };
  1.1561 +  };
  1.1562 +
  1.1563 +  /**
  1.1564 +   * Change the JS evaluation scope.
  1.1565 +   *
  1.1566 +   * @param DOMElement|string|window aWindow
  1.1567 +   *        The window object to use for eval scope. This can be a string that
  1.1568 +   *        is used to perform document.querySelector(), to find the iframe that
  1.1569 +   *        you want to cd() to. A DOMElement can be given as well, the
  1.1570 +   *        .contentWindow property is used. Lastly, you can directly pass
  1.1571 +   *        a window object. If you call cd() with no arguments, the current
  1.1572 +   *        eval scope is cleared back to its default (the top window).
  1.1573 +   */
  1.1574 +  aOwner.sandbox.cd = function JSTH_cd(aWindow)
  1.1575 +  {
  1.1576 +    if (!aWindow) {
  1.1577 +      aOwner.consoleActor.evalWindow = null;
  1.1578 +      aOwner.helperResult = { type: "cd" };
  1.1579 +      return;
  1.1580 +    }
  1.1581 +
  1.1582 +    if (typeof aWindow == "string") {
  1.1583 +      aWindow = aOwner.window.document.querySelector(aWindow);
  1.1584 +    }
  1.1585 +    if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
  1.1586 +      aWindow = aWindow.contentWindow;
  1.1587 +    }
  1.1588 +    if (!(aWindow instanceof Ci.nsIDOMWindow)) {
  1.1589 +      aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
  1.1590 +      return;
  1.1591 +    }
  1.1592 +
  1.1593 +    aOwner.consoleActor.evalWindow = aWindow;
  1.1594 +    aOwner.helperResult = { type: "cd" };
  1.1595 +  };
  1.1596 +
  1.1597 +  /**
  1.1598 +   * Inspects the passed aObject. This is done by opening the PropertyPanel.
  1.1599 +   *
  1.1600 +   * @param object aObject
  1.1601 +   *        Object to inspect.
  1.1602 +   */
  1.1603 +  aOwner.sandbox.inspect = function JSTH_inspect(aObject)
  1.1604 +  {
  1.1605 +    let dbgObj = aOwner.makeDebuggeeValue(aObject);
  1.1606 +    let grip = aOwner.createValueGrip(dbgObj);
  1.1607 +    aOwner.helperResult = {
  1.1608 +      type: "inspectObject",
  1.1609 +      input: aOwner.evalInput,
  1.1610 +      object: grip,
  1.1611 +    };
  1.1612 +  };
  1.1613 +
  1.1614 +  /**
  1.1615 +   * Prints aObject to the output.
  1.1616 +   *
  1.1617 +   * @param object aObject
  1.1618 +   *        Object to print to the output.
  1.1619 +   * @return string
  1.1620 +   */
  1.1621 +  aOwner.sandbox.pprint = function JSTH_pprint(aObject)
  1.1622 +  {
  1.1623 +    if (aObject === null || aObject === undefined || aObject === true ||
  1.1624 +        aObject === false) {
  1.1625 +      aOwner.helperResult = {
  1.1626 +        type: "error",
  1.1627 +        message: "helperFuncUnsupportedTypeError",
  1.1628 +      };
  1.1629 +      return null;
  1.1630 +    }
  1.1631 +
  1.1632 +    aOwner.helperResult = { rawOutput: true };
  1.1633 +
  1.1634 +    if (typeof aObject == "function") {
  1.1635 +      return aObject + "\n";
  1.1636 +    }
  1.1637 +
  1.1638 +    let output = [];
  1.1639 +
  1.1640 +    let obj = WebConsoleUtils.unwrap(aObject);
  1.1641 +    for (let name in obj) {
  1.1642 +      let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
  1.1643 +      if (desc.get || desc.set) {
  1.1644 +        // TODO: Bug 842672 - toolkit/ imports modules from browser/.
  1.1645 +        let getGrip = VariablesView.getGrip(desc.get);
  1.1646 +        let setGrip = VariablesView.getGrip(desc.set);
  1.1647 +        let getString = VariablesView.getString(getGrip);
  1.1648 +        let setString = VariablesView.getString(setGrip);
  1.1649 +        output.push(name + ":", "  get: " + getString, "  set: " + setString);
  1.1650 +      }
  1.1651 +      else {
  1.1652 +        let valueGrip = VariablesView.getGrip(obj[name]);
  1.1653 +        let valueString = VariablesView.getString(valueGrip);
  1.1654 +        output.push(name + ": " + valueString);
  1.1655 +      }
  1.1656 +    }
  1.1657 +
  1.1658 +    return "  " + output.join("\n  ");
  1.1659 +  };
  1.1660 +
  1.1661 +  /**
  1.1662 +   * Print a string to the output, as-is.
  1.1663 +   *
  1.1664 +   * @param string aString
  1.1665 +   *        A string you want to output.
  1.1666 +   * @return void
  1.1667 +   */
  1.1668 +  aOwner.sandbox.print = function JSTH_print(aString)
  1.1669 +  {
  1.1670 +    aOwner.helperResult = { rawOutput: true };
  1.1671 +    return String(aString);
  1.1672 +  };
  1.1673 +}
  1.1674 +exports.JSTermHelpers = JSTermHelpers;
  1.1675 +
  1.1676 +
  1.1677 +/**
  1.1678 + * A ReflowObserver that listens for reflow events from the page.
  1.1679 + * Implements nsIReflowObserver.
  1.1680 + *
  1.1681 + * @constructor
  1.1682 + * @param object aWindow
  1.1683 + *        The window for which we need to track reflow.
  1.1684 + * @param object aOwner
  1.1685 + *        The listener owner which needs to implement:
  1.1686 + *        - onReflowActivity(aReflowInfo)
  1.1687 + */
  1.1688 +
  1.1689 +function ConsoleReflowListener(aWindow, aListener)
  1.1690 +{
  1.1691 +  this.docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
  1.1692 +                         .getInterface(Ci.nsIWebNavigation)
  1.1693 +                         .QueryInterface(Ci.nsIDocShell);
  1.1694 +  this.listener = aListener;
  1.1695 +  this.docshell.addWeakReflowObserver(this);
  1.1696 +}
  1.1697 +
  1.1698 +exports.ConsoleReflowListener = ConsoleReflowListener;
  1.1699 +
  1.1700 +ConsoleReflowListener.prototype =
  1.1701 +{
  1.1702 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
  1.1703 +                                         Ci.nsISupportsWeakReference]),
  1.1704 +  docshell: null,
  1.1705 +  listener: null,
  1.1706 +
  1.1707 +  /**
  1.1708 +   * Forward reflow event to listener.
  1.1709 +   *
  1.1710 +   * @param DOMHighResTimeStamp aStart
  1.1711 +   * @param DOMHighResTimeStamp aEnd
  1.1712 +   * @param boolean aInterruptible
  1.1713 +   */
  1.1714 +  sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible)
  1.1715 +  {
  1.1716 +    let frame = components.stack.caller.caller;
  1.1717 +
  1.1718 +    let filename = frame.filename;
  1.1719 +
  1.1720 +    if (filename) {
  1.1721 +      // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
  1.1722 +      // we only take the last part.
  1.1723 +      filename = filename.split(" ").pop();
  1.1724 +    }
  1.1725 +
  1.1726 +    this.listener.onReflowActivity({
  1.1727 +      interruptible: aInterruptible,
  1.1728 +      start: aStart,
  1.1729 +      end: aEnd,
  1.1730 +      sourceURL: filename,
  1.1731 +      sourceLine: frame.lineNumber,
  1.1732 +      functionName: frame.name
  1.1733 +    });
  1.1734 +  },
  1.1735 +
  1.1736 +  /**
  1.1737 +   * On uninterruptible reflow
  1.1738 +   *
  1.1739 +   * @param DOMHighResTimeStamp aStart
  1.1740 +   * @param DOMHighResTimeStamp aEnd
  1.1741 +   */
  1.1742 +  reflow: function CRL_reflow(aStart, aEnd)
  1.1743 +  {
  1.1744 +    this.sendReflow(aStart, aEnd, false);
  1.1745 +  },
  1.1746 +
  1.1747 +  /**
  1.1748 +   * On interruptible reflow
  1.1749 +   *
  1.1750 +   * @param DOMHighResTimeStamp aStart
  1.1751 +   * @param DOMHighResTimeStamp aEnd
  1.1752 +   */
  1.1753 +  reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd)
  1.1754 +  {
  1.1755 +    this.sendReflow(aStart, aEnd, true);
  1.1756 +  },
  1.1757 +
  1.1758 +  /**
  1.1759 +   * Unregister listener.
  1.1760 +   */
  1.1761 +  destroy: function CRL_destroy()
  1.1762 +  {
  1.1763 +    this.docshell.removeWeakReflowObserver(this);
  1.1764 +    this.listener = this.docshell = null;
  1.1765 +  },
  1.1766 +};
  1.1767 +
  1.1768 +function gSequenceId()
  1.1769 +{
  1.1770 +  return gSequenceId.n++;
  1.1771 +}
  1.1772 +gSequenceId.n = 0;

mercurial