This question already has answers here:
How do you use window.postMessage across domains?
(3 answers)
Closed 5 years ago.
I have a simple HTML5 page with an iframe whose src attribute is initially empty string. The page renders without any JavaScript errors.
The src attribute of iframe element is only set when the window has loaded, so initially an empty iframe loads. The iframe src is being set to a page from another domain.
The problem I am facing is that the postMessage method works without throwing any errors, however the source page is not firing the message event even though it's set up before the iframe page starts loading. I am having the alert messages from iframe page show up, which means the postMessage method did not throw any errors.
Question
What am I missing when subscribing to the message event in source page?
Source page
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Cross domain iframe messaging</title>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script type="text/javascript">
var iframec = null;
window.addEventListener("load", function () {
iframec = document.getElementById("iframec");
//set up event listener for iframe object so it can receive message from page shown in iframe
iframec.contentWindow.addEventListener("message", function (event) {
alert("received: " + event.data);
}, false);
//load the iframe page but after you have set up to receive messages
iframec.src = "http://www.abcx.com";
});
</script>
</head>
<body>
<form id="form1">
<div>
<h1>Testing iframe messaging in a Cross Domain scenario</h1>
<p> Trying to send a message to an iframe that comes from a page in another domain. The postMessage method does not throw an error when called from the other domain page, but it's not being received by the page having iframe element in it.</p>
<div id="divComments"></div>
<iframe src="" id="iframec" name="iframec" style="border:none;margin:0;padding:0;width:100%; "></iframe>
</div>
</form>
</body>
</html>
Iframe Page JavaScript that is not throwing any error (i.e. page at http://www.abcx.com)
<script>
$( document ).ready(function() {
alert("loaded the iframe page on another domain. Just before postMessage");
window.postMessage("Some message was sent from other domain message", "*");
alert("loaded the iframe page on another domain. Just after postMessage");
});
</script>
You're hooking up the listener on the wrong window, and using the wrong window to send the message. (This is fairly easy to get wrong. :-) )
In the main window, you want to receive message events on the main window, not the iframe, so:
window.addEventListener("message", function (event) {
// ^^^^^^^
alert("received: " + event.data);
}, false);
In the iframe, you want to send to window.parent (well, parent), not window:
parent.postMessage("Some message was sent from other domain message", "*");
// ^^^^^^
With both of those changes, the message sent by the iframe is received by the main window.
I found a similar question here
On the page you're trying to load, it should be using top.postMessage or parent.postMessage
Additionally, you should be attaching the listener to the window, not the iframe (and make sure to filter the origin, or else localhost will throw you a false positive)
This is the version of yours I was playing with:
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script>
var iframec = null;
window.addEventListener("load", function () {
if(!iframec){
iframec = document.getElementById("iframec");
//set up event listener for iframe object so it can receive message from page shown in iframe
window.addEventListener("message", function (event) {
if(event.origin == '[your domain here]') alert("received from " + event.origin + ": " + event.data);
}, false);
//load the iframe page but after you have set up to receive messages
iframec.src = "[iframe target url]";
}
});
</script>
And the target for the iframe:
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script>
$( document ).ready(function() {
alert('loaded the page. Just before postMessage');
top.postMessage("Some message was sent from appsprocure", "*");
alert("loaded the page. Just after postMessage");
});
</script>
</head>
<body><h1>Hello!</h1></body>
</html>
Related
I'm trying to make a loadable widget that would call an api when a button is clicked.
Widget page itself
widget.html
<html>
<head>
<title>Widget</title>
</head>
<body>
<button id="test"></button>
<script>
window.onload = init;
function clicked() {
// some logic
}
function init() {
document.getElementById('test').addEventListener('click', clicked, false);
}
export {clicked}
</script>
</body>
</html>
The page it embeds on
index.html
<!DOCTYPE HTML>
<html>
<head>
<title>Getting started</title>
</head>
<body>
<div>
<div id="widget_box"></div>
<script src="http://localhost:8080/js/widget.js" type="text/javascript"></script>
<script type="text/javascript">wdgt.init('widget_box');</script>
</div>
</body>
</html>
js code that loads the widget
widget.js
var wdgt= {
idBox: 'wdgt',
url_widget: 'http://localhost:8080/pages/widgets/widget.html',
url_style: 'http://localhost:8080/css/widget.css',
init: function (id) {
console.log("Begin Widget initialization");
if (!id) {
id = this.idBox;
}
if (document.getElementById(id)) {
this.addStyle();
try {
var XHR = ("onload" in new XMLHttpRequest()) ? XMLHttpRequest : XDomainRequest;
var xhr = new XHR();
xhr.open('GET', this.url_widget, true);
xhr.onload = function () {
if (this.response) {
document.getElementById(id).innerHTML = this.response;
}
}
xhr.onerror = function () {
console.log('onerror ' + this.status);
}
xhr.send();
} catch (ignore) {
}
} else {
console.log('The specified block id="' + id + '" is missing');
}
},
addStyle: function () {
style = document.createElement('link');
style.rel = 'stylesheet';
style.type = 'text/css';
style.href = this.url_style;
document.head.appendChild(style);
}
}
If I open the widget in a separate window, the code attached to the button works. But when I try to embed it, nothing happens.
Moreover, in this script, document.getElementById() returns null when trying to find the button.
The back is a simple Spring application that returns index.html
TL;DR: It's not supposed to work. You should use iframe element to fetch and render external pages.
Your script widget.js is trying to render the contents of fetched widget.html into a <div> element.
The browser will to do the best, though it won't be able to create another DOM tree inside of <div>. Because when you feed a whole HTML page to innerHTML, you basically ask the browser to render a document inside a document. This is not allowed.
Still it will render the contents of you document's body. So you'll be able to see the button. But it won't execute any <script> tags. It's a safety measure to prevent XSS attacks. A couple of dirty ways to execute your code do exist, but I won't recommend them.
So what should you do to add an external widget into a page? The answer is neat: meet the <iframe>! This dude is created just for those things. For you case, you should rewrite index.html this way:
<!DOCTYPE HTML>
<html>
<head>
<title>Getting started</title>
</head>
<body>
<iframe src="http://localhost:8080/pages/widgets/widget.html" width="800" height="600"></iframe>
</body>
</html>
With iframes, you don't need to manually fetch your widget HTML. The browser will download and render it inside your iframe. Like a document inside a document with all the styles, embedded resources and scripts.
Please note, that when you embed something using iframe, security limitations apply. You won't be able to directly interact with your main page from inside the iframe and vice versa. It will be like a separate window inside you document. And it will have additional constraints. Please address to the docs.
Am hoping I haven't overlooked a solution here on SO, but I have been pulling my hair out trying to figure out what I am doing wrong and hoping somebody can see what it is. Here is the configuration:
I have a page that is served on DOMAIN1.COM. For the most part is is a form with a submit button and lots of jQuery. The form can be lengthy and so a user needs to scroll down the page to input form fields. The submit button is at the bottom of this form while the output results are at the top of the form. When the user clicks the submit button he/she cannot see the results because they are off of the top of the page. What I want to do is use jQuery scrollTo() to position to the top of the page once the submit button is clicked.
The problem is that the page above (and submit button) is within an iFrame on DOMAIN2.COM and so when the submit button is clicked I have a cross domain situation that I need to overcome.
I posted a question here, but my question wasn't really accurate. The thread evolved into helpful information and pointed me to a script that uses jQuery postMessage() to communicate cross domain, but I am having a problem implementing the proposed solution. Here's my code:
DOMAIN1.COM (child):
<html>
<head>
<!-- Load jquery -->
<script type="text/javascript" src="./jquery-2.0.3.min.js"></script>
<!-- Cross-domain scripting goodness (scroll to top) -->
<script type="text/javascript" src="./jquery.ba-postmessage.js"></script>
<script type="text/javascript">
// EVENT Handler - Form Submission
$(function () {
$("#btnSubmit").bind('click', function (event) {
// Get the parent page URL as it was passed in,
// for browsers that don't support window.postMessage
var parent_url = decodeURIComponent(document.location);
window.postMessage("scrollTop", parent_url, parent);
});
});
</script>
</head>
<body>
<form>
... Lots of form fields resulting in vertical height ...
<input type="submit" id="btnSubmit" value="Submit" />
</form>
</body>
</html>
DOMAIN2.COM (parent):
<html>
<head>
<!-- Load jquery -->
<script type="text/javascript" src="./jquery-2.0.3.min.js"></script>
<!-- Cross-domain scripting goodness (scroll to top) -->
<script type="text/javascript" src="./jquery.ba-postmessage.js"></script>
<script type="text/javascript">
// Cross-domain - scroll window to top
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
if (event.data == "scrollTop") {
window.scrollTo(0, 0);
}
}
</script>
</head>
<body>
<iframe src="http://domain1.com/index.html"></iframe>
</body>
</html>
Running both pages above on the same domain provides the desired results - clicking submit scrolls the page to the top.
Running on separate domains as documented and coded above, the page does not scroll to top, my output results fail, and jQuery outputs this error message in Firebug:
DataCloneError: The object could not be cloned.
If I change the code to leave out the parent parameter in the page on DOMAIN1.COM like:
var parent_url = decodeURIComponent(document.location);
window.postMessage("scrollTop", parent_url); //, parent);
Then nothing happens at all and no errors are output. It does not appear that receiveMessage() on DOMAIN2.COM gets called at all.
Can anyone see what I might be doing wrong that prevents this code from working cross domain?
I think it very late now but for others, it may work.
You are almost there; You need to use
window.postMessage("scrollTop","#domain of parent page exact page");
Instead of your code snippet:
window.postMessage("scrollTop", parent_url, parent);
If at the time the event is scheduled to be dispatched the scheme, hostname, or port of otherWindow's document does not match that provided in targetOrigin, the event will not be sent by Dom. Read documentation here
In Parent Page use following code snippet.
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
if (event.origin !== "#domain of iframe")
return;
if (event.data == "scrollTop"){
window.scrollTo(0,0);
}
}
Here is my code on http://my-localhost.com/iframe-test.html
<html>
<head><title>Welcome Iframe Test</title></head>
<body>
<iframe src="http://www.my-website.com/index.html" width="500" height="500"></iframe>
<script type="text/javascript">
function alertMyMessage(msg){
alert(msg);
}
</script>
</body>
</html>
Here is code on http://www.my-website.com/index.html
<html>
<head></title>Welcome to my Server</title></head>
<body>
<h1>Welcome to My Server</ht>
Click Here
</body>
</html>
When i Click the "Click Here" Link. i got following Error.
Uncaught SecurityError: Blocked a frame with origin
"http://www.my-website.com" from accessing a frame with origin
"http://my-localhost.com". Protocols, domains, and ports must match.
Please Help me to Fix this Issue, or give some other solution for this.
You cannot access the DOM of a page loaded in a frame in a different origin. This is blocked for security reasons (imagine a random website you visited opening your web mail service in a hidden iframe and you can see why).
The closest you can come, and then only if you control both websites, is to pass messages between them using the web messaging api.
In one page, write a function to handle the messages and then add it as a message event listener.
function receiveMessage(event)
{
alert(event.data);
}
addEventListener("message", receiveMessage, false);
In the other, send the message:
parent.postMessage("This is a message", "*");
See MDN for more information
You can use postMessage!
PARENT
if (window.addEventListener) {
window.addEventListener ("message", receive, false);
}
else {
if (window.attachEvent) {
window.attachEvent("onmessage",receive, false);
}
}
function receive(event){
var data = event.data;
if(typeof(window[data.func]) == "function"){
window[data.func].call(null, data.params[0]);
}
}
function alertMyMessage(msg){
alert(msg);
}
IFRAME
function send(){
window.parent.window.postMessage(
{'func':'alertMyMessage','params':['Thanks for Helping me']},
'http://www.my-website.com'
);
}
REFERENCE
https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage
I have two local .html files in the same folder. One page opens a window with the other page, and attempts to call a function in the newly opened window. However, the function call fails, and I get this in the console:
Unsafe JavaScript attempt to access frame with URL file:///***/A.html from frame with URL file:///***/B.html. Domains, protocols and ports must match.
This happens on both Chrome and Webkit (Mac). Is there any way I can either: disable the cross-domain checks for the file:// protocol, or call a javascript function in a different local file?
You can use window.postMessage to do something like this:
The initial window html file:
<!DOCTYPE html>
<html>
<head>
<script>
var otherWindow;
function openOther() {
otherWindow = window.open("other.html", "otherWindow");
}
function otherFunc() {
otherWindow.postMessage("otherFunc", "*");
}
</script>
</head>
<body>
<div onclick="openOther()">Open the other window</div>
<div onclick="otherFunc()">Call the other window's function</div>
</body>
</html>
Html for the second window:
<!DOCTYPE html>
<html>
<head>
<script>
window.addEventListener("message", function(event) {
alert("The other window's function executed.");
}, false);
</script>
</head>
<body>
<div>This is the other window.</div>
</body>
</html>
Here's a good reference for window.postMessage.
I have a signup form which is inside an iframe on my site and I want to create a redirect when the url of the iframe changes (when user successfully signed-up).
These 2 sites are cross domain and I know that it is almost impossible to pull the url cross domains but is there a workaround? I know the src will not change and I was thinking to use onload(), when the iframe loads a second time (when user successfully signed-up), execute a function to redirect to a thank you page.
Here is an example using the javascript 'Porthole'.
Its possible, but keep in mind the safety issues with iframes. The solution: if you have control of the original page + iframe, you can 'trick' the browser by implementing some javascripts on both sides.
First create a 'proxy' page on both domains. Name it 'proxy.html' or something (note: you have to use 'porthole.min.js', you can get this from the sources in the bottom)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- Replace the url with your own location -->
<script type="text/javascript" src="porthole.min.js"></script>
<script type="text/javascript">
window.onload=function(){ Porthole.WindowProxyDispatcher.start(); };
</script>
</head>
<body>
</body>
</html>
On the parent page: (refer to the iframe proxy.html page)
<script type="text/javascript" src="porthole.min.js"></script>
<iframe id="guestFrame" name="guestFrame" src="http://iframe.otherdomain.com/"></iframe>
<script type="text/javascript">
var iframeDomain = 'http://iframe.otherdomain.com';
var redirectUrl = 'http://www.mydomain.com/redirect-to/signed-up';
function onMessage(messageEvent) {
if (messageEvent.origin == iframeDomain) {
if (messageEvent.data["action"]
&& messageEvent.data["action"] == 'signed-up) {
window.location.href = redirectUrl; // The final action!
// This is the eventual redirect that will happen
// once your visitor has signed-up within the iframe
}
}
}
var windowProxy;
window.onload=function(){
// Create a proxy window to send to and receive messages from the iFrame
windowProxy = new Porthole.WindowProxy(
'http://iframe.otherdomain.com/proxy.html', 'guestFrame');
// Register an event handler to receive messages;
windowProxy.addEventListener(onMessage);
};
</script>
On the iframe page (refer to the parent proxy.html page)
<script type="text/javascript">
var windowProxy;
window.onload=function(){
// Create a proxy window to send to and receive messages from the parent
windowProxy = new Porthole.WindowProxy(
'http://www.mydomain.com/proxy.html');
// Register an event handler to receive messages;
windowProxy.addEventListener(function(event) {
// handle event (not used here, the iframe does not need to listen)
});
};
</script>
From the iframe you can send a message with javascript to the parent page (and also the other way). If you use only 1 javascript within the iframe domain, you can do something like this to send a message to the parent frame when the url is changed to something specific, for example 'signed-up.php' (untested, but you'll get the idea)
<script type="text/javascript">
window.onload=function(){
if(window.location.href.indexOf("signed-up.php") > -1) {
windowProxy.post({'action': 'signed-up'}); // Send message to the parent frame
// On the parent page, the function 'onMessage' is triggered.
}
};
</script>
Sources:
http://ternarylabs.github.io/porthole/
Github: https://github.com/ternarylabs/porthole
Demo: http://sandbox.ternarylabs.com/porthole/