一、Manifest V2 支持時間表
Chrome 瀏覽器官方已經(jīng)給出確定的時間來棄用 V2 版本的插件了。
最早從 2024 年 6 月的
Chrome127 開始,我們將開始停用 Chrome 的不穩(wěn)定版本(開發(fā)者版、Canary版和Beta版)中的Manifest V2擴展程序。受此變化影響的用戶會在瀏覽器中看到Manifest V2擴展程序自動停用,并且無法再從Chrome應(yīng)用商店安裝Manifest V2擴展程序。此外,Manifest V2擴展程序在Chrome應(yīng)用商店中將不再擁有“精選”徽章(如果目前已有該徽章)。
如果企業(yè)如果使用
ExtensionManifestV2Availability政策確保其組織中的Manifest V2擴展程序能持續(xù)正常運行,則其組織中還有一年的時間(即在 2025 年 6 月之前)遷移Manifest V2擴展程序。在此之前,已啟用此政策的瀏覽器不會受到棄用安排的影響。
如果擴展程序發(fā)布商目前仍在發(fā)布
Manifest V2擴展程序,我們強烈建議您在 2024 年 6 月之前完成向Manifest V3的遷移。
官方時間線
1、2022 年 6 月:Chrome 應(yīng)用商店 - 不再有新的專用擴展程序
Chrome 應(yīng)用商店不再接受公開范圍設(shè)為“不公開”的新 Manifest V2 擴展程序。
2、2024 年 6 月:在穩(wěn)定發(fā)布前棄用 Chrome MV2
3、2024 年 6 月 + 1-X 個月:棄用 Chrome MV2 并穩(wěn)定發(fā)布
4、2025 年 6 月:Chrome MV2 棄用(企業(yè)版)
二、Manifest V3 遷移核對列表
1、Manifest.json 文件
1. 更新 manifest_version 版本號
將 manifest_version 字段的值從 2 更改為 3。
{
"manifest_version": 3
}
2. 更新 permissions 和 host_permissions 字段
Manifest V3 中的主機權(quán)限是一個單獨的字段;
不需要在 permissions 或 optional_permissions 中指定這些權(quán)限;
有單獨的字段 host_permissions 來表示。
2.1. V2 版本
{
"permissions": [
"tabs",
"bookmarks",
"https://www.blogger.com/",
],
"optional_permissions": [
"unlimitedStorage",
"*://*/*",
]
}
2.2. V3 版本
{
"permissions": [
"tabs",
"bookmarks"
],
"optional_permissions": [
"unlimitedStorage"
],
"host_permissions": [
"https://www.blogger.com/",
],
"optional_host_permissions": [
"*://*/*",
]
}
3. 更新 web_accessible_resources 字段
Manifest V3 會限制哪些網(wǎng)站和擴展程序可以訪問擴展程序中的資源,以此來限制數(shù)據(jù)公開范圍;
在 Manifest V2 中,默認(rèn)情況下,指定資源可供所有網(wǎng)站訪問;
在下面的 Manifest V3 示例中,這些資源僅可供匹配的網(wǎng)站使用,而只有某些圖片可供所有網(wǎng)站使用。
3.1. V2 版本
{
"web_accessible_resources": [
"images/*",
"style/extension.css",
"script/extension.js"
],
}
3.2. V3 版本
{
"web_accessible_resources": [
{
"resources": [
"images/*"
],
"matches": [
"*://*/*"
]
},
{
"resources": [
"style/extension.css",
"script/extension.js"
],
"matches": [
"https://example.com/*"
]
}
],
}
2、遷移到 Service Worker
使用 service worker 替換 background 或 event pages,以確保后臺代碼遠(yuǎn)離主線程,這樣可以讓擴展程序僅在需要時運行,從而節(jié)省資源。
1. Background 和 Service Worker 之間的區(qū)別
1.1. Service Worker 和 background 之間的差異
- 在主線程以外運行,這意味著不會干擾擴展程序內(nèi)容;
- 具有特殊功能,例如攔截擴展程序來源上的提取事件,例如攔截工具欄彈出式窗口中的提取事件;
- 可以通過客戶端界面與其他上下文進(jìn)行通信和交互。
1.2. 改動點
- 由于它們無法訪問
DOM或window接口,因此需要將此類調(diào)用移至其他API或移至屏幕外文檔中; - 不應(yīng)注冊事件監(jiān)聽器來響應(yīng)返回的
promise或在事件回調(diào)內(nèi)部; - 不向后兼容
XMLHttpRequest(),因此需要將接口的調(diào)用替換為fetch(); - 由于它們在不使用時終止,因此需要保留應(yīng)用狀態(tài),而不是依賴于全局變量;
- 終止
Service Worker還可以在計時器完成之前結(jié)束計時器。需要將其替換為alarms。
2. 更新 manifest.json 中的 background 字段
在 Manifest V3 中,background 頁面被 Service Worker 所取代:
- 將
manifest.json中的"background.scripts"替換為"background.service_worker"; -
"service_worker"字段接受字符串,而不是字符串?dāng)?shù)組; - 從
manifest.json中移除"background.persistent"。
2.1. V2 版本
{
"background": {
"scripts": [
"backgroundContextMenus.js",
"backgroundOauth.js"
],
"persistent": false
},
}
2.2. V3 版本
{
"background": {
"service_worker": "service_worker.js",
"type": "module"
}
}
"service_worker" 字段接受單個字符串。只有使用 ES module (使用 import 關(guān)鍵字)時,才需要 "type" 字段。其值將始終為 "module"。
3. 將 DOM 和 window 調(diào)用移至屏幕外文檔
某些擴展程序需要訪問 DOM 和 window 對象,無需打開新的窗口或標(biāo)簽頁。Offscreen API 支持這類使用情形,因為這類 API 可以打開和關(guān)閉與擴展程序打包在一起的未顯示文檔,而不會干擾用戶體驗。除了消息傳遞之外,屏幕外文檔不會與其他擴展程序上下文共享 API,而是起到完整網(wǎng)頁的作用,供擴展程序進(jìn)行互動。
如需使用 Offscreen API,請通過 Service Worker 創(chuàng)建屏幕外文檔。
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
4. 將 localStorage 轉(zhuǎn)換為其他類型
Web 平臺的 Storage 接口(可從 window.localStorage 訪問)無法在 Service Worker 中使用。
5. 同步注冊監(jiān)聽器
異步注冊監(jiān)聽器(例如在 promise 或 callback 中)注冊并不一定能在 Manifest V3 中有效。
5.1. V2 版本
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
在 Manifest V3 中,系統(tǒng)會在分派事件時重新初始化 Service Worker。這意味著當(dāng)事件觸發(fā)時,系統(tǒng)不會注冊監(jiān)聽器(因為它們是異步添加的),系統(tǒng)還會錯過事件。
5.2. V3 版本
改為將事件監(jiān)聽器注冊移至腳本的頂層。這樣可以確保 Chrome 能夠立即找到并調(diào)用操作的點擊處理程序,即使擴展程序尚未執(zhí)行其啟動邏輯也是如此。
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
6. 將 XMLHttpRequest() 替換為全局 fetch()
無法從 Service Worker、擴展程序或其他方法調(diào)用 XMLHttpRequest()。將后臺腳本對 XMLHttpRequest() 的調(diào)用替換為對fetch() 的調(diào)用。
const response = await fetch('https://www.example.com/greeting.json');
console.log(response.statusText);
7. 保存狀態(tài)
Service Worker 是臨時的,這意味著它們可能會在用戶的瀏覽器會話期間反復(fù)啟動、運行和終止。這也意味著,自之前的上下文銷毀后,數(shù)據(jù)并非立即在全局變量中可用。如需解決此問題,請使用存儲 API。
對于 Manifest V3 來說,要將全局變量替換為對 Storage API 的調(diào)用。
chrome.runtime.onMessage.addListener(({ type, name }) => {
if (type === "set-name") {
chrome.storage.local.set({ name });
}
});
chrome.action.onClicked.addListener(async (tab) => {
const { name } = await chrome.storage.local.get(["name"]);
chrome.tabs.sendMessage(tab.id, { name });
});
8. 把計時器/定時器替換為 alarms
setTimeout() 或 setInterval() 在 Web 開發(fā)中比較常見。不過,在 Service Worker 中可能會失敗,因為每當(dāng) Service Worker 終止時,計時器就會取消。
8.1. V2 版本
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
}, TIMEOUT);
8.2. V3 版本
async function startAlarm(name, duration) {
await chrome.alarms.create(name, { delayInMinutes: 3 });
}
chrome.alarms.onAlarm.addListener(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
});
3、API 調(diào)用
1. 將 tab.executeScript() 替換為 scripting.executeScript()
在 Manifest V3 中,executeScript() 從 tabs API 移至 scripting API。這就需要在實際的代碼更改的基礎(chǔ)上更改 manifest.json 文件中的權(quán)限。
對于 executeScript() 方法:
-
"scripting"權(quán)限; -
host permissions權(quán)限或"activeTab"權(quán)限。
scripting.executeScript() 方法與其使用 tabs.executeScript() 的方式類似。但還是有一些區(qū)別。
- 舊方法只能接受一個文件,而新方法可以接受一組文件;
- 還將傳遞
ScriptInjection對象,而不是InjectDetails。
1.1. V2 版本
async function getCurrentTab() {/* ... */}
let tab = await getCurrentTab();
chrome.tabs.executeScript(
tab.id,
{
file: 'content-script.js'
}
);
代碼在
background腳本中
1.2. V3 版本
async function getCurrentTab()
let tab = await getCurrentTab();
chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content-script.js']
});
代碼在 service worker 中
2. 將 tab.insertCSS() 和 tab.removeCSS() 替換為 scripting.insertCSS() 和 scripting.removeCSS()
在 Manifest V3 中,insertCSS() 和 removeCSS() 已從 tabs API 移至 scriptingAPI。不僅需要更改代碼,還需要對 manifest.json 文件中的權(quán)限進(jìn)行更改:
-
"scripting"權(quán)限; -
host permissions權(quán)限或"activeTab"權(quán)限。
scripting API 上的函數(shù)與 tabs 上的函數(shù)類似。但還是有一些區(qū)別。
- 調(diào)用這些方法時,需要傳遞
CSSInjection對象,而不是InjectDetails; -
tabId作為CSSInjection.target的成員(而不是方法參數(shù))進(jìn)行傳遞。
2.1. V2 版本
chrome.tabs.insertCSS(tabId, injectDetails, () => {
// callback code
});
代碼在
background腳本中
2.2. V3 版本
const insertPromise = await chrome.scripting.insertCSS({
files: ["style.css"],
target: { tabId: tab.id }
});
// Remaining code.
代碼在
service worker中
3. 將 Browser Actions 和 Page Actions 替換為 Actions
在 Manifest V2 中,Browser Actions 和 Page Actions 是兩個單獨的概念。雖然一開始他們扮演的角色不同,但隨著時間的推移,它們之間的差異越來越小。在 Manifest V3 中,這些概念已整合到 Action API 中。這需要更改 manifest.json 和擴展代碼。
Manifest V3 中的操作與瀏覽器操作最為相似;不過,action API 不像 pageAction 那樣提供 hide() 和 show()。如果仍需要頁面操作,可以使用聲明性內(nèi)容模擬這些操作,也可以使用標(biāo)簽頁 ID 調(diào)用 enable() 或 disable()。
3.1. 將 "browser_action" 和 "page_action" 替換為 "action"
在 manifest.json 中,將 "browser_action" 和 "page_action" 字段替換為 "action" 字段。如需了解 "action" 字段的相關(guān)信息,請參閱參考文檔。
3.1.1. V2 版本
{
"page_action": { ... },
"browser_action": {
"default_popup": "popup.html"
}
}
3.1.2. V3 版本
{
"action": {
"default_popup": "popup.html"
}
}
3.2. 將 BrowserAction API 和 pageAction API 替換為 Action API
如果 Manifest V2 使用 browserAction 和 pageAction API,現(xiàn)在應(yīng)使用 action API。
3.2.1. V2 版本
chrome.browserAction.onClicked.addListener(tab => { ... });
chrome.pageAction.onClicked.addListener(tab => { ... });
3.2.2. V3 版本
chrome.action.onClicked.addListener(tab => { ... });
4. 將 callback 替換為 promise
在 Manifest V3 中,許多擴展程序 API 方法都會返回 promise。
為了實現(xiàn)向后兼容性,許多方法在添加 promise 支持后會繼續(xù)支持回調(diào)。需要注意的是,不能在同一函數(shù)調(diào)用中同時使用這兩者。如果傳遞回調(diào),則函數(shù)不會返回 promise;如果希望返回 promise,則也不要傳遞回調(diào)。某些 API 功能(例如事件監(jiān)聽器)將繼續(xù)需要回調(diào)。
如需從回調(diào)轉(zhuǎn)換為 promise,請移除回調(diào)并處理返回的 promise。
4.1. Callback
chrome.permissions.request(newPerms, (granted) => {
if (granted) {
console.log('granted');
} else {
console.log('not granted');
}
});
4.2. Promise
const newPerms = { permissions: ['topSites'] };
chrome.permissions.request(newPerms)
.then((granted) => {
if (granted) {
console.log('granted');
} else {
console.log('not granted');
}
});
5. 替換需要 Manifest V2 background 上下文的函數(shù)
其他擴展程序上下文只能使用消息傳遞與擴展程序 Service Worker 交互。因此,需要替換需要后臺上下文的調(diào)用,具體而言:
chrome.runtime.getBackgroundPage()chrome.extension.getBackgroundPage()chrome.extension.getExtensionTabs()
擴展程序腳本應(yīng)使用消息傳遞在 Service Worker 和擴展程序的其他部分之間進(jìn)行通信。目前,這需要使用 sendMessage(),并在擴展 Service Worker 中實現(xiàn) chrome.runtime.onMessage。從長遠(yuǎn)來看,應(yīng)計劃將這些調(diào)用替換為 postMessage() 和 Service Worker 的消息事件處理程序。
6. 替換不受支持的 API
需要在 Manifest V3 中更改下列方法和屬性。
Manifest V2 方法或?qū)傩?/th>
| 替換為 Manifest V3 方法或?qū)傩?/th>
|
|---|---|
chrome.extension.connect() |
chrome.runtime.connect() |
chrome.extension.connectNative() |
chrome.runtime.connectNative() |
chrome.extension.getExtensionTabs() |
chrome.extension.getViews() |
chrome.extension.getURL() |
chrome.runtime.getURL() |
chrome.extension.lastError |
如果方法返回 promise,請使用 promise.catch()
|
chrome.extension.onConnect |
chrome.runtime.onConnect |
chrome.extension.onConnectExternal |
chrome.runtime.onConnectExternal |
chrome.extension.onMessage |
chrome.runtime.onMessage |
chrome.extension.onRequest |
chrome.runtime.onRequest |
chrome.extension.onRequestExternal |
chrome.runtime.onMessageExternal |
chrome.extension.sendMessage() |
chrome.runtime.sendMessage() |
chrome.extension.sendNativeMessage() |
chrome.runtime.sendNativeMessage() |
chrome.extension.sendRequest() |
chrome.runtime.sendMessage() |
chrome.runtime.onSuspend(background 腳本) |
在擴展 Service Worker 中不受支持。請改用 beforeunload 文檔事件。 |
chrome.tabs.getAllInWindow() |
chrome.tabs.query() |
chrome.tabs.getSelected() |
chrome.tabs.query() |
chrome.tabs.onActiveChanged |
chrome.tabs.onActivated |
chrome.tabs.onHighlightChanged |
chrome.tabs.onHighlighted |
chrome.tabs.onSelectionChanged |
chrome.tabs.onActivated |
chrome.tabs.sendRequest() |
chrome.runtime.sendMessage() |
chrome.tabs.Tab.selected |
chrome.tabs.Tab.highlighted |
4、替換屏蔽 Web 請求監(jiān)聽器
Manifest V3 更改了擴展程序處理網(wǎng)絡(luò)請求修改的方式。擴展程序會指定規(guī)則來描述在滿足一組給定條件時要執(zhí)行的操作,而不是攔截網(wǎng)絡(luò)請求并在運行時使用 chrome.webRequest 更改請求。
Web Request API 和聲明式網(wǎng)絡(luò)請求 API 有很大的區(qū)別。需要根據(jù)用例重新編寫代碼,而不是將一個函數(shù)調(diào)用替換為另一個函數(shù)調(diào)用。
1. 更新 permissions
對 manifest.json 中的 "permissions" 字段進(jìn)行以下更改。
- 如果不再需要觀察網(wǎng)絡(luò)請求,請移除
"webRequest"權(quán)限; - 將匹配模式從
"permissions"移至"host_permissions"。
需要根據(jù)使用場景添加其他權(quán)限。這些權(quán)限通過其支持的用例進(jìn)行描述。
2. 創(chuàng)建聲明性網(wǎng)絡(luò)請求規(guī)則
如需創(chuàng)建聲明性 net 請求規(guī)則,需要向 manifest.json 添加 "declarative_net_request" 對象。"declarative_net_request" 代碼塊包含指向規(guī)則文件的 "rule_resource" 對象數(shù)組。規(guī)則文件包含一組對象,用于指定操作以及調(diào)用這些操作的條件。
3. 常見使用場景
3.1. 屏蔽單個網(wǎng)址
Manifest V2 中的一個常見用例是在后臺腳本中使用 onBeforeRequest 事件來屏蔽網(wǎng)絡(luò)請求。
3.1.1. Background 腳本改為 V3 規(guī)則文件
3.1.1.1. 規(guī)則文件
rule.json
[
{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "||example.com",
"resourceTypes": ["main_frame"]
}
}
]
- 示例
[圖片上傳失敗...(image-67c199-1705057590208)]
-
Manifest.json文件引入
{
"name": "URL Blocker",
"version": "0.1",
"manifest_version": 3,
"description": "Uses the chrome.declarativeNetRequest API to block requests.",
"background": {
"service_worker": "service_worker.js"
},
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "rules_1.json"
}
]
},
"permissions": ["declarativeNetRequest", "declarativeNetRequestFeedback"]
}
3.1.1.2. V2 版本
chrome.webRequest.onBeforeRequest.addListener((e) => {
return { cancel: true };
}, { urls: ["https://www.example.com/*"] }, ["blocking"]);
3.1.1.3. V3 版本
對于 Manifest V3,請使用 "block" 操作類型創(chuàng)建新的 declarativeNetRequest 規(guī)則。請注意示例規(guī)則中的 "condition" 對象。其 "urlFilter" 取代了傳遞給 webRequest 監(jiān)聽器的 urls 選項。"resourceTypes" 數(shù)組指定要屏蔽的資源的類別。
[
{
"id" : 1,
"priority": 1,
"action" : { "type" : "block" },
"condition" : {
"urlFilter" : "||example.com",
"resourceTypes" : ["main_frame"]
}
}
]
3.1.2. 需要更新該擴展程序的權(quán)限。
在 manifest.json 中,將 "webRequestBlocking" 權(quán)限替換為 "declarativeNetRequest" 權(quán)限。請注意,由于屏蔽內(nèi)容不需要主機權(quán)限,因此該網(wǎng)址已從 "permissions" 字段中移除。
3.1.2.1. V2 版本
"permissions": [
"webRequestBlocking",
"https://*.example.com/*"
]
3.1.2.2. V3 版本
"permissions": [
"declarativeNetRequest",
]
3.2. 重定向多個網(wǎng)址
Manifest V2 中的另一個常見用例是使用 BeforeRequest 事件重定向網(wǎng)絡(luò)請求。
3.2.1. Background 腳本改為 V3 規(guī)則文件
3.2.1.1. V2 版本
chrome.webRequest.onBeforeRequest.addListener((e) => {
console.log(e);
return { redirectUrl: "https://developer.chrome.com/docs/extensions/mv3/intro/" };
}, {
urls: [
"https://developer.chrome.com/docs/extensions/mv2/"
]
},
["blocking"]
);
3.2.1.2. V3 版本
對于 Manifest V3,請使用 "redirect" 操作類型。與之前一樣,"urlFilter" 會替換傳遞給 webRequest 監(jiān)聽器的 url 選項。請注意,在此示例中,規(guī)則文件的 "action" 對象包含一個 "redirect" 字段,其中包含要返回的網(wǎng)址,而不是要過濾的網(wǎng)址。
[
{
"id" : 1,
"priority": 1,
"action": {
"type": "redirect",
"redirect": { "url": "https://developer.chrome.com/docs/extensions/mv3/intro/" }
},
"condition": {
"urlFilter": "https://developer.chrome.com/docs/extensions/mv2/",
"resourceTypes": ["main_frame"]
}
}
3.2.2. 需要更改擴展程序的權(quán)限
將 "webRequestBlocking" 權(quán)限替換為 "declarativeNetRequest" 權(quán)限。系統(tǒng)再次將這些網(wǎng)址從 manifest.json 移到了規(guī)則文件中。請注意,除了主機權(quán)限之外,重定向還需要 "declarativeNetRequestWithHostAccess" 權(quán)限。
3.2.2.1. V2 版本
"permissions": [
"webRequestBlocking",
"https://developer.chrome.com/docs/extensions/*",
"https://developer.chrome.com/docs/extensions/reference"
]
3.2.2.2. V3 版本
"permissions": [
"declarativeNetRequestWithHostAccess"
],
"host_permissions": [
"https://developer.chrome.com/*"
]
3.3. 屏蔽 Cookie
在 Manifest V2 中,要屏蔽 Cookie,需要先攔截網(wǎng)絡(luò)請求標(biāo)頭,然后再發(fā)送這些標(biāo)頭并移除特定的 Cookie。
3.3.1. Background 腳本改為 V3 規(guī)則文件
3.3.1.1. V2 版本
chrome.webRequest.onBeforeSendHeaders.addListener(
function(details) {
removeHeader(details.requestHeaders, 'cookie');
return {requestHeaders: details.requestHeaders};
},
// filters
{urls: ['https://*/*', 'http://*/*']},
// extraInfoSpec
['blocking', 'requestHeaders', 'extraHeaders']);
3.3.1.2. V3 版本
Manifest V3 也通過規(guī)則文件中的規(guī)則實現(xiàn)這一點。這次的操作類型為 "modifyHeaders"。該文件接受 "requestHeaders" 對象數(shù)組,用于指定要修改的標(biāo)頭以及如何修改這些標(biāo)頭。請注意,"condition" 對象僅包含 "resourceTypes" 數(shù)組。它支持的值與前面的示例相同。
[
{
"id": 1,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{ "header": "cookie", "operation": "remove" }
]
},
"condition": {
"urlFilter": "|*?no-cookies=1",
"resourceTypes": ["main_frame"]
}
}
]
3.3.2. 需要更新該擴展程序的權(quán)限。
將 "webRequestBlocking" 權(quán)限替換為 "declarativeNetRequest" 權(quán)限。
3.3.2.1. V2 版本
"permissions": [
"webRequestBlocking",
"https://developer.chrome.com/docs/extensions/*",
"https://developer.chrome.com/docs/extensions/reference"
]
3.3.2.2. V3 版本
"permissions": [
"declarativeNetRequestWithHostAccess"
],
"host_permissions": [
"https://developer.chrome.com/*"
]