// Copyright 2023 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 Host from '../../core/host/host.js';
import { renderElementIntoDOM } from '../../testing/DOMHelpers.js';
import { expectCall, expectCalled } from '../../testing/ExpectStubCall.js';
import { getVeId } from '../../testing/VisualLoggingHelpers.js';
import * as VisualLoggingTesting from './visual_logging-testing.js';
describe('LoggingDriver', () => {
    let recordImpression;
    let throttler;
    let throttle;
    let processingThrottler;
    let onerror;
    before(() => {
        onerror = window.onerror;
        window.onerror = (message, url, lineNumber, column, error) => {
            if (message !== 'ResizeObserver loop completed with undelivered notifications.' && onerror) {
                onerror.apply(window, [message, url, lineNumber, column, error]);
            }
        };
    });
    after(() => {
        window.onerror = onerror;
    });
    beforeEach(() => {
        throttler = new Common.Throttler.Throttler(1000000000);
        throttle = sinon.stub(throttler, 'schedule');
        processingThrottler = new Common.Throttler.Throttler(1000000000);
        recordImpression = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordImpression');
    });
    afterEach(async () => {
        await VisualLoggingTesting.LoggingDriver.stopLogging();
    });
    async function addLoggableElements() {
        const parent = document.createElement('div');
        parent.id = 'parent';
        parent.setAttribute('jslog', 'TreeItem; track: hover');
        parent.style.width = '300px';
        parent.style.height = '300px';
        const element = document.createElement('div');
        element.id = 'element';
        element.setAttribute('jslog', 'TreeItem; context:42; track: click, keydown, hover, drag, resize, change');
        element.style.width = '300px';
        element.style.height = '300px';
        parent.appendChild(element);
        renderElementIntoDOM(parent);
        await new Promise(resolve => setTimeout(resolve, 0));
    }
    it('logs impressions on startLogging', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [
            { id: getVeId('#element'), type: 1, context: 42, parent: getVeId('#parent'), width: 300, height: 300 },
            { id: getVeId('#parent'), type: 1, width: 300, height: 300 },
        ]);
    });
    async function assertImpressionRecordedDeferred() {
        const [work] = await expectCalled(throttle);
        sinon.assert.notCalled(recordImpression);
        await work();
        sinon.assert.called(recordImpression);
    }
    it('does not log impressions when document hidden', async () => {
        await addLoggableElements();
        sinon.stub(document, 'hidden').value(true);
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        sinon.assert.notCalled(recordImpression);
    });
    it('does not log impressions when parent hidden', async () => {
        await addLoggableElements();
        const parent = document.getElementById('parent');
        parent.style.height = '0';
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        sinon.assert.notCalled(recordImpression);
    });
    it('logs impressions when visibility changes', async () => {
        let hidden = true;
        await addLoggableElements();
        sinon.stub(document, 'hidden').get(() => hidden);
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        hidden = false;
        const event = document.createEvent('Event');
        event.initEvent('visibilitychange', true, true);
        document.dispatchEvent(event);
        await assertImpressionRecordedDeferred();
    });
    it('logs impressions on scroll', async () => {
        await addLoggableElements();
        const parent = document.getElementById('parent');
        parent.style.marginTop = '2000px';
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        const scrollend = sinon.stub();
        window.addEventListener('scrollend', scrollend);
        window.scrollTo({
            top: 2000,
            left: 0,
            behavior: 'instant',
        });
        await expectCalled(scrollend);
        await assertImpressionRecordedDeferred();
        scrollend.resetHistory();
        window.scrollTo({
            top: 0,
            left: 0,
            behavior: 'instant',
        });
        await expectCalled(scrollend);
    });
    it('logs impressions on mutation', async () => {
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        await addLoggableElements();
        await assertImpressionRecordedDeferred();
    });
    it('logs impressions on mutation in shadow DOM', async () => {
        const parent = document.createElement('div');
        renderElementIntoDOM(parent);
        const shadow = parent.attachShadow({ mode: 'open' });
        const shadowContent = document.createElement('div');
        shadow.appendChild(shadowContent);
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        shadowContent.innerHTML = '<div jslog="TreeItem" style="width:300px;height:300px"></div>';
        await assertImpressionRecordedDeferred();
    });
    it('does not log impressions for content in closed details element but does when opened', async () => {
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        const details = document.createElement('details');
        details.style.width = '100px';
        details.style.height = '100px';
        details.innerHTML = '<div id="details-content" jslog="TreeItem" style="width: 100px; height: 100px;"></div>';
        renderElementIntoDOM(details);
        let [work] = await expectCalled(throttle);
        await work();
        // This will fail with the bug, as an impression will be recorded.
        sinon.assert.notCalled(recordImpression);
        throttle.resetHistory();
        recordImpression.resetHistory();
        details.open = true;
        // Opening details will trigger mutation observer.
        [work] = await expectCalled(throttle);
        await work();
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [{
                id: getVeId('#details-content'),
                type: 1,
                width: 100,
                height: 100,
            }]);
    });
    it('logs impressions on mutation in additional document', async () => {
        const iframe = document.createElement('iframe');
        renderElementIntoDOM(iframe);
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        const iframeDocument = iframe.contentDocument;
        assert.exists(iframeDocument);
        await VisualLoggingTesting.LoggingDriver.addDocument(iframeDocument);
        iframeDocument.body.innerHTML = '<div jslog="TreeItem" style="width:300px;height:300px"></div>';
        await assertImpressionRecordedDeferred();
    });
    it('correctly determines visibility in additional document', async () => {
        const iframe = document.createElement('iframe');
        renderElementIntoDOM(iframe);
        iframe.style.width = '100px';
        iframe.style.height = '100px';
        iframe.width = '100';
        iframe.height = '100';
        const iframeDocument = iframe.contentDocument;
        assert.exists(iframeDocument);
        iframeDocument.body.innerHTML = // Second div should not be out of viewport and not logged
            `<div style="width:150px;height:150px"></div>
         <div jslog="TreeItem" style="width:150px;height:150px"></div>`;
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        await VisualLoggingTesting.LoggingDriver.addDocument(iframeDocument);
        sinon.assert.notCalled(recordImpression);
    });
    it('hashes a string context', async () => {
        const element = document.createElement('div');
        element.setAttribute('jslog', 'TreeItem; track: hover; context: foobar');
        element.style.width = '300px';
        element.style.height = '300px';
        renderElementIntoDOM(element);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        assert.strictEqual(recordImpression.firstCall.firstArg.impressions[0]?.context, -103332984);
    });
    it('logs clicks', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        const element = document.getElementById('element');
        element.click();
        await expectCalled(recordClick);
    });
    it('logs right clicks', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('contextmenu'));
        await expectCalled(recordClick);
    });
    it('logs middle clicks', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('auxclick'));
        await expectCalled(recordClick);
    });
    it('does not log clicks if not configured', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        const parent = document.getElementById('parent');
        parent.click();
        await new Promise(resolve => setTimeout(resolve, 0));
        sinon.assert.notCalled(recordClick);
    });
    it('does not log click on double click', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        element.setAttribute('jslog', 'TreeItem; context:42; track: click, dblclick');
        await VisualLoggingTesting.LoggingDriver.startLogging({ clickLogThrottler: throttler });
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        element.dispatchEvent(new MouseEvent('click'));
        element.dispatchEvent(new MouseEvent('dblclick'));
        const [logging] = await expectCalled(throttle);
        sinon.assert.calledTwice(throttle);
        sinon.assert.notCalled(recordClick);
        await logging();
        sinon.assert.calledOnce(recordClick);
        assert.isTrue(recordClick.firstCall.firstArg.doubleClick);
    });
    it('does not log click on parent when clicked on child', async () => {
        await addLoggableElements();
        const parent = document.getElementById('parent');
        parent.setAttribute('jslog', 'TreeItem; track: click');
        await VisualLoggingTesting.LoggingDriver.startLogging({ clickLogThrottler: throttler });
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        const element = document.getElementById('element');
        element.click();
        const [logging] = await expectCalled(throttle);
        sinon.assert.notCalled(recordClick);
        await logging();
        sinon.assert.calledOnce(recordClick);
        assert.strictEqual(recordClick.firstCall.firstArg.veid, getVeId(element));
    });
    const logsSelectOptions = (event) => async () => {
        const parent = document.createElement('div');
        parent.innerHTML = `
      <select jslog="TreeItem; context: 0" id="select" style="width: 30px; height: 20px">
        <option jslog="TreeItem; context: 1">1</option>
        <option jslog="TreeItem; context: 2">2</option>
      </select>`;
        renderElementIntoDOM(parent);
        const select = document.getElementById('select');
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler, clickLogThrottler: throttler });
        sinon.assert.calledOnce(recordImpression);
        const impressions = recordImpression.firstCall.firstArg.impressions;
        assert.sameDeepMembers(impressions, [
            { id: getVeId(select), type: 1, width: 30, height: 20, context: 0 },
        ]);
        recordImpression.resetHistory();
        throttle.callsArg(0);
        select.dispatchEvent(event);
        await expectCalled(recordClick);
        sinon.assert.calledOnce(recordClick);
        assert.strictEqual(recordClick.firstCall.firstArg.veid, getVeId(select));
        await expectCalled(recordImpression);
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [
            { id: getVeId('option:first-child'), type: 1, parent: getVeId(select), context: 1, width: 0, height: 0 },
            { id: getVeId('option:last-child'), type: 1, parent: getVeId(select), context: 2, width: 0, height: 0 },
        ]);
    };
    it('logs impressions on select options on click', logsSelectOptions(new MouseEvent('click')));
    it('logs impressions on select options on space press', logsSelectOptions(new KeyboardEvent('keypress', { key: ' ' })));
    it('logs impressions on select options on F4', logsSelectOptions(new KeyboardEvent('keydown', { code: 'F4' })));
    it('logs option click on select change', async () => {
        const parent = document.createElement('div');
        parent.innerHTML = `
      <select jslog="TreeItem; context: 0" id="select">
        <option jslog="TreeItem; context: 1; track: click">1</option>
        <option jslog="TreeItem; context: 2; track: click">2</option>
      </select>`;
        renderElementIntoDOM(parent);
        await VisualLoggingTesting.LoggingDriver.startLogging({ clickLogThrottler: throttler });
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        const select = document.getElementById('select');
        assert.exists(select);
        select.selectedIndex = 1;
        select.dispatchEvent(new Event('change'));
        await expectCalled(throttle).then(([logging]) => logging());
        sinon.assert.calledOnce(recordClick);
        assert.deepEqual(recordClick.firstCall.firstArg, { veid: getVeId(select.selectedOptions[0]), doubleClick: false });
    });
    it('logs keydown', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ keyboardLogThrottler: throttler });
        const recordKeyDown = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordKeyDown');
        const element = document.getElementById('element');
        element.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }));
        element.dispatchEvent(new KeyboardEvent('keydown', { key: 'b' }));
        const [logging] = await expectCalled(throttle);
        sinon.assert.calledTwice(throttle);
        sinon.assert.notCalled(recordKeyDown);
        await logging();
        sinon.assert.calledOnce(recordKeyDown);
    });
    it('logs keydown for specific codes', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        element.setAttribute('jslog', 'TreeItem; context:42; track: keydown: KeyA|KeyB');
        await VisualLoggingTesting.LoggingDriver.startLogging({ keyboardLogThrottler: throttler });
        const recordKeyDown = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordKeyDown');
        element.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyC' }));
        await new Promise(resolve => setTimeout(resolve, 0));
        sinon.assert.notCalled(throttle);
        element.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyA' }));
        let [logging] = await expectCalled(throttle);
        sinon.assert.notCalled(recordKeyDown);
        await logging();
        sinon.assert.calledOnce(recordKeyDown);
        recordKeyDown.resetHistory();
        element.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyB' }));
        [logging] = await expectCalled(throttle);
        sinon.assert.notCalled(recordKeyDown);
        await logging();
        sinon.assert.calledOnce(recordKeyDown);
    });
    it('logs change', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.getElementById('element');
        element.dispatchEvent(new Event('change'));
        sinon.assert.calledOnce(recordChange);
    });
    it('logs change for each input type', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.getElementById('element');
        element.dispatchEvent(new InputEvent('input', { inputType: 'insertText' }));
        await new Promise(resolve => setTimeout(resolve, 0));
        sinon.assert.notCalled(recordChange);
        element.dispatchEvent(new InputEvent('input', { inputType: 'insertText' }));
        await new Promise(resolve => setTimeout(resolve, 0));
        sinon.assert.notCalled(recordChange);
        let logging = expectCalled(recordChange);
        element.dispatchEvent(new InputEvent('input', { inputType: 'inserFromPaste' }));
        await logging;
        logging = expectCalled(recordChange);
        element.dispatchEvent(new InputEvent('input', { inputType: 'inserFromDrop' }));
        await logging;
        logging = expectCalled(recordChange);
        element.dispatchEvent(new Event('change'));
        await logging;
    });
    it('logs change on focus out after input', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.getElementById('element');
        element.dispatchEvent(new InputEvent('input', { inputType: 'insertText' }));
        element.dispatchEvent(new Event('focusout'));
        await expectCalled(recordChange);
    });
    it('logs change on new impressions', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler });
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.getElementById('element');
        const parent = document.getElementById('parent');
        element.dispatchEvent(new InputEvent('input', { inputType: 'insertText' }));
        throttle.callsArg(0);
        parent.appendChild(element.cloneNode());
        await expectCalled(recordChange);
    });
    it('logs change on resize', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler });
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.getElementById('element');
        element.dispatchEvent(new InputEvent('input', { inputType: 'insertText' }));
        throttle.callsArg(0);
        element.style.width = '400px';
        await expectCalled(recordChange);
    });
    it('does not log change on focus out without input', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging();
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.getElementById('element');
        element.dispatchEvent(new Event('focusout'));
        await new Promise(resolve => setTimeout(resolve, 0));
        assert.isFalse(recordChange.calledOnce);
    });
    it('logs state with change of a checkbox', async () => {
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.createElement('input');
        element.setAttribute('jslog', 'TreeItem; track: change');
        element.type = 'checkbox';
        element.checked = true;
        renderElementIntoDOM(element);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        let logging = expectCall(recordChange);
        element.dispatchEvent(new Event('change'));
        let [event] = await logging;
        assert.strictEqual(event.context, 1530936795);
        element.checked = false;
        logging = expectCall(recordChange);
        element.dispatchEvent(new Event('change'));
        [event] = await logging;
        assert.strictEqual(event.context, 1936227034);
    });
    it('logs state with change of a radio', async () => {
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const element = document.createElement('input');
        element.setAttribute('jslog', 'TreeItem; track: change');
        element.type = 'radio';
        element.checked = true;
        renderElementIntoDOM(element);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        let logging = expectCall(recordChange);
        element.dispatchEvent(new Event('change'));
        let [event] = await logging;
        assert.strictEqual(event.context, 1530936795);
        element.checked = false;
        logging = expectCall(recordChange);
        element.dispatchEvent(new Event('change'));
        [event] = await logging;
        assert.strictEqual(event.context, 1936227034);
    });
    it('logs state with change of a label`s control', async () => {
        const recordChange = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordChange');
        const label = document.createElement('label');
        label.setAttribute('jslog', 'TreeItem; track: change');
        const input = document.createElement('input');
        input.type = 'radio';
        input.checked = true;
        input.style.display = 'none';
        label.appendChild(input);
        renderElementIntoDOM(label);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        let logging = expectCall(recordChange);
        input.dispatchEvent(new Event('change'));
        let [event] = await logging;
        assert.strictEqual(event.context, 1530936795);
        input.checked = false;
        logging = expectCall(recordChange);
        input.dispatchEvent(new Event('change'));
        [event] = await logging;
        assert.strictEqual(event.context, 1936227034);
    });
    it('logs hover', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ hoverLogThrottler: throttler });
        const recordHover = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordHover');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('mouseover'));
        const [logging] = await expectCalled(throttle);
        sinon.assert.notCalled(recordHover);
        await logging();
        sinon.assert.calledOnce(recordHover);
    });
    it('does not log hover if too short', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ hoverLogThrottler: throttler });
        const recordHover = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordHover');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('mouseover'));
        await expectCalled(throttle);
        sinon.assert.notCalled(recordHover);
        element.dispatchEvent(new MouseEvent('mouseout'));
        await expectCalled(throttle).then(([work]) => work());
        sinon.assert.notCalled(recordHover);
    });
    it('does not log hover if in descendent', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ hoverLogThrottler: throttler });
        const recordHover = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordHover');
        const parent = document.getElementById('parent');
        const element = document.getElementById('element');
        parent.dispatchEvent(new MouseEvent('mouseover'));
        await expectCalled(throttle);
        throttle.resetHistory();
        element.dispatchEvent(new MouseEvent('mouseover'));
        await expectCalled(throttle).then(([work]) => work());
        sinon.assert.called(recordHover);
        assert.deepEqual(recordHover.firstCall.firstArg, { veid: getVeId(element) });
    });
    it('logs drag', async () => {
        const dragLogThrottler = new Common.Throttler.Throttler(1000000000);
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ dragLogThrottler });
        const recordDrag = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordDrag');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('pointerdown'));
        assert.exists(dragLogThrottler.process);
        sinon.assert.notCalled(recordDrag);
        await dragLogThrottler.process?.();
        sinon.assert.called(recordDrag);
        sinon.assert.calledOnce(recordDrag);
    });
    it('does not log drag if too short in time', async () => {
        const dragLogThrottler = new Common.Throttler.Throttler(1000000000);
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ dragLogThrottler });
        const recordDrag = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordDrag');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('pointerdown'));
        assert.exists(dragLogThrottler.process);
        sinon.assert.notCalled(recordDrag);
        element.dispatchEvent(new MouseEvent('pointerup'));
        await dragLogThrottler.process?.();
        sinon.assert.notCalled(recordDrag);
    });
    it('logs drag if short in time but long in distance', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ dragLogThrottler: throttler });
        const recordDrag = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordDrag');
        const element = document.getElementById('element');
        element.dispatchEvent(new MouseEvent('pointerdown', { screenX: 0, screenY: 0 }));
        await expectCalled(throttle);
        sinon.assert.notCalled(recordDrag);
        element.dispatchEvent(new MouseEvent('pointerup', { screenX: 100, screenY: 100 }));
        await throttler.process?.();
        sinon.assert.notCalled(recordDrag);
    });
    it('logs resize', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler, processingThrottler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        const element = document.getElementById('element');
        element.style.height = '400px';
        const [logging] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        await logging();
        sinon.assert.calledOnce(recordResize);
    });
    it('does not log resize if too small', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        const element = document.getElementById('element');
        element.style.height = '301px';
        sinon.assert.notCalled(recordResize);
    });
    it('logs resize on visibility change', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler, processingThrottler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        const element = document.getElementById('element');
        element.style.display = 'none';
        const [logging] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        logging();
        await expectCalled(recordResize);
        sinon.assert.calledOnce(recordResize);
        assert.deepEqual(recordResize.firstCall.firstArg, { veid: getVeId(element), width: 0, height: 0 });
        recordResize.resetHistory();
        element.style.display = 'block';
        sinon.assert.notCalled(recordResize);
        throttle.callsArg(0);
        await expectCall(recordResize);
        sinon.assert.calledOnce(recordResize);
        assert.deepEqual(recordResize.firstCall.firstArg, { veid: getVeId(element), width: 300, height: 300 });
    });
    it('throttles resize per element', async () => {
        await addLoggableElements();
        const element1 = document.getElementById('element');
        const element2 = element1.cloneNode();
        document.getElementById('parent')?.appendChild(element2);
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler, processingThrottler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        element1.style.height = '200px';
        await expectCall(throttle, { callCount: 2 });
        element2.style.height = '200px';
        await expectCall(throttle, { callCount: 2 });
        element1.style.height = '10px';
        await expectCall(throttle, { callCount: 2 });
        element2.style.height = '10px';
        const [work] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        await work();
        sinon.assert.calledTwice(recordResize);
        assert.strictEqual(recordResize.firstCall.firstArg.height, 10);
        assert.strictEqual(recordResize.lastCall.firstArg.height, 10);
        assert.notStrictEqual(recordResize.firstCall.firstArg.veid, recordResize.lastCall.firstArg.veid);
    });
    it('only logs resize of the outer element when disappearing', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        const child = document.createElement('div');
        child.setAttribute('jslog', 'TreeItem; track: resize');
        child.style.width = '100%';
        child.style.height = '100%';
        element.appendChild(child);
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler, processingThrottler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        element.style.width = '0';
        const [work] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        await work();
        await expectCalled(recordResize);
        sinon.assert.calledOnce(recordResize);
        assert.deepEqual(recordResize.firstCall.firstArg, { veid: getVeId(element), width: 0, height: 0 });
    });
    it('only logs resize of the outer element when appearing', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        element.style.width = '0';
        const child = document.createElement('div');
        child.setAttribute('jslog', 'TreeItem; track: resize');
        child.style.width = '100%';
        child.style.height = '100%';
        element.appendChild(child);
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler, processingThrottler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        element.style.width = '400px';
        const [work] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        await work();
        await expectCalled(recordResize);
        sinon.assert.calledOnce(recordResize);
        assert.deepEqual(recordResize.firstCall.firstArg, { veid: getVeId(element), width: 400, height: 300 });
    });
    it('logs regular resize of both elements', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        const child = document.createElement('div');
        child.setAttribute('jslog', 'TreeItem; track: resize');
        child.style.width = '100%';
        child.style.height = '100%';
        element.appendChild(child);
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler, processingThrottler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        element.style.width = '400px';
        const [work] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        await work();
        await expectCalled(recordResize);
        sinon.assert.calledTwice(recordResize);
        assert.sameDeepMembers([recordResize.firstCall.firstArg, recordResize.lastCall.firstArg], [{ veid: getVeId(element), width: 400, height: 300 }, { veid: getVeId(child), width: 400, height: 300 }]);
    });
    it('does not log resize intial impressions due to visibility change', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        element.style.display = 'none';
        await VisualLoggingTesting.LoggingDriver.startLogging({ processingThrottler: throttler, resizeLogThrottler: throttler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        recordImpression.resetHistory();
        element.style.display = 'block';
        await expectCalled(throttle).then(([work]) => work());
        sinon.assert.called(throttle);
        sinon.assert.calledOnce(recordImpression);
        sinon.assert.notCalled(recordResize);
        await new Promise(resolve => setTimeout(resolve, 0));
        sinon.assert.notCalled(recordResize);
    });
    it('properly handles the switch between visible elements', async () => {
        await addLoggableElements();
        const element1 = document.getElementById('element');
        const child = document.createElement('div');
        child.id = 'child';
        child.setAttribute('jslog', 'TreeItem; track: resize');
        child.style.width = '100%';
        child.style.height = '100%';
        element1.appendChild(child);
        const element2 = element1.cloneNode(/* deep=*/ true);
        element2.id = 'element2';
        document.getElementById('parent')?.appendChild(element2);
        // First ensure both top level elements have impressions logged
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        // Now hide one and wait for logging to finish
        throttle.callsArg(0);
        element2.style.display = 'none';
        await expectCalled(recordResize, { callCount: 1 });
        throttle.reset();
        recordResize.reset();
        // Now the actual test: hiding one element and show the other one
        element1.style.display = 'none';
        element2.style.display = 'block';
        // Throttler is called by both resize and intersection observer for each element
        await expectCalled(throttle, { callCount: 4 }).then(([work]) => work());
        sinon.assert.calledTwice(recordResize);
        assert.sameDeepMembers(recordResize.getCalls().map(c => c.firstArg), [
            { veid: VisualLoggingTesting.LoggingState.getLoggingState(element1)?.veid, width: 0, height: 0 },
            { veid: VisualLoggingTesting.LoggingState.getLoggingState(element2)?.veid, width: 300, height: 300 },
        ]);
    });
    it('logs resize when removed from DOM', async () => {
        await addLoggableElements();
        await VisualLoggingTesting.LoggingDriver.startLogging({ resizeLogThrottler: throttler });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        const element = document.getElementById('element');
        const parent = document.getElementById('parent');
        parent.removeChild(element);
        const [logging] = await expectCall(throttle, { callCount: 2 });
        sinon.assert.notCalled(recordResize);
        await logging();
        sinon.assert.calledOnce(recordResize);
        assert.deepEqual(recordResize.firstCall.firstArg, { veid: getVeId(element), width: 0, height: 0 });
    });
    it('logs click, then resize, then impressions', async () => {
        await addLoggableElements();
        const processingThrottler = new Common.Throttler.Throttler(10);
        const clickLogThrottler = new Common.Throttler.Throttler(100);
        const keyboardLogThrottler = new Common.Throttler.Throttler(100);
        const resizeLogThrottler = new Common.Throttler.Throttler(100);
        await VisualLoggingTesting.LoggingDriver.startLogging({
            processingThrottler,
            clickLogThrottler,
            keyboardLogThrottler,
            resizeLogThrottler,
        });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        const recordClick = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordClick');
        recordImpression.resetHistory();
        const element = document.getElementById('element');
        const parent = document.getElementById('parent');
        parent.removeChild(element);
        parent.appendChild(element.cloneNode());
        element.click();
        await Promise.all([
            expectCalled(recordImpression),
            expectCalled(recordResize),
            expectCalled(recordClick),
        ]);
        assert.isTrue(recordClick.calledBefore(recordResize));
        assert.isTrue(recordResize.calledBefore(recordImpression));
    });
    // Flaky test.
    it.skip('[crbug.com/453711161] logs keydown, then resize, then impressions', async () => {
        await addLoggableElements();
        const element = document.getElementById('element');
        element.setAttribute('jslog', 'TreeItem; context:42; track: keydown: KeyA, resize');
        const keyboardLogThrottler = new Common.Throttler.Throttler(100);
        const resizeLogThrottler = new Common.Throttler.Throttler(100);
        await VisualLoggingTesting.LoggingDriver.startLogging({
            processingThrottler: throttler,
            keyboardLogThrottler,
            resizeLogThrottler,
        });
        const recordResize = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordResize');
        const recordKeyDown = sinon.stub(Host.InspectorFrontendHost.InspectorFrontendHostInstance, 'recordKeyDown');
        recordImpression.resetHistory();
        throttle.callsArg(0);
        const parent = document.getElementById('parent');
        parent.removeChild(element);
        parent.appendChild(element.cloneNode());
        element.dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyA', key: 'a' }));
        await Promise.all([
            expectCalled(recordImpression),
            expectCalled(recordResize),
            expectCalled(recordKeyDown),
        ]);
        assert.isTrue(recordKeyDown.calledBefore(recordResize));
        assert.isTrue(recordResize.calledBefore(recordImpression));
    });
    it('logs non-DOM impressions', async () => {
        await addLoggableElements();
        const loggable = {};
        const parent = document.getElementById('parent');
        VisualLoggingTesting.NonDomState.registerLoggable(loggable, { ve: 1, context: '123' }, parent);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [
            { id: getVeId(loggable), type: 1, context: 123, parent: getVeId(parent), width: 0, height: 0 },
            { id: getVeId('#element'), type: 1, context: 42, parent: getVeId(parent), width: 300, height: 300 },
            { id: getVeId(parent), type: 1, width: 300, height: 300 },
        ]);
    });
    it('logs non-DOM impressions after parent was logged', async () => {
        await addLoggableElements();
        const loggable1 = {};
        const parent = document.getElementById('parent');
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        VisualLoggingTesting.NonDomState.registerLoggable(loggable1, { ve: 1, context: '123' }, parent);
        recordImpression.resetHistory();
        await VisualLoggingTesting.LoggingDriver.scheduleProcessing();
        await expectCalled(recordImpression);
        assert.sameDeepMembers(recordImpression.lastCall.firstArg.impressions, [
            { id: getVeId(loggable1), type: 1, context: 123, parent: getVeId(parent), width: 0, height: 0 },
        ]);
        recordImpression.resetHistory();
        const loggable2 = {};
        VisualLoggingTesting.NonDomState.registerLoggable(loggable2, { ve: 1, context: '345' }, parent);
        await VisualLoggingTesting.LoggingDriver.scheduleProcessing();
        await expectCalled(recordImpression);
        assert.sameDeepMembers(recordImpression.lastCall.firstArg.impressions, [
            { id: getVeId(loggable2), type: 1, context: 345, parent: getVeId(parent), width: 0, height: 0 },
        ]);
    });
    it('logs root non-DOM impressions', async () => {
        await addLoggableElements();
        const loggable = {};
        VisualLoggingTesting.NonDomState.registerLoggable(loggable, { ve: 1, context: '123' }, undefined);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [
            { id: getVeId(loggable), type: 1, context: 123, width: 0, height: 0 },
            { id: getVeId('#element'), type: 1, context: 42, parent: getVeId('#parent'), width: 300, height: 300 },
            { id: getVeId('#parent'), type: 1, width: 300, height: 300 },
        ]);
        assert.isEmpty(VisualLoggingTesting.NonDomState.getNonDomLoggables());
    });
    it('postpones logging non-DOM impressions with detached parent', async () => {
        await addLoggableElements();
        const loggable = {};
        const parent = document.createElement('div');
        VisualLoggingTesting.NonDomState.registerLoggable(loggable, { ve: 1, context: '123' }, parent);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [
            { id: getVeId('#element'), type: 1, context: 42, parent: getVeId('#parent'), width: 300, height: 300 },
            { id: getVeId('#parent'), type: 1, width: 300, height: 300 },
        ]);
        assert.deepInclude(VisualLoggingTesting.NonDomState.getNonDomLoggables(parent), { loggable, config: { ve: 1, context: '123' }, parent, size: undefined });
    });
    it('does not log a non-DOM impression twice for the same loggable', async () => {
        await addLoggableElements();
        const loggable = {};
        const parent = document.getElementById('parent');
        VisualLoggingTesting.NonDomState.registerLoggable(loggable, { ve: 1, context: '123' }, parent);
        VisualLoggingTesting.NonDomState.registerLoggable(loggable, { ve: 1, context: '123' }, parent);
        await VisualLoggingTesting.LoggingDriver.startLogging();
        sinon.assert.calledOnce(recordImpression);
        assert.sameDeepMembers(recordImpression.firstCall.firstArg.impressions, [
            { id: getVeId(loggable), type: 1, context: 123, parent: getVeId(parent), width: 0, height: 0 },
            { id: getVeId('#element'), type: 1, context: 42, parent: getVeId(parent), width: 300, height: 300 },
            { id: getVeId(parent), type: 1, width: 300, height: 300 },
        ]);
    });
});
//# sourceMappingURL=LoggingDriver.test.js.map