DOM


DOM

DOM (Document Object Model) は、XML や HTML で記述されたドキュメントを操作するための API で、W3C (World Wide Web Consortium) によって策定された。

XHTML

XHTML (Extensible HyperText Markup Language) は、HTMLをXMLの文法で定義し直した マークアップ言語である。 ちなみにWikipediaによれば、時おり見かける "eXtensible HyperText Markup Language" との記述は間違いで「XはExを表している」とのこと。

XHTMLはHTMLに次の規則をプラスしたもの、と理解しておけば十分。

sandbox.html
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html
 PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>Sandbox</title>
  </head>
  <body>
    <div id="container">
      <h3 id="title">カート</h3>
      <ul id="list">
        <li id="apple">りんご</li>
        <li>バナナ</li>
      </ul>
      <input id="new_value" type="text" />
      <input id="add_button" type="button" value="追加" />
    </div>
  </body>
</html>

「DOMツリー」とは文章の階層構造をツリー構造で表現したもので、要素をノードとして扱う。

javascript からは、これらのノードを「ノードの種類に対応したオブジェクト」 として扱う。目的とする要素のElementノードを取得するには次の3通りの方法がある。

要素を操作するには、3通りの方法がある。

属性の値を取得する方法は2通りある。

属性の値を設定する方法も3通りある。


イベント処理

イベント処理を実現するには3通りの方法がある。

イベントリスナの登録

イベントリスナの登録に使うメソッドは次の3種類である。

addEventListener(イベント種別,イベントハンドラ,動作モード)

「イベント種別」は文字列として指定する。

load, unload, abort, error, select, change, submit, reset, focus, blur, resize, scroll,
click, mousedown, mouseup, mouseover, mousemove, mouseout,
keyup, keydown

「イベントハンドラ」は、「イベントが発生した場合に実行したい処理」を渡す。 本来は「handleEventというプロパティの値」として設定したオブジェクトを 指定するのだが、通常は関数名を渡して問題ない。

  document.getElementById('add_button')
          .addEventListener('click',
	                    function(event) {
                              console.log(event.timeStamp);
                             }, false);

  document.getElementById('add_button')
          .addEventListener('click', {
            handleEvent: function(event) { console.log(event.timeStamp); }
          }, false);

「動作モード」は「キャプチャフェーズで動作するかどうか」を表す。 DOMにおけるイベント処理は、「キャプチャフェース」と「バブリングフェーズ」 という2段階のフェーズで実行される。

イベントの伝播を途中で止めたい場合は、Eventインターフェイスの stopPropagationメソッドを呼ぶ。 下の例では、キャプチャフェーズで、DOMの木構造で'list'要素より 下の要素には'click'イベントが伝わらなくなる。

  document.getElementById('list')
          .addEventListener('click', function (event) {
            console.log('capture: list' + event.target.innerHTML);
            event.stopPropergation(); // 下の要素へはイベントを伝播しない
          }, true);

removeEventListener(イベント種別,イベントハンドラ,動作モード)

登録したイベントリスナを削除するためには、 addEventListenerで登録したときと同じものを引数に指定して removeEventListenerを呼び出す。

イベント処理をページに仕込む

HTMLからjavascriptのプログラムをロードする方法は2通りある。

script要素はHTML中のの部分にでも記述できる。 通常はhead要素の中に記述するが、body要素の内部に記述しても問題ない。 ブラウザはページの読み込み中にscript要素を見つけると即座にその内容を実行する。

Globalオブジェクトは1つであるが、 script要素単位で実行される ことに注意が必要である。

  [エラーが起きる]
    <script type="text/javascript">
    f();    // error
    </script>
    <script type="text/javascript">
    function f() { alert('hello'); }
    </script>
  [エラーが起きない]
    <script type="text/javascript">
    function f() { alert('hello'); }
    </script>
    <script type="text/javascript">
    f();    // alert表示にhelloと表示される。
    </script>

JavaScriptのライブラリを複数読み込む場合は、依存関係の順に読み込むように 注意が必要である。 また、ブラウザがDOMツリーを構築する前に、JavaScriptのプログラムが DOMツリーを参照する(イベントリスナを登録するなど)と、ノードが見つからない というエラーが起きる。

  [エラーが起きる]
    <script type="text/javascript">
    document.getElementById('title')
            .addEventListener('click',function(event) {
              console.log(event.target.innerHTML);
            }, false);
    </script>
    <h3 id="title">カート<h3>

したがって、「JavaScriptのプログラムはhead要素で記述しておき、 loadイベントが発生したときに実行を開始する」ように設定するのが 望ましいと思われる。 loadイベントは、ページ中の全てのソースの読み込みが完了したときに発生する イベントである。

  <head>
  ...
    <script type="text/javascript">
      window.addListener('load',function() {
        // ここに処理を記述する
      }, false);
    </script>
  </head>
  または
  <body onload="javascriptの関数呼び出し">
  </body>

