1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7base.require('base.sorted_array_utils');
8base.require('tracing.tracks.container_track');
9base.require('ui');
10
11base.exportTo('tracing.tracks', function() {
12
13  /**
14   * A track that displays a SliceGroup.
15   * @constructor
16   * @extends {ContainerTrack}
17   */
18
19  var SliceGroupTrack = ui.define(
20      'slice-group-track', tracing.tracks.ContainerTrack);
21
22  SliceGroupTrack.prototype = {
23
24    __proto__: tracing.tracks.ContainerTrack.prototype,
25
26    decorate: function(viewport) {
27      tracing.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
28      this.classList.add('slice-group-track');
29      this.tooltip_ = '';
30      this.heading_ = '';
31    },
32
33    get group() {
34      return this.group_;
35    },
36
37    set group(g) {
38      this.group_ = g;
39      this.updateContents_();
40    },
41
42    get heading() {
43      return this.heading_;
44    },
45
46    set heading(h) {
47      this.heading_ = h;
48      this.updateContents_();
49    },
50
51    get tooltip() {
52      return this.tooltip_;
53    },
54
55    set tooltip(t) {
56      this.tooltip_ = t;
57      this.updateContents_();
58    },
59
60    set decorateHit(f) {
61      this.decorateHit_ = f;
62      this.updateContents_();
63    },
64
65    addSliceTrack_: function(slices) {
66      var track = new tracing.tracks.SliceTrack(this.viewport);
67      track.slices = slices;
68      track.decorateHit = this.decorateHit_;
69      track.categoryFilter_ = this.categoryFilter;
70      this.appendChild(track);
71      return track;
72    },
73
74    get subRows() {
75      return base.asArray(this.children).map(function(sliceTrack) {
76        return sliceTrack.slices;
77      });
78    },
79
80    get hasVisibleContent() {
81      return this.children.length > 0;
82    },
83
84    updateContents_: function() {
85      if (!this.group_) {
86        this.updateHeadingAndTooltip_();
87        return;
88      }
89
90      var slices = tracing.filterSliceArray(this.categoryFilter,
91                                            this.group_.slices);
92      if (this.areArrayContentsSame_(this.filteredSlices_, slices)) {
93        this.updateHeadingAndTooltip_();
94        return;
95      }
96
97      this.filteredSlices_ = slices;
98
99      this.detach();
100      if (!slices.length)
101        return;
102      var subRows = this.buildSubRows_(slices);
103      for (var srI = 0; srI < subRows.length; srI++) {
104        var subRow = subRows[srI];
105        if (!subRow.length)
106          continue;
107        this.addSliceTrack_(subRow);
108      }
109      this.updateHeadingAndTooltip_();
110    },
111
112    updateHeadingAndTooltip_: function() {
113      if (!this.firstChild)
114        return;
115      this.firstChild.heading = this.heading_;
116      this.firstChild.tooltip = this.tooltip_;
117    },
118
119    /**
120     * Breaks up the list of slices into N rows, each of which is a list of
121     * slices that are non overlapping.
122     */
123    buildSubRows_: function(slices) {
124      // This function works by walking through slices by start time.
125      //
126      // The basic idea here is to insert each slice as deep into the subrow
127      // list as it can go such that every subSlice is fully contained by its
128      // parent slice.
129      //
130      // Visually, if we start with this:
131      //  0:  [    a       ]
132      //  1:    [  b  ]
133      //  2:    [c][d]
134      //
135      // To place this slice:
136      //               [e]
137      // We first check row 2's last item, [d]. [e] wont fit into [d] (they dont
138      // even intersect). So we go to row 1. That gives us [b], and [d] wont fit
139      // into that either. So, we go to row 0 and its last slice, [a]. That can
140      // completely contain [e], so that means we should add [e] as a subchild
141      // of [a]. That puts it on row 1, yielding:
142      //  0:  [    a       ]
143      //  1:    [  b  ][e]
144      //  2:    [c][d]
145      //
146      // If we then get this slice:
147      //                      [f]
148      // We do the same deepest-to-shallowest walk of the subrows trying to fit
149      // it. This time, it doesn't fit in any open slice. So, we simply append
150      // it to row 0:
151      //  0:  [    a       ]  [f]
152      //  1:    [  b  ][e]
153      //  2:    [c][d]
154      if (!slices.length)
155        return [];
156
157      var ops = [];
158      for (var i = 0; i < slices.length; i++) {
159        if (slices[i].subSlices)
160          slices[i].subSlices.splice(0,
161                                     slices[i].subSlices.length);
162        ops.push(i);
163      }
164
165      ops.sort(function(ix, iy) {
166        var x = slices[ix];
167        var y = slices[iy];
168        if (x.start != y.start)
169          return x.start - y.start;
170
171        // Elements get inserted into the slices array in order of when the
172        // slices end.  Because slices must be properly nested, we break
173        // start-time ties by assuming that the elements appearing earlier in
174        // the slices array (and thus ending earlier) start later.
175        return iy - ix;
176      });
177
178      var subRows = [[]];
179      this.badSlices_ = [];  // TODO(simonjam): Connect this again.
180
181      for (var i = 0; i < ops.length; i++) {
182        var op = ops[i];
183        var slice = slices[op];
184
185        // Try to fit the slice into the existing subrows.
186        var inserted = false;
187        for (var j = subRows.length - 1; j >= 0; j--) {
188          if (subRows[j].length == 0)
189            continue;
190
191          var insertedSlice = subRows[j][subRows[j].length - 1];
192          if (slice.start < insertedSlice.start) {
193            this.badSlices_.push(slice);
194            inserted = true;
195          }
196          if (slice.start >= insertedSlice.start &&
197              slice.end <= insertedSlice.end) {
198            // Insert it into subRow j + 1.
199            while (subRows.length <= j + 1)
200              subRows.push([]);
201            subRows[j + 1].push(slice);
202            if (insertedSlice.subSlices)
203              insertedSlice.subSlices.push(slice);
204            inserted = true;
205            break;
206          }
207        }
208        if (inserted)
209          continue;
210
211        // Append it to subRow[0] as a root.
212        subRows[0].push(slice);
213      }
214
215      return subRows;
216    },
217
218    areArrayContentsSame_: function(a, b) {
219      if (!a || !b)
220        return false;
221      if (!a.length || !b.length)
222        return false;
223      if (a.length != b.length)
224        return false;
225      for (var i = 0; i < a.length; ++i) {
226        if (a[i] != b[i])
227          return false;
228      }
229      return true;
230    }
231  };
232
233  return {
234    SliceGroupTrack: SliceGroupTrack
235  };
236});
237