Hatena::ブログ(Diary)

IT戦記 このページをアンテナに追加 RSSフィード

2008-10-19

サーバー起動用に JavaScript が 1 秒以上実行されなくて alert しないブラウザを作る

f:id:amachang:20081019211736p:image

はじめに

サムネイルサーバーのようなサービスを作るときには、ウェブサーバー上にブラウザを乗せる必要があります。

問題

ただ、そういった用途でブラウザを使う場合。

JavaScript無限ループに落ち入らないように配慮する必要があります。

ほとんどの場合は、 JavaScript 自体をオフにすることが多いのですが JavaScript を実行したいような場合もあるでしょう。

解決方法

今回は、 WebKit でその解決方法を紹介します。

1. まず WebKitソースコードを取得する
svn co http://svn.webkit.org/repository/webkit/trunk WebKit
2. 次に、タイマーの時間を短くする

WebCore/bindings/js/JSDOMWindowBase.cpp を編集

JSDOMWindowBase::JSDOMWindowBase(PassRefPtr<StructureID> structure, PassRefPtr<DOMWindow> window, JSDOMWindowShell* shell)
    : JSGlobalObject(structure, new JSDOMWindowBaseData(window, shell), shell)
{
    // Time in milliseconds before the script timeout handler kicks in.
    // ■ ここを 10000 から 1000 にする
    setTimeoutTime(1000);

    GlobalPropertyInfo staticGlobals[] = {
        GlobalPropertyInfo(Identifier(globalExec(), "document"), jsNull(), DontDelete | ReadOnly),
        GlobalPropertyInfo(Identifier(globalExec(), "window"), d()->shell, DontDelete | ReadOnly)
    };   
    
    addStaticGlobals(staticGlobals, sizeof(staticGlobals) / sizeof(GlobalPropertyInfo));
}
3. 次に、 JavaScript が 1 秒以上実行されると、無条件(確認なし)で終了

WebCore/page/Chrome.cpp を編集

bool Chrome::shouldInterruptJavaScript()
{
    // ■ この行を追加
    return true;

    // Defer loads in case the client method runs a new event loop that would 
    // otherwise cause the load to continue while we're in the middle of executing JavaScript.
    PageGroupLoadDeferrer deferrer(m_page, true);

    return m_client->shouldInterruptJavaScript();
}
4. alert, confirm, prompt を切る

WebCore/page/DOMWindow.cpp を編集

void DOMWindow::alert(const String& message)
{
    // ■ この行を追加   
    return;

    if (!m_frame)
        return;
    
    Document* doc = m_frame->document();
    ASSERT(doc);
    if (doc)
        doc->updateRendering();
    
    Page* page = m_frame->page();
    if (!page)
        return;

    page->chrome()->runJavaScriptAlert(m_frame, message);
}   

bool DOMWindow::confirm(const String& message)
{
    // ■ この行を追加   
    return false;

    if (!m_frame)
        return false;

    Document* doc = m_frame->document();
    ASSERT(doc);
    if (doc)
        doc->updateRendering();

    Page* page = m_frame->page();
    if (!page)
        return false;

    return page->chrome()->runJavaScriptConfirm(m_frame, message);
}

String DOMWindow::prompt(const String& message, const String& defaultValue)
{
    // ■ この行を追加   
    return String();

    if (!m_frame)
        return String();

    Document* doc = m_frame->document();
    ASSERT(doc);
    if (doc)
        doc->updateRendering();

    Page* page = m_frame->page();
    if (!page)
        return String();

    String returnValue;
    if (page->chrome()->runJavaScriptPrompt(m_frame, message, defaultValue, returnValue))
        return returnValue;

    return String();
}
5. window.open, window.showModalDialog によるポップアップを切る

WebCore/bindings/js/JSDOMWindowBase.cpp を編集

