Bug 1621015 - Profiler popup button should be split into a start/stop button, and a down arrow, r=julienw.

Differential Revision: https://phabricator.services.mozilla.com/D77680
This commit is contained in:
Florian Quèze 2020-07-03 14:28:17 +00:00
parent fc8e03a55d
commit 668686ae05
5 changed files with 246 additions and 7 deletions

View File

@ -256,7 +256,7 @@ toolbar[brighttext] {
list-style-image: url("chrome://browser/skin/developer.svg");
}
#profiler-button {
#profiler-button > .toolbarbutton-icon {
list-style-image: url("chrome://devtools/skin/images/tool-profiler.svg");
}
@ -269,18 +269,27 @@ toolbar[brighttext] {
fill-opacity: 1;
}
#profiler-button.profiler-active image {
#profiler-button.profiler-active > image {
background-color: #0060df33;
}
#profiler-button.profiler-active:hover image {
#profiler-button.profiler-active:hover > image {
background-color: #0060df22;
}
#profiler-button.profiler-paused image {
#profiler-button.profiler-paused > image {
opacity: 0.4;
}
#profiler-button > .toolbarbutton-menu-dropmarker {
-moz-context-properties: fill, fill-opacity;
}
.widget-overflow-list #profiler-button > .toolbarbutton-menu-dropmarker,
#customization-palette #profiler-button > .toolbarbutton-menu-dropmarker {
display: none;
}
#preferences-button {
list-style-image: url("chrome://global/skin/icons/settings.svg");
}

View File

