Chrome扩展要完蛋,清单第三版搞了啥,老司机带路保平安?

4,265字
18–27 分钟
in

Chrome扩展圈子最近炸了锅,老早写的插件突然被喷“过时”。啥情况?原来谷歌搞了个叫Manifest V3的新规矩,老版V2的扩展从2023年起逐步被下架。这玩意说白了就是一套新的API规则,管着插件怎么跟浏览器唠嗑。不跟上这波改动,辛辛苦苦码的扩展可能直接翻车。这篇东西就用大白话拆解V3到底变了哪几块,手把手给老扩展动手术,保它继续活蹦乱跳。

目录

清单新旧有啥不同

Manifest V3和V2的核心差异集中在四个关节眼上。把这几处整明白,迁移就稳了一大半。

后台脚本变服务员

V2时代,扩展后台跑的是持久或非持久的背景页面(background pages)。V3直接换成服务员(service worker),这货不干活就自动休眠,省内存杠杠的。对比一下代码就懂:

V2写法

"background": {
  "scripts": ["background.js"],
  "persistent": true
}

V3写法

"background": {
  "service_worker": "background.js"
}

改完这处,原来的后台逻辑基本不用动,但得注意服务员里不能用windowdocument这些DOM玩意儿,就像外卖小哥只管跑腿不管做饭。

权限拆分更细

V3把主机权限单独拎出来,不跟普通权限搅和在一块。以前写在一堆,现在分家过日子:

V2

"permissions": ["activeTab", "storage", "https://api.example.com/*"]

V3

"permissions": ["activeTab", "storage"],
"host_permissions": ["https://api.example.com/*"]

这么搞的好处是上架时审核更透明,哪家网站能被插件摸到一目了然。

安全策略变对象

内容安全策略(CSP)在V3里从字符串改成了对象,区分扩展页面和沙盒环境:

V2

"content_security_policy": "script-src 'self' https://trusted.com; object-src 'self'"

V3

"content_security_policy": {
  "extension_pages": "script-src 'self' https://trusted.com; object-src 'self'",
  "sandbox": "script-src 'self' https://trusted.com; object-src 'self'"
}

注意不能再用外部域的script-src,所有JS必须打包进扩展里,就像搬家不能指望邻居借你锅碗瓢盆。

拦网请求换规矩

V2里改网络请求用webRequest API,想拦广告就动态分析URL。V3换成了declarativeNetRequest,得提前写好要拦截的地址列表,像给保安一张黑名单,不能说“看着像坏人就拦”。

V2权限

"permissions": ["webRequest", "webRequestBlocking"]

V3权限

"permissions": ["declarativeNetRequest"]

代码里原来用chrome.webRequest.onBeforeRequest的地方,要改成chrome.declarativeNetRequest.updateDynamicRules。这波改动让广告拦截器有点伤,但普通功能扩展基本没影响。

动手改四个地方

迁移操作不玄乎,按下面四步走,老扩展原地复活。

改版本号和按钮

打开manifest.json,第一件事把"manifest_version"从2改成3。然后找"browser_action""page_action",全换成"action"。以前写chrome.browserAction.onClicked的地方,统统改成chrome.action.onClicked。这就像把家里的老式拉线开关换成触摸面板,功能一样但更利索。

挪主机权限

"permissions"数组里把带http://https://*://的项揪出来,塞到新增的"host_permissions"里。普通权限像"storage""cookies"留在原地。举个例子,原来写:

"permissions": ["activeTab", "storage", "https://www.css-tricks.com/*", "*://*/*"]

改成:

"permissions": ["activeTab", "storage"],
"host_permissions": ["https://www.css-tricks.com/*"],
"optional_host_permissions": ["*://*/*"]

注意optional_host_permissions是V3新加的,运行时可以动态申请额外权限,适合那些“大部分网站不需要,但某些功能需要”的场景。

整容安全策略

