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...
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
Related
Hello i am trying to run the following script on a different server:
$$.find('button#reboot').on('click', function() {
var popup = new mimosa.popup();
popup.title('Confirmation');
popup.content('<p>Rebooting will cause service interruption.</p><p>Do you wish to continue?</p>');
popup.ok(function() {
mimosa.system.reboot("Reboot Button");
});
popup.cancel();
popup.show();
});
can anybody tell me or give me an example on how that's done?
What you're looking to do is communicate between window objects cross-origin. This can be accomplished using the postMessage api.
See: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
You'd have to set up a post-message listener on your server. When that listener receives the postMessage, have it run the script.
Receiving window:
window.addEventListener("runScript", receiveMessage, false);
function receiveMessage(event)
{
if (event.origin !== "http://example.org:8080") {
return;
}
...
}
My main page contains an <iframe> which points to an HTML file that contains a <frameset>. It looks similarly to the following (simplified for readability):
<html>
<body>
<iframe id="content" src="/same/domain/url" />
</body>
</html>
The <iframe> contents look like this:
<html>
<body>
<frameset>
<frame name="menu" src="/same/domain/menu/url" />
<frame name="main" src="/same/domain/initial/main/url" />
</frameset>
</body>
</html>
I am trying to read the current location href of the main frame from the main page.
$("#content").contents().find("frame[name=main]")[0].contentWindow.location.href
Unfortunately, on IE8 it gives me the "Permission denied" error. This looks like cross-domain scripting prevention mechanism, but all URLs come from the same domain/protocol/port. For the same javascript code Chrome gives me the correct value (surprise, surprise).
Please note that:
I can't use the frame's src attribute as the user might have already used the "menu" frame to navigate to another page (still, same domain).
I have no control over the contents of the iframe pages, these are supplied by another part of the application and are unmodifiable from my perspective
How do I get around this?
A way to do this would be to use postMessage api which allows for message passing between different windows / frames.
On the root window, listen for messages
window.attachEvent("onmessage", (e) => {
// handle message
});
Post a message to a child frame (iframe is a dom node).
iframe.contentWindow.postMessage({}, "*");
Within the child
window.parent.postMessage({}, "*");
This allows a simplistic event-driven communication scheme where you dispatch actions in the form of messages and receive response laters
as onmessage events.
In your case you would have something along:
// within child iframe
window.attachEvent("message", function (e) {
// IE8 does not support object passing, only strings
var message = JSON.parse(e.data);
// wait for a GET_HREF message
// and respond to it with the
// data.
if (message.type === "GET_HREF") {
window.parent.postMessage(JSON.stringify({
type: "GET_HREF",
data: $("frame")
.map(function () {
return this.href;
})
.get()
}));
}
});
// within parent window
window.attachEvent("message", function (e) {
// IE8 does not support object passing, only strings
var message = JSON.parse(e.data);
// wait for a GET_HREF message
if (message.type === "GET_HREF") {
updateHref(message.data);
}
});
iframe.contentWindow.postMessage(JSON.stringify({
type: "GET_HREF"
}), "*");
I've uploaded an HTML containing the following section:
<script>
window.onload = function(){
window.addEventListener('message', receiver, false);
}
function receiver(event) {
if (event.origin == 'http://documentA.com') {
if (event.data == 'Hello B') {
//event.source.postMessage('Hello A, how are you?', event.origin);
alert("Recognized.");
} else {
//alert(event.data);
alert("Unrecognized!");
}
}
}
</script>
The document is accessible and I can view it in the browser. Then, I open a console window using FireBug and type in the following call (as described at Wikipedia).
window.postMessage("12345", "http://server:port/Index4.htm");
As a result I get undefined and frankly I have no clue if it's a good thing or a bad thing. Probably bad, especially since I don't get to see any alerts. What to do?
At first:
You have to post the message to the iframe. Try to use:
document.getElementById("yourIFrameID").contentWindow.postMessage()
I did the following in Opera Dragonfly, Firebug and IE9 and it works with no problems:
//Add iframe: <iframe id="teest" src="http://www.dict.cc/?s=annahme">
var frame = document.getElementById("teest")
Switch to iframe context:
//Add event listener
window.addEventListener("message", function(ev){ console.log(ev.data); }, false);
Back in the top-context, send some stuff:
frame.contentWindow.postMessage("foo", "http://www.dict.cc")
//Works. console: "foo"
frame.contentWindow.postMessage("foo", "http://www.dict.cc:80")
//Works. console: "foo"
frame.contentWindow.postMessage("foo", "http://www.dict.cc/?s=annahme")
//Works. console: "foo"
frame.contentWindow.postMessage("foo", "http://dict.cc/?s=annahme")
//Does NOT work. console: Nothing Happened
So, I see difficulties if you forget to add the right subdomain to the second parameter or if you visit a "https" page but have "http" in your string.
Try to remove all if's in your event handler and print out what you get before using if (or even better inspect it with Firebug or Dragonfly)
Is there any way to catch an error when loading an iframe from another domain. Here is an example in jsfiddle. http://jsfiddle.net/2Udzu/ . I need to show a message if I receive an error.
Here is what I would like to do, but it doesn't work:
$('iframe')[0].onerror = function(e) {
alert('There was an error loading the iFrame');
}
Anyone have any ideas?
The onerror is applicable only for script errors. Frame content error checking must be done using any other method. Here's one example.
<script>
function chkFrame(fr) {
if (!fr.contentDocument.location) alert('Cross domain');
}
</script>
<iframe src="http://www.google.com/" onload="chkFrame(this)"></iframe>
Due to cross domain restriction, there's no way to detect whether a page is successfully loaded or if the page can't be loaded due to client errors (HTTP 4xx errors) and server errors (HTTP 5xx errors).
If both the parent site and the iframe-url is accessible by you, a way to know that the page is fully loaded (without "sameorigin" issues) is sending a message (postMessage) from the child to the parent like this;
Parent site (containing the iframe)
//Listen for message
window.addEventListener("message", function(event) {
if (event.data === "loading_success") {
//Yay
}
});
//Check whether message has come through or not
iframe_element.onload = function () {
//iframe loaded...
setTimeout(function() {
if (!iframeLoaded) {
//iframe loaded but no message from the site - URL not allowed
alert("Failure!");
}
}, 500);
};
Child site (URL from the iframe)
parent.postMessage("loading_success", "https://the_origin_site.url/");
You could get the_origin_site.url by using a server-side language like PHP if you want the possibility for multiple origins
The accepted answer only works if the domain you're trying to put in an iframe is the same as the one you're requesting from - this solution works for cross-domain where you have access to the scripts on both domains.
I am using following code to detect whether a x-frame-option error occured or another with jquery
$(iframe).load(function (e) {
try
{
// try access to check
console.log(this.contentWindow.document);
// Access possible ...
}
catch (e)
{
// Could not access. Read out error type
console.log(e);
var messageLC = e.message.toLowerCase();
if (messageLC.indexOf("x-frame-options") > -1 || messageLC.indexOf('blocked a frame with origin') > -1 || messageLC.indexOf('accessing a cross-origin') > -1)
{
// show Error Msg with cause of cross-origin access denied
}
else
{
// Shoe Error Msg with other cause
}
}
});
For example I'm on domain1:
a.click(function(){
window.win=window.open(domain2,'name');
});
Now I'm on domain2 and I'm closing it. How will window.win know that user closed that window? Is there any event or property to check via interval?
There is a property which is not part of any W3C spec. It's called closed and would get accessed like
if( window.win.closed ) {
// window was closed
}
I'm not sure about the cross-browser compatibilty for that property. I'm also not sure how this behaves on cross-origin domains. But if you try it please let me and the rest of this community know about it.
Another option is that you take care for the notification yourself. That means, you are listening for the onbeforeunload within the popup-window. When the event fires, you could use HTML5's postMessage method to communicate between cross-domain windows. For instance:
MainWindow:
window.addEventListener('message', function(e) {
if( e.origin === 'http://www.popupdomain.com' ) {
if( e.data === 'closed' ) {
alert('popup window was closed');
}
}
}, false);
Domain2:
window.onbeforeunload = function() {
window.opener.postMessage('closed', 'http://www.popupdomain.com');
};
The only caveat on this solution is that it's only compatible with browser that support basic HTML5. There are other (sneaky) ways to have a cross-domain communication on old'ish browsers, but I guess that is another story.
You can check if the cross domain was closed by using an interval check on the windows closed property.
var url = "http://stackoverflow.com";
var width = 500;
var height = 500;
var closeCheckInterval = 500; //Check every 500 ms.
var popup = window.open(url, "_new", 'width=' + width + ', height=' + height);
popup.focus();
var closeCheck = setInterval(function () {
try {
(popup == null || popup.closed) && (clearInterval(closeCheck), onWindowClosed());
} catch (ex) { }
}, closeCheckInterval);
var onWindowClosed = function () {
...
// Stuff to do after window has closed
...
}
I was working on the similar problem in which a window of domain1 is opened from domain2. I needed to keep a check on when the window gets closed. I tried following :-
I used window.onunload event , it didn't work because of Same Origin Policy and showed following error
Error: attempt to run compile-and-go script on a cleared scope
Source File: chrome://firebug/content/net/httpLib.js
Error: attempt to run compile-and-go script on a cleared scope
Source File: chrome://firebug/content/firefox/tabWatcher.js
But I maintained an array of Window objects and applied "Window.closed" property , it works even in cross domain. :)
Also you can try postMessage API or CORS