Cross Domain IFrame element.scrollIntoView() Safari Issue - javascript

I have ran into an issue that is plagued all over google but none of the provided solutions work correctly. I assume the majority of these solutions do not account for cross domain.
I have a website which I would like to instruct users to embed into their site with a (full page) IFrame. The problem is on some versions of Safari only. Elements within the IFrame can not scroll themselves into view.
I notice that if I do a same domain test the IFrame can scroll itself using window.parent.scrollTo(0,element.top). This works, but not cross domain. Another odd thing is that no other browser requires the window.parent method to scroll the IFrame, only Safari. All other browsers can use element.scrollIntoView() from within the IFrame. Note that I already use the JavaScript workaround to please Safari with cross-protocol IFrames.
Another issue I've only seen on Safari Mobile inside IFrame is that Bootstrap Modals appear out of view at the top of the IFrame when scrolled down. Although, I'm sure if we can correctly set the scroll position we should be able to set the modal position as well.
Here's what I've tried;
1. window.frames['IFrameName'].document.
getElementById("elmId").scrollIntoView();
Offset trick
Velocity.js
My last resort here (I think) is to use postMessage from within my IFrame to notify the parent domain to set the scroll position of the frame.
It seems to me that this issue has been around for an awful long time. Is there a better approach than this?

This ended up being a lot more research than code. What was going on was - I had code that resized the IFrame based on the content.
In all other browsers this works fine and eliminates the scroll bars. Turns out that Safari automatically sizes the Iframe leaving scrolls of it's own. In my application there are zero static pages. This leaves me with the issue of not being able to use the scrolling=no fix described in the link.
After discovering exactly what was going on I took a different approach to fixing elm.scrollIntoView(). The code is more comments then anything but the important parts are;
Detecting when to apply the Iframe fix with RequiresIframeScrollFix
Using elm.getBoundingClientRect().top to get our scroll position from within the Iframe.
Communicating to the parent to scroll with window.parent.postMessage
Receiving the message in the parent with window.addEventListener('message',...)
Here's what it looks like.
Iframe Site
Our Iframe site currently scrolls it's elements into view like this elm.scrollIntoView(); We've changed that to the following.
if (RequiresIframeScrollFix())
window.parent.postMessage(elm.getBoundingClientRect().top, "*"); // Tell IFrame parent to do the scrolling. If this is not a test environment, replace "*" with the parent domain.
else
elm.scrollIntoView(); // If not scroll into view as usual.
Optional: fix for bootstrap modal positioning in IOS IFrames using elm.getBoundingClientRect().top.
$('#modalId').css('top', elm.getBoundingClientRect().top); // This fixes modal not in view on Safari Iframes.
RequiresIframeScrollFix() is mostly made up of some well document code laying around SO to determine if we're in an Iframe on the IPad or IPhone.
// Used to help identify problematic userAgents.
var debugNavigator = false;
// Detects an issue on mobile where the Parent is an iframe which cannot have it's scroll bars removed.
// Presumably not a bug as safari will autosize it's iframes: https://salomvary.com/iframe-resize-ios-safari.html
// Can use "scrolling=no" fix instead if the parent knows the initial size of your iframe.
function RequiresIframeScrollFix() {
try {
// Debug navigator Agent
if (debugNavigator)
alert(navigator.userAgent);
// We know this issue happens inside an IFrame on;
// Safari iPhone
// Safari iPad
// Safari Desktop Works fine.
// Check for safari
var is_safari = navigator.userAgent.indexOf("Safari") > -1;
// Chrome has Safari in the user agent so we need to filter (https://stackoverflow.com/a/7768006/1502448)
var is_chrome = navigator.userAgent.indexOf('Chrome') > -1;
if ((is_chrome) && (is_safari)) { is_safari = false; }
// If we need to narrow this down even further we can use a more robust browser detection (https://stackoverflow.com/questions/5916900)
// Problematic browsers can be adjusted here.
if (is_safari && inIframe() && (
navigator.userAgent.match(/iPad/i) ||
navigator.userAgent.match(/iPhone/i)
))
return true;
else
return false;
} catch (e) {
alert(e.message);
}
}
// (https://stackoverflow.com/questions/326069/)
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
Parent Site
Our parent site holds the IFrame that was automatically sized by Safari Mobile. Therefore the parent site now has scroll bars of it's own, and not the IFrame. We set our listener up inside the parent site to scroll itself when it receives a message from the IFramed site.
// Safari Mobile Iframe Cross Domain Scroll Fix.
window.onload = function () {
// Calback function to process our postMessages.
function receiveMessage(e) {
try {
// Set the scroll position from our postMessage data.
// Non-Test pages should uncomment the line below.
//if (e.origin.includes("your-iframe-domain.com"))
window.scrollTo(0, e.data);
}
catch (err) {
}
}
// Setup and event to receives messages form our iframe (or window)
window.addEventListener('message', receiveMessage);
}
Hopefully this helps someone else dissect Safari Iframe problems on mobile. Also, let me know if I've overlooked a better solution.

