blob: e31994c74159611e10cf8b6b61374b764e867d9e [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import * as Common from '../../core/common/common.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 NetworkTimeCalculator from '../../models/network_time_calculator/network_time_calculator.js';
import {assertScreenshot, getCleanTextContentFromElements, renderElementIntoDOM} from '../../testing/DOMHelpers.js';
import {stubNoopSettings} from '../../testing/EnvironmentHelpers.js';
import {setupLocaleHooks} from '../../testing/LocaleHelpers.js';
import {createViewFunctionStub} from '../../testing/ViewFunctionHelpers.js';
import * as Network from './network.js';
const {urlString} = Platform.DevToolsPath;
function createNetworkRequest(
matchedSource: Protocol.Network.ServiceWorkerRouterSource,
actualSource: Protocol.Network.ServiceWorkerRouterSource): SDK.NetworkRequest.NetworkRequest {
const request = SDK.NetworkRequest.NetworkRequest.create(
'requestId' as Protocol.Network.RequestId, urlString`http://devtools-frontend.test`, urlString``, null, null,
null);
request.mimeType = 'application/wasm';
request.finished = true;
const timingInfo: Protocol.Network.ResourceTiming = {
requestTime: 500,
proxyStart: 0,
proxyEnd: 0,
dnsStart: 0,
dnsEnd: 0,
connectStart: 0,
connectEnd: 0,
sslStart: 0,
sslEnd: 0,
workerReady: 400,
workerStart: 500,
workerRouterEvaluationStart: -200,
workerFetchStart: 600,
workerRespondWithSettled: 700,
sendStart: 800,
sendEnd: 900,
pushStart: 0,
pushEnd: 0,
receiveHeadersStart: 1000,
receiveHeadersEnd: 0,
};
if (matchedSource === Protocol.Network.ServiceWorkerRouterSource.Cache) {
timingInfo.workerCacheLookupStart = -100;
}
request.timing = timingInfo;
request.serviceWorkerRouterInfo = {
ruleIdMatched: 1,
matchedSourceType: matchedSource,
actualSourceType: actualSource,
};
return request;
}
describe('ResourceTimingView', () => {
setupLocaleHooks();
it('RequestTimeRanges has router evaluation field with SW router source as network', async () => {
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Network, Protocol.Network.ServiceWorkerRouterSource.Network);
const timingInfo = request.timing as Protocol.Network.ResourceTiming;
const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
const routerEvaluationTime = timingInfo.workerRouterEvaluationStart as number;
const sendStart = timingInfo.sendStart;
const routerEvaluation = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_ROUTER_EVALUATION);
assert.isTrue(Boolean(routerEvaluation), 'worker router evaluation exists');
assert.strictEqual(routerEvaluation?.start, timingInfo.requestTime + routerEvaluationTime / 1000);
assert.strictEqual(routerEvaluation?.end, timingInfo.requestTime + sendStart / 1000);
const cacheLookup = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_CACHE_LOOKUP);
assert.isFalse(Boolean(cacheLookup), 'worker cache lookup does not exist');
});
it('RequestTimeRanges has router evaluation field with SW router source as fetch-event', async () => {
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.FetchEvent, Protocol.Network.ServiceWorkerRouterSource.FetchEvent);
const timingInfo = request.timing as Protocol.Network.ResourceTiming;
const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
const routerEvaluationTime = timingInfo.workerRouterEvaluationStart as number;
const workerStart = timingInfo.workerStart;
const routerEvaluation = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_ROUTER_EVALUATION);
assert.isTrue(Boolean(routerEvaluation), 'worker router evaluation exists');
assert.strictEqual(routerEvaluation?.start, timingInfo.requestTime + routerEvaluationTime / 1000);
assert.strictEqual(routerEvaluation?.end, timingInfo.requestTime + workerStart / 1000);
const cacheLookup = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_CACHE_LOOKUP);
assert.isFalse(Boolean(cacheLookup), 'worker cache lookup does not exist');
});
it('RequestTimeRanges has router evaluation field with SW router source as cache hit', async () => {
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Cache, Protocol.Network.ServiceWorkerRouterSource.Cache);
const timingInfo = request.timing as Protocol.Network.ResourceTiming;
const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
const routerEvaluationTime = timingInfo.workerRouterEvaluationStart as number;
const cacheLookupStart = timingInfo.workerCacheLookupStart as number;
const routerEvaluation = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_ROUTER_EVALUATION);
assert.isTrue(Boolean(routerEvaluation), 'worker router evaluation exists');
assert.strictEqual(routerEvaluation?.start, timingInfo.requestTime + routerEvaluationTime / 1000);
assert.strictEqual(routerEvaluation?.end, timingInfo.requestTime + cacheLookupStart / 1000);
const cacheLookup = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_CACHE_LOOKUP);
assert.isTrue(Boolean(cacheLookup), 'worker cache lookup does not exist');
assert.strictEqual(cacheLookup?.start, timingInfo.requestTime + cacheLookupStart / 1000);
assert.strictEqual(cacheLookup?.end, timingInfo.requestTime + timingInfo.receiveHeadersStart / 1000);
});
it('RequestTimeRanges has router evaluation field with SW router source as cache miss', async () => {
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Cache, Protocol.Network.ServiceWorkerRouterSource.Network);
const timingInfo = request.timing as Protocol.Network.ResourceTiming;
const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
const routerEvaluationTime = timingInfo.workerRouterEvaluationStart as number;
const cacheLookupStart = timingInfo.workerCacheLookupStart as number;
const routerEvaluation = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_ROUTER_EVALUATION);
assert.isTrue(Boolean(routerEvaluation), 'worker router evaluation exists');
assert.strictEqual(routerEvaluation?.start, timingInfo.requestTime + routerEvaluationTime / 1000);
assert.strictEqual(routerEvaluation?.end, timingInfo.requestTime + cacheLookupStart / 1000);
const cacheLookup = timeRanges.find(
timeRange => timeRange.name === NetworkTimeCalculator.RequestTimeRangeNames.SERVICE_WORKER_CACHE_LOOKUP);
assert.isTrue(Boolean(cacheLookup), 'worker cache lookup does not exist');
assert.strictEqual(cacheLookup?.start, timingInfo.requestTime + cacheLookupStart / 1000);
assert.strictEqual(cacheLookup?.end, timingInfo.requestTime + timingInfo.sendStart / 1000);
});
it('Timing table has router evaluation field with detail tabs', async () => {
stubNoopSettings();
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Network, Protocol.Network.ServiceWorkerRouterSource.Network);
const component = Network.RequestTimingView.RequestTimingView.create(
request, new NetworkTimeCalculator.NetworkTimeCalculator(true));
const div = document.createElement('div');
renderElementIntoDOM(div);
component.markAsRoot();
component.show(div);
await component.updateComplete;
// Test if we correctly set details element
const routerEvaluationDetailsElement = document.querySelector('.router-evaluation-timing-bar-details');
assert.isNotNull(routerEvaluationDetailsElement, 'router evaluation details does not exist');
assert.strictEqual(
routerEvaluationDetailsElement.childElementCount, 1,
'router evaluation details child element count does not match');
assert.isNotNull(routerEvaluationDetailsElement.firstElementChild, 'router evaluation first element is non null');
// Test if we correctly set the tree item inside shadow root
const shadowElement = routerEvaluationDetailsElement.firstElementChild.shadowRoot;
assert.isNotNull(shadowElement, 'shadow element does not exist');
const content = getCleanTextContentFromElements(shadowElement, '.network-fetch-details-treeitem');
assert.lengthOf(content, 2, 'does not match the tree item');
// Check the content of the view. Since the value is set from matched to actual,
// the order should be the same.
const networkString = String(Protocol.Network.ServiceWorkerRouterSource.Network);
assert.strictEqual(content[0], `Matched source: ${networkString}`, 'matched source does not match');
assert.strictEqual(content[1], `Actual source: ${networkString}`, 'actual source does not match');
});
it('Timing table shows throttling indicator', async () => {
stubNoopSettings();
const container = document.createElement('div');
renderElementIntoDOM(container, {includeCommonStyles: true});
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Cache, Protocol.Network.ServiceWorkerRouterSource.Cache);
const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
const wasThrottled = new SDK.NetworkManager.AppliedNetworkConditions(SDK.NetworkManager.Slow3GConditions, '');
const input: Parameters<typeof Network.RequestTimingView.DEFAULT_VIEW>[0] = {
requestUnfinished: false,
requestStartTime: 0,
requestIssueTime: 0,
totalDuration: 100,
startTime: 0,
endTime: 100,
timeRanges,
calculator: new NetworkTimeCalculator.NetworkTimeCalculator(true),
serverTimings: [],
wasThrottled
};
Network.RequestTimingView.DEFAULT_VIEW(input, {}, container);
await assertScreenshot('network/request-timing-view-throttling.png');
const icon = container.querySelector<HTMLElement>('devtools-icon[name=watch]');
assert.exists(icon);
const revealStub = sinon.stub(Common.Revealer.RevealerRegistry.instance(), 'reveal');
icon.click();
sinon.assert.calledOnceWithExactly(revealStub, wasThrottled, false);
});
it('correctly passes requestUnfinished to the view', async () => {
stubNoopSettings();
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Network, Protocol.Network.ServiceWorkerRouterSource.Network);
request.finished = false;
const calculator = new NetworkTimeCalculator.NetworkTimeCalculator(true);
const viewStub = createViewFunctionStub(Network.RequestTimingView.RequestTimingView);
const component = new Network.RequestTimingView.RequestTimingView(undefined, viewStub);
renderElementIntoDOM(component);
component.request = request;
component.calculator = calculator;
const input = await viewStub.nextInput;
assert.isTrue(input.requestUnfinished, 'requestUnfinished should be true when request is not finished');
const requestFinished = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Network, Protocol.Network.ServiceWorkerRouterSource.Network);
requestFinished.finished = true;
component.request = requestFinished;
const inputFinished = await viewStub.nextInput;
assert.isFalse(inputFinished.requestUnfinished, 'requestUnfinished should be false when request is finished');
});
it('shows caution message in DEFAULT_VIEW if and only if requestUnfinished is true', async () => {
stubNoopSettings();
const container = document.createElement('div');
renderElementIntoDOM(container);
const request = createNetworkRequest(
Protocol.Network.ServiceWorkerRouterSource.Network, Protocol.Network.ServiceWorkerRouterSource.Network);
const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
const calculator = new NetworkTimeCalculator.NetworkTimeCalculator(true);
const baseInput: Parameters<typeof Network.RequestTimingView.DEFAULT_VIEW>[0] = {
requestUnfinished: false,
requestStartTime: 0,
requestIssueTime: 0,
totalDuration: 100,
startTime: 0,
endTime: 100,
timeRanges,
calculator,
serverTimings: [],
};
// Case 1: requestUnfinished = true
Network.RequestTimingView.DEFAULT_VIEW({...baseInput, requestUnfinished: true}, {}, container);
const cautionElementTrue = container.querySelector('.caution');
assert.isNotNull(cautionElementTrue, 'caution element should exist when requestUnfinished is true');
assert.include(cautionElementTrue?.textContent, 'CAUTION: request is not finished yet!');
// Case 2: requestUnfinished = false
Network.RequestTimingView.DEFAULT_VIEW({...baseInput, requestUnfinished: false}, {}, container);
const cautionElementFalse = container.querySelector('.caution');
assert.isNull(cautionElementFalse, 'caution element should not exist when requestUnfinished is false');
});
});