1 /* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009 Anthony Ricaud <rik (at) webkit.org> 4 * Copyright (C) 2011 Google Inc. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 16 * its contributors may be used to endorse or promote products derived 17 * from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 importScript("RequestView.js"); 32 importScript("NetworkItemView.js"); 33 importScript("RequestCookiesView.js"); 34 importScript("RequestHeadersView.js"); 35 importScript("RequestHTMLView.js"); 36 importScript("RequestJSONView.js"); 37 importScript("RequestPreviewView.js"); 38 importScript("RequestResponseView.js"); 39 importScript("RequestTimingView.js"); 40 importScript("ResourceWebSocketFrameView.js"); 41 42 /** 43 * @constructor 44 * @extends {WebInspector.View} 45 * @param {WebInspector.Setting} coulmnsVisibilitySetting 46 */ 47 WebInspector.NetworkLogView = function(coulmnsVisibilitySetting) 48 { 49 WebInspector.View.call(this); 50 this.registerRequiredCSS("networkLogView.css"); 51 52 this._coulmnsVisibilitySetting = coulmnsVisibilitySetting; 53 this._allowRequestSelection = false; 54 this._requests = []; 55 this._requestsById = {}; 56 this._requestsByURL = {}; 57 this._staleRequests = {}; 58 this._requestGridNodes = {}; 59 this._lastRequestGridNodeId = 0; 60 this._mainRequestLoadTime = -1; 61 this._mainRequestDOMContentLoadedTime = -1; 62 this._typeFilterElements = {}; 63 this._typeFilter = WebInspector.NetworkLogView._trivialTypeFilter; 64 this._matchedRequests = []; 65 this._highlightedSubstringChanges = []; 66 this._filteredOutRequests = new Map(); 67 68 this._matchedRequestsMap = {}; 69 this._currentMatchedRequestIndex = -1; 70 71 this._createStatusbarButtons(); 72 this._createStatusBarItems(); 73 this._linkifier = new WebInspector.Linkifier(); 74 75 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestStarted, this._onRequestStarted, this); 76 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestUpdated, this._onRequestUpdated, this); 77 WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.RequestFinished, this._onRequestUpdated, this); 78 79 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this); 80 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.Load, this._loadEventFired, this); 81 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.DOMContentLoaded, this._domContentLoadedEventFired, this); 82 83 this._initializeView(); 84 85 WebInspector.networkLog.requests.forEach(this._appendRequest.bind(this)); 86 } 87 88 WebInspector.NetworkLogView.HTTPSchemas = {"http": true, "https": true, "ws": true, "wss": true}; 89 WebInspector.NetworkLogView._responseHeaderColumns = ["Cache-Control", "Connection", "Content-Encoding", "Content-Length", "ETag", "Keep-Alive", "Last-Modified", "Server", "Vary"]; 90 WebInspector.NetworkLogView._defaultColumnsVisibility = { 91 method: true, status: true, domain: false, type: true, initiator: true, cookies: false, setCookies: false, size: true, time: true, 92 "Cache-Control": false, "Connection": false, "Content-Encoding": false, "Content-Length": false, "ETag": false, "Keep-Alive": false, "Last-Modified": false, "Server": false, "Vary": false 93 }; 94 WebInspector.NetworkLogView._defaultRefreshDelay = 500; 95 WebInspector.NetworkLogView.ALL_TYPES = "all"; 96 97 WebInspector.NetworkLogView.prototype = { 98 _initializeView: function() 99 { 100 this.element.id = "network-container"; 101 102 this._createSortingFunctions(); 103 this._createTable(); 104 this._createTimelineGrid(); 105 this._createSummaryBar(); 106 107 if (!this.useLargeRows) 108 this._setLargerRequests(this.useLargeRows); 109 110 this._allowPopover = true; 111 this._popoverHelper = new WebInspector.PopoverHelper(this.element, this._getPopoverAnchor.bind(this), this._showPopover.bind(this), this._onHidePopover.bind(this)); 112 // Enable faster hint. 113 this._popoverHelper.setTimeout(100); 114 115 this.calculator = new WebInspector.NetworkTransferTimeCalculator(); 116 this._toggleTypeFilter(WebInspector.NetworkLogView.ALL_TYPES, false); 117 118 this.switchToDetailedView(); 119 }, 120 121 get statusBarItems() 122 { 123 return [this._largerRequestsButton.element, this._preserveLogToggle.element, this._clearButton.element, this._filterBarElement, this._progressBarContainer]; 124 }, 125 126 get useLargeRows() 127 { 128 return WebInspector.settings.resourcesLargeRows.get(); 129 }, 130 131 set allowPopover(flag) 132 { 133 this._allowPopover = flag; 134 }, 135 136 elementsToRestoreScrollPositionsFor: function() 137 { 138 if (!this._dataGrid) // Not initialized yet. 139 return []; 140 return [this._dataGrid.scrollContainer]; 141 }, 142 143 onResize: function() 144 { 145 this._updateOffscreenRows(); 146 }, 147 148 _createTimelineGrid: function() 149 { 150 this._timelineGrid = new WebInspector.TimelineGrid(); 151 this._timelineGrid.element.addStyleClass("network-timeline-grid"); 152 this._dataGrid.element.appendChild(this._timelineGrid.element); 153 }, 154 155 _createTable: function() 156 { 157 var columns = []; 158 columns.push({ 159 id: "name", 160 titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Name"), WebInspector.UIString("Path")), 161 title: WebInspector.UIString("Name"), 162 sortable: true, 163 weight: 20, 164 disclosure: true 165 }); 166 167 columns.push({ 168 id: "method", 169 title: WebInspector.UIString("Method"), 170 sortable: true, 171 weight: 6 172 }); 173 174 columns.push({ 175 id: "status", 176 titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Status"), WebInspector.UIString("Text")), 177 title: WebInspector.UIString("Status"), 178 sortable: true, 179 weight: 6 180 }); 181 182 columns.push({ 183 id: "domain", 184 title: WebInspector.UIString("Domain"), 185 sortable: true, 186 weight: 6 187 }); 188 189 columns.push({ 190 id: "type", 191 title: WebInspector.UIString("Type"), 192 sortable: true, 193 weight: 6 194 }); 195 196 columns.push({ 197 id: "initiator", 198 title: WebInspector.UIString("Initiator"), 199 sortable: true, 200 weight: 10 201 }); 202 203 columns.push({ 204 id: "cookies", 205 title: WebInspector.UIString("Cookies"), 206 sortable: true, 207 weight: 6, 208 align: WebInspector.DataGrid.Align.Right 209 }); 210 211 columns.push({ 212 id: "setCookies", 213 title: WebInspector.UIString("Set-Cookies"), 214 sortable: true, 215 weight: 6, 216 align: WebInspector.DataGrid.Align.Right 217 }); 218 219 columns.push({ 220 id: "size", 221 titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Size"), WebInspector.UIString("Content")), 222 title: WebInspector.UIString("Size"), 223 sortable: true, 224 weight: 6, 225 align: WebInspector.DataGrid.Align.Right 226 }); 227 228 columns.push({ 229 id: "time", 230 titleDOMFragment: this._makeHeaderFragment(WebInspector.UIString("Time"), WebInspector.UIString("Latency")), 231 title: WebInspector.UIString("Time"), 232 sortable: true, 233 weight: 6, 234 align: WebInspector.DataGrid.Align.Right 235 }); 236 237 var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns; 238 for (var i = 0; i < responseHeaderColumns.length; ++i) { 239 var headerName = responseHeaderColumns[i]; 240 var descriptor = { 241 id: headerName, 242 title: WebInspector.UIString(headerName), 243 weight: 6 244 } 245 if (headerName === "Content-Length") 246 descriptor.align = WebInspector.DataGrid.Align.Right; 247 columns.push(descriptor); 248 } 249 250 columns.push({ 251 id: "timeline", 252 titleDOMFragment: document.createDocumentFragment(), 253 title: WebInspector.UIString("Timeline"), 254 sortable: false, 255 weight: 40, 256 sort: WebInspector.DataGrid.Order.Ascending 257 }); 258 259 this._dataGrid = new WebInspector.DataGrid(columns); 260 this._dataGrid.setName("networkLog"); 261 this._dataGrid.resizeMethod = WebInspector.DataGrid.ResizeMethod.Last; 262 this._dataGrid.element.addStyleClass("network-log-grid"); 263 this._dataGrid.element.addEventListener("contextmenu", this._contextMenu.bind(this), true); 264 this._dataGrid.show(this.element); 265 266 // Event listeners need to be added _after_ we attach to the document, so that owner document is properly update. 267 this._dataGrid.addEventListener(WebInspector.DataGrid.Events.SortingChanged, this._sortItems, this); 268 this._dataGrid.addEventListener(WebInspector.DataGrid.Events.ColumnsResized, this._updateDividersIfNeeded, this); 269 this._dataGrid.scrollContainer.addEventListener("scroll", this._updateOffscreenRows.bind(this)); 270 271 this._patchTimelineHeader(); 272 }, 273 274 _makeHeaderFragment: function(title, subtitle) 275 { 276 var fragment = document.createDocumentFragment(); 277 fragment.createTextChild(title); 278 var subtitleDiv = fragment.createChild("div", "network-header-subtitle"); 279 subtitleDiv.createTextChild(subtitle); 280 return fragment; 281 }, 282 283 _patchTimelineHeader: function() 284 { 285 var timelineSorting = document.createElement("select"); 286 287 var option = document.createElement("option"); 288 option.value = "startTime"; 289 option.label = WebInspector.UIString("Timeline"); 290 timelineSorting.appendChild(option); 291 292 option = document.createElement("option"); 293 option.value = "startTime"; 294 option.label = WebInspector.UIString("Start Time"); 295 timelineSorting.appendChild(option); 296 297 option = document.createElement("option"); 298 option.value = "responseTime"; 299 option.label = WebInspector.UIString("Response Time"); 300 timelineSorting.appendChild(option); 301 302 option = document.createElement("option"); 303 option.value = "endTime"; 304 option.label = WebInspector.UIString("End Time"); 305 timelineSorting.appendChild(option); 306 307 option = document.createElement("option"); 308 option.value = "duration"; 309 option.label = WebInspector.UIString("Duration"); 310 timelineSorting.appendChild(option); 311 312 option = document.createElement("option"); 313 option.value = "latency"; 314 option.label = WebInspector.UIString("Latency"); 315 timelineSorting.appendChild(option); 316 317 var header = this._dataGrid.headerTableHeader("timeline"); 318 header.replaceChild(timelineSorting, header.firstChild); 319 320 timelineSorting.addEventListener("click", function(event) { event.consume() }, false); 321 timelineSorting.addEventListener("change", this._sortByTimeline.bind(this), false); 322 this._timelineSortSelector = timelineSorting; 323 }, 324 325 _createSortingFunctions: function() 326 { 327 this._sortingFunctions = {}; 328 this._sortingFunctions.name = WebInspector.NetworkDataGridNode.NameComparator; 329 this._sortingFunctions.method = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "method", false); 330 this._sortingFunctions.status = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "statusCode", false); 331 this._sortingFunctions.domain = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "domain", false); 332 this._sortingFunctions.type = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "mimeType", false); 333 this._sortingFunctions.initiator = WebInspector.NetworkDataGridNode.InitiatorComparator; 334 this._sortingFunctions.cookies = WebInspector.NetworkDataGridNode.RequestCookiesCountComparator; 335 this._sortingFunctions.setCookies = WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator; 336 this._sortingFunctions.size = WebInspector.NetworkDataGridNode.SizeComparator; 337 this._sortingFunctions.time = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", false); 338 this._sortingFunctions.timeline = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false); 339 this._sortingFunctions.startTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "startTime", false); 340 this._sortingFunctions.endTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "endTime", false); 341 this._sortingFunctions.responseTime = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "responseReceivedTime", false); 342 this._sortingFunctions.duration = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "duration", true); 343 this._sortingFunctions.latency = WebInspector.NetworkDataGridNode.RequestPropertyComparator.bind(null, "latency", true); 344 345 var timeCalculator = new WebInspector.NetworkTransferTimeCalculator(); 346 var durationCalculator = new WebInspector.NetworkTransferDurationCalculator(); 347 348 this._calculators = {}; 349 this._calculators.timeline = timeCalculator; 350 this._calculators.startTime = timeCalculator; 351 this._calculators.endTime = timeCalculator; 352 this._calculators.responseTime = timeCalculator; 353 this._calculators.duration = durationCalculator; 354 this._calculators.latency = durationCalculator; 355 }, 356 357 _sortItems: function() 358 { 359 this._removeAllNodeHighlights(); 360 var columnIdentifier = this._dataGrid.sortColumnIdentifier(); 361 if (columnIdentifier === "timeline") { 362 this._sortByTimeline(); 363 return; 364 } 365 var sortingFunction = this._sortingFunctions[columnIdentifier]; 366 if (!sortingFunction) 367 return; 368 369 this._dataGrid.sortNodes(sortingFunction, !this._dataGrid.isSortOrderAscending()); 370 this._timelineSortSelector.selectedIndex = 0; 371 this._updateOffscreenRows(); 372 373 this.searchCanceled(); 374 375 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 376 action: WebInspector.UserMetrics.UserActionNames.NetworkSort, 377 column: columnIdentifier, 378 sortOrder: this._dataGrid.sortOrder() 379 }); 380 }, 381 382 _sortByTimeline: function() 383 { 384 this._removeAllNodeHighlights(); 385 var selectedIndex = this._timelineSortSelector.selectedIndex; 386 if (!selectedIndex) 387 selectedIndex = 1; // Sort by start time by default. 388 var selectedOption = this._timelineSortSelector[selectedIndex]; 389 var value = selectedOption.value; 390 391 var sortingFunction = this._sortingFunctions[value]; 392 this._dataGrid.sortNodes(sortingFunction); 393 this.calculator = this._calculators[value]; 394 if (this.calculator.startAtZero) 395 this._timelineGrid.hideEventDividers(); 396 else 397 this._timelineGrid.showEventDividers(); 398 this._dataGrid.markColumnAsSortedBy("timeline", WebInspector.DataGrid.Order.Ascending); 399 this._updateOffscreenRows(); 400 }, 401 402 /** 403 * @param {string} typeName 404 * @param {string} label 405 */ 406 _addTypeFilter: function(typeName, label) 407 { 408 var typeFilterElement = this._filterBarElement.createChild("li", typeName); 409 typeFilterElement.typeName = typeName; 410 typeFilterElement.createTextChild(label); 411 typeFilterElement.addEventListener("click", this._onTypeFilterClicked.bind(this), false); 412 this._typeFilterElements[typeName] = typeFilterElement; 413 }, 414 415 _createStatusBarItems: function() 416 { 417 var filterBarElement = document.createElement("div"); 418 filterBarElement.className = "scope-bar status-bar-item"; 419 filterBarElement.title = WebInspector.UIString("Use %s Click to select multiple types.", WebInspector.KeyboardShortcut.shortcutToString("", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta)); 420 this._filterBarElement = filterBarElement; 421 422 this._addTypeFilter(WebInspector.NetworkLogView.ALL_TYPES, WebInspector.UIString("All")); 423 filterBarElement.createChild("div", "scope-bar-divider"); 424 425 for (var typeId in WebInspector.resourceTypes) { 426 var type = WebInspector.resourceTypes[typeId]; 427 this._addTypeFilter(type.name(), type.categoryTitle()); 428 } 429 430 this._progressBarContainer = document.createElement("div"); 431 this._progressBarContainer.className = "status-bar-item"; 432 }, 433 434 _createSummaryBar: function() 435 { 436 var tbody = this._dataGrid.dataTableBody; 437 var tfoot = document.createElement("tfoot"); 438 var tr = tfoot.createChild("tr", "revealed network-summary-bar"); 439 var td = tr.createChild("td"); 440 td.setAttribute("colspan", 7); 441 tbody.parentNode.insertBefore(tfoot, tbody); 442 this._summaryBarElement = td; 443 }, 444 445 _updateSummaryBar: function() 446 { 447 var requestsNumber = this._requests.length; 448 449 if (!requestsNumber) { 450 if (this._summaryBarElement._isDisplayingWarning) 451 return; 452 this._summaryBarElement._isDisplayingWarning = true; 453 this._summaryBarElement.removeChildren(); 454 this._summaryBarElement.createChild("div", "warning-icon-small"); 455 this._summaryBarElement.appendChild(document.createTextNode( 456 WebInspector.UIString("No requests captured. Reload the page to see detailed information on the network activity."))); 457 return; 458 } 459 delete this._summaryBarElement._isDisplayingWarning; 460 461 var transferSize = 0; 462 var selectedRequestsNumber = 0; 463 var selectedTransferSize = 0; 464 var baseTime = -1; 465 var maxTime = -1; 466 for (var i = 0; i < this._requests.length; ++i) { 467 var request = this._requests[i]; 468 var requestTransferSize = request.transferSize; 469 transferSize += requestTransferSize; 470 if (!this._filteredOutRequests.get(request)) { 471 selectedRequestsNumber++; 472 selectedTransferSize += requestTransferSize; 473 } 474 if (request.url === WebInspector.inspectedPageURL) 475 baseTime = request.startTime; 476 if (request.endTime > maxTime) 477 maxTime = request.endTime; 478 } 479 var text = ""; 480 if (selectedRequestsNumber !== requestsNumber) { 481 text += String.sprintf(WebInspector.UIString("%d / %d requests"), selectedRequestsNumber, requestsNumber); 482 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s / %s transferred"), Number.bytesToString(selectedTransferSize), Number.bytesToString(transferSize)); 483 } else { 484 text += String.sprintf(WebInspector.UIString("%d requests"), requestsNumber); 485 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s transferred"), Number.bytesToString(transferSize)); 486 } 487 if (baseTime !== -1 && this._mainRequestLoadTime !== -1 && this._mainRequestDOMContentLoadedTime !== -1 && this._mainRequestDOMContentLoadedTime > baseTime) { 488 text += " \u2758 " + String.sprintf(WebInspector.UIString("%s (load: %s, DOMContentLoaded: %s)"), 489 Number.secondsToString(maxTime - baseTime), 490 Number.secondsToString(this._mainRequestLoadTime - baseTime), 491 Number.secondsToString(this._mainRequestDOMContentLoadedTime - baseTime)); 492 } 493 this._summaryBarElement.textContent = text; 494 }, 495 496 /** 497 * @param {!Event} e 498 */ 499 _onTypeFilterClicked: function(e) 500 { 501 var toggle; 502 if (WebInspector.isMac()) 503 toggle = e.metaKey && !e.ctrlKey && !e.altKey && !e.shiftKey; 504 else 505 toggle = e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey; 506 507 this._toggleTypeFilter(e.target.typeName, toggle); 508 509 this._removeAllNodeHighlights(); 510 this.searchCanceled(); 511 this._filterRequests(); 512 }, 513 514 /** 515 * @param {string} typeName 516 * @param {boolean} allowMultiSelect 517 */ 518 _toggleTypeFilter: function(typeName, allowMultiSelect) 519 { 520 if (allowMultiSelect && typeName !== WebInspector.NetworkLogView.ALL_TYPES) 521 this._typeFilterElements[WebInspector.NetworkLogView.ALL_TYPES].removeStyleClass("selected"); 522 else { 523 for (var key in this._typeFilterElements) 524 this._typeFilterElements[key].removeStyleClass("selected"); 525 } 526 527 var filterElement = this._typeFilterElements[typeName]; 528 filterElement.enableStyleClass("selected", !filterElement.hasStyleClass("selected")); 529 530 var allowedTypes = {}; 531 for (var key in this._typeFilterElements) { 532 if (this._typeFilterElements[key].hasStyleClass("selected")) 533 allowedTypes[key] = true; 534 } 535 536 if (typeName === WebInspector.NetworkLogView.ALL_TYPES) 537 this._typeFilter = WebInspector.NetworkLogView._trivialTypeFilter; 538 else 539 this._typeFilter = WebInspector.NetworkLogView._typeFilter.bind(null, allowedTypes); 540 }, 541 542 _scheduleRefresh: function() 543 { 544 if (this._needsRefresh) 545 return; 546 547 this._needsRefresh = true; 548 549 if (this.isShowing() && !this._refreshTimeout) 550 this._refreshTimeout = setTimeout(this.refresh.bind(this), WebInspector.NetworkLogView._defaultRefreshDelay); 551 }, 552 553 _updateDividersIfNeeded: function() 554 { 555 if (!this._dataGrid) 556 return; 557 var timelineColumn = this._dataGrid.columns.timeline; 558 for (var i = 0; i < this._dataGrid.resizers.length; ++i) { 559 if (timelineColumn.ordinal === this._dataGrid.resizers[i].rightNeighboringColumnIndex) { 560 // Position timline grid location. 561 this._timelineGrid.element.style.left = this._dataGrid.resizers[i].style.left; 562 } 563 } 564 565 var proceed = true; 566 if (!this.isShowing()) { 567 this._scheduleRefresh(); 568 proceed = false; 569 } else { 570 this.calculator.setDisplayWindow(this._timelineGrid.dividersElement.clientWidth); 571 proceed = this._timelineGrid.updateDividers(this.calculator); 572 } 573 if (!proceed) 574 return; 575 576 if (this.calculator.startAtZero || !this.calculator.computePercentageFromEventTime) { 577 // If our current sorting method starts at zero, that means it shows all 578 // requests starting at the same point, and so onLoad event and DOMContent 579 // event lines really wouldn't make much sense here, so don't render them. 580 // Additionally, if the calculator doesn't have the computePercentageFromEventTime 581 // function defined, we are probably sorting by size, and event times aren't relevant 582 // in this case. 583 return; 584 } 585 586 this._timelineGrid.removeEventDividers(); 587 if (this._mainRequestLoadTime !== -1) { 588 var percent = this.calculator.computePercentageFromEventTime(this._mainRequestLoadTime); 589 590 var loadDivider = document.createElement("div"); 591 loadDivider.className = "network-event-divider network-red-divider"; 592 593 var loadDividerPadding = document.createElement("div"); 594 loadDividerPadding.className = "network-event-divider-padding"; 595 loadDividerPadding.title = WebInspector.UIString("Load event fired"); 596 loadDividerPadding.appendChild(loadDivider); 597 loadDividerPadding.style.left = percent + "%"; 598 this._timelineGrid.addEventDivider(loadDividerPadding); 599 } 600 601 if (this._mainRequestDOMContentLoadedTime !== -1) { 602 var percent = this.calculator.computePercentageFromEventTime(this._mainRequestDOMContentLoadedTime); 603 604 var domContentLoadedDivider = document.createElement("div"); 605 domContentLoadedDivider.className = "network-event-divider network-blue-divider"; 606 607 var domContentLoadedDividerPadding = document.createElement("div"); 608 domContentLoadedDividerPadding.className = "network-event-divider-padding"; 609 domContentLoadedDividerPadding.title = WebInspector.UIString("DOMContentLoaded event fired"); 610 domContentLoadedDividerPadding.appendChild(domContentLoadedDivider); 611 domContentLoadedDividerPadding.style.left = percent + "%"; 612 this._timelineGrid.addEventDivider(domContentLoadedDividerPadding); 613 } 614 }, 615 616 _refreshIfNeeded: function() 617 { 618 if (this._needsRefresh) 619 this.refresh(); 620 }, 621 622 _invalidateAllItems: function() 623 { 624 for (var i = 0; i < this._requests.length; ++i) { 625 var request = this._requests[i]; 626 this._staleRequests[request.requestId] = request; 627 } 628 }, 629 630 get calculator() 631 { 632 return this._calculator; 633 }, 634 635 set calculator(x) 636 { 637 if (!x || this._calculator === x) 638 return; 639 640 this._calculator = x; 641 this._calculator.reset(); 642 643 this._invalidateAllItems(); 644 this.refresh(); 645 }, 646 647 _requestGridNode: function(request) 648 { 649 return this._requestGridNodes[request.__gridNodeId]; 650 }, 651 652 _createRequestGridNode: function(request) 653 { 654 var node = new WebInspector.NetworkDataGridNode(this, request); 655 request.__gridNodeId = this._lastRequestGridNodeId++; 656 this._requestGridNodes[request.__gridNodeId] = node; 657 return node; 658 }, 659 660 _createStatusbarButtons: function() 661 { 662 this._preserveLogToggle = new WebInspector.StatusBarButton(WebInspector.UIString("Preserve Log upon Navigation"), "record-profile-status-bar-item"); 663 this._preserveLogToggle.addEventListener("click", this._onPreserveLogClicked, this); 664 665 this._clearButton = new WebInspector.StatusBarButton(WebInspector.UIString("Clear"), "clear-status-bar-item"); 666 this._clearButton.addEventListener("click", this._reset, this); 667 668 this._largerRequestsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Use small resource rows."), "network-larger-resources-status-bar-item"); 669 this._largerRequestsButton.toggled = WebInspector.settings.resourcesLargeRows.get(); 670 this._largerRequestsButton.addEventListener("click", this._toggleLargerRequests, this); 671 }, 672 673 _loadEventFired: function(event) 674 { 675 this._mainRequestLoadTime = event.data || -1; 676 // Schedule refresh to update boundaries and draw the new line. 677 this._scheduleRefresh(); 678 }, 679 680 _domContentLoadedEventFired: function(event) 681 { 682 this._mainRequestDOMContentLoadedTime = event.data || -1; 683 // Schedule refresh to update boundaries and draw the new line. 684 this._scheduleRefresh(); 685 }, 686 687 wasShown: function() 688 { 689 this._refreshIfNeeded(); 690 }, 691 692 willHide: function() 693 { 694 this._popoverHelper.hidePopover(); 695 }, 696 697 refresh: function() 698 { 699 this._needsRefresh = false; 700 if (this._refreshTimeout) { 701 clearTimeout(this._refreshTimeout); 702 delete this._refreshTimeout; 703 } 704 705 this._removeAllNodeHighlights(); 706 var wasScrolledToLastRow = this._dataGrid.isScrolledToLastRow(); 707 var boundariesChanged = false; 708 if (this.calculator.updateBoundariesForEventTime) { 709 boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestLoadTime) || boundariesChanged; 710 boundariesChanged = this.calculator.updateBoundariesForEventTime(this._mainRequestDOMContentLoadedTime) || boundariesChanged; 711 } 712 713 for (var requestId in this._staleRequests) { 714 var request = this._staleRequests[requestId]; 715 var node = this._requestGridNode(request); 716 if (!node) { 717 // Create the timeline tree element and graph. 718 node = this._createRequestGridNode(request); 719 this._dataGrid.rootNode().appendChild(node); 720 } 721 node.refreshRequest(); 722 this._applyFilter(node); 723 724 if (this.calculator.updateBoundaries(request)) 725 boundariesChanged = true; 726 727 if (!node.isFilteredOut()) 728 this._updateHighlightIfMatched(request); 729 } 730 731 if (boundariesChanged) { 732 // The boundaries changed, so all item graphs are stale. 733 this._invalidateAllItems(); 734 } 735 736 for (var requestId in this._staleRequests) 737 this._requestGridNode(this._staleRequests[requestId]).refreshGraph(this.calculator); 738 739 this._staleRequests = {}; 740 this._sortItems(); 741 this._updateSummaryBar(); 742 this._dataGrid.updateWidths(); 743 // FIXME: evaluate performance impact of moving this before a call to sortItems() 744 if (wasScrolledToLastRow) 745 this._dataGrid.scrollToLastRow(); 746 }, 747 748 _onPreserveLogClicked: function(e) 749 { 750 this._preserveLogToggle.toggled = !this._preserveLogToggle.toggled; 751 }, 752 753 _reset: function() 754 { 755 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.ViewCleared); 756 757 this._clearSearchMatchedList(); 758 if (this._popoverHelper) 759 this._popoverHelper.hidePopover(); 760 761 if (this._calculator) 762 this._calculator.reset(); 763 764 this._requests = []; 765 this._requestsById = {}; 766 this._requestsByURL = {}; 767 this._staleRequests = {}; 768 this._requestGridNodes = {}; 769 770 if (this._dataGrid) { 771 this._dataGrid.rootNode().removeChildren(); 772 this._updateDividersIfNeeded(); 773 this._updateSummaryBar(); 774 } 775 776 this._mainRequestLoadTime = -1; 777 this._mainRequestDOMContentLoadedTime = -1; 778 }, 779 780 get requests() 781 { 782 return this._requests; 783 }, 784 785 requestById: function(id) 786 { 787 return this._requestsById[id]; 788 }, 789 790 _onRequestStarted: function(event) 791 { 792 this._appendRequest(event.data); 793 }, 794 795 _appendRequest: function(request) 796 { 797 this._requests.push(request); 798 799 // In case of redirect request id is reassigned to a redirected 800 // request and we need to update _requestsById and search results. 801 if (this._requestsById[request.requestId]) { 802 var oldRequest = request.redirects[request.redirects.length - 1]; 803 this._requestsById[oldRequest.requestId] = oldRequest; 804 805 this._updateSearchMatchedListAfterRequestIdChanged(request.requestId, oldRequest.requestId); 806 } 807 this._requestsById[request.requestId] = request; 808 809 this._requestsByURL[request.url] = request; 810 811 // Pull all the redirects of the main request upon commit load. 812 if (request.redirects) { 813 for (var i = 0; i < request.redirects.length; ++i) 814 this._refreshRequest(request.redirects[i]); 815 } 816 817 this._refreshRequest(request); 818 }, 819 820 /** 821 * @param {WebInspector.Event} event 822 */ 823 _onRequestUpdated: function(event) 824 { 825 var request = /** @type {WebInspector.NetworkRequest} */ (event.data); 826 this._refreshRequest(request); 827 }, 828 829 /** 830 * @param {WebInspector.NetworkRequest} request 831 */ 832 _refreshRequest: function(request) 833 { 834 this._staleRequests[request.requestId] = request; 835 this._scheduleRefresh(); 836 }, 837 838 clear: function() 839 { 840 if (this._preserveLogToggle.toggled) 841 return; 842 this._reset(); 843 }, 844 845 _mainFrameNavigated: function(event) 846 { 847 if (this._preserveLogToggle.toggled) 848 return; 849 850 var frame = /** @type {WebInspector.ResourceTreeFrame} */ (event.data); 851 var loaderId = frame.loaderId; 852 853 // Preserve provisional load requests. 854 var requestsToPreserve = []; 855 for (var i = 0; i < this._requests.length; ++i) { 856 var request = this._requests[i]; 857 if (request.loaderId === loaderId) 858 requestsToPreserve.push(request); 859 } 860 861 this._reset(); 862 863 // Restore preserved items. 864 for (var i = 0; i < requestsToPreserve.length; ++i) 865 this._appendRequest(requestsToPreserve[i]); 866 }, 867 868 switchToDetailedView: function() 869 { 870 if (!this._dataGrid) 871 return; 872 if (this._dataGrid.selectedNode) 873 this._dataGrid.selectedNode.selected = false; 874 875 this.element.removeStyleClass("brief-mode"); 876 this._detailedMode = true; 877 this._updateColumns(); 878 }, 879 880 switchToBriefView: function() 881 { 882 this.element.addStyleClass("brief-mode"); 883 this._removeAllNodeHighlights(); 884 this._detailedMode = false; 885 this._updateColumns(); 886 this._popoverHelper.hidePopover(); 887 }, 888 889 _toggleLargerRequests: function() 890 { 891 WebInspector.settings.resourcesLargeRows.set(!WebInspector.settings.resourcesLargeRows.get()); 892 this._setLargerRequests(WebInspector.settings.resourcesLargeRows.get()); 893 }, 894 895 _setLargerRequests: function(enabled) 896 { 897 this._largerRequestsButton.toggled = enabled; 898 if (!enabled) { 899 this._largerRequestsButton.title = WebInspector.UIString("Use large resource rows."); 900 this._dataGrid.element.addStyleClass("small"); 901 this._timelineGrid.element.addStyleClass("small"); 902 } else { 903 this._largerRequestsButton.title = WebInspector.UIString("Use small resource rows."); 904 this._dataGrid.element.removeStyleClass("small"); 905 this._timelineGrid.element.removeStyleClass("small"); 906 } 907 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, { largeRows: enabled }); 908 this._updateOffscreenRows(); 909 }, 910 911 _getPopoverAnchor: function(element) 912 { 913 if (!this._allowPopover) 914 return; 915 var anchor = element.enclosingNodeOrSelfWithClass("network-graph-bar") || element.enclosingNodeOrSelfWithClass("network-graph-label"); 916 if (anchor && anchor.parentElement.request && anchor.parentElement.request.timing) 917 return anchor; 918 anchor = element.enclosingNodeOrSelfWithClass("network-script-initiated"); 919 if (anchor && anchor.request && anchor.request.initiator) 920 return anchor; 921 922 return null; 923 }, 924 925 /** 926 * @param {Element} anchor 927 * @param {WebInspector.Popover} popover 928 */ 929 _showPopover: function(anchor, popover) 930 { 931 var content; 932 if (anchor.hasStyleClass("network-script-initiated")) 933 content = this._generateScriptInitiatedPopoverContent(anchor.request); 934 else 935 content = WebInspector.RequestTimingView.createTimingTable(anchor.parentElement.request); 936 popover.show(content, anchor); 937 }, 938 939 _onHidePopover: function() 940 { 941 this._linkifier.reset(); 942 }, 943 944 /** 945 * @param {!WebInspector.NetworkRequest} request 946 * @return {!Element} 947 */ 948 _generateScriptInitiatedPopoverContent: function(request) 949 { 950 var stackTrace = request.initiator.stackTrace; 951 var framesTable = document.createElement("table"); 952 for (var i = 0; i < stackTrace.length; ++i) { 953 var stackFrame = stackTrace[i]; 954 var row = document.createElement("tr"); 955 row.createChild("td").textContent = stackFrame.functionName ? stackFrame.functionName : WebInspector.UIString("(anonymous function)"); 956 row.createChild("td").textContent = " @ "; 957 row.createChild("td").appendChild(this._linkifier.linkifyLocation(stackFrame.url, stackFrame.lineNumber - 1, stackFrame.columnNumber - 1)); 958 framesTable.appendChild(row); 959 } 960 return framesTable; 961 }, 962 963 _updateColumns: function() 964 { 965 var columnsVisibility = this._coulmnsVisibilitySetting.get(); 966 var detailedMode = !!this._detailedMode; 967 for (var columnIdentifier in columnsVisibility) { 968 var visible = detailedMode && columnsVisibility[columnIdentifier]; 969 this._dataGrid.setColumnVisible(columnIdentifier, visible); 970 } 971 this._dataGrid.setColumnVisible("timeline", detailedMode); 972 this._dataGrid.applyColumnWeights(); 973 }, 974 975 /** 976 * @param {string} columnIdentifier 977 */ 978 _toggleColumnVisibility: function(columnIdentifier) 979 { 980 var columnsVisibility = this._coulmnsVisibilitySetting.get(); 981 columnsVisibility[columnIdentifier] = !columnsVisibility[columnIdentifier]; 982 this._coulmnsVisibilitySetting.set(columnsVisibility); 983 984 this._updateColumns(); 985 }, 986 987 /** 988 * @return {!Array.<string>} 989 */ 990 _getConfigurableColumnIDs: function() 991 { 992 if (this._configurableColumnIDs) 993 return this._configurableColumnIDs; 994 995 var columns = this._dataGrid.columns; 996 function compare(id1, id2) 997 { 998 return columns[id1].title.compareTo(columns[id2].title); 999 } 1000 1001 var columnIDs = Object.keys(this._coulmnsVisibilitySetting.get()); 1002 this._configurableColumnIDs = columnIDs.sort(compare); 1003 return this._configurableColumnIDs; 1004 }, 1005 1006 _contextMenu: function(event) 1007 { 1008 var contextMenu = new WebInspector.ContextMenu(event); 1009 1010 if (this._detailedMode && event.target.isSelfOrDescendant(this._dataGrid.headerTableBody)) { 1011 var columnsVisibility = this._coulmnsVisibilitySetting.get(); 1012 var columnIDs = this._getConfigurableColumnIDs(); 1013 for (var i = 0; i < columnIDs.length; ++i) { 1014 var columnIdentifier = columnIDs[i]; 1015 var column = this._dataGrid.columns[columnIdentifier]; 1016 contextMenu.appendCheckboxItem(column.title, this._toggleColumnVisibility.bind(this, columnIdentifier), !!columnsVisibility[columnIdentifier]); 1017 } 1018 contextMenu.show(); 1019 return; 1020 } 1021 1022 var gridNode = this._dataGrid.dataGridNodeFromNode(event.target); 1023 var request = gridNode && gridNode._request; 1024 1025 if (request) { 1026 contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, request.url, false)); 1027 contextMenu.appendSeparator(); 1028 contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), this._copyLocation.bind(this, request)); 1029 if (request.requestHeadersText) 1030 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy request headers" : "Copy Request Headers"), this._copyRequestHeaders.bind(this, request)); 1031 if (request.responseHeadersText) 1032 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy response headers" : "Copy Response Headers"), this._copyResponseHeaders.bind(this, request)); 1033 contextMenu.appendItem(WebInspector.UIString("Copy as cURL"), this._copyCurlCommand.bind(this, request)); 1034 } 1035 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy all as HAR" : "Copy All as HAR"), this._copyAll.bind(this)); 1036 1037 contextMenu.appendSeparator(); 1038 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as HAR with content" : "Save as HAR with Content"), this._exportAll.bind(this)); 1039 1040 contextMenu.appendSeparator(); 1041 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cache" : "Clear Browser Cache"), this._clearBrowserCache.bind(this)); 1042 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Clear browser cookies" : "Clear Browser Cookies"), this._clearBrowserCookies.bind(this)); 1043 1044 if (request && request.type === WebInspector.resourceTypes.XHR) { 1045 contextMenu.appendSeparator(); 1046 contextMenu.appendItem(WebInspector.UIString("Replay XHR"), this._replayXHR.bind(this, request.requestId)); 1047 contextMenu.appendSeparator(); 1048 } 1049 1050 contextMenu.show(); 1051 }, 1052 1053 _replayXHR: function(requestId) 1054 { 1055 NetworkAgent.replayXHR(requestId); 1056 }, 1057 1058 _copyAll: function() 1059 { 1060 var harArchive = { 1061 log: (new WebInspector.HARLog(this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter))).build() 1062 }; 1063 InspectorFrontendHost.copyText(JSON.stringify(harArchive, null, 2)); 1064 }, 1065 1066 _copyLocation: function(request) 1067 { 1068 InspectorFrontendHost.copyText(request.url); 1069 }, 1070 1071 _copyRequestHeaders: function(request) 1072 { 1073 InspectorFrontendHost.copyText(request.requestHeadersText); 1074 }, 1075 1076 _copyResponseHeaders: function(request) 1077 { 1078 InspectorFrontendHost.copyText(request.responseHeadersText); 1079 }, 1080 1081 /** 1082 * @param {WebInspector.NetworkRequest} request 1083 */ 1084 _copyCurlCommand: function(request) 1085 { 1086 InspectorFrontendHost.copyText(this._generateCurlCommand(request)); 1087 }, 1088 1089 _exportAll: function() 1090 { 1091 var filename = WebInspector.inspectedPageDomain + ".har"; 1092 var stream = new WebInspector.FileOutputStream(); 1093 stream.open(filename, openCallback.bind(this)); 1094 function openCallback() 1095 { 1096 var progressIndicator = new WebInspector.ProgressIndicator(); 1097 this._progressBarContainer.appendChild(progressIndicator.element); 1098 var harWriter = new WebInspector.HARWriter(); 1099 harWriter.write(stream, this._requests.filter(WebInspector.NetworkLogView.HTTPRequestsFilter), progressIndicator); 1100 } 1101 }, 1102 1103 _clearBrowserCache: function() 1104 { 1105 if (confirm(WebInspector.UIString("Are you sure you want to clear browser cache?"))) 1106 NetworkAgent.clearBrowserCache(); 1107 }, 1108 1109 _clearBrowserCookies: function() 1110 { 1111 if (confirm(WebInspector.UIString("Are you sure you want to clear browser cookies?"))) 1112 NetworkAgent.clearBrowserCookies(); 1113 }, 1114 1115 _updateOffscreenRows: function() 1116 { 1117 var dataTableBody = this._dataGrid.dataTableBody; 1118 var rows = dataTableBody.children; 1119 var recordsCount = rows.length; 1120 if (recordsCount < 2) 1121 return; // Filler row only. 1122 1123 var visibleTop = this._dataGrid.scrollContainer.scrollTop; 1124 var visibleBottom = visibleTop + this._dataGrid.scrollContainer.offsetHeight; 1125 1126 var rowHeight = 0; 1127 1128 // Filler is at recordsCount - 1. 1129 var unfilteredRowIndex = 0; 1130 for (var i = 0; i < recordsCount - 1; ++i) { 1131 var row = rows[i]; 1132 1133 var dataGridNode = this._dataGrid.dataGridNodeFromNode(row); 1134 if (dataGridNode.isFilteredOut()) { 1135 row.removeStyleClass("offscreen"); 1136 continue; 1137 } 1138 1139 if (!rowHeight) 1140 rowHeight = row.offsetHeight; 1141 1142 var rowIsVisible = unfilteredRowIndex * rowHeight < visibleBottom && (unfilteredRowIndex + 1) * rowHeight > visibleTop; 1143 if (rowIsVisible !== row.rowIsVisible) { 1144 row.enableStyleClass("offscreen", !rowIsVisible); 1145 row.rowIsVisible = rowIsVisible; 1146 } 1147 unfilteredRowIndex++; 1148 } 1149 }, 1150 1151 _matchRequest: function(request) 1152 { 1153 if (!this._searchRegExp) 1154 return -1; 1155 1156 if (!request.name().match(this._searchRegExp) && !request.path().match(this._searchRegExp)) 1157 return -1; 1158 1159 if (request.requestId in this._matchedRequestsMap) 1160 return this._matchedRequestsMap[request.requestId]; 1161 1162 var matchedRequestIndex = this._matchedRequests.length; 1163 this._matchedRequestsMap[request.requestId] = matchedRequestIndex; 1164 this._matchedRequests.push(request.requestId); 1165 1166 return matchedRequestIndex; 1167 }, 1168 1169 _clearSearchMatchedList: function() 1170 { 1171 delete this._searchRegExp; 1172 this._matchedRequests = []; 1173 this._matchedRequestsMap = {}; 1174 this._removeAllHighlights(); 1175 }, 1176 1177 _updateSearchMatchedListAfterRequestIdChanged: function(oldRequestId, newRequestId) 1178 { 1179 var requestIndex = this._matchedRequestsMap[oldRequestId]; 1180 if (requestIndex) { 1181 delete this._matchedRequestsMap[oldRequestId]; 1182 this._matchedRequestsMap[newRequestId] = requestIndex; 1183 this._matchedRequests[requestIndex] = newRequestId; 1184 } 1185 }, 1186 1187 _updateHighlightIfMatched: function(request) 1188 { 1189 var matchedRequestIndex = this._matchRequest(request); 1190 if (matchedRequestIndex === -1) 1191 return; 1192 1193 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length); 1194 1195 if (this._currentMatchedRequestIndex !== -1 && this._currentMatchedRequestIndex !== matchedRequestIndex) 1196 return; 1197 1198 this._highlightNthMatchedRequestForSearch(matchedRequestIndex, false); 1199 }, 1200 1201 _removeAllHighlights: function() 1202 { 1203 this._removeAllNodeHighlights(); 1204 for (var i = 0; i < this._highlightedSubstringChanges.length; ++i) 1205 WebInspector.revertDomChanges(this._highlightedSubstringChanges[i]); 1206 this._highlightedSubstringChanges = []; 1207 }, 1208 1209 /** 1210 * @param {WebInspector.NetworkRequest} request 1211 * @param {boolean} reveal 1212 * @param {RegExp=} regExp 1213 */ 1214 _highlightMatchedRequest: function(request, reveal, regExp) 1215 { 1216 var node = this._requestGridNode(request); 1217 if (!node) 1218 return; 1219 1220 var nameMatched = request.name().match(regExp); 1221 var pathMatched = request.path().match(regExp); 1222 if (!nameMatched && pathMatched && !this._largerRequestsButton.toggled) 1223 this._toggleLargerRequests(); 1224 var highlightedSubstringChanges = node._highlightMatchedSubstring(regExp); 1225 this._highlightedSubstringChanges.push(highlightedSubstringChanges); 1226 if (reveal) { 1227 node.reveal(); 1228 this._highlightNode(node); 1229 } 1230 }, 1231 1232 /** 1233 * @param {number} matchedRequestIndex 1234 * @param {boolean} reveal 1235 */ 1236 _highlightNthMatchedRequestForSearch: function(matchedRequestIndex, reveal) 1237 { 1238 var request = this.requestById(this._matchedRequests[matchedRequestIndex]); 1239 if (!request) 1240 return; 1241 this._removeAllHighlights(); 1242 this._highlightMatchedRequest(request, reveal, this._searchRegExp); 1243 var node = this._requestGridNode(request); 1244 if (node) 1245 this._currentMatchedRequestIndex = matchedRequestIndex; 1246 1247 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._currentMatchedRequestIndex); 1248 }, 1249 1250 /** 1251 * @param {string} query 1252 * @param {boolean} shouldJump 1253 */ 1254 performSearch: function(query, shouldJump) 1255 { 1256 var newMatchedRequestIndex = 0; 1257 var currentMatchedRequestId; 1258 if (this._currentMatchedRequestIndex !== -1) 1259 currentMatchedRequestId = this._matchedRequests[this._currentMatchedRequestIndex]; 1260 1261 this._clearSearchMatchedList(); 1262 this._searchRegExp = createPlainTextSearchRegex(query, "i"); 1263 1264 var childNodes = this._dataGrid.dataTableBody.childNodes; 1265 var requestNodes = Array.prototype.slice.call(childNodes, 0, childNodes.length - 1); // drop the filler row. 1266 1267 for (var i = 0; i < requestNodes.length; ++i) { 1268 var dataGridNode = this._dataGrid.dataGridNodeFromNode(requestNodes[i]); 1269 if (dataGridNode.isFilteredOut()) 1270 continue; 1271 if (this._matchRequest(dataGridNode._request) !== -1 && dataGridNode._request.requestId === currentMatchedRequestId) 1272 newMatchedRequestIndex = this._matchedRequests.length - 1; 1273 } 1274 1275 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._matchedRequests.length); 1276 if (shouldJump) 1277 this._highlightNthMatchedRequestForSearch(newMatchedRequestIndex, true); 1278 }, 1279 1280 /** 1281 * @param {!WebInspector.NetworkDataGridNode} node 1282 */ 1283 _applyFilter: function(node) 1284 { 1285 var filter = this._filterRegExp; 1286 var request = node._request; 1287 var matches = false; 1288 if (this._typeFilter(request)) { 1289 matches = !filter || filter.test(request.name()) || filter.test(request.path()); 1290 if (filter && matches) 1291 this._highlightMatchedRequest(request, false, filter); 1292 } 1293 node.element.enableStyleClass("filtered-out", !matches); 1294 if (matches) 1295 this._filteredOutRequests.remove(request); 1296 else 1297 this._filteredOutRequests.put(request, true); 1298 }, 1299 1300 /** 1301 * @param {string} query 1302 */ 1303 performFilter: function(query) 1304 { 1305 delete this._filterRegExp; 1306 if (query) 1307 this._filterRegExp = createPlainTextSearchRegex(query, "i"); 1308 this._filterRequests(); 1309 }, 1310 1311 _filterRequests: function() 1312 { 1313 this._removeAllHighlights(); 1314 this._filteredOutRequests.clear(); 1315 1316 var nodes = this._dataGrid.rootNode().children; 1317 for (var i = 0; i < nodes.length; ++i) 1318 this._applyFilter(nodes[i]); 1319 this._updateSummaryBar(); 1320 this._updateOffscreenRows(); 1321 }, 1322 1323 jumpToPreviousSearchResult: function() 1324 { 1325 if (!this._matchedRequests.length) 1326 return; 1327 this._highlightNthMatchedRequestForSearch((this._currentMatchedRequestIndex + this._matchedRequests.length - 1) % this._matchedRequests.length, true); 1328 }, 1329 1330 jumpToNextSearchResult: function() 1331 { 1332 if (!this._matchedRequests.length) 1333 return; 1334 this._highlightNthMatchedRequestForSearch((this._currentMatchedRequestIndex + 1) % this._matchedRequests.length, true); 1335 }, 1336 1337 searchCanceled: function() 1338 { 1339 this._clearSearchMatchedList(); 1340 this.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, 0); 1341 }, 1342 1343 revealAndHighlightRequest: function(request) 1344 { 1345 this._removeAllNodeHighlights(); 1346 1347 var node = this._requestGridNode(request); 1348 if (node) { 1349 this._dataGrid.element.focus(); 1350 node.reveal(); 1351 this._highlightNode(node); 1352 } 1353 }, 1354 1355 _removeAllNodeHighlights: function() 1356 { 1357 if (this._highlightedNode) { 1358 this._highlightedNode.element.removeStyleClass("highlighted-row"); 1359 delete this._highlightedNode; 1360 } 1361 }, 1362 1363 _highlightNode: function(node) 1364 { 1365 node.element.addStyleClass("highlighted-row"); 1366 this._highlightedNode = node; 1367 }, 1368 1369 /** 1370 * @param {WebInspector.NetworkRequest} request 1371 * @return {string} 1372 */ 1373 _generateCurlCommand: function(request) 1374 { 1375 var command = ["curl"]; 1376 var ignoredHeaders = {}; 1377 1378 function escapeCharacter(x) 1379 { 1380 var code = x.charCodeAt(0); 1381 if (code < 256) { 1382 // Add leading zero when needed to not care about the next character. 1383 return code < 16 ? "\\x0" + code.toString(16) : "\\x" + code.toString(16); 1384 } 1385 code = code.toString(16); 1386 return "\\u" + ("0000" + code).substr(code.length, 4); 1387 } 1388 1389 function escape(str) 1390 { 1391 if (/[^\x20-\x7E]|\'/.test(str)) { 1392 // Use ANSI-C quoting syntax. 1393 return "$\'" + str.replace(/\\/g, "\\\\") 1394 .replace(/\'/g, "\\\'") 1395 .replace(/\n/g, "\\n") 1396 .replace(/\r/g, "\\r") 1397 .replace(/[^\x20-\x7E]/g, escapeCharacter) + "'"; 1398 } else { 1399 // Use single quote syntax. 1400 return "'" + str + "'"; 1401 } 1402 } 1403 command.push(escape(request.url)); 1404 1405 var inferredMethod = "GET"; 1406 var data = []; 1407 var requestContentType = request.requestContentType(); 1408 if (requestContentType && requestContentType.startsWith("application/x-www-form-urlencoded") && request.requestFormData) { 1409 data.push("--data"); 1410 data.push(escape(request.requestFormData)); 1411 ignoredHeaders["Content-Length"] = true; 1412 inferredMethod = "POST"; 1413 } else if (request.requestFormData) { 1414 data.push("--data-binary"); 1415 data.push(escape(request.requestFormData)); 1416 ignoredHeaders["Content-Length"] = true; 1417 inferredMethod = "POST"; 1418 } 1419 1420 if (request.requestMethod !== inferredMethod) { 1421 command.push("-X"); 1422 command.push(request.requestMethod); 1423 } 1424 1425 for (var i = 0; i < request.requestHeaders.length; i++) { 1426 var header = request.requestHeaders[i]; 1427 if (header.name in ignoredHeaders) 1428 continue; 1429 command.push("-H"); 1430 command.push(escape(header.name + ": " + header.value)); 1431 } 1432 command = command.concat(data); 1433 command.push("--compressed"); 1434 return command.join(" "); 1435 }, 1436 1437 __proto__: WebInspector.View.prototype 1438 } 1439 1440 /** 1441 * @param {!WebInspector.NetworkRequest} request 1442 * @return {boolean} 1443 */ 1444 WebInspector.NetworkLogView.HTTPRequestsFilter = function(request) 1445 { 1446 return request.parsedURL.isValid && (request.parsedURL.scheme in WebInspector.NetworkLogView.HTTPSchemas); 1447 } 1448 1449 1450 WebInspector.NetworkLogView.EventTypes = { 1451 ViewCleared: "ViewCleared", 1452 RowSizeChanged: "RowSizeChanged", 1453 RequestSelected: "RequestSelected", 1454 SearchCountUpdated: "SearchCountUpdated", 1455 SearchIndexUpdated: "SearchIndexUpdated" 1456 }; 1457 1458 /** 1459 * @constructor 1460 * @extends {WebInspector.Panel} 1461 * @implements {WebInspector.ContextMenu.Provider} 1462 */ 1463 WebInspector.NetworkPanel = function() 1464 { 1465 WebInspector.Panel.call(this, "network"); 1466 this.registerRequiredCSS("networkPanel.css"); 1467 this._injectStyles(); 1468 1469 this.createSidebarView(); 1470 this.splitView.hideMainElement(); 1471 1472 var defaultColumnsVisibility = WebInspector.NetworkLogView._defaultColumnsVisibility; 1473 var networkLogColumnsVisibilitySetting = WebInspector.settings.createSetting("networkLogColumnsVisibility", defaultColumnsVisibility); 1474 var savedColumnsVisibility = networkLogColumnsVisibilitySetting.get(); 1475 var columnsVisibility = {}; 1476 for (var columnId in defaultColumnsVisibility) 1477 columnsVisibility[columnId] = savedColumnsVisibility.hasOwnProperty(columnId) ? savedColumnsVisibility[columnId] : defaultColumnsVisibility[columnId]; 1478 networkLogColumnsVisibilitySetting.set(columnsVisibility); 1479 1480 this._networkLogView = new WebInspector.NetworkLogView(networkLogColumnsVisibilitySetting); 1481 this._networkLogView.show(this.sidebarElement); 1482 1483 this._viewsContainerElement = this.splitView.mainElement; 1484 this._viewsContainerElement.id = "network-views"; 1485 this._viewsContainerElement.addStyleClass("hidden"); 1486 if (!this._networkLogView.useLargeRows) 1487 this._viewsContainerElement.addStyleClass("small"); 1488 1489 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.ViewCleared, this._onViewCleared, this); 1490 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RowSizeChanged, this._onRowSizeChanged, this); 1491 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._onRequestSelected, this); 1492 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchCountUpdated, this._onSearchCountUpdated, this); 1493 this._networkLogView.addEventListener(WebInspector.NetworkLogView.EventTypes.SearchIndexUpdated, this._onSearchIndexUpdated, this); 1494 1495 this._closeButtonElement = this._viewsContainerElement.createChild("div", "close-button"); 1496 this._closeButtonElement.id = "network-close-button"; 1497 this._closeButtonElement.addEventListener("click", this._toggleGridMode.bind(this), false); 1498 this._viewsContainerElement.appendChild(this._closeButtonElement); 1499 1500 function viewGetter() 1501 { 1502 return this.visibleView; 1503 } 1504 WebInspector.GoToLineDialog.install(this, viewGetter.bind(this)); 1505 } 1506 1507 WebInspector.NetworkPanel.prototype = { 1508 get statusBarItems() 1509 { 1510 return this._networkLogView.statusBarItems; 1511 }, 1512 1513 elementsToRestoreScrollPositionsFor: function() 1514 { 1515 return this._networkLogView.elementsToRestoreScrollPositionsFor(); 1516 }, 1517 1518 // FIXME: only used by the layout tests, should not be exposed. 1519 _reset: function() 1520 { 1521 this._networkLogView._reset(); 1522 }, 1523 1524 handleShortcut: function(event) 1525 { 1526 if (this._viewingRequestMode && event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) { 1527 this._toggleGridMode(); 1528 event.handled = true; 1529 return; 1530 } 1531 1532 WebInspector.Panel.prototype.handleShortcut.call(this, event); 1533 }, 1534 1535 wasShown: function() 1536 { 1537 WebInspector.Panel.prototype.wasShown.call(this); 1538 }, 1539 1540 get requests() 1541 { 1542 return this._networkLogView.requests; 1543 }, 1544 1545 requestById: function(id) 1546 { 1547 return this._networkLogView.requestById(id); 1548 }, 1549 1550 _requestByAnchor: function(anchor) 1551 { 1552 return anchor.requestId ? this.requestById(anchor.requestId) : this._networkLogView._requestsByURL[anchor.href]; 1553 }, 1554 1555 canShowAnchorLocation: function(anchor) 1556 { 1557 return !!this._requestByAnchor(anchor); 1558 }, 1559 1560 showAnchorLocation: function(anchor) 1561 { 1562 var request = this._requestByAnchor(anchor); 1563 this.revealAndHighlightRequest(request) 1564 }, 1565 1566 revealAndHighlightRequest: function(request) 1567 { 1568 this._toggleGridMode(); 1569 if (request) 1570 this._networkLogView.revealAndHighlightRequest(request); 1571 }, 1572 1573 _onViewCleared: function(event) 1574 { 1575 this._closeVisibleRequest(); 1576 this._toggleGridMode(); 1577 this._viewsContainerElement.removeChildren(); 1578 this._viewsContainerElement.appendChild(this._closeButtonElement); 1579 }, 1580 1581 _onRowSizeChanged: function(event) 1582 { 1583 this._viewsContainerElement.enableStyleClass("small", !event.data.largeRows); 1584 }, 1585 1586 _onSearchCountUpdated: function(event) 1587 { 1588 WebInspector.searchController.updateSearchMatchesCount(event.data, this); 1589 }, 1590 1591 _onSearchIndexUpdated: function(event) 1592 { 1593 WebInspector.searchController.updateCurrentMatchIndex(event.data, this); 1594 }, 1595 1596 _onRequestSelected: function(event) 1597 { 1598 this._showRequest(event.data); 1599 }, 1600 1601 _showRequest: function(request) 1602 { 1603 if (!request) 1604 return; 1605 1606 this._toggleViewingRequestMode(); 1607 1608 if (this.visibleView) { 1609 this.visibleView.detach(); 1610 delete this.visibleView; 1611 } 1612 1613 var view = new WebInspector.NetworkItemView(request); 1614 view.show(this._viewsContainerElement); 1615 this.visibleView = view; 1616 }, 1617 1618 _closeVisibleRequest: function() 1619 { 1620 this.element.removeStyleClass("viewing-resource"); 1621 1622 if (this.visibleView) { 1623 this.visibleView.detach(); 1624 delete this.visibleView; 1625 } 1626 }, 1627 1628 _toggleGridMode: function() 1629 { 1630 if (this._viewingRequestMode) { 1631 this._viewingRequestMode = false; 1632 this.element.removeStyleClass("viewing-resource"); 1633 this.splitView.hideMainElement(); 1634 } 1635 1636 this._networkLogView.switchToDetailedView(); 1637 this._networkLogView.allowPopover = true; 1638 this._networkLogView._allowRequestSelection = false; 1639 }, 1640 1641 _toggleViewingRequestMode: function() 1642 { 1643 if (this._viewingRequestMode) 1644 return; 1645 this._viewingRequestMode = true; 1646 1647 this.element.addStyleClass("viewing-resource"); 1648 this.splitView.showMainElement(); 1649 this._networkLogView.allowPopover = false; 1650 this._networkLogView._allowRequestSelection = true; 1651 this._networkLogView.switchToBriefView(); 1652 }, 1653 1654 /** 1655 * @param {string} query 1656 * @param {boolean} shouldJump 1657 */ 1658 performSearch: function(query, shouldJump) 1659 { 1660 this._networkLogView.performSearch(query, shouldJump); 1661 }, 1662 1663 /** 1664 * @return {boolean} 1665 */ 1666 canFilter: function() 1667 { 1668 return true; 1669 }, 1670 1671 /** 1672 * @param {string} query 1673 */ 1674 performFilter: function(query) 1675 { 1676 this._networkLogView.performFilter(query); 1677 }, 1678 1679 jumpToPreviousSearchResult: function() 1680 { 1681 this._networkLogView.jumpToPreviousSearchResult(); 1682 }, 1683 1684 jumpToNextSearchResult: function() 1685 { 1686 this._networkLogView.jumpToNextSearchResult(); 1687 }, 1688 1689 searchCanceled: function() 1690 { 1691 this._networkLogView.searchCanceled(); 1692 }, 1693 1694 /** 1695 * @param {WebInspector.ContextMenu} contextMenu 1696 * @param {Object} target 1697 */ 1698 appendApplicableItems: function(event, contextMenu, target) 1699 { 1700 if (!(target instanceof WebInspector.NetworkRequest)) 1701 return; 1702 if (this.visibleView && this.visibleView.isShowing() && this.visibleView.request() === target) 1703 return; 1704 1705 function reveal() 1706 { 1707 WebInspector.inspectorView.setCurrentPanel(this); 1708 this.revealAndHighlightRequest(/** @type {WebInspector.NetworkRequest} */ (target)); 1709 } 1710 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Reveal in Network panel" : "Reveal in Network Panel"), reveal.bind(this)); 1711 }, 1712 1713 _injectStyles: function() 1714 { 1715 var style = document.createElement("style"); 1716 var rules = []; 1717 1718 var columns = WebInspector.NetworkLogView._defaultColumnsVisibility; 1719 1720 var hideSelectors = []; 1721 var bgSelectors = []; 1722 for (var columnId in columns) { 1723 hideSelectors.push("#network-container .hide-" + columnId + "-column ." + columnId + "-column"); 1724 bgSelectors.push(".network-log-grid.data-grid td." + columnId + "-column"); 1725 } 1726 rules.push(hideSelectors.join(", ") + "{border-right: 0 none transparent;}"); 1727 rules.push(bgSelectors.join(", ") + "{background-color: rgba(0, 0, 0, 0.07);}"); 1728 1729 style.textContent = rules.join("\n"); 1730 document.head.appendChild(style); 1731 }, 1732 1733 __proto__: WebInspector.Panel.prototype 1734 } 1735 1736 /** 1737 * @constructor 1738 * @implements {WebInspector.TimelineGrid.Calculator} 1739 */ 1740 WebInspector.NetworkBaseCalculator = function() 1741 { 1742 } 1743 1744 WebInspector.NetworkBaseCalculator.prototype = { 1745 computePosition: function(time) 1746 { 1747 return (time - this._minimumBoundary) / this.boundarySpan() * this._workingArea; 1748 }, 1749 1750 computeBarGraphPercentages: function(item) 1751 { 1752 return {start: 0, middle: 0, end: (this._value(item) / this.boundarySpan()) * 100}; 1753 }, 1754 1755 computeBarGraphLabels: function(item) 1756 { 1757 const label = this.formatTime(this._value(item)); 1758 return {left: label, right: label, tooltip: label}; 1759 }, 1760 1761 boundarySpan: function() 1762 { 1763 return this._maximumBoundary - this._minimumBoundary; 1764 }, 1765 1766 updateBoundaries: function(item) 1767 { 1768 this._minimumBoundary = 0; 1769 1770 var value = this._value(item); 1771 if (typeof this._maximumBoundary === "undefined" || value > this._maximumBoundary) { 1772 this._maximumBoundary = value; 1773 return true; 1774 } 1775 return false; 1776 }, 1777 1778 reset: function() 1779 { 1780 delete this._minimumBoundary; 1781 delete this._maximumBoundary; 1782 }, 1783 1784 maximumBoundary: function() 1785 { 1786 return this._maximumBoundary; 1787 }, 1788 1789 minimumBoundary: function() 1790 { 1791 return this._minimumBoundary; 1792 }, 1793 1794 zeroTime: function() 1795 { 1796 return this._minimumBoundary; 1797 }, 1798 1799 _value: function(item) 1800 { 1801 return 0; 1802 }, 1803 1804 formatTime: function(value) 1805 { 1806 return value.toString(); 1807 }, 1808 1809 setDisplayWindow: function(clientWidth) 1810 { 1811 this._workingArea = clientWidth; 1812 this.paddingLeft = 0; 1813 } 1814 } 1815 1816 /** 1817 * @constructor 1818 * @extends {WebInspector.NetworkBaseCalculator} 1819 */ 1820 WebInspector.NetworkTimeCalculator = function(startAtZero) 1821 { 1822 WebInspector.NetworkBaseCalculator.call(this); 1823 this.startAtZero = startAtZero; 1824 } 1825 1826 WebInspector.NetworkTimeCalculator.prototype = { 1827 computeBarGraphPercentages: function(request) 1828 { 1829 if (request.startTime !== -1) 1830 var start = ((request.startTime - this._minimumBoundary) / this.boundarySpan()) * 100; 1831 else 1832 var start = 0; 1833 1834 if (request.responseReceivedTime !== -1) 1835 var middle = ((request.responseReceivedTime - this._minimumBoundary) / this.boundarySpan()) * 100; 1836 else 1837 var middle = (this.startAtZero ? start : 100); 1838 1839 if (request.endTime !== -1) 1840 var end = ((request.endTime - this._minimumBoundary) / this.boundarySpan()) * 100; 1841 else 1842 var end = (this.startAtZero ? middle : 100); 1843 1844 if (this.startAtZero) { 1845 end -= start; 1846 middle -= start; 1847 start = 0; 1848 } 1849 1850 return {start: start, middle: middle, end: end}; 1851 }, 1852 1853 computePercentageFromEventTime: function(eventTime) 1854 { 1855 // This function computes a percentage in terms of the total loading time 1856 // of a specific event. If startAtZero is set, then this is useless, and we 1857 // want to return 0. 1858 if (eventTime !== -1 && !this.startAtZero) 1859 return ((eventTime - this._minimumBoundary) / this.boundarySpan()) * 100; 1860 1861 return 0; 1862 }, 1863 1864 updateBoundariesForEventTime: function(eventTime) 1865 { 1866 if (eventTime === -1 || this.startAtZero) 1867 return false; 1868 1869 if (typeof this._maximumBoundary === "undefined" || eventTime > this._maximumBoundary) { 1870 this._maximumBoundary = eventTime; 1871 return true; 1872 } 1873 return false; 1874 }, 1875 1876 computeBarGraphLabels: function(request) 1877 { 1878 var rightLabel = ""; 1879 if (request.responseReceivedTime !== -1 && request.endTime !== -1) 1880 rightLabel = this.formatTime(request.endTime - request.responseReceivedTime); 1881 1882 var hasLatency = request.latency > 0; 1883 if (hasLatency) 1884 var leftLabel = this.formatTime(request.latency); 1885 else 1886 var leftLabel = rightLabel; 1887 1888 if (request.timing) 1889 return {left: leftLabel, right: rightLabel}; 1890 1891 if (hasLatency && rightLabel) { 1892 var total = this.formatTime(request.duration); 1893 var tooltip = WebInspector.UIString("%s latency, %s download (%s total)", leftLabel, rightLabel, total); 1894 } else if (hasLatency) 1895 var tooltip = WebInspector.UIString("%s latency", leftLabel); 1896 else if (rightLabel) 1897 var tooltip = WebInspector.UIString("%s download", rightLabel); 1898 1899 if (request.cached) 1900 tooltip = WebInspector.UIString("%s (from cache)", tooltip); 1901 return {left: leftLabel, right: rightLabel, tooltip: tooltip}; 1902 }, 1903 1904 updateBoundaries: function(request) 1905 { 1906 var didChange = false; 1907 1908 var lowerBound; 1909 if (this.startAtZero) 1910 lowerBound = 0; 1911 else 1912 lowerBound = this._lowerBound(request); 1913 1914 if (lowerBound !== -1 && (typeof this._minimumBoundary === "undefined" || lowerBound < this._minimumBoundary)) { 1915 this._minimumBoundary = lowerBound; 1916 didChange = true; 1917 } 1918 1919 var upperBound = this._upperBound(request); 1920 if (upperBound !== -1 && (typeof this._maximumBoundary === "undefined" || upperBound > this._maximumBoundary)) { 1921 this._maximumBoundary = upperBound; 1922 didChange = true; 1923 } 1924 1925 return didChange; 1926 }, 1927 1928 formatTime: function(value) 1929 { 1930 return Number.secondsToString(value); 1931 }, 1932 1933 _lowerBound: function(request) 1934 { 1935 return 0; 1936 }, 1937 1938 _upperBound: function(request) 1939 { 1940 return 0; 1941 }, 1942 1943 __proto__: WebInspector.NetworkBaseCalculator.prototype 1944 } 1945 1946 /** 1947 * @constructor 1948 * @extends {WebInspector.NetworkTimeCalculator} 1949 */ 1950 WebInspector.NetworkTransferTimeCalculator = function() 1951 { 1952 WebInspector.NetworkTimeCalculator.call(this, false); 1953 } 1954 1955 WebInspector.NetworkTransferTimeCalculator.prototype = { 1956 formatTime: function(value) 1957 { 1958 return Number.secondsToString(value); 1959 }, 1960 1961 _lowerBound: function(request) 1962 { 1963 return request.startTime; 1964 }, 1965 1966 _upperBound: function(request) 1967 { 1968 return request.endTime; 1969 }, 1970 1971 __proto__: WebInspector.NetworkTimeCalculator.prototype 1972 } 1973 1974 /** 1975 * @constructor 1976 * @extends {WebInspector.NetworkTimeCalculator} 1977 */ 1978 WebInspector.NetworkTransferDurationCalculator = function() 1979 { 1980 WebInspector.NetworkTimeCalculator.call(this, true); 1981 } 1982 1983 WebInspector.NetworkTransferDurationCalculator.prototype = { 1984 formatTime: function(value) 1985 { 1986 return Number.secondsToString(value); 1987 }, 1988 1989 _upperBound: function(request) 1990 { 1991 return request.duration; 1992 }, 1993 1994 __proto__: WebInspector.NetworkTimeCalculator.prototype 1995 } 1996 1997 /** 1998 * @constructor 1999 * @extends {WebInspector.DataGridNode} 2000 * @param {!WebInspector.NetworkLogView} parentView 2001 * @param {!WebInspector.NetworkRequest} request 2002 */ 2003 WebInspector.NetworkDataGridNode = function(parentView, request) 2004 { 2005 WebInspector.DataGridNode.call(this, {}); 2006 this._parentView = parentView; 2007 this._request = request; 2008 this._linkifier = new WebInspector.Linkifier(); 2009 } 2010 2011 WebInspector.NetworkDataGridNode.prototype = { 2012 /** override */ 2013 createCells: function() 2014 { 2015 // Out of sight, out of mind: create nodes offscreen to save on render tree update times when running updateOffscreenRows() 2016 this._element.addStyleClass("offscreen"); 2017 this._nameCell = this._createDivInTD("name"); 2018 this._methodCell = this._createDivInTD("method"); 2019 this._statusCell = this._createDivInTD("status"); 2020 this._domainCell = this._createDivInTD("domain"); 2021 this._typeCell = this._createDivInTD("type"); 2022 this._initiatorCell = this._createDivInTD("initiator"); 2023 this._cookiesCell = this._createDivInTD("cookies"); 2024 this._setCookiesCell = this._createDivInTD("setCookies"); 2025 this._sizeCell = this._createDivInTD("size"); 2026 this._timeCell = this._createDivInTD("time"); 2027 2028 this._responseHeaderCells = {}; 2029 var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns; 2030 for (var i = 0; i < responseHeaderColumns.length; ++i) 2031 this._responseHeaderCells[responseHeaderColumns[i]] = this._createDivInTD(responseHeaderColumns[i]); 2032 2033 this._timelineCell = this._createDivInTD("timeline"); 2034 this._createTimelineBar(this._timelineCell); 2035 this._nameCell.addEventListener("click", this._onClick.bind(this), false); 2036 this._nameCell.addEventListener("dblclick", this._openInNewTab.bind(this), false); 2037 }, 2038 2039 wasDetached: function() 2040 { 2041 this._linkifier.reset(); 2042 }, 2043 2044 isFilteredOut: function() 2045 { 2046 if (this._parentView._filteredOutRequests.get(this._request)) 2047 return true; 2048 return !this._parentView._typeFilter(this._request); 2049 }, 2050 2051 _onClick: function() 2052 { 2053 if (!this._parentView._allowRequestSelection) 2054 this.select(); 2055 }, 2056 2057 select: function() 2058 { 2059 this._parentView.dispatchEventToListeners(WebInspector.NetworkLogView.EventTypes.RequestSelected, this._request); 2060 WebInspector.DataGridNode.prototype.select.apply(this, arguments); 2061 2062 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, { 2063 action: WebInspector.UserMetrics.UserActionNames.NetworkRequestSelected, 2064 url: this._request.url 2065 }); 2066 }, 2067 2068 _highlightMatchedSubstring: function(regexp) 2069 { 2070 var domChanges = []; 2071 var matchInfo = this._element.textContent.match(regexp); 2072 if (matchInfo) 2073 WebInspector.highlightSearchResult(this._nameCell, matchInfo.index, matchInfo[0].length, domChanges); 2074 return domChanges; 2075 }, 2076 2077 _openInNewTab: function() 2078 { 2079 InspectorFrontendHost.openInNewTab(this._request.url); 2080 }, 2081 2082 get selectable() 2083 { 2084 return this._parentView._allowRequestSelection && !this.isFilteredOut(); 2085 }, 2086 2087 _createDivInTD: function(columnIdentifier) 2088 { 2089 var td = this.createTD(columnIdentifier); 2090 var div = td.createChild("div"); 2091 this._element.appendChild(td); 2092 return div; 2093 }, 2094 2095 /** 2096 * @param {!Element} cell 2097 */ 2098 _createTimelineBar: function(cell) 2099 { 2100 cell.className = "network-graph-side"; 2101 2102 this._barAreaElement = document.createElement("div"); 2103 // this._barAreaElement.className = "network-graph-bar-area hidden"; 2104 this._barAreaElement.className = "network-graph-bar-area"; 2105 this._barAreaElement.request = this._request; 2106 cell.appendChild(this._barAreaElement); 2107 2108 this._barLeftElement = document.createElement("div"); 2109 this._barLeftElement.className = "network-graph-bar waiting"; 2110 this._barAreaElement.appendChild(this._barLeftElement); 2111 2112 this._barRightElement = document.createElement("div"); 2113 this._barRightElement.className = "network-graph-bar"; 2114 this._barAreaElement.appendChild(this._barRightElement); 2115 2116 2117 this._labelLeftElement = document.createElement("div"); 2118 this._labelLeftElement.className = "network-graph-label waiting"; 2119 this._barAreaElement.appendChild(this._labelLeftElement); 2120 2121 this._labelRightElement = document.createElement("div"); 2122 this._labelRightElement.className = "network-graph-label"; 2123 this._barAreaElement.appendChild(this._labelRightElement); 2124 2125 cell.addEventListener("mouseover", this._refreshLabelPositions.bind(this), false); 2126 }, 2127 2128 refreshRequest: function() 2129 { 2130 this._refreshNameCell(); 2131 this._refreshMethodCell(); 2132 this._refreshStatusCell(); 2133 this._refreshDomainCell(); 2134 this._refreshTypeCell(); 2135 this._refreshInitiatorCell(); 2136 this._refreshCookiesCell(); 2137 this._refreshSetCookiesCell(); 2138 this._refreshSizeCell(); 2139 this._refreshTimeCell(); 2140 2141 var responseHeaderColumns = WebInspector.NetworkLogView._responseHeaderColumns; 2142 for (var i = 0; i < responseHeaderColumns.length; ++i) 2143 this._refreshResponseHeaderCell(responseHeaderColumns[i]); 2144 2145 if (this._request.cached) 2146 this._timelineCell.addStyleClass("resource-cached"); 2147 2148 this._element.addStyleClass("network-item"); 2149 this._element.enableStyleClass("network-error-row", this._request.failed || (this._request.statusCode >= 400)); 2150 this._updateElementStyleClasses(this._element); 2151 }, 2152 2153 /** 2154 * @param {!Element} element 2155 */ 2156 _updateElementStyleClasses: function(element) 2157 { 2158 var typeClassName = "network-type-" + this._request.type.name(); 2159 if (!element.hasStyleClass(typeClassName)) { 2160 element.removeMatchingStyleClasses("network-type-\\w+"); 2161 element.addStyleClass(typeClassName); 2162 } 2163 }, 2164 2165 _refreshResponseHeaderCell: function(headerName) 2166 { 2167 var cell = this._responseHeaderCells[headerName]; 2168 var value = this._request.responseHeaderValue(headerName); 2169 cell.setTextAndTitle(value ? value : ""); 2170 }, 2171 2172 _refreshNameCell: function() 2173 { 2174 this._nameCell.removeChildren(); 2175 2176 if (this._request.type === WebInspector.resourceTypes.Image) { 2177 var previewImage = document.createElement("img"); 2178 previewImage.className = "image-network-icon-preview"; 2179 this._request.populateImageSource(previewImage); 2180 2181 var iconElement = document.createElement("div"); 2182 iconElement.className = "icon"; 2183 iconElement.appendChild(previewImage); 2184 } else { 2185 var iconElement = document.createElement("img"); 2186 iconElement.className = "icon"; 2187 } 2188 this._nameCell.appendChild(iconElement); 2189 this._nameCell.appendChild(document.createTextNode(this._request.name())); 2190 this._appendSubtitle(this._nameCell, this._request.path()); 2191 this._nameCell.title = this._request.url; 2192 }, 2193 2194 _refreshMethodCell: function() 2195 { 2196 this._methodCell.setTextAndTitle(this._request.requestMethod); 2197 }, 2198 2199 _refreshStatusCell: function() 2200 { 2201 this._statusCell.removeChildren(); 2202 2203 if (this._request.failed) { 2204 var failText = this._request.canceled ? WebInspector.UIString("(canceled)") : WebInspector.UIString("(failed)"); 2205 if (this._request.localizedFailDescription) { 2206 this._statusCell.appendChild(document.createTextNode(failText)); 2207 this._appendSubtitle(this._statusCell, this._request.localizedFailDescription); 2208 this._statusCell.title = failText + " " + this._request.localizedFailDescription; 2209 } else 2210 this._statusCell.setTextAndTitle(failText); 2211 this._statusCell.addStyleClass("network-dim-cell"); 2212 return; 2213 } 2214 2215 this._statusCell.removeStyleClass("network-dim-cell"); 2216 2217 if (this._request.statusCode) { 2218 this._statusCell.appendChild(document.createTextNode("" + this._request.statusCode)); 2219 this._appendSubtitle(this._statusCell, this._request.statusText); 2220 this._statusCell.title = this._request.statusCode + " " + this._request.statusText; 2221 if (this._request.cached) 2222 this._statusCell.addStyleClass("network-dim-cell"); 2223 } else { 2224 if (!this._request.isHttpFamily() && this._request.finished) 2225 this._statusCell.setTextAndTitle(WebInspector.UIString("Success")); 2226 else if (this._request.isPingRequest()) 2227 this._statusCell.setTextAndTitle(WebInspector.UIString("(ping)")); 2228 else 2229 this._statusCell.setTextAndTitle(WebInspector.UIString("(pending)")); 2230 this._statusCell.addStyleClass("network-dim-cell"); 2231 } 2232 }, 2233 2234 _refreshDomainCell: function() 2235 { 2236 this._domainCell.removeChildren(); 2237 this._domainCell.appendChild(document.createTextNode(this._request.domain)); 2238 this._domainCell.title = this._request.parsedURL.host; 2239 }, 2240 2241 _refreshTypeCell: function() 2242 { 2243 if (this._request.mimeType) { 2244 this._typeCell.removeStyleClass("network-dim-cell"); 2245 this._typeCell.setTextAndTitle(this._request.mimeType); 2246 } else if (this._request.isPingRequest()) { 2247 this._typeCell.removeStyleClass("network-dim-cell"); 2248 this._typeCell.setTextAndTitle(this._request.requestContentType() || ""); 2249 } else { 2250 this._typeCell.addStyleClass("network-dim-cell"); 2251 this._typeCell.setTextAndTitle(WebInspector.UIString("Pending")); 2252 } 2253 }, 2254 2255 _refreshInitiatorCell: function() 2256 { 2257 this._initiatorCell.removeChildren(); 2258 this._initiatorCell.removeStyleClass("network-dim-cell"); 2259 this._initiatorCell.removeStyleClass("network-script-initiated"); 2260 delete this._initiatorCell.request; 2261 2262 var request = this._request; 2263 var initiator = request.initiatorInfo(); 2264 2265 switch (initiator.type) { 2266 case WebInspector.NetworkRequest.InitiatorType.Parser: 2267 this._initiatorCell.title = initiator.url + ":" + initiator.lineNumber; 2268 this._initiatorCell.appendChild(WebInspector.linkifyResourceAsNode(initiator.url, initiator.lineNumber - 1)); 2269 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Parser")); 2270 break; 2271 2272 case WebInspector.NetworkRequest.InitiatorType.Redirect: 2273 this._initiatorCell.title = initiator.url; 2274 this._initiatorCell.appendChild(WebInspector.linkifyRequestAsNode(request.redirectSource)); 2275 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Redirect")); 2276 break; 2277 2278 case WebInspector.NetworkRequest.InitiatorType.Script: 2279 var urlElement = this._linkifier.linkifyLocation(initiator.url, initiator.lineNumber - 1, initiator.columnNumber - 1); 2280 urlElement.title = ""; 2281 this._initiatorCell.appendChild(urlElement); 2282 this._appendSubtitle(this._initiatorCell, WebInspector.UIString("Script")); 2283 this._initiatorCell.addStyleClass("network-script-initiated"); 2284 this._initiatorCell.request = request; 2285 break; 2286 2287 default: 2288 this._initiatorCell.title = ""; 2289 this._initiatorCell.addStyleClass("network-dim-cell"); 2290 this._initiatorCell.setTextAndTitle(WebInspector.UIString("Other")); 2291 } 2292 }, 2293 2294 _refreshCookiesCell: function() 2295 { 2296 var requestCookies = this._request.requestCookies; 2297 this._cookiesCell.setTextAndTitle(requestCookies ? "" + requestCookies.length : ""); 2298 }, 2299 2300 _refreshSetCookiesCell: function() 2301 { 2302 var responseCookies = this._request.responseCookies; 2303 this._setCookiesCell.setTextAndTitle(responseCookies ? "" + responseCookies.length : ""); 2304 }, 2305 2306 _refreshSizeCell: function() 2307 { 2308 if (this._request.cached) { 2309 this._sizeCell.setTextAndTitle(WebInspector.UIString("(from cache)")); 2310 this._sizeCell.addStyleClass("network-dim-cell"); 2311 } else { 2312 var resourceSize = Number.bytesToString(this._request.resourceSize); 2313 var transferSize = Number.bytesToString(this._request.transferSize); 2314 this._sizeCell.setTextAndTitle(transferSize); 2315 this._sizeCell.removeStyleClass("network-dim-cell"); 2316 this._appendSubtitle(this._sizeCell, resourceSize); 2317 } 2318 }, 2319 2320 _refreshTimeCell: function() 2321 { 2322 if (this._request.duration > 0) { 2323 this._timeCell.removeStyleClass("network-dim-cell"); 2324 this._timeCell.setTextAndTitle(Number.secondsToString(this._request.duration)); 2325 this._appendSubtitle(this._timeCell, Number.secondsToString(this._request.latency)); 2326 } else { 2327 this._timeCell.addStyleClass("network-dim-cell"); 2328 this._timeCell.setTextAndTitle(WebInspector.UIString("Pending")); 2329 } 2330 }, 2331 2332 _appendSubtitle: function(cellElement, subtitleText) 2333 { 2334 var subtitleElement = document.createElement("div"); 2335 subtitleElement.className = "network-cell-subtitle"; 2336 subtitleElement.textContent = subtitleText; 2337 cellElement.appendChild(subtitleElement); 2338 }, 2339 2340 refreshGraph: function(calculator) 2341 { 2342 var percentages = calculator.computeBarGraphPercentages(this._request); 2343 this._percentages = percentages; 2344 2345 this._barAreaElement.removeStyleClass("hidden"); 2346 this._updateElementStyleClasses(this._timelineCell); 2347 2348 this._barLeftElement.style.setProperty("left", percentages.start + "%"); 2349 this._barRightElement.style.setProperty("right", (100 - percentages.end) + "%"); 2350 2351 this._barLeftElement.style.setProperty("right", (100 - percentages.end) + "%"); 2352 this._barRightElement.style.setProperty("left", percentages.middle + "%"); 2353 2354 var labels = calculator.computeBarGraphLabels(this._request); 2355 this._labelLeftElement.textContent = labels.left; 2356 this._labelRightElement.textContent = labels.right; 2357 2358 var tooltip = (labels.tooltip || ""); 2359 this._barLeftElement.title = tooltip; 2360 this._labelLeftElement.title = tooltip; 2361 this._labelRightElement.title = tooltip; 2362 this._barRightElement.title = tooltip; 2363 }, 2364 2365 _refreshLabelPositions: function() 2366 { 2367 if (!this._percentages) 2368 return; 2369 this._labelLeftElement.style.removeProperty("left"); 2370 this._labelLeftElement.style.removeProperty("right"); 2371 this._labelLeftElement.removeStyleClass("before"); 2372 this._labelLeftElement.removeStyleClass("hidden"); 2373 2374 this._labelRightElement.style.removeProperty("left"); 2375 this._labelRightElement.style.removeProperty("right"); 2376 this._labelRightElement.removeStyleClass("after"); 2377 this._labelRightElement.removeStyleClass("hidden"); 2378 2379 const labelPadding = 10; 2380 const barRightElementOffsetWidth = this._barRightElement.offsetWidth; 2381 const barLeftElementOffsetWidth = this._barLeftElement.offsetWidth; 2382 2383 if (this._barLeftElement) { 2384 var leftBarWidth = barLeftElementOffsetWidth - labelPadding; 2385 var rightBarWidth = (barRightElementOffsetWidth - barLeftElementOffsetWidth) - labelPadding; 2386 } else { 2387 var leftBarWidth = (barLeftElementOffsetWidth - barRightElementOffsetWidth) - labelPadding; 2388 var rightBarWidth = barRightElementOffsetWidth - labelPadding; 2389 } 2390 2391 const labelLeftElementOffsetWidth = this._labelLeftElement.offsetWidth; 2392 const labelRightElementOffsetWidth = this._labelRightElement.offsetWidth; 2393 2394 const labelBefore = (labelLeftElementOffsetWidth > leftBarWidth); 2395 const labelAfter = (labelRightElementOffsetWidth > rightBarWidth); 2396 const graphElementOffsetWidth = this._timelineCell.offsetWidth; 2397 2398 if (labelBefore && (graphElementOffsetWidth * (this._percentages.start / 100)) < (labelLeftElementOffsetWidth + 10)) 2399 var leftHidden = true; 2400 2401 if (labelAfter && (graphElementOffsetWidth * ((100 - this._percentages.end) / 100)) < (labelRightElementOffsetWidth + 10)) 2402 var rightHidden = true; 2403 2404 if (barLeftElementOffsetWidth == barRightElementOffsetWidth) { 2405 // The left/right label data are the same, so a before/after label can be replaced by an on-bar label. 2406 if (labelBefore && !labelAfter) 2407 leftHidden = true; 2408 else if (labelAfter && !labelBefore) 2409 rightHidden = true; 2410 } 2411 2412 if (labelBefore) { 2413 if (leftHidden) 2414 this._labelLeftElement.addStyleClass("hidden"); 2415 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.start) + "%"); 2416 this._labelLeftElement.addStyleClass("before"); 2417 } else { 2418 this._labelLeftElement.style.setProperty("left", this._percentages.start + "%"); 2419 this._labelLeftElement.style.setProperty("right", (100 - this._percentages.middle) + "%"); 2420 } 2421 2422 if (labelAfter) { 2423 if (rightHidden) 2424 this._labelRightElement.addStyleClass("hidden"); 2425 this._labelRightElement.style.setProperty("left", this._percentages.end + "%"); 2426 this._labelRightElement.addStyleClass("after"); 2427 } else { 2428 this._labelRightElement.style.setProperty("left", this._percentages.middle + "%"); 2429 this._labelRightElement.style.setProperty("right", (100 - this._percentages.end) + "%"); 2430 } 2431 }, 2432 2433 __proto__: WebInspector.DataGridNode.prototype 2434 } 2435 2436 /** 2437 * @param {WebInspector.NetworkRequest} request 2438 * @return {boolean} 2439 */ 2440 WebInspector.NetworkLogView._trivialTypeFilter = function(request) 2441 { 2442 return true; 2443 } 2444 2445 /** 2446 * @param {!Object.<string, boolean>} allowedTypes 2447 * @param {WebInspector.NetworkRequest} request 2448 * @return {boolean} 2449 */ 2450 WebInspector.NetworkLogView._typeFilter = function(allowedTypes, request) 2451 { 2452 return request.type.name() in allowedTypes; 2453 } 2454 2455 2456 WebInspector.NetworkDataGridNode.NameComparator = function(a, b) 2457 { 2458 var aFileName = a._request.name(); 2459 var bFileName = b._request.name(); 2460 if (aFileName > bFileName) 2461 return 1; 2462 if (bFileName > aFileName) 2463 return -1; 2464 return 0; 2465 } 2466 2467 WebInspector.NetworkDataGridNode.SizeComparator = function(a, b) 2468 { 2469 if (b._request.cached && !a._request.cached) 2470 return 1; 2471 if (a._request.cached && !b._request.cached) 2472 return -1; 2473 2474 return a._request.transferSize - b._request.transferSize; 2475 } 2476 2477 WebInspector.NetworkDataGridNode.InitiatorComparator = function(a, b) 2478 { 2479 var aInitiator = a._request.initiatorInfo(); 2480 var bInitiator = b._request.initiatorInfo(); 2481 2482 if (aInitiator.type < bInitiator.type) 2483 return -1; 2484 if (aInitiator.type > bInitiator.type) 2485 return 1; 2486 2487 if (aInitiator.source < bInitiator.source) 2488 return -1; 2489 if (aInitiator.source > bInitiator.source) 2490 return 1; 2491 2492 if (aInitiator.lineNumber < bInitiator.lineNumber) 2493 return -1; 2494 if (aInitiator.lineNumber > bInitiator.lineNumber) 2495 return 1; 2496 2497 if (aInitiator.columnNumber < bInitiator.columnNumber) 2498 return -1; 2499 if (aInitiator.columnNumber > bInitiator.columnNumber) 2500 return 1; 2501 2502 return 0; 2503 } 2504 2505 WebInspector.NetworkDataGridNode.RequestCookiesCountComparator = function(a, b) 2506 { 2507 var aScore = a._request.requestCookies ? a._request.requestCookies.length : 0; 2508 var bScore = b._request.requestCookies ? b._request.requestCookies.length : 0; 2509 return aScore - bScore; 2510 } 2511 2512 WebInspector.NetworkDataGridNode.ResponseCookiesCountComparator = function(a, b) 2513 { 2514 var aScore = a._request.responseCookies ? a._request.responseCookies.length : 0; 2515 var bScore = b._request.responseCookies ? b._request.responseCookies.length : 0; 2516 return aScore - bScore; 2517 } 2518 2519 WebInspector.NetworkDataGridNode.RequestPropertyComparator = function(propertyName, revert, a, b) 2520 { 2521 var aValue = a._request[propertyName]; 2522 var bValue = b._request[propertyName]; 2523 if (aValue > bValue) 2524 return revert ? -1 : 1; 2525 if (bValue > aValue) 2526 return revert ? 1 : -1; 2527 return 0; 2528 } 2529