chrome.webRequest.onBeforeRequest 行为不可预测

chrome.webRequest.onBeforeRequest acting unpredictably

我正在尝试制作一个网络过滤 chrome 扩展程序,它将阻止某些网站并将其 html 替换为扩展程序中包含的阻止页面。

let bans = ["*://*.amazon.com/*", "*://*.youtube.com/*", "*://*.netflix.com/*","*://*.facebook.com/*", "*://*.twitter.com/*"];

chrome.webRequest.onBeforeRequest.addListener(
    function(details){
        try{
            chrome.tabs.executeScript(null, {file: "content.js"});
        }
        catch(error){
            console.error(error);
        }
        return {cancel: true};
    },
    {urls: bans},
    ["blocking"]
);

这应该意味着如果我尝试访问该禁止列表中的任何网站,内容脚本应该用我自己的阻止页面替换该页面。然而,出于某种原因,有些网站从未加载阻止页面,其他网站根本没有被阻止,有些网站似乎运行良好。更奇怪的是,听众似乎在根本未列在禁令数组中的网站上被触发。我无法找出这些行为之间的任何类型。

我不认为它们是问题的根源,但这是我的清单(清单 v2)中的权限

"web_accessible_resources": [
  "certBlockPage.html",
  "blockPageLight.html"
],

"incognito": "split",

"permissions": [
  "webNavigation",
  "webRequest",
  "webRequestBlocking",
  "tabs",
  "windows",
  "identity",
  "http://*/*",
  "https://*/*",
  "<all_urls>"
  ]

这是 content.js 文件

window.onload = function(){
fetch(chrome.runtime.getURL('certBlockPage.html'))
    .then(r => r.text())
    .then(html => {
        document.open();
        document.write(html);
        document.close;
  });

}

有几个问题。

  1. 您没有在 executeScript 的选项中指定 runAt: 'document_start',因此它会等待,以防您在该选项卡中的旧页面仍在加载时导航到被禁止的站点。
  2. executeScript 是异步的,所以它可以很容易地 运行 在 load 事件已经在选项卡中触发之后,所以你的 window.onload 永远不会 运行。不要使用onload,直接运行代码。
  3. 您总是 运行 在当前聚焦的选项卡中执行脚本(术语是 活动)但该选项卡可能未聚焦(不活动).您需要使用 details.tabIddetails.frameId.
  4. 用户可能打开了一个新标签并键入了被阻止的 url 或单击了它的 link,这被您的 {cancel: true} 阻止了,但是 executeScript 将失败,因为新标签当前显示在此选项卡中的页面不能 运行 内容脚本。对于任何其他chrome://或chrome -Extension:// tab或当网络错误显示在选项卡中时。
  5. 如果您在不删除之前的注册的情况下再次调用 onBeforeRequest.addListener,两者都将处于活动状态。
  6. document.write 将在具有严格 CSP
  7. 的站点上失败

解决方案 1:webRequest + executeScript

后台脚本:

updateListener(['amazon.com', 'youtube.com', 'netflix.com']);

function updateListener(hosts) {
  chrome.webRequest.onBeforeRequest.removeListener(onBeforeRequest);
  chrome.webRequest.onBeforeRequest.addListener(
    onBeforeRequest, {
      types: ['main_frame', 'sub_frame'],
      urls: hosts.map(h => `*://*.${h}/*`),
    }, [
      'blocking',
    ]);
}

function onBeforeRequest(details) {
  const {tabId, frameId} = details;
  chrome.tabs.executeScript(tabId, {
    file: 'content.js',
    runAt: 'document_start',
    matchAboutBlank: true,
    frameId,
  }, () => chrome.runtime.lastError && redirectTab(tabId));
  // Cancel the navigation without showing that it was canceled
  return {redirectUrl: 'javascript:void 0'};
}

function redirectTab(tabId) {
  chrome.tabs.update(tabId, {url: 'certBlockPage.html'});
}

content.js:

fetch(chrome.runtime.getURL('certBlockPage.html'))
  .then(r => r.text())
  .then(html => {
    try {
      document.open();
      document.write(html);
      document.close();
    } catch (e) {
      location.href = chrome.runtime.getURL('certBlockPage.html');
    }
  });

解决方案 2:webRequest + 重定向

不需要内容脚本。

const bannedHosts = ['amazon.com', 'youtube.com', 'netflix.com'];

chrome.webRequest.onBeforeRequest.addListener(
  details => ({
    redirectUrl: chrome.runtime.getURL('certBlockPage.html'),
  }), {
    types: ['main_frame', 'sub_frame'],
    urls: bannedHosts.map(h => `*://*.${h}/*`),
  }, [
    'blocking',
  ]);

解决方案 3:declarativeNetRequest + 重定向

这是最快的方法,但仅限于一组非常简单的预定义操作。请参阅文档,不要忘记在 manifest.json 中将 "declarativeNetRequest" 添加到 "permissions",并将 "<all_urls>" 添加到 host_permissions(ManifestV3 仍然不支持可选权限) .

const bannedHosts = ['amazon.com', 'youtube.com', 'netflix.com'];

chrome.declarativeNetRequest.updateDynamicRules({
  removeRuleIds: bannedHosts.map((h, i) => i + 1),
  addRules: bannedHosts.map((h, i) => ({
    id: i + 1,
    action: {type: 'redirect', redirect: {extensionPath: '/certBlockPage.html'}},
    condition: {urlFilter: `||${h}/`, resourceTypes: ['main_frame', 'sub_frame']},
  })),
});