Buzzurlの中の人日記

Mash up Caravanで発表した資料です。 JavaScriptでHTMLを生成するやり方に関する基本的な内容ですが、意外とこの辺がまとまったドキュメントってWeb上にないのではないかなと思います。

↓の Prev Next でページ送りします

LingrTickrを使ってバックチャンネルのチャットを表示するように仕込んで行ったんですが、会場が地下だったためネットつながらなくてぽしゃったのが心残りでした。

Previous | Next

Mash up Caravan 技術セッション

JavaScriptによるHTML生成
実践ノウハウ

  • 株式会社ECナビ 須藤洋一
ブログ(*「ふっかつのじゅもんがちがいます」)
http://blog.ajiyoshi.org
ソーシャルブックマーク
http://buzzurl.jp/user/y-sudo

自己紹介

  • ソーシャルブックマークBuzzurl(http://buzzurl.jp)のプロダクトマネージャ
  • 企画設計から開発テスト運用までなんでもやってます

バックチャンネル

http://www.lingr.com/room/buzzurl

  • ここで発言すると表示されるかもしれません

前提知識

  • JavaScriptを読み書きできること
  • prototype.jsを多少触ったことがあること
  • HTML/HTTPに関する基礎知識

取り扱わない話題

  • JavaScriptの書き方
  • 個別のライブラリの詳細な使い方

基本のおさらい

JavaScriptでHTMLを生成する(1)

  • Ajax/JSONPで何かするときはほぼ必須
  • Web上であまり情報が見つからない
  • どんな場合でも使える方法はおそらくない
    • LivedoorReaderのようなフルAjaxアプリケーション
    • フォームや読み込みを一部Ajax化
    • クライアントサイドマッシュアップ
    • Greasemonkey

JavaScriptでHTMLを生成する(2)

大きく2つの方法

  1. DOM操作APIを使う
  2. innerHTMLを使う

(1) DOM操作APIを使う

例:Buzzurlへのリンクを追加する

var url = "http://buzzurl.jp";
var a = document.createElement("a");
a.href = url;
a.appendChild(document.createTextNode("Buzzurl(バザール)"));
document.getElementById("target_1").appendChild(a); 

ここに追加されます

Issue:

DOM APIはかったるい

  • どんなHTMLになるか分かりにくい
  • 複雑なHTMLを作ろうと思うと辛い
  • 見栄えとロジックを分離しづらい

Solution:

  • 既存のライブラリを使う
    • jQuery
    • Mochikit.DOM
    • XMLBuilder
  • 自作のライブラリを作る
  • innerHTMLを使う

(2) innerHTMLを使う

サンプル

var html = "現在時刻:" + (new Date()).toString();
document.getElementById("target_2").innerHTML = html; 

ここに表示されます

Issue:

innerHTMLもかったるい

  • 文字列連結でHTMLを作るのは面倒
  • 変数を適切にエスケープしないとXSSの原因になりやすい(後述)

Solution:

テンプレートエンジンを使う

  • 既存のライブラリ
    • prototype.js
    • ajax pages
    • TrimPath
    • ...etc. etc.
  • 自前で作る

テンプレートエンジンを使う

例:prototype.jsのTemplate

<textarea id="templ_1">
	<a href="#{home}"><img src="#{photo}"></a>
</textarea>
var t = new Template($F("templ_1"));
var data = {
	home:"http://buzzurl.jp/user/y-sudo",
	photo:"http://file.blog.ajiyoshi.org/photo.jpg"
};
$("target_3").innerHTML = t.evaluate(data);

実践セキュリティ

実践セキュリティ

安全でないデータ

<textarea id="templ_1">
	<a href="#{home}"><img src="#{photo}"></a>
</textarea>
var t = new Template($F("templ_1"));
var unsafe = {
	home:"http://buzzurl.jp/user/y-sudo",
	photo:'http://file.blog.ajiyoshi.org/photo.jpg" onload="alert(\'xss\')'
};
$("target_4").innerHTML = t.evaluate(unsafe); 

実践セキュリティ

表示する直前にHTMLエスケープ

<textarea id="templ_0">
	<img src="http://file.blog.ajiyoshi.org/photo.jpg" onload="alert('XSS')">
</textarea>
function escape_html(str){
	str = str.replace(/&/g, "&amp;");
	str = str.replace(/"/g, "&quot;");
	str = str.replace(/'/g, "&#x27;");
	str = str.replace(/</g, "&lt;");
	str = str.replace(/>/g, "&gt;");
	return str;
}

var unsafe = $F("templ_0");
$("target_esc").innerHTML = escape_html(unsafe);

実践セキュリティ

HTMLエスケープ

<textarea id="templ_1">
	<a href="#{home}"><img src="#{photo}"></a>
</textarea>
var t = new Template($F("templ_1"));
var unsafe = {
	home:"http://buzzurl.jp/user/y-sudo",
	photo:'http://cdn.buzzurl.jp/user/y-sudo/photo" onload="alert(\'xss\')'
};
var escaped = {
	home:escape_html(unsafe.home),
	photo:escape_html(unsafe.photo)
};
$("target_5").innerHTML = t.evaluate(escaped);

実践セキュリティ

注意:prototype.jsは " や ' をエスケープしない

<textarea id="templ_1">
	<a href="#{home}"><img src="#{photo}"></a>
</textarea>
var t = new Template($F("templ_1"));
var unsafe = {
	home:"http://buzzurl.jp/user/y-sudo",
	photo:'http://file.blog.ajiyoshi.org/photo.jpg" onload="alert(\'xss\')'
};
var escaped = {
	home:unsafe.home.escapeHTML(), photo:unsafe.photo.escapeHTML()
};
$("target_6").innerHTML = t.evaluate(escaped);

実践セキュリティ

まとめ

  • 表示の直前でエスケープするのが鉄則
  • HTMLエスケープされた状態で受け取ることもありうる
  • アプリケーション側はエスケープ済みと期待すべきではない
  • 混在環境ではアンエスケープしてからエスケープする

実践パフォーマンス

実践パフォーマンス

次のことをすると とても 重い

  1. 巨大なDOMツリーを作成
  2. onclick等のイベントハンドラを含むとさらに重い(特にIE)
  3. imgはさらにさらに重い

実践パフォーマンス

単純な<a>タグ

function now(){ return (new Date()).getTime() }
var num = Math.floor($F('num_7'));
var html = (new Array(num)).map( function(){ return $F('templ_2') } ).join("");
$("target_7").innerHTML = "";
setTimeout(function(){
	var start = now();
	var p = document.createElement("p");
	p.innerHTML = html;
	$("target_7").appendChild(p);
	var end = now();
	var li = document.createElement("li");
	li.appendChild(document.createTextNode((end-start)+"msec to "+(num*100)+" elements"));
	$("score_7").insertBefore(li, $("score_7").firstChild);
}, 100);

実践パフォーマンス

イベントハンドラ付きの<a>

function now(){ return (new Date()).getTime() }
var num = Math.floor($F('num_8'));
var html = (new Array(num)).map( function(){ return $F('templ_3') } ).join("");
$("target_8").innerHTML = "";
setTimeout(function(){
	var start = now();
	var p = document.createElement("p");
	p.innerHTML = html;
	$("target_8").appendChild(p);
	var end = now();
	var li = document.createElement("li");
	li.appendChild(document.createTextNode((end-start)+"msec to "+(num*100)+" elements"));
	$("score_8").insertBefore(li, $("score_8").firstChild);
}, 100);

実践パフォーマンス

イベントハンドラ付きの<img>

function now(){ return (new Date()).getTime() }
var num = Math.floor($F('num_9'));
var html = (new Array(num)).map( function(){ return $F('templ_4') } ).join("");
$("target_9").innerHTML = "";
setTimeout(function(){
	var start = now();
	var p = document.createElement("p");
	p.innerHTML = html;
	$("target_9").appendChild(p);
	var end = now();
	var li = document.createElement("li");
	li.appendChild(document.createTextNode((end-start)+"msec to "+(num*100)+" elements"));
	$("score_9").insertBefore(li, $("score_9").firstChild);
}, 100);

DOM操作は重い

まとめ

  • なるべく巨大なDOMツリーの作成/更新をしない
  • 必要最小限の更新にとどめる
  • 文字列の構築と描画の間に制御を返す

おしまい

  • どうみても終わりです。
  • 本当にご清聴ありがとうございました。
この記事にコメントする
お名前
タイトル
文字色
URL
コメント
パスワード Vodafone絵文字 i-mode絵文字 Ezweb絵文字
この記事へのトラックバック
この記事にトラックバックする:
プロフィール
HN:
ajiyoshi
性別:
男性
自己紹介:
プログラマです。
ソーシャルブックマークサービス「Buzzurl」の開発者です。

はてなブックマークカウンタ


旧*「ふっかつのじゅもんがちがいます」カウンタ
Buzzurl

powered by Buzzurl

Twitter

カレンダー
08 2010/09 10
S M T W T F S
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
最新コメント
[07/23 つらら]
[07/23 れいら]
[06/16 婚活]
[05/28 あっだ]
[05/28 もも]
最新トラックバック
バーコード
ブログ内検索
忍者ポイント
カウンター
アクセス解析
あわせて読みたい
あわせて読みたい
Powered by ニンジャブログ  Designed by ゆきぱんだ
Copyright c *「ふっかつのじゅもんがちがいます。」 All Rights Reserved
忍者ブログ / [PR]レシピ 医師求人