--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -246,16 +246,22 @@ webconsole.menu.copyObject.label=Copy ob
webconsole.menu.copyObject.accesskey=o
# LOCALIZATION NOTE (webconsole.menu.selectAll.label)
# Label used for a context-menu item that will select all the content of the webconsole
# output.
webconsole.menu.selectAll.label=Select all
webconsole.menu.selectAll.accesskey=A
+# LOCALIZATION NOTE (webconsole.menu.openInSidebar.label)
+# Label used for a context-menu item displayed for object/variable logs. Clicking on it
+# opens the webconsole sidebar for the logged variable.
+webconsole.menu.openInSidebar.label=Open in sidebar
+webconsole.menu.openInSidebar.accesskey=V
+
# LOCALIZATION NOTE (webconsole.clearButton.tooltip)
# Label used for the tooltip on the clear logs button in the console top toolbar bar.
# Clicking on it will clear the content of the console.
webconsole.clearButton.tooltip=Clear the Web Console output
# LOCALIZATION NOTE (webconsole.toggleFilterButton.tooltip)
# Label used for the tooltip on the toggle filter bar button in the console top
# toolbar bar. Clicking on it will toggle the visibility of an additional bar which
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -1212,27 +1212,29 @@ body #output-container {
/* Set to prevent the sidebar from extending past the right edge of the page */
width: unset;
}
.sidebar-wrapper {
display: grid;
grid-template-rows: auto 1fr;
width: 100%;
+ overflow: hidden;
}
.webconsole-sidebar-toolbar {
grid-row: 1 / 2;
min-height: var(--primary-toolbar-height);
display: flex;
justify-content: end;
}
.sidebar-contents {
grid-row: 2 / 3;
+ overflow: scroll;
}
.webconsole-sidebar-toolbar .sidebar-close-button {
padding: 4px 0;
margin: 0;
margin-inline-end: -3px;
}
--- a/devtools/client/webconsole/new-console-output/actions/ui.js
+++ b/devtools/client/webconsole/new-console-output/actions/ui.js
@@ -2,25 +2,27 @@
/* 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 { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const { getMessage } = require("devtools/client/webconsole/new-console-output/selectors/messages");
const Services = require("Services");
const {
FILTER_BAR_TOGGLE,
INITIALIZE,
PERSIST_TOGGLE,
PREFS,
SELECT_NETWORK_MESSAGE_TAB,
- SIDEBAR_TOGGLE,
+ SIDEBAR_CLOSE,
+ SHOW_OBJECT_IN_SIDEBAR,
TIMESTAMPS_TOGGLE,
} = require("devtools/client/webconsole/new-console-output/constants");
function filterBarToggle(show) {
return (dispatch, getState) => {
dispatch({
type: FILTER_BAR_TOGGLE,
});
@@ -54,22 +56,40 @@ function selectNetworkMessageTab(id) {
}
function initialize() {
return {
type: INITIALIZE
};
}
-function sidebarToggle(show) {
+function sidebarClose(show) {
return {
- type: SIDEBAR_TOGGLE,
+ type: SIDEBAR_CLOSE,
+ };
+}
+
+function showObjectInSidebar(actorId, messageId) {
+ return (dispatch, getState) => {
+ let { parameters } = getMessage(getState(), messageId);
+ if (Array.isArray(parameters)) {
+ for (let parameter of parameters) {
+ if (parameter.actor === actorId) {
+ dispatch({
+ type: SHOW_OBJECT_IN_SIDEBAR,
+ grip: parameter
+ });
+ return;
+ }
+ }
+ }
};
}
module.exports = {
filterBarToggle,
initialize,
persistToggle,
selectNetworkMessageTab,
- sidebarToggle,
+ sidebarClose,
+ showObjectInSidebar,
timestampsToggle,
};
--- a/devtools/client/webconsole/new-console-output/components/FilterBar.js
+++ b/devtools/client/webconsole/new-console-output/components/FilterBar.js
@@ -27,25 +27,23 @@ class FilterBar extends Component {
dispatch: PropTypes.func.isRequired,
filter: PropTypes.object.isRequired,
serviceContainer: PropTypes.shape({
attachRefToHud: PropTypes.func.isRequired,
}).isRequired,
filterBarVisible: PropTypes.bool.isRequired,
persistLogs: PropTypes.bool.isRequired,
filteredMessagesCount: PropTypes.object.isRequired,
- sidebarToggle: PropTypes.bool,
};
}
constructor(props) {
super(props);
this.onClickMessagesClear = this.onClickMessagesClear.bind(this);
this.onClickFilterBarToggle = this.onClickFilterBarToggle.bind(this);
- this.onClickSidebarToggle = this.onClickSidebarToggle.bind(this);
this.onClickRemoveAllFilters = this.onClickRemoveAllFilters.bind(this);
this.onClickRemoveTextFilter = this.onClickRemoveTextFilter.bind(this);
this.onSearchInput = this.onSearchInput.bind(this);
this.onChangePersistToggle = this.onChangePersistToggle.bind(this);
this.renderFiltersConfigBar = this.renderFiltersConfigBar.bind(this);
this.renderFilteredMessagesBar = this.renderFilteredMessagesBar.bind(this);
}
@@ -82,20 +80,16 @@ class FilterBar extends Component {
onClickMessagesClear() {
this.props.dispatch(actions.messagesClear());
}
onClickFilterBarToggle() {
this.props.dispatch(actions.filterBarToggle());
}
- onClickSidebarToggle() {
- this.props.dispatch(actions.sidebarToggle());
- }
-
onClickRemoveAllFilters() {
this.props.dispatch(actions.defaultFiltersReset());
}
onClickRemoveTextFilter() {
this.props.dispatch(actions.filterTextSet(""));
}
@@ -221,17 +215,16 @@ class FilterBar extends Component {
}
render() {
const {
filter,
filterBarVisible,
persistLogs,
filteredMessagesCount,
- sidebarToggle,
} = this.props;
let children = [
dom.div({
className: "devtools-toolbar webconsole-filterbar-primary",
key: "primary-bar",
},
dom.button({
@@ -256,23 +249,16 @@ class FilterBar extends Component {
onInput: this.onSearchInput
}),
FilterCheckbox({
label: l10n.getStr("webconsole.enablePersistentLogs.label"),
title: l10n.getStr("webconsole.enablePersistentLogs.tooltip"),
onChange: this.onChangePersistToggle,
checked: persistLogs,
}),
- sidebarToggle ?
- dom.button({
- className: "devtools-button webconsole-sidebar-button",
- title: l10n.getStr("webconsole.toggleFilterButton.tooltip"),
- onClick: this.onClickSidebarToggle
- }, "Toggle Sidebar")
- : null,
)
];
if (filteredMessagesCount.global > 0) {
children.push(this.renderFilteredMessagesBar());
}
if (filterBarVisible) {
@@ -293,13 +279,12 @@ class FilterBar extends Component {
function mapStateToProps(state) {
let uiState = getAllUi(state);
return {
filter: getAllFilters(state),
filterBarVisible: uiState.filterBarVisible,
persistLogs: uiState.persistLogs,
filteredMessagesCount: getFilteredMessagesCount(state),
- sidebarToggle: state.prefs.sidebarToggle,
};
}
module.exports = connect(mapStateToProps)(FilterBar);
--- a/devtools/client/webconsole/new-console-output/components/SideBar.js
+++ b/devtools/client/webconsole/new-console-output/components/SideBar.js
@@ -9,48 +9,50 @@ const PropTypes = require("devtools/clie
const { connect } = require("devtools/client/shared/vendor/react-redux");
const actions = require("devtools/client/webconsole/new-console-output/actions/index");
const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
class SideBar extends Component {
static get propTypes() {
return {
dispatch: PropTypes.func.isRequired,
- sidebarVisible: PropTypes.bool
+ sidebarVisible: PropTypes.bool,
+ grip: PropTypes.object,
};
}
constructor(props) {
super(props);
- this.onClickSidebarToggle = this.onClickSidebarToggle.bind(this);
+ this.onClickSidebarClose = this.onClickSidebarClose.bind(this);
}
- onClickSidebarToggle() {
- this.props.dispatch(actions.sidebarToggle());
+ onClickSidebarClose() {
+ this.props.dispatch(actions.sidebarClose());
}
render() {
let {
sidebarVisible,
+ grip,
} = this.props;
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.onClickSidebarToggle
+ onClick: this.onClickSidebarClose
})
),
dom.aside({
className: "sidebar-contents"
- }, "Sidebar WIP")
+ }, JSON.stringify(grip, null, 2))
);
return (
sidebarVisible ?
SplitBox({
className: "sidebar",
endPanel,
endPanelControl: true,
@@ -61,12 +63,13 @@ class SideBar extends Component {
: null
);
}
}
function mapStateToProps(state, props) {
return {
sidebarVisible: state.ui.sidebarVisible,
+ grip: state.ui.gripInSidebar,
};
}
module.exports = connect(mapStateToProps)(SideBar);
--- a/devtools/client/webconsole/new-console-output/components/message-types/ConsoleApiCall.js
+++ b/devtools/client/webconsole/new-console-output/components/message-types/ConsoleApiCall.js
@@ -112,16 +112,17 @@ function ConsoleApiCall(props) {
frame,
stacktrace,
attachment,
serviceContainer,
dispatch,
indent,
timeStamp,
timestampsVisible,
+ parameters,
});
}
function formatReps(options = {}) {
const {
dispatch,
loadedObjectProperties,
loadedObjectEntries,
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -19,17 +19,18 @@ const actionTypes = {
MESSAGE_TABLE_RECEIVE: "MESSAGE_TABLE_RECEIVE",
MESSAGES_ADD: "MESSAGES_ADD",
MESSAGES_CLEAR: "MESSAGES_CLEAR",
NETWORK_MESSAGE_UPDATE: "NETWORK_MESSAGE_UPDATE",
NETWORK_UPDATE_REQUEST: "NETWORK_UPDATE_REQUEST",
PERSIST_TOGGLE: "PERSIST_TOGGLE",
REMOVED_ACTORS_CLEAR: "REMOVED_ACTORS_CLEAR",
SELECT_NETWORK_MESSAGE_TAB: "SELECT_NETWORK_MESSAGE_TAB",
- SIDEBAR_TOGGLE: "SIDEBAR_TOGGLE",
+ SIDEBAR_CLOSE: "SIDEBAR_CLOSE",
+ SHOW_OBJECT_IN_SIDEBAR: "SHOW_OBJECT_IN_SIDEBAR",
TIMESTAMPS_TOGGLE: "TIMESTAMPS_TOGGLE",
};
const prefs = {
PREFS: {
FILTER: {
ERROR: "devtools.webconsole.filter.error",
WARN: "devtools.webconsole.filter.warn",
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -114,21 +114,33 @@ NewConsoleOutputWrapper.prototype = {
let messageVariable = target.closest(".objectBox");
// Ensure that console.group and console.groupCollapsed commands are not captured
let variableText = (messageVariable
&& !(messageEl.classList.contains("startGroup"))
&& !(messageEl.classList.contains("startGroupCollapsed")))
? messageVariable.textContent : null;
// Retrieve closes actor id from the DOM.
- let actorEl = target.closest("[data-link-actor-id]");
+ let actorEl = target.closest("[data-link-actor-id]") ||
+ target.querySelector("[data-link-actor-id]");
let actor = actorEl ? actorEl.dataset.linkActorId : null;
+ let rootObjectInspector = target.closest(".object-inspector");
+ let rootActor = rootObjectInspector ?
+ rootObjectInspector.querySelector("[data-link-actor-id]") : null;
+ let rootActorId = rootActor ? rootActor.dataset.linkActorId : null;
+
+ let sidebarTogglePref = store.getState().prefs.sidebarToggle;
+ let openSidebar = sidebarTogglePref ? (messageId) => {
+ store.dispatch(actions.showObjectInSidebar(rootActorId, messageId));
+ } : null;
+
let menu = createContextMenu(this.jsterm, this.parentNode,
- { actor, clipboardText, variableText, message, serviceContainer });
+ { actor, clipboardText, variableText, message,
+ serviceContainer, openSidebar, rootActorId });
// Emit the "menu-open" event for testing.
menu.once("open", () => this.emit("menu-open"));
menu.popup(screenX, screenY, this.toolbox);
return menu;
};
--- a/devtools/client/webconsole/new-console-output/reducers/ui.js
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.js
@@ -5,50 +5,60 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const {
FILTER_BAR_TOGGLE,
INITIALIZE,
PERSIST_TOGGLE,
SELECT_NETWORK_MESSAGE_TAB,
- SIDEBAR_TOGGLE,
+ SIDEBAR_CLOSE,
+ SHOW_OBJECT_IN_SIDEBAR,
TIMESTAMPS_TOGGLE,
MESSAGES_CLEAR,
} = require("devtools/client/webconsole/new-console-output/constants");
const {
PANELS,
} = require("devtools/client/netmonitor/src/constants");
const UiState = (overrides) => Object.freeze(Object.assign({
filterBarVisible: false,
initialized: false,
networkMessageActiveTabId: PANELS.HEADERS,
persistLogs: false,
sidebarVisible: false,
timestampsVisible: true,
+ gripInSidebar: null
}, overrides));
function ui(state = UiState(), action) {
switch (action.type) {
case FILTER_BAR_TOGGLE:
return Object.assign({}, state, {filterBarVisible: !state.filterBarVisible});
case PERSIST_TOGGLE:
return Object.assign({}, state, {persistLogs: !state.persistLogs});
case TIMESTAMPS_TOGGLE:
return Object.assign({}, state, {timestampsVisible: action.visible});
case SELECT_NETWORK_MESSAGE_TAB:
return Object.assign({}, state, {networkMessageActiveTabId: action.id});
- case SIDEBAR_TOGGLE:
- return Object.assign({}, state, {sidebarVisible: !state.sidebarVisible});
+ case SIDEBAR_CLOSE:
+ return Object.assign({}, state, {
+ sidebarVisible: !state.sidebarVisible,
+ gripInSidebar: null
+ });
case INITIALIZE:
return Object.assign({}, state, {initialized: true});
case MESSAGES_CLEAR:
- return Object.assign({}, state, {sidebarVisible: false});
+ return Object.assign({}, state, {sidebarVisible: false, gripInSidebar: null});
+ case SHOW_OBJECT_IN_SIDEBAR:
+ if (action.grip === state.gripInSidebar) {
+ return state;
+ }
+ return Object.assign({}, state, {sidebarVisible: true, gripInSidebar: action.grip});
}
return state;
}
module.exports = {
UiState,
ui,
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser.ini
@@ -255,16 +255,17 @@ skip-if = true # Bug 1405252
[browser_webconsole_context_menu_copy_entire_message.js]
subsuite = clipboard
skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
[browser_webconsole_context_menu_copy_link_location.js]
subsuite = clipboard
skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
[browser_webconsole_context_menu_copy_object.js]
subsuite = clipboard
+[browser_webconsole_context_menu_object_in_sidebar.js]
[browser_webconsole_context_menu_open_url.js]
[browser_webconsole_context_menu_store_as_global.js]
[browser_webconsole_csp_ignore_reflected_xss_message.js]
skip-if = (e10s && debug) || (e10s && os == 'win') # Bug 1221499 enabled these on windows
[browser_webconsole_cspro.js]
skip-if = true # Bug 1408932
# old console skip-if = e10s && (os == 'win' || os == 'mac') # Bug 1243967
[browser_webconsole_document_focus.js]
--- a/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_close_sidebar.js
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_close_sidebar.js
@@ -58,14 +58,24 @@ add_task(async function () {
let onSidebarShown = waitForNodeMutation(wrapper, { childList: true });
closeButton.click();
await onSidebarShown;
sidebar = hud.ui.document.querySelector(".sidebar");
ok(!sidebar, "Sidebar hidden after clicking on close button");
});
async function showSidebar(hud) {
- let toggleButton = hud.ui.document.querySelector(".webconsole-sidebar-button");
+ let onMessage = waitForMessage(hud, "Object");
+ ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
+ content.wrappedJSObject.console.log({a: 1});
+ });
+ await onMessage;
+
+ let objectNode = hud.ui.outputNode.querySelector(".object-inspector .objectBox");
let wrapper = hud.ui.document.querySelector(".webconsole-output-wrapper");
let onSidebarShown = waitForNodeMutation(wrapper, { childList: true });
- toggleButton.click();
+
+ let contextMenu = await openContextMenu(hud, objectNode);
+ let openInSidebar = contextMenu.querySelector("#console-menu-open-sidebar");
+ openInSidebar.click();
await onSidebarShown;
+ await hideContextMenu(hud);
}
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/mochitest/browser_webconsole_context_menu_object_in_sidebar.js
@@ -0,0 +1,90 @@
+/* -*- 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 "Open in sidebar" context menu entry is active for
+// the correct objects and opens the sidebar when clicked.
+
+"use strict";
+
+const TEST_URI =
+ "data:text/html;charset=utf8," +
+ "<script>console.log({a:1},100,{b:1},'foo',false,null,undefined);</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, "foo");
+ let [objectA, objectB] =
+ message.querySelectorAll(".object-inspector .objectBox-object");
+ let number = findMessage(hud, "100", ".objectBox");
+ 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 sidebarText = hud.ui.document.querySelector(".sidebar-contents").textContent;
+ ok(sidebarText.includes('"a":'), "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);
+ 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}");
+
+ 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");
+
+ info("Checking context menu entry is disabled for bool");
+ let boolContextMenuEnabled = await isContextMenuEntryEnabled(hud, bool);
+ ok(!boolContextMenuEnabled, "Context menu entry is disabled for bool");
+
+ info("Checking context menu entry is disabled for null message");
+ let nullContextMenuEnabled = await isContextMenuEntryEnabled(hud, nullMessage);
+ ok(!nullContextMenuEnabled, "Context menu entry is disabled for nullMessage");
+
+ info("Checking context menu entry is disabled for undefined message");
+ let undefinedContextMenuEnabled = await isContextMenuEntryEnabled(hud, undefinedMsg);
+ ok(!undefinedContextMenuEnabled, "Context menu entry is disabled for undefinedMsg");
+});
+
+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);
+}
+
+async function isContextMenuEntryEnabled(hud, node) {
+ let contextMenu = await openContextMenu(hud, node);
+ let openInSidebar = contextMenu.querySelector("#console-menu-open-sidebar");
+ let enabled = !openInSidebar.attributes.disabled;
+ await hideContextMenu(hud);
+ return enabled;
+}
--- a/devtools/client/webconsole/new-console-output/test/store/ui.test.js
+++ b/devtools/client/webconsole/new-console-output/test/store/ui.test.js
@@ -2,36 +2,98 @@
* 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 { stubPackets, stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
describe("Testing UI", () => {
let store;
beforeEach(() => {
store = setupStore();
});
describe("Toggle sidebar", () => {
it("sidebar is toggled on and off", () => {
- store.dispatch(actions.sidebarToggle());
+ store.dispatch(actions.sidebarClose());
expect(store.getState().ui.sidebarVisible).toEqual(true);
- store.dispatch(actions.sidebarToggle());
+ store.dispatch(actions.sidebarClose());
expect(store.getState().ui.sidebarVisible).toEqual(false);
});
});
describe("Hide sidebar on clear", () => {
it("sidebar is hidden on clear", () => {
- store.dispatch(actions.sidebarToggle());
+ store.dispatch(actions.sidebarClose());
expect(store.getState().ui.sidebarVisible).toEqual(true);
store.dispatch(actions.messagesClear());
expect(store.getState().ui.sidebarVisible).toEqual(false);
store.dispatch(actions.messagesClear());
expect(store.getState().ui.sidebarVisible).toEqual(false);
});
});
+
+ 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;
+ 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;
+ 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;
+ 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;
+ 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/context-menu.js
+++ b/devtools/client/webconsole/new-console-output/utils/context-menu.js
@@ -27,23 +27,28 @@ const { l10n } = require("devtools/clien
* @param {Object} options
* - {String} actor (optional) actor id to use for context menu actions
* - {String} clipboardText (optional) text to "Copy" if no selection is available
* - {String} variableText (optional) which is the textual frontend
* representation of the variable
* - {Object} message (optional) message object containing metadata such as:
* - {String} source
* - {String} request
+ * - {Function} openSidebar (optional) function that will open the object
+ * inspector sidebar
+ * - {String} rootActorId (optional) actor id for the root object being clicked on
*/
function createContextMenu(jsterm, parentNode, {
actor,
clipboardText,
variableText,
message,
- serviceContainer
+ serviceContainer,
+ openSidebar,
+ rootActorId,
}) {
let win = parentNode.ownerDocument.defaultView;
let selection = win.getSelection();
let { source, request } = message || {};
let menu = new Menu({
id: "webconsole-menu"
@@ -160,12 +165,23 @@ function createContextMenu(jsterm, paren
accesskey: l10n.getStr("webconsole.menu.selectAll.accesskey"),
disabled: false,
click: () => {
let webconsoleOutput = parentNode.querySelector(".webconsole-output");
selection.selectAllChildren(webconsoleOutput);
},
}));
+ // Open object in sidebar.
+ if (openSidebar) {
+ menu.append(new MenuItem({
+ id: "console-menu-open-sidebar",
+ label: l10n.getStr("webconsole.menu.openInSidebar.label"),
+ acesskey: l10n.getStr("webconsole.menu.openInSidebar.accesskey"),
+ disabled: !rootActorId,
+ click: () => openSidebar(message.messageId),
+ }));
+ }
+
return menu;
}
exports.createContextMenu = createContextMenu;