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:
Markus Stange 2021-11-10 21:20:34 +00:00
Родитель e97b882edc
Коммит b72813b697
9 изменённых файлов: 293 добавлений и 337 удалений

Просмотреть файл

@ -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
*/
/**