セキュリティについて

XSS

XSS (Cross Site Scripting)とは、「対象となるサーバに悪意のあるスクリプトを送り込み、 訪問者のブラウザ上で実行させる攻撃手法」である。 他のユーザになりすましたり、サイトの内容を書き換えるなどできてしまう。 本来は悪意のないページに自分のプログラムを表示させるように仕込むことで、攻撃を開始する。

たとえば、WWWサーバ上に 「誰かが投稿した文字列をそのまま画面に表示してしまう掲示板」の Webページがあったとする。 その場合、投稿文章中にjavascriptのプログラムをまぜておかれると、 そのページを表示したユーザのブラウザ内でそのプログラムが動作させられてしまう。

  (例) <script> alert('XSS'); </script>

document.cookieを外部に送信したり、 ページの内容をDOMツリーを操作して書き換えたりできるので 大変危険である。

対策は、「Webサーバ側でユーザから送られた文字列は HTMLエスケープ(HTMLにおいて特別の意味を持つ文字を置き換える) してから表示する」ことである。

CSRF

CSRF (Cross-Site Request Forgeries) とは、「対象となるサービスに強制的に リクエストを送信させる攻撃手法」である。 悪意のあるページになんらかの方法でアクセスさせることで、攻撃を開始する。

ユーザ U のブラウザが、正常なWWWサーバ A と通信し、セッションを 張っていたり、クッキーを取得していたりして、認証を受けた状態であるとする。 その後、ユーザ U が同じブラウザで悪意のあるWWWサーバ B にアクセスして Bの罠ページのscriptが動作すると、ユーザUのブラウザからWWWサーバAへ なんらかのリクエストを送ることがありえる。 ユーザ U の意志には反しているのだが、認証の済んだ正しいブラウザから アクセスされているのでWWWサーバ A はユーザUの要求だと思ってリクエストを 実行してしまう。パスワード変更、送金、など大変なことが起こり得る。

対策は、第三者が知ることができない情報をパラメータで送っておき、それを サーバ側で確認すること。たとえばhidden属性で情報を送っておいて、 正常なリクエストではその情報が含まれるようにすると、 CSRFで生成されたリクエストではその情報がないことで区別ができる。

JavaScript Hijacking

JSON (JavaScript Object Notation) データの読み込みをフックする攻撃手法。 JSONで送受信するデータが攻撃者に渡る可能性がある。

JSONとは Ajaxを用いたWebアプリケーション環境で、データの送受信によく利用されている。 JSONデータはJavaScriptのプログラムでもあるのでscript要素で指定して外部から参照することができる。

  [JSONのデータロード]
  <script type="text/javascript" src="http://ホスト/search?format=json">
  </script>

標的とされたWebサーバにユーザがログインしている状態で、悪意あるjavascriptプログラムが 実行されると、標的とされたWebサーバ上のJSONデータを読み出されて攻撃者に送られてしまう。


JavaScriptの例

JavaScriptの例1

js_sample01.html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <script language="JavaScript" type="text/javascript">
  //<![CDATA
function main() {
    var img = document.getElementById("js_image");
    var count = 0;
    while (true) {
	alert("change image");
	if (++count % 2 == 1) img.src = "tsuda02.jpg";
	else img.src = "tsuda01.jpg";
    }
}
  //]]>
  </script>
</head>
<body onload="main()">
  <img id="js_image" src="tsuda01.jpg" />
</body>
</html>

JavaScriptの例2

js_sample02.html
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <script language="JavaScript" type="text/javascript">
    //<![CDATA
var img;
var count;
var timer;
function main() {
    img = document.getElementById("js_image");
    count = 0;
    timer = setInterval(chgImage, 1000);
}
function chgImage() {
    if (++count % 2 == 1) img.src = "tsuda02.jpg";
    else img.src = "tsuda01.jpg";
    if (count > 5) clearInterval(timer);
}
//]]>
  </script>
</head>
<body onload="main()">
  <img id="js_image" src="tsuda01.jpg" />
</body>
</html>

JavaScriptの例3

js_sample03.html
<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Script-Type" content="text/javascript" />
  <script language="JavaScript" type="text/javascript">
    //<![CDATA
var img;
var count;
function main() {
    img = document.getElementById("js_image");
    count = 0;
}
function chgImage() {
    if (++count % 2 == 1) img.src = "tsuda02.jpg";
    else img.src = "tsuda01.jpg";
}
//]]>
  </script>
</head>
<body onload="main()">
  <img id="js_image" src="tsuda01.jpg" />
  <form action="javascript:void%200" onsubmit="chgImage(); return false;">
    <button type="submit">change</button>
  </form>
</body>
</html>