4

In the following HTML mockup, the nested iframes are originating from different subdomains. This is causing messages such as error: Permission denied to access property "document"

<html>
<head>
    <title></title>
</head>
<body>
    <div>
        <iframe id="outer_iframe_1" src="https://subdomain1.example.com"></iframe>
    </div>
    <div>
        <iframe id="outer_iframe_2" src="https://subdomain2.example.com">
        <div>
            <iframe id="inner_iframe_2" src="https://subdomain4.example.com"></iframe>
        </div>
        </iframe>
    </div>
    <div>
        <iframe id="outer_iframe_3" src="https://subdomain3.example.com"></iframe>
    </div>
</body>
</html>

I am intending to fetch and modify values within the nested iframes (eg. inner_frame_2) with a Userscript, so bypassing the Same-origin policy should be possible. But examples of GM_xmlhttpRequest seem to rely on GET/POST requests, whereas I only want to deal with the already loaded page data within these iframes.

Am I misunderstanding GM_xmlhttpRequest, or is there another approach I should be taking here?

David Metcalfe
  • 2,237
  • 1
  • 31
  • 44

2 Answers2

6

I think the only way you can do it, is by using the window.postMessage() method to send messages with data from iframes to the top window. To catch each iframe inside Greasemonkey script see Brock Adams answer on Apply a Greasemonkey userscript to an iframe?; you'll have to use the GM @match directive like this:

// @match        http://subdomain1.example.com/*

or

// @match        *.example.com/*

Then you can check if the current window is the top window, and/or check the document.domain to identify the iframe:

// ==UserScript==
// @name         New Userscript
// @match        http://main-domain.something
// @match        *.example.com/*
// ==/UserScript==

(function() {
    'use strict';

    if (window.top === window.self) {
        // Here we are at the top window and we setup our message event listener
    }
    else {
        // Here we get inside the iframes.
        // We can address and check each iframe url with document.domain
    }
})();

We need to hook an event for "message" to the top window that will handle each message it receives from the iframes with the data:

window.addEventListener("message", function(event) {
    // do something with the event.data
}, false);

And we can identify the iframes by using document.domain; do whatever manipulation we need to the iframe elements; retrieve all the data we want and send the message to the top window:

window.top.postMessage({
    // data object we send to the top window
}, "*");

I created a demo to try this and it works pretty well. My top window URL is http://zikro.gr/dbg/gm/iframes/main.php and the subdomains are like http://subdomain1.zikro.gr/. The top window HTML is identical to yours with my iframe urls and the GM script:

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://zikro.gr/dbg/gm/iframes/main.php
// @match        *.zikro.gr/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    if (window.top === window.self) {
        // Here we are at the top window and we setup our message event listener
        document.body.style.backgroundColor = "#f00"; // Just a UI change to identify the top window
        window.addEventListener("message", function(event) {
            window.console.log("This is data from '" + event.data.title +
                               "'; with message '" + event.data.message +
                               "'; with data '" + event.data.data +"'" +
                               "'; from domain '" + event.data.domain + "'");
        }, false);
    }
    else {
        // Here we get inside the iframes.
        // We can address and check each iframe url with document.domain

        document.body.style.backgroundColor = "#0f0"; // Just a UI change to identify the iframe window

        // We change something inside the iframe
        var dataDiv = document.getElementsByTagName('div')[0];
        dataDiv.innerHTML += " with a change!";

        // And we post a message to the top window with all the data we want inside an object
        window.top.postMessage({
            title: document.title,
            domain: document.domain,
            message: "Hello from, iframe - " + document.title,
            data: dataDiv.innerText
        }, "*");
    }

})();

And a screen capture for those who don't have Greasemonkey/Tampermoney installed to test this:

Script in action

PS: It's not valid to add elements directly inside an iframe tag like this:

<iframe id="outer_iframe_2" src="https://subdomain2.example.com">
<div>
    <iframe id="inner_iframe_2" src="https://subdomain4.example.com"></iframe>
</div>
</iframe>
Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
  • Your example page is not working anymore, maybe because the top page is forced to `https` and the iframes have a `http` address. BTW I found at least 3 copies over the internet, before I found your original reply ;-) – close Mar 11 '21 at 16:38
  • @close There are no subdomains because I've moved the server and I didn't get to backup these. I'll try to fix it or delete the whole demo section. – Christos Lytras Mar 11 '21 at 17:14
  • I still have trouble to fathom why you are forced to use such an extreme amount of bloat to do such a simple and common thing. – Akito Jan 24 '22 at 00:59
  • @Akito you are welcome to write your own answer and enlight us all regarding making things simple. Please go ahead. – Christos Lytras Jan 24 '22 at 07:48
0

This isn't a direct answer to your question, but this is for those who want to use javascript to manipulate a webpage for data processing purposes.

Softwares like PhantomJS are designed for "browser automation" and allow removing the cross origin policy altogether.

phantomjs.exe --web-security=no script.js

Inside your script, you can use

page.open("http://fiddle.jshell.net/9aQv5/show/", function(status) { // Load a webpage
    page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js", function() { // Add support jor jQuery
        page.evaluate(function() { // Run custom script
            console.log($("body").find("iframe").attr("src"));

            console.log($("body").find("iframe").contents().find("iframe").attr("src"));

            console.log($("body").find("iframe").contents().find("iframe").contents().find("#about-puppy-linux").html());
        });
        phantom.exit(0);
    });
});

You get the following output:

//fiddle.jshell.net/9aQv5/show/light/
http://www.puppylinux.com/
About Puppy Linux
Mar Cnu
  • 1,165
  • 11
  • 16
  • I take it this only removes the security for your particular userscript (which you have to trust anyway)? Still, the `window.postMessage()` method seems more portable, as it doesn't require the userscript host to circumvent the policy. – Discrete lizard Sep 08 '20 at 13:27