Home | History | Annotate | Download | only in net_internals
      1 // Copyright 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 // TODO(viona): Write a README document/instructions.
      6 
      7 /** This view displays the event waterfall. */
      8 var WaterfallView = (function() {
      9   'use strict';
     10 
     11   // We inherit from DivView.
     12   var superClass = DivView;
     13 
     14   /**
     15    * @constructor
     16    */
     17   function WaterfallView() {
     18     assertFirstConstructorCall(WaterfallView);
     19 
     20     // Call superclass's constructor.
     21     superClass.call(this, WaterfallView.MAIN_BOX_ID);
     22 
     23     SourceTracker.getInstance().addSourceEntryObserver(this);
     24 
     25     // For adjusting the range of view.
     26     $(WaterfallView.SCALE_ID).addEventListener(
     27         'click', this.setStartEndTimes_.bind(this), true);
     28 
     29     $(WaterfallView.MAIN_BOX_ID).addEventListener(
     30         'mousewheel', this.scrollToZoom_.bind(this), true);
     31 
     32     $(WaterfallView.MAIN_BOX_ID).addEventListener(
     33         'scroll', this.scrollInfoTable_.bind(this), true);
     34 
     35     this.initializeSourceList_();
     36 
     37     window.onload = this.scrollInfoTable_();
     38   }
     39 
     40   WaterfallView.TAB_ID = 'tab-handle-waterfall';
     41   WaterfallView.TAB_NAME = 'Waterfall';
     42   WaterfallView.TAB_HASH = '#waterfall';
     43 
     44   // IDs for special HTML elements in events_waterfall_view.html.
     45   WaterfallView.MAIN_BOX_ID = 'waterfall-view-tab-content';
     46   WaterfallView.BAR_TABLE_ID = 'waterfall-view-time-bar-table';
     47   WaterfallView.BAR_TBODY_ID = 'waterfall-view-time-bar-tbody';
     48   WaterfallView.SCALE_ID = 'waterfall-view-adjust-to-window';
     49   WaterfallView.TIME_SCALE_HEADER_ID = 'waterfall-view-time-scale-labels';
     50   WaterfallView.TIME_RANGE_ID = 'waterfall-view-time-range-submit';
     51   WaterfallView.START_TIME_ID = 'waterfall-view-start-input';
     52   WaterfallView.END_TIME_ID = 'waterfall-view-end-input';
     53   WaterfallView.INFO_TABLE_ID = 'waterfall-view-information-table';
     54   WaterfallView.INFO_TBODY_ID = 'waterfall-view-information-tbody';
     55   WaterfallView.CONTROLS_ID = 'waterfall-view-controls';
     56   WaterfallView.ID_HEADER_ID = 'waterfall-view-id-header';
     57   WaterfallView.URL_HEADER_ID = 'waterfall-view-url-header';
     58 
     59   // The number of units mouse wheel deltas increase for each tick of the
     60   // wheel.
     61   var MOUSE_WHEEL_UNITS_PER_CLICK = 120;
     62 
     63   // Amount we zoom for one vertical tick of the mouse wheel, as a ratio.
     64   var MOUSE_WHEEL_ZOOM_RATE = 1.25;
     65   // Amount we scroll for one horizontal tick of the mouse wheel, in pixels.
     66   var MOUSE_WHEEL_SCROLL_RATE = MOUSE_WHEEL_UNITS_PER_CLICK;
     67 
     68   cr.addSingletonGetter(WaterfallView);
     69 
     70   WaterfallView.prototype = {
     71     // Inherit the superclass's methods.
     72     __proto__: superClass.prototype,
     73 
     74     /**
     75      * Creates new WaterfallRows for URL Requests when the sourceEntries are
     76      * updated if they do not already exist.
     77      * Updates pre-existing WaterfallRows that correspond to updated sources.
     78      */
     79     onSourceEntriesUpdated: function(sourceEntries) {
     80       for (var i = 0; i < sourceEntries.length; ++i) {
     81         var sourceEntry = sourceEntries[i];
     82         var id = sourceEntry.getSourceId();
     83         if (sourceEntry.getSourceType() == EventSourceType.URL_REQUEST) {
     84           var row = this.sourceIdToRowMap_[id];
     85           if (!row) {
     86             var newRow = new WaterfallRow(this, sourceEntry);
     87             this.sourceIdToRowMap_[id] = newRow;
     88           } else {
     89             row.onSourceUpdated();
     90           }
     91         }
     92       }
     93       this.scrollInfoTable_();
     94       this.positionBarTable_();
     95       this.updateTimeScale_(this.scaleFactor_);
     96     },
     97 
     98     onAllSourceEntriesDeleted: function() {
     99       this.initializeSourceList_();
    100     },
    101 
    102     onLoadLogFinish: function(data) {
    103       return true;
    104     },
    105 
    106     getScaleFactor: function() {
    107       return this.scaleFactor_;
    108     },
    109 
    110     setGeometry: function(left, top, width, height) {
    111       superClass.prototype.setGeometry.call(this, left, top, width, height);
    112       this.scrollInfoTable_();
    113     },
    114 
    115     show: function(isVisible) {
    116       superClass.prototype.show.call(this, isVisible);
    117       if (isVisible) {
    118         this.scrollInfoTable_();
    119       }
    120     },
    121 
    122     /**
    123      * Initializes the list of source entries.  If source entries are already
    124      * being displayed, removes them all in the process.
    125      */
    126     initializeSourceList_: function() {
    127       this.sourceIdToRowMap_ = {};
    128       $(WaterfallView.BAR_TBODY_ID).innerHTML = '';
    129       $(WaterfallView.INFO_TBODY_ID).innerHTML = '';
    130       this.scaleFactor_ = 0.1;
    131     },
    132 
    133     /**
    134      * Changes scroll position of the window such that horizontally, everything
    135      * within the specified range fits into the user's viewport.
    136      */
    137     adjustToWindow_: function(windowStart, windowEnd) {
    138       var waterfallLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
    139           $(WaterfallView.INFO_TABLE_ID).offsetLeft +
    140           $(WaterfallView.ID_HEADER_ID).offsetWidth;
    141       var maxWidth = $(WaterfallView.MAIN_BOX_ID).offsetWidth - waterfallLeft;
    142       var totalDuration = 0;
    143       if (windowEnd != -1) {
    144         totalDuration = windowEnd - windowStart;
    145       } else {
    146         for (var id in this.sourceIdToRowMap_) {
    147           var row = this.sourceIdToRowMap_[id];
    148           var rowDuration = row.getEndTicks();
    149           if (totalDuration < rowDuration && !row.hide) {
    150             totalDuration = rowDuration;
    151           }
    152         }
    153       }
    154       if (totalDuration <= 0) {
    155         return;
    156       }
    157       this.scaleAll_(maxWidth / totalDuration);
    158       $(WaterfallView.MAIN_BOX_ID).scrollLeft =
    159           windowStart * this.scaleFactor_;
    160     },
    161 
    162     /** Updates the time tick indicators. */
    163     updateTimeScale_: function(scaleFactor) {
    164       var timePerTick = 1;
    165       var minTickDistance = 20;
    166 
    167       $(WaterfallView.TIME_SCALE_HEADER_ID).innerHTML = '';
    168 
    169       // Holder provides environment to prevent wrapping.
    170       var timeTickRow = addNode($(WaterfallView.TIME_SCALE_HEADER_ID), 'div');
    171       timeTickRow.classList.add('waterfall-view-time-scale-row');
    172 
    173       var availableWidth = $(WaterfallView.BAR_TBODY_ID).clientWidth;
    174       var tickDistance = scaleFactor * timePerTick;
    175 
    176       while (tickDistance < minTickDistance) {
    177         timePerTick = timePerTick * 10;
    178         tickDistance = scaleFactor * timePerTick;
    179       }
    180 
    181       var tickCount = availableWidth / tickDistance;
    182       for (var i = 0; i < tickCount; ++i) {
    183         var timeCell = addNode(timeTickRow, 'div');
    184         setNodeWidth(timeCell, tickDistance);
    185         timeCell.classList.add('waterfall-view-time-scale');
    186         timeCell.title = i * timePerTick + ' to ' +
    187            (i + 1) * timePerTick + ' ms';
    188         // Red marker for every 5th bar.
    189         if (i % 5 == 0) {
    190           timeCell.classList.add('waterfall-view-time-scale-special');
    191         }
    192       }
    193     },
    194 
    195     /**
    196      * Scales all existing rows by scaleFactor.
    197      */
    198     scaleAll_: function(scaleFactor) {
    199       this.scaleFactor_ = scaleFactor;
    200       for (var id in this.sourceIdToRowMap_) {
    201         var row = this.sourceIdToRowMap_[id];
    202         row.updateRow();
    203       }
    204       this.updateTimeScale_(scaleFactor);
    205     },
    206 
    207     scrollToZoom_: function(event) {
    208       // To use scrolling to control zoom, hold down the alt key and scroll.
    209       if ('wheelDelta' in event && event.altKey) {
    210         event.preventDefault();
    211         var zoomFactor = Math.pow(MOUSE_WHEEL_ZOOM_RATE,
    212              event.wheelDeltaY / MOUSE_WHEEL_UNITS_PER_CLICK);
    213 
    214         var waterfallLeft = $(WaterfallView.ID_HEADER_ID).offsetWidth +
    215             $(WaterfallView.URL_HEADER_ID).offsetWidth;
    216         var oldCursorPosition = event.pageX +
    217             $(WaterfallView.MAIN_BOX_ID).scrollLeft;
    218         var oldCursorPositionInTable = oldCursorPosition - waterfallLeft;
    219 
    220         this.scaleAll_(this.scaleFactor_ * zoomFactor);
    221 
    222         // Shifts the view when scrolling. newScroll could be less than 0 or
    223         // more than the maximum scroll position, but both cases are handled
    224         // by the inbuilt scrollLeft implementation.
    225         var newScroll =
    226             oldCursorPositionInTable * zoomFactor - event.pageX + waterfallLeft;
    227         $(WaterfallView.MAIN_BOX_ID).scrollLeft = newScroll;
    228       }
    229     },
    230 
    231     /**
    232      * Positions the bar table such that it is in line with the right edge of
    233      * the info table.
    234      */
    235     positionBarTable_: function() {
    236       var offsetLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
    237           $(WaterfallView.INFO_TABLE_ID).offsetLeft;
    238       $(WaterfallView.BAR_TABLE_ID).style.left = offsetLeft + 'px';
    239     },
    240 
    241     /**
    242      * Moves the info table when the page is scrolled vertically, ensuring that
    243      * the correct information is displayed on the page, and that no elements
    244      * are blocked unnecessarily.
    245      */
    246     scrollInfoTable_: function(event) {
    247       $(WaterfallView.INFO_TABLE_ID).style.top =
    248           $(WaterfallView.MAIN_BOX_ID).offsetTop +
    249           $(WaterfallView.BAR_TABLE_ID).offsetTop -
    250           $(WaterfallView.MAIN_BOX_ID).scrollTop + 'px';
    251 
    252       if ($(WaterfallView.INFO_TABLE_ID).offsetHeight >
    253           $(WaterfallView.MAIN_BOX_ID).clientHeight) {
    254         var scroll = $(WaterfallView.MAIN_BOX_ID).scrollTop;
    255         var bottomClip =
    256             $(WaterfallView.MAIN_BOX_ID).clientHeight -
    257             $(WaterfallView.BAR_TABLE_ID).offsetTop +
    258             $(WaterfallView.MAIN_BOX_ID).scrollTop;
    259         // Clips the information table such that it does not cover the scroll
    260         // bars or the controls bar.
    261         $(WaterfallView.INFO_TABLE_ID).style.clip = 'rect(' + scroll +
    262             'px auto ' + bottomClip + 'px auto)';
    263       }
    264     },
    265 
    266     /** Parses user input, then calls adjustToWindow to shift that into view. */
    267     setStartEndTimes_: function() {
    268       var windowStart = parseInt($(WaterfallView.START_TIME_ID).value);
    269       var windowEnd = parseInt($(WaterfallView.END_TIME_ID).value);
    270       if ($(WaterfallView.END_TIME_ID).value == '') {
    271         windowEnd = -1;
    272       }
    273       if ($(WaterfallView.START_TIME_ID).value == '') {
    274         windowStart = 0;
    275       }
    276       this.adjustToWindow_(windowStart, windowEnd);
    277     },
    278 
    279 
    280   };
    281 
    282   return WaterfallView;
    283 })();
    284