| Index: netlog_viewer/source_entry.js | 
| diff --git a/netlog_viewer/source_entry.js b/netlog_viewer/source_entry.js | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..5c1389eb5c785f89102a92cc4da3168fe1332758 | 
| --- /dev/null | 
| +++ b/netlog_viewer/source_entry.js | 
| @@ -0,0 +1,350 @@ | 
| +// Copyright (c) 2012 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. | 
| + | 
| +var SourceEntry = (function() { | 
| +  'use strict'; | 
| + | 
| +  /** | 
| +   * A SourceEntry gathers all log entries with the same source. | 
| +   * | 
| +   * @constructor | 
| +   */ | 
| +  function SourceEntry(logEntry, maxPreviousSourceId) { | 
| +    this.maxPreviousSourceId_ = maxPreviousSourceId; | 
| +    this.entries_ = []; | 
| +    this.description_ = ''; | 
| + | 
| +    // Set to true on most net errors. | 
| +    this.isError_ = false; | 
| + | 
| +    // If the first entry is a BEGIN_PHASE, set to false. | 
| +    // Set to true when an END_PHASE matching the first entry is encountered. | 
| +    this.isInactive_ = true; | 
| + | 
| +    if (logEntry.phase == EventPhase.PHASE_BEGIN) | 
| +      this.isInactive_ = false; | 
| + | 
| +    this.update(logEntry); | 
| +  } | 
| + | 
| +  SourceEntry.prototype = { | 
| +    update: function(logEntry) { | 
| +      // Only the last event should have the same type first event, | 
| +      if (!this.isInactive_ && | 
| +          logEntry.phase == EventPhase.PHASE_END && | 
| +          logEntry.type == this.entries_[0].type) { | 
| +        this.isInactive_ = true; | 
| +      } | 
| + | 
| +      // If we have a net error code, update |this.isError_| if appropriate. | 
| +      if (logEntry.params) { | 
| +        var netErrorCode = logEntry.params.net_error; | 
| +        // Skip both cases where netErrorCode is undefined, and cases where it | 
| +        // is 0, indicating no actual error occurred. | 
| +        if (netErrorCode) { | 
| +          // Ignore error code caused by not finding an entry in the cache. | 
| +          if (logEntry.type != EventType.HTTP_CACHE_OPEN_ENTRY || | 
| +              netErrorCode != NetError.ERR_FAILED) { | 
| +            this.isError_ = true; | 
| +          } | 
| +        } | 
| +      } | 
| + | 
| +      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) | 
| +        this.updateDescription_(); | 
| +    }, | 
| + | 
| +    updateDescription_: function() { | 
| +      var e = this.getStartEntry_(); | 
| +      this.description_ = ''; | 
| +      if (!e) | 
| +        return; | 
| + | 
| +      if (e.source.type == EventSourceType.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. | 
| +        this.description_ = EventTypeNames[e.type]; | 
| +        return; | 
| +      } | 
| + | 
| +      if (e.params == undefined) { | 
| +        return; | 
| +      } | 
| + | 
| +      switch (e.source.type) { | 
| +        case EventSourceType.URL_REQUEST: | 
| +        // TODO(ricea): Remove SOCKET_STREAM after M41 is released. | 
| +        case EventSourceType.SOCKET_STREAM: | 
| +        case EventSourceType.HTTP_STREAM_JOB: | 
| +          this.description_ = e.params.url; | 
| +          break; | 
| +        case EventSourceType.CONNECT_JOB: | 
| +          this.description_ = e.params.group_name; | 
| +          break; | 
| +        case EventSourceType.HOST_RESOLVER_IMPL_JOB: | 
| +        case EventSourceType.HOST_RESOLVER_IMPL_PROC_TASK: | 
| +          this.description_ = e.params.host; | 
| +          break; | 
| +        case EventSourceType.DISK_CACHE_ENTRY: | 
| +        case EventSourceType.MEMORY_CACHE_ENTRY: | 
| +          this.description_ = e.params.key; | 
| +          break; | 
| +        case EventSourceType.QUIC_SESSION: | 
| +          if (e.params.host != undefined) | 
| +            this.description_ = e.params.host; | 
| +          break; | 
| +        case EventSourceType.HTTP2_SESSION: | 
| +          if (e.params.host) | 
| +            this.description_ = e.params.host + ' (' + e.params.proxy + ')'; | 
| +          break; | 
| +        case EventSourceType.HTTP_PIPELINED_CONNECTION: | 
| +          if (e.params.host_and_port) | 
| +            this.description_ = e.params.host_and_port; | 
| +          break; | 
| +        case EventSourceType.SOCKET: | 
| +        case EventSourceType.PROXY_CLIENT_SOCKET: | 
| +          // Use description of parent source, if any. | 
| +          if (e.params.source_dependency != undefined) { | 
| +            var parentId = e.params.source_dependency.id; | 
| +            this.description_ = | 
| +                SourceTracker.getInstance().getDescription(parentId); | 
| +          } | 
| +          break; | 
| +        case EventSourceType.UDP_SOCKET: | 
| +          if (e.params.address != undefined) { | 
| +            this.description_ = e.params.address; | 
| +            // If the parent of |this| is a HOST_RESOLVER_IMPL_JOB, use | 
| +            // '<DNS Server IP> [<host we're resolving>]'. | 
| +            if (this.entries_[0].type == EventType.SOCKET_ALIVE && | 
| +                this.entries_[0].params && | 
| +                this.entries_[0].params.source_dependency != undefined) { | 
| +              var parentId = this.entries_[0].params.source_dependency.id; | 
| +              var parent = SourceTracker.getInstance().getSourceEntry(parentId); | 
| +              if (parent && | 
| +                  parent.getSourceType() == | 
| +                      EventSourceType.HOST_RESOLVER_IMPL_JOB && | 
| +                  parent.getDescription().length > 0) { | 
| +                this.description_ += ' [' + parent.getDescription() + ']'; | 
| +              } | 
| +            } | 
| +          } | 
| +          break; | 
| +        case EventSourceType.ASYNC_HOST_RESOLVER_REQUEST: | 
| +        case EventSourceType.DNS_TRANSACTION: | 
| +          this.description_ = e.params.hostname; | 
| +          break; | 
| +        case EventSourceType.DOWNLOAD: | 
| +          switch (e.type) { | 
| +            case EventType.DOWNLOAD_FILE_RENAMED: | 
| +              this.description_ = e.params.new_filename; | 
| +              break; | 
| +            case EventType.DOWNLOAD_FILE_OPENED: | 
| +              this.description_ = e.params.file_name; | 
| +              break; | 
| +            case EventType.DOWNLOAD_ITEM_ACTIVE: | 
| +              this.description_ = e.params.file_name; | 
| +              break; | 
| +          } | 
| +          break; | 
| +        case EventSourceType.FILESTREAM: | 
| +          this.description_ = e.params.file_name; | 
| +          break; | 
| +        case EventSourceType.IPV6_PROBE_JOB: | 
| +          if (e.type == EventType.IPV6_PROBE_RUNNING && | 
| +              e.phase == EventPhase.PHASE_END) { | 
| +            this.description_ = e.params.ipv6_supported ? 'IPv6 Supported' : | 
| +                                                          'IPv6 Not Supported'; | 
| +          } | 
| +          break; | 
| +      } | 
| + | 
| +      if (this.description_ == undefined) | 
| +        this.description_ = ''; | 
| +    }, | 
| + | 
| +    /** | 
| +     * 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... | 
| +     */ | 
| +    getDescription: function() { | 
| +      return this.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 | 
| +     * entries. | 
| +     */ | 
| +    getStartEntry_: function() { | 
| +      if (this.entries_.length < 1) | 
| +        return undefined; | 
| +      if (this.entries_[0].source.type == EventSourceType.FILESTREAM) { | 
| +        var e = this.findLogEntryByType_(EventType.FILE_STREAM_OPEN); | 
| +        if (e != undefined) | 
| +          return e; | 
| +      } | 
| +      if (this.entries_[0].source.type == EventSourceType.DOWNLOAD) { | 
| +        // If any rename occurred, use the last name | 
| +        e = this.findLastLogEntryStartByType_( | 
| +            EventType.DOWNLOAD_FILE_RENAMED); | 
| +        if (e != undefined) | 
| +          return e; | 
| +        // Otherwise, if the file was opened, use that name | 
| +        e = this.findLogEntryByType_(EventType.DOWNLOAD_FILE_OPENED); | 
| +        if (e != undefined) | 
| +          return e; | 
| +        // History items are never opened, so use the activation info | 
| +        e = this.findLogEntryByType_(EventType.DOWNLOAD_ITEM_ACTIVE); | 
| +        if (e != undefined) | 
| +          return e; | 
| +      } | 
| +      if (this.entries_.length >= 2) { | 
| +        // Needed for compatability with log dumps prior to M26. | 
| +        // TODO(mmenke):  Remove this. | 
| +        if (this.entries_[0].type == EventType.SOCKET_POOL_CONNECT_JOB && | 
| +            this.entries_[0].params == undefined) { | 
| +          return this.entries_[1]; | 
| +        } | 
| +        if (this.entries_[1].type == EventType.UDP_CONNECT) | 
| +          return this.entries_[1]; | 
| +        if (this.entries_[0].type == EventType.REQUEST_ALIVE && | 
| +            this.entries_[0].params == undefined) { | 
| +          var startIndex = 1; | 
| +          // Skip over delegate events for URL_REQUESTs. | 
| +          for (; startIndex + 1 < this.entries_.length; ++startIndex) { | 
| +            var type = this.entries_[startIndex].type; | 
| +            if (type != EventType.URL_REQUEST_DELEGATE && | 
| +                type != EventType.DELEGATE_INFO) { | 
| +              break; | 
| +            } | 
| +          } | 
| +          return this.entries_[startIndex]; | 
| +        } | 
| +        if (this.entries_[1].type == EventType.IPV6_PROBE_RUNNING) | 
| +          return this.entries_[1]; | 
| +      } | 
| +      return this.entries_[0]; | 
| +    }, | 
| + | 
| +    /** | 
| +     * Returns the first entry with the specified type, or undefined if not | 
| +     * found. | 
| +     */ | 
| +    findLogEntryByType_: function(type) { | 
| +      for (var i = 0; i < this.entries_.length; ++i) { | 
| +        if (this.entries_[i].type == type) { | 
| +          return this.entries_[i]; | 
| +        } | 
| +      } | 
| +      return undefined; | 
| +    }, | 
| + | 
| +    /** | 
| +     * Returns the beginning of the last entry with the specified type, or | 
| +     * undefined if not found. | 
| +     */ | 
| +    findLastLogEntryStartByType_: function(type) { | 
| +      for (var i = this.entries_.length - 1; i >= 0; --i) { | 
| +        if (this.entries_[i].type == type) { | 
| +          if (this.entries_[i].phase != EventPhase.PHASE_END) | 
| +            return this.entries_[i]; | 
| +        } | 
| +      } | 
| +      return undefined; | 
| +    }, | 
| + | 
| +    getLogEntries: function() { | 
| +      return this.entries_; | 
| +    }, | 
| + | 
| +    getSourceTypeString: function() { | 
| +      return EventSourceTypeNames[this.entries_[0].source.type]; | 
| +    }, | 
| + | 
| +    getSourceType: function() { | 
| +      return this.entries_[0].source.type; | 
| +    }, | 
| + | 
| +    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. | 
| +     */ | 
| +    getMaxPreviousEntrySourceId: function() { | 
| +      return this.maxPreviousSourceId_; | 
| +    }, | 
| + | 
| +    isInactive: function() { | 
| +      return this.isInactive_; | 
| +    }, | 
| + | 
| +    isError: function() { | 
| +      return this.isError_; | 
| +    }, | 
| + | 
| +    /** | 
| +     * Returns time ticks of first event. | 
| +     */ | 
| +    getStartTicks: function() { | 
| +      return this.entries_[0].time; | 
| +    }, | 
| + | 
| +    /** | 
| +     * Returns time of last event if inactive.  Returns current time otherwise. | 
| +     * Returned time is a "time ticks" value. | 
| +     */ | 
| +    getEndTicks: function() { | 
| +      if (!this.isInactive_) | 
| +        return timeutil.getCurrentTimeTicks(); | 
| +      return this.entries_[this.entries_.length - 1].time; | 
| +    }, | 
| + | 
| +    /** | 
| +     * 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. | 
| +     */ | 
| +    getDuration: function() { | 
| +      var startTime = this.getStartTicks(); | 
| +      var endTime = this.getEndTicks(); | 
| +      return endTime - startTime; | 
| +    }, | 
| + | 
| +    /** | 
| +     * Prints descriptive text about |entries_| to a new node added to the end | 
| +     * of |parent|. | 
| +     */ | 
| +    printAsText: function(parent) { | 
| +      var tablePrinter = this.createTablePrinter(); | 
| + | 
| +      // Format the table for fixed-width text. | 
| +      tablePrinter.toText(0, parent); | 
| +    }, | 
| + | 
| +    /** | 
| +     * Creates a table printer for the SourceEntry. | 
| +     */ | 
| +    createTablePrinter: function() { | 
| +      return createLogEntryTablePrinter( | 
| +          this.entries_, | 
| +          SourceTracker.getInstance().getPrivacyStripping(), | 
| +          SourceTracker.getInstance().getUseRelativeTimes() ? | 
| +              timeutil.getBaseTime() : 0, | 
| +          Constants.clientInfo.numericDate); | 
| +    }, | 
| +  }; | 
| + | 
| +  return SourceEntry; | 
| +})(); | 
| + | 
|  |