The other option is iframeResizer library. There are two methods you can use from within iframePage: scrollTo and scrollToOffset, which do pretty much the same what you've described - they communicate via messages. It solved this problem for us in Safari.
Inside the parent page, when setting up resizing for the iframe, you have to assign a callback function to its onScroll event:
iframeNode.iframeResize({
...,
onScroll: ({x,y}) => callback
}
And inside iframe page:
if('parentIFrame' in window){
window.parentIFrame.scrollTo(0, someNode.offsetTop);
}

Related

Scroll window to top of Iframe from iFrame with different domain than parent

Context:
PARENT PAGE loads an IFRAME that contains an app/plugin
PARENT PAGE has different domain than the IFRAME
PARENT PAGE and IFRAME both have installed the iframeResizer plugin -> the iframe has no scroll bar (all content is shown on parent page) - IFRAME is always resized based on its document height
PARENT PAGE html/js cannot be changed, only the IFRAME
Problem:
If the user is at the bottom of the IFRAME, and performs an action, how do you scroll him at the TOP OF THE IFRAME?
Solutions that DO NOT work:
window.scrollTo(0,0); // from iframe
anything with window.parent from iframe (cannot access)
use an anchor at top of iframe body and rewrite window.location
Solutions that do work?
will post one as an answer (it works, but it's weird...and not good for IE)
You can try using document.body.scrollIntoView().
It should work on IE8+ and the other browsers (I only tested it in Chrome, FF and IE11)
See codepen demo here.
document.body.scrollIntoView();
This works fine in Chrome & Mozilla (IE not working, why?) ... but it doesn't seem like the right approach.
DEMO Code pen demo
Create an input field in the IFRAME
Style the INPUT to make it not visible to the user
Scroll parent page by focusing the input from the IFRAME
// IFRAME JS
// initialize a "scroller" object
var scrollPage = (function(){
var input = document.createElement("input");
input.style.position="absolute";
input.style.top="-50px";
document.body.insertBefore(input, document.body.firstChild);
return function(){
input.focus();
};
})();
// use the "scroller" when needed
scrollPage();

Prevent access to parent from iFrame

I have a website, which has a few ad banners on it, and the banners. Many of these banners try to access the parent window. I need to add the website into an iFrame for preview purposes, so editors can see both the desktop and the mobile website. This works quite well, but some banners try to access the parent window, and when the parent window no longer is the main website, the content is overwritten, so the iFrame only displays the ad.
I have tried different ways to set disable access to parent window from iFrame. In HTML 5 there is an sandbox attribute for the iFrames, but I need to enable so many things, such as allow-scripts, that the banners still get access to the parent frame.
I have tried several different variants of the following code, on the page that is rendered inside the iframe:
// Variant 1
window._parent = window.parent;
window.parent = null;
// Variant 2
window.parent = "";
// Variant 3
window.parent = window;
// Variant 4 - from the parent document
window.onload = function() {
var frm = document.getElementById('childFrame');
var win = frm.contentWindow || (frm.contentDocument && frm.contentDocument.parentWindow) || (frm.document && frm.document.parentWindow);
if (win) win.parent = null;
}
And a lot more, but you can probably see the concept, however nothing seem to work properly. Often it seems like everything goes in to a infinite loop, which makes the browser unresponsive.

Get hosting XULDocument for a ContentWindow or HTMLDocument in Firefox extension?

I have an Firefox Extension with a XUL overlay containing some Javascript with some event handlers. Among those is an EventListener for DOMContentLoaded. I have a second overlay I only want to load when visiting a certain website.
Here's some of the code I have so far:
var appcontent = document.getElementById("appcontent"); // browser
if (appcontent) appcontent.addEventListener("DOMContentLoaded", onPageLoad, true);
function onPageLoad(event) {
var doc = iEvent.originalTarget; // doc is document that triggered "onload" event
var win = doc.defaultView;
if (doc.nodeName != 'document') return; // only documents
if (win != win.top) return; //only top window.
if (win.frameElement) return; // skip iframes/frames
if(doc.location.href.search('http://somewebsite.com/') > -1) {
//Find XULDocument somehow
//var xulDoc = ??????;
xulDoc.loadOverlay('chrome://myextension/content/secondoverlay.xul', null);
}
}
How can I retrieve the XULDocument hosting the DOM, given the DOMContentLoaded event data?
Well, the XUL window is just window, aka. the global scope.
So the following two lines should both work and should be the same:
window.document.loadOverlay(...);
document.loadOverlay(...);
However, this most is not really what you want, because the XUL window is still the main browser.xul which hosts all content windows. There is no dedicated XUL window per content window!
Loading the second overlay will overlay the whole browser window, not just the "tab" (or whatever) and will stay once you close the tab (content window) or navigate away.
Now, the question is: What do you really want to achieve?
Display some UI (toolbar button, menu, whatever) only for certain pages? Then usually you'd overlay all your stuff once (on the initial load, i.e. in the "first" overlay) and just hide/show your UI according to your rules. See Tabbed Browser for some code snippets when dealing with tab switching and/or page loads.
Or you really want to apply something to the content window itself. Then you'd usually just modify the DOM of the content window directly.

Refresh iframe when screen is clicked

Not too familiar with javascript. I have done some searching and am having trouble implementing this code into my current project.
Basically want to refresh the iframe when the screen is clicked. I currently have this code:
document.getElementById('').contentWindow.location.reload(true);
I want to integrate it into this code:
$(".launch").loadthis({ direction: "left", connect: true });
How would I go about this?
There are some issues that are unclear in your question.
1) What do you mean when you say "Screen is clicked" ?
Anywhere in the open browser window?
Anywhere in the open browser window including the iframe?
2) Is your IFRAME 100% of the width and height of the browser window?
Regardless, below I break down your problem and hopefully give you a solution...
How do we refresh a webpage?
You can refresh a webpage using:
location.reload(true);
How do we refresh an iframe?
You may refresh an IFRAME using the code here:
Refresh an iframe
<iframe id="myiframe" src="http://google.com"></iframe>
document.getElementById("myiframe").src = "http://www.google.com?"+(+new Date());
How do we detect the click?
You can use the solution here:
https://stackoverflow.com/a/2622026/1688441
document.onclick= function(event) {
// Compensate for IE<9's non-standard event model
//
if (event===undefined) event= window.event;
var target= 'target' in event? event.target : event.srcElement;
alert('clicked on '+target.tagName);
};
Remaining issues:
What happens if user clicks within IFRAME?
If the user clicks within the IFRAME, and we control the code of the IFRAME we can have a click listener within the HTML/JAVASCRIPT of the IFRAME and trigger a refresh.
If we have different domains, due to security reasons there is not much that can be done.
window.addEventListeneder("click", windowClickHandler);
function windowClickHandler(event) {
document.getElementById('iframe_id').contentWindow.location.reload();
}
Also see this question;

