Access an Iframe variable from parent window - javascript

I have a webpage at url https://parent.com and it has an iframe injected into it with source https://iframe.com. Iframe has a global variable defined called iframe_variable. I want to access the iframe_variable from parent document.
I know browsers don't allow cross origin communication and they provide a postMessage API to do it securely.
Constraint: I do not have access to any of parent or iframe code.
On Browser console, I somehow want to access iframe_variable
I have tried the following:
Get reference of iframe first.
var iframe = document.getElementsByTagName('iframe')[0]; // There is only one iframe on document
Create a listener for message event posted from parent window.
var iframeListener = function(e) {
console.log("Got message from parent");
e.source.postMessage(JSON.stringify({'IFRAME_VARIABLE': window.IFRAME_VARIABLE}));
}
Create a listener for parent window to accept 'message' posted from iframe.
parentListener = function(e) {
console.log('Got message from iframe');
var data = JSON.parse(e.data);
window.VARIABLE = data.IFRAME_VARIABLE;
}
Attach parent_listener to message event.
window.addEventListener('message', parentListener, false);
Now if i try to post a message to iframe from parent as follows:
iframe.contentWindow.postMessage('test message', '*')
It doesn't trigger 'iframeListener'. The reason is because it is not registered against the message event in iframe.
I don't think I can even do that from the browser console when I am on parent.com as any attempt to do iframe.contentWindow.addEventListener will result in an error as it will be an attempt to access a different domain.
Is there a workaround that? Is there anything that I am missing in my understanding and research.
P.S: I have not written the origin checks for simplicity. I know I must check for the origin a message is posted from. Not doing that leaves a huge security hole.

Related

How to postmessage HTMLIFrameElement?

I'm having issue passing HTMLIFrameElement object from parent site to iframe (located on a different domain) using postMessage method.
This is my code that I already tried:
var frame = document.getElementById('myHTMLIFrameElement');
frame = JSON.parse(JSON.stringify(frame));
event.source.postMessage(frame, "*");
Unfortunately, JSON.parse/JSON.stringify does not seem to be the right way of handling HTMLIFrameElement object. Can you please advise how to pass through HTMLIFrameElement object correctly?
Parsing DOM elements as JSON does not do anything useful as far as I know.
Since postMessage() is part of the window object, you could try using the window object of the frame, which is found under the contentWindow property of the iframe.
So you could try something like:
var frame = document.getElementById('myHTMLIFrameElement');
frame.contentWindow.postMessage( "stuffYouWantToSendToTheIframe", '*' );
And then in the script inside the iframe:
window.addEventListener('message', function( event ) {
// handle message
});

Send JavaScript to an iFrame that uses a different port?

