Javascript  |  383行  |  10.68 KB

// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * Each row in the filtered items list is backed by a SourceEntry. This
 * instance contains all of the data pertaining to that row, and notifies
 * its parent view (the EventsView) whenever its data changes.
 *
 * @constructor
 */
function SourceEntry(parentView, maxPreviousSourceId) {
  this.maxPreviousSourceId_ = maxPreviousSourceId;
  this.entries_ = [];
  this.parentView_ = parentView;
  this.isSelected_ = false;
  this.isMatchedByFilter_ = false;
  // If the first entry is a BEGIN_PHASE, set to true.
  // Set to false when an END_PHASE matching the first entry is encountered.
  this.isActive_ = false;
}

SourceEntry.prototype.isSelected = function() {
  return this.isSelected_;
};

SourceEntry.prototype.setSelectedStyles = function(isSelected) {
  changeClassName(this.row_, 'selected', isSelected);
  this.getSelectionCheckbox().checked = isSelected;
};

SourceEntry.prototype.setMouseoverStyle = function(isMouseOver) {
  changeClassName(this.row_, 'mouseover', isMouseOver);
};

SourceEntry.prototype.setIsMatchedByFilter = function(isMatchedByFilter) {
  if (this.isMatchedByFilter() == isMatchedByFilter)
    return;  // No change.

  this.isMatchedByFilter_ = isMatchedByFilter;

  this.setFilterStyles(isMatchedByFilter);

  if (isMatchedByFilter) {
    this.parentView_.incrementPostfilterCount(1);
  } else {
    this.parentView_.incrementPostfilterCount(-1);
    // If we are filtering an entry away, make sure it is no longer
    // part of the selection.
    this.setSelected(false);
  }
};

SourceEntry.prototype.isMatchedByFilter = function() {
  return this.isMatchedByFilter_;
};

SourceEntry.prototype.setFilterStyles = function(isMatchedByFilter) {
  // Hide rows which have been filtered away.
  if (isMatchedByFilter) {
    this.row_.style.display = '';
  } else {
    this.row_.style.display = 'none';
  }
};

SourceEntry.prototype.update = function(logEntry) {
  if (logEntry.phase == LogEventPhase.PHASE_BEGIN &&
      this.entries_.length == 0)
    this.isActive_ = true;

  // Only the last event should have the same type first event,
  if (this.isActive_ &&
      logEntry.phase == LogEventPhase.PHASE_END &&
      logEntry.type == this.entries_[0].type)
    this.isActive_ = false;

  var prevStartEntry = this.getStartEntry_();
  this.entries_.push(logEntry);
  var curStartEntry = this.getStartEntry_();

  // If we just got the first entry for this source.
  if (prevStartEntry != curStartEntry) {
    if (!prevStartEntry)
      this.createRow_();
    else
      this.updateDescription_();
  }

  // Update filters.
  var matchesFilter = this.matchesFilter(this.parentView_.currentFilter_);
  this.setIsMatchedByFilter(matchesFilter);
};

SourceEntry.prototype.onCheckboxToggled_ = function() {
  this.setSelected(this.getSelectionCheckbox().checked);
};

SourceEntry.prototype.matchesFilter = function(filter) {
  // Safety check.
  if (this.row_ == null)
    return false;

  if (filter.isActive && !this.isActive_)
    return false;
  if (filter.isInactive && this.isActive_)
    return false;

  // Check source type, if needed.
  if (filter.type) {
    var sourceType = this.getSourceTypeString().toLowerCase();
    if (filter.type.indexOf(sourceType) == -1)
      return false;
  }

  // Check source ID, if needed.
  if (filter.id) {
    if (filter.id.indexOf(this.getSourceId() + '') == -1)
      return false;
  }

  if (filter.text == '')
    return true;

  var filterText = filter.text;
  var entryText = PrintSourceEntriesAsText(this.entries_).toLowerCase();

  return entryText.indexOf(filterText) != -1;
};

SourceEntry.prototype.setSelected = function(isSelected) {
  if (isSelected == this.isSelected())
    return;

  this.isSelected_ = isSelected;

  this.setSelectedStyles(isSelected);
  this.parentView_.modifySelectionArray(this, isSelected);
  this.parentView_.onSelectionChanged();
};

SourceEntry.prototype.onClicked_ = function() {
  this.parentView_.clearSelection();
  this.setSelected(true);
};

SourceEntry.prototype.onMouseover_ = function() {
  this.setMouseoverStyle(true);
};

SourceEntry.prototype.onMouseout_ = function() {
  this.setMouseoverStyle(false);
};

SourceEntry.prototype.updateDescription_ = function() {
  this.descriptionCell_.innerHTML = '';
  addTextNode(this.descriptionCell_, this.getDescription());
};

SourceEntry.prototype.createRow_ = function() {
  // Create a row.
  var tr = addNode(this.parentView_.tableBody_, 'tr');
  tr._id = this.getSourceId();
  tr.style.display = 'none';
  this.row_ = tr;

  var selectionCol = addNode(tr, 'td');
  var checkbox = addNode(selectionCol, 'input');
  checkbox.type = 'checkbox';

  var idCell = addNode(tr, 'td');
  idCell.style.textAlign = 'right';

  var typeCell = addNode(tr, 'td');
  var descriptionCell = addNode(tr, 'td');
  this.descriptionCell_ = descriptionCell;

  // Connect listeners.
  checkbox.onchange = this.onCheckboxToggled_.bind(this);

  var onclick = this.onClicked_.bind(this);
  idCell.onclick = onclick;
  typeCell.onclick = onclick;
  descriptionCell.onclick = onclick;

  tr.onmouseover = this.onMouseover_.bind(this);
  tr.onmouseout = this.onMouseout_.bind(this);

  // Set the cell values to match this source's data.
  if (this.getSourceId() >= 0)
    addTextNode(idCell, this.getSourceId());
  else
    addTextNode(idCell, '-');
  var sourceTypeString = this.getSourceTypeString();
  addTextNode(typeCell, sourceTypeString);
  this.updateDescription_();

  // Add a CSS classname specific to this source type (so CSS can specify
  // different stylings for different types).
  changeClassName(this.row_, 'source_' + sourceTypeString, true);
};

