Merge pull request #295164 from microsoft/mrleemurray/driving-black-cod

Add support for reduced transparency in accessibility settings
This commit is contained in:
Lee Murray
2026-02-13 13:56:42 +00:00
committed by GitHub
6 changed files with 332 additions and 0 deletions

View File

@@ -682,3 +682,167 @@
opacity: 1;
color: var(--vscode-descriptionForeground);
}
/* ============================================================================================
* Reduced Transparency - disable backdrop-filter blur and color-mix transparency effects
* for improved rendering performance. Controlled by workbench.reduceTransparency setting.
* ============================================================================================ */
/* Reset blur variables to none */
.monaco-workbench.monaco-reduce-transparency {
--backdrop-blur-sm: none;
--backdrop-blur-md: none;
--backdrop-blur-lg: none;
}
/* Quick Input (Command Palette) */
.monaco-workbench.monaco-reduce-transparency .quick-input-widget {
background-color: var(--vscode-quickInput-background) !important;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Notifications */
.monaco-workbench.monaco-reduce-transparency .notification-toast-container {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-notifications-background) !important;
}
.monaco-workbench.monaco-reduce-transparency .notifications-center {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-notifications-background) !important;
}
/* Context Menu / Action Widget */
.monaco-workbench.monaco-reduce-transparency .action-widget {
background: var(--vscode-menu-background) !important;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Suggest Widget */
.monaco-workbench.monaco-reduce-transparency .monaco-editor .suggest-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-editorSuggestWidget-background) !important;
}
/* Find Widget */
.monaco-workbench.monaco-reduce-transparency .monaco-editor .find-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
.monaco-workbench.monaco-reduce-transparency .inline-chat-gutter-menu {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Dialog */
.monaco-workbench.monaco-reduce-transparency .monaco-dialog-box {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-editor-background) !important;
}
/* Peek View */
.monaco-workbench.monaco-reduce-transparency .monaco-editor .peekview-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-peekViewEditor-background) !important;
}
/* Hover */
.monaco-reduce-transparency .monaco-hover {
background-color: var(--vscode-editorHoverWidget-background) !important;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
.monaco-reduce-transparency .monaco-hover.workbench-hover,
.monaco-reduce-transparency .workbench-hover {
background-color: var(--vscode-editorHoverWidget-background) !important;
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
}
/* Keybinding Widget */
.monaco-workbench.monaco-reduce-transparency .defineKeybindingWidget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Chat Editor Overlay */
.monaco-workbench.monaco-reduce-transparency .chat-editor-overlay-widget,
.monaco-workbench.monaco-reduce-transparency .chat-diff-change-content-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Debug Toolbar */
.monaco-workbench.monaco-reduce-transparency .debug-toolbar {
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
}
.monaco-workbench.monaco-reduce-transparency .debug-hover-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Parameter Hints */
.monaco-workbench.monaco-reduce-transparency .monaco-editor .parameter-hints-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-editorWidget-background) !important;
}
/* Sticky Scroll */
.monaco-workbench.monaco-reduce-transparency .monaco-editor .sticky-widget {
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
background: var(--vscode-editor-background) !important;
}
.monaco-workbench.monaco-reduce-transparency .monaco-editor .sticky-widget .sticky-widget-lines {
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
background: var(--vscode-editor-background) !important;
}
/* Rename Box */
.monaco-reduce-transparency .monaco-editor .rename-box.preview {
-webkit-backdrop-filter: none !important;
backdrop-filter: none !important;
}
/* Notebook */
.monaco-workbench.monaco-reduce-transparency .notebookOverlay .monaco-list-row .cell-title-toolbar {
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* Command Center */
.monaco-workbench.monaco-reduce-transparency .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center {
background: var(--vscode-commandCenter-background) !important;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
.monaco-workbench.monaco-reduce-transparency .part.titlebar > .titlebar-container > .titlebar-center > .window-title > .command-center .action-item.command-center-center:hover {
background: var(--vscode-commandCenter-activeBackground) !important;
}
/* Breadcrumbs */
.monaco-workbench.monaco-reduce-transparency .breadcrumbs-picker-widget {
-webkit-backdrop-filter: none;
backdrop-filter: none;
background: var(--vscode-breadcrumbPicker-background) !important;
}
/* Quick Input filter input */
.monaco-workbench.monaco-reduce-transparency .quick-input-widget .quick-input-filter .monaco-inputbox {
background: var(--vscode-input-background) !important;
}

View File

@@ -24,6 +24,10 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe
protected _systemMotionReduced: boolean;
protected readonly _onDidChangeReducedMotion = this._register(new Emitter<void>());
protected _configTransparencyReduced: 'auto' | 'on' | 'off';
protected _systemTransparencyReduced: boolean;
protected readonly _onDidChangeReducedTransparency = this._register(new Emitter<void>());
private _linkUnderlinesEnabled: boolean;
protected readonly _onDidChangeLinkUnderline = this._register(new Emitter<void>());
@@ -45,6 +49,10 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe
this._configMotionReduced = this._configurationService.getValue('workbench.reduceMotion');
this._onDidChangeReducedMotion.fire();
}
if (e.affectsConfiguration('workbench.reduceTransparency')) {
this._configTransparencyReduced = this._configurationService.getValue('workbench.reduceTransparency');
this._onDidChangeReducedTransparency.fire();
}
}));
updateContextKey();
this._register(this.onDidChangeScreenReaderOptimized(() => updateContextKey()));
@@ -53,9 +61,14 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe
this._systemMotionReduced = reduceMotionMatcher.matches;
this._configMotionReduced = this._configurationService.getValue<'auto' | 'on' | 'off'>('workbench.reduceMotion');
const reduceTransparencyMatcher = mainWindow.matchMedia(`(prefers-reduced-transparency: reduce)`);
this._systemTransparencyReduced = reduceTransparencyMatcher.matches;
this._configTransparencyReduced = this._configurationService.getValue<'auto' | 'on' | 'off'>('workbench.reduceTransparency');
this._linkUnderlinesEnabled = this._configurationService.getValue('accessibility.underlineLinks');
this.initReducedMotionListeners(reduceMotionMatcher);
this.initReducedTransparencyListeners(reduceTransparencyMatcher);
this.initLinkUnderlineListeners();
}
@@ -78,6 +91,24 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe
this._register(this.onDidChangeReducedMotion(() => updateRootClasses()));
}
private initReducedTransparencyListeners(reduceTransparencyMatcher: MediaQueryList) {
this._register(addDisposableListener(reduceTransparencyMatcher, 'change', () => {
this._systemTransparencyReduced = reduceTransparencyMatcher.matches;
if (this._configTransparencyReduced === 'auto') {
this._onDidChangeReducedTransparency.fire();
}
}));
const updateRootClasses = () => {
const reduce = this.isTransparencyReduced();
this._layoutService.mainContainer.classList.toggle('monaco-reduce-transparency', reduce);
};
updateRootClasses();
this._register(this.onDidChangeReducedTransparency(() => updateRootClasses()));
}
private initLinkUnderlineListeners() {
this._register(this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('accessibility.underlineLinks')) {
@@ -119,6 +150,15 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe
return config === 'on' || (config === 'auto' && this._systemMotionReduced);
}
get onDidChangeReducedTransparency(): Event<void> {
return this._onDidChangeReducedTransparency.event;
}
isTransparencyReduced(): boolean {
const config = this._configTransparencyReduced;
return config === 'on' || (config === 'auto' && this._systemTransparencyReduced);
}
alwaysUnderlineAccessKeys(): Promise<boolean> {
return Promise.resolve(false);
}

View File

@@ -14,10 +14,12 @@ export interface IAccessibilityService {
readonly onDidChangeScreenReaderOptimized: Event<void>;
readonly onDidChangeReducedMotion: Event<void>;
readonly onDidChangeReducedTransparency: Event<void>;
alwaysUnderlineAccessKeys(): Promise<boolean>;
isScreenReaderOptimized(): boolean;
isMotionReduced(): boolean;
isTransparencyReduced(): boolean;
getAccessibilitySupport(): AccessibilitySupport;
setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void;
alert(message: string): void;

View File

@@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* 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 { Event } from '../../../../base/common/event.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
import { TestInstantiationService } from '../../../instantiation/test/common/instantiationServiceMock.js';
import { IConfigurationService, IConfigurationChangeEvent } from '../../../configuration/common/configuration.js';
import { TestConfigurationService } from '../../../configuration/test/common/testConfigurationService.js';
import { IContextKeyService } from '../../../contextkey/common/contextkey.js';
import { MockContextKeyService } from '../../../keybinding/test/common/mockKeybindingService.js';
import { ILayoutService } from '../../../layout/browser/layoutService.js';
import { AccessibilityService } from '../../browser/accessibilityService.js';
suite('AccessibilityService', () => {
const store = ensureNoDisposablesAreLeakedInTestSuite();
let configurationService: TestConfigurationService;
let container: HTMLElement;
function createService(config: Record<string, unknown> = {}): AccessibilityService {
const instantiationService = store.add(new TestInstantiationService());
configurationService = new TestConfigurationService({
'editor.accessibilitySupport': 'off',
'workbench.reduceMotion': 'off',
'workbench.reduceTransparency': 'off',
'accessibility.underlineLinks': false,
...config,
});
instantiationService.stub(IConfigurationService, configurationService);
instantiationService.stub(IContextKeyService, store.add(new MockContextKeyService()));
container = document.createElement('div');
instantiationService.stub(ILayoutService, {
mainContainer: container,
activeContainer: container,
getContainer() { return container; },
onDidLayoutContainer: Event.None,
});
return store.add(instantiationService.createInstance(AccessibilityService));
}
suite('isTransparencyReduced', () => {
test('returns false when config is off', () => {
const service = createService({ 'workbench.reduceTransparency': 'off' });
assert.strictEqual(service.isTransparencyReduced(), false);
});
test('returns true when config is on', () => {
const service = createService({ 'workbench.reduceTransparency': 'on' });
assert.strictEqual(service.isTransparencyReduced(), true);
});
test('adds CSS class when config is on', () => {
createService({ 'workbench.reduceTransparency': 'on' });
assert.strictEqual(container.classList.contains('monaco-reduce-transparency'), true);
});
test('does not add CSS class when config is off', () => {
createService({ 'workbench.reduceTransparency': 'off' });
assert.strictEqual(container.classList.contains('monaco-reduce-transparency'), false);
});
test('fires event and updates class on config change', () => {
const service = createService({ 'workbench.reduceTransparency': 'off' });
assert.strictEqual(service.isTransparencyReduced(), false);
let fired = false;
store.add(service.onDidChangeReducedTransparency(() => { fired = true; }));
// Simulate config change
configurationService.setUserConfiguration('workbench.reduceTransparency', 'on');
configurationService.onDidChangeConfigurationEmitter.fire({
affectsConfiguration(id: string) { return id === 'workbench.reduceTransparency'; },
} satisfies Partial<IConfigurationChangeEvent> as unknown as IConfigurationChangeEvent);
assert.strictEqual(fired, true);
assert.strictEqual(service.isTransparencyReduced(), true);
assert.strictEqual(container.classList.contains('monaco-reduce-transparency'), true);
});
});
suite('isMotionReduced', () => {
test('returns false when config is off', () => {
const service = createService({ 'workbench.reduceMotion': 'off' });
assert.strictEqual(service.isMotionReduced(), false);
});
test('returns true when config is on', () => {
const service = createService({ 'workbench.reduceMotion': 'on' });
assert.strictEqual(service.isMotionReduced(), true);
});
test('adds CSS classes when config is on', () => {
createService({ 'workbench.reduceMotion': 'on' });
assert.strictEqual(container.classList.contains('monaco-reduce-motion'), true);
assert.strictEqual(container.classList.contains('monaco-enable-motion'), false);
});
test('adds CSS classes when config is off', () => {
createService({ 'workbench.reduceMotion': 'off' });
assert.strictEqual(container.classList.contains('monaco-reduce-motion'), false);
assert.strictEqual(container.classList.contains('monaco-enable-motion'), true);
});
});
});

View File

@@ -12,9 +12,11 @@ export class TestAccessibilityService implements IAccessibilityService {
onDidChangeScreenReaderOptimized = Event.None;
onDidChangeReducedMotion = Event.None;
onDidChangeReducedTransparency = Event.None;
isScreenReaderOptimized(): boolean { return false; }
isMotionReduced(): boolean { return true; }
isTransparencyReduced(): boolean { return false; }
alwaysUnderlineAccessKeys(): Promise<boolean> { return Promise.resolve(false); }
setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { }
getAccessibilitySupport(): AccessibilitySupport { return AccessibilitySupport.Unknown; }

View File

@@ -715,6 +715,18 @@ const registry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Con
tags: ['accessibility'],
enum: ['on', 'off', 'auto']
},
'workbench.reduceTransparency': {
type: 'string',
description: localize('workbench.reduceTransparency', "Controls whether the workbench should render with fewer transparency and blur effects for improved performance."),
'enumDescriptions': [
localize('workbench.reduceTransparency.on', "Always render without transparency and blur effects."),
localize('workbench.reduceTransparency.off', "Do not reduce transparency and blur effects."),
localize('workbench.reduceTransparency.auto', "Reduce transparency and blur effects based on OS configuration."),
],
default: 'off',
tags: ['accessibility'],
enum: ['on', 'off', 'auto']
},
'workbench.navigationControl.enabled': {
'type': 'boolean',
'default': true,