16

我正在开发一个 chrome 扩展并遇到了一个大问题。

我正在使用内容脚本在网站上注入我的 javascript 代码。该网站有一个 iframe。我可以更改 iframe 的源代码,但似乎无法访问 iframe 的 contentWindow 属性。我需要它在当前的 carret 位置插入文本。

所以基本上这段代码在页面的上下文中完美地工作:

$("#iframe1").contentWindow.document.execCommand("InsertHTML", false, 'test text');

但是当我尝试在我的 chrome 扩展的上下文中运行它时,我得到了这个错误:

TypeError: Cannot read property 'document' of undefined

奇怪的是我可以访问iframe的html。因此,这段代码可以在 chrome 扩展中完美运行:

$("#iframe1").contents().find('div').html('test')

我尝试将 "all_frames": true 放在清单文件中,但没有运气:(

4

1 回答 1

30

为了理解您的代码为什么不起作用,我附上了我之前回答的片段

内容脚本无权访问页面的全局window对象。对于内容脚本,以下适用:

  • window变量不引用页面的全局对象。相反,它指的是一个新的上下文,一个页面上的“层”。页面的 DOM 是完全可访问的。#执行环境

给定一个包含以下内容的文档   <iframe id="frameName" src="http://domain/"></iframe>

  • 对框架内容的访问受到页面同源策略的限制;您的扩展程序的权限不会放松政策。
  • frames[0]frames['frameName'],(通常指帧的包含全局window对象)是undefined.
  • var iframe = document.getElementById('frameName');
    • iframe.contentDocument返回document包含框架的对象,因为内容脚本可以访问页面的 DOM。此属性null适用于同源策略。
    • iframe.contentDocument.defaultView(指window与文档关联的对象)未定义
    • iframe.contentWindow未定义的。

同源帧的解决方案

在您的情况下,以下任一方法都可以:

// jQuery:
$("#iframe1").contents()[0].execCommand( ... );

// VanillaJS
document.getElementById("iframe1").contentDocument.execCommand( ... );

// "Unlock" contentWindow property by injecting code in context of page
var s = document.createElement('script');
s.textContent = 'document.getElementById("iframe1").contentWindow.document.execCommand( ... );';
document.head.appendChild(s);

通用解决方案

通用解决方案"all_frames": true在清单文件中使用,并使用如下内容:

if (window != top) {
    parent.postMessage({fromExtension:true}, '*');
    addEventListener('message', function(event) {
        if (event.data && event.data.inserHTML) {
            document.execCommand('insertHTML', false, event.data.insertHTML);
        }
    });
} else {
    var test_html = 'test string';
    // Explanation of injection at https://stackoverflow.com/a/9517879/938089 :
    // Run code in the context of the page, so that the `contentWindow`
    //  property becomes accessible
    var script = document.createElement('script');
    script.textContent = '(' + function(s_html) {
        addEventListener('message', function(event) {
            if (event.data.fromExtension === true) {
                var iframe = document.getElementById('iframe1');
                if (iframe && (iframe.contentWindow === event.source)) {
                    // Window recognised, post message back
                    iframe.contentWindow.postMessage({insertHTML: s_html}, '*');
                }
            }
        });
    } + ')(' + JSON.stringify(test_html) + ');';
    (document.head||document.documentElement).appendChild(script);
    script.parentNode.removeChild(script);
}

此演示仅用于教育目的,请勿在实际扩展中使用此演示。为什么?因为它postMessage用来传递消息。这些事件也可以由客户端生成,从而导致安全漏洞(XSS:任意 HTML 注入)。

替代方案postMessage是 Chrome 的消息 API。有关演示,请参阅此答案。但是,您将无法比较这些window对象。你能做的就是依靠window.name财产。该window.name属性会自动设置为 iframename属性的值(仅在加载 iframe 时设置一次)。

于 2012-11-07T14:24:46.467 回答