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:
Communicating directly between iframes is also possible by combining window. parent with target as defined above. In conclusion, the postMessage method is a more dynamic alternative to the single DOM, better suited if you load multiple pages in one iframe, but not always easier and it still requires the use of the DOM.
All you have to do is first dispatch an event from the iframe to the parent that notifies the parent that the iframe is loaded (essentially a "ready message"). The parent will be listening for messages and if it receives the "ready message" event, it can then reply to the iframe with whatever message you want to send.
to select the iframe with querySelector . Then we define the canAccessIFrame function that checks if the iframe has the contentDocument property defined. If it's defined then it's not a cross-domain iframe or it's cross domain and cross domain is allowed. Otherwise, false is returned.
A cross domain inline frame (iframe) can be used to embed a small portion of one website within a larger "parent" page hosted on a different domain. An inline frame, often known as an iframe, is a feature of the HyperText Markup Language (HTML) that allows a small portion of one webpage to be displayed within another.
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!
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With