Как я могу получить доступ к элементам DOM в iFrame
Я пишу плагин jQuery, который должен иметь возможность работать с элементами DOM в iFrame. Я просто тестирую это локально прямо сейчас (т. е. url-адрес файла://.../образец.html) и в Chrome я продолжаю нажимать " SecurityError: не удалось прочитать свойство "contentDocument" из "HTMLIFrameElement": заблокирован кадр с исходным "null" от доступа к кадру перекрестного происхождения."и в Safari я просто получаю пустой документ.
учитывая, что как родительский файл, так и файл iFrame сойдя с моего локального диска (в разработке) и сойдя с того же сервера (в производстве), я бы подумал, что я не буду подвержен перекрестным проблемам.
есть ли способ убедить браузер, что мои локальные файлы на самом деле принадлежат одному домену?
интересно, что в Safari, используя консоль напрямую, я могу ввести $("iframe").get(0).contentDocument.find("ol")
и он с радостью находит мой список. В Chrome эта же строка выдает ошибку безопасности, как если бы она была выполненный.в сторону>
обновление
основываясь на приведенных ниже предложениях, я запустил простой локальный веб-сервер, чтобы проверить это, и теперь не получаю ошибку cross-origin - yay - но и я не получаю никакого контента.
мой Javascript выглядит как
$(document).ready(function(){
var myFrame = $("iframe"),
myDocument = $(myFrame.get(0).contentDocument),
myElements;
myDocument.ready(function(){
myElements = myDocument.find("ul, ol");
console.debug("success - iFrame", myFrame, "document", myDocument, "elements", myElements);
});
});
на myDocument.ready
есть ли просто убедиться, что документ iFrame готов - на самом деле это не имеет значения.
С myElements
будучи пустой. ([]
в safari или jQuery.fn.init[0]
в Chrome)
но если я вручную введу это в консоль:
$($("iframe").get(0).contentDocument).find("ol, ul")
Я получаю свои списки, как ожидалось. Это сейчас в Safari и Chrome.
Итак, мой вопрос: почему мой скрипт не может видеть элементы DOM, но тот же код, введенный непосредственно в консоль браузера, может с радостью видеть элементы DOM?
2 ответов
Chrome имеет ограничения безопасности по умолчанию, которые не позволяют получить доступ к другим окнам с жесткого диска, даже если они технически того же происхождения. В Chrome есть флаг, который может ослабить это ограничение безопасности (аргумент командной строки в Windows-это то, что я помню), хотя я бы не рекомендовал работать с этим флагом больше, чем быстрый тест. См.этот пост или в этой статье для получения информации о аргументе командной строки.
если вы запускаете файлы с веб-сервера (даже если это локальный веб-сервер) вместо жесткого диска, у вас не будет этой проблемы.
или вы можете протестировать в других браузерах, которые не являются столь ограничительными.
теперь, когда вы изменили вопрос на что-то другое, вам нужно дождаться загрузки окна iframe, прежде чем вы сможете получить доступ к содержимому в нем, и вы не можете использовать jQuery .ready()
на другом документе (он не работает на другом документе).
$(document).ready(function() {
// get the iframe in my documnet
var iframe = document.getElementById("testFrame");
// get the window associated with that iframe
var iWindow = iframe.contentWindow;
// wait for the window to load before accessing the content
iWindow.addEventListener("load", function() {
// get the document from the window
var doc = iframe.contentDocument || iframe.contentWindow.document;
// find the target in the iframe content
var target = doc.getElementById("target");
target.innerHTML = "Found It!";
});
});
тестовая страница здесь.
EDIT: после дальнейших исследований я обнаружил, что jQuery выполнит часть этой работы для вас, как это, и решение jQuery, похоже, работает во всех основных браузерах:
$(document).ready(function() {
$("#testFrame").load(function() {
var doc = this.contentDocument || this.contentWindow.document;
var target = doc.getElementById("target");
target.innerHTML = "Found It!";
});
});
тестовая страница здесь.
глядя на реализацию jQuery для этого, все, что он действительно делает, это настройка load
прослушиватель событий на iFrame себя.
если вы хотите знать мелкие детали, которые вошли в отладку / решение первого метода выше:
пытаясь решить эту проблему, я обнаружил некоторые довольно странные вещи (в Chrome) с iFrames. Когда вы впервые смотрите в окно iframe, есть документ, и он говорит, что его readyState === "complete"
такие, что вы думаете, что это сделано загрузка, но это ложь. Фактический документ и фактический <body>
тег из загружаемого документа через URL в iframe на самом деле еще нет. Я доказал это, поместив пользовательский атрибут на <body data-test="hello">
и проверка пользовательского атрибута. Глядь. Хотя document.readyState === "complete"
, этот пользовательский атрибут отсутствует в <body>
tag. Итак, я заключаю (по крайней мере, в Chrome), что iFrame изначально имеет фиктивный и пустой документ и тело в нем, которые не являются фактическими, которые будут на месте после Загрузки URL-адреса в iFrame. Это делает весь этот процесс обнаруживать когда он готов быть довольно запутанным (это стоило мне часов, выясняя это). На самом деле, если я установил таймер интервала и опрос iWindow.document.body.getAttribute("data-test")
, Я увижу его показать как undefined
неоднократно, а затем, наконец, он появится с правильным значением и все это с document.readyState === "complete"
что означает, что он полностью лжет.
я думаю, что происходит то, что iFrame начинается с фиктивного и пустого документа и тела, которое затем заменяется после загрузки содержимого. С другой стороны, iFrame window
это настоящее окно. Итак, единственные способы, которые я нашел, чтобы действительно ждать загрузки контента, - это контролировать load
событие на iFrame window
поскольку это, похоже, не врет. Если вы знали, что есть определенный контент, который вы ждете, вы также можете опросить, пока этот контент не будет доступен. Но даже тогда вы должны быть осторожны, потому что вы не можете забрать iframe.contentWindow.document
слишком рано, потому что это будет неправильный документ, если вы получите его слишком рано. Все это довольно сломанный. Я не могу найти способ использовать DOMContentLoaded
извне самого документа iFrame, потому что у вас нет способа узнать тогда фактический document
объект на месте, чтобы вы могли прикрепить к нему обработчик событий. Так. .. Я остановился на load
событие в окне iFrame, которое, похоже, работает.
если вы действительно контролируете код в iFrame, то вы можете вызвать событие более легко из самого iFrame, либо с помощью jQuery с $(document).ready()
в коде iFrame с его собственной версией jQuery или путем вызова функции в Родительском окне из скрипта, расположенного после целевого элемента (таким образом, гарантируя, что целевой элемент загружен и готов).
Далее Редактировать
после кучу больше исследований и тестирования, вот функция, которая скажет вам, когда iFrame попадает в DOMContentLoaded
событие, а не ждет load
событие (которое может занять больше времени с образами и стилем листы.)
// This function ONLY works for iFrames of the same origin as their parent
function iFrameReady(iFrame, fn) {
var timer;
var fired = false;
function ready() {
if (!fired) {
fired = true;
clearTimeout(timer);
fn.call(this);
}
}
function readyState() {
if (this.readyState === "complete") {
ready.call(this);
}
}
// cross platform event handler for compatibility with older IE versions
function addEvent(elem, event, fn) {
if (elem.addEventListener) {
return elem.addEventListener(event, fn);
} else {
return elem.attachEvent("on" + event, function () {
return fn.call(elem, window.event);
});
}
}
// use iFrame load as a backup - though the other events should occur first
addEvent(iFrame, "load", function () {
ready.call(iFrame.contentDocument || iFrame.contentWindow.document);
});
function checkLoaded() {
var doc = iFrame.contentDocument || iFrame.contentWindow.document;
// We can tell if there is a dummy document installed because the dummy document
// will have an URL that starts with "about:". The real document will not have that URL
if (doc.URL.indexOf("about:") !== 0) {
if (doc.readyState === "complete") {
ready.call(doc);
} else {
// set event listener for DOMContentLoaded on the new document
addEvent(doc, "DOMContentLoaded", ready);
addEvent(doc, "readystatechange", readyState);
}
} else {
// still same old original document, so keep looking for content or new document
timer = setTimeout(checkLoaded, 1);
}
}
checkLoaded();
}
это просто называется так:
// call this when you know the iFrame has been loaded
// in jQuery, you would put this in a $(document).ready()
iFrameReady(document.getElementById("testFrame"), function() {
var target = this.getElementById("target");
target.innerHTML = "Found It!";
});
учитывая, что как родительский файл, так и файл iFrame сходят с моего локального диска (в разработке) и будут сходить с того же сервера (в производстве), я бы подумал, что я не буду подвержен перекрестным проблемам происхождения.
"пожалуйста, откройте эту совершенно безобидной HTML-документ, который я прикрепил к этому письму."Есть веские причины для браузеров применять междоменную безопасность к локальным файлам.
есть как я могу убедить браузер, что мои локальные файлы на самом деле принадлежат одному домену?
установить веб-сервер. Тест через http://localhost
. В качестве бонуса получите все другие преимущества HTTP-сервера (например, возможность использовать относительные URI, начинающиеся с /
и разработка с кодом на стороне сервера).