Access Iframe Content from a Chrome'S Extension Content Script

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 and Tab 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] 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).

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



Leave a reply



Submit