
IFrames get a bad rap. In the early days their cross-browser support was spotty and even now, incorporating them in mission-critical deployments requires a good reason and a considered approach. But there are good reasons. And given a perfect storm of capabilities and limitations, an iframe can serve as the avenue to a seamless experience where user interaction would otherwise be punctuated by popups, scrollbars, or host switches.
In work with two separate vendors recently I was confronted with the challenge of giving logged-in users single-sign-on access to third party sites from within one of our applications. The envisioned solution was the one above and the iframe’s very purpose in life is to enable this sort of segregated mashup. For the seamless experience, however, we needed to be able to re-size the iframe container to populating pages of varying height while not breaking the third party apps’ capability to dynamically generate new windows with content.
Intelligent Re-sizing
There is one of rule of internet law that must be adhered to for the communications described below to be allowed: both host and iframed pages must reside under the same generic domain. (Host page, for example, at http://subdomain1.domain.tld and iframed pages at http://subdomain2.domain.tld.) Cross-domain security policies require that the fully-qualified domains match each other exactly (which the examples do not). Subdomains, however, are allowed to be loosened to a common generic domain through JavaScript’s document.domain property. The example available through the link at top right of this page has http://alexaitken.net request pages on http://dev.alexaitken.net in to an iframe (with black border), then resizes the iframe to its contents. The items below are the key components of the solution.
- JavaScript within the alexaitken.net host page that sets document.domain property to ‘alexaitken.net’ (even though this is the host page’s domain, the property needs to be set explicitly for communications.)
- JavaScript within each of the dev.alexaitken.net pages that sets document.domain property to ‘alexaitken.net’ (a loosened version of their true domain)
- Listening from the host page for the iframe’s load event.
- On load, calculation of the height of the iframe page (document.getElementsByTagName(‘iframe’)[0].contentWindow.document.body) from the host page.
- Setting of the iframe element’s height to the height of its contained document.
As IE and Webkit browsers appear to introduce a race condition that makes the iframe page’s document.domain property inaccessible at the time the iframe fires its load event, an interval tries accessing iframe page properties until successful. The code for this first piece of the solution looks like this:
1: $(document).ready(function(){ //for some reason this runs twice on clicking through to full post without using 'one'
2:
3: //scoped variables
4: var availabilityInterval, //setInterval used to re-fire resumeCode function until race condition disappears (for IE (and webkit?)) and document.domain setting in child window is recognized by parent.
5: $childBody, //iframe window's document.body
6: iframe, //iframe
7: iframeOnload,//iframe onload function
8: preIframeHeight, //measure of childBody's offsetHeight before iframe's height is reset.
9: postIframeHeight, //measure of childBody's offsetHeight after iframe's height is reset.
10: resizingInterval, //setInterval used to re-fire setHeight function until preIframeHeight = postIframeHeight. used to account for animations within childBody.
11: resumeCode, //function: fires until communication between parent and child window is established and at that point re-sizes and shows iframe. Also binds the resizing function to all future iframe page loads.
12: setIframeHeight; //function: sets height of iframe according to its child window content.
13:
14: document.domain='alexaitken.net';
15:
16: resumeCode=function(){
17: try{
18: if(iframe.contentWindow.okToResumeParent===true){
19: iframeOnload();
20: clearInterval(availabilityInterval);
21: }
22: }catch(error){};
23: };
24:
25: setIframeHeight=function(){
26: preIframeHeight=iframe.style.height;
27: iframe.style.height=$childBody.height()+20+'px';//50 handles items getting cut off vertically sometimes...
28: postIframeHeight=iframe.style.height;
29: if(preIframeHeight===postIframeHeight){clearInterval(resizingInterval);}
30: };
31:
32: iframeOnload=function(){
33: $childBody=$(iframe.contentWindow.document.body);
34: setIframeHeight();
35: $childBody.bind('click',function(){ //tests and resizes child page as necessary for each click within
36: resizingInterval=setInterval(setIframeHeight,100);
37: });
38: };
39:
40: //Behavior attachment logic
41: iframe=$('iframe').get(0);//returns native dom element - at this point, we know it's been created and added
42: $('iframe').unbind().bind('load',function(){
43: availabilityInterval=setInterval(resumeCode,50); //check 20 times/second for disappearance of race condition (resumCode clears check and kicks off the page init);
44: });
45:
46: });
Dynamic Windows from Iframe Pages
One of the applications we framed with this solution used JavaScript in some cases to generate new windows with dynamic content. Mozilla and Webkit dutifully created and populated the windows from the iframe page. But IE of course pulled its usual trick of taking a different tack.
What we found on this one was that creating a new window with a simple window.open() statement actually defaulted the IE window location to about:blank, which the browser considered a different domain. Subsequent attempts to write content to the window failed because of cross-domain issues.
Here the solution was to place a dummy file empty of all content except a document.domain-setting JS snippet within the current domain’s file system, request that file as the URL of the new window, and use document.write to populate it with the dynamic html content. The one potential gotcha is in the fact that unless the new content also includes JS that sets document.domain, the first document.write statement will work but further attempts to access document properties and methods (such as document.close) will fail. Once domain is set in the dummy file and written in with new dynamic content, even IE gives up the ghost. That code looks like this:
1: var openAndWrite=function(){
2: var winOptions="status=no,toolbar=no,location=no,menubar=no,directories=no,resizable=no,scrollbars=no,height=300,width=300",
3: w=window.open('dummy.htm','receiver',winOptions),
4: intervalOne,
5: intervalTwo;
6: intervalOne=setInterval(function(){
7: try{
8: w.document.write(
9: '<html><head><title>Dynamic Window</title>'+
10: '<link rel="stylesheet" type="text/css" href="/_system/css/core.css">'+
11: '<script type="text/javascript" src="docDomainSetter.js"><\/script>'+
12: '</head><body style="padding:10px">'+
13: 'This window and contents written dynamically via function call from iframed page with loosened document.domain property.'+
14: '</body></html>'
15: );
16: clearInterval(intervalOne);
17: intervalTwo=setInterval(function(){
18: w.document.close();
19: clearInterval(intervalOne);
20: },50)
21: }
22: catch(error){}
23: },50)
24: };
Babies and Bath Water
Some sites I came across in researching this solution maintained flat-out that iframes have no place in modern web development and should be avoided at all costs. In my situation however, they proved the only option capable of providing the experience we wanted to offer our customers. So I maintain that while yes, they can be complicated to implement, they have their time and place. On arriving at these, we as competent interface and experience developers should be willing to assume a degree of system complexity in order to make the lives of our users better.
[...] AlexAitken.net – IFrame Solutions iframe security trickery (tags: web programming javascript tutorial example) [...]