I'm working on a website with cross-domain iframes that are resized to the correct height using postMessage. The only problem I'm having is identifying which iframe has which height. The way I've currently got it set up is that when one iframe sends its height to the parent, all the iframes' heights are changed.
Parent:
var eventMethod = window.addEventListener ? "addEventListener" : "attachEvent";
var eventer = window[eventMethod];
var messageEvent = eventMethod == "attachEvent" ? "onmessage" : "message";
eventer(messageEvent, function(e) {
$('iframe').height(e.data);
}, false);
Iframe:
var updateHeight = function() {
if(window.parent) {
window.parent.postMessage($('.widget').outerHeight(), '*');
}
};
Is there some way to identify which iframe sent the message event?
Yes, you can identify the IFRAME which did the postMessage. There are different situations:
the source IFRAME has the same-origin URL (e.g. http://example.com/) as the Window which receives the message: the IFRAME is identified using
myIFRAME.contentWindow == event.source
the source IFRAME has a same-origin but relative URL (e.g. /myApp/myPage.html) to the parent HTML page: the IFRAME is identified using
myIFRAME.contentWindow == event.source.parent
the source IFRAME has a cross-origin URL (e.g. http://example.com/) different of the page which receives the message (e.g http://example.org/): the above methods do not work (the comparison is always false and accessing properties of event.source lead to Access Deniederrors) and the IFRAME must be identified based on its origin domain;
myIFRAME.src.indexOf(event.origin)==0
In order to manage these three different situations, I'm using the following:
var sourceFrame = null; // this is the IFRAME which send the postMessage
var myFrames = document.getElementsByTagName("IFRAME");
var eventSource = event.source; // event is the event raised by the postMessage
var eventOrigin = event.origin; // origin domain, e.g. http://example.com
// detect the source for IFRAMEs with same-origin URL
for (var i=0; i<myFrames.length; i++) {
var f = myFrames[i];
if (f.contentWindow==eventSource || // for absolute URLs
f.contentWindow==eventSource.parent) { // for relative URLs
sourceFrame = f;
break;
}
}
// detect the source for IFRAMEs with cross-origin URL (because accessing/comparing event.source properties is not allowed for cross-origin URL)
if (sourceFrame==null) {
for (var i=0; i<myFrames.length; i++) {
if (myFrames[i].src.indexOf(eventOrigin)==0) {
sourceFrame = myFrames[i];
break;
}
}
}
For cross-domain URLs, note that we cannot differentiate the true source if event.origin is a domain common to more than one IFRAMEs.
Some people use === instead of == but I did not found any difference in this context, so I'm using the shortest comparator.
This implementation has been tested and works under:
MSIE 9
Firefox 17
As an alternative (suggested by Griffin), you could use a IFRAME src with an unique idenfitier (e.g. timestamp), and the IFRAME'd web application will send back the this unique identifier in the posted messages. While the IFRAME identification would be simpler, this approach requires to modify the IFRAME'd web application (which is not always possible). This also may lead to security issues (e.g. the IFRAME'd web application tries to guess the unique identifier of other IFRAMEs applications).
I have found the solution from here: How to share a data between a window and a frame in JavaScript
Parent:
var frames = document.getElementsByTagName('iframe');
for (var i = 0; i < frames.length; i++) {
if (frames[i].contentWindow === event.source) {
$(frames[i]).height(event.data); //the height sent from iframe
break;
}
}
i have an idea to solve this issue. when you create the iframe give a name/id to the iframe. .
and, in the script inside iframe send the message as object which will look like
window.parent.postMessage({"height" : $('.widget').outerHeight(), "frmname" : window.name}, '*');
in the parent listener,
eventer(messageEvent, function(e) {`enter code here`
$(e.data.frmname).height(e.data.height);
}, false);
The following works for me cross-origin:
window.addEventListener('message', function (event) {
if (event.data.size) {
Array.prototype.forEach.call(document.getElementsByTagName('iframe'), function (element) {
if (element.contentWindow === event.source) {
element.style.height = `${event.data.size.height}px`;
}
});
}
}, false);
Tested in Chromium 64 and Firefox 59.
If the source iframe is nested in more than one parent iframe, then you will need to recurse over the window.frames property of each iframe and compare it against the messageEvent#source property.
For example, if the message is generated by iframe#level3 of this Dom.
<iframe Id=level1>
<iframe Id=level2>
<iframe Id=level3 />
</iframe>
</iframe>
You should be able to find the index of the ancestor iframe in the current window using.
FindMe = event.source
FrameIndex = find(window)
frames[FrameIndex].frameElement == getElByTagName(iframe)[FrameIndex]
function find(target){
for (i=0; I< target.frames.length; i ++)
if(target.frames[i] == FindMe || find(target.frames[i]))
return i
return false
}
Its important to note that
The Window.frames property is not restricted by cross domain policy
This technique will work regardless of how deeply nested the source iframe happens to be
window.frames is a collection of window objects not iframe elements.
access to the propetties s of the memebers of the window.frames collection is resttictred by Same Origin (I.e you may not be able to access the frameElement or location properties of window.frames[i]
The event should also have a property "source" that can be compared to the iframes "contentWindow" property.
Related
I want to get the URL from an iframe when the user redirects by clicking links in the iframe. The source of the iframe is not the same as the web application.
For example:
<iframe src="startingUrl" class="embed-responsive-item" id="iframe" sandbox="" allowfullscreen</iframe>
I add a load listener on the iframe to detect when the user redirects to other urls in this iframe:
const iframe = document.getElementById("iframe");
iframe.addEventListener("load", (evt) => {
const location = iframe.contentWindow.location;
console.log(location); // this gives me a Location object where I can see the href property
console.log(location.href); // this gives me a SecurityError: Permission denied to get property "href" on cross-origin object, I also tried to get a copy of the object but that doesn't work either.
});
I know what causes this problem and I also know it is not possible. But I need to find a way to get the current URL of the page. If this is a no go then I want that the user who uses this web application can copy the url of the iframe and put it in an input field.
Now they can do "View frame source" in chrome and This frame: view frame source or info in Firefox. But this is too complicated for the user. Is there a way they can see the URL in the iFrame or a way for the user to get the URL simpler.
The site in the iFrame is not mine.
All help is much appreciated!
Short answer: This is a no go, unless you have the support of the other site in your iframe and they are willing to add the code in #๋ฐ์์ answer.
Longer answer: You could set up a proxy server to inject the required code to make this work, but then you will run into legal and ethical difficulties, so I am not going to explain how to do that in depth.
Another approach might be to create a browser extension and have your users install that. Again I should point out FaceBook has in the past ran into ethical difficulties taking this approach.
Ultimately their are very good security reasons why the browser stops you doing this and you should probably respect those reasons and not do it.
If you don't see the code below, check the link below.
console.log(iframe.src);
Check out this link
SecurityError: Blocked a frame with origin from accessing a cross-origin frame
let frame = document.getElementById('your-frame-id');
frame.contentWindow.postMessage(/*any variable or object here*/, 'http://your-second-site.com');
window.addEventListener('message', event => {
// IMPORTANT: check the origin of the data!
if (event.origin.startsWith('http://your-first-site.com')) {
// The data was sent from your site.
// Data sent with postMessage is stored in event.data:
console.log(event.data);
} else {
// The data was NOT sent from your site!
// Be careful! Do not use it. This else branch is
// here just for clarity, you usually shouldn't need it.
return;
}
});
You will want to override the error being automatically thrown:
const iframe = document.getElementById('iframe');
iframe.addEventListener('load', evt => {
const loc = iframe.contentWindow.location;
try{
loc.href;
}
catch(e){
if(e.name === 'SecurityError'){
console.log(iframe.src);
}
}
});
<iframe src='https://example.com' class='embed-responsive-item' id='iframe' sandbox='' allowfullscreen></iframe>
I have this piece of javascript code doing my clickouts and it should enable correct click-out tracking. clickDestinations are all different, and there are many ( cross domain ).
var response = window.open(clickDestination, randomName);
if (typeof response.focus === 'function') {
alert('tracking this click-out');
}
Problem with this implementation is the clickDestination was given by users and some of it is very old, so there is no guarantee that http or https protocol is correctly set.
When window.open is called with the wrong protocol, ex. with https on sites where https is not supported, i get "This site canโt be reached" page (ERR_CONNECTION_CLOSED). But my tracker tracks anyway since var response is a window object.
Any ideas how can i detect the mistake and not track in this case ?
First idea valid if url is on the same domain (same origin policy applies here):
var w = window.open(url);
// if window opened successfully
if ( w ) {
w.onload = function() {
alert('tracking this click-out');
};
}
Second idea:
window.open returns a reference to the newly created window.
If the call failed, it will be null instead. Ref.
So in case the connection fails because the server at specified URL does not support https or either http null will be returned so you can use this information to skip your tracking code.
Example (not tested):
var response = window.open(clickDestination, randomName);
// if destination cannot be open, skip tracking code
if(!response){
return;
}
if (typeof response.focus === 'function') {
alert('tracking this click-out');
}
I've made a component for an SAP solution (whatever) that is embedded into a report through an iframe. After I deployed the report on an SAP plateform (BO), I got this error (on Chrome, but does not work on IE or FF either):
Uncaught SecurityError: Blocked a frame with origin "http://support.domain.com" from accessing a frame with origin "http://support.domain.com". The frame requesting access set "document.domain" to "domain.com", but the frame being accessed did not. Both must set "document.domain" to the same value to allow access.
The iframe is embedded into my component so it's suppose to run on the same domain with same port than report.
I found this post on SO and this one, but it does not really helped me to understand what I need to do.
Is there a way to get rid of this, or at least work around this ?
Thanks :).
EDIT:
Host Page URL : http://support.domain.com/BOE/OpenDocument/opendoc/openDocument.jsp?sIDType=CUID&iDocID=AbmffWLjCAlFsLj14TjuDWg
URL of the file calling a property on the iframe (and generating the error) : http://support.domain.com/BOE/OpenDocument/1411281523/zenwebclient/zen/mimes/sdk_include/com.domain.ds.extension/res/cmp/js/component.js
URL of the frame :
http://support.domain.com/BOE/OpenDocument/1411281523/zenwebclient/zen/mimes/sdk_include/com.domain.ds.extension/res/cmp/js/map/js/map.html
The iframe embed itself some script tag, I can see everything loading fine in the Network tag of the console.
Maybe it can help.
EDIT 2 :
I just realized SAP report is itself embedded into an iframe. That means my iframe is within an iframe, that might be the issue. Still, when lauching the report from Eclipse, everything is working.
I've finally found a solution.
The top of my iframe had a domain.location set to domain.com and my iframe a domain.location set to support.domain.com.
Event though I still think that both belong to the same domain, browsers don't like it it seems so.
Re-setting the domain.location did the work.
To answer the ones asking about how to re-set location.domain, here is the snippet of code my team used to use. This is quite old (2y ago), not really optimized and we do not use it anymore, but I guess it's worth sharing.
Basically, what we were doing is load the iframe with passing it top domain in the URL parameters.
var topDomain = (function handleDomain(parameters) {
if (typeof parameters === "undefined") {
return;
}
parameters = parameters.split("&");
var parameter = [],
domain;
for (var i = 0; i<parameters.length; ++i) {
parameter.push(parameters[i]);
}
for (var j = 0; j<parameter.length; ++j) {
if (parameter[j].indexOf("domain") > -1) {
domain = parameter[j];
break;
}
}
if (typeof domain !== "undefined") {
domain = domain.split("=");
return domain[1];
}
return;
})(window.location.search),
domain = document.domain;
if (domain.indexOf(topDomain) > -1 && domain !== topDomain) {
document.domain = topDomain;
}
The previous answer is no longer valid:
Document.domain - https://developer.mozilla.org/en-US/docs/Web/API/Document/domain
Deprecated: This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.
The current solution would be to use message exchanges. See samples on:
The solution is https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
For example I'm on domain1:
a.click(function(){
window.win=window.open(domain2,'name');
});
Now I'm on domain2 and I'm closing it. How will window.win know that user closed that window? Is there any event or property to check via interval?
There is a property which is not part of any W3C spec. It's called closed and would get accessed like
if( window.win.closed ) {
// window was closed
}
I'm not sure about the cross-browser compatibilty for that property. I'm also not sure how this behaves on cross-origin domains. But if you try it please let me and the rest of this community know about it.
Another option is that you take care for the notification yourself. That means, you are listening for the onbeforeunload within the popup-window. When the event fires, you could use HTML5's postMessage method to communicate between cross-domain windows. For instance:
MainWindow:
window.addEventListener('message', function(e) {
if( e.origin === 'http://www.popupdomain.com' ) {
if( e.data === 'closed' ) {
alert('popup window was closed');
}
}
}, false);
Domain2:
window.onbeforeunload = function() {
window.opener.postMessage('closed', 'http://www.popupdomain.com');
};
The only caveat on this solution is that it's only compatible with browser that support basic HTML5. There are other (sneaky) ways to have a cross-domain communication on old'ish browsers, but I guess that is another story.
You can check if the cross domain was closed by using an interval check on the windows closed property.
var url = "http://stackoverflow.com";
var width = 500;
var height = 500;
var closeCheckInterval = 500; //Check every 500 ms.
var popup = window.open(url, "_new", 'width=' + width + ', height=' + height);
popup.focus();
var closeCheck = setInterval(function () {
try {
(popup == null || popup.closed) && (clearInterval(closeCheck), onWindowClosed());
} catch (ex) { }
}, closeCheckInterval);
var onWindowClosed = function () {
...
// Stuff to do after window has closed
...
}
I was working on the similar problem in which a window of domain1 is opened from domain2. I needed to keep a check on when the window gets closed. I tried following :-
I used window.onunload event , it didn't work because of Same Origin Policy and showed following error
Error: attempt to run compile-and-go script on a cleared scope
Source File: chrome://firebug/content/net/httpLib.js
Error: attempt to run compile-and-go script on a cleared scope
Source File: chrome://firebug/content/firefox/tabWatcher.js
But I maintained an array of Window objects and applied "Window.closed" property , it works even in cross domain. :)
Also you can try postMessage API or CORS
The main problem is that my extension is loading into every iframes on a target webpage. It puts buttons that appear inside the iframes as well. I want them to disappear. The window and document objects are shown as the parent's window and document objects. So it's impossible to check the document location for example because it shows the parent's location instead of the iframe's location.
You could write a user script which uses the #noframes metadata header key and include the user script in to your Jetpack with this user script package for the addon sdk.
Writing user scripts is much easier than writing Page Mods too.
Edit: now (Add-on SDK version 1.11 released) pagemod supports what you are looking for. See the docs and the new attachTo option.
The following information is outdated (can be used if you build against Add-on SDK 1.10 or previous:
Unfortunately pagemod injects your script in all the frames of the page, and not just once per page on the top frame as what you achieve with Chrome's content scripts. Of course, there are workarounds for stopping the execution of the script (see the other answers).
But if you really want to inject your script only once per page, on the top frame, you have to use tabs. With this solution you script only gets injected when it has to.
Bellow I provide and example for porting from pagemod to tabs, keeping all the message reception system with port working. It can easily be modified if you need to not just receive, but also send messages using that same port object. The script gets injected when we are in the domain youtube.com:
Old pagemod way:
var self = require("self");
var pagemod = require("page-mod");
pagemod.PageMod(
{
include: "*.youtube.com",
contentScriptFile: self.data.url("myContentScript.js"),
onAttach: function(worker)
{
worker.port.on("myMessageId", function(payload)
{
console.log("Message received: " + payload);
});
}
});
New tabs way:
var self = require("self");
var tabs = require("tabs");
tabs.on("ready", function(tab)
{
if (tab != undefined && tab.url != undefined && tab.url.split("/")[2] != undefined)
{
var domain = "youtube.com";
var host = tab.url.split("/")[2];
if (host == domain || host.substr(host.length - domain.length - 1) == "." + domain)
{
var worker = tab.attach({ contentScriptFile: self.data.url("myContentScript.js") });
worker.port.on("myMessageId", function(payload)
{
console.log("Message received: " + payload);
});
}
}
});
One workaround is to put something like this in your content script:
if (window.frameElement === null){
// I'm in the topmost window
// Add buttons and things to the page.
}else{
// I'm in an iFrame... do nothing!
}
The content script will still be added to every page, but it's a relatively simple and lightweight check for iFrames.