10

I have an application running inside an iframe on a "foreign" page (different domain etc.). To allow some basic communication between the iframe & the parent, I load some script of mine on the parent page and use postMessage to do some cross document messaging.

Most of the time this communication works as intended, but sometimes I see some errors reported to my error tracking tool and can't figure out why they happen.

Here's some exemplary code:

PluginOnParent.js

// ...
window.addEventListener('message', function(e) {
    // Check message origin etc...
    if (e.data.type === 'iFrameRequest') {
        e.source.postMessage({
            type: 'parentResponse',
            responseData: someInterestingData
        }, e.origin);
    }
    // ...
}, false);
// ...

AppInsideIFrame.js

// ...
var timeoutId;


try {
    if (window.self === window.top) {
        // We're not inside an IFrame, don't do anything...
        return;
    }
} catch (e) {
    // Browsers can block access to window.top due to same origin policy.
    // See http://stackoverflow.com/a/326076
    // If this happens, we are inside an IFrame...
}

function messageHandler(e) {
    if (e.data && (e.data.type === 'parentResponse')) {
        window.clearTimeout(timeoutId);
        window.removeEventListener('message', messageHandler);
        // Do some stuff with the sent data
    }
}

timeoutId = window.setTimeout(function() {
    errorTracking.report('Communication with parent page failed');
    window.removeEventListener('message', messageHandler);
}, 500);

window.addEventListener('message', messageHandler, false);
window.parent.postMessage({ type: 'iFrameRequest' }, '*');
// ...

What happens here, when the timeout hits and the error is reported?

Some more info & thoughts of mine:

  • I have no control over the parent page myself
  • It doesn't seem to be a general "configuration" issue (CORS etc.) since the error happens on the same page where it works most of the time
  • We don't support IE < 10 and other "legacy" browser versions at all, so those are no issue here
  • My error reporting tool reports a multitude of different browsers amongst which are the latest versions of them (FF 49, Chrome 43 on Android 5, Chrome 53 on Win and Android 6, Mobile Safari 10, ...)
    • Therefore it doesn't seem like it's an issue related to specific browsers or versions.
  • The timeout of 500 ms is just some magic number I chose which I thought would be completely safe...
suamikim
  • 5,350
  • 9
  • 40
  • 75
  • Is this happening in a test environment or in the wild? – Dan Def Oct 25 '16 at 07:36
  • It's happening in the wild and I'm not able to reproduce it (at least until now). – suamikim Oct 25 '16 at 07:38
  • in that case, I wonder whether some users are ending up on your inner page outside of an iframe and that's why the messaging is not working? You could add some code that checks that the inner page is in an iframe, a quick Google gave a few SO answers on how to do so. – Dan Def Oct 25 '16 at 07:41
  • @DanDef I already perform such a check but left it out in my original question. I've added the bit of code now in the example code above. See the first few lines of `AppInsideIFrame.js` – suamikim Oct 25 '16 at 09:23
  • ok, not that then! If you time the round trip, starting when you start the timeout and end when you clear the timeout, how long does it usually take? I wonder whether if the browser is busy it is simply exceeding the timeout. Especially if you are running this code before the document is ready. Do instances of the error decrease if you increase the timeout to say, 2 seconds? – Dan Def Oct 25 '16 at 13:12
  • Unfortunately I don't have any metrics right now which tell me how long the round trip usually takes but I'll try to get them at least from my test environments. The code is definitely never executed before the document is ready. Increasing the timeout is kind of a last resort to me and can't be tested "quickly" since I have to wait for the next deployment. Nonetheless I'm also going to check this if I don't find no other clues until then. – suamikim Oct 25 '16 at 14:23
  • 2
    Have you tried raising that timeout to a super unrealistic value? Something like 5000000000.. I'm just thinking that if somehow the parent page is taking longer to load than yours, the timeout may fail. Also I would make your timeout the last thing in the script - add the event and send the first `postMessage` before creating the timeout.. – Michael Coxon Oct 27 '16 at 11:30

2 Answers2

1

The problem appears to be in your PluginOnParent.js, where you are sending your response. Instead of using "e.origin" (which upon inspection in the developer tools was returning "null") -- try using the literal '*', as it states in the following documentation on postMessage usage (in the description for targetOrigin):

https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage

Also, as a bonus, I just tested this across two different domains and it works as well. I placed Parent.html on one domains web server, and changed the iframe's src to be child.html on a completely different domain, and they communicated together just fine.

Parent.html

<html>
<head>
    <script type="text/javascript">
        function parentInitialize() {
            window.addEventListener('message', function (e) {
                // Check message origin etc...
                if (e.data.type === 'iFrameRequest') {
                    var obj = {
                        type: 'parentResponse',
                        responseData: 'some response'
                    };
                    e.source.postMessage(obj, '*');
                }
                // ...
            })
  }
    </script>
</head>
<body style="background-color: rgb(72, 222, 218);" onload="javascript: parentInitialize();">
    <iframe src="child.html" style="width: 500px; height:350px;"></iframe>
</body>
</html>

Child.html

<html>
<head>
    <script type="text/javascript">
        function childInitialize() {
            // ...
            var timeoutId;


            try {
                if (window.self === window.top) {
                    // We're not inside an IFrame, don't do anything...
                    return;
                }
            } catch (e) {
                // Browsers can block access to window.top due to same origin policy.
                // See http://stackoverflow.com/a/326076
                // If this happens, we are inside an IFrame...
            }

            function messageHandler(e) {
                if (e.data && (e.data.type === 'parentResponse')) {
                    window.clearTimeout(timeoutId);
                    window.removeEventListener('message', messageHandler);
                    // Do some stuff with the sent data
                    var obj = document.getElementById("status");
                    obj.value = e.data.responseData;
                }
            }

            timeoutId = window.setTimeout(function () {
                var obj = document.getElementById("status");
                obj.value = 'Communication with parent page failed';
                window.removeEventListener('message', messageHandler);
            }, 500);

            window.addEventListener('message', messageHandler, false);
            window.parent.postMessage({ type: 'iFrameRequest' }, '*');
            // ...
        }
    </script>
</head>
<body style="background-color: rgb(0, 148, 255);" onload="javascript: childInitialize();">
    <textarea type="text" style="width:400px; height:250px;" id="status" />
</body>
</html>

Hope that helps!

Michael A. Allen
  • 617
  • 5
  • 13
  • Unfortunately I wasn't able to try out your suggestions by now but since they seem like the most promising ones in this thread I give you the bounty nonetheless. Either way, I'll keep this issue updated once I know more. – suamikim Oct 29 '16 at 17:27
  • Thank you -- also, in case you are interested, there was a similar thread to this one that I chimed in on as well, but they wanted an ongoing timer communicating between the two pages. I mentioned this example to that person and borrowed a bit of your code as an example, and modified it to his scenario. It might also be useful to you, so I thought I would mention it: http://stackoverflow.com/questions/14028372/iframe-cross-domain-issue?noredirect=1&lq=1 - if nothing else, it shows constant error-free communication every second or so. – Michael A. Allen Oct 31 '16 at 15:23
1

Most of the time this communication works as intended, but sometimes I see some errors reported to my error tracking tool and can't figure out why they happen.

What happens here, when the timeout hits and the error is reported?

I have no control over the parent page myself

Not certain what the function errorTracking.report does when called, though does not appear that an actual error relating to message event occurs?

The timeout of 500 ms is just some magic number I chose which I thought would be completely safe...

With duration set at 500, setTimeout could be called before a message event fires at window.

timeoutId = window.setTimeout(function() {
    errorTracking.report('Communication with parent page failed');
    window.removeEventListener('message', messageHandler);
}, 500);

Adjust the duration of setTimeout to a greater duration.


Alternatively substitute onerror handler or window.addEventListener for setTimeout

Notes

When a syntax(?) error occurs in a script, loaded from a different origin, the details of the syntax error are not reported to prevent leaking information (see bug 363897). Instead the error reported is simply "Script error." This behavior can be overriden in some browsers using the crossorigin attribute on and having the server send the appropriate CORS HTTP response headers. A workaround is to isolate "Script error." and handle it knowing that the error detail is only viewable in the browser console and not accessible via JavaScript.

For example

// handle errors
onerror = function messageErrorHandlerAtAppInsideIFrame(e) {
  console.error("Error at messageErrorIndex", e)
}

to handle any actual errors during communicating between different contexts, or origins.

Use postMessage at load event of iframe to communicate with message handlers at parent window.

http://plnkr.co/edit/M85MDHF1kPPwTE2E0UGt?p=preview

guest271314
  • 1
  • 15
  • 104
  • 177