зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1625309 - Remove the frame script and instead use the WebChannel to expose the profile and symbolication. r=julienw
Differential Revision: https://phabricator.services.mozilla.com/D121933
This commit is contained in:
Родитель
e97b882edc
Коммит
b72813b697
|
@ -341,7 +341,6 @@ devtools.jar:
|
|||
# Perfomance
|
||||
content/performance/index.xhtml (performance/index.xhtml)
|
||||
content/performance-new/index.xhtml (performance-new/index.xhtml)
|
||||
content/performance-new/frame-script.js (performance-new/frame-script.js)
|
||||
|
||||
# Memory
|
||||
content/memory/index.xhtml (memory/index.xhtml)
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
/* 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/. */
|
||||
|
||||
/**
|
||||
* This file contains the globals for the Gecko Profiler frame script environment.
|
||||
*/
|
||||
|
||||
interface ContentWindow {
|
||||
wrappedJSObject: {
|
||||
connectToGeckoProfiler?: (
|
||||
interface: import("./perf").GeckoProfilerFrameScriptInterface
|
||||
) => void;
|
||||
};
|
||||
}
|
||||
|
||||
declare var content: ContentWindow;
|
|
@ -291,7 +291,6 @@ declare namespace MockedExports {
|
|||
* Then add the file path to the KnownModules above.
|
||||
*/
|
||||
import: <S extends keyof KnownModules>(module: S) => KnownModules[S];
|
||||
createObjectIn: (content: ContentWindow) => object;
|
||||
exportFunction: (fn: Function, scope: object, options?: object) => void;
|
||||
cloneInto: (value: any, scope: object, options?: object) => void;
|
||||
isInAutomation: boolean;
|
||||
|
|
|
@ -452,26 +452,85 @@ export interface Presets {
|
|||
[presetName: string]: PresetDefinition;
|
||||
}
|
||||
|
||||
export type MessageFromFrontend =
|
||||
| {
|
||||
type: "STATUS_QUERY";
|
||||
requestId: number;
|
||||
}
|
||||
| {
|
||||
type: "ENABLE_MENU_BUTTON";
|
||||
requestId: number;
|
||||
};
|
||||
// Should be kept in sync with the types in https://github.com/firefox-devtools/profiler/blob/main/src/app-logic/web-channel.js .
|
||||
// Compatibility is handled as follows:
|
||||
// - The front-end needs to worry about compatibility and handle older browser versions.
|
||||
// - The browser can require the latest front-end version and does not need to keep any legacy functionality for older front-end versions.
|
||||
|
||||
export type MessageToFrontend =
|
||||
| {
|
||||
type: "STATUS_RESPONSE";
|
||||
menuButtonIsEnabled: boolean;
|
||||
requestId: number;
|
||||
}
|
||||
| {
|
||||
type: "ENABLE_MENU_BUTTON_DONE";
|
||||
requestId: number;
|
||||
};
|
||||
type MessageFromFrontend = {
|
||||
requestId: number;
|
||||
} & RequestFromFrontend;
|
||||
|
||||
export type RequestFromFrontend =
|
||||
| StatusQueryRequest
|
||||
| EnableMenuButtonRequest
|
||||
| GetProfileRequest
|
||||
| GetSymbolTableRequest
|
||||
| QuerySymbolicationApiRequest;
|
||||
|
||||
type StatusQueryRequest = { type: "STATUS_QUERY" };
|
||||
type EnableMenuButtonRequest = { type: "ENABLE_MENU_BUTTON" };
|
||||
type GetProfileRequest = { type: "GET_PROFILE" };
|
||||
type GetSymbolTableRequest = {
|
||||
type: "GET_SYMBOL_TABLE";
|
||||
debugName: string;
|
||||
breakpadId: string;
|
||||
};
|
||||
type QuerySymbolicationApiRequest = {
|
||||
type: "QUERY_SYMBOLICATION_API";
|
||||
path: string;
|
||||
requestJson: string;
|
||||
};
|
||||
|
||||
export type MessageToFrontend<R> =
|
||||
| OutOfBandErrorMessageToFrontend
|
||||
| ErrorResponseMessageToFrontend
|
||||
| SuccessResponseMessageToFrontend<R>;
|
||||
|
||||
type OutOfBandErrorMessageToFrontend = {
|
||||
errno: number;
|
||||
error: string;
|
||||
};
|
||||
|
||||
type ErrorResponseMessageToFrontend = {
|
||||
type: "ERROR_RESPONSE";
|
||||
requestId: number;
|
||||
error: string;
|
||||
};
|
||||
|
||||
type SuccessResponseMessageToFrontend<R> = {
|
||||
type: "SUCCESS_RESPONSE";
|
||||
requestId: number;
|
||||
response: R;
|
||||
};
|
||||
|
||||
export type ResponseToFrontend =
|
||||
| StatusQueryResponse
|
||||
| EnableMenuButtonResponse
|
||||
| GetProfileResponse
|
||||
| GetSymbolTableResponse
|
||||
| QuerySymbolicationApiResponse;
|
||||
|
||||
type StatusQueryResponse = {
|
||||
menuButtonIsEnabled: boolean;
|
||||
// The version indicates which message types are supported by the browser.
|
||||
// No version:
|
||||
// Shipped in Firefox 76.
|
||||
// Supports the following message types:
|
||||
// - STATUS_QUERY
|
||||
// - ENABLE_MENU_BUTTON
|
||||
// Version 1:
|
||||
// Shipped in Firefox 93.
|
||||
// Adds support for the following message types:
|
||||
// - GET_PROFILE
|
||||
// - GET_SYMBOL_TABLE
|
||||
// - QUERY_SYMBOLICATION_API
|
||||
version: number;
|
||||
};
|
||||
type EnableMenuButtonResponse = void;
|
||||
type GetProfileResponse = ArrayBuffer | MinimallyTypedGeckoProfile;
|
||||
type GetSymbolTableResponse = SymbolTableAsTuple;
|
||||
type QuerySymbolicationApiResponse = string;
|
||||
|
||||
/**
|
||||
* This represents an event channel that can talk to a content page on the web.
|
||||
|
@ -484,7 +543,7 @@ export type MessageToFrontend =
|
|||
export class ProfilerWebChannel {
|
||||
constructor(id: string, url: MockedExports.nsIURI);
|
||||
send: (
|
||||
message: MessageToFrontend,
|
||||
message: MessageToFrontend<ResponseToFrontend>,
|
||||
target: MockedExports.WebChannelTarget
|
||||
) => void;
|
||||
listen: (
|
||||
|
@ -496,6 +555,26 @@ export class ProfilerWebChannel {
|
|||
) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The per-tab information that is stored when a new profile is captured
|
||||
* and a profiler tab is opened, to serve the correct profile to the tab
|
||||
* that sends the WebChannel message.
|
||||
*/
|
||||
export type ProfilerBrowserInfo = {
|
||||
profileCaptureResult: ProfileCaptureResult;
|
||||
symbolicationService: SymbolicationService;
|
||||
};
|
||||
|
||||
export type ProfileCaptureResult =
|
||||
| {
|
||||
type: "SUCCESS";
|
||||
profile: MinimallyTypedGeckoProfile | ArrayBuffer;
|
||||
}
|
||||
| {
|
||||
type: "ERROR";
|
||||
error: Error;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes all of the profiling features that can be turned on and
|
||||
* off in about:profiling.
|
||||
|
|
|
@ -36,10 +36,6 @@ const lazy = createLazyLoaders({
|
|||
),
|
||||
});
|
||||
|
||||
const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
|
||||
const SYMBOL_TABLE_REQUEST_EVENT = "devtools:perf-html-request-symbol-table";
|
||||
const SYMBOL_TABLE_RESPONSE_EVENT = "devtools:perf-html-reply-symbol-table";
|
||||
|
||||
/** @type {PerformancePref["UIBaseUrl"]} */
|
||||
const UI_BASE_URL_PREF = "devtools.performance.recording.ui-base-url";
|
||||
/** @type {PerformancePref["UIBaseUrlPathPref"]} */
|
||||
|
@ -58,23 +54,13 @@ const UI_BASE_URL_PATH_DEFAULT = "/from-addon";
|
|||
/**
|
||||
* Once a profile is received from the actor, it needs to be opened up in
|
||||
* profiler.firefox.com to be analyzed. This function opens up profiler.firefox.com
|
||||
* into a new browser tab, and injects the profile via a frame script.
|
||||
*
|
||||
* @param {MinimallyTypedGeckoProfile | ArrayBuffer | {}} profile - The Gecko profile.
|
||||
* into a new browser tab.
|
||||
* @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 {SymbolicationService} symbolicationService - An object which implements the
|
||||
* SymbolicationService interface, whose getSymbolTable method will be invoked
|
||||
* when profiler.firefox.com sends SYMBOL_TABLE_REQUEST_EVENT messages to us. This
|
||||
* method should obtain a symbol table for the requested binary and resolve the
|
||||
* returned promise with it.
|
||||
* @returns {MockedExports.Browser} The browser for the opened tab.
|
||||
*/
|
||||
function openProfilerAndDisplayProfile(
|
||||
profile,
|
||||
profilerViewMode,
|
||||
symbolicationService
|
||||
) {
|
||||
function openProfilerTab(profilerViewMode) {
|
||||
const Services = lazy.Services();
|
||||
// Find the most recently used window, as the DevTools client could be in a variety
|
||||
// of hosts.
|
||||
|
@ -115,36 +101,7 @@ function openProfilerAndDisplayProfile(
|
|||
}
|
||||
);
|
||||
browser.selectedTab = tab;
|
||||
const mm = tab.linkedBrowser.messageManager;
|
||||
mm.loadFrameScript(
|
||||
"chrome://devtools/content/performance-new/frame-script.js",
|
||||
false
|
||||
);
|
||||
mm.sendAsyncMessage(TRANSFER_EVENT, profile);
|
||||
mm.addMessageListener(SYMBOL_TABLE_REQUEST_EVENT, e => {
|
||||
const { debugName, breakpadId } = e.data;
|
||||
symbolicationService.getSymbolTable(debugName, breakpadId).then(
|
||||
result => {
|
||||
const [addr, index, buffer] = result;
|
||||
mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, {
|
||||
status: "success",
|
||||
debugName,
|
||||
breakpadId,
|
||||
result: [addr, index, buffer],
|
||||
});
|
||||
},
|
||||
error => {
|
||||
// Re-wrap the error object into an object that is Structured Clone-able.
|
||||
const { name, message, lineNumber, fileName } = error;
|
||||
mm.sendAsyncMessage(SYMBOL_TABLE_RESPONSE_EVENT, {
|
||||
status: "error",
|
||||
debugName,
|
||||
breakpadId,
|
||||
error: { name, message, lineNumber, fileName },
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
return tab.linkedBrowser;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -221,7 +178,7 @@ function openFilePickerForObjdir(window, objdirs, changeObjdirs) {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
openProfilerAndDisplayProfile,
|
||||
openProfilerTab,
|
||||
sharedLibrariesFromProfile,
|
||||
restartBrowserWithEnvironmentVariable,
|
||||
getEnvironmentVariable,
|
||||
|
|
|
@ -1,200 +0,0 @@
|
|||
/* 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/. */
|
||||
// @ts-check
|
||||
/// <reference path="./@types/frame-script.d.ts" />
|
||||
/* global content */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @typedef {import("./@types/perf").GetSymbolTableCallback} GetSymbolTableCallback
|
||||
* @typedef {import("./@types/perf").ContentFrameMessageManager} ContentFrameMessageManager
|
||||
* @typedef {import("./@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
|
||||
*/
|
||||
|
||||
/**
|
||||
* This frame script injects itself into profiler.firefox.com and injects the profile
|
||||
* into the page. It is mostly taken from the Gecko Profiler Addon implementation.
|
||||
*/
|
||||
|
||||
const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
|
||||
const SYMBOL_TABLE_REQUEST_EVENT = "devtools:perf-html-request-symbol-table";
|
||||
const SYMBOL_TABLE_RESPONSE_EVENT = "devtools:perf-html-reply-symbol-table";
|
||||
|
||||
/** @type {null | MinimallyTypedGeckoProfile} */
|
||||
let gProfile = null;
|
||||
const symbolReplyPromiseMap = new Map();
|
||||
|
||||
/**
|
||||
* TypeScript wants to use the DOM library definition, which conflicts with our
|
||||
* own definitions for the frame message manager. Instead, coerce the `this`
|
||||
* variable into the proper interface.
|
||||
*
|
||||
* @type {ContentFrameMessageManager}
|
||||
*/
|
||||
let frameScript;
|
||||
{
|
||||
const any = /** @type {any} */ (this);
|
||||
frameScript = any;
|
||||
}
|
||||
|
||||
frameScript.addMessageListener(TRANSFER_EVENT, e => {
|
||||
gProfile = e.data;
|
||||
// Eagerly try and see if the framescript was evaluated after perf loaded its scripts.
|
||||
connectToPage();
|
||||
// If not try again at DOMContentLoaded which should be called after the script
|
||||
// tag was synchronously loaded in.
|
||||
frameScript.addEventListener("DOMContentLoaded", connectToPage);
|
||||
});
|
||||
|
||||
frameScript.addMessageListener(SYMBOL_TABLE_RESPONSE_EVENT, e => {
|
||||
const { debugName, breakpadId, status, result, error } = e.data;
|
||||
const promiseKey = [debugName, breakpadId].join(":");
|
||||
const { resolve, reject } = symbolReplyPromiseMap.get(promiseKey);
|
||||
symbolReplyPromiseMap.delete(promiseKey);
|
||||
|
||||
if (status === "success") {
|
||||
const [addresses, index, buffer] = result;
|
||||
resolve([addresses, index, buffer]);
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
function connectToPage() {
|
||||
const unsafeWindow = content.wrappedJSObject;
|
||||
if (unsafeWindow.connectToGeckoProfiler) {
|
||||
unsafeWindow.connectToGeckoProfiler(
|
||||
makeAccessibleToPage(
|
||||
{
|
||||
getProfile: () =>
|
||||
gProfile
|
||||
? Promise.resolve(gProfile)
|
||||
: Promise.reject(
|
||||
new Error("No profile was available to inject into the page.")
|
||||
),
|
||||
getSymbolTable: (debugName, breakpadId) =>
|
||||
getSymbolTable(debugName, breakpadId),
|
||||
},
|
||||
unsafeWindow
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {GetSymbolTableCallback} */
|
||||
function getSymbolTable(debugName, breakpadId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
frameScript.sendAsyncMessage(SYMBOL_TABLE_REQUEST_EVENT, {
|
||||
debugName,
|
||||
breakpadId,
|
||||
});
|
||||
symbolReplyPromiseMap.set([debugName, breakpadId].join(":"), {
|
||||
resolve,
|
||||
reject,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The following functions handle the security of cloning the object into the page.
|
||||
// The code was taken from the original Gecko Profiler Add-on to maintain
|
||||
// compatibility with the existing profile importing mechanism:
|
||||
// See: https://github.com/firefox-devtools/Gecko-Profiler-Addon/blob/78138190b42565f54ce4022a5b28583406489ed2/data/tab-framescript.js
|
||||
|
||||
/**
|
||||
* Create a promise that can be used in the page.
|
||||
*
|
||||
* @template T
|
||||
* @param {(resolve: Function, reject: Function) => Promise<T>} fun
|
||||
* @param {any} contentGlobal
|
||||
* @returns Promise<T>
|
||||
*/
|
||||
function createPromiseInPage(fun, contentGlobal) {
|
||||
/**
|
||||
* Use the any type here, as this is pretty dynamic, and probably not worth typing.
|
||||
* @param {any} resolve
|
||||
* @param {any} reject
|
||||
*/
|
||||
function funThatClonesObjects(resolve, reject) {
|
||||
return fun(
|
||||
/** @type {(result: any) => any} */
|
||||
result => resolve(Cu.cloneInto(result, contentGlobal)),
|
||||
/** @type {(result: any) => any} */
|
||||
error => {
|
||||
if (error.name) {
|
||||
// Turn the JSON error object into a real Error object.
|
||||
const { name, message, fileName, lineNumber } = error;
|
||||
const ErrorObjConstructor =
|
||||
name in contentGlobal &&
|
||||
contentGlobal.Error.isPrototypeOf(contentGlobal[name])
|
||||
? contentGlobal[name]
|
||||
: contentGlobal.Error;
|
||||
const e = new ErrorObjConstructor(message, fileName, lineNumber);
|
||||
e.name = name;
|
||||
reject(e);
|
||||
} else {
|
||||
reject(Cu.cloneInto(error, contentGlobal));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return new contentGlobal.Promise(
|
||||
Cu.exportFunction(funThatClonesObjects, contentGlobal)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that calls the original function and tries to make the
|
||||
* return value available to the page.
|
||||
* @param {Function} fun
|
||||
* @param {any} contentGlobal
|
||||
* @return {Function}
|
||||
*/
|
||||
function wrapFunction(fun, contentGlobal) {
|
||||
return function() {
|
||||
// @ts-ignore - Ignore the use of `this`.
|
||||
const result = fun.apply(this, arguments);
|
||||
if (typeof result === "object") {
|
||||
if ("then" in result && typeof result.then === "function") {
|
||||
// fun returned a promise.
|
||||
return createPromiseInPage(
|
||||
(resolve, reject) => result.then(resolve, reject),
|
||||
contentGlobal
|
||||
);
|
||||
}
|
||||
return Cu.cloneInto(result, contentGlobal);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a simple object containing values that are objects or functions.
|
||||
* The objects or functions are wrapped in such a way that they can be
|
||||
* consumed by the page.
|
||||
* @template T
|
||||
* @param {T} obj
|
||||
* @param {any} contentGlobal
|
||||
* @return {T}
|
||||
*/
|
||||
function makeAccessibleToPage(obj, contentGlobal) {
|
||||
/** @type {any} - This value is probably too dynamic to type. */
|
||||
const result = Cu.createObjectIn(contentGlobal);
|
||||
for (const field in obj) {
|
||||
switch (typeof obj[field]) {
|
||||
case "function":
|
||||
// @ts-ignore - Ignore the obj[field] call. This code is too dynamic.
|
||||
Cu.exportFunction(wrapFunction(obj[field], contentGlobal), result, {
|
||||
defineAs: field,
|
||||
});
|
||||
break;
|
||||
case "object":
|
||||
Cu.cloneInto(obj[field], result, { defineAs: field });
|
||||
break;
|
||||
default:
|
||||
result[field] = obj[field];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
* @typedef {import("./@types/perf").PanelWindow} PanelWindow
|
||||
* @typedef {import("./@types/perf").Store} Store
|
||||
* @typedef {import("./@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
|
||||
* @typedef {import("./@types/perf").ProfileCaptureResult} ProfileCaptureResult
|
||||
* @typedef {import("./@types/perf").ProfilerViewMode} ProfilerViewMode
|
||||
*/
|
||||
"use strict";
|
||||
|
@ -64,13 +65,17 @@ const selectors = require("devtools/client/performance-new/store/selectors");
|
|||
const reducers = require("devtools/client/performance-new/store/reducers");
|
||||
const actions = require("devtools/client/performance-new/store/actions");
|
||||
const {
|
||||
openProfilerAndDisplayProfile,
|
||||
openProfilerTab,
|
||||
sharedLibrariesFromProfile,
|
||||
} = require("devtools/client/performance-new/browser");
|
||||
const { createLocalSymbolicationService } = ChromeUtils.import(
|
||||
"resource://devtools/client/performance-new/symbolication.jsm.js"
|
||||
);
|
||||
const { presets, getProfilerViewModeForCurrentPreset } = ChromeUtils.import(
|
||||
const {
|
||||
presets,
|
||||
getProfilerViewModeForCurrentPreset,
|
||||
registerProfileCaptureForBrowser,
|
||||
} = ChromeUtils.import(
|
||||
"resource://devtools/client/performance-new/popup/background.jsm.js"
|
||||
);
|
||||
|
||||
|
@ -140,9 +145,16 @@ async function gInit(perfFront, pageContext, openAboutProfiling) {
|
|||
objdirs,
|
||||
perfFront
|
||||
);
|
||||
openProfilerAndDisplayProfile(
|
||||
profile,
|
||||
profilerViewMode,
|
||||
const browser = openProfilerTab(profilerViewMode);
|
||||
|
||||
/**
|
||||
* @type {ProfileCaptureResult}
|
||||
*/
|
||||
const profileCaptureResult = { type: "SUCCESS", profile };
|
||||
|
||||
registerProfileCaptureForBrowser(
|
||||
browser,
|
||||
profileCaptureResult,
|
||||
symbolicationService
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,12 +29,17 @@ const AppConstants = ChromeUtils.import(
|
|||
* @typedef {import("../@types/perf").Library} Library
|
||||
* @typedef {import("../@types/perf").PerformancePref} PerformancePref
|
||||
* @typedef {import("../@types/perf").ProfilerWebChannel} ProfilerWebChannel
|
||||
* @typedef {import("../@types/perf").MessageFromFrontend} MessageFromFrontend
|
||||
* @typedef {import("../@types/perf").PageContext} PageContext
|
||||
* @typedef {import("../@types/perf").PrefObserver} PrefObserver
|
||||
* @typedef {import("../@types/perf").PrefPostfix} PrefPostfix
|
||||
* @typedef {import("../@types/perf").Presets} Presets
|
||||
* @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode
|
||||
* @typedef {import("../@types/perf").MessageFromFrontend} MessageFromFrontend
|
||||
* @typedef {import("../@types/perf").RequestFromFrontend} RequestFromFrontend
|
||||
* @typedef {import("../@types/perf").ResponseToFrontend} ResponseToFrontend
|
||||
* @typedef {import("../@types/perf").SymbolicationService} SymbolicationService
|
||||
* @typedef {import("../@types/perf").ProfilerBrowserInfo} ProfilerBrowserInfo
|
||||
* @typedef {import("../@types/perf").ProfileCaptureResult} ProfileCaptureResult
|
||||
*/
|
||||
|
||||
/** @type {PerformancePref["Entries"]} */
|
||||
|
@ -56,6 +61,13 @@ const POPUP_FEATURE_FLAG_PREF = "devtools.performance.popup.feature-flag";
|
|||
/* This will be used to observe all profiler-related prefs. */
|
||||
const PREF_PREFIX = "devtools.performance.recording.";
|
||||
|
||||
// The version of the profiler WebChannel.
|
||||
// This is reported from the STATUS_QUERY message, and identifies the
|
||||
// capabilities of the WebChannel. The front-end can handle old WebChannel
|
||||
// versions and has a full list of versions and capabilities here:
|
||||
// https://github.com/firefox-devtools/profiler/blob/main/src/app-logic/web-channel.js
|
||||
const CURRENT_WEBCHANNEL_VERSION = 1;
|
||||
|
||||
// Lazily load the require function, when it's needed.
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
|
@ -264,12 +276,16 @@ async function captureProfile(pageContext) {
|
|||
// more samples while the parent process waits for subprocess profiles.
|
||||
Services.profiler.Pause();
|
||||
|
||||
const profile = await Services.profiler
|
||||
/**
|
||||
* @type {ProfileCaptureResult}
|
||||
*/
|
||||
const profileCaptureResult = await Services.profiler
|
||||
.getProfileDataAsGzippedArrayBuffer()
|
||||
.catch(
|
||||
/** @type {(e: any) => {}} */ e => {
|
||||
console.error(e);
|
||||
return {};
|
||||
.then(
|
||||
profile => ({ type: "SUCCESS", profile }),
|
||||
error => {
|
||||
console.error(error);
|
||||
return { type: "ERROR", error };
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -283,10 +299,11 @@ async function captureProfile(pageContext) {
|
|||
objdirs
|
||||
);
|
||||
|
||||
const { openProfilerAndDisplayProfile } = lazy.BrowserModule();
|
||||
openProfilerAndDisplayProfile(
|
||||
profile,
|
||||
profilerViewMode,
|
||||
const { openProfilerTab } = lazy.BrowserModule();
|
||||
const browser = openProfilerTab(profilerViewMode);
|
||||
registerProfileCaptureForBrowser(
|
||||
browser,
|
||||
profileCaptureResult,
|
||||
symbolicationService
|
||||
);
|
||||
|
||||
|
@ -609,40 +626,48 @@ function removePrefObserver(observer) {
|
|||
}
|
||||
|
||||
/**
|
||||
* This handler handles any messages coming from the WebChannel from profiler.firefox.com.
|
||||
* This map stores information that is associated with a "profile capturing"
|
||||
* action, so that we can look up this information for WebChannel messages
|
||||
* from the profiler tab.
|
||||
* Most importantly, this stores the captured profile. When the profiler tab
|
||||
* requests the profile, we can respond to the message with the correct profile.
|
||||
* This works even if the request happens long after the tab opened. It also
|
||||
* works for an "old" tab even if new profiles have been captured since that
|
||||
* tab was opened.
|
||||
* Supporting tab refresh is important because the tab sometimes reloads itself:
|
||||
* If an old version of the front-end is cached in the service worker, and the
|
||||
* browser supplies a profile with a newer format version, then the front-end
|
||||
* updates its service worker and reloads itself, so that the updated version
|
||||
* can parse the profile.
|
||||
*
|
||||
* @param {ProfilerWebChannel} channel
|
||||
* @param {string} id
|
||||
* @param {any} message
|
||||
* @param {MockedExports.WebChannelTarget} target
|
||||
* This is a WeakMap so that the profile can be garbage-collected when the tab
|
||||
* is closed.
|
||||
*
|
||||
* @type {WeakMap<MockedExports.Browser, ProfilerBrowserInfo>}
|
||||
*/
|
||||
function handleWebChannelMessage(channel, id, message, target) {
|
||||
if (typeof message !== "object" || typeof message.type !== "string") {
|
||||
console.error(
|
||||
"An malformed message was received by the profiler's WebChannel handler.",
|
||||
message
|
||||
);
|
||||
return;
|
||||
}
|
||||
const messageFromFrontend = /** @type {MessageFromFrontend} */ (message);
|
||||
const { requestId } = messageFromFrontend;
|
||||
switch (messageFromFrontend.type) {
|
||||
const infoForBrowserMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* This handler computes the response for any messages coming
|
||||
* from the WebChannel from profiler.firefox.com.
|
||||
*
|
||||
* @param {RequestFromFrontend} request
|
||||
* @param {MockedExports.Browser} browser - The tab's browser.
|
||||
* @return {Promise<ResponseToFrontend>}
|
||||
*/
|
||||
async function getResponseForMessage(request, browser) {
|
||||
switch (request.type) {
|
||||
case "STATUS_QUERY": {
|
||||
// The content page wants to know if this channel exists. It does, so respond
|
||||
// back to the ping.
|
||||
const { ProfilerMenuButton } = lazy.ProfilerMenuButton();
|
||||
channel.send(
|
||||
{
|
||||
type: "STATUS_RESPONSE",
|
||||
menuButtonIsEnabled: ProfilerMenuButton.isInNavbar(),
|
||||
requestId,
|
||||
},
|
||||
target
|
||||
);
|
||||
break;
|
||||
return {
|
||||
version: CURRENT_WEBCHANNEL_VERSION,
|
||||
menuButtonIsEnabled: ProfilerMenuButton.isInNavbar(),
|
||||
};
|
||||
}
|
||||
case "ENABLE_MENU_BUTTON": {
|
||||
const { ownerDocument } = target.browser;
|
||||
const { ownerDocument } = browser;
|
||||
if (!ownerDocument) {
|
||||
throw new Error(
|
||||
"Could not find the owner document for the current browser while enabling " +
|
||||
|
@ -664,24 +689,124 @@ function handleWebChannelMessage(channel, id, message, target) {
|
|||
// Open the popup with a message.
|
||||
ProfilerMenuButton.openPopup(ownerDocument);
|
||||
|
||||
// Respond back that we've done it.
|
||||
channel.send(
|
||||
{
|
||||
type: "ENABLE_MENU_BUTTON_DONE",
|
||||
requestId,
|
||||
},
|
||||
target
|
||||
// There is no response data for this message.
|
||||
return undefined;
|
||||
}
|
||||
case "GET_PROFILE": {
|
||||
const infoForBrowser = infoForBrowserMap.get(browser);
|
||||
if (infoForBrowser === undefined) {
|
||||
throw new Error("Could not find a profile for this tab.");
|
||||
}
|
||||
const { profileCaptureResult } = infoForBrowser;
|
||||
switch (profileCaptureResult.type) {
|
||||
case "SUCCESS":
|
||||
return profileCaptureResult.profile;
|
||||
case "ERROR":
|
||||
throw profileCaptureResult.error;
|
||||
default:
|
||||
const { UnhandledCaseError } = lazy.Utils();
|
||||
throw new UnhandledCaseError(
|
||||
profileCaptureResult,
|
||||
"profileCaptureResult"
|
||||
);
|
||||
}
|
||||
}
|
||||
case "GET_SYMBOL_TABLE": {
|
||||
const infoForBrowser = infoForBrowserMap.get(browser);
|
||||
if (infoForBrowser === undefined) {
|
||||
throw new Error("Could not find a symbolication service for this tab.");
|
||||
}
|
||||
const { debugName, breakpadId } = request;
|
||||
return infoForBrowser.symbolicationService.getSymbolTable(
|
||||
debugName,
|
||||
breakpadId
|
||||
);
|
||||
}
|
||||
case "QUERY_SYMBOLICATION_API": {
|
||||
const infoForBrowser = infoForBrowserMap.get(browser);
|
||||
if (infoForBrowser === undefined) {
|
||||
throw new Error("Could not find a symbolication service for this tab.");
|
||||
}
|
||||
const { path, requestJson } = request;
|
||||
return infoForBrowser.symbolicationService.querySymbolicationApi(
|
||||
path,
|
||||
requestJson
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(
|
||||
"An unknown message type was received by the profiler's WebChannel handler.",
|
||||
message
|
||||
request
|
||||
);
|
||||
const { UnhandledCaseError } = lazy.Utils();
|
||||
throw new UnhandledCaseError(request, "WebChannel request");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler handles any messages coming from the WebChannel from profiler.firefox.com.
|
||||
*
|
||||
* @param {ProfilerWebChannel} channel
|
||||
* @param {string} id
|
||||
* @param {any} message
|
||||
* @param {MockedExports.WebChannelTarget} target
|
||||
*/
|
||||
async function handleWebChannelMessage(channel, id, message, target) {
|
||||
if (typeof message !== "object" || typeof message.type !== "string") {
|
||||
console.error(
|
||||
"An malformed message was received by the profiler's WebChannel handler.",
|
||||
message
|
||||
);
|
||||
return;
|
||||
}
|
||||
const messageFromFrontend = /** @type {MessageFromFrontend} */ (message);
|
||||
const { requestId } = messageFromFrontend;
|
||||
|
||||
try {
|
||||
const response = await getResponseForMessage(
|
||||
messageFromFrontend,
|
||||
target.browser
|
||||
);
|
||||
channel.send(
|
||||
{
|
||||
type: "SUCCESS_RESPONSE",
|
||||
requestId,
|
||||
response,
|
||||
},
|
||||
target
|
||||
);
|
||||
} catch (error) {
|
||||
channel.send(
|
||||
{
|
||||
type: "ERROR_RESPONSE",
|
||||
requestId,
|
||||
error: `${error.name}: ${error.message}`,
|
||||
},
|
||||
target
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MockedExports.Browser} browser - The tab's browser.
|
||||
* @param {ProfileCaptureResult} profileCaptureResult - The Gecko profile.
|
||||
* @param {SymbolicationService} symbolicationService - An object which implements the
|
||||
* SymbolicationService interface, whose getSymbolTable method will be invoked
|
||||
* when profiler.firefox.com sends GET_SYMBOL_TABLE WebChannel messages to us. This
|
||||
* method should obtain a symbol table for the requested binary and resolve the
|
||||
* returned promise with it.
|
||||
*/
|
||||
function registerProfileCaptureForBrowser(
|
||||
browser,
|
||||
profileCaptureResult,
|
||||
symbolicationService
|
||||
) {
|
||||
infoForBrowserMap.set(browser, {
|
||||
profileCaptureResult,
|
||||
symbolicationService,
|
||||
});
|
||||
}
|
||||
|
||||
// Provide a fake module.exports for the JSM to be properly read by TypeScript.
|
||||
/** @type {any} */ (this).module = { exports: {} };
|
||||
|
||||
|
@ -698,6 +823,7 @@ module.exports = {
|
|||
revertRecordingSettings,
|
||||
changePreset,
|
||||
handleWebChannelMessage,
|
||||
registerProfileCaptureForBrowser,
|
||||
addPrefObserver,
|
||||
removePrefObserver,
|
||||
getProfilerViewModeForCurrentPreset,
|
||||
|
|
|
@ -19,6 +19,7 @@ const {
|
|||
* @typedef {import("../@types/perf").RecordingSettings} RecordingSettings
|
||||
* @typedef {import("../@types/perf").Presets} Presets
|
||||
* @typedef {import("../@types/perf").PanelWindow} PanelWindow
|
||||
* @typedef {import("../@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
Загрузка…
Ссылка в новой задаче