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

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

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

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

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!";
    });
});

Тестовая страница здесь.


РЕДАКТИРОВАТЬ: После дальнейших исследований я обнаружил, что 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 или путем вызова функции в родительском окне из скрипта, расположенного после целевого элемента (таким образом, гарантируя, что целевой элемент загружен и готов).


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

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

Другие вопросы по тегам