簡體   English   中英

如何在iFrame中訪問DOM元素

[英]How can I access the DOM elements within an iFrame

我正在編寫一個jQuery插件,需要能夠在iFrame中針對DOM元素運行。 我現在正在本地測試這個(即url是file://.../example.html),在Chrome中我一直在點擊“SecurityError:無法從'HTMLIFrameElement'讀取'contentDocument'屬性:阻止了一個框架原點“null”來訪問跨域框架。“ 在Safari中我只得到一個空文檔。

鑒於父文件和iFrame的文件都來自我的本地磁盤(在開發中)並且將從相同的服務器(生產中)下來,我認為我不會受到跨源問題的影響。

有沒有辦法可以說服瀏覽器我的本地文件實際上是同一個域?

<旁邊> 有趣的是在Safari中,直接使用控制台,我可以輸入$("iframe").get(0).contentDocument.find("ol") ,它很高興找到我的列表。 在Chrome中,同一行會拋出安全錯誤,就像它正在執行一樣。 </一旁>

更新

基於下面的建議,我已經啟動了一個簡單的本地Web服務器來測試這個,現在我沒有得到跨源錯誤 - 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為空。 [] in safari或Chrome中的jQuery.fn.init[0]

但是,如果我手動將其鍵入控制台:

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

我按預期得到了我的清單。 Safari和Chrome現在都是這種情況。

所以我的問題變成了:為什么我的腳本不能看到DOM元素,但是當直接輸入瀏覽器的控制台時,相同的代碼可以很高興地看到DOM元素?

Chrome具有默認安全限制,即使它們在技術上是相同的來源,也不允許您從硬盤訪問其他窗口。 Chrome中有一個標志可以放寬安全限制(Windows上的命令行參數是我記得的),但我不建議使用該標志運行以進行快速測試。 有關命令行參數的信息,請參閱此文章本文

如果您從Web服務器(即使它是本地Web服務器)而不是您的硬盤驅動器運行文件,您將不會遇到此問題。

或者,您可以在其他不受限制的瀏覽器中進行測試。


現在您已將問題更改為不同的內容,您必須等待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實現時,它真正做的就是在iFrame上設置一個load事件監聽器。


如果您想了解調試/解決上述第一種方法的細節:

在我試圖解決這個問題時,我發現了一些非常奇怪的東西(在Chrome中)與iFrames。 當你第一次看到iframe的窗口時,有一個文件,它說它的readyState === "complete" ,你認為它已經完成加載,但它正在撒謊。 從URL加載到iframe的文檔中的實際文檔和實際<body>標記實際上還沒有。 我通過在<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是真正的窗口。 因此,我發現實際等待加載內容的唯一方法是監視iFrame window上的load事件,因為這似乎並不存在。 如果您知道有一些您正在等待的特定內容,您也可以進行輪詢直到該內容可用。 但是,即使這樣你也要小心,因為你不能iframe.contentWindow.document獲取iframe.contentWindow.document因為如果你過早地獲取它將會是錯誤的文檔。 整件事情非常糟糕。 我找不到任何方法在iFrame文檔本身之外使用DOMContentLoaded ,因為您無法知道實際的document對象是否到位,您可以將事件處理程序附加到它。 所以...我在iFrame窗口上安裝了load事件,這似乎有效。


如果您實際控制了iFrame中的代碼,那么您可以更輕松地從iFrame本身觸發事件,方法是在iFrame代碼中使用帶有$(document).ready()的jQuery和它自己的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文檔。” 瀏覽器有充分的理由將跨域安全性應用於本地文件。

有沒有辦法可以說服瀏覽器我的本地文件實際上是同一個域?

安裝Web服務器。 通過http://localhost測試。 作為獎勵,獲得HTTP服務器的所有其他好處(例如能夠使用以/開頭並使用服務器端代碼開發的相對URI)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM