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