access iframe content from a chrome's extension content script
There's generally no direct way of accessing a different-origin window
object. If you want to securely communicate between content scripts in different frames, you have to send a message to the background page which in turn sends the message back to the tab.
Here is an example:
Part of manifest.json
:
"background": {"scripts":["bg.js"]},
"content_scripts": [
{"js": ["main.js"], "matches": ["<all_urls>"]},
{"js": ["sub.js"], "matches": ["<all_urls>"], "all_frames":true}
]
main.js
:
var isTop = true;
chrome.runtime.onMessage.addListener(function(details) {
alert('Message from frame: ' + details.data);
});
sub.js
:
if (!window.isTop) { // true or undefined
// do something...
var data = 'test';
// Send message to top frame, for example:
chrome.runtime.sendMessage({sendBack:true, data:data});
}
Background script 'bg.js':
chrome.runtime.onMessage.addListener(function(message, sender) {
if (message.sendBack) {
chrome.tabs.sendMessage(sender.tab.id, message.data);
}
});
An alternative method is to use chrome.tabs.executeScript
in bg.js
to trigger a function in the main content script.
Relevant documentation
- Message passing
c.runtime.sendMessage
/c.tabs.sendMessage
/c.runtime.onMessage
MessageSender
andTab
types.- Content scripts
chrome.tabs.executeScript
Accessing iframe from chrome extension
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]
andframes['frameName']
, (normally referring to the the frame's containing globalwindow
object) isundefined
.var iframe = document.getElementById('frameName');
iframe.contentDocument
returns adocument
object of the containing frame, because content scripts have access to the DOM of a page. This property isnull
when the Same origin policy applies.iframe.contentDocument.defaultView
(refers to thewindow
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).
Access DOM elements inside iframe from chrome extension
Brute force: inject a script in the iframe
If you are having trouble accessing the contents of an iframe, the brute force method to do so is to inject a content script directly into the iframe and access the iframe from that content script.
Inject into a single frame:
You can explicitly specify the frame into which your script is injected by providing the frame ID in your call to chrome.tabs.executeScript()
. This could be something like:
chrome.tabs.executeScript(tabOfInterestId,{
frameId: frameIdToInject,
file: scriptFileWhichDoesWhatIWantInTheIframe.js
},function(results){
//Handle any results
});
If you don't know the frame ID for the frame into which you desire to inject, you can obtain it from chrome.webNavigation.getAllFrames()
like:
chrome.webNavigation.getAllFrames({tabId:tabOfInterestId},function(frames){
//Do what you want with the array of frame descriptor Objects
});
Injecting into all frames:
If you want to inject into all frames in a tab, you can do what you show in the Question that you are already doing:
chrome.tabs.executeScript({
"file": "script.js",
"allFrames" : true
});
This will inject the script.js file into all frames within the active tab of the current window which exist at the time the tabs.executeScript()
call is executed. A different copy of script.js will be injected in each frame within the tab, including the base frame. This will not inject script.js in any iframes which are added to the tab after this call to tabs.executeScript()
is made. For iframes, the context in which the injected script exists will be valid (i.e. the script will exist) until the URL for the iframe changes (e.g. the src
attribute changes), or a parent frame (e.g. the base frame of the tab) changes URL (i.e. a new page is loaded in a parent frame).
How to access an iframe from chrome extension?
Continued from SO44122853
I see you figured out that the content was loaded in an iframe. So I have a working demo here PLUNKER, the snippet is here just in case the plunker goes down.
Details are commented in PLUNKER
Demo
Not functional due to the need to run 2 separate pages
<!DOCTYPE html><html>
<head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1, user-scalable=no"> <style></style></head>
<body> <!--iframe is same as the one on the site, with the exception of the src--> <iframe id="ptifrmtgtframe" name="TargetContent" title="Main Content" frameborder="0" scrolling="auto" width='90%' src="tables.html"></iframe> <!--The data is displayed as a one string--> <output id='display'></output> <script> // Reference the iframe var iFID = document.getElementById("ptifrmtgtframe"); // Register the load event on iframe iFID.onload = function(e) { // Callback is extractText function return extractText('#ptifrmtgtframe', '#display', '.PSLONGEDITBOX'); }
/* Pass iframe and display as a single selector || Pass targets as a multiple selector */ function extractText(iframe, display, targets) { var iArray = []; var iFrame = document.querySelector(iframe); var iView = document.querySelector(display); var iNode = ""; /* .contentWindow is property that refers to content || that is in an iframe. This is the heart of the || demo. */ var iContent = iFrame.contentDocument || iFrame.contentWindow.document; var iTarget = iContent.querySelectorAll(targets); /* .map() will call a function on each element || and return a new array as well. */ Array.from(iTarget).map(function(node, idx) {
iNode = node.textContent; iView.textContent += iNode; iArray.push(iNode); return iArray; }); console.log(iArray); } </script></body>
Accessing iframes from a Chrome content-script extension
I've resolved the problem. The following option has to be specified in the content_scripts section of the manifest.json: "all_frames": true
. Without it, the script is only applied to the top frame.
// Sometimes one just has to RTFM carefully.
Content script for iframe inside extension popup or background script
Use "all_frames": true
in your content script declaration to inject it into an iframe:
"content_scripts": [{
"matches": [ "http://example.com/*" ],
"js": [ "content.js" ],
"all_frames": true
}],
To differentiate this iframe from normal tabs you can add a dummy parameter to the URL when you create the iframe e.g. http://example.com/?foo
so you can match it in manifest.json like "http://example.com/*foo*"
for example.
Then you can use messaging: the content script initiates it, and the extension script registers a listener.
Trivial one-time sendMessage:
content.js:
chrome.runtime.sendMessage('test', response => {
console.log(response);
});popup.js (or background.js and so on):
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log('popup got', msg, 'from', sender);
sendResponse('response');
});Long-lived port:
content.js:
let port = chrome.runtime.connect({name: 'test'});
port.onMessage.addListener((msg, port) => {
console.log(msg);
});
port.postMessage('from-iframe');popup.js (or background.js and so on):
let iframePort; // in case you want to alter its behavior later in another function
chrome.runtime.onConnect.addListener(port => {
iframePort = port;
port.onMessage.addListener((msg, port) => {
console.log(msg);
});
port.postMessage('from-popup');
});
An example of popup.html is really straightforward:
<html>
<body>
<iframe width="500" height="500" src="http://example.com"></iframe>
<script src="popup.js"></script>
</body>
</html>
Of course you can also add the iframe(s) programmatically using DOM manipulation.
Related Topics
Why Media Queries Has Less Priority Than No Media Queries Css
Grid Areas Not Laying Out Properly in CSS Grid
Float Does Not Work in a Flex Container
Are Empty HTML5 Data Attributes Valid
Valid to Use <A> (Anchor Tag) Without Href Attribute
How to Vertically Center Text in a Dynamically Height Div
Why Are Button's Discouraged from Navigation
Remove Outline from Select Box in Ff
How to Submit a Post Form Using the <A Href="..."> Tag
Elongated Hexagon Shaped Button Using Only One Element
How to Specify a Local File Within HTML Using the File: Scheme
What Is It When a Link Has a Pound "#" Sign in It
How to Force a Page Break in HTML Printing