I have a script meant to be included by third party sites that creates an iframe (on our origin). Something like this:
// this script exists on the *host site*
const iframe = document.createElement("iframe");
iframe.src = "https://us.com/iframe";
parent.appendChild(iframe);
The script exposes an API which internally communicates with the iframe through postMessage, something like:
// This script exists on the *host site*
function getSomeProperty()
{
return new Promise(function (resolve, reject)
{
theIFrame.postMessage("getSomeProperty", "us.com")
window.addEventListener("message", function (event)
{
// ... get the response, making sure its from us.com, etc.
// This is just pseudo code, we don't create a new listener
// on every call in our real code.
resolve(answer);
}
});
}
All this is fine, my question is with regard to the iframe's corresponding "message" listener. Specifically, I want to only respect requests from the creating window/origin. In other words, if some parallel iframe on a separate origin (say, an ad) constructs a postMessage to us, we want to of course ignore this. My question is whether it is sufficient to simply check whether the sending window is window.parent:
const parent = window.parent;
// This event handler is in the *embedded iframe*
window.addEventListener("message", function (event)
{
// This is being sent from a window other than
// the one that created us, bail!
if (event.window !== parent)
return;
// it is safe to respond...
}
As far as I understand it, the only way that this would not be a sufficient check is if it was possible for window.parent to change origins while simultaneously keeping us around. I can imagine scenarios where an iframe could be removed from one host and appendChild'ed onto another host, but I believe this could only happen if the DOM manipulator was on the same origin (and thus have access to said DOM) and placing us into another window of the same origin too.
Currently, we employ an extra paranoid defense of passing the original origin to the iframe through the query string as such:
const iframe = document.createElement("iframe");
iframe.src = `https://us.com/iframe?location=${window.location.href}`;
Thus allowing us to check that window === window.parent AND that origin === originalOrigin. However, we'd really like to move away from this model since it necessarily breaks caching across different sites (as each one generates a different src URL due to the different query string). So, is it safe for us to move to solely checking window === window.parent?
When using the postMessage API, the only truly safe way to ensure messages are coming from where they say is to use a strict targetOrigin. This way you can ensure that messages are coming from a window you control.
You're right in saying that this condition would prevent incoming messages other than from the window that created them. However, an iFrame with src="https://us.com/iframe"; could still be created on any origin. What if it was created on a malicious page? This would still satisfy the condition such that window.parent === parent is true.
Related
I'm using MessageChannel() to pass messages between a page and iframe. In my scenario, the iframe is the communications initiator and the page containing it receives, transforms and responds back to the iframe.
As I was implementing the system I at first took and saved a reference to the port passed to the iframe, cached it and continue to use it for each subsequent communication.
iframe:
window.onmessage = (e) => {
console.log("iframe port established");
//using this port for all following communications
parentPort = e.ports[0];
onmessage = establishedConnectionHandler;
}
I'm running all subsequent communications from the iframe to the parent through parentPort:
parentPort.postMessage({command, guid, message});
even though the docs state that the message channel is a one-shot communication this appears to work and makes initiating communications convenient.
My question - is this supported functionality or am I exploiting undefined behavior?
Here is the source.
Edit - I must have misunderstood the intent of the example MDN:
button.onclick = function(e) {
e.preventDefault();
var channel = new MessageChannel();
otherWindow.postMessage(textInput.value, '*', [channel.port2]);
channel.port1.onmessage = handleMessage;
function handleMessage(e) {
para.innerHTML = e.data;
textInput.value = '';
}
}
This is refactored in Kaiido's Plunker example.
That's not really clear what you are doing, even reading your code on github...
You seem to be confusing the WindowObject.postMessage method and the MessagePort's one. WindowObject's one should be used only once, at the negotiation part.
So let's take a step back to explain more basically how things should be understood:
You should think of message channels as a Yoghurt-pot Telephone® [pdf].
––––– –––––
po(r)t1 |~~~~~~~~~~~~~~~~~~~~~~~| po(r)t2
––––– –––––
One user has to create it.
Then he will give (transfer) one of the po(r)ts to the other user.
Once this is done, each user has only access to its own po(r)t.
So to be able to receive messages from the other user, they have to put their ear on their own po(r)t (attach an event handler).
And to send messages, they will say the message (postMessage) inside the only po(r)t they still have, the same they are listening to.
So to add some lines of code, what you should do is:
Generate The Yoghurt-pot telephone® a.k.a MessageChannel.
var yoghurt_phone = new MessageChannel();
Keep one of the po(r)t and give the other one to the other user (iframe). To do this, we use the WindowObject.postMessage method, which is not the same as the one we'll use to communicate through the MessagePorts.
mains_yoghurt_pot = yoghurt_phone.port1;
frame.contentWindow.postMessage( // this is like a physical meeting
'look I made a cool Yoghurt-phone', // some useless message
'*', // show your id?
[yoghurt_phone.port2] // TRANSFER the po(r)t
);
From the frame, receive the po(r)t and keep it tight.
window.onmessage = function physicalMeeting(evt) {
if(evt.ports && evt.ports.length) { // only if we have been given a po(r)t
frames_yoghurt_pot = evt.ports[0];
// ...
From now on, each user has its own po(r)t, and only a single po(r)t. So at both ends, you need to setup listeners on their own single po(r)t.
// from main doc
mains_yoghurt_pot.onmessage = frameTalksToMe;
// from iframe doc
frames_yoghurt_pot.onmessage = mainTalksToMe;
And then when one of the two users wants to tell something to the other one, they'll do from their own po(r)t.
// from main doc
mains_yoghurt_pot.postMessage('hello frame');
// or from iframe doc
frames_yoghurt_pot.postMessage('hello main');
Fixed OP's code as a plunker.
I'm trying to use the JavaScript FullScreen API, using workarounds for current non-standard implementations from here:
https://developer.mozilla.org/en/DOM/Using_full-screen_mode#AutoCompatibilityTable
Sadly, it behaves very erratically. I only care about Chrome (using v17), but since I was having problems I did some tests in Firefox 10 for comparison, results are similar.
The code below attempts to set the browser to fullscreen, sometimes it works, sometimes not. It ALWAYS calls the alert to indicate it is requesting fullscreen. Here's what I've found:
It USUALLY sets fullscreen. It can get to a state where this stops working, but the alert still happens, i.e. it is still requesting FullScreen, but it doesn't work.
It can work if called from a keypress handler (document.onkeypress), but not when called on page loading (window.onload).
My code is as follows:
function DoFullScreen() {
var isInFullScreen = (document.fullScreenElement && document.fullScreenElement !== null) || // alternative standard method
(document.mozFullScreen || document.webkitIsFullScreen);
var docElm = document.documentElement;
if (!isInFullScreen) {
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
}
else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
alert("Mozilla entering fullscreen!");
}
else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
alert("Webkit entering fullscreen!");
}
}
}
requestFullscreen() can not be called automatically is because of security reasons (at least in Chrome). Therefore it can only be called by a user action such as:
click (button, link...)
key (keydown, keypress...)
And if your document is contained in a frame:
allowfullscreen needs to be present on the <iframe> element*
* W3 Spec:
"...To prevent embedded content from going fullscreen only embedded content specifically allowed via the allowfullscreen attribute of the HTML iframe element will be able to go fullscreen. This prevents untrusted content from going fullscreen..."
Read more: W3 Spec on Fullscreen
Also mentioned by #abergmeier, on Firefox your fullscreen request must be executed within 1 second after the user-generated event was fired.
I know this is quite an old question but it is still the top result in Google when searching for FireFox's error message when calling mozRequestFullScreen() from code that wasn't triggered by any user interaction.
Request for full-screen was denied because
Element.mozRequestFullScreen() was not called from inside a short
running user-generated event handler.
As already discussed this is a security setting and therefore is the correct behaviour in normal browser environment (end user machine).
But I am writting an HTML5-based digital signage application which runs under a controlled environment without any user interaction intended. It is vital for my apllication to be able to switch to fullscreen automatically.
Luckily FireFox offers a possibilty to remove this restriction on the browser, which is rather hard to find. I will write it here as future reference for everybody finding this page via the Google search as I did
On the about:config page search for the following key and set it to false
full-screen-api.allow-trusted-requests-only
For my digital signage application I also removed the prompt the browser shows when entering fullscren:
full-screen-api.approval-required
Hopefully this might save someone the hours I wasted to find these settings.
You have nothing wrong with your function. In Firefox, if you call that function directly, it will prevent to for full-screen. As you know, Request for full-screen was denied because docElm.mozRequestFullScreen(); was not called from inside a short running user-generated event handler. So, You have to call the function on event such as onClick in Firefox.
Full Screen Mode
Another unexpected issue with requestFullscreen() is that parent frames need to have the allowfullscreen attribute, otherwise Firefox outputs the following error:
Request for fullscreen was denied because at least one of the document’s containing elements is not an iframe or does not have an “allowfullscreen” attribute.
Aside from iframes, this can be caused by your page being within a frameset frame. Because frameset is deprecated, there is no support for the HTML5 allowfullscreen attribute, and the requestFullscreen() call fails.
The Firefox documentation explicitly states this on MDN, but I think it bears reiterating here, for developers who might not read the documentation first.... ahem
Only elements in the top-level document or in an with the allowfullscreen attribute can be displayed full-screen. This means that elements inside a frame or an object can't.
I realize this is an old post, but in case someone else finds this I'd like to add a few suggestions and sample code.
To help avoid this error...
Failed to execute 'requestFullscreen' on 'Element': API can only be
initiated by a user gesture.
Don't test for the existence of requestFullscreen(), which is a method. Instead, test for the existence of a property like document.fullscreenEnabled.
Also consider the following...
Move your fullscreen check into its own function so you can reuse it.
Make DoFullScreen() reusable by passing the element you want to affect as a parameter.
Use a guard clause at the top of DoFullScreen() to exit out of the function immediately if the window is already in fullscreen mode. This also simplifies the logic.
Set a default value for your DoFullScreen() element parameter to ensure the requestFullscreen() method is always called on an existing element. Defaulting to document.documentElement will probably save you some keystrokes.
// Move your fullscreen check into its own function
function isFullScreen() {
return Boolean(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
// Make DoFullScreen() reusable by passing the element as a parameter
function DoFullScreen(el) {
// Use a guard clause to exit out of the function immediately
if (isFullScreen()) return false;
// Set a default value for your element parameter
if (el === undefined) el = document.documentElement;
// Test for the existence of document.fullscreenEnabled instead of requestFullscreen()
if (document.fullscreenEnabled) {
el.requestFullscreen();
} else if (document.webkitFullscreenEnabled) {
el.webkitRequestFullscreen();
} else if (document.mozFullScreenEnabled) {
el.mozRequestFullScreen();
} else if (document.msFullscreenEnabled) {
el.msRequestFullscreen();
}
}
(function () {
const btnFullscreenContent = document.querySelector(".request-fullscreen-content");
const el = document.querySelector(".fullscreen-content");
// Request the .fullscreen-content element go into fullscreen mode
btnFullscreenContent .addEventListener("click", function (){ DoFullScreen(el) }, false);
const btnFullscreenDocument = document.querySelector(".request-fullscreen-document");
// Request the document.documentElement go into fullscreen mode by not passing element
btnFullscreenDocument .addEventListener("click", function (){ requestFullscreen() }, false);
})();
I'm trying to use the JavaScript FullScreen API, using workarounds for current non-standard implementations from here:
https://developer.mozilla.org/en/DOM/Using_full-screen_mode#AutoCompatibilityTable
Sadly, it behaves very erratically. I only care about Chrome (using v17), but since I was having problems I did some tests in Firefox 10 for comparison, results are similar.
The code below attempts to set the browser to fullscreen, sometimes it works, sometimes not. It ALWAYS calls the alert to indicate it is requesting fullscreen. Here's what I've found:
It USUALLY sets fullscreen. It can get to a state where this stops working, but the alert still happens, i.e. it is still requesting FullScreen, but it doesn't work.
It can work if called from a keypress handler (document.onkeypress), but not when called on page loading (window.onload).
My code is as follows:
function DoFullScreen() {
var isInFullScreen = (document.fullScreenElement && document.fullScreenElement !== null) || // alternative standard method
(document.mozFullScreen || document.webkitIsFullScreen);
var docElm = document.documentElement;
if (!isInFullScreen) {
if (docElm.requestFullscreen) {
docElm.requestFullscreen();
}
else if (docElm.mozRequestFullScreen) {
docElm.mozRequestFullScreen();
alert("Mozilla entering fullscreen!");
}
else if (docElm.webkitRequestFullScreen) {
docElm.webkitRequestFullScreen();
alert("Webkit entering fullscreen!");
}
}
}
requestFullscreen() can not be called automatically is because of security reasons (at least in Chrome). Therefore it can only be called by a user action such as:
click (button, link...)
key (keydown, keypress...)
And if your document is contained in a frame:
allowfullscreen needs to be present on the <iframe> element*
* W3 Spec:
"...To prevent embedded content from going fullscreen only embedded content specifically allowed via the allowfullscreen attribute of the HTML iframe element will be able to go fullscreen. This prevents untrusted content from going fullscreen..."
Read more: W3 Spec on Fullscreen
Also mentioned by #abergmeier, on Firefox your fullscreen request must be executed within 1 second after the user-generated event was fired.
I know this is quite an old question but it is still the top result in Google when searching for FireFox's error message when calling mozRequestFullScreen() from code that wasn't triggered by any user interaction.
Request for full-screen was denied because
Element.mozRequestFullScreen() was not called from inside a short
running user-generated event handler.
As already discussed this is a security setting and therefore is the correct behaviour in normal browser environment (end user machine).
But I am writting an HTML5-based digital signage application which runs under a controlled environment without any user interaction intended. It is vital for my apllication to be able to switch to fullscreen automatically.
Luckily FireFox offers a possibilty to remove this restriction on the browser, which is rather hard to find. I will write it here as future reference for everybody finding this page via the Google search as I did
On the about:config page search for the following key and set it to false
full-screen-api.allow-trusted-requests-only
For my digital signage application I also removed the prompt the browser shows when entering fullscren:
full-screen-api.approval-required
Hopefully this might save someone the hours I wasted to find these settings.
You have nothing wrong with your function. In Firefox, if you call that function directly, it will prevent to for full-screen. As you know, Request for full-screen was denied because docElm.mozRequestFullScreen(); was not called from inside a short running user-generated event handler. So, You have to call the function on event such as onClick in Firefox.
Full Screen Mode
Another unexpected issue with requestFullscreen() is that parent frames need to have the allowfullscreen attribute, otherwise Firefox outputs the following error:
Request for fullscreen was denied because at least one of the document’s containing elements is not an iframe or does not have an “allowfullscreen” attribute.
Aside from iframes, this can be caused by your page being within a frameset frame. Because frameset is deprecated, there is no support for the HTML5 allowfullscreen attribute, and the requestFullscreen() call fails.
The Firefox documentation explicitly states this on MDN, but I think it bears reiterating here, for developers who might not read the documentation first.... ahem
Only elements in the top-level document or in an with the allowfullscreen attribute can be displayed full-screen. This means that elements inside a frame or an object can't.
I realize this is an old post, but in case someone else finds this I'd like to add a few suggestions and sample code.
To help avoid this error...
Failed to execute 'requestFullscreen' on 'Element': API can only be
initiated by a user gesture.
Don't test for the existence of requestFullscreen(), which is a method. Instead, test for the existence of a property like document.fullscreenEnabled.
Also consider the following...
Move your fullscreen check into its own function so you can reuse it.
Make DoFullScreen() reusable by passing the element you want to affect as a parameter.
Use a guard clause at the top of DoFullScreen() to exit out of the function immediately if the window is already in fullscreen mode. This also simplifies the logic.
Set a default value for your DoFullScreen() element parameter to ensure the requestFullscreen() method is always called on an existing element. Defaulting to document.documentElement will probably save you some keystrokes.
// Move your fullscreen check into its own function
function isFullScreen() {
return Boolean(
document.fullscreenElement ||
document.webkitFullscreenElement ||
document.mozFullScreenElement ||
document.msFullscreenElement
);
}
// Make DoFullScreen() reusable by passing the element as a parameter
function DoFullScreen(el) {
// Use a guard clause to exit out of the function immediately
if (isFullScreen()) return false;
// Set a default value for your element parameter
if (el === undefined) el = document.documentElement;
// Test for the existence of document.fullscreenEnabled instead of requestFullscreen()
if (document.fullscreenEnabled) {
el.requestFullscreen();
} else if (document.webkitFullscreenEnabled) {
el.webkitRequestFullscreen();
} else if (document.mozFullScreenEnabled) {
el.mozRequestFullScreen();
} else if (document.msFullscreenEnabled) {
el.msRequestFullscreen();
}
}
(function () {
const btnFullscreenContent = document.querySelector(".request-fullscreen-content");
const el = document.querySelector(".fullscreen-content");
// Request the .fullscreen-content element go into fullscreen mode
btnFullscreenContent .addEventListener("click", function (){ DoFullScreen(el) }, false);
const btnFullscreenDocument = document.querySelector(".request-fullscreen-document");
// Request the document.documentElement go into fullscreen mode by not passing element
btnFullscreenDocument .addEventListener("click", function (){ requestFullscreen() }, false);
})();
This is WebKit browsers specific (meaning that I only need to make it work in WebKit specific, i.e. iOS/Android browsers, but I'm testing in Chrome).
I have a page. The page loads one or more iframes, with contents from another domain. I need to receive messages (using postMessage()) from these iframes, and I need to be able to identify which iframe a specific message came from.
I can't find a way to do that that does not involve throwing something the iframe URL that the iframe contents then can pass back to me. I would like to not have to meddle with the URL, as there is no guarantee I can safely do that (redirects can throw the parameters out, for example).
I tried something that I thought was reasonable. When I create the iframe element (it's done from J/S), I associated a property with the element, let's say 'shared_secret'. When I get the message event back from the frame, I tried to locating the element that the calling frame was created with, and reading that property.
function onMessage(evt) {
var callerId = evt.source.frameElement.shared_secret;
// ....
}
window.addEventListener(message, onMessage);
var frameEl = document.createElement('iframe');
frameEl.shared_secret = 'sommething blue';
frameEl.src = 'http://aliens.com/my.html';
somewhereInMyDoc.appendChild(frameEl);
When the frame loads, it will run:
window.parent.postMessage('do you know who I am?', '*');
However, frameElement turns out undefined in the above onMessage(). I guess for the security reasons, it does work perfectly when the parent/child are from the same domain.
And it's actually ironic. Parent window can not access event.source.frameElement because event.source is an alien window. iFrame window can not call window.frameElement, because frameElement is in an alien window. So nobody can get access to it.
So, is there something that I can use as a token that I can set on a newly loaded frame, and somehow get back?
Thank you.
For people looking for some code, here is what I used to find the iframe who sent the message :
/**
* Returns the iframe corresponding to a message event or null if not found.
*
* 'e' is the event object
*/
function getFrameTarget (e) {
var frames = document.getElementsByTagName('iframe'),
frameId = 0,
framesLength = frames.length;
for (; frameId < framesLength; frameId++) {
if (frames[frameId].contentWindow === e.source) {
return frames[frameId];
}
}
return null;
}
Thanks to Pawel Veselov and DMoses !
This should be credited to https://stackoverflow.com/users/695461/dmoses.
You can actually compare the content window object of the frame element to the event.source of the message event, and the comparison will yield TRUE if they are, in fact, the same.
So, to solve my particular problem, I'll need to keep the list of frame elements that I've created (sprinkling them, if needed, with whatever additional properties), and when the event comes in, iterating through all, looking for one that has its contentWindow property equal to the event.source property.
UPDATE
We did, through encountering some nasty bugs, also found out that you should put an 'id' on the iframe created from within the parent window. Especially if that window is itself in an iframe. Otherwise, certain (Android 4.x being known for sure) browsers will yield true comparison even if the message is being received from a completely different child frame.
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.