| // Copyright 2015 The Chromium Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| /** | |
| * @unrestricted | |
| */ | |
| export default class NetworkOverview extends PerfUI.TimelineOverviewBase { | |
| constructor() { | |
| super(); | |
| this._selectedFilmStripTime = -1; | |
| this.element.classList.add('network-overview'); | |
| /** @type {number} */ | |
| this._numBands = 1; | |
| /** @type {boolean} */ | |
| this._updateScheduled = false; | |
| this._highlightedRequest = null; | |
| SDK.targetManager.addModelListener( | |
| SDK.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this); | |
| SDK.targetManager.addModelListener( | |
| SDK.ResourceTreeModel, SDK.ResourceTreeModel.Events.DOMContentLoaded, this._domContentLoadedEventFired, this); | |
| this.reset(); | |
| } | |
| setHighlightedRequest(request) { | |
| this._highlightedRequest = request; | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @param {?SDK.FilmStripModel} filmStripModel | |
| */ | |
| setFilmStripModel(filmStripModel) { | |
| this._filmStripModel = filmStripModel; | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @param {number} time | |
| */ | |
| selectFilmStripFrame(time) { | |
| this._selectedFilmStripTime = time; | |
| this.scheduleUpdate(); | |
| } | |
| clearFilmStripFrame() { | |
| this._selectedFilmStripTime = -1; | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _loadEventFired(event) { | |
| const time = /** @type {number} */ (event.data.loadTime); | |
| if (time) { | |
| this._loadEvents.push(time * 1000); | |
| } | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _domContentLoadedEventFired(event) { | |
| const data = /** @type {number} */ (event.data); | |
| if (data) { | |
| this._domContentLoadedEvents.push(data * 1000); | |
| } | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @param {string} connectionId | |
| * @return {number} | |
| */ | |
| _bandId(connectionId) { | |
| if (!connectionId || connectionId === '0') { | |
| return -1; | |
| } | |
| if (this._bandMap.has(connectionId)) { | |
| return /** @type {number} */ (this._bandMap.get(connectionId)); | |
| } | |
| const result = this._nextBand++; | |
| this._bandMap.set(connectionId, result); | |
| return result; | |
| } | |
| /** | |
| * @param {!SDK.NetworkRequest} request | |
| */ | |
| updateRequest(request) { | |
| if (!this._requestsSet.has(request)) { | |
| this._requestsSet.add(request); | |
| this._requestsList.push(request); | |
| } | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @override | |
| */ | |
| wasShown() { | |
| this.onResize(); | |
| } | |
| /** | |
| * @override | |
| */ | |
| onResize() { | |
| const width = this.element.offsetWidth; | |
| const height = this.element.offsetHeight; | |
| this.calculator().setDisplayWidth(width); | |
| this.resetCanvas(); | |
| const numBands = (((height - _padding - 1) / _bandHeight) - 1) | 0; | |
| this._numBands = (numBands > 0) ? numBands : 1; | |
| this.scheduleUpdate(); | |
| } | |
| /** | |
| * @override | |
| */ | |
| reset() { | |
| /** @type {?SDK.FilmStripModel} */ | |
| this._filmStripModel = null; | |
| /** @type {number} */ | |
| this._span = 1; | |
| /** @type {?Network.NetworkTimeBoundary} */ | |
| this._lastBoundary = null; | |
| /** @type {number} */ | |
| this._nextBand = 0; | |
| /** @type {!Map.<string, number>} */ | |
| this._bandMap = new Map(); | |
| /** @type {!Array.<!SDK.NetworkRequest>} */ | |
| this._requestsList = []; | |
| /** @type {!Set.<!SDK.NetworkRequest>} */ | |
| this._requestsSet = new Set(); | |
| /** @type {!Array.<number>} */ | |
| this._loadEvents = []; | |
| /** @type {!Array.<number>} */ | |
| this._domContentLoadedEvents = []; | |
| // Clear screen. | |
| this.resetCanvas(); | |
| } | |
| /** | |
| * @protected | |
| */ | |
| scheduleUpdate() { | |
| if (this._updateScheduled || !this.isShowing()) { | |
| return; | |
| } | |
| this._updateScheduled = true; | |
| this.element.window().requestAnimationFrame(this.update.bind(this)); | |
| } | |
| /** | |
| * @override | |
| */ | |
| update() { | |
| this._updateScheduled = false; | |
| const calculator = this.calculator(); | |
| const newBoundary = new Network.NetworkTimeBoundary(calculator.minimumBoundary(), calculator.maximumBoundary()); | |
| if (!this._lastBoundary || !newBoundary.equals(this._lastBoundary)) { | |
| const span = calculator.boundarySpan(); | |
| while (this._span < span) { | |
| this._span *= 1.25; | |
| } | |
| calculator.setBounds(calculator.minimumBoundary(), calculator.minimumBoundary() + this._span); | |
| this._lastBoundary = new Network.NetworkTimeBoundary(calculator.minimumBoundary(), calculator.maximumBoundary()); | |
| } | |
| const context = this.context(); | |
| const linesByType = {}; | |
| const paddingTop = _padding; | |
| /** | |
| * @param {string} type | |
| */ | |
| function drawLines(type) { | |
| const lines = linesByType[type]; | |
| if (!lines) { | |
| return; | |
| } | |
| const n = lines.length; | |
| context.beginPath(); | |
| context.strokeStyle = RequestTimeRangeNameToColor[type]; | |
| for (let i = 0; i < n;) { | |
| const y = lines[i++] * _bandHeight + paddingTop; | |
| const startTime = lines[i++]; | |
| let endTime = lines[i++]; | |
| if (endTime === Number.MAX_VALUE) { | |
| endTime = calculator.maximumBoundary(); | |
| } | |
| context.moveTo(calculator.computePosition(startTime), y); | |
| context.lineTo(calculator.computePosition(endTime) + 1, y); | |
| } | |
| context.stroke(); | |
| } | |
| /** | |
| * @param {string} type | |
| * @param {number} y | |
| * @param {number} start | |
| * @param {number} end | |
| */ | |
| function addLine(type, y, start, end) { | |
| let lines = linesByType[type]; | |
| if (!lines) { | |
| lines = []; | |
| linesByType[type] = lines; | |
| } | |
| lines.push(y, start, end); | |
| } | |
| const requests = this._requestsList; | |
| const n = requests.length; | |
| for (let i = 0; i < n; ++i) { | |
| const request = requests[i]; | |
| const band = this._bandId(request.connectionId); | |
| const y = (band === -1) ? 0 : (band % this._numBands + 1); | |
| const timeRanges = | |
| Network.RequestTimingView.calculateRequestTimeRanges(request, this.calculator().minimumBoundary()); | |
| for (let j = 0; j < timeRanges.length; ++j) { | |
| const type = timeRanges[j].name; | |
| if (band !== -1 || type === Network.RequestTimeRangeNames.Total) { | |
| addLine(type, y, timeRanges[j].start * 1000, timeRanges[j].end * 1000); | |
| } | |
| } | |
| } | |
| context.clearRect(0, 0, this.width(), this.height()); | |
| context.save(); | |
| context.scale(window.devicePixelRatio, window.devicePixelRatio); | |
| context.lineWidth = 2; | |
| drawLines(Network.RequestTimeRangeNames.Total); | |
| drawLines(Network.RequestTimeRangeNames.Blocking); | |
| drawLines(Network.RequestTimeRangeNames.Connecting); | |
| drawLines(Network.RequestTimeRangeNames.ServiceWorker); | |
| drawLines(Network.RequestTimeRangeNames.ServiceWorkerPreparation); | |
| drawLines(Network.RequestTimeRangeNames.Push); | |
| drawLines(Network.RequestTimeRangeNames.Proxy); | |
| drawLines(Network.RequestTimeRangeNames.DNS); | |
| drawLines(Network.RequestTimeRangeNames.SSL); | |
| drawLines(Network.RequestTimeRangeNames.Sending); | |
| drawLines(Network.RequestTimeRangeNames.Waiting); | |
| drawLines(Network.RequestTimeRangeNames.Receiving); | |
| if (this._highlightedRequest) { | |
| const size = 5; | |
| const borderSize = 2; | |
| const request = this._highlightedRequest; | |
| const band = this._bandId(request.connectionId); | |
| const y = ((band === -1) ? 0 : (band % this._numBands + 1)) * _bandHeight + paddingTop; | |
| const timeRanges = | |
| Network.RequestTimingView.calculateRequestTimeRanges(request, this.calculator().minimumBoundary()); | |
| // This is the value of var(--selection-bg-color) | |
| // to match the selection color used in the performance panel | |
| context.fillStyle = '#1a73e8'; | |
| const start = timeRanges[0].start * 1000; | |
| const end = timeRanges[0].end * 1000; | |
| context.fillRect( | |
| calculator.computePosition(start) - borderSize, y - size / 2 - borderSize, | |
| calculator.computePosition(end) - calculator.computePosition(start) + 1 + 2 * borderSize, size * borderSize); | |
| for (let j = 0; j < timeRanges.length; ++j) { | |
| const type = timeRanges[j].name; | |
| if (band !== -1 || type === Network.RequestTimeRangeNames.Total) { | |
| context.beginPath(); | |
| context.strokeStyle = RequestTimeRangeNameToColor[type]; | |
| context.lineWidth = size; | |
| const start = timeRanges[j].start * 1000; | |
| const end = timeRanges[j].end * 1000; | |
| context.moveTo(calculator.computePosition(start) - 0, y); | |
| context.lineTo(calculator.computePosition(end) + 1, y); | |
| context.stroke(); | |
| } | |
| } | |
| } | |
| const height = this.element.offsetHeight; | |
| context.lineWidth = 1; | |
| context.beginPath(); | |
| context.strokeStyle = Network.NetworkLogView.getDCLEventColor(); | |
| for (let i = this._domContentLoadedEvents.length - 1; i >= 0; --i) { | |
| const x = Math.round(calculator.computePosition(this._domContentLoadedEvents[i])) + 0.5; | |
| context.moveTo(x, 0); | |
| context.lineTo(x, height); | |
| } | |
| context.stroke(); | |
| context.beginPath(); | |
| context.strokeStyle = Network.NetworkLogView.getLoadEventColor(); | |
| for (let i = this._loadEvents.length - 1; i >= 0; --i) { | |
| const x = Math.round(calculator.computePosition(this._loadEvents[i])) + 0.5; | |
| context.moveTo(x, 0); | |
| context.lineTo(x, height); | |
| } | |
| context.stroke(); | |
| if (this._selectedFilmStripTime !== -1) { | |
| context.lineWidth = 2; | |
| context.beginPath(); | |
| context.strokeStyle = '#FCCC49'; // Keep in sync with .network-frame-divider CSS rule. | |
| const x = Math.round(calculator.computePosition(this._selectedFilmStripTime)); | |
| context.moveTo(x, 0); | |
| context.lineTo(x, height); | |
| context.stroke(); | |
| } | |
| context.restore(); | |
| } | |
| } | |
| export const RequestTimeRangeNameToColor = { | |
| [Network.RequestTimeRangeNames.Total]: '#CCCCCC', | |
| [Network.RequestTimeRangeNames.Blocking]: '#AAAAAA', | |
| [Network.RequestTimeRangeNames.Connecting]: '#FF9800', | |
| [Network.RequestTimeRangeNames.ServiceWorker]: '#FF9800', | |
| [Network.RequestTimeRangeNames.ServiceWorkerPreparation]: '#FF9800', | |
| [Network.RequestTimeRangeNames.Push]: '#8CDBff', | |
| [Network.RequestTimeRangeNames.Proxy]: '#A1887F', | |
| [Network.RequestTimeRangeNames.DNS]: '#009688', | |
| [Network.RequestTimeRangeNames.SSL]: '#9C27B0', | |
| [Network.RequestTimeRangeNames.Sending]: '#B0BEC5', | |
| [Network.RequestTimeRangeNames.Waiting]: '#00C853', | |
| [Network.RequestTimeRangeNames.Receiving]: '#03A9F4', | |
| }; | |
| /** @type {number} */ | |
| export const _bandHeight = 3; | |
| /** @type {number} */ | |
| export const _padding = 5; | |
| /* Legacy exported object */ | |
| self.Network = self.Network || {}; | |
| /* Legacy exported object */ | |
| Network = Network || {}; | |
| /** | |
| * @constructor | |
| */ | |
| Network.NetworkOverview = NetworkOverview; | |
| /** @typedef {{start: number, end: number}} */ | |
| Network.NetworkOverview.Window; | |
| Network.NetworkOverview.RequestTimeRangeNameToColor = RequestTimeRangeNameToColor; | |
| /** @type {number} */ | |
| Network.NetworkOverview._bandHeight = _bandHeight; | |
| /** @type {number} */ | |
| Network.NetworkOverview._padding = _padding; |