Chrome扩展程序:在页面加载前注入JS

49

在页面加载之前是否可以注入JS,或者必须使用内容脚本,并等待文档完成?

例如,有没有更快的方法执行JS,当页面打开时立即将其变红?

2个回答

69

在清单文件中使用 "run_at": "document_start" 声明一个内容脚本,以使其尽可能快地运行,即在构建文档根节点后立即运行(当 <head> 不存在时)。

对于您特定的示例,最好声明一个内容样式,类似于内容脚本,但使用 "css" 键代替 "js"

如果您想要尽可能快地动态运行一个脚本,那么可以在触发 chrome.webNavigation.onCommitted 事件时调用 chrome.tabs.executeScript


1
@RobW,onComitted会在“至少部分文档已从服务器接收”时运行。那么我们如何获得此之前的时刻呢?就是用户点击地址栏并点击<Enter>键的那一刻[--例如当他重新加载页面或加载新页面时]。 - Pacerier
2
@RobW,文档另外也写到:“如果通过Chrome Instant或Instant Pages触发导航,则完全加载的页面将被交换到当前标签页中”。在这种情况下,是否仍然有可能在任何网页代码运行之前运行一个脚本来用自己的代理win对象替换网页的“window”对象? - Pacerier
2
顺便说一句,manifest文件中的document_start内容脚本将在chrome.webNavigation.onCommitted之前运行,但是它无法访问bg可用的chrome函数。对于bg来说,chrome.webNavigation.onCommitted仍然是最早的方法。 - Pacerier
1
使用executeScript API时,不能保证您的脚本能够及时运行。如果这很重要,请在manifest.json中声明一个脚本。 - Rob W
1
@RobW,顺便说一下,我在manifest js文档的开头尝试了这个脚本document.documentElement.appendChild(document.createElement('script')).innerHTML = 'window.open=null;document.createElement=null',它有效果。但是document.documentElement.appendChild(document.createElement('script')).innerHTML = 'document=null;window=null'不行。你知道如何覆盖windowdocument吗? - Pacerier
显示剩余10条评论

3
对于Manifest V3,102.0.5005.58 Stable版本的内容脚本中添加了一个新字段:world
与此相关的还有很多Chrome错误:#634381#1054624#1207006等。
您需要在内容脚本中使用"world": "main",并搭配"run_at": "document_start"和CSP,以允许扩展程序进行注入。否则,注入的脚本将被拒绝,显示如下错误:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'wasm-unsafe-eval'". Either the 'unsafe-inline' keyword, a hash ('sha256-*'), or a nonce ('nonce-...') is required to enable inline execution.

"world": "MAIN":

[Extensions] Add main world injections for dynamic content scripts

This CL adds a new field "world" for dynamic content scripts which allows the extension to specify if the script will run in the isolated or main world. By default, scripts which do not specify this field will run in the isolated world.

有效的属性值为"ISOLATED"(默认)或"MAIN"
我使用了以下示例文件:
manifest.json
{
  "name": "script injection",
  "version": "0",
  "manifest_version": 3,
  "minimum_chrome_version": "103.0",
  "content_scripts": [
    {
      "matches": ["*://*/*"],
      "js": ["inject.js"],
      "run_at": "document_start",
      "world": "MAIN"
    }
  ],
  "web_accessible_resources": [{
    "resources": ["injected.js"],
    "matches": ["<all_urls>"]
  }],
  "content_security_policy": {
    "extension_pages": "default-src 'self' 'wasm-unsafe-eval';"
  }
}

inject.js

let el = document.createElement("script");
el.src = chrome.runtime.getURL("injected.js");
document.documentElement.appendChild(el);
console.log("injected");

injected.js

console.log(typeof alert); // "function"
console.log("injected file");
delete window.alert;
console.log(typeof alert); // "undefined"

[enter image description here]


谢谢你的回答!我正在使用 "world": "MAIN",但如果页面被缓存,在简单刷新时我的脚本不会运行得足够早。我的代码几乎与你的完全相同,如果你的代码可以工作,我会继续研究我的代码,看看有什么区别。 - Brad
@Brad 我已经尝试了几个网站,甚至本地文件。脚本总是直接注入的。我在两台机器上进行了测试(Ryzon 9)和一台慢速笔记本电脑(Celeron N4020)。-重新加载并通过缓存的网站向后/向前导航也可以正常工作。alert始终不可用。-提到的两台机器目前都在chrome 109.0.5414.75稳定版上。 - Christopher
嗨,Christopher,感谢您的评论和测试。我仍然无法确保我的脚本首先运行。为了说明问题,这是我通常会放在injected.js中的代码:Object.defineProperty(navigator.mediaDevices,'getUserMedia',{ get:(...args)=>{ console.log('Intercepted!'); } }); 然后,我使用此URL进行测试:https://webrtc.github.io/samples/src/content/getusermedia/audio/。第一次加载时,它可以正常工作(我在控制台上看到“intercepted”)。如果我单击刷新,它就不起作用。如果我按Ctrl + Shift刷新,它就可以正常工作。如果我禁用缓存,它也可以正常工作。 - Brad
另外,我已经尝试设置 el.async = false;,但据我所见并没有任何区别。 - Brad
1
这里有一个更具体的错误,供寻找问题的人使用:https://bugs.chromium.org/p/chromium/issues/detail?id=634381 - Brad
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接