> For the complete documentation index, see [llms.txt](https://408550179s-organization.gitbook.io/blog/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://408550179s-organization.gitbook.io/blog/chrome-cha-jian-qing-li-dang-qian-wang-zhan-huan-cun-he-ben-di-cun-chu.md).

# Chrome插件清理当前网站缓存和本地存储

前端开发或给客户部署页面时，经常遇到一个很现实的问题：代码已经发了，但对方浏览器还在命中旧缓存。

普通刷新、强刷、清浏览器缓存都不一定稳定，因为缓存来源可能有很多：

* HTTP cache
* Cache Storage
* Service Worker
* Cookie
* LocalStorage
* SessionStorage
* IndexedDB

所以我做了一个 Chrome MV3 插件，目标很简单：只清理当前网站的数据，然后自动硬刷新当前页面。

## manifest 权限

MV3 里需要声明这些权限：

```json
{
  "manifest_version": 3,
  "name": "站点缓存清理工具",
  "version": "1.1.0",
  "permissions": [
    "browsingData",
    "activeTab",
    "tabs",
    "storage",
    "scripting"
  ],
  "host_permissions": ["<all_urls>"],
  "background": {
    "service_worker": "background.js"
  },
  "commands": {
    "clear-and-reload": {
      "suggested_key": {
        "default": "Alt+Shift+R",
        "mac": "Alt+Shift+R"
      },
      "description": "清除当前网站数据并硬刷新"
    }
  }
}
```

{% hint style="info" %}
如果要上 Chrome Web Store，可以在商店版构建时去掉 `host_permissions`，减少审核压力。
{% endhint %}

## 只清当前站点

不要直接清全浏览器缓存。更安全的方式是根据当前 tab 的 URL 计算 origin：

```js
function getOrigins(url) {
  const parsed = new URL(url);
  const origins = new Set([parsed.origin]);

  if (parsed.protocol === "https:") {
    origins.add(`http://${parsed.host}`);
  } else if (parsed.protocol === "http:") {
    origins.add(`https://${parsed.host}`);
  }

  return [...origins];
}
```

同时要排除浏览器内部页面：

```js
function isBlockedUrl(url) {
  return (
    !url ||
    url.startsWith("chrome://") ||
    url.startsWith("chrome-extension://") ||
    url.startsWith("edge://") ||
    url.startsWith("about:")
  );
}
```

## browsingData 清理

```js
const DATA_TYPES = {
  cache: true,
  cacheStorage: true,
  cookies: true,
  fileSystems: true,
  indexedDB: true,
  localStorage: true,
  serviceWorkers: true,
  webSQL: true,
};

function clearSiteData(origins) {
  return new Promise((resolve, reject) => {
    chrome.browsingData.remove({ origins }, DATA_TYPES, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
        return;
      }
      resolve();
    });
  });
}
```

这里可以清掉大部分站点数据，但有一个问题：`sessionStorage` 不一定能通过 `browsingData` 处理干净。

## 在 MAIN world 清 sessionStorage

`sessionStorage` 属于页面上下文，所以需要把脚本注入到页面的 MAIN world：

```js
async function purgePageContext(tabId, cacheOnly) {
  try {
    await chrome.scripting.executeScript({
      target: { tabId, allFrames: true },
      world: "MAIN",
      func: async (onlyCache) => {
        try {
          sessionStorage.clear();
        } catch (_) {}

        if (!onlyCache) {
          try {
            localStorage.clear();
          } catch (_) {}
        }

        if ("serviceWorker" in navigator) {
          const regs = await navigator.serviceWorker.getRegistrations();
          await Promise.all(regs.map((reg) => reg.unregister()));
        }

        if ("caches" in window) {
          const keys = await caches.keys();
          await Promise.all(keys.map((key) => caches.delete(key)));
        }

        if (!onlyCache && indexedDB.databases) {
          const dbs = await indexedDB.databases();
          await Promise.all(
            dbs.map(
              (db) =>
                new Promise((resolve) => {
                  const req = indexedDB.deleteDatabase(db.name);
                  req.onsuccess = req.onerror = req.onblocked = () => resolve();
                }),
            ),
          );
        }
      },
      args: [cacheOnly],
    });
  } catch {
    // 有些页面不允许注入，忽略即可
  }
}
```

这个点很关键：如果只调用 `browsingData.remove`，用户页面的 session 状态有时还会留着，导致“明明清了缓存但页面还是不对”。

## 清理后硬刷新

```js
function reloadTab(tabId) {
  return new Promise((resolve, reject) => {
    chrome.tabs.reload(tabId, { bypassCache: true }, () => {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
        return;
      }
      resolve();
    });
  });
}
```

完整流程就是：

```js
async function clearCurrentTab(tab, options = {}) {
  if (!tab?.id || isBlockedUrl(tab.url)) return;

  const origins = getOrigins(tab.url);
  await clearSiteData(origins);
  await purgePageContext(tab.id, options.cacheOnly);
  await reloadTab(tab.id);
}
```

## 外部分发自动更新

如果不走 Chrome Web Store，也可以用 CRX + `updates.xml` 做外部分发。构建时生成：

```xml
<?xml version='1.0' encoding='UTF-8'?>
<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>
  <app appid='扩展ID'>
    <updatecheck codebase='https://cdn.example.com/extension.crx' version='1.1.0' />
  </app>
</gupdate>
```

注意：

* `update_url` 必须是 HTTPS。
* CRX 签名私钥不能提交到仓库。
* 固定公钥后扩展 ID 才能保持不变。

## 总结

这类插件看起来只是“一键清缓存”，但真正要稳定，需要同时处理：

* 当前站点 origin 限制。
* Service Worker 反注册。
* Cache Storage 删除。
* `sessionStorage` 的 MAIN world 注入。
* 清理后强制刷新。
* 商店版和外部分发版的 manifest 差异。

做好之后，对调试线上缓存问题非常省时间。
