I'm currently developing a clicktail clone. I've recorded all the mouse interactions and window scrolls and my plan is to play them back by opening the URL that has been recorded into an iframe and then have a mouse image move to the captured coordinates, images indicating when a click occurred and the iframe scrolling to the captured scroll positions
this was working nicely while I was viewing a page from my domain in the iframe, but as soon as I display a page from a different domain, I get access denied errors from the FF console and the same issues from IE
this is due to the Same origin policy for JavaScript.
I have been reading this article -> Ways to circumvent the same-origin policy
it seems that this is becoming an issue for many developers and there are hacks to get round it.
can anyone suggest a suitable hack for my situation ?
You could always fake it. Maybe you could have your place your iframe in a container div (css: overflow: hidden; height: /* some height */), with the iframe element set to the full height of the page, and scroll the div?
You could use a PHP proxy on your domain that (a) reads the target URL to a string, (b) adds a base tag so that images, links, etc. work correctly and then (c) prints the string.
The end result is a page that is identical to the page from the external domain but is hosted on your domain. This means that you can execute JavaScript in the child frame from the parent frame.
The code for the proxy is as follows:
<?php
ini_set("user_agent", $_SERVER['HTTP_USER_AGENT']); // temporarily override CURLs user agent with the user's own
$page = file_get_contents($_REQUEST["www"]);
$page = preg_replace("/<[\s]*head[^>]*>/i", "<head><base href='".$_REQUEST["www"]."' /><base target='_blank' />", $page);
echo $page;
?>
One consideration when using this method is that when the user (or JavaScript) clicks a link in the proxied page, the user will be taken to a page on the original domain (or elsewhere). This means that your JavaScript will no longer be able to access or execute scripts in the iframe.
To make this consequence more transparent, links are set to target='_blank' in the code above.
Related
Since there is no way to prevent an iframe from redirecting the top frame besides sandboxing which prevents other features required for viewability tracking I would like to track redirects. Since one site can have more than one iframe, it could be any of these.
Is there any way to track/find out which one (specific iframe) caused the top frame redirect?
Here is a sandbox (use browser console and enable preserve log):
Note the iframe content is usually cross domain. For ease of use its within the sandox.
We can access to the iframe content with somethig like iframe.contentWindow.document but this is possible if we observe Same-origin policy.
Another approach could be setting a Content-Security-Policy header like:
<meta http-equiv="Content-Security-Policy" content="frame-src http://example.com">
This header in the parent page prevents to load sites different to http://example.com in frames, There is also a way to report the refuse behavior sending a post but unfortunately can't be setting with <meta> tag (it's only server side). With this approach we have to perform a white list, so I think maybe it's not useful in this case. But, if the white list is given the first time, is possible to set all sites available, so when the iframe redirect, browser will refuse to load it.
If it's not the case of same-origin and the possibility of performing a white list, then I think the better we can do is calling iframe onunload event, unfortunately this event are going to be fired also when iframe page reloads not only on redirection. I think it's the closest approach. To achieve that, this code works.
var srcs = ["iframe2.html","iframe.html","iframe2.html"];
for (let i = 0; i < srcs.length; i++) {
var iframe = document.createElement('iframe');
iframe.src = srcs[i];
iframe.name = "i"+i;
document.body.appendChild(iframe);
window["i"+i].onunload = function(){console.log("change "+i)}
}
Of course onunload is fired the first time, when all iframes load, so redirections are 2th 3th and so on. But we could exclude that first case.
Here a full example https://codesandbox.io/s/o16yk7mqy , I've created iframe3.html that doesn't refresh neither reload to show clearly the point. Also I've created a simple List of redirect or reload iframes.
UPDATE
As I understand now, what you want is to set iframes with sandbox property and whitelist all what you want but without allow-top-navigation, something like:
<iframe src="iframe.html" sandbox="allow-script allow-forms allow-popups allow-pointer-lock allow-same-origin"></iframe>
This Example doesn't allow allow-top-navigation https://codesandbox.io/s/lpmv6wr6y9
This Example here https://codesandbox.io/s/4x8v1mojq7 allow allow-top-navigation but codesandbox prevents the frame to redirect so if we try https://4x8v1mojq7.codesandbox.io/ that is the url created by codesandbox, we could see the top frame reload.
As I said in comments, at least Chrome 64.0.3282.167, when we delegate all but allow-top-navigation when the iframe attempt to redirect top frame, it throw an exception. The behavior is different in Firefox (at least 58.0.2). Firefox deny top navigation but continues with the code.
So, as conclusion the best approach in my opinion is or a combination of sanbox and onunload or just onunload. Of course, if it could be possible, Content-Security-Policy is the safest and more flexible way. It depends of the implementation. It's almost impossible I think not to involve server side code to perform a perfect solution. There are white list to check, like this API https://developers.google.com/safe-browsing/v4/ and there are black list to check, look at this post https://security.stackexchange.com/questions/32058/looking-for-url-blacklists-of-malicious-websites .
If you have control over all your frames you can implement interaction between frames with postMessage. Flow:
Frame want to execute redirect - it sends a message to parent frame with redirect request.
Parent frame executing redirect and know which frame caused a redirect.
Parent:
window.addEventListener("message", (event) => {
// show message source (your frame)
console.log(event.source);
const message = event.data;
console.log(`Frame ID: ${message.frameId}`);
if(message.messageType === "redirect") {
window.location.href = message.redirectUrl;
}
});
Child frame:
function redirect(url) {
var message = {
messageType: "redirect",
frameId: "frame1"
redirectUrl: url
}
window.parent.postMessage(message, "*");
}
You can show a dialog box before redirecting to another domain/application and then the user can decide - to stay or leave the current application. You can also track the current target (i.e. iframe in your case).
window.onbeforeunload = function (e) {
console.log(e.currentTarget.location.href);
return 'Stop redirection. Show dialog box.';
};
From the individual iframes, can you set a cookie when the redirect happens? Say it happened from iframe1, you may set a cookie like
document.trackFrame = "FrameName=iframe1";
And once the redirect completes, can you try reading the cookie and there by determine which iframe caused the re-direct?
I am allowing a part of my site to be shown in another site (different domain).
My concern is to set the height of iframe to the height of its content. I could use this solution, but my case is cross browser case, so this doesnot work.
I tried to get the height of iframe by:
window.parent.document.getElementById('id_iframe').contentDocument.body.scrollHeight;
but this is giving only the visible height.
What am I missing?
You can't use JavaScript to access content the user has loaded from another domain. It would be a security risk.
With the cooperation of the other site, you can receive a message (via postMessage) sent by the other site when its load event fires that tells you its height (and then you can resize in response to that).
It could also sent new heights when resize events fire in it.
My object is to remove the scrollbars of an iframe for cross domain servers, which I don't have access to.
Page A has an iframe which displays contents of an external server
The content is not fixed, its height changes with time so I can not fix the height, it has to be dynamic.
It has to be handled with JavaScript. I have a list of objects corresponding to the users selection. When a different selection is made, different content types are shown.
I have tried to a solution with postMessage, but it does not solve the problem as I don't have access to the server. I was thinking more of a view, which resizes the iframe when the page is loaded.
You can't. There is no way to determine the size of a page in a cross-domain iframe without explicit support from scripts running on that page. Since you've already said that you can't modify that page, there's no way to do this.
I'm interested in linking to or embedding an external page, scrolling to a specific point. Is this possible, or do cross-browser securities prevent it?
One workaround I've considered is creating the iframe within a div, giving the iframe a negative margin and then overflow:hidden; the container div.
Thanks.
A possible solution could be to use JavaScript to scroll the page. However, if the page you are embedding is on a different domain, you cannot access its content with JavaScript if it is in an iframe (due to the same origin policy). However, if it is on the same domain as the host page, you should be able to access it using JavaScript and then scroll using the window.scrollTo(x,y) method or similar (see this page on MDN).
Also, if the page you are embedding has a named anchor (<a name="blah">) or a block-level element with a specific id (<div id="blah">) at the point you want to scroll to, you can link to it or embed it by using a URL such as http://example.com/page#blah and it will scroll to blah automatically. This is not under the same-origin policy, so you can do something like <iframe src="http://example.com/page#blah"></iframe> and the frame will automatically be scrolled to blah, even if it is not on the same domain as the host.
The "iframe with negative margin" solution you mentioned could work, but that might be hard to implement and would probably cause problems, especially if you want full cross-browser compatibility.
The scene: I'm writing an embeddable widget. It takes the form of a <script> tag, which builds an iframe containing everything it needs to display. The iframe has no src, and the script writes to it with theIframe.contentWindow.document.write(). This keeps the widget contained, and keeps element ids and script from conflicting with the page on which the widget is embedded.
The trick: The widget has to be able to change its size. To do this, it sets its containing iframe's style.height. This requires access to the outer page's DOM. In Firefox and IE, this is allowed, because the iframe's document and the outer document are considered to share an origin.
The twist: In Safari, however, the two documents are considered not to share an origin. The inner document is considered to be at about:blank, while the outer document is clearly using a different protocol and "domain" (if blank can be considered the domain).
The question: How can I build an iframe programmatically whose document Safari/WebKit will consider to have the same origin as the document of the window creating it?
Edit: After further experimentation, I can't find a way to programmatically create an iframe whose location is not about:blank regardless of whether I change its contents.
If I create the frame with document.createElement(), give it a src which points to a real HTML resource on the same origin called "foo.html", and document.body.appendChild() it, Safari's console shows the element as expected in the DOM, but the contents of the page do not appear, and the document is listed in the sidebar as "about:blank".
If I include the HTML for the iframe directly in the page, the contents of foo.html appear, and "foo.html" appears in the sidebar.
If I insert the HTML using document.write(), I get the same result as with document.body.appendChild().
Both programmatic versions work in Firefox.
The best suggestion I could give is to have the iframe set to a blank page on the same server (ie blank.html) and then edit the content. A pain in the rear, I know but it's a workaround.
You could also try
iframe.contentDocument.open("replace");
iframe.contentDocument.write("<b>This is some content</b>");
iframe.contentDocument.close();
However, I'm not sure if that only works in IE. Sorry I couldn't be more helpful than that.
Aha. This seems to be a bug in WebKit. When an iframe is created programmatically, its src attribute is ignored. Instead, the frame defaults to about:blank and must be directed to a URL to point elsewhere. For example:
theIframe.contentWindow.location = theIframe.src