author | Greg Tatum <gtatum@mozilla.com> |
Tue, 28 Apr 2020 17:51:09 +0000 | |
changeset 526538 | de5aad1dc90e32d9483d5324fd808bfa92086410 |
parent 526537 | 8dfe8cb5a8311dc6062c8d9a421dbbf65bc5050e |
child 526539 | 050c45257373eb0ea2a18cfbfb3a17a5c821f62f |
push id | 37358 |
push user | opoprus@mozilla.com |
push date | Wed, 29 Apr 2020 03:05:14 +0000 |
treeherder | mozilla-central@6bb8423186c1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ochameau |
bugs | 1607801 |
milestone | 77.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/performance-new/@types/gecko.d.ts +++ b/devtools/client/performance-new/@types/gecko.d.ts @@ -40,16 +40,18 @@ declare namespace MockedExports { typeof import("resource:///modules/CustomizableWidgets.jsm"); "resource://devtools/shared/Loader.jsm": typeof import("resource://devtools/shared/Loader.jsm"); "resource://devtools/client/performance-new/popup/background.jsm.js": typeof import("resource://devtools/client/performance-new/popup/background.jsm.js"); "resource://devtools/client/shared/browser-loader.js": any; "resource://devtools/client/performance-new/popup/menu-button.jsm.js": typeof import("devtools/client/performance-new/popup/menu-button.jsm.js"); + "resource://devtools/client/performance-new/typescript-lazy-load.jsm.js": + typeof import("devtools/client/performance-new/typescript-lazy-load.jsm.js"); "resource://devtools/client/performance-new/popup/panel.jsm.js": typeof import("devtools/client/performance-new/popup/panel.jsm.js"); "resource:///modules/PanelMultiView.jsm": typeof import("resource:///modules/PanelMultiView.jsm"); } interface ChromeUtils { /** @@ -60,16 +62,17 @@ declare namespace MockedExports { * of type * * 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; + defineModuleGetter: (target: any, variable: string, path: string) => void; } interface MessageManager { loadFrameScript(url: string, flag: boolean): void; sendAsyncMessage: (event: string, data: any) => void; addMessageListener: (event: string, listener: (event: any) => void) => void; } @@ -278,16 +281,20 @@ declare module "resource://gre/modules/S declare module "Services" { export = MockedExports.Services; } declare module "chrome" { export = MockedExports.chrome; } +declare module "ChromeUtils" { + export = ChromeUtils; +} + declare module "resource://gre/modules/osfile.jsm" { export = MockedExports.osfileJSM; } declare module "resource://gre/modules/AppConstants.jsm" { export = MockedExports.AppConstantsJSM; }
--- a/devtools/client/performance-new/browser.js +++ b/devtools/client/performance-new/browser.js @@ -15,47 +15,28 @@ * @typedef {import("./@types/perf").PerformancePref} PerformancePref * @typedef {import("./@types/perf").RecordingStateFromPreferences} RecordingStateFromPreferences * @typedef {import("./@types/perf").RestartBrowserWithEnvironmentVariable} RestartBrowserWithEnvironmentVariable * @typedef {import("./@types/perf").GetEnvironmentVariable} GetEnvironmentVariable * @typedef {import("./@types/perf").GetActiveBrowsingContextID} GetActiveBrowsingContextID * @typedef {import("./@types/perf").MinimallyTypedGeckoProfile} MinimallyTypedGeckoProfile */ -/** - * 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 ChromeUtils = require("ChromeUtils"); +const { createLazyLoaders } = ChromeUtils.import( + "resource://devtools/client/performance-new/typescript-lazy-load.jsm.js" ); -const lazyChrome = requireLazy(() => require("chrome")); - -const lazyOS = requireLazy(() => require("resource://gre/modules/osfile.jsm")); - -const lazyProfilerGetSymbols = requireLazy(() => - require("resource://gre/modules/ProfilerGetSymbols.jsm") -); +const lazy = createLazyLoaders({ + Chrome: () => require("chrome"), + Services: () => require("Services"), + OS: () => ChromeUtils.import("resource://gre/modules/osfile.jsm"), + ProfilerGetSymbols: () => + ChromeUtils.import("resource://gre/modules/ProfilerGetSymbols.jsm"), +}); 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"]} */ @@ -79,17 +60,17 @@ const UI_BASE_URL_PATH_DEFAULT = "/from- * @param {MinimallyTypedGeckoProfile} profile - The Gecko profile. * @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(); + const Services = lazy.Services(); // Find the most recently used window, as the DevTools client could be in a variety // of hosts. const win = Services.wm.getMostRecentWindow("navigator:browser"); if (!win) { throw new Error("No browser window"); } const browser = win.gBrowser; win.focus(); @@ -216,17 +197,17 @@ async function getSymbolTableFromDebugge ]; } /** * @param {string} path * @returns {Promise<boolean>} */ async function doesFileExistAtPath(path) { - const { OS } = lazyOS(); + const { OS } = lazy.OS(); try { const result = await OS.File.stat(path); return !result.isDir; } catch (e) { if (e instanceof OS.File.Error && e.becauseNoSuchFile) { return false; } throw e; @@ -249,31 +230,31 @@ async function doesFileExistAtPath(path) * 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<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 { OS } = lazy.OS(); const candidatePaths = []; for (const objdirPath of objdirs) { // Binaries are usually expected to exist at objdir/dist/bin/filename. candidatePaths.push(OS.Path.join(objdirPath, "dist", "bin", filename)); // Also search in the "objdir" directory itself (not just in dist/bin). // If, for some unforeseen reason, the relevant binary is not inside the // objdirs dist/bin/ directory, this provides a way out because it lets the // user specify the actual location. candidatePaths.push(OS.Path.join(objdirPath, filename)); } for (const path of candidatePaths) { if (await doesFileExistAtPath(path)) { - const { ProfilerGetSymbols } = lazyProfilerGetSymbols(); + const { ProfilerGetSymbols } = lazy.ProfilerGetSymbols(); try { return await ProfilerGetSymbols.getSymbolTable(path, path, breakpadId); } catch (e) { // ProfilerGetSymbols.getSymbolTable was unsuccessful. So either the // file wasn't parseable or its contents didn't match the specified // breakpadId, or some other error occurred. // Advance to the next candidate path. } @@ -307,17 +288,17 @@ function createMultiModalGetSymbolTableF 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(); + const { ProfilerGetSymbols } = lazy.ProfilerGetSymbols(); // 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. return ProfilerGetSymbols.getSymbolTable(path, debugPath, breakpadId); } // The file does not exist, which probably indicates that the profile was // obtained on a different machine, i.e. the debuggee is truly remote // (e.g. on an Android phone). @@ -350,48 +331,48 @@ function createMultiModalGetSymbolTableF } /** * Restarts the browser with a given environment variable set to a value. * * @type {RestartBrowserWithEnvironmentVariable} */ function restartBrowserWithEnvironmentVariable(envName, value) { - const { Services } = lazyServices(); - const { Cc, Ci } = lazyChrome(); + const Services = lazy.Services(); + const { Cc, Ci } = lazy.Chrome(); const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); env.set(envName, value); Services.startup.quit( Services.startup.eForceQuit | Services.startup.eRestart ); } /** * Gets an environment variable from the browser. * * @type {GetEnvironmentVariable} */ function getEnvironmentVariable(envName) { - const { Cc, Ci } = lazyChrome(); + const { Cc, Ci } = lazy.Chrome(); const env = Cc["@mozilla.org/process/environment;1"].getService( Ci.nsIEnvironment ); return env.get(envName); } /** * @param {Window} window * @param {string[]} objdirs * @param {(objdirs: string[]) => unknown} changeObjdirs */ function openFilePickerForObjdir(window, objdirs, changeObjdirs) { - const { Cc, Ci } = lazyChrome(); + const { Cc, Ci } = lazy.Chrome(); const FilePicker = Cc["@mozilla.org/filepicker;1"].createInstance( Ci.nsIFilePicker ); FilePicker.init(window, "Pick build directory", FilePicker.modeGetFolder); FilePicker.open(rv => { if (rv == FilePicker.returnOK) { const path = FilePicker.file.path; if (path && !objdirs.includes(path)) {
--- a/devtools/client/performance-new/moz.build +++ b/devtools/client/performance-new/moz.build @@ -10,16 +10,17 @@ DIRS += [ 'popup', ] DevToolsModules( 'browser.js', 'initializer.js', 'panel.js', 'preference-management.js', + 'typescript-lazy-load.jsm.js', 'utils.js', ) BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini'] XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini'] with Files('**'): BUG_COMPONENT = ('DevTools', 'Performance Tools (Profiler/Timeline)')
--- a/devtools/client/performance-new/popup/background.jsm.js +++ b/devtools/client/performance-new/popup/background.jsm.js @@ -9,16 +9,19 @@ * configuration of the profiler. It is in a JSM so that the logic can be shared * with both the popup client, and the keyboard shortcuts. The shortcuts don't need * 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. const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { createLazyLoaders } = ChromeUtils.import( + "resource://devtools/client/performance-new/typescript-lazy-load.jsm.js" +); // For some reason TypeScript was giving me an error when de-structuring AppConstants. I // suspect a bug in TypeScript was at play. const AppConstants = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ).AppConstants; /** * @typedef {import("../@types/perf").RecordingStateFromPreferences} RecordingStateFromPreferences @@ -43,92 +46,44 @@ const THREADS_PREF = "devtools.performan const OBJDIRS_PREF = "devtools.performance.recording.objdirs"; /** @type {PerformancePref["Duration"]} */ const DURATION_PREF = "devtools.performance.recording.duration"; /** @type {PerformancePref["Preset"]} */ const PRESET_PREF = "devtools.performance.recording.preset"; /** @type {PerformancePref["PopupFeatureFlag"]} */ const POPUP_FEATURE_FLAG_PREF = "devtools.performance.popup.feature-flag"; +// Lazily load the require function, when it's needed. +ChromeUtils.defineModuleGetter( + this, + "require", + "resource://devtools/shared/Loader.jsm" +); + // 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. - -/** - * 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(() => - ChromeUtils.import("resource://gre/modules/osfile.jsm") -); - -const lazyProfilerGetSymbols = requireLazy(() => - ChromeUtils.import("resource://gre/modules/ProfilerGetSymbols.jsm") -); - -const lazyBrowserModule = requireLazy(() => { - const { require } = ChromeUtils.import( - "resource://devtools/shared/Loader.jsm" - ); - const browserModule = require("devtools/client/performance-new/browser"); - return browserModule; +const lazy = createLazyLoaders({ + OS: () => ChromeUtils.import("resource://gre/modules/osfile.jsm"), + Utils: () => require("devtools/client/performance-new/utils"), + BrowserModule: () => require("devtools/client/performance-new/browser"), + RecordingUtils: () => + require("devtools/shared/performance-new/recording-utils"), + CustomizableUI: () => + ChromeUtils.import("resource:///modules/CustomizableUI.jsm"), + PreferenceManagement: () => + require("devtools/client/performance-new/preference-management"), + ProfilerGetSymbols: () => + ChromeUtils.import("resource://gre/modules/ProfilerGetSymbols.jsm"), + ProfilerMenuButton: () => + ChromeUtils.import( + "resource://devtools/client/performance-new/popup/menu-button.jsm.js" + ), }); -const lazyPreferenceManagement = requireLazy(() => { - const { require } = ChromeUtils.import( - "resource://devtools/shared/Loader.jsm" - ); - - const preferenceManagementModule = require("devtools/client/performance-new/preference-management"); - return preferenceManagementModule; -}); - -const lazyRecordingUtils = requireLazy(() => { - const { require } = ChromeUtils.import( - "resource://devtools/shared/Loader.jsm" - ); - - const recordingUtils = require("devtools/shared/performance-new/recording-utils"); - return recordingUtils; -}); - -const lazyUtils = requireLazy(() => { - const { require } = ChromeUtils.import( - "resource://devtools/shared/Loader.jsm" - ); - const recordingUtils = require("devtools/client/performance-new/utils"); - return recordingUtils; -}); - -const lazyProfilerMenuButton = requireLazy(() => - ChromeUtils.import( - "resource://devtools/client/performance-new/popup/menu-button.jsm.js" - ) -); - -const lazyCustomizableUI = requireLazy(() => - ChromeUtils.import("resource:///modules/CustomizableUI.jsm") -); - /** @type {Presets} */ const presets = { "web-developer": { label: "Web Developer", description: "Recommended preset for most web app debugging, with low overhead.", entries: 10000000, interval: 1, @@ -205,26 +160,26 @@ async function getSymbolsFromThisBrowser "and symbols for it can not be obtained. This usually happens if a content " + "process uses a library that's not used in the parent process - " + "Services.profiler.sharedLibraries only knows about libraries in the " + "parent process." ); } const { path, debugPath } = cachedLibInfo; - const { OS } = lazyOS(); + const { OS } = lazy.OS(); if (!OS.Path.split(path).absolute) { throw new Error( "Services.profiler.sharedLibraries did not contain an absolute path for " + `the library ${debugName} ${breakpadId}, so symbols for this library can not ` + "be obtained." ); } - const { ProfilerGetSymbols } = lazyProfilerGetSymbols(); + const { ProfilerGetSymbols } = lazy.ProfilerGetSymbols(); return ProfilerGetSymbols.getSymbolTable(path, debugPath, breakpadId); } /** * This function is called directly by devtools/startup/DevToolsStartup.jsm when * using the shortcut keys to capture a profile. * @type {() => Promise<void>} @@ -242,41 +197,41 @@ async function captureProfile() { .getProfileDataAsGzippedArrayBuffer() .catch( /** @type {(e: any) => {}} */ e => { console.error(e); return {}; } ); - const receiveProfile = lazyBrowserModule().receiveProfile; + const receiveProfile = lazy.BrowserModule().receiveProfile; receiveProfile(profile, getSymbolsFromThisBrowser); Services.profiler.StopProfiler(); } /** * This function is only called by devtools/startup/DevToolsStartup.jsm when * starting the profiler using the shortcut keys, through toggleProfiler below. * @param {PageContext} pageContext */ function startProfiler(pageContext) { - const { translatePreferencesToState } = lazyPreferenceManagement(); + const { translatePreferencesToState } = lazy.PreferenceManagement(); const { entries, interval, features, threads, duration, } = translatePreferencesToState( getRecordingPreferences(pageContext, Services.profiler.GetFeatures()) ); // Get the active BrowsingContext ID from browser. - const { getActiveBrowsingContextID } = lazyRecordingUtils(); + const { getActiveBrowsingContextID } = lazy.RecordingUtils(); const activeBrowsingContextID = getActiveBrowsingContextID(); Services.profiler.StartProfiler( entries, interval, features, threads, activeBrowsingContextID, @@ -347,17 +302,17 @@ function getPrefPostfix(pageContext) { case "devtools": case "aboutprofiling": // Don't use any postfix on the prefs. return ""; case "devtools-remote": case "aboutprofiling-remote": return ".remote"; default: { - const { UnhandledCaseError } = lazyUtils(); + const { UnhandledCaseError } = lazy.Utils(); throw new UnhandledCaseError(pageContext, "Page Context"); } } } /** * @param {PageContext} pageContext * @param {string[]} supportedFeatures @@ -521,17 +476,17 @@ function handleWebChannelMessage(channel return; } const messageFromFrontend = /** @type {MessageFromFrontend} */ (message); const { requestId } = messageFromFrontend; switch (messageFromFrontend.type) { case "STATUS_QUERY": { // The content page wants to know if this channel exists. It does, so respond // back to the ping. - const { ProfilerMenuButton } = lazyProfilerMenuButton(); + const { ProfilerMenuButton } = lazy.ProfilerMenuButton(); channel.send( { type: "STATUS_RESPONSE", menuButtonIsEnabled: ProfilerMenuButton.isInNavbar(), requestId, }, target ); @@ -544,22 +499,22 @@ function handleWebChannelMessage(channel "Could not find the owner document for the current browser while enabling " + "the profiler menu button" ); } // Ensure the widget is enabled. Services.prefs.setBoolPref(POPUP_FEATURE_FLAG_PREF, true); // Enable the profiler menu button. - const { ProfilerMenuButton } = lazyProfilerMenuButton(); + const { ProfilerMenuButton } = lazy.ProfilerMenuButton(); ProfilerMenuButton.addToNavbar(ownerDocument); // Dispatch the change event manually, so that the shortcuts will also be // added. - const { CustomizableUI } = lazyCustomizableUI(); + const { CustomizableUI } = lazy.CustomizableUI(); CustomizableUI.dispatchToolboxEvent("customizationchange"); // Open the popup with a message. ProfilerMenuButton.openPopup(ownerDocument); // Respond back that we've done it. channel.send( {
--- a/devtools/client/performance-new/popup/menu-button.jsm.js +++ b/devtools/client/performance-new/popup/menu-button.jsm.js @@ -1,94 +1,71 @@ /* 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").PerformancePref} PerformancePref - */ - -/** * This file controls the enabling and disabling of the menu button for the profiler. * Care should be taken to keep it minimal as it can be run with browser initialization. */ -/** - * 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; - }; -} - // Provide an exports object for the JSM to be properly read by TypeScript. /** @type {any} */ (this).exports = {}; -const lazyServices = requireLazy(() => - ChromeUtils.import("resource://gre/modules/Services.jsm") -); -const lazyCustomizableUI = requireLazy(() => - ChromeUtils.import("resource:///modules/CustomizableUI.jsm") +const { createLazyLoaders } = ChromeUtils.import( + "resource://devtools/client/performance-new/typescript-lazy-load.jsm.js" ); -const lazyCustomizableWidgets = requireLazy(() => - ChromeUtils.import("resource:///modules/CustomizableWidgets.jsm") -); -const lazyPopupPanel = requireLazy(() => - ChromeUtils.import( - "resource://devtools/client/performance-new/popup/panel.jsm.js" - ) -); + +const lazy = createLazyLoaders({ + Services: () => ChromeUtils.import("resource://gre/modules/Services.jsm"), + CustomizableUI: () => + ChromeUtils.import("resource:///modules/CustomizableUI.jsm"), + CustomizableWidgets: () => + ChromeUtils.import("resource:///modules/CustomizableWidgets.jsm"), + PopupPanel: () => + ChromeUtils.import( + "resource://devtools/client/performance-new/popup/panel.jsm.js" + ), +}); const WIDGET_ID = "profiler-button"; /** * Add the profiler button to the navbar. * * @param {ChromeDocument} document The browser's document. * @return {void} */ function addToNavbar(document) { - const { CustomizableUI } = lazyCustomizableUI(); + const { CustomizableUI } = lazy.CustomizableUI(); CustomizableUI.addWidgetToArea(WIDGET_ID, CustomizableUI.AREA_NAVBAR); } /** * Remove the widget and place it in the customization palette. This will also * disable the shortcuts. * * @return {void} */ function remove() { - const { CustomizableUI } = lazyCustomizableUI(); + const { CustomizableUI } = lazy.CustomizableUI(); CustomizableUI.removeWidgetFromArea(WIDGET_ID); } /** * See if the profiler menu button is in the navbar, or other active areas. The * placement is null when it's inactive in the customization palette. * * @return {boolean} */ function isInNavbar() { - const { CustomizableUI } = lazyCustomizableUI(); + const { CustomizableUI } = lazy.CustomizableUI(); return Boolean(CustomizableUI.getPlacementOfWidget("profiler-button")); } /** * Opens the popup for the profiler. * @param {Document} document */ function openPopup(document) { @@ -103,19 +80,19 @@ function openPopup(document) { /** * This function creates the widget definition for the CustomizableUI. It should * only be run if the profiler button is enabled. * @param {(isEnabled: boolean) => void} toggleProfilerKeyShortcuts * @return {void} */ function initialize(toggleProfilerKeyShortcuts) { - const { CustomizableUI } = lazyCustomizableUI(); - const { CustomizableWidgets } = lazyCustomizableWidgets(); - const { Services } = lazyServices(); + const { CustomizableUI } = lazy.CustomizableUI(); + const { CustomizableWidgets } = lazy.CustomizableWidgets(); + const { Services } = lazy.Services(); const widget = CustomizableUI.getWidget(WIDGET_ID); if (widget && widget.provider == CustomizableUI.PROVIDER_API) { // This widget has already been created. return; } const viewId = "PanelUI-profiler"; @@ -138,17 +115,17 @@ function initialize(toggleProfilerKeySho */ function handleCustomizationChange() { const isEnabled = isInNavbar(); toggleProfilerKeyShortcuts(isEnabled); if (!isEnabled) { // The profiler menu button is no longer in the navbar, make sure that the // "intro-displayed" preference is reset. - /** @type {PerformancePref["PopupIntroDisplayed"]} */ + /** @type {import("../@types/perf").PerformancePref["PopupIntroDisplayed"]} */ const popupIntroDisplayedPref = "devtools.performance.popup.intro-displayed"; Services.prefs.setBoolPref(popupIntroDisplayedPref, false); if (Services.profiler.IsActive()) { Services.profiler.StopProfiler(); } } @@ -174,17 +151,17 @@ function initialize(toggleProfilerKeySho // The popup logic is stored in a separate script so it doesn't have // to be parsed at browser startup, and will only be lazily loaded // when the popup is viewed. const { selectElementsInPanelview, createViewControllers, addPopupEventHandlers, initializePopup, - } = lazyPopupPanel(); + } = lazy.PopupPanel(); const panelElements = selectElementsInPanelview(event.target); const panelView = createViewControllers(panelState, panelElements); addPopupEventHandlers(panelState, panelElements, panelView); initializePopup(panelState, panelElements, panelView); } catch (error) { // Surface any errors better in the console. console.error(error); @@ -204,17 +181,17 @@ function initialize(toggleProfilerKeySho /** * Perform any general initialization for this widget. This is called once per * browser window. * * @type {(document: HTMLDocument) => void} */ onBeforeCreated: document => { - /** @type {PerformancePref["PopupIntroDisplayed"]} */ + /** @type {import("../@types/perf").PerformancePref["PopupIntroDisplayed"]} */ const popupIntroDisplayedPref = "devtools.performance.popup.intro-displayed"; // Determine the state of the popup's info being collapsed BEFORE the view // is shown, and update the collapsed state. This way the transition animation // isn't run. panelState.isInfoCollapsed = Services.prefs.getBoolPref( popupIntroDisplayedPref
--- a/devtools/client/performance-new/popup/panel.jsm.js +++ b/devtools/client/performance-new/popup/panel.jsm.js @@ -14,50 +14,29 @@ */ /** * @typedef {Object} State - The mutable state of the popup. * @property {Array<() => void>} cleanup - Functions to cleanup once the view is hidden. * @property {boolean} isInfoCollapsed */ -/** - * 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 { createLazyLoaders } = ChromeUtils.import( + "resource://devtools/client/performance-new/typescript-lazy-load.jsm.js" +); -// Provide an exports object for the JSM to be properly read by TypeScript. -/** @type {any} */ (this).module = {}; - -const lazyServices = requireLazy(() => - ChromeUtils.import("resource://gre/modules/Services.jsm") -); -const lazyPanelMultiView = requireLazy(() => - ChromeUtils.import("resource:///modules/PanelMultiView.jsm") -); -const lazyBackground = requireLazy(() => - ChromeUtils.import( - "resource://devtools/client/performance-new/popup/background.jsm.js" - ) -); +const lazy = createLazyLoaders({ + Services: () => ChromeUtils.import("resource://gre/modules/Services.jsm"), + PanelMultiView: () => + ChromeUtils.import("resource:///modules/PanelMultiView.jsm"), + Background: () => + ChromeUtils.import( + "resource://devtools/client/performance-new/popup/background.jsm.js" + ), +}); /** * This function collects all of the selection of the elements inside of the panel. * * @param {XULElement} panelview */ function selectElementsInPanelview(panelview) { const document = panelview.ownerDocument; @@ -122,39 +101,39 @@ function createViewControllers(state, el const { height } = info.getBoundingClientRect(); info.style.marginBlockEnd = `-${height}px`; } else { info.style.marginBlockEnd = "0"; } }, updatePresets() { - const { Services } = lazyServices(); - const { presets, getRecordingPreferences } = lazyBackground(); + const { Services } = lazy.Services(); + const { presets, getRecordingPreferences } = lazy.Background(); const { presetName } = getRecordingPreferences( "aboutprofiling", Services.profiler.GetFeatures() ); const preset = presets[presetName]; if (preset) { elements.presetDescription.style.display = "block"; elements.presetCustom.style.display = "none"; elements.presetDescription.textContent = preset.description; elements.presetsMenuList.value = presetName; } else { elements.presetDescription.style.display = "none"; elements.presetCustom.style.display = "block"; } - const { PanelMultiView } = lazyPanelMultiView(); + const { PanelMultiView } = lazy.PanelMultiView(); // Update the description height sizing. PanelMultiView.forNode(elements.panelview).descriptionHeightWorkaround(); }, updateProfilerActive() { - const { Services } = lazyServices(); + const { Services } = lazy.Services(); const isProfilerActive = Services.profiler.IsActive(); elements.inactive.setAttribute( "hidden", isProfilerActive ? "true" : "false" ); elements.active.setAttribute( "hidden", isProfilerActive ? "false" : "true" @@ -165,18 +144,18 @@ function createViewControllers(state, el createPresetsList() { // Check the DOM if the presets were built or not. We can't cache this value // in the `State` object, as the `State` object will be removed if the // button is removed from the toolbar, but the DOM changes will still persist. if (elements.menupopup.getAttribute("presetsbuilt") === "true") { // The presets were already built. return; } - const { Services } = lazyServices(); - const { presets } = lazyBackground(); + const { Services } = lazy.Services(); + const { presets } = lazy.Background(); const currentPreset = Services.prefs.getCharPref( "devtools.performance.recording.preset" ); const menuitems = Object.entries(presets).map(([id, preset]) => { const menuitem = elements.document.createXULElement("menuitem"); menuitem.setAttribute("label", preset.label); menuitem.setAttribute("value", id); @@ -225,17 +204,17 @@ function initializePopup(state, elements // the size of the container. It needs to wait a second before the bounding box // returns an actual size. view.updateInfoCollapse(); view.updateProfilerActive(); view.updatePresets(); // XUL <description> elements don't vertically size correctly, this is // the workaround for it. - const { PanelMultiView } = lazyPanelMultiView(); + const { PanelMultiView } = lazy.PanelMultiView(); PanelMultiView.forNode(elements.panelview).descriptionHeightWorkaround(); // Now wait for another rAF, and turn the animations back on. elements.window.requestAnimationFrame(() => { elements.header.setAttribute("animationready", "true"); }); }); } @@ -250,17 +229,17 @@ function initializePopup(state, elements * @param {ViewController} view */ function addPopupEventHandlers(state, elements, view) { const { changePreset, startProfiler, stopProfiler, captureProfile, - } = lazyBackground(); + } = lazy.Background(); /** * Adds a handler that automatically is removed once the panel is hidden. * * @param {HTMLElement} element * @param {string} type * @param {(event: Event) => void} handler */ @@ -321,25 +300,28 @@ function addPopupEventHandlers(state, el }); addHandler(elements.presetsCustomButton, "click", () => { elements.window.openTrustedLinkIn("about:profiling", "tab"); view.hidePopup(); }); // Update the view when the profiler starts/stops. - const { Services } = lazyServices(); + const { Services } = lazy.Services(); Services.obs.addObserver(view.updateProfilerActive, "profiler-started"); Services.obs.addObserver(view.updateProfilerActive, "profiler-stopped"); state.cleanup.push(() => { Services.obs.removeObserver(view.updateProfilerActive, "profiler-started"); Services.obs.removeObserver(view.updateProfilerActive, "profiler-stopped"); }); } +// Provide an exports object for the JSM to be properly read by TypeScript. +/** @type {any} */ (this).module = {}; + module.exports = { selectElementsInPanelview, createViewControllers, addPopupEventHandlers, initializePopup, }; // Object.keys() confuses the linting which expects a static array expression.
new file mode 100644 --- /dev/null +++ b/devtools/client/performance-new/typescript-lazy-load.jsm.js @@ -0,0 +1,54 @@ +/* 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"; + +/** + * TypeScript can't understand the lazyRequireGetter mechanism, due to how it defines + * properties as a getter. This function, instead provides lazy loading in a + * TypeScript-friendly manner. It applies the lazy load memoization to each property + * of the provided object. + * + * Example usage: + * + * const lazy = createLazyLoaders({ + * moduleA: () => require("module/a"), + * moduleB: () => require("module/b"), + * }); + * + * Later: + * + * const moduleA = lazy.moduleA(); + * const { objectInModuleB } = lazy.moduleB(); + * + * @template T + * @param {T} definition - An object where each property has a function that loads a module. + * @returns {T} - The load memoized version of T. + */ +function createLazyLoaders(definition) { + /** @type {any} */ + const result = {}; + for (const [key, callback] of Object.entries(definition)) { + /** @type {any} */ + let cache; + result[key] = () => { + if (cache === undefined) { + cache = callback(); + } + return cache; + }; + } + return result; +} + +// Provide an exports object for the JSM to be properly read by TypeScript. +/** @type {any} */ (this).module = {}; + +module.exports = { + createLazyLoaders, +}; + +// Object.keys() confuses the linting which expects a static array expression. +// eslint-disable-next-line +var EXPORTED_SYMBOLS = Object.keys(module.exports);