refactor(storageCleaner): simplify architecture and improve type semantics
- Remove RELOAD_TAB message chain, use chrome.tabs.reload() directly - Rename IndexedDB label to '站点存储' for accuracy - Introduce StorageSizeInfo type to distinguish bytes vs count - Rename totalSize to totalBytes for clarity - Merge runCleanScript into runScript to reduce duplication - Rename CleaningResult.success to overallSuccess to avoid confusion - Remove unused domain state and setDomain call Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,10 +34,14 @@ describe('messages', () => {
|
||||
const mockTab = { id: 123, url: 'https://example.com' };
|
||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual(mockResponse);
|
||||
expect(mockSendMessage).toHaveBeenCalledWith(MessageAction.RELOAD_TAB, { tabId: 123 }, 123);
|
||||
expect(mockSendMessage).toHaveBeenCalledWith(
|
||||
MessageAction.RESTORE_RIGHT_CLICK,
|
||||
undefined,
|
||||
123,
|
||||
);
|
||||
});
|
||||
|
||||
it('应该支持不带数据的消息发送', async () => {
|
||||
@@ -60,11 +64,11 @@ describe('messages', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
(chrome.tabs.query as any).mockResolvedValue([]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({ success: false, message: '无法获取当前标签页' });
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'[Messaging] 无法获取当前标签页,无法发送动作: reloadTab',
|
||||
'[Messaging] 无法获取当前标签页,无法发送动作: restoreRightClick',
|
||||
);
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
@@ -73,7 +77,7 @@ describe('messages', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
(chrome.tabs.query as any).mockResolvedValue([{ url: 'https://example.com' }]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({ success: false, message: '无法获取当前标签页' });
|
||||
consoleSpy.mockRestore();
|
||||
@@ -87,7 +91,7 @@ describe('messages', () => {
|
||||
const mockTab = { id: 123, url: 'https://example.com' };
|
||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
@@ -104,7 +108,7 @@ describe('messages', () => {
|
||||
const mockTab = { id: 123, url: 'https://example.com' };
|
||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
@@ -121,7 +125,7 @@ describe('messages', () => {
|
||||
const mockTab = { id: 123, url: 'https://example.com' };
|
||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
@@ -138,7 +142,7 @@ describe('messages', () => {
|
||||
const mockTab = { id: 123, url: 'https://example.com' };
|
||||
(chrome.tabs.query as any).mockResolvedValue([mockTab]);
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
@@ -151,7 +155,7 @@ describe('messages', () => {
|
||||
const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
(chrome.tabs.query as any).mockRejectedValue(new Error('Query failed'));
|
||||
|
||||
const result = await sendMessageToContent(MessageAction.RELOAD_TAB, { tabId: 123 });
|
||||
const result = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { defineExtensionMessaging } from '@webext-core/messaging';
|
||||
|
||||
export enum MessageAction {
|
||||
RELOAD_TAB = 'reloadTab',
|
||||
SIDE_PANEL_STATE_CHANGED = 'sidePanelStateChanged',
|
||||
CONTEXT_MENU_CLICKED = 'contextMenuClicked',
|
||||
RESTORE_RIGHT_CLICK = 'restoreRightClick',
|
||||
@@ -21,7 +20,6 @@ export interface ContextMenuClickedPayload {
|
||||
}
|
||||
|
||||
export interface ProtocolMap {
|
||||
[MessageAction.RELOAD_TAB](data: { tabId: number; delay?: number }): MessageResponse;
|
||||
[MessageAction.SIDE_PANEL_STATE_CHANGED](data: { isOpen: boolean }): void;
|
||||
[MessageAction.CONTEXT_MENU_CLICKED](data: ContextMenuClickedPayload): void;
|
||||
[MessageAction.RESTORE_RIGHT_CLICK](data: undefined): MessageResponse & { restored: boolean };
|
||||
|
||||
+91
-74
@@ -215,98 +215,115 @@ export async function clearCookies(url: string): Promise<StorageCleanResult> {
|
||||
async function runCleanScript(
|
||||
tabId: number,
|
||||
func: () => { count: number } | Promise<{ count: number }>,
|
||||
errorLabel: string,
|
||||
): Promise<StorageCleanResult> {
|
||||
try {
|
||||
const [result] = await chrome.scripting.executeScript({ target: { tabId }, func });
|
||||
if (result?.result && typeof result.result === 'object' && 'count' in result.result) {
|
||||
return { success: true, count: result.result.count };
|
||||
}
|
||||
return { success: false, error: 'No result returned' };
|
||||
} catch (error) {
|
||||
return { success: false, error: String(error) };
|
||||
const raw = await runScript(tabId, func, errorLabel, { count: 0 });
|
||||
if (raw && typeof raw === 'object' && 'count' in raw) {
|
||||
return { success: true, count: raw.count };
|
||||
}
|
||||
return { success: false, error: 'No result returned' };
|
||||
}
|
||||
|
||||
export async function injectClearLocalStorage(tabId: number): Promise<StorageCleanResult> {
|
||||
return runCleanScript(tabId, () => {
|
||||
const count = localStorage.length;
|
||||
localStorage.clear();
|
||||
return { count };
|
||||
});
|
||||
return runCleanScript(
|
||||
tabId,
|
||||
() => {
|
||||
const count = localStorage.length;
|
||||
localStorage.clear();
|
||||
return { count };
|
||||
},
|
||||
'clear LocalStorage',
|
||||
);
|
||||
}
|
||||
|
||||
export async function injectClearSessionStorage(tabId: number): Promise<StorageCleanResult> {
|
||||
return runCleanScript(tabId, () => {
|
||||
const count = sessionStorage.length;
|
||||
sessionStorage.clear();
|
||||
return { count };
|
||||
});
|
||||
return runCleanScript(
|
||||
tabId,
|
||||
() => {
|
||||
const count = sessionStorage.length;
|
||||
sessionStorage.clear();
|
||||
return { count };
|
||||
},
|
||||
'clear SessionStorage',
|
||||
);
|
||||
}
|
||||
|
||||
export async function injectClearIndexedDB(tabId: number): Promise<StorageCleanResult> {
|
||||
return runCleanScript(tabId, async () => {
|
||||
if (typeof indexedDB.databases !== 'function') {
|
||||
return { count: 0 };
|
||||
}
|
||||
const databases = await indexedDB.databases();
|
||||
let count = 0;
|
||||
for (const db of databases) {
|
||||
if (!db.name) continue;
|
||||
const dbName = db.name;
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const deleteReq = indexedDB.deleteDatabase(dbName);
|
||||
const timeout = setTimeout(() => {
|
||||
console.warn('IndexedDB delete timeout:', dbName);
|
||||
resolve();
|
||||
}, 5000);
|
||||
deleteReq.onblocked = () => {
|
||||
console.warn('IndexedDB delete blocked:', dbName);
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
deleteReq.onsuccess = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
deleteReq.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error(`Failed to delete ${dbName}`));
|
||||
};
|
||||
});
|
||||
count++;
|
||||
} catch (e) {
|
||||
console.error('Delete DB error:', e);
|
||||
return runCleanScript(
|
||||
tabId,
|
||||
async () => {
|
||||
if (typeof indexedDB.databases !== 'function') {
|
||||
return { count: 0 };
|
||||
}
|
||||
}
|
||||
return { count };
|
||||
});
|
||||
const databases = await indexedDB.databases();
|
||||
let count = 0;
|
||||
for (const db of databases) {
|
||||
if (!db.name) continue;
|
||||
const dbName = db.name;
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const deleteReq = indexedDB.deleteDatabase(dbName);
|
||||
const timeout = setTimeout(() => {
|
||||
console.warn('IndexedDB delete timeout:', dbName);
|
||||
resolve();
|
||||
}, 5000);
|
||||
deleteReq.onblocked = () => {
|
||||
console.warn('IndexedDB delete blocked:', dbName);
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
deleteReq.onsuccess = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
deleteReq.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error(`Failed to delete ${dbName}`));
|
||||
};
|
||||
});
|
||||
count++;
|
||||
} catch (e) {
|
||||
console.error('Delete DB error:', e);
|
||||
}
|
||||
}
|
||||
return { count };
|
||||
},
|
||||
'clear IndexedDB',
|
||||
);
|
||||
}
|
||||
|
||||
export async function injectClearCacheStorage(tabId: number): Promise<StorageCleanResult> {
|
||||
return runCleanScript(tabId, async () => {
|
||||
if ('caches' in window) {
|
||||
const cacheNames = await caches.keys();
|
||||
for (const name of cacheNames) {
|
||||
await caches.delete(name);
|
||||
return runCleanScript(
|
||||
tabId,
|
||||
async () => {
|
||||
if ('caches' in window) {
|
||||
const cacheNames = await caches.keys();
|
||||
for (const name of cacheNames) {
|
||||
await caches.delete(name);
|
||||
}
|
||||
return { count: cacheNames.length };
|
||||
}
|
||||
return { count: cacheNames.length };
|
||||
}
|
||||
return { count: 0 };
|
||||
});
|
||||
return { count: 0 };
|
||||
},
|
||||
'clear CacheStorage',
|
||||
);
|
||||
}
|
||||
|
||||
export async function injectUnregisterServiceWorkers(tabId: number): Promise<StorageCleanResult> {
|
||||
return runCleanScript(tabId, async () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
for (const registration of registrations) {
|
||||
await registration.unregister();
|
||||
return runCleanScript(
|
||||
tabId,
|
||||
async () => {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
for (const registration of registrations) {
|
||||
await registration.unregister();
|
||||
}
|
||||
return { count: registrations.length };
|
||||
}
|
||||
return { count: registrations.length };
|
||||
}
|
||||
return { count: 0 };
|
||||
});
|
||||
return { count: 0 };
|
||||
},
|
||||
'unregister ServiceWorkers',
|
||||
);
|
||||
}
|
||||
|
||||
export async function clearStorage(
|
||||
@@ -314,7 +331,7 @@ export async function clearStorage(
|
||||
url: string,
|
||||
options: StorageCleanerOptions,
|
||||
): Promise<CleaningResult> {
|
||||
const result: CleaningResult = { success: true };
|
||||
const result: CleaningResult = { overallSuccess: true };
|
||||
|
||||
if (options.localStorage) {
|
||||
result.localStorage = await injectClearLocalStorage(tabId);
|
||||
@@ -339,7 +356,7 @@ export async function clearStorage(
|
||||
(r): r is StorageCleanResult => r?.success === false,
|
||||
);
|
||||
if (failures.length > 0) {
|
||||
result.success = false;
|
||||
result.overallSuccess = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user