Bug 1433611 - Adjust the widths of the split box on toggling of the split rule view. r=pbro
When the inspector is in landscape mode, it will first try to toggle on the 3 pane mode by
creating a middle/bottom-left panel with the same sidebar width, but if doubling the
original sidebar width will be bigger than half of the toolbox's width, we will instead
toggle on the 3 pane mode with all panels being of equal widths.
When the inspector is in portrait mode, it will just toggle on its T shape pane such that
the 2 bottom panes are equal widths.
We also increased the breakpoint width of the toolbox's side view to 1000px so that it
will keep its T shape in portrait mode until the toolbox's width is bigger than 1000px.
MozReview-Commit-ID: A5HngPkqHXe
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -78,17 +78,23 @@ class GridInspector {
* Initializes the grid inspector by fetching the LayoutFront from the walker, loading
* the highlighter settings and initalizing the SwatchColorPicker instance.
*/
async init() {
if (!this.inspector) {
return;
}
- this.layoutInspector = await this.inspector.walker.getLayoutInspector();
+ try {
+ this.layoutInspector = await this.inspector.walker.getLayoutInspector();
+ } catch (e) {
+ // This call might fail if called asynchrously after the toolbox is finished
+ // closing.
+ return;
+ }
this.loadHighlighterSettings();
// Create a shared SwatchColorPicker instance to be reused by all GridItem components.
this.swatchColorPickerTooltip = new SwatchColorPickerTooltip(
this.inspector.toolbox.doc,
this.inspector,
{
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -7,16 +7,17 @@
/* global window, BrowserLoader */
"use strict";
const Services = require("Services");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const {executeSoon} = require("devtools/shared/DevToolsUtils");
+const {Toolbox} = require("devtools/client/framework/toolbox");
const {PrefObserver} = require("devtools/client/shared/prefs");
const Telemetry = require("devtools/client/shared/telemetry");
const HighlightersOverlay = require("devtools/client/inspector/shared/highlighters-overlay");
const ReflowTracker = require("devtools/client/inspector/shared/reflow-tracker");
const Store = require("devtools/client/inspector/store");
const InspectorStyleChangeTracker = require("devtools/client/inspector/shared/style-change-tracker");
// Use privileged promise in panel documents to prevent having them to freeze
@@ -41,19 +42,23 @@ const INSPECTOR_L10N =
new LocalizationHelper("devtools/client/locales/inspector.properties");
loader.lazyGetter(this, "TOOLBOX_L10N", function() {
return new LocalizationHelper("devtools/client/locales/toolbox.properties");
});
// Sidebar dimensions
const INITIAL_SIDEBAR_SIZE = 350;
-// If the toolbox width is smaller than given amount of pixels,
-// the sidebar automatically switches from 'landscape' to 'portrait' mode.
-const PORTRAIT_MODE_WIDTH = 700;
+// If the toolbox's width is smaller than the given amount of pixels, the sidebar
+// automatically switches from 'landscape/horizontal' to 'portrait/vertical' mode.
+const PORTRAIT_MODE_WIDTH_THRESHOLD = 700;
+// If the toolbox's width docked to the side is smaller than the given amount of pixels,
+// the sidebar automatically switches from 'landscape/horizontal' to 'portrait/vertical'
+// mode.
+const SIDE_PORTAIT_MODE_WIDTH_THRESHOLD = 1000;
const SHOW_SPLIT_SIDEBAR_TOGGLE_PREF = "devtools.inspector.split-sidebar-toggle";
const SPLIT_RULE_VIEW_PREF = "devtools.inspector.split-rule-enabled";
/**
* Represents an open instance of the Inspector for a tab.
* The inspector controls the breadcrumbs, the markup view, and the sidebar
* (computed view, rule view, font view and animation inspector).
@@ -450,17 +455,19 @@ Inspector.prototype = {
/**
* Check if the inspector should use the landscape mode.
*
* @return {Boolean} true if the inspector should be in landscape mode.
*/
useLandscapeMode: function() {
let { clientWidth } = this.panelDoc.getElementById("inspector-splitter-box");
- return clientWidth > PORTRAIT_MODE_WIDTH;
+ return this.isSplitRuleViewEnabled && this.toolbox.hostType == Toolbox.HostType.SIDE ?
+ clientWidth > SIDE_PORTAIT_MODE_WIDTH_THRESHOLD :
+ clientWidth > PORTRAIT_MODE_WIDTH_THRESHOLD;
},
/**
* Build Splitter located between the main and side area of
* the Inspector panel.
*/
setupSplitter: function() {
let SplitBox = this.React.createFactory(this.browserRequire(
@@ -478,17 +485,17 @@ Inspector.prototype = {
startPanel: this.InspectorTabPanel({
id: "inspector-main-content"
}),
endPanel: SplitBox({
initialWidth: splitSidebarWidth,
minSize: 10,
maxSize: "80%",
splitterSize: this.isSplitRuleViewEnabled ? 1 : 0,
- endPanelControl: false,
+ endPanelControl: this.isSplitRuleViewEnabled,
startPanel: this.InspectorTabPanel({
id: "inspector-rules-container"
}),
endPanel: this.InspectorTabPanel({
id: "inspector-sidebar-container"
}),
ref: splitbox => {
this.sidebarSplitBox = splitbox;
@@ -584,64 +591,119 @@ Inspector.prototype = {
this.isSplitRuleViewEnabled = !this.isSplitRuleViewEnabled;
Services.prefs.setBoolPref(SPLIT_RULE_VIEW_PREF, this.isSplitRuleViewEnabled);
await this.setupToolbar();
await this.addRuleView();
},
/**
- * Adds the rule view to the main or split sidebar depending on whether or not it is
- * split view mode. The default tab specifies whether or not the rule view should be
- * selected. The defaultTab defaults to the rule view when the rule view is being merged
- * back into the sidebar from the split sidebar. Otherwise, we specify the default tab
- * when handling the sidebar setup.
+ * Adds the rule view to the middle/bottom-left panel or inspector sidebar depending on
+ * whether or not it is 3 pane mode. The default tab specifies whether or not the rule
+ * view should be selected. The defaultTab defaults to the rule view when reverting to
+ * the 2 pane mode and the rule view is being merged back into the inspector sidebar
+ * from middle/bottom-left panel. Otherwise, we specify the default tab when handling
+ * the sidebar setup.
*
* @params {String} defaultTab
* Thie id of the default tab for the sidebar.
*/
async addRuleView(defaultTab = "ruleview") {
- let ruleViewSidebar = this.sidebarSplitBox.startPanelContainer;
+ const ruleViewSidebar = this.sidebarSplitBox.startPanelContainer;
+ const toolboxWidth =
+ this.panelDoc.getElementById("inspector-splitter-box").clientWidth;
if (this.isSplitRuleViewEnabled) {
- // Removes the rule view from the main sidebar and adds the rule view to the split
- // sidebar.
+ // Convert to 3 pane mode by removing the rule view from the inspector sidebar
+ // and adding the rule view to the middle/bottom-left panel.
+
ruleViewSidebar.style.display = "block";
- // Show the splitter inside the sidebar split box.
- this.sidebarSplitBox.setState({ splitterSize: 1 });
+ // Get the inspector sidebar's (right/bottom panel) width.
+ const sidebarWidth = this.splitBox.state.width;
+
+ // Whether or not doubling the inspector sidebar's (right/bottom panel) width will
+ // be bigger than half the size of the toolbox's width.
+ const canDoubleSidebarWidth = (sidebarWidth * 2) < (toolboxWidth / 2);
+ // This variable represents the width of the right/bottom-right panel width in 3
+ // pane mode.
+ let sidebarSplitboxWidth;
+
+ if (this.useLandscapeMode()) {
+ // Resize the main split box's end panel that contains the middle and right panel.
+ // Attempts to resize the main split box's end panel to be double the size of the
+ // existing sidebar's width when switching to 3 pane mode. However, if the middle
+ // and right panel's width together is greater than half of the toolbox's width,
+ // split all 3 panels to be equally sized by resizing the end panel to be 2/3 of
+ // the current toolbox's width.
+ this.splitBox.setState({
+ width: canDoubleSidebarWidth ? sidebarWidth * 2 : toolboxWidth * 2 / 3,
+ });
+
+ // In landscape/horizontal mode, set the right panel back to its original
+ // inspector sidebar width if we can double the sidebar width. Otherwise, set
+ // the width of the right panel to be 1/3 of the toolbox's width since all 3
+ // panels will be equally sized.
+ sidebarSplitboxWidth = canDoubleSidebarWidth ? sidebarWidth : toolboxWidth / 3;
+ } else {
+ // In portrait/vertical mode, set the bottom-right panel to be 1/2 of the
+ // toolbox's width.
+ sidebarSplitboxWidth = toolboxWidth / 2;
+ }
+
+ // Show the splitter inside the sidebar split box. Sets the width of the inspector
+ // sidebar and specify that the end (right/bottom-right) panel of the sidebar split
+ // box should be controlled when resizing.
+ this.sidebarSplitBox.setState({
+ endPanelControl: true,
+ splitterSize: 1,
+ width: sidebarSplitboxWidth,
+ });
// Force the rule view panel creation by calling getPanel
this.getPanel("ruleview");
await this.sidebar.removeTab("ruleview");
this.ruleViewSideBar.addExistingTab(
"ruleview",
INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
true);
this.ruleViewSideBar.show("ruleview");
} else {
- // Removes the rule view from the split sidebar and adds the rule view to the main
- // sidebar.
+ // Removes the rule view from the 3 pane mode and adds the rule view to the main
+ // inspector sidebar.
+
ruleViewSidebar.style.display = "none";
- // Hide the splitter to prevent any drag events in the sidebar split box.
- this.sidebarSplitBox.setState({ splitterSize: 0 });
+ // Set the width of the split box (right/bottom panel) to be the width of the
+ // inspector sidebar.
+ this.splitBox.setState({
+ width: this.useLandscapeMode() ? this.sidebarSplitBox.state.width : toolboxWidth,
+ });
+
+ // Hide the splitter to prevent any drag events in the sidebar split box and
+ // specify that the end (right/bottom) panel should be uncontrolled when resizing.
+ this.sidebarSplitBox.setState({
+ endPanelControl: false,
+ splitterSize: 0,
+ });
this.ruleViewSideBar.hide();
await this.ruleViewSideBar.removeTab("ruleview");
this.sidebar.addExistingTab(
"ruleview",
INSPECTOR_L10N.getStr("inspector.sidebar.ruleViewTitle"),
defaultTab == "ruleview",
0);
}
+
+ this.emit("ruleview-added");
},
/**
* Lazily get and create panel instances displayed in the sidebar
*/
getPanel: function(id) {
if (this._panels.has(id)) {
return this._panels.get(id);
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -156,16 +156,20 @@ skip-if = (os == 'linux' && bits == 32 &
subsuite = clipboard
skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
[browser_inspector_menu-04-use-in-console.js]
[browser_inspector_menu-05-attribute-items.js]
[browser_inspector_menu-06-other.js]
[browser_inspector_navigation.js]
[browser_inspector_navigate_to_errors.js]
[browser_inspector_open_on_neterror.js]
+[browser_inspector_pane-toggle-01.js]
+[browser_inspector_pane-toggle-02.js]
+[browser_inspector_pane-toggle-03.js]
+[browser_inspector_pane-toggle-04.js]
[browser_inspector_picker-stop-on-destroy.js]
[browser_inspector_picker-stop-on-tool-change.js]
[browser_inspector_portrait_mode.js]
[browser_inspector_pseudoclass-lock.js]
[browser_inspector_pseudoclass-menu.js]
[browser_inspector_reload-01.js]
[browser_inspector_reload-02.js]
[browser_inspector_remove-iframe-during-load.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-01.js
@@ -0,0 +1,32 @@
+/* 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/ */
+
+"use strict";
+
+// Tests that the inspector panel has a 3 pane toggle button, and that
+// this button is visible both in BOTTOM and SIDE hosts.
+
+Services.prefs.setBoolPref("devtools.inspector.split-sidebar-toggle", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.inspector.split-sidebar-toggle");
+});
+
+add_task(async function() {
+ info("Open the inspector in a bottom toolbox host");
+ const { inspector, toolbox } = await openInspectorForURL("about:blank", "bottom");
+
+ const button = inspector.panelDoc.querySelector(".sidebar-toggle");
+ ok(button, "The toggle button exists in the DOM");
+ ok(button.getAttribute("title"), "The title tooltip has initial state");
+ ok(button.classList.contains("pane-collapsed"), "The button is in collapsed state");
+ ok(!!button.getClientRects().length, "The button is visible");
+
+ info("Switch the host to side type");
+ await toolbox.switchHost("side");
+
+ ok(!!button.getClientRects().length, "The button is still visible");
+ ok(button.classList.contains("pane-collapsed"),
+ "The button is still in collapsed state");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-02.js
@@ -0,0 +1,51 @@
+/* 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/ */
+
+"use strict";
+
+// Tests that the 3 pane toggle button can toggle on and off the inspector's 3 pane mode,
+// and the 3 panes rendered are all of equal widths in the BOTTOM host.
+
+Services.prefs.setBoolPref("devtools.inspector.split-sidebar-toggle", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.inspector.split-sidebar-toggle");
+});
+
+add_task(async function() {
+ const { inspector } = await openInspectorForURL("about:blank");
+ const { panelDoc: doc } = inspector;
+ const button = doc.querySelector(".sidebar-toggle");
+ const ruleViewSidebar = inspector.sidebarSplitBox.startPanelContainer;
+ const toolboxWidth = doc.getElementById("inspector-splitter-box").clientWidth;
+
+ ok(button.classList.contains("pane-collapsed"), "The button is in collapsed state");
+
+ info("Click on the toggle button to toggle ON 3 pane inspector");
+ EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelDoc.defaultView);
+ await inspector.once("ruleview-added");
+
+ info("Checking the state of the 3 pane inspector");
+ let sidebarWidth = inspector.splitBox.state.width;
+ let sidebarSplitBoxWidth = inspector.sidebarSplitBox.state.width;
+ ok(!button.classList.contains("pane-collapsed"), "The button is in expanded state");
+ ok(doc.getElementById("ruleview-panel"), "The rule view panel exist");
+ is(inspector.sidebar.getCurrentTabID(), "layoutview",
+ "Layout view is shown in the sidebar");
+ is(ruleViewSidebar.style.display, "block", "The split rule view sidebar is displayed");
+ is(sidebarWidth, toolboxWidth * 2 / 3, "Got correct main split box width");
+ is(sidebarSplitBoxWidth, toolboxWidth / 3, "Got correct sidebar split box width");
+
+ info("Click on the toggle button to toggle OFF the 3 pane inspector");
+ EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelDoc.defaultView);
+ await inspector.once("ruleview-added");
+
+ info("Checking the state of the 2 pane inspector");
+ sidebarWidth = inspector.splitBox.state.width;
+ ok(button.classList.contains("pane-collapsed"), "The button is in collapsed state");
+ is(inspector.sidebar.getCurrentTabID(), "ruleview",
+ "Rule view is shown in the sidebar");
+ is(ruleViewSidebar.style.display, "none", "The split rule view sidebar is hidden");
+ is(sidebarWidth, sidebarSplitBoxWidth, "Got correct sidebar width");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-03.js
@@ -0,0 +1,44 @@
+/* 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/ */
+
+"use strict";
+
+// Tests that the 3 pane inspector toggle can render the middle and right panels of equal
+// sizes when the original sidebar can be doubled in width and be smaller than half the
+// toolbox's width in the BOTTOM host.
+
+Services.prefs.setBoolPref("devtools.inspector.split-sidebar-toggle", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.inspector.split-sidebar-toggle");
+});
+
+const SIDEBAR_WIDTH = 200;
+
+add_task(async function() {
+ const { inspector } = await openInspectorForURL("about:blank");
+ const { panelDoc: doc } = inspector;
+ const button = doc.querySelector(".sidebar-toggle");
+
+ info("Set the sidebar width to 200px");
+ inspector.splitBox.setState({ width: SIDEBAR_WIDTH });
+
+ info("Click on the toggle button to toggle ON 3 pane inspector");
+ EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelDoc.defaultView);
+ await inspector.once("ruleview-added");
+
+ info("Checking the sizes of the 3 pane inspector");
+ let sidebarWidth = inspector.splitBox.state.width;
+ let sidebarSplitBoxWidth = inspector.sidebarSplitBox.state.width;
+ is(sidebarWidth, SIDEBAR_WIDTH * 2, "Got correct main split box width");
+ is(sidebarSplitBoxWidth, SIDEBAR_WIDTH, "Got correct sidebar split box width");
+
+ info("Click on the toggle button to toggle OFF the 3 pane inspector");
+ EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelDoc.defaultView);
+ await inspector.once("ruleview-added");
+
+ info("Checking the sidebar size of the 2 pane inspector");
+ sidebarWidth = inspector.splitBox.state.width;
+ is(sidebarWidth, SIDEBAR_WIDTH, "Got correct sidebar width");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_pane-toggle-04.js
@@ -0,0 +1,41 @@
+/* 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/ */
+
+"use strict";
+
+// Tests that the 3 pane inspector toggle button can render the bottom-left and
+// bottom-right panels of equal sizes in the SIDE host.
+
+Services.prefs.setBoolPref("devtools.inspector.split-sidebar-toggle", true);
+
+registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.inspector.split-sidebar-toggle");
+});
+
+add_task(async function() {
+ const { inspector, toolbox } = await openInspectorForURL("about:blank");
+ const { panelDoc: doc } = inspector;
+
+ info("Switch the host to side type");
+ await toolbox.switchHost("side");
+
+ const button = doc.querySelector(".sidebar-toggle");
+ const toolboxWidth = doc.getElementById("inspector-splitter-box").clientWidth;
+
+ info("Click on the toggle button to toggle ON 3 pane inspector");
+ EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelDoc.defaultView);
+ await inspector.once("ruleview-added");
+
+ info("Checking the sizes of the 3 pane inspector");
+ let sidebarSplitBoxWidth = inspector.sidebarSplitBox.state.width;
+ is(sidebarSplitBoxWidth, toolboxWidth / 2, "Got correct sidebar split box width");
+
+ info("Click on the toggle button to toggle OFF the 3 pane inspector");
+ EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelDoc.defaultView);
+ await inspector.once("ruleview-added");
+ debugger;
+ info("Checking the sidebar size of the 2 pane inspector");
+ let sidebarWidth = inspector.splitBox.state.width;
+ is(sidebarWidth, toolboxWidth, "Got correct sidebar width");
+});
--- a/devtools/client/shared/components/splitter/SplitBox.js
+++ b/devtools/client/shared/components/splitter/SplitBox.js
@@ -70,47 +70,56 @@ class SplitBox extends Component {
super(props);
/**
* The state stores the current orientation (vertical or horizontal)
* and the current size (width/height). All these values can change
* during the component's life time.
*/
this.state = {
+ endPanelControl: props.endPanelControl,
vert: props.vert,
splitterSize: props.splitterSize,
width: props.initialWidth || props.initialSize,
height: props.initialHeight || props.initialSize
};
this.onStartMove = this.onStartMove.bind(this);
this.onStopMove = this.onStopMove.bind(this);
this.onMove = this.onMove.bind(this);
}
componentWillReceiveProps(nextProps) {
- let { splitterSize, vert } = nextProps;
+ let {
+ endPanelControl,
+ splitterSize,
+ vert,
+ } = nextProps;
+
+ if (endPanelControl != this.props.endPanelControl) {
+ this.setState({ endPanelControl });
+ }
if (splitterSize != this.props.splitterSize) {
this.setState({ splitterSize });
}
if (vert !== this.props.vert) {
this.setState({ vert });
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.width != this.state.width ||
+ nextState.endPanelControl != this.props.endPanelControl ||
nextState.height != this.state.height ||
nextState.vert != this.state.vert ||
nextState.splitterSize != this.state.splitterSize ||
nextProps.startPanel != this.props.startPanel ||
nextProps.endPanel != this.props.endPanel ||
- nextProps.endPanelControl != this.props.endPanelControl ||
nextProps.minSize != this.props.minSize ||
nextProps.maxSize != this.props.maxSize;
}
componentDidUpdate(prevProps, prevState) {
if (this.props.onControlledPanelResized && (prevState.width !== this.state.width ||
prevState.height !== this.state.height)) {
this.props.onControlledPanelResized(this.state.width, this.state.height);
@@ -149,19 +158,19 @@ class SplitBox extends Component {
* Adjust size of the controlled panel. Depending on the current
* orientation we either remember the width or height of
* the splitter box.
*/
onMove(x, y) {
const node = ReactDOM.findDOMNode(this);
let size;
- let { endPanelControl } = this.props;
+ let { endPanelControl, vert } = this.state;
- if (this.state.vert) {
+ if (vert) {
// Switch the control flag in case of RTL. Note that RTL
// has impact on vertical splitter only.
if (document.dir === "rtl") {
endPanelControl = !endPanelControl;
}
size = endPanelControl ?
(node.offsetLeft + node.offsetWidth) - x :
@@ -179,18 +188,18 @@ class SplitBox extends Component {
height: size
});
}
}
// Rendering
render() {
- const { splitterSize, vert } = this.state;
- const { startPanel, endPanel, endPanelControl, minSize, maxSize } = this.props;
+ const { endPanelControl, splitterSize, vert } = this.state;
+ const { startPanel, endPanel, minSize, maxSize } = this.props;
let style = Object.assign({}, this.props.style);
// Calculate class names list.
let classNames = ["split-box"];
classNames.push(vert ? "vert" : "horz");
if (this.props.className) {
classNames = classNames.concat(this.props.className.split(" "));
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -62,16 +62,20 @@ window {
.inspector-tabpanel {
min-width: 200px;
}
#inspector-splitter-box .controlled.pane-collapsed {
visibility: collapse;
}
+#inspector-splitter-box .sidebar-toggle::before {
+ transform: unset;
+}
+
/* Use flex layout for the Inspector toolbar. For now, it's done
specifically for the Inspector toolbar since general rule applied
on .devtools-toolbar breaks breadcrumbs and also toolbars in other
panels (e.g. webconsole, debugger), these are not ready for HTML
layout yet. */
#inspector-toolbar.devtools-toolbar {
display: flex;
}