Bug 1667061 - Add ProfilerViewMode for preset and append view querystring while opening the front-end. r=gregtatum

Depends on D91266

Differential Revision: https://phabricator.services.mozilla.com/D91267
This commit is contained in:
Nazım Can Altınova 2020-12-04 10:47:22 +00:00
parent 349951062c
commit f4d36497aa
8 changed files with 178 additions and 8 deletions

View File

@ -117,6 +117,7 @@ export interface State {
threads: string[];
objdirs: string[];
presetName: string;
profilerViewMode: ProfilerViewMode | undefined;
initializedValues: InitializedValues | null;
promptEnvRestart: null | string;
}
@ -168,6 +169,7 @@ export type GetSymbolTableCallback = (
export type ReceiveProfile = (
geckoProfile: MinimallyTypedGeckoProfile,
profilerViewMode: ProfilerViewMode | undefined,
getSymbolTableCallback: GetSymbolTableCallback
) => void;
@ -426,6 +428,12 @@ export interface ScaleFunctions {
fromFractionToSingleDigitValue: NumberScaler;
}
/**
* View mode for the Firefox Profiler front-end timeline.
* `undefined` is defaulted to full automatically.
*/
export type ProfilerViewMode = "full" | "active-tab" | "origins";
export interface PresetDefinition {
label: string;
description: string;
@ -434,6 +442,7 @@ export interface PresetDefinition {
features: string[];
threads: string[];
duration: number;
profilerViewMode?: ProfilerViewMode;
}
export interface Presets {

View File

@ -18,6 +18,7 @@
* @typedef {import("./@types/perf").GetEnvironmentVariable} GetEnvironmentVariable
* @typedef {import("./@types/perf").GetActiveBrowsingContextID} GetActiveBrowsingContextID
* @typedef {import("./@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
* * @typedef {import("./@types/perf").ProfilerViewMode} ProfilerViewMode
*/
const ChromeUtils = require("ChromeUtils");
@ -62,13 +63,16 @@ const UI_BASE_URL_PATH_DEFAULT = "/from-addon";
* into a new browser tab, and injects the profile via a frame script.
*
* @param {MinimallyTypedGeckoProfile} profile - The Gecko profile.
* @param {ProfilerViewMode | undefined} profilerViewMode - View mode for the Firefox Profiler
* front-end timeline. While opening the url, we should append a query string
* if a view other than "full" needs to be displayed.
* @param {GetSymbolTableCallback} getSymbolTableCallback - A callback function with the signature
* (debugName, breakpadId) => Promise<SymbolTableAsTuple>, which will be invoked
* when profiler.firefox.com sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This
* function should obtain a symbol table for the requested binary and resolve the
* returned promise with it.
*/
function receiveProfile(profile, getSymbolTableCallback) {
function receiveProfile(profile, profilerViewMode, getSymbolTableCallback) {
const Services = lazy.Services();
// Find the most recently used window, as the DevTools client could be in a variety
// of hosts.
@ -90,11 +94,22 @@ function receiveProfile(profile, getSymbolTableCallback) {
UI_BASE_URL_PATH_DEFAULT
);
const tab = browser.addWebTab(`${baseUrl}${baseUrlPath}`, {
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
userContextId: browser.contentPrincipal.userContextId,
}),
});
// We automatically open up the "full" mode if no query string is present.
// `undefined` also means nothing is specified, and it should open the "full"
// timeline view in that case.
const viewModeQueryString =
profilerViewMode !== undefined && profilerViewMode !== "full"
? `?view=${profilerViewMode}`
: "";
const tab = browser.addWebTab(
`${baseUrl}${baseUrlPath}${viewModeQueryString}`,
{
triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal({
userContextId: browser.contentPrincipal.userContextId,
}),
}
);
browser.selectedTab = tab;
const mm = tab.linkedBrowser.messageManager;
mm.loadFrameScript(

View File

@ -33,6 +33,7 @@ const AppConstants = ChromeUtils.import(
* @typedef {import("../@types/perf").MessageFromFrontend} MessageFromFrontend
* @typedef {import("../@types/perf").PageContext} PageContext
* @typedef {import("../@types/perf").Presets} Presets
* @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode
*/
/** @type {PerformancePref["Entries"]} */
@ -93,6 +94,7 @@ const presets = {
features: ["screenshots", "js"],
threads: ["GeckoMain", "Compositor", "Renderer", "DOM Worker"],
duration: 0,
profilerViewMode: "active-tab",
},
"firefox-platform": {
label: "Firefox Platform",
@ -192,6 +194,29 @@ async function getSymbolsFromThisBrowser(pageContext, debugName, breakpadId) {
return getSymbolTableMultiModal(lib, objdirs);
}
/**
* Return the proper view mode for the Firefox Profiler front-end timeline by
* looking at the proper preset that is selected.
* Return value can be undefined when the preset is unknown or custom.
* @param {PageContext} pageContext
* @return {ProfilerViewMode | undefined}
*/
function getProfilerViewModeForCurrentPreset(pageContext) {
const postfix = getPrefPostfix(pageContext);
const presetName = Services.prefs.getCharPref(PRESET_PREF + postfix);
if (presetName === "custom") {
return undefined;
}
const preset = presets[presetName];
if (!preset) {
console.error(`Unknown profiler preset was encountered: "${presetName}"`);
return undefined;
}
return preset.profilerViewMode;
}
/**
* This function is called directly by devtools/startup/DevToolsStartup.jsm when
* using the shortcut keys to capture a profile.
@ -221,8 +246,9 @@ async function captureProfile(pageContext) {
}
);
const profilerViewMode = getProfilerViewModeForCurrentPreset(pageContext);
const receiveProfile = lazy.BrowserModule().receiveProfile;
receiveProfile(profile, (debugName, breakpadId) => {
receiveProfile(profile, profilerViewMode, (debugName, breakpadId) => {
return getSymbolsFromThisBrowser(pageContext, debugName, breakpadId);
});

View File

@ -212,7 +212,8 @@ exports.getProfileAndStopProfiler = () => {
const getSymbolTable = selectors.getSymbolTableGetter(getState())(profile);
const receiveProfile = selectors.getReceiveProfileFn(getState());
receiveProfile(profile, getSymbolTable);
const profilerViewMode = selectors.getProfilerViewMode(getState());
receiveProfile(profile, profilerViewMode, getSymbolTable);
dispatch(changeRecordingState("available-to-record"));
};
};

View File

@ -16,6 +16,7 @@
* @typedef {import("../@types/perf").GetEnvironmentVariable} GetEnvironmentVariable
* @typedef {import("../@types/perf").PageContext} PageContext
* @typedef {import("../@types/perf").Presets} Presets
* @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode
*/
/**
* @template S
@ -56,6 +57,9 @@ const getPresets = state => getInitializedValues(state).presets;
/** @type {Selector<string>} */
const getPresetName = state => state.presetName;
/** @type {Selector<ProfilerViewMode | undefined>} */
const getProfilerViewMode = state => state.profilerViewMode;
/**
* When remote profiling, there will be a back button to the settings.
*
@ -160,6 +164,7 @@ module.exports = {
getObjdirs,
getPresets,
getPresetName,
getProfilerViewMode,
getOpenRemoteDevTools,
getOpenAboutProfiling,
getRecordingSettings,

View File

@ -29,6 +29,7 @@ skip-if = tsan # Frequently times out on TSan
[browser_webchannel-enable-menu-button.js]
[browser_popup-profiler-states.js]
[browser_popup-record-capture.js]
[browser_popup-record-capture-view.js]
[browser_popup-record-discard.js]
# The popupshown and popuphidden events are not firing correctly on linux, as of

View File

@ -0,0 +1,67 @@
/* 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 FRONTEND_BASE_URL =
"http://example.com/browser/devtools/client/performance-new/test/browser/fake-frontend.html";
add_task(async function test() {
info(
"Test that the profiler pop-up correctly opens the captured profile on the " +
"correct frontend view by adding proper view query string"
);
await setProfilerFrontendUrl(FRONTEND_BASE_URL);
await makeSureProfilerPopupIsEnabled();
// First check for "firefox-platform" preset which will have no "view" query
// string because this is where our traditional "full" view opens up.
await openPopupAndAssertUrlForPreset({
preset: "firefox-platform",
expectedUrl: FRONTEND_BASE_URL,
});
// Now, let's check for "web-developer" preset. This will open up the frontend
// with "active-tab" view query string. Frontend will understand and open the active tab view for it.
await openPopupAndAssertUrlForPreset({
preset: "web-developer",
expectedUrl: FRONTEND_BASE_URL + "?view=active-tab",
});
});
async function openPopupAndAssertUrlForPreset({ preset, expectedUrl }) {
// First, switch to the preset we want to test.
BackgroundJSM.changePreset(
"aboutprofiling",
preset,
[] // We don't need any features for this test.
);
// Let's capture a profile and assert newly created tab's url.
await openPopupAndEnsureCloses(window, async () => {
{
const button = await getElementByLabel(document, "Start Recording");
info("Click the button to start recording.");
button.click();
}
{
const button = await getElementByLabel(document, "Capture");
info("Click the button to capture the recording.");
button.click();
}
info(
"If the profiler successfully captures a profile, it will create a new " +
"tab with the proper view query string depending on the preset."
);
await waitForTabUrl({
initialTitle: "Waiting on the profile",
successTitle: "Profile received",
errorTitle: "Error",
expectedUrl,
});
});
}

View File

@ -365,6 +365,52 @@ async function checkTabLoadedProfile({
});
}
/**
* This function checks the url of a tab so we can assert the frontend's url
* with our expected url. This function runs in a loop every
* requestAnimationFrame, and checks for a initialTitle. Asserts as soon as it
* finds that title. We don't have to look for success title or error title
* since we only care about the url.
* @param {{
* initialTitle: string,
* successTitle: string,
* errorTitle: string,
* expectedUrl: string
* }}
*/
async function waitForTabUrl({
initialTitle,
successTitle,
errorTitle,
expectedUrl,
}) {
const logPeriodically = createPeriodicLogger();
info(`Waiting for the selected tab to have the url "${expectedUrl}".`);
return waitUntil(() => {
switch (gBrowser.selectedTab.textContent) {
case initialTitle:
case successTitle:
if (gBrowser.currentURI.spec === expectedUrl) {
ok(true, `The selected tab has the url ${expectedUrl}`);
BrowserTestUtils.removeTab(gBrowser.selectedTab);
return true;
}
throw new Error(
`Found a different url on the fake frontend: ${gBrowser.currentURI.spec}`
);
case errorTitle:
throw new Error(
"The fake frontend indicated that there was an error injecting the profile."
);
default:
logPeriodically(`> Waiting for the fake frontend tab to be loaded.`);
return false;
}
});
}
/**
* This function checks the document title of a tab as an easy way to pass
* messages from a content page to the mochitest.