JavaScriptの動かないコード (中級編) iframe内にDOMアクセスできないエラー
以下のJavaScriptコードが意図した動作をしないのは,なぜですか。(制限時間1分)
やりたい事:
- tenki.htmlの中に,天気が書かれている。
- index.htmlのインラインフレーム内に tenki.html を表示して,その内容を解析する。
※2つのHTMLは,同じフォルダ上にある。
index.html
<input type="button" value="クリックして天気を表示" onClick="f()"> <br> <!-- ここに tenki.html を表示します。 --> <iframe id="ifr"></iframe> <script language="JavaScript"> function f() { // iframeを取得 var e_ifr = document.getElementById( "ifr" ); // iframe内にページを表示 e_ifr.src = "tenki.html"; // 天気を取得 var e_tenki = frames["ifr"].document.getElementById("tenki"); alert("天気は" + e_tenki.innerHTML); } </script>
tenki.html
きょうの天気は <span id="tenki">晴れ</span> です。
答え
ボタンをクリックすると,IE・FFともにtenki.htmlが読み込まれる。
しかし,IEでは画面表示の直前で「オブジェクトがありません」のエラーが出る。
天気のアラートは表示されない。
もう一度クリックすると,なぜか2度目は「天気は晴れ」というアラートがちゃんと表示される。
Firefoxでは,画面は遷移するがDOMアクセスの段階でエラーになり,やはり天気のアラートは表示されない。
Firebugでは frames.ifr is undefined というメッセージが出る。
こちらは,何度クリックしてもだめ。
どちらのブラウザでも,ページは表示できているが,DOMアクセスに失敗しているのだ。
本題の原因ではないが,注意すべき点
(1)
まずIEでは,フレームのエレメントに
フレームエレメント.location.href = 〜
とすればページが切り替わるが,これだとFirefoxでは動かない。
かわりに
フレームエレメント.src = 〜
とすべき。
冒頭のコードは,この点はクリアしているので,画面がちゃんと遷移する。
firefoxでiframeをlocation.hrefしたい
http://blog.gpso.info/2006/09/firefox...
(2)
次に,フレーム内のHTML documentの指定方法だが,
frames[ フレームのid ].document
という指定方法はIE限定。
クロスブラウザなコードとしてcontentWindowを使い
フレームエレメント.contentWindow.document
とするべきだ。
(FirefoxならcontentDocumentを使ってもよい。)
iframe 内のwindowオブジェクトを参照するにはどうすれば良いでしょうか?
http://code.nanigac.com/forum/view/381
- IE の iframe 要素には contentWindow プロパティはあるようですが、contentDocument プロパティがない
(3)
次に,「iframeでアクセスができない」という時にまず最初に思い出したいのは,セキュリティ上の理由から「外部サイトをDOM操作することはできない」という点。
(=「アクセスが拒否されました」問題)
外部ページの文字の置換について
http://qa.daigakuin.ne.jp/qa4582112.html外部サイトのデータ(ウインドウ、フレーム等)へは「アクセス禁止」です。
書き込むことだけではなく、読み込むこともできません。
http://q.hatena.ne.jp/1222630954
ソースが外部サーバー(ドメイン)であるiframeは、クロスサイトの制限によってdomのアクセスが拒否されます。
しかし,冒頭のコードで表示しようとしたのは外部サイトではなく,同じフォルダ上にあるファイルだ。
自分のサイト内で準備したhtmlページを表示して,JavaScriptで操作したいだけなのに,どうして問題が生じるのか。
原因
答えは,IEでの挙動を観察すればわかる。
一度目のクリックでは,画面が表示完了していない状態でエラーが出る。
二度目のクリックでは,一度目のクリックの時に表示した画面がiframe内に既に存在するので,そこへのDOMアクセスが成功する。
つまりiframe内での画面遷移の遅延が原因となる。
これを解決するためには,iframeに対してonloadなどのイベントを設定する必要がある。
JavaScript インラインフレーム(iframe)関連
http://wiki.bit-hive.com/tomizoo/pg/J...
イベントの設定方法やフレーム内へのアクセス手段は,ブラウザごとに異なっている。
下記で,正しく動くコードを
- IE用
- FF用
- 両方用
の順番で見てみよう。
IE用
フレーム要素単体のエレメントではonloadがきかないので,かわりにonreadystatechangeイベントを割り当てる。
これはXmlHttpRequestでよく使うイベントだけど,通常のDOM要素にも適用できる。
<input type="button" value="クリックして表示" onClick="f()"> <br> <iframe id="ifr"></iframe> <script language="JavaScript"> function f() { // IEで動くコード var e_ifr = document.getElementById( "ifr" ); e_ifr.onreadystatechange = function(){ //http://wiki.bit-hive.com/tomizoo/pg/JavaScript%20%A5%A4%A5%F3%A5%E9%A5%A4%A5%F3%A5%D5%A5%EC%A1%BC%A5%E0(iframe)%B4%D8%CF%A2 // IEの場合、onloadプロパティに関数を登録しておいても動かない if( window.event.srcElement.readyState == "complete" ) // http://language-and-engineering.hatenablog.jp/entry/20081124/1227474876 // IEではグローバルの変数として "event" がある { // 天気を取得 var e_tenki = e_ifr.contentWindow.document.getElementById("tenki"); alert("天気は" + e_tenki.innerHTML); } }; // フレーム内にページを表示 e_ifr.src = "tenki.html"; } </script>
FF用
IEのようにはひねらず,onloadが使える。
<input type="button" value="クリックして表示" onClick="f()"> <br> <iframe id="ifr"></iframe> <script language="JavaScript"> function f() { // ffで動くコード var e_ifr = document.getElementById( "ifr" ); e_ifr.onload = function(){ // 天気を取得 var e_tenki = e_ifr.contentWindow.document.getElementById("tenki"); // http://questionbox.jp.msn.com/qa2575056.html alert("天気は" + e_tenki.innerHTML); }; // フレーム内にページを表示 e_ifr.src = "tenki.html"; } </script>
クロスブラウザなコード
上記2パターンを統合すると,例えばこんな風になる。
<input type="button" value="クリックして表示" onClick="f()"> <br> <iframe id="ifr"></iframe> <script language="JavaScript"> function f() { // ie + ffで動くコード var e_ifr = document.getElementById( "ifr" ); // iframeに読み込み完了時のイベントを設定 ( function( target_func ){ // 読み込み完了時のイベントリスナを設定 if( /*@cc_on ! @*/ false ) { // IE用 e_ifr.onreadystatechange = function(){ if( window.event.srcElement.readyState == "complete" ) { target_func(); } } } else { // FF用 e_ifr.onload = target_func; } } ) ( function(){ // 定義した無名関数に,引数として別の無名関数を渡す // iframe内のdocumentを取得 var doc = e_ifr.contentWindow.document; // docは読み込みが終わってから評価する必要がある。 // 天気を取得 var e_tenki = doc.getElementById("tenki"); alert("天気は" + e_tenki.innerHTML); } ); // iframe内にページを表示 e_ifr.src = "tenki.html"; } </script>
@cc_on は,IE用の条件付きコンパイルのステートメント。
if( /*@cc_on ! @*/ false )でIEかどうか判定ができる。
@cc_on ってなに?
http://blog.clouder.jp/archives/00100...
この判定結果をもとに,iframe内のdocumentの取得と,イベントの割り当てを行なっている。
補足
自サイト内で通信やらページ部品呼び出しやらを行ないたいのならば,通常はAjaxなりCGIなりを介せば済むし,DOM操作で困る事もない。
普通はそういう手段で済ませたい。
しかしHTAで業務アプリを云々していると,そうはいかない時もある。…
関連する記事:
JavaScriptの動かないコード (中級編) DOMで子要素を指定する際のエラー
http://language-and-engineering.hatenablog.jp/entry/20081101/1225717167
JavaScriptの動かないコード (中級編) iframe内のDOM要素を別フレームにコピーできないエラー
http://language-and-engineering.hatenablog.jp/entry/20090214/p1
JavaScriptの動かないコード (初級編) DOMアクセスできない
http://language-and-engineering.hatenablog.jp/entry/20080828/1219938234