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 5/** 6 * This file contains the shared types for the performance-new client. 7 */ 8 9import { 10 Reducer as ReduxReducer, 11 Store as ReduxStore, 12} from "devtools/client/shared/vendor/redux"; 13 14export interface PanelWindow { 15 gToolbox?: any; 16 gStore?: Store; 17 gInit( 18 perfFront: PerfFront, 19 traits: RootTraits, 20 pageContext: PageContext, 21 openAboutProfiling: () => void 22 ): Promise<void>; 23 gDestroy(): void; 24 gIsPanelDestroyed?: boolean; 25} 26 27/** 28 * TS-TODO - Stub. 29 */ 30export interface Target { 31 // TODO 32 client: any; 33} 34 35/** 36 * TS-TODO - Stub. 37 */ 38export interface Toolbox { 39 target: Target; 40} 41 42/** 43 * TS-TODO - Stub. 44 */ 45export interface Commands { 46 client: any; 47 targetCommand: { 48 targetFront: { 49 getTrait: ( 50 traitName: "noDisablingOnPrivateBrowsing" 51 ) => boolean | undefined; 52 }; 53 }; 54} 55 56/** 57 * TS-TODO - Stub. 58 */ 59export interface PerfFront { 60 startProfiler: (options: RecordingSettings) => Promise<boolean>; 61 getProfileAndStopProfiler: () => Promise<any>; 62 stopProfilerAndDiscardProfile: () => Promise<void>; 63 getSymbolTable: ( 64 path: string, 65 breakpadId: string 66 ) => Promise<[number[], number[], number[]]>; 67 isActive: () => Promise<boolean>; 68 isSupportedPlatform: () => Promise<boolean>; 69 isLockedForPrivateBrowsing: () => Promise<boolean>; 70 on: (type: string, listener: () => void) => void; 71 off: (type: string, listener: () => void) => void; 72 destroy: () => void; 73 getSupportedFeatures: () => Promise<string[]>; 74} 75 76/** 77 * TS-TODO - Stub 78 */ 79export interface PreferenceFront { 80 clearUserPref: (prefName: string) => Promise<void>; 81 getStringPref: (prefName: string) => Promise<string>; 82 setStringPref: (prefName: string, value: string) => Promise<void>; 83 getCharPref: (prefName: string) => Promise<string>; 84 setCharPref: (prefName: string, value: string) => Promise<void>; 85 getIntPref: (prefName: string) => Promise<number>; 86 setIntPref: (prefName: string, value: number) => Promise<void>; 87} 88 89export interface RootTraits { 90 // In Firefox >= 98, this will be true, and will be missing for older 91 // versions. The functionality controlled by this property can be removed once 92 // Firefox 98 hits release. 93 noDisablingOnPrivateBrowsing?: boolean; 94 95 // There are other properties too, but we don't use them here as they're not 96 // related to the performance panel. 97} 98 99export type RecordingState = 100 // The initial state before we've queried the PerfActor 101 | "not-yet-known" 102 // The profiler is available, we haven't started recording yet. 103 | "available-to-record" 104 // An async request has been sent to start the profiler. 105 | "request-to-start-recording" 106 // An async request has been sent to get the profile and stop the profiler. 107 | "request-to-get-profile-and-stop-profiler" 108 // An async request has been sent to stop the profiler. 109 | "request-to-stop-profiler" 110 // The profiler notified us that our request to start it actually started 111 // it, or it was already started. 112 | "recording" 113 // Profiling is not available when in private browsing mode. 114 | "locked-by-private-browsing"; 115 116// We are currently migrating to a new UX workflow with about:profiling. 117// This type provides an easy way to change the implementation based 118// on context. 119export type PageContext = 120 | "devtools" 121 | "devtools-remote" 122 | "aboutprofiling" 123 | "aboutprofiling-remote"; 124 125export type PrefPostfix = "" | ".remote"; 126 127export interface State { 128 recordingState: RecordingState; 129 recordingUnexpectedlyStopped: boolean; 130 isSupportedPlatform: boolean | null; 131 recordingSettings: RecordingSettings; 132 initializedValues: InitializedValues | null; 133 promptEnvRestart: null | string; 134} 135 136export type Selector<T> = (state: State) => T; 137 138export type ThunkDispatch = <Returns>(action: ThunkAction<Returns>) => Returns; 139export type PlainDispatch = (action: Action) => Action; 140export type GetState = () => State; 141export type SymbolTableAsTuple = [Uint32Array, Uint32Array, Uint8Array]; 142 143/** 144 * The `dispatch` function can accept either a plain action or a thunk action. 145 * This is similar to a type `(action: Action | ThunkAction) => any` except this 146 * allows to type the return value as well. 147 */ 148export type Dispatch = PlainDispatch & ThunkDispatch; 149 150export type ThunkAction<Returns> = ({ 151 dispatch, 152 getState, 153}: { 154 dispatch: Dispatch; 155 getState: GetState; 156}) => Returns; 157 158export interface Library { 159 start: number; 160 end: number; 161 offset: number; 162 name: string; 163 path: string; 164 debugName: string; 165 debugPath: string; 166 breakpadId: string; 167 arch: string; 168} 169 170/** 171 * Only provide types for the GeckoProfile as much as we need it. There is no 172 * reason to maintain a full type definition here. 173 */ 174export interface MinimallyTypedGeckoProfile { 175 libs: Library[]; 176 processes: MinimallyTypedGeckoProfile[]; 177} 178 179export type GetSymbolTableCallback = ( 180 debugName: string, 181 breakpadId: string 182) => Promise<SymbolTableAsTuple>; 183 184export interface SymbolicationService { 185 getSymbolTable: GetSymbolTableCallback; 186 querySymbolicationApi: (path: string, requestJson: string) => Promise<string>; 187} 188 189export type ReceiveProfile = ( 190 geckoProfile: MinimallyTypedGeckoProfile, 191 profilerViewMode: ProfilerViewMode | undefined, 192 getSymbolTableCallback: GetSymbolTableCallback 193) => void; 194 195/** 196 * This is the type signature for a function to restart the browser with a given 197 * environment variable. Currently only implemented for the popup. 198 */ 199export type RestartBrowserWithEnvironmentVariable = ( 200 envName: string, 201 value: string 202) => void; 203 204/** 205 * This is the type signature for the event listener that's called once the 206 * profile has been obtained. 207 */ 208export type OnProfileReceived = (profile: MinimallyTypedGeckoProfile) => void; 209 210/** 211 * This is the type signature for a function to query the browser for an 212 * environment variable. Currently only implemented for the popup. 213 */ 214export type GetEnvironmentVariable = (envName: string) => string; 215 216/** 217 * This is the type signature for a function to query the browser for the 218 * ID of the active tab. 219 */ 220export type GetActiveBrowserID = () => number; 221 222/** 223 * This interface is injected into profiler.firefox.com 224 */ 225interface GeckoProfilerFrameScriptInterface { 226 getProfile: () => Promise<MinimallyTypedGeckoProfile>; 227 getSymbolTable: GetSymbolTableCallback; 228} 229 230export interface RecordingSettings { 231 presetName: string; 232 entries: number; 233 interval: number; // in milliseconds 234 features: string[]; 235 threads: string[]; 236 objdirs: string[]; 237 // The duration is currently not wired up to the UI yet. See Bug 1587165. 238 duration?: number; 239} 240 241/** 242 * A Redux Reducer that knows about the performance-new client's Actions. 243 */ 244export type Reducer<S> = (state: S | undefined, action: Action) => S; 245 246export interface InitializedValues { 247 // The current list of presets, loaded in from a JSM. 248 presets: Presets; 249 // Determine the current page context. 250 pageContext: PageContext; 251 // The list of profiler features that the current target supports. 252 supportedFeatures: string[]; 253 // Allow about:profiling to switch back to the remote devtools panel. 254 openRemoteDevTools?: () => void; 255} 256 257/** 258 * Export a store that is opinionated about our State definition, and the union 259 * of all Actions, as well as specific Dispatch behavior. 260 */ 261export type Store = ReduxStore<State, Action>; 262 263export type Action = 264 | { 265 type: "REPORT_PROFILER_READY"; 266 isActive: boolean; 267 isLockedForPrivateBrowsing: boolean; 268 } 269 | { 270 type: "REPORT_PROFILER_STARTED"; 271 } 272 | { 273 type: "REPORT_PROFILER_STOPPED"; 274 } 275 | { 276 type: "REPORT_PRIVATE_BROWSING_STARTED"; 277 } 278 | { 279 type: "REPORT_PRIVATE_BROWSING_STOPPED"; 280 } 281 | { 282 type: "REQUESTING_TO_START_RECORDING"; 283 } 284 | { 285 type: "REQUESTING_TO_STOP_RECORDING"; 286 } 287 | { 288 type: "REQUESTING_PROFILE"; 289 } 290 | { 291 type: "OBTAINED_PROFILE"; 292 } 293 | { 294 type: "CHANGE_INTERVAL"; 295 interval: number; 296 } 297 | { 298 type: "CHANGE_ENTRIES"; 299 entries: number; 300 } 301 | { 302 type: "CHANGE_FEATURES"; 303 features: string[]; 304 promptEnvRestart: string | null; 305 } 306 | { 307 type: "CHANGE_THREADS"; 308 threads: string[]; 309 } 310 | { 311 type: "CHANGE_OBJDIRS"; 312 objdirs: string[]; 313 } 314 | { 315 type: "INITIALIZE_STORE"; 316 isSupportedPlatform: boolean; 317 presets: Presets; 318 pageContext: PageContext; 319 openRemoteDevTools?: () => void; 320 supportedFeatures: string[]; 321 } 322 | { 323 type: "CHANGE_PRESET"; 324 presetName: string; 325 preset: PresetDefinition | undefined; 326 } 327 | { 328 type: "UPDATE_SETTINGS_FROM_PREFERENCES"; 329 recordingSettingsFromPreferences: RecordingSettings; 330 }; 331 332export interface InitializeStoreValues { 333 isSupportedPlatform: boolean; 334 presets: Presets; 335 pageContext: PageContext; 336 supportedFeatures: string[]; 337 openRemoteDevTools?: () => void; 338} 339 340export type PopupBackgroundFeatures = { [feature: string]: boolean }; 341 342// TS-TODO - Stub 343export interface ContentFrameMessageManager { 344 addMessageListener: (event: string, listener: (event: any) => void) => void; 345 addEventListener: (event: string, listener: (event: any) => void) => void; 346 sendAsyncMessage: (name: string, data: any) => void; 347} 348 349/** 350 * This interface serves as documentation for all of the prefs used by the 351 * performance-new client. Each preference access string access can be coerced to 352 * one of the properties of this interface. 353 */ 354export interface PerformancePref { 355 /** 356 * The recording preferences by default are controlled by different presets. 357 * This pref stores that preset. 358 */ 359 Preset: "devtools.performance.recording.preset"; 360 /** 361 * Stores the total number of entries to be used in the profile buffer. 362 */ 363 Entries: "devtools.performance.recording.entries"; 364 /** 365 * The recording interval, stored in microseconds. Note that the StartProfiler 366 * interface uses milliseconds, but this lets us store higher precision numbers 367 * inside of an integer preference store. 368 */ 369 Interval: "devtools.performance.recording.interval"; 370 /** 371 * The features enabled for the profiler, stored as a comma-separated list. 372 */ 373 Features: "devtools.performance.recording.features"; 374 /** 375 * The threads to profile, stored as a comma-separated list. 376 */ 377 Threads: "devtools.performance.recording.threads"; 378 /** 379 * The location of the objdirs to use, stored as a comma-separated list. 380 */ 381 ObjDirs: "devtools.performance.recording.objdirs"; 382 /** 383 * The duration of the profiling window to use in seconds. Setting this to 0 384 * will cause no profile window to be used, and the values will naturally roll 385 * off from the profiling buffer. 386 * 387 * This is currently not hooked up to any UI. See Bug 1587165. 388 */ 389 Duration: "devtools.performance.recording.duration"; 390 /** 391 * Normally this defaults to https://profiler.firefox.com, but this can be overridden 392 * to point the profiler to a different URL, such as http://localhost:4242/ for 393 * local development workflows. 394 */ 395 UIBaseUrl: "devtools.performance.recording.ui-base-url"; 396 /** 397 * This pref allows tests to override the /from-browser in order to more easily 398 * test the profile injection mechanism. 399 */ 400 UIBaseUrlPathPref: "devtools.performance.recording.ui-base-url-path"; 401 /** 402 * This controls whether we enable the active tab view when capturing in web 403 * developer preset. 404 * We're not enabling the active-tab view in all environments until we 405 * iron out all its issues. 406 */ 407 UIEnableActiveTabView: "devtools.performance.recording.active-tab-view.enabled"; 408 /** 409 * The profiler popup has some introductory text explaining what it is the first 410 * time that you open it. After that, it is not displayed by default. 411 */ 412 PopupIntroDisplayed: "devtools.performance.popup.intro-displayed"; 413 /** 414 * This preference is used outside of the performance-new type system 415 * (in DevToolsStartup). It toggles the availability of the profiler menu 416 * button in the customization palette. 417 */ 418 PopupFeatureFlag: "devtools.performance.popup.feature-flag"; 419} 420 421/* The next 2 types bring some duplication from gecko.d.ts, but this is simpler 422 * this way. */ 423 424/** 425 * This is a function called by a preference observer. 426 */ 427export type PrefObserverFunction = ( 428 aSubject: nsIPrefBranch, 429 aTopic: "nsPref:changed", 430 aData: string 431) => unknown; 432 433/** 434 * This is the type of an observer we can pass to Service.prefs.addObserver and 435 * Service.prefs.removeObserver. 436 */ 437export type PrefObserver = 438 | PrefObserverFunction 439 | { observe: PrefObserverFunction }; 440 441/** 442 * Scale a number value. 443 */ 444export type NumberScaler = (value: number) => number; 445 446/** 447 * A collection of functions to scale numbers. 448 */ 449export interface ScaleFunctions { 450 fromFractionToValue: NumberScaler; 451 fromValueToFraction: NumberScaler; 452 fromFractionToSingleDigitValue: NumberScaler; 453 steps: number; 454} 455 456/** 457 * View mode for the Firefox Profiler front-end timeline. 458 * `undefined` is defaulted to full automatically. 459 */ 460export type ProfilerViewMode = "full" | "active-tab" | "origins"; 461 462export interface PresetDefinition { 463 entries: number; 464 interval: number; 465 features: string[]; 466 threads: string[]; 467 duration: number; 468 profilerViewMode?: ProfilerViewMode; 469 l10nIds: { 470 popup: { 471 label: string; 472 description: string; 473 }; 474 devtools: { 475 label: string; 476 description: string; 477 }; 478 }; 479} 480 481export interface Presets { 482 [presetName: string]: PresetDefinition; 483} 484 485// Should be kept in sync with the types in https://github.com/firefox-devtools/profiler/blob/main/src/app-logic/web-channel.js . 486// Compatibility is handled as follows: 487// - The front-end needs to worry about compatibility and handle older browser versions. 488// - The browser can require the latest front-end version and does not need to keep any legacy functionality for older front-end versions. 489 490type MessageFromFrontend = { 491 requestId: number; 492} & RequestFromFrontend; 493 494export type RequestFromFrontend = 495 | StatusQueryRequest 496 | EnableMenuButtonRequest 497 | GetProfileRequest 498 | GetSymbolTableRequest 499 | QuerySymbolicationApiRequest; 500 501type StatusQueryRequest = { type: "STATUS_QUERY" }; 502type EnableMenuButtonRequest = { type: "ENABLE_MENU_BUTTON" }; 503type GetProfileRequest = { type: "GET_PROFILE" }; 504type GetSymbolTableRequest = { 505 type: "GET_SYMBOL_TABLE"; 506 debugName: string; 507 breakpadId: string; 508}; 509type QuerySymbolicationApiRequest = { 510 type: "QUERY_SYMBOLICATION_API"; 511 path: string; 512 requestJson: string; 513}; 514 515export type MessageToFrontend<R> = 516 | OutOfBandErrorMessageToFrontend 517 | ErrorResponseMessageToFrontend 518 | SuccessResponseMessageToFrontend<R>; 519 520type OutOfBandErrorMessageToFrontend = { 521 errno: number; 522 error: string; 523}; 524 525type ErrorResponseMessageToFrontend = { 526 type: "ERROR_RESPONSE"; 527 requestId: number; 528 error: string; 529}; 530 531type SuccessResponseMessageToFrontend<R> = { 532 type: "SUCCESS_RESPONSE"; 533 requestId: number; 534 response: R; 535}; 536 537export type ResponseToFrontend = 538 | StatusQueryResponse 539 | EnableMenuButtonResponse 540 | GetProfileResponse 541 | GetSymbolTableResponse 542 | QuerySymbolicationApiResponse; 543 544type StatusQueryResponse = { 545 menuButtonIsEnabled: boolean; 546 // The version indicates which message types are supported by the browser. 547 // No version: 548 // Shipped in Firefox 76. 549 // Supports the following message types: 550 // - STATUS_QUERY 551 // - ENABLE_MENU_BUTTON 552 // Version 1: 553 // Shipped in Firefox 93. 554 // Adds support for the following message types: 555 // - GET_PROFILE 556 // - GET_SYMBOL_TABLE 557 // - QUERY_SYMBOLICATION_API 558 version: number; 559}; 560type EnableMenuButtonResponse = void; 561type GetProfileResponse = ArrayBuffer | MinimallyTypedGeckoProfile; 562type GetSymbolTableResponse = SymbolTableAsTuple; 563type QuerySymbolicationApiResponse = string; 564 565/** 566 * This represents an event channel that can talk to a content page on the web. 567 * This interface is a manually typed version of toolkit/modules/WebChannel.jsm 568 * and is opinionated about the types of messages we can send with it. 569 * 570 * The definition is here rather than gecko.d.ts because it was simpler than getting 571 * generics working with the ChromeUtils.import machinery. 572 */ 573export class ProfilerWebChannel { 574 constructor(id: string, url: MockedExports.nsIURI); 575 send: ( 576 message: MessageToFrontend<ResponseToFrontend>, 577 target: MockedExports.WebChannelTarget 578 ) => void; 579 listen: ( 580 handler: ( 581 idle: string, 582 message: MessageFromFrontend, 583 target: MockedExports.WebChannelTarget 584 ) => void 585 ) => void; 586} 587 588/** 589 * The per-tab information that is stored when a new profile is captured 590 * and a profiler tab is opened, to serve the correct profile to the tab 591 * that sends the WebChannel message. 592 */ 593export type ProfilerBrowserInfo = { 594 profileCaptureResult: ProfileCaptureResult; 595 symbolicationService: SymbolicationService; 596}; 597 598export type ProfileCaptureResult = 599 | { 600 type: "SUCCESS"; 601 profile: MinimallyTypedGeckoProfile | ArrayBuffer; 602 } 603 | { 604 type: "ERROR"; 605 error: Error; 606 }; 607 608/** 609 * Describes all of the profiling features that can be turned on and 610 * off in about:profiling. 611 */ 612export interface FeatureDescription { 613 // The name of the feature as shown in the UI. 614 name: string; 615 // The key value of the feature, this will be stored in prefs, and used in the 616 // nsiProfiler interface. 617 value: string; 618 // The full description of the preset, this will need to be localized. 619 title: string; 620 // This will give the user a hint that it's recommended on. 621 recommended?: boolean; 622 // This will give the user a hint that it's an experimental feature. 623 experimental?: boolean; 624 // This will give a reason if the feature is disabled. 625 disabledReason?: string; 626} 627 628// The key has the shape `${debugName}:${breakpadId}`. 629export type LibInfoMapKey = string; 630 631// This is a subset of the full Library struct. 632export type LibInfoMapValue = { 633 name: string; 634 path: string; 635 debugName: string; 636 debugPath: string; 637 breakpadId: string; 638 arch: string; 639}; 640 641export type SymbolicationWorkerInitialMessage = { 642 request: SymbolicationWorkerRequest; 643 // A map that allows looking up library info based on debugName + breakpadId. 644 // This is rather redundant at the moment, but it will make more sense once 645 // we can request symbols for multiple different libraries with one worker 646 // message. 647 libInfoMap: Map<LibInfoMapKey, LibInfoMapValue>; 648 // An array of objdir paths on the host machine that should be searched for 649 // relevant build artifacts. 650 objdirs: string[]; 651 // The profiler-get-symbols wasm module. 652 module: WebAssembly.Module; 653}; 654 655export type SymbolicationWorkerRequest = 656 | { 657 type: "GET_SYMBOL_TABLE"; 658 // The debugName of the binary whose symbols should be obtained. 659 debugName: string; 660 // The breakpadId for the binary whose symbols should be obtained. 661 breakpadId: string; 662 } 663 | { 664 type: "QUERY_SYMBOLICATION_API"; 665 // The API entry path, such as "/symbolicate/v5". 666 path: string; 667 // The payload JSON, as a string. 668 requestJson: string; 669 }; 670 671export type SymbolicationWorkerError = { 672 name: string; 673 message: string; 674 fileName?: string; 675 lineNumber?: number; 676}; 677 678export type SymbolicationWorkerReplyData<R> = 679 | { 680 result: R; 681 } 682 | { 683 error: SymbolicationWorkerError; 684 }; 685 686// This type is used in the symbolication worker for the return type of the 687// FileAndPathHelper's readFile method. 688// FIXME: Or rather, this type *would* be used if the worker code was checked 689// by TypeScript. 690export interface FileHandle { 691 // Return the length of the file in bytes. 692 getLength: () => number; 693 // Synchronously read the bytes at offset `offset` into the array `dest`. 694 readBytesInto: (dest: Uint8Array, offset: number) => void; 695 // Called when the file is no longer needed, to allow closing the file. 696 drop: () => void; 697} 698