我正在开发镀铬扩展并遇到了一个大问题。
我正在使用内容脚本在网站上注入我的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但没有运气:(
答案 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时只需一次)。