chrome-devtools-frontend
Version:
Chrome DevTools UI
326 lines (281 loc) • 12.5 kB
text/typescript
// 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.
import type * as Common from '../../core/common/common.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js';
import * as ThemeSupport from '../../ui/legacy/theme_support/theme_support.js';
import * as Coordinator from '../../ui/components/render_coordinator/render_coordinator.js';
import {NetworkLogView} from './NetworkLogView.js';
import {NetworkTimeBoundary} from './NetworkTimeCalculator.js';
import {RequestTimeRangeNames, RequestTimingView} from './RequestTimingView.js';
const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
export class NetworkOverview extends PerfUI.TimelineOverviewPane.TimelineOverviewBase {
private selectedFilmStripTime: number;
private numBands: number;
private highlightedRequest: SDK.NetworkRequest.NetworkRequest|null;
private loadEvents!: number[];
private domContentLoadedEvents!: number[];
private nextBand!: number;
private bandMap!: Map<string, number>;
private requestsList!: SDK.NetworkRequest.NetworkRequest[];
private requestsSet!: Set<SDK.NetworkRequest.NetworkRequest>;
private span!: number;
private filmStripModel?: SDK.FilmStripModel.FilmStripModel|null;
private lastBoundary?: NetworkTimeBoundary|null;
constructor() {
super();
this.selectedFilmStripTime = -1;
this.element.classList.add('network-overview');
this.numBands = 1;
this.highlightedRequest = null;
SDK.TargetManager.TargetManager.instance().addModelListener(
SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this.loadEventFired, this,
{scoped: true});
SDK.TargetManager.TargetManager.instance().addModelListener(
SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.DOMContentLoaded,
this.domContentLoadedEventFired, this, {scoped: true});
this.reset();
}
setHighlightedRequest(request: SDK.NetworkRequest.NetworkRequest|null): void {
this.highlightedRequest = request;
this.scheduleUpdate();
}
setFilmStripModel(filmStripModel: SDK.FilmStripModel.FilmStripModel|null): void {
this.filmStripModel = filmStripModel;
this.scheduleUpdate();
}
selectFilmStripFrame(time: number): void {
this.selectedFilmStripTime = time;
this.scheduleUpdate();
}
clearFilmStripFrame(): void {
this.selectedFilmStripTime = -1;
this.scheduleUpdate();
}
private loadEventFired(
event: Common.EventTarget
.EventTargetEvent<{resourceTreeModel: SDK.ResourceTreeModel.ResourceTreeModel, loadTime: number}>): void {
const time = event.data.loadTime;
if (time) {
this.loadEvents.push(time * 1000);
}
this.scheduleUpdate();
}
private domContentLoadedEventFired(event: Common.EventTarget.EventTargetEvent<number>): void {
const {data} = event;
if (data) {
this.domContentLoadedEvents.push(data * 1000);
}
this.scheduleUpdate();
}
private bandId(connectionId: string): number {
if (!connectionId || connectionId === '0') {
return -1;
}
if (this.bandMap.has(connectionId)) {
return this.bandMap.get(connectionId) as number;
}
const result = this.nextBand++;
this.bandMap.set(connectionId, result);
return result;
}
updateRequest(request: SDK.NetworkRequest.NetworkRequest): void {
if (!this.requestsSet.has(request)) {
this.requestsSet.add(request);
this.requestsList.push(request);
}
this.scheduleUpdate();
}
override wasShown(): void {
this.onResize();
}
override calculator(): PerfUI.TimelineOverviewPane.TimelineOverviewCalculator {
return super.calculator() as PerfUI.TimelineOverviewPane.TimelineOverviewCalculator;
}
override onResize(): void {
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(): void {
this.filmStripModel = null;
this.span = 1;
this.lastBoundary = null;
this.nextBand = 0;
this.bandMap = new Map();
this.requestsList = [];
this.requestsSet = new Set();
this.loadEvents = [];
this.domContentLoadedEvents = [];
// Clear screen.
this.resetCanvas();
}
scheduleUpdate(): void {
if (!this.isShowing()) {
return;
}
void coordinator.write(this.update.bind(this));
}
override update(): void {
const calculator = this.calculator();
const newBoundary = new 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 NetworkTimeBoundary(calculator.minimumBoundary(), calculator.maximumBoundary());
}
const context = this.context();
const linesByType = new Map<string, number[]>();
const paddingTop = _padding;
function drawLines(type: string): void {
const lines = linesByType.get(type);
if (!lines) {
return;
}
const n = lines.length;
context.beginPath();
context.strokeStyle = ThemeSupport.ThemeSupport.instance().getComputedValue('--color-background-opacity-80');
context.lineWidth = BORDER_WIDTH;
context.fillStyle = ThemeSupport.ThemeSupport.instance().getComputedValue(RequestTimeRangeNameToColor[type]);
for (let i = 0; i < n;) {
const y = lines[i++] * _bandHeight + paddingTop;
const startTime = lines[i++];
let endTime: number = lines[i++];
if (endTime === Number.MAX_VALUE) {
endTime = calculator.maximumBoundary();
}
const startX = calculator.computePosition(startTime);
const endX = calculator.computePosition(endTime) + 1;
context.fillRect(startX, y, endX - startX, _bandHeight);
context.strokeRect(startX, y, endX - startX, _bandHeight);
}
}
function addLine(type: string, y: number, start: number, end: number): void {
let lines = linesByType.get(type);
if (!lines) {
lines = [];
linesByType.set(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 = RequestTimingView.calculateRequestTimeRanges(request, this.calculator().minimumBoundary());
for (let j = 0; j < timeRanges.length; ++j) {
const type = timeRanges[j].name;
if (band !== -1 || type === 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(RequestTimeRangeNames.Total);
drawLines(RequestTimeRangeNames.Blocking);
drawLines(RequestTimeRangeNames.Connecting);
drawLines(RequestTimeRangeNames.ServiceWorker);
drawLines(RequestTimeRangeNames.ServiceWorkerPreparation);
drawLines(RequestTimeRangeNames.ServiceWorkerRespondWith);
drawLines(RequestTimeRangeNames.Push);
drawLines(RequestTimeRangeNames.Proxy);
drawLines(RequestTimeRangeNames.DNS);
drawLines(RequestTimeRangeNames.SSL);
drawLines(RequestTimeRangeNames.Sending);
drawLines(RequestTimeRangeNames.Waiting);
drawLines(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 = RequestTimingView.calculateRequestTimeRanges(request, this.calculator().minimumBoundary());
context.fillStyle = ThemeSupport.ThemeSupport.instance().getComputedValue('--legacy-selection-bg-color');
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 === RequestTimeRangeNames.Total) {
context.beginPath();
context.strokeStyle =
ThemeSupport.ThemeSupport.instance().getComputedValue(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 = ThemeSupport.ThemeSupport.instance().getComputedValue(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 = ThemeSupport.ThemeSupport.instance().getComputedValue(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 = ThemeSupport.ThemeSupport.instance().getComputedValue('--network-frame-divider-color');
const x = Math.round(calculator.computePosition(this.selectedFilmStripTime));
context.moveTo(x, 0);
context.lineTo(x, height);
context.stroke();
}
context.restore();
}
}
export const RequestTimeRangeNameToColor = {
[RequestTimeRangeNames.Total]: '--override-network-overview-total',
[RequestTimeRangeNames.Blocking]: '--override-network-overview-blocking',
[RequestTimeRangeNames.Connecting]: '--override-network-overview-connecting',
[RequestTimeRangeNames.ServiceWorker]: '--override-network-overview-service-worker',
[RequestTimeRangeNames.ServiceWorkerPreparation]: '--override-network-overview-service-worker',
[RequestTimeRangeNames.ServiceWorkerRespondWith]: '--override-network-overview-service-worker-respond-with',
[RequestTimeRangeNames.Push]: '--override-network-overview-push',
[RequestTimeRangeNames.Proxy]: '--override-network-overview-proxy',
[RequestTimeRangeNames.DNS]: '--override-network-overview-dns',
[RequestTimeRangeNames.SSL]: '--override-network-overview-ssl',
[RequestTimeRangeNames.Sending]: '--override-network-overview-sending',
[RequestTimeRangeNames.Waiting]: '--override-network-overview-waiting',
[RequestTimeRangeNames.Receiving]: '--override-network-overview-receiving',
[RequestTimeRangeNames.Queueing]: '--override-network-overview-queueing',
} as {[key: string]: string};
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention
export const _bandHeight: number = 3;
// TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration)
// eslint-disable-next-line @typescript-eslint/naming-convention
export const _padding: number = 5;
// Border between bars in network overview panel for accessibility.
const BORDER_WIDTH = 1;