【问题标题】:Can the window object be modified from a Chrome extension? [duplicate]可以从 Chrome 扩展程序修改窗口对象吗?
【发布时间】:2012-09-05 22:03:10
【问题描述】:

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

我能够通过使用Content Script 成功地将 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 对象?

【问题讨论】:

  • 那是不可能的。您是否正在查看正确的控制台,脚本是否真正执行(检查文档并查找脚本标签/检查语法错误)?该脚本当然是在页面上下文中运行的,因此,mything 必须存在。

标签: javascript google-chrome-extension


【解决方案1】:

你不能,不能直接。来自content scripts documentation

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

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

(强调)

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

但是,您可以通过 DOM 传递消息,方法是使用 window.postMessage 方法。您的页面和内容脚本都会监听 message 事件,并且每当您从其中一个地方调用 window.postMessage 时,另一个地方都会收到它。 “内容脚本”文档页面上有一个example of this

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

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

【讨论】:

  • 谢谢,我没有逐字使用您的建议,但这个概念帮助我解决了真正的问题。
  • 你也可以使用 eval("window.foo = {bar:function(){/*whatever*/}};");
  • <script> 元素破解工作完美。我很好奇,即使页面已经加载,这些 Javascript 标记是否会在添加到 <head> 后立即执行?
  • 如果是内联脚本,即<script>//do something</script>,那么是的,它会立即(同步)执行。如果是远程脚本<script src="foo.js"></script>,那么它将在脚本下载后执行。在添加 async 标记之前,这将是防止脚本下载阻塞页面呈现的常用方法。
  • 您可以修改window 对象,使用它自己的修改器,如addEventListener。无需文档元素注入。
【解决方案2】:

经过数小时尝试不同的尝试并面临 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 上,由于 Content-Security-Policy 错误,上述解决方案不起作用。但以下解决方法目前正在奏效,至少目前是这样:

  1. 将 2 个脚本添加到 content_scripts,例如inject.jsscript.js
  2. inject 脚本将获取script.js 文件的完整绝对网址并加载它:
;(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 文件的完整绝对网址并加载它:
;(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

【讨论】:

  • 我在 Firefox 中使用 cloneInto 命令进行管理:developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/… 遗憾的是这在 Chrome 中不起作用...
  • 感谢您的解决方案,这安全吗?还是有其他安全的方法?
  • 这是失败的Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-2hqy/01Xj3kkdA60wVjY8N6m2WXv5URv1rhk3jCDyHY='), or a nonce ('nonce-...') is required to enable inline execution.
【解决方案3】:

正如其他人指出的那样,上下文脚本不会在与页面相同的上下文中运行,因此,要访问正确的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 标记,将其附加到文档后,您注入的代码将按预期在页面上下文中运行。

【讨论】:

    【解决方案4】:

    chrome 扩展的content_script 在它自己的上下文中运行,该上下文与窗口是分开的。您可以将脚本注入到页面中,以便它在与页面窗口相同的上下文中运行,如下所示:Chrome extension - retrieving global variable from webpage

    我能够调用窗口对象上的方法并通过在页面的 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 中监听它们(如上面的链接所示)。

    请务必将注入的脚本添加到扩展文件中,并将脚本文件的路径添加到您的manifest within "web_accessible_resources",否则您会收到错误消息。

    希望对某人有所帮助\ (•◡•) /

    【讨论】:

      【解决方案5】:

      我一直在玩这个。我发现我可以通过将我的 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”

      【讨论】:

        【解决方案6】:

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

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

        效果很好,没有问题。

        【讨论】:

          【解决方案7】:

          内容脚本可以调用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!';
          });
          

          【讨论】:

          • 这似乎只适用于现有变量而不适用于新变量,有人可以验证吗?
          • 此脚本中引用的此窗口对象是内容脚本的一部分,而不是嵌入页面,因此不是问题的正确解决方案
          • 扩展和页面使用不同的上下文,这个答案具有误导性。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多