1<!-- Copyright (C) 2019 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--> 15<template> 16 <md-card-content class="container"> 17 <div class="rects" v-if="hasScreenView"> 18 <rects 19 :bounds="bounds" 20 :rects="rects" 21 :highlight="highlight" 22 @rect-click="onRectClick" 23 /> 24 </div> 25 26 <div class="hierarchy"> 27 <flat-card> 28 <md-content 29 md-tag="md-toolbar" 30 md-elevation="0" 31 class="card-toolbar md-transparent md-dense" 32 > 33 <h2 class="md-title" style="flex: 1;">Hierarchy</h2> 34 <md-checkbox 35 v-model="showHierachyDiff" 36 v-if="diffVisualizationAvailable" 37 > 38 Show Diff 39 </md-checkbox> 40 <md-checkbox v-model="store.simplifyNames"> 41 Simplify names 42 </md-checkbox> 43 <md-checkbox v-model="store.onlyVisible">Only visible</md-checkbox> 44 <md-checkbox v-model="store.flattened">Flat</md-checkbox> 45 <md-field md-inline class="filter"> 46 <label>Filter...</label> 47 <md-input v-model="hierarchyPropertyFilterString"></md-input> 48 </md-field> 49 </md-content> 50 <div class="tree-view-wrapper"> 51 <tree-view 52 class="treeview" 53 :item="tree" 54 @item-selected="itemSelected" 55 :selected="hierarchySelected" 56 :filter="hierarchyFilter" 57 :flattened="store.flattened" 58 :items-clickable="true" 59 :useGlobalCollapsedState="true" 60 :simplify-names="store.simplifyNames" 61 ref="hierarchy" 62 /> 63 </div> 64 </flat-card> 65 </div> 66 67 <div class="properties"> 68 <flat-card> 69 <md-content 70 md-tag="md-toolbar" 71 md-elevation="0" 72 class="card-toolbar md-transparent md-dense" 73 > 74 <h2 class="md-title" style="flex: 1">Properties</h2> 75 <md-checkbox 76 v-model="showPropertiesDiff" 77 v-if="diffVisualizationAvailable" 78 > 79 Show Diff 80 </md-checkbox> 81 <md-field md-inline class="filter"> 82 <label>Filter...</label> 83 <md-input v-model="propertyFilterString"></md-input> 84 </md-field> 85 </md-content> 86 <div class="properties-content"> 87 <div v-if="elementSummary" class="element-summary"> 88 <div v-for="elem in elementSummary" v-bind:key="elem.key"> 89 <!-- eslint-disable-next-line max-len --> 90 <span class="key">{{ elem.key }}:</span> <span class="value">{{ elem.value }}</span> 91 </div> 92 </div> 93 <div v-if="selectedTree" class="tree-view-wrapper"> 94 <tree-view 95 class="treeview" 96 :item="selectedTree" 97 :filter="propertyFilter" 98 :collapseChildren="true" 99 :useGlobalCollapsedState="true" 100 :elementView="PropertiesTreeElement" 101 /> 102 </div> 103 <div class="no-properties" v-else> 104 <i class="material-icons none-icon"> 105 filter_none 106 </i> 107 <span>No element selected in the hierachy.</span> 108 </div> 109 </div> 110 </flat-card> 111 </div> 112 113 </md-card-content> 114</template> 115<script> 116import TreeView from './TreeView.vue'; 117import Rects from './Rects.vue'; 118import FlatCard from './components/FlatCard.vue'; 119import PropertiesTreeElement from './PropertiesTreeElement.vue'; 120 121import {ObjectTransformer} from './transform.js'; 122import {DiffGenerator, defaultModifiedCheck} from './utils/diff.js'; 123import {TRACE_TYPES, DUMP_TYPES} from './decode.js'; 124import {stableIdCompatibilityFixup} from './utils/utils.js'; 125import {CompatibleFeatures} from './utils/compatibility.js'; 126 127function formatProto(obj) { 128 if (obj?.prettyPrint) { 129 return obj.prettyPrint(); 130 } 131} 132 133function findEntryInTree(tree, id) { 134 if (tree.stableId === id) { 135 return tree; 136 } 137 138 if (!tree.children) { 139 return null; 140 } 141 142 for (const child of tree.children) { 143 const foundEntry = findEntryInTree(child, id); 144 if (foundEntry) { 145 return foundEntry; 146 } 147 } 148 149 return null; 150} 151 152export default { 153 name: 'traceview', 154 props: ['store', 'file', 'summarizer'], 155 data() { 156 return { 157 propertyFilterString: '', 158 hierarchyPropertyFilterString: '', 159 selectedTree: null, 160 hierarchySelected: null, 161 lastSelectedStableId: null, 162 bounds: {}, 163 rects: [], 164 item: null, 165 tree: null, 166 highlight: null, 167 showHierachyDiff: false, 168 showPropertiesDiff: false, 169 PropertiesTreeElement, 170 }; 171 }, 172 methods: { 173 itemSelected(item) { 174 this.hierarchySelected = item; 175 this.selectedTree = this.getTransformedProperties(item); 176 this.highlight = item.rect; 177 this.lastSelectedStableId = item.stableId; 178 this.$emit('focus'); 179 }, 180 getTransformedProperties(item) { 181 const transformer = new ObjectTransformer( 182 item.obj, 183 item.name, 184 stableIdCompatibilityFixup(item), 185 ).setOptions({ 186 skip: item.skip, 187 formatter: formatProto, 188 }); 189 190 if (this.showPropertiesDiff && this.diffVisualizationAvailable) { 191 const prevItem = this.getItemFromPrevTree(item); 192 transformer.withDiff(prevItem?.obj); 193 } 194 195 return transformer.transform(); 196 }, 197 onRectClick(item) { 198 if (item) { 199 this.itemSelected(item); 200 } 201 }, 202 generateTreeFromItem(item) { 203 if (!this.showHierachyDiff || !this.diffVisualizationAvailable) { 204 return item; 205 } 206 207 return new DiffGenerator(this.item) 208 .compareWith(this.getDataWithOffset(-1)) 209 .withUniqueNodeId((node) => { 210 return node.stableId; 211 }) 212 .withModifiedCheck(defaultModifiedCheck) 213 .generateDiffTree(); 214 }, 215 setData(item) { 216 this.item = item; 217 this.tree = this.generateTreeFromItem(item); 218 219 const rects = item.rects //.toArray() 220 this.rects = [...rects].reverse(); 221 this.bounds = item.bounds; 222 223 this.hierarchySelected = null; 224 this.selectedTree = null; 225 this.highlight = null; 226 227 function findItem(item, stableId) { 228 if (item.stableId === stableId) { 229 return item; 230 } 231 if (Array.isArray(item.children)) { 232 for (const child of item.children) { 233 const found = findItem(child, stableId); 234 if (found) { 235 return found; 236 } 237 } 238 } 239 return null; 240 } 241 242 if (this.lastSelectedStableId) { 243 const found = findItem(item, this.lastSelectedStableId); 244 if (found) { 245 this.itemSelected(found); 246 } 247 } 248 }, 249 arrowUp() { 250 return this.$refs.hierarchy.selectPrev(); 251 }, 252 arrowDown() { 253 return this.$refs.hierarchy.selectNext(); 254 }, 255 getDataWithOffset(offset) { 256 const index = this.file.selectedIndex + offset; 257 258 if (index < 0 || index >= this.file.data.length) { 259 return null; 260 } 261 262 return this.file.data[index]; 263 }, 264 getItemFromPrevTree(entry) { 265 if (!this.showPropertiesDiff || !this.hierarchySelected) { 266 return null; 267 } 268 269 const id = entry.stableId; 270 if (!id) { 271 throw new Error('Entry has no stableId...'); 272 } 273 274 const prevTree = this.getDataWithOffset(-1); 275 if (!prevTree) { 276 console.warn('No previous entry'); 277 return null; 278 } 279 280 const prevEntry = findEntryInTree(prevTree, id); 281 if (!prevEntry) { 282 console.warn('Didn\'t exist in last entry'); 283 // TODO: Maybe handle this in some way. 284 } 285 286 return prevEntry; 287 }, 288 }, 289 created() { 290 this.setData(this.file.data[this.file.selectedIndex ?? 0]); 291 }, 292 watch: { 293 selectedIndex() { 294 this.setData(this.file.data[this.file.selectedIndex ?? 0]); 295 }, 296 showHierachyDiff() { 297 this.tree = this.generateTreeFromItem(this.item); 298 }, 299 showPropertiesDiff() { 300 if (this.hierarchySelected) { 301 this.selectedTree = 302 this.getTransformedProperties(this.hierarchySelected); 303 } 304 }, 305 }, 306 computed: { 307 diffVisualizationAvailable() { 308 return CompatibleFeatures.DiffVisualization && ( 309 this.file.type == TRACE_TYPES.WINDOW_MANAGER || 310 this.file.type == TRACE_TYPES.SURFACE_FLINGER 311 ); 312 }, 313 selectedIndex() { 314 return this.file.selectedIndex; 315 }, 316 hierarchyFilter() { 317 const hierarchyPropertyFilter = 318 getFilter(this.hierarchyPropertyFilterString); 319 return this.store.onlyVisible ? (c) => { 320 return c.visible && hierarchyPropertyFilter(c); 321 } : hierarchyPropertyFilter; 322 }, 323 propertyFilter() { 324 return getFilter(this.propertyFilterString); 325 }, 326 hasScreenView() { 327 return this.file.type == TRACE_TYPES.WINDOW_MANAGER || 328 this.file.type == TRACE_TYPES.SURFACE_FLINGER || 329 this.file.type == DUMP_TYPES.WINDOW_MANAGER || 330 this.file.type == DUMP_TYPES.SURFACE_FLINGER; 331 }, 332 elementSummary() { 333 if (!this.hierarchySelected || !this.summarizer) { 334 return null; 335 } 336 337 const summary = this.summarizer(this.hierarchySelected); 338 339 if (summary?.length === 0) { 340 return null; 341 } 342 343 return summary; 344 }, 345 }, 346 components: { 347 'tree-view': TreeView, 348 'rects': Rects, 349 'flat-card': FlatCard, 350 }, 351}; 352 353function getFilter(filterString) { 354 const filterStrings = filterString.split(','); 355 const positive = []; 356 const negative = []; 357 filterStrings.forEach((f) => { 358 if (f.startsWith('!')) { 359 const str = f.substring(1); 360 negative.push((s) => s.indexOf(str) === -1); 361 } else { 362 const str = f; 363 positive.push((s) => s.indexOf(str) !== -1); 364 } 365 }); 366 const filter = (item) => { 367 const apply = (f) => f(String(item.name)); 368 return (positive.length === 0 || positive.some(apply)) && 369 (negative.length === 0 || negative.every(apply)); 370 }; 371 return filter; 372} 373 374</script> 375<style scoped> 376.container { 377 display: flex; 378 flex-wrap: wrap; 379} 380 381.rects { 382 flex: none; 383 margin: 8px; 384} 385 386.hierarchy, 387.properties { 388 flex: 1; 389 margin: 8px; 390 min-width: 400px; 391 min-height: 50rem; 392} 393 394.rects, 395.hierarchy, 396.properties { 397 padding: 5px; 398} 399 400.flat-card { 401 display: flex; 402 flex-direction: column; 403 height: 100%; 404} 405 406.hierarchy>.tree-view, 407.properties>.tree-view { 408 margin: 16px; 409} 410 411.treeview { 412 overflow: auto; 413 white-space: pre-line; 414} 415 416.no-properties { 417 display: flex; 418 flex: 1; 419 flex-direction: column; 420 align-self: center; 421 align-items: center; 422 justify-content: center; 423 padding: 50px 25px; 424} 425 426.no-properties .none-icon { 427 font-size: 35px; 428 margin-bottom: 10px; 429} 430 431.no-properties span { 432 font-weight: 100; 433} 434 435.filter { 436 width: auto; 437} 438 439.element-summary { 440 padding: 1rem; 441 border-bottom: thin solid rgba(0,0,0,.12); 442} 443 444.element-summary .key { 445 font-weight: 500; 446} 447 448.element-summary .value { 449 color: rgba(0, 0, 0, 0.75); 450} 451 452.properties-content { 453 display: flex; 454 flex-direction: column; 455 flex: 1; 456} 457 458.tree-view-wrapper { 459 display: flex; 460 flex-direction: column; 461 flex: 1; 462} 463 464.treeview { 465 flex: 1 0 0; 466} 467</style> 468