44

I am developing a Chrome extension to use with my own Web-application, processing Web-pages (including cross-domain ones), loaded into iFrame on my application's main page. So I have to send message, containing the URL of the page to be processed, from my main page to the content script, injected into the target iFrame to get access to the processed page's window.

I try to implement the approach, described here. Below are the essential parts of all files involved:

index.html:

...
<head>
    ...
    <script type="text/javascript" src="base.js"></script>
</head>
<body>
    <div>
        <input type="text" id="page_url">&nbsp;<input type="button" id="get_page" value="Get page">
    </div>
    <iframe id="cross_domain_page" src=""></iframe>
</body>

base.js:

(function() {
    "use strict";
    function pageProcessing() {
        let urlButton = window.document.getElementById("get_page");
        urlButton.addEventListener("click", () => {
            let url = window.document.getElementById("page_url").value;
            window.postMessage(
                {
                    sender: "get_page_button1",
                    message: url
                },
                "*"
            );
            window.postMessage(
                {
                    sender: "get_page_button2",
                    message: url
                },
                "*"
            );
        });
    }
    window.document.addEventListener('readystatechange', () => {
            if (window.document.readyState == 'complete') {
                pageProcessing();
            }
        }
    );
})();

manifest.json:

{
  "manifest_version": 2,
  "name": "GetPage",
  "version": "0.1",
  "content_scripts": [
    { "js": ["main.js"], "matches": ["<all_urls>"] },
    { "js": ["frame.js"], "matches": ["<all_urls>"], "all_frames": true, "match_about_blank": true }
  ],
  "permissions": ["activeTab"]
}

main.js:

(function() {
    "use strict";
    window.isTop = true;
})();

frame.js:

(function() {
    "use strict";
    window.addEventListener("message", (event) => {
        if (event.data &&
            event.data.sender == "get_page_button1") {
            if (window.isTop) {
                alert("Main window alert");
            } else {
                alert("Frame window alert 1");
            }
        }
    });
    if (!window.isTop) {
        window.addEventListener("message", (event) => {
            if (event.data &&
                event.data.sender == "get_page_button2") {
                alert("Frame window alert 2");
            }
        });
    }
})();

The problem is that when I click "get_page" button the only alert I see is the main window alert. As for my understanding it means that the message, posted from the main window, does not reach the content script, injected into iFrame.

What is wrong with my script and how to fix the problem?

1 Answers1

79

window.postMessage in your web app sends to the main document's window, not to the iframe's.

Specify the iframe's window object:

document.getElementById('cross_domain_page').contentWindow.postMessage(.......)

Alternatively, you can switch to the more secure externally_connectable messaging.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • It looks strange for me that the function, working in this way, is named "POSTmessage", because it actually GETS a message for a specific window. Naming conventions are so weird sometimes. )) But you solution works, thank you very much, @wOxxOm. – Kostiantyn Ivashchenko May 01 '20 at 20:13
  • 3
    does it follow from your answer that to send message from `iFrame` back to the main window I have to use something like `if (!window.isTop) { ... window.parent.postMessage(...); }`? – Kostiantyn Ivashchenko May 02 '20 at 00:48
  • 3
    Will the postMessage will work with cross-domain iframes? I'm not able to access contentWindow of my iframe which loads cross domain page. – Hulk Nov 02 '20 at 18:31
  • 2
    postMessage is designed to work with cross-origin iframes/windows. Ask a new question with [MCVE](/help/mcve). – wOxxOm Nov 03 '20 at 06:59
  • 1
    THANK YOU @wOxxOm! Spent so many hours on this! – Rohan Taneja Mar 12 '21 at 22:13
  • 2
    This is not working for me. The listener within the iframe is not picking up the message. – Akhil Ravindran Jan 11 '22 at 17:42
  • Same problem as @AkhilRavindran. The listener within the iframe is not picking up the message. Does it still work on chrome extensions? – Fernão de Rocha Guerra Feb 23 '22 at 03:58
  • 2
    Don't post a "me too" in comments. Make a new question with a proper [MCVE](/help/mcve). – wOxxOm Feb 23 '22 at 05:37
  • There's something I don't understand. In the postMessage call there's the targetOrigin parameter. If we specify the target as '*' shouldn't it be sent to the iframe with the general `window.postMessage`? – Itayst Oct 03 '22 at 09:56
  • 1
    @Itayst, no, postMessage sends into the object on which you call it i.e. in your example it's a `window`, which corresponds to the current environment where the code runs, not to the target. – wOxxOm Oct 03 '22 at 11:48
  • @wOxxOm I see, thanks. So if the message is sent to the specified object, what's the purpose of the `targetOrigin` parameter? – Itayst Oct 03 '22 at 13:34
  • 1
    In Web platform it's impossible to read the URL origin of a cross-origin page/frame, which is why by specifying it in postMessage you say "I expect this site to be there, do nothing otherwise". – wOxxOm Oct 03 '22 at 15:58