sourceentry.js revision 3f50c38dc070f4bb515c1b64450dae14f316474e
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 */
12function 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
23SourceEntry.prototype.isSelected = function() {
24  return this.isSelected_;
25};
26
27SourceEntry.prototype.setSelectedStyles = function(isSelected) {
28  changeClassName(this.row_, 'selected', isSelected);
29  this.getSelectionCheckbox().checked = isSelected;
30};
31
32SourceEntry.prototype.setMouseoverStyle = function(isMouseOver) {
33  changeClassName(this.row_, 'mouseover', isMouseOver);
34};
35
36SourceEntry.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
54SourceEntry.prototype.isMatchedByFilter = function() {
55  return this.isMatchedByFilter_;
56};
57
58SourceEntry.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
67SourceEntry.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
95SourceEntry.prototype.onCheckboxToggled_ = function() {
96  this.setSelected(this.getSelectionCheckbox().checked);
97};
98
99SourceEntry.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
131SourceEntry.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
142SourceEntry.prototype.onClicked_ = function() {
143  this.parentView_.clearSelection();
144  this.setSelected(true);
145};
146
147SourceEntry.prototype.onMouseover_ = function() {
148  this.setMouseoverStyle(true);
149};
150
151SourceEntry.prototype.onMouseout_ = function() {
152  this.setMouseoverStyle(false);
153};
154
155SourceEntry.prototype.updateDescription_ = function() {
156  this.descriptionCell_.innerHTML = '';
157  addTextNode(this.descriptionCell_, this.getDescription());
158};
159
160SourceEntry.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 */
208SourceEntry.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      description = e.params.url;
227      break;
228    case LogSourceType.CONNECT_JOB:
229      description = e.params.group_name;
230      break;
231    case LogSourceType.HOST_RESOLVER_IMPL_REQUEST:
232    case LogSourceType.HOST_RESOLVER_IMPL_JOB:
233      description = e.params.host;
234      break;
235    case LogSourceType.DISK_CACHE_ENTRY:
236      description = e.params.key;
237      break;
238    case LogSourceType.SPDY_SESSION:
239      if (e.params.host)
240        description = e.params.host + ' (' + e.params.proxy + ')';
241      break;
242    case LogSourceType.SOCKET:
243      if (e.params.source_dependency != undefined) {
244        var connectJobSourceEntry =
245            this.parentView_.getSourceEntry(e.params.source_dependency.id);
246        if (connectJobSourceEntry)
247          description = connectJobSourceEntry.getDescription();
248      }
249      break;
250  }
251
252  if (description == undefined)
253    return '';
254  return description;
255};
256
257/**
258 * Returns the starting entry for this source. Conceptually this is the
259 * first entry that was logged to this source. However, we skip over the
260 * TYPE_REQUEST_ALIVE entries which wrap TYPE_URL_REQUEST_START_JOB /
261 * TYPE_SOCKET_STREAM_CONNECT.
262 */
263SourceEntry.prototype.getStartEntry_ = function() {
264  if (this.entries_.length < 1)
265    return undefined;
266  if (this.entries_.length >= 2) {
267    if (this.entries_[0].type == LogEventType.REQUEST_ALIVE ||
268        this.entries_[0].type == LogEventType.SOCKET_POOL_CONNECT_JOB)
269      return this.entries_[1];
270  }
271  return this.entries_[0];
272};
273
274SourceEntry.prototype.getLogEntries = function() {
275  return this.entries_;
276};
277
278SourceEntry.prototype.getSourceTypeString = function() {
279  return getKeyWithValue(LogSourceType, this.entries_[0].source.type);
280};
281
282SourceEntry.prototype.getSelectionCheckbox = function() {
283  return this.row_.childNodes[0].firstChild;
284};
285
286SourceEntry.prototype.getSourceId = function() {
287  return this.entries_[0].source.id;
288};
289
290/**
291 * Returns the largest source ID seen before this object was received.
292 * Used only for sorting SourceEntries without a source by source ID.
293 */
294SourceEntry.prototype.getMaxPreviousEntrySourceId = function() {
295  return this.maxPreviousSourceId_;
296};
297
298SourceEntry.prototype.isActive = function() {
299  return this.isActive_;
300};
301
302/**
303 * Returns time of last event if inactive.  Returns current time otherwise.
304 */
305SourceEntry.prototype.getEndTime = function() {
306  if (this.isActive_) {
307    return (new Date()).getTime();
308  }
309  else {
310    var endTicks = this.entries_[this.entries_.length - 1].time;
311    return g_browser.convertTimeTicksToDate(endTicks).getTime();
312  }
313};
314
315/**
316 * Returns the time between the first and last events with a matching
317 * source ID.  If source is still active, uses the current time for the
318 * last event.
319 */
320SourceEntry.prototype.getDuration = function() {
321  var startTicks = this.entries_[0].time;
322  var startTime = g_browser.convertTimeTicksToDate(startTicks).getTime();
323  var endTime = this.getEndTime();
324  return endTime - startTime;
325};
326
327/**
328 * Returns source ID of the entry whose row is currently above this one's.
329 * Returns null if no such node exists.
330 */
331SourceEntry.prototype.getPreviousNodeSourceId = function() {
332  if (!this.hasRow())
333    return null;
334  var prevNode = this.row_.previousSibling;
335  if (prevNode == null)
336    return null;
337  return prevNode._id;
338};
339
340/**
341 * Returns source ID of the entry whose row is currently below this one's.
342 * Returns null if no such node exists.
343 */
344SourceEntry.prototype.getNextNodeSourceId = function() {
345  if (!this.hasRow())
346    return null;
347  var nextNode = this.row_.nextSibling;
348  if (nextNode == null)
349    return null;
350  return nextNode._id;
351};
352
353SourceEntry.prototype.hasRow = function() {
354  return this.row_ != null;
355};
356
357/**
358 * Moves current object's row before |entry|'s row.
359 */
360SourceEntry.prototype.moveBefore = function(entry) {
361  if (this.hasRow() && entry.hasRow()) {
362    this.row_.parentNode.insertBefore(this.row_, entry.row_);
363  }
364};
365
366/**
367 * Moves current object's row after |entry|'s row.
368 */
369SourceEntry.prototype.moveAfter = function(entry) {
370  if (this.hasRow() && entry.hasRow()) {
371    this.row_.parentNode.insertBefore(this.row_, entry.row_.nextSibling);
372  }
373};
374
375SourceEntry.prototype.remove = function() {
376  this.setSelected(false);
377  this.setIsMatchedByFilter(false);
378  this.row_.parentNode.removeChild(this.row_);
379};
380
381