web-dev-qa-db-fra.com

jquery - accès iframe refusé dans IE sur certaines pages

Je suis l'auteur de printThis, un plugin JQuery pour l'impression.

https://github.com/jasonday/printThis

J'ai un utilisateur qui a soulevé un problème, que je n'ai pas pu résoudre et malheureusement, je ne peux pas partager la page (problèmes de confidentialité). 

Sur le site de l'utilisateur, le problème apparaît sur certaines pages d'IE, mais pas sur d'autres. L'impression échoue car l'iframe reste vide.

L'erreur dans IE est dans jQuery:

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

À l'aide de la journalisation, j'ai pu déterminer qu'il échouait autour de cette ligne:

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

Mais encore une fois, cela ne se produit que sur certaines pages et je n'ai pas été en mesure de le recréer en dehors du site de cet utilisateur.

Ma question: Y a-t-il une meilleure approche ici? ou une méthode pour rendre l'objet $doc plus résistant aux balles?


// -----------------------------------------------------------------------
// 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);

METTRE &AGRAVE; JOUR

Problème à cause de document.domain

Ce type de page a document.domain défini et IE n'hérite pas de document.domain du parent. 

Pour corriger cette partie, j'ai modifié la création d'iframe en javascript standard et paramétré la source pour écrire document.domain lors de la création d'iframe.

    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);

Donc, cela corrige l'accès refusé, mais maintenant, le cadre ne sera pas imprimé. J'ai essayé beaucoup de méthodes différentes pour accéder à l'objet, mais aucune d'entre elles ne fonctionne.

