зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1586757 - Add types to everything but the components; r=julienw
Differential Revision: https://phabricator.services.mozilla.com/D47913 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
edef14c2cb
Коммит
0b3c8bde66
|
@ -0,0 +1,17 @@
|
|||
/* 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;
|
|
@ -0,0 +1,183 @@
|
|||
/* 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-TODO - Needs typing.
|
||||
*
|
||||
* This file contains type stubs for loading things from Gecko. All of these
|
||||
* types should be used in the correct places eventually.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Namespace anything that has its types mocked out here. These definitions are
|
||||
* only "good enough" to get the type checking to pass in this directory.
|
||||
* Eventually some more structured solution should be found. This namespace is
|
||||
* global and makes sure that all the definitions inside do not clash with
|
||||
* naming.
|
||||
*/
|
||||
declare namespace MockedExports {
|
||||
interface ChromeUtils {
|
||||
/**
|
||||
* Use a JSDoc import declaration to pull in the correct type.
|
||||
*/
|
||||
import: (path: string) => any;
|
||||
createObjectIn: (content: ContentWindow) => object;
|
||||
exportFunction: (fn: Function, scope: object, options?: object) => void;
|
||||
cloneInto: (value: any, scope: object, options?: object) => void;
|
||||
}
|
||||
|
||||
interface MessageManager {
|
||||
loadFrameScript(url: string, flag: boolean): void;
|
||||
sendAsyncMessage: (event: string, data: any) => void;
|
||||
addMessageListener: (event: string, listener: (event: any) => void) => void;
|
||||
}
|
||||
|
||||
interface Browser {
|
||||
addWebTab: (url: string, options: any) => BrowserTab;
|
||||
contentPrincipal: any;
|
||||
selectedTab: BrowserTab;
|
||||
messageManager: MessageManager;
|
||||
}
|
||||
|
||||
interface BrowserTab {
|
||||
linkedBrowser: Browser;
|
||||
}
|
||||
|
||||
interface ChromeWindow {
|
||||
gBrowser: Browser;
|
||||
}
|
||||
|
||||
type GetPref<T> = (prefName: string, defaultValue?: T) => T;
|
||||
type SetPref<T> = (prefName: string, value?: T) => T;
|
||||
|
||||
type Services = {
|
||||
prefs: {
|
||||
clearUserPref: (prefName: string) => void;
|
||||
getStringPref: GetPref<string>;
|
||||
setStringPref: SetPref<string>;
|
||||
getCharPref: GetPref<string>;
|
||||
setCharPref: SetPref<string>;
|
||||
getIntPref: GetPref<number>;
|
||||
setIntPref: SetPref<number>;
|
||||
getBoolPref: GetPref<boolean>;
|
||||
setBoolPref: SetPref<boolean>;
|
||||
addObserver: any;
|
||||
};
|
||||
profiler: any;
|
||||
platform: string;
|
||||
obs: {
|
||||
addObserver: (observer: object, type: string) => void;
|
||||
removeObserver: (observer: object, type: string) => void;
|
||||
};
|
||||
wm: {
|
||||
getMostRecentWindow: (name: string) => ChromeWindow;
|
||||
};
|
||||
focus: {
|
||||
activeWindow: ChromeWindow;
|
||||
};
|
||||
scriptSecurityManager: any;
|
||||
};
|
||||
|
||||
const ServicesJSM: {
|
||||
Services: Services;
|
||||
};
|
||||
|
||||
const EventEmitter: {
|
||||
decorate: (target: object) => void;
|
||||
};
|
||||
|
||||
const ProfilerGetSymbolsJSM: {
|
||||
ProfilerGetSymbols: {
|
||||
getSymbolTable: (
|
||||
path: string,
|
||||
debugPath: string,
|
||||
breakpadId: string
|
||||
) => any;
|
||||
};
|
||||
};
|
||||
|
||||
const AppConstantsJSM: {
|
||||
AppConstants: {
|
||||
platform: string;
|
||||
};
|
||||
};
|
||||
|
||||
const osfileJSM: {
|
||||
OS: {
|
||||
Path: {
|
||||
split: (
|
||||
path: string
|
||||
) => {
|
||||
absolute: boolean;
|
||||
components: string[];
|
||||
winDrive?: string;
|
||||
};
|
||||
join: (...pathParts: string[]) => string;
|
||||
};
|
||||
File: {
|
||||
stat: (path: string) => Promise<{ isDir: boolean }>;
|
||||
Error: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const Services: Services;
|
||||
}
|
||||
|
||||
declare module "devtools/shared/event-emitter2" {
|
||||
export = MockedExports.EventEmitter;
|
||||
}
|
||||
|
||||
declare module "resource://gre/modules/Services.jsm" {
|
||||
export = MockedExports.ServicesJSM;
|
||||
}
|
||||
|
||||
declare module "Services" {
|
||||
export = MockedExports.Services;
|
||||
}
|
||||
|
||||
declare module "resource://gre/modules/osfile.jsm" {
|
||||
export = MockedExports.osfileJSM;
|
||||
}
|
||||
|
||||
declare module "resource://gre/modules/AppConstants.jsm" {
|
||||
export = MockedExports.AppConstantsJSM;
|
||||
}
|
||||
|
||||
declare module "resource://gre/modules/ProfilerGetSymbols.jsm" {
|
||||
export = MockedExports.ProfilerGetSymbolsJSM;
|
||||
}
|
||||
|
||||
declare var ChromeUtils: MockedExports.ChromeUtils;
|
||||
declare var Cu: MockedExports.ChromeUtils;
|
||||
|
||||
/**
|
||||
* This is a variant on the normal Document, as it contains chrome-specific properties.
|
||||
*/
|
||||
declare interface ChromeDocument extends Document {
|
||||
/**
|
||||
* Create a XUL element of a specific type. Right now this function
|
||||
* only refines iframes, but more tags could be added.
|
||||
*/
|
||||
createXULElement: ((type: "iframe") => XULIframeElement) &
|
||||
((type: string) => XULElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a variant on the HTMLElement, as it contains chrome-specific properties.
|
||||
*/
|
||||
declare interface ChromeHTMLElement extends HTMLElement {
|
||||
ownerDocument: ChromeDocument;
|
||||
}
|
||||
|
||||
declare interface XULElement extends HTMLElement {
|
||||
ownerDocument: ChromeDocument;
|
||||
}
|
||||
|
||||
declare interface XULIframeElement extends XULElement {
|
||||
contentWindow: ChromeWindow;
|
||||
src: string;
|
||||
}
|
||||
|
||||
declare interface ChromeWindow extends Window {}
|
|
@ -1,12 +1,17 @@
|
|||
/* 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 shared types for the performance-new client.
|
||||
*/
|
||||
|
||||
import {
|
||||
Reducer as ReduxReducer,
|
||||
Store as ReduxStore,
|
||||
} from "devtools/client/shared/vendor/redux";
|
||||
|
||||
export interface PanelWindow extends Window {
|
||||
export interface PanelWindow {
|
||||
gToolbox?: any;
|
||||
gTarget?: any;
|
||||
gInit(perfFront: any, preferenceFront: any): void;
|
||||
|
@ -14,7 +19,7 @@ export interface PanelWindow extends Window {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* TS-TODO - Stub.
|
||||
*/
|
||||
export interface Target {
|
||||
// TODO
|
||||
|
@ -22,14 +27,14 @@ export interface Target {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* TS-TODO - Stub.
|
||||
*/
|
||||
export interface Toolbox {
|
||||
target: Target;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* TS-TODO - Stub.
|
||||
*/
|
||||
export interface PerfFront {
|
||||
startProfiler: any;
|
||||
|
@ -42,9 +47,17 @@ export interface PerfFront {
|
|||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* TS-TODO - Stub
|
||||
*/
|
||||
export interface PreferenceFront {}
|
||||
export interface PreferenceFront {
|
||||
clearUserPref: (prefName: string) => Promise<void>;
|
||||
getStringPref: (prefName: string) => Promise<string>;
|
||||
setStringPref: (prefName: string, value: string) => Promise<void>;
|
||||
getCharPref: (prefName: string) => Promise<string>;
|
||||
setCharPref: (prefName: string, value: string) => Promise<void>;
|
||||
getIntPref: (prefName: string) => Promise<number>;
|
||||
setIntPref: (prefName: string, value: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export type RecordingState =
|
||||
// The initial state before we've queried the PerfActor
|
||||
|
@ -122,7 +135,15 @@ export type ReceiveProfile = (
|
|||
getSymbolTableCallback: GetSymbolTableCallback
|
||||
) => void;
|
||||
|
||||
export type SetRecordingPreferences = (settings: Object) => void;
|
||||
export type SetRecordingPreferences = (settings: object) => void;
|
||||
|
||||
/**
|
||||
* This interface is injected into profiler.firefox.com
|
||||
*/
|
||||
interface GeckoProfilerFrameScriptInterface {
|
||||
getProfile: () => Promise<object>;
|
||||
getSymbolTable: GetSymbolTableCallback;
|
||||
}
|
||||
|
||||
export interface RecordingStateFromPreferences {
|
||||
entries: number;
|
||||
|
@ -148,7 +169,7 @@ export interface InitializedValues {
|
|||
// or inside of devtools.
|
||||
isPopup: boolean;
|
||||
// The popup and devtools panel use different codepaths for getting symbol tables.
|
||||
getSymbolTableGetter: (profile: Object) => GetSymbolTableCallback;
|
||||
getSymbolTableGetter: (profile: object) => GetSymbolTableCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,5 +216,27 @@ export type Action =
|
|||
setRecordingPreferences: SetRecordingPreferences;
|
||||
isPopup: boolean;
|
||||
recordingSettingsFromPreferences: RecordingStateFromPreferences;
|
||||
getSymbolTableGetter: (profile: Object) => GetSymbolTableCallback;
|
||||
getSymbolTableGetter: (profile: object) => GetSymbolTableCallback;
|
||||
};
|
||||
|
||||
export type PopupBackgroundFeatures = { [feature: string]: boolean };
|
||||
|
||||
/**
|
||||
* The state of the profiler popup.
|
||||
*/
|
||||
export interface PopupBackgroundState {
|
||||
isRunning: boolean;
|
||||
settingsOpen: boolean;
|
||||
features: PopupBackgroundFeatures;
|
||||
buffersize: number;
|
||||
windowLength: number;
|
||||
interval: number;
|
||||
threads: string;
|
||||
}
|
||||
|
||||
// TS-TODO - Stub
|
||||
export interface ContentFrameMessageManager {
|
||||
addMessageListener: (event: string, listener: (event: any) => void) => void;
|
||||
addEventListener: (event: string, listener: (event: any) => void) => void;
|
||||
sendAsyncMessage: (name: string, data: any) => void;
|
||||
}
|
||||
|
|
|
@ -1,16 +1,47 @@
|
|||
/* 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
|
||||
"use strict";
|
||||
|
||||
// The "loader" is globally available, much like "require".
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "OS", "resource://gre/modules/osfile.jsm", true);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"ProfilerGetSymbols",
|
||||
"resource://gre/modules/ProfilerGetSymbols.jsm",
|
||||
true
|
||||
/**
|
||||
* @typedef {import("./@types/perf").Action} Action
|
||||
* @typedef {import("./@types/perf").Library} Library
|
||||
* @typedef {import("./@types/perf").PerfFront} PerfFront
|
||||
* @typedef {import("./@types/perf").SymbolTableAsTuple} SymbolTableAsTuple
|
||||
* @typedef {import("./@types/perf").RecordingState} RecordingState
|
||||
* @typedef {import("./@types/perf").GetSymbolTableCallback} GetSymbolTableCallback
|
||||
* @typedef {import("./@types/perf").PreferenceFront} PreferenceFront
|
||||
*/
|
||||
|
||||
/**
|
||||
* TS-TODO
|
||||
*
|
||||
* This function replaces lazyRequireGetter, and TypeScript can understand it. It's
|
||||
* currently duplicated until we have consensus that TypeScript is a good idea.
|
||||
*
|
||||
* @template T
|
||||
* @type {(callback: () => T) => () => T}
|
||||
*/
|
||||
function requireLazy(callback) {
|
||||
/** @type {T | undefined} */
|
||||
let cache;
|
||||
return () => {
|
||||
if (cache === undefined) {
|
||||
cache = callback();
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
}
|
||||
|
||||
const lazyServices = requireLazy(() =>
|
||||
require("resource://gre/modules/Services.jsm")
|
||||
);
|
||||
|
||||
const lazyOS = requireLazy(() => require("resource://gre/modules/osfile.jsm"));
|
||||
|
||||
const lazyProfilerGetSymbols = requireLazy(() =>
|
||||
require("resource://gre/modules/ProfilerGetSymbols.jsm")
|
||||
);
|
||||
|
||||
const TRANSFER_EVENT = "devtools:perf-html-transfer-profile";
|
||||
|
@ -35,13 +66,14 @@ const OBJDIRS_PREF = "devtools.performance.recording.objdirs";
|
|||
* into a new browser tab, and injects the profile via a frame script.
|
||||
*
|
||||
* @param {object} profile - The Gecko profile.
|
||||
* @param {function} getSymbolTableCallback - A callback function with the signature
|
||||
* @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) {
|
||||
const { Services } = lazyServices();
|
||||
// Find the most recently used window, as the DevTools client could be in a variety
|
||||
// of hosts.
|
||||
const win = Services.wm.getMostRecentWindow("navigator:browser");
|
||||
|
@ -104,7 +136,7 @@ function receiveProfile(profile, getSymbolTableCallback) {
|
|||
* function always returns a valid array of strings.
|
||||
* @param {PreferenceFront} preferenceFront
|
||||
* @param {string} prefName
|
||||
* @param {array of string} defaultValue
|
||||
* @param {string[]} defaultValue
|
||||
*/
|
||||
async function _getArrayOfStringsPref(preferenceFront, prefName, defaultValue) {
|
||||
let array;
|
||||
|
@ -132,9 +164,10 @@ async function _getArrayOfStringsPref(preferenceFront, prefName, defaultValue) {
|
|||
* even exists. Gracefully handle malformed data or missing data. Ensure that this
|
||||
* function always returns a valid array of strings.
|
||||
* @param {string} prefName
|
||||
* @param {array of string} defaultValue
|
||||
* @param {string[]} defaultValue
|
||||
*/
|
||||
async function _getArrayOfStringsHostPref(prefName, defaultValue) {
|
||||
const { Services } = lazyServices();
|
||||
let array;
|
||||
try {
|
||||
const text = Services.prefs.getStringPref(
|
||||
|
@ -220,10 +253,11 @@ async function getRecordingPreferencesFromDebuggee(
|
|||
* and some of them on the host browser instance.
|
||||
*
|
||||
* @param {PreferenceFront} preferenceFront
|
||||
* @param {object} defaultSettings See the getRecordingSettings selector for the shape
|
||||
* @param {object} settings See the getRecordingSettings selector for the shape
|
||||
* of the object and how it gets defined.
|
||||
*/
|
||||
async function setRecordingPreferencesOnDebuggee(preferenceFront, settings) {
|
||||
const { Services } = lazyServices();
|
||||
await Promise.all([
|
||||
preferenceFront.setIntPref(
|
||||
`devtools.performance.recording.entries`,
|
||||
|
@ -273,9 +307,14 @@ async function setRecordingPreferencesOnDebuggee(preferenceFront, settings) {
|
|||
* profile has been passed to the UI.
|
||||
*
|
||||
* @param {object} profile - The profile JSON object
|
||||
* @returns {(debugName: string, breakpadId: string) => Library | undefined}
|
||||
*/
|
||||
function createLibraryMap(profile) {
|
||||
const map = new Map();
|
||||
|
||||
/**
|
||||
* @param {object} processProfile
|
||||
*/
|
||||
function fillMapForProcessRecursive(processProfile) {
|
||||
for (const lib of processProfile.libs) {
|
||||
const { debugName, breakpadId } = lib;
|
||||
|
@ -294,6 +333,12 @@ function createLibraryMap(profile) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PerfFront} perfFront
|
||||
* @param {string} path
|
||||
* @param {string} breakpadId
|
||||
* @returns {Promise<SymbolTableAsTuple>}
|
||||
*/
|
||||
async function getSymbolTableFromDebuggee(perfFront, path, breakpadId) {
|
||||
const [addresses, index, buffer] = await perfFront.getSymbolTable(
|
||||
path,
|
||||
|
@ -308,7 +353,12 @@ async function getSymbolTableFromDebuggee(perfFront, path, breakpadId) {
|
|||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async function doesFileExistAtPath(path) {
|
||||
const { OS } = lazyOS();
|
||||
try {
|
||||
const result = await OS.File.stat(path);
|
||||
return !result.isDir;
|
||||
|
@ -332,15 +382,16 @@ async function doesFileExistAtPath(path) {
|
|||
* An objdir, or "object directory", is a directory on the host machine that's
|
||||
* used to store build artifacts ("object files") from the compilation process.
|
||||
*
|
||||
* @param {array of string} objdirs An array of objdir paths on the host machine
|
||||
* @param {string[]} objdirs An array of objdir paths on the host machine
|
||||
* that should be searched for relevant build artifacts.
|
||||
* @param {string} filename The file name of the binary.
|
||||
* @param {string} breakpadId The breakpad ID of the binary.
|
||||
* @returns {Promise} The symbol table of the first encountered binary with a
|
||||
* @returns {Promise<SymbolTableAsTuple>} The symbol table of the first encountered binary with a
|
||||
* matching breakpad ID, in SymbolTableAsTuple format. An exception is thrown (the
|
||||
* promise is rejected) if nothing was found.
|
||||
*/
|
||||
async function getSymbolTableFromLocalBinary(objdirs, filename, breakpadId) {
|
||||
const { OS } = lazyOS();
|
||||
const candidatePaths = [];
|
||||
for (const objdirPath of objdirs) {
|
||||
// Binaries are usually expected to exist at objdir/dist/bin/filename.
|
||||
|
@ -354,6 +405,7 @@ async function getSymbolTableFromLocalBinary(objdirs, filename, breakpadId) {
|
|||
|
||||
for (const path of candidatePaths) {
|
||||
if (await doesFileExistAtPath(path)) {
|
||||
const { ProfilerGetSymbols } = lazyProfilerGetSymbols();
|
||||
try {
|
||||
return await ProfilerGetSymbols.getSymbolTable(path, path, breakpadId);
|
||||
} catch (e) {
|
||||
|
@ -379,18 +431,25 @@ async function getSymbolTableFromLocalBinary(objdirs, filename, breakpadId) {
|
|||
* The profiler popup uses a more simplified version of this function as
|
||||
* it's dealing with a simpler situation.
|
||||
*
|
||||
* @param {Profile} profile - The raw profie (not gzipped).
|
||||
* @param {array of string} objdirs - An array of objdir paths on the host machine
|
||||
* @param {object} profile - The raw profie (not gzipped).
|
||||
* @param {string[]} objdirs - An array of objdir paths on the host machine
|
||||
* that should be searched for relevant build artifacts.
|
||||
* @param {PerfFront} perfFront
|
||||
* @return {Function}
|
||||
* @return {GetSymbolTableCallback}
|
||||
*/
|
||||
function createMultiModalGetSymbolTableFn(profile, objdirs, perfFront) {
|
||||
const libraryGetter = createLibraryMap(profile);
|
||||
|
||||
return async function getSymbolTable(debugName, breakpadId) {
|
||||
const { name, path, debugPath } = libraryGetter(debugName, breakpadId);
|
||||
const result = libraryGetter(debugName, breakpadId);
|
||||
if (!result) {
|
||||
throw new Error(
|
||||
`Could not find the library for "${debugName}", "${breakpadId}".`
|
||||
);
|
||||
}
|
||||
const { name, path, debugPath } = result;
|
||||
if (await doesFileExistAtPath(path)) {
|
||||
const { ProfilerGetSymbols } = lazyProfilerGetSymbols();
|
||||
// This profile was obtained from this machine, and not from a
|
||||
// different device (e.g. an Android phone). Dump symbols from the file
|
||||
// on this machine directly.
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
/* 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";
|
||||
/* global addMessageListener, addEventListener, content, sendAsyncMessage */
|
||||
|
||||
/**
|
||||
* @typedef {import("./@types/perf").GetSymbolTableCallback} GetSymbolTableCallback
|
||||
* @typedef {import("./@types/perf").ContentFrameMessageManager} ContentFrameMessageManager
|
||||
*/
|
||||
|
||||
/**
|
||||
* This frame script injects itself into profiler.firefox.com and injects the profile
|
||||
|
@ -13,19 +20,33 @@ 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 | Object} */
|
||||
let gProfile = null;
|
||||
const symbolReplyPromiseMap = new Map();
|
||||
|
||||
addMessageListener(TRANSFER_EVENT, e => {
|
||||
/**
|
||||
* 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.
|
||||
addEventListener("DOMContentLoaded", connectToPage);
|
||||
frameScript.addEventListener("DOMContentLoaded", connectToPage);
|
||||
});
|
||||
|
||||
addMessageListener(SYMBOL_TABLE_RESPONSE_EVENT, e => {
|
||||
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);
|
||||
|
@ -55,9 +76,13 @@ function connectToPage() {
|
|||
}
|
||||
}
|
||||
|
||||
/** @type {GetSymbolTableCallback} */
|
||||
function getSymbolTable(debugName, breakpadId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
sendAsyncMessage(SYMBOL_TABLE_REQUEST_EVENT, { debugName, breakpadId });
|
||||
frameScript.sendAsyncMessage(SYMBOL_TABLE_REQUEST_EVENT, {
|
||||
debugName,
|
||||
breakpadId,
|
||||
});
|
||||
symbolReplyPromiseMap.set([debugName, breakpadId].join(":"), {
|
||||
resolve,
|
||||
reject,
|
||||
|
@ -72,11 +97,23 @@ function getSymbolTable(debugName, breakpadId) {
|
|||
|
||||
/**
|
||||
* Create a promise that can be used in the page.
|
||||
*
|
||||
* @template T
|
||||
* @param {(resolve: Function, reject: Function) => Promise<T>} fun
|
||||
* @param {object} 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 => reject(Cu.cloneInto(error, contentGlobal))
|
||||
);
|
||||
}
|
||||
|
@ -88,9 +125,13 @@ function createPromiseInPage(fun, contentGlobal) {
|
|||
/**
|
||||
* Returns a function that calls the original function and tries to make the
|
||||
* return value available to the page.
|
||||
* @param {Function} fun
|
||||
* @param {object} 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") {
|
||||
|
@ -110,12 +151,18 @@ function wrapFunction(fun, contentGlobal) {
|
|||
* 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 {object} 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,
|
||||
});
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
/* 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
|
||||
/* exported gInit, gDestroy, loader */
|
||||
"use strict";
|
||||
|
||||
/* exported gInit, gDestroy, loader */
|
||||
/**
|
||||
* @typedef {import("./@types/perf").PerfFront} PerfFront
|
||||
* @typedef {import("./@types/perf").PreferenceFront} PreferenceFront
|
||||
*/
|
||||
|
||||
const { BrowserLoader } = ChromeUtils.import(
|
||||
"resource://devtools/client/shared/browser-loader.js"
|
||||
|
@ -36,8 +41,8 @@ const {
|
|||
/**
|
||||
* Initialize the panel by creating a redux store, and render the root component.
|
||||
*
|
||||
* @param perfFront - The Perf actor's front. Used to start and stop recordings.
|
||||
* @param preferenceFront - Used to get the recording preferences from the device.
|
||||
* @param {PerfFront} perfFront - The Perf actor's front. Used to start and stop recordings.
|
||||
* @param {PreferenceFront} preferenceFront - Used to get the recording preferences from the device.
|
||||
*/
|
||||
async function gInit(perfFront, preferenceFront) {
|
||||
const store = createStore(reducers);
|
||||
|
@ -67,12 +72,14 @@ async function gInit(perfFront, preferenceFront) {
|
|||
|
||||
// Configure the getSymbolTable function for the DevTools workflow.
|
||||
// See createMultiModalGetSymbolTableFn for more information.
|
||||
getSymbolTableGetter: profile =>
|
||||
createMultiModalGetSymbolTableFn(
|
||||
profile,
|
||||
selectors.getPerfFront(store.getState()),
|
||||
selectors.getObjdirs(store.getState())
|
||||
),
|
||||
getSymbolTableGetter:
|
||||
/** @type {(profile: Object) => GetSymbolTableCallback} */
|
||||
profile =>
|
||||
createMultiModalGetSymbolTableFn(
|
||||
profile,
|
||||
selectors.getPerfFront(store.getState()),
|
||||
selectors.getObjdirs(store.getState())
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
/* 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
|
||||
"use strict";
|
||||
|
||||
// @ts-ignore - No support yet for lazyRequireGetter.
|
||||
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
||||
|
||||
/**
|
||||
* This file contains the PerformancePanel, which uses a common API for DevTools to
|
||||
* start and load everything. This will call `gInit` from the initializer.js file,
|
||||
|
@ -14,9 +12,9 @@ loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
|
|||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import("./types").PanelWindow} PanelWindow
|
||||
* @typedef {import("./types").Toolbox} Toolbox
|
||||
* @typedef {import("./types").Target} Target
|
||||
* @typedef {import("./@types/perf").PanelWindow} PanelWindow
|
||||
* @typedef {import("./@types/perf").Toolbox} Toolbox
|
||||
* @typedef {import("./@types/perf").Target} Target
|
||||
*/
|
||||
|
||||
class PerformancePanel {
|
||||
|
@ -28,7 +26,7 @@ class PerformancePanel {
|
|||
this.panelWin = iframeWindow;
|
||||
this.toolbox = toolbox;
|
||||
|
||||
// @ts-ignore - No support yet for lazyRequireGetter.
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* 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
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
|
@ -10,29 +11,64 @@
|
|||
* access to any UI, and need to be loaded independent of the popup.
|
||||
*/
|
||||
|
||||
// The following are not lazily loaded as they are needed during initialization.f
|
||||
// The following are not lazily loaded as they are needed during initialization.
|
||||
|
||||
/** @type {import("resource://gre/modules/Services.jsm")} */
|
||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
/** @type {import("resource://gre/modules/AppConstants.jsm")} */
|
||||
const { AppConstants } = ChromeUtils.import(
|
||||
"resource://gre/modules/AppConstants.jsm"
|
||||
);
|
||||
const { loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
|
||||
|
||||
/**
|
||||
* @typedef {import("../@types/perf").RecordingStateFromPreferences} RecordingStateFromPreferences
|
||||
* @typedef {import("../@types/perf").PopupBackgroundFeatures} PopupBackgroundFeatures
|
||||
* @typedef {import("../@types/perf").SymbolTableAsTuple} SymbolTableAsTuple
|
||||
*/
|
||||
|
||||
// The following utilities are lazily loaded as they are not needed when controlling the
|
||||
// global state of the profiler, and only are used during specific funcationality like
|
||||
// symbolication or capturing a profile.
|
||||
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"ProfilerGetSymbols",
|
||||
"resource://gre/modules/ProfilerGetSymbols.jsm"
|
||||
|
||||
/**
|
||||
* TS-TODO
|
||||
*
|
||||
* This function replaces lazyRequireGetter, and TypeScript can understand it. It's
|
||||
* currently duplicated until we have consensus that TypeScript is a good idea.
|
||||
*
|
||||
* @template T
|
||||
* @type {(callback: () => T) => () => T}
|
||||
*/
|
||||
function requireLazy(callback) {
|
||||
/** @type {T | undefined} */
|
||||
let cache;
|
||||
return () => {
|
||||
if (cache === undefined) {
|
||||
cache = callback();
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
}
|
||||
|
||||
const lazyOS = requireLazy(() =>
|
||||
/** @type {import("resource://gre/modules/osfile.jsm")} */
|
||||
(ChromeUtils.import("resource://gre/modules/osfile.jsm"))
|
||||
);
|
||||
loader.lazyRequireGetter(
|
||||
this,
|
||||
"receiveProfile",
|
||||
"devtools/client/performance-new/browser",
|
||||
true
|
||||
|
||||
const lazyProfilerGetSymbols = requireLazy(() =>
|
||||
/** @type {import("resource://gre/modules/ProfilerGetSymbols.jsm")} */
|
||||
(ChromeUtils.import("resource://gre/modules/ProfilerGetSymbols.jsm"))
|
||||
);
|
||||
|
||||
const lazyReceiveProfile = requireLazy(() => {
|
||||
const { require } = ChromeUtils.import(
|
||||
"resource://devtools/shared/Loader.jsm"
|
||||
);
|
||||
/** @type {import("devtools/client/performance-new/browser")} */
|
||||
const browserModule = require("devtools/client/performance-new/browser");
|
||||
return browserModule.receiveProfile;
|
||||
});
|
||||
|
||||
// This pref contains the JSON serialization of the popup's profiler state with
|
||||
// a string key based off of the debug name and breakpad id.
|
||||
const PROFILER_STATE_PREF = "devtools.performance.popup";
|
||||
|
@ -42,15 +78,35 @@ const DEFAULT_BUFFER_SIZE = 10000000; // 90MB
|
|||
const DEFAULT_THREADS = "GeckoMain,Compositor";
|
||||
const DEFAULT_STACKWALK_FEATURE = true;
|
||||
|
||||
// This Map caches the symbols from the shared libraries.
|
||||
/**
|
||||
* This Map caches the symbols from the shared libraries.
|
||||
* @type {Map<string, { path: string, debugPath: string }>}
|
||||
*/
|
||||
const symbolCache = new Map();
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* path: string,
|
||||
* debugName: string,
|
||||
* debugPath: string,
|
||||
* breakpadId: string
|
||||
* }} Libs
|
||||
*
|
||||
* @type {(libs: Libs[]) => void}
|
||||
*/
|
||||
const primeSymbolStore = libs => {
|
||||
for (const { path, debugName, debugPath, breakpadId } of libs) {
|
||||
symbolCache.set(`${debugName}/${breakpadId}`, { path, debugPath });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import("../@types/perf").PopupBackgroundState} PopupBackgroundState
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {PopupBackgroundState}
|
||||
*/
|
||||
const state = initializeState();
|
||||
|
||||
const forTestsOnly = {
|
||||
|
@ -66,6 +122,9 @@ const forTestsOnly = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Partial<PopupBackgroundState>} newState
|
||||
*/
|
||||
function adjustState(newState) {
|
||||
// Deep clone the object, since this can be called through popup.xhtml,
|
||||
// which can be unloaded thus leaving this object dead.
|
||||
|
@ -80,6 +139,10 @@ function adjustState(newState) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TS-TODO - Fix any.
|
||||
* @type {(debugName: string, breakpadId: string) => Promise<SymbolTableAsTuple>}
|
||||
*/
|
||||
async function getSymbolsFromThisBrowser(debugName, breakpadId) {
|
||||
if (symbolCache.size === 0) {
|
||||
primeSymbolStore(Services.profiler.sharedLibraries);
|
||||
|
@ -98,6 +161,7 @@ async function getSymbolsFromThisBrowser(debugName, breakpadId) {
|
|||
}
|
||||
|
||||
const { path, debugPath } = cachedLibInfo;
|
||||
const { OS } = lazyOS();
|
||||
if (!OS.Path.split(path).absolute) {
|
||||
throw new Error(
|
||||
"Services.profiler.sharedLibraries did not contain an absolute path for " +
|
||||
|
@ -106,9 +170,13 @@ async function getSymbolsFromThisBrowser(debugName, breakpadId) {
|
|||
);
|
||||
}
|
||||
|
||||
const { ProfilerGetSymbols } = lazyProfilerGetSymbols();
|
||||
return ProfilerGetSymbols.getSymbolTable(path, debugPath, breakpadId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {() => Promise<void>}
|
||||
*/
|
||||
async function captureProfile() {
|
||||
if (!state.isRunning) {
|
||||
// The profiler is not active, ignore this shortcut.
|
||||
|
@ -120,11 +188,14 @@ async function captureProfile() {
|
|||
|
||||
const profile = await Services.profiler
|
||||
.getProfileDataAsGzippedArrayBuffer()
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
return {};
|
||||
});
|
||||
.catch(
|
||||
/** @type {(e: any) => {}} */ e => {
|
||||
console.error(e);
|
||||
return {};
|
||||
}
|
||||
);
|
||||
|
||||
const receiveProfile = lazyReceiveProfile();
|
||||
receiveProfile(profile, getSymbolsFromThisBrowser);
|
||||
|
||||
Services.profiler.StopProfiler();
|
||||
|
@ -133,6 +204,9 @@ async function captureProfile() {
|
|||
/**
|
||||
* Not all features are supported on every version of Firefox. Get the list of checked
|
||||
* features, add a few defaults, and filter for what is actually supported.
|
||||
* @param {PopupBackgroundFeatures} features
|
||||
* @param {string[]} threads
|
||||
* @returns {string[]}
|
||||
*/
|
||||
function getEnabledFeatures(features, threads) {
|
||||
const enabledFeatures = Object.keys(features).filter(f => features[f]);
|
||||
|
@ -160,10 +234,16 @@ function startProfiler() {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {() => void}
|
||||
*/
|
||||
function stopProfiler() {
|
||||
Services.profiler.StopProfiler();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {() => void}
|
||||
*/
|
||||
function toggleProfiler() {
|
||||
if (state.isRunning) {
|
||||
stopProfiler();
|
||||
|
@ -172,6 +252,9 @@ function toggleProfiler() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {() => void}
|
||||
*/
|
||||
function restartProfiler() {
|
||||
stopProfiler();
|
||||
startProfiler();
|
||||
|
@ -181,6 +264,11 @@ function restartProfiler() {
|
|||
const isRunningObserver = {
|
||||
_observers: new Set(),
|
||||
|
||||
/**
|
||||
* @param {string} subject
|
||||
* @param {string} topic
|
||||
* @param {unknown} data
|
||||
*/
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "profiler-started":
|
||||
|
@ -204,6 +292,9 @@ const isRunningObserver = {
|
|||
Services.obs.removeObserver(this, "profiler-stopped");
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {(isActive: boolean) => any} observer
|
||||
*/
|
||||
addObserver(observer) {
|
||||
if (this._observers.size === 0) {
|
||||
this._startListening();
|
||||
|
@ -214,6 +305,9 @@ const isRunningObserver = {
|
|||
Promise.resolve(Services.profiler.IsActive()).then(observer);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {(isActive: boolean) => any} observer
|
||||
*/
|
||||
removeObserver(observer) {
|
||||
if (this._observers.delete(observer) && this._observers.size === 0) {
|
||||
this._stopListening();
|
||||
|
@ -221,6 +315,9 @@ const isRunningObserver = {
|
|||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {PopupBackgroundState | null}
|
||||
*/
|
||||
function getStoredStateOrNull() {
|
||||
// Pull out the stored state from preferences, it is a raw string.
|
||||
const storedStateString = Services.prefs.getStringPref(
|
||||
|
@ -242,7 +339,11 @@ function getStoredStateOrNull() {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} prefName
|
||||
* @param {string[]} defaultValue
|
||||
* @return {string[]}
|
||||
*/
|
||||
function _getArrayOfStringsPref(prefName, defaultValue) {
|
||||
let array;
|
||||
try {
|
||||
|
@ -262,6 +363,11 @@ function _getArrayOfStringsPref(prefName, defaultValue) {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} prefName
|
||||
* @param {string[]} defaultValue
|
||||
* @return {string[]}
|
||||
*/
|
||||
function _getArrayOfStringsHostPref(prefName, defaultValue) {
|
||||
let array;
|
||||
try {
|
||||
|
@ -284,7 +390,11 @@ function _getArrayOfStringsHostPref(prefName, defaultValue) {
|
|||
return defaultValue;
|
||||
}
|
||||
|
||||
function getRecordingPreferencesFromBrowser(defaultSettings = {}) {
|
||||
/**
|
||||
* @param {RecordingStateFromPreferences} defaultSettings
|
||||
* @return {RecordingStateFromPreferences}
|
||||
*/
|
||||
function getRecordingPreferencesFromBrowser(defaultSettings) {
|
||||
const [entries, interval, features, threads, objdirs] = [
|
||||
Services.prefs.getIntPref(
|
||||
`devtools.performance.recording.entries`,
|
||||
|
@ -313,6 +423,9 @@ function getRecordingPreferencesFromBrowser(defaultSettings = {}) {
|
|||
return { entries, interval: newInterval, features, threads, objdirs };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {RecordingStateFromPreferences} settings
|
||||
*/
|
||||
function setRecordingPreferencesOnBrowser(settings) {
|
||||
Services.prefs.setIntPref(
|
||||
`devtools.performance.recording.entries`,
|
||||
|
@ -337,6 +450,9 @@ function setRecordingPreferencesOnBrowser(settings) {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {PopupBackgroundState}
|
||||
*/
|
||||
function initializeState() {
|
||||
const features = {
|
||||
java: false,
|
||||
|
@ -368,18 +484,28 @@ function initializeState() {
|
|||
// Validate the stored state. It's possible a feature was added or removed
|
||||
// since the profiler was last run.
|
||||
for (const key of Object.keys(features)) {
|
||||
features[key] =
|
||||
key in storedFeatures ? Boolean(storedFeatures[key]) : features[key];
|
||||
/** @type {{[key: string]: boolean}} */
|
||||
const featureAsObjMap = features;
|
||||
featureAsObjMap[key] =
|
||||
key in storedFeatures
|
||||
? Boolean(storedFeatures[key])
|
||||
: featureAsObjMap[key];
|
||||
}
|
||||
}
|
||||
|
||||
// This function is created inline to make it easy to validate
|
||||
// the stored state using the captured storedState value.
|
||||
/**
|
||||
* This function is created inline to make it easy to validate
|
||||
* the stored state using the captured storedState value.
|
||||
* @template Value
|
||||
* @type {(key: string, type: string, defaultValue: Value) => Value}
|
||||
*/
|
||||
function validateStoredState(key, type, defaultValue) {
|
||||
if (!storedState) {
|
||||
return defaultValue;
|
||||
}
|
||||
const storedValue = storedState[key];
|
||||
/** @type {object} */
|
||||
const storedStateAsObjMap = storedState;
|
||||
const storedValue = storedStateAsObjMap[key];
|
||||
return typeof storedValue === type ? storedValue : defaultValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
/* 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
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @typedef {import("../@types/perf").PerfFront} PerfFront
|
||||
* @typedef {import("../@types/perf").PreferenceFront} PreferenceFront
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file initializes the profiler popup UI. It is in charge of initializing
|
||||
* the browser specific environment, and then passing those requirements into
|
||||
|
@ -25,7 +30,7 @@ const { require } = BrowserLoader({
|
|||
});
|
||||
|
||||
/**
|
||||
* The background.jsm manages the profiler state, and can be loaded multiple time
|
||||
* The background.jsm.js manages the profiler state, and can be loaded multiple time
|
||||
* for various components. This pop-up needs a copy, and it is also used by the
|
||||
* profiler shortcuts. In order to do this, the background code needs to live in a
|
||||
* JSM module, that can be shared with the DevTools keyboard shortcut manager.
|
||||
|
@ -102,8 +107,9 @@ async function gInit() {
|
|||
|
||||
function resizeWindow() {
|
||||
window.requestAnimationFrame(() => {
|
||||
if (window.gResizePopup) {
|
||||
window.gResizePopup(document.body.clientHeight);
|
||||
const { gResizePopup } = /** @type {any} */ (window);
|
||||
if (gResizePopup) {
|
||||
gResizePopup(document.body.clientHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/* 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
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
|
@ -8,20 +9,37 @@
|
|||
* 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"
|
||||
/**
|
||||
* TS-TODO
|
||||
*
|
||||
* This function replaces lazyRequireGetter, and TypeScript can understand it. It's
|
||||
* currently duplicated until we have consensus that TypeScript is a good idea.
|
||||
*
|
||||
* @template T
|
||||
* @type {(callback: () => T) => () => T}
|
||||
*/
|
||||
function requireLazy(callback) {
|
||||
/** @type {T | undefined} */
|
||||
let cache;
|
||||
return () => {
|
||||
if (cache === undefined) {
|
||||
cache = callback();
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
}
|
||||
|
||||
const lazyServices = requireLazy(() =>
|
||||
/** @type {import("resource://gre/modules/Services.jsm")} */
|
||||
(ChromeUtils.import("resource://gre/modules/Services.jsm"))
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"CustomizableUI",
|
||||
"resource:///modules/CustomizableUI.jsm"
|
||||
const lazyCustomizableUI = requireLazy(() =>
|
||||
/** @type {import("resource:///modules/CustomizableUI.jsm")} */
|
||||
ChromeUtils.import("resource:///modules/CustomizableUI.jsm")
|
||||
);
|
||||
ChromeUtils.defineModuleGetter(
|
||||
this,
|
||||
"CustomizableWidgets",
|
||||
"resource:///modules/CustomizableWidgets.jsm"
|
||||
const lazyCustomizableWidgets = requireLazy(() =>
|
||||
/** @type {import("resource:///modules/CustomizableWidgets.jsm")} */
|
||||
ChromeUtils.import("resource:///modules/CustomizableWidgets.jsm")
|
||||
);
|
||||
|
||||
// The profiler's menu button and its popup can be enabled/disabled by the user.
|
||||
|
@ -31,26 +49,40 @@ ChromeUtils.defineModuleGetter(
|
|||
const BUTTON_ENABLED_PREF = "devtools.performance.popup.enabled";
|
||||
const WIDGET_ID = "profiler-button";
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isEnabled() {
|
||||
const { Services } = lazyServices();
|
||||
return Services.prefs.getBoolPref(BUTTON_ENABLED_PREF, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLDocument} document
|
||||
* @param {boolean} isChecked
|
||||
* @return {void}
|
||||
*/
|
||||
function setMenuItemChecked(document, isChecked) {
|
||||
const menuItem = document.querySelector("#menu_toggleProfilerButtonMenu");
|
||||
if (!menuItem) {
|
||||
return;
|
||||
}
|
||||
menuItem.setAttribute("checked", isChecked);
|
||||
menuItem.setAttribute("checked", isChecked.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the menu button, and initialize the widget if needed.
|
||||
*
|
||||
* @param {Object} document - The browser's document.
|
||||
* @param {object} document - The browser's document.
|
||||
* @return {void}
|
||||
*/
|
||||
function toggle(document) {
|
||||
const { CustomizableUI } = lazyCustomizableUI();
|
||||
const { Services } = lazyServices();
|
||||
|
||||
const toggledValue = !isEnabled();
|
||||
Services.prefs.setBoolPref(BUTTON_ENABLED_PREF, toggledValue);
|
||||
|
||||
if (toggledValue) {
|
||||
initialize();
|
||||
CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR);
|
||||
|
@ -65,70 +97,108 @@ function toggle(document) {
|
|||
}
|
||||
}
|
||||
|
||||
// This function takes the button element, and returns a function that's used to
|
||||
// update the profiler button whenever the profiler activation status changed.
|
||||
const updateButtonColorForElement = buttonElement => () => {
|
||||
const isRunning = Services.profiler.IsActive();
|
||||
/**
|
||||
* This function takes the button element, and returns a function that's used to
|
||||
* update the profiler button whenever the profiler activation status changed.
|
||||
*
|
||||
* @param {HTMLElement} buttonElement
|
||||
* @returns {() => void}
|
||||
*/
|
||||
function updateButtonColorForElement(buttonElement) {
|
||||
return () => {
|
||||
const { Services } = lazyServices();
|
||||
const isRunning = Services.profiler.IsActive();
|
||||
|
||||
// Use photon blue-60 when active.
|
||||
buttonElement.style.fill = isRunning ? "#0060df" : "";
|
||||
};
|
||||
// Use photon blue-60 when active.
|
||||
buttonElement.style.fill = isRunning ? "#0060df" : "";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function creates the widget definition for the CustomizableUI. It should
|
||||
* only be run if the profiler button is enabled.
|
||||
* @return {void}
|
||||
*/
|
||||
function initialize() {
|
||||
const { CustomizableUI } = lazyCustomizableUI();
|
||||
const { CustomizableWidgets } = lazyCustomizableWidgets();
|
||||
const { Services } = lazyServices();
|
||||
|
||||
const widget = CustomizableUI.getWidget(WIDGET_ID);
|
||||
if (widget && widget.provider == CustomizableUI.PROVIDER_API) {
|
||||
// This widget has already been created.
|
||||
return;
|
||||
}
|
||||
|
||||
let observer;
|
||||
/** @typedef {() => void} Observer */
|
||||
|
||||
/** @type {null | Observer} */
|
||||
let observer = null;
|
||||
|
||||
const item = {
|
||||
id: WIDGET_ID,
|
||||
type: "view",
|
||||
viewId: "PanelUI-profiler",
|
||||
tooltiptext: "profiler-button.tooltiptext",
|
||||
onViewShowing: event => {
|
||||
const panelview = event.target;
|
||||
const document = panelview.ownerDocument;
|
||||
|
||||
// Create an iframe and append it to the panelview.
|
||||
const iframe = document.createXULElement("iframe");
|
||||
iframe.id = "PanelUI-profilerIframe";
|
||||
iframe.className = "PanelUI-developer-iframe";
|
||||
iframe.src =
|
||||
"chrome://devtools/content/performance-new/popup/popup.xhtml";
|
||||
onViewShowing:
|
||||
/**
|
||||
* @type {(event: {
|
||||
* target: ChromeHTMLElement | XULElement,
|
||||
* detail: {
|
||||
* addBlocker: (blocker: Promise<void>) => void
|
||||
* }
|
||||
* }) => void}
|
||||
*/
|
||||
event => {
|
||||
const panelview = event.target;
|
||||
const document = panelview.ownerDocument;
|
||||
if (!document) {
|
||||
throw new Error(
|
||||
"Expected to find a document on the panelview element."
|
||||
);
|
||||
}
|
||||
|
||||
panelview.appendChild(iframe);
|
||||
// Create an iframe and append it to the panelview.
|
||||
const iframe = document.createXULElement("iframe");
|
||||
iframe.id = "PanelUI-profilerIframe";
|
||||
iframe.className = "PanelUI-developer-iframe";
|
||||
iframe.src =
|
||||
"chrome://devtools/content/performance-new/popup/popup.xhtml";
|
||||
|
||||
// Provide a mechanism for the iframe to close the popup.
|
||||
iframe.contentWindow.gClosePopup = () => {
|
||||
CustomizableUI.hidePanelForNode(iframe);
|
||||
};
|
||||
panelview.appendChild(iframe);
|
||||
/** @type {any} - Cast to an any since we're assigning values to the window object. */
|
||||
const contentWindow = iframe.contentWindow;
|
||||
|
||||
// Provide a mechanism for the iframe to resize the popup.
|
||||
iframe.contentWindow.gResizePopup = height => {
|
||||
iframe.style.height = `${Math.min(600, height)}px`;
|
||||
};
|
||||
// Provide a mechanism for the iframe to close the popup.
|
||||
contentWindow.gClosePopup = () => {
|
||||
CustomizableUI.hidePanelForNode(iframe);
|
||||
};
|
||||
|
||||
// The popup has an annoying rendering "blip" when first rendering the react
|
||||
// components. This adds a blocker until the content is ready to show.
|
||||
event.detail.addBlocker(
|
||||
new Promise(resolve => {
|
||||
iframe.contentWindow.gReportReady = () => {
|
||||
// Delete the function gReportReady so we don't leave any dangling
|
||||
// references between windows.
|
||||
delete iframe.contentWindow.gReportReady;
|
||||
// Now resolve this promise to open the window.
|
||||
resolve();
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
// Provide a mechanism for the iframe to resize the popup.
|
||||
/** @type {(height: number) => void} */
|
||||
contentWindow.gResizePopup = height => {
|
||||
iframe.style.height = `${Math.min(600, height)}px`;
|
||||
};
|
||||
|
||||
// The popup has an annoying rendering "blip" when first rendering the react
|
||||
// components. This adds a blocker until the content is ready to show.
|
||||
event.detail.addBlocker(
|
||||
new Promise(resolve => {
|
||||
contentWindow.gReportReady = () => {
|
||||
// Delete the function gReportReady so we don't leave any dangling
|
||||
// references between windows.
|
||||
delete contentWindow.gReportReady;
|
||||
// Now resolve this promise to open the window.
|
||||
resolve();
|
||||
};
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* @type {(event: { target: ChromeHTMLElement | XULElement }) => void}
|
||||
*/
|
||||
onViewHiding(event) {
|
||||
const document = event.target.ownerDocument;
|
||||
|
||||
|
@ -141,9 +211,13 @@ function initialize() {
|
|||
// Remove the iframe so it doesn't leak.
|
||||
iframe.remove();
|
||||
},
|
||||
|
||||
/** @type {(document: HTMLDocument) => void} */
|
||||
onBeforeCreated: document => {
|
||||
setMenuItemChecked(document, true);
|
||||
},
|
||||
|
||||
/** @type {(document: HTMLElement) => void} */
|
||||
onCreated: buttonElement => {
|
||||
observer = updateButtonColorForElement(buttonElement);
|
||||
Services.obs.addObserver(observer, "profiler-started");
|
||||
|
@ -153,12 +227,16 @@ function initialize() {
|
|||
// already running at startup.
|
||||
observer();
|
||||
},
|
||||
|
||||
onDestroyed: () => {
|
||||
Services.obs.removeObserver(observer, "profiler-started");
|
||||
Services.obs.removeObserver(observer, "profiler-stopped");
|
||||
observer = null;
|
||||
if (observer) {
|
||||
Services.obs.removeObserver(observer, "profiler-started");
|
||||
Services.obs.removeObserver(observer, "profiler-stopped");
|
||||
observer = null;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
CustomizableUI.createWidget(item);
|
||||
CustomizableWidgets.push(item);
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ exports.changeEntries = entries =>
|
|||
|
||||
/**
|
||||
* Updates the recording settings for the features.
|
||||
* @param {Object} features
|
||||
* @param {object} features
|
||||
* @return {ThunkAction<void>}
|
||||
*/
|
||||
exports.changeFeatures = features =>
|
||||
|
|
|
@ -178,7 +178,7 @@ function setProfilerFrontendUrl(url) {
|
|||
* test harness. This function runs in a loop every requestAnimationFrame, and
|
||||
* checks for a sucess title. In addition, an "initialTitle" and "errorTitle"
|
||||
* can be specified for nicer test output.
|
||||
* @param {Object}
|
||||
* @param {object}
|
||||
* {
|
||||
* initialTitle: string,
|
||||
* successTitle: string,
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
"target": "esnext",
|
||||
"lib": ["esnext", "dom"]
|
||||
},
|
||||
"files": ["./@types/gecko.d.ts"],
|
||||
// Add a @ts-check comment to a JS file to start type checking it.
|
||||
"include": ["./**/*.js", "./@types/*"]
|
||||
"include": ["./**/*.js"]
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче