Bug 1143497 - Offer a way to extend WebConsole commands. r=bgrins

This commit is contained in:
Florent Fayolle 2015-04-06 09:41:00 -04:00
parent a2ecc81eb0
commit 5337ffc494
4 changed files with 558 additions and 295 deletions

View File

@ -32,8 +32,8 @@ XPCOMUtils.defineLazyGetter(this, "events", () => {
});
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
"ConsoleAPIListener", "JSTermHelpers", "JSPropertyProvider",
"ConsoleReflowListener"]) {
"ConsoleAPIListener", "addWebConsoleCommands", "JSPropertyProvider",
"ConsoleReflowListener"]) {
Object.defineProperty(this, name, {
get: function(prop) {
if (prop == "WebConsoleUtils") {
@ -295,11 +295,11 @@ WebConsoleActor.prototype =
consoleReflowListener: null,
/**
* The JSTerm Helpers names cache.
* The Web Console Commands names cache.
* @private
* @type array
*/
_jstermHelpersCache: null,
_webConsoleCommandsCache: null,
actorPrefix: "console",
@ -358,7 +358,7 @@ WebConsoleActor.prototype =
}
this._actorPool = null;
this._jstermHelpersCache = null;
this._webConsoleCommandsCache = null;
this._lastConsoleInputEvaluation = null;
this._evalWindow = null;
this._netEvents.clear();
@ -882,14 +882,16 @@ WebConsoleActor.prototype =
// helper functions.
let lastNonAlphaIsDot = /[.][a-zA-Z0-9$]*$/.test(reqText);
if (!lastNonAlphaIsDot) {
if (!this._jstermHelpersCache) {
if (!this._webConsoleCommandsCache) {
let helpers = {
sandbox: Object.create(null)
};
JSTermHelpers(helpers);
this._jstermHelpersCache = Object.getOwnPropertyNames(helpers.sandbox);
addWebConsoleCommands(helpers);
this._webConsoleCommandsCache =
Object.getOwnPropertyNames(helpers.sandbox);
}
matches = matches.concat(this._jstermHelpersCache.filter(n => n.startsWith(result.matchProp)));
matches = matches.concat(this._webConsoleCommandsCache
.filter(n => n.startsWith(result.matchProp)));
}
return {
@ -966,13 +968,13 @@ WebConsoleActor.prototype =
* @private
* @param object aDebuggerGlobal
* A Debugger.Object that wraps a content global. This is used for the
* JSTerm helpers.
* Web Console Commands.
* @return object
* The same object as |this|, but with an added |sandbox| property.
* The sandbox holds methods and properties that can be used as
* bindings during JS evaluation.
*/
_getJSTermHelpers: function WCA__getJSTermHelpers(aDebuggerGlobal)
_getWebConsoleCommands: function(aDebuggerGlobal)
{
let helpers = {
window: this.evalWindow,
@ -983,7 +985,7 @@ WebConsoleActor.prototype =
helperResult: null,
consoleActor: this,
};
JSTermHelpers(helpers);
addWebConsoleCommands(helpers);
let evalWindow = this.evalWindow;
function maybeExport(obj, name) {
@ -1037,9 +1039,9 @@ WebConsoleActor.prototype =
*
* The Debugger.Frame comes from the jsdebugger's Debugger instance, which
* is different from the Web Console's Debugger instance. This means that
* for evaluation to work, we need to create a new instance for the jsterm
* helpers - they need to be Debugger.Objects coming from the jsdebugger's
* Debugger instance.
* for evaluation to work, we need to create a new instance for the Web
* Console Commands helpers - they need to be Debugger.Objects coming from the
* jsdebugger's Debugger instance.
*
* When |bindObjectActor| is used objects can come from different iframes,
* from different domains. To avoid permission-related errors when objects
@ -1069,7 +1071,8 @@ WebConsoleActor.prototype =
* - window: the Debugger.Object for the global where the string was
* evaluated.
* - result: the result of the evaluation.
* - helperResult: any result coming from a JSTerm helper function.
* - helperResult: any result coming from a Web Console commands
* function.
* - url: the url to evaluate the script as. Defaults to
* "debugger eval code".
*/
@ -1125,8 +1128,8 @@ WebConsoleActor.prototype =
}
}
// Get the JSTerm helpers for the given debugger window.
let helpers = this._getJSTermHelpers(dbgWindow);
// Get the Web Console commands for the given debugger window.
let helpers = this._getWebConsoleCommands(dbgWindow);
let bindings = helpers.sandbox;
if (bindSelf) {
bindings._self = bindSelf;
@ -1140,7 +1143,8 @@ WebConsoleActor.prototype =
}
// Check if the Debugger.Frame or Debugger.Object for the global include
// $ or $$. We will not overwrite these functions with the jsterm helpers.
// $ or $$. We will not overwrite these functions with the Web Console
// commands.
let found$ = false, found$$ = false;
if (frame) {
let env = frame.environment;

View File

@ -11,6 +11,7 @@ support-files =
[test_basics.html]
[test_bug819670_getter_throws.html]
[test_cached_messages.html]
[test_commands_registration.html]
[test_consoleapi.html]
[test_consoleapi_innerID.html]
[test_file_uri.html]

View File

@ -0,0 +1,159 @@
<!DOCTYPE HTML>
<html lang="en">
<head>
<meta charset="utf8">
<title>Test for Web Console commands registration.</title>
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript;version=1.8" src="common.js"></script>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
</head>
<body>
<p>Test for Web Console commands registration.</p>
<p id="quack"></p>
<script class="testbody" type="text/javascript;version=1.8">
SimpleTest.waitForExplicitFinish();
let gState;
let tests;
let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
let {WebConsoleCommands} = devtools.require("devtools/toolkit/webconsole/utils");
function evaluateJS(input) {
return new Promise((resolve) => gState.client.evaluateJS(input, resolve));
}
function startTest()
{
removeEventListener("load", startTest);
attachConsole(["PageError"], onAttach, true);
}
function onAttach(aState, aResponse)
{
gState = aState;
runTests(tests, testEnd);
}
tests = [
Task.async(function* registerNewCommand() {
let win;
WebConsoleCommands.register("setFoo", (owner, value) => {
owner.window.foo = value;
return "ok";
});
ok(WebConsoleCommands.hasCommand("setFoo"),
"The command should be registered");
let command = "setFoo('bar')";
let response = yield evaluateJS(command);
checkObject(response, {
from: gState.actor,
input: command,
result: "ok"
});
is(top.foo, "bar", "top.foo should equal to 'bar'");
nextTest();
}),
Task.async(function* wrapCommand() {
let origKeys = WebConsoleCommands.getCommand("keys");
let newKeys = (...args) => {
let [owner, arg0] = args;
if (arg0 === ">o_/") {
return "bang!";
}
else {
return origKeys(...args);
}
};
WebConsoleCommands.register("keys", newKeys);
is(WebConsoleCommands.getCommand("keys"), newKeys,
"the keys() command should have been replaced");
let response = yield evaluateJS("keys('>o_/')");
checkObject(response, {
from: gState.actor,
result: "bang!"
});
response = yield evaluateJS("keys({foo: 'bar'})");
checkObject(response, {
from: gState.actor,
result: {
class: "Array",
preview: {
items: ["foo"]
}
}
});
WebConsoleCommands.register("keys", origKeys);
is(WebConsoleCommands.getCommand("keys"), origKeys,
"the keys() command should be restored");
nextTest();
}),
Task.async(function* unregisterCommand() {
WebConsoleCommands.unregister("setFoo");
let response = yield evaluateJS("setFoo");
checkObject(response, {
from: gState.actor,
input: "setFoo",
result: {
type: "undefined"
},
exceptionMessage: /setFoo is not defined/
});
nextTest();
}),
Task.async(function* registerAccessor() {
WebConsoleCommands.register("$foo", {
get(owner) {
let foo = owner.window.frames[0].window.document.getElementById("quack");
return owner.makeDebuggeeValue(foo);
}
});
let command = "$foo.textContent = '>o_/'";
let response = yield evaluateJS(command);
checkObject(response, {
from: gState.actor,
input: command,
result: ">o_/"
});
is(document.getElementById("quack").textContent, ">o_/",
"#foo textContent should equal to \">o_/\"");
WebConsoleCommands.unregister("$foo");
ok(!WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered");
nextTest();
})
];
function testEnd()
{
// If this is the first run, reload the page and do it again.
// Otherwise, end the test.
delete top.foo;
closeDebugger(gState, function() {
gState = null;
SimpleTest.finish();
});
}
addEventListener("load", startTest);
</script>
</body>
</html>

View File

@ -14,7 +14,7 @@ loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
loader.lazyImporter(this, "LayoutHelpers", "resource://gre/modules/devtools/LayoutHelpers.jsm");
// TODO: Bug 842672 - toolkit/ imports modules from browser/.
// Note that these are only used in JSTermHelpers, see $0 and pprint().
// Note that these are only used in WebConsoleCommands, see $0 and pprint().
loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
@ -32,7 +32,7 @@ const REGEX_MATCH_FUNCTION_NAME = /^\(?function\s+([^(\s]+)\s*\(/;
const REGEX_MATCH_FUNCTION_ARGS = /^\(?function\s*[^\s(]*\s*\((.+?)\)/;
// Number of terminal entries for the self-xss prevention to go away
const CONSOLE_ENTRY_THRESHOLD = 5
const CONSOLE_ENTRY_THRESHOLD = 5;
// Provide an easy way to bail out of even attempting an autocompletion
// if an object has way too many properties. Protects against large objects
@ -550,7 +550,7 @@ let WebConsoleUtils = {
_usageCount: 0,
get usageCount() {
if (WebConsoleUtils._usageCount < CONSOLE_ENTRY_THRESHOLD) {
WebConsoleUtils._usageCount = Services.prefs.getIntPref("devtools.selfxss.count")
WebConsoleUtils._usageCount = Services.prefs.getIntPref("devtools.selfxss.count");
if (Services.prefs.getBoolPref("devtools.chrome.enabled")) {
WebConsoleUtils.usageCount = CONSOLE_ENTRY_THRESHOLD;
}
@ -912,7 +912,7 @@ function JSPropertyProvider(aDbgObject, anEnvironment, aInputValue, aCursor)
return null;
}
if (/\[\d+\]$/.test(prop)) {
if (/\[\d+\]$/.test(prop)) {
// The property to autocomplete is a member of array. For example
// list[i][j]..[n]. Traverse the array to get the actual element.
obj = getArrayMemberProperty(obj, prop);
@ -1507,307 +1507,406 @@ ConsoleAPIListener.prototype =
},
};
/**
* WebConsole commands manager.
*
* Defines a set of functions /variables ("commands") that are available from
* the Web Console but not from the web page.
*
*/
let WebConsoleCommands = {
_registeredCommands: new Map(),
/**
* 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_$(aOwner, aSelector)
* {
* return aOwner.window.document.querySelector(aSelector);
* });
*
* WebConsoleCommands.register("$0", {
* get: function(aOwner) {
* return aOwner.makeDebuggeeValue(aOwner.selectedNode);
* }
* });
*/
register: function(name, command) {
this._registeredCommands.set(name, command);
},
/**
* Unregister a command.
*
* @param {string} name The name of the command
*/
unregister: function(name) {
this._registeredCommands.delete(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 aId
* The ID of the element you want.
* @return nsIDOMNode or null
* The result of calling document.querySelector(aSelector).
*/
WebConsoleCommands.register("$", function JSTH_$(aOwner, aSelector)
{
return aOwner.window.document.querySelector(aSelector);
});
/**
* Find the nodes matching a CSS selector.
*
* @param string aSelector
* A string that is passed to window.document.querySelectorAll.
* @return nsIDOMNodeList
* Returns the result of document.querySelectorAll(aSelector).
*/
WebConsoleCommands.register("$$", function JSTH_$$(aOwner, aSelector)
{
return aOwner.window.document.querySelectorAll(aSelector);
});
/**
* Returns the result of the last console input evaluation
*
* @return object|undefined
* Returns last console evaluation or undefined
*/
WebConsoleCommands.register("$_", {
get: function(aOwner) {
return aOwner.consoleActor.getLastConsoleInputEvaluation();
}
});
/**
* JSTerm helper functions.
* Runs an xPath query and returns all matched nodes.
*
* Defines a set of functions ("helper functions") that are available from the
* Web Console but not from the web page.
*
* A list of helper functions used by Firebug can be found here:
* http://getfirebug.com/wiki/index.php/Command_Line_API
*
* @param object aOwner
* The owning object.
* @param string aXPath
* xPath search query to execute.
* @param [optional] nsIDOMNode aContext
* Context to run the xPath query on. Uses window.document if not set.
* @return array of nsIDOMNode
*/
function JSTermHelpers(aOwner)
WebConsoleCommands.register("$x", function JSTH_$x(aOwner, aXPath, aContext)
{
/**
* Find a node by ID.
*
* @param string aId
* The ID of the element you want.
* @return nsIDOMNode or null
* The result of calling document.querySelector(aSelector).
*/
aOwner.sandbox.$ = function JSTH_$(aSelector)
{
return aOwner.window.document.querySelector(aSelector);
let nodes = new aOwner.window.wrappedJSObject.Array();
let doc = aOwner.window.document;
aContext = aContext || doc;
let results = doc.evaluate(aXPath, aContext, 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.register("$0", {
get: function(aOwner) {
return aOwner.makeDebuggeeValue(aOwner.selectedNode);
}
});
/**
* Clears the output of the WebConsole.
*/
WebConsoleCommands.register("clear", function JSTH_clear(aOwner)
{
aOwner.helperResult = {
type: "clearOutput",
};
});
/**
* Find the nodes matching a CSS selector.
*
* @param string aSelector
* A string that is passed to window.document.querySelectorAll.
* @return nsIDOMNodeList
* Returns the result of document.querySelectorAll(aSelector).
*/
aOwner.sandbox.$$ = function JSTH_$$(aSelector)
{
return aOwner.window.document.querySelectorAll(aSelector);
/**
* Clears the input history of the WebConsole.
*/
WebConsoleCommands.register("clearHistory", function JSTH_clearHistory(aOwner)
{
aOwner.helperResult = {
type: "clearHistory",
};
});
/**
* Returns the result of the last console input evaluation
*
* @return object|undefined
* Returns last console evaluation or undefined
*/
Object.defineProperty(aOwner.sandbox, "$_", {
get: function() {
return aOwner.consoleActor.getLastConsoleInputEvaluation();
},
enumerable: true,
configurable: true
});
/**
* Returns the result of Object.keys(aObject).
*
* @param object aObject
* Object to return the property names from.
* @return array of strings
*/
WebConsoleCommands.register("keys", function JSTH_keys(aOwner, aObject)
{
return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
});
/**
* Runs an xPath query and returns all matched nodes.
*
* @param string aXPath
* xPath search query to execute.
* @param [optional] nsIDOMNode aContext
* Context to run the xPath query on. Uses window.document if not set.
* @return array of nsIDOMNode
*/
aOwner.sandbox.$x = function JSTH_$x(aXPath, aContext)
{
let nodes = new aOwner.window.wrappedJSObject.Array();
let doc = aOwner.window.document;
aContext = aContext || doc;
/**
* Returns the values of all properties on aObject.
*
* @param object aObject
* Object to display the values from.
* @return array of string
*/
WebConsoleCommands.register("values", function JSTH_values(aOwner, aObject)
{
let arrValues = new aOwner.window.wrappedJSObject.Array();
let obj = WebConsoleUtils.unwrap(aObject);
let results = doc.evaluate(aXPath, aContext, null,
Ci.nsIDOMXPathResult.ANY_TYPE, null);
let node;
while ((node = results.iterateNext())) {
nodes.push(node);
}
for (let prop in obj) {
arrValues.push(obj[prop]);
}
return nodes;
};
return arrValues;
});
/**
* Returns the currently selected object in the highlighter.
*
* @return Object representing the current selection in the
* Inspector, or null if no selection exists.
*/
Object.defineProperty(aOwner.sandbox, "$0", {
get: function() {
return aOwner.makeDebuggeeValue(aOwner.selectedNode)
},
enumerable: true,
configurable: true
});
/**
* Opens a help window in MDN.
*/
WebConsoleCommands.register("help", function JSTH_help(aOwner)
{
aOwner.helperResult = { type: "help" };
});
/**
* Clears the output of the JSTerm.
*/
aOwner.sandbox.clear = function JSTH_clear()
{
aOwner.helperResult = {
type: "clearOutput",
};
};
/**
* Clears the input history of the JSTerm.
*/
aOwner.sandbox.clearHistory = function JSTH_clearHistory()
{
aOwner.helperResult = {
type: "clearHistory",
};
};
/**
* Returns the result of Object.keys(aObject).
*
* @param object aObject
* Object to return the property names from.
* @return array of strings
*/
aOwner.sandbox.keys = function JSTH_keys(aObject)
{
return aOwner.window.wrappedJSObject.Object.keys(WebConsoleUtils.unwrap(aObject));
};
/**
* Returns the values of all properties on aObject.
*
* @param object aObject
* Object to display the values from.
* @return array of string
*/
aOwner.sandbox.values = function JSTH_values(aObject)
{
let arrValues = new aOwner.window.wrappedJSObject.Array();
let obj = WebConsoleUtils.unwrap(aObject);
for (let prop in obj) {
arrValues.push(obj[prop]);
}
return arrValues;
};
/**
* Opens a help window in MDN.
*/
aOwner.sandbox.help = function JSTH_help()
{
aOwner.helperResult = { type: "help" };
};
/**
* Change the JS evaluation scope.
*
* @param DOMElement|string|window aWindow
* 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).
*/
aOwner.sandbox.cd = function JSTH_cd(aWindow)
{
if (!aWindow) {
aOwner.consoleActor.evalWindow = null;
aOwner.helperResult = { type: "cd" };
return;
}
if (typeof aWindow == "string") {
aWindow = aOwner.window.document.querySelector(aWindow);
}
if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
aWindow = aWindow.contentWindow;
}
if (!(aWindow instanceof Ci.nsIDOMWindow)) {
aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
return;
}
aOwner.consoleActor.evalWindow = aWindow;
/**
* Change the JS evaluation scope.
*
* @param DOMElement|string|window aWindow
* 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.register("cd", function JSTH_cd(aOwner, aWindow)
{
if (!aWindow) {
aOwner.consoleActor.evalWindow = null;
aOwner.helperResult = { type: "cd" };
};
return;
}
/**
* Inspects the passed aObject. This is done by opening the PropertyPanel.
*
* @param object aObject
* Object to inspect.
*/
aOwner.sandbox.inspect = function JSTH_inspect(aObject)
{
let dbgObj = aOwner.makeDebuggeeValue(aObject);
let grip = aOwner.createValueGrip(dbgObj);
if (typeof aWindow == "string") {
aWindow = aOwner.window.document.querySelector(aWindow);
}
if (aWindow instanceof Ci.nsIDOMElement && aWindow.contentWindow) {
aWindow = aWindow.contentWindow;
}
if (!(aWindow instanceof Ci.nsIDOMWindow)) {
aOwner.helperResult = { type: "error", message: "cdFunctionInvalidArgument" };
return;
}
aOwner.consoleActor.evalWindow = aWindow;
aOwner.helperResult = { type: "cd" };
});
/**
* Inspects the passed aObject. This is done by opening the PropertyPanel.
*
* @param object aObject
* Object to inspect.
*/
WebConsoleCommands.register("inspect", function JSTH_inspect(aOwner, aObject)
{
let dbgObj = aOwner.makeDebuggeeValue(aObject);
let grip = aOwner.createValueGrip(dbgObj);
aOwner.helperResult = {
type: "inspectObject",
input: aOwner.evalInput,
object: grip,
};
});
/**
* Prints aObject to the output.
*
* @param object aObject
* Object to print to the output.
* @return string
*/
WebConsoleCommands.register("pprint", function JSTH_pprint(aOwner, aObject)
{
if (aObject === null || aObject === undefined || aObject === true ||
aObject === false) {
aOwner.helperResult = {
type: "inspectObject",
input: aOwner.evalInput,
object: grip,
type: "error",
message: "helperFuncUnsupportedTypeError",
};
return null;
}
aOwner.helperResult = { rawOutput: true };
if (typeof aObject == "function") {
return aObject + "\n";
}
let output = [];
let obj = WebConsoleUtils.unwrap(aObject);
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 aValue
* A value you want to output as a string.
* @return void
*/
WebConsoleCommands.register("print", function JSTH_print(aOwner, aValue)
{
aOwner.helperResult = { rawOutput: true };
if (typeof aValue === "symbol") {
return Symbol.prototype.toString.call(aValue);
}
// 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(aValue));
});
/**
* Copy the String representation of a value to the clipboard.
*
* @param any aValue
* A value you want to copy as a string.
* @return void
*/
WebConsoleCommands.register("copy", function JSTH_copy(aOwner, aValue)
{
let payload;
try {
if (aValue instanceof Ci.nsIDOMElement) {
payload = aValue.outerHTML;
} else if (typeof aValue == "string") {
payload = aValue;
} else {
payload = JSON.stringify(aValue, null, " ");
}
} catch (ex) {
payload = "/* " + ex + " */";
}
aOwner.helperResult = {
type: "copyValueToClipboard",
value: payload,
};
});
/**
* Prints aObject to the output.
*
* @param object aObject
* Object to print to the output.
* @return string
*/
aOwner.sandbox.pprint = function JSTH_pprint(aObject)
{
if (aObject === null || aObject === undefined || aObject === true ||
aObject === false) {
aOwner.helperResult = {
type: "error",
message: "helperFuncUnsupportedTypeError",
};
return null;
/**
* (Internal only) Add the bindings to |owner.sandbox|.
* This is intended to be used by the WebConsole actor only.
*
* @param object aOwner
* 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
});
aOwner.helperResult = { rawOutput: true };
if (typeof aObject == "function") {
return aObject + "\n";
}
let output = [];
let obj = WebConsoleUtils.unwrap(aObject);
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);
if (typeof command.get === "function") {
clone.get = command.get.bind(undefined, owner);
}
else {
let valueGrip = VariablesView.getGrip(obj[name]);
let valueString = VariablesView.getString(valueGrip);
output.push(name + ": " + valueString);
if (typeof command.set === "function") {
clone.set = command.set.bind(undefined, owner);
}
}
return " " + output.join("\n ");
};
/**
* Print the String representation of a value to the output, as-is.
*
* @param any aValue
* A value you want to output as a string.
* @return void
*/
aOwner.sandbox.print = function JSTH_print(aValue)
{
aOwner.helperResult = { rawOutput: true };
if (typeof aValue === "symbol") {
return Symbol.prototype.toString.call(aValue);
Object.defineProperty(owner.sandbox, name, clone);
}
// 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(aValue));
};
/**
* Copy the String representation of a value to the clipboard.
*
* @param any aValue
* A value you want to copy as a string.
* @return void
*/
aOwner.sandbox.copy = function JSTH_copy(aValue)
{
let payload;
try {
if (aValue instanceof Ci.nsIDOMElement) {
payload = aValue.outerHTML;
} else if (typeof aValue == "string") {
payload = aValue;
} else {
payload = JSON.stringify(aValue, null, " ");
}
} catch (ex) {
payload = "/* " + ex + " */";
}
aOwner.helperResult = {
type: "copyValueToClipboard",
value: payload,
};
};
}
}
exports.JSTermHelpers = JSTermHelpers;
exports.addWebConsoleCommands = addWebConsoleCommands;
/**
* A ReflowObserver that listens for reflow events from the page.