To understand why your code does not work, I include a fragment of my previous answer:
Content scripts do not have any access to a page's global window
object. For content scripts, the following applies:
- The
window
variable does not refer to the page's global object. Instead, it refers to a new context, a "layer" over the page. The page's DOM is fully accessible. #execution-environment
Given a document consisting of <iframe id="frameName" src="http://domain/"></iframe>
:
- Access to the contents of a frame is restricted by the Same origin policy of the page; the permissions of your extension does not relax the policy.
frames[0]
and frames['frameName']
, (normally referring to the the frame's containing global window
object) is undefined
.
var iframe = document.getElementById('frameName');
iframe.contentDocument
returns a document
object of the containing frame, because content scripts have access to the DOM of a page. This property is null
when the Same origin policy applies.
iframe.contentDocument.defaultView
(refers to the window
object associated with the document) is undefined.
iframe.contentWindow
is undefined.
Solution for same-origin frames
In your case, either of the following will work:
// 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);
Generic solution
The generic solution is using "all_frames": true
in the manifest file, and use something like this:
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);
}
This demo is for educational purposes only, do not use this demo in a real extension. Why? Because it uses postMessage
to pass messages around. These events can also be generated by the client, which causes a security leak (XSS: arbitrary HTML injection).
The alternative to postMessage
is Chrome's message API. For a demo, see this answer. You won't be able to compare the window
objects though. What you can do is to rely the window.name
property. The window.name
property is automatically set to the value of the iframe's name
attribute (just once, when the iframe is loaded).
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…