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 var WaterfallRow = (function() {
      6   'use strict';
      7 
      8   /**
      9    * A WaterfallRow represents the row corresponding to a single SourceEntry
     10    * displayed by the EventsWaterfallView.
     11    *
     12    * @constructor
     13    */
     14 
     15   // TODO(viona):
     16   // -Support nested events.
     17   // -Handle updating length when an event is stalled.
     18   function WaterfallRow(parentView, sourceEntry, eventList) {
     19     this.parentView_ = parentView;
     20     this.sourceEntry_ = sourceEntry;
     21 
     22     this.eventTypes_ = eventList;
     23     this.description_ = sourceEntry.getDescription();
     24 
     25     this.createRow_();
     26   }
     27 
     28   WaterfallRow.prototype = {
     29     onSourceUpdated: function() {
     30       this.updateRow();
     31     },
     32 
     33     updateRow: function() {
     34       var scale = this.parentView_.getScaleFactor();
     35       // In some cases, the REQUEST_ALIVE event has been received, while the
     36       // URL Request to start the job has not been received. In that case, the
     37       // description obtained is incorrect. The following fixes that.
     38       if (this.description_ == '') {
     39         this.urlCell_.innerHTML = '';
     40         this.description_ = this.sourceEntry_.getDescription();
     41         addTextNode(this.urlCell_, this.description_);
     42       }
     43 
     44       this.rowCell_.innerHTML = '';
     45 
     46       var matchingEventPairs = this.findLogEntryPairs_(this.eventTypes_);
     47 
     48       // Creates the spacing in the beginning to show start time.
     49       var startTime = this.parentView_.getStartTime();
     50       var sourceEntryStartTime = this.sourceEntry_.getStartTime();
     51       var delay = sourceEntryStartTime - startTime;
     52       var frontNode = addNode(this.rowCell_, 'div');
     53       setNodeWidth(frontNode, delay * scale);
     54 
     55       var barCell = addNode(this.rowCell_, 'div');
     56       barCell.classList.add('waterfall-view-bar');
     57 
     58       if (this.sourceEntry_.isError()) {
     59         barCell.classList.add('error');
     60       }
     61 
     62       var currentEnd = sourceEntryStartTime;
     63       for (var i = 0; i < matchingEventPairs.length; ++i) {
     64         var event = matchingEventPairs[i];
     65         var startTicks = event.startEntry.time;
     66         var endTicks = event.endEntry.time;
     67         event.eventType = event.startEntry.type;
     68         event.startTime = timeutil.convertTimeTicksToTime(startTicks);
     69         event.endTime = timeutil.convertTimeTicksToTime(endTicks);
     70         event.eventDuration = event.endTime - event.startTime;
     71 
     72         // Handles the spaces between events.
     73         if (currentEnd < event.startTime) {
     74           var eventDuration = event.startTime - currentEnd;
     75           var padNode = this.createNode_(
     76               barCell, eventDuration, this.type_, 'source');
     77         }
     78 
     79         // Creates event bars.
     80         var eventType = eventTypeToCssClass_(EventTypeNames[event.eventType]);
     81         var eventNode = this.createNode_(
     82             barCell, event.eventDuration, eventType, event);
     83         currentEnd = event.startTime + event.eventDuration;
     84       }
     85 
     86       // Creates a bar for the part after the last event.
     87       if (this.getEndTime() > currentEnd) {
     88         var endDuration = (this.getEndTime() - currentEnd);
     89         var endNode = this.createNode_(
     90             barCell, endDuration, this.type_, 'source');
     91       }
     92     },
     93 
     94     getStartTime: function() {
     95       return this.sourceEntry_.getStartTime();
     96     },
     97 
     98     getEndTime: function() {
     99       return this.sourceEntry_.getEndTime();
    100     },
    101 
    102     clearPopup_: function(parentNode) {
    103       parentNode.innerHTML = '';
    104     },
    105 
    106     // TODO(viona): Create popup at mouse location.
    107     createPopup_: function(parentNode, event, eventType, duration, mouse) {
    108       var tableStart = this.parentView_.getStartTime();
    109 
    110       var newPopup = addNode(parentNode, 'div');
    111       newPopup.classList.add('waterfall-view-popup');
    112 
    113       var popupList = addNode(newPopup, 'ul');
    114       popupList.classList.add('waterfall-view-popup-list');
    115 
    116       this.createPopupItem_(
    117           popupList, 'Event Type', eventType);
    118       this.createPopupItem_(
    119           popupList, 'Event Duration', duration.toFixed(0) + 'ms');
    120 
    121       if (event != 'source') {
    122         this.createPopupItem_(
    123             popupList, 'Event Start Time', event.startTime - tableStart + 'ms');
    124         this.createPopupItem_(
    125             popupList, 'Event End Time', event.endTime - tableStart + 'ms');
    126       }
    127 
    128       this.createPopupItem_(
    129           popupList, 'Source Start Time',
    130           this.getStartTime() - tableStart + 'ms');
    131       this.createPopupItem_(
    132           popupList, 'Source End Time', this.getEndTime() - tableStart + 'ms');
    133       this.createPopupItem_(
    134           popupList, 'Source ID', this.sourceEntry_.getSourceId());
    135       var urlListItem = this.createPopupItem_(
    136           popupList, 'Source Description: ', this.description_);
    137 
    138       var viewWidth = $(WaterfallView.MAIN_BOX_ID).offsetWidth;
    139       // Controls the overflow of extremely long URLs by cropping to window.
    140       if (urlListItem.offsetWidth > viewWidth) {
    141         urlListItem.style.width = viewWidth + 'px';
    142       }
    143       urlListItem.classList.add('waterfall-view-popup-list-url-item');
    144 
    145       this.createPopupItem_(
    146           popupList, 'Has Error', this.sourceEntry_.isError());
    147 
    148       // Fixes cases where the popup appears 'off-screen'.
    149       var popupLeft = mouse.pageX + $(WaterfallView.MAIN_BOX_ID).scrollLeft;
    150       var popupRight = popupLeft + newPopup.offsetWidth;
    151       var viewRight = viewWidth + $(WaterfallView.MAIN_BOX_ID).scrollLeft;
    152 
    153       if (viewRight - popupRight < 0) {
    154         popupLeft = viewRight - newPopup.offsetWidth;
    155       }
    156       newPopup.style.left = popupLeft + 'px';
    157 
    158       var popupTop = mouse.pageY + $(WaterfallView.MAIN_BOX_ID).scrollTop;
    159       var popupBottom = popupTop + newPopup.offsetHeight;
    160       var viewBottom = $(WaterfallView.MAIN_BOX_ID).offsetHeight +
    161           $(WaterfallView.MAIN_BOX_ID).scrollTop;
    162 
    163       if (viewBottom - popupBottom < 0) {
    164         popupTop = viewBottom - newPopup.offsetHeight;
    165       }
    166       newPopup.style.top = popupTop + 'px';
    167     },
    168 
    169     createPopupItem_: function(parentPopup, key, popupInformation) {
    170       var popupItem = addNode(parentPopup, 'li');
    171       addTextNode(popupItem, key + ': ' + popupInformation);
    172       return popupItem;
    173     },
    174 
    175     createRow_: function() {
    176       // Create a row.
    177       var tr = addNode($(WaterfallView.TBODY_ID), 'tr');
    178       tr._id = this.sourceEntry_.getSourceId();
    179       this.tableRow = tr;
    180 
    181       var idCell = addNode(tr, 'td');
    182       addTextNode(idCell, this.sourceEntry_.getSourceId());
    183 
    184       var urlCell = addNode(tr, 'td');
    185       urlCell.classList.add('waterfall-view-url-cell');
    186       addTextNode(urlCell, this.description_);
    187       this.urlCell_ = urlCell;
    188 
    189       // Creates the color bar.
    190 
    191       var rowCell = addNode(tr, 'td');
    192       rowCell.classList.add('waterfall-view-row');
    193       this.rowCell_ = rowCell;
    194 
    195       var sourceTypeString = this.sourceEntry_.getSourceTypeString();
    196       this.type_ = eventTypeToCssClass_(sourceTypeString);
    197 
    198       this.updateRow();
    199     },
    200 
    201     // Generates nodes.
    202     createNode_: function(parentNode, duration, eventType, event) {
    203       var scale = this.parentView_.getScaleFactor();
    204       var newNode = addNode(parentNode, 'div');
    205       setNodeWidth(newNode, duration * scale);
    206       newNode.classList.add(eventType);
    207       newNode.addEventListener(
    208           'mouseover',
    209           this.createPopup_.bind(this, newNode, event, eventType, duration),
    210           true);
    211       newNode.addEventListener(
    212           'mouseout', this.clearPopup_.bind(this, newNode), true);
    213       return newNode;
    214     },
    215 
    216     /**
    217      * Finds pairs of starting and ending events of all types that are in
    218      * typeList. Currently does not handle nested events. Can consider adding
    219      * starting events to a stack and popping off as their close events are
    220      * found. Returns an array of objects containing start/end entry pairs,
    221      * in the format {startEntry, endEntry}.
    222      */
    223     findLogEntryPairs_: function() {
    224       var typeList = this.eventTypes_;
    225       var matchingEventPairs = [];
    226       var startEntries = {};
    227       var entries = this.sourceEntry_.getLogEntries();
    228       for (var i = 0; i < entries.length; ++i) {
    229         var currentEntry = entries[i];
    230         var type = currentEntry.type;
    231         if (typeList.indexOf(type) < 0) {
    232           continue;
    233         }
    234         if (currentEntry.phase == EventPhase.PHASE_BEGIN) {
    235           startEntries[type] = currentEntry;
    236         }
    237         if (startEntries[type] && currentEntry.phase == EventPhase.PHASE_END) {
    238           var event = {
    239             startEntry: startEntries[type],
    240             endEntry: currentEntry,
    241           };
    242           matchingEventPairs.push(event);
    243         }
    244       }
    245       return matchingEventPairs;
    246     },
    247   };
    248 
    249   function eventTypeToCssClass_(rawEventType) {
    250     return rawEventType.toLowerCase().replace(/_/g, '-');
    251   }
    252 
    253   return WaterfallRow;
    254 })();
    255