Bug 1379570 - Adapt the inspect command to the new console frontend. r=bgrins

MozReview-Commit-ID: LsT12pOJhvV

--HG--
rename : devtools/client/webconsole/test/browser_jsterm_inspect.js => devtools/client/webconsole/new-console-output/test/mochitest/browser_jsterm_inspect.js
extra : rebase_source : b3e179be7528dd54a5c7b3250150fe311d47b88f
This commit is contained in:
Nicolas Chevobbe 2017-07-11 13:50:59 +02:00
Родитель 8eee0f1bac
Коммит 15ea45ad09
14 изменённых файлов: 418 добавлений и 203 удалений

Просмотреть файл

@ -342,7 +342,9 @@ JSTerm.prototype = {
this.clearHistory();
break;
case "inspectObject":
this.inspectObjectActor(helperResult.object);
if (!this.hud.NEW_CONSOLE_OUTPUT_ENABLED) {
this.inspectObjectActor(helperResult.object);
}
break;
case "error":
try {
@ -361,9 +363,14 @@ JSTerm.prototype = {
}
// Hide undefined results coming from JSTerm helper functions.
if (!errorMessage && result && typeof result == "object" &&
result.type == "undefined" &&
helperResult && !helperHasRawOutput) {
if (!errorMessage
&& result
&& typeof result == "object"
&& result.type == "undefined"
&& helperResult
&& !helperHasRawOutput
&& !(this.hud.NEW_CONSOLE_OUTPUT_ENABLED && helperResult.type === "inspectObject")
) {
callback && callback();
return;
}

Просмотреть файл

@ -20,6 +20,7 @@ const {
const { ObjectClient } = require("devtools/shared/client/main");
const {
MESSAGE_TYPE,
JSTERM_COMMANDS,
} = require("../constants");
const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
@ -45,6 +46,7 @@ GripMessageBody.propTypes = {
escapeWhitespace: PropTypes.bool,
loadedObjectProperties: PropTypes.object,
type: PropTypes.string,
helperType: PropTypes.string,
};
GripMessageBody.defaultProps = {
@ -62,7 +64,6 @@ function GripMessageBody(props) {
escapeWhitespace,
mode = MODE.LONG,
loadedObjectProperties,
type,
} = props;
let styleObject;
@ -89,7 +90,7 @@ function GripMessageBody(props) {
const objectInspectorProps = {
// Auto-expand the ObjectInspector when the message is a console.dir one.
autoExpandDepth: type === MESSAGE_TYPE.DIR ? 1 : 0,
autoExpandDepth: shouldAutoExpandObjectInspector(props) ? 1 : 0,
mode,
// TODO: we disable focus since it's not currently working well in ObjectInspector.
// Let's remove the property below when problem are fixed in OI.
@ -161,4 +162,16 @@ function cleanupStyle(userProvidedStyle, createElement) {
}, {});
}
function shouldAutoExpandObjectInspector(props) {
const {
helperType,
type,
} = props;
return (
type === MESSAGE_TYPE.DIR
|| helperType === JSTERM_COMMANDS.INSPECT
);
}
module.exports = GripMessageBody;

Просмотреть файл

@ -42,6 +42,7 @@ function EvaluationResult(props) {
const {
source,
type,
helperType,
level,
id: messageId,
exceptionDocURL,
@ -70,6 +71,8 @@ function EvaluationResult(props) {
useQuotes: true,
escapeWhitespace: false,
loadedObjectProperties,
type,
helperType,
});
}

Просмотреть файл

@ -83,5 +83,16 @@ const chromeRDPEnums = {
}
};
const jstermCommands = {
JSTERM_COMMANDS: {
INSPECT: "inspectObject"
}
};
// Combine into a single constants object
module.exports = Object.assign({}, actionTypes, prefs, chromeRDPEnums);
module.exports = Object.assign({},
actionTypes,
chromeRDPEnums,
jstermCommands,
prefs,
);

Просмотреть файл

@ -49,6 +49,13 @@ describe("EvaluationResult component:", () => {
expect(wrapper.find(".message.error").length).toBe(1);
});
it("renders an inspect command result", () => {
const message = stubPreparedMessages.get("inspect({a: 1})");
const wrapper = render(EvaluationResult({ message }));
expect(wrapper.find(".message-body").text()).toBe("Object { a: 1 }");
});
it("displays a [Learn more] link", () => {
const store = setupStore([]);

Просмотреть файл

@ -145,7 +145,8 @@ p {
const evaluationResultCommands = [
"new Date(0)",
"asdf()",
"1 + @"
"1 + @",
"inspect({a: 1})"
];
let evaluationResult = new Map(evaluationResultCommands.map(cmd => [cmd, cmd]));

Просмотреть файл

@ -19,6 +19,7 @@ stubPreparedMessages.set("new Date(0)", new ConsoleMessage({
"source": "javascript",
"timeStamp": 1479159921364,
"type": "result",
"helperType": null,
"level": "log",
"parameters": {
"type": "object",
@ -47,6 +48,7 @@ stubPreparedMessages.set("asdf()", new ConsoleMessage({
"source": "javascript",
"timeStamp": 1479159921377,
"type": "result",
"helperType": null,
"level": "error",
"messageText": "ReferenceError: asdf is not defined",
"parameters": {
@ -72,6 +74,7 @@ stubPreparedMessages.set("1 + @", new ConsoleMessage({
"source": "javascript",
"timeStamp": 1479159921399,
"type": "result",
"helperType": null,
"level": "error",
"messageText": "SyntaxError: illegal character",
"parameters": {
@ -91,12 +94,52 @@ stubPreparedMessages.set("1 + @", new ConsoleMessage({
"indent": 0
}));
stubPreparedMessages.set("inspect({a: 1})", new ConsoleMessage({
"id": "1",
"allowRepeating": true,
"source": "javascript",
"timeStamp": 1499776070751,
"type": "result",
"helperType": "inspectObject",
"level": "log",
"parameters": {
"type": "object",
"actor": "server1.conn0.child1/obj35",
"class": "Object",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "Object",
"ownProperties": {
"a": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": 1
}
},
"ownPropertiesLength": 1,
"safeGetterValues": {}
}
},
"repeatId": "{\"frame\":null,\"groupId\":null,\"indent\":0,\"level\":\"log\",\"parameters\":{\"type\":\"object\",\"actor\":\"server1.conn0.child1/obj35\",\"class\":\"Object\",\"extensible\":true,\"frozen\":false,\"sealed\":false,\"ownPropertyLength\":1,\"preview\":{\"kind\":\"Object\",\"ownProperties\":{\"a\":{\"configurable\":true,\"enumerable\":true,\"writable\":true,\"value\":1}},\"ownPropertiesLength\":1,\"safeGetterValues\":{}}},\"source\":\"javascript\",\"type\":\"result\",\"userProvidedStyles\":null}",
"stacktrace": null,
"frame": null,
"groupId": null,
"userProvidedStyles": null,
"notes": null,
"indent": 0
}));
stubPreparedMessages.set("longString message Error", new ConsoleMessage({
"id": "1",
"allowRepeating": true,
"source": "javascript",
"timeStamp": 1493108241073,
"type": "result",
"helperType": null,
"level": "error",
"messageText": {
"type": "longString",
@ -210,6 +253,44 @@ stubPackets.set("1 + @", {
"notes": null
});
stubPackets.set("inspect({a: 1})", {
"from": "server1.conn0.child1/consoleActor2",
"input": "inspect({a: 1})",
"result": {
"type": "undefined"
},
"timestamp": 1499776070751,
"exception": null,
"frame": null,
"helperResult": {
"type": "inspectObject",
"input": "inspect({a: 1})",
"object": {
"type": "object",
"actor": "server1.conn0.child1/obj35",
"class": "Object",
"extensible": true,
"frozen": false,
"sealed": false,
"ownPropertyLength": 1,
"preview": {
"kind": "Object",
"ownProperties": {
"a": {
"configurable": true,
"enumerable": true,
"writable": true,
"value": 1
}
},
"ownPropertiesLength": 1,
"safeGetterValues": {}
}
}
},
"notes": null
});
stubPackets.set("longString message Error", {
"from": "server1.conn0.child1/consoleActor2",
"input": "throw new Error(\"Long error \".repeat(10000))",

Просмотреть файл

@ -18,6 +18,7 @@ support-files =
test-stacktrace-location-debugger-link.html
!/devtools/client/framework/test/shared-head.js
[browser_jsterm_inspect.js]
[browser_netmonitor_shows_reqs_in_webconsole.js]
[browser_webconsole_batching.js]
[browser_webconsole_console_dir.js]

Просмотреть файл

@ -0,0 +1,62 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Check that the inspect() jsterm helper function works.
"use strict";
const TEST_URI = "data:text/html;charset=utf8,<p>test inspect() command";
add_task(async function () {
let toolbox = await openNewTabAndToolbox(TEST_URI, "webconsole");
let hud = toolbox.getCurrentPanel().hud;
let jsterm = hud.jsterm;
info("Test `inspect(window)`");
// Add a global value so we can check it later.
await jsterm.execute("testProp = 'testValue'");
await jsterm.execute("inspect(window)");
const inspectWindowNode = await waitFor(() =>
findInspectResultMessage(hud.ui.experimentalOutputNode, 1));
let objectInspectors = [...inspectWindowNode.querySelectorAll(".tree")];
is(objectInspectors.length, 1, "There is the expected number of object inspectors");
const [windowOi] = objectInspectors;
let windowOiNodes = windowOi.querySelectorAll(".node");
// The tree can be collapsed since the properties are fetched asynchronously.
if (windowOiNodes.length === 1) {
// If this is the case, we wait for the properties to be fetched and displayed.
await waitForNodeMutation(windowOi, {
childList: true
});
windowOiNodes = windowOi.querySelectorAll(".node");
}
let propertiesNodes = [...windowOi.querySelectorAll(".object-label")];
const testPropertyLabelNode = propertiesNodes.find(el => el.textContent === "testProp");
ok(testPropertyLabelNode, "The testProp property label is displayed as expected");
const testPropertyValueNode = testPropertyLabelNode
.closest(".node")
.querySelector(".objectBox");
is(testPropertyValueNode.textContent, '"testValue"',
"The testProp property value is displayed as expected");
/* Check that a primitive value can be inspected, too */
info("Test `inspect(1)`");
await jsterm.execute("inspect(1)");
const inspectPrimitiveNode = await waitFor(() =>
findInspectResultMessage(hud.ui.experimentalOutputNode, 2));
is(inspectPrimitiveNode.textContent, 1, "The primitive is displayed as expected");
});
function findInspectResultMessage(node, index) {
return node.querySelectorAll(".message.result")[index];
}

Просмотреть файл

@ -93,13 +93,3 @@ add_task(async function () {
function findConsoleDir(node, index) {
return node.querySelectorAll(".dir.message")[index];
}
function waitForNodeMutation(node, observeConfig = {}) {
return new Promise(resolve => {
const observer = new MutationObserver(mutations => {
resolve(mutations);
observer.disconnect();
});
observer.observe(node, observeConfig);
});
}

Просмотреть файл

@ -99,13 +99,3 @@ add_task(async function () {
is(arrayOiNodes.length, 9, "There is the expected number of nodes in the tree");
});
function waitForNodeMutation(node, observeConfig = {}) {
return new Promise(resolve => {
const observer = new MutationObserver(mutations => {
resolve(mutations);
observer.disconnect();
});
observer.observe(node, observeConfig);
});
}

Просмотреть файл

@ -4,7 +4,7 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from ../../../../framework/test/shared-head.js */
/* exported WCUL10n, openNewTabAndConsole, waitForMessages, waitFor, findMessage,
openContextMenu, hideContextMenu, loadDocument */
openContextMenu, hideContextMenu, loadDocument, waitForNodeMutation */
"use strict";
@ -186,3 +186,21 @@ function loadDocument(browser, url) {
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url);
});
}
/**
* Returns a promise that resolves when the node passed as an argument mutate
* according to the passed configuration.
*
* @param {Node} node - The node to observe mutations on.
* @param {Object} observeConfig - A configuration object for MutationObserver.observe.
* @returns {Promise}
*/
function waitForNodeMutation(node, observeConfig = {}) {
return new Promise(resolve => {
const observer = new MutationObserver(mutations => {
resolve(mutations);
observer.disconnect();
});
observer.observe(node, observeConfig);
});
}

Просмотреть файл

@ -31,6 +31,7 @@ exports.ConsoleMessage = function (props) {
source: null,
timeStamp: null,
type: null,
helperType: null,
level: null,
messageText: null,
parameters: null,

Просмотреть файл

@ -41,200 +41,230 @@ function transformPacket(packet) {
switch (packet.type) {
case "consoleAPICall": {
let { message } = packet;
let parameters = message.arguments;
let type = message.level;
let level = getLevelFromType(type);
let messageText = null;
const timer = message.timer;
// Special per-type conversion.
switch (type) {
case "clear":
// We show a message to users when calls console.clear() is called.
parameters = [l10n.getStr("consoleCleared")];
break;
case "count":
// Chrome RDP doesn't have a special type for count.
type = MESSAGE_TYPE.LOG;
let {counter} = message;
let label = counter.label ? counter.label : l10n.getStr("noCounterLabel");
messageText = `${label}: ${counter.count}`;
parameters = null;
break;
case "time":
parameters = null;
if (timer && timer.error) {
messageText = l10n.getFormatStr(timer.error, [timer.name]);
level = MESSAGE_LEVEL.WARN;
} else {
// We don't show anything for console.time calls to match Chrome's behaviour.
type = MESSAGE_TYPE.NULL_MESSAGE;
}
break;
case "timeEnd":
parameters = null;
if (timer && timer.error) {
messageText = l10n.getFormatStr(timer.error, [timer.name]);
level = MESSAGE_LEVEL.WARN;
} else if (timer) {
// We show the duration to users when calls console.timeEnd() is called,
// if corresponding console.time() was called before.
let duration = Math.round(timer.duration * 100) / 100;
messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]);
} else {
// If the `timer` property does not exists, we don't output anything.
type = MESSAGE_TYPE.NULL_MESSAGE;
}
break;
case "table":
const supportedClasses = [
"Array", "Object", "Map", "Set", "WeakMap", "WeakSet"];
if (
!Array.isArray(parameters) ||
parameters.length === 0 ||
!supportedClasses.includes(parameters[0].class)
) {
// If the class of the first parameter is not supported,
// we handle the call as a simple console.log
type = "log";
}
break;
case "group":
type = MESSAGE_TYPE.START_GROUP;
if (parameters.length === 0) {
parameters = [l10n.getStr("noGroupLabel")];
}
break;
case "groupCollapsed":
type = MESSAGE_TYPE.START_GROUP_COLLAPSED;
if (parameters.length === 0) {
parameters = [l10n.getStr("noGroupLabel")];
}
break;
case "groupEnd":
type = MESSAGE_TYPE.END_GROUP;
parameters = null;
break;
case "dirxml":
// Handle console.dirxml calls as simple console.log
type = "log";
break;
}
const frame = message.filename ? {
source: message.filename,
line: message.lineNumber,
column: message.columnNumber,
} : null;
return new ConsoleMessage({
source: MESSAGE_SOURCE.CONSOLE_API,
type,
level,
parameters,
messageText,
stacktrace: message.stacktrace ? message.stacktrace : null,
frame,
timeStamp: message.timeStamp,
userProvidedStyles: message.styles,
});
return transformConsoleAPICallPacket(packet);
}
case "navigationMessage": {
let { message } = packet;
return new ConsoleMessage({
source: MESSAGE_SOURCE.CONSOLE_API,
type: MESSAGE_TYPE.LOG,
level: MESSAGE_LEVEL.LOG,
messageText: "Navigated to " + message.url,
timeStamp: message.timeStamp
});
return transformNavigationMessagePacket(packet);
}
case "logMessage": {
let { message } = packet;
return new ConsoleMessage({
source: MESSAGE_SOURCE.CONSOLE_API,
type: MESSAGE_TYPE.LOG,
level: MESSAGE_LEVEL.LOG,
messageText: message.message,
timeStamp: message.timeStamp
});
return transformLogMessagePacket(packet);
}
case "pageError": {
let { pageError } = packet;
let level = MESSAGE_LEVEL.ERROR;
if (pageError.warning || pageError.strict) {
level = MESSAGE_LEVEL.WARN;
} else if (pageError.info) {
level = MESSAGE_LEVEL.INFO;
}
const frame = pageError.sourceName ? {
source: pageError.sourceName,
line: pageError.lineNumber,
column: pageError.columnNumber
} : null;
let matchesCSS = /^(?:CSS|Layout)\b/.test(pageError.category);
let messageSource = matchesCSS ? MESSAGE_SOURCE.CSS
: MESSAGE_SOURCE.JAVASCRIPT;
return new ConsoleMessage({
source: messageSource,
type: MESSAGE_TYPE.LOG,
level,
messageText: pageError.errorMessage,
stacktrace: pageError.stacktrace ? pageError.stacktrace : null,
frame,
exceptionDocURL: pageError.exceptionDocURL,
timeStamp: pageError.timeStamp,
notes: pageError.notes,
});
return transformPageErrorPacket(packet);
}
case "networkEvent": {
let { networkEvent } = packet;
return new NetworkEventMessage({
actor: networkEvent.actor,
isXHR: networkEvent.isXHR,
request: networkEvent.request,
response: networkEvent.response,
timeStamp: networkEvent.timeStamp,
totalTime: networkEvent.totalTime,
});
return transformNetworkEventPacket(packet);
}
case "evaluationResult":
default: {
let {
exceptionMessage: messageText,
exceptionDocURL,
frame,
result: parameters,
timestamp: timeStamp,
notes,
} = packet;
const level = messageText ? MESSAGE_LEVEL.ERROR : MESSAGE_LEVEL.LOG;
return new ConsoleMessage({
source: MESSAGE_SOURCE.JAVASCRIPT,
type: MESSAGE_TYPE.RESULT,
level,
messageText,
parameters,
exceptionDocURL,
frame,
timeStamp,
notes,
});
return transformEvaluationResultPacket(packet);
}
}
}
function transformConsoleAPICallPacket(packet) {
let { message } = packet;
let parameters = message.arguments;
let type = message.level;
let level = getLevelFromType(type);
let messageText = null;
const timer = message.timer;
// Special per-type conversion.
switch (type) {
case "clear":
// We show a message to users when calls console.clear() is called.
parameters = [l10n.getStr("consoleCleared")];
break;
case "count":
// Chrome RDP doesn't have a special type for count.
type = MESSAGE_TYPE.LOG;
let {counter} = message;
let label = counter.label ? counter.label : l10n.getStr("noCounterLabel");
messageText = `${label}: ${counter.count}`;
parameters = null;
break;
case "time":
parameters = null;
if (timer && timer.error) {
messageText = l10n.getFormatStr(timer.error, [timer.name]);
level = MESSAGE_LEVEL.WARN;
} else {
// We don't show anything for console.time calls to match Chrome's behaviour.
type = MESSAGE_TYPE.NULL_MESSAGE;
}
break;
case "timeEnd":
parameters = null;
if (timer && timer.error) {
messageText = l10n.getFormatStr(timer.error, [timer.name]);
level = MESSAGE_LEVEL.WARN;
} else if (timer) {
// We show the duration to users when calls console.timeEnd() is called,
// if corresponding console.time() was called before.
let duration = Math.round(timer.duration * 100) / 100;
messageText = l10n.getFormatStr("timeEnd", [timer.name, duration]);
} else {
// If the `timer` property does not exists, we don't output anything.
type = MESSAGE_TYPE.NULL_MESSAGE;
}
break;
case "table":
const supportedClasses = [
"Array", "Object", "Map", "Set", "WeakMap", "WeakSet"];
if (
!Array.isArray(parameters) ||
parameters.length === 0 ||
!supportedClasses.includes(parameters[0].class)
) {
// If the class of the first parameter is not supported,
// we handle the call as a simple console.log
type = "log";
}
break;
case "group":
type = MESSAGE_TYPE.START_GROUP;
if (parameters.length === 0) {
parameters = [l10n.getStr("noGroupLabel")];
}
break;
case "groupCollapsed":
type = MESSAGE_TYPE.START_GROUP_COLLAPSED;
if (parameters.length === 0) {
parameters = [l10n.getStr("noGroupLabel")];
}
break;
case "groupEnd":
type = MESSAGE_TYPE.END_GROUP;
parameters = null;
break;
case "dirxml":
// Handle console.dirxml calls as simple console.log
type = "log";
break;
}
const frame = message.filename ? {
source: message.filename,
line: message.lineNumber,
column: message.columnNumber,
} : null;
return new ConsoleMessage({
source: MESSAGE_SOURCE.CONSOLE_API,
type,
level,
parameters,
messageText,
stacktrace: message.stacktrace ? message.stacktrace : null,
frame,
timeStamp: message.timeStamp,
userProvidedStyles: message.styles,
});
}
function transformNavigationMessagePacket(packet) {
let { message } = packet;
return new ConsoleMessage({
source: MESSAGE_SOURCE.CONSOLE_API,
type: MESSAGE_TYPE.LOG,
level: MESSAGE_LEVEL.LOG,
messageText: "Navigated to " + message.url,
timeStamp: message.timeStamp
});
}
function transformLogMessagePacket(packet) {
let { message } = packet;
return new ConsoleMessage({
source: MESSAGE_SOURCE.CONSOLE_API,
type: MESSAGE_TYPE.LOG,
level: MESSAGE_LEVEL.LOG,
messageText: message.message,
timeStamp: message.timeStamp
});
}
function transformPageErrorPacket(packet) {
let { pageError } = packet;
let level = MESSAGE_LEVEL.ERROR;
if (pageError.warning || pageError.strict) {
level = MESSAGE_LEVEL.WARN;
} else if (pageError.info) {
level = MESSAGE_LEVEL.INFO;
}
const frame = pageError.sourceName ? {
source: pageError.sourceName,
line: pageError.lineNumber,
column: pageError.columnNumber
} : null;
let matchesCSS = /^(?:CSS|Layout)\b/.test(pageError.category);
let messageSource = matchesCSS ? MESSAGE_SOURCE.CSS
: MESSAGE_SOURCE.JAVASCRIPT;
return new ConsoleMessage({
source: messageSource,
type: MESSAGE_TYPE.LOG,
level,
messageText: pageError.errorMessage,
stacktrace: pageError.stacktrace ? pageError.stacktrace : null,
frame,
exceptionDocURL: pageError.exceptionDocURL,
timeStamp: pageError.timeStamp,
notes: pageError.notes,
});
}
function transformNetworkEventPacket(packet) {
let { networkEvent } = packet;
return new NetworkEventMessage({
actor: networkEvent.actor,
isXHR: networkEvent.isXHR,
request: networkEvent.request,
response: networkEvent.response,
timeStamp: networkEvent.timeStamp,
totalTime: networkEvent.totalTime,
});
}
function transformEvaluationResultPacket(packet) {
let {
exceptionMessage: messageText,
exceptionDocURL,
frame,
result,
helperResult,
timestamp: timeStamp,
notes,
} = packet;
let parameters = helperResult && helperResult.object
? helperResult.object
: result;
const level = messageText ? MESSAGE_LEVEL.ERROR : MESSAGE_LEVEL.LOG;
return new ConsoleMessage({
source: MESSAGE_SOURCE.JAVASCRIPT,
type: MESSAGE_TYPE.RESULT,
helperType: helperResult ? helperResult.type : null,
level,
messageText,
parameters,
exceptionDocURL,
frame,
timeStamp,
notes,
});
}
// Helpers
function getRepeatId(message) {
return JSON.stringify({