Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jquery - iframe access denied in IE on some pages

I'm the author of printThis, a jquery plugin for printing.

https://github.com/jasonday/printThis

I have a user that has brought up an issue, that I have been unable to crack and unfortunately, I am unable to share the page (privacy concerns).

On the user's site, the issue presents on some pages in IE, but not others. The print is failing to happen, as the iframe remains empty.

The error in IE is within jQuery:

contents: function (a) {
            return f.nodeName(a,
                "iframe") ? a.contentDocument || a.contentWindow.document : f.makeArray(a.childNodes)
        }

Using logging, I was able to determine it was failing around this line:

var $doc = $("#" + strFrameName).contents();

But again, this only happens on some pages and I have been unable to recreate in any instance outside of this user's site.

My question: Is there a better approach here? or a method to make the $doc object more bulletproof?


// -----------------------------------------------------------------------
// printThis v1.1
// Printing plug-in for jQuery
//
// Resources (based on) :
//              jPrintArea: http://plugins.jquery.com/project/jPrintArea
//              jqPrint: https://github.com/permanenttourist/jquery.jqprint
//              Ben Nadal: http://www.bennadel.com/blog/1591-Ask-Ben-Print-Part-Of-A-Web-Page-With-jQuery.htm
//
// Dual licensed under the MIT and GPL licenses:
//              http://www.opensource.org/licenses/mit-license.php
//              http://www.gnu.org/licenses/gpl.html
//
// (c) Jason Day 2012
//
// Usage:
//
// $("#mySelector").printThis({
//      debug: false, //show the iframe for debugging
//      importCSS: true, // import page CSS
//      printContainer: true, // grab outer container as well as the contents of the selector
//      loadCSS: "path/to/my.css" //path to additional css file
//  });
//
// Notes:
//  - the loadCSS option does not need @media print
//------------------------------------------------------------------------

(function($) {
    var opt;

    $.fn.printThis = function (options) {
        opt = $.extend({}, $.fn.printThis.defaults, options);

        var $element = (this instanceof jQuery) ? this : $(this);

    // if Opera, open a new tab
        if ($.browser.opera)
        {
            var tab = window.open("","Print Preview");
            tab.document.open();


        }
    // add dynamic iframe to DOM
        else
        {
        var strFrameName = ("printThis-" + (new Date()).getTime());

            var $iframe = $("<iframe id='" + strFrameName +"' src='about:blank'/>");

            if (!opt.debug) { $iframe.css({ position: "absolute", width: "0px", height: "0px", left: "-600px", top: "-600px" }); }

            $iframe.appendTo("body");

        }
    // allow iframe to fully render before action
    setTimeout ( function () {

        if ($.browser.opera)
            {
        var $doc = tab.document;
        } else
        {
        var $doc = $("#" + strFrameName).contents();
        }



        // import page css
        if (opt.importCSS)
        {
                $("link[rel=stylesheet]").each(function(){
                var href = $(this).attr('href');
                if(href){
                        var media = $(this).attr('media') || 'all';
                        $doc.find("head").append("<link type='text/css' rel='stylesheet' href='" + href + "' media='"+media+"'>");
                    }
        });
        }

        // add another stylesheet
        if (opt.loadCSS)
        {
        $doc.find("head").append("<link type='text/css' rel='stylesheet' href='" + opt.loadCSS + "'>");

        }

        //add title of the page
        if (opt.titlePage)
        {
        $doc.find("head").append('<title>'+opt.titlePage+'</title>');
        } 
        //grab outer container
        if (opt.printContainer) { $doc.find("body").append($element.outer()); }
        else { $element.each( function() { $doc.find("body").append($(this).html()); }); }

        //$doc.close();
        // print
        ($.browser.opera ? tab : $iframe[0].contentWindow).focus();
        setTimeout( function() { ($.browser.opera ? tab : $iframe[0].contentWindow).print(); if (tab) { tab.close(); } }, 1000);

        //removed iframe after 60 seconds
        setTimeout(
        function(){
        $iframe.remove();
        },
        (60 * 1000)
        );
    }, 333 );
    }


    $.fn.printThis.defaults = {
        debug: false, //show the iframe for debugging
        importCSS: true, // import page CSS
        printContainer: true, // grab outer container as well as the contents of the selector
        loadCSS: "", //path to additional css file
        titlePage: "" //add title to print page
    };


    jQuery.fn.outer = function() {
      return $($('<div></div>').html(this.clone())).html();
    }
})(jQuery);

UPDATE

Issue to due to document.domain

This type of page has document.domain set and IE does not inherit document.domain from the parent.

To fix that portion, I changed the iframe creation to standard javascript and set the source to write document.domain on iframe creation.

    var printI= document.createElement('iframe');

    printI.name = "printIframe";

    printI.id = strFrameName;

    document.body.appendChild(printI);

    printI.src = "javascript:document.write('<head><script>document.domain=\"mydomain.com\";</script></head><body></body>')";


   var $iframe = $("#" + strFrameName);

So this fixes the access denied, however now the frame won't print. I've tried a lot of different methods for accessing the object, however none of them are working.

A) how would you access the frame in this scenario (i've tried most of the methods outlined on SO) to get IE to recognize and print

or

B) can anyone think of a better way to get the document.domain into the iframe on creation with jQuery? (can't be afterwards, as the access denied issue will come up)

like image 803
Jason Avatar asked Feb 14 '13 16:02

Jason


1 Answers

In your code, you are using setTimeout to execute your function after the iframe has loaded.

// allow iframe to fully render before action
setTimeout ( function () {
...
}, 333 );  //333ms

but this is a mistake as you don't know if the time given is enough to load the iframe or not. Javascript execution is asynchronous so, there is no guarantee that setTimeout will offset the execution of the function until iframe loads. Since load time is different for different pages. Some cannot execute the code properly, pointing to the line which you find to be causing errors.

var $doc = $("#" + strFrameName).contents();  //only after loading

The correct way is to use event load or onload to get to know if the DOM object has loaded properly or not.

<script>
document.getElementById("myframe").onload = function() {
  alert("myframe is loaded");
};
</script>
//or
<iframe id="myFrame" onload="myFunction();"></iframe>
like image 60
user568109 Avatar answered Oct 12 '22 00:10

user568109