1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/ui/base/draw_helpers.html">
9<link rel="import" href="/tracing/ui/base/ui.html">
10<link rel="import" href="/tracing/ui/tracks/alert_track.html">
11<link rel="import" href="/tracing/ui/tracks/container_track.html">
12<link rel="import" href="/tracing/ui/tracks/device_track.html">
13<link rel="import" href="/tracing/ui/tracks/global_memory_dump_track.html">
14<link rel="import" href="/tracing/ui/tracks/highlighter.html">
15<link rel="import" href="/tracing/ui/tracks/interaction_track.html">
16<link rel="import" href="/tracing/ui/tracks/kernel_track.html">
17<link rel="import" href="/tracing/ui/tracks/process_track.html">
18
19<style>
20.model-track {
21  -webkit-box-flex: 1;
22}
23</style>
24
25<script>
26'use strict';
27
28tr.exportTo('tr.ui.tracks', function() {
29  var SelectionState = tr.model.SelectionState;
30  var EventPresenter = tr.ui.b.EventPresenter;
31
32  /**
33   * Visualizes a Model by building ProcessTracks and CpuTracks.
34   * @constructor
35   */
36  var ModelTrack = tr.ui.b.define('model-track', tr.ui.tracks.ContainerTrack);
37
38
39  ModelTrack.prototype = {
40
41    __proto__: tr.ui.tracks.ContainerTrack.prototype,
42
43    decorate: function(viewport) {
44      tr.ui.tracks.ContainerTrack.prototype.decorate.call(this, viewport);
45      this.classList.add('model-track');
46
47      var typeInfos = tr.ui.tracks.Highlighter.getAllRegisteredTypeInfos();
48      this.highlighters_ = typeInfos.map(
49        function(typeInfo) {
50          return new typeInfo.constructor(viewport);
51        });
52
53      this.upperMode_ = false;
54      this.annotationViews_ = [];
55    },
56
57    // upperMode is true if the track is being used on the ruler.
58    get upperMode() {
59      return this.upperMode_;
60    },
61
62    set upperMode(upperMode) {
63      this.upperMode_ = upperMode;
64      this.updateContents_();
65    },
66
67    detach: function() {
68      tr.ui.tracks.ContainerTrack.prototype.detach.call(this);
69    },
70
71    get model() {
72      return this.model_;
73    },
74
75    set model(model) {
76      this.model_ = model;
77      this.updateContents_();
78
79      this.model_.addEventListener('annotationChange',
80          this.updateAnnotations_.bind(this));
81    },
82
83    get hasVisibleContent() {
84      return this.children.length > 0;
85    },
86
87    updateContents_: function() {
88      this.textContent = '';
89      if (!this.model_)
90        return;
91
92      if (this.upperMode_)
93        this.updateContentsForUpperMode_();
94      else
95        this.updateContentsForLowerMode_();
96    },
97
98    updateContentsForUpperMode_: function() {
99    },
100
101    updateContentsForLowerMode_: function() {
102      if (this.model_.userModel.expectations.length) {
103        var mrt = new tr.ui.tracks.InteractionTrack(this.viewport_);
104        mrt.model = this.model_;
105        this.appendChild(mrt);
106      }
107
108      if (this.model_.alerts.length) {
109        var at = new tr.ui.tracks.AlertTrack(this.viewport_);
110        at.alerts = this.model_.alerts;
111        this.appendChild(at);
112      }
113
114      if (this.model_.globalMemoryDumps.length) {
115        var gmdt = new tr.ui.tracks.GlobalMemoryDumpTrack(this.viewport_);
116        gmdt.memoryDumps = this.model_.globalMemoryDumps;
117        this.appendChild(gmdt);
118      }
119
120      this.appendDeviceTrack_();
121      this.appendKernelTrack_();
122
123      // Get a sorted list of processes.
124      var processes = this.model_.getAllProcesses();
125      processes.sort(tr.model.Process.compare);
126
127      for (var i = 0; i < processes.length; ++i) {
128        var process = processes[i];
129
130        var track = new tr.ui.tracks.ProcessTrack(this.viewport);
131        track.process = process;
132        if (!track.hasVisibleContent)
133          continue;
134
135        this.appendChild(track);
136      }
137      this.viewport_.rebuildEventToTrackMap();
138      this.viewport_.rebuildContainerToTrackMap();
139
140      for (var i = 0; i < this.highlighters_.length; i++) {
141        this.highlighters_[i].processModel(this.model_);
142      }
143
144      this.updateAnnotations_();
145    },
146
147    updateAnnotations_: function() {
148      this.annotationViews_ = [];
149      var annotations = this.model_.getAllAnnotations();
150      for (var i = 0; i < annotations.length; i++) {
151        this.annotationViews_.push(
152            annotations[i].getOrCreateView(this.viewport_));
153      }
154      this.invalidateDrawingContainer();
155    },
156
157    addEventsToTrackMap: function(eventToTrackMap) {
158      if (!this.model_)
159        return;
160
161      var tracks = this.children;
162      for (var i = 0; i < tracks.length; ++i)
163        tracks[i].addEventsToTrackMap(eventToTrackMap);
164
165      if (this.instantEvents === undefined)
166        return;
167
168      var vp = this.viewport_;
169      this.instantEvents.forEach(function(ev) {
170        eventToTrackMap.addEvent(ev, this);
171      }.bind(this));
172    },
173
174    appendDeviceTrack_: function() {
175      var device = this.model.device;
176      var track = new tr.ui.tracks.DeviceTrack(this.viewport);
177      track.device = this.model.device;
178      if (!track.hasVisibleContent)
179        return;
180      this.appendChild(track);
181    },
182
183    appendKernelTrack_: function() {
184      var kernel = this.model.kernel;
185      var track = new tr.ui.tracks.KernelTrack(this.viewport);
186      track.kernel = this.model.kernel;
187      if (!track.hasVisibleContent)
188        return;
189      this.appendChild(track);
190    },
191
192    drawTrack: function(type) {
193      var ctx = this.context();
194      if (!this.model_)
195        return;
196
197      var pixelRatio = window.devicePixelRatio || 1;
198      var bounds = this.getBoundingClientRect();
199      var canvasBounds = ctx.canvas.getBoundingClientRect();
200
201      ctx.save();
202      ctx.translate(0, pixelRatio * (bounds.top - canvasBounds.top));
203
204      var dt = this.viewport.currentDisplayTransform;
205      var viewLWorld = dt.xViewToWorld(0);
206      var viewRWorld = dt.xViewToWorld(bounds.width * pixelRatio);
207
208      switch (type) {
209        case tr.ui.tracks.DrawType.GRID:
210          this.viewport.drawMajorMarkLines(ctx);
211          // The model is the only thing that draws grid lines.
212          ctx.restore();
213          return;
214
215        case tr.ui.tracks.DrawType.FLOW_ARROWS:
216          if (this.model_.flowIntervalTree.size === 0) {
217            ctx.restore();
218            return;
219          }
220
221          this.drawFlowArrows_(viewLWorld, viewRWorld);
222          ctx.restore();
223          return;
224
225        case tr.ui.tracks.DrawType.INSTANT_EVENT:
226          if (!this.model_.instantEvents ||
227              this.model_.instantEvents.length === 0)
228            break;
229
230          tr.ui.b.drawInstantSlicesAsLines(
231              ctx,
232              this.viewport.currentDisplayTransform,
233              viewLWorld,
234              viewRWorld,
235              bounds.height,
236              this.model_.instantEvents,
237              4);
238
239          break;
240
241        case tr.ui.tracks.DrawType.MARKERS:
242          if (!this.viewport.interestRange.isEmpty) {
243            this.viewport.interestRange.draw(ctx, viewLWorld, viewRWorld);
244            this.viewport.interestRange.drawIndicators(
245                ctx, viewLWorld, viewRWorld);
246          }
247          ctx.restore();
248          return;
249
250        case tr.ui.tracks.DrawType.HIGHLIGHTS:
251          for (var i = 0; i < this.highlighters_.length; i++) {
252            this.highlighters_[i].drawHighlight(ctx, dt, viewLWorld, viewRWorld,
253                bounds.height);
254          }
255          ctx.restore();
256          return;
257
258        case tr.ui.tracks.DrawType.ANNOTATIONS:
259          for (var i = 0; i < this.annotationViews_.length; i++) {
260            this.annotationViews_[i].draw(ctx);
261          }
262          ctx.restore();
263          return;
264      }
265      ctx.restore();
266
267      tr.ui.tracks.ContainerTrack.prototype.drawTrack.call(this, type);
268    },
269
270    drawFlowArrows_: function(viewLWorld, viewRWorld) {
271      var ctx = this.context();
272      var dt = this.viewport.currentDisplayTransform;
273      dt.applyTransformToCanvas(ctx);
274
275      var pixWidth = dt.xViewVectorToWorld(1);
276
277      ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)';
278      ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
279      ctx.lineWidth = pixWidth > 1.0 ? 1 : pixWidth;
280
281      var events =
282          this.model_.flowIntervalTree.findIntersection(viewLWorld, viewRWorld);
283
284      // When not showing flow events, show only highlighted/selected ones.
285      var onlyHighlighted = !this.viewport.showFlowEvents;
286      var canvasBounds = ctx.canvas.getBoundingClientRect();
287      for (var i = 0; i < events.length; ++i) {
288        if (onlyHighlighted &&
289            events[i].selectionState !== SelectionState.SELECTED &&
290            events[i].selectionState !== SelectionState.HIGHLIGHTED)
291          continue;
292        this.drawFlowArrow_(ctx, events[i], canvasBounds, pixWidth);
293      }
294    },
295
296    drawFlowArrow_: function(ctx, flowEvent,
297                             canvasBounds, pixWidth) {
298      var pixelRatio = window.devicePixelRatio || 1;
299
300      var startTrack = this.viewport.trackForEvent(flowEvent.startSlice);
301      var endTrack = this.viewport.trackForEvent(flowEvent.endSlice);
302
303      // TODO(nduca): Figure out how to draw flow arrows even when
304      // processes are collapsed, bug #931.
305      if (startTrack === undefined || endTrack === undefined)
306        return;
307
308      var startBounds = startTrack.getBoundingClientRect();
309      var endBounds = endTrack.getBoundingClientRect();
310
311      if (flowEvent.selectionState == SelectionState.SELECTED) {
312        ctx.shadowBlur = 1;
313        ctx.shadowColor = 'red';
314        ctx.shadowOffsety = 2;
315        ctx.strokeStyle = 'red';
316      } else if (flowEvent.selectionState == SelectionState.HIGHLIGHTED) {
317        ctx.shadowBlur = 1;
318        ctx.shadowColor = 'red';
319        ctx.shadowOffsety = 2;
320        ctx.strokeStyle = 'red';
321      } else if (flowEvent.selectionState == SelectionState.DIMMED) {
322        ctx.shadowBlur = 0;
323        ctx.shadowOffsetX = 0;
324        ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
325      } else {
326        var hasBoost = false;
327        var startSlice = flowEvent.startSlice;
328        hasBoost |= startSlice.selectionState === SelectionState.SELECTED;
329        hasBoost |= startSlice.selectionState === SelectionState.HIGHLIGHTED;
330        var endSlice = flowEvent.endSlice;
331        hasBoost |= endSlice.selectionState === SelectionState.SELECTED;
332        hasBoost |= endSlice.selectionState === SelectionState.HIGHLIGHTED;
333        if (hasBoost) {
334          ctx.shadowBlur = 1;
335          ctx.shadowColor = 'rgba(255, 0, 0, 0.4)';
336          ctx.shadowOffsety = 2;
337          ctx.strokeStyle = 'rgba(255, 0, 0, 0.4)';
338        } else {
339          ctx.shadowBlur = 0;
340          ctx.shadowOffsetX = 0;
341          ctx.strokeStyle = 'rgba(0, 0, 0, 0.4)';
342        }
343      }
344
345      var startSize = startBounds.left + startBounds.top +
346          startBounds.bottom + startBounds.right;
347      var endSize = endBounds.left + endBounds.top +
348          endBounds.bottom + endBounds.right;
349      // Nothing to do if both ends of the track are collapsed.
350      if (startSize === 0 && endSize === 0)
351        return;
352
353      var startY = this.calculateTrackY_(startTrack, canvasBounds);
354      var endY = this.calculateTrackY_(endTrack, canvasBounds);
355
356      var pixelStartY = pixelRatio * startY;
357      var pixelEndY = pixelRatio * endY;
358      var half = (flowEvent.end - flowEvent.start) / 2;
359
360      ctx.beginPath();
361      ctx.moveTo(flowEvent.start, pixelStartY);
362      ctx.bezierCurveTo(
363          flowEvent.start + half, pixelStartY,
364          flowEvent.start + half, pixelEndY,
365          flowEvent.end, pixelEndY);
366      ctx.stroke();
367
368      var arrowWidth = 5 * pixWidth * pixelRatio;
369      var distance = flowEvent.end - flowEvent.start;
370      if (distance <= (2 * arrowWidth))
371        return;
372
373      var tipX = flowEvent.end;
374      var tipY = pixelEndY;
375      var arrowHeight = (endBounds.height / 4) * pixelRatio;
376      tr.ui.b.drawTriangle(ctx,
377          tipX, tipY,
378          tipX - arrowWidth, tipY - arrowHeight,
379          tipX - arrowWidth, tipY + arrowHeight);
380      ctx.fill();
381    },
382
383    calculateTrackY_: function(track, canvasBounds) {
384      var bounds = track.getBoundingClientRect();
385      var size = bounds.left + bounds.top + bounds.bottom + bounds.right;
386      if (size === 0)
387        return this.calculateTrackY_(track.parentNode, canvasBounds);
388
389      return bounds.top - canvasBounds.top + (bounds.height / 2);
390    },
391
392    addIntersectingEventsInRangeToSelectionInWorldSpace: function(
393        loWX, hiWX, viewPixWidthWorld, selection) {
394      function onPickHit(instantEvent) {
395        selection.push(instantEvent);
396      }
397      var instantEventWidth = 3 * viewPixWidthWorld;
398      tr.b.iterateOverIntersectingIntervals(this.model_.instantEvents,
399          function(x) { return x.start; },
400          function(x) { return x.duration + instantEventWidth; },
401          loWX, hiWX,
402          onPickHit.bind(this));
403
404      tr.ui.tracks.ContainerTrack.prototype.
405          addIntersectingEventsInRangeToSelectionInWorldSpace.
406          apply(this, arguments);
407    },
408
409    addClosestEventToSelection: function(worldX, worldMaxDist, loY, hiY,
410                                         selection) {
411      this.addClosestInstantEventToSelection(this.model_.instantEvents,
412                                             worldX, worldMaxDist, selection);
413      tr.ui.tracks.ContainerTrack.prototype.addClosestEventToSelection.
414          apply(this, arguments);
415    }
416  };
417
418  return {
419    ModelTrack: ModelTrack
420  };
421});
422</script>
423