fix(ui): disable modal backdrop blur under software rendering (#1092)

With hardware acceleration disabled, Chrome rasterizes in software and a
full-viewport backdrop-filter forces a per-frame CPU blur over everything
behind the modal, freezing the whole browser.

Detect software rendering via the unmasked WebGL renderer string at app
startup and drop the backdrop blur in that case. Also route the download
modal's sticky toolbar through the shared --modal-backdrop-blur variable
instead of a hardcoded blur(8px).
This commit is contained in:
Will Miao
2026-09-02 12:24:32 +08:00
parent bc33e32c6f
commit b37238d790
5 changed files with 143 additions and 3 deletions
@@ -0,0 +1,59 @@
import { describe, it, expect, beforeEach } from 'vitest';
import {
isSoftwareRendererString,
applyModalBackdropBlurPolicy,
} from '../../../static/js/utils/renderingCapability.js';
describe('isSoftwareRendererString', () => {
it('detects SwiftShader (Chrome with hardware acceleration disabled)', () => {
expect(isSoftwareRendererString(
'WebKit WebGL SwiftShader'
)).toBe(true);
expect(isSoftwareRendererString(
'ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)'
)).toBe(true);
});
it('detects Mesa software rasterizers (Linux)', () => {
expect(isSoftwareRendererString('llvmpipe (LLVM 17.0.6, 256 bits)')).toBe(true);
expect(isSoftwareRendererString('softpipe')).toBe(true);
});
it('detects generic software renderer strings', () => {
expect(isSoftwareRendererString('Software Renderer')).toBe(true);
expect(isSoftwareRendererString('Microsoft Basic Render Driver')).toBe(true);
});
it('accepts hardware GPU strings', () => {
expect(isSoftwareRendererString(
'ANGLE (NVIDIA, NVIDIA GeForce RTX 4090 Direct3D11 vs_5_0 ps_5_0, D3D11)'
)).toBe(false);
expect(isSoftwareRendererString(
'ANGLE (AMD, AMD Radeon RX 7900 XTX (0x0000744C) Direct3D11 vs_5_0 ps_5_0, D3D11)'
)).toBe(false);
expect(isSoftwareRendererString('Apple M4 Pro')).toBe(false);
expect(isSoftwareRendererString('Mesa Intel(R) UHD Graphics 620 (KBL GT2)')).toBe(false);
});
it('handles empty input', () => {
expect(isSoftwareRendererString('')).toBe(false);
expect(isSoftwareRendererString(null)).toBe(false);
});
});
describe('applyModalBackdropBlurPolicy', () => {
beforeEach(() => {
document.documentElement.classList.remove('no-modal-backdrop-blur');
});
it('adds the disabling class under software rendering', () => {
applyModalBackdropBlurPolicy(true);
expect(document.documentElement.classList.contains('no-modal-backdrop-blur')).toBe(true);
});
it('removes the disabling class under hardware rendering', () => {
document.documentElement.classList.add('no-modal-backdrop-blur');
applyModalBackdropBlurPolicy(false);
expect(document.documentElement.classList.contains('no-modal-backdrop-blur')).toBe(false);
});
});