xref: /aosp_15_r20/external/perfetto/ui/src/plugins/dev.perfetto.RecordTrace/chrome_settings.ts (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import m from 'mithril';
16import {DataSource} from './recordingV2/recording_interfaces_v2';
17import {
18  RecordingState,
19  getBuiltinChromeCategoryList,
20  isChromeTarget,
21} from './state';
22import {
23  MultiSelect,
24  MultiSelectDiff,
25  Option as MultiSelectOption,
26} from '../../widgets/multiselect';
27import {Section} from '../../widgets/section';
28import {CategoryGetter, CompactProbe, Toggle} from './record_widgets';
29import {RecordingSectionAttrs} from './recording_sections';
30
31function extractChromeCategories(
32  dataSources: DataSource[],
33): string[] | undefined {
34  for (const dataSource of dataSources) {
35    if (dataSource.name === 'chromeCategories') {
36      return dataSource.descriptor as string[];
37    }
38  }
39  return undefined;
40}
41
42class ChromeCategoriesSelection
43  implements m.ClassComponent<RecordingSectionAttrs>
44{
45  private recState: RecordingState;
46  private defaultCategoryOptions: MultiSelectOption[] | undefined = undefined;
47  private disabledByDefaultCategoryOptions: MultiSelectOption[] | undefined =
48    undefined;
49
50  constructor({attrs}: m.CVnode<RecordingSectionAttrs>) {
51    this.recState = attrs.recState;
52  }
53
54  private updateValue(attrs: CategoryGetter, diffs: MultiSelectDiff[]) {
55    const values = attrs.get(this.recState.recordConfig);
56    for (const diff of diffs) {
57      const value = diff.id;
58      const index = values.indexOf(value);
59      const enabled = diff.checked;
60      if (enabled && index === -1) {
61        values.push(value);
62      }
63      if (!enabled && index !== -1) {
64        values.splice(index, 1);
65      }
66    }
67  }
68
69  view({attrs}: m.CVnode<RecordingSectionAttrs>) {
70    const categoryConfigGetter: CategoryGetter = {
71      get: (cfg) => cfg.chromeCategoriesSelected,
72      set: (cfg, val) => (cfg.chromeCategoriesSelected = val),
73    };
74
75    if (
76      this.defaultCategoryOptions === undefined ||
77      this.disabledByDefaultCategoryOptions === undefined
78    ) {
79      // If we are attempting to record via the Chrome extension, we receive the
80      // list of actually supported categories via DevTools. Otherwise, we fall
81      // back to an integrated list of categories from a recent version of
82      // Chrome.
83      const enabled = new Set(
84        categoryConfigGetter.get(this.recState.recordConfig),
85      );
86      let categories =
87        attrs.recState.chromeCategories ||
88        extractChromeCategories(attrs.dataSources);
89      if (!categories || !isChromeTarget(attrs.recState.recordingTarget)) {
90        categories = getBuiltinChromeCategoryList();
91      }
92      this.defaultCategoryOptions = [];
93      this.disabledByDefaultCategoryOptions = [];
94      const disabledPrefix = 'disabled-by-default-';
95      categories.forEach((cat) => {
96        const checked = enabled.has(cat);
97
98        if (
99          cat.startsWith(disabledPrefix) &&
100          this.disabledByDefaultCategoryOptions !== undefined
101        ) {
102          this.disabledByDefaultCategoryOptions.push({
103            id: cat,
104            name: cat.replace(disabledPrefix, ''),
105            checked: checked,
106          });
107        } else if (
108          !cat.startsWith(disabledPrefix) &&
109          this.defaultCategoryOptions !== undefined
110        ) {
111          this.defaultCategoryOptions.push({
112            id: cat,
113            name: cat,
114            checked: checked,
115          });
116        }
117      });
118    }
119
120    return m(
121      'div.chrome-categories',
122      m(
123        Section,
124        {title: 'Additional Categories'},
125        m(MultiSelect, {
126          options: this.defaultCategoryOptions,
127          repeatCheckedItemsAtTop: false,
128          fixedSize: false,
129          onChange: (diffs: MultiSelectDiff[]) => {
130            diffs.forEach(({id, checked}) => {
131              if (this.defaultCategoryOptions === undefined) {
132                return;
133              }
134              for (const option of this.defaultCategoryOptions) {
135                if (option.id == id) {
136                  option.checked = checked;
137                }
138              }
139            });
140            this.updateValue(categoryConfigGetter, diffs);
141          },
142        }),
143      ),
144      m(
145        Section,
146        {title: 'High Overhead Categories'},
147        m(MultiSelect, {
148          options: this.disabledByDefaultCategoryOptions,
149          repeatCheckedItemsAtTop: false,
150          fixedSize: false,
151          onChange: (diffs: MultiSelectDiff[]) => {
152            diffs.forEach(({id, checked}) => {
153              if (this.disabledByDefaultCategoryOptions === undefined) {
154                return;
155              }
156              for (const option of this.disabledByDefaultCategoryOptions) {
157                if (option.id == id) {
158                  option.checked = checked;
159                }
160              }
161            });
162            this.updateValue(categoryConfigGetter, diffs);
163          },
164        }),
165      ),
166    );
167  }
168}
169
170export class ChromeSettings implements m.ClassComponent<RecordingSectionAttrs> {
171  view({attrs}: m.CVnode<RecordingSectionAttrs>) {
172    const recCfg = attrs.recState.recordConfig;
173    return m(
174      `.record-section${attrs.cssClass}`,
175      CompactProbe({
176        title: 'Task scheduling',
177        setEnabled: (cfg, val) => (cfg.taskScheduling = val),
178        isEnabled: (cfg) => cfg.taskScheduling,
179        recCfg,
180      }),
181      CompactProbe({
182        title: 'IPC flows',
183        setEnabled: (cfg, val) => (cfg.ipcFlows = val),
184        isEnabled: (cfg) => cfg.ipcFlows,
185        recCfg,
186      }),
187      CompactProbe({
188        title: 'Javascript execution',
189        setEnabled: (cfg, val) => (cfg.jsExecution = val),
190        isEnabled: (cfg) => cfg.jsExecution,
191        recCfg,
192      }),
193      CompactProbe({
194        title: 'Web content rendering, layout and compositing',
195        setEnabled: (cfg, val) => (cfg.webContentRendering = val),
196        isEnabled: (cfg) => cfg.webContentRendering,
197        recCfg,
198      }),
199      CompactProbe({
200        title: 'UI rendering & surface compositing',
201        setEnabled: (cfg, val) => (cfg.uiRendering = val),
202        isEnabled: (cfg) => cfg.uiRendering,
203        recCfg,
204      }),
205      CompactProbe({
206        title: 'Input events',
207        setEnabled: (cfg, val) => (cfg.inputEvents = val),
208        isEnabled: (cfg) => cfg.inputEvents,
209        recCfg,
210      }),
211      CompactProbe({
212        title: 'Navigation & Loading',
213        setEnabled: (cfg, val) => (cfg.navigationAndLoading = val),
214        isEnabled: (cfg) => cfg.navigationAndLoading,
215        recCfg,
216      }),
217      CompactProbe({
218        title: 'Chrome Logs',
219        setEnabled: (cfg, val) => (cfg.chromeLogs = val),
220        isEnabled: (cfg) => cfg.chromeLogs,
221        recCfg,
222      }),
223      CompactProbe({
224        title: 'Audio',
225        setEnabled: (cfg, val) => (cfg.audio = val),
226        isEnabled: (cfg) => cfg.audio,
227        recCfg,
228      }),
229      CompactProbe({
230        title: 'Video',
231        setEnabled: (cfg, val) => (cfg.video = val),
232        isEnabled: (cfg) => cfg.video,
233        recCfg,
234      }),
235      m(Toggle, {
236        title: 'Remove untyped and sensitive data like URLs from the trace',
237        descr:
238          'Not recommended unless you intend to share the trace' +
239          ' with third-parties.',
240        setEnabled: (cfg, val) => (cfg.chromePrivacyFiltering = val),
241        isEnabled: (cfg) => cfg.chromePrivacyFiltering,
242        recCfg,
243      }),
244      m(ChromeCategoriesSelection, attrs),
245    );
246  }
247}
248