| // Copyright 2021 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. |
| |
| /* |
| * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /* eslint-disable rulesdir/no_underscored_properties */ |
| |
| import * as Common from '../../core/common/common.js'; |
| import * as i18n from '../../core/i18n/i18n.js'; |
| import type * as SDK from '../../core/sdk/sdk.js'; // eslint-disable-line no-unused-vars |
| import type * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js'; // eslint-disable-line no-unused-vars |
| |
| const UIStrings = { |
| /** |
| *@description Latency download total format in Network Time Calculator of the Network panel |
| *@example {20ms} PH1 |
| *@example {20ms} PH2 |
| *@example {40ms} PH3 |
| */ |
| sLatencySDownloadSTotal: '{PH1} latency, {PH2} download ({PH3} total)', |
| /** |
| *@description Latency format in Network Time Calculator of the Network panel |
| *@example {20ms} PH1 |
| */ |
| sLatency: '{PH1} latency', |
| /** |
| * @description Duration of the download in ms/s shown for a completed network request. |
| * @example {5ms} PH1 |
| */ |
| sDownload: '{PH1} download', |
| /** |
| *@description From service worker format in Network Time Calculator of the Network panel |
| *@example {20ms latency} PH1 |
| */ |
| sFromServiceworker: '{PH1} (from `ServiceWorker`)', |
| /** |
| *@description From cache format in Network Time Calculator of the Network panel |
| *@example {20ms latency} PH1 |
| */ |
| sFromCache: '{PH1} (from cache)', |
| }; |
| const str_ = i18n.i18n.registerUIStrings('panels/network/NetworkTimeCalculator.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| export interface Label { |
| left: string; |
| right: string; |
| tooltip?: string; |
| } |
| |
| export class NetworkTimeBoundary { |
| minimum: number; |
| maximum: number; |
| constructor(minimum: number, maximum: number) { |
| this.minimum = minimum; |
| this.maximum = maximum; |
| } |
| |
| equals(other: NetworkTimeBoundary): boolean { |
| return (this.minimum === other.minimum) && (this.maximum === other.maximum); |
| } |
| } |
| |
| export class NetworkTimeCalculator extends Common.ObjectWrapper.ObjectWrapper implements |
| PerfUI.TimelineGrid.Calculator { |
| startAtZero: boolean; |
| _minimumBoundary: number; |
| _maximumBoundary: number; |
| _boundryChangedEventThrottler: Common.Throttler.Throttler; |
| _window: NetworkTimeBoundary|null; |
| _workingArea?: number; |
| |
| constructor(startAtZero: boolean) { |
| super(); |
| this.startAtZero = startAtZero; |
| this._minimumBoundary = -1; |
| this._maximumBoundary = -1; |
| this._boundryChangedEventThrottler = new Common.Throttler.Throttler(0); |
| this._window = null; |
| } |
| |
| setWindow(window: NetworkTimeBoundary|null): void { |
| this._window = window; |
| this._boundaryChanged(); |
| } |
| |
| setInitialUserFriendlyBoundaries(): void { |
| this._minimumBoundary = 0; |
| this._maximumBoundary = 1; |
| } |
| |
| computePosition(time: number): number { |
| return (time - this.minimumBoundary()) / this.boundarySpan() * (this._workingArea || 0); |
| } |
| |
| formatValue(value: number, precision?: number): string { |
| return Number.secondsToString(value, Boolean(precision)); |
| } |
| |
| minimumBoundary(): number { |
| return this._window ? this._window.minimum : this._minimumBoundary; |
| } |
| |
| zeroTime(): number { |
| return this._minimumBoundary; |
| } |
| |
| maximumBoundary(): number { |
| return this._window ? this._window.maximum : this._maximumBoundary; |
| } |
| |
| boundary(): NetworkTimeBoundary { |
| return new NetworkTimeBoundary(this.minimumBoundary(), this.maximumBoundary()); |
| } |
| |
| boundarySpan(): number { |
| return this.maximumBoundary() - this.minimumBoundary(); |
| } |
| |
| reset(): void { |
| this._minimumBoundary = -1; |
| this._maximumBoundary = -1; |
| this._boundaryChanged(); |
| } |
| |
| _value(): number { |
| return 0; |
| } |
| |
| setDisplayWidth(clientWidth: number): void { |
| this._workingArea = clientWidth; |
| } |
| |
| computeBarGraphPercentages(request: SDK.NetworkRequest.NetworkRequest): { |
| start: number, |
| middle: number, |
| end: number, |
| } { |
| let start; |
| let middle; |
| let end; |
| if (request.startTime !== -1) { |
| start = ((request.startTime - this.minimumBoundary()) / this.boundarySpan()) * 100; |
| } else { |
| start = 0; |
| } |
| |
| if (request.responseReceivedTime !== -1) { |
| middle = ((request.responseReceivedTime - this.minimumBoundary()) / this.boundarySpan()) * 100; |
| } else { |
| middle = (this.startAtZero ? start : 100); |
| } |
| |
| if (request.endTime !== -1) { |
| end = ((request.endTime - this.minimumBoundary()) / this.boundarySpan()) * 100; |
| } else { |
| end = (this.startAtZero ? middle : 100); |
| } |
| |
| if (this.startAtZero) { |
| end -= start; |
| middle -= start; |
| start = 0; |
| } |
| |
| return {start: start, middle: middle, end: end}; |
| } |
| |
| computePercentageFromEventTime(eventTime: number): number { |
| // This function computes a percentage in terms of the total loading time |
| // of a specific event. If startAtZero is set, then this is useless, and we |
| // want to return 0. |
| if (eventTime !== -1 && !this.startAtZero) { |
| return ((eventTime - this.minimumBoundary()) / this.boundarySpan()) * 100; |
| } |
| |
| return 0; |
| } |
| |
| percentageToTime(percentage: number): number { |
| return percentage * this.boundarySpan() / 100 + this.minimumBoundary(); |
| } |
| |
| _boundaryChanged(): void { |
| this._boundryChangedEventThrottler.schedule(async () => { |
| this.dispatchEventToListeners(Events.BoundariesChanged); |
| }); |
| } |
| |
| updateBoundariesForEventTime(eventTime: number): void { |
| if (eventTime === -1 || this.startAtZero) { |
| return; |
| } |
| |
| if (this._maximumBoundary === undefined || eventTime > this._maximumBoundary) { |
| this._maximumBoundary = eventTime; |
| this._boundaryChanged(); |
| } |
| } |
| |
| computeBarGraphLabels(request: SDK.NetworkRequest.NetworkRequest): Label { |
| let rightLabel = ''; |
| if (request.responseReceivedTime !== -1 && request.endTime !== -1) { |
| rightLabel = Number.secondsToString(request.endTime - request.responseReceivedTime); |
| } |
| |
| const hasLatency = request.latency > 0; |
| const leftLabel = hasLatency ? Number.secondsToString(request.latency) : rightLabel; |
| |
| if (request.timing) { |
| return {left: leftLabel, right: rightLabel, tooltip: undefined}; |
| } |
| |
| let tooltip; |
| if (hasLatency && rightLabel) { |
| const total = Number.secondsToString(request.duration); |
| tooltip = i18nString(UIStrings.sLatencySDownloadSTotal, {PH1: leftLabel, PH2: rightLabel, PH3: total}); |
| } else if (hasLatency) { |
| tooltip = i18nString(UIStrings.sLatency, {PH1: leftLabel}); |
| } else if (rightLabel) { |
| tooltip = i18nString(UIStrings.sDownload, {PH1: rightLabel}); |
| } |
| |
| if (request.fetchedViaServiceWorker) { |
| tooltip = i18nString(UIStrings.sFromServiceworker, {PH1: tooltip}); |
| } else if (request.cached()) { |
| tooltip = i18nString(UIStrings.sFromCache, {PH1: tooltip}); |
| } |
| return {left: leftLabel, right: rightLabel, tooltip: tooltip}; |
| } |
| |
| updateBoundaries(request: SDK.NetworkRequest.NetworkRequest): void { |
| const lowerBound = this._lowerBound(request); |
| const upperBound = this._upperBound(request); |
| let changed = false; |
| if (lowerBound !== -1 || this.startAtZero) { |
| changed = this._extendBoundariesToIncludeTimestamp(this.startAtZero ? 0 : lowerBound); |
| } |
| if (upperBound !== -1) { |
| changed = this._extendBoundariesToIncludeTimestamp(upperBound) || changed; |
| } |
| if (changed) { |
| this._boundaryChanged(); |
| } |
| } |
| |
| _extendBoundariesToIncludeTimestamp(timestamp: number): boolean { |
| const previousMinimumBoundary = this._minimumBoundary; |
| const previousMaximumBoundary = this._maximumBoundary; |
| const minOffset = _minimumSpread; |
| if (this._minimumBoundary === -1 || this._maximumBoundary === -1) { |
| this._minimumBoundary = timestamp; |
| this._maximumBoundary = timestamp + minOffset; |
| } else { |
| this._minimumBoundary = Math.min(timestamp, this._minimumBoundary); |
| this._maximumBoundary = Math.max(timestamp, this._minimumBoundary + minOffset, this._maximumBoundary); |
| } |
| return previousMinimumBoundary !== this._minimumBoundary || previousMaximumBoundary !== this._maximumBoundary; |
| } |
| |
| _lowerBound(_request: SDK.NetworkRequest.NetworkRequest): number { |
| return 0; |
| } |
| |
| _upperBound(_request: SDK.NetworkRequest.NetworkRequest): number { |
| return 0; |
| } |
| } |
| |
| // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration) |
| // eslint-disable-next-line @typescript-eslint/naming-convention |
| export const _minimumSpread = 0.1; |
| |
| // TODO(crbug.com/1167717): Make this a const enum again |
| // eslint-disable-next-line rulesdir/const_enum |
| export enum Events { |
| BoundariesChanged = 'BoundariesChanged', |
| } |
| |
| export class NetworkTransferTimeCalculator extends NetworkTimeCalculator { |
| constructor() { |
| super(false); |
| } |
| |
| formatValue(value: number, precision?: number): string { |
| return Number.secondsToString(value - this.zeroTime(), Boolean(precision)); |
| } |
| |
| _lowerBound(request: SDK.NetworkRequest.NetworkRequest): number { |
| return request.issueTime(); |
| } |
| |
| _upperBound(request: SDK.NetworkRequest.NetworkRequest): number { |
| return request.endTime; |
| } |
| } |
| |
| export class NetworkTransferDurationCalculator extends NetworkTimeCalculator { |
| constructor() { |
| super(true); |
| } |
| |
| formatValue(value: number, precision?: number): string { |
| return Number.secondsToString(value, Boolean(precision)); |
| } |
| |
| _upperBound(request: SDK.NetworkRequest.NetworkRequest): number { |
| return request.duration; |
| } |
| } |