Home | History | Annotate | Download | only in tracing
      1 // Copyright (c) 2013 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 
      7 /**
      8  * @fileoverview View visualizes TRACE_EVENT events using the
      9  * tracing.Timeline component and adds in selection summary and control buttons.
     10  */
     11 base.requireStylesheet('tracing.timeline_view');
     12 base.requireTemplate('tracing.timeline_view');
     13 
     14 base.require('base.utils');
     15 base.require('base.settings');
     16 base.require('tracing.analysis.analysis_view');
     17 base.require('tracing.category_filter_dialog');
     18 base.require('tracing.filter');
     19 base.require('tracing.find_control');
     20 base.require('tracing.timeline_track_view');
     21 base.require('ui.overlay');
     22 base.require('ui.drag_handle');
     23 
     24 base.exportTo('tracing', function() {
     25 
     26   /**
     27    * View
     28    * @constructor
     29    * @extends {HTMLDivElement}
     30    */
     31   var TimelineView = ui.define('div');
     32 
     33   TimelineView.prototype = {
     34     __proto__: HTMLDivElement.prototype,
     35 
     36     decorate: function() {
     37       this.classList.add('timeline-view');
     38 
     39       var node = base.instantiateTemplate('#timeline-view-template');
     40       this.appendChild(node);
     41 
     42       this.titleEl_ = this.querySelector('.title');
     43       this.leftControlsEl_ = this.querySelector('#left-controls');
     44       this.rightControlsEl_ = this.querySelector('#right-controls');
     45       this.timelineContainer_ = this.querySelector('.container');
     46 
     47       this.categoryFilterButton_ = this.createCategoryFilterButton_();
     48       this.categoryFilterButton_.callback =
     49           this.updateCategoryFilter_.bind(this);
     50 
     51       this.findCtl_ = new tracing.FindControl();
     52       this.findCtl_.controller = new tracing.FindController();
     53 
     54       this.rightControls.appendChild(this.createImportErrorsButton_());
     55       this.rightControls.appendChild(this.categoryFilterButton_);
     56       this.rightControls.appendChild(this.createMetadataButton_());
     57       this.rightControls.appendChild(this.findCtl_);
     58       this.rightControls.appendChild(this.createHelpButton_());
     59 
     60       this.dragEl_ = new ui.DragHandle();
     61       this.appendChild(this.dragEl_);
     62 
     63       this.analysisEl_ = new tracing.analysis.AnalysisView();
     64       this.analysisEl_.addEventListener(
     65           'requestSelectionChange',
     66           this.onRequestSelectionChange_.bind(this));
     67       this.appendChild(this.analysisEl_);
     68 
     69       // Bookkeeping.
     70       this.onSelectionChanged_ = this.onSelectionChanged_.bind(this);
     71       document.addEventListener('keypress', this.onKeypress_.bind(this), true);
     72 
     73       this.dragEl_.target = this.analysisEl_;
     74     },
     75 
     76     createImportErrorsButton_: function() {
     77       var node = base.instantiateTemplate('#import-errors-btn-template');
     78       var showEl = node.querySelector('.view-import-errors-button');
     79       var containerEl = node.querySelector('.info-button-container');
     80       var textEl = containerEl.querySelector('.info-button-text');
     81 
     82       var dlg = new ui.Overlay();
     83       dlg.classList.add('view-import-errors-overlay');
     84       dlg.obeyCloseEvents = true;
     85       dlg.appendChild(containerEl);
     86 
     87       function onClick() {
     88         dlg.visible = true;
     89         textEl.textContent = this.model.importErrors.join('\n');
     90       }
     91       showEl.addEventListener('click', onClick.bind(this));
     92 
     93       function updateVisibility() {
     94         showEl.style.display =
     95             (this.model && this.model.importErrors.length) ? '' : 'none';
     96       }
     97       var updateVisibility_ = updateVisibility.bind(this);
     98       updateVisibility_();
     99       this.addEventListener('modelChange', updateVisibility_);
    100 
    101       return showEl;
    102     },
    103 
    104     updateCategoryFilter_: function(categories) {
    105       if (!this.timeline_)
    106         return;
    107       this.timeline_.categoryFilter = new tracing.CategoryFilter(categories);
    108     },
    109 
    110     createCategoryFilterButton_: function() {
    111       var node = base.instantiateTemplate('#category-filter-btn-template');
    112       var showEl = node.querySelector('.view-info-button');
    113 
    114       function onClick() {
    115         var dlg = new tracing.CategoryFilterDialog();
    116         dlg.categories = this.model.categories;
    117         dlg.settings_key = 'categories';
    118         dlg.settingUpdatedCallback = this.updateCategoryFilter_.bind(this);
    119         dlg.visible = true;
    120       }
    121       showEl.addEventListener('click', onClick.bind(this));
    122 
    123       function updateVisibility() {
    124         showEl.style.display = this.model ? '' : 'none';
    125       }
    126       var updateVisibility_ = updateVisibility.bind(this);
    127       updateVisibility_();
    128       this.addEventListener('modelChange', updateVisibility_);
    129 
    130       return showEl;
    131     },
    132 
    133     createHelpButton_: function() {
    134       var node = base.instantiateTemplate('#help-btn-template');
    135       var showEl = node.querySelector('.view-help-button');
    136       var helpTextEl = node.querySelector('.view-help-text');
    137 
    138       var dlg = new ui.Overlay();
    139       dlg.classList.add('view-help-overlay');
    140       dlg.obeyCloseEvents = true;
    141       dlg.additionalCloseKeyCodes.push('?'.charCodeAt(0));
    142       dlg.appendChild(helpTextEl);
    143 
    144       function onClick(e) {
    145         dlg.visible = true;
    146 
    147         helpTextEl.textContent = this.timeline_ ? this.timeline_.keyHelp :
    148             'No content loaded. For interesting help, load something.';
    149 
    150         // Stop event so it doesn't trigger new click listener on document.
    151         e.stopPropagation();
    152         return false;
    153       }
    154       showEl.addEventListener('click', onClick.bind(this));
    155 
    156       return showEl;
    157     },
    158 
    159     createMetadataButton_: function() {
    160       var node = base.instantiateTemplate('#metadata-btn-template');
    161       var showEl = node.querySelector('.view-metadata-button');
    162       var containerEl = node.querySelector('.info-button-container');
    163       var textEl = containerEl.querySelector('.info-button-text');
    164 
    165       var dlg = new ui.Overlay();
    166       dlg.classList.add('view-metadata-overlay');
    167       dlg.obeyCloseEvents = true;
    168       dlg.appendChild(containerEl);
    169 
    170       function onClick() {
    171         dlg.visible = true;
    172 
    173         var metadataStrings = [];
    174 
    175         var model = this.model;
    176         for (var data in model.metadata) {
    177           var meta = model.metadata[data];
    178           var name = JSON.stringify(meta.name);
    179           var value = JSON.stringify(meta.value, undefined, ' ');
    180 
    181           metadataStrings.push(name + ': ' + value);
    182         }
    183         textEl.textContent = metadataStrings.join('\n');
    184       }
    185       showEl.addEventListener('click', onClick.bind(this));
    186 
    187       function updateVisibility() {
    188         showEl.style.display =
    189             (this.model && this.model.metadata.length) ? '' : 'none';
    190       }
    191       var updateVisibility_ = updateVisibility.bind(this);
    192       updateVisibility_();
    193       this.addEventListener('modelChange', updateVisibility_);
    194 
    195       return showEl;
    196     },
    197 
    198     get leftControls() {
    199       return this.leftControlsEl_;
    200     },
    201 
    202     get rightControls() {
    203       return this.rightControlsEl_;
    204     },
    205 
    206     get viewTitle() {
    207       return this.titleEl_.textContent.substring(
    208           this.titleEl_.textContent.length - 2);
    209     },
    210 
    211     set viewTitle(text) {
    212       if (text === undefined) {
    213         this.titleEl_.textContent = '';
    214         this.titleEl_.hidden = true;
    215         return;
    216       }
    217       this.titleEl_.hidden = false;
    218       this.titleEl_.textContent = text;
    219     },
    220 
    221     set traceData(traceData) {
    222       this.model = new tracing.TraceModel(traceData);
    223     },
    224 
    225     get model() {
    226       if (this.timeline_)
    227         return this.timeline_.model;
    228       return undefined;
    229     },
    230 
    231     set model(model) {
    232       var modelInstanceChanged = model != this.model;
    233       var modelValid = model && !model.bounds.isEmpty;
    234 
    235       // Remove old timeline if the model has completely changed.
    236       if (modelInstanceChanged) {
    237         this.timelineContainer_.textContent = '';
    238         if (this.timeline_) {
    239           this.timeline_.removeEventListener(
    240               'selectionChange', this.onSelectionChanged_);
    241           this.timeline_.detach();
    242           this.timeline_ = undefined;
    243           this.findCtl_.controller.timeline = undefined;
    244         }
    245       }
    246 
    247       // Create new timeline if needed.
    248       if (modelValid && !this.timeline_) {
    249         this.timeline_ = new tracing.TimelineTrackView();
    250         this.timeline_.focusElement =
    251             this.focusElement_ ? this.focusElement_ : this.parentElement;
    252         this.timelineContainer_.appendChild(this.timeline_);
    253         this.findCtl_.controller.timeline = this.timeline_;
    254         this.timeline_.addEventListener(
    255             'selectionChange', this.onSelectionChanged_);
    256 
    257         this.analysisEl_.clearSelectionHistory();
    258       }
    259 
    260       // Set the model.
    261       if (modelValid)
    262         this.timeline_.model = model;
    263       base.dispatchSimpleEvent(this, 'modelChange');
    264 
    265       // Do things that are selection specific
    266       if (modelInstanceChanged)
    267         this.onSelectionChanged_();
    268     },
    269 
    270     get timeline() {
    271       return this.timeline_;
    272     },
    273 
    274     get settings() {
    275       if (!this.settings_)
    276         this.settings_ = new base.Settings();
    277       return this.settings_;
    278     },
    279 
    280     /**
    281      * Sets the element whose focus state will determine whether
    282      * to respond to keybaord input.
    283      */
    284     set focusElement(value) {
    285       this.focusElement_ = value;
    286       if (this.timeline_)
    287         this.timeline_.focusElement = value;
    288     },
    289 
    290     /**
    291      * @return {Element} The element whose focused state determines
    292      * whether to respond to keyboard inputs.
    293      * Defaults to the parent element.
    294      */
    295     get focusElement() {
    296       if (this.focusElement_)
    297         return this.focusElement_;
    298       return this.parentElement;
    299     },
    300 
    301     /**
    302      * @return {boolean} Whether the current timeline is attached to the
    303      * document.
    304      */
    305     get isAttachedToDocument_() {
    306       var cur = this;
    307       while (cur.parentNode)
    308         cur = cur.parentNode;
    309       return cur == this.ownerDocument;
    310     },
    311 
    312     get listenToKeys_() {
    313       if (!this.isAttachedToDocument_)
    314         return;
    315       if (!this.focusElement_)
    316         return true;
    317       if (this.focusElement.tabIndex >= 0)
    318         return document.activeElement == this.focusElement;
    319       return true;
    320     },
    321 
    322     onKeypress_: function(e) {
    323       if (!this.listenToKeys_)
    324         return;
    325 
    326       if (event.keyCode == '/'.charCodeAt(0)) { // / key
    327         this.findCtl_.focus();
    328         event.preventDefault();
    329         return;
    330       } else if (e.keyCode == '?'.charCodeAt(0)) {
    331         this.querySelector('.view-help-button').click();
    332         e.preventDefault();
    333       }
    334     },
    335 
    336     beginFind: function() {
    337       if (this.findInProgress_)
    338         return;
    339       this.findInProgress_ = true;
    340       var dlg = tracing.FindControl();
    341       dlg.controller = new tracing.FindController();
    342       dlg.controller.timeline = this.timeline;
    343       dlg.visible = true;
    344       dlg.addEventListener('close', function() {
    345         this.findInProgress_ = false;
    346       }.bind(this));
    347       dlg.addEventListener('findNext', function() {
    348       });
    349       dlg.addEventListener('findPrevious', function() {
    350       });
    351     },
    352 
    353     onSelectionChanged_: function(e) {
    354       var oldScrollTop = this.timelineContainer_.scrollTop;
    355 
    356       var selection = this.timeline_ ?
    357           this.timeline_.selection :
    358           new tracing.Selection();
    359       this.analysisEl_.selection = selection;
    360       this.timelineContainer_.scrollTop = oldScrollTop;
    361     },
    362 
    363     onRequestSelectionChange_: function(e) {
    364       this.timeline_.selection = e.selection;
    365       e.stopPropagation();
    366     }
    367   };
    368 
    369   return {
    370     TimelineView: TimelineView
    371   };
    372 });
    373