// 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 { assert } from 'chai';
import sinon from 'sinon';
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 Logs from '../../models/logs/logs.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 ObjectUI from '../../ui/legacy/components/object_ui/object_ui.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Network from './network.js';
const { urlString } = Platform.DevToolsPath;
function createNetworkRequest(matchedSource, actualSource) {
    const request = SDK.NetworkRequest.NetworkRequest.create('requestId', urlString `http://devtools-frontend.test`, urlString ``, null, null, null);
    request.mimeType = 'application/wasm';
    request.finished = true;
    const timingInfo = {
        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 === "cache" /* 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("network" /* Protocol.Network.ServiceWorkerRouterSource.Network */, "network" /* Protocol.Network.ServiceWorkerRouterSource.Network */);
        const timingInfo = request.timing;
        const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
        const routerEvaluationTime = timingInfo.workerRouterEvaluationStart;
        const sendStart = timingInfo.sendStart;
        const routerEvaluation = timeRanges.find(timeRange => timeRange.name === "serviceworker-routerevaluation" /* 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 === "serviceworker-cachelookup" /* 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("fetch-event" /* Protocol.Network.ServiceWorkerRouterSource.FetchEvent */, "fetch-event" /* Protocol.Network.ServiceWorkerRouterSource.FetchEvent */);
        const timingInfo = request.timing;
        const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
        const routerEvaluationTime = timingInfo.workerRouterEvaluationStart;
        const workerStart = timingInfo.workerStart;
        const routerEvaluation = timeRanges.find(timeRange => timeRange.name === "serviceworker-routerevaluation" /* 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 === "serviceworker-cachelookup" /* 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("cache" /* Protocol.Network.ServiceWorkerRouterSource.Cache */, "cache" /* Protocol.Network.ServiceWorkerRouterSource.Cache */);
        const timingInfo = request.timing;
        const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
        const routerEvaluationTime = timingInfo.workerRouterEvaluationStart;
        const cacheLookupStart = timingInfo.workerCacheLookupStart;
        const routerEvaluation = timeRanges.find(timeRange => timeRange.name === "serviceworker-routerevaluation" /* 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 === "serviceworker-cachelookup" /* 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("cache" /* Protocol.Network.ServiceWorkerRouterSource.Cache */, "network" /* Protocol.Network.ServiceWorkerRouterSource.Network */);
        const timingInfo = request.timing;
        const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
        const routerEvaluationTime = timingInfo.workerRouterEvaluationStart;
        const cacheLookupStart = timingInfo.workerCacheLookupStart;
        const routerEvaluation = timeRanges.find(timeRange => timeRange.name === "serviceworker-routerevaluation" /* 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 === "serviceworker-cachelookup" /* 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("network" /* Protocol.Network.ServiceWorkerRouterSource.Network */, "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("network" /* 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("cache" /* Protocol.Network.ServiceWorkerRouterSource.Cache */, "cache" /* Protocol.Network.ServiceWorkerRouterSource.Cache */);
        const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
        const wasThrottled = new SDK.NetworkManager.AppliedNetworkConditions(SDK.NetworkManager.Slow3GConditions, '');
        const input = {
            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('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("network" /* Protocol.Network.ServiceWorkerRouterSource.Network */, "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("network" /* Protocol.Network.ServiceWorkerRouterSource.Network */, "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("network" /* Protocol.Network.ServiceWorkerRouterSource.Network */, "network" /* Protocol.Network.ServiceWorkerRouterSource.Network */);
        const timeRanges = NetworkTimeCalculator.calculateRequestTimeRanges(request, 100);
        const calculator = new NetworkTimeCalculator.NetworkTimeCalculator(true);
        const baseInput = {
            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');
    });
    it('renders read-only object properties for Service Worker fetch details', async () => {
        stubNoopSettings();
        const request = createNetworkRequest("network" /* Protocol.Network.ServiceWorkerRouterSource.Network */, "network" /* Protocol.Network.ServiceWorkerRouterSource.Network */);
        request.fetchedViaServiceWorker = true;
        const origRequest = {
            url: request.url(),
            method: 'GET',
            headers: {},
            initialPriority: "High" /* Protocol.Network.ResourcePriority.High */,
            referrerPolicy: "strict-origin-when-cross-origin" /* Protocol.Network.RequestReferrerPolicy.StrictOriginWhenCrossOrigin */,
        };
        const response = {
            url: request.url(),
            status: 200,
            statusText: 'OK',
            headers: {},
            mimeType: 'text/html',
            charset: '',
            connectionReused: false,
            connectionId: 0,
            encodedDataLength: 0,
            securityState: "secure" /* Protocol.Security.SecurityState.Secure */,
        };
        sinon.stub(Logs.NetworkLog.NetworkLog.instance(), 'originalRequestForURL').returns(origRequest);
        sinon.stub(Logs.NetworkLog.NetworkLog.instance(), 'originalResponseForURL').returns(response);
        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;
        const detailsTreeElement = component.contentElement.querySelector('.network-fetch-timing-bar-details > *');
        assert.exists(detailsTreeElement);
        assert.exists(detailsTreeElement.shadowRoot);
        const rootElements = detailsTreeElement.shadowRoot.querySelectorAll('li.object-properties-section-root-element');
        assert.lengthOf(rootElements, 2);
        for (const rootElementNode of rootElements) {
            const rootElement = UI.TreeOutline.TreeElement.getTreeElementBylistItemNode(rootElementNode);
            assert.exists(rootElement);
            await rootElement.onpopulate();
            const firstProperty = rootElement.childAt(0);
            assert.instanceOf(firstProperty, ObjectUI.ObjectPropertiesSection.ObjectPropertyTreeElement);
            assert.isFalse(firstProperty.editable);
        }
    });
});
//# sourceMappingURL=RequestTimingView.test.js.map