diff --git a/src/views/personal-settings-view.jsx b/src/views/personal-settings-view.jsx index 929a62e4..32ba5818 100644 --- a/src/views/personal-settings-view.jsx +++ b/src/views/personal-settings-view.jsx @@ -367,20 +367,40 @@ function CacheClear({onToast, onClose}) { const confirm = async () => { setBusy(true); try { - // navigator.storage/caches can be entirely absent (private browsing in some browsers, older - // WebViews) rather than just rejecting -- referencing a missing global throws synchronously, - // which a bare .catch() on the call below can't catch (it never gets that far), so without - // this check the whole confirm() throws, `done` never becomes true, and the "已完成" state is - // never reached even though the button correctly stops spinning via `finally`. - const hasStorageEstimate = typeof navigator !== 'undefined' && navigator.storage && navigator.storage.estimate; + // Was a before/after navigator.storage.estimate() diff around the caches.delete() call -- + // measured live (see this round's regression notes): estimate() does NOT reflect a deletion + // synchronously, Chrome keeps reporting the pre-delete `usageDetails.caches` figure for well + // over 500ms after `caches.keys()` already comes back empty. Every real run of this button + // therefore computed `before - after` from two numbers that were still identical, landing on + // "已释放约 0.0 MB" even when megabytes of real thumbnails/temp files were actually deleted -- + // confirmed by seeding a 2MB dummy Cache Storage entry and watching this path report 0.0 MB + // freed for it. Summing each entry's real blob size before deleting it sidesteps the OS-level + // storage accounting lag entirely -- the number shown is exactly what this run deleted, not an + // estimate that may or may not have caught up yet. + // caches can be entirely absent (private browsing in some browsers, older WebViews) rather than + // just rejecting -- referencing a missing global throws synchronously, which a bare .catch() on + // the call below can't catch (it never gets that far), so without this check the whole + // confirm() throws, `done` never becomes true, and the "已完成" state is never reached even + // though the button correctly stops spinning via `finally`. const hasCaches = typeof caches !== 'undefined'; - const before = hasStorageEstimate ? (await navigator.storage.estimate().catch(() => ({usage: 0}))).usage || 0 : 0; + let freedBytes = 0; if (hasCaches) { const keys = await caches.keys(); + for (const key of keys) { + const cache = await caches.open(key); + const requests = await cache.keys(); + for (const request of requests) { + const response = await cache.match(request); + if (!response) continue; + // clone() -- match() gives back the one real (non-clonable-twice) body for this entry; + // reading .blob() off it directly would leave nothing for a concurrent reader of the + // same cache, however unlikely that is here. + freedBytes += (await response.clone().blob().catch(() => null))?.size || 0; + } + } await Promise.all(keys.map(k => caches.delete(k))); } - const after = hasStorageEstimate ? (await navigator.storage.estimate().catch(() => ({usage: 0}))).usage || 0 : 0; - setFreed(Math.max(0, before - after)); + setFreed(freedBytes); setDone(true); } catch (err) { onToast(err.message || '清理失败,请重试');