mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-07 00:36:41 +08:00
fix(glass): scope fixed shell backplate (#603)
This commit is contained in:
@@ -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',
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user