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