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