How could an iframe read its parent location URL, the domains of the parent window and the iframe being different?
I know that the "common" answer to this question is that because of the domains conflict, browsers don't allow cross-domain accesses.
So the following won't work:
parent.location.href
But maybe someone could think out of the box here and propose something similar to the Cross-document messaging hack?
Perhaps you could check document.referrer? Of course, that will only work if the user has not yet clicked a link inside the iframe.
I got it!!
My solution is the following:
1. Use the onload attribute with the frame
<iframe src="http://website.com/" onload="src+='#'+document.location"></iframe>
2. Monitor hash changes in the frame
var referrer = '?';
window.onhashchange = function() {
var ref = document.location.hash;
if(ref && ref.length > 1) {
var t = ref.split('#');
ref = t[1];
}
referrer = ref;
};
And "voilĂ " :-)
By the way, you'll find some more details about the onhashchange event.
Related
I'm working on a phaser game that's to be embedded in a website via iframe. The game supports multiple languages, so we've taken to using the site the game was accessed from as an indicator (phaser-game.com/ru would be in Russian, phaser-game.com/ar would be in Arabic, etc).
Here's the code so far (fired via window.addEventListener('load', getDomainSetLanguage);:
function getDomainSetLanguage()
{
let url = (window.location !== window.parent.location) ? document.referrer : document.location.href;
console.log('url = ' + url);
for (let i = 0; i < COUNTRIES_DOMAIN.length; i++)
{
if (url.indexOf(COUNTRIES_DOMAIN[i].URL) >= 0)
{
DOMAIN_ID = COUNTRIES_DOMAIN[i].ID;
LANGUAGE_ID = COUNTRIES_DOMAIN[i].LANGUAGE_ID;
break;
}
}
if (DOMAIN_ID === -1)
{
DOMAIN_ID = 1;
}
if (LANGUAGE_ID === -1)
{
LANGUAGE_ID = 1;
}
console.log('DOMAIN_ID = ' + DOMAIN_ID + "; LANGUAGE_ID = " + LANGUAGE_ID);
}
Now this works fine, on the surface. However, the game does trigger a reload every now and then, and when the game comes back, it now gets it's own URL, not the parent's / iframe's.
This has the result of the game language defaulting to English.
Note that this only occurs in Chrome and Safari. FireFox works just fine.
Is there something I'm missing? Or is there anything else I can try?
I've tried logging the values of document.referrer and document.location.href, but I'm just getting browser errors about permissions and stuff and the game defaults to English.
I read from here that Chrome (and possibly Safari) doesn't fire the onload function of objects in the iframe, but I'm not sure if this applies to me, as I have a lot of other functions tied to onload that do work.
It should be mentioned that I cannot modify the iframe itself, so any solution must be from the game itself.
Thanks!
let url = (window.location !== window.parent.location) ? document.referrer : document.location.href;
This line from your code makes it so that when you're inside of an iframe, document.referrer is used as the URL to determine the language from.
As per the MDN page on Document.referrer:
The Document.referrer property returns the URI of the page that linked to this page.
Inside an <iframe>, the Document.referrer will initially be set to the same value as the href of the parent window's Window.location.
This means it will work on initial load just fine, as you've experienced.
As far as I can tell, the specification isn't explicit about how to handle reloading. This is probably the cause of the differences in browser behaviour. It isn't too crazy to think that is should be empty after a reload, as it wasn't loaded from the parent page that time around.
An alternate solution would be to use window.parent.location.href, which always refers to the URL of the iframe's parent window (read more in Difference between document.referrer and window.parent.location.href).
Your line of code could look something like this:
// if parent and child href are equal, using either yields the same result
// if there is no parent, window.parent will be equal to window
// therefore, the conditional statement isn't necessary
let url = window.parent.location.href;
I have a site which has a media player embedded inside an iframe. The media player and the site are on the same domain, preventing cross-origin issues. Each page, the main page as well as the media player page, have a bit of code which finds the height and width of any parent iframe:
var height = $(parent.window).height();
var width = $(parent.window).width();
No problems so far....until:
A client wants to embed my site inside an iframe on his own site. His site is on a different domain. Now, my iframe is inside another iframe and my code is throwing cross-origin errors.
The following does not throw errors:
var test1 = parent.window; // returns my site
var test2 = window.top; // returns client site
The following does throw cross-origin errors:
var test3 = parent.window.document;
var test4 = $(parent.window);
var test5 = window.top.document;
var test6 = $(window.top);
How do I get the height of the iframe on my domain without the cross-origin errors? I'm hoping for a pure javascript/jQuery solution.
Options which will not work for my solution are:
Using document.domain to white list the site.
Modifying the web.config to white list the site.
Like in Inception, I must go deeper. Please help.
You will need to use Javascript's messager. First, you need to define a function like this:
function myReceiver(event) {
//Do something
}
Then you need an event listener:
window.addEventListener("message", myReceiver);
You will need something like this on both sides. Now, you can send a message like this to the iframe:
innerWindow.contentWindow.postMessage({ message: {ResponseKey: "your response key", info1: "something1", info2: "something2"}}, innerWindow.src)
and this is how you can send a message to the parent:
window.parent.postMessage({ message: {ResponseKey: "your response key", info1: "something1", info2: "something2"}}, myorigin);
The only missing item in the puzzle is myorigin. You will be able to find it out in your iframe using event.origin || event.originalEvent.origin in the message receiver event.
However, the pages using your site in their pages inside an iframe will have to include a Javascript library which will handle the communication you need. I know how painful is this research, I have spent days when I have done it before to find out the answer.
Your code is running from the iframe in the middle of the parent and the child window. So, anytime you call
window.parent
and your site is embedded inside an iframe and the parent is a different domain (Same origin policy), an error will be thrown. I would recommend first checking if the parent is the same origin. You need to wrap this check in a try catch.
NOTE: Most browsers, but not Edge, will not throw an error if the parent is http://localhost:xxx and the iframe is http://localhost:zzz where xxx is a different port number than zzz. So, you also need to manually check the origins match by comparing the protocol, domain, and port.
var isEmbeddedInCrossOriginIframe = false;
try {
var originSelf = (window.self.location.protocol + '//' +
window.self.location.hostname +
(window.self.location.port ? ':' +
window.self.location.port : '')).toLowerCase();
var originParentOrSelf = (window.parent.location.protocol + '//' +
window.parent.location.hostname +
(window.parent.location.port ? ':' +
window.parent.location.port : '')).toLowerCase();
isEmbeddedInCrossOriginIframe = originSelf != originParentOrSelf;
}
catch(err) {
isEmbeddedInCrossOriginIframe = true;
//console.log(err);
}
Your solution will then be:
var height = $(isEmbeddedInCrossOriginIframe ? window : parent.window)
.height();
var width = $(isEmbeddedInCrossOriginIframe ? window : parent.window)
.width();
I have a component within AEM (Adobe Experience Manager - a cms) on a page and I want to include this page onto another page (from a different domain) using an iframe. So in the code for the component I am using window.postMessage() and I'm trying to listen to that event in the parent. I have tried communicating the other way, parent to iframe and it worked fine, but I need to communicate the other way. So the component is a search component and when you click on a search result I want to redirect but from the parent window so I'm trying to send the URL to redirect to and then handle the redirection within the parent's JS code.
The code looks like:
(From the parent - html)
<iframe
width="1080"
height="700"
id="theFrame"
src="http://localhost:4502/content/zebra1/global/en_us/hey.html#q=print"
frameborder="0">
</iframe>
(From the parent - js)
function receiveMessage(e)
{
var key = e.message ? "message" : "data";
var data = e[key];
var redirect = JSON.parse(data);
redirectUrl = (redirect.origin ? redirect.origin : '') + (redirect.url ?
redirect.url : '');
if (redirectUrl) {
window.location.href = redirectUrl;
}
}
window.addEventListener("message", receiveMessage, false);
(From the iframe/child - js)
goToSearchResults : function( event ){
var windowOrigin = location.origin;
if( arguments[0].length == 3){
var redirect = {
origin: windowOrigin,
url: arguments[0][1].url || ''
};
if(!$('#supportSearchWrap').data('iframe')) {
location.replace(redirect.url);
} else {
window.postMessage(JSON.stringify(redirect), windowOrigin);
}
}
logger.log( redirect.origin + redirect.url , this.model );
}
It's not working for me. Does anyone see what I'm doing wrong or a better way to do this?
window.postMessage - The window refers to the instance of the window object to which you're posting your message. In your case, it should be the parent of the iframe window.
You can get that reference inside the iframe using window.parent or simply parent.
Also, the targetOrigin property should match the targeted window properties. From MDN docs, it is as below.
targetOrigin
Specifies what the origin of otherWindow must be for the event to be dispatched, either as the literal string "*" (indicating no preference) or as a URI. 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 dispatched; only if all three match will the event be dispatched.
I tried:
var sub_window = window.open(url);
sub_window.onunload = function(){
console.log("sub window closed");
};
but it didn't work...
It will not work for remote urls, because you do not have permission to interact with those.
It will work just fine for local urls, but you need to give a delay, so that the event gets applied to the actual opened page.
var w = window.open(url);
setTimeout(function(){
w.onunload = function(){alert('done');};
},1000);
Update
Answering to your comment,
The same origin policy wikipedia does not allow this, but just for this case of subdomains you might be able to override it wikipedia by setting the document.domain to 'xxx.com' (if you have control of both pages)
Inside a facebook application I need to check what is the top frame (the main window) URL, and show content accordingly.
I tried using the following:
if (top.location.toString().toLowerCase().indexOf("facebook.com") <0) { ... }
Which works well if the page is not inside an iframe, but when the page is loaded within an iframe (as it does when used as facebook application) the code generates
"Uncaught TypeError: Property
'toString' of object # is not a
function".
Is there any way I can fix this code (with cross-browser compatibility - maybe with jQuery)?
Thanks!
Joel
It is true that cross origin concerns will prevent you from accessing this top window location. However, if you just want the parent window location of the iframe you can get at it via the document.referrer string.
Within your iframe you'd grab the url:
var parentURL = document.referrer
https://developer.mozilla.org/en-US/docs/Web/API/document.referrer
I've used this successfully in my own iframe apps. Also, be aware that if you navigate within your iframe the referrer will change.
Nicholas Zakas has a write-up on his blog:
http://www.nczonline.net/blog/2013/04/16/getting-the-url-of-an-iframes-parent/
The problem you are having that you are not allowed to access top.location across different document domains.
This is a security feature built in to browsers.
Read up on XSS and why the security precautions are in place :)
You can also learn a great deal by reading about the same origin policy
With Martin Jespersen adviced fix, I could check address in iFrame and standart top address:
//this is fix for IE
if (!window.location.origin) {
window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
}
//and here we will get object of address
var urls = (window.location != window.parent.location) ? document.referrer: document.location;
//Martins adviced fix for checking if You are not in iFrame
if (window.top === window) {
urls = urls.origin;
}
//and now indexOf works in both ways - for iFrame and standart top address
if (urls.indexOf("facebook.com") != -1 ) {
//do stuff
}
This could work:
if (self!=top && document.referrer.toLowerCase().indexOf("facebook.com") <0) { ... }
...as long as you don't navigate inside the frame.
But it's not really a good solution ^^
If you need as much information as possible about the top page location:
function getTopLinkInfo() {
var topLinkInfo = {};
try {
// Only for same origins
topLinkInfo.topHref = top.location.href;
}
// Security exception: different origins
catch (error) {
try {
var ancestorOrigins = window.location.ancestorOrigins;
// Firefox doesn't support window.location.ancestorOrigins
if (ancestorOrigins) {
topLinkInfo.parentsDomains = [];
for (var i = 0; i < ancestorOrigins.length; i++) {
topLinkInfo.parentsDomains.unshift(ancestorOrigins[i]);
}
}
// Sometimes referrer refers to the parent URL (but not always,
// e.g. after iframe redirects).
var bottomW = window;
var topW = window.parent;
topLinkInfo.parentsReferrers = [];
// In case of empty referrers
topLinkInfo.parentsHrefs = [];
while (topW !== bottomW) {
topLinkInfo.parentsReferrers.unshift(bottomW.document.referrer);
topLinkInfo.parentsHrefs.unshift(bottomW.location.href);
bottomW = bottomW.parent;
topW = topW.parent;
}
} catch (error) {/* Permission denied to access a cross-origin frame */}
}
return topLinkInfo;
}
var result = getTopLinkInfo();
console.table(result);
console.info(result);