A) comment accéder au cadre dans ce scénario (j'ai essayé la plupart des méthodes décrites dans SO) pour que IE soit reconnu et imprimé

ou

B) Quelqu'un peut-il penser à un meilleur moyen de placer le document.domain dans l'iframe lors de la création avec jQuery? (ne peut pas être après, car le problème d'accès refusé sera soulevé)

12
Jason

Le problème est dû au fait que IE n'hérite pas du document.domain parent.

Malheureusement, une fois que vous avez pénétré dans cette zone trouble, il a fallu quelques piratages spécifiques pour que cela fonctionne correctement. 

Effectuer une vérification pour savoir si document.domain est explicitement défini et si le navigateur est IE. 

Plugin complètement mis à jour:

https://github.com/jasonday/printThis

(function ($) {
    var opt;
    $.fn.printThis = function (options) {
        opt = $.extend({}, $.fn.printThis.defaults, options);
        var $element = this instanceof jQuery ? this : $(this);

            var strFrameName = "printThis-" + (new Date()).getTime();

            if(window.location.hostname !== document.domain && navigator.userAgent.match(/msie/i)){
                // Ugly IE hacks due to IE not inheriting document.domain from parent
                // checks if document.domain is set by comparing the Host name against document.domain
                var iframeSrc = "javascript:document.write(\"<head><script>document.domain=\\\"" + document.domain + "\\\";</script></head><body></body>\")";
                var printI= document.createElement('iframe');
                printI.name = "printIframe";
                printI.id = strFrameName;
                printI.className = "MSIE";
                document.body.appendChild(printI);
                printI.src = iframeSrc;

            } else {
                 // other browsers inherit document.domain, and IE works if document.domain is not explicitly set
                var $frame = $("<iframe id='" + strFrameName +"' name='printIframe' />");
                $frame.appendTo("body");
            }


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

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


        // $iframe.ready() and $iframe.load were inconsistent between browsers    
        setTimeout ( function () {

            var $doc = $iframe.contents();

            // import page stylesheets
            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 title to iframe
            if (opt.pageTitle) $doc.find("head").append("<title>" + opt.pageTitle + "</title>");

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

            // grab $.selector as container
            if (opt.printContainer) $doc.find("body").append($element.outer());

            // otherwise just print interior elements of container
            else $element.each(function () {
                $doc.find("body").append($(this).html())
            });

            if($iframe.hasClass("MSIE")){
                // check if the iframe was created with the ugly hack
                // and perform another ugly hack out of neccessity
                window.frames["printIframe"].focus();
                setTimeout(function () {
                   $doc.find("head").append("<script>  window.print(); </script>");
                }, 500 );
            } else {
                // proper method
                $iframe[0].contentWindow.focus();
                $iframe[0].contentWindow.print();  
            }

             //remove iframe after print
            if (!opt.debug) {
                setTimeout(function () {
                    $iframe.remove();
                }, 1000);
            }


        }, 333 );

    };

    // defaults
    $.fn.printThis.defaults = {
        debug: false,           // show the iframe for debugging
        importCSS: true,        // import parent page css
        printContainer: true,   // print outer container/$.selector
        loadCSS: "",            // load an additional css file
        pageTitle: ""           // add title to print page
    };

    // $.selector container
    jQuery.fn.outer = function () {
        return $($("<div></div>").html(this.clone())).html()
    }
})(jQuery);
4
Jason

Dans votre code, vous utilisez setTimeout pour exécuter votre fonction après le chargement de l'iframe.

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

mais ceci est une erreur car vous ne savez pas si le temps imparti est suffisant pour charger l’iframe ou non. L'exécution de Javascript est asynchrone, donc rien ne garantit que setTimeout décale l'exécution de la fonction jusqu'au chargement de l'iframe. Depuis le temps de chargement est différent pour différentes pages. Certains ne peuvent pas exécuter le code correctement, pointant vers la ligne que vous trouvez être à l'origine des erreurs. 

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

La méthode correcte consiste à utiliser l'événement load ou onload pour savoir si l'objet DOM a été chargé correctement ou non.

<script>
document.getElementById("myframe").onload = function() {
  alert("myframe is loaded");
};
</script>
//or
<iframe id="myFrame" onload="myFunction();"></iframe>
6
user568109

Tant que vous définissez iframe src, la même origine doit être vérifiée par rapport à l'élément parent, même si vous la définissez comme 'about: blank'. J'imagine que IE échoue lors de la vérification correcte ou que du javascript a été exécuté et que le document.location est défini sur un emplacement différent de celui de l'iframe créé.

Que diriez-vous de ne pas définir src du tout comme suit? ça devrait quand même marcher.

var $iframe = $("<iframe id='" + strFrameName +"'/>");
$iframe.appendTo("body");
var $iframeDoc = $iframe[0].contentWindow.document;

$iframeDoc.open();
$iframeDoc.write("foo");
$iframeDoc.close();
5
allenhwkim

Cette réponse a déjà été énoncée dans la question initiale UPDATE, mais je voulais ajouter une réponse plus succincte à la question initiale relative à la contournement de l'erreur d'autorisation refusée SCRIPT70 (je l'ai rencontré sur IE11/Win7 avec JQuery 3.2.1).

Au lieu de $('<iframe .../>').appendTo($('body'))

Faire ceci: 

var $iframe = $('<iframe .../>');
document.body.appendChild($iframe[0]);

Réponse tirée d'ici: https://bugs.jquery.com/ticket/13936#comment:28

0
splashout

IE fonctionne avec iframe comme tous les autres navigateurs (du moins pour les fonctions principales). Vous devez juste garder un ensemble de règles:

  • avant de charger du code javascript dans l'iframe (cette partie de js devant connaître le parent de l'iframe), assurez-vous que le document.domain du parent a été modifié.
  • lorsque toutes les ressources iframe sont chargées, changez document.domain pour qu'il soit identique à celui défini dans parent. (Vous devez le faire ultérieurement car la définition de domaine entraînera l'échec de la demande de la ressource iframe)

  • vous pouvez maintenant faire une référence pour la fenêtre parente: var winn = window.parent

  • vous pouvez maintenant faire référence au code HTML parent afin de le manipuler: var parentContent = $ ('html', winn.document)
  • à ce stade, vous devriez avoir accès à la fenêtre/le document parent IE et vous pouvez le modifier comme vous le souhaitez.
0
Ionut Tocila