可以从Chrome扩展修改窗口对象吗

Can the window object be modified from a Chrome extension?

本文关键字:窗口 对象 修改 扩展 Chrome      更新时间:2023-09-26

我想做一个Chrome扩展,在window中提供一个新对象。当在加载了扩展的浏览器中查看网页时,我希望window.mything可以通过Javascript使用。window.mything对象将有一些我将在扩展中定义的函数,当在启用了扩展的浏览器中查看页面时,这些函数应该可以从console.log或任何Javascript文件中调用。

我能够通过使用内容脚本成功地将Javascript文件注入页面:

var s = document.createElement("script"); 
s.src = chrome.extension.getURL("mything.js");
document.getElementsByTagName("head")[0].appendChild(s);

mything.js看起来像这样:

window.mything = {thing: true};
console.log(window);

每当加载页面时,我都会在控制台中看到整个window对象。但是,我无法从控制台与window.mything对象进行交互。似乎注入的脚本并没有真正修改全局window对象。

如何从Chrome扩展修改全局window对象?

你不能,不能直接。来自内容脚本文档:

但是,内容脚本有一些局限性。他们不能:

  • 使用chrome.*API(chrome.extension的部分除外)
  • 使用由其扩展页面定义的变量或函数
  • 使用网页或其他内容脚本定义的变量或函数

(增加重点)

内容脚本看到的window对象与页面看到的window对象不同。

但是,您可以使用window.postMessage方法通过DOM传递消息。页面和内容脚本都会侦听消息事件,无论何时从其中一个位置调用window.postMessage,另一个位置都会收到它。"内容脚本"文档页面上有一个示例。

编辑:您可以通过从内容脚本中注入脚本来向页面添加一些方法。如果不使用postMessage之类的东西,它仍然无法与扩展的其余部分进行通信,但您至少可以在页面的window 中添加一些内容

var elt = document.createElement("script");
elt.innerHTML = "window.foo = {bar:function(){/*whatever*/}};"
document.head.appendChild(elt);

在尝试了几个小时的不同尝试并面临CORS等安全问题后,我找到了在ChromeFirefoxSafari上编辑window对象的方法。你需要为每种策略使用不同的策略:

  1. 将您的脚本添加到content_scripts
  2. 在脚本文件中,将script附加到页面上,并使其内联运行您的自定义代码。像这样:
;(function() {
  function script() {
    // your main code here
    window.foo = 'bar'
  }
  function inject(fn) {
    const script = document.createElement('script')
    script.text = `(${fn.toString()})();`
    document.documentElement.appendChild(script)
  }
  inject(script)
})()

Firefox

在Firefox上,由于Content-Security-Policy错误,上述解决方案不起作用。但以下变通方法目前正在发挥作用,至少目前是这样:

  1. content_scripts添加2个脚本,例如inject.jsscript.js
  2. inject脚本将获得script.js文件的完整绝对url并加载它:
;(function() {
  const b = typeof browser !== 'undefined' ? browser : chrome
  const script = document.createElement('script')
  script.src = b.runtime.getURL('script.js')
  document.documentElement.appendChild(script)
})()
  1. 您的script.js将包含您的主代码:
;(function() {
  // your main code here
  window.foo = 'bar'
})()

Safari

它与Firefox非常相似。

  1. 创建2个javascript文件,例如inject.jsscript.js
  2. inject脚本将获得script.js文件的完整绝对url并加载它:
;(function() {
  const script = document.createElement('script')
  script.src = safari.extension.baseURI + 'script.js'
  document.documentElement.appendChild(script)
})()
  1. 您的script.js将包含您的主代码:
;(function() {
  // your main code here
  window.foo = 'bar'
})()

源代码

请在此处查看完整代码:https://github.com/brunolemos/simplified-twitter

正如其他人所指出的,上下文脚本与页面的上下文不在同一上下文中运行,因此,要访问正确的window,需要向页面中注入代码。

以下是我的看法:

function codeToInject() {
    // Do here whatever your script requires. For example:
    window.foo = "bar";
}
function embed(fn) {
    const script = document.createElement("script");
    script.text = `(${fn.toString()})();`;
    document.documentElement.appendChild(script);
}
embed(codeToInject);

清洁且易于使用。无论你需要在页面的上下文中运行什么,都可以把它放在codeToInject()中(你可以随意称呼它)。embed()函数负责打包函数并将其发送到页面中运行。

embed()函数的作用是在页面中创建一个script标记,并将函数codeToInject()作为IIFE嵌入其中。一旦新的script标记被附加到文档中,浏览器就会立即执行它,并且您注入的代码将按预期在页面的上下文中运行。

chrome扩展的content_script在其独立于窗口的上下文中运行。不过,您可以将脚本注入页面,使其在与页面窗口相同的上下文中运行,如下所示:Chrome扩展-从网页检索全局变量

我能够调用窗口对象上的方法,并通过在页面的DOM:中添加script.js来修改窗口属性

var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
(document.head||document.documentElement).appendChild(s);
s.onload = function() {
    s.remove();
};

并在注入的脚本文件中创建自定义事件侦听器:

document.addEventListener('_my_custom_event', function(e) {
  // do whatever you'd like! Like access the window obj
  window.myData = e.detail.my_event_data;
})

并在content_script:中调度该事件

var foo = 'bar'
document.dispatchEvent(new CustomEvent('_save_OG_Editor', {
  'detail': {
    'my_event_data': foo
  }
}))

反之亦然;在script.js中调度事件,并在扩展的content_script中侦听它们(如上面的链接所示)。

只要确保将注入的脚本添加到扩展的文件中,并将脚本文件的路径添加到"web_accessible_resources"中的清单中,否则就会出现错误。

希望能帮助别人◡•)/

我一直在玩这个。我发现我可以通过将javascript包装到window.location=调用中来与浏览器的window对象进行交互。

var myInjectedJs = "window.foo='This exists in the ''real'' window object';"
window.location = "javascript:" + myInjectedJs;
var myInjectedJs2 = "window.bar='So does this.';"
window.location = "javascript:" + myInjectedJs2;

它有效,但仅适用于正在设置的window.location的最后一个实例。如果您访问文档的窗口对象,它将有一个变量"bar",而不是"foo"

感谢这里的其他答案,这就是我使用的:

((source)=>{
  const script = document.createElement("script");
  script.text = `(${source.toString()})();`;
  document.documentElement.appendChild(script);
})(function (){
  // Your code here
  // ...
})

效果很好,没有问题。

内容脚本可以调用window方法,然后可以使用这些方法来更改window对象。这比<script>标签注入更容易,并且即使在<head><body>尚未被解析时(例如,当使用run_at: document_start时)也能工作。

// In Content Script
window.addEventListener('load', loadEvent => {
    let window = loadEvent.currentTarget;
    window.document.title='You changed me!';
});