UNPKG

chrome-devtools-frontend

Version:
323 lines (280 loc) • 12.6 kB
// 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. /* eslint-disable rulesdir/no-imperative-dom-api */ import type * as Common from '../../core/common/common.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Trace from '../../models/trace/trace.js'; import * as RenderCoordinator from '../../ui/components/render_coordinator/render_coordinator.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 {NetworkLogView} from './NetworkLogView.js'; import {NetworkTimeBoundary} from './NetworkTimeCalculator.js'; import {RequestTimeRangeNames, RequestTimingView} from './RequestTimingView.js'; 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 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(); } 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.TimelineOverviewCalculator.TimelineOverviewCalculator { return super.calculator() as PerfUI.TimelineOverviewCalculator.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) / BAND_HEIGHT) - 1) | 0; this.numBands = (numBands > 0) ? numBands : 1; this.scheduleUpdate(); } override reset(): void { 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 RenderCoordinator.write('NetworkOverview.render', 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(), Trace.Types.Timing.Milli(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++] * BAND_HEIGHT + paddingTop; const startTime = lines[i++]; let endTime: number = lines[i++]; if (endTime === Number.MAX_VALUE) { endTime = calculator.maximumBoundary(); } const startX = calculator.computePosition(Trace.Types.Timing.Milli(startTime)); const endX = calculator.computePosition(Trace.Types.Timing.Milli(endTime)) + 1; context.fillRect(startX, y, Math.max(endX - startX, MIN_BAND_WIDTH), BAND_HEIGHT); context.strokeRect(startX, y, Math.max(endX - startX, MIN_BAND_WIDTH), BAND_HEIGHT); } } 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.SERVICE_WORKER); drawLines(RequestTimeRangeNames.SERVICE_WORKER_PREPARATION); drawLines(RequestTimeRangeNames.SERVICE_WORKER_RESPOND_WITH); 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)) * BAND_HEIGHT + paddingTop; const timeRanges = RequestTimingView.calculateRequestTimeRanges(request, this.calculator().minimumBoundary()); context.fillStyle = ThemeSupport.ThemeSupport.instance().getComputedValue('--sys-color-tonal-container'); // The network overview works in seconds, but the calcululator deals in // milliseconds, hence the multiplication by 1000. const start = Trace.Types.Timing.Milli(timeRanges[0].start * 1000); const end = Trace.Types.Timing.Milli(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 = Trace.Types.Timing.Milli(timeRanges[j].start * 1000); const end = Trace.Types.Timing.Milli(timeRanges[j].end * 1000); context.moveTo(Number(calculator.computePosition(start)) - 0, y); context.lineTo(Number(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 position = calculator.computePosition(Trace.Types.Timing.Milli(this.domContentLoadedEvents[i])); const x = Math.round(position) + 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 position = calculator.computePosition(Trace.Types.Timing.Milli(this.loadEvents[i])); const x = Math.round(position) + 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 timeInMilliseconds = Trace.Types.Timing.Milli(this.selectedFilmStripTime); const x = Math.round(calculator.computePosition(timeInMilliseconds)); context.moveTo(x, 0); context.lineTo(x, height); context.stroke(); } context.restore(); } } export const RequestTimeRangeNameToColor = { [RequestTimeRangeNames.TOTAL]: '--network-overview-total', [RequestTimeRangeNames.BLOCKING]: '--network-overview-blocking', [RequestTimeRangeNames.CONNECTING]: '--network-overview-connecting', [RequestTimeRangeNames.SERVICE_WORKER]: '--network-overview-service-worker', [RequestTimeRangeNames.SERVICE_WORKER_PREPARATION]: '--network-overview-service-worker', [RequestTimeRangeNames.SERVICE_WORKER_RESPOND_WITH]: '--network-overview-service-worker-respond-with', [RequestTimeRangeNames.PUSH]: '--network-overview-push', [RequestTimeRangeNames.PROXY]: '--override-network-overview-proxy', [RequestTimeRangeNames.DNS]: '--network-overview-dns', [RequestTimeRangeNames.SSL]: '--network-overview-ssl', [RequestTimeRangeNames.SENDING]: '--override-network-overview-sending', [RequestTimeRangeNames.WAITING]: '--network-overview-waiting', [RequestTimeRangeNames.RECEIVING]: '--network-overview-receiving', [RequestTimeRangeNames.QUEUEING]: '--network-overview-queueing', } as Record<string, string>; const BAND_HEIGHT = 3; const PADDING = 5; // Minimum rectangle width for very short requests. const MIN_BAND_WIDTH = 10; // Border between bars in network overview panel for accessibility. const BORDER_WIDTH = 1;