Bug 1419086 - Render the ObjectInspector in the console sidebar. r=nchevobbe
MozReview-Commit-ID: RpJf4N5X02
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -1235,9 +1235,13 @@ body #output-container {
.webconsole-sidebar-toolbar .sidebar-close-button {
padding: 4px 0;
margin: 0;
margin-inline-end: -3px;
}
.sidebar-close-button::before {
background-image: var(--close-button-image);
+}
+
+.sidebar-contents .object-inspector {
+ min-width: 100%;
}
\ No newline at end of file
--- a/devtools/client/webconsole/new-console-output/components/GripMessageBody.js
+++ b/devtools/client/webconsole/new-console-output/components/GripMessageBody.js
@@ -8,28 +8,25 @@
// If this is being run from Mocha, then the browser loader hasn't set up
// define. We need to do that before loading Rep.
if (typeof define === "undefined") {
require("amd-loader");
}
// React
-const { createFactory } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
-const ObjectClient = require("devtools/shared/client/object-client");
const {
MESSAGE_TYPE,
JSTERM_COMMANDS,
} = require("../constants");
+const { getObjectInspector } = require("devtools/client/webconsole/new-console-output/utils/object-inspector");
const reps = require("devtools/client/shared/components/reps/reps");
-const { REPS, MODE } = reps;
-const ObjectInspector = createFactory(reps.ObjectInspector);
-const { Grip } = REPS;
+const { MODE } = reps;
GripMessageBody.displayName = "GripMessageBody";
GripMessageBody.propTypes = {
grip: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.object,
@@ -60,74 +57,30 @@ function GripMessageBody(props) {
mode = MODE.LONG,
} = props;
let styleObject;
if (userProvidedStyle && userProvidedStyle !== "") {
styleObject = cleanupStyle(userProvidedStyle, serviceContainer.createElement);
}
- let onDOMNodeMouseOver;
- let onDOMNodeMouseOut;
- let onInspectIconClick;
- if (serviceContainer) {
- onDOMNodeMouseOver = serviceContainer.highlightDomElement
- ? (object) => serviceContainer.highlightDomElement(object)
- : null;
- onDOMNodeMouseOut = serviceContainer.unHighlightDomElement;
- onInspectIconClick = serviceContainer.openNodeInInspector
- ? (object, e) => {
- // Stop the event propagation so we don't trigger ObjectInspector expand/collapse.
- e.stopPropagation();
- serviceContainer.openNodeInInspector(object);
- }
- : null;
- }
-
- const objectInspectorProps = {
- // Auto-expand the ObjectInspector when the message is a console.dir one.
+ let objectInspectorProps = {
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.
- disabledFocus: true,
- roots: [{
- path: (grip && grip.actor) || JSON.stringify(grip),
- contents: {
- value: grip
- }
- }],
- createObjectClient: object =>
- new ObjectClient(serviceContainer.hudProxy.client, object),
- releaseActor: actor => {
- if (!actor || !serviceContainer.hudProxy.releaseActor) {
- return;
- }
- serviceContainer.hudProxy.releaseActor(actor);
- },
- onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger,
- openLink: serviceContainer.openLink,
};
if (typeof grip === "string" || (grip && grip.type === "longString")) {
Object.assign(objectInspectorProps, {
useQuotes,
escapeWhitespace,
style: styleObject
});
- } else {
- Object.assign(objectInspectorProps, {
- onDOMNodeMouseOver,
- onDOMNodeMouseOut,
- onInspectIconClick,
- defaultRep: Grip,
- });
}
- return ObjectInspector(objectInspectorProps);
+ return getObjectInspector(grip, serviceContainer, objectInspectorProps);
}
// Regular expression that matches the allowed CSS property names.
const allowedStylesRegex = new RegExp(
"^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|" +
"margin|padding|text|transition|outline|white-space|word|writing|" +
"(?:min-|max-)?width|(?:min-|max-)?height)"
);
--- a/devtools/client/webconsole/new-console-output/components/SideBar.js
+++ b/devtools/client/webconsole/new-console-output/components/SideBar.js
@@ -2,22 +2,27 @@
* 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { getObjectInspector } = require("devtools/client/webconsole/new-console-output/utils/object-inspector");
const actions = require("devtools/client/webconsole/new-console-output/actions/index");
const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
+const reps = require("devtools/client/shared/components/reps/reps");
+const { MODE } = reps;
+
class SideBar extends Component {
static get propTypes() {
return {
+ serviceContainer: PropTypes.object,
dispatch: PropTypes.func.isRequired,
sidebarVisible: PropTypes.bool,
grip: PropTypes.object,
};
}
constructor(props) {
super(props);
@@ -27,32 +32,38 @@ class SideBar extends Component {
onClickSidebarClose() {
this.props.dispatch(actions.sidebarClose());
}
render() {
let {
sidebarVisible,
grip,
+ serviceContainer,
} = this.props;
+ let objectInspector = getObjectInspector(grip, serviceContainer, {
+ autoExpandDepth: 1,
+ mode: MODE.SHORT,
+ });
+
let endPanel = dom.aside({
className: "sidebar-wrapper"
},
dom.header({
className: "devtools-toolbar webconsole-sidebar-toolbar"
},
dom.button({
className: "devtools-button sidebar-close-button",
onClick: this.onClickSidebarClose
})
),
dom.aside({
className: "sidebar-contents"
- }, JSON.stringify(grip, null, 2))
+ }, objectInspector)
);
return (
sidebarVisible ?
SplitBox({
className: "sidebar",
endPanel,
endPanelControl: true,
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -344,16 +344,17 @@ skip-if = true # Bug 1405636
[browser_webconsole_network_exceptions.js]
[browser_webconsole_network_messages_expand.js]
[browser_webconsole_network_messages_openinnet.js]
[browser_webconsole_network_requests_from_chrome.js]
[browser_webconsole_nodes_highlight.js]
[browser_webconsole_nodes_select.js]
[browser_webconsole_notifications.js]
skip-if = true # Bug 1405637
+[browser_webconsole_object_in_sidebar.js]
[browser_webconsole_object_inspector.js]
[browser_webconsole_object_inspector_entries.js]
[browser_webconsole_observer_notifications.js]
[browser_webconsole_optimized_out_vars.js]
[browser_webconsole_output_copy.js]
subsuite = clipboard
skip-if = true # Bug 1404364
# old console skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_object_in_sidebar.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_object_in_sidebar.js
@@ -25,32 +25,54 @@ add_task(async function () {
let string = findMessage(hud, "foo", ".objectBox");
let bool = findMessage(hud, "false", ".objectBox");
let nullMessage = findMessage(hud, "null", ".objectBox");
let undefinedMsg = findMessage(hud, "undefined", ".objectBox");
info("Showing sidebar for {a:1}");
await showSidebarWithContextMenu(hud, objectA, true);
+ let sidebarContents = hud.ui.document.querySelector(".sidebar-contents");
+ let objectInspector = sidebarContents.querySelector(".object-inspector");
+ let oiNodes = objectInspector.querySelectorAll(".node");
+ if (oiNodes.length === 1) {
+ // If this is the case, we wait for the properties to be fetched and displayed.
+ await waitForNodeMutation(objectInspector, {
+ childList: true
+ });
+ }
+
let sidebarText = hud.ui.document.querySelector(".sidebar-contents").textContent;
- ok(sidebarText.includes('"a":'), "Sidebar is shown for {a:1}");
+ ok(sidebarText.includes("a: 1"), "Sidebar is shown for {a:1}");
info("Showing sidebar for {a:1} again");
await showSidebarWithContextMenu(hud, objectA, false);
ok(hud.ui.document.querySelector(".sidebar"),
"Sidebar is still shown after clicking on same object");
is(hud.ui.document.querySelector(".sidebar-contents").textContent, sidebarText,
"Sidebar is not updated after clicking on same object");
info("Showing sidebar for {b:1}");
await showSidebarWithContextMenu(hud, objectB, false);
+
+ sidebarContents = hud.ui.document.querySelector(".sidebar-contents");
+ objectInspector = sidebarContents.querySelector(".object-inspector");
+ oiNodes = objectInspector.querySelectorAll(".node");
+ if (oiNodes.length === 1) {
+ // If this is the case, we wait for the properties to be fetched and displayed.
+ await waitForNodeMutation(objectInspector, {
+ childList: true
+ });
+ }
+
isnot(hud.ui.document.querySelector(".sidebar-contents").textContent, sidebarText,
"Sidebar is updated for {b:1}");
sidebarText = hud.ui.document.querySelector(".sidebar-contents").textContent;
- ok(sidebarText.includes('"b":'), "Sidebar contents shown for {b:1}");
+
+ ok(sidebarText.includes("b: 1"), "Sidebar contents shown for {b:1}");
info("Checking context menu entry is disabled for number");
let numberContextMenuEnabled = await isContextMenuEntryEnabled(hud, number);
ok(!numberContextMenuEnabled, "Context menu entry is disabled for number");
info("Checking context menu entry is disabled for string");
let stringContextMenuEnabled = await isContextMenuEntryEnabled(hud, string);
ok(!stringContextMenuEnabled, "Context menu entry is disabled for string");
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_object_in_sidebar.js
@@ -0,0 +1,57 @@
+/* -*- 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/ */
+
+// Test that the ObjectInspector is rendered correctly in the sidebar.
+
+"use strict";
+
+const TEST_URI =
+ "data:text/html;charset=utf8," +
+ "<script>console.log({a:1,b:2,c:3});</script>";
+
+add_task(async function () {
+ // Should be removed when sidebar work is complete
+ await pushPref("devtools.webconsole.sidebarToggle", true);
+
+ let hud = await openNewTabAndConsole(TEST_URI);
+
+ let message = findMessage(hud, "Object");
+ let object = message.querySelector(".object-inspector .objectBox-object");
+
+ await showSidebarWithContextMenu(hud, object, true);
+
+ let sidebarContents = hud.ui.document.querySelector(".sidebar-contents");
+ let objectInspectors = [...sidebarContents.querySelectorAll(".tree")];
+ is(objectInspectors.length, 1, "There is the expected number of object inspectors");
+ let [objectInspector] = objectInspectors;
+ let oiNodes = objectInspector.querySelectorAll(".node");
+ if (oiNodes.length === 1) {
+ // If this is the case, we wait for the properties to be fetched and displayed.
+ await waitForNodeMutation(objectInspector, {
+ childList: true
+ });
+ oiNodes = objectInspector.querySelectorAll(".node");
+ }
+
+ // There are 5 nodes: the root, a, b, c, and proto.
+ is(oiNodes.length, 5, "There is the expected number of nodes in the tree");
+ let propertiesNodes = [...objectInspector.querySelectorAll(".object-label")]
+ .map(el => el.textContent);
+ const arrayPropertiesNames = ["a", "b", "c", "__proto__"];
+ is(JSON.stringify(propertiesNodes), JSON.stringify(arrayPropertiesNames));
+});
+
+async function showSidebarWithContextMenu(hud, node, expectMutation) {
+ let wrapper = hud.ui.document.querySelector(".webconsole-output-wrapper");
+ let onSidebarShown = waitForNodeMutation(wrapper, { childList: true });
+
+ let contextMenu = await openContextMenu(hud, node);
+ let openInSidebar = contextMenu.querySelector("#console-menu-open-sidebar");
+ openInSidebar.click();
+ if (expectMutation) {
+ await onSidebarShown;
+ }
+ await hideContextMenu(hud);
+}
--- a/devtools/client/webconsole/new-console-output/test/store/ui.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/ui.test.js
@@ -1,18 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const expect = require("expect");
const actions = require("devtools/client/webconsole/new-console-output/actions/index");
-const { setupStore } = require("devtools/client/webconsole/new-console-output/test/helpers");
-const { getAllMessagesById } = require("devtools/client/webconsole/new-console-output/selectors/messages");
+const { setupStore, getFirstMessage, getLastMessage } = require("devtools/client/webconsole/new-console-output/test/helpers");
const { stubPackets, stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
describe("Testing UI", () => {
let store;
beforeEach(() => {
store = setupStore();
});
@@ -38,62 +37,58 @@ describe("Testing UI", () => {
});
describe("Show object in sidebar", () => {
it("sidebar is shown with correct object", () => {
const packet = stubPackets.get("inspect({a: 1})");
const message = stubPreparedMessages.get("inspect({a: 1})");
store.dispatch(actions.messageAdd(packet));
- const messages = getAllMessagesById(store.getState());
const actorId = message.parameters[0].actor;
- const messageId = messages.first().id;
+ const messageId = getFirstMessage(store.getState()).id;
store.dispatch(actions.showObjectInSidebar(actorId, messageId));
expect(store.getState().ui.sidebarVisible).toEqual(true);
expect(store.getState().ui.gripInSidebar).toEqual(message.parameters[0]);
});
it("sidebar is not updated for the same object", () => {
const packet = stubPackets.get("inspect({a: 1})");
const message = stubPreparedMessages.get("inspect({a: 1})");
store.dispatch(actions.messageAdd(packet));
- const messages = getAllMessagesById(store.getState());
const actorId = message.parameters[0].actor;
- const messageId = messages.first().id;
+ const messageId = getFirstMessage(store.getState()).id;
store.dispatch(actions.showObjectInSidebar(actorId, messageId));
expect(store.getState().ui.sidebarVisible).toEqual(true);
expect(store.getState().ui.gripInSidebar).toEqual(message.parameters[0]);
let state = store.getState().ui;
store.dispatch(actions.showObjectInSidebar(actorId, messageId));
expect(store.getState().ui).toEqual(state);
});
it("sidebar shown and updated for new object", () => {
const packet = stubPackets.get("inspect({a: 1})");
const message = stubPreparedMessages.get("inspect({a: 1})");
store.dispatch(actions.messageAdd(packet));
- const messages = getAllMessagesById(store.getState());
const actorId = message.parameters[0].actor;
- const messageId = messages.first().id;
+ const messageId = getFirstMessage(store.getState()).id;
store.dispatch(actions.showObjectInSidebar(actorId, messageId));
expect(store.getState().ui.sidebarVisible).toEqual(true);
expect(store.getState().ui.gripInSidebar).toEqual(message.parameters[0]);
const newPacket = stubPackets.get("new Date(0)");
const newMessage = stubPreparedMessages.get("new Date(0)");
store.dispatch(actions.messageAdd(newPacket));
- const newMessages = getAllMessagesById(store.getState());
const newActorId = newMessage.parameters[0].actor;
- const newMessageId = newMessages.last().id;
+ const newMessageId = getLastMessage(store.getState()).id;
store.dispatch(actions.showObjectInSidebar(newActorId, newMessageId));
expect(store.getState().ui.sidebarVisible).toEqual(true);
expect(store.getState().ui.gripInSidebar).toEqual(newMessage.parameters[0]);
});
});
});
--- a/devtools/client/webconsole/new-console-output/utils/moz.build
+++ b/devtools/client/webconsole/new-console-output/utils/moz.build
@@ -2,9 +2,10 @@
# 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/.
DevToolsModules(
'context-menu.js',
'id-generator.js',
'messages.js',
+ 'object-inspector.js',
)
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/utils/object-inspector.js
@@ -0,0 +1,82 @@
+/* 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 { createFactory } = require("devtools/client/shared/vendor/react");
+const ObjectClient = require("devtools/shared/client/object-client");
+
+const reps = require("devtools/client/shared/components/reps/reps");
+const { REPS, MODE } = reps;
+const ObjectInspector = createFactory(reps.ObjectInspector);
+const { Grip } = REPS;
+
+/**
+ * Create and return an ObjectInspector for the given grip.
+ *
+ * @param {Object} grip
+ * The object grip to create an ObjectInspector for.
+ * @param {Object} serviceContainer
+ * Object containing various utility functions
+ * @param {Object} override
+ * Object containing props that should override the default props passed to
+ * ObjectInspector.
+ * @returns {ObjectInspector}
+ * An ObjectInspector for the given grip.
+ */
+function getObjectInspector(grip, serviceContainer, override) {
+ let onDOMNodeMouseOver;
+ let onDOMNodeMouseOut;
+ let onInspectIconClick;
+ if (serviceContainer) {
+ onDOMNodeMouseOver = serviceContainer.highlightDomElement
+ ? (object) => serviceContainer.highlightDomElement(object)
+ : null;
+ onDOMNodeMouseOut = serviceContainer.unHighlightDomElement;
+ onInspectIconClick = serviceContainer.openNodeInInspector
+ ? (object, e) => {
+ // Stop the event propagation so we don't trigger ObjectInspector expand/collapse.
+ e.stopPropagation();
+ serviceContainer.openNodeInInspector(object);
+ }
+ : null;
+ }
+
+ const objectInspectorProps = {
+ autoExpandDepth: 0,
+ mode: MODE.LONG,
+ // 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.
+ disabledFocus: true,
+ roots: [{
+ path: (grip && grip.actor) || JSON.stringify(grip),
+ contents: {
+ value: grip
+ }
+ }],
+ createObjectClient: object =>
+ new ObjectClient(serviceContainer.hudProxy.client, object),
+ releaseActor: actor => {
+ if (!actor || !serviceContainer.hudProxy.releaseActor) {
+ return;
+ }
+ serviceContainer.hudProxy.releaseActor(actor);
+ },
+ onViewSourceInDebugger: serviceContainer.onViewSourceInDebugger,
+ openLink: serviceContainer.openLink,
+ };
+
+ if (!(typeof grip === "string" || (grip && grip.type === "longString"))) {
+ Object.assign(objectInspectorProps, {
+ onDOMNodeMouseOver,
+ onDOMNodeMouseOut,
+ onInspectIconClick,
+ defaultRep: Grip,
+ });
+ }
+
+ return ObjectInspector({...objectInspectorProps, ...override});
+}
+
+exports.getObjectInspector = getObjectInspector;