blob: 7e082dab9c363c016624204a5bd8cd4abe533369 [file] [edit]
// Copyright 2026 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 {
clickTreeElementWithPartialText,
expandSelectedNodeRecursively,
waitForPartialContentOfSelectedElementsNode,
} from '../helpers/elements-helpers.js';
describe('The Styles Pane', () => {
it('does not double-redraw when toggling the last active style property', async ({devToolsPage, inspectedPage}) => {
// 1. Navigate directly to a fully-formed document.
// By providing the target styles and element at navigation time, we completely bypass
// the dynamic DOMMutated -> ComputedStyleUpdated 500ms race condition throttle in DevTools.
const html = `
<!DOCTYPE html>
<style>
.other-rule { color: blue; }
.toggled-rule { background-color: red; }
</style>
<div id="test-target" class="other-rule toggled-rule"></div>
`;
await inspectedPage.goTo(`data:text/html,${encodeURIComponent(html)}`);
// 2. Wait for the styles pane to populate initially
await devToolsPage.waitFor('.styles-selector');
await expandSelectedNodeRecursively(devToolsPage);
await clickTreeElementWithPartialText(devToolsPage, 'test-target');
await waitForPartialContentOfSelectedElementsNode(devToolsPage, 'test-target');
// 3. Retrieve the correct property checkboxes by their explicit DevTools ARIA labels
const getCheckboxByAria = async (ariaLabel: string) => {
await devToolsPage.waitForFunction(async () => {
const boxes = await devToolsPage.$$(`input.enabled-button[aria-label="${ariaLabel}"]`, undefined, 'pierce');
return boxes.length >= 1;
});
const matches = await devToolsPage.$$(`input.enabled-button[aria-label="${ariaLabel}"]`, undefined, 'pierce');
return matches[0];
};
const toggledCheckbox = await getCheckboxByAria('background-color red');
const controlCheckbox = await getCheckboxByAria('color blue');
// 4. Toggle the correct .toggled-rule checkbox
// This immediately modifies the computed style for 'background-color', firing ComputedStyleChanged.
// We use .evaluate to bypass Puppeteer's visibility check, since the checkbox is opacity: 0 until hovered.
await toggledCheckbox.evaluate(el => (el as HTMLElement).click());
// Wait for the inspected page to actually reflect the disabled property.
await inspectedPage.waitForFunction(async () => {
return await inspectedPage.evaluate(() => {
const el = document.getElementById('test-target');
return el ? window.getComputedStyle(el).backgroundColor !== 'rgb(255, 0, 0)' : false;
});
});
// Quick yield to give the DevTools frontend time to process the CSSModelChanged/ComputedStyleChanged events
// and potentially trigger the double-flash if the bug was present. (Throttler uses 0ms timeout if idle)
await new Promise(r => setTimeout(r, 50));
// 5. Assert that toggling the property didn't cause the ENTIRE Styles panel to completely rebuild (double-flash)
let isConnected = false;
try {
// We check controlCheckbox (which belongs to a different rule, .other-rule).
// Optimal updates only rebuild the precisely edited section (where toggledCheckbox lives).
// A double-flash bug would cause a full pane ResetCache & update, wiping controlCheckbox as well!
isConnected = await controlCheckbox.evaluate(el => el.isConnected);
} catch {
// ElementHandle throws if the node is already fully detached from the DOM footprint
isConnected = false;
}
assert.isTrue(
isConnected,
'Toggling the last active style property caused an additional unexpected redraw (double flash)',
);
});
});