从chrome扩展程序访问iframe

时间:2012-11-07 08:55:36

标签: javascript iframe google-chrome-extension

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

我正在使用内容脚本在网站上注入我的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但没有运气:(

1 个答案:

答案 0 :(得分:19)

为了理解您的代码无效的原因,我添加了a fragment of my previous answer

  

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

     
      
  • window 变量不引用页面的全局对象。相反,它指的是新的上下文,页面上的“层”。页面的DOM完全可访问。 #execution-environment
  •   
     

鉴于由<iframe id="frameName" src="http://domain/"></iframe>组成的文件:

     
      
  • 对页面内容的访问受页面Same origin policy的限制;您的扩展程序的权限不会放松政策。
  •   
  • 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。有关演示,请参阅this answer。但是,您将无法比较window个对象。您可以做的是依靠window.name属性。 window.name属性会自动设置为iframe name属性的值(加载iframe时只需一次)。