--- a/devtools/client/webconsole/utils.js
+++ b/devtools/client/webconsole/utils.js
@@ -2,28 +2,17 @@
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {Cc, Ci, Cu, components} = require("chrome");
-const {isWindowIncluded} = require("devtools/shared/layout/utils");
const Services = require("Services");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-
-// TODO: Bug 842672 - browser/ imports modules from toolkit/.
-// Note that these are only used in WebConsoleCommands, see $0 and pprint().
-loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this,
- "swm",
- "@mozilla.org/serviceworkers/manager;1",
- "nsIServiceWorkerManager");
// Match the function name from the result of toString() or toSource().
//
// Examples:
// (function foobar(a, b) { ...
// function foobar2(a) { ...
// function() { ...
const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
@@ -48,27 +37,16 @@ var WebConsoleUtils = {
supportsString: function (string) {
let str = Cc["@mozilla.org/supports-string;1"]
.createInstance(Ci.nsISupportsString);
str.data = string;
return str;
},
/**
- * Given a message, return one of CONSOLE_WORKER_IDS if it matches
- * one of those.
- *
- * @return string
- */
- getWorkerType: function (message) {
- let id = message ? message.innerID : null;
- return CONSOLE_WORKER_IDS[CONSOLE_WORKER_IDS.indexOf(id)] || null;
- },
-
- /**
* Clone an object.
*
* @param object object
* The object you want cloned.
* @param boolean recursive
* Tells if you want to dig deeper into the object, to clone
* recursively.
* @param function [filter]
@@ -120,164 +98,16 @@ var WebConsoleUtils = {
let style = win.getComputedStyle(from);
to.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
to.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
to.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
to.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
},
/**
- * Gets the ID of the inner window of this DOM window.
- *
- * @param nsIDOMWindow window
- * @return integer
- * Inner ID for the given window.
- */
- getInnerWindowId: function (window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
- },
-
- /**
- * Recursively gather a list of inner window ids given a
- * top level window.
- *
- * @param nsIDOMWindow window
- * @return Array
- * list of inner window ids.
- */
- getInnerWindowIDsForFrames: function (window) {
- let innerWindowID = this.getInnerWindowId(window);
- let ids = [innerWindowID];
-
- if (window.frames) {
- for (let i = 0; i < window.frames.length; i++) {
- let frame = window.frames[i];
- ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
- }
- }
-
- return ids;
- },
-
- /**
- * Gets the ID of the outer window of this DOM window.
- *
- * @param nsIDOMWindow window
- * @return integer
- * Outer ID for the given window.
- */
- getOuterWindowId: function (window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
- },
-
- /**
- * Tells if the given function is native or not.
- *
- * @param function func
- * The function you want to check if it is native or not.
- * @return boolean
- * True if the given function is native, false otherwise.
- */
- isNativeFunction: function (func) {
- return typeof func == "function" && !("prototype" in func);
- },
-
- /**
- * Tells if the given property of the provided object is a
- * non-native getter or not.
- *
- * @param object object
- * The object that contains the property.
- * @param string prop
- * The property you want to check if it is a getter or not.
- * @return boolean
- * True if the given property is a getter, false otherwise.
- */
- isNonNativeGetter: function (object, prop) {
- if (typeof object != "object") {
- return false;
- }
- let desc = this.getPropertyDescriptor(object, prop);
- return desc && desc.get && !this.isNativeFunction(desc.get);
- },
-
- /**
- * Get the property descriptor for the given object.
- *
- * @param object object
- * The object that contains the property.
- * @param string prop
- * The property you want to get the descriptor for.
- * @return object
- * Property descriptor.
- */
- getPropertyDescriptor: function (object, prop) {
- let desc = null;
- while (object) {
- try {
- if ((desc = Object.getOwnPropertyDescriptor(object, prop))) {
- break;
- }
- } catch (ex) {
- // Native getters throw here. See bug 520882.
- // null throws TypeError.
- if (ex.name != "NS_ERROR_XPC_BAD_CONVERT_JS" &&
- ex.name != "NS_ERROR_XPC_BAD_OP_ON_WN_PROTO" &&
- ex.name != "TypeError") {
- throw ex;
- }
- }
-
- try {
- object = Object.getPrototypeOf(object);
- } catch (ex) {
- if (ex.name == "TypeError") {
- return desc;
- }
- throw ex;
- }
- }
- return desc;
- },
-
- /**
- * Sort function for object properties.
- *
- * @param object a
- * Property descriptor.
- * @param object b
- * Property descriptor.
- * @return integer
- * -1 if a.name < b.name,
- * 1 if a.name > b.name,
- * 0 otherwise.
- */
- propertiesSort: function (a, b) {
- // Convert the pair.name to a number for later sorting.
- let number = parseFloat(a.name);
- let bNumber = parseFloat(b.name);
-
- // Sort numbers, then string.
- if (!isNaN(number) && isNaN(bNumber)) {
- return -1;
- } else if (isNaN(number) && !isNaN(bNumber)) {
- return 1;
- } else if (!isNaN(number) && !isNaN(bNumber)) {
- return number - bNumber;
- } else if (a.name < b.name) {
- return -1;
- } else if (a.name > b.name) {
- return 1;
- }
- return 0;
- },
-
- /**
* Create a grip for the given value. If the value is an object,
* an object wrapper will be created.
*
* @param mixed value
* The value you want to create a grip for, before sending it to the
* client.
* @param function objectWrapper
* If the value is an object then the objectWrapper function is
@@ -314,51 +144,16 @@ var WebConsoleUtils = {
default:
console.error("Failed to provide a grip for value of " + typeof value
+ ": " + value);
return null;
}
},
/**
- * Check if the given object is an iterator or a generator.
- *
- * @param object object
- * The object you want to check.
- * @return boolean
- * True if the given object is an iterator or a generator, otherwise
- * false is returned.
- */
- isIteratorOrGenerator: function (object) {
- if (object === null) {
- return false;
- }
-
- if (typeof object == "object") {
- if (typeof object.__iterator__ == "function" ||
- object.constructor && object.constructor.name == "Iterator") {
- return true;
- }
-
- try {
- let str = object.toString();
- if (typeof object.next == "function" &&
- str.indexOf("[object Generator") == 0) {
- return true;
- }
- } catch (ex) {
- // window.history.next throws in the typeof check above.
- return false;
- }
- }
-
- return false;
- },
-
- /**
* Determine if the given request mixes HTTP with HTTPS content.
*
* @param string request
* Location of the requested content.
* @param string location
* Location of the current page.
* @return boolean
* True if the content is mixed, false if not.
@@ -608,874 +403,8 @@ WebConsoleUtils.L10n.prototype = {
array.length);
} catch (ex) {
console.error("Failed to format string: " + name);
throw ex;
}
return result;
},
};
-
-// /////////////////////////////////////////////////////////////////////////////
-// The page errors listener
-// /////////////////////////////////////////////////////////////////////////////
-
-/**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow [window]
- * Optional - the window object for which we are created. This is used
- * for filtering out messages that belong to other windows.
- * @param object listener
- * The listener object must have one method:
- * - onConsoleServiceMessage(). This method is invoked with one argument,
- * the nsIConsoleMessage, whenever a relevant message is received.
- */
-function ConsoleServiceListener(window, listener) {
- this.window = window;
- this.listener = listener;
-}
-exports.ConsoleServiceListener = ConsoleServiceListener;
-
-ConsoleServiceListener.prototype =
-{
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
-
- /**
- * The content window for which we listen to page errors.
- * @type nsIDOMWindow
- */
- window: null,
-
- /**
- * The listener object which is notified of messages from the console service.
- * @type object
- */
- listener: null,
-
- /**
- * Initialize the nsIConsoleService listener.
- */
- init: function () {
- Services.console.registerListener(this);
- },
-
- /**
- * The nsIConsoleService observer. This method takes all the script error
- * messages belonging to the current window and sends them to the remote Web
- * Console instance.
- *
- * @param nsIConsoleMessage message
- * The message object coming from the nsIConsoleService.
- */
- observe: function (message) {
- if (!this.listener) {
- return;
- }
-
- if (this.window) {
- if (!(message instanceof Ci.nsIScriptError) ||
- !message.outerWindowID ||
- !this.isCategoryAllowed(message.category)) {
- return;
- }
-
- let errorWindow = Services.wm.getOuterWindowWithId(message.outerWindowID);
- if (!errorWindow || !isWindowIncluded(this.window, errorWindow)) {
- return;
- }
- }
-
- this.listener.onConsoleServiceMessage(message);
- },
-
- /**
- * Check if the given message category is allowed to be tracked or not.
- * We ignore chrome-originating errors as we only care about content.
- *
- * @param string category
- * The message category you want to check.
- * @return boolean
- * True if the category is allowed to be logged, false otherwise.
- */
- isCategoryAllowed: function (category) {
- if (!category) {
- return false;
- }
-
- switch (category) {
- case "XPConnect JavaScript":
- case "component javascript":
- case "chrome javascript":
- case "chrome registration":
- case "XBL":
- case "XBL Prototype Handler":
- case "XBL Content Sink":
- case "xbl javascript":
- return false;
- }
-
- return true;
- },
-
- /**
- * Get the cached page errors for the current inner window and its (i)frames.
- *
- * @param boolean [includePrivate=false]
- * Tells if you want to also retrieve messages coming from private
- * windows. Defaults to false.
- * @return array
- * The array of cached messages. Each element is an nsIScriptError or
- * an nsIConsoleMessage
- */
- getCachedMessages: function (includePrivate = false) {
- let errors = Services.console.getMessageArray() || [];
-
- // if !this.window, we're in a browser console. Still need to filter
- // private messages.
- if (!this.window) {
- return errors.filter((error) => {
- if (error instanceof Ci.nsIScriptError) {
- if (!includePrivate && error.isFromPrivateWindow) {
- return false;
- }
- }
-
- return true;
- });
- }
-
- let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
-
- return errors.filter((error) => {
- if (error instanceof Ci.nsIScriptError) {
- if (!includePrivate && error.isFromPrivateWindow) {
- return false;
- }
- if (ids &&
- (ids.indexOf(error.innerWindowID) == -1 ||
- !this.isCategoryAllowed(error.category))) {
- return false;
- }
- } else if (ids && ids[0]) {
- // If this is not an nsIScriptError and we need to do window-based
- // filtering we skip this message.
- return false;
- }
-
- return true;
- });
- },
-
- /**
- * Remove the nsIConsoleService listener.
- */
- destroy: function () {
- Services.console.unregisterListener(this);
- this.listener = this.window = null;
- },
-};
-
-// /////////////////////////////////////////////////////////////////////////////
-// The window.console API observer
-// /////////////////////////////////////////////////////////////////////////////
-
-/**
- * The window.console API observer. This allows the window.console API messages
- * to be sent to the remote Web Console instance.
- *
- * @constructor
- * @param nsIDOMWindow window
- * Optional - the window object for which we are created. This is used
- * for filtering out messages that belong to other windows.
- * @param object owner
- * The owner object must have the following methods:
- * - onConsoleAPICall(). This method is invoked with one argument, the
- * Console API message that comes from the observer service, whenever
- * a relevant console API call is received.
- * @param object filteringOptions
- * Optional - The filteringOptions that this listener should listen to:
- * - addonId: filter console messages based on the addonId.
- */
-function ConsoleAPIListener(window, owner, {addonId} = {}) {
- this.window = window;
- this.owner = owner;
- this.addonId = addonId;
-}
-exports.ConsoleAPIListener = ConsoleAPIListener;
-
-ConsoleAPIListener.prototype =
-{
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
-
- /**
- * The content window for which we listen to window.console API calls.
- * @type nsIDOMWindow
- */
- window: null,
-
- /**
- * The owner object which is notified of window.console API calls. It must
- * have a onConsoleAPICall method which is invoked with one argument: the
- * console API call object that comes from the observer service.
- *
- * @type object
- * @see WebConsoleActor
- */
- owner: null,
-
- /**
- * The addonId that we listen for. If not null then only messages from this
- * console will be returned.
- */
- addonId: null,
-
- /**
- * Initialize the window.console API observer.
- */
- init: function () {
- // Note that the observer is process-wide. We will filter the messages as
- // needed, see CAL_observe().
- Services.obs.addObserver(this, "console-api-log-event", false);
- },
-
- /**
- * The console API message observer. When messages are received from the
- * observer service we forward them to the remote Web Console instance.
- *
- * @param object message
- * The message object receives from the observer service.
- * @param string topic
- * The message topic received from the observer service.
- */
- observe: function (message, topic) {
- if (!this.owner) {
- return;
- }
-
- // Here, wrappedJSObject is not a security wrapper but a property defined
- // by the XPCOM component which allows us to unwrap the XPCOM interface and
- // access the underlying JSObject.
- let apiMessage = message.wrappedJSObject;
-
- if (!this.isMessageRelevant(apiMessage)) {
- return;
- }
-
- this.owner.onConsoleAPICall(apiMessage);
- },
-
- /**
- * Given a message, return true if this window should show it and false
- * if it should be ignored.
- *
- * @param message
- * The message from the Storage Service
- * @return bool
- * Do we care about this message?
- */
- isMessageRelevant: function (message) {
- let workerType = WebConsoleUtils.getWorkerType(message);
-
- if (this.window && workerType === "ServiceWorker") {
- // For messages from Service Workers, message.ID is the
- // scope, which can be used to determine whether it's controlling
- // a window.
- let scope = message.ID;
-
- if (!swm.shouldReportToWindow(this.window, scope)) {
- return false;
- }
- }
-
- if (this.window && !workerType) {
- let msgWindow = Services.wm.getCurrentInnerWindowWithId(message.innerID);
- if (!msgWindow || !isWindowIncluded(this.window, msgWindow)) {
- // Not the same window!
- return false;
- }
- }
-
- if (this.addonId) {
- // ConsoleAPI.jsm messages contains a consoleID, (and it is currently
- // used in Addon SDK add-ons), the standard 'console' object
- // (which is used in regular webpages and in WebExtensions pages)
- // contains the originAttributes of the source document principal.
-
- // Filtering based on the originAttributes used by
- // the Console API object.
- if (message.originAttributes &&
- message.originAttributes.addonId == this.addonId) {
- return true;
- }
-
- // Filtering based on the old-style consoleID property used by
- // the legacy Console JSM module.
- if (message.consoleID && message.consoleID == `addon/${this.addonId}`) {
- return true;
- }
-
- return false;
- }
-
- return true;
- },
-
- /**
- * Get the cached messages for the current inner window and its (i)frames.
- *
- * @param boolean [includePrivate=false]
- * Tells if you want to also retrieve messages coming from private
- * windows. Defaults to false.
- * @return array
- * The array of cached messages.
- */
- getCachedMessages: function (includePrivate = false) {
- let messages = [];
- let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
- .getService(Ci.nsIConsoleAPIStorage);
-
- // if !this.window, we're in a browser console. Retrieve all events
- // for filtering based on privacy.
- if (!this.window) {
- messages = ConsoleAPIStorage.getEvents();
- } else {
- let ids = WebConsoleUtils.getInnerWindowIDsForFrames(this.window);
- ids.forEach((id) => {
- messages = messages.concat(ConsoleAPIStorage.getEvents(id));
- });
- }
-
- CONSOLE_WORKER_IDS.forEach((id) => {
- messages = messages.concat(ConsoleAPIStorage.getEvents(id));
- });
-
- messages = messages.filter(msg => {
- return this.isMessageRelevant(msg);
- });
-
- if (includePrivate) {
- return messages;
- }
-
- return messages.filter((m) => !m.private);
- },
-
- /**
- * Destroy the console API listener.
- */
- destroy: function () {
- Services.obs.removeObserver(this, "console-api-log-event");
- this.window = this.owner = null;
- },
-};
-
-/**
- * WebConsole commands manager.
- *
- * Defines a set of functions /variables ("commands") that are available from
- * the Web Console but not from the web page.
- *
- */
-var WebConsoleCommands = {
- _registeredCommands: new Map(),
- _originalCommands: new Map(),
-
- /**
- * @private
- * Reserved for built-in commands. To register a command from the code of an
- * add-on, see WebConsoleCommands.register instead.
- *
- * @see WebConsoleCommands.register
- */
- _registerOriginal: function (name, command) {
- this.register(name, command);
- this._originalCommands.set(name, this.getCommand(name));
- },
-
- /**
- * Register a new command.
- * @param {string} name The command name (exemple: "$")
- * @param {(function|object)} command The command to register.
- * It can be a function so the command is a function (like "$()"),
- * or it can also be a property descriptor to describe a getter / value (like
- * "$0").
- *
- * The command function or the command getter are passed a owner object as
- * their first parameter (see the example below).
- *
- * Note that setters don't work currently and "enumerable" and "configurable"
- * are forced to true.
- *
- * @example
- *
- * WebConsoleCommands.register("$", function JSTH_$(owner, selector)
- * {
- * return owner.window.document.querySelector(selector);
- * });
- *
- * WebConsoleCommands.register("$0", {
- * get: function(owner) {
- * return owner.makeDebuggeeValue(owner.selectedNode);
- * }
- * });
- */
- register: function (name, command) {
- this._registeredCommands.set(name, command);
- },
-
- /**
- * Unregister a command.
- *
- * If the command being unregister overrode a built-in command,
- * the latter is restored.
- *
- * @param {string} name The name of the command
- */
- unregister: function (name) {
- this._registeredCommands.delete(name);
- if (this._originalCommands.has(name)) {
- this.register(name, this._originalCommands.get(name));
- }
- },
-
- /**
- * Returns a command by its name.
- *
- * @param {string} name The name of the command.
- *
- * @return {(function|object)} The command.
- */
- getCommand: function (name) {
- return this._registeredCommands.get(name);
- },
-
- /**
- * Returns true if a command is registered with the given name.
- *
- * @param {string} name The name of the command.
- *
- * @return {boolean} True if the command is registered.
- */
- hasCommand: function (name) {
- return this._registeredCommands.has(name);
- },
-};
-
-exports.WebConsoleCommands = WebConsoleCommands;
-
-/*
- * Built-in commands.
- *
- * A list of helper functions used by Firebug can be found here:
- * http://getfirebug.com/wiki/index.php/Command_Line_API
- */
-
-/**
- * Find a node by ID.
- *
- * @param string id
- * The ID of the element you want.
- * @return nsIDOMNode or null
- * The result of calling document.querySelector(selector).
- */
-WebConsoleCommands._registerOriginal("$", function (owner, selector) {
- return owner.window.document.querySelector(selector);
-});
-
-/**
- * Find the nodes matching a CSS selector.
- *
- * @param string selector
- * A string that is passed to window.document.querySelectorAll.
- * @return nsIDOMNodeList
- * Returns the result of document.querySelectorAll(selector).
- */
-WebConsoleCommands._registerOriginal("$$", function (owner, selector) {
- let nodes = owner.window.document.querySelectorAll(selector);
-
- // Calling owner.window.Array.from() doesn't work without accessing the
- // wrappedJSObject, so just loop through the results instead.
- let result = new owner.window.Array();
- for (let i = 0; i < nodes.length; i++) {
- result.push(nodes[i]);
- }
- return result;
-});
-
-/**
- * Returns the result of the last console input evaluation
- *
- * @return object|undefined
- * Returns last console evaluation or undefined
- */
-WebConsoleCommands._registerOriginal("$_", {
- get: function (owner) {
- return owner.consoleActor.getLastConsoleInputEvaluation();
- }
-});
-
-/**
- * Runs an xPath query and returns all matched nodes.
- *
- * @param string xPath
- * xPath search query to execute.
- * @param [optional] nsIDOMNode context
- * Context to run the xPath query on. Uses window.document if not set.
- * @return array of nsIDOMNode
- */
-WebConsoleCommands._registerOriginal("$x", function (owner, xPath, context) {
- let nodes = new owner.window.Array();
-
- // Not waiving Xrays, since we want the original Document.evaluate function,
- // instead of anything that's been redefined.
- let doc = owner.window.document;
- context = context || doc;
-
- let results = doc.evaluate(xPath, context, null,
- Ci.nsIDOMXPathResult.ANY_TYPE, null);
- let node;
- while ((node = results.iterateNext())) {
- nodes.push(node);
- }
-
- return nodes;
-});
-
-/**
- * Returns the currently selected object in the highlighter.
- *
- * @return Object representing the current selection in the
- * Inspector, or null if no selection exists.
- */
-WebConsoleCommands._registerOriginal("$0", {
- get: function (owner) {
- return owner.makeDebuggeeValue(owner.selectedNode);
- }
-});
-
-/**
- * Clears the output of the WebConsole.
- */
-WebConsoleCommands._registerOriginal("clear", function (owner) {
- owner.helperResult = {
- type: "clearOutput",
- };
-});
-
-/**
- * Clears the input history of the WebConsole.
- */
-WebConsoleCommands._registerOriginal("clearHistory", function (owner) {
- owner.helperResult = {
- type: "clearHistory",
- };
-});
-
-/**
- * Returns the result of Object.keys(object).
- *
- * @param object object
- * Object to return the property names from.
- * @return array of strings
- */
-WebConsoleCommands._registerOriginal("keys", function (owner, object) {
- // Need to waive Xrays so we can iterate functions and accessor properties
- return Cu.cloneInto(Object.keys(Cu.waiveXrays(object)), owner.window);
-});
-
-/**
- * Returns the values of all properties on object.
- *
- * @param object object
- * Object to display the values from.
- * @return array of string
- */
-WebConsoleCommands._registerOriginal("values", function (owner, object) {
- let values = [];
- // Need to waive Xrays so we can iterate functions and accessor properties
- let waived = Cu.waiveXrays(object);
- let names = Object.getOwnPropertyNames(waived);
-
- for (let name of names) {
- values.push(waived[name]);
- }
-
- return Cu.cloneInto(values, owner.window);
-});
-
-/**
- * Opens a help window in MDN.
- */
-WebConsoleCommands._registerOriginal("help", function (owner) {
- owner.helperResult = { type: "help" };
-});
-
-/**
- * Change the JS evaluation scope.
- *
- * @param DOMElement|string|window window
- * The window object to use for eval scope. This can be a string that
- * is used to perform document.querySelector(), to find the iframe that
- * you want to cd() to. A DOMElement can be given as well, the
- * .contentWindow property is used. Lastly, you can directly pass
- * a window object. If you call cd() with no arguments, the current
- * eval scope is cleared back to its default (the top window).
- */
-WebConsoleCommands._registerOriginal("cd", function (owner, window) {
- if (!window) {
- owner.consoleActor.evalWindow = null;
- owner.helperResult = { type: "cd" };
- return;
- }
-
- if (typeof window == "string") {
- window = owner.window.document.querySelector(window);
- }
- if (window instanceof Ci.nsIDOMElement && window.contentWindow) {
- window = window.contentWindow;
- }
- if (!(window instanceof Ci.nsIDOMWindow)) {
- owner.helperResult = {
- type: "error",
- message: "cdFunctionInvalidArgument"
- };
- return;
- }
-
- owner.consoleActor.evalWindow = window;
- owner.helperResult = { type: "cd" };
-});
-
-/**
- * Inspects the passed object. This is done by opening the PropertyPanel.
- *
- * @param object object
- * Object to inspect.
- */
-WebConsoleCommands._registerOriginal("inspect", function (owner, object) {
- let dbgObj = owner.makeDebuggeeValue(object);
- let grip = owner.createValueGrip(dbgObj);
- owner.helperResult = {
- type: "inspectObject",
- input: owner.evalInput,
- object: grip,
- };
-});
-
-/**
- * Prints object to the output.
- *
- * @param object object
- * Object to print to the output.
- * @return string
- */
-WebConsoleCommands._registerOriginal("pprint", function (owner, object) {
- if (object === null || object === undefined || object === true ||
- object === false) {
- owner.helperResult = {
- type: "error",
- message: "helperFuncUnsupportedTypeError",
- };
- return null;
- }
-
- owner.helperResult = { rawOutput: true };
-
- if (typeof object == "function") {
- return object + "\n";
- }
-
- let output = [];
-
- let obj = object;
- for (let name in obj) {
- let desc = WebConsoleUtils.getPropertyDescriptor(obj, name) || {};
- if (desc.get || desc.set) {
- // TODO: Bug 842672 - toolkit/ imports modules from browser/.
- let getGrip = VariablesView.getGrip(desc.get);
- let setGrip = VariablesView.getGrip(desc.set);
- let getString = VariablesView.getString(getGrip);
- let setString = VariablesView.getString(setGrip);
- output.push(name + ":", " get: " + getString, " set: " + setString);
- } else {
- let valueGrip = VariablesView.getGrip(obj[name]);
- let valueString = VariablesView.getString(valueGrip);
- output.push(name + ": " + valueString);
- }
- }
-
- return " " + output.join("\n ");
-});
-
-/**
- * Print the String representation of a value to the output, as-is.
- *
- * @param any value
- * A value you want to output as a string.
- * @return void
- */
-WebConsoleCommands._registerOriginal("print", function (owner, value) {
- owner.helperResult = { rawOutput: true };
- if (typeof value === "symbol") {
- return Symbol.prototype.toString.call(value);
- }
- // Waiving Xrays here allows us to see a closer representation of the
- // underlying object. This may execute arbitrary content code, but that
- // code will run with content privileges, and the result will be rendered
- // inert by coercing it to a String.
- return String(Cu.waiveXrays(value));
-});
-
-/**
- * Copy the String representation of a value to the clipboard.
- *
- * @param any value
- * A value you want to copy as a string.
- * @return void
- */
-WebConsoleCommands._registerOriginal("copy", function (owner, value) {
- let payload;
- try {
- if (value instanceof Ci.nsIDOMElement) {
- payload = value.outerHTML;
- } else if (typeof value == "string") {
- payload = value;
- } else {
- payload = JSON.stringify(value, null, " ");
- }
- } catch (ex) {
- payload = "/* " + ex + " */";
- }
- owner.helperResult = {
- type: "copyValueToClipboard",
- value: payload,
- };
-});
-
-/**
- * (Internal only) Add the bindings to |owner.sandbox|.
- * This is intended to be used by the WebConsole actor only.
- *
- * @param object owner
- * The owning object.
- */
-function addWebConsoleCommands(owner) {
- if (!owner) {
- throw new Error("The owner is required");
- }
- for (let [name, command] of WebConsoleCommands._registeredCommands) {
- if (typeof command === "function") {
- owner.sandbox[name] = command.bind(undefined, owner);
- } else if (typeof command === "object") {
- let clone = Object.assign({}, command, {
- // We force the enumerability and the configurability (so the
- // WebConsoleActor can reconfigure the property).
- enumerable: true,
- configurable: true
- });
-
- if (typeof command.get === "function") {
- clone.get = command.get.bind(undefined, owner);
- }
- if (typeof command.set === "function") {
- clone.set = command.set.bind(undefined, owner);
- }
-
- Object.defineProperty(owner.sandbox, name, clone);
- }
- }
-}
-
-exports.addWebConsoleCommands = addWebConsoleCommands;
-
-/**
- * A ReflowObserver that listens for reflow events from the page.
- * Implements nsIReflowObserver.
- *
- * @constructor
- * @param object window
- * The window for which we need to track reflow.
- * @param object owner
- * The listener owner which needs to implement:
- * - onReflowActivity(reflowInfo)
- */
-
-function ConsoleReflowListener(window, listener) {
- this.docshell = window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
- this.listener = listener;
- this.docshell.addWeakReflowObserver(this);
-}
-
-exports.ConsoleReflowListener = ConsoleReflowListener;
-
-ConsoleReflowListener.prototype =
-{
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
- Ci.nsISupportsWeakReference]),
- docshell: null,
- listener: null,
-
- /**
- * Forward reflow event to listener.
- *
- * @param DOMHighResTimeStamp start
- * @param DOMHighResTimeStamp end
- * @param boolean interruptible
- */
- sendReflow: function (start, end, interruptible) {
- let frame = components.stack.caller.caller;
-
- let filename = frame ? frame.filename : null;
-
- if (filename) {
- // Because filename could be of the form "xxx.js -> xxx.js -> xxx.js",
- // we only take the last part.
- filename = filename.split(" ").pop();
- }
-
- this.listener.onReflowActivity({
- interruptible: interruptible,
- start: start,
- end: end,
- sourceURL: filename,
- sourceLine: frame ? frame.lineNumber : null,
- functionName: frame ? frame.name : null
- });
- },
-
- /**
- * On uninterruptible reflow
- *
- * @param DOMHighResTimeStamp start
- * @param DOMHighResTimeStamp end
- */
- reflow: function (start, end) {
- this.sendReflow(start, end, false);
- },
-
- /**
- * On interruptible reflow
- *
- * @param DOMHighResTimeStamp start
- * @param DOMHighResTimeStamp end
- */
- reflowInterruptible: function (start, end) {
- this.sendReflow(start, end, true);
- },
-
- /**
- * Unregister listener.
- */
- destroy: function () {
- this.docshell.removeWeakReflowObserver(this);
- this.listener = this.docshell = null;
- },
-};
-
-function gSequenceId() {
- return gSequenceId.n++;
-}
-gSequenceId.n = 0;
--- a/devtools/server/actors/utils/webconsole-utils.js
+++ b/devtools/server/actors/utils/webconsole-utils.js
@@ -15,49 +15,25 @@ const {XPCOMUtils} = require("resource:/
// Note that these are only used in WebConsoleCommands, see $0 and pprint().
loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
XPCOMUtils.defineLazyServiceGetter(this,
"swm",
"@mozilla.org/serviceworkers/manager;1",
"nsIServiceWorkerManager");
-// Match the function name from the result of toString() or toSource().
-//
-// Examples:
-// (function foobar(a, b) { ...
-// function foobar2(a) { ...
-// function() { ...
-const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
-
-// Number of terminal entries for the self-xss prevention to go away
-const CONSOLE_ENTRY_THRESHOLD = 5;
-
const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
"SharedWorker",
"ServiceWorker",
"Worker"
];
var WebConsoleUtils = {
/**
- * Wrap a string in an nsISupportsString object.
- *
- * @param string string
- * @return nsISupportsString
- */
- supportsString: function (string) {
- let str = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- str.data = string;
- return str;
- },
-
- /**
* Given a message, return one of CONSOLE_WORKER_IDS if it matches
* one of those.
*
* @return string
*/
getWorkerType: function (message) {
let id = message ? message.innerID : null;
return CONSOLE_WORKER_IDS[CONSOLE_WORKER_IDS.indexOf(id)] || null;
@@ -103,33 +79,16 @@ var WebConsoleUtils = {
}
}
}
return temp;
},
/**
- * Copies certain style attributes from one element to another.
- *
- * @param nsIDOMNode from
- * The target node.
- * @param nsIDOMNode to
- * The destination node.
- */
- copyTextStyles: function (from, to) {
- let win = from.ownerDocument.defaultView;
- let style = win.getComputedStyle(from);
- to.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
- to.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
- to.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
- to.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
- },
-
- /**
* Gets the ID of the inner window of this DOM window.
*
* @param nsIDOMWindow window
* @return integer
* Inner ID for the given window.
*/
getInnerWindowId: function (window) {
return window.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -154,59 +113,16 @@ var WebConsoleUtils = {
ids = ids.concat(this.getInnerWindowIDsForFrames(frame));
}
}
return ids;
},
/**
- * Gets the ID of the outer window of this DOM window.
- *
- * @param nsIDOMWindow window
- * @return integer
- * Outer ID for the given window.
- */
- getOuterWindowId: function (window) {
- return window.QueryInterface(Ci.nsIInterfaceRequestor)
- .getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
- },
-
- /**
- * Tells if the given function is native or not.
- *
- * @param function func
- * The function you want to check if it is native or not.
- * @return boolean
- * True if the given function is native, false otherwise.
- */
- isNativeFunction: function (func) {
- return typeof func == "function" && !("prototype" in func);
- },
-
- /**
- * Tells if the given property of the provided object is a
- * non-native getter or not.
- *
- * @param object object
- * The object that contains the property.
- * @param string prop
- * The property you want to check if it is a getter or not.
- * @return boolean
- * True if the given property is a getter, false otherwise.
- */
- isNonNativeGetter: function (object, prop) {
- if (typeof object != "object") {
- return false;
- }
- let desc = this.getPropertyDescriptor(object, prop);
- return desc && desc.get && !this.isNativeFunction(desc.get);
- },
-
- /**
* Get the property descriptor for the given object.
*
* @param object object
* The object that contains the property.
* @param string prop
* The property you want to get the descriptor for.
* @return object
* Property descriptor.
@@ -236,48 +152,16 @@ var WebConsoleUtils = {
}
throw ex;
}
}
return desc;
},
/**
- * Sort function for object properties.
- *
- * @param object a
- * Property descriptor.
- * @param object b
- * Property descriptor.
- * @return integer
- * -1 if a.name < b.name,
- * 1 if a.name > b.name,
- * 0 otherwise.
- */
- propertiesSort: function (a, b) {
- // Convert the pair.name to a number for later sorting.
- let number = parseFloat(a.name);
- let bNumber = parseFloat(b.name);
-
- // Sort numbers, then string.
- if (!isNaN(number) && isNaN(bNumber)) {
- return -1;
- } else if (isNaN(number) && !isNaN(bNumber)) {
- return 1;
- } else if (!isNaN(number) && !isNaN(bNumber)) {
- return number - bNumber;
- } else if (a.name < b.name) {
- return -1;
- } else if (a.name > b.name) {
- return 1;
- }
- return 0;
- },
-
- /**
* Create a grip for the given value. If the value is an object,
* an object wrapper will be created.
*
* @param mixed value
* The value you want to create a grip for, before sending it to the
* client.
* @param function objectWrapper
* If the value is an object then the objectWrapper function is
@@ -312,313 +196,20 @@ var WebConsoleUtils = {
case "function":
return objectWrapper(value);
default:
console.error("Failed to provide a grip for value of " + typeof value
+ ": " + value);
return null;
}
},
-
- /**
- * Check if the given object is an iterator or a generator.
- *
- * @param object object
- * The object you want to check.
- * @return boolean
- * True if the given object is an iterator or a generator, otherwise
- * false is returned.
- */
- isIteratorOrGenerator: function (object) {
- if (object === null) {
- return false;
- }
-
- if (typeof object == "object") {
- if (typeof object.__iterator__ == "function" ||
- object.constructor && object.constructor.name == "Iterator") {
- return true;
- }
-
- try {
- let str = object.toString();
- if (typeof object.next == "function" &&
- str.indexOf("[object Generator") == 0) {
- return true;
- }
- } catch (ex) {
- // window.history.next throws in the typeof check above.
- return false;
- }
- }
-
- return false;
- },
-
- /**
- * Determine if the given request mixes HTTP with HTTPS content.
- *
- * @param string request
- * Location of the requested content.
- * @param string location
- * Location of the current page.
- * @return boolean
- * True if the content is mixed, false if not.
- */
- isMixedHTTPSRequest: function (request, location) {
- try {
- let requestURI = Services.io.newURI(request, null, null);
- let contentURI = Services.io.newURI(location, null, null);
- return (contentURI.scheme == "https" && requestURI.scheme != "https");
- } catch (ex) {
- return false;
- }
- },
-
- /**
- * Helper function to deduce the name of the provided function.
- *
- * @param funtion function
- * The function whose name will be returned.
- * @return string
- * Function name.
- */
- getFunctionName: function (func) {
- let name = null;
- if (func.name) {
- name = func.name;
- } else {
- let desc;
- try {
- desc = func.getOwnPropertyDescriptor("displayName");
- } catch (ex) {
- // Ignore.
- }
- if (desc && typeof desc.value == "string") {
- name = desc.value;
- }
- }
- if (!name) {
- try {
- let str = (func.toString() || func.toSource()) + "";
- name = (str.match(REGEX_MATCH_FUNCTION_NAME) || [])[1];
- } catch (ex) {
- // Ignore.
- }
- }
- return name;
- },
-
- /**
- * Get the object class name. For example, the |window| object has the Window
- * class name (based on [object Window]).
- *
- * @param object object
- * The object you want to get the class name for.
- * @return string
- * The object class name.
- */
- getObjectClassName: function (object) {
- if (object === null) {
- return "null";
- }
- if (object === undefined) {
- return "undefined";
- }
-
- let type = typeof object;
- if (type != "object") {
- // Grip class names should start with an uppercase letter.
- return type.charAt(0).toUpperCase() + type.substr(1);
- }
-
- let className;
-
- try {
- className = ((object + "").match(/^\[object (\S+)\]$/) || [])[1];
- if (!className) {
- className = ((object.constructor + "")
- .match(/^\[object (\S+)\]$/) || [])[1];
- }
- if (!className && typeof object.constructor == "function") {
- className = this.getFunctionName(object.constructor);
- }
- } catch (ex) {
- // Ignore.
- }
-
- return className;
- },
-
- /**
- * Check if the given value is a grip with an actor.
- *
- * @param mixed grip
- * Value you want to check if it is a grip with an actor.
- * @return boolean
- * True if the given value is a grip with an actor.
- */
- isActorGrip: function (grip) {
- return grip && typeof (grip) == "object" && grip.actor;
- },
-
- /**
- * Value of devtools.selfxss.count preference
- *
- * @type number
- * @private
- */
- _usageCount: 0,
- get usageCount() {
- if (WebConsoleUtils._usageCount < CONSOLE_ENTRY_THRESHOLD) {
- WebConsoleUtils._usageCount =
- Services.prefs.getIntPref("devtools.selfxss.count");
- if (Services.prefs.getBoolPref("devtools.chrome.enabled")) {
- WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
- }
- }
- return WebConsoleUtils._usageCount;
- },
- set usageCount(newUC) {
- if (newUC <= CONSOLE_ENTRY_THRESHOLD) {
- WebConsoleUtils._usageCount = newUC;
- Services.prefs.setIntPref("devtools.selfxss.count", newUC);
- }
- },
- /**
- * The inputNode "paste" event handler generator. Helps prevent
- * self-xss attacks
- *
- * @param nsIDOMElement inputField
- * @param nsIDOMElement notificationBox
- * @returns A function to be added as a handler to 'paste' and
- *'drop' events on the input field
- */
- pasteHandlerGen: function (inputField, notificationBox, msg, okstring) {
- let handler = function (event) {
- if (WebConsoleUtils.usageCount >= CONSOLE_ENTRY_THRESHOLD) {
- inputField.removeEventListener("paste", handler);
- inputField.removeEventListener("drop", handler);
- return true;
- }
- if (notificationBox.getNotificationWithValue("selfxss-notification")) {
- event.preventDefault();
- event.stopPropagation();
- return false;
- }
-
- let notification = notificationBox.appendNotification(msg,
- "selfxss-notification", null,
- notificationBox.PRIORITY_WARNING_HIGH, null,
- function (eventType) {
- // Cleanup function if notification is dismissed
- if (eventType == "removed") {
- inputField.removeEventListener("keyup", pasteKeyUpHandler);
- }
- });
-
- function pasteKeyUpHandler(event2) {
- let value = inputField.value || inputField.textContent;
- if (value.includes(okstring)) {
- notificationBox.removeNotification(notification);
- inputField.removeEventListener("keyup", pasteKeyUpHandler);
- WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
- }
- }
- inputField.addEventListener("keyup", pasteKeyUpHandler);
-
- event.preventDefault();
- event.stopPropagation();
- return false;
- };
- return handler;
- },
};
exports.Utils = WebConsoleUtils;
-// ////////////////////////////////////////////////////////////////////////
-// Localization
-// ////////////////////////////////////////////////////////////////////////
-
-WebConsoleUtils.L10n = function (bundleURI) {
- this._bundleUri = bundleURI;
-};
-
-WebConsoleUtils.L10n.prototype = {
- _stringBundle: null,
-
- get stringBundle() {
- if (!this._stringBundle) {
- this._stringBundle = Services.strings.createBundle(this._bundleUri);
- }
- return this._stringBundle;
- },
-
- /**
- * Generates a formatted timestamp string for displaying in console messages.
- *
- * @param integer [milliseconds]
- * Optional, allows you to specify the timestamp in milliseconds since
- * the UNIX epoch.
- * @return string
- * The timestamp formatted for display.
- */
- timestampString: function (milliseconds) {
- let d = new Date(milliseconds ? milliseconds : null);
- let hours = d.getHours(), minutes = d.getMinutes();
- let seconds = d.getSeconds();
- milliseconds = d.getMilliseconds();
- let parameters = [hours, minutes, seconds, milliseconds];
- return this.getFormatStr("timestampFormat", parameters);
- },
-
- /**
- * Retrieve a localized string.
- *
- * @param string name
- * The string name you want from the Web Console string bundle.
- * @return string
- * The localized string.
- */
- getStr: function (name) {
- let result;
- try {
- result = this.stringBundle.GetStringFromName(name);
- } catch (ex) {
- console.error("Failed to get string: " + name);
- throw ex;
- }
- return result;
- },
-
- /**
- * Retrieve a localized string formatted with values coming from the given
- * array.
- *
- * @param string name
- * The string name you want from the Web Console string bundle.
- * @param array array
- * The array of values you want in the formatted string.
- * @return string
- * The formatted local string.
- */
- getFormatStr: function (name, array) {
- let result;
- try {
- result = this.stringBundle.formatStringFromName(name, array,
- array.length);
- } catch (ex) {
- console.error("Failed to format string: " + name);
- throw ex;
- }
- return result;
- },
-};
-
// /////////////////////////////////////////////////////////////////////////////
// The page errors listener
// /////////////////////////////////////////////////////////////////////////////
/**
* The nsIConsoleService listener. This is used to send all of the console
* messages (JavaScript, CSS and more) to the remote Web Console instance.
*
@@ -1469,13 +1060,8 @@ ConsoleReflowListener.prototype =
/**
* Unregister listener.
*/
destroy: function () {
this.docshell.removeWeakReflowObserver(this);
this.listener = this.docshell = null;
},
};
-
-function gSequenceId() {
- return gSequenceId.n++;
-}
-gSequenceId.n = 0;