web-dev-qa-db-ja.com

iFrame内のDOM要素にアクセスするにはどうすればよいですか

IFrame内のDOM要素に対して実行できる必要があるjQueryプラグインを書いています。私は今これをローカルでテストしています(つまり、urlはfile://.../example.htmlです)でChromeを押し続けます。「SecurityError:「contentDocument」の読み取りに失敗しました'HTMLIFrameElement'のプロパティ:Originがnullのフレームがクロスオリジンフレームにアクセスするのをブロックしました。」Safariでは、空のドキュメントを取得するだけです。

親ファイルとiFrameのファイルの両方がローカルディスク(開発中)から出ており、同じサーバー(実稼働中)から出てくることを考えると、私はクロスオリジンの問題の影響を受けないと思っていたでしょう。 。

ローカルファイルが実際に同じドメインのものであることをブラウザーに納得させる方法はありますか?

<aside>おもしろいことに、Safariでは、直接コンソールを使用して、$("iframe").get(0).contentDocument.find("ol")と入力すれば、リストが見つかります。 In Chromeこの同じ行は、実行されているかのようにセキュリティエラーをスローします。</ aside>

更新

以下の提案に基づいて、これをテストするために簡単なローカルWebサーバーを起動しましたが、クロスオリジンエラーは発生していません-いや-また、コンテンツも取得していません。

私の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の両方に当てはまります。

だから私の質問は次のようになります:なぜ私のスクリプトはDOM要素を見ることができませんが、ブラウザのコンソールに直接入力したときに同じコードがDOM要素を喜んで見ることができますか?

19
Dave Sag

Chromeには、技術的には同じOriginであっても、ハードディスクから他のウィンドウにアクセスできないデフォルトのセキュリティ制限があります。 Chromeにそのセキュリティ制限を緩和できるフラグがあります(Windowsのコマンドライン引数は私が覚えているものです)が、簡単なテスト以上にそのフラグで実行することはお勧めしません。コマンドライン引数については this post または this article をご覧ください。

ハードドライブではなく、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!";
    });
});
_

テストページ ここ


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実装を見ると、iFrame自体にloadイベントリスナーを設定するだけです。


上記の最初の方法をデバッグ/解決するために行った細かい点について知りたい場合:

この問題を解決しようとして、iChromeで(Chromeで)かなり奇妙なことを発見しました。最初に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 loadwindowイベントを監視することです。待機している特定のコンテンツがあることがわかっている場合は、そのコンテンツが利用可能になるまでポーリングすることもできます。しかし、それでも、_iframe.contentWindow.document_をすぐに取得することはできないので注意する必要があります。全体がかなり壊れています。 iFrameドキュメント自体の外部からDOMContentLoadedを使用する方法が見つかりません。実際のdocumentオブジェクトが配置されていることを知る方法がないため、イベントハンドラをアタッチできます。 。だから...私はiFrameウィンドウのloadイベントに落ち着きましたが、動作するようです。


実際にiFrameのコードを制御する場合は、独自のバージョンのjQueryでiFrameコードの$(document).ready()とともにjQueryを使用するか、関数を呼び出すことにより、iFrame自体からイベントをより簡単にトリガーできます。ターゲット要素の後にあるスクリプトからの親ウィンドウで(したがって、ターゲット要素が読み込まれて準備ができていることを確認します)。


さらに編集する

さらに多くの調査とテストの後、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!";
});
_
28
jfriend00

親ファイルとiFrameのファイルの両方がローカルディスク(開発中)から出ており、同じサーバー(実稼働中)から出てくることを考えると、私はクロスオリジンの問題の影響を受けないと思っていたでしょう。 。

「これを開いてください完全に無害このメールに添付したHTMLドキュメント。」ブラウザがローカルファイルにクロスドメインセキュリティを適用するのには、十分な理由があります。

ローカルファイルが実際に同じドメインのものであることをブラウザーに納得させる方法はありますか?

Webサーバーをインストールします。 http://localhostでテストします。おまけとして、HTTPサーバーのその他の利点(/で始まる相対URIを使用し、サーバー側コードで開発できるなど)をすべて入手してください。

1
Quentin