Skip to content

Commit e4dad5e

Browse files
revoconnernchevobbe
authored andcommitted
Bug 1594489 - Add a menu to control the Inspector split orientation. r=nchevobbe,fluent-reviewers,devtools-reviewers,bolsson
Adds a meatball-style menu to the Inspector toolbar with three radio options: Auto (the existing width-based behavior, default), Side by side, and Stacked. The choice is stored in the new devtools.inspector.split-orientation pref ("auto", "side" or "stacked"). A pref observer caches the value and keeps the layout and the menu in sync when the pref changes externally. Also adds the menuitemradio role to the shared menu button styling in tooltips.css, since MenuItem supports it but the stylesheet only covered menuitem and menuitemcheckbox. Differential Revision: https://phabricator.services.mozilla.com/D318610
1 parent 3d3c5e5 commit e4dad5e

10 files changed

Lines changed: 329 additions & 8 deletions

File tree

‎browser/app/profile/firefox.js‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3173,6 +3173,8 @@ pref("devtools.inspector.activeSidebar", "layoutview");
31733173
pref("devtools.inspector.three-pane-enabled", true);
31743174
// Enable the 3 pane mode in the chrome inspector
31753175
pref("devtools.inspector.chrome.three-pane-enabled", false);
3176+
// Splitter orientation: "side", "stacked", or "auto" (width-based)
3177+
pref("devtools.inspector.split-orientation", "auto");
31763178
// Collapse pseudo-elements by default in the rule-view
31773179
pref("devtools.inspector.show_pseudo_elements", false);
31783180
// The default size for image preview tooltips in the rule-view/computed-view/markup-view

‎devtools/client/inspector/index.xhtml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
id="inspector-eyedropper-toggle"
123123
class="devtools-button"
124124
></button>
125+
<div id="inspector-split-orientation-menu"></div>
125126
</div>
126127

127128
<!-- Markup Container -->

‎devtools/client/inspector/inspector.js‎

Lines changed: 168 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,13 @@ const SIDE_PORTAIT_MODE_WIDTH_THRESHOLD = 1000;
9999
const THREE_PANE_ENABLED_PREF = "devtools.inspector.three-pane-enabled";
100100
const THREE_PANE_CHROME_ENABLED_PREF =
101101
"devtools.inspector.chrome.three-pane-enabled";
102+
const SPLIT_ORIENTATION_PREF = "devtools.inspector.split-orientation";
103+
// Possible values for SPLIT_ORIENTATION_PREF; AUTO keeps the width-threshold behavior.
104+
const SPLIT_ORIENTATIONS = {
105+
AUTO: "auto",
106+
SIDE: "side",
107+
STACKED: "stacked",
108+
};
102109
const DEFAULT_COLOR_UNIT_PREF = "devtools.defaultColorUnit";
103110

104111
/**
@@ -168,6 +175,14 @@ class Inspector extends EventEmitter {
168175
DEFAULT_COLOR_UNIT_PREF,
169176
this.#handleDefaultColorUnitPrefChange
170177
);
178+
this.prefObserver.on(
179+
SPLIT_ORIENTATION_PREF,
180+
this.#onSplitOrientationPrefChange
181+
);
182+
this.#splitOrientationPrefValue = Services.prefs.getCharPref(
183+
SPLIT_ORIENTATION_PREF,
184+
SPLIT_ORIENTATIONS.AUTO
185+
);
171186
this.defaultColorUnit = Services.prefs.getStringPref(
172187
DEFAULT_COLOR_UNIT_PREF
173188
);
@@ -197,6 +212,11 @@ class Inspector extends EventEmitter {
197212
#InspectorTabPanel;
198213
#InspectorSplitBox;
199214
#TabBar;
215+
#MenuButton;
216+
#MenuItem;
217+
#MenuList;
218+
#splitOrientationL10nStrings;
219+
#splitOrientationPrefValue;
200220
#updateProgress;
201221

202222
/**
@@ -236,6 +256,7 @@ class Inspector extends EventEmitter {
236256
// parent of the iframe in the DOM tree which would reset the state of the
237257
// iframe if it had already been initialized.
238258
this.#setupSplitter();
259+
this.#setupSplitOrientationMenu();
239260

240261
// Optional NodeFront/ElementIdentifier set on inspector startup, to be selected once the first root
241262
// node is available.
@@ -942,16 +963,50 @@ class Inspector extends EventEmitter {
942963
return this.#TabBar;
943964
}
944965

966+
get MenuButton() {
967+
if (!this.#MenuButton) {
968+
this.#MenuButton = this.React.createFactory(
969+
this.browserRequire("devtools/client/shared/components/menu/MenuButton")
970+
);
971+
}
972+
return this.#MenuButton;
973+
}
974+
975+
get MenuItem() {
976+
if (!this.#MenuItem) {
977+
this.#MenuItem = this.React.createFactory(
978+
this.browserRequire("devtools/client/shared/components/menu/MenuItem")
979+
);
980+
}
981+
return this.#MenuItem;
982+
}
983+
984+
get MenuList() {
985+
if (!this.#MenuList) {
986+
this.#MenuList = this.React.createFactory(
987+
this.browserRequire("devtools/client/shared/components/menu/MenuList")
988+
);
989+
}
990+
return this.#MenuList;
991+
}
992+
945993
/**
946-
* Check if the inspector should use the landscape mode.
994+
* Check if the Inspector panels should be laid out side by side.
947995
*
948-
* @return {boolean} true if the inspector should be in landscape mode.
996+
* @return {boolean} true if the inspector should use the side-by-side layout.
949997
*/
950-
#useLandscapeMode() {
998+
#useSideBySideLayout() {
951999
if (!this.panelDoc) {
9521000
return true;
9531001
}
9541002

1003+
if (this.#splitOrientationPrefValue === SPLIT_ORIENTATIONS.SIDE) {
1004+
return true;
1005+
}
1006+
if (this.#splitOrientationPrefValue === SPLIT_ORIENTATIONS.STACKED) {
1007+
return false;
1008+
}
1009+
9551010
const splitterBox = this.panelDoc.getElementById("inspector-splitter-box");
9561011
const width = splitterBox.clientWidth;
9571012

@@ -996,7 +1051,7 @@ class Inspector extends EventEmitter {
9961051
}),
9971052
ref: this.sidebarSplitBoxRef,
9981053
}),
999-
vert: this.#useLandscapeMode(),
1054+
vert: this.#useSideBySideLayout(),
10001055
onControlledPanelResized: this.onSidebarResized,
10011056
});
10021057

@@ -1019,13 +1074,119 @@ class Inspector extends EventEmitter {
10191074
return;
10201075
}
10211076

1022-
this.splitBox.setState({ vert: this.#useLandscapeMode() });
1077+
this.splitBox.setState({ vert: this.#useSideBySideLayout() });
10231078
this.emit("inspector-resize");
10241079
},
10251080
LAZY_RESIZE_INTERVAL_MS,
10261081
this
10271082
);
10281083

1084+
/**
1085+
* Build the toolbar button opening the menu that controls the orientation of
1086+
* the splitter between the Inspector panels.
1087+
*/
1088+
async #setupSplitOrientationMenu() {
1089+
const [buttonTitle, auto, sideBySide, stacked] =
1090+
await this.panelDoc.l10n.formatValues([
1091+
{ id: "inspector-split-orientation-button-title" },
1092+
{ id: "inspector-split-orientation-auto" },
1093+
{ id: "inspector-split-orientation-side-by-side" },
1094+
{ id: "inspector-split-orientation-stacked" },
1095+
]);
1096+
1097+
// The inspector could have been destroyed while waiting for the strings.
1098+
if (!this.panelDoc) {
1099+
return;
1100+
}
1101+
1102+
this.#splitOrientationL10nStrings = {
1103+
buttonTitle,
1104+
auto,
1105+
sideBySide,
1106+
stacked,
1107+
};
1108+
this.#renderSplitOrientationMenu();
1109+
}
1110+
1111+
#renderSplitOrientationMenu() {
1112+
const strings = this.#splitOrientationL10nStrings;
1113+
const container = this.panelDoc?.getElementById(
1114+
"inspector-split-orientation-menu"
1115+
);
1116+
if (!strings || !container) {
1117+
return;
1118+
}
1119+
1120+
const orientation = this.#splitOrientationPrefValue;
1121+
const items = [
1122+
this.MenuItem({
1123+
key: SPLIT_ORIENTATIONS.AUTO,
1124+
id: "inspector-split-orientation-auto",
1125+
role: "menuitemradio",
1126+
checked: orientation === SPLIT_ORIENTATIONS.AUTO,
1127+
label: strings.auto,
1128+
icon: "chrome://devtools/skin/images/dock-auto.svg",
1129+
onClick: () =>
1130+
Services.prefs.setCharPref(
1131+
SPLIT_ORIENTATION_PREF,
1132+
SPLIT_ORIENTATIONS.AUTO
1133+
),
1134+
}),
1135+
this.MenuItem({
1136+
key: SPLIT_ORIENTATIONS.SIDE,
1137+
id: "inspector-split-orientation-side",
1138+
role: "menuitemradio",
1139+
checked: orientation === SPLIT_ORIENTATIONS.SIDE,
1140+
label: strings.sideBySide,
1141+
icon: "chrome://devtools/skin/images/dock-side-right.svg",
1142+
onClick: () =>
1143+
Services.prefs.setCharPref(
1144+
SPLIT_ORIENTATION_PREF,
1145+
SPLIT_ORIENTATIONS.SIDE
1146+
),
1147+
}),
1148+
this.MenuItem({
1149+
key: SPLIT_ORIENTATIONS.STACKED,
1150+
id: "inspector-split-orientation-stacked",
1151+
role: "menuitemradio",
1152+
checked: orientation === SPLIT_ORIENTATIONS.STACKED,
1153+
label: strings.stacked,
1154+
icon: "chrome://devtools/skin/images/dock-bottom.svg",
1155+
onClick: () =>
1156+
Services.prefs.setCharPref(
1157+
SPLIT_ORIENTATION_PREF,
1158+
SPLIT_ORIENTATIONS.STACKED
1159+
),
1160+
}),
1161+
];
1162+
1163+
this.ReactDOM.render(
1164+
this.MenuButton(
1165+
{
1166+
id: "inspector-split-orientation-button",
1167+
menuId: "inspector-split-orientation-menu-panel",
1168+
toolboxDoc: this.#toolbox.doc,
1169+
className: "devtools-button",
1170+
title: strings.buttonTitle,
1171+
},
1172+
() => this.MenuList({}, items)
1173+
),
1174+
container
1175+
);
1176+
}
1177+
1178+
#onSplitOrientationPrefChange = () => {
1179+
this.#splitOrientationPrefValue = Services.prefs.getCharPref(
1180+
SPLIT_ORIENTATION_PREF,
1181+
SPLIT_ORIENTATIONS.AUTO
1182+
);
1183+
if (this.#destroyed || !this.splitBox) {
1184+
return;
1185+
}
1186+
this.splitBox.setState({ vert: this.#useSideBySideLayout() });
1187+
this.#renderSplitOrientationMenu();
1188+
};
1189+
10291190
getSidebarSize() {
10301191
let width;
10311192
let height;
@@ -1139,7 +1300,7 @@ class Inspector extends EventEmitter {
11391300
// bottom-right panel in vertical mode width in 3 pane mode.
11401301
let sidebarSplitboxWidth;
11411302

1142-
if (this.#useLandscapeMode()) {
1303+
if (this.#useSideBySideLayout()) {
11431304
// Whether or not doubling the inspector sidebar's (right panel in horizontal mode
11441305
// or bottom panel in vertical mode) width will be bigger than half of the
11451306
// toolbox's width.
@@ -1224,7 +1385,7 @@ class Inspector extends EventEmitter {
12241385
"inspector-splitter-box"
12251386
);
12261387
this.splitBox.setState({
1227-
width: this.#useLandscapeMode()
1388+
width: this.#useSideBySideLayout()
12281389
? this.sidebarSplitBoxRef.current.state.width
12291390
: splitterBox.clientWidth,
12301391
});

‎devtools/client/inspector/test/browser.toml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ skip-if = [
207207

208208
["browser_inspector_sidebarstate.js"]
209209

210+
["browser_inspector_split-orientation.js"]
211+
210212
["browser_inspector_startup.js"]
211213

212214
["browser_inspector_switch-to-inspector-on-pick.js"]
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* Any copyright is dedicated to the Public Domain.
2+
* http://creativecommons.org/publicdomain/zero/1.0/ */
3+
4+
"use strict";
5+
6+
// Test the Inspector toolbar menu controlling the orientation of the splitter
7+
// between the Inspector panels.
8+
9+
const SPLIT_ORIENTATION_PREF = "devtools.inspector.split-orientation";
10+
const TEST_URI = "data:text/html;charset=utf-8,<h1>split orientation</h1>";
11+
12+
add_task(async function () {
13+
registerCleanupFunction(() => {
14+
Services.prefs.clearUserPref(SPLIT_ORIENTATION_PREF);
15+
});
16+
17+
const { inspector, toolbox } = await openInspectorForURL(TEST_URI);
18+
19+
const button = await waitFor(() =>
20+
inspector.panelDoc.getElementById("inspector-split-orientation-button")
21+
);
22+
ok(button, "The split orientation menu button is rendered");
23+
24+
is(
25+
Services.prefs.getCharPref(SPLIT_ORIENTATION_PREF),
26+
"auto",
27+
"The split orientation pref defaults to auto"
28+
);
29+
30+
info("Open the menu and check the default state");
31+
let items = await openSplitOrientationMenu(inspector, toolbox);
32+
is(
33+
items.auto.getAttribute("aria-checked"),
34+
"true",
35+
"The automatic item is checked by default"
36+
);
37+
ok(
38+
!items.side.hasAttribute("aria-checked"),
39+
"The side-by-side item is not checked"
40+
);
41+
ok(
42+
!items.stacked.hasAttribute("aria-checked"),
43+
"The stacked item is not checked"
44+
);
45+
46+
info("Lock the layout to side by side");
47+
EventUtils.synthesizeMouseAtCenter(items.side, {}, toolbox.win);
48+
await waitFor(
49+
() => Services.prefs.getCharPref(SPLIT_ORIENTATION_PREF) === "side"
50+
);
51+
await waitFor(() => inspector.splitBox.state.vert === true);
52+
ok(true, "The pref was set and the layout switched to side by side");
53+
await waitForSplitOrientationMenuToClose(toolbox);
54+
55+
info("Reopen the menu and lock the layout to stacked");
56+
items = await openSplitOrientationMenu(inspector, toolbox);
57+
is(
58+
items.side.getAttribute("aria-checked"),
59+
"true",
60+
"The side-by-side item is now checked"
61+
);
62+
EventUtils.synthesizeMouseAtCenter(items.stacked, {}, toolbox.win);
63+
await waitFor(
64+
() => Services.prefs.getCharPref(SPLIT_ORIENTATION_PREF) === "stacked"
65+
);
66+
await waitFor(() => inspector.splitBox.state.vert === false);
67+
ok(true, "The pref was set and the layout switched to stacked");
68+
await waitForSplitOrientationMenuToClose(toolbox);
69+
70+
info("Change the pref externally while the inspector is open");
71+
Services.prefs.setCharPref(SPLIT_ORIENTATION_PREF, "side");
72+
await waitFor(() => inspector.splitBox.state.vert === true);
73+
ok(true, "The layout updated after an external pref change");
74+
75+
items = await openSplitOrientationMenu(inspector, toolbox);
76+
is(
77+
items.side.getAttribute("aria-checked"),
78+
"true",
79+
"The menu reflects the external pref change"
80+
);
81+
82+
info("Close the menu with the escape key");
83+
EventUtils.synthesizeKey("VK_ESCAPE", {}, toolbox.win);
84+
await waitForSplitOrientationMenuToClose(toolbox);
85+
});
86+
87+
async function openSplitOrientationMenu(inspector, toolbox) {
88+
const button = inspector.panelDoc.getElementById(
89+
"inspector-split-orientation-button"
90+
);
91+
EventUtils.synthesizeMouseAtCenter(button, {}, inspector.panelWin);
92+
93+
info("Waiting for the split orientation menu to be displayed");
94+
await waitFor(() => {
95+
const panel = toolbox.doc.getElementById(
96+
"inspector-split-orientation-menu-panel"
97+
);
98+
return panel?.classList.contains("tooltip-visible");
99+
});
100+
101+
return {
102+
auto: toolbox.doc.getElementById("inspector-split-orientation-auto"),
103+
side: toolbox.doc.getElementById("inspector-split-orientation-side"),
104+
stacked: toolbox.doc.getElementById("inspector-split-orientation-stacked"),
105+
};
106+
}
107+
108+
function waitForSplitOrientationMenuToClose(toolbox) {
109+
info("Waiting for the split orientation menu to be hidden");
110+
return waitFor(() => {
111+
const panel = toolbox.doc.getElementById(
112+
"inspector-split-orientation-menu-panel"
113+
);
114+
return !panel || !panel.classList.contains("tooltip-visible");
115+
});
116+
}

0 commit comments

Comments
 (0)