blob: ffb05a44f3547ddeb48f5ca5d9cdb3fa8574db26 [file] [log] [blame]
// 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;