本文參與了SegmentFault 思否 2023 年度有獎徵文活動,歡迎正在閲讀的你也加入。
一、Manifest V2 支持時間表
Chrome 瀏覽器官方已經給出確定的時間來棄用 V2 版本的插件了。
最早從 2024 年 6 月的
Chrome127 開始,我們將開始停用 Chrome 的不穩定版本(開發者版、Canary版和Beta版)中的Manifest V2擴展程序。受此變化影響的用户會在瀏覽器中看到Manifest V2擴展程序自動停用,並且無法再從Chrome應用商店安裝Manifest V2擴展程序。此外,Manifest V2擴展程序在Chrome應用商店中將不再擁有“精選”徽章(如果目前已有該徽章)。如果企業如果使用
ExtensionManifestV2Availability政策確保其組織中的Manifest V2擴展程序能持續正常運行,則其組織中還有一年的時間(即在 2025 年 6 月之前)遷移Manifest V2擴展程序。在此之前,已啓用此政策的瀏覽器不會受到棄用安排的影響。如果擴展程序發佈商目前仍在發佈
Manifest V2擴展程序,我們強烈建議您在 2024 年 6 月之前完成向Manifest V3的遷移。官方時間線
1、2022 年 6 月:Chrome 應用商店 - 不再有新的專用擴展程序
Chrome 應用商店不再接受公開範圍設為“不公開”的新 Manifest V2 擴展程序。
2、2024 年 6 月:在穩定發佈前棄用 Chrome MV2
3、2024 年 6 月 + 1-X 個月:棄用 Chrome MV2 並穩定發佈
4、2025 年 6 月:Chrome MV2 棄用(企業版)
二、Manifest V3 遷移核對列表
1、Manifest.json 文件
1. 更新 manifest_version 版本號
將 manifest_version 字段的值從 2 更改為 3。
{
"manifest_version": 3
}
2. 更新 permissions 和 host_permissions 字段
Manifest V3 中的主機權限是一個單獨的字段;
不需要在 permissions 或 optional_permissions 中指定這些權限;
有單獨的字段 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 會限制哪些網站和擴展程序可以訪問擴展程序中的資源,以此來限制數據公開範圍;
在 Manifest V2 中,默認情況下,指定資源可供所有網站訪問;
在下面的 Manifest V3 示例中,這些資源僅可供匹配的網站使用,而只有某些圖片可供所有網站使用。
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,以確保後台代碼遠離主線程,這樣可以讓擴展程序僅在需要時運行,從而節省資源。
1. Background 和 Service Worker 之間的區別
1.1. Service Worker 和 background 之間的差異
- 在主線程以外運行,這意味着不會干擾擴展程序內容;
- 具有特殊功能,例如攔截擴展程序來源上的提取事件,例如攔截工具欄彈出式窗口中的提取事件;
- 可以通過客户端界面與其他上下文進行通信和交互。
1.2. 改動點
- 由於它們無法訪問
DOM或window接口,因此需要將此類調用移至其他API或移至屏幕外文檔中; - 不應註冊事件監聽器來響應返回的
promise或在事件回調內部; - 不向後兼容
XMLHttpRequest(),因此需要將接口的調用替換為fetch(); - 由於它們在不使用時終止,因此需要保留應用狀態,而不是依賴於全局變量;
- 終止
Service Worker還可以在計時器完成之前結束計時器。需要將其替換為alarms。
2. 更新 manifest.json 中的 background 字段
在 Manifest V3 中,background 頁面被 Service Worker 所取代:
- 將
manifest.json中的"background.scripts"替換為"background.service_worker"; "service_worker"字段接受字符串,而不是字符串數組;- 從
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 關鍵字)時,才需要 "type" 字段。其值將始終為 "module"。
3. 將 DOM 和 window 調用移至屏幕外文檔
某些擴展程序需要訪問 DOM 和 window 對象,無需打開新的窗口或標籤頁。Offscreen API 支持這類使用情形,因為這類 API 可以打開和關閉與擴展程序打包在一起的未顯示文檔,而不會干擾用户體驗。除了消息傳遞之外,屏幕外文檔不會與其他擴展程序上下文共享 API,而是起到完整網頁的作用,供擴展程序進行互動。
如需使用 Offscreen API,請通過 Service Worker 創建屏幕外文檔。
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
4. 將 localStorage 轉換為其他類型
Web 平台的 Storage 接口(可從 window.localStorage 訪問)無法在 Service Worker 中使用。
請使用 chrome.storage.local
5. 同步註冊監聽器
異步註冊監聽器(例如在 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 中,系統會在分派事件時重新初始化 Service Worker。這意味着當事件觸發時,系統不會註冊監聽器(因為它們是異步添加的),系統還會錯過事件。
5.2. V3 版本
改為將事件監聽器註冊移至腳本的頂層。這樣可以確保 Chrome 能夠立即找到並調用操作的點擊處理程序,即使擴展程序尚未執行其啓動邏輯也是如此。
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
6. 將 XMLHttpRequest() 替換為全局 fetch()
無法從 Service Worker、擴展程序或其他方法調用 XMLHttpRequest()。將後台腳本對 XMLHttpRequest() 的調用替換為對fetch() 的調用。
const response = await fetch('https://www.example.com/greeting.json');
console.log(response.statusText);
7. 保存狀態
Service Worker 是臨時的,這意味着它們可能會在用户的瀏覽器會話期間反覆啓動、運行和終止。這也意味着,自之前的上下文銷燬後,數據並非立即在全局變量中可用。如需解決此問題,請使用存儲 API。
對於 Manifest V3 來説,要將全局變量替換為對 Storage API 的調用。
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 開發中比較常見。不過,在 Service Worker 中可能會失敗,因為每當 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 調用
1. 將 tab.executeScript() 替換為 scripting.executeScript()
在 Manifest V3 中,executeScript() 從 tabs API 移至 scripting API。這就需要在實際的代碼更改的基礎上更改 manifest.json 文件中的權限。
對於 executeScript() 方法:
"scripting"權限;host permissions權限或"activeTab"權限。
scripting.executeScript() 方法與其使用 tabs.executeScript() 的方式類似。但還是有一些區別。
- 舊方法只能接受一個文件,而新方法可以接受一組文件;
- 還將傳遞
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 文件中的權限進行更改:
"scripting"權限;host permissions權限或"activeTab"權限。
scripting API 上的函數與 tabs 上的函數類似。但還是有一些區別。
- 調用這些方法時,需要傳遞
CSSInjection對象,而不是InjectDetails; tabId作為CSSInjection.target的成員(而不是方法參數)進行傳遞。
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()。如果仍需要頁面操作,可以使用聲明性內容模擬這些操作,也可以使用標籤頁 ID 調用 enable() 或 disable()。
3.1. 將 "browser_action" 和 "page_action" 替換為 "action"
在 manifest.json 中,將 "browser_action" 和 "page_action" 字段替換為 "action" 字段。如需瞭解 "action" 字段的相關信息,請參閲參考文檔。
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,現在應使用 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。
為了實現向後兼容性,許多方法在添加 promise 支持後會繼續支持回調。需要注意的是,不能在同一函數調用中同時使用這兩者。如果傳遞迴調,則函數不會返回 promise;如果希望返回 promise,則也不要傳遞迴調。某些 API 功能(例如事件監聽器)將繼續需要回調。
如需從回調轉換為 promise,請移除回調並處理返回的 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 上下文的函數
其他擴展程序上下文只能使用消息傳遞與擴展程序 Service Worker 交互。因此,需要替換需要後台上下文的調用,具體而言:
chrome.runtime.getBackgroundPage()chrome.extension.getBackgroundPage()chrome.extension.getExtensionTabs()
擴展程序腳本應使用消息傳遞在 Service Worker 和擴展程序的其他部分之間進行通信。目前,這需要使用 sendMessage(),並在擴展 Service Worker 中實現 chrome.runtime.onMessage。從長遠來看,應計劃將這些調用替換為 postMessage() 和 Service Worker 的消息事件處理程序。
6. 替換不受支持的 API
需要在 Manifest V3 中更改下列方法和屬性。
Manifest V2 方法或屬性 |
替換為 Manifest V3 方法或屬性 |
|---|---|
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 請求監聽器
Manifest V3 更改了擴展程序處理網絡請求修改的方式。擴展程序會指定規則來描述在滿足一組給定條件時要執行的操作,而不是攔截網絡請求並在運行時使用 chrome.webRequest 更改請求。
Web Request API 和聲明式網絡請求 API 有很大的區別。需要根據用例重新編寫代碼,而不是將一個函數調用替換為另一個函數調用。
1. 更新 permissions
對 manifest.json 中的 "permissions" 字段進行以下更改。
- 如果不再需要觀察網絡請求,請移除
"webRequest"權限; - 將匹配模式從
"permissions"移至"host_permissions"。
需要根據使用場景添加其他權限。這些權限通過其支持的用例進行描述。
2. 創建聲明性網絡請求規則
如需創建聲明性 net 請求規則,需要向 manifest.json 添加 "declarative_net_request" 對象。"declarative_net_request" 代碼塊包含指向規則文件的 "rule_resource" 對象數組。規則文件包含一組對象,用於指定操作以及調用這些操作的條件。
3. 常見使用場景
3.1. 屏蔽單個網址
Manifest V2 中的一個常見用例是在後台腳本中使用 onBeforeRequest 事件來屏蔽網絡請求。
3.1.1. Background 腳本改為 V3 規則文件
3.1.1.1. 規則文件
-
rule.json[ { "id": 1, "priority": 1, "action": { "type": "block" }, "condition": { "urlFilter": "||example.com", "resourceTypes": ["main_frame"] } } ] - 示例
-
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" 操作類型創建新的 declarativeNetRequest 規則。請注意示例規則中的 "condition" 對象。其 "urlFilter" 取代了傳遞給 webRequest 監聽器的 urls 選項。"resourceTypes" 數組指定要屏蔽的資源的類別。
[
{
"id" : 1,
"priority": 1,
"action" : { "type" : "block" },
"condition" : {
"urlFilter" : "||example.com",
"resourceTypes" : ["main_frame"]
}
}
]
3.1.2. 需要更新該擴展程序的權限。
在 manifest.json 中,將 "webRequestBlocking" 權限替換為 "declarativeNetRequest" 權限。請注意,由於屏蔽內容不需要主機權限,因此該網址已從 "permissions" 字段中移除。
3.1.2.1. V2 版本
"permissions": [
"webRequestBlocking",
"https://*.example.com/*"
]
3.1.2.2. V3 版本
"permissions": [
"declarativeNetRequest",
]
3.2. 重定向多個網址
Manifest V2 中的另一個常見用例是使用 BeforeRequest 事件重定向網絡請求。
3.2.1. Background 腳本改為 V3 規則文件
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 監聽器的 url 選項。請注意,在此示例中,規則文件的 "action" 對象包含一個 "redirect" 字段,其中包含要返回的網址,而不是要過濾的網址。
[
{
"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. 需要更改擴展程序的權限
將 "webRequestBlocking" 權限替換為 "declarativeNetRequest" 權限。系統再次將這些網址從 manifest.json 移到了規則文件中。請注意,除了主機權限之外,重定向還需要 "declarativeNetRequestWithHostAccess" 權限。
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,需要先攔截網絡請求標頭,然後再發送這些標頭並移除特定的 Cookie。
3.3.1. Background 腳本改為 V3 規則文件
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 也通過規則文件中的規則實現這一點。這次的操作類型為 "modifyHeaders"。該文件接受 "requestHeaders" 對象數組,用於指定要修改的標頭以及如何修改這些標頭。請注意,"condition" 對象僅包含 "resourceTypes" 數組。它支持的值與前面的示例相同。
[
{
"id": 1,
"priority": 1,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{ "header": "cookie", "operation": "remove" }
]
},
"condition": {
"urlFilter": "|*?no-cookies=1",
"resourceTypes": ["main_frame"]
}
}
]
3.3.2. 需要更新該擴展程序的權限。
將 "webRequestBlocking" 權限替換為 "declarativeNetRequest" 權限。
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/*"
]
引用
- 【mv2-deprecation-timeline】
- 【transition-to-mv3】
- 【Update the manifest】
- 【Migrate to a service worker】
- 【Update your code】
- 【Replace blocking web request listeners】