mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-15 10:15:04 +01:00
* Avoid :has() selectors that trigger workbench-wide style invalidation A :has() whose anchor exists in the live DOM makes Blink re-evaluate it on DOM mutations anywhere in the document - including the inline style writes the editor performs on every layout. With a populated chat working set, every forced style recalc traversed the whole visible workbench (~218ms per Recalculate Style in a large workspace; sash drag nearly frozen). Same issue class as #324985. Per-rule profiling showed the chat editing session scrollbar rule alone accounted for 77% of the per-recalc cost; the root-anchored rules in floatingPanels.css and sessionsPolicyBlocked.css are the same hazard. - chat.css: key the scrollbar padding off an 'overflowing' class toggled in chatInputPart.ts, which already computes the condition. The padding now applies whenever the list overflows instead of only while the auto-hide scrollbar is faded in. - floatingPanels.css: use 'activitybar-compact' (mirrored by activitybarPart.ts) and 'noactivitybar' (toggled by layout.ts like the other part visibility classes). - sessionsPolicyBlocked.css: toggle a class for the overlay's lifetime. - build/stylelint.ts: error on root-anchored :has() (body/html/:root/ .monaco-workbench), with a has-anchor-checker disable escape. * Match root-anchored :has() as complete tokens at any compound position Addresses Copilot review feedback: the previous regex missed root tokens that are not first in the compound (.style-override.monaco-workbench:has()) and matched incomplete tokens (.monaco-workbench-test, bodyguard). Extracted into build/lib/stylelint/validateHasSelectors.ts with focused tests. * Use layoutService.mainContainer for the policy-blocked root class Review feedback: inject IWorkbenchLayoutService instead of walking the DOM with container.closest(). The caller already passes mainContainer as the overlay container. Stub the layout service in the component fixture. * small tweaks * Drop build/ changes; lint rule moves to a separate PR * build: prevent root-anchored :has() selectors Add a stylelint check for root-level :has() selectors that cause workbench-wide style invalidation, together with focused parser tests. This is split from #327052 and should merge after it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: zmr233 <zmr_233@outlook.com> Co-authored-by: zmr-233 <132034425+zmr-233@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
70 lines
4.2 KiB
TypeScript
70 lines
4.2 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import assert from 'assert';
|
|
import { suite, test } from 'node:test';
|
|
import { containsRootAnchoredHas, findRootAnchoredHas } from '../stylelint/validateHasSelectors.ts';
|
|
|
|
suite('stylelint root-anchored :has() check', () => {
|
|
|
|
test('flags root anchors regardless of position in the compound', () => {
|
|
assert.ok(containsRootAnchoredHas('body:has(.automation-dialog) .context-view.monaco-component'));
|
|
assert.ok(containsRootAnchoredHas('.monaco-workbench.floating-panels:has(.part.activitybar.compact) .part.statusbar'));
|
|
assert.ok(containsRootAnchoredHas('.style-override.monaco-workbench:has(.part.sidebar) .part'));
|
|
assert.ok(containsRootAnchoredHas('BODY:HAS(.foo)'));
|
|
assert.ok(containsRootAnchoredHas(':root:has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas(':is(body, .embedded):has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas(':where(html, .embedded):has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas('body:not(.x):has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas('.foo, body:has(.bar)'));
|
|
assert.ok(containsRootAnchoredHas('.monaco\\2d workbench:has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas('.monaco/**/-workbench:has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas('body:/**/has(.foo)'));
|
|
assert.ok(containsRootAnchoredHas('body:\\68 as(.foo)'));
|
|
assert.ok(containsRootAnchoredHas(':has(.foo).monaco-workbench'));
|
|
assert.ok(containsRootAnchoredHas(':has(.foo):is(body, .embedded)'));
|
|
});
|
|
|
|
test('ignores :has() anchored below the root', () => {
|
|
assert.ok(!containsRootAnchoredHas('.monaco-workbench .part > .title:has(.start-debug-action-item) > .title-actions'));
|
|
assert.ok(!containsRootAnchoredHas('.style-override.monaco-workbench .part:not(.editor):has(> .content)'));
|
|
assert.ok(!containsRootAnchoredHas('.interactive-session .foo:has(.bar)'));
|
|
assert.ok(!containsRootAnchoredHas('.automations-list-widget .automations-row:has(:focus-visible)'));
|
|
assert.ok(!containsRootAnchoredHas('.foo:not(.monaco-workbench):has(.bar)'));
|
|
assert.ok(!containsRootAnchoredHas('[data-example=".monaco-workbench"]:has(.bar)'));
|
|
});
|
|
|
|
test('requires complete tokens', () => {
|
|
assert.ok(!containsRootAnchoredHas('.monaco-workbench-test:has(.foo)'));
|
|
assert.ok(!containsRootAnchoredHas('bodyguard:has(.foo)'));
|
|
assert.ok(!containsRootAnchoredHas('.htmlish:has(.foo)'));
|
|
assert.ok(!containsRootAnchoredHas('.bodyclass:has(.foo)'));
|
|
});
|
|
|
|
test('scans only stylesheet selectors', () => {
|
|
assert.ok(findRootAnchoredHas('/* body:has(.foo) */\n.foo {}') === undefined);
|
|
assert.ok(findRootAnchoredHas('a { --example: body:has(.foo); }') === undefined);
|
|
assert.ok(findRootAnchoredHas('@supports selector(body:has(.foo)) { .foo {} }') === undefined);
|
|
});
|
|
|
|
test('scans nested stylesheet selectors', () => {
|
|
assert.ok(findRootAnchoredHas('body { &:has(.foo) {} }') !== undefined);
|
|
assert.ok(findRootAnchoredHas('.monaco-workbench { color: red; &:has(.foo) {} }') !== undefined);
|
|
assert.ok(findRootAnchoredHas(':is(body, .embedded) { &:has(.foo) {} }') !== undefined);
|
|
assert.ok(findRootAnchoredHas('.embedded, body { &:has(.foo) {} }') !== undefined);
|
|
assert.ok(findRootAnchoredHas('@media (width > 0) { .monaco-workbench { &:has(.foo) {} } }') !== undefined);
|
|
assert.ok(findRootAnchoredHas('body { @media (width > 0) { &:has(.foo) {} } }') !== undefined);
|
|
assert.ok(findRootAnchoredHas('body { @supports (display: grid) { &:has(.foo) {} } }') !== undefined);
|
|
assert.ok(findRootAnchoredHas('.first { color: red } body:has(.foo) {}') !== undefined);
|
|
});
|
|
|
|
test('does not propagate root anchoring to nested descendants', () => {
|
|
assert.ok(findRootAnchoredHas('.monaco-workbench .child { &:has(.foo) {} }') === undefined);
|
|
assert.ok(findRootAnchoredHas('body { .child { &:has(.foo) {} } }') === undefined);
|
|
assert.ok(findRootAnchoredHas('body { & .child:has(.foo) {} }') === undefined);
|
|
assert.ok(findRootAnchoredHas('.embedded { @media (width > 0) { &:has(.foo) {} } }') === undefined);
|
|
});
|
|
});
|