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