initializer.js (7981B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 // @ts-check 5 /* exported gInit, gDestroy, loader */ 6 7 /** 8 * @typedef {import("../@types/perf").PerfFront} PerfFront 9 * @typedef {import("../@types/perf").PreferenceFront} PreferenceFront 10 * @typedef {import("../@types/perf").RecordingSettings} RecordingSettings 11 * @typedef {import("../@types/perf").PageContext} PageContext 12 * @typedef {import("../@types/perf").PanelWindow} PanelWindow 13 * @typedef {import("../@types/perf").Store} Store 14 * @typedef {import("../@types/perf").ProfileCaptureResult} ProfileCaptureResult 15 * @typedef {import("../@types/perf").ProfilerViewMode} ProfilerViewMode 16 * @typedef {import("../@types/perf").RootTraits} RootTraits 17 */ 18 "use strict"; 19 20 { 21 // Create the browser loader, but take care not to conflict with 22 // TypeScript. See devtools/client/performance-new/typescript.md and 23 // the section on "Do not overload require" for more information. 24 25 const { BrowserLoader } = ChromeUtils.importESModule( 26 "resource://devtools/shared/loader/browser-loader.sys.mjs" 27 ); 28 const browserLoader = BrowserLoader({ 29 baseURI: "resource://devtools/client/performance-new/", 30 window, 31 }); 32 33 /** 34 * @type {any} - Coerce the current scope into an `any`, and assign the 35 * loaders to the scope. They can then be used freely below. 36 */ 37 const scope = this; 38 scope.require = browserLoader.require; 39 scope.loader = browserLoader.loader; 40 } 41 42 const ReactDOM = require("resource://devtools/client/shared/vendor/react-dom.mjs"); 43 const React = require("resource://devtools/client/shared/vendor/react.mjs"); 44 const FluentReact = require("resource://devtools/client/shared/vendor/fluent-react.js"); 45 const { 46 FluentL10n, 47 } = require("resource://devtools/client/shared/fluent-l10n/fluent-l10n.js"); 48 const Provider = React.createFactory( 49 require("resource://devtools/client/shared/vendor/react-redux.js").Provider 50 ); 51 const LocalizationProvider = React.createFactory( 52 FluentReact.LocalizationProvider 53 ); 54 const DevToolsPanel = React.createFactory( 55 require("resource://devtools/client/performance-new/components/panel/DevToolsPanel.js") 56 ); 57 const ProfilerEventHandling = React.createFactory( 58 require("resource://devtools/client/performance-new/components/panel/ProfilerEventHandling.js") 59 ); 60 const ProfilerPreferenceObserver = React.createFactory( 61 require("resource://devtools/client/performance-new/components/shared/ProfilerPreferenceObserver.js") 62 ); 63 const createStore = require("resource://devtools/client/shared/redux/create-store.js"); 64 const selectors = require("resource://devtools/client/performance-new/store/selectors.js"); 65 const reducers = require("resource://devtools/client/performance-new/store/reducers.js"); 66 const actions = require("resource://devtools/client/performance-new/store/actions.js"); 67 const { 68 openProfilerTab, 69 } = require("resource://devtools/client/performance-new/shared/browser.js"); 70 const { createLocalSymbolicationService } = ChromeUtils.importESModule( 71 "resource://devtools/shared/performance-new/symbolication.sys.mjs" 72 ); 73 const { registerProfileCaptureForBrowser } = ChromeUtils.importESModule( 74 "resource://devtools/client/performance-new/shared/background.sys.mjs" 75 ); 76 const { presets, getProfilerViewModeForCurrentPreset } = 77 ChromeUtils.importESModule( 78 "resource://devtools/shared/performance-new/prefs-presets.sys.mjs" 79 ); 80 81 /** 82 * This file initializes the DevTools Panel UI. It is in charge of initializing 83 * the DevTools specific environment, and then passing those requirements into 84 * the UI. 85 */ 86 87 /** 88 * Initialize the panel by creating a redux store, and render the root component. 89 * 90 * @param {PerfFront} perfFront - The Perf actor's front. Used to start and stop recordings. 91 * @param {RootTraits} traits - The traits coming from the root actor. This 92 * makes it possible to change some code path 93 * depending on the server version. 94 * @param {PageContext} pageContext - The context that the UI is being loaded in under. 95 * @param {(() => void)} openAboutProfiling - Optional call to open about:profiling 96 */ 97 async function gInit(perfFront, traits, pageContext, openAboutProfiling) { 98 const store = createStore(reducers); 99 const isSupportedPlatform = await perfFront.isSupportedPlatform(); 100 const supportedFeatures = await perfFront.getSupportedFeatures(); 101 102 { 103 // Expose the store as a global, for testing. 104 const anyWindow = /** @type {any} */ (window); 105 const panelWindow = /** @type {PanelWindow} */ (anyWindow); 106 // The store variable is a `ReduxStore`, not our `Store` type, as defined 107 // in perf.d.ts. Coerce it into the `Store` type. 108 const anyStore = /** @type {any} */ (store); 109 panelWindow.gStore = anyStore; 110 } 111 112 const l10n = new FluentL10n(); 113 await l10n.init([ 114 "devtools/client/perftools.ftl", 115 // For -brand-shorter-name used in some profiler preset descriptions. 116 "branding/brand.ftl", 117 // Needed for the onboarding UI 118 "devtools/client/toolbox-options.ftl", 119 "toolkit/branding/brandings.ftl", 120 ]); 121 122 // Do some initialization, especially with privileged things that are part of the 123 // the browser. 124 store.dispatch( 125 actions.initializeStore({ 126 isSupportedPlatform, 127 presets, 128 supportedFeatures, 129 pageContext, 130 }) 131 ); 132 133 /** 134 * @param {MockedExports.ProfileAndAdditionalInformation | null} profileAndAdditionalInformation 135 * @param {Error | string} [error] 136 */ 137 const onProfileReceived = async (profileAndAdditionalInformation, error) => { 138 const objdirs = selectors.getObjdirs(store.getState()); 139 const profilerViewMode = getProfilerViewModeForCurrentPreset(pageContext); 140 const browser = await openProfilerTab({ profilerViewMode }); 141 142 if (error || !profileAndAdditionalInformation) { 143 if (!error) { 144 error = 145 "No profile data has been passed to onProfileReceived, and no specific error has been specified. This is unexpected."; 146 } 147 /** 148 * @type {ProfileCaptureResult} 149 */ 150 const profileCaptureResult = { 151 type: "ERROR", 152 error: typeof error === "string" ? new Error(error) : error, 153 }; 154 registerProfileCaptureForBrowser( 155 browser, 156 profileCaptureResult, 157 null, 158 null 159 ); 160 return; 161 } 162 163 const { profile, additionalInformation } = profileAndAdditionalInformation; 164 const sharedLibraries = additionalInformation?.sharedLibraries ?? []; 165 if (!sharedLibraries.length) { 166 console.error( 167 `[devtools perf] No shared libraries information have been retrieved from the profiled target, this is unexpected.` 168 ); 169 } 170 const symbolicationService = createLocalSymbolicationService( 171 sharedLibraries, 172 objdirs, 173 perfFront 174 ); 175 176 /** 177 * @type {ProfileCaptureResult} 178 */ 179 const profileCaptureResult = { type: "SUCCESS", profile }; 180 181 registerProfileCaptureForBrowser( 182 browser, 183 profileCaptureResult, 184 symbolicationService, 185 additionalInformation?.jsSources ?? null 186 ); 187 }; 188 189 const onEditSettingsLinkClicked = openAboutProfiling; 190 191 ReactDOM.render( 192 Provider( 193 { store }, 194 LocalizationProvider( 195 { bundles: l10n.getBundles() }, 196 React.createElement( 197 React.Fragment, 198 null, 199 ProfilerEventHandling({ perfFront, traits }), 200 ProfilerPreferenceObserver(), 201 DevToolsPanel({ 202 perfFront, 203 onProfileReceived, 204 onEditSettingsLinkClicked, 205 }) 206 ) 207 ) 208 ), 209 document.querySelector("#root") 210 ); 211 212 window.addEventListener("unload", () => gDestroy(), { once: true }); 213 } 214 215 function gDestroy() { 216 const root = document.querySelector("#root"); 217 if (root) { 218 ReactDOM.unmountComponentAtNode(root); 219 } 220 }