Jquery - доступ к iframe запрещен в IE на некоторых страницах

Я автор printThis, плагин jquery для печати. ​​

https://github.com/jasonday/printThis

У меня есть пользователь, который вызвал проблему, что я не смог взломать, и, к сожалению, я не могу разделить эту страницу (проблемы конфиденциальности).

На сайте пользователя проблема возникает на некоторых страницах в IE, но не на других. Печать не выполняется, поскольку iframe остается пустым.

Ошибка в IE находится внутри jQuery:

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

Используя журнал, я смог определить, что он не работает в этой строке:

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

Но опять же, это происходит только на некоторых страницах, и я не смог воссоздать ни в одном экземпляре вне этого пользовательского сайта.

Мой вопрос: есть ли здесь лучший подход? или способ сделать объект $doc более пуленепробиваемым?


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

Проблема из-за document.domain

Этот тип страницы имеет document.domain, а IE не наследует document.domain от родителя.

Чтобы исправить эту часть, я изменил создание iframe на стандартный javascript и установил источник для записи document.domain при создании 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);

Таким образом, это устраняет отказ доступа, однако теперь кадр не будет печатать. Я пробовал много разных методов для доступа к объекту, однако ни один из них не работает.

A), как бы вы получили доступ к кадру в этом сценарии (я пробовал большинство методов, описанных в SO), чтобы IE распознал и распечатал

или

B) может ли кто-нибудь подумать о лучшем способе получения документа .domain в iframe при создании с помощью jQuery? (не может быть потом, поскольку проблема с доступом запрещена)

Ответ 1

Проблема связана с тем, что IE не наследует родительский document.domain.

К сожалению, как только вы попадаете в эту темную область, для того, чтобы это нормально работало, потребовались определенные хаки.

В основном выполняется проверка того, явно ли установлен document.domain, а браузер - IE.

Полный обновленный плагин:

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

Ответ 2

В вашем коде вы используете setTimeout для выполнения своей функции после загрузки iframe.

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

но это ошибка, поскольку вы не знаете, достаточно ли времени для загрузки iframe или нет. Выполнение Javascript является асинхронным, поэтому нет гарантии, что setTimeout будет компенсировать выполнение функции до загрузки iframe. Поскольку время загрузки различно для разных страниц. Некоторые из них не могут правильно выполнить код, указывая на строку, которая, по вашему мнению, вызывает ошибки.

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

Правильный способ - использовать событие load или onload, чтобы узнать, правильно ли загружен объект DOM или нет.

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

Ответ 3

Пока вы устанавливаете iframe src, то же самое происхождение должно быть проверено в отношении родительского элемента, даже если вы установите его как "about: blank". Я предполагаю, что IE не работает в правильном checkng или запущен javascript и устанавливает document.location в другое, чем iframe.

Как насчет того, чтобы установить src вообще не так, как показано ниже? он все равно должен работать.

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

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

Ответ 4

IE работает с iframe, как и все другие браузеры (по крайней мере, для основных функций). Вам просто нужно соблюдать набор правил:

  • перед загрузкой любого javascript в iframe (эта часть js, которая должна знать о родительском элементе iframe), убедитесь, что у родителя изменился document.domain.
  • когда загружаются все ресурсы iframe, измените document.domain таким же, как тот, который определен в родительском. (Вам нужно сделать это позже, потому что настройка домена приведет к сбою запроса ресурса iframe)

  • теперь вы можете сделать ссылку для родительского окна: var winn = window.parent

  • теперь вы можете сделать ссылку на родительский HTML, чтобы манипулировать им: var parentContent = $('html', winn.document)
  • В этот момент у вас должен быть доступ к родительскому окну/документу IE, и вы можете изменить его, как обычно,

Ответ 5

Этот ответ уже был указан в исходном вопросе UPDATE, но я хотел добавить более краткий ответ на исходный вопрос, связанный с тем, чтобы обойти ошибку отказа от разрешения SCRIPT70 (я столкнулся с этим в IE11/Win7 с JQuery 3.2.1).

Вместо $('<iframe .../>').appendTo($('body'))

Сделайте это:

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

Ответ отсюда: https://bugs.jquery.com/ticket/13936#comment:28