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/base/settings.html">
9<link rel="import" href="/tracing/base/utils.html">
10<link rel="import" href="/tracing/core/scripting_controller.html">
11<link rel="import" href="/tracing/metrics/all_metrics.html">
12<link rel="import" href="/tracing/ui/analysis/analysis_view.html">
13<link rel="import" href="/tracing/ui/base/dom_helpers.html">
14<link rel="import" href="/tracing/ui/base/drag_handle.html">
15<link rel="import" href="/tracing/ui/base/dropdown.html">
16<link rel="import" href="/tracing/ui/base/favicons.html">
17<link rel="import" href="/tracing/ui/base/hotkey_controller.html">
18<link rel="import" href="/tracing/ui/base/info_bar_group.html">
19<link rel="import" href="/tracing/ui/base/overlay.html">
20<link rel="import" href="/tracing/ui/base/toolbar_button.html">
21<link rel="import" href="/tracing/ui/base/utils.html">
22<link rel="import" href="/tracing/ui/brushing_state_controller.html">
23<link rel="import" href="/tracing/ui/find_control.html">
24<link rel="import" href="/tracing/ui/find_controller.html">
25<link rel="import" href="/tracing/ui/scripting_control.html">
26<link rel="import" href="/tracing/ui/side_panel/side_panel_container.html">
27<link rel="import" href="/tracing/ui/timeline_track_view.html">
28<link rel="import" href="/tracing/ui/timeline_view_help_overlay.html">
29<link rel="import" href="/tracing/ui/timeline_view_metadata_overlay.html">
30<link rel="import" href="/tracing/value/ui/preferred_display_unit.html">
31
32<polymer-element name='tr-ui-timeline-view'>
33  <template>
34    <style>
35    :host {
36      flex-direction: column;
37      cursor: default;
38      display: flex;
39      font-family: sans-serif;
40      padding: 0;
41    }
42
43    #control {
44      background-color: #e6e6e6;
45      background-image: -webkit-gradient(linear, 0 0, 0 100%,
46          from(#E5E5E5), to(#D1D1D1));
47      flex: 0 0 auto;
48      overflow-x: auto;
49    }
50
51    #control::-webkit-scrollbar { height: 0px; }
52
53    #control > #bar {
54      font-size: 12px;
55      display: flex;
56      flex-direction: row;
57      margin: 1px;
58    }
59
60    #control > #bar > #title {
61      display: flex;
62      align-items: center;
63      padding-left: 8px;
64      padding-right: 8px;
65      flex: 1 1 auto;
66    }
67
68    #control > #bar > #left_controls,
69    #control > #bar > #right_controls {
70      display: flex;
71      flex-direction: row;
72      align-items: stretch;
73    }
74
75    #control > #bar > #left_controls > * { margin-right: 2px; }
76    #control > #bar > #right_controls > * { margin-left: 2px; }
77    #control > #collapsing_controls { display: flex; }
78
79    middle-container {
80      flex: 1 1 auto;
81      flex-direction: row;
82      border-bottom: 1px solid #8e8e8e;
83      display: flex;
84      min-height: 0;
85    }
86
87    middle-container ::content track-view-container {
88      flex: 1 1 auto;
89      display: flex;
90      min-height: 0;
91      min-width: 0;
92    }
93
94    middle-container ::content track-view-container > * { flex: 1 1 auto; }
95    middle-container > x-timeline-view-side-panel-container { flex: 0 0 auto; }
96    tr-ui-b-drag-handle { flex: 0 0 auto; }
97    tr-ui-a-analysis-view { flex: 0 0 auto; }
98    </style>
99
100    <tv-ui-b-hotkey-controller id="hkc"></tv-ui-b-hotkey-controller>
101    <div id="control">
102      <div id="bar">
103        <div id="left_controls"></div>
104        <div id="title">^_^</div>
105        <div id="right_controls">
106          <tr-ui-b-toolbar-button id="view_metadata_button">
107            M
108          </tr-ui-b-toolbar-button>
109          <tr-ui-b-dropdown id="view_options_dropdown"></tr-ui-b-dropdown>
110          <tr-ui-find-control id="view_find_control"></tr-ui-find-control>
111          <tr-ui-b-toolbar-button id="view_console_button">
112            &#187;
113          </tr-ui-b-toolbar-button>
114          <tr-ui-b-toolbar-button id="view_help_button">
115            ?
116          </tr-ui-b-toolbar-button>
117        </div>
118      </div>
119      <div id="collapsing_controls"></div>
120      <tr-ui-b-info-bar-group id="import-warnings">
121      </tr-ui-b-info-bar-group>
122    </div>
123    <middle-container>
124      <content></content>
125
126      <tr-ui-side-panel-container id="side_panel_container">
127      </tr-ui-side-panel-container>
128    </middle-container>
129    <tr-ui-b-drag-handle id="drag_handle"></tr-ui-b-drag-handle>
130    <tr-ui-a-analysis-view id="analysis"></tr-ui-a-analysis-view>
131
132    <tr-v-ui-preferred-display-unit id="display_unit">
133    </tr-v-ui-preferred-display-unit>
134  </template>
135
136  <script>
137  'use strict';
138
139  Polymer({
140    ready: function() {
141      this.tabIndex = 0; // Let the timeline able to receive key events.
142
143      this.titleEl_ = this.$.title;
144      this.leftControlsEl_ = this.$.left_controls;
145      this.rightControlsEl_ = this.$.right_controls;
146      this.collapsingControlsEl_ = this.$.collapsing_controls;
147      this.sidePanelContainer_ = this.$.side_panel_container;
148
149      this.brushingStateController_ = new tr.c.BrushingStateController(this);
150
151      this.findCtl_ = this.$.view_find_control;
152      this.findCtl_.controller = new tr.ui.FindController(
153          this.brushingStateController_);
154
155      this.scriptingCtl_ = document.createElement('tr-ui-scripting-control');
156      this.scriptingCtl_.controller = new tr.c.ScriptingController(
157          this.brushingStateController_);
158
159      this.sidePanelContainer_.brushingStateController =
160          this.brushingStateController_;
161
162      if (window.tr.metrics && window.tr.metrics.sh &&
163          window.tr.metrics.sh.SystemHealthMetric) {
164        this.railScoreSpan_ = document.createElement(
165            'tr-metrics-ui-sh-system-health-span');
166        this.rightControls.appendChild(this.railScoreSpan_);
167      } else {
168        this.railScoreSpan_ = undefined;
169      }
170
171      this.optionsDropdown_ = this.$.view_options_dropdown;
172      this.optionsDropdown_.iconElement.textContent = 'View Options';
173
174      this.showFlowEvents_ = false;
175      this.optionsDropdown_.appendChild(tr.ui.b.createCheckBox(
176          this, 'showFlowEvents',
177          'tr.ui.TimelineView.showFlowEvents', false,
178          'Flow events'));
179      this.highlightVSync_ = false;
180      this.highlightVSyncCheckbox_ = tr.ui.b.createCheckBox(
181          this, 'highlightVSync',
182          'tr.ui.TimelineView.highlightVSync', false,
183          'Highlight VSync');
184      this.optionsDropdown_.appendChild(this.highlightVSyncCheckbox_);
185
186      this.initMetadataButton_();
187      this.initConsoleButton_();
188      this.initHelpButton_();
189
190      this.collapsingControls.appendChild(this.scriptingCtl_);
191
192      this.dragEl_ = this.$.drag_handle;
193
194      this.analysisEl_ = this.$.analysis;
195      this.analysisEl_.brushingStateController = this.brushingStateController_;
196
197      this.addEventListener(
198          'requestSelectionChange',
199          function(e) {
200            var sc = this.brushingStateController_;
201            sc.changeSelectionFromRequestSelectionChangeEvent(e.selection);
202          }.bind(this));
203
204      // Bookkeeping.
205      this.onViewportChanged_ = this.onViewportChanged_.bind(this);
206      this.bindKeyListeners_();
207
208      this.dragEl_.target = this.analysisEl_;
209    },
210
211    domReady: function() {
212      this.trackViewContainer_ = this.querySelector('#track_view_container');
213    },
214
215    get globalMode() {
216      return this.hotkeyController.globalMode;
217    },
218
219    set globalMode(globalMode) {
220      globalMode = !!globalMode;
221      this.brushingStateController_.historyEnabled = globalMode;
222      this.hotkeyController.globalMode = globalMode;
223    },
224
225    get hotkeyController() {
226      return this.$.hkc;
227    },
228
229    updateDocumentFavicon: function() {
230      var hue;
231      if (!this.model)
232        hue = 'blue';
233      else
234        hue = this.model.faviconHue;
235
236      var faviconData = tr.ui.b.FaviconsByHue[hue];
237      if (faviconData === undefined)
238        faviconData = tr.ui.b.FaviconsByHue['blue'];
239
240      // Find link if its there
241      var link = document.head.querySelector('link[rel="shortcut icon"]');
242      if (!link) {
243        link = document.createElement('link');
244        link.rel = 'shortcut icon';
245        document.head.appendChild(link);
246      }
247      link.href = faviconData;
248    },
249
250    get showFlowEvents() {
251      return this.showFlowEvents_;
252    },
253
254    set showFlowEvents(showFlowEvents) {
255      this.showFlowEvents_ = showFlowEvents;
256      if (!this.trackView_)
257        return;
258      this.trackView_.viewport.showFlowEvents = showFlowEvents;
259    },
260
261    get highlightVSync() {
262      return this.highlightVSync_;
263    },
264
265    set highlightVSync(highlightVSync) {
266      this.highlightVSync_ = highlightVSync;
267      if (!this.trackView_)
268        return;
269      this.trackView_.viewport.highlightVSync = highlightVSync;
270    },
271
272    initHelpButton_: function() {
273      var helpButtonEl = this.$.view_help_button;
274
275      function onClick(e) {
276        var dlg = new tr.ui.b.Overlay();
277        dlg.title = 'Chrome Tracing Help';
278        dlg.appendChild(
279            document.createElement('tr-ui-timeline-view-help-overlay'));
280        dlg.visible = true;
281
282        // Stop event so it doesn't trigger new click listener on document.
283        e.stopPropagation();
284      }
285      helpButtonEl.addEventListener('click', onClick.bind(this));
286    },
287
288    initConsoleButton_: function() {
289      var toggleEl = this.$.view_console_button;
290
291      function onClick(e) {
292        this.scriptingCtl_.toggleVisibility();
293        e.stopPropagation();
294        return false;
295      }
296      toggleEl.addEventListener('click', onClick.bind(this));
297    },
298
299    initMetadataButton_: function() {
300      var showEl = this.$.view_metadata_button;
301
302      function onClick(e) {
303        var dlg = new tr.ui.b.Overlay();
304        dlg.title = 'Metadata for trace';
305
306        var metadataOverlay = document.createElement(
307            'tr-ui-timeline-view-metadata-overlay');
308        metadataOverlay.metadata = this.model.metadata;
309
310        dlg.appendChild(metadataOverlay);
311        dlg.visible = true;
312
313        e.stopPropagation();
314        return false;
315      }
316      showEl.addEventListener('click', onClick.bind(this));
317
318      this.updateMetadataButtonVisibility_();
319    },
320
321    updateMetadataButtonVisibility_: function() {
322      var showEl = this.$.view_metadata_button;
323      showEl.style.display =
324          (this.model && this.model.metadata.length) ? '' : 'none';
325    },
326
327    get leftControls() {
328      return this.leftControlsEl_;
329    },
330
331    get rightControls() {
332      return this.rightControlsEl_;
333    },
334
335    get collapsingControls() {
336      return this.collapsingControlsEl_;
337    },
338
339    get viewTitle() {
340      return this.titleEl_.textContent.substring(
341          this.titleEl_.textContent.length - 2);
342    },
343
344    set viewTitle(text) {
345      if (text === undefined) {
346        this.titleEl_.textContent = '';
347        this.titleEl_.hidden = true;
348        return;
349      }
350      this.titleEl_.hidden = false;
351      this.titleEl_.textContent = text;
352    },
353
354    get model() {
355      if (this.trackView_)
356        return this.trackView_.model;
357      return undefined;
358    },
359
360    set model(model) {
361      var modelInstanceChanged = model != this.model;
362      var modelValid = model && !model.bounds.isEmpty;
363
364      var importWarningsEl = this.shadowRoot.querySelector('#import-warnings');
365      importWarningsEl.textContent = '';
366
367      // Remove old trackView if the model has completely changed.
368      if (modelInstanceChanged) {
369        if (this.railScoreSpan_)
370          this.railScoreSpan_.model = undefined;
371        this.trackViewContainer_.textContent = '';
372        if (this.trackView_) {
373          this.trackView_.viewport.removeEventListener(
374              'change', this.onViewportChanged_);
375          this.trackView_.brushingStateController = undefined;
376          this.trackView_.detach();
377          this.trackView_ = undefined;
378        }
379        this.brushingStateController_.modelWillChange();
380      }
381
382      // Create new trackView if needed.
383      if (modelValid && !this.trackView_) {
384        this.trackView_ = document.createElement('tr-ui-timeline-track-view');
385        this.trackView_.timelineView = this;
386
387        this.trackView.brushingStateController = this.brushingStateController_;
388
389        this.trackViewContainer_.appendChild(this.trackView_);
390        this.trackView_.viewport.addEventListener(
391            'change', this.onViewportChanged_);
392      }
393
394      // Set the model.
395      if (modelValid) {
396        this.trackView_.model = model;
397        this.trackView_.viewport.showFlowEvents = this.showFlowEvents;
398        this.trackView_.viewport.highlightVSync = this.highlightVSync;
399        if (this.railScoreSpan_)
400          this.railScoreSpan_.model = model;
401
402        this.$.display_unit.preferredTimeDisplayMode = model.intrinsicTimeUnit;
403      }
404
405      if (model) {
406        model.importWarningsThatShouldBeShownToUser.forEach(
407            function(importWarning) {
408              importWarningsEl.addMessage(
409                  'Import Warning: ' + importWarning.type + ': ' +
410                  importWarning.message);
411            }, this);
412      }
413
414      // Do things that are selection specific
415      if (modelInstanceChanged) {
416        this.updateMetadataButtonVisibility_();
417        this.brushingStateController_.modelDidChange();
418        this.onViewportChanged_();
419      }
420    },
421
422    get brushingStateController() {
423      return this.brushingStateController_;
424    },
425
426    get trackView() {
427      return this.trackView_;
428    },
429
430    get settings() {
431      if (!this.settings_)
432        this.settings_ = new tr.b.Settings();
433      return this.settings_;
434    },
435
436    /**
437     * Deprecated. Kept around because third_party code occasionally calls
438     * this to set up embedding.
439     */
440    set focusElement(value) {
441      throw new Error('This is deprecated. Please set globalMode to true.');
442    },
443
444    bindKeyListeners_: function() {
445      var hkc = this.hotkeyController;
446
447      // Shortcuts that *can* steal focus from the console and the filter text
448      // box.
449      hkc.addHotKey(new tr.ui.b.HotKey({
450        eventType: 'keypress',
451        keyCode: '`'.charCodeAt(0),
452        useCapture: true,
453        thisArg: this,
454        callback: function(e) {
455          this.scriptingCtl_.toggleVisibility();
456          if (!this.scriptingCtl_.hasFocus)
457            this.focus();
458          e.stopPropagation();
459        }
460      }));
461
462      // Shortcuts that *can* steal focus from the filter text box.
463      hkc.addHotKey(new tr.ui.b.HotKey({
464        eventType: 'keypress',
465        keyCode: '/'.charCodeAt(0),
466        useCapture: true,
467        thisArg: this,
468        callback: function(e) {
469          if (this.scriptingCtl_.hasFocus)
470            return;
471          if (this.findCtl_.hasFocus)
472            this.focus();
473          else
474            this.findCtl_.focus();
475          e.preventDefault();
476          e.stopPropagation();
477        }
478      }));
479
480      // Shortcuts that *can't* steal focus.
481      hkc.addHotKey(new tr.ui.b.HotKey({
482        eventType: 'keypress',
483        keyCode: '?'.charCodeAt(0),
484        useCapture: false,
485        thisArg: this,
486        callback: function(e) {
487          this.$.view_help_button.click();
488          e.stopPropagation();
489        }
490      }));
491
492      hkc.addHotKey(new tr.ui.b.HotKey({
493        eventType: 'keypress',
494        keyCode: 'v'.charCodeAt(0),
495        useCapture: false,
496        thisArg: this,
497        callback: function(e) {
498          this.toggleHighlightVSync_();
499          e.stopPropagation();
500        }
501      }));
502    },
503
504    onViewportChanged_: function(e) {
505      var spc = this.sidePanelContainer_;
506      if (!this.trackView_) {
507        spc.rangeOfInterest.reset();
508        return;
509      }
510
511      var vr = this.trackView_.viewport.interestRange.asRangeObject();
512      if (!spc.rangeOfInterest.equals(vr))
513        spc.rangeOfInterest = vr;
514
515      if (this.railScoreSpan_ && this.model)
516        this.railScoreSpan_.model = this.model;
517    },
518
519    toggleHighlightVSync_: function() {
520      this.highlightVSyncCheckbox_.checked =
521          !this.highlightVSyncCheckbox_.checked;
522    },
523
524    setFindCtlText: function(string) {
525      this.findCtl_.setText(string);
526    }
527  });
528  </script>
529</polymer-element>
530