diff --git a/entrypoints/popup/pages/StorageCleanerPage.tsx b/entrypoints/popup/pages/StorageCleanerPage.tsx
index 8bd210e..a71a762 100644
--- a/entrypoints/popup/pages/StorageCleanerPage.tsx
+++ b/entrypoints/popup/pages/StorageCleanerPage.tsx
@@ -29,6 +29,9 @@ import {
getCookieSize,
getLocalStorageSize,
getSessionStorageSize,
+ getIndexedDBSize,
+ getCacheStorageSize,
+ getServiceWorkerCount,
formatSize,
} from '@/utils/storageCleaner';
@@ -78,11 +81,14 @@ export default function StorageCleanerPage() {
const tabId = tab.id!;
setDomain(new URL(url).hostname);
- const [savedPrefs, cSize, lsSize, ssSize] = await Promise.all([
+ const [savedPrefs, cSize, lsSize, ssSize, idbSize, cacheCount, swCount] = await Promise.all([
storageUtil.get('storageCleaner/preferences', DEFAULT_PREFERENCES),
getCookieSize(url),
getLocalStorageSize(tabId),
getSessionStorageSize(tabId),
+ getIndexedDBSize(tabId),
+ getCacheStorageSize(tabId),
+ getServiceWorkerCount(tabId),
]);
setAutoRefresh(savedPrefs?.autoRefresh ?? DEFAULT_PREFERENCES.autoRefresh);
@@ -91,6 +97,9 @@ export default function StorageCleanerPage() {
cookies: cSize,
localStorage: lsSize,
sessionStorage: ssSize,
+ indexedDB: idbSize,
+ cacheStorage: cacheCount,
+ serviceWorkers: swCount,
});
};
@@ -181,17 +190,20 @@ export default function StorageCleanerPage() {
);
}
- const totalSize = Object.values(sizes).reduce((acc, curr) => acc + curr, 0);
+ // 这里的总大小仅包含以字节计算的项
+ const totalSize = (sizes.cookies || 0) + (sizes.localStorage || 0) + (sizes.sessionStorage || 0) + (sizes.indexedDB || 0);
const OptionItem = ({
label,
checked,
size,
+ isCount = false,
onChange,
}: {
label: string;
checked: boolean;
size?: number;
+ isCount?: boolean;
onChange: () => void;
}) => (
-
+
{label}
- {size !== undefined && size > 0 && (
+ {size !== undefined && size > 0 ? (
- {formatSize(size)}
+ {isCount ? `${size} 个` : formatSize(size)}
+
+ ) : (
+
+ 无数据
)}
-
+
handleOptionChange('indexedDB')}
/>
@@ -340,6 +382,8 @@ export default function StorageCleanerPage() {
handleOptionChange('cacheStorage')}
/>
@@ -347,7 +391,9 @@ export default function StorageCleanerPage() {
handleOptionChange('serviceWorkers')}
+ size={sizes.serviceWorkers}
+ isCount
+ onChange={() => handleOptionChange('serviceWorkers')}
/>
diff --git a/utils/storageCleaner.ts b/utils/storageCleaner.ts
index 0a125ae..f097f60 100644
--- a/utils/storageCleaner.ts
+++ b/utils/storageCleaner.ts
@@ -11,7 +11,8 @@ const RESTRICTED_PROTOCOLS = [
] as const;
export async function getCurrentTab() {
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
+ // 使用 lastFocusedWindow: true 确保在 Side Panel 或 Popup 中都能获取到用户正在查看的标签页
+ const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
return tab;
}
@@ -23,8 +24,10 @@ export function isRestrictedUrl(url?: string): boolean {
export async function getCookieSize(url: string): Promise {
try {
const cookies = await chrome.cookies.getAll({ url });
- return cookies.reduce((acc, c) => acc + c.name.length + c.value.length, 0);
- } catch {
+ // 估算:名称 + 值 + 域名 + 路径 的长度
+ return cookies.reduce((acc, c) => acc + c.name.length + c.value.length + (c.domain?.length || 0) + (c.path?.length || 0), 0);
+ } catch (error) {
+ console.error('Failed to get cookie size:', error);
return 0;
}
}
@@ -34,11 +37,16 @@ export async function getLocalStorageSize(tabId: number): Promise {
const [result] = await chrome.scripting.executeScript({
target: { tabId },
func: () => {
- return Object.entries(localStorage).reduce((acc, [k, v]) => acc + k.length + v.length, 0);
+ try {
+ return Object.entries(localStorage).reduce((acc, [k, v]) => acc + k.length + v.length, 0);
+ } catch {
+ return 0;
+ }
},
});
return (result?.result as number) || 0;
- } catch {
+ } catch (error) {
+ console.error('Failed to get LocalStorage size:', error);
return 0;
}
}
@@ -48,17 +56,95 @@ export async function getSessionStorageSize(tabId: number): Promise {
const [result] = await chrome.scripting.executeScript({
target: { tabId },
func: () => {
- return Object.entries(sessionStorage).reduce((acc, [k, v]) => acc + k.length + v.length, 0);
+ try {
+ return Object.entries(sessionStorage).reduce((acc, [k, v]) => acc + k.length + v.length, 0);
+ } catch {
+ return 0;
+ }
},
});
return (result?.result as number) || 0;
- } catch {
+ } catch (error) {
+ console.error('Failed to get SessionStorage size:', error);
+ return 0;
+ }
+}
+
+export async function getIndexedDBSize(tabId: number): Promise {
+ try {
+ const [result] = await chrome.scripting.executeScript({
+ target: { tabId },
+ func: async () => {
+ try {
+ // 注意:navigator.storage.estimate() 返回的是整个 Origin 的估算值
+ // 包含 IndexedDB, CacheStorage, ServiceWorker 注册等
+ if (navigator.storage && navigator.storage.estimate) {
+ const estimate = await navigator.storage.estimate();
+ return estimate.usage || 0;
+ }
+ return 0;
+ } catch {
+ return 0;
+ }
+ },
+ });
+ return (result?.result as number) || 0;
+ } catch (error) {
+ console.error('Failed to get IndexedDB size:', error);
+ return 0;
+ }
+}
+
+export async function getCacheStorageSize(tabId: number): Promise {
+ try {
+ const [result] = await chrome.scripting.executeScript({
+ target: { tabId },
+ func: async () => {
+ try {
+ if ('caches' in window) {
+ const keys = await caches.keys();
+ return keys.length; // 对于 CacheStorage,我们先返回缓存库的数量
+ }
+ return 0;
+ } catch {
+ return 0;
+ }
+ },
+ });
+ // 由于获取具体字节数较慢,这里返回的是缓存条目的数量标识,UI 上可以特殊处理
+ return (result?.result as number) || 0;
+ } catch (error) {
+ console.error('Failed to get CacheStorage size:', error);
+ return 0;
+ }
+}
+
+export async function getServiceWorkerCount(tabId: number): Promise {
+ try {
+ const [result] = await chrome.scripting.executeScript({
+ target: { tabId },
+ func: async () => {
+ try {
+ if ('serviceWorker' in navigator) {
+ const regs = await navigator.serviceWorker.getRegistrations();
+ return regs.length;
+ }
+ return 0;
+ } catch {
+ return 0;
+ }
+ },
+ });
+ return (result?.result as number) || 0;
+ } catch (error) {
+ console.error('Failed to get ServiceWorker count:', error);
return 0;
}
}
export function formatSize(bytes: number): string {
if (bytes === 0) return '0 B';
+ if (bytes < 1024) return `${bytes} B`; // 处理小于 1KB 的情况
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));