1// Copyright (c) 2012 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
5var SourceEntry = (function() {
6  'use strict';
7
8  /**
9   * A SourceEntry gathers all log entries with the same source.
10   *
11   * @constructor
12   */
13  function SourceEntry(logEntry, maxPreviousSourceId) {
14    this.maxPreviousSourceId_ = maxPreviousSourceId;
15    this.entries_ = [];
16    this.description_ = '';
17
18    // Set to true on most net errors.
19    this.isError_ = false;
20
21    // If the first entry is a BEGIN_PHASE, set to false.
22    // Set to true when an END_PHASE matching the first entry is encountered.
23    this.isInactive_ = true;
24
25    if (logEntry.phase == EventPhase.PHASE_BEGIN)
26      this.isInactive_ = false;
27
28    this.update(logEntry);
29  }
30
31  SourceEntry.prototype = {
32    update: function(logEntry) {
33      // Only the last event should have the same type first event,
34      if (!this.isInactive_ &&
35          logEntry.phase == EventPhase.PHASE_END &&
36          logEntry.type == this.entries_[0].type) {
37        this.isInactive_ = true;
38      }
39
40      // If we have a net error code, update |this.isError_| if appropriate.
41      if (logEntry.params) {
42        var netErrorCode = logEntry.params.net_error;
43        // Skip both cases where netErrorCode is undefined, and cases where it
44        // is 0, indicating no actual error occurred.
45        if (netErrorCode) {
46          // Ignore error code caused by not finding an entry in the cache.
47          if (logEntry.type != EventType.HTTP_CACHE_OPEN_ENTRY ||
48              netErrorCode != NetError.FAILED) {
49            this.isError_ = true;
50          }
51        }
52      }
53
54      var prevStartEntry = this.getStartEntry_();
55      this.entries_.push(logEntry);
56      var curStartEntry = this.getStartEntry_();
57
58      // If we just got the first entry for this source.
59      if (prevStartEntry != curStartEntry)
60        this.updateDescription_();
61    },
62
63    updateDescription_: function() {
64      var e = this.getStartEntry_();
65      this.description_ = '';
66      if (!e)
67        return;
68
69      if (e.source.type == EventSourceType.NONE) {
70        // NONE is what we use for global events that aren't actually grouped
71        // by a "source ID", so we will just stringize the event's type.
72        this.description_ = EventTypeNames[e.type];
73        return;
74      }
75
76      if (e.params == undefined) {
77        return;
78      }
79
80      switch (e.source.type) {
81        case EventSourceType.URL_REQUEST:
82        case EventSourceType.SOCKET_STREAM:
83        case EventSourceType.HTTP_STREAM_JOB:
84          this.description_ = e.params.url;
85          break;
86        case EventSourceType.CONNECT_JOB:
87          this.description_ = e.params.group_name;
88          break;
89        case EventSourceType.HOST_RESOLVER_IMPL_REQUEST:
90        case EventSourceType.HOST_RESOLVER_IMPL_JOB:
91        case EventSourceType.HOST_RESOLVER_IMPL_PROC_TASK:
92          this.description_ = e.params.host;
93          break;
94        case EventSourceType.DISK_CACHE_ENTRY:
95        case EventSourceType.MEMORY_CACHE_ENTRY:
96          this.description_ = e.params.key;
97          break;
98        case EventSourceType.QUIC_SESSION:
99          if (e.params.host != undefined)
100            this.description_ = e.params.host;
101          break;
102        case EventSourceType.SPDY_SESSION:
103          if (e.params.host)
104            this.description_ = e.params.host + ' (' + e.params.proxy + ')';
105          break;
106        case EventSourceType.HTTP_PIPELINED_CONNECTION:
107          if (e.params.host_and_port)
108            this.description_ = e.params.host_and_port;
109          break;
110        case EventSourceType.SOCKET:
111        case EventSourceType.PROXY_CLIENT_SOCKET:
112          // Use description of parent source, if any.
113          if (e.params.source_dependency != undefined) {
114            var parentId = e.params.source_dependency.id;
115            this.description_ =
116                SourceTracker.getInstance().getDescription(parentId);
117          }
118          break;
119        case EventSourceType.UDP_SOCKET:
120          if (e.params.address != undefined) {
121            this.description_ = e.params.address;
122            // If the parent of |this| is a HOST_RESOLVER_IMPL_JOB, use
123            // '<DNS Server IP> [<host we're resolving>]'.
124            if (this.entries_[0].type == EventType.SOCKET_ALIVE &&
125                this.entries_[0].params &&
126                this.entries_[0].params.source_dependency != undefined) {
127              var parentId = this.entries_[0].params.source_dependency.id;
128              var parent = SourceTracker.getInstance().getSourceEntry(parentId);
129              if (parent &&
130                  parent.getSourceType() ==
131                      EventSourceType.HOST_RESOLVER_IMPL_JOB &&
132                  parent.getDescription().length > 0) {
133                this.description_ += ' [' + parent.getDescription() + ']';
134              }
135            }
136          }
137          break;
138        case EventSourceType.ASYNC_HOST_RESOLVER_REQUEST:
139        case EventSourceType.DNS_TRANSACTION:
140          this.description_ = e.params.hostname;
141          break;
142        case EventSourceType.DOWNLOAD:
143          switch (e.type) {
144            case EventType.DOWNLOAD_FILE_RENAMED:
145              this.description_ = e.params.new_filename;
146              break;
147            case EventType.DOWNLOAD_FILE_OPENED:
148              this.description_ = e.params.file_name;
149              break;
150            case EventType.DOWNLOAD_ITEM_ACTIVE:
151              this.description_ = e.params.file_name;
152              break;
153          }
154          break;
155        case EventSourceType.FILESTREAM:
156          this.description_ = e.params.file_name;
157          break;
158        case EventSourceType.IPV6_PROBE_JOB:
159          if (e.type == EventType.IPV6_PROBE_RUNNING &&
160              e.phase == EventPhase.PHASE_END) {
161            this.description_ = e.params.ipv6_supported ? 'IPv6 Supported' :
162                                                          'IPv6 Not Supported';
163          }
164          break;
165      }
166
167      if (this.description_ == undefined)
168        this.description_ = '';
169    },
170
171    /**
172     * Returns a description for this source log stream, which will be displayed
173     * in the list view. Most often this is a URL that identifies the request,
174     * or a hostname for a connect job, etc...
175     */
176    getDescription: function() {
177      return this.description_;
178    },
179
180    /**
181     * Returns the starting entry for this source. Conceptually this is the
182     * first entry that was logged to this source. However, we skip over the
183     * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB /
184     * TYPE_SOCKET_STREAM_CONNECT.
185     */
186    getStartEntry_: function() {
187      if (this.entries_.length < 1)
188        return undefined;
189      if (this.entries_[0].source.type == EventSourceType.FILESTREAM) {
190        var e = this.findLogEntryByType_(EventType.FILE_STREAM_OPEN);
191        if (e != undefined)
192          return e;
193      }
194      if (this.entries_[0].source.type == EventSourceType.DOWNLOAD) {
195        // If any rename occurred, use the last name
196        e = this.findLastLogEntryStartByType_(
197            EventType.DOWNLOAD_FILE_RENAMED);
198        if (e != undefined)
199          return e;
200        // Otherwise, if the file was opened, use that name
201        e = this.findLogEntryByType_(EventType.DOWNLOAD_FILE_OPENED);
202        if (e != undefined)
203          return e;
204        // History items are never opened, so use the activation info
205        e = this.findLogEntryByType_(EventType.DOWNLOAD_ITEM_ACTIVE);
206        if (e != undefined)
207          return e;
208      }
209      if (this.entries_.length >= 2) {
210        // Needed for compatability with log dumps prior to M26.
211        // TODO(mmenke):  Remove this.
212        if (this.entries_[0].type == EventType.SOCKET_POOL_CONNECT_JOB &&
213            this.entries_[0].params == undefined) {
214          return this.entries_[1];
215        }
216        if (this.entries_[1].type == EventType.UDP_CONNECT)
217          return this.entries_[1];
218        if (this.entries_[0].type == EventType.REQUEST_ALIVE &&
219            this.entries_[0].params == undefined) {
220          var startIndex = 1;
221          // Skip over URL_REQUEST_BLOCKED_ON_DELEGATE events for URL_REQUESTs.
222          while (startIndex + 1 < this.entries_.length &&
223                 this.entries_[startIndex].type ==
224                     EventType.URL_REQUEST_BLOCKED_ON_DELEGATE) {
225            ++startIndex;
226          }
227          return this.entries_[startIndex];
228        }
229        if (this.entries_[1].type == EventType.IPV6_PROBE_RUNNING)
230          return this.entries_[1];
231      }
232      return this.entries_[0];
233    },
234
235    /**
236     * Returns the first entry with the specified type, or undefined if not
237     * found.
238     */
239    findLogEntryByType_: function(type) {
240      for (var i = 0; i < this.entries_.length; ++i) {
241        if (this.entries_[i].type == type) {
242          return this.entries_[i];
243        }
244      }
245      return undefined;
246    },
247
248    /**
249     * Returns the beginning of the last entry with the specified type, or
250     * undefined if not found.
251     */
252    findLastLogEntryStartByType_: function(type) {
253      for (var i = this.entries_.length - 1; i >= 0; --i) {
254        if (this.entries_[i].type == type) {
255          if (this.entries_[i].phase != EventPhase.PHASE_END)
256            return this.entries_[i];
257        }
258      }
259      return undefined;
260    },
261
262    getLogEntries: function() {
263      return this.entries_;
264    },
265
266    getSourceTypeString: function() {
267      return EventSourceTypeNames[this.entries_[0].source.type];
268    },
269
270    getSourceType: function() {
271      return this.entries_[0].source.type;
272    },
273
274    getSourceId: function() {
275      return this.entries_[0].source.id;
276    },
277
278    /**
279     * Returns the largest source ID seen before this object was received.
280     * Used only for sorting SourceEntries without a source by source ID.
281     */
282    getMaxPreviousEntrySourceId: function() {
283      return this.maxPreviousSourceId_;
284    },
285
286    isInactive: function() {
287      return this.isInactive_;
288    },
289
290    isError: function() {
291      return this.isError_;
292    },
293
294    getStartTime: function() {
295      var startTicks = this.entries_[0].time;
296      return timeutil.convertTimeTicksToTime(startTicks);
297    },
298
299    /**
300     * Returns time of last event if inactive.  Returns current time otherwise.
301     */
302    getEndTime: function() {
303      if (!this.isInactive_) {
304        return timeutil.getCurrentTime();
305      } else {
306        var endTicks = this.entries_[this.entries_.length - 1].time;
307        return timeutil.convertTimeTicksToTime(endTicks);
308      }
309    },
310
311    /**
312     * Returns the time between the first and last events with a matching
313     * source ID.  If source is still active, uses the current time for the
314     * last event.
315     */
316    getDuration: function() {
317      var startTime = this.getStartTime();
318      var endTime = this.getEndTime();
319      return endTime - startTime;
320    },
321
322    /**
323     * Prints descriptive text about |entries_| to a new node added to the end
324     * of |parent|.
325     */
326    printAsText: function(parent) {
327      // The date will be undefined if not viewing a loaded log file.
328      printLogEntriesAsText(this.entries_, parent,
329                            SourceTracker.getInstance().getPrivacyStripping(),
330                            Constants.clientInfo.numericDate);
331    }
332  };
333
334  return SourceEntry;
335})();
336