Getting URL of the top frame - javascript

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);

Related

javascript - Getting the iframe's URL after reload of iframe content doesn't work in Chrome and Safari

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 am trying to show a div conditionally, only if the page is framed on a domain besides my own

example domain : mydomain.com
if(location.hostname.indexOf("mydomain") >= 0){
$("#load").css({display: "none"});
}
this is currently working to hide the div, but even on a different domain it is hiding the div, which i think may be caused by the iframe is still mydomain? I asked this before and it got deleted, but i got a response which was sort of like the above code. i changed it to get the hostname, since window.parent.location would show the div still for some people on my website. i also tried with an else statement to display block, which didn't render any different result. thanks for your time.
You should be able to get the top url using the document location and referrer; so your final code would look something like this:
var url = (window.location != window.parent.location) ? document.referrer : document.location.href;
if(url.indexOf("mydomain") >= 0){
$("#load").css({display: "none"});
}
Edit:
To get the hostname from document.referrer, it may be necessary to do some parsing:
function getHost(fullURL){
return fullURL.split('/')[2]
}
//Usage:
var url = (window.location != window.parent.location) ? getHost(document.referrer) : document.location.href;
Well location.hostName will be your page in the iframe, you are not looking at the parent page's host name. So you would need to look at the parent. So the fun thing is, you can not read the parent's location because of the same origin policy.
So what can you do?
var isInIframe = window.self !== window.top;
var isDifferent = false;
try { // different domain is going to throw an error when you read it
var foo = window.top.location.href
} catch (e) {
isDifferent = true
}
var showIt = isInIframe && isDifferent

Get the height of a same domain iframe when that iframe is inside a different domain iframe?

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();

How to determine if JS code executes in iframe? [duplicate]

