toolkit/devtools/webconsole/utils.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
michael@0 2 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 "use strict";
michael@0 8
michael@0 9 const {Cc, Ci, Cu, components} = require("chrome");
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 12
michael@0 13 loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
michael@0 14 loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
michael@0 15
michael@0 16 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
michael@0 17 // Note that these are only used in JSTermHelpers, see $0 and pprint().
michael@0 18 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
michael@0 19 loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
michael@0 20 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
michael@0 21 loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm");
michael@0 22
michael@0 23 // Match the function name from the result of toString() or toSource().
michael@0 24 //
michael@0 25 // Examples:
michael@0 26 // (function foobar(a, b) { ...
michael@0 27 // function foobar2(a) { ...
michael@0 28 // function() { ...
michael@0 29 const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
michael@0 30
michael@0 31 // Match the function arguments from the result of toString() or toSource().
michael@0 32 const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
michael@0 33
michael@0 34 let WebConsoleUtils = {
michael@0 35 /**
michael@0 36 * Convenience function to unwrap a wrapped object.
michael@0 37 *
michael@0 38 * @param aObject the object to unwrap.
michael@0 39 * @return aObject unwrapped.
michael@0 40 */
michael@0 41 unwrap: function WCU_unwrap(aObject)
michael@0 42 {
michael@0 43 try {
michael@0 44 return XPCNativeWrapper.unwrap(aObject);
michael@0 45 }
michael@0 46 catch (ex) {
michael@0 47 return aObject;
michael@0 48 }
michael@0 49 },
michael@0 50
michael@0 51 /**
michael@0 52 * Wrap a string in an nsISupportsString object.
michael@0 53 *
michael@0 54 * @param string aString
michael@0 55 * @return nsISupportsString
michael@0 56 */
michael@0 57 supportsString: function WCU_supportsString(aString)
michael@0 58 {
michael@0 59 let str = Cc["@mozilla.org/supports-string;1"].
michael@0 60 createInstance(Ci.nsISupportsString);
michael@0 61 str.data = aString;
michael@0 62 return str;
michael@0 63 },
michael@0 64
michael@0 65 /**
michael@0 66 * Clone an object.
michael@0 67 *
michael@0 68 * @param object aObject
michael@0 69 * The object you want cloned.
michael@0 70 * @param boolean aRecursive
michael@0 71 * Tells if you want to dig deeper into the object, to clone
michael@0 72 * recursively.
michael@0 73 * @param function [aFilter]
michael@0 74 * Optional, filter function, called for every property. Three
michael@0 75 * arguments are passed: key, value and object. Return true if the
michael@0 76 * property should be added to the cloned object. Return false to skip
michael@0 77 * the property.
michael@0 78 * @return object
michael@0 79 * The cloned object.
michael@0 80 */
michael@0 81 cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter)
michael@0 82 {
michael@0 83 if (typeof aObject != "object") {
michael@0 84 return aObject;
michael@0 85 }
michael@0 86
michael@0 87 let temp;
michael@0 88
michael@0 89 if (Array.isArray(aObject)) {
michael@0 90 temp = [];
michael@0 91 Array.forEach(aObject, function(aValue, aIndex) {
michael@0 92 if (!aFilter || aFilter(aIndex, aValue, aObject)) {
michael@0 93 temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue);
michael@0 94 }
michael@0 95 });
michael@0 96 }
michael@0 97 else {
michael@0 98 temp = {};
michael@0 99 for (let key in aObject) {
michael@0 100 let value = aObject[key];
michael@0 101 if (aObject.hasOwnProperty(key) &&
michael@0 102 (!aFilter || aFilter(key, value, aObject))) {
michael@0 103 temp[key] = aRecursive ? WCU_cloneObject(value) : value;
michael@0 104 }
michael@0 105 }
michael@0 106 }
michael@0 107
michael@0 108 return temp;
michael@0 109 },
michael@0 110
michael@0 111 /**
michael@0 112 * Copies certain style attributes from one element to another.
michael@0 113 *
michael@0 114 * @param nsIDOMNode aFrom
michael@0 115 * The target node.
michael@0 116 * @param nsIDOMNode aTo
michael@0 117 * The destination node.
michael@0 118 */
michael@0 119 copyTextStyles: function WCU_copyTextStyles(aFrom, aTo)
michael@0 120 {
michael@0 121 let win = aFrom.ownerDocument.defaultView;
michael@0 122 let style = win.getComputedStyle(aFrom);
michael@0 123 aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
michael@0 124 aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
michael@0 125 aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
michael@0 126 aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
michael@0 127 },
michael@0 128
michael@0 129 /**
michael@0 130 * Gets the ID of the inner window of this DOM window.
michael@0 131 *
michael@0 132 * @param nsIDOMWindow aWindow
michael@0 133 * @return integer
michael@0 134 * Inner ID for the given aWindow.
michael@0 135 */
michael@0 136 getInnerWindowId: function WCU_getInnerWindowId(aWindow)
michael@0 137 {
michael@0 138 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
michael@0 139 getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
michael@0 140 },
michael@0 141
michael@0 142 /**
michael@0 143 * Recursively gather a list of inner window ids given a
michael@0 144 * top level window.
michael@0 145 *
michael@0 146 * @param nsIDOMWindow aWindow
michael@0 147 * @return Array
michael@0 148 * list of inner window ids.
michael@0 149 */
michael@0 150 getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow)
michael@0 151 {
michael@0 152 let innerWindowID = this.getInnerWindowId(aWindow);
michael@0 153 let ids = [innerWindowID];
michael@0 154
michael@0 155 if (aWindow.frames) {
michael@0 156 for (let i = 0; i < aWindow.frames.length; i++) {
michael@0 157 let frame = aWindow.frames[i];
michael@0 158 ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
michael@0 159 }
michael@0 160 }
michael@0 161
michael@0 162 return ids;
michael@0 163 },
michael@0 164
michael@0 165
michael@0 166 /**
michael@0 167 * Gets the ID of the outer window of this DOM window.
michael@0 168 *
michael@0 169 * @param nsIDOMWindow aWindow
michael@0 170 * @return integer
michael@0 171 * Outer ID for the given aWindow.
michael@0 172 */
michael@0 173 getOuterWindowId: function WCU_getOuterWindowId(aWindow)
michael@0 174 {
michael@0 175 return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
michael@0 176 getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
michael@0 177 },
michael@0 178
michael@0 179 /**
michael@0 180 * Abbreviates the given source URL so that it can be displayed flush-right
michael@0 181 * without being too distracting.
michael@0 182 *
michael@0 183 * @param string aSourceURL
michael@0 184 * The source URL to shorten.
michael@0 185 * @param object [aOptions]
michael@0 186 * Options:
michael@0 187 * - onlyCropQuery: boolean that tells if the URL abbreviation function
michael@0 188 * should only remove the query parameters and the hash fragment from
michael@0 189 * the given URL.
michael@0 190 * @return string
michael@0 191 * The abbreviated form of the source URL.
michael@0 192 */
michael@0 193 abbreviateSourceURL:
michael@0 194 function WCU_abbreviateSourceURL(aSourceURL, aOptions = {})
michael@0 195 {
michael@0 196 if (!aOptions.onlyCropQuery && aSourceURL.substr(0, 5) == "data:") {
michael@0 197 let commaIndex = aSourceURL.indexOf(",");
michael@0 198 if (commaIndex > -1) {
michael@0 199 aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1);
michael@0 200 }
michael@0 201 }
michael@0 202
michael@0 203 // Remove any query parameters.
michael@0 204 let hookIndex = aSourceURL.indexOf("?");
michael@0 205 if (hookIndex > -1) {
michael@0 206 aSourceURL = aSourceURL.substring(0, hookIndex);
michael@0 207 }
michael@0 208
michael@0 209 // Remove any hash fragments.
michael@0 210 let hashIndex = aSourceURL.indexOf("#");
michael@0 211 if (hashIndex > -1) {
michael@0 212 aSourceURL = aSourceURL.substring(0, hashIndex);
michael@0 213 }
michael@0 214
michael@0 215 // Remove a trailing "/".
michael@0 216 if (aSourceURL[aSourceURL.length - 1] == "/") {
michael@0 217 aSourceURL = aSourceURL.replace(/\/+$/, "");
michael@0 218 }
michael@0 219
michael@0 220 // Remove all but the last path component.
michael@0 221 if (!aOptions.onlyCropQuery) {
michael@0 222 let slashIndex = aSourceURL.lastIndexOf("/");
michael@0 223 if (slashIndex > -1) {
michael@0 224 aSourceURL = aSourceURL.substring(slashIndex + 1);
michael@0 225 }
michael@0 226 }
michael@0 227
michael@0 228 return aSourceURL;
michael@0 229 },
michael@0 230
michael@0 231 /**
michael@0 232 * Tells if the given function is native or not.
michael@0 233 *
michael@0 234 * @param function aFunction
michael@0 235 * The function you want to check if it is native or not.
michael@0 236 * @return boolean
michael@0 237 * True if the given function is native, false otherwise.
michael@0 238 */
michael@0 239 isNativeFunction: function WCU_isNativeFunction(aFunction)
michael@0 240 {
michael@0 241 return typeof aFunction == "function" && !("prototype" in aFunction);
michael@0 242 },
michael@0 243
michael@0 244 /**
michael@0 245 * Tells if the given property of the provided object is a non-native getter or
michael@0 246 * not.
michael@0 247 *
michael@0 248 * @param object aObject
michael@0 249 * The object that contains the property.
michael@0 250 * @param string aProp
michael@0 251 * The property you want to check if it is a getter or not.
michael@0 252 * @return boolean
michael@0 253 * True if the given property is a getter, false otherwise.
michael@0 254 */
michael@0 255 isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp)
michael@0 256 {
michael@0 257 if (typeof aObject != "object") {
michael@0 258 return false;
michael@0 259 }
michael@0 260 let desc = this.getPropertyDescriptor(aObject, aProp);
michael@0 261 return desc && desc.get && !this.isNativeFunction(desc.get);
michael@0 262 },
michael@0 263
michael@0 264 /**
michael@0 265 * Get the property descriptor for the given object.
michael@0 266 *
michael@0 267 * @param object aObject
michael@0 268 * The object that contains the property.
michael@0 269 * @param string aProp
michael@0 270 * The property you want to get the descriptor for.
michael@0 271 * @return object
michael@0 272 * Property descriptor.
michael@0 273 */
michael@0 274 getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp)
michael@0 275 {
michael@0 276 let desc = null;
michael@0 277 while (aObject) {
michael@0 278 try {
michael@0 279 if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) {
michael@0 280 break;
michael@0 281 }
michael@0 282 }
michael@0 283 catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" ||
michael@0 284 ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" ||
michael@0 285 ex.name == "TypeError")) {
michael@0 286 // Native getters throw here. See bug 520882.
michael@0 287 // null throws TypeError.
michael@0 288 }
michael@0 289 try {
michael@0 290 aObject = Object.getPrototypeOf(aObject);
michael@0 291 }
michael@0 292 catch (ex if (ex.name == "TypeError")) {
michael@0 293 return desc;
michael@0 294 }
michael@0 295 }
michael@0 296 return desc;
michael@0 297 },
michael@0 298
michael@0 299 /**
michael@0 300 * Sort function for object properties.
michael@0 301 *
michael@0 302 * @param object a
michael@0 303 * Property descriptor.
michael@0 304 * @param object b
michael@0 305 * Property descriptor.
michael@0 306 * @return integer
michael@0 307 * -1 if a.name < b.name,
michael@0 308 * 1 if a.name > b.name,
michael@0 309 * 0 otherwise.
michael@0 310 */
michael@0 311 propertiesSort: function WCU_propertiesSort(a, b)
michael@0 312 {
michael@0 313 // Convert the pair.name to a number for later sorting.
michael@0 314 let aNumber = parseFloat(a.name);
michael@0 315 let bNumber = parseFloat(b.name);
michael@0 316
michael@0 317 // Sort numbers.
michael@0 318 if (!isNaN(aNumber) && isNaN(bNumber)) {
michael@0 319 return -1;
michael@0 320 }
michael@0 321 else if (isNaN(aNumber) && !isNaN(bNumber)) {
michael@0 322 return 1;
michael@0 323 }
michael@0 324 else if (!isNaN(aNumber) && !isNaN(bNumber)) {
michael@0 325 return aNumber - bNumber;
michael@0 326 }
michael@0 327 // Sort string.
michael@0 328 else if (a.name < b.name) {
michael@0 329 return -1;
michael@0 330 }
michael@0 331 else if (a.name > b.name) {
michael@0 332 return 1;
michael@0 333 }
michael@0 334 else {
michael@0 335 return 0;
michael@0 336 }
michael@0 337 },
michael@0 338
michael@0 339 /**
michael@0 340 * Create a grip for the given value. If the value is an object,
michael@0 341 * an object wrapper will be created.
michael@0 342 *
michael@0 343 * @param mixed aValue
michael@0 344 * The value you want to create a grip for, before sending it to the
michael@0 345 * client.
michael@0 346 * @param function aObjectWrapper
michael@0 347 * If the value is an object then the aObjectWrapper function is
michael@0 348 * invoked to give us an object grip. See this.getObjectGrip().
michael@0 349 * @return mixed
michael@0 350 * The value grip.
michael@0 351 */
michael@0 352 createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper)
michael@0 353 {
michael@0 354 switch (typeof aValue) {
michael@0 355 case "boolean":
michael@0 356 return aValue;
michael@0 357 case "string":
michael@0 358 return aObjectWrapper(aValue);
michael@0 359 case "number":
michael@0 360 if (aValue === Infinity) {
michael@0 361 return { type: "Infinity" };
michael@0 362 }
michael@0 363 else if (aValue === -Infinity) {
michael@0 364 return { type: "-Infinity" };
michael@0 365 }
michael@0 366 else if (Number.isNaN(aValue)) {
michael@0 367 return { type: "NaN" };
michael@0 368 }
michael@0 369 else if (!aValue && 1 / aValue === -Infinity) {
michael@0 370 return { type: "-0" };
michael@0 371 }
michael@0 372 return aValue;
michael@0 373 case "undefined":
michael@0 374 return { type: "undefined" };
michael@0 375 case "object":
michael@0 376 if (aValue === null) {
michael@0 377 return { type: "null" };
michael@0 378 }
michael@0 379 case "function":
michael@0 380 return aObjectWrapper(aValue);
michael@0 381 default:
michael@0 382 Cu.reportError("Failed to provide a grip for value of " + typeof aValue
michael@0 383 + ": " + aValue);
michael@0 384 return null;
michael@0 385 }
michael@0 386 },
michael@0 387
michael@0 388 /**
michael@0 389 * Check if the given object is an iterator or a generator.
michael@0 390 *
michael@0 391 * @param object aObject
michael@0 392 * The object you want to check.
michael@0 393 * @return boolean
michael@0 394 * True if the given object is an iterator or a generator, otherwise
michael@0 395 * false is returned.
michael@0 396 */
michael@0 397 isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject)
michael@0 398 {
michael@0 399 if (aObject === null) {
michael@0 400 return false;
michael@0 401 }
michael@0 402
michael@0 403 if (typeof aObject == "object") {
michael@0 404 if (typeof aObject.__iterator__ == "function" ||
michael@0 405 aObject.constructor && aObject.constructor.name == "Iterator") {
michael@0 406 return true;
michael@0 407 }
michael@0 408
michael@0 409 try {
michael@0 410 let str = aObject.toString();
michael@0 411 if (typeof aObject.next == "function" &&
michael@0 412 str.indexOf("[object Generator") == 0) {
michael@0 413 return true;
michael@0 414 }
michael@0 415 }
michael@0 416 catch (ex) {
michael@0 417 // window.history.next throws in the typeof check above.
michael@0 418 return false;
michael@0 419 }
michael@0 420 }
michael@0 421
michael@0 422 return false;
michael@0 423 },
michael@0 424
michael@0 425 /**
michael@0 426 * Determine if the given request mixes HTTP with HTTPS content.
michael@0 427 *
michael@0 428 * @param string aRequest
michael@0 429 * Location of the requested content.
michael@0 430 * @param string aLocation
michael@0 431 * Location of the current page.
michael@0 432 * @return boolean
michael@0 433 * True if the content is mixed, false if not.
michael@0 434 */
michael@0 435 isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation)
michael@0 436 {
michael@0 437 try {
michael@0 438 let requestURI = Services.io.newURI(aRequest, null, null);
michael@0 439 let contentURI = Services.io.newURI(aLocation, null, null);
michael@0 440 return (contentURI.scheme == "https" && requestURI.scheme != "https");
michael@0 441 }
michael@0 442 catch (ex) {
michael@0 443 return false;
michael@0 444 }
michael@0 445 },
michael@0 446
michael@0 447 /**
michael@0 448 * Helper function to deduce the name of the provided function.
michael@0 449 *
michael@0 450 * @param funtion aFunction
michael@0 451 * The function whose name will be returned.
michael@0 452 * @return string
michael@0 453 * Function name.
michael@0 454 */
michael@0 455 getFunctionName: function WCF_getFunctionName(aFunction)
michael@0 456 {
michael@0 457 let name = null;
michael@0 458 if (aFunction.name) {
michael@0 459 name = aFunction.name;
michael@0 460 }
michael@0 461 else {
michael@0 462 let desc;
michael@0 463 try {
michael@0 464 desc = aFunction.getOwnPropertyDescriptor("displayName");
michael@0 465 }
michael@0 466 catch (ex) { }
michael@0 467 if (desc && typeof desc.value == "string") {
michael@0 468 name = desc.value;
michael@0 469 }
michael@0 470 }
michael@0 471 if (!name) {
michael@0 472 try {
michael@0 473 let str = (aFunction.toString() || aFunction.toSource()) + "";
michael@0 474 name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
michael@0 475 }
michael@0 476 catch (ex) { }
michael@0 477 }
michael@0 478 return name;
michael@0 479 },
michael@0 480
michael@0 481 /**
michael@0 482 * Get the object class name. For example, the |window| object has the Window
michael@0 483 * class name (based on [object Window]).
michael@0 484 *
michael@0 485 * @param object aObject
michael@0 486 * The object you want to get the class name for.
michael@0 487 * @return string
michael@0 488 * The object class name.
michael@0 489 */
michael@0 490 getObjectClassName: function WCU_getObjectClassName(aObject)
michael@0 491 {
michael@0 492 if (aObject === null) {
michael@0 493 return "null";
michael@0 494 }
michael@0 495 if (aObject === undefined) {
michael@0 496 return "undefined";
michael@0 497 }
michael@0 498
michael@0 499 let type = typeof aObject;
michael@0 500 if (type != "object") {
michael@0 501 // Grip class names should start with an uppercase letter.
michael@0 502 return type.charAt(0).toUpperCase() + type.substr(1);
michael@0 503 }
michael@0 504
michael@0 505 let className;
michael@0 506
michael@0 507 try {
michael@0 508 className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1];
michael@0 509 if (!className) {
michael@0 510 className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1];
michael@0 511 }
michael@0 512 if (!className && typeof aObject.constructor == "function") {
michael@0 513 className = this.getFunctionName(aObject.constructor);
michael@0 514 }
michael@0 515 }
michael@0 516 catch (ex) { }
michael@0 517
michael@0 518 return className;
michael@0 519 },
michael@0 520
michael@0 521 /**
michael@0 522 * Check if the given value is a grip with an actor.
michael@0 523 *
michael@0 524 * @param mixed aGrip
michael@0 525 * Value you want to check if it is a grip with an actor.
michael@0 526 * @return boolean
michael@0 527 * True if the given value is a grip with an actor.
michael@0 528 */
michael@0 529 isActorGrip: function WCU_isActorGrip(aGrip)
michael@0 530 {
michael@0 531 return aGrip && typeof(aGrip) == "object" && aGrip.actor;
michael@0 532 },
michael@0 533 };
michael@0 534 exports.Utils = WebConsoleUtils;
michael@0 535
michael@0 536 //////////////////////////////////////////////////////////////////////////
michael@0 537 // Localization
michael@0 538 //////////////////////////////////////////////////////////////////////////
michael@0 539
michael@0 540 WebConsoleUtils.l10n = function WCU_l10n(aBundleURI)
michael@0 541 {
michael@0 542 this._bundleUri = aBundleURI;
michael@0 543 };
michael@0 544
michael@0 545 WebConsoleUtils.l10n.prototype = {
michael@0 546 _stringBundle: null,
michael@0 547
michael@0 548 get stringBundle()
michael@0 549 {
michael@0 550 if (!this._stringBundle) {
michael@0 551 this._stringBundle = Services.strings.createBundle(this._bundleUri);
michael@0 552 }
michael@0 553 return this._stringBundle;
michael@0 554 },
michael@0 555
michael@0 556 /**
michael@0 557 * Generates a formatted timestamp string for displaying in console messages.
michael@0 558 *
michael@0 559 * @param integer [aMilliseconds]
michael@0 560 * Optional, allows you to specify the timestamp in milliseconds since
michael@0 561 * the UNIX epoch.
michael@0 562 * @return string
michael@0 563 * The timestamp formatted for display.
michael@0 564 */
michael@0 565 timestampString: function WCU_l10n_timestampString(aMilliseconds)
michael@0 566 {
michael@0 567 let d = new Date(aMilliseconds ? aMilliseconds : null);
michael@0 568 let hours = d.getHours(), minutes = d.getMinutes();
michael@0 569 let seconds = d.getSeconds(), milliseconds = d.getMilliseconds();
michael@0 570 let parameters = [hours, minutes, seconds, milliseconds];
michael@0 571 return this.getFormatStr("timestampFormat", parameters);
michael@0 572 },
michael@0 573
michael@0 574 /**
michael@0 575 * Retrieve a localized string.
michael@0 576 *
michael@0 577 * @param string aName
michael@0 578 * The string name you want from the Web Console string bundle.
michael@0 579 * @return string
michael@0 580 * The localized string.
michael@0 581 */
michael@0 582 getStr: function WCU_l10n_getStr(aName)
michael@0 583 {
michael@0 584 let result;
michael@0 585 try {
michael@0 586 result = this.stringBundle.GetStringFromName(aName);
michael@0 587 }
michael@0 588 catch (ex) {
michael@0 589 Cu.reportError("Failed to get string: " + aName);
michael@0 590 throw ex;
michael@0 591 }
michael@0 592 return result;
michael@0 593 },
michael@0 594
michael@0 595 /**
michael@0 596 * Retrieve a localized string formatted with values coming from the given
michael@0 597 * array.
michael@0 598 *
michael@0 599 * @param string aName
michael@0 600 * The string name you want from the Web Console string bundle.
michael@0 601 * @param array aArray
michael@0 602 * The array of values you want in the formatted string.
michael@0 603 * @return string
michael@0 604 * The formatted local string.
michael@0 605 */
michael@0 606 getFormatStr: function WCU_l10n_getFormatStr(aName, aArray)
michael@0 607 {
michael@0 608 let result;
michael@0 609 try {
michael@0 610 result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
michael@0 611 }
michael@0 612 catch (ex) {
michael@0 613 Cu.reportError("Failed to format string: " + aName);
michael@0 614 throw ex;
michael@0 615 }
michael@0 616 return result;
michael@0 617 },
michael@0 618 };
michael@0 619
michael@0 620
michael@0 621 //////////////////////////////////////////////////////////////////////////
michael@0 622 // JS Completer
michael@0 623 //////////////////////////////////////////////////////////////////////////
michael@0 624
michael@0 625 (function _JSPP(WCU) {
michael@0 626 const STATE_NORMAL = 0;
michael@0 627 const STATE_QUOTE = 2;
michael@0 628 const STATE_DQUOTE = 3;
michael@0 629
michael@0 630 const OPEN_BODY = "{[(".split("");
michael@0 631 const CLOSE_BODY = "}])".split("");
michael@0 632 const OPEN_CLOSE_BODY = {
michael@0 633 "{": "}",
michael@0 634 "[": "]",
michael@0 635 "(": ")",
michael@0 636 };
michael@0 637
michael@0 638 const MAX_COMPLETIONS = 1500;
michael@0 639
michael@0 640 /**
michael@0 641 * Analyses a given string to find the last statement that is interesting for
michael@0 642 * later completion.
michael@0 643 *
michael@0 644 * @param string aStr
michael@0 645 * A string to analyse.
michael@0 646 *
michael@0 647 * @returns object
michael@0 648 * If there was an error in the string detected, then a object like
michael@0 649 *
michael@0 650 * { err: "ErrorMesssage" }
michael@0 651 *
michael@0 652 * is returned, otherwise a object like
michael@0 653 *
michael@0 654 * {
michael@0 655 * state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
michael@0 656 * startPos: index of where the last statement begins
michael@0 657 * }
michael@0 658 */
michael@0 659 function findCompletionBeginning(aStr)
michael@0 660 {
michael@0 661 let bodyStack = [];
michael@0 662
michael@0 663 let state = STATE_NORMAL;
michael@0 664 let start = 0;
michael@0 665 let c;
michael@0 666 for (let i = 0; i < aStr.length; i++) {
michael@0 667 c = aStr[i];
michael@0 668
michael@0 669 switch (state) {
michael@0 670 // Normal JS state.
michael@0 671 case STATE_NORMAL:
michael@0 672 if (c == '"') {
michael@0 673 state = STATE_DQUOTE;
michael@0 674 }
michael@0 675 else if (c == "'") {
michael@0 676 state = STATE_QUOTE;
michael@0 677 }
michael@0 678 else if (c == ";") {
michael@0 679 start = i + 1;
michael@0 680 }
michael@0 681 else if (c == " ") {
michael@0 682 start = i + 1;
michael@0 683 }
michael@0 684 else if (OPEN_BODY.indexOf(c) != -1) {
michael@0 685 bodyStack.push({
michael@0 686 token: c,
michael@0 687 start: start
michael@0 688 });
michael@0 689 start = i + 1;
michael@0 690 }
michael@0 691 else if (CLOSE_BODY.indexOf(c) != -1) {
michael@0 692 var last = bodyStack.pop();
michael@0 693 if (!last || OPEN_CLOSE_BODY[last.token] != c) {
michael@0 694 return {
michael@0 695 err: "syntax error"
michael@0 696 };
michael@0 697 }
michael@0 698 if (c == "}") {
michael@0 699 start = i + 1;
michael@0 700 }
michael@0 701 else {
michael@0 702 start = last.start;
michael@0 703 }
michael@0 704 }
michael@0 705 break;
michael@0 706
michael@0 707 // Double quote state > " <
michael@0 708 case STATE_DQUOTE:
michael@0 709 if (c == "\\") {
michael@0 710 i++;
michael@0 711 }
michael@0 712 else if (c == "\n") {
michael@0 713 return {
michael@0 714 err: "unterminated string literal"
michael@0 715 };
michael@0 716 }
michael@0 717 else if (c == '"') {
michael@0 718 state = STATE_NORMAL;
michael@0 719 }
michael@0 720 break;
michael@0 721
michael@0 722 // Single quote state > ' <
michael@0 723 case STATE_QUOTE:
michael@0 724 if (c == "\\") {
michael@0 725 i++;
michael@0 726 }
michael@0 727 else if (c == "\n") {
michael@0 728 return {
michael@0 729 err: "unterminated string literal"
michael@0 730 };
michael@0 731 }
michael@0 732 else if (c == "'") {
michael@0 733 state = STATE_NORMAL;
michael@0 734 }
michael@0 735 break;
michael@0 736 }
michael@0 737 }
michael@0 738
michael@0 739 return {
michael@0 740 state: state,
michael@0 741 startPos: start
michael@0 742 };
michael@0 743 }
michael@0 744
michael@0 745 /**
michael@0 746 * Provides a list of properties, that are possible matches based on the passed
michael@0 747 * Debugger.Environment/Debugger.Object and inputValue.
michael@0 748 *
michael@0 749 * @param object aDbgObject
michael@0 750 * When the debugger is not paused this Debugger.Object wraps the scope for autocompletion.
michael@0 751 * It is null if the debugger is paused.
michael@0 752 * @param object anEnvironment
michael@0 753 * When the debugger is paused this Debugger.Environment is the scope for autocompletion.
michael@0 754 * It is null if the debugger is not paused.
michael@0 755 * @param string aInputValue
michael@0 756 * Value that should be completed.
michael@0 757 * @param number [aCursor=aInputValue.length]
michael@0 758 * Optional offset in the input where the cursor is located. If this is
michael@0 759 * omitted then the cursor is assumed to be at the end of the input
michael@0 760 * value.
michael@0 761 * @returns null or object
michael@0 762 * If no completion valued could be computed, null is returned,
michael@0 763 * otherwise a object with the following form is returned:
michael@0 764 * {
michael@0 765 * matches: [ string, string, string ],
michael@0 766 * matchProp: Last part of the inputValue that was used to find
michael@0 767 * the matches-strings.
michael@0 768 * }
michael@0 769 */
michael@0 770 function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
michael@0 771 {
michael@0 772 if (aCursor === undefined) {
michael@0 773 aCursor = aInputValue.length;
michael@0 774 }
michael@0 775
michael@0 776 let inputValue = aInputValue.substring(0, aCursor);
michael@0 777
michael@0 778 // Analyse the inputValue and find the beginning of the last part that
michael@0 779 // should be completed.
michael@0 780 let beginning = findCompletionBeginning(inputValue);
michael@0 781
michael@0 782 // There was an error analysing the string.
michael@0 783 if (beginning.err) {
michael@0 784 return null;
michael@0 785 }
michael@0 786
michael@0 787 // If the current state is not STATE_NORMAL, then we are inside of an string
michael@0 788 // which means that no completion is possible.
michael@0 789 if (beginning.state != STATE_NORMAL) {
michael@0 790 return null;
michael@0 791 }
michael@0 792
michael@0 793 let completionPart = inputValue.substring(beginning.startPos);
michael@0 794
michael@0 795 // Don't complete on just an empty string.
michael@0 796 if (completionPart.trim() == "") {
michael@0 797 return null;
michael@0 798 }
michael@0 799
michael@0 800 let lastDot = completionPart.lastIndexOf(".");
michael@0 801 if (lastDot > 0 &&
michael@0 802 (completionPart[0] == "'" || completionPart[0] == '"') &&
michael@0 803 completionPart[lastDot - 1] == completionPart[0]) {
michael@0 804 // We are completing a string literal.
michael@0 805 let matchProp = completionPart.slice(lastDot + 1);
michael@0 806 return getMatchedProps(String.prototype, matchProp);
michael@0 807 }
michael@0 808
michael@0 809 // We are completing a variable / a property lookup.
michael@0 810 let properties = completionPart.split(".");
michael@0 811 let matchProp = properties.pop().trimLeft();
michael@0 812 let obj = aDbgObject;
michael@0 813
michael@0 814 // The first property must be found in the environment if the debugger is
michael@0 815 // paused.
michael@0 816 if (anEnvironment) {
michael@0 817 if (properties.length == 0) {
michael@0 818 return getMatchedPropsInEnvironment(anEnvironment, matchProp);
michael@0 819 }
michael@0 820 obj = getVariableInEnvironment(anEnvironment, properties.shift());
michael@0 821 }
michael@0 822
michael@0 823 if (!isObjectUsable(obj)) {
michael@0 824 return null;
michael@0 825 }
michael@0 826
michael@0 827 // We get the rest of the properties recursively starting from the Debugger.Object
michael@0 828 // that wraps the first property
michael@0 829 for (let prop of properties) {
michael@0 830 prop = prop.trim();
michael@0 831 if (!prop) {
michael@0 832 return null;
michael@0 833 }
michael@0 834
michael@0 835 if (/\[\d+\]$/.test(prop)) {
michael@0 836 // The property to autocomplete is a member of array. For example
michael@0 837 // list[i][j]..[n]. Traverse the array to get the actual element.
michael@0 838 obj = getArrayMemberProperty(obj, prop);
michael@0 839 }
michael@0 840 else {
michael@0 841 obj = DevToolsUtils.getProperty(obj, prop);
michael@0 842 }
michael@0 843
michael@0 844 if (!isObjectUsable(obj)) {
michael@0 845 return null;
michael@0 846 }
michael@0 847 }
michael@0 848
michael@0 849 // If the final property is a primitive
michael@0 850 if (typeof obj != "object") {
michael@0 851 return getMatchedProps(obj, matchProp);
michael@0 852 }
michael@0 853
michael@0 854 return getMatchedPropsInDbgObject(obj, matchProp);
michael@0 855 }
michael@0 856
michael@0 857 /**
michael@0 858 * Get the array member of aObj for the given aProp. For example, given
michael@0 859 * aProp='list[0][1]' the element at [0][1] of aObj.list is returned.
michael@0 860 *
michael@0 861 * @param object aObj
michael@0 862 * The object to operate on.
michael@0 863 * @param string aProp
michael@0 864 * The property to return.
michael@0 865 * @return null or Object
michael@0 866 * Returns null if the property couldn't be located. Otherwise the array
michael@0 867 * member identified by aProp.
michael@0 868 */
michael@0 869 function getArrayMemberProperty(aObj, aProp)
michael@0 870 {
michael@0 871 // First get the array.
michael@0 872 let obj = aObj;
michael@0 873 let propWithoutIndices = aProp.substr(0, aProp.indexOf("["));
michael@0 874 obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
michael@0 875 if (!isObjectUsable(obj)) {
michael@0 876 return null;
michael@0 877 }
michael@0 878
michael@0 879 // Then traverse the list of indices to get the actual element.
michael@0 880 let result;
michael@0 881 let arrayIndicesRegex = /\[[^\]]*\]/g;
michael@0 882 while ((result = arrayIndicesRegex.exec(aProp)) !== null) {
michael@0 883 let indexWithBrackets = result[0];
michael@0 884 let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
michael@0 885 let index = parseInt(indexAsText);
michael@0 886
michael@0 887 if (isNaN(index)) {
michael@0 888 return null;
michael@0 889 }
michael@0 890
michael@0 891 obj = DevToolsUtils.getProperty(obj, index);
michael@0 892
michael@0 893 if (!isObjectUsable(obj)) {
michael@0 894 return null;
michael@0 895 }
michael@0 896 }
michael@0 897
michael@0 898 return obj;
michael@0 899 }
michael@0 900
michael@0 901 /**
michael@0 902 * Check if the given Debugger.Object can be used for autocomplete.
michael@0 903 *
michael@0 904 * @param Debugger.Object aObject
michael@0 905 * The Debugger.Object to check.
michael@0 906 * @return boolean
michael@0 907 * True if further inspection into the object is possible, or false
michael@0 908 * otherwise.
michael@0 909 */
michael@0 910 function isObjectUsable(aObject)
michael@0 911 {
michael@0 912 if (aObject == null) {
michael@0 913 return false;
michael@0 914 }
michael@0 915
michael@0 916 if (typeof aObject == "object" && aObject.class == "DeadObject") {
michael@0 917 return false;
michael@0 918 }
michael@0 919
michael@0 920 return true;
michael@0 921 }
michael@0 922
michael@0 923 /**
michael@0 924 * @see getExactMatch_impl()
michael@0 925 */
michael@0 926 function getVariableInEnvironment(anEnvironment, aName)
michael@0 927 {
michael@0 928 return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport);
michael@0 929 }
michael@0 930
michael@0 931 /**
michael@0 932 * @see getMatchedProps_impl()
michael@0 933 */
michael@0 934 function getMatchedPropsInEnvironment(anEnvironment, aMatch)
michael@0 935 {
michael@0 936 return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport);
michael@0 937 }
michael@0 938
michael@0 939 /**
michael@0 940 * @see getMatchedProps_impl()
michael@0 941 */
michael@0 942 function getMatchedPropsInDbgObject(aDbgObject, aMatch)
michael@0 943 {
michael@0 944 return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport);
michael@0 945 }
michael@0 946
michael@0 947 /**
michael@0 948 * @see getMatchedProps_impl()
michael@0 949 */
michael@0 950 function getMatchedProps(aObj, aMatch)
michael@0 951 {
michael@0 952 if (typeof aObj != "object") {
michael@0 953 aObj = aObj.constructor.prototype;
michael@0 954 }
michael@0 955 return getMatchedProps_impl(aObj, aMatch, JSObjectSupport);
michael@0 956 }
michael@0 957
michael@0 958 /**
michael@0 959 * Get all properties in the given object (and its parent prototype chain) that
michael@0 960 * match a given prefix.
michael@0 961 *
michael@0 962 * @param mixed aObj
michael@0 963 * Object whose properties we want to filter.
michael@0 964 * @param string aMatch
michael@0 965 * Filter for properties that match this string.
michael@0 966 * @return object
michael@0 967 * Object that contains the matchProp and the list of names.
michael@0 968 */
michael@0 969 function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties})
michael@0 970 {
michael@0 971 let matches = new Set();
michael@0 972
michael@0 973 // We need to go up the prototype chain.
michael@0 974 let iter = chainIterator(aObj);
michael@0 975 for (let obj of iter) {
michael@0 976 let props = getProperties(obj);
michael@0 977 for (let prop of props) {
michael@0 978 if (prop.indexOf(aMatch) != 0) {
michael@0 979 continue;
michael@0 980 }
michael@0 981
michael@0 982 // If it is an array index, we can't take it.
michael@0 983 // This uses a trick: converting a string to a number yields NaN if
michael@0 984 // the operation failed, and NaN is not equal to itself.
michael@0 985 if (+prop != +prop) {
michael@0 986 matches.add(prop);
michael@0 987 }
michael@0 988
michael@0 989 if (matches.size > MAX_COMPLETIONS) {
michael@0 990 break;
michael@0 991 }
michael@0 992 }
michael@0 993
michael@0 994 if (matches.size > MAX_COMPLETIONS) {
michael@0 995 break;
michael@0 996 }
michael@0 997 }
michael@0 998
michael@0 999 return {
michael@0 1000 matchProp: aMatch,
michael@0 1001 matches: [...matches],
michael@0 1002 };
michael@0 1003 }
michael@0 1004
michael@0 1005 /**
michael@0 1006 * Returns a property value based on its name from the given object, by
michael@0 1007 * recursively checking the object's prototype.
michael@0 1008 *
michael@0 1009 * @param object aObj
michael@0 1010 * An object to look the property into.
michael@0 1011 * @param string aName
michael@0 1012 * The property that is looked up.
michael@0 1013 * @returns object|undefined
michael@0 1014 * A Debugger.Object if the property exists in the object's prototype
michael@0 1015 * chain, undefined otherwise.
michael@0 1016 */
michael@0 1017 function getExactMatch_impl(aObj, aName, {chainIterator, getProperty})
michael@0 1018 {
michael@0 1019 // We need to go up the prototype chain.
michael@0 1020 let iter = chainIterator(aObj);
michael@0 1021 for (let obj of iter) {
michael@0 1022 let prop = getProperty(obj, aName, aObj);
michael@0 1023 if (prop) {
michael@0 1024 return prop.value;
michael@0 1025 }
michael@0 1026 }
michael@0 1027 return undefined;
michael@0 1028 }
michael@0 1029
michael@0 1030
michael@0 1031 let JSObjectSupport = {
michael@0 1032 chainIterator: function(aObj)
michael@0 1033 {
michael@0 1034 while (aObj) {
michael@0 1035 yield aObj;
michael@0 1036 aObj = Object.getPrototypeOf(aObj);
michael@0 1037 }
michael@0 1038 },
michael@0 1039
michael@0 1040 getProperties: function(aObj)
michael@0 1041 {
michael@0 1042 return Object.getOwnPropertyNames(aObj);
michael@0 1043 },
michael@0 1044
michael@0 1045 getProperty: function()
michael@0 1046 {
michael@0 1047 // getProperty is unsafe with raw JS objects.
michael@0 1048 throw "Unimplemented!";
michael@0 1049 },
michael@0 1050 };
michael@0 1051
michael@0 1052 let DebuggerObjectSupport = {
michael@0 1053 chainIterator: function(aObj)
michael@0 1054 {
michael@0 1055 while (aObj) {
michael@0 1056 yield aObj;
michael@0 1057 aObj = aObj.proto;
michael@0 1058 }
michael@0 1059 },
michael@0 1060
michael@0 1061 getProperties: function(aObj)
michael@0 1062 {
michael@0 1063 return aObj.getOwnPropertyNames();
michael@0 1064 },
michael@0 1065
michael@0 1066 getProperty: function(aObj, aName, aRootObj)
michael@0 1067 {
michael@0 1068 // This is left unimplemented in favor to DevToolsUtils.getProperty().
michael@0 1069 throw "Unimplemented!";
michael@0 1070 },
michael@0 1071 };
michael@0 1072
michael@0 1073 let DebuggerEnvironmentSupport = {
michael@0 1074 chainIterator: function(aObj)
michael@0 1075 {
michael@0 1076 while (aObj) {
michael@0 1077 yield aObj;
michael@0 1078 aObj = aObj.parent;
michael@0 1079 }
michael@0 1080 },
michael@0 1081
michael@0 1082 getProperties: function(aObj)
michael@0 1083 {
michael@0 1084 return aObj.names();
michael@0 1085 },
michael@0 1086
michael@0 1087 getProperty: function(aObj, aName)
michael@0 1088 {
michael@0 1089 // TODO: we should use getVariableDescriptor() here - bug 725815.
michael@0 1090 let result = aObj.getVariable(aName);
michael@0 1091 // FIXME: Need actual UI, bug 941287.
michael@0 1092 if (result.optimizedOut || result.missingArguments) {
michael@0 1093 return null;
michael@0 1094 }
michael@0 1095 return result === undefined ? null : { value: result };
michael@0 1096 },
michael@0 1097 };
michael@0 1098
michael@0 1099
michael@0 1100 exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
michael@0 1101 })(WebConsoleUtils);
michael@0 1102
michael@0 1103 ///////////////////////////////////////////////////////////////////////////////
michael@0 1104 // The page errors listener
michael@0 1105 ///////////////////////////////////////////////////////////////////////////////
michael@0 1106
michael@0 1107 /**
michael@0 1108 * The nsIConsoleService listener. This is used to send all of the console
michael@0 1109 * messages (JavaScript, CSS and more) to the remote Web Console instance.
michael@0 1110 *
michael@0 1111 * @constructor
michael@0 1112 * @param nsIDOMWindow [aWindow]
michael@0 1113 * Optional - the window object for which we are created. This is used
michael@0 1114 * for filtering out messages that belong to other windows.
michael@0 1115 * @param object aListener
michael@0 1116 * The listener object must have one method:
michael@0 1117 * - onConsoleServiceMessage(). This method is invoked with one argument,
michael@0 1118 * the nsIConsoleMessage, whenever a relevant message is received.
michael@0 1119 */
michael@0 1120 function ConsoleServiceListener(aWindow, aListener)
michael@0 1121 {
michael@0 1122 this.window = aWindow;
michael@0 1123 this.listener = aListener;
michael@0 1124 if (this.window) {
michael@0 1125 this.layoutHelpers = new LayoutHelpers(this.window);
michael@0 1126 }
michael@0 1127 }
michael@0 1128 exports.ConsoleServiceListener = ConsoleServiceListener;
michael@0 1129
michael@0 1130 ConsoleServiceListener.prototype =
michael@0 1131 {
michael@0 1132 QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
michael@0 1133
michael@0 1134 /**
michael@0 1135 * The content window for which we listen to page errors.
michael@0 1136 * @type nsIDOMWindow
michael@0 1137 */
michael@0 1138 window: null,
michael@0 1139
michael@0 1140 /**
michael@0 1141 * The listener object which is notified of messages from the console service.
michael@0 1142 * @type object
michael@0 1143 */
michael@0 1144 listener: null,
michael@0 1145
michael@0 1146 /**
michael@0 1147 * Initialize the nsIConsoleService listener.
michael@0 1148 */
michael@0 1149 init: function CSL_init()
michael@0 1150 {
michael@0 1151 Services.console.registerListener(this);
michael@0 1152 },
michael@0 1153
michael@0 1154 /**
michael@0 1155 * The nsIConsoleService observer. This method takes all the script error
michael@0 1156 * messages belonging to the current window and sends them to the remote Web
michael@0 1157 * Console instance.
michael@0 1158 *
michael@0 1159 * @param nsIConsoleMessage aMessage
michael@0 1160 * The message object coming from the nsIConsoleService.
michael@0 1161 */
michael@0 1162 observe: function CSL_observe(aMessage)
michael@0 1163 {
michael@0 1164 if (!this.listener) {
michael@0 1165 return;
michael@0 1166 }
michael@0 1167
michael@0 1168 if (this.window) {
michael@0 1169 if (!(aMessage instanceof Ci.nsIScriptError) ||
michael@0 1170 !aMessage.outerWindowID ||
michael@0 1171 !this.isCategoryAllowed(aMessage.category)) {
michael@0 1172 return;
michael@0 1173 }
michael@0 1174
michael@0 1175 let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
michael@0 1176 if (!errorWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(errorWindow)) {
michael@0 1177 return;
michael@0 1178 }
michael@0 1179 }
michael@0 1180
michael@0 1181 this.listener.onConsoleServiceMessage(aMessage);
michael@0 1182 },
michael@0 1183
michael@0 1184 /**
michael@0 1185 * Check if the given message category is allowed to be tracked or not.
michael@0 1186 * We ignore chrome-originating errors as we only care about content.
michael@0 1187 *
michael@0 1188 * @param string aCategory
michael@0 1189 * The message category you want to check.
michael@0 1190 * @return boolean
michael@0 1191 * True if the category is allowed to be logged, false otherwise.
michael@0 1192 */
michael@0 1193 isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
michael@0 1194 {
michael@0 1195 if (!aCategory) {
michael@0 1196 return false;
michael@0 1197 }
michael@0 1198
michael@0 1199 switch (aCategory) {
michael@0 1200 case "XPConnect JavaScript":
michael@0 1201 case "component javascript":
michael@0 1202 case "chrome javascript":
michael@0 1203 case "chrome registration":
michael@0 1204 case "XBL":
michael@0 1205 case "XBL Prototype Handler":
michael@0 1206 case "XBL Content Sink":
michael@0 1207 case "xbl javascript":
michael@0 1208 return false;
michael@0 1209 }
michael@0 1210
michael@0 1211 return true;
michael@0 1212 },
michael@0 1213
michael@0 1214 /**
michael@0 1215 * Get the cached page errors for the current inner window and its (i)frames.
michael@0 1216 *
michael@0 1217 * @param boolean [aIncludePrivate=false]
michael@0 1218 * Tells if you want to also retrieve messages coming from private
michael@0 1219 * windows. Defaults to false.
michael@0 1220 * @return array
michael@0 1221 * The array of cached messages. Each element is an nsIScriptError or
michael@0 1222 * an nsIConsoleMessage
michael@0 1223 */
michael@0 1224 getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
michael@0 1225 {
michael@0 1226 let errors = Services.console.getMessageArray() || [];
michael@0 1227
michael@0 1228 // if !this.window, we're in a browser console. Still need to filter
michael@0 1229 // private messages.
michael@0 1230 if (!this.window) {
michael@0 1231 return errors.filter((aError) => {
michael@0 1232 if (aError instanceof Ci.nsIScriptError) {
michael@0 1233 if (!aIncludePrivate && aError.isFromPrivateWindow) {
michael@0 1234 return false;
michael@0 1235 }
michael@0 1236 }
michael@0 1237
michael@0 1238 return true;
michael@0 1239 });
michael@0 1240 }
michael@0 1241
michael@0 1242 let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
michael@0 1243
michael@0 1244 return errors.filter((aError) => {
michael@0 1245 if (aError instanceof Ci.nsIScriptError) {
michael@0 1246 if (!aIncludePrivate && aError.isFromPrivateWindow) {
michael@0 1247 return false;
michael@0 1248 }
michael@0 1249 if (ids &&
michael@0 1250 (ids.indexOf(aError.innerWindowID) == -1 ||
michael@0 1251 !this.isCategoryAllowed(aError.category))) {
michael@0 1252 return false;
michael@0 1253 }
michael@0 1254 }
michael@0 1255 else if (ids && ids[0]) {
michael@0 1256 // If this is not an nsIScriptError and we need to do window-based
michael@0 1257 // filtering we skip this message.
michael@0 1258 return false;
michael@0 1259 }
michael@0 1260
michael@0 1261 return true;
michael@0 1262 });
michael@0 1263 },
michael@0 1264
michael@0 1265 /**
michael@0 1266 * Remove the nsIConsoleService listener.
michael@0 1267 */
michael@0 1268 destroy: function CSL_destroy()
michael@0 1269 {
michael@0 1270 Services.console.unregisterListener(this);
michael@0 1271 this.listener = this.window = null;
michael@0 1272 },
michael@0 1273 };
michael@0 1274
michael@0 1275
michael@0 1276 ///////////////////////////////////////////////////////////////////////////////
michael@0 1277 // The window.console API observer
michael@0 1278 ///////////////////////////////////////////////////////////////////////////////
michael@0 1279
michael@0 1280 /**
michael@0 1281 * The window.console API observer. This allows the window.console API messages
michael@0 1282 * to be sent to the remote Web Console instance.
michael@0 1283 *
michael@0 1284 * @constructor
michael@0 1285 * @param nsIDOMWindow aWindow
michael@0 1286 * Optional - the window object for which we are created. This is used
michael@0 1287 * for filtering out messages that belong to other windows.
michael@0 1288 * @param object aOwner
michael@0 1289 * The owner object must have the following methods:
michael@0 1290 * - onConsoleAPICall(). This method is invoked with one argument, the
michael@0 1291 * Console API message that comes from the observer service, whenever
michael@0 1292 * a relevant console API call is received.
michael@0 1293 */
michael@0 1294 function ConsoleAPIListener(aWindow, aOwner)
michael@0 1295 {
michael@0 1296 this.window = aWindow;
michael@0 1297 this.owner = aOwner;
michael@0 1298 if (this.window) {
michael@0 1299 this.layoutHelpers = new LayoutHelpers(this.window);
michael@0 1300 }
michael@0 1301 }
michael@0 1302 exports.ConsoleAPIListener = ConsoleAPIListener;
michael@0 1303
michael@0 1304 ConsoleAPIListener.prototype =
michael@0 1305 {
michael@0 1306 QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
michael@0 1307
michael@0 1308 /**
michael@0 1309 * The content window for which we listen to window.console API calls.
michael@0 1310 * @type nsIDOMWindow
michael@0 1311 */
michael@0 1312 window: null,
michael@0 1313
michael@0 1314 /**
michael@0 1315 * The owner object which is notified of window.console API calls. It must
michael@0 1316 * have a onConsoleAPICall method which is invoked with one argument: the
michael@0 1317 * console API call object that comes from the observer service.
michael@0 1318 *
michael@0 1319 * @type object
michael@0 1320 * @see WebConsoleActor
michael@0 1321 */
michael@0 1322 owner: null,
michael@0 1323
michael@0 1324 /**
michael@0 1325 * Initialize the window.console API observer.
michael@0 1326 */
michael@0 1327 init: function CAL_init()
michael@0 1328 {
michael@0 1329 // Note that the observer is process-wide. We will filter the messages as
michael@0 1330 // needed, see CAL_observe().
michael@0 1331 Services.obs.addObserver(this, "console-api-log-event", false);
michael@0 1332 },
michael@0 1333
michael@0 1334 /**
michael@0 1335 * The console API message observer. When messages are received from the
michael@0 1336 * observer service we forward them to the remote Web Console instance.
michael@0 1337 *
michael@0 1338 * @param object aMessage
michael@0 1339 * The message object receives from the observer service.
michael@0 1340 * @param string aTopic
michael@0 1341 * The message topic received from the observer service.
michael@0 1342 */
michael@0 1343 observe: function CAL_observe(aMessage, aTopic)
michael@0 1344 {
michael@0 1345 if (!this.owner) {
michael@0 1346 return;
michael@0 1347 }
michael@0 1348
michael@0 1349 let apiMessage = aMessage.wrappedJSObject;
michael@0 1350 if (this.window) {
michael@0 1351 let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID);
michael@0 1352 if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) {
michael@0 1353 // Not the same window!
michael@0 1354 return;
michael@0 1355 }
michael@0 1356 }
michael@0 1357
michael@0 1358 this.owner.onConsoleAPICall(apiMessage);
michael@0 1359 },
michael@0 1360
michael@0 1361 /**
michael@0 1362 * Get the cached messages for the current inner window and its (i)frames.
michael@0 1363 *
michael@0 1364 * @param boolean [aIncludePrivate=false]
michael@0 1365 * Tells if you want to also retrieve messages coming from private
michael@0 1366 * windows. Defaults to false.
michael@0 1367 * @return array
michael@0 1368 * The array of cached messages.
michael@0 1369 */
michael@0 1370 getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false)
michael@0 1371 {
michael@0 1372 let messages = [];
michael@0 1373 let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
michael@0 1374 .getService(Ci.nsIConsoleAPIStorage);
michael@0 1375
michael@0 1376 // if !this.window, we're in a browser console. Retrieve all events
michael@0 1377 // for filtering based on privacy.
michael@0 1378 if (!this.window) {
michael@0 1379 messages = ConsoleAPIStorage.getEvents();
michael@0 1380 } else {
michael@0 1381 let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
michael@0 1382 ids.forEach((id) => {
michael@0 1383 messages = messages.concat(ConsoleAPIStorage.getEvents(id));
michael@0 1384 });
michael@0 1385 }
michael@0 1386
michael@0 1387 if (aIncludePrivate) {
michael@0 1388 return messages;
michael@0 1389 }
michael@0 1390
michael@0 1391 return messages.filter((m) => !m.private);
michael@0 1392 },
michael@0 1393
michael@0 1394 /**
michael@0 1395 * Destroy the console API listener.
michael@0 1396 */
michael@0 1397 destroy: function CAL_destroy()
michael@0 1398 {
michael@0 1399 Services.obs.removeObserver(this, "console-api-log-event");
michael@0 1400 this.window = this.owner = null;
michael@0 1401 },
michael@0 1402 };
michael@0 1403
michael@0 1404
michael@0 1405
michael@0 1406 /**
michael@0 1407 * JSTerm helper functions.
michael@0 1408 *
michael@0 1409 * Defines a set of functions ("helper functions") that are available from the
michael@0 1410 * Web Console but not from the web page.
michael@0 1411 *
michael@0 1412 * A list of helper functions used by Firebug can be found here:
michael@0 1413 * http://getfirebug.com/wiki/index.php/Command_Line_API
michael@0 1414 *
michael@0 1415 * @param object aOwner
michael@0 1416 * The owning object.
michael@0 1417 */
michael@0 1418 function JSTermHelpers(aOwner)
michael@0 1419 {
michael@0 1420 /**
michael@0 1421 * Find a node by ID.
michael@0 1422 *
michael@0 1423 * @param string aId
michael@0 1424 * The ID of the element you want.
michael@0 1425 * @return nsIDOMNode or null
michael@0 1426 * The result of calling document.querySelector(aSelector).
michael@0 1427 */
michael@0 1428 aOwner.sandbox.$ = function JSTH_$(aSelector)
michael@0 1429 {
michael@0 1430 return aOwner.window.document.querySelector(aSelector);
michael@0 1431 };
michael@0 1432
michael@0 1433 /**
michael@0 1434 * Find the nodes matching a CSS selector.
michael@0 1435 *
michael@0 1436 * @param string aSelector
michael@0 1437 * A string that is passed to window.document.querySelectorAll.
michael@0 1438 * @return nsIDOMNodeList
michael@0 1439 * Returns the result of document.querySelectorAll(aSelector).
michael@0 1440 */
michael@0 1441 aOwner.sandbox.$$ = function JSTH_$$(aSelector)
michael@0 1442 {
michael@0 1443 return aOwner.window.document.querySelectorAll(aSelector);
michael@0 1444 };
michael@0 1445
michael@0 1446 /**
michael@0 1447 * Runs an xPath query and returns all matched nodes.
michael@0 1448 *
michael@0 1449 * @param string aXPath
michael@0 1450 * xPath search query to execute.
michael@0 1451 * @param [optional] nsIDOMNode aContext
michael@0 1452 * Context to run the xPath query on. Uses window.document if not set.
michael@0 1453 * @return array of nsIDOMNode
michael@0 1454 */
michael@0 1455 aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
michael@0 1456 {
michael@0 1457 let nodes = new aOwner.window.wrappedJSObject.Array();
michael@0 1458 let doc = aOwner.window.document;
michael@0 1459 aContext = aContext || doc;
michael@0 1460
michael@0 1461 let results = doc.evaluate(aXPath, aContext, null,
michael@0 1462 Ci.nsIDOMXPathResult.ANY_TYPE, null);
michael@0 1463 let node;
michael@0 1464 while ((node = results.iterateNext())) {
michael@0 1465 nodes.push(node);
michael@0 1466 }
michael@0 1467
michael@0 1468 return nodes;
michael@0 1469 };
michael@0 1470
michael@0 1471 /**
michael@0 1472 * Returns the currently selected object in the highlighter.
michael@0 1473 *
michael@0 1474 * TODO: this implementation crosses the client/server boundaries! This is not
michael@0 1475 * usable within a remote browser. To implement this feature correctly we need
michael@0 1476 * support for remote inspection capabilities within the Inspector as well.
michael@0 1477 * See bug 787975.
michael@0 1478 *
michael@0 1479 * @return nsIDOMElement|null
michael@0 1480 * The DOM element currently selected in the highlighter.
michael@0 1481 */
michael@0 1482 Object.defineProperty(aOwner.sandbox, "$0", {
michael@0 1483 get: function() {
michael@0 1484 let window = aOwner.chromeWindow();
michael@0 1485 if (!window) {
michael@0 1486 return null;
michael@0 1487 }
michael@0 1488
michael@0 1489 let target = null;
michael@0 1490 try {
michael@0 1491 target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
michael@0 1492 }
michael@0 1493 catch (ex) {
michael@0 1494 // If we report this exception the user will get it in the Browser
michael@0 1495 // Console every time when she evaluates any string.
michael@0 1496 }
michael@0 1497
michael@0 1498 if (!target) {
michael@0 1499 return null;
michael@0 1500 }
michael@0 1501
michael@0 1502 let toolbox = gDevTools.getToolbox(target);
michael@0 1503 let node = toolbox && toolbox.selection ? toolbox.selection.node : null;
michael@0 1504
michael@0 1505 return node ? aOwner.makeDebuggeeValue(node) : null;
michael@0 1506 },
michael@0 1507 enumerable: true,
michael@0 1508 configurable: false
michael@0 1509 });
michael@0 1510
michael@0 1511 /**
michael@0 1512 * Clears the output of the JSTerm.
michael@0 1513 */
michael@0 1514 aOwner.sandbox.clear = function JSTH_clear()
michael@0 1515 {
michael@0 1516 aOwner.helperResult = {
michael@0 1517 type: "clearOutput",
michael@0 1518 };
michael@0 1519 };
michael@0 1520
michael@0 1521 /**
michael@0 1522 * Returns the result of Object.keys(aObject).
michael@0 1523 *
michael@0 1524 * @param object aObject
michael@0 1525 * Object to return the property names from.
michael@0 1526 * @return array of strings
michael@0 1527 */
michael@0 1528 aOwner.sandbox.keys = function JSTH_keys(aObject)
michael@0 1529 {
michael@0 1530 return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
michael@0 1531 };
michael@0 1532
michael@0 1533 /**
michael@0 1534 * Returns the values of all properties on aObject.
michael@0 1535 *
michael@0 1536 * @param object aObject
michael@0 1537 * Object to display the values from.
michael@0 1538 * @return array of string
michael@0 1539 */
michael@0 1540 aOwner.sandbox.values = function JSTH_values(aObject)
michael@0 1541 {
michael@0 1542 let arrValues = new aOwner.window.wrappedJSObject.Array();
michael@0 1543 let obj = WebConsoleUtils.unwrap(aObject);
michael@0 1544
michael@0 1545 for (let prop in obj) {
michael@0 1546 arrValues.push(obj[prop]);
michael@0 1547 }
michael@0 1548
michael@0 1549 return arrValues;
michael@0 1550 };
michael@0 1551
michael@0 1552 /**
michael@0 1553 * Opens a help window in MDN.
michael@0 1554 */
michael@0 1555 aOwner.sandbox.help = function JSTH_help()
michael@0 1556 {
michael@0 1557 aOwner.helperResult = { type: "help" };
michael@0 1558 };
michael@0 1559
michael@0 1560 /**
michael@0 1561 * Change the JS evaluation scope.
michael@0 1562 *
michael@0 1563 * @param DOMElement|string|window aWindow
michael@0 1564 * The window object to use for eval scope. This can be a string that
michael@0 1565 * is used to perform document.querySelector(), to find the iframe that
michael@0 1566 * you want to cd() to. A DOMElement can be given as well, the
michael@0 1567 * .contentWindow property is used. Lastly, you can directly pass
michael@0 1568 * a window object. If you call cd() with no arguments, the current
michael@0 1569 * eval scope is cleared back to its default (the top window).
michael@0 1570 */
michael@0 1571 aOwner.sandbox.cd = function JSTH_cd(aWindow)
michael@0 1572 {
michael@0 1573 if (!aWindow) {
michael@0 1574 aOwner.consoleActor.evalWindow = null;
michael@0 1575 aOwner.helperResult = { type: "cd" };
michael@0 1576 return;
michael@0 1577 }
michael@0 1578
michael@0 1579 if (typeof aWindow == "string") {
michael@0 1580 aWindow = aOwner.window.document.querySelector(aWindow);
michael@0 1581 }
michael@0 1582 if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
michael@0 1583 aWindow = aWindow.contentWindow;
michael@0 1584 }
michael@0 1585 if (!(aWindow instanceof Ci.nsIDOMWindow)) {
michael@0 1586 aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
michael@0 1587 return;
michael@0 1588 }
michael@0 1589
michael@0 1590 aOwner.consoleActor.evalWindow = aWindow;
michael@0 1591 aOwner.helperResult = { type: "cd" };
michael@0 1592 };
michael@0 1593
michael@0 1594 /**
michael@0 1595 * Inspects the passed aObject. This is done by opening the PropertyPanel.
michael@0 1596 *
michael@0 1597 * @param object aObject
michael@0 1598 * Object to inspect.
michael@0 1599 */
michael@0 1600 aOwner.sandbox.inspect = function JSTH_inspect(aObject)
michael@0 1601 {
michael@0 1602 let dbgObj = aOwner.makeDebuggeeValue(aObject);
michael@0 1603 let grip = aOwner.createValueGrip(dbgObj);
michael@0 1604 aOwner.helperResult = {
michael@0 1605 type: "inspectObject",
michael@0 1606 input: aOwner.evalInput,
michael@0 1607 object: grip,
michael@0 1608 };
michael@0 1609 };
michael@0 1610
michael@0 1611 /**
michael@0 1612 * Prints aObject to the output.
michael@0 1613 *
michael@0 1614 * @param object aObject
michael@0 1615 * Object to print to the output.
michael@0 1616 * @return string
michael@0 1617 */
michael@0 1618 aOwner.sandbox.pprint = function JSTH_pprint(aObject)
michael@0 1619 {
michael@0 1620 if (aObject === null || aObject === undefined || aObject === true ||
michael@0 1621 aObject === false) {
michael@0 1622 aOwner.helperResult = {
michael@0 1623 type: "error",
michael@0 1624 message: "helperFuncUnsupportedTypeError",
michael@0 1625 };
michael@0 1626 return null;
michael@0 1627 }
michael@0 1628
michael@0 1629 aOwner.helperResult = { rawOutput: true };
michael@0 1630
michael@0 1631 if (typeof aObject == "function") {
michael@0 1632 return aObject + "\n";
michael@0 1633 }
michael@0 1634
michael@0 1635 let output = [];
michael@0 1636
michael@0 1637 let obj = WebConsoleUtils.unwrap(aObject);
michael@0 1638 for (let name in obj) {
michael@0 1639 let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
michael@0 1640 if (desc.get || desc.set) {
michael@0 1641 // TODO: Bug 842672 - toolkit/ imports modules from browser/.
michael@0 1642 let getGrip = VariablesView.getGrip(desc.get);
michael@0 1643 let setGrip = VariablesView.getGrip(desc.set);
michael@0 1644 let getString = VariablesView.getString(getGrip);
michael@0 1645 let setString = VariablesView.getString(setGrip);
michael@0 1646 output.push(name + ":", " get: " + getString, " set: " + setString);
michael@0 1647 }
michael@0 1648 else {
michael@0 1649 let valueGrip = VariablesView.getGrip(obj[name]);
michael@0 1650 let valueString = VariablesView.getString(valueGrip);
michael@0 1651 output.push(name + ": " + valueString);
michael@0 1652 }
michael@0 1653 }
michael@0 1654
michael@0 1655 return " " + output.join("\n ");
michael@0 1656 };
michael@0 1657
michael@0 1658 /**
michael@0 1659 * Print a string to the output, as-is.
michael@0 1660 *
michael@0 1661 * @param string aString
michael@0 1662 * A string you want to output.
michael@0 1663 * @return void
michael@0 1664 */
michael@0 1665 aOwner.sandbox.print = function JSTH_print(aString)
michael@0 1666 {
michael@0 1667 aOwner.helperResult = { rawOutput: true };
michael@0 1668 return String(aString);
michael@0 1669 };
michael@0 1670 }
michael@0 1671 exports.JSTermHelpers = JSTermHelpers;
michael@0 1672
michael@0 1673
michael@0 1674 /**
michael@0 1675 * A ReflowObserver that listens for reflow events from the page.
michael@0 1676 * Implements nsIReflowObserver.
michael@0 1677 *
michael@0 1678 * @constructor
michael@0 1679 * @param object aWindow
michael@0 1680 * The window for which we need to track reflow.
michael@0 1681 * @param object aOwner
michael@0 1682 * The listener owner which needs to implement:
michael@0 1683 * - onReflowActivity(aReflowInfo)
michael@0 1684 */
michael@0 1685
michael@0 1686 function ConsoleReflowListener(aWindow, aListener)
michael@0 1687 {
michael@0 1688 this.docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
michael@0 1689 .getInterface(Ci.nsIWebNavigation)
michael@0 1690 .QueryInterface(Ci.nsIDocShell);
michael@0 1691 this.listener = aListener;
michael@0 1692 this.docshell.addWeakReflowObserver(this);
michael@0 1693 }
michael@0 1694
michael@0 1695 exports.ConsoleReflowListener = ConsoleReflowListener;
michael@0 1696
michael@0 1697 ConsoleReflowListener.prototype =
michael@0 1698 {
michael@0 1699 QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
michael@0 1700 Ci.nsISupportsWeakReference]),
michael@0 1701 docshell: null,
michael@0 1702 listener: null,
michael@0 1703
michael@0 1704 /**
michael@0 1705 * Forward reflow event to listener.
michael@0 1706 *
michael@0 1707 * @param DOMHighResTimeStamp aStart
michael@0 1708 * @param DOMHighResTimeStamp aEnd
michael@0 1709 * @param boolean aInterruptible
michael@0 1710 */
michael@0 1711 sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible)
michael@0 1712 {
michael@0 1713 let frame = components.stack.caller.caller;
michael@0 1714
michael@0 1715 let filename = frame.filename;
michael@0 1716
michael@0 1717 if (filename) {
michael@0 1718 // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
michael@0 1719 // we only take the last part.
michael@0 1720 filename = filename.split(" ").pop();
michael@0 1721 }
michael@0 1722
michael@0 1723 this.listener.onReflowActivity({
michael@0 1724 interruptible: aInterruptible,
michael@0 1725 start: aStart,
michael@0 1726 end: aEnd,
michael@0 1727 sourceURL: filename,
michael@0 1728 sourceLine: frame.lineNumber,
michael@0 1729 functionName: frame.name
michael@0 1730 });
michael@0 1731 },
michael@0 1732
michael@0 1733 /**
michael@0 1734 * On uninterruptible reflow
michael@0 1735 *
michael@0 1736 * @param DOMHighResTimeStamp aStart
michael@0 1737 * @param DOMHighResTimeStamp aEnd
michael@0 1738 */
michael@0 1739 reflow: function CRL_reflow(aStart, aEnd)
michael@0 1740 {
michael@0 1741 this.sendReflow(aStart, aEnd, false);
michael@0 1742 },
michael@0 1743
michael@0 1744 /**
michael@0 1745 * On interruptible reflow
michael@0 1746 *
michael@0 1747 * @param DOMHighResTimeStamp aStart
michael@0 1748 * @param DOMHighResTimeStamp aEnd
michael@0 1749 */
michael@0 1750 reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd)
michael@0 1751 {
michael@0 1752 this.sendReflow(aStart, aEnd, true);
michael@0 1753 },
michael@0 1754
michael@0 1755 /**
michael@0 1756 * Unregister listener.
michael@0 1757 */
michael@0 1758 destroy: function CRL_destroy()
michael@0 1759 {
michael@0 1760 this.docshell.removeWeakReflowObserver(this);
michael@0 1761 this.listener = this.docshell = null;
michael@0 1762 },
michael@0 1763 };
michael@0 1764
michael@0 1765 function gSequenceId()
michael@0 1766 {
michael@0 1767 return gSequenceId.n++;
michael@0 1768 }
michael@0 1769 gSequenceId.n = 0;

mercurial