/**
 * Returns a description for this source log stream, which will be displayed
 * in the list view. Most often this is a URL that identifies the request,
 * or a hostname for a connect job, etc...
 */
SourceEntry.prototype.getDescription = function() {
  var e = this.getStartEntry_();
  if (!e)
    return '';

  if (e.source.type == LogSourceType.NONE) {
    // NONE is what we use for global events that aren't actually grouped
    // by a "source ID", so we will just stringize the event's type.
    return getKeyWithValue(LogEventType, e.type);
  }

  if (e.params == undefined)
    return '';

  var description = '';
  switch (e.source.type) {
    case LogSourceType.URL_REQUEST:
    case LogSourceType.SOCKET_STREAM:
    case LogSourceType.HTTP_STREAM_JOB:
      description = e.params.url;
      break;
    case LogSourceType.CONNECT_JOB:
      description = e.params.group_name;
      break;
    case LogSourceType.HOST_RESOLVER_IMPL_REQUEST:
    case LogSourceType.HOST_RESOLVER_IMPL_JOB:
      description = e.params.host;
      break;
    case LogSourceType.DISK_CACHE_ENTRY:
    case LogSourceType.MEMORY_CACHE_ENTRY:
      description = e.params.key;
      break;
    case LogSourceType.SPDY_SESSION:
      if (e.params.host)
        description = e.params.host + ' (' + e.params.proxy + ')';
      break;
    case LogSourceType.SOCKET:
      if (e.params.source_dependency != undefined) {
        var connectJobSourceEntry =
            this.parentView_.getSourceEntry(e.params.source_dependency.id);
        if (connectJobSourceEntry)
          description = connectJobSourceEntry.getDescription();
      }
      break;
  }

  if (description == undefined)
    return '';
  return description;
};

/**
 * Returns the starting entry for this source. Conceptually this is the
 * first entry that was logged to this source. However, we skip over the
 * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB /
 * TYPE_SOCKET_STREAM_CONNECT.
 */
SourceEntry.prototype.getStartEntry_ = function() {
  if (this.entries_.length < 1)
    return undefined;
  if (this.entries_.length >= 2) {
    if (this.entries_[0].type == LogEventType.REQUEST_ALIVE ||
        this.entries_[0].type == LogEventType.SOCKET_POOL_CONNECT_JOB)
      return this.entries_[1];
  }
  return this.entries_[0];
};

SourceEntry.prototype.getLogEntries = function() {
  return this.entries_;
};

SourceEntry.prototype.getSourceTypeString = function() {
  return getKeyWithValue(LogSourceType, this.entries_[0].source.type);
};

SourceEntry.prototype.getSelectionCheckbox = function() {
  return this.row_.childNodes[0].firstChild;
};

SourceEntry.prototype.getSourceId = function() {
  return this.entries_[0].source.id;
};

/**
 * Returns the largest source ID seen before this object was received.
 * Used only for sorting SourceEntries without a source by source ID.
 */
SourceEntry.prototype.getMaxPreviousEntrySourceId = function() {
  return this.maxPreviousSourceId_;
};

SourceEntry.prototype.isActive = function() {
  return this.isActive_;
};

/**
 * Returns time of last event if inactive.  Returns current time otherwise.
 */
SourceEntry.prototype.getEndTime = function() {
  if (this.isActive_) {
    return (new Date()).getTime();
  }
  else {
    var endTicks = this.entries_[this.entries_.length - 1].time;
    return g_browser.convertTimeTicksToDate(endTicks).getTime();
  }
};

/**
 * Returns the time between the first and last events with a matching
 * source ID.  If source is still active, uses the current time for the
 * last event.
 */
SourceEntry.prototype.getDuration = function() {
  var startTicks = this.entries_[0].time;
  var startTime = g_browser.convertTimeTicksToDate(startTicks).getTime();
  var endTime = this.getEndTime();
  return endTime - startTime;
};

/**
 * Returns source ID of the entry whose row is currently above this one's.
 * Returns null if no such node exists.
 */
SourceEntry.prototype.getPreviousNodeSourceId = function() {
  if (!this.hasRow())
    return null;
  var prevNode = this.row_.previousSibling;
  if (prevNode == null)
    return null;
  return prevNode._id;
};

/**
 * Returns source ID of the entry whose row is currently below this one's.
 * Returns null if no such node exists.
 */
SourceEntry.prototype.getNextNodeSourceId = function() {
  if (!this.hasRow())
    return null;
  var nextNode = this.row_.nextSibling;
  if (nextNode == null)
    return null;
  return nextNode._id;
};

SourceEntry.prototype.hasRow = function() {
  return this.row_ != null;
};

/**
 * Moves current object's row before |entry|'s row.
 */
SourceEntry.prototype.moveBefore = function(entry) {
  if (this.hasRow() && entry.hasRow()) {
    this.row_.parentNode.insertBefore(this.row_, entry.row_);
  }
};

/**
 * Moves current object's row after |entry|'s row.
 */
SourceEntry.prototype.moveAfter = function(entry) {
  if (this.hasRow() && entry.hasRow()) {
    this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling);
  }
};

SourceEntry.prototype.remove = function() {
  this.setSelected(false);
  this.setIsMatchedByFilter(false);
  this.row_.parentNode.removeChild(this.row_);
};