"content_security_policy"字段从字符串改成对象结构。如果原来写了外部域名(比如https://ajax.googleapis.com),必须删掉,因为V3禁止远程代码。所有脚本必须打包在扩展文件夹里。像这样改:

改前

"content_security_policy": "script-src 'self' https://cdn.jsdelivr.net; object-src 'self'"

改后

"content_security_policy": {
  "extension_pages": "script-src 'self'; object-src 'self'",
  "sandbox": "script-src 'self'; object-src 'self'"
}

要是扩展用了eval()或者new Function()这种动态执行字符串的骚操作,也得换掉,改用预定义函数或者chrome.scripting.executeScript注入文件。

替换请求拦截器

如果扩展里用到了chrome.webRequest API,得迁移到declarativeNetRequest。举个例子,原来拦某个广告域名:

V2代码

chrome.webRequest.onBeforeRequest.addListener(
  function(details) { return {cancel: true}; },
  {urls: ["*://*.doubleclick.net/*"]},
  ["blocking"]
);

V3改法:先在manifest.json加上"declarativeNetRequest"权限,然后定义规则:

const rule = {
  id: 1,
  priority: 1,
  action: { type: "block" },
  condition: { urlFilter: "*://*.doubleclick.net/*", resourceTypes: ["main_frame", "script"] }
};
chrome.declarativeNetRequest.updateDynamicRules({ addRules: [rule], removeRuleIds: [1] });

规则得预先写好,不能运行时动态生成URL模式。这就像提前给保安一张通缉令名单,不能看见谁长得像再打电话问。

迁移时可能会遇到服务员(service worker)执行上下文不兼容的情况。比如V2里用chrome.runtime.getBackgroundPage()拿后台页面的window对象,V3里服务员没有页面概念,得改成消息传递。原来这么写:

// V2: 在弹窗里直接调后台函数
chrome.runtime.getBackgroundPage(function(bg) {
  bg.doSomething();
});

改成V3后,弹窗发消息,服务员监听:

// 弹窗里
chrome.runtime.sendMessage({type: "doSomething"});

// background.js (service worker)
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  if (msg.type === "doSomething") { doSomething(); }
});

这就像以前直接推门进后台办公室喊人,现在得先按门铃传话。

时间线别踩坑

谷歌给老扩展判了死缓,具体节点得盯紧:

时间点会发生啥
2023年1月开发版Chrome关闭V2支持
2023年6月商店禁止新V2扩展上架
2024年1月正式版全面停用V2扩展

早改早省心,拖到最后一刻容易翻车。尤其是那些依赖webRequest的广告拦截类扩展,得花时间重新设计规则集。

另外注意,V3不让执行远程代码,所有JS、CSS、Wasm必须打包在扩展文件夹里。以前从CDN拉代码的骚操作,比如chrome.scripting.executeScript({code: fetch('https://some.cdn/script.js')}),直接报废。正确姿势是把文件放本地,用chrome.runtime.getURL('script.js')拿地址再注入。

跨域请求(CORS)也得挪到服务员里处理。以前内容脚本直接fetch第三方API,现在服务员代劳,结果通过消息回传。这就像点外卖别让客人自己跑腿,让服务员统一取餐。

还有个坑:V2里chrome.tabs.executeScript变成了V3的chrome.scripting.executeScript,参数格式也变了。原来写chrome.tabs.executeScript({code: 'alert("hi")'}),V3里得:

chrome.scripting.executeScript({
  target: {tabId: tab.id},
  func: () => alert("hi")
});

或者注入文件:files: ["content.js"]

最后,写死"manifest_version": 3后,可以用chrome.runtime.getManifest()看看版本号,确认迁移成功。跑一遍功能测试,尤其后台逻辑、权限请求、网络拦截这几块。

这波迁移看着烦,但其实改完一劳永逸。老司机带路到这,剩下的就是动手干。改完的扩展能在新规矩下继续浪,不怕被商店扫地出门。