Frame Busting buster not completely working for IE

I've been working on a Frame busting buster (what's in a name, hehe), which kept my users on my page and open a new window with the target URL. I'm using a Lightbox script to display iframes, this is what I'm doing:
1) Added an event for all .lightbox clicks, f.e:
$('.lightbox').live("click", function(e) {
e.preventDefault();
$('#redirectURL').val($(this).attr('href'));
$(this).lightbox();
}
2) Added a frame busting buster:
<script type="text/javascript">
var prevent_bust = 0
window.onbeforeunload = function() { prevent_bust++ }
setInterval(function() {
if (prevent_bust > 0) {
prevent_bust -= 2
window.top.location = 'http://server-which-responds-with-204.com'
}
}, 1)
</script>
3) Modified the frame busting buster code to fit my needs, which are:
detect if an iframe wants to change the window.top.location
if so, prevent this from happening using the 204 server respond
open a new page: window.open( $('#redirectURL', '_blank' );
close lightbox: $('.jquery-lightbox-button-close').click();
So far, this is what I've come up with:
var prevent_bust = 0
window.onbeforeunload = function() { prevent_bust++ }
setInterval(function() {
if (prevent_bust > 0) {
prevent_bust -= 2;
redirectURL = $('#redirectURL').val();
if(redirectURL != "") {
window.top.location = 'http://www.****.com/ajax/nocontent.php';
window.open(redirectURL, "_blank");
$('.jquery-lightbox-button-close').click();
$('#redirectURL').val('');
} else {
window.top.location = 'http://www.****.com/ajax/nocontent.php';
}
}
}, 1);
// EDIT: Before I forget, 'nocontent.php' is a file that returns a 204 header
For Firefox it acts as I programmed it, if there's a change detected in the window.top.location it opens a new frame/page and prevents the iframe from reloading the top location and to round it up, it closes the jQuery lightbox.
Safari/Chrome act similar, they open a new browser screen (not sure if theres an option to say target="_newtab" or something?). Only bad thing is they do not really display a message of the popup is blocked, but I can work around that by displaying a popup balloon on my website with a link to the page.
Internet Explorer is, what a shocker, the only black sheep left.. IE does not open a new popup, nor blocks the window.top.location reset by the iFrame and simply continues refreshing the complete page to the '#targetURL'. It does the same with the default busting code.. so it's not because of some of my edits.
Anyone who is able to spot a mistake in my code?
Also, I would need a little modification that sees if the request has been made by an iframe or by the user itself, because now there is really NO option for a user to leave my page by changing the address in the toolbar or by clicking a link, which is not really needed LOL.
Thanks in advance.
PENDO, I tried to simulate the whole process you described, ligthbox-jquery, javascript their own codes and controls opening pages via lightbox. I could not simulate at all, and as time is running out I'm sending a suggestion to broaden the range of possibilities and solutions.
I suggest replacing the redirect page:
...
redirectUrl = $ ('# redirectUrl'). val ();
...
window.top.location = 'http://www .****. with / ajax / nocontent.php';
window.open (redirectUrl, "_blank");
Replaced with a DIV container that simulates a page, using ajax calls and taking the content and overwritten the contents of the DIV.
...
$.post(redirectoURL /* or desired URL */, function(data) {
$('DIV.simulateContent').html(data);
});
...
or
...
$('DIV.simulateContent').load(redirectoURL);
...
This approach also avoids the problem of preventing the user from even leaving your page using the address bar (as you yourself mentioned).
Sorry, let me give you a complete solution, but time prevented me.
PENDO, a little more work on alternatives to the problem, I found a customizable jQuery lightbox plugin for working with custom windows yet (iframe, html, inline ajax etc.). Maybe it will help. The following link:
http://jacklmoore.com/colorbox/
If you don't need javascript running in your iframe in IE, you can set the iframe security attribute :
<iframe security="restricted" src="http://domain.com" />
http://msdn.microsoft.com/en-us/library/ms534622(v=VS.85).aspx

Categories