スポンサーリンク

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>
です。

答え


ボタンをクリックすると,IEFFともに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