Home | History | Annotate | Download | only in src
      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 
      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('timeline_view');
     12 
     13 base.require('timeline_track_view');
     14 base.require('timeline_analysis_view');
     15 base.require('category_filter_dialog');
     16 base.require('filter');
     17 base.require('find_control');
     18 base.require('overlay');
     19 base.require('importer.trace_event_importer');
     20 base.require('importer.linux_perf_importer');
     21 base.require('importer.v8_log_importer');
     22 base.require('settings');
     23 
     24 base.exportTo('tracing', function() {
     25 
     26   /**
     27    * View
     28    * @constructor
     29    * @extends {HTMLDivElement}
     30    */
     31   var TimelineView = tracing.ui.define('div');
     32 
     33   TimelineView.prototype = {
     34     __proto__: HTMLDivElement.prototype,
     35 
     36     decorate: function() {
     37       this.classList.add('view');
     38 
     39       // Create individual elements.
     40       this.titleEl_ = document.createElement('div');
     41       this.titleEl_.textContent = 'Tracing: ';
     42       this.titleEl_.className = 'title';
     43 
     44       this.controlDiv_ = document.createElement('div');
     45       this.controlDiv_.className = 'control';
     46 
     47       this.leftControlsEl_ = document.createElement('div');
     48       this.leftControlsEl_.className = 'controls';
     49       this.rightControlsEl_ = document.createElement('div');
     50       this.rightControlsEl_.className = 'controls';
     51 
     52       var spacingEl = document.createElement('div');
     53       spacingEl.className = 'spacer';
     54 
     55       this.timelineContainer_ = document.createElement('div');
     56       this.timelineContainer_.className = 'container';
     57 
     58       var analysisContainer_ = document.createElement('div');
     59       analysisContainer_.className = 'analysis-container';
     60 
     61       this.analysisEl_ = new tracing.TimelineAnalysisView();
     62 
     63       this.dragEl_ = new DragHandle();
     64       this.dragEl_.target = analysisContainer_;
     65 
     66       this.findCtl_ = new tracing.FindControl();
     67       this.findCtl_.controller = new tracing.FindController();
     68 
     69       this.importErrorsButton_ = this.createImportErrorsButton_();
     70       this.categoryFilterButton_ = this.createCategoryFilterButton_();
     71       this.categoryFilterButton_.callback =
     72           this.updateCategoryFilterFromSettings_.bind(this);
     73       this.metadataButton_ = this.createMetadataButton_();
     74 
     75       // Connect everything up.
     76       this.rightControls.appendChild(this.importErrorsButton_);
     77       this.rightControls.appendChild(this.categoryFilterButton_);
     78       this.rightControls.appendChild(this.metadataButton_);
     79       this.rightControls.appendChild(this.findCtl_);
     80       this.controlDiv_.appendChild(this.titleEl_);
     81       this.controlDiv_.appendChild(this.leftControlsEl_);
     82       this.controlDiv_.appendChild(spacingEl);
     83       this.controlDiv_.appendChild(this.rightControlsEl_);
     84       this.appendChild(this.controlDiv_);
     85 
     86       this.appendChild(this.timelineContainer_);
     87       this.appendChild(this.dragEl_);
     88 
     89       analysisContainer_.appendChild(this.analysisEl_);
     90       this.appendChild(analysisContainer_);
     91 
     92       this.rightControls.appendChild(this.createHelpButton_());
     93 
     94       // Bookkeeping.
     95       this.onSelectionChangedBoundToThis_ = this.onSelectionChanged_.bind(this);
     96       document.addEventListener('keypress', this.onKeypress_.bind(this), true);
     97     },
     98 
     99     createImportErrorsButton_: function() {
    100       var dlg = new tracing.ui.Overlay();
    101       dlg.classList.add('view-import-errors-overlay');
    102       dlg.autoClose = true;
    103 
    104       var showEl = document.createElement('div');
    105       showEl.className = 'button view-import-errors-button view-info-button';
    106       showEl.textContent = 'Import errors!';
    107 
    108       var textEl = document.createElement('div');
    109       textEl.className = 'info-button-text import-errors-dialog-text';
    110 
    111       var containerEl = document.createElement('div');
    112       containerEl.className = 'info-button-container' +
    113           'import-errors-dialog';
    114 
    115       containerEl.textContent = 'Errors occurred during import:';
    116       containerEl.appendChild(textEl);
    117       dlg.appendChild(containerEl);
    118 
    119       var that = this;
    120       function onClick() {
    121         dlg.visible = true;
    122         textEl.textContent = that.model.importErrors.join('\n');
    123       }
    124       showEl.addEventListener('click', onClick.bind(this));
    125 
    126       function updateVisibility() {
    127         if (that.model &&
    128             that.model.importErrors.length)
    129           showEl.style.display = '';
    130         else
    131           showEl.style.display = 'none';
    132       }
    133       updateVisibility();
    134       that.addEventListener('modelChange', updateVisibility);
    135 
    136       return showEl;
    137     },
    138 
    139     createCategoryFilterButton_: function() {
    140       // Set by the embedder of the help button that we create in this function.
    141       var callback;
    142 
    143       var showEl = document.createElement('div');
    144       showEl.className = 'button view-info-button';
    145       showEl.textContent = 'Categories';
    146       showEl.__defineSetter__('callback', function(value) {
    147         callback = value;
    148       });
    149 
    150 
    151       var that = this;
    152       function onClick() {
    153         var dlg = new tracing.CategoryFilterDialog();
    154         dlg.categories = that.model.categories;
    155         dlg.settings = that.settings;
    156         dlg.settings_key = 'categories';
    157         dlg.settingUpdatedCallback = callback;
    158         dlg.visible = true;
    159       }
    160 
    161       function updateVisibility() {
    162         if (that.model)
    163           showEl.style.display = '';
    164         else
    165           showEl.style.display = 'none';
    166       }
    167       updateVisibility();
    168       that.addEventListener('modelChange', updateVisibility);
    169 
    170       showEl.addEventListener('click', onClick.bind(this));
    171       return showEl;
    172     },
    173 
    174     createHelpButton_: function() {
    175       var dlg = new tracing.ui.Overlay();
    176       dlg.classList.add('view-help-overlay');
    177       dlg.autoClose = true;
    178       dlg.additionalCloseKeyCodes.push('?'.charCodeAt(0));
    179 
    180       var showEl = document.createElement('div');
    181       showEl.className = 'button view-help-button';
    182       showEl.textContent = '?';
    183 
    184       var helpTextEl = document.createElement('div');
    185       helpTextEl.style.whiteSpace = 'pre';
    186       helpTextEl.style.fontFamily = 'monospace';
    187       dlg.appendChild(helpTextEl);
    188 
    189       function onClick(e) {
    190         dlg.visible = true;
    191         if (this.timeline_)
    192           helpTextEl.textContent = this.timeline_.keyHelp;
    193         else
    194           helpTextEl.textContent = 'No content loaded. For interesting help,' +
    195               ' load something.';
    196 
    197         // Stop event so it doesn't trigger new click listener on document.
    198         e.stopPropagation();
    199         return false;
    200       }
    201 
    202       showEl.addEventListener('click', onClick.bind(this));
    203 
    204       return showEl;
    205     },
    206 
    207     createMetadataButton_: function() {
    208       var dlg = new tracing.ui.Overlay();
    209       dlg.classList.add('view-metadata-overlay');
    210       dlg.autoClose = true;
    211 
    212       var showEl = document.createElement('div');
    213       showEl.className = 'button view-metadata-button view-info-button';
    214       showEl.textContent = 'Metadata';
    215 
    216       var textEl = document.createElement('div');
    217       textEl.className = 'info-button-text metadata-dialog-text';
    218 
    219       var containerEl = document.createElement('div');
    220       containerEl.className = 'info-button-container metadata-dialog';
    221 
    222       containerEl.textContent = 'Metadata Info:';
    223       containerEl.appendChild(textEl);
    224       dlg.appendChild(containerEl);
    225 
    226       var that = this;
    227       function onClick() {
    228         dlg.visible = true;
    229 
    230         var metadataStrings = [];
    231 
    232         var model = that.model;
    233         for (var data in model.metadata) {
    234           metadataStrings.push(JSON.stringify(model.metadata[data].name) +
    235                                ': ' + JSON.stringify(model.metadata[data].value, undefined, ' '));
    236         }
    237         textEl.textContent = metadataStrings.join('\n');
    238       }
    239       showEl.addEventListener('click', onClick.bind(this));
    240 
    241       function updateVisibility() {
    242         if (that.model &&
    243             that.model.metadata.length)
    244           showEl.style.display = '';
    245         else
    246           showEl.style.display = 'none';
    247       }
    248       updateVisibility();
    249       that.addEventListener('modelChange', updateVisibility);
    250 
    251       return showEl;
    252     },
    253 
    254     get leftControls() {
    255       return this.leftControlsEl_;
    256     },
    257 
    258     get rightControls() {
    259       return this.rightControlsEl_;
    260     },
    261 
    262     get title() {
    263       return this.titleEl_.textContent.substring(
    264           this.titleEl_.textContent.length - 2);
    265     },
    266 
    267     set title(text) {
    268       this.titleEl_.textContent = text + ':';
    269     },
    270 
    271     set traceData(traceData) {
    272       this.model = new tracing.Model(traceData);
    273     },
    274 
    275     get model() {
    276       if (this.timeline_)
    277         return this.timeline_.model;
    278       return undefined;
    279     },
    280 
    281     set model(model) {
    282       var modelInstanceChanged = model != this.model;
    283       var modelValid = model && !model.bounds.isEmpty;
    284 
    285       // Remove old timeline if the model has completely changed.
    286       if (modelInstanceChanged) {
    287         this.timelineContainer_.textContent = '';
    288         if (this.timeline_) {
    289           this.timeline_.removeEventListener(
    290               'selectionChange', this.onSelectionChangedBoundToThis_);
    291           this.timeline_.detach();
    292           this.timeline_ = undefined;
    293           this.findCtl_.controller.timeline = undefined;
    294         }
    295       }
    296 
    297       // Create new timeline if needed.
    298       if (modelValid && !this.timeline_) {
    299         this.timeline_ = new tracing.TimelineTrackView();
    300         this.timeline_.focusElement =
    301             this.focusElement_ ? this.focusElement_ : this.parentElement;
    302         this.timelineContainer_.appendChild(this.timeline_);
    303         this.findCtl_.controller.timeline = this.timeline_;
    304         this.timeline_.addEventListener(
    305             'selectionChange', this.onSelectionChangedBoundToThis_);
    306         this.updateCategoryFilterFromSettings_();
    307       }
    308 
    309       // Set the model.
    310       if (modelValid)
    311         this.timeline_.model = model;
    312       base.dispatchSimpleEvent(this, 'modelChange');
    313 
    314       // Do things that are selection specific
    315       if (modelInstanceChanged)
    316         this.onSelectionChanged_();
    317     },
    318 
    319     get timeline() {
    320       return this.timeline_;
    321     },
    322 
    323     get settings() {
    324       if (!this.settings_)
    325         this.settings_ = new base.Settings();
    326       return this.settings_;
    327     },
    328 
    329     /**
    330      * Sets the element whose focus state will determine whether
    331      * to respond to keybaord input.
    332      */
    333     set focusElement(value) {
    334       this.focusElement_ = value;
    335       if (this.timeline_)
    336         this.timeline_.focusElement = value;
    337     },
    338 
    339     /**
    340      * @return {Element} The element whose focused state determines
    341      * whether to respond to keyboard inputs.
    342      * Defaults to the parent element.
    343      */
    344     get focusElement() {
    345       if (this.focusElement_)
    346         return this.focusElement_;
    347       return this.parentElement;
    348     },
    349 
    350     /**
    351      * @return {boolean} Whether the current timeline is attached to the
    352      * document.
    353      */
    354     get isAttachedToDocument_() {
    355       var cur = this;
    356       while (cur.parentNode)
    357         cur = cur.parentNode;
    358       return cur == this.ownerDocument;
    359     },
    360 
    361     get listenToKeys_() {
    362       if (!this.isAttachedToDocument_)
    363         return;
    364       if (!this.focusElement_)
    365         return true;
    366       if (this.focusElement.tabIndex >= 0)
    367         return document.activeElement == this.focusElement;
    368       return true;
    369     },
    370 
    371     onKeypress_: function(e) {
    372       if (!this.listenToKeys_)
    373         return;
    374 
    375       if (event.keyCode == '/'.charCodeAt(0)) { // / key
    376         this.findCtl_.focus();
    377         event.preventDefault();
    378         return;
    379       } else if (e.keyCode == '?'.charCodeAt(0)) {
    380         this.querySelector('.view-help-button').click();
    381         e.preventDefault();
    382       }
    383     },
    384 
    385     beginFind: function() {
    386       if (this.findInProgress_)
    387         return;
    388       this.findInProgress_ = true;
    389       var dlg = tracing.FindControl();
    390       dlg.controller = new tracing.FindController();
    391       dlg.controller.timeline = this.timeline;
    392       dlg.visible = true;
    393       dlg.addEventListener('close', function() {
    394         this.findInProgress_ = false;
    395       }.bind(this));
    396       dlg.addEventListener('findNext', function() {
    397       });
    398       dlg.addEventListener('findPrevious', function() {
    399       });
    400     },
    401 
    402     onSelectionChanged_: function(e) {
    403       var oldScrollTop = this.timelineContainer_.scrollTop;
    404 
    405       var selection = this.timeline_ ?
    406           this.timeline_.selection :
    407           new tracing.Selection();
    408       this.analysisEl_.selection = selection;
    409       this.timelineContainer_.scrollTop = oldScrollTop;
    410     },
    411 
    412     updateCategoryFilterFromSettings_: function() {
    413       if (!this.timeline_)
    414         return;
    415 
    416       // Get the disabled categories from settings.
    417       var categories = this.settings.keys('categories');
    418       var disabledCategories = [];
    419       for (var i = 0; i < categories.length; i++) {
    420         if (this.settings.get(categories[i], 'true', 'categories') == 'false')
    421           disabledCategories.push(categories[i]);
    422       }
    423 
    424       this.timeline_.categoryFilter =
    425           new tracing.CategoryFilter(disabledCategories);
    426     }
    427   };
    428 
    429   /**
    430    * Timeline Drag Handle
    431    * Detects when user clicks handle determines new height of container based
    432    * on user's vertical mouse move and resizes the target.
    433    * @constructor
    434    * @extends {HTMLDivElement}
    435    * You will need to set target to be the draggable element
    436    */
    437   var DragHandle = tracing.ui.define('div');
    438 
    439   DragHandle.prototype = {
    440     __proto__: HTMLDivElement.prototype,
    441 
    442     decorate: function() {
    443       this.className = 'drag-handle';
    444       this.lastMousePosY = 0;
    445       this.dragAnalysis = this.dragAnalysis.bind(this);
    446       this.onMouseUp = this.onMouseUp.bind(this);
    447       this.addEventListener('mousedown', this.onMouseDown);
    448     },
    449 
    450     dragAnalysis: function(e) {
    451       // Compute the difference in height position.
    452       var dy = this.lastMousePosY - e.clientY;
    453       // If style is not set, start off with computed height.
    454       if (!this.target.style.height)
    455         this.target.style.height = window.getComputedStyle(this.target).height;
    456       // Calculate new height of the container.
    457       this.target.style.height = parseInt(this.target.style.height) + dy + 'px';
    458       this.lastMousePosY = e.clientY;
    459     },
    460 
    461     onMouseDown: function(e) {
    462       this.lastMousePosY = e.clientY;
    463       document.addEventListener('mousemove', this.dragAnalysis);
    464       document.addEventListener('mouseup', this.onMouseUp);
    465       e.stopPropagation();
    466       return false;
    467     },
    468 
    469     onMouseUp: function(e) {
    470       document.removeEventListener('mousemove', this.dragAnalysis);
    471       document.removeEventListener('mouseup', this.onMouseUp);
    472     }
    473   };
    474 
    475   return {
    476     TimelineView: TimelineView
    477   };
    478 });
    479