IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

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


はじめに

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

問題

ただ、そういった用途でブラウザを使う場合。
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 とか指定すればできます。

まとめ

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