Bug 1307227 - Integrate the profiler popup widget into the browser; r=jdescottes

This commit takes the popup, and wires it up the button using the
CustomizableUI interface. It is integrated with the DevTools initialization
code.

Differential Revision: https://phabricator.services.mozilla.com/D31629

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Greg Tatum 2019-05-31 15:54:18 +00:00
parent ae0bdac16c
commit eccc1fe1ef
12 changed files with 351 additions and 14 deletions

View File

@ -625,6 +625,10 @@
<vbox id="PanelUI-developerItems" class="panel-subview-body"/>
</panelview>
<panelview id="PanelUI-profiler" flex="1">
<iframe id="PanelUI-profilerIframe" className="PanelUI-developer-iframe" />
</panelview>
<panelview id="PanelUI-characterEncodingView" flex="1">
<vbox class="panel-subview-body">
<vbox id="PanelUI-characterEncodingView-pinned"

View File

@ -29,6 +29,9 @@ developer-button.label = Developer
# LOCALIZATION NOTE(developer-button.tooltiptext): %S is the keyboard shortcut
developer-button.tooltiptext2 = Open Web developer tools (%S)
profiler-button.label = Profiler
profiler-button.tooltiptext = Record a performance profile
sidebar-button.label = Sidebars
sidebar-button.tooltiptext2 = Show sidebars

View File

@ -250,6 +250,14 @@ toolbar[brighttext] {
list-style-image: url("chrome://browser/skin/developer.svg");
}
#profiler-button {
list-style-image: url("chrome://devtools/skin/images/profiler-stopwatch.svg");
}
#PanelUI-profilerIframe {
width: 352px;
}
#preferences-button {
list-style-image: url("chrome://browser/skin/settings.svg");
}

View File

@ -29,12 +29,10 @@ devtools.jar:
content/performance/index.xul (performance/index.xul)
content/performance-new/index.xhtml (performance-new/index.xhtml)
content/performance-new/frame-script.js (performance-new/frame-script.js)
content/performance-new/popup/background.jsm (performance-new/popup/background.jsm)
content/performance-new/popup/icons/capture-profile-icon.svg (performance-new/popup/icons/capture-profile-icon.svg)
content/performance-new/popup/initializer.js (performance-new/popup/initializer.js)
content/performance-new/popup/popup.css (performance-new/popup/popup.css)
content/performance-new/popup/popup.html (performance-new/popup/popup.html)
content/performance-new/popup/popup.js (performance-new/popup/popup.js)
content/memory/index.xhtml (memory/index.xhtml)
content/framework/toolbox-window.xul (framework/toolbox-window.xul)
content/framework/toolbox-options.xhtml (framework/toolbox-options.xhtml)

View File

@ -44,6 +44,11 @@ browserToolboxMenu.accesskey = e
browserContentToolboxMenu.label = Browser Content Toolbox
browserContentToolboxMenu.accesskey = x
# LOCALIZATION NOTE (toggleProfilerButtonMenu.label): This is the label for the
# application menu item that toggles the profiler button to record performance profiles.
toggleProfilerButtonMenu.label = Enable Profiler Toolbar Icon
toggleProfilerButtonMenu.accesskey = P
webide.label = WebIDE
webide.accesskey = W

View File

