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要素を喜んで見ることができますか?
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 load
のwindow
イベントを監視することです。待機している特定のコンテンツがあることがわかっている場合は、そのコンテンツが利用可能になるまでポーリングすることもできます。しかし、それでも、_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!";
});
_
親ファイルとiFrameのファイルの両方がローカルディスク(開発中)から出ており、同じサーバー(実稼働中)から出てくることを考えると、私はクロスオリジンの問題の影響を受けないと思っていたでしょう。 。
「これを開いてください完全に無害このメールに添付したHTMLドキュメント。」ブラウザがローカルファイルにクロスドメインセキュリティを適用するのには、十分な理由があります。
ローカルファイルが実際に同じドメインのものであることをブラウザーに納得させる方法はありますか?
Webサーバーをインストールします。 http://localhost
でテストします。おまけとして、HTTPサーバーのその他の利点(/
で始まる相対URIを使用し、サーバー側コードで開発できるなど)をすべて入手してください。