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