DOM (Document Object Model) は、XML や HTML で記述されたドキュメントを操作するための API で、W3C (World Wide Web Consortium) によって策定された。
XHTML (Extensible HyperText Markup Language) は、HTMLをXMLの文法で定義し直した マークアップ言語である。 ちなみにWikipediaによれば、時おり見かける "eXtensible HyperText Markup Language" との記述は間違いで「XはExを表している」とのこと。
XHTMLはHTMLに次の規則をプラスしたもの、と理解しておけば十分。
sandbox.html |
|
javascript からは、これらのノードを「ノードの種類に対応したオブジェクト」 として扱う。目的とする要素のElementノードを取得するには次の3通りの方法がある。
console.log(document.body .childNodes[1] // id=container .childNodes[1] // id=title .firstChild // Textノード .nodeValue); // Textノードの内容 "カート"
console.log(document.getElementById('title') .firstChild // Textノード .nodeValue); // Textノードの内容 "カート"
console.log(document.getElementsByTagName('li')[1] .firstChild // Textノード .nodeValue); // Textノードの内容 "バナナ"
要素を操作するには、3通りの方法がある。
document.getElementById('apple') .firstChild .nodeValue = 'リンゴ';
var tomato = document.createElement('li'); tomato.id = 'tomato'; tomato.appendChild(document.createTextNode('トマト')); document.getElementById('list') .appendChild(tomato); // 追加 var melon = document.createElement('li'); melon.id = 'melon'; melon.appendChild(document.createTextNode('メロン')); document.getELementById('list') .replaceChild(melon,documentElementById('apple')); // 置き換え
Elementノードには innerHTML というプロパティがあり、 これがその要素より下の木構造をHTML文字列として保持している。 このinnerHTMLの値を上書きしたり、追加したりすることで、その要素以下の 木構造を変更することができる。
document.getElementById('list').innerHTML += '<li id="tomato">トマト</li>';
innerHTMLへの代入は非常に遅い(ブラウザのrefreshが起きる)ので注意が必要。
[悪い例] var list = document.getElementById('list'); for (var i=0; i<5; i++) { list.innerHTML += '<li>' + i + '</li>'; } [良い例] var buf = ''; for (var i=0; i<5; i++) { buf += '<li>' + i + '</li>'; } document.getElementById('list').innerHTML += buff;
innerHTMLはタグの内容が解釈されるので、内容が可変の文字を出力する場合は HTMLエスケープを施して実行されないようにする必要がある(XSS対策)。
// 'りんご'が'a'に変わるが、その'a'の上にマウスがくるとコードが実行されてしまう。 document.getElementById('apple').innerHTML = '<li onmouseover="alert(\'XSS\')">a</li>';
属性の値を取得する方法は2通りある。
var type = document.getElementById('add_button') .getAttribute('type'); // 'button'という値が取れる
var type = document.getElementById('add_button') .getAttributeNode('type') .value; // 'button'という値が取れる
属性の値を設定する方法も3通りある。
var type = document.getElementById('add_button') .setAttribute('type','checkbox');
document.getElementById('add_button') .getAttributeNode('type') .value = "checkbox";
var type = document.createAttribute('type'); type.value = 'checkbox'; document.getElementById('add_button') .setAttributeNode(type);
イベント処理を実現するには3通りの方法がある。
(例) <input type="button" onclick="alert('Hello');" />
function handler() { alert('hello'); } document.getElementById('add_button').onclick = handler;
大規模なソフトウェアでは、この方法を取るべき。
イベントリスナの登録に使うメソッドは次の3種類である。
「イベント種別」は文字列として指定する。
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段階のフェーズで実行される。
イベントが発生する(イベントは発生したノードをtargetノードという)と、 ブラウザはルートからtargetノードへとDOMツリーを順にたどり、 その過程で対応するイベントリスナを順に実行する。
キャプチャフェーズの後、こんどはtargetノードからルートノードへとたどって、 その過程で体操するイベントリスナを順に実行する。
イベントの伝播を途中で止めたい場合は、Eventインターフェイスの stopPropagationメソッドを呼ぶ。 下の例では、キャプチャフェーズで、DOMの木構造で'list'要素より 下の要素には'click'イベントが伝わらなくなる。
document.getElementById('list') .addEventListener('click', function (event) { console.log('capture: list' + event.target.innerHTML); event.stopPropergation(); // 下の要素へはイベントを伝播しない }, true);
登録したイベントリスナを削除するためには、 addEventListenerで登録したときと同じものを引数に指定して removeEventListenerを呼び出す。
HTMLからjavascriptのプログラムをロードする方法は2通りある。
<script type="text/javascript"> //<![CDATA[ alert('hello'); //]]> </script>
script要素を認識できない古いブラウザでは、javascriptのプログラムがコメントとして 扱われるように <![CDATA[ ... ]] > で囲んでいます。
<script type="text/javascript" charset="utf-8" src="js/sandbox.js"> </script>
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 (Cross Site Scripting)とは、「対象となるサーバに悪意のあるスクリプトを送り込み、 訪問者のブラウザ上で実行させる攻撃手法」である。 他のユーザになりすましたり、サイトの内容を書き換えるなどできてしまう。 本来は悪意のないページに自分のプログラムを表示させるように仕込むことで、攻撃を開始する。
たとえば、WWWサーバ上に 「誰かが投稿した文字列をそのまま画面に表示してしまう掲示板」の Webページがあったとする。 その場合、投稿文章中にjavascriptのプログラムをまぜておかれると、 そのページを表示したユーザのブラウザ内でそのプログラムが動作させられてしまう。
(例) <script> alert('XSS'); </script>
document.cookieを外部に送信したり、 ページの内容をDOMツリーを操作して書き換えたりできるので 大変危険である。
対策は、「Webサーバ側でユーザから送られた文字列は HTMLエスケープ(HTMLにおいて特別の意味を持つ文字を置き換える) してから表示する」ことである。
CSRF (Cross-Site Request Forgeries) とは、「対象となるサービスに強制的に リクエストを送信させる攻撃手法」である。 悪意のあるページになんらかの方法でアクセスさせることで、攻撃を開始する。
ユーザ U のブラウザが、正常なWWWサーバ A と通信し、セッションを 張っていたり、クッキーを取得していたりして、認証を受けた状態であるとする。 その後、ユーザ U が同じブラウザで悪意のあるWWWサーバ B にアクセスして Bの罠ページのscriptが動作すると、ユーザUのブラウザからWWWサーバAへ なんらかのリクエストを送ることがありえる。 ユーザ U の意志には反しているのだが、認証の済んだ正しいブラウザから アクセスされているのでWWWサーバ A はユーザUの要求だと思ってリクエストを 実行してしまう。パスワード変更、送金、など大変なことが起こり得る。
対策は、第三者が知ることができない情報をパラメータで送っておき、それを サーバ側で確認すること。たとえばhidden属性で情報を送っておいて、 正常なリクエストではその情報が含まれるようにすると、 CSRFで生成されたリクエストではその情報がないことで区別ができる。
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データを読み出されて攻撃者に送られてしまう。
js_sample01.html |
|
js_sample02.html |
|
js_sample03.html |
|