動態

詳情 返回 返回

Chrome 瀏覽器插件從 Manifest V2 升級到 V3 版本所需要修改的點 - 動態 詳情

本文參與了SegmentFault 思否 2023 年度有獎徵文活動,歡迎正在閲讀的你也加入。

一、Manifest V2 支持時間表

Chrome 瀏覽器官方已經給出確定的時間來棄用 V2 版本的插件了。

最早從 2024 年 6 月Chrome 127 開始,我們將開始停用 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. 更新 permissionshost_permissions 字段

Manifest V3 中的主機權限是一個單獨的字段;

不需要在 permissionsoptional_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 替換 backgroundevent pages,以確保後台代碼遠離主線程,這樣可以讓擴展程序僅在需要時運行,從而節省資源。

1. BackgroundService Worker 之間的區別

1.1. Service Workerbackground 之間的差異
  • 在主線程以外運行,這意味着不會干擾擴展程序內容;
  • 具有特殊功能,例如攔截擴展程序來源上的提取事件,例如攔截工具欄彈出式窗口中的提取事件;
  • 可以通過客户端界面與其他上下文進行通信和交互。
1.2. 改動點
  • 由於它們無法訪問 DOMwindow 接口,因此需要將此類調用移至其他 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. 將 DOMwindow 調用移至屏幕外文檔

某些擴展程序需要訪問 DOMwindow 對象,無需打開新的窗口或標籤頁。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. 同步註冊監聽器

異步註冊監聽器(例如在 promisecallback 中)註冊並不一定能在 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 ActionsPage Actions 替換為 Actions

Manifest V2 中,Browser ActionsPage 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 APIpageAction API 替換為 Action API

如果 Manifest V2 使用 browserActionpageAction 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.onSuspendbackground 腳本) 在擴展 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. 規則文件
  1. rule.json

    [
      {
     "id": 1,
     "priority": 1,
     "action": { "type": "block" },
     "condition": {
       "urlFilter": "||example.com",
       "resourceTypes": ["main_frame"]
     }
      }
    ]
  2. 示例

image.png

  1. 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】
user avatar linlinma 頭像 tizuqiudehongcha 頭像 cynthia_59675eba1a2ee 頭像 icecreamlj 頭像 tongbo 頭像 zengh 頭像 shine_zhu 頭像 judei 頭像 neronero 頭像 dragonir 頭像 hui_61e3b3803b922 頭像 tingzhu_guo 頭像
點贊 12 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.