• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {produce} from 'immer';
16import m from 'mithril';
17
18import {Actions} from '../../common/actions';
19import {DataSource} from '../../common/recordingV2/recording_interfaces_v2';
20import {getBuiltinChromeCategoryList, isChromeTarget} from '../../common/state';
21import {
22  MultiSelect,
23  MultiSelectDiff,
24  Option as MultiSelectOption,
25} from '../../widgets/multiselect';
26import {Section} from '../../widgets/section';
27import {globals} from '../globals';
28import {
29  CategoryGetter,
30  CompactProbe,
31  Toggle,
32  ToggleAttrs,
33} from '../record_widgets';
34
35import {RecordingSectionAttrs} from './recording_sections';
36
37function extractChromeCategories(
38  dataSources: DataSource[],
39): string[] | undefined {
40  for (const dataSource of dataSources) {
41    if (dataSource.name === 'chromeCategories') {
42      return dataSource.descriptor as string[];
43    }
44  }
45  return undefined;
46}
47
48class ChromeCategoriesSelection
49  implements m.ClassComponent<RecordingSectionAttrs>
50{
51  private defaultCategoryOptions: MultiSelectOption[] | undefined = undefined;
52  private disabledByDefaultCategoryOptions: MultiSelectOption[] | undefined =
53    undefined;
54
55  static updateValue(attrs: CategoryGetter, diffs: MultiSelectDiff[]) {
56    const traceCfg = produce(globals.state.recordConfig, (draft) => {
57      const values = attrs.get(draft);
58      for (const diff of diffs) {
59        const value = diff.id;
60        const index = values.indexOf(value);
61        const enabled = diff.checked;
62        if (enabled && index === -1) {
63          values.push(value);
64        }
65        if (!enabled && index !== -1) {
66          values.splice(index, 1);
67        }
68      }
69    });
70    globals.dispatch(Actions.setRecordConfig({config: traceCfg}));
71  }
72
73  view({attrs}: m.CVnode<RecordingSectionAttrs>) {
74    const categoryConfigGetter: CategoryGetter = {
75      get: (cfg) => cfg.chromeCategoriesSelected,
76      set: (cfg, val) => (cfg.chromeCategoriesSelected = val),
77    };
78
79    if (
80      this.defaultCategoryOptions === undefined ||
81      this.disabledByDefaultCategoryOptions === undefined
82    ) {
83      // If we are attempting to record via the Chrome extension, we receive the
84      // list of actually supported categories via DevTools. Otherwise, we fall
85      // back to an integrated list of categories from a recent version of
86      // Chrome.
87      const enabled = new Set(
88        categoryConfigGetter.get(globals.state.recordConfig),
89      );
90      let categories =
91        globals.state.chromeCategories ||
92        extractChromeCategories(attrs.dataSources);
93      if (!categories || !isChromeTarget(globals.state.recordingTarget)) {
94        categories = getBuiltinChromeCategoryList();
95      }
96      this.defaultCategoryOptions = [];
97      this.disabledByDefaultCategoryOptions = [];
98      const disabledPrefix = 'disabled-by-default-';
99      categories.forEach((cat) => {
100        const checked = enabled.has(cat);
101
102        if (
103          cat.startsWith(disabledPrefix) &&
104          this.disabledByDefaultCategoryOptions !== undefined
105        ) {
106          this.disabledByDefaultCategoryOptions.push({
107            id: cat,
108            name: cat.replace(disabledPrefix, ''),
109            checked: checked,
110          });
111        } else if (
112          !cat.startsWith(disabledPrefix) &&
113          this.defaultCategoryOptions !== undefined
114        ) {
115          this.defaultCategoryOptions.push({
116            id: cat,
117            name: cat,
118            checked: checked,
119          });
120        }
121      });
122    }
123
124    return m(
125      'div.chrome-categories',
126      m(
127        Section,
128        {title: 'Additional Categories'},
129        m(MultiSelect, {
130          options: this.defaultCategoryOptions,
131          repeatCheckedItemsAtTop: false,
132          fixedSize: false,
133          onChange: (diffs: MultiSelectDiff[]) => {
134            diffs.forEach(({id, checked}) => {
135              if (this.defaultCategoryOptions === undefined) {
136                return;
137              }
138              for (const option of this.defaultCategoryOptions) {
139                if (option.id == id) {
140                  option.checked = checked;
141                }
142              }
143            });
144            ChromeCategoriesSelection.updateValue(categoryConfigGetter, diffs);
145          },
146        }),
147      ),
148      m(
149        Section,
150        {title: 'High Overhead Categories'},
151        m(MultiSelect, {
152          options: this.disabledByDefaultCategoryOptions,
153          repeatCheckedItemsAtTop: false,
154          fixedSize: false,
155          onChange: (diffs: MultiSelectDiff[]) => {
156            diffs.forEach(({id, checked}) => {
157              if (this.disabledByDefaultCategoryOptions === undefined) {
158                return;
159              }
160              for (const option of this.disabledByDefaultCategoryOptions) {
161                if (option.id == id) {
162                  option.checked = checked;
163                }
164              }
165            });
166            ChromeCategoriesSelection.updateValue(categoryConfigGetter, diffs);
167          },
168        }),
169      ),
170    );
171  }
172}
173
174export class ChromeSettings implements m.ClassComponent<RecordingSectionAttrs> {
175  view({attrs}: m.CVnode<RecordingSectionAttrs>) {
176    return m(
177      `.record-section${attrs.cssClass}`,
178      CompactProbe({
179        title: 'Task scheduling',
180        setEnabled: (cfg, val) => (cfg.taskScheduling = val),
181        isEnabled: (cfg) => cfg.taskScheduling,
182      }),
183      CompactProbe({
184        title: 'IPC flows',
185        setEnabled: (cfg, val) => (cfg.ipcFlows = val),
186        isEnabled: (cfg) => cfg.ipcFlows,
187      }),
188      CompactProbe({
189        title: 'Javascript execution',
190        setEnabled: (cfg, val) => (cfg.jsExecution = val),
191        isEnabled: (cfg) => cfg.jsExecution,
192      }),
193      CompactProbe({
194        title: 'Web content rendering, layout and compositing',
195        setEnabled: (cfg, val) => (cfg.webContentRendering = val),
196        isEnabled: (cfg) => cfg.webContentRendering,
197      }),
198      CompactProbe({
199        title: 'UI rendering & surface compositing',
200        setEnabled: (cfg, val) => (cfg.uiRendering = val),
201        isEnabled: (cfg) => cfg.uiRendering,
202      }),
203      CompactProbe({
204        title: 'Input events',
205        setEnabled: (cfg, val) => (cfg.inputEvents = val),
206        isEnabled: (cfg) => cfg.inputEvents,
207      }),
208      CompactProbe({
209        title: 'Navigation & Loading',
210        setEnabled: (cfg, val) => (cfg.navigationAndLoading = val),
211        isEnabled: (cfg) => cfg.navigationAndLoading,
212      }),
213      CompactProbe({
214        title: 'Chrome Logs',
215        setEnabled: (cfg, val) => (cfg.chromeLogs = val),
216        isEnabled: (cfg) => cfg.chromeLogs,
217      }),
218      CompactProbe({
219        title: 'Audio',
220        setEnabled: (cfg, val) => (cfg.audio = val),
221        isEnabled: (cfg) => cfg.audio,
222      }),
223      CompactProbe({
224        title: 'Video',
225        setEnabled: (cfg, val) => (cfg.video = val),
226        isEnabled: (cfg) => cfg.video,
227      }),
228      m(Toggle, {
229        title: 'Remove untyped and sensitive data like URLs from the trace',
230        descr:
231          'Not recommended unless you intend to share the trace' +
232          ' with third-parties.',
233        setEnabled: (cfg, val) => (cfg.chromePrivacyFiltering = val),
234        isEnabled: (cfg) => cfg.chromePrivacyFiltering,
235      } as ToggleAttrs),
236      m(ChromeCategoriesSelection, attrs),
237    );
238  }
239}
240