michael@0: /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const {Cc, Ci, Cu, components} = require("chrome"); michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm"); michael@0: loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm"); michael@0: michael@0: // TODO: Bug 842672 - toolkit/ imports modules from browser/. michael@0: // Note that these are only used in JSTermHelpers, see $0 and pprint(). michael@0: loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); michael@0: loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm"); michael@0: loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm"); michael@0: loader.lazyImporter(this, "DevToolsUtils", "resource://gre/modules/devtools/DevToolsUtils.jsm"); michael@0: michael@0: // Match the function name from the result of toString() or toSource(). michael@0: // michael@0: // Examples: michael@0: // (function foobar(a, b) { ... michael@0: // function foobar2(a) { ... michael@0: // function() { ... michael@0: const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/; michael@0: michael@0: // Match the function arguments from the result of toString() or toSource(). michael@0: const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/; michael@0: michael@0: let WebConsoleUtils = { michael@0: /** michael@0: * Convenience function to unwrap a wrapped object. michael@0: * michael@0: * @param aObject the object to unwrap. michael@0: * @return aObject unwrapped. michael@0: */ michael@0: unwrap: function WCU_unwrap(aObject) michael@0: { michael@0: try { michael@0: return XPCNativeWrapper.unwrap(aObject); michael@0: } michael@0: catch (ex) { michael@0: return aObject; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Wrap a string in an nsISupportsString object. michael@0: * michael@0: * @param string aString michael@0: * @return nsISupportsString michael@0: */ michael@0: supportsString: function WCU_supportsString(aString) michael@0: { michael@0: let str = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: str.data = aString; michael@0: return str; michael@0: }, michael@0: michael@0: /** michael@0: * Clone an object. michael@0: * michael@0: * @param object aObject michael@0: * The object you want cloned. michael@0: * @param boolean aRecursive michael@0: * Tells if you want to dig deeper into the object, to clone michael@0: * recursively. michael@0: * @param function [aFilter] michael@0: * Optional, filter function, called for every property. Three michael@0: * arguments are passed: key, value and object. Return true if the michael@0: * property should be added to the cloned object. Return false to skip michael@0: * the property. michael@0: * @return object michael@0: * The cloned object. michael@0: */ michael@0: cloneObject: function WCU_cloneObject(aObject, aRecursive, aFilter) michael@0: { michael@0: if (typeof aObject != "object") { michael@0: return aObject; michael@0: } michael@0: michael@0: let temp; michael@0: michael@0: if (Array.isArray(aObject)) { michael@0: temp = []; michael@0: Array.forEach(aObject, function(aValue, aIndex) { michael@0: if (!aFilter || aFilter(aIndex, aValue, aObject)) { michael@0: temp.push(aRecursive ? WCU_cloneObject(aValue) : aValue); michael@0: } michael@0: }); michael@0: } michael@0: else { michael@0: temp = {}; michael@0: for (let key in aObject) { michael@0: let value = aObject[key]; michael@0: if (aObject.hasOwnProperty(key) && michael@0: (!aFilter || aFilter(key, value, aObject))) { michael@0: temp[key] = aRecursive ? WCU_cloneObject(value) : value; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return temp; michael@0: }, michael@0: michael@0: /** michael@0: * Copies certain style attributes from one element to another. michael@0: * michael@0: * @param nsIDOMNode aFrom michael@0: * The target node. michael@0: * @param nsIDOMNode aTo michael@0: * The destination node. michael@0: */ michael@0: copyTextStyles: function WCU_copyTextStyles(aFrom, aTo) michael@0: { michael@0: let win = aFrom.ownerDocument.defaultView; michael@0: let style = win.getComputedStyle(aFrom); michael@0: aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText; michael@0: aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText; michael@0: aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText; michael@0: aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText; michael@0: }, michael@0: michael@0: /** michael@0: * Gets the ID of the inner window of this DOM window. michael@0: * michael@0: * @param nsIDOMWindow aWindow michael@0: * @return integer michael@0: * Inner ID for the given aWindow. michael@0: */ michael@0: getInnerWindowId: function WCU_getInnerWindowId(aWindow) michael@0: { michael@0: return aWindow.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID; michael@0: }, michael@0: michael@0: /** michael@0: * Recursively gather a list of inner window ids given a michael@0: * top level window. michael@0: * michael@0: * @param nsIDOMWindow aWindow michael@0: * @return Array michael@0: * list of inner window ids. michael@0: */ michael@0: getInnerWindowIDsForFrames: function WCU_getInnerWindowIDsForFrames(aWindow) michael@0: { michael@0: let innerWindowID = this.getInnerWindowId(aWindow); michael@0: let ids = [innerWindowID]; michael@0: michael@0: if (aWindow.frames) { michael@0: for (let i = 0; i < aWindow.frames.length; i++) { michael@0: let frame = aWindow.frames[i]; michael@0: ids = ids.concat(this.getInnerWindowIDsForFrames(frame)); michael@0: } michael@0: } michael@0: michael@0: return ids; michael@0: }, michael@0: michael@0: michael@0: /** michael@0: * Gets the ID of the outer window of this DOM window. michael@0: * michael@0: * @param nsIDOMWindow aWindow michael@0: * @return integer michael@0: * Outer ID for the given aWindow. michael@0: */ michael@0: getOuterWindowId: function WCU_getOuterWindowId(aWindow) michael@0: { michael@0: return aWindow.QueryInterface(Ci.nsIInterfaceRequestor). michael@0: getInterface(Ci.nsIDOMWindowUtils).outerWindowID; michael@0: }, michael@0: michael@0: /** michael@0: * Abbreviates the given source URL so that it can be displayed flush-right michael@0: * without being too distracting. michael@0: * michael@0: * @param string aSourceURL michael@0: * The source URL to shorten. michael@0: * @param object [aOptions] michael@0: * Options: michael@0: * - onlyCropQuery: boolean that tells if the URL abbreviation function michael@0: * should only remove the query parameters and the hash fragment from michael@0: * the given URL. michael@0: * @return string michael@0: * The abbreviated form of the source URL. michael@0: */ michael@0: abbreviateSourceURL: michael@0: function WCU_abbreviateSourceURL(aSourceURL, aOptions = {}) michael@0: { michael@0: if (!aOptions.onlyCropQuery && aSourceURL.substr(0, 5) == "data:") { michael@0: let commaIndex = aSourceURL.indexOf(","); michael@0: if (commaIndex > -1) { michael@0: aSourceURL = "data:" + aSourceURL.substring(commaIndex + 1); michael@0: } michael@0: } michael@0: michael@0: // Remove any query parameters. michael@0: let hookIndex = aSourceURL.indexOf("?"); michael@0: if (hookIndex > -1) { michael@0: aSourceURL = aSourceURL.substring(0, hookIndex); michael@0: } michael@0: michael@0: // Remove any hash fragments. michael@0: let hashIndex = aSourceURL.indexOf("#"); michael@0: if (hashIndex > -1) { michael@0: aSourceURL = aSourceURL.substring(0, hashIndex); michael@0: } michael@0: michael@0: // Remove a trailing "/". michael@0: if (aSourceURL[aSourceURL.length - 1] == "/") { michael@0: aSourceURL = aSourceURL.replace(/\/+$/, ""); michael@0: } michael@0: michael@0: // Remove all but the last path component. michael@0: if (!aOptions.onlyCropQuery) { michael@0: let slashIndex = aSourceURL.lastIndexOf("/"); michael@0: if (slashIndex > -1) { michael@0: aSourceURL = aSourceURL.substring(slashIndex + 1); michael@0: } michael@0: } michael@0: michael@0: return aSourceURL; michael@0: }, michael@0: michael@0: /** michael@0: * Tells if the given function is native or not. michael@0: * michael@0: * @param function aFunction michael@0: * The function you want to check if it is native or not. michael@0: * @return boolean michael@0: * True if the given function is native, false otherwise. michael@0: */ michael@0: isNativeFunction: function WCU_isNativeFunction(aFunction) michael@0: { michael@0: return typeof aFunction == "function" && !("prototype" in aFunction); michael@0: }, michael@0: michael@0: /** michael@0: * Tells if the given property of the provided object is a non-native getter or michael@0: * not. michael@0: * michael@0: * @param object aObject michael@0: * The object that contains the property. michael@0: * @param string aProp michael@0: * The property you want to check if it is a getter or not. michael@0: * @return boolean michael@0: * True if the given property is a getter, false otherwise. michael@0: */ michael@0: isNonNativeGetter: function WCU_isNonNativeGetter(aObject, aProp) michael@0: { michael@0: if (typeof aObject != "object") { michael@0: return false; michael@0: } michael@0: let desc = this.getPropertyDescriptor(aObject, aProp); michael@0: return desc && desc.get && !this.isNativeFunction(desc.get); michael@0: }, michael@0: michael@0: /** michael@0: * Get the property descriptor for the given object. michael@0: * michael@0: * @param object aObject michael@0: * The object that contains the property. michael@0: * @param string aProp michael@0: * The property you want to get the descriptor for. michael@0: * @return object michael@0: * Property descriptor. michael@0: */ michael@0: getPropertyDescriptor: function WCU_getPropertyDescriptor(aObject, aProp) michael@0: { michael@0: let desc = null; michael@0: while (aObject) { michael@0: try { michael@0: if ((desc = Object.getOwnPropertyDescriptor(aObject, aProp))) { michael@0: break; michael@0: } michael@0: } michael@0: catch (ex if (ex.name == "NS_ERROR_XPC_BAD_CONVERT_JS" || michael@0: ex.name == "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" || michael@0: ex.name == "TypeError")) { michael@0: // Native getters throw here. See bug 520882. michael@0: // null throws TypeError. michael@0: } michael@0: try { michael@0: aObject = Object.getPrototypeOf(aObject); michael@0: } michael@0: catch (ex if (ex.name == "TypeError")) { michael@0: return desc; michael@0: } michael@0: } michael@0: return desc; michael@0: }, michael@0: michael@0: /** michael@0: * Sort function for object properties. michael@0: * michael@0: * @param object a michael@0: * Property descriptor. michael@0: * @param object b michael@0: * Property descriptor. michael@0: * @return integer michael@0: * -1 if a.name < b.name, michael@0: * 1 if a.name > b.name, michael@0: * 0 otherwise. michael@0: */ michael@0: propertiesSort: function WCU_propertiesSort(a, b) michael@0: { michael@0: // Convert the pair.name to a number for later sorting. michael@0: let aNumber = parseFloat(a.name); michael@0: let bNumber = parseFloat(b.name); michael@0: michael@0: // Sort numbers. michael@0: if (!isNaN(aNumber) && isNaN(bNumber)) { michael@0: return -1; michael@0: } michael@0: else if (isNaN(aNumber) && !isNaN(bNumber)) { michael@0: return 1; michael@0: } michael@0: else if (!isNaN(aNumber) && !isNaN(bNumber)) { michael@0: return aNumber - bNumber; michael@0: } michael@0: // Sort string. michael@0: else if (a.name < b.name) { michael@0: return -1; michael@0: } michael@0: else if (a.name > b.name) { michael@0: return 1; michael@0: } michael@0: else { michael@0: return 0; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Create a grip for the given value. If the value is an object, michael@0: * an object wrapper will be created. michael@0: * michael@0: * @param mixed aValue michael@0: * The value you want to create a grip for, before sending it to the michael@0: * client. michael@0: * @param function aObjectWrapper michael@0: * If the value is an object then the aObjectWrapper function is michael@0: * invoked to give us an object grip. See this.getObjectGrip(). michael@0: * @return mixed michael@0: * The value grip. michael@0: */ michael@0: createValueGrip: function WCU_createValueGrip(aValue, aObjectWrapper) michael@0: { michael@0: switch (typeof aValue) { michael@0: case "boolean": michael@0: return aValue; michael@0: case "string": michael@0: return aObjectWrapper(aValue); michael@0: case "number": michael@0: if (aValue === Infinity) { michael@0: return { type: "Infinity" }; michael@0: } michael@0: else if (aValue === -Infinity) { michael@0: return { type: "-Infinity" }; michael@0: } michael@0: else if (Number.isNaN(aValue)) { michael@0: return { type: "NaN" }; michael@0: } michael@0: else if (!aValue && 1 / aValue === -Infinity) { michael@0: return { type: "-0" }; michael@0: } michael@0: return aValue; michael@0: case "undefined": michael@0: return { type: "undefined" }; michael@0: case "object": michael@0: if (aValue === null) { michael@0: return { type: "null" }; michael@0: } michael@0: case "function": michael@0: return aObjectWrapper(aValue); michael@0: default: michael@0: Cu.reportError("Failed to provide a grip for value of " + typeof aValue michael@0: + ": " + aValue); michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Check if the given object is an iterator or a generator. michael@0: * michael@0: * @param object aObject michael@0: * The object you want to check. michael@0: * @return boolean michael@0: * True if the given object is an iterator or a generator, otherwise michael@0: * false is returned. michael@0: */ michael@0: isIteratorOrGenerator: function WCU_isIteratorOrGenerator(aObject) michael@0: { michael@0: if (aObject === null) { michael@0: return false; michael@0: } michael@0: michael@0: if (typeof aObject == "object") { michael@0: if (typeof aObject.__iterator__ == "function" || michael@0: aObject.constructor && aObject.constructor.name == "Iterator") { michael@0: return true; michael@0: } michael@0: michael@0: try { michael@0: let str = aObject.toString(); michael@0: if (typeof aObject.next == "function" && michael@0: str.indexOf("[object Generator") == 0) { michael@0: return true; michael@0: } michael@0: } michael@0: catch (ex) { michael@0: // window.history.next throws in the typeof check above. michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: }, michael@0: michael@0: /** michael@0: * Determine if the given request mixes HTTP with HTTPS content. michael@0: * michael@0: * @param string aRequest michael@0: * Location of the requested content. michael@0: * @param string aLocation michael@0: * Location of the current page. michael@0: * @return boolean michael@0: * True if the content is mixed, false if not. michael@0: */ michael@0: isMixedHTTPSRequest: function WCU_isMixedHTTPSRequest(aRequest, aLocation) michael@0: { michael@0: try { michael@0: let requestURI = Services.io.newURI(aRequest, null, null); michael@0: let contentURI = Services.io.newURI(aLocation, null, null); michael@0: return (contentURI.scheme == "https" && requestURI.scheme != "https"); michael@0: } michael@0: catch (ex) { michael@0: return false; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Helper function to deduce the name of the provided function. michael@0: * michael@0: * @param funtion aFunction michael@0: * The function whose name will be returned. michael@0: * @return string michael@0: * Function name. michael@0: */ michael@0: getFunctionName: function WCF_getFunctionName(aFunction) michael@0: { michael@0: let name = null; michael@0: if (aFunction.name) { michael@0: name = aFunction.name; michael@0: } michael@0: else { michael@0: let desc; michael@0: try { michael@0: desc = aFunction.getOwnPropertyDescriptor("displayName"); michael@0: } michael@0: catch (ex) { } michael@0: if (desc && typeof desc.value == "string") { michael@0: name = desc.value; michael@0: } michael@0: } michael@0: if (!name) { michael@0: try { michael@0: let str = (aFunction.toString() || aFunction.toSource()) + ""; michael@0: name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1]; michael@0: } michael@0: catch (ex) { } michael@0: } michael@0: return name; michael@0: }, michael@0: michael@0: /** michael@0: * Get the object class name. For example, the |window| object has the Window michael@0: * class name (based on [object Window]). michael@0: * michael@0: * @param object aObject michael@0: * The object you want to get the class name for. michael@0: * @return string michael@0: * The object class name. michael@0: */ michael@0: getObjectClassName: function WCU_getObjectClassName(aObject) michael@0: { michael@0: if (aObject === null) { michael@0: return "null"; michael@0: } michael@0: if (aObject === undefined) { michael@0: return "undefined"; michael@0: } michael@0: michael@0: let type = typeof aObject; michael@0: if (type != "object") { michael@0: // Grip class names should start with an uppercase letter. michael@0: return type.charAt(0).toUpperCase() + type.substr(1); michael@0: } michael@0: michael@0: let className; michael@0: michael@0: try { michael@0: className = ((aObject + "").match(/^\[object (\S+)\]$/) || [])[1]; michael@0: if (!className) { michael@0: className = ((aObject.constructor + "").match(/^\[object (\S+)\]$/) || [])[1]; michael@0: } michael@0: if (!className && typeof aObject.constructor == "function") { michael@0: className = this.getFunctionName(aObject.constructor); michael@0: } michael@0: } michael@0: catch (ex) { } michael@0: michael@0: return className; michael@0: }, michael@0: michael@0: /** michael@0: * Check if the given value is a grip with an actor. michael@0: * michael@0: * @param mixed aGrip michael@0: * Value you want to check if it is a grip with an actor. michael@0: * @return boolean michael@0: * True if the given value is a grip with an actor. michael@0: */ michael@0: isActorGrip: function WCU_isActorGrip(aGrip) michael@0: { michael@0: return aGrip && typeof(aGrip) == "object" && aGrip.actor; michael@0: }, michael@0: }; michael@0: exports.Utils = WebConsoleUtils; michael@0: michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: // Localization michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: michael@0: WebConsoleUtils.l10n = function WCU_l10n(aBundleURI) michael@0: { michael@0: this._bundleUri = aBundleURI; michael@0: }; michael@0: michael@0: WebConsoleUtils.l10n.prototype = { michael@0: _stringBundle: null, michael@0: michael@0: get stringBundle() michael@0: { michael@0: if (!this._stringBundle) { michael@0: this._stringBundle = Services.strings.createBundle(this._bundleUri); michael@0: } michael@0: return this._stringBundle; michael@0: }, michael@0: michael@0: /** michael@0: * Generates a formatted timestamp string for displaying in console messages. michael@0: * michael@0: * @param integer [aMilliseconds] michael@0: * Optional, allows you to specify the timestamp in milliseconds since michael@0: * the UNIX epoch. michael@0: * @return string michael@0: * The timestamp formatted for display. michael@0: */ michael@0: timestampString: function WCU_l10n_timestampString(aMilliseconds) michael@0: { michael@0: let d = new Date(aMilliseconds ? aMilliseconds : null); michael@0: let hours = d.getHours(), minutes = d.getMinutes(); michael@0: let seconds = d.getSeconds(), milliseconds = d.getMilliseconds(); michael@0: let parameters = [hours, minutes, seconds, milliseconds]; michael@0: return this.getFormatStr("timestampFormat", parameters); michael@0: }, michael@0: michael@0: /** michael@0: * Retrieve a localized string. michael@0: * michael@0: * @param string aName michael@0: * The string name you want from the Web Console string bundle. michael@0: * @return string michael@0: * The localized string. michael@0: */ michael@0: getStr: function WCU_l10n_getStr(aName) michael@0: { michael@0: let result; michael@0: try { michael@0: result = this.stringBundle.GetStringFromName(aName); michael@0: } michael@0: catch (ex) { michael@0: Cu.reportError("Failed to get string: " + aName); michael@0: throw ex; michael@0: } michael@0: return result; michael@0: }, michael@0: michael@0: /** michael@0: * Retrieve a localized string formatted with values coming from the given michael@0: * array. michael@0: * michael@0: * @param string aName michael@0: * The string name you want from the Web Console string bundle. michael@0: * @param array aArray michael@0: * The array of values you want in the formatted string. michael@0: * @return string michael@0: * The formatted local string. michael@0: */ michael@0: getFormatStr: function WCU_l10n_getFormatStr(aName, aArray) michael@0: { michael@0: let result; michael@0: try { michael@0: result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length); michael@0: } michael@0: catch (ex) { michael@0: Cu.reportError("Failed to format string: " + aName); michael@0: throw ex; michael@0: } michael@0: return result; michael@0: }, michael@0: }; michael@0: michael@0: michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: // JS Completer michael@0: ////////////////////////////////////////////////////////////////////////// michael@0: michael@0: (function _JSPP(WCU) { michael@0: const STATE_NORMAL = 0; michael@0: const STATE_QUOTE = 2; michael@0: const STATE_DQUOTE = 3; michael@0: michael@0: const OPEN_BODY = "{[(".split(""); michael@0: const CLOSE_BODY = "}])".split(""); michael@0: const OPEN_CLOSE_BODY = { michael@0: "{": "}", michael@0: "[": "]", michael@0: "(": ")", michael@0: }; michael@0: michael@0: const MAX_COMPLETIONS = 1500; michael@0: michael@0: /** michael@0: * Analyses a given string to find the last statement that is interesting for michael@0: * later completion. michael@0: * michael@0: * @param string aStr michael@0: * A string to analyse. michael@0: * michael@0: * @returns object michael@0: * If there was an error in the string detected, then a object like michael@0: * michael@0: * { err: "ErrorMesssage" } michael@0: * michael@0: * is returned, otherwise a object like michael@0: * michael@0: * { michael@0: * state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE, michael@0: * startPos: index of where the last statement begins michael@0: * } michael@0: */ michael@0: function findCompletionBeginning(aStr) michael@0: { michael@0: let bodyStack = []; michael@0: michael@0: let state = STATE_NORMAL; michael@0: let start = 0; michael@0: let c; michael@0: for (let i = 0; i < aStr.length; i++) { michael@0: c = aStr[i]; michael@0: michael@0: switch (state) { michael@0: // Normal JS state. michael@0: case STATE_NORMAL: michael@0: if (c == '"') { michael@0: state = STATE_DQUOTE; michael@0: } michael@0: else if (c == "'") { michael@0: state = STATE_QUOTE; michael@0: } michael@0: else if (c == ";") { michael@0: start = i + 1; michael@0: } michael@0: else if (c == " ") { michael@0: start = i + 1; michael@0: } michael@0: else if (OPEN_BODY.indexOf(c) != -1) { michael@0: bodyStack.push({ michael@0: token: c, michael@0: start: start michael@0: }); michael@0: start = i + 1; michael@0: } michael@0: else if (CLOSE_BODY.indexOf(c) != -1) { michael@0: var last = bodyStack.pop(); michael@0: if (!last || OPEN_CLOSE_BODY[last.token] != c) { michael@0: return { michael@0: err: "syntax error" michael@0: }; michael@0: } michael@0: if (c == "}") { michael@0: start = i + 1; michael@0: } michael@0: else { michael@0: start = last.start; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: // Double quote state > " < michael@0: case STATE_DQUOTE: michael@0: if (c == "\\") { michael@0: i++; michael@0: } michael@0: else if (c == "\n") { michael@0: return { michael@0: err: "unterminated string literal" michael@0: }; michael@0: } michael@0: else if (c == '"') { michael@0: state = STATE_NORMAL; michael@0: } michael@0: break; michael@0: michael@0: // Single quote state > ' < michael@0: case STATE_QUOTE: michael@0: if (c == "\\") { michael@0: i++; michael@0: } michael@0: else if (c == "\n") { michael@0: return { michael@0: err: "unterminated string literal" michael@0: }; michael@0: } michael@0: else if (c == "'") { michael@0: state = STATE_NORMAL; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return { michael@0: state: state, michael@0: startPos: start michael@0: }; michael@0: } michael@0: michael@0: /** michael@0: * Provides a list of properties, that are possible matches based on the passed michael@0: * Debugger.Environment/Debugger.Object and inputValue. michael@0: * michael@0: * @param object aDbgObject michael@0: * When the debugger is not paused this Debugger.Object wraps the scope for autocompletion. michael@0: * It is null if the debugger is paused. michael@0: * @param object anEnvironment michael@0: * When the debugger is paused this Debugger.Environment is the scope for autocompletion. michael@0: * It is null if the debugger is not paused. michael@0: * @param string aInputValue michael@0: * Value that should be completed. michael@0: * @param number [aCursor=aInputValue.length] michael@0: * Optional offset in the input where the cursor is located. If this is michael@0: * omitted then the cursor is assumed to be at the end of the input michael@0: * value. michael@0: * @returns null or object michael@0: * If no completion valued could be computed, null is returned, michael@0: * otherwise a object with the following form is returned: michael@0: * { michael@0: * matches: [ string, string, string ], michael@0: * matchProp: Last part of the inputValue that was used to find michael@0: * the matches-strings. michael@0: * } michael@0: */ michael@0: function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor) michael@0: { michael@0: if (aCursor === undefined) { michael@0: aCursor = aInputValue.length; michael@0: } michael@0: michael@0: let inputValue = aInputValue.substring(0, aCursor); michael@0: michael@0: // Analyse the inputValue and find the beginning of the last part that michael@0: // should be completed. michael@0: let beginning = findCompletionBeginning(inputValue); michael@0: michael@0: // There was an error analysing the string. michael@0: if (beginning.err) { michael@0: return null; michael@0: } michael@0: michael@0: // If the current state is not STATE_NORMAL, then we are inside of an string michael@0: // which means that no completion is possible. michael@0: if (beginning.state != STATE_NORMAL) { michael@0: return null; michael@0: } michael@0: michael@0: let completionPart = inputValue.substring(beginning.startPos); michael@0: michael@0: // Don't complete on just an empty string. michael@0: if (completionPart.trim() == "") { michael@0: return null; michael@0: } michael@0: michael@0: let lastDot = completionPart.lastIndexOf("."); michael@0: if (lastDot > 0 && michael@0: (completionPart[0] == "'" || completionPart[0] == '"') && michael@0: completionPart[lastDot - 1] == completionPart[0]) { michael@0: // We are completing a string literal. michael@0: let matchProp = completionPart.slice(lastDot + 1); michael@0: return getMatchedProps(String.prototype, matchProp); michael@0: } michael@0: michael@0: // We are completing a variable / a property lookup. michael@0: let properties = completionPart.split("."); michael@0: let matchProp = properties.pop().trimLeft(); michael@0: let obj = aDbgObject; michael@0: michael@0: // The first property must be found in the environment if the debugger is michael@0: // paused. michael@0: if (anEnvironment) { michael@0: if (properties.length == 0) { michael@0: return getMatchedPropsInEnvironment(anEnvironment, matchProp); michael@0: } michael@0: obj = getVariableInEnvironment(anEnvironment, properties.shift()); michael@0: } michael@0: michael@0: if (!isObjectUsable(obj)) { michael@0: return null; michael@0: } michael@0: michael@0: // We get the rest of the properties recursively starting from the Debugger.Object michael@0: // that wraps the first property michael@0: for (let prop of properties) { michael@0: prop = prop.trim(); michael@0: if (!prop) { michael@0: return null; michael@0: } michael@0: michael@0: if (/\[\d+\]$/.test(prop)) { michael@0: // The property to autocomplete is a member of array. For example michael@0: // list[i][j]..[n]. Traverse the array to get the actual element. michael@0: obj = getArrayMemberProperty(obj, prop); michael@0: } michael@0: else { michael@0: obj = DevToolsUtils.getProperty(obj, prop); michael@0: } michael@0: michael@0: if (!isObjectUsable(obj)) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: // If the final property is a primitive michael@0: if (typeof obj != "object") { michael@0: return getMatchedProps(obj, matchProp); michael@0: } michael@0: michael@0: return getMatchedPropsInDbgObject(obj, matchProp); michael@0: } michael@0: michael@0: /** michael@0: * Get the array member of aObj for the given aProp. For example, given michael@0: * aProp='list[0][1]' the element at [0][1] of aObj.list is returned. michael@0: * michael@0: * @param object aObj michael@0: * The object to operate on. michael@0: * @param string aProp michael@0: * The property to return. michael@0: * @return null or Object michael@0: * Returns null if the property couldn't be located. Otherwise the array michael@0: * member identified by aProp. michael@0: */ michael@0: function getArrayMemberProperty(aObj, aProp) michael@0: { michael@0: // First get the array. michael@0: let obj = aObj; michael@0: let propWithoutIndices = aProp.substr(0, aProp.indexOf("[")); michael@0: obj = DevToolsUtils.getProperty(obj, propWithoutIndices); michael@0: if (!isObjectUsable(obj)) { michael@0: return null; michael@0: } michael@0: michael@0: // Then traverse the list of indices to get the actual element. michael@0: let result; michael@0: let arrayIndicesRegex = /\[[^\]]*\]/g; michael@0: while ((result = arrayIndicesRegex.exec(aProp)) !== null) { michael@0: let indexWithBrackets = result[0]; michael@0: let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2); michael@0: let index = parseInt(indexAsText); michael@0: michael@0: if (isNaN(index)) { michael@0: return null; michael@0: } michael@0: michael@0: obj = DevToolsUtils.getProperty(obj, index); michael@0: michael@0: if (!isObjectUsable(obj)) { michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: return obj; michael@0: } michael@0: michael@0: /** michael@0: * Check if the given Debugger.Object can be used for autocomplete. michael@0: * michael@0: * @param Debugger.Object aObject michael@0: * The Debugger.Object to check. michael@0: * @return boolean michael@0: * True if further inspection into the object is possible, or false michael@0: * otherwise. michael@0: */ michael@0: function isObjectUsable(aObject) michael@0: { michael@0: if (aObject == null) { michael@0: return false; michael@0: } michael@0: michael@0: if (typeof aObject == "object" && aObject.class == "DeadObject") { michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * @see getExactMatch_impl() michael@0: */ michael@0: function getVariableInEnvironment(anEnvironment, aName) michael@0: { michael@0: return getExactMatch_impl(anEnvironment, aName, DebuggerEnvironmentSupport); michael@0: } michael@0: michael@0: /** michael@0: * @see getMatchedProps_impl() michael@0: */ michael@0: function getMatchedPropsInEnvironment(anEnvironment, aMatch) michael@0: { michael@0: return getMatchedProps_impl(anEnvironment, aMatch, DebuggerEnvironmentSupport); michael@0: } michael@0: michael@0: /** michael@0: * @see getMatchedProps_impl() michael@0: */ michael@0: function getMatchedPropsInDbgObject(aDbgObject, aMatch) michael@0: { michael@0: return getMatchedProps_impl(aDbgObject, aMatch, DebuggerObjectSupport); michael@0: } michael@0: michael@0: /** michael@0: * @see getMatchedProps_impl() michael@0: */ michael@0: function getMatchedProps(aObj, aMatch) michael@0: { michael@0: if (typeof aObj != "object") { michael@0: aObj = aObj.constructor.prototype; michael@0: } michael@0: return getMatchedProps_impl(aObj, aMatch, JSObjectSupport); michael@0: } michael@0: michael@0: /** michael@0: * Get all properties in the given object (and its parent prototype chain) that michael@0: * match a given prefix. michael@0: * michael@0: * @param mixed aObj michael@0: * Object whose properties we want to filter. michael@0: * @param string aMatch michael@0: * Filter for properties that match this string. michael@0: * @return object michael@0: * Object that contains the matchProp and the list of names. michael@0: */ michael@0: function getMatchedProps_impl(aObj, aMatch, {chainIterator, getProperties}) michael@0: { michael@0: let matches = new Set(); michael@0: michael@0: // We need to go up the prototype chain. michael@0: let iter = chainIterator(aObj); michael@0: for (let obj of iter) { michael@0: let props = getProperties(obj); michael@0: for (let prop of props) { michael@0: if (prop.indexOf(aMatch) != 0) { michael@0: continue; michael@0: } michael@0: michael@0: // If it is an array index, we can't take it. michael@0: // This uses a trick: converting a string to a number yields NaN if michael@0: // the operation failed, and NaN is not equal to itself. michael@0: if (+prop != +prop) { michael@0: matches.add(prop); michael@0: } michael@0: michael@0: if (matches.size > MAX_COMPLETIONS) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (matches.size > MAX_COMPLETIONS) { michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return { michael@0: matchProp: aMatch, michael@0: matches: [...matches], michael@0: }; michael@0: } michael@0: michael@0: /** michael@0: * Returns a property value based on its name from the given object, by michael@0: * recursively checking the object's prototype. michael@0: * michael@0: * @param object aObj michael@0: * An object to look the property into. michael@0: * @param string aName michael@0: * The property that is looked up. michael@0: * @returns object|undefined michael@0: * A Debugger.Object if the property exists in the object's prototype michael@0: * chain, undefined otherwise. michael@0: */ michael@0: function getExactMatch_impl(aObj, aName, {chainIterator, getProperty}) michael@0: { michael@0: // We need to go up the prototype chain. michael@0: let iter = chainIterator(aObj); michael@0: for (let obj of iter) { michael@0: let prop = getProperty(obj, aName, aObj); michael@0: if (prop) { michael@0: return prop.value; michael@0: } michael@0: } michael@0: return undefined; michael@0: } michael@0: michael@0: michael@0: let JSObjectSupport = { michael@0: chainIterator: function(aObj) michael@0: { michael@0: while (aObj) { michael@0: yield aObj; michael@0: aObj = Object.getPrototypeOf(aObj); michael@0: } michael@0: }, michael@0: michael@0: getProperties: function(aObj) michael@0: { michael@0: return Object.getOwnPropertyNames(aObj); michael@0: }, michael@0: michael@0: getProperty: function() michael@0: { michael@0: // getProperty is unsafe with raw JS objects. michael@0: throw "Unimplemented!"; michael@0: }, michael@0: }; michael@0: michael@0: let DebuggerObjectSupport = { michael@0: chainIterator: function(aObj) michael@0: { michael@0: while (aObj) { michael@0: yield aObj; michael@0: aObj = aObj.proto; michael@0: } michael@0: }, michael@0: michael@0: getProperties: function(aObj) michael@0: { michael@0: return aObj.getOwnPropertyNames(); michael@0: }, michael@0: michael@0: getProperty: function(aObj, aName, aRootObj) michael@0: { michael@0: // This is left unimplemented in favor to DevToolsUtils.getProperty(). michael@0: throw "Unimplemented!"; michael@0: }, michael@0: }; michael@0: michael@0: let DebuggerEnvironmentSupport = { michael@0: chainIterator: function(aObj) michael@0: { michael@0: while (aObj) { michael@0: yield aObj; michael@0: aObj = aObj.parent; michael@0: } michael@0: }, michael@0: michael@0: getProperties: function(aObj) michael@0: { michael@0: return aObj.names(); michael@0: }, michael@0: michael@0: getProperty: function(aObj, aName) michael@0: { michael@0: // TODO: we should use getVariableDescriptor() here - bug 725815. michael@0: let result = aObj.getVariable(aName); michael@0: // FIXME: Need actual UI, bug 941287. michael@0: if (result.optimizedOut || result.missingArguments) { michael@0: return null; michael@0: } michael@0: return result === undefined ? null : { value: result }; michael@0: }, michael@0: }; michael@0: michael@0: michael@0: exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider); michael@0: })(WebConsoleUtils); michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // The page errors listener michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: /** michael@0: * The nsIConsoleService listener. This is used to send all of the console michael@0: * messages (JavaScript, CSS and more) to the remote Web Console instance. michael@0: * michael@0: * @constructor michael@0: * @param nsIDOMWindow [aWindow] michael@0: * Optional - the window object for which we are created. This is used michael@0: * for filtering out messages that belong to other windows. michael@0: * @param object aListener michael@0: * The listener object must have one method: michael@0: * - onConsoleServiceMessage(). This method is invoked with one argument, michael@0: * the nsIConsoleMessage, whenever a relevant message is received. michael@0: */ michael@0: function ConsoleServiceListener(aWindow, aListener) michael@0: { michael@0: this.window = aWindow; michael@0: this.listener = aListener; michael@0: if (this.window) { michael@0: this.layoutHelpers = new LayoutHelpers(this.window); michael@0: } michael@0: } michael@0: exports.ConsoleServiceListener = ConsoleServiceListener; michael@0: michael@0: ConsoleServiceListener.prototype = michael@0: { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]), michael@0: michael@0: /** michael@0: * The content window for which we listen to page errors. michael@0: * @type nsIDOMWindow michael@0: */ michael@0: window: null, michael@0: michael@0: /** michael@0: * The listener object which is notified of messages from the console service. michael@0: * @type object michael@0: */ michael@0: listener: null, michael@0: michael@0: /** michael@0: * Initialize the nsIConsoleService listener. michael@0: */ michael@0: init: function CSL_init() michael@0: { michael@0: Services.console.registerListener(this); michael@0: }, michael@0: michael@0: /** michael@0: * The nsIConsoleService observer. This method takes all the script error michael@0: * messages belonging to the current window and sends them to the remote Web michael@0: * Console instance. michael@0: * michael@0: * @param nsIConsoleMessage aMessage michael@0: * The message object coming from the nsIConsoleService. michael@0: */ michael@0: observe: function CSL_observe(aMessage) michael@0: { michael@0: if (!this.listener) { michael@0: return; michael@0: } michael@0: michael@0: if (this.window) { michael@0: if (!(aMessage instanceof Ci.nsIScriptError) || michael@0: !aMessage.outerWindowID || michael@0: !this.isCategoryAllowed(aMessage.category)) { michael@0: return; michael@0: } michael@0: michael@0: let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID); michael@0: if (!errorWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(errorWindow)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: this.listener.onConsoleServiceMessage(aMessage); michael@0: }, michael@0: michael@0: /** michael@0: * Check if the given message category is allowed to be tracked or not. michael@0: * We ignore chrome-originating errors as we only care about content. michael@0: * michael@0: * @param string aCategory michael@0: * The message category you want to check. michael@0: * @return boolean michael@0: * True if the category is allowed to be logged, false otherwise. michael@0: */ michael@0: isCategoryAllowed: function CSL_isCategoryAllowed(aCategory) michael@0: { michael@0: if (!aCategory) { michael@0: return false; michael@0: } michael@0: michael@0: switch (aCategory) { michael@0: case "XPConnect JavaScript": michael@0: case "component javascript": michael@0: case "chrome javascript": michael@0: case "chrome registration": michael@0: case "XBL": michael@0: case "XBL Prototype Handler": michael@0: case "XBL Content Sink": michael@0: case "xbl javascript": michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Get the cached page errors for the current inner window and its (i)frames. michael@0: * michael@0: * @param boolean [aIncludePrivate=false] michael@0: * Tells if you want to also retrieve messages coming from private michael@0: * windows. Defaults to false. michael@0: * @return array michael@0: * The array of cached messages. Each element is an nsIScriptError or michael@0: * an nsIConsoleMessage michael@0: */ michael@0: getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false) michael@0: { michael@0: let errors = Services.console.getMessageArray() || []; michael@0: michael@0: // if !this.window, we're in a browser console. Still need to filter michael@0: // private messages. michael@0: if (!this.window) { michael@0: return errors.filter((aError) => { michael@0: if (aError instanceof Ci.nsIScriptError) { michael@0: if (!aIncludePrivate && aError.isFromPrivateWindow) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: }); michael@0: } michael@0: michael@0: let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window); michael@0: michael@0: return errors.filter((aError) => { michael@0: if (aError instanceof Ci.nsIScriptError) { michael@0: if (!aIncludePrivate && aError.isFromPrivateWindow) { michael@0: return false; michael@0: } michael@0: if (ids && michael@0: (ids.indexOf(aError.innerWindowID) == -1 || michael@0: !this.isCategoryAllowed(aError.category))) { michael@0: return false; michael@0: } michael@0: } michael@0: else if (ids && ids[0]) { michael@0: // If this is not an nsIScriptError and we need to do window-based michael@0: // filtering we skip this message. michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Remove the nsIConsoleService listener. michael@0: */ michael@0: destroy: function CSL_destroy() michael@0: { michael@0: Services.console.unregisterListener(this); michael@0: this.listener = this.window = null; michael@0: }, michael@0: }; michael@0: michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: // The window.console API observer michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: /** michael@0: * The window.console API observer. This allows the window.console API messages michael@0: * to be sent to the remote Web Console instance. michael@0: * michael@0: * @constructor michael@0: * @param nsIDOMWindow aWindow michael@0: * Optional - the window object for which we are created. This is used michael@0: * for filtering out messages that belong to other windows. michael@0: * @param object aOwner michael@0: * The owner object must have the following methods: michael@0: * - onConsoleAPICall(). This method is invoked with one argument, the michael@0: * Console API message that comes from the observer service, whenever michael@0: * a relevant console API call is received. michael@0: */ michael@0: function ConsoleAPIListener(aWindow, aOwner) michael@0: { michael@0: this.window = aWindow; michael@0: this.owner = aOwner; michael@0: if (this.window) { michael@0: this.layoutHelpers = new LayoutHelpers(this.window); michael@0: } michael@0: } michael@0: exports.ConsoleAPIListener = ConsoleAPIListener; michael@0: michael@0: ConsoleAPIListener.prototype = michael@0: { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]), michael@0: michael@0: /** michael@0: * The content window for which we listen to window.console API calls. michael@0: * @type nsIDOMWindow michael@0: */ michael@0: window: null, michael@0: michael@0: /** michael@0: * The owner object which is notified of window.console API calls. It must michael@0: * have a onConsoleAPICall method which is invoked with one argument: the michael@0: * console API call object that comes from the observer service. michael@0: * michael@0: * @type object michael@0: * @see WebConsoleActor michael@0: */ michael@0: owner: null, michael@0: michael@0: /** michael@0: * Initialize the window.console API observer. michael@0: */ michael@0: init: function CAL_init() michael@0: { michael@0: // Note that the observer is process-wide. We will filter the messages as michael@0: // needed, see CAL_observe(). michael@0: Services.obs.addObserver(this, "console-api-log-event", false); michael@0: }, michael@0: michael@0: /** michael@0: * The console API message observer. When messages are received from the michael@0: * observer service we forward them to the remote Web Console instance. michael@0: * michael@0: * @param object aMessage michael@0: * The message object receives from the observer service. michael@0: * @param string aTopic michael@0: * The message topic received from the observer service. michael@0: */ michael@0: observe: function CAL_observe(aMessage, aTopic) michael@0: { michael@0: if (!this.owner) { michael@0: return; michael@0: } michael@0: michael@0: let apiMessage = aMessage.wrappedJSObject; michael@0: if (this.window) { michael@0: let msgWindow = Services.wm.getCurrentInnerWindowWithId(apiMessage.innerID); michael@0: if (!msgWindow || !this.layoutHelpers.isIncludedInTopLevelWindow(msgWindow)) { michael@0: // Not the same window! michael@0: return; michael@0: } michael@0: } michael@0: michael@0: this.owner.onConsoleAPICall(apiMessage); michael@0: }, michael@0: michael@0: /** michael@0: * Get the cached messages for the current inner window and its (i)frames. michael@0: * michael@0: * @param boolean [aIncludePrivate=false] michael@0: * Tells if you want to also retrieve messages coming from private michael@0: * windows. Defaults to false. michael@0: * @return array michael@0: * The array of cached messages. michael@0: */ michael@0: getCachedMessages: function CAL_getCachedMessages(aIncludePrivate = false) michael@0: { michael@0: let messages = []; michael@0: let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"] michael@0: .getService(Ci.nsIConsoleAPIStorage); michael@0: michael@0: // if !this.window, we're in a browser console. Retrieve all events michael@0: // for filtering based on privacy. michael@0: if (!this.window) { michael@0: messages = ConsoleAPIStorage.getEvents(); michael@0: } else { michael@0: let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window); michael@0: ids.forEach((id) => { michael@0: messages = messages.concat(ConsoleAPIStorage.getEvents(id)); michael@0: }); michael@0: } michael@0: michael@0: if (aIncludePrivate) { michael@0: return messages; michael@0: } michael@0: michael@0: return messages.filter((m) => !m.private); michael@0: }, michael@0: michael@0: /** michael@0: * Destroy the console API listener. michael@0: */ michael@0: destroy: function CAL_destroy() michael@0: { michael@0: Services.obs.removeObserver(this, "console-api-log-event"); michael@0: this.window = this.owner = null; michael@0: }, michael@0: }; michael@0: michael@0: michael@0: michael@0: /** michael@0: * JSTerm helper functions. michael@0: * michael@0: * Defines a set of functions ("helper functions") that are available from the michael@0: * Web Console but not from the web page. michael@0: * michael@0: * A list of helper functions used by Firebug can be found here: michael@0: * http://getfirebug.com/wiki/index.php/Command_Line_API michael@0: * michael@0: * @param object aOwner michael@0: * The owning object. michael@0: */ michael@0: function JSTermHelpers(aOwner) michael@0: { michael@0: /** michael@0: * Find a node by ID. michael@0: * michael@0: * @param string aId michael@0: * The ID of the element you want. michael@0: * @return nsIDOMNode or null michael@0: * The result of calling document.querySelector(aSelector). michael@0: */ michael@0: aOwner.sandbox.$ = function JSTH_$(aSelector) michael@0: { michael@0: return aOwner.window.document.querySelector(aSelector); michael@0: }; michael@0: michael@0: /** michael@0: * Find the nodes matching a CSS selector. michael@0: * michael@0: * @param string aSelector michael@0: * A string that is passed to window.document.querySelectorAll. michael@0: * @return nsIDOMNodeList michael@0: * Returns the result of document.querySelectorAll(aSelector). michael@0: */ michael@0: aOwner.sandbox.$$ = function JSTH_$$(aSelector) michael@0: { michael@0: return aOwner.window.document.querySelectorAll(aSelector); michael@0: }; michael@0: michael@0: /** michael@0: * Runs an xPath query and returns all matched nodes. michael@0: * michael@0: * @param string aXPath michael@0: * xPath search query to execute. michael@0: * @param [optional] nsIDOMNode aContext michael@0: * Context to run the xPath query on. Uses window.document if not set. michael@0: * @return array of nsIDOMNode michael@0: */ michael@0: aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext) michael@0: { michael@0: let nodes = new aOwner.window.wrappedJSObject.Array(); michael@0: let doc = aOwner.window.document; michael@0: aContext = aContext || doc; michael@0: michael@0: let results = doc.evaluate(aXPath, aContext, null, michael@0: Ci.nsIDOMXPathResult.ANY_TYPE, null); michael@0: let node; michael@0: while ((node = results.iterateNext())) { michael@0: nodes.push(node); michael@0: } michael@0: michael@0: return nodes; michael@0: }; michael@0: michael@0: /** michael@0: * Returns the currently selected object in the highlighter. michael@0: * michael@0: * TODO: this implementation crosses the client/server boundaries! This is not michael@0: * usable within a remote browser. To implement this feature correctly we need michael@0: * support for remote inspection capabilities within the Inspector as well. michael@0: * See bug 787975. michael@0: * michael@0: * @return nsIDOMElement|null michael@0: * The DOM element currently selected in the highlighter. michael@0: */ michael@0: Object.defineProperty(aOwner.sandbox, "$0", { michael@0: get: function() { michael@0: let window = aOwner.chromeWindow(); michael@0: if (!window) { michael@0: return null; michael@0: } michael@0: michael@0: let target = null; michael@0: try { michael@0: target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab); michael@0: } michael@0: catch (ex) { michael@0: // If we report this exception the user will get it in the Browser michael@0: // Console every time when she evaluates any string. michael@0: } michael@0: michael@0: if (!target) { michael@0: return null; michael@0: } michael@0: michael@0: let toolbox = gDevTools.getToolbox(target); michael@0: let node = toolbox && toolbox.selection ? toolbox.selection.node : null; michael@0: michael@0: return node ? aOwner.makeDebuggeeValue(node) : null; michael@0: }, michael@0: enumerable: true, michael@0: configurable: false michael@0: }); michael@0: michael@0: /** michael@0: * Clears the output of the JSTerm. michael@0: */ michael@0: aOwner.sandbox.clear = function JSTH_clear() michael@0: { michael@0: aOwner.helperResult = { michael@0: type: "clearOutput", michael@0: }; michael@0: }; michael@0: michael@0: /** michael@0: * Returns the result of Object.keys(aObject). michael@0: * michael@0: * @param object aObject michael@0: * Object to return the property names from. michael@0: * @return array of strings michael@0: */ michael@0: aOwner.sandbox.keys = function JSTH_keys(aObject) michael@0: { michael@0: return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject)); michael@0: }; michael@0: michael@0: /** michael@0: * Returns the values of all properties on aObject. michael@0: * michael@0: * @param object aObject michael@0: * Object to display the values from. michael@0: * @return array of string michael@0: */ michael@0: aOwner.sandbox.values = function JSTH_values(aObject) michael@0: { michael@0: let arrValues = new aOwner.window.wrappedJSObject.Array(); michael@0: let obj = WebConsoleUtils.unwrap(aObject); michael@0: michael@0: for (let prop in obj) { michael@0: arrValues.push(obj[prop]); michael@0: } michael@0: michael@0: return arrValues; michael@0: }; michael@0: michael@0: /** michael@0: * Opens a help window in MDN. michael@0: */ michael@0: aOwner.sandbox.help = function JSTH_help() michael@0: { michael@0: aOwner.helperResult = { type: "help" }; michael@0: }; michael@0: michael@0: /** michael@0: * Change the JS evaluation scope. michael@0: * michael@0: * @param DOMElement|string|window aWindow michael@0: * The window object to use for eval scope. This can be a string that michael@0: * is used to perform document.querySelector(), to find the iframe that michael@0: * you want to cd() to. A DOMElement can be given as well, the michael@0: * .contentWindow property is used. Lastly, you can directly pass michael@0: * a window object. If you call cd() with no arguments, the current michael@0: * eval scope is cleared back to its default (the top window). michael@0: */ michael@0: aOwner.sandbox.cd = function JSTH_cd(aWindow) michael@0: { michael@0: if (!aWindow) { michael@0: aOwner.consoleActor.evalWindow = null; michael@0: aOwner.helperResult = { type: "cd" }; michael@0: return; michael@0: } michael@0: michael@0: if (typeof aWindow == "string") { michael@0: aWindow = aOwner.window.document.querySelector(aWindow); michael@0: } michael@0: if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) { michael@0: aWindow = aWindow.contentWindow; michael@0: } michael@0: if (!(aWindow instanceof Ci.nsIDOMWindow)) { michael@0: aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" }; michael@0: return; michael@0: } michael@0: michael@0: aOwner.consoleActor.evalWindow = aWindow; michael@0: aOwner.helperResult = { type: "cd" }; michael@0: }; michael@0: michael@0: /** michael@0: * Inspects the passed aObject. This is done by opening the PropertyPanel. michael@0: * michael@0: * @param object aObject michael@0: * Object to inspect. michael@0: */ michael@0: aOwner.sandbox.inspect = function JSTH_inspect(aObject) michael@0: { michael@0: let dbgObj = aOwner.makeDebuggeeValue(aObject); michael@0: let grip = aOwner.createValueGrip(dbgObj); michael@0: aOwner.helperResult = { michael@0: type: "inspectObject", michael@0: input: aOwner.evalInput, michael@0: object: grip, michael@0: }; michael@0: }; michael@0: michael@0: /** michael@0: * Prints aObject to the output. michael@0: * michael@0: * @param object aObject michael@0: * Object to print to the output. michael@0: * @return string michael@0: */ michael@0: aOwner.sandbox.pprint = function JSTH_pprint(aObject) michael@0: { michael@0: if (aObject === null || aObject === undefined || aObject === true || michael@0: aObject === false) { michael@0: aOwner.helperResult = { michael@0: type: "error", michael@0: message: "helperFuncUnsupportedTypeError", michael@0: }; michael@0: return null; michael@0: } michael@0: michael@0: aOwner.helperResult = { rawOutput: true }; michael@0: michael@0: if (typeof aObject == "function") { michael@0: return aObject + "\n"; michael@0: } michael@0: michael@0: let output = []; michael@0: michael@0: let obj = WebConsoleUtils.unwrap(aObject); michael@0: for (let name in obj) { michael@0: let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {}; michael@0: if (desc.get || desc.set) { michael@0: // TODO: Bug 842672 - toolkit/ imports modules from browser/. michael@0: let getGrip = VariablesView.getGrip(desc.get); michael@0: let setGrip = VariablesView.getGrip(desc.set); michael@0: let getString = VariablesView.getString(getGrip); michael@0: let setString = VariablesView.getString(setGrip); michael@0: output.push(name + ":", " get: " + getString, " set: " + setString); michael@0: } michael@0: else { michael@0: let valueGrip = VariablesView.getGrip(obj[name]); michael@0: let valueString = VariablesView.getString(valueGrip); michael@0: output.push(name + ": " + valueString); michael@0: } michael@0: } michael@0: michael@0: return " " + output.join("\n "); michael@0: }; michael@0: michael@0: /** michael@0: * Print a string to the output, as-is. michael@0: * michael@0: * @param string aString michael@0: * A string you want to output. michael@0: * @return void michael@0: */ michael@0: aOwner.sandbox.print = function JSTH_print(aString) michael@0: { michael@0: aOwner.helperResult = { rawOutput: true }; michael@0: return String(aString); michael@0: }; michael@0: } michael@0: exports.JSTermHelpers = JSTermHelpers; michael@0: michael@0: michael@0: /** michael@0: * A ReflowObserver that listens for reflow events from the page. michael@0: * Implements nsIReflowObserver. michael@0: * michael@0: * @constructor michael@0: * @param object aWindow michael@0: * The window for which we need to track reflow. michael@0: * @param object aOwner michael@0: * The listener owner which needs to implement: michael@0: * - onReflowActivity(aReflowInfo) michael@0: */ michael@0: michael@0: function ConsoleReflowListener(aWindow, aListener) michael@0: { michael@0: this.docshell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell); michael@0: this.listener = aListener; michael@0: this.docshell.addWeakReflowObserver(this); michael@0: } michael@0: michael@0: exports.ConsoleReflowListener = ConsoleReflowListener; michael@0: michael@0: ConsoleReflowListener.prototype = michael@0: { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver, michael@0: Ci.nsISupportsWeakReference]), michael@0: docshell: null, michael@0: listener: null, michael@0: michael@0: /** michael@0: * Forward reflow event to listener. michael@0: * michael@0: * @param DOMHighResTimeStamp aStart michael@0: * @param DOMHighResTimeStamp aEnd michael@0: * @param boolean aInterruptible michael@0: */ michael@0: sendReflow: function CRL_sendReflow(aStart, aEnd, aInterruptible) michael@0: { michael@0: let frame = components.stack.caller.caller; michael@0: michael@0: let filename = frame.filename; michael@0: michael@0: if (filename) { michael@0: // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js", michael@0: // we only take the last part. michael@0: filename = filename.split(" ").pop(); michael@0: } michael@0: michael@0: this.listener.onReflowActivity({ michael@0: interruptible: aInterruptible, michael@0: start: aStart, michael@0: end: aEnd, michael@0: sourceURL: filename, michael@0: sourceLine: frame.lineNumber, michael@0: functionName: frame.name michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * On uninterruptible reflow michael@0: * michael@0: * @param DOMHighResTimeStamp aStart michael@0: * @param DOMHighResTimeStamp aEnd michael@0: */ michael@0: reflow: function CRL_reflow(aStart, aEnd) michael@0: { michael@0: this.sendReflow(aStart, aEnd, false); michael@0: }, michael@0: michael@0: /** michael@0: * On interruptible reflow michael@0: * michael@0: * @param DOMHighResTimeStamp aStart michael@0: * @param DOMHighResTimeStamp aEnd michael@0: */ michael@0: reflowInterruptible: function CRL_reflowInterruptible(aStart, aEnd) michael@0: { michael@0: this.sendReflow(aStart, aEnd, true); michael@0: }, michael@0: michael@0: /** michael@0: * Unregister listener. michael@0: */ michael@0: destroy: function CRL_destroy() michael@0: { michael@0: this.docshell.removeWeakReflowObserver(this); michael@0: this.listener = this.docshell = null; michael@0: }, michael@0: }; michael@0: michael@0: function gSequenceId() michael@0: { michael@0: return gSequenceId.n++; michael@0: } michael@0: gSequenceId.n = 0;