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