author | Julian Descottes <jdescottes@mozilla.com> |
Wed, 01 Mar 2017 17:32:55 +0100 | |
changeset 392057 | c865b539abeab0daaa42083088adeaf35f8458e2 |
parent 392056 | 17a5205504383bc73553fe208749dc4b943c71d6 |
child 392058 | 10479537baf6bdc5d7168c586a17067b757b1023 |
push id | 7198 |
push user | jlorenzo@mozilla.com |
push date | Tue, 18 Apr 2017 12:07:49 +0000 |
treeherder | mozilla-beta@d57aa49c3948 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | gregtatum |
bugs | 1335608 |
milestone | 54.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/framework/components/moz.build +++ b/devtools/client/framework/components/moz.build @@ -3,10 +3,11 @@ # 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( 'toolbox-controller.js', 'toolbox-tab.js', + 'toolbox-tabs.js', 'toolbox-toolbar.js', )
new file mode 100644 --- /dev/null +++ b/devtools/client/framework/components/toolbox-tabs.js @@ -0,0 +1,154 @@ +/* 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 {DOM, createClass, createFactory, PropTypes} = require("devtools/client/shared/vendor/react"); + +const {findDOMNode} = require("devtools/client/shared/vendor/react-dom"); +const {button, div} = DOM; + +const Menu = require("devtools/client/framework/menu"); +const MenuItem = require("devtools/client/framework/menu-item"); +const ToolboxTab = createFactory(require("devtools/client/framework/components/toolbox-tab")); + +module.exports = createClass({ + displayName: "ToolboxTabs", + + // See toolbox-toolbar propTypes for details on the props used here. + propTypes: { + currentToolId: PropTypes.string, + focusButton: PropTypes.func, + focusedButton: PropTypes.string, + highlightedTool: PropTypes.string, + panelDefinitions: PropTypes.array, + selectTool: PropTypes.func, + toolbox: PropTypes.object, + L10N: PropTypes.object, + }, + + getInitialState() { + return { + overflow: false, + }; + }, + + componentDidUpdate() { + this.addFlowEvents(); + }, + + componentWillUnmount() { + this.removeFlowEvents(); + }, + + addFlowEvents() { + this.removeFlowEvents(); + let node = findDOMNode(this); + if (node) { + node.addEventListener("overflow", this.onOverflow); + node.addEventListener("underflow", this.onUnderflow); + } + }, + + removeFlowEvents() { + let node = findDOMNode(this); + if (node) { + node.removeEventListener("overflow", this.onOverflow); + node.removeEventListener("underflow", this.onUnderflow); + } + }, + + onOverflow() { + this.setState({ + overflow: true + }); + }, + + onUnderflow() { + this.setState({ + overflow: false + }); + }, + + /** + * Render all of the tabs, based on the panel definitions and builds out + * a toolbox tab for each of them. Will render an all-tabs button if the + * container has an overflow. + */ + render() { + let { + currentToolId, + focusButton, + focusedButton, + highlightedTool, + panelDefinitions, + selectTool, + } = this.props; + + let tabs = panelDefinitions.map(panelDefinition => ToolboxTab({ + currentToolId, + focusButton, + focusedButton, + highlightedTool, + panelDefinition, + selectTool, + })); + + // A wrapper is needed to get flex sizing correct in XUL. + return div( + { + className: "toolbox-tabs-wrapper" + }, + div( + { + className: "toolbox-tabs" + }, + tabs + ), + this.state.overflow ? renderAllToolsButton(this.props) : null + ); + }, +}); + +/** + * Render a button to access all tools, displayed only when the toolbar presents an + * overflow. + */ +function renderAllToolsButton(props) { + let { + currentToolId, + panelDefinitions, + selectTool, + toolbox, + L10N, + } = props; + + return button({ + className: "all-tools-menu all-tabs-menu", + tabIndex: -1, + title: L10N.getStr("toolbox.allToolsButton.tooltip"), + onClick: ({ target }) => { + let menu = new Menu({ + id: "all-tools-menupopup" + }); + panelDefinitions.forEach(({id, label}) => { + menu.append(new MenuItem({ + checked: currentToolId === id, + click: () => { + selectTool(id); + }, + id: "all-tools-menupopup-" + id, + label, + })); + }); + + let rect = target.getBoundingClientRect(); + let screenX = target.ownerDocument.defaultView.mozInnerScreenX; + let screenY = target.ownerDocument.defaultView.mozInnerScreenY; + + // Display the popup below the button. + menu.popup(rect.left + screenX, rect.bottom + screenY, toolbox); + return menu; + }, + }); +}
--- a/devtools/client/framework/components/toolbox-toolbar.js +++ b/devtools/client/framework/components/toolbox-toolbar.js @@ -1,16 +1,18 @@ /* 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 {DOM, createClass, createFactory, PropTypes} = require("devtools/client/shared/vendor/react"); const {div, button} = DOM; + const ToolboxTab = createFactory(require("devtools/client/framework/components/toolbox-tab")); +const ToolboxTabs = createFactory(require("devtools/client/framework/components/toolbox-tabs")); /** * This is the overall component for the toolbox toolbar. It is designed to not know how * the state is being managed, and attempts to be as pure as possible. The * ToolboxController component controls the changing state, and passes in everything as * props. */ module.exports = createClass({ @@ -34,69 +36,43 @@ module.exports = createClass({ focusButton: PropTypes.func, // The options button definition. optionsPanel: PropTypes.object, // Hold off displaying the toolbar until enough information is ready for it to render // nicely. canRender: PropTypes.bool, // Localization interface. L10N: PropTypes.object, + // The devtools toolbox + toolbox: PropTypes.object, }, /** * The render function is kept fairly short for maintainability. See the individual * render functions for how each of the sections is rendered. */ render() { const containerProps = {className: "devtools-tabbar"}; return this.props.canRender ? ( div( containerProps, renderToolboxButtonsStart(this.props), - renderTabs(this.props), + ToolboxTabs(this.props), renderToolboxButtonsEnd(this.props), renderOptions(this.props), renderSeparator(), renderDockButtons(this.props) ) ) : div(containerProps); } }); /** - * Render all of the tabs, this takes in the panel definitions and builds out - * the buttons for each of them. - * - * @param {Array} panelDefinitions - Array of objects that define panels. - * @param {String} currentToolId - The currently selected tool's id; e.g. "inspector". - * @param {String} highlightedTool - If a tool is highlighted, this is it's id. - * @param {Function} selectTool - Function to select a tool in the toolbox. - * @param {String} focusedButton - The id of the focused button. - * @param {Function} focusButton - Keep a record of the currently focused button. - */ -function renderTabs({panelDefinitions, currentToolId, highlightedTool, selectTool, - focusedButton, focusButton}) { - // A wrapper is needed to get flex sizing correct in XUL. - return div({className: "toolbox-tabs-wrapper"}, - div({className: "toolbox-tabs"}, - ...panelDefinitions.map(panelDefinition => ToolboxTab({ - panelDefinition, - currentToolId, - highlightedTool, - selectTool, - focusedButton, - focusButton, - })) - ) - ); -} - -/** * A little helper function to call renderToolboxButtons for buttons at the start * of the toolbox. */ function renderToolboxButtonsStart(props) { return renderToolboxButtons(props, true); } /** @@ -145,17 +121,17 @@ function renderToolboxButtons({toolboxBu onFocus: () => focusButton(id), tabIndex: id === focusedButton ? "0" : "-1" }); }) ); } /** - * The options button is a ToolboxTab just like in the renderTabs() function. However + * The options button is a ToolboxTab just like in the ToolboxTabs component. However * it is separate from the normal tabs, so deal with it separately here. * * @param {Object} optionsPanel - A single panel definition for the options panel. * @param {String} currentToolId - The currently selected tool's id; e.g. "inspector". * @param {Function} selectTool - Function to select a tool in the toolbox. * @param {String} focusedButton - The id of the focused button. * @param {Function} focusButton - Keep a record of the currently focused button. */
--- a/devtools/client/framework/test/browser.ini +++ b/devtools/client/framework/test/browser.ini @@ -75,16 +75,17 @@ skip-if = e10s # Bug 1069044 - destroyIn [browser_toolbox_target.js] [browser_toolbox_tabsswitch_shortcuts.js] [browser_toolbox_textbox_context_menu.js] [browser_toolbox_theme.js] [browser_toolbox_theme_registration.js] [browser_toolbox_toggle.js] [browser_toolbox_tool_ready.js] [browser_toolbox_tool_remote_reopen.js] +[browser_toolbox_toolbar_overflow.js] [browser_toolbox_tools_per_toolbox_registration.js] [browser_toolbox_transport_events.js] [browser_toolbox_view_source_01.js] [browser_toolbox_view_source_02.js] [browser_toolbox_view_source_03.js] [browser_toolbox_view_source_04.js] [browser_toolbox_window_reload_target.js] [browser_toolbox_window_shortcuts.js]
new file mode 100644 --- /dev/null +++ b/devtools/client/framework/test/browser_toolbox_toolbar_overflow.js @@ -0,0 +1,87 @@ +/* -*- 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/ */ + +/* import-globals-from shared-head.js */ +"use strict"; + +// Test that a button to access tools hidden by toolbar overflow is displayed when the +// toolbar starts to present an overflow. +let { Toolbox } = require("devtools/client/framework/toolbox"); + +add_task(function* () { + let tab = yield addTab("about:blank"); + + info("Open devtools on the Inspector in a separate window"); + let toolbox = yield openToolboxForTab(tab, "inspector", Toolbox.HostType.WINDOW); + + let hostWindow = toolbox.win.parent; + let originalWidth = hostWindow.outerWidth; + let originalHeight = hostWindow.outerHeight; + + info("Resize devtools window to a width that should not trigger any overflow"); + let onResize = once(hostWindow, "resize"); + hostWindow.resizeTo(640, 300); + yield onResize; + + let allToolsButton = toolbox.doc.querySelector(".all-tools-menu"); + ok(!allToolsButton, "The all tools button is not displayed"); + + info("Resize devtools window to a width that should trigger an overflow"); + onResize = once(hostWindow, "resize"); + hostWindow.resizeTo(300, 300); + yield onResize; + + info("Wait until the all tools button is available"); + yield waitUntil(() => toolbox.doc.querySelector(".all-tools-menu")); + + allToolsButton = toolbox.doc.querySelector(".all-tools-menu"); + ok(allToolsButton, "The all tools button is displayed"); + + info("Open the all-tools-menupopup and verify that the inspector button is checked"); + let menuPopup = yield openAllToolsMenu(toolbox); + + let inspectorButton = toolbox.doc.querySelector("#all-tools-menupopup-inspector"); + ok(inspectorButton, "The inspector button is available"); + ok(inspectorButton.getAttribute("checked"), "The inspector button is checked"); + + let consoleButton = toolbox.doc.querySelector("#all-tools-menupopup-webconsole"); + ok(consoleButton, "The console button is available"); + ok(!consoleButton.getAttribute("checked"), "The console button is not checked"); + + info("Switch to the webconsole using the all-tools-menupopup popup"); + let onSelected = toolbox.once("webconsole-selected"); + consoleButton.click(); + yield onSelected; + + info("Closing the all-tools-menupopup popup"); + let onPopupHidden = once(menuPopup, "popuphidden"); + menuPopup.hidePopup(); + yield onPopupHidden; + + info("Re-open the all-tools-menupopup and verify that the console button is checked"); + menuPopup = yield openAllToolsMenu(toolbox); + + inspectorButton = toolbox.doc.querySelector("#all-tools-menupopup-inspector"); + ok(!inspectorButton.getAttribute("checked"), "The inspector button is not checked"); + + consoleButton = toolbox.doc.querySelector("#all-tools-menupopup-webconsole"); + ok(consoleButton.getAttribute("checked"), "The console button is checked"); + + info("Restore the original window size"); + hostWindow.resizeTo(originalWidth, originalHeight); +}); + +function* openAllToolsMenu(toolbox) { + let allToolsButton = toolbox.doc.querySelector(".all-tools-menu"); + EventUtils.synthesizeMouseAtCenter(allToolsButton, {}, toolbox.win); + + let menuPopup = toolbox.doc.querySelector("#all-tools-menupopup"); + ok(menuPopup, "all-tools-menupopup is available"); + + info("Waiting for the menu popup to be displayed"); + yield waitUntil(() => menuPopup && menuPopup.state === "open"); + + return menuPopup; +}
--- a/devtools/client/framework/toolbox.js +++ b/devtools/client/framework/toolbox.js @@ -999,16 +999,17 @@ Toolbox.prototype = { // Ensure the toolbar doesn't try to render until the tool is ready. const element = this.React.createElement(this.ToolboxController, { L10N, currentToolId: this.currentToolId, selectTool: this.selectTool, closeToolbox: this.destroy, focusButton: this._onToolbarFocus, toggleMinimizeMode: this._toggleMinimizeMode, + toolbox: this }); this.component = this.ReactDOM.render(element, this._componentMount); }, /** * Reset tabindex attributes across all focusable elements inside the toolbar. * Only have one element with tabindex=0 at a time to make sure that tabbing
--- a/devtools/client/locales/en-US/toolbox.properties +++ b/devtools/client/locales/en-US/toolbox.properties @@ -168,8 +168,12 @@ toolbox.frames.tooltip=Select an iframe # the button to force the popups/panels to stay visible on blur. # This is only visible in the browser toolbox as it is meant for # addon developers and Firefox contributors. toolbox.noautohide.tooltip=Disable popup auto hide # LOCALIZATION NOTE (toolbox.closebutton.tooltip): This is the tooltip for # the close button the developer tools toolbox. toolbox.closebutton.tooltip=Close Developer Tools + +# LOCALIZATION NOTE (toolbox.allToolsButton.tooltip): This is the tooltip for the +# "all tools" button displayed when some tools are hidden by overflow of the toolbar. +toolbox.allToolsButton.tooltip=Select another tool
--- a/devtools/client/shared/components/tabs/tabs.css +++ b/devtools/client/shared/components/tabs/tabs.css @@ -46,29 +46,16 @@ .tabs .panels { height: calc(100% - 24px); } .tabs .tab-panel { height: 100%; } -.tabs .all-tabs-menu { - position: absolute; - top: 0; - offset-inline-end: 0; - width: 15px; - height: 100%; - border-inline-start: 1px solid var(--theme-splitter-color); - background: var(--theme-tab-toolbar-background); - background-image: url("chrome://devtools/skin/images/dropmarker.svg"); - background-repeat: no-repeat; - background-position: center; -} - .tabs .tabs-navigation, .tabs .tabs-navigation { position: relative; border-bottom: 1px solid var(--theme-splitter-color); background: var(--theme-tab-toolbar-background); } .theme-dark .tabs .tabs-menu-item,
--- a/devtools/client/themes/common.css +++ b/devtools/client/themes/common.css @@ -667,8 +667,26 @@ checkbox:-moz-focusring { @keyframes throbber-spin { from { transform: none; } to { transform: rotate(360deg); } } + +/* Common tabs styles */ + +.all-tabs-menu { + position: absolute; + + top: 0; + offset-inline-end: 0; + width: 15px; + height: 100%; + + border-inline-start: 1px solid var(--theme-splitter-color); + + background: var(--theme-tab-toolbar-background); + background-image: url("chrome://devtools/skin/images/dropmarker.svg"); + background-repeat: no-repeat; + background-position: center; +}
--- a/devtools/client/themes/toolbox.css +++ b/devtools/client/themes/toolbox.css @@ -50,24 +50,31 @@ background: var(--theme-tab-toolbar-background); border-bottom-color: var(--theme-splitter-color); display: flex; } .toolbox-tabs { margin: 0; flex: 1; + overflow: hidden; } .toolbox-tabs-wrapper { position: relative; display: flex; flex: 1; } +.toolbox-tabs-wrapper .all-tools-menu { + border-inline-end: 1px solid var(--theme-splitter-color); + border-top-width: 0; + border-bottom-width: 0; +} + .toolbox-tabs { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: flex; }