fix(glass): scope fixed shell backplate (#603)

This commit is contained in:
InfinityPacer
2026-07-30 22:47:22 +08:00
committed by GitHub
parent bf77c8ee04
commit e67edd9feb
6 changed files with 105 additions and 17 deletions
@@ -76,6 +76,8 @@ export default defineComponent({
}) })
return () => { return () => {
const hasFixedShellBackplate = fixedShellBackplate.layers.value.length > 0
// 👉 Vertical nav // 👉 Vertical nav
const verticalNav = h( const verticalNav = h(
VerticalNav, VerticalNav,
@@ -88,8 +90,7 @@ export default defineComponent({
}, },
) )
const fixedShellBackplateNode = const fixedShellBackplateNode = hasFixedShellBackplate
fixedShellBackplate.layers.value.length > 0
? h(GlassFixedShellBackplate, { ? h(GlassFixedShellBackplate, {
isOverlayNav: mdAndDown.value, isOverlayNav: mdAndDown.value,
isOverlayNavActive: isOverlayNavActive.value, isOverlayNavActive: isOverlayNavActive.value,
@@ -158,6 +159,7 @@ export default defineComponent({
isCollapsedLayout.value && 'layout-vertical-nav-collapsed', isCollapsedLayout.value && 'layout-vertical-nav-collapsed',
isHorizontalLayout.value && 'layout-horizontal-nav-active', isHorizontalLayout.value && 'layout-horizontal-nav-active',
isHorizontalLayout.value && isNavbarScrolled && 'layout-horizontal-nav-scrolled', isHorizontalLayout.value && isNavbarScrolled && 'layout-horizontal-nav-scrolled',
hasFixedShellBackplate && 'layout-fixed-shell-backplate-active',
route.meta.layoutWrapperClasses, route.meta.layoutWrapperClasses,
!isHorizontalLayout.value && isNavbarScrolled && 'window-scrolled', !isHorizontalLayout.value && isNavbarScrolled && 'window-scrolled',
], ],
+3
View File
@@ -29,6 +29,7 @@ import { useGlobalOfflineStatus, type ConnectionFailureReason } from '@/composab
import { useAppActivityLifecycle } from '@/composables/useAppActivityLifecycle' import { useAppActivityLifecycle } from '@/composables/useAppActivityLifecycle'
import { useGlassWallpaperTransaction } from '@/composables/useGlassWallpaperTransaction' import { useGlassWallpaperTransaction } from '@/composables/useGlassWallpaperTransaction'
import { import {
isChromiumFixedShellBackplateBrowser,
provideGlassFixedShellBackplate, provideGlassFixedShellBackplate,
shouldUseGlassFixedShellBackplate, shouldUseGlassFixedShellBackplate,
type GlassFixedShellBackplateLayer, type GlassFixedShellBackplateLayer,
@@ -308,6 +309,7 @@ function getBackgroundLayerStyle(layer: LoginBackgroundLayer) {
} }
} }
const needsStableFixedBackdrop = isChromiumFixedShellBackplateBrowser()
const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLayer[]>(() => { const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLayer[]>(() => {
const hasWallpaper = renderedBackgroundLayers.value.some(layer => Boolean(layer.url)) const hasWallpaper = renderedBackgroundLayers.value.some(layer => Boolean(layer.url))
if ( if (
@@ -315,6 +317,7 @@ const fixedShellBackplateLayers = computed<readonly GlassFixedShellBackplateLaye
appearance: effectiveGlassSettings.value.glassAppearance, appearance: effectiveGlassSettings.value.glassAppearance,
hasWallpaper, hasWallpaper,
isAuthenticated: Boolean(isLogin.value), isAuthenticated: Boolean(isLogin.value),
needsStableFixedBackdrop,
quality: effectiveGlassSettings.value.glassQuality, quality: effectiveGlassSettings.value.glassQuality,
themeName: globalTheme.name.value, themeName: globalTheme.name.value,
}) })
@@ -1,17 +1,22 @@
import { describe, expect, it } from 'vitest' import { describe, expect, it } from 'vitest'
import { shouldUseGlassFixedShellBackplate } from '@/composables/useGlassFixedShellBackplate' import {
isChromiumFixedShellBackplateBrowser,
shouldUseGlassFixedShellBackplate,
} from '@/composables/useGlassFixedShellBackplate'
describe('shouldUseGlassFixedShellBackplate', () => { describe('shouldUseGlassFixedShellBackplate', () => {
const eligible = { const eligible = {
appearance: 'frosted' as const, appearance: 'frosted' as const,
hasWallpaper: true, hasWallpaper: true,
isAuthenticated: true, isAuthenticated: true,
needsStableFixedBackdrop: true,
quality: 'css' as const, quality: 'css' as const,
themeName: 'glass', themeName: 'glass',
} }
it('enables the direct wallpaper backplate only for authenticated frosted CSS rendering', () => { it('enables the direct wallpaper backplate only for affected authenticated Chromium rendering', () => {
expect(shouldUseGlassFixedShellBackplate(eligible)).toBe(true) expect(shouldUseGlassFixedShellBackplate(eligible)).toBe(true)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, needsStableFixedBackdrop: false })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'clear' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'clear' })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'tinted' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, appearance: 'tinted' })).toBe(false)
expect(shouldUseGlassFixedShellBackplate({ ...eligible, quality: 'balanced' })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, quality: 'balanced' })).toBe(false)
@@ -21,3 +26,58 @@ describe('shouldUseGlassFixedShellBackplate', () => {
expect(shouldUseGlassFixedShellBackplate({ ...eligible, hasWallpaper: false })).toBe(false) expect(shouldUseGlassFixedShellBackplate({ ...eligible, hasWallpaper: false })).toBe(false)
}) })
}) })
describe('isChromiumFixedShellBackplateBrowser', () => {
it('uses the Chromium engine brand for Chrome and Edge', () => {
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36',
userAgentData: {
brands: [{ brand: 'Chromium' }, { brand: 'Google Chrome' }],
},
}),
).toBe(true)
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0',
userAgentData: {
brands: [{ brand: 'Chromium' }, { brand: 'Microsoft Edge' }],
},
}),
).toBe(true)
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Version/26.4 Safari/605.1.15',
userAgentData: {
brands: [{ brand: 'Safari' }],
},
}),
).toBe(false)
})
it.each([
['Chrome', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36'],
['Edge', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0'],
['Opera', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 OPR/121.0.0.0'],
['Vivaldi', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 Vivaldi/7.6.0.0'],
['Yandex Browser', 'Mozilla/5.0 Chrome/140.0.0.0 Safari/537.36 YaBrowser/25.8.0.0'],
['Samsung Internet', 'Mozilla/5.0 Chrome/140.0.0.0 Mobile Safari/537.36 SamsungBrowser/28.0'],
['Android WebView', 'Mozilla/5.0; wv) Version/4.0 Chrome/140.0.0.0 Mobile Safari/537.36'],
['Chromium', 'Mozilla/5.0 Chromium/140.0.0.0 Safari/537.36'],
])('falls back to Chromium UA tokens for %s', (_browser, userAgent) => {
expect(isChromiumFixedShellBackplateBrowser({ userAgent })).toBe(true)
})
it('leaves Safari and iOS Chrome on the native backdrop path', () => {
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 Version/26.4 Safari/605.1.15',
}),
).toBe(false)
expect(
isChromiumFixedShellBackplateBrowser({
userAgent: 'Mozilla/5.0 CriOS/140.0.0.0 Mobile/15E148 Safari/604.1',
}),
).toBe(false)
})
})
+23 -1
View File
@@ -21,12 +21,25 @@ interface GlassFixedShellBackplateEligibility {
hasWallpaper: boolean hasWallpaper: boolean
/** 当前页面是否处于认证后的主布局。 */ /** 当前页面是否处于认证后的主布局。 */
isAuthenticated: boolean isAuthenticated: boolean
/** 当前浏览器是否需要绕开 Chromium fixed document backdrop 的重合成路径。 */
needsStableFixedBackdrop: boolean
/** 当前玻璃渲染质量。 */ /** 当前玻璃渲染质量。 */
quality: ThemeCustomizerGlassQuality quality: ThemeCustomizerGlassQuality
/** Vuetify 当前解析后的主题名。 */ /** Vuetify 当前解析后的主题名。 */
themeName: string themeName: string
} }
interface GlassFixedShellBrowserIdentity {
/** User-Agent Client Hints 暴露的浏览器品牌。 */
userAgentData?: {
brands?: readonly {
brand: string
}[]
}
/** Client Hints 不可用时使用的传统浏览器标识。 */
userAgent: string
}
const GLASS_FIXED_SHELL_BACKPLATE_KEY: InjectionKey<GlassFixedShellBackplateContext> = const GLASS_FIXED_SHELL_BACKPLATE_KEY: InjectionKey<GlassFixedShellBackplateContext> =
Symbol('glass-fixed-shell-backplate') Symbol('glass-fixed-shell-backplate')
const EMPTY_FIXED_SHELL_LAYERS = shallowRef<readonly GlassFixedShellBackplateLayer[]>([]) const EMPTY_FIXED_SHELL_LAYERS = shallowRef<readonly GlassFixedShellBackplateLayer[]>([])
@@ -35,10 +48,19 @@ const EMPTY_FIXED_SHELL_CONTEXT: GlassFixedShellBackplateContext = {
transitionDurationMs: 0, transitionDurationMs: 0,
} }
/** 只有磨砂标准使用直接壁纸背板;其他材质和 GPU 质量继续走既有呈现路径。 */ /** Chromium 使用独立 fixed 背板规避 document backdrop 重合成;WebKit 保留原生采样路径。 */
export function isChromiumFixedShellBackplateBrowser(browserIdentity: GlassFixedShellBrowserIdentity = navigator) {
const brands = browserIdentity.userAgentData?.brands
if (brands?.length) return brands.some(({ brand }) => brand === 'Chromium')
return /\b(?:Chrome|Chromium)\/\d+/u.test(browserIdentity.userAgent)
}
/** 只有需要稳定 fixed 背板的 Chromium 磨砂标准布局使用直接壁纸采样。 */
export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) { export function shouldUseGlassFixedShellBackplate(options: GlassFixedShellBackplateEligibility) {
return ( return (
options.isAuthenticated && options.isAuthenticated &&
options.needsStableFixedBackdrop &&
options.themeName === 'glass' && options.themeName === 'glass' &&
options.appearance === 'frosted' && options.appearance === 'frosted' &&
options.quality === 'css' && options.quality === 'css' &&
@@ -39,7 +39,7 @@ describe('glass overlay material styles', () => {
expect(styles).toContain('--glass-fixed-shell-backplate-filter: blur(min(var(--glass-blur-raised), 60px))') expect(styles).toContain('--glass-fixed-shell-backplate-filter: blur(min(var(--glass-blur-raised), 60px))')
expect(styles).toMatch( expect(styles).toMatch(
/&\[data-glass-appearance='frosted'\]\[data-glass-quality='css'\][\s\S]*?\.layout-vertical-nav::before,[\s\S]*?\.layout-navbar,[\s\S]*?backdrop-filter:\s*none\s*!important;/, /&\[data-glass-appearance='frosted'\]\[data-glass-quality='css'\][\s\S]*?\.layout-wrapper\.layout-fixed-shell-backplate-active \.layout-vertical-nav::before,[\s\S]*?\.layout-wrapper\.layout-fixed-shell-backplate-active \.layout-navbar,[\s\S]*?backdrop-filter:\s*none\s*!important;/,
) )
expect(backplate).toContain('filter: var(--glass-fixed-shell-backplate-filter)') expect(backplate).toContain('filter: var(--glass-fixed-shell-backplate-filter)')
expect(backplate).not.toContain('backdrop-filter') expect(backplate).not.toContain('backdrop-filter')
+5 -4
View File
@@ -462,11 +462,12 @@ html[data-theme='glass'] {
} }
} }
// 磨砂标准直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 document backdrop。 // Chromium 磨砂标准直接渲染稳定壁纸背板,固定功能层不得读取随页面重绘的 document backdrop。
&[data-glass-appearance='frosted'][data-glass-quality='css'] body[data-theme='glass'] { &[data-glass-appearance='frosted'][data-glass-quality='css'] body[data-theme='glass'] {
.layout-vertical-nav::before, .layout-wrapper.layout-fixed-shell-backplate-active .layout-vertical-nav::before,
.layout-navbar, .layout-wrapper.layout-fixed-shell-backplate-active .layout-navbar,
.layout-wrapper.layout-horizontal-nav-active.layout-horizontal-nav-scrolled.layout-navbar-fixed .layout-navbar { .layout-wrapper.layout-fixed-shell-backplate-active.layout-horizontal-nav-active.layout-horizontal-nav-scrolled.layout-navbar-fixed
.layout-navbar {
-webkit-backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
backdrop-filter: none !important; backdrop-filter: none !important;
} }