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