@ -38,6 +38,7 @@ loader.lazyRequireGetter(this, "openDocLink", "devtools/client/shared/link", tru
loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
loader.lazyImporter(this, "ProfilerMenuButton", "resource://devtools/client/performance-new/popup/menu-button.jsm");
const isAboutDebuggingEnabled =
Services.prefs.getBoolPref("devtools.aboutdebugging.new-enabled", false);
@ -100,6 +101,13 @@ exports.menuitems = [
},
keyId: "browserConsole",
},
{ id: "menu_toggleProfilerButtonMenu",
l10nKey: "toggleProfilerButtonMenu",
checkbox: true,
oncommand(event) {
ProfilerMenuButton.toggle(event.target.ownerDocument);
},
},
{ id: "menu_responsiveUI",
l10nKey: "responsiveDesignMode",
oncommand(event) {

View File

@ -0,0 +1,150 @@
/* 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";
/**
* This file controls the enabling and disabling of the menu button for the profiler.
* Care should be taken to keep it minimal as it can be run with browser initialization.
*/
ChromeUtils.defineModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "CustomizableUI",
"resource:///modules/CustomizableUI.jsm");
ChromeUtils.defineModuleGetter(this, "CustomizableWidgets",
"resource:///modules/CustomizableWidgets.jsm");
// The profiler's menu button and its popup can be enabled/disabled by the user.
// This is the pref to control whether the user has turned it on or not.
// This pref is repeated across many files in order to avoid loading this file if
// it's not needed. Make sure and search the rest of the codebase for other uses.
const BUTTON_ENABLED_PREF = "devtools.performance.popup.enabled";
const WIDGET_ID = "profiler-button";
function isEnabled() {
return Services.prefs.getBoolPref(BUTTON_ENABLED_PREF, false);
}
function setMenuItemChecked(document, isChecked) {
const menuItem = document.querySelector("#menu_toggleProfilerButtonMenu");
if (!menuItem) {
return;
}
menuItem.setAttribute("checked", isChecked);
}
/**
* Toggle the menu button, and initialize the widget if needed.
*
* @param {Object} document - The browser's document.
*/
function toggle(document) {
const toggledValue = !isEnabled();
Services.prefs.setBoolPref(BUTTON_ENABLED_PREF, toggledValue);
if (toggledValue) {
initialize();
CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR);
} else {
setMenuItemChecked(document, false);
CustomizableUI.destroyWidget(WIDGET_ID);
// The widgets are not being properly destroyed. This is a workaround
// until Bug 1552565 lands.
const element = document.getElementById("PanelUI-profiler");
delete element._addedEventListeners;
}
}
/**
* This is a utility function to get the iframe from an event.
* @param {Object} The event fired by the CustomizableUI interface, contains a target.
*/
function getIframeFromEvent(event) {
const panelview = event.target;
const document = panelview.ownerDocument;
// Create the iframe, and append it.
const iframe = document.getElementById("PanelUI-profilerIframe");
if (!iframe) {
throw new Error("Unable to select the PanelUI-profilerIframe.");
}
return iframe;
}
/**
* This function creates the widget definition for the CustomizableUI. It should
* only be run if the profiler button is enabled.
*/
function initialize() {
const widget = CustomizableUI.getWidget(WIDGET_ID);
if (widget && widget.provider == CustomizableUI.PROVIDER_API) {
// This widget has already been created.
return;
}
let observer;
const item = {
id: WIDGET_ID,
type: "view",
viewId: "PanelUI-profiler",
tooltiptext: "profiler-button.tooltiptext",
onViewShowing: (event) => {
const iframe = getIframeFromEvent(event);
iframe.src = "chrome://devtools/content/performance-new/popup/popup.html";
// Provide a mechanism for the iframe to close the popup.
iframe.contentWindow.gClosePopup = () => {
CustomizableUI.hidePanelForNode(iframe);
};
// Provide a mechanism for the iframe to resize the popup.
iframe.contentWindow.gResizePopup = height => {
iframe.style.height = `${Math.min(600, height)}px`;
};
},
onViewHiding(event) {
const iframe = getIframeFromEvent(event);
// Unset the iframe src so that when the popup DOM element is moved, the popup's
// contents aren't re-initialized.
iframe.src = "";
},
onBeforeCreated: (document) => {
setMenuItemChecked(document, true);
observer = {
observe(subject, topic, data) {
switch (topic) {
case "profiler-started": {
const button = document.querySelector("#profiler-button");
if (button) {
// Photon blue-60.
button.style.fill = "#0060df";
}
break;
}
case "profiler-stopped": {
const button = document.querySelector("#profiler-button");
if (button) {
button.style.fill = "";
}
break;
}
}
},
};
Services.obs.addObserver(observer, "profiler-started");
Services.obs.addObserver(observer, "profiler-stopped");
},
onDestroyed: () => {
Services.obs.removeObserver(observer, "profiler-started");
Services.obs.removeObserver(observer, "profiler-stopped");
},
};
CustomizableUI.createWidget(item);
CustomizableWidgets.push(item);
}
const ProfilerMenuButton = { toggle, initialize };
var EXPORTED_SYMBOLS = ["ProfilerMenuButton"];

