// Copyright 2010 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable @devtools/no-imperative-dom-api */ import '../../ui/kit/kit.js'; import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Protocol from '../../generated/protocol.js'; import * as Logs from '../../models/logs/logs.js'; import * as NetworkTimeCalculator from '../../models/network_time_calculator/network_time_calculator.js'; import * as uiI18n from '../../ui/i18n/i18n.js'; import * as ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js'; import * as UI from '../../ui/legacy/legacy.js'; import {Directives, html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js'; import * as VisualLogging from '../../ui/visual_logging/visual_logging.js'; import networkingTimingTableStyles from './networkTimingTable.css.js'; const {repeat, classMap, ifDefined} = Directives; const UIStrings = { /** * @description Text used to label the time taken to receive an HTTP/2 Push message. */ receivingPush: 'Receiving `Push`', /** * @description Text in Request Timing View of the Network panel */ queueing: 'Queueing', /** * @description Text in Request Timing View of the Network panel */ stalled: 'Stalled', /** * @description Text in Request Timing View of the Network panel */ initialConnection: 'Initial connection', /** * @description Text in Request Timing View of the Network panel */ dnsLookup: 'DNS Lookup', /** * @description Text in Request Timing View of the Network panel */ proxyNegotiation: 'Proxy negotiation', /** * @description Text used to label the time taken to read an HTTP/2 Push message. */ readingPush: 'Reading `Push`', /** * @description Text in Request Timing View of the Network panel */ contentDownload: 'Content Download', /** * @description Text in Request Timing View of the Network panel */ requestSent: 'Request sent', /** * @description Text in Request Timing View of the Network panel */ requestToServiceworker: 'Request to `ServiceWorker`', /** * @description Text in Request Timing View of the Network panel */ startup: 'Startup', /** * @description Text in Request Timing View of the Network panel */ respondwith: 'respondWith', /** * @description Text in Request Timing View of the Network panel */ ssl: 'SSL', /** * @description Text for sum */ total: 'Total', /** * @description Text in Request Timing View of the Network panel */ waitingTtfb: 'Waiting for server response', /** * @description Text in Signed Exchange Info View of the Network panel */ label: 'Label', /** * @description Text in Request Timing View of the Network panel */ routerEvaluation: 'Router Evaluation', /** * @description Text in Request Timing View of the Network panel */ routerCacheLookup: 'Cache Lookup', /** * @description Inner element text content in Network Log View Columns of the Network panel */ waterfall: 'Waterfall', /** * @description Text for the duration of something */ duration: 'Duration', /** * @description Text of a DOM element in Request Timing View of the Network panel * @example {120.39ms} PH1 */ queuedAtS: 'Queued at {PH1}', /** * @description Text of a DOM element in Request Timing View of the Network panel * @example {120.39ms} PH1 */ startedAtS: 'Started at {PH1}', /** * @description Text in Request Timing View of the Network panel */ serverPush: 'Server Push', /** * @description Text of a DOM element in Request Timing View of the Network panel */ resourceScheduling: 'Resource Scheduling', /** * @description Text in Request Timing View of the Network panel */ connectionStart: 'Connection Start', /** * @description Text in Request Timing View of the Network panel */ requestResponse: 'Request/Response', /** * @description Text of a DOM element in Request Timing View of the Network panel */ cautionRequestIsNotFinishedYet: 'CAUTION: request is not finished yet!', /** * @description Text in Request Timing View of the Network panel */ explanation: 'Explanation', /** * @description Text of a DOM element in Request Timing View of the Network panel */ serverTiming: 'Server Timing', /** * @description Text of a DOM element in Request Timing View of the Network panel */ time: 'TIME', /** * @description Label for the Server Timing API */ theServerTimingApi: 'the Server Timing API', /** * @description Text to inform about the ServerTiming API, which can be used to report timing information to DevTools about the substeps that the server performed to answer the requests. Timing information is, e.g., the duration of the substep. * @example {https://web.dev/custom-metrics/#server-timing-api} PH1 */ duringDevelopmentYouCanUseSToAdd: 'During development, you can use {PH1} to add insights into the server-side timing of this request.', /** * @description Header for last column of network timing tab. */ durationC: 'DURATION', /** * @description Description for treeitem in ServiceWorker Fetch Details */ originalRequest: 'Original Request', /** * @description Description for treeitem in ServiceWorker Fetch Details */ responseReceived: 'Response Received', /** * @description Text for an unspecified service worker response source */ unknown: 'Unknown', /** * @description Displays how a particular response was fetched * @example {Network fetch} PH1 */ sourceOfResponseS: 'Source of response: {PH1}', /** * @description Name of storage cache from which a response was fetched * @example {v1} PH1 */ cacheStorageCacheNameS: 'Cache storage cache name: {PH1}', /** * @description Text for unknown cache storage name */ cacheStorageCacheNameUnknown: 'Cache storage cache name: Unknown', /** * @description Time at which a response was retrieved * @example {Fri Apr 10 2020 17:20:27 GMT-0700 (Pacific Daylight Time)} PH1 */ retrievalTimeS: 'Retrieval Time: {PH1}', /** * @description Text used to show that serviceworker fetch response source is ServiceWorker Cache Storage */ serviceworkerCacheStorage: '`ServiceWorker` cache storage', /** * @description Text used to show that serviceworker fetch response source is HTTP cache */ fromHttpCache: 'From HTTP cache', /** * @description Text used to show that data was retrieved via a Network fetch */ networkFetch: 'Network fetch', /** * @description Text used to show that data was retrieved using ServiceWorker fallback code */ fallbackCode: 'Fallback code', /** * @description Name of the specified source for SW static routing API. * @example {network} PH1 */ routerMatchedSource: 'Matched source: {PH1}', /** * @description Name of the actually used source for SW static routing API. * @example {network} PH1 */ routerActualSource: 'Actual source: {PH1}', /** * @description Cell title in Network Data Grid Node of the Network panel * @example {Fast 4G} PH1 */ wasThrottled: 'Request was throttled ({PH1})', } as const; const str_ = i18n.i18n.registerUIStrings('panels/network/RequestTimingView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); function timeRangeTitle(name: NetworkTimeCalculator.RequestTimeRangeNames): string { switch (name) { case NetworkTimeCalculator.RequestTimeRangeNames.PUSH: return i18nString(UIStrings.receivingPush); case NetworkTimeCalculator.RequestTimeRangeNames.QUEUEING: return i18nString(UIStrings.queueing); case NetworkTimeCalculator.RequestTimeRangeNames.BLOCKING: return i18nString(UIStrings.stalled); case NetworkTimeCalculator.RequestTimeRangeNames.CONNECTING: return i18nString(UIStrings.initialConnection); case NetworkTimeCalculator.RequestTimeRangeNames.DNS: return i18nString(UIStrings.dnsLookup); case NetworkTimeCalculator.RequestTimeRangeNames.PROXY: return i18nString(UIStrings.proxyNegotiation); case NetworkTimeCalculator.RequestTimeRangeNames.RECEIVING_PUSH: return i18nString(UIStrings.readingPush); case NetworkTimeCalculator.RequestTimeRangeNames.RECEIVING: return i18nString(UIStrings.contentDownload); case NetworkTimeCalculator.RequestTimeRangeNames.SENDING: return i18nString(UIStrings.requestSent); case NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER: return i18nString(UIStrings.requestToServiceworker); case NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_PREPARATION: return i18nString(UIStrings.startup); case NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_ROUTER_EVALUATION: return i18nString(UIStrings.routerEvaluation); case NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_CACHE_LOOKUP: return i18nString(UIStrings.routerCacheLookup); case NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_RESPOND_WITH: return i18nString(UIStrings.respondwith); case NetworkTimeCalculator.RequestTimeRangeNames.SSL: return i18nString(UIStrings.ssl); case NetworkTimeCalculator.RequestTimeRangeNames.TOTAL: return i18nString(UIStrings.total); case NetworkTimeCalculator.RequestTimeRangeNames.WAITING: return i18nString(UIStrings.waitingTtfb); default: return name; } } function groupHeader(name: NetworkTimeCalculator.RequestTimeRangeNames): string { if (name === NetworkTimeCalculator.RequestTimeRangeNames.PUSH) { return i18nString(UIStrings.serverPush); } if (name === NetworkTimeCalculator.RequestTimeRangeNames.QUEUEING) { return i18nString(UIStrings.resourceScheduling); } if (NetworkTimeCalculator.ConnectionSetupRangeNames.has(name)) { return i18nString(UIStrings.connectionStart); } if (NetworkTimeCalculator.ServiceWorkerRangeNames.has(name)) { return 'Service Worker'; } return i18nString(UIStrings.requestResponse); } function getLocalizedResponseSourceForCode(swResponseSource: Protocol.Network.ServiceWorkerResponseSource): Common.UIString.LocalizedString { switch (swResponseSource) { case Protocol.Network.ServiceWorkerResponseSource.CacheStorage: return i18nString(UIStrings.serviceworkerCacheStorage); case Protocol.Network.ServiceWorkerResponseSource.HttpCache: return i18nString(UIStrings.fromHttpCache); case Protocol.Network.ServiceWorkerResponseSource.Network: return i18nString(UIStrings.networkFetch); default: return i18nString(UIStrings.fallbackCode); } } interface ViewInput { requestUnfinished: boolean; requestStartTime: number; requestIssueTime: number; totalDuration: number; startTime: number; endTime: number; timeRanges: NetworkTimeCalculator.RequestTimeRange[]; calculator: NetworkTimeCalculator.NetworkTimeCalculator; serverTimings: SDK.ServerTiming.ServerTiming[]; fetchDetails?: UI.TreeOutline.TreeOutlineInShadow; routerDetails?: UI.TreeOutline.TreeOutlineInShadow; wasThrottled?: SDK.NetworkManager.AppliedNetworkConditions; } type View = (input: ViewInput, output: object, target: HTMLElement) => void; export const DEFAULT_VIEW: View = (input, output, target) => { const revealThrottled = (): void => { if (input.wasThrottled) { void Common.Revealer.reveal(input.wasThrottled); } }; const scale = 100 / (input.endTime - input.startTime); const isClickable = (range: NetworkTimeCalculator.RequestTimeRange): boolean => range.name === 'serviceworker-respondwith' || range.name === 'serviceworker-routerevaluation'; const addServerTiming = (serverTiming: SDK.ServerTiming.ServerTiming): LitTemplate => { const colorGenerator = new Common.Color.Generator({min: 0, max: 360, count: 36}, {min: 50, max: 80, count: undefined}, 80); const isTotal = serverTiming.metric.toLowerCase() === 'total'; const metricDesc = [serverTiming.metric, serverTiming.description].filter(Boolean).join(' — '); const left = serverTiming.value === null ? -1 : scale * (input.endTime - input.startTime - (serverTiming.value / 1000)); const lastRange = input.timeRanges.findLast(range => range.name !== NetworkTimeCalculator.RequestTimeRangeNames.TOTAL); const lastTimingRightEdge = lastRange ? (scale * (input.endTime - lastRange.end)) : 100; const classes = classMap({ ['network-timing-footer']: isTotal, ['server-timing-row']: !isTotal, // Mark entries from a bespoke format ['synthetic']: serverTiming.metric.startsWith('(c'), }); // clang-format off return html`
| ${i18nString(UIStrings.label)} | ${i18nString(UIStrings.waterfall)} | ${i18nString(UIStrings.duration)} | |
|---|---|---|---|
| ${i18nString(UIStrings.queuedAtS, {PH1: input.calculator.formatValue(input.requestIssueTime, 2)})} | |||
| ${i18nString(UIStrings.startedAtS, {PH1: input.calculator.formatValue(input.requestStartTime, 2)})} | |||
| ${group.name} | ${i18nString(UIStrings.durationC)} | ||
| ${timeRangeTitle(range.name)} | ` : html`${timeRangeTitle(range.name)} | `}
|
|
| ${i18nString(UIStrings.cautionRequestIsNotFinishedYet)} | |||
|
|
${input.wasThrottled ? html` |
||
|
|
|||
| ${i18nString(UIStrings.serverTiming)} | ${i18nString(UIStrings.time)} | ||
|
${uiI18n.getFormatLocalizedStringTemplate(str_, UIStrings.duringDevelopmentYouCanUseSToAdd, {PH1:
html` |
|||