Подтвердить что ты не робот

Как я могу получить доступ к элементам DOM в iFrame

Я пишу плагин jQuery, который должен иметь возможность запускать элементы DOM в iFrame. Я просто тестирую это локально прямо сейчас (т.е. Url is file://.../example.html), а в Chrome я продолжаю бить "SecurityError: не удалось прочитать свойство contentDocument из" HTMLIFrameElement ": заблокирован кадр с источником" null "от доступа к кадру с перекрестным началом". и в Safari я просто получаю пустой документ.

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

Есть ли способ убедить браузер, что мои локальные файлы на самом деле одного домена?

<aside> Интересно, что в Safari, используя консоль напрямую, я могу ввести $("iframe").get(0).contentDocument.find("ol"), и он счастливо находит мой список. В Chrome эта же строка выдает ошибку безопасности так же, как если бы она выполнялась. </aside>

Обновление

Основываясь на приведенных ниже предложениях, я запустил простой локальный веб-сервер, чтобы проверить это, и теперь я не получаю ошибку перекрестного происхождения - 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 пуст. ([] в сафари или jQuery.fn.init[0] в Chrome)

Но если я вручную наберу это в консоли:

$($("iframe").get(0).contentDocument).find("ol, ul")

Я получаю свои списки, как ожидалось. Теперь это происходит как в Safari, так и в Chrome.

Итак, мой вопрос: почему мой script не видит элементы DOM, а тот же код при вводе непосредственно в консоль браузера может с радостью видеть элементы DOM?

4b9b3361

Ответ 1

У 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>. Итак, я заключаю (по крайней мере, в 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, либо путем вызова функции в родительское окно из script, расположенное после вашего целевого элемента (таким образом, чтобы целевой элемент был загружен и готов).


Дальнейшее редактирование

После целой серии исследований и тестирования, здесь функция, которая сообщит вам, когда 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!";
});

Ответ 2

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

"Пожалуйста, откройте этот совершенно безобидный HTML-документ, который я приложил к этому письму". Есть достаточные основания для того, чтобы браузеры применяли междоменную защиту к локальным файлам.

Есть ли способ убедить браузер, что мои локальные файлы на самом деле одного домена?

Установите веб-сервер. Проверить через http://localhost. В качестве бонуса получите все другие преимущества HTTP-сервера (например, возможность использовать относительные URI, которые начинаются с / и разрабатываются с использованием кода на стороне сервера).