static bool allowPopUp(ExecState* exec)
{
    // ■ この行を追加
    return false;

    Frame* frame = asJSDOMWindow(exec->dynamicGlobalObject())->impl()->frame();

    ASSERT(frame);
    if (frame->script()->processingUserGesture())
        return true;
    Settings* settings = frame->settings();
    return settings && settings->JavaScriptCanOpenWindowsAutomatically();
}
補足:ここまでの diff です
Index: WebCore/bindings/js/JSDOMWindowBase.cpp
===================================================================
--- WebCore/bindings/js/JSDOMWindowBase.cpp	(revision 37703)
+++ WebCore/bindings/js/JSDOMWindowBase.cpp	(working copy)
@@ -175,7 +175,7 @@
     : JSGlobalObject(structure, new JSDOMWindowBaseData(window, shell), shell)
 {
     // Time in milliseconds before the script timeout handler kicks in.
-    setTimeoutTime(10000);
+    setTimeoutTime(1000);
 
     GlobalPropertyInfo staticGlobals[] = {
         GlobalPropertyInfo(Identifier(globalExec(), "document"), jsNull(), DontDelete | ReadOnly),
@@ -223,6 +223,8 @@
 
 static bool allowPopUp(ExecState* exec)
 {
+    return false;
+
     Frame* frame = asJSDOMWindow(exec->dynamicGlobalObject())->impl()->frame();
 
     ASSERT(frame);
Index: WebCore/page/DOMWindow.cpp
===================================================================
--- WebCore/page/DOMWindow.cpp	(revision 37703)
+++ WebCore/page/DOMWindow.cpp	(working copy)
@@ -469,6 +469,8 @@
 
 void DOMWindow::alert(const String& message)
 {
+    return;
+
     if (!m_frame)
         return;
 
@@ -486,6 +488,8 @@
 
 bool DOMWindow::confirm(const String& message)
 {
+    return false;
+
     if (!m_frame)
         return false;
 
@@ -503,6 +507,8 @@
 
 String DOMWindow::prompt(const String& message, const String& defaultValue)
 {
+    return String();
+
     if (!m_frame)
         return String();
 
Index: WebCore/page/Chrome.cpp
===================================================================
--- WebCore/page/Chrome.cpp	(revision 37703)
+++ WebCore/page/Chrome.cpp	(working copy)
@@ -302,6 +302,8 @@
 
 bool Chrome::shouldInterruptJavaScript()
 {
+    return true;
+
     // Defer loads in case the client method runs a new event loop that would 
     // otherwise cause the load to continue while we're in the middle of executing JavaScript.
     PageGroupLoadDeferrer deferrer(m_page, true);
6. ビルドします

今回は Gtk を使う例です。

$ cd WebKit
$ WebKitTools/Scripts/update-webkit
$ WebKitTools/Scripts/set-webkit-configuration --release
$ WebKitTools/Scripts/build-webkit --gtk

Configure の途中で「bison がないよ><」とか「flex がないよ><」とかいろいろ言われると思いますので、その都度インストールしてくださいね。

7. 起動

さあ、完成です。簡単ですね!

さっそく起動しましょう。

$ WebKitBuild/Release/Programs/GtkLaucher

起動しました!

このブラウザで様々なページを見てみてください!

どんなに重いページでも JavaScript が 1 秒以上連続で実行されませんね。

さらに様々なポップアップや alert 系のウィンドウも出ません。

ばっちりですね!

その他のこと

ただ、これだけで安心という訳ではありません。

いちおう Cookie や Storage 関係も切っておいたほうがいいでしょう。

Storage は build-webkit で --no-storage とか指定すればできます。

まとめ

今日は初めてブラウザのコードに手を入れた記念日。

2008-10-18

Xvfb と WebKit と Gtk と楽しい!

f:id:amachang:20081019041243g:image

おおおおお

めもめも

$ export DISPLAY="localhost:1.0"
$ Xvfb :1 -screen 0 1024x768x16 &
$ WebKitBuild/Debug/Programs/GtkLaunchaer
$ xwd > /tmp/hoge.wd && convert /tmp/hoge.wd hoge.gif

コンテンツ部分だけー

f:id:amachang:20081019042341g:image

おおおお

$ xwininfo -tree -root

xwininfo: Window id: 0x41 (the root window) (has no name)

  Root window id: 0x41 (the root window) (has no name)
  Parent window id: 0x0 (none)
     3 children:
     0x20001d "Google - WebKit Launcher": ("lt-GtkLauncher" "Lt-GtkLauncher")  800x600+0+0  +0+0
        8 children:
        0x200029 (has no name): ()  18x18+782+582  +782+582
        0x200028 (has no name): ()  800x541+0+38  +0+38
        0x200027 (has no name): ()  34x34+764+2  +764+2
        0x200023 (has no name): ()  694x25+70+6  +70+6
           1 child:
           0x200026 (has no name): ()  690x21+2+2  +72+8
        0x200022 (has no name): ()  34x34+36+2  +36+2
        0x200021 (has no name): ()  34x34+2+2  +2+2
        0x200020 (has no name): ()  800x38+0+0  +0+0
        0x20001e (has no name): ()  1x1+-1+-1  +-1+-1
     0x200003 "lt-GtkLauncher": ("lt-GtkLauncher" "Lt-GtkLauncher")  200x200+0+0  +0+0
        5 children:
        0x200056 (has no name): ()  1x1+-1+-1  +-1+-1
        0x200040 (has no name): ()  1x1+-1+-1  +-1+-1
        0x20001c (has no name): ()  1x1+-1+-1  +-1+-1
        0x20001b (has no name): ()  1x1+-1+-1  +-1+-1
        0x200004 (has no name): ()  1x1+-1+-1  +-1+-1
     0x200001 "lt-GtkLauncher": ("lt-GtkLauncher" "Lt-GtkLauncher")  10x10+10+10  +10+10
        1 child:
        0x200002 (has no name): ()  1x1+-1+-1  +9+9

$ xwd -id 0x200028 > /tmp/hoge.wd && convert /tmp/hoge.wd hoge.gif

おおおおおお

フォントがないよ!ふぉんと(ホント)だ!

f:id:amachang:20081019050432g:image

フォント入れた!

自分のブログを取ってみた

f:id:amachang:20081019050730g:image

2008-09-24

Safari4 と IE8 で実装された DOM Storage とは何か

はじめに

皆様 JavaScript のスピード競争が激化し、 ECMAScript 3.1 の仕様の策定が進むなど、激動の JavaScript 時代をいかがお過ごしでしょうか。

さて今日は、今、ちまたで大ブレイクの兆しを見せている DOM Storage という仕様を紹介したいと思います。

DOM Storage とは何か

まず、 DOM Storage とはどんなものなのでしょうか。

とても簡単に言ってしまえば、とてもたくさんのデータが保存できてサーバーに自動で送られない Cookie みたいなものです。

さらに、 Cookie とは違って JavaScript からとても扱い易く作られています。

では、この DOMStorage の具体的なソースコードを見てみましょう。

<!DOCTYPE html>
<html>
<head><title>DOMStorage の使い方</title></head>
<body><script>

// DOM Storage は localStorage というオブジェクトを経由してアクセスされます。
if (!localStorage.name) {

    // 使い方はただ代入するだけ
    localStorage.name = prompt('初めまして>< 名前を教えてください')
}

// 簡単にデータを取り出せます
document.write(localStorage.name + 'さんこんにちは!');
</script></body>
</html>

たった 4 行です。

この HTML を DOM Storage に対応しているブラウザで見ると、最初の一回だけ名前を聞かれて以降は聞かれないという挙動をします。

サンプルはこちら

簡単ですね!

よく分からない><

もう少し説明します。

当たり前のことですが、普通の JavaScript のオブジェクトってページ遷移すると値を失いますよね。

object.count = object.count || 0;
object.count++;
alert(object.count);

でも、 localStorage というオブジェクトのプロパティに値を代入しておくと、ページ遷移後でも値が保存されたままになるのです。

localStorage.count = localStorage.count || 0;
localStorage.count = parseInt(localStorage) + 1; // localStorage には文字列として保存されるので、 parseInt が必要
alert(localStorage.count);

それが DOM Storage です。

オブジェクトとかもぶち込めるの?

ぶち込めることはぶち込めるのですが、データはすべて文字列化されて保存されます。

つまり、"[object Object]" というような文字列が保存されたりします。

具体的にはどう嬉しいの?

今までの Web アプリケーションでは、クライアントデータの保存はほとんどサーバサイドで行われていました。

DOM Storage を使うと、ローカルのデータはローカルに保存するという当たり前のことが出来るようになります。

使われ方としては、

  • 設定や状態の保存
  • 大きなデータの一時的な保存
  • HTML や JSON などのキャッシュ

メリットとしては

  • サーバ資源の節約
  • パフォーマンスの改善

などがあげられます。

どのブラウザで使えるの?

DOM Storage は以下のブラウザで使うことができます。

  • IE8+
  • Safari4+
  • Firefox2+

新しいブラウザでは、軒並み使えるようになる予定ですので、当然 Opera でも対応してくることになると思います。

新しい仕様と古い仕様

ただ一つ、注意する点は DOM Storage には古い仕様と新しい仕様があって、

  • 古い仕様を実装しているブラウザ
    • Firefox2
    • Firefox3
  • 新しい仕様を実装しているブラウザ
    • Safari4
    • IE8

となっています。

一番最初に書いた例は、新しい仕様での書き方なので、実際には古い仕様でも動くように改造する必要があります。

改造と言っても単純で、

<!DOCTYPE html>
<html>
<head><title>DOM Storage の使い方</title></head>
<body><script>
// *** この 1 行を追加するだけ
window.localStorage = window.localStorage || window.globalStorage[location.hostname];
// *** この 1 行を追加するだけ

if (!localStorage.name) {
    localStorage.name = prompt('初めまして>< 名前を教えてください')
}
document.write(localStorage.name + 'さんこんにちは!');
</script></body>
</html>

上の例のように 1 行を追加するだけです。

具体的に保存できる容量は?

容量は仕様には定義されていませんが、例えば IE8 では 10 MB の大容量を扱うことができます。

Safari4 や Firefox の容量を知っている人がいれば教えてください><

仕様の全貌

この DOM Storage というものの仕様は HTML5 という仕様の一部で、Structured client-side storageという章で定義されています。

localStorage と sessionStorage

ここまで、 localStorage しか紹介してきませんでしたが、 DOM Storage には以下の二つのオブジェクトがあります。

  • localStorage
  • sessionStorage

localStorage はブラウザを落としても、データが残るオブジェクトで、 sessionStorage はそのタブ(ウィンドウ)が起動している間だけデータを保持します。

データの追加、書き換え

ここまでの例では、代入によるデータの追加方法だけを紹介していましたが、 setItem 関数によるデータの追加方法もあります。

localStorage.setItem('hoge', 'fuga'); // localStorage.hoge = 'fuga'; と同じ意味
sessionStorage.setItem('hoge', 'fuga'); // sessionStorage.hoge = 'fuga'; と同じ意味
データの参照

データの参照方法はプロパティとしての参照方法以外に、 getItem 関数による参照方法があります。

alert(localStorage.getItem('hoge')); // alert(localStorage.hoge); と同じ意味
alert(sessionStorage.getItem('hoge')); // alert(sessionStorage.hoge); と同じ意味
データの削除

データの削除方法は delete 演算子、または、 removeItem を使います。

delete localStorage.hoge;
localStorage.removeItem('hoge');
データの列挙

保存されたデータの列挙方法は、 length プロパティと key 関数を使います。

for (var i = 0; i < localStorage.length; i ++) {
  var key = localStorage.key(i);
  var data = localStorage.getItem(key);
}

length プロパティは保存されたデータの個数を表し、 key 関数は数値を受け取りデータのプロパティ名(キー)を返します。

データが共有される範囲

データは同じ origin (プロトコル + ドメイン + ポート)で共有されます。

例えば、

storage イベント

DOM Storage は同じドメインなら複数のドキュメント間で共有できるのが便利ですが、別のページから突如書き換えられてしまう可能性があります。

そのような場合を細かく制御するために、 storage イベントというものが存在します。

使い方は、以下のようになります。

document.onstorage = function(e) {
  e = e || envet; // for IE

  if (e.window != window) { 
    alert(
      '別のドキュメントによって' +
      e.key + 'が' +
      e.oldValue + 'から' +
      e.newValue + 'に書き換えられました!');
  }
};

上の例のようにイベントオブジェクト e の

  • e.window には、書き換えたドキュメントの window オブジェクトが、
  • e.key には、追加したデータのプロパティ名(キー)が、
  • e.oldValue には、書き換えられる前のデータが、
  • e.newValue には、書き換えられた後のデータが、

入っています。

でも IE6, IE7 ユーザーが・・・そんなあなたに

「一番ユーザが多いブラウザ(IE6 と IE7)で使えないんじゃ、いくら便利なものとは言え使えないよ><」というのはもっともだと思います。

でも、そこで諦める必要はありません。 IE 独自の機能を使って、IE6, IE7 でも DOM Storage が使えるようになってしまう夢のようなライブラリ「Ex DOM Storage」が今日リリースされました。

これで、思う存分 DOM Storage を使い倒すことができます。素晴らしい!

簡単な使い方

以下のように、 script タグを読み込んでやるだけです!簡単!

<html><head>
  <title>hoge</title>
  <script src="http://svn.coderepos.org/share/lang/javascript/exdomstorage/trunk/src/exdomstorage.js"></script>
</head>
<body>...</body>
</html>

ただし、自分のサーバーに置くときは、この js ファイル以外にも以下のリンク先にあるファイルをすべて置いて、適切な mime-type を設定しなければなりません。

no title

さらに詳細な情報は

作者様のページをどうぞ

まとめ

  • IE8 や Safari4 からは、 DOM Storage が使えるようになる。
  • Firefox では古い仕様だが、簡単に新しい仕様と同じように使うことができる。
  • IE6 や IE7 で DOM Storage を使えるようにするためのライブラリがリリースされた。

という訳で、 DOM Storage は未来の技術から、現在使える技術へと姿を変えました。皆様も DOM Storage を使って、クライアントサイドのパワーをフルにいかした Web アプリケーションを作ってみてはいかがでしょうか。

2008-09-17

Mac や Linux で Google Chrome を動かそう!「CrossOver Chromium」を試してみた。

CrossOver Chromium とは

簡単に言えば Windows 用の Google Chrome (正確には Chromium)を wine というソフトウェアを使って Mac や Linux 上で動かすものです。

f:id:amachang:20080917141830p:image

インストール手順

使うための手順を書いておきます。(Mac の例です。)

まず、ダウンロード

以下のサイトの右側からダウンロードします。

screenshot

404 Not Found | CodeWeavers

dmg をマウントして Application ディレクトリにドラッグ&ドロップ

アイコンをドラッグします

f:id:amachang:20080917141822p:image

で、起動

初回起動時は時間がかかります。。。

f:id:amachang:20080917141823p:image

待つと、起動します。

f:id:amachang:20080917141824p:image

おおおおー。動いてるー。簡単だー

色々試してみよう

起動したので、ちゃんと動くか試してみました。

ブラウザとして

f:id:amachang:20080917143842p:image

多少不安定ですが、フォント以外は全然問題なくレンダリングされています。(フォントはしょうがないっすね)

タスクマネージャは

f:id:amachang:20080917141825p:image

全然機能してない見たいです。。

Web Inspector (JavaScript コンソール)は

ちゃんと動いてます。

f:id:amachang:20080917141826p:image

おおお。やるー

Gears は

f:id:amachang:20080917141828p:image

動いてないですねー><

全体的な印象

wine 経由でもかなり軽いです。(IEs for Mac とかと比べると幸せになれます。)

ただ、まだまだ不安定でたまに落ちます。

でも、 Google Chrome のレンダリングを軽く確認したいときには使えそうな感じですね。手元に Windows が無いときも多いので、僕はもうちょっと使ってみたいと思います。

というわけで

最後は CrossOver Chromium さんのおちゃめな NG シーンでお別れしましょう。さよーならー

f:id:amachang:20080917141829p:image

f:id:amachang:20080917141827p:image

2008-05-06

bison の error トークンで行き詰まる

このゴールデンウィークに WebKit の CSSGrammar.y を写経していて bison では error トークンという特別なトークンが扱えることを知った。

kmyacc には、ない。

行き詰まった><

  • yacc の仕組みをちゃんとを勉強する
  • error トークンと同等のことを行う方法を考える