@ -26,6 +26,10 @@ const lazy = createLazyLoaders({
ChromeUtils.import(
"resource://devtools/client/performance-new/popup/panel.jsm.js"
),
Background: () =>
ChromeUtils.import(
"resource://devtools/client/performance-new/popup/background.jsm.js"
),
});
const WIDGET_ID = "profiler-button";
@ -75,7 +79,13 @@ function openPopup(document) {
if (!button) {
throw new Error("Could not find the profiler button.");
}
button.click();
// Sending a click event anywhere on the button could start the profiler
// instead of opening the popup. Sending a command event on a view widget
// will make CustomizableUI show the view.
const cmdEvent = document.createEvent("xulcommandevent");
cmdEvent.initCommandEvent("command", true, true, button.ownerGlobal);
button.dispatchEvent(cmdEvent);
}
/**
@ -233,6 +243,17 @@ function initialize(toggleProfilerKeyShortcuts) {
// is in the overflow menu.
buttonElement.classList.add("subviewbutton-nav");
// Our buttons has 2 targets: the icon which starts the profiler or
// captures the profile, and the dropmarker which opens the popup.
// To have this behavior, we need to enable showing the dropmarker...
buttonElement.setAttribute("wantdropmarker", "true");
// ... and to implement our custom click and keyboard event handling
// to decide which of the 2 behaviors should be triggered.
buttonElement.addEventListener("click", item);
// This would be better as a keypress handler, but CustomizableUI's
// keypress handler runs before keypress handlers we could add here.
buttonElement.addEventListener("keydown", item);
function setButtonActive() {
buttonElement.setAttribute(
"tooltiptext",
@ -273,8 +294,80 @@ function initialize(toggleProfilerKeyShortcuts) {
Services.obs.removeObserver(setButtonActive, "profiler-started");
Services.obs.removeObserver(setButtonInactive, "profiler-stopped");
Services.obs.removeObserver(setButtonPaused, "profiler-paused");
buttonElement.removeEventListener("click", item);
buttonElement.removeEventListener("keydown", item);
});
},
handleEvent: event => {
function startOrCapture() {
if (Services.profiler.IsPaused()) {
// A profile is already being captured, ignore this event.
return;
}
const { startProfiler, captureProfile } = lazy.Background();
if (Services.profiler.IsActive()) {
captureProfile("aboutprofiling");
} else {
startProfiler("aboutprofiling");
}
}
if (event.type == "click") {
// Only handle clicks on the left button.
if (event.button != 0) {
return;
}
const button = event.target;
// Ignore the click event while in the overflow menu
if (button.getAttribute("cui-anchorid") == "nav-bar-overflow-button") {
return;
}
// If we are within the area of the icon, handle the event.
// Otherwise we are on the dropmarker and CustomizableUI will take
// care of opening the panel.
const win = button.ownerGlobal;
const iconBounds = win.windowUtils.getBoundsWithoutFlushing(
button.icon
);
if (
win.RTL_UI ? event.x >= iconBounds.left : event.x <= iconBounds.right
) {
startOrCapture();
event.stopPropagation();
event.preventDefault();
}
} else if (event.type == "keydown") {
if (event.key == " " || event.key == "Enter") {
startOrCapture();
event.stopPropagation();
event.preventDefault();
return;
}
if (event.key == "ArrowDown") {
// Trigger a "command" event that CustomizableUI will handle.
const button = event.target;
const cmdEvent = button.ownerDocument.createEvent("xulcommandevent");
cmdEvent.initCommandEvent(
"command",
true,
true,
button.ownerGlobal,
0,
event.ctrlKey,
event.altKey,
event.shiftKey,
event.metaKey,
null,
event.mozInputSource
);
event.currentTarget.dispatchEvent(cmdEvent);
}
}
},
};
CustomizableUI.createWidget(item);

View File

@ -36,3 +36,5 @@ support-files =
# Bug 941073. Also see: 1178420, 1115757, 1401049, 1269392
[browser_popup-private-browsing.js]
skip-if = os == 'linux'
[browser_split-toolbar-button.js]

View File

@ -0,0 +1,135 @@
/* 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";
function isActive() {
return Services.profiler.IsActive();
}
/**
* Force focus to an element that isn't focusable.
* Toolbar buttons aren't focusable because if they were, clicking them would
* focus them, which is undesirable. Therefore, they're only made focusable
* when a user is navigating with the keyboard. This function forces focus as
* is done during toolbar keyboard navigation.
*/
function forceFocus(elem) {
elem.setAttribute("tabindex", "-1");
elem.focus();
elem.removeAttribute("tabindex");
}
async function waitForProfileAndCloseTab() {
await waitUntil(
() => !button.classList.contains("profiler-paused"),
"Waiting until the profiler is no longer paused"
);
await checkTabLoadedProfile({
initialTitle: "Waiting on the profile",
successTitle: "Profile received",
errorTitle: "Error",
});
}
var button;
add_task(async function setup() {
info(
"Add the profiler button to the toolbar and ensure capturing a profile loads a local url."
);
await setProfilerFrontendUrl(
"http://example.com/browser/devtools/client/performance-new/test/browser/fake-frontend.html"
);
await makeSureProfilerPopupIsEnabled();
button = document.getElementById("profiler-button");
});
add_task(async function click_icon() {
info("Test that the profiler icon starts and captures a profile.");
ok(!button.hasAttribute("open"), "should start with the panel closed");
ok(!isActive(), "should start with the profiler inactive");
await EventUtils.synthesizeMouseAtCenter(button.icon, {});
ok(isActive(), "should have started the profiler");
await EventUtils.synthesizeMouseAtCenter(button.icon, {});
await waitForProfileAndCloseTab();
});
add_task(async function click_dropmarker() {
info("Test that the profiler icon dropmarker opens the panel.");
ok(!button.hasAttribute("open"), "should start with the panel closed");
ok(!isActive(), "should start with the profiler inactive");
const popupShownPromise = waitForProfilerPopupEvent("popupshown");
await EventUtils.synthesizeMouseAtCenter(button.dropmarker, {});
await popupShownPromise;
info("Ensure the panel is open and the profiler still inactive.");
ok(button.getAttribute("open") == "true", "panel should be open");
ok(!isActive(), "profiler should still be inactive");
await getElementByLabel(document, "Start Recording");
info("Press Escape to close the panel.");
const popupHiddenPromise = waitForProfilerPopupEvent("popuphidden");
EventUtils.synthesizeKey("KEY_Escape");
await popupHiddenPromise;
ok(!button.hasAttribute("open"), "panel should be closed");
});
add_task(async function space_key() {
info("Test that the Space key starts and captures a profile.");
ok(!button.hasAttribute("open"), "should start with the panel closed");
ok(!isActive(), "should start with the profiler inactive");
forceFocus(button);
info("Press Space to start the profiler.");
EventUtils.synthesizeKey(" ");
ok(isActive(), "should have started the profiler");
info("Press Space again to capture the profile.");
EventUtils.synthesizeKey(" ");
await waitForProfileAndCloseTab();
});
add_task(async function enter_key() {
info("Test that the Enter key starts and captures a profile.");
ok(!button.hasAttribute("open"), "should start with the panel closed");
ok(!isActive(), "should start with the profiler inactive");
forceFocus(button);
info("Pressing Enter should start the profiler.");
EventUtils.synthesizeKey("KEY_Enter");
ok(isActive(), "should have started the profiler");
info("Pressing Enter again to capture the profile.");
EventUtils.synthesizeKey("KEY_Enter");
await waitForProfileAndCloseTab();
});
add_task(async function arrowDown_key() {
info("Test that ArrowDown key dropmarker opens the panel.");
ok(!button.hasAttribute("open"), "should start with the panel closed");
ok(!isActive(), "should start with the profiler inactive");
forceFocus(button);
info("Pressing the down arrow should open the panel.");
const popupShownPromise = waitForProfilerPopupEvent("popupshown");
EventUtils.synthesizeKey("KEY_ArrowDown");
await popupShownPromise;
ok(!isActive(), "profiler should still be inactive");
ok(button.getAttribute("open") == "true", "panel should be open");
info("Press Escape to close the panel.");
const popupHiddenPromise = waitForProfilerPopupEvent("popuphidden");
EventUtils.synthesizeKey("KEY_Escape");
await popupHiddenPromise;
ok(!button.hasAttribute("open"), "panel should be closed");
});

View File

@ -239,8 +239,8 @@ async function _toggleOpenProfilerPopup(window) {
const popupShown = waitForProfilerPopupEvent("popupshown");
info("> Trigger a click on the profiler menu button.");
profilerButton.click();
info("> Trigger a click on the profiler button dropmarker.");
await EventUtils.synthesizeMouseAtCenter(profilerButton.dropmarker, {});
if (profilerButton.getAttribute("open") !== "true") {
throw new Error(