Home | History | Annotate | Download | only in net_internals
      1 // Copyright (c) 2010 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 /**
      6  * Each row in the filtered items list is backed by a SourceEntry. This
      7  * instance contains all of the data pertaining to that row, and notifies
      8  * its parent view (the EventsView) whenever its data changes.
      9  *
     10  * @constructor
     11  */
     12 function SourceEntry(parentView, maxPreviousSourceId) {
     13   this.maxPreviousSourceId_ = maxPreviousSourceId;
     14   this.entries_ = [];
     15   this.parentView_ = parentView;
     16   this.isSelected_ = false;
     17   this.isMatchedByFilter_ = false;
     18   // If the first entry is a BEGIN_PHASE, set to true.
     19   // Set to false when an END_PHASE matching the first entry is encountered.
     20   this.isActive_ = false;
     21 }
     22 
     23 SourceEntry.prototype.isSelected = function() {
     24   return this.isSelected_;
     25 };
     26 
     27 SourceEntry.prototype.setSelectedStyles = function(isSelected) {
     28   changeClassName(this.row_, 'selected', isSelected);
     29   this.getSelectionCheckbox().checked = isSelected;
     30 };
     31 
     32 SourceEntry.prototype.setMouseoverStyle = function(isMouseOver) {
     33   changeClassName(this.row_, 'mouseover', isMouseOver);
     34 };
     35 
     36 SourceEntry.prototype.setIsMatchedByFilter = function(isMatchedByFilter) {
     37   if (this.isMatchedByFilter() == isMatchedByFilter)
     38     return;  // No change.
     39 
     40   this.isMatchedByFilter_ = isMatchedByFilter;
     41 
     42   this.setFilterStyles(isMatchedByFilter);
     43 
     44   if (isMatchedByFilter) {
     45     this.parentView_.incrementPostfilterCount(1);
     46   } else {
     47     this.parentView_.incrementPostfilterCount(-1);
     48     // If we are filtering an entry away, make sure it is no longer
     49     // part of the selection.
     50     this.setSelected(false);
     51   }
     52 };
     53 
     54 SourceEntry.prototype.isMatchedByFilter = function() {
     55   return this.isMatchedByFilter_;
     56 };
     57 
     58 SourceEntry.prototype.setFilterStyles = function(isMatchedByFilter) {
     59   // Hide rows which have been filtered away.
     60   if (isMatchedByFilter) {
     61     this.row_.style.display = '';
     62   } else {
     63     this.row_.style.display = 'none';
     64   }
     65 };
     66 
     67 SourceEntry.prototype.update = function(logEntry) {
     68   if (logEntry.phase == LogEventPhase.PHASE_BEGIN &&
     69       this.entries_.length == 0)
     70     this.isActive_ = true;
     71 
     72   // Only the last event should have the same type first event,
     73   if (this.isActive_ &&
     74       logEntry.phase == LogEventPhase.PHASE_END &&
     75       logEntry.type == this.entries_[0].type)
     76     this.isActive_ = false;
     77 
     78   var prevStartEntry = this.getStartEntry_();
     79   this.entries_.push(logEntry);
     80   var curStartEntry = this.getStartEntry_();
     81 
     82   // If we just got the first entry for this source.
     83   if (prevStartEntry != curStartEntry) {
     84     if (!prevStartEntry)
     85       this.createRow_();
     86     else
     87       this.updateDescription_();
     88   }
     89 
     90   // Update filters.
     91   var matchesFilter = this.matchesFilter(this.parentView_.currentFilter_);
     92   this.setIsMatchedByFilter(matchesFilter);
     93 };
     94 
     95 SourceEntry.prototype.onCheckboxToggled_ = function() {
     96   this.setSelected(this.getSelectionCheckbox().checked);
     97 };
     98 
     99 SourceEntry.prototype.matchesFilter = function(filter) {
    100   // Safety check.
    101   if (this.row_ == null)
    102     return false;
    103 
    104   if (filter.isActive && !this.isActive_)
    105     return false;
    106   if (filter.isInactive && this.isActive_)
    107     return false;
    108 
    109   // Check source type, if needed.
    110   if (filter.type) {
    111     var sourceType = this.getSourceTypeString().toLowerCase();
    112     if (filter.type.indexOf(sourceType) == -1)
    113       return false;
    114   }
    115 
    116   // Check source ID, if needed.
    117   if (filter.id) {
    118     if (filter.id.indexOf(this.getSourceId() + '') == -1)
    119       return false;
    120   }
    121 
    122   if (filter.text == '')
    123     return true;
    124 
    125   var filterText = filter.text;
    126   var entryText = PrintSourceEntriesAsText(this.entries_).toLowerCase();
    127 
    128   return entryText.indexOf(filterText) != -1;
    129 };
    130 
    131 SourceEntry.prototype.setSelected = function(isSelected) {
    132   if (isSelected == this.isSelected())
    133     return;
    134 
    135   this.isSelected_ = isSelected;
    136 
    137   this.setSelectedStyles(isSelected);
    138   this.parentView_.modifySelectionArray(this, isSelected);
    139   this.parentView_.onSelectionChanged();
    140 };
    141 
    142 SourceEntry.prototype.onClicked_ = function() {
    143   this.parentView_.clearSelection();
    144   this.setSelected(true);
    145 };
    146 
    147 SourceEntry.prototype.onMouseover_ = function() {
    148   this.setMouseoverStyle(true);
    149 };
    150 
    151 SourceEntry.prototype.onMouseout_ = function() {
    152   this.setMouseoverStyle(false);
    153 };
    154 
    155 SourceEntry.prototype.updateDescription_ = function() {
    156   this.descriptionCell_.innerHTML = '';
    157   addTextNode(this.descriptionCell_, this.getDescription());
    158 };
    159 
    160 SourceEntry.prototype.createRow_ = function() {
    161   // Create a row.
    162   var tr = addNode(this.parentView_.tableBody_, 'tr');
    163   tr._id = this.getSourceId();
    164   tr.style.display = 'none';
    165   this.row_ = tr;
    166 
    167   var selectionCol = addNode(tr, 'td');
    168   var checkbox = addNode(selectionCol, 'input');
    169   checkbox.type = 'checkbox';
    170 
    171   var idCell = addNode(tr, 'td');
    172   idCell.style.textAlign = 'right';
    173 
    174   var typeCell = addNode(tr, 'td');
    175   var descriptionCell = addNode(tr, 'td');
    176   this.descriptionCell_ = descriptionCell;
    177 
    178   // Connect listeners.
    179   checkbox.onchange = this.onCheckboxToggled_.bind(this);
    180 
    181   var onclick = this.onClicked_.bind(this);
    182   idCell.onclick = onclick;
    183   typeCell.onclick = onclick;
    184   descriptionCell.onclick = onclick;
    185 
    186   tr.onmouseover = this.onMouseover_.bind(this);
    187   tr.onmouseout = this.onMouseout_.bind(this);
    188 
    189   // Set the cell values to match this source's data.
    190   if (this.getSourceId() >= 0)
    191     addTextNode(idCell, this.getSourceId());
    192   else
    193     addTextNode(idCell, '-');
    194   var sourceTypeString = this.getSourceTypeString();
    195   addTextNode(typeCell, sourceTypeString);
    196   this.updateDescription_();
    197 
    198   // Add a CSS classname specific to this source type (so CSS can specify
    199   // different stylings for different types).
    200   changeClassName(this.row_, 'source_' + sourceTypeString, true);
    201 };
    202 
    203 /**
    204  * Returns a description for this source log stream, which will be displayed
    205  * in the list view. Most often this is a URL that identifies the request,
    206  * or a hostname for a connect job, etc...
    207  */
    208 SourceEntry.prototype.getDescription = function() {
    209   var e = this.getStartEntry_();
    210   if (!e)
    211     return '';
    212 
    213   if (e.source.type == LogSourceType.NONE) {
    214     // NONE is what we use for global events that aren't actually grouped
    215     // by a "source ID", so we will just stringize the event's type.
    216     return getKeyWithValue(LogEventType, e.type);
    217   }
    218 
    219   if (e.params == undefined)
    220     return '';
    221 
    222   var description = '';
    223   switch (e.source.type) {
    224     case LogSourceType.URL_REQUEST:
    225     case LogSourceType.SOCKET_STREAM:
    226     case LogSourceType.HTTP_STREAM_JOB:
    227       description = e.params.url;
    228       break;
    229     case LogSourceType.CONNECT_JOB:
    230       description = e.params.group_name;
    231       break;
    232     case LogSourceType.HOST_RESOLVER_IMPL_REQUEST:
    233     case LogSourceType.HOST_RESOLVER_IMPL_JOB:
    234       description = e.params.host;
    235       break;
    236     case LogSourceType.DISK_CACHE_ENTRY:
    237     case LogSourceType.MEMORY_CACHE_ENTRY:
    238       description = e.params.key;
    239       break;
    240     case LogSourceType.SPDY_SESSION:
    241       if (e.params.host)
    242         description = e.params.host + ' (' + e.params.proxy + ')';
    243       break;
    244     case LogSourceType.SOCKET:
    245       if (e.params.source_dependency != undefined) {
    246         var connectJobSourceEntry =
    247             this.parentView_.getSourceEntry(e.params.source_dependency.id);
    248         if (connectJobSourceEntry)
    249           description = connectJobSourceEntry.getDescription();
    250       }
    251       break;
    252   }
    253 
    254   if (description == undefined)
    255     return '';
    256   return description;
    257 };
    258 
    259 /**
    260  * Returns the starting entry for this source. Conceptually this is the
    261  * first entry that was logged to this source. However, we skip over the
    262  * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB /
    263  * TYPE_SOCKET_STREAM_CONNECT.
    264  */
    265 SourceEntry.prototype.getStartEntry_ = function() {
    266   if (this.entries_.length < 1)
    267     return undefined;
    268   if (this.entries_.length >= 2) {
    269     if (this.entries_[0].type == LogEventType.REQUEST_ALIVE ||
    270         this.entries_[0].type == LogEventType.SOCKET_POOL_CONNECT_JOB)
    271       return this.entries_[1];
    272   }
    273   return this.entries_[0];
    274 };
    275 
    276 SourceEntry.prototype.getLogEntries = function() {
    277   return this.entries_;
    278 };
    279 
    280 SourceEntry.prototype.getSourceTypeString = function() {
    281   return getKeyWithValue(LogSourceType, this.entries_[0].source.type);
    282 };
    283 
    284 SourceEntry.prototype.getSelectionCheckbox = function() {
    285   return this.row_.childNodes[0].firstChild;
    286 };
    287 
    288 SourceEntry.prototype.getSourceId = function() {
    289   return this.entries_[0].source.id;
    290 };
    291 
    292 /**
    293  * Returns the largest source ID seen before this object was received.
    294  * Used only for sorting SourceEntries without a source by source ID.
    295  */
    296 SourceEntry.prototype.getMaxPreviousEntrySourceId = function() {
    297   return this.maxPreviousSourceId_;
    298 };
    299 
    300 SourceEntry.prototype.isActive = function() {
    301   return this.isActive_;
    302 };
    303 
    304 /**
    305  * Returns time of last event if inactive.  Returns current time otherwise.
    306  */
    307 SourceEntry.prototype.getEndTime = function() {
    308   if (this.isActive_) {
    309     return (new Date()).getTime();
    310   }
    311   else {
    312     var endTicks = this.entries_[this.entries_.length - 1].time;
    313     return g_browser.convertTimeTicksToDate(endTicks).getTime();
    314   }
    315 };
    316 
    317 /**
    318  * Returns the time between the first and last events with a matching
    319  * source ID.  If source is still active, uses the current time for the
    320  * last event.
    321  */
    322 SourceEntry.prototype.getDuration = function() {
    323   var startTicks = this.entries_[0].time;
    324   var startTime = g_browser.convertTimeTicksToDate(startTicks).getTime();
    325   var endTime = this.getEndTime();
    326   return endTime - startTime;
    327 };
    328 
    329 /**
    330  * Returns source ID of the entry whose row is currently above this one's.
    331  * Returns null if no such node exists.
    332  */
    333 SourceEntry.prototype.getPreviousNodeSourceId = function() {
    334   if (!this.hasRow())
    335     return null;
    336   var prevNode = this.row_.previousSibling;
    337   if (prevNode == null)
    338     return null;
    339   return prevNode._id;
    340 };
    341 
    342 /**
    343  * Returns source ID of the entry whose row is currently below this one's.
    344  * Returns null if no such node exists.
    345  */
    346 SourceEntry.prototype.getNextNodeSourceId = function() {
    347   if (!this.hasRow())
    348     return null;
    349   var nextNode = this.row_.nextSibling;
    350   if (nextNode == null)
    351     return null;
    352   return nextNode._id;
    353 };
    354 
    355 SourceEntry.prototype.hasRow = function() {
    356   return this.row_ != null;
    357 };
    358 
    359 /**
    360  * Moves current object's row before |entry|'s row.
    361  */
    362 SourceEntry.prototype.moveBefore = function(entry) {
    363   if (this.hasRow() && entry.hasRow()) {
    364     this.row_.parentNode.insertBefore(this.row_, entry.row_);
    365   }
    366 };
    367 
    368 /**
    369  * Moves current object's row after |entry|'s row.
    370  */
    371 SourceEntry.prototype.moveAfter = function(entry) {
    372   if (this.hasRow() && entry.hasRow()) {
    373     this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling);
    374   }
    375 };
    376 
    377 SourceEntry.prototype.remove = function() {
    378   this.setSelected(false);
    379   this.setIsMatchedByFilter(false);
    380   this.row_.parentNode.removeChild(this.row_);
    381 };
    382 
    383