docs: update storage cleaner design with code review feedback

- Add routing implementation details
- Add restricted page detection
- Add complete executeScript examples
- Add error handling for IndexedDB.databases
- Add Service Workers multiple registrations handling
- Add popup lifecycle notes
- Improve TypeScript types with error handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
雨霖铃
2026-03-20 00:22:34 +08:00
parent d2dc0057b3
commit 11af171212
@@ -25,6 +25,41 @@ entrypoints/popup/pages/
在弹窗中添加标签页切换功能,用户可以在时间戳转换和存储清理之间切换。 在弹窗中添加标签页切换功能,用户可以在时间戳转换和存储清理之间切换。
**路由实现方案:**
使用简单的状态管理进行页面切换:
```typescript
// App.tsx
type PageType = 'timestamp' | 'storageCleaner';
function App() {
const [currentPage, setCurrentPage] = useState<PageType>('timestamp');
return (
<div className="app">
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 2 }}>
<Button
variant={currentPage === 'timestamp' ? 'contained' : 'outlined'}
onClick={() => setCurrentPage('timestamp')}
>
</Button>
<Button
variant={currentPage === 'storageCleaner' ? 'contained' : 'outlined'}
onClick={() => setCurrentPage('storageCleaner')}
sx={{ ml: 1 }}
>
</Button>
</Box>
{currentPage === 'timestamp' && <TimestampPage />}
{currentPage === 'storageCleaner' && <StorageCleanerPage />}
</div>
);
}
```
## 用户界面设计 ## 用户界面设计
### 页面组成 ### 页面组成
@@ -56,13 +91,18 @@ entrypoints/popup/pages/
### 获取当前标签页域名 ### 获取当前标签页域名
使用 Chrome Tabs API 获取当前活动标签页: 使用 Chrome Tabs API 获取当前活动标签页,并过滤受限页面
```typescript ```typescript
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.url) {
throw new Error('无法获取当前标签页 URL'); // 检查受限页面
const restrictedProtocols = ['chrome:', 'chrome-extension:', 'about:', 'edge:', 'view-source:'];
if (!tab?.url || restrictedProtocols.some((p) => tab.url!.startsWith(p))) {
throw new Error('存储清理功能不支持此页面');
} }
const domain = new URL(tab.url).hostname; const domain = new URL(tab.url).hostname;
``` ```
@@ -85,6 +125,17 @@ for (const cookie of cookies) {
### 注入脚本清理其他存储 ### 注入脚本清理其他存储
使用 `chrome.scripting.executeScript` 注入清理脚本:
```typescript
const result = await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
// 清理逻辑在页面上下文中执行
},
});
```
需要注入到页面执行的存储清理逻辑: 需要注入到页面执行的存储清理逻辑:
#### 清理 localStorage #### 清理 localStorage
@@ -106,58 +157,73 @@ return count;
#### 清理 IndexedDB #### 清理 IndexedDB
```javascript ```javascript
const databases = await indexedDB.databases(); // 检查 databases indexedDB 方法是否可用
let count = 0; if (typeof indexedDB.databases === 'function') {
for (const db of databases) { const databases = await indexedDB.databases();
const deleteReq = indexedDB.deleteDatabase(db.name); let count = 0;
deleteReq.onblocked = () => { for (const db of databases) {
console.warn('IndexedDB delete blocked:', db.name); const deleteReq = indexedDB.databases(db.name);
}; deleteReq.onblocked = () => {
await new Promise((resolve, reject) => { console.warn('IndexedDB delete blocked:', db.name);
deleteReq.onsuccess = resolve; };
deleteReq.onerror = reject; await new Promise((resolve, reject) => {
}); deleteReq.onsuccess = resolve;
count++; deleteReq.onerror = reject;
});
count++;
}
return count;
} }
// 降级方案:使用传统方法
let count = 0;
// 尝试遍历已知数据库或提示用户手动清除
return count; return count;
``` ```
#### 清理 Cache Storage #### 清理 Cache Storage
```javascript ```javascript
const cacheNames = await caches.keys(); if ('caches' in window) {
for (const name of cacheNames) { const cacheNames = await caches.keys();
await caches.delete(name); for (const name of cacheNames) {
await caches.delete(name);
}
return cacheNames.length;
} }
return cacheNames.length; return 0;
``` ```
#### 注销 Service Workers #### 注销 Service Workers
```javascript ```javascript
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
const registration = await navigator.serviceWorker.getRegistration(); const registrations = await navigator.serviceWorker.getRegistrations();
if (registration) { let count = 0;
for (const registration of registrations) {
await registration.unregister(); await registration.unregister();
return true; count++;
} }
return count;
} }
return false; return 0;
``` ```
### 数据流 ### 数据流
1. 页面加载时获取当前标签页 URL 并显示域名 1. 页面加载时获取当前标签页 URL 并显示域名
2. 用户勾选要清理的存储类型 2. 检查是否为受限页面(chrome://, about:// 等),如果是则显示错误提示
3. 用户选择是否自动刷新页面 3. 用户勾选要清理的存储类型
4. 用户点击清理按钮 4. 用户选择是否自动刷新页面
5. 弹出确认对话框询问用户确认 5. 用户点击清理按钮
6. 确认后执行清理: 6. 弹出确认对话框询问用户确认
7. 确认后执行清理:
- 如果选择 Cookies:直接使用 chrome.cookies API 删除 - 如果选择 Cookies:直接使用 chrome.cookies API 删除
- 其他存储类型:向页面注入清理脚本 - 其他存储类型:向页面注入清理脚本
7. 收集所有清理结果并统计 8. 收集所有清理结果并统计
8. 显示清理结果 9. 显示清理结果
9. 如果勾选"自动刷新"或用户点击"刷新页面"按钮,执行页面刷新 10. 如果勾选"自动刷新"或用户点击"刷新页面"按钮,执行页面刷新
**注意:** 当触发页面刷新时,popup 会自动关闭。需要在刷新前显示提示信息。
### 页面刷新 ### 页面刷新
@@ -167,14 +233,29 @@ await chrome.tabs.reload(tab.id);
## 错误处理 ## 错误处理
| 错误场景 | 处理方式 | | 错误场景 | 处理方式 |
| ------------------- | ---------------------------------- | | ------------------------------- | ---------------------------------------- |
| 无法获取当前标签页 | 显示错误提示:"无法获取当前标签页" | | 无法获取当前标签页 | 显示错误提示:"无法获取当前标签页" |
| 无法访问页面 URL | 显示错误提示:"无法访问此页面" | | 受限页面(chrome://, about:// | 显示错误提示:"存储清理功能不支持此页面" |
| IndexedDB onblocked | 显示警告但继续执行其他清理 | | 无法访问页面 URL | 显示错误提示:"无法访问此页面" |
| 清理失败 | 显示具体错误信息 | | IndexedDB onblocked | 显示警告但继续执行其他清理 |
| Cookies 删除失败 | 记录错误,显示清理失败提示 | | IndexedDB.databases 不可用 | 使用降级方案或提示用户手动清除 |
| 无权限 | 提示用户刷新扩展或检查权限 | | 清理失败 | 显示具体错误信息 |
| Cookies 删除失败 | 记录错误,显示清理失败提示 |
| 无权限 | 提示用户刷新扩展或检查权限 |
| 脚本注入失败 | 显示错误提示:"无法注入清理脚本" |
**Popup 生命周期说明:**
- Popup 在页面失去焦点时会关闭
- 刷新页面后 Popup 会自动关闭
- 需要在刷新前显示提示:"页面即将刷新,Popup 将关闭"
**Popup 生命周期说明:**
- Popup 在页面失去焦点时会关闭
- 刷新页面后 Popup 会自动关闭
- 需要在刷新前显示提示:"页面即将刷新,Popup 将关闭"
## 权限需求 ## 权限需求
@@ -207,13 +288,25 @@ export interface StorageCleanerOptions {
serviceWorkers: boolean; serviceWorkers: boolean;
} }
export type StorageCleanResult =
| {
success: true;
count: number;
}
| {
success: false;
error: string;
};
export interface CleaningResult { export interface CleaningResult {
localStorage?: number; success: boolean;
sessionStorage?: number; error?: string;
indexedDB?: number; localStorage?: StorageCleanResult;
cookies?: number; sessionStorage?: StorageCleanResult;
cacheStorage?: number; indexedDB?: StorageCleanResult;
serviceWorkers?: boolean; cookies?: StorageCleanResult;
cacheStorage?: StorageCleanResult;
serviceWorkers?: StorageCleanResult;
} }
``` ```