Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions background/cache-names.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const CacheNames = {
fonts: ns('fonts'),
cdn: ns('cdn'),
cdnCss: ns('cdn-css'),
favicons: ns('favicons'),
} as const

export const EXPECTED_CACHES = new Set<string>(Object.values(CacheNames))
Expand All @@ -32,6 +33,9 @@ export const LEGACY_CACHES: string[] = [
'remote-fonts-css-cache',
'widgetify-public-api',
'critical-resources-v1',
'images-cache-v1',
'cross-origin-cache-v1',
'widgetify-api-cache-v1',
]

export function resolveCacheName(logical: CacheName): string {
Expand Down
19 changes: 19 additions & 0 deletions background/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ export function setupCaching() {
})
)

registerRoute(
({ url }) =>
(url.hostname === 'www.google.com' &&
url.pathname.startsWith('/s2/favicons')) ||
(url.hostname.endsWith('.gstatic.com') &&
url.pathname.startsWith('/faviconV2')),
new CacheFirst({
cacheName: CacheNames.favicons,
plugins: [
new CacheableResponsePlugin({ statuses: [0, 200] }),
new ExpirationPlugin({
maxEntries: 200,
maxAgeSeconds: 30 * DAY,
purgeOnQuotaError: true,
}),
],
})
)

registerRoute(
({ url }) =>
url.origin === CDN_ORIGIN &&
Expand Down
1 change: 1 addition & 0 deletions background/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export async function enforceCacheBudget(): Promise<void> {
CacheNames.cdn,
CacheNames.api,
CacheNames.fonts,
CacheNames.favicons,
]
for (const name of trimOrder) {
const cache = await caches.open(name)
Expand Down
88 changes: 77 additions & 11 deletions src/layouts/setting/tabs/wallpapers/hooks/use-wallpaper-apply.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import { getRandomWallpaper } from '@/services/hooks/wallpapers/getWallpaperCate
import { safeAwait } from '@/services/api'
import { SwEventType } from '@/common/types/sw-events'

function pinWallpaperForOffline(wallpaper: StoredWallpaper) {
if (wallpaper.type !== 'IMAGE' && wallpaper.type !== 'VIDEO') return
if (!/^https?:\/\//.test(wallpaper.src)) return
function pinWallpaperForOffline(wallpaper: StoredWallpaper): Promise<void> {
if (wallpaper.type !== 'IMAGE' && wallpaper.type !== 'VIDEO') return Promise.resolve()
if (!/^https?:\/\//.test(wallpaper.src)) return Promise.resolve()

browser.runtime
return browser.runtime
.sendMessage({
type: SwEventType.SetActiveWallpaper,
src: wallpaper.src,
wallpaperType: wallpaper.type,
})
.catch(() => {})
.then(() => undefined)
.catch(() => undefined)
}

const GRADIENT_DIRECTION_MAP: Record<string, string> = {
Expand All @@ -30,18 +31,62 @@ const GRADIENT_DIRECTION_MAP: Record<string, string> = {
'to-bl': 'to bottom left',
}

let applyToken = 0

const RETRY_LIMIT = 4
const RETRY_BASE_MS = 500

function setImageStyles() {
document.body.style.backgroundPosition = 'center'
document.body.style.backgroundRepeat = 'no-repeat'
document.body.style.backgroundSize = 'cover'
document.body.style.backgroundColor = ''
}

// A background-image whose request failed is never retried by the browser, and
// re-assigning the same url() is a no-op. Clear it for one frame to force a
// fresh evaluation once the bytes are actually available.
function forceReapplyImage(src: string, token: number) {
if (token !== applyToken) return
document.body.style.backgroundImage = 'none'
requestAnimationFrame(() => {
if (token !== applyToken) return
setImageStyles()
document.body.style.backgroundImage = `url("${src}")`
})
}

// background-image emits no load/error events, so probe the URL out-of-band. If
// the first paint missed (cold worker, or a cache emptied by an update), wait
// for the worker to pin the wallpaper, then retry and re-apply once loadable.
function verifyImage(src: string, pinned: Promise<void>, token: number, attempt: number) {
const probe = new Image()
probe.onload = () => {
if (attempt > 0) forceReapplyImage(src, token)
}
probe.onerror = () => {
if (token !== applyToken || attempt >= RETRY_LIMIT) return
const retry = () => verifyImage(src, pinned, token, attempt + 1)
if (attempt === 0) {
pinned.then(() => window.setTimeout(retry, RETRY_BASE_MS))
} else {
window.setTimeout(retry, RETRY_BASE_MS * (attempt + 1))
}
}
probe.src = src
}

function applyWallpaper(wallpaper: StoredWallpaper) {
pinWallpaperForOffline(wallpaper)
const token = ++applyToken
const pinned = pinWallpaperForOffline(wallpaper)

const existingVideo = document.getElementById('background-video')
if (existingVideo) existingVideo.remove()

if (wallpaper.type === 'IMAGE') {
document.body.style.backgroundImage = `url(${wallpaper.src})`
document.body.style.backgroundPosition = 'center'
document.body.style.backgroundRepeat = 'no-repeat'
document.body.style.backgroundSize = 'cover'
document.body.style.backgroundColor = ''
setImageStyles()
document.body.style.backgroundImage = `url("${wallpaper.src}")`
verifyImage(wallpaper.src, pinned, token, 0)
} else if (wallpaper.type === 'GRADIENT' && wallpaper.gradient) {
const { from, to, direction } = wallpaper.gradient
const cssDirection = GRADIENT_DIRECTION_MAP[direction] ?? direction
Expand Down Expand Up @@ -75,8 +120,19 @@ function applyWallpaper(wallpaper: StoredWallpaper) {
objectFit: 'cover',
})

let videoRetries = 0
video.onerror = () => {
document.body.style.backgroundColor = '#000'
// Retry driven by the error event (not a one-shot check) so it also
// recovers when the error arrives after pinning has resolved.
if (token !== applyToken || videoRetries >= RETRY_LIMIT) return
videoRetries++
const delay = RETRY_BASE_MS * videoRetries
pinned.then(() =>
window.setTimeout(() => {
if (token === applyToken && video.isConnected) video.load()
}, delay)
)
}
video.onloadeddata = () => {
video.play().catch((e) => console.warn('Play failed:', e))
Expand Down Expand Up @@ -144,8 +200,18 @@ export function useWallpaperApply() {
}
)

// Back online: re-pin and re-apply so a wallpaper that couldn't be fetched
// while offline (e.g. right after an update cleared its cache) recovers on
// its own instead of staying black until the user re-selects one.
const handleOnline = async () => {
const wallpaper: StoredWallpaper | null = await getFromStorage('wallpaper')
if (wallpaper) applyWallpaper(wallpaper)
}
window.addEventListener('online', handleOnline)

return () => {
unsubscribe()
window.removeEventListener('online', handleOnline)
}
}, [])
}
Loading