I am writing an iframe based facebook app. Now I want to use the same html page to render the normal website as well as the canvas page within facebook. I want to know if I can determine whether the page has been loaded inside the iframe or directly in the browser?
Browsers can block access to window.top due to same origin policy. IE bugs also take place. Here's the working code:
function inIframe () {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
top and self are both window objects (along with parent), so you're seeing if your window is the top window.
When in an iframe on the same origin as the parent, the window.frameElement method returns the element (e.g. iframe or object) in which the window is embedded. Otherwise, if browsing in a top-level context, or if the parent and the child frame have different origins, it will evaluate to null.
window.frameElement
? 'embedded in iframe or object'
: 'not embedded or cross-origin'
This is an HTML Standard with basic support in all modern browsers.
if ( window !== window.parent )
{
// The page is in an iframe
}
else
{
// The page is not in an iframe
}
I'm not sure how this example works for older Web browsers but I use this for IE, Firefox and Chrome without an issue:
var iFrameDetection = (window === window.parent) ? false : true;
RoBorg is correct, but I wanted to add a side note.
In IE7/IE8 when Microsoft added Tabs to their browser they broke one thing that will cause havoc with your JS if you are not careful.
Imagine this page layout:
MainPage.html
IframedPage1.html (named "foo")
IframedPage2.html (named "bar")
IframedPage3.html (named "baz")
Now in frame "baz" you click a link (no target, loads in the "baz" frame) it works fine.
If the page that gets loaded, lets call it special.html, uses JS to check if "it" has a parent frame named "bar" it will return true (expected).
Now lets say that the special.html page when it loads, checks the parent frame (for existence and its name, and if it is "bar" it reloads itself in the bar frame. e.g.
if(window.parent && window.parent.name == 'bar'){
window.parent.location = self.location;
}
So far so good. Now comes the bug.
Lets say instead of clicking on the original link like normal, and loading the special.html page in the "baz" frame, you middle-clicked it or chose to open it in a new Tab.
When that new tab loads (with no parent frames at all!) IE will enter an endless loop of page loading! because IE "copies over" the frame structure in JavaScript such that the new tab DOES have a parent, and that parent HAS the name "bar".
The good news, is that checking:
if(self == top){
//this returns true!
}
in that new tab does return true, and thus you can test for this odd condition.
The accepted answer didn't work for me inside the content script of a Firefox 6.0 Extension (Addon-SDK 1.0): Firefox executes the content script in each: the top-level window and in all iframes.
Inside the content script I get the following results:
(window !== window.top) : false
(window.self !== window.top) : true
The strange thing about this output is that it's always the same regardless whether the code is run inside an iframe or the top-level window.
On the other hand Google Chrome seems to execute my content script only once within the top-level window, so the above wouldn't work at all.
What finally worked for me in a content script in both browsers is this:
console.log(window.frames.length + ':' + parent.frames.length);
Without iframes this prints 0:0, in a top-level window containing one frame it prints 1:1, and in the only iframe of a document it prints 0:1.
This allows my extension to determine in both browsers if there are any iframes present, and additionally in Firefox if it is run inside one of the iframes.
I'm using this:
var isIframe = (self.frameElement && (self.frameElement+"").indexOf("HTMLIFrameElement") > -1);
Use this javascript function as an example on how to accomplish this.
function isNoIframeOrIframeInMyHost() {
// Validation: it must be loaded as the top page, or if it is loaded in an iframe
// then it must be embedded in my own domain.
// Info: IF top.location.href is not accessible THEN it is embedded in an iframe
// and the domains are different.
var myresult = true;
try {
var tophref = top.location.href;
var tophostname = top.location.hostname.toString();
var myhref = location.href;
if (tophref === myhref) {
myresult = true;
} else if (tophostname !== "www.yourdomain.com") {
myresult = false;
}
} catch (error) {
// error is a permission error that top.location.href is not accessible
// (which means parent domain <> iframe domain)!
myresult = false;
}
return myresult;
}
Best-for-now Legacy Browser Frame Breaking Script
The other solutions did not worked for me. This one works on all browsers:
One way to defend against clickjacking is to include a "frame-breaker" script in each page that should not be framed. The following methodology will prevent a webpage from being framed even in legacy browsers, that do not support the X-Frame-Options-Header.
In the document HEAD element, add the following:
<style id="antiClickjack">body{display:none !important;}</style>
First apply an ID to the style element itself:
<script type="text/javascript">
if (self === top) {
var antiClickjack = document.getElementById("antiClickjack");
antiClickjack.parentNode.removeChild(antiClickjack);
} else {
top.location = self.location;
}
</script>
This way, everything can be in the document HEAD and you only need one method/taglib in your API.
Reference: https://www.codemagi.com/blog/post/194
I actually used to check window.parent and it worked for me, but lately window is a cyclic object and always has a parent key, iframe or no iframe.
As the comments suggest hard comparing with window.parent works. Not sure if this will work if iframe is exactly the same webpage as parent.
window === window.parent;
Since you are asking in the context of a facebook app, you might want to consider detecting this at the server when the initial request is made. Facebook will pass along a bunch of querystring data including the fb_sig_user key if it is called from an iframe.
Since you probably need to check and use this data anyway in your app, use it to determine the the appropriate context to render.
function amiLoadedInIFrame() {
try {
// Introduce a new propery in window.top
window.top.dummyAttribute = true;
// If window.dummyAttribute is there.. then window and window.top are same intances
return !window.dummyAttribute;
} catch(e) {
// Exception will be raised when the top is in different domain
return true;
}
}
Following on what #magnoz was saying, here is a code implementation of his answer.
constructor() {
let windowLen = window.frames.length;
let parentLen = parent.frames.length;
if (windowLen == 0 && parentLen >= 1) {
this.isInIframe = true
console.log('Is in Iframe!')
} else {
console.log('Is in main window!')
}
}
It's an ancient piece of code that I've used a few times:
if (parent.location.href == self.location.href) {
window.location.href = 'https://www.facebook.com/pagename?v=app_1357902468';
}
If you want to know if the user is accessing your app from facebook page tab or canvas check for the Signed Request. If you don't get it, probably the user is not accessing from facebook.
To make sure confirm the signed_request fields structure and fields content.
With the php-sdk you can get the Signed Request like this:
$signed_request = $facebook->getSignedRequest();
You can read more about Signed Request here:
https://developers.facebook.com/docs/reference/php/facebook-getSignedRequest/
and here:
https://developers.facebook.com/docs/reference/login/signed-request/
This ended being the simplest solution for me.
<p id="demofsdfsdfs"></p>
<script>
if(window.self !== window.top) {
//run this code if in an iframe
document.getElementById("demofsdfsdfs").innerHTML = "in frame";
}else{
//run code if not in an iframe
document.getElementById("demofsdfsdfs").innerHTML = "no frame";
}
</script>
if (window.frames.length != parent.frames.length) { page loaded in iframe }
But only if number of iframes differs in your page and page who are loading you in iframe. Make no iframe in your page to have 100% guarantee of result of this code
Write this javascript in each page
if (self == top)
{ window.location = "Home.aspx"; }
Then it will automatically redirects to home page.

How can I determine if the document.referrer is from my own site?

Each time a page is requested I get the referrer of the page it came from. I need to track just referrer from other sites, I don't want to track going from one page to another within my site. How can I do that?
document.referrer.indexOf(location.protocol + "//" + location.host) === 0;
Originally posted at JavaScript - Am I the Referrer?
When someone comes to our website for the first time, we store the referrer in a cookie. This way, if they download our demo, we can get the original referrer from the cookie and we learn what sites are effective in driving leads to us.
Of course, every subsequent page a visitor hits on our website will show the referrer as our website. We don't want those. What we first did to avoid this was look for the text "windward" in the referrer and if so, assume that was from our site. The problem with this is we found a lot of referrer urls now have windward in them, either as a search term or part of a url that talks about Windward. (This is good news, it means we are now a well known product.)
So that brought me to our most recent approach. This should work for any site and should only reject referrers from the same site.
function IsReferredFromMe()
{
var ref = document.referrer;
if ((ref == null) || (ref.length == 0)) {
return false;
}
if (ref.indexOf("http://") == 0) {
ref = ref.substring(7);
}
ref = ref.toLowerCase();
var myDomain = document.domain;
if ((myDomain == null) || (myDomain.length == 0)) {
return false;
}
if (myDomain.indexOf("http://") == 0) {
myDomain = myDomain.substring(7);
}
myDomain = myDomain.toLowerCase();
return ref.indexOf(myDomain) == 0;
}
Solutions presented works in case of no sub domain in website in case of sub domain is there then we have to check just before the domain itself if any sub domains presented:
document.referrer.replace("http://", '').replace("https://", '').split('/')[0].match(new RegExp(".*" +location.host.replace("www.", '')))
this solution will add .* before the domain to detect that sub domain is from same domain.
If pages of “the same website” you think have the same origin (the same protocol, host, and port.),
check it this way:
function the_referrer_has_the_same_origin() {
try {
const referrer = new URL(document.referrer);
return (referrer.origin === location.origin);
} catch(invalid_url_error) {
return false;
}
}
// Works as intended for `https://www.google.com` and `https://www.google.com:443`.
.
If you’d like a short one and not to consider unlikely situations, try this:
document.referrer.startsWith(location.origin)
// Fails for `https://www.google.com` and `https://www.google.com:443`.
.
document.referrer.includes(location.host);

Categories

Resources