Printing iframe content using JavaScript

17 Dec 2009

JavaScript is on top of the list of things I’d like to become better at. So I was quite happy when assigned a JavaScript-related defect in an application at work. Apparently, there was a bug in the JavaScript routine used for printing only the content of an iframe on a page. Rather than printing only the iframe’s content, it printed the page hosting the iframe, cropping parts of the iframe’s content.

I started out confident that printing would be easy to fix. But after Googling and playing around with JavaScript for a while, frustration, fueled by the number of non-working hits I’d stumbled on, started to grow on me. Suppose you start out with an HTML document hosting an iframe and a few lines of JavaScript.

<html>
    <head>
        <title>IFrame printing example</title>
        <script type="text/javascript">
            function myPrint() {
                var browser = navigator.appName;
                if (browser == "Microsoft Internet Explorer") {
                    window.frames["frame"].focus();
                    window.frames.print();
                }
                else if (browser == "Netscape")
                    alert("Printing not supported in Firefox");
            }
        </script>
    </head>
    <body>
        <iframe id="frame" name="frame" width="100"
            height="100" src="http://google.com">
        </iframe>
        <a href="#" onclick="javascript:myPrint();">Print</a>
    </body>
</html>

A number of issues arise when printing the iframe. First you need to consider if you’re always hosting the main document on the same domain as the iframe’s source. If so, printing only the iframe with Internet Explorer works like a charm. If, on the other hand, the main document and the iframe’s source reside on different domains, it triggers Internet Explorer to displays a bar across the top of the window.

Now, to print the iframe’s content, you must allow blocked content. Only then can JavaScript access the iframe’s object model. But at the same time, you make yourself vulnerable to cross-site scripting attacks.

As far as Firefox support goes, you can issue the same API calls as for Internet Explorer. But Firefox will always print the main page, including only the visible parts of the iframe. To my knowledge there’s no way to make Firefox print the iframe’s content as seamlessly as Internet Explorer. One workaround, though, would be to open a new window, grab a reference to the iframe element, and move its contents to the new window before printing it. For cross-domain content, however, Firefox responds with a “permission denied” error when accessing the iframe element. And there’s no option to allow blocked content. Another workaround may therefore be to use Ajax to retrieve the source of the iframe for any domain and insert it into the new window. That, however, wouldn’t work in my case since the iframe may contain a form that the user may have started filling out before hitting print.

As a last option, I considered writing a proxy to retrieve cross-domain hosted content. This would make Internet Explorer never display the bar and allow Firefox to always open a new window with the iframe’s content loaded, even if the user started filling out a form first. But this approach sort of defies the purpose of the browser bar and the permission denied error. Unless you absolutely trust your iframe’s source, you’re exposing your users without their consent.

In the end I went with the simplest and most secure solution of not supporting printing at all.