I have been having some issues sending JavaScript to an iFrame that uses a different port and after searching online it seems that the 'different port' part is causing the issue.
Here is the code sending JavaScript to the iFrame:
<script>
var network = document.getElementById("1").contentWindow.kiwi.components.Network();
$(".irc-channel-selector").click(function(event){
network.join('#' + $(this).attr('data-irc-channel'));
});
</script>
The iFrame does not use port 80 which appears to be the problem:
<iframe id="1" src="http://www.example.com:7888">
I understand that I can use something called postMessage to do the same as what I need but having read up on it online I'm not sure how it should be used, it seems pretty complex whereas I'm only used to basic JavaScript such as the code that I wrote above.
Can someone provide an example on how I can use this postMessage to mimic the behaviour above? Reading online documentation I do not understand how to use it in my scenario! :(
It's not very complicated to achieve this with postMessage. First, inside the iframe, you must expect a message:
var network = kiwi.components.Network();
function receive(event) {
// it is important to check the origin. Here I'm assuming that the parent window has that origin (same address, default port).
// if you don't check the origin any other site could include your iframe and send messages to it
if (event.origin === "http://www.example.com") {
var message = event.data;
// Assuming you might want different message types in the future. Otherwise message could be just the channel itself.
if (message.type === "JOIN") {
network.join(message.channel);
}
}
}
window.addEventListener("message", receive, false);
Now your iframe page is expecting a message to make it join a channel. The parent page can send that message with:
$(".irc-channel-selector").click(function(event){
var message = {
type: "JOIN",
channel: '#' + $(this).attr('data-irc-channel')
};
// Notice that again we're using the specific origin you used in your iframe
document.getElementById("1").contentWindow.postMessage(message, "http://www.example.com:7888");
});
Here's a far more simple fiddle where a message is sent to the same window, since I'd have to host a page somewhere to have an iframe in jsfiddle: https://jsfiddle.net/3h1Lw0j4/1/ -- Anyway it's useful to see how event.origin behaves.

postMessage() generates error "undefined is not a function"

I'm trying to get postMessage() to work to communicate between an iframe and my main website. However using the exact syntax given in the example code on MDN, I am being presented with a nice Undefined is not a function error. I've tried several things, such as initializing the iframe inside Javascript and appending it to my page, but that left me with the same error. Same for have seperate selectors to select my iframe.
I have the following Javascript code:
<script type="text/javascript">
$(document).ready(function() {
$('.editor').postMessage("A", "domain here");
});
function receiveMessage(event)
{
if (event.origin !== "domain here")
return;
// Do something
}
window.addEventListener("message", receiveMessage, false);
</script>
The script above tries to send a message to my iframe on the page, which looks like:
<iframe src="domain here/frameListener.html" class="editor"></iframe>
It then has a function receiveMessage to catch any messages being send as a response to the main webpage. Last but not least, I've tried the answers given in this question: But that did not fix my problem. It is therefore not a duplicate.
How can I get rid of this error message?
postMessage is not a jQuery function so you need to get the actual window DOM element and call it on that:
$('.editor').get(0).contentWindow.postMessage("A", "domain here");
Furthermore, you need to access the contentWindow property of the iframe. Here is an excerpt from the MDN docs:
otherWindow.postMessage(message, targetOrigin, [transfer]);
otherWindow
A reference to another window; such a reference may be
obtained, for example, using the contentWindow property of an iframe
element, the object returned by window.open, or by named or numeric
index on window.frames.

Problems with window.postMessage on Chrome

I have been stuck on this for hours.
I have a.html on http://example.com that contains an iframe with src to
b.html on http://subdomain.example.com. a.html has some JS code
to postMessage to the iframe.
The code to postMessage is simple:
iframe_window.postMessage('message', iframe_element.src)
But this way, Chrome throws an error:
Unable to post message to http://subdomain.example.com. Recipient has origin null.
I have also tried:
iframe_window.postMessage('message', 'http://subdomain.example.com')
But NO LUCK!
This is the ONLY WAY it works:
iframe_window.postMessage('message', '*')
But I have heard '*' is not good to use.
No problems in Firefox.
It looks like this might be an issue with the child iframe not being loaded at the time the signal is sent, thus iframe.src doesn't have the proper value.
I did some testing and got the same error as you, but when I wrapped the postMessage call in a setTimeout and waited 100ms then there was no error, which tells me that this is an initialisation race condition.
Here's how I implemented a cleaner solution without the setTimeout:
Parent:
window.addEventListener("DOMContentLoaded", function() {
var iframe = document.querySelector("iframe")
, _window = iframe.contentWindow
window.addEventListener("message", function(e) {
// wait for child to signal that it's loaded.
if ( e.data === "loaded" && e.origin === iframe.src.split("/").splice(0, 3).join("/")) {
// send the child a message.
_window.postMessage("Test", iframe.src)
}
})
}, false)
Child:
window.addEventListener("DOMContentLoaded", function() {
// signal the parent that we're loaded.
window.parent.postMessage("loaded", "*")
// listen for messages from the parent.
window.addEventListener("message", function(e) {
var message = document.createElement("h1")
message.innerHTML = e.data
document.body.appendChild(message)
}, false)
}, false)
This is a simple solution in which the child will signal to anyone that it's loaded (using "*", which is okay, because nothing sensitive is being sent.) The parent listens for a loaded event and checks that it's the child that it's interested in that's emitting it.
The parent then sends a message to the child, which is ready to receive it. When the child gets the message it puts the data in an <h1> and appends that to the <body>.
I tested this in Chrome with actual subdomains and this solution worked for me.
A shorter solution is to wrap the postMessage inside iframe_element.onload function.

How can I handle errors in loading an iframe?

I have an <iframe> that other sites can include so their users can POST a form back to my site. I'd like to handle gracefully the cases where my site is down or my server can't serve the <iframe> contents (that is, a response timeout or a 4xx or 5xx error). I tried adding an onError to the <iframe> object, but that didn't seem to do anything:
showIFrame = function() {
var iframe = document.createElement('iframe');
iframe.id = 'myIFrame';
iframe.src = 'http://myserver.com/someURLThatFailsToLoad';
iframe.onError = iframe.onerror = myHandler;
document.body.appendChild(iframe);
};
myHandler = function(error) {
document.getElementById('myIFrame').style.display = 'none';
console.error('Error loading iframe contents: ' + error);
return true;
};
If my server returns a 404 I just get the contents of the not-found page in my <iframe>. In fact, that error handler isn't ever triggered. Is there a way to make this work?
(I'm currently testing in Chrome, but I'd like it to also work for FF and IE >= 7.)
To detect whether your server is down or not, you can include an empty script file from your own domain. When the server is down, the onerror event handler will fire:
var el = document.createElement('script');
el.onerror = errorFunction;
el.src = "somebogusscript.js?" + new Date().getTime();
document.body.appendChild(el);
Note: don't forget to add a random string to the src attribute to avoid the client using a cached version (which could stop a look at the server at all).
Perhaps you could try onErrorUpdate for the event handler? I couldn't see an onError handler for iFrames. If that doesn't work, you could try onLoad and then check the source of the iframe or the title of it for a 404 message.
Such as:
if (frameDoc.title == 'title the server sends for 404') {
Source:
http://bytes.com/topic/javascript/answers/166288-catch-404-when-using-iframe
iFrame Methods: http://www.java2s.com/Code/HTMLCSSReference/HTML-Tag-Reference/iframeJavaScriptMethods.htm
iFrame Properties: http://www.java2s.com/Code/HTMLCSSReference/HTML-Tag-Reference/iframeJavaScriptProperties.htm
One technique is to set a JavaScript timeout when you make the request. If your timeout fires before the iframe onload event, the content didn't load. You could then set iframe.src to about:blank, delete, or reuse the iframe.

Categories

Resources