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;