View File

@ -5,7 +5,7 @@
DevToolsModules(
'background.jsm',
'initializer.js',
'menu-button.jsm',
'popup.js',
)

View File

@ -99,6 +99,7 @@ function initializePopup() {
.addEventListener("click", async () => {
if (document.documentElement.classList.contains("status-running")) {
await background.captureProfile();
window.gClosePopup();
}
});
@ -175,6 +176,12 @@ function renderState(state) {
}
renderControls(state);
window.requestAnimationFrame(() => {
if (window.gResizePopup) {
window.gResizePopup(document.body.clientHeight);
}
});
}
function renderControls(state) {

View File

@ -2,5 +2,5 @@
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<path fill="context-fill" d="M10.85 6.85a.5.5 0 0 0-.7-.7l-2.5 2.5.7.7 2.5-2.5zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 1a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1zM8 4a5 5 0 1 0 0 10A5 5 0 0 0 8 4zM1 9a7 7 0 1 1 14 0A7 7 0 0 1 1 9z"/>
<path fill="context-fill" fill-opacity="context-fill-opacity" d="M10.85 6.85a.5.5 0 0 0-.7-.7l-2.5 2.5.7.7 2.5-2.5zM8 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2zM5 1a1 1 0 0 1 1-1h4a1 1 0 1 1 0 2H6a1 1 0 0 1-1-1zM8 4a5 5 0 1 0 0 10A5 5 0 0 0 8 4zM1 9a7 7 0 1 1 14 0A7 7 0 0 1 1 9z"/>
</svg>

Before

Width:  |  Height:  |  Size: 541 B

After

Width:  |  Height:  |  Size: 577 B

View File

@ -13,6 +13,7 @@
* - Inject the wrench icon in toolbar customization, which is used
* by the "Web Developer" list displayed in the hamburger menu,
* - Register the JSON Viewer protocol handler.
* - Inject the profiler recording button in toolbar customization.
*
* Only once any of these entry point is fired, this module ensures starting
* core modules like 'devtools-browser.js' that hooks the browser windows
@ -29,6 +30,7 @@ const kDebuggerPrefs = [
const DEVTOOLS_ENABLED_PREF = "devtools.enabled";
const DEVTOOLS_POLICY_DISABLED_PREF = "devtools.policy.disabled";
const PROFILER_POPUP_ENABLED_PREF = "devtools.performance.popup.enabled";
const { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
@ -42,6 +44,8 @@ ChromeUtils.defineModuleGetter(this, "CustomizableWidgets",
"resource:///modules/CustomizableWidgets.jsm");
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
ChromeUtils.defineModuleGetter(this, "ProfilerMenuButton",
"resource://devtools/client/performance-new/popup/menu-button.jsm");
// We don't want to spend time initializing the full loader here so we create
// our own lazy require.
@ -182,12 +186,47 @@ XPCOMUtils.defineLazyGetter(this, "KeyShortcuts", function() {
});
}
if (isProfilerButtonEnabled()) {
shortcuts.push(...getProfilerKeyShortcuts());
}
return shortcuts;
});
function getProfilerKeyShortcuts() {
return [
// Start/stop the profiler
{
id: "profilerStartStop",
shortcut: KeyShortcutsBundle.GetStringFromName("profilerStartStop.commandkey"),
modifiers: "control,shift",
},
// Capture a profile
{
id: "profilerCapture",
shortcut: KeyShortcutsBundle.GetStringFromName("profilerCapture.commandkey"),
modifiers: "control,shift",
},
];
}
/**
* Instead of loading the ProfilerMenuButton.jsm file, provide an independent check
* to see if it is turned on.
*/
function isProfilerButtonEnabled() {
return Services.prefs.getBoolPref(PROFILER_POPUP_ENABLED_PREF, false);
}
XPCOMUtils.defineLazyGetter(this, "ProfilerPopupBackground", function() {
return ChromeUtils.import(
"resource://devtools/client/performance-new/popup/background.jsm");
});
function DevToolsStartup() {
this.onEnabledPrefChanged = this.onEnabledPrefChanged.bind(this);
this.onWindowReady = this.onWindowReady.bind(this);
this.toggleProfilerKeyShortcuts = this.toggleProfilerKeyShortcuts.bind(this);
}
DevToolsStartup.prototype = {
@ -217,6 +256,12 @@ DevToolsStartup.prototype = {
*/
developerToggleCreated: false,
/**
* Flag that indicates if the profiler recording popup was already added to
* customizableUI.
*/
profilerRecordingButtonCreated: false,
isDisabledByPolicy: function() {
return Services.prefs.getBoolPref(DEVTOOLS_POLICY_DISABLED_PREF, false);
},
@ -236,10 +281,14 @@ DevToolsStartup.prototype = {
// Only top level Firefox Windows fire a browser-delayed-startup-finished event
Services.obs.addObserver(this.onWindowReady, "browser-delayed-startup-finished");
if (AppConstants.MOZ_DEV_EDITION && !this.isDisabledByPolicy()) {
// On DevEdition, the developer toggle is displayed by default in the navbar area
// and should be created before the first paint.
this.hookDeveloperToggle();
if (!this.isDisabledByPolicy()) {
if (AppConstants.MOZ_DEV_EDITION) {
// On DevEdition, the developer toggle is displayed by default in the navbar
// area and should be created before the first paint.
this.hookDeveloperToggle();
}
this.hookProfilerRecordingButton();
}
// Update menu items when devtools.enabled changes.
@ -360,6 +409,7 @@ DevToolsStartup.prototype = {
// initialized before the first browser-delayed-startup-finished event is received.
// We use a dedicated flag because we still need to hook the developer toggle.
this.hookDeveloperToggle();
this.hookProfilerRecordingButton();
// The developer menu hook only needs to be added if devtools have not been
// initialized yet.
@ -455,6 +505,22 @@ DevToolsStartup.prototype = {
this.developerToggleCreated = true;
},
/**
* Dynamically register a profiler recording button in the
* customization menu. You can use this button by right clicking
* on Firefox toolbar and dragging it from the customization panel
* to the toolbar. (i.e. this isn't displayed by default to users.)
*/
hookProfilerRecordingButton() {
if (this.profilerRecordingButtonCreated) {
return;
}
this.profilerRecordingButtonCreated = true;
if (isProfilerButtonEnabled()) {
ProfilerMenuButton.initialize();
}
},
/*
* We listen to the "Web Developer" system menu, which is under "Tools" main item.
* This menu item is hardcoded empty in Firefox UI. We listen for its opening to
@ -579,20 +645,90 @@ DevToolsStartup.prototype = {
const keyset = doc.createXULElement("keyset");
keyset.setAttribute("id", "devtoolsKeyset");
for (const key of KeyShortcuts) {
const xulKey = this.createKey(doc, key, () => this.onKey(window, key));
keyset.appendChild(xulKey);
}
this.attachKeys(doc, KeyShortcuts, keyset);
// Appending a <key> element is not always enough. The <keyset> needs
// to be detached and reattached to make sure the <key> is taken into
// account (see bug 832984).
const mainKeyset = doc.getElementById("mainKeyset");
mainKeyset.parentNode.insertBefore(keyset, mainKeyset);
// Watch for the profiler to enable or disable the profiler popup, then toggle
// the keyboard shortcuts on and off.
Services.prefs.addObserver(PROFILER_POPUP_ENABLED_PREF,
this.toggleProfilerKeyShortcuts);
},
/**
* This method attaches on the key elements to the devtools keyset.
*/
attachKeys(doc, keyShortcuts, keyset = doc.getElementById("devtoolsKeyset")) {
const window = doc.defaultView;
for (const key of keyShortcuts) {
const xulKey = this.createKey(doc, key, () => this.onKey(window, key));
keyset.appendChild(xulKey);
}
},
/**
* This method removes keys from the devtools keyset.
*/
removeKeys(doc, keyShortcuts) {
for (const key of keyShortcuts) {
const keyElement = doc.getElementById(this.getKeyElementId(key));
if (keyElement) {
keyElement.remove();
}
}
},
/**
* We only want to have the keyboard shortcuts active when the menu button is on.
* This function either adds or removes the elements.
*/
toggleProfilerKeyShortcuts() {
const isEnabled = isProfilerButtonEnabled();
const profilerKeyShortcuts = getProfilerKeyShortcuts();
for (const { document } of Services.wm.getEnumerator(null)) {
const devtoolsKeyset = document.getElementById("devtoolsKeyset");
const mainKeyset = document.getElementById("mainKeyset");
if (!devtoolsKeyset || !mainKeyset) {
// There may not be devtools keyset on this window.
continue;
}
if (isEnabled) {
this.attachKeys(document, profilerKeyShortcuts);
} else {
this.removeKeys(document, profilerKeyShortcuts);
}
// Appending a <key> element is not always enough. The <keyset> needs
// to be detached and reattached to make sure the <key> is taken into
// account (see bug 832984).
mainKeyset.parentNode.insertBefore(devtoolsKeyset, mainKeyset);
}
if (!isEnabled) {
// Ensure the profiler isn't left profiling in the background.
ProfilerPopupBackground.stopProfiler();
}
},
async onKey(window, key) {
try {
// The profiler doesn't care if DevTools is loaded, so provide a quick check
// first to bail out of checking if DevTools is available.
switch (key.id) {
case "profilerStartStop": {
ProfilerPopupBackground.toggleProfiler();
return;
}
case "profilerCapture": {
ProfilerPopupBackground.captureProfile();
return;
}
}
if (!Services.prefs.getBoolPref(DEVTOOLS_ENABLED_PREF)) {
const id = key.toolId || key.id;
this.openInstallPage("KeyShortcut", id);
@ -610,13 +746,23 @@ DevToolsStartup.prototype = {
}
},
getKeyElementId({ id, toolId }) {
return "key_" + (id || toolId);
},
// Create a <xul:key> DOM Element
createKey(doc, { id, toolId, shortcut, modifiers: mod }, oncommand) {
createKey(doc, key, oncommand) {
const { shortcut, modifiers: mod } = key;
const k = doc.createXULElement("key");
k.id = "key_" + (id || toolId);
k.id = this.getKeyElementId(key);
if (shortcut.startsWith("VK_")) {
k.setAttribute("keycode", shortcut);
if (shortcut.match(/^VK_\d$/)) {
// Add the event keydown attribute to ensure that shortcuts work for combinations
// such as ctrl shift 1.
k.setAttribute("event", "keydown");
}
} else {
k.setAttribute("key", shortcut);
}

View File

@ -61,3 +61,11 @@ dom.commandkey=W
# LOCALIZATION NOTE (accessibilityF12.commandkey):
# Key pressed to open a toolbox with the accessibility panel selected
accessibilityF12.commandkey=VK_F12
# LOCALIZATION NOTE (profilerStartStop.commandkey):
# Key pressed to start or stop the performance profiler
profilerStartStop.commandkey=VK_1
# LOCALIZATION NOTE (profilerCapture.commandkey):
# Key pressed to capture a recorded performance profile
profilerCapture.commandkey=VK_2