• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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