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       if (this.startTime_ == null && sourceEntries.length > 0) {
     81         var logEntries = sourceEntries[0].getLogEntries();
     82         this.startTime_ = timeutil.convertTimeTicksToTime(logEntries[0].time);
     83         // Initial scale factor.
     84         this.scaleFactor_ = 0.1;
     85       }
     86       for (var i = 0; i < sourceEntries.length; ++i) {
     87         var sourceEntry = sourceEntries[i];
     88         var id = sourceEntry.getSourceId();
     89         if (sourceEntry.getSourceType() == EventSourceType.URL_REQUEST) {
     90           var row = this.sourceIdToRowMap_[id];
     91           if (!row) {
     92             var newRow = new WaterfallRow(this, sourceEntry);
     93             this.sourceIdToRowMap_[id] = newRow;
     94           } else {
     95             row.onSourceUpdated();
     96           }
     97         }
     98       }
     99       this.scrollInfoTable_();
    100       this.positionBarTable_();
    101       this.updateTimeScale_(this.scaleFactor_);
    102     },
    103 
    104     onAllSourceEntriesDeleted: function() {
    105       this.initializeSourceList_();
    106     },
    107 
    108     onLoadLogFinish: function(data) {
    109       return true;
    110     },
    111 
    112     getScaleFactor: function() {
    113       return this.scaleFactor_;
    114     },
    115 
    116     getStartTime: function() {
    117       return this.startTime_;
    118     },
    119 
    120     setGeometry: function(left, top, width, height) {
    121       superClass.prototype.setGeometry.call(this, left, top, width, height);
    122       this.scrollInfoTable_();
    123     },
    124 
    125     show: function(isVisible) {
    126       superClass.prototype.show.call(this, isVisible);
    127       if (isVisible) {
    128         this.scrollInfoTable_();
    129       }
    130     },
    131 
    132     /**
    133      * Initializes the list of source entries.  If source entries are already
    134      * being displayed, removes them all in the process.
    135      */
    136     initializeSourceList_: function() {
    137       this.sourceIdToRowMap_ = {};
    138       $(WaterfallView.BAR_TBODY_ID).innerHTML = '';
    139       $(WaterfallView.INFO_TBODY_ID).innerHTML = '';
    140       this.startTime_ = null;
    141       this.scaleFactor_ = null;
    142     },
    143 
    144     /**
    145      * Changes scroll position of the window such that horizontally, everything
    146      * within the specified range fits into the user's viewport.
    147      */
    148     adjustToWindow_: function(windowStart, windowEnd) {
    149       var waterfallLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
    150           $(WaterfallView.INFO_TABLE_ID).offsetLeft +
    151           $(WaterfallView.ID_HEADER_ID).offsetWidth;
    152       var maxWidth = $(WaterfallView.MAIN_BOX_ID).offsetWidth - waterfallLeft;
    153       var totalDuration = 0;
    154       if (windowEnd != -1) {
    155         totalDuration = windowEnd - windowStart;
    156       } else {
    157         for (var id in this.sourceIdToRowMap_) {
    158           var row = this.sourceIdToRowMap_[id];
    159           var rowDuration = row.getEndTime() - this.startTime_;
    160           if (totalDuration < rowDuration && !row.hide) {
    161             totalDuration = rowDuration;
    162           }
    163         }
    164       }
    165       if (totalDuration <= 0) {
    166         return;
    167       }
    168       this.scaleAll_(maxWidth / totalDuration);
    169       $(WaterfallView.MAIN_BOX_ID).scrollLeft =
    170           windowStart * this.scaleFactor_;
    171     },
    172 
    173     /** Updates the time tick indicators. */
    174     updateTimeScale_: function(scaleFactor) {
    175       var timePerTick = 1;
    176       var minTickDistance = 20;
    177 
    178       $(WaterfallView.TIME_SCALE_HEADER_ID).innerHTML = '';
    179 
    180       // Holder provides environment to prevent wrapping.
    181       var timeTickRow = addNode($(WaterfallView.TIME_SCALE_HEADER_ID), 'div');
    182       timeTickRow.classList.add('waterfall-view-time-scale-row');
    183 
    184       var availableWidth = $(WaterfallView.BAR_TBODY_ID).clientWidth;
    185       var tickDistance = scaleFactor * timePerTick;
    186 
    187       while (tickDistance < minTickDistance) {
    188         timePerTick = timePerTick * 10;
    189         tickDistance = scaleFactor * timePerTick;
    190       }
    191 
    192       var tickCount = availableWidth / tickDistance;
    193       for (var i = 0; i < tickCount; ++i) {
    194         var timeCell = addNode(timeTickRow, 'div');
    195         setNodeWidth(timeCell, tickDistance);
    196         timeCell.classList.add('waterfall-view-time-scale');
    197         timeCell.title = i * timePerTick + ' to ' +
    198            (i + 1) * timePerTick + ' ms';
    199         // Red marker for every 5th bar.
    200         if (i % 5 == 0) {
    201           timeCell.classList.add('waterfall-view-time-scale-special');
    202         }
    203       }
    204     },
    205 
    206     /**
    207      * Scales all existing rows by scaleFactor.
    208      */
    209     scaleAll_: function(scaleFactor) {
    210       this.scaleFactor_ = scaleFactor;
    211       for (var id in this.sourceIdToRowMap_) {
    212         var row = this.sourceIdToRowMap_[id];
    213         row.updateRow();
    214       }
    215       this.updateTimeScale_(scaleFactor);
    216     },
    217 
    218     scrollToZoom_: function(event) {
    219       // To use scrolling to control zoom, hold down the alt key and scroll.
    220       if ('wheelDelta' in event && event.altKey) {
    221         event.preventDefault();
    222         var zoomFactor = Math.pow(MOUSE_WHEEL_ZOOM_RATE,
    223              event.wheelDeltaY / MOUSE_WHEEL_UNITS_PER_CLICK);
    224 
    225         var waterfallLeft = $(WaterfallView.ID_HEADER_ID).offsetWidth +
    226             $(WaterfallView.URL_HEADER_ID).offsetWidth;
    227         var oldCursorPosition = event.pageX +
    228             $(WaterfallView.MAIN_BOX_ID).scrollLeft;
    229         var oldCursorPositionInTable = oldCursorPosition - waterfallLeft;
    230 
    231         this.scaleAll_(this.scaleFactor_ * zoomFactor);
    232 
    233         // Shifts the view when scrolling. newScroll could be less than 0 or
    234         // more than the maximum scroll position, but both cases are handled
    235         // by the inbuilt scrollLeft implementation.
    236         var newScroll =
    237             oldCursorPositionInTable * zoomFactor - event.pageX + waterfallLeft;
    238         $(WaterfallView.MAIN_BOX_ID).scrollLeft = newScroll;
    239       }
    240     },
    241 
    242     /**
    243      * Positions the bar table such that it is in line with the right edge of
    244      * the info table.
    245      */
    246     positionBarTable_: function() {
    247       var offsetLeft = $(WaterfallView.INFO_TABLE_ID).offsetWidth +
    248           $(WaterfallView.INFO_TABLE_ID).offsetLeft;
    249       $(WaterfallView.BAR_TABLE_ID).style.left = offsetLeft + 'px';
    250     },
    251 
    252     /**
    253      * Moves the info table when the page is scrolled vertically, ensuring that
    254      * the correct information is displayed on the page, and that no elements
    255      * are blocked unnecessarily.
    256      */
    257     scrollInfoTable_: function(event) {
    258       $(WaterfallView.INFO_TABLE_ID).style.top =
    259           $(WaterfallView.MAIN_BOX_ID).offsetTop +
    260           $(WaterfallView.BAR_TABLE_ID).offsetTop -
    261           $(WaterfallView.MAIN_BOX_ID).scrollTop + 'px';
    262 
    263       if ($(WaterfallView.INFO_TABLE_ID).offsetHeight >
    264           $(WaterfallView.MAIN_BOX_ID).clientHeight) {
    265         var scroll = $(WaterfallView.MAIN_BOX_ID).scrollTop;
    266         var bottomClip =
    267             $(WaterfallView.MAIN_BOX_ID).clientHeight -
    268             $(WaterfallView.BAR_TABLE_ID).offsetTop +
    269             $(WaterfallView.MAIN_BOX_ID).scrollTop;
    270         // Clips the information table such that it does not cover the scroll
    271         // bars or the controls bar.
    272         $(WaterfallView.INFO_TABLE_ID).style.clip = 'rect(' + scroll +
    273             'px auto ' + bottomClip + 'px auto)';
    274       }
    275     },
    276 
    277     /** Parses user input, then calls adjustToWindow to shift that into view. */
    278     setStartEndTimes_: function() {
    279       var windowStart = parseInt($(WaterfallView.START_TIME_ID).value);
    280       var windowEnd = parseInt($(WaterfallView.END_TIME_ID).value);
    281       if ($(WaterfallView.END_TIME_ID).value == '') {
    282         windowEnd = -1;
    283       }
    284       if ($(WaterfallView.START_TIME_ID).value == '') {
    285         windowStart = 0;
    286       }
    287       this.adjustToWindow_(windowStart, windowEnd);
    288     },
    289 
    290 
    291   };
    292 
    293   return WaterfallView;
    294 })();
    295