酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

WebBrowserコントロールでScriptタグがないHTMLを表示時InvokeScriptメソッドに失敗する場合の対策

@Jspinylobsterさんが、ScriptタグのないHTMLページでは、WebBrowserコントロールのInvokeScriptメソッドが実行出来ないのではないか?と質問されていました。

結論から入るとHTMLファイルにScriptタグがないとInvokeScriptメソッドで例外を吐きます。どういう場合に発生するのかと対策方法について検討してみました。

現象を再現させてみた

テスト用のHTMLファイルを用意しました。Scriptタグはありません。

<html>
  <head>
    <meta name="viewport" content="width=device-width"/>
    <meta name="viewport" content="user-scalable=no"/> 
  </head>
  <body>
    <form method="POST" action="dummy.cgi">
      UserID: <input type="text" id="username" />
      <br />
      Password: <input type="password" id="password" />
      <br />
      <input type="submit" value="login" />
    </form>
  </body>
</html>

WebBrowserコントロールを使って自動ログイン機能を実装するで書いた通り、ユーザー欄に"hogehoge"を入力するInvokeScriptメソッドを実行してみましょう。

private void button1_Click(object sender, RoutedEventArgs e) {
    webBrowser.InvokeScript("eval", "document.getElementById('username').value='hogehoge'");
}

実行すると「An unknown error has occurred. Error: 80020006.」というSystemExceptionが発生してしまい、HTMLファイルにScriptタグがないとInvokeScriptメソッドは実行できないことが分かりました。

次にScriptタグの存在しないHTMLで自動ログインを実行するためにはどうすれば良いのか見当してみましょう。

対策編:Scriptタグの存在しないHTMLで自動ログインを実行するためには?

今も流行っているのか分からないのですが、少し昔にURLの代わりにJavaScriptのプログラムコードをお気に入りに追加しておき、そのお気に入りを選択するだけで簡易なプログラムを実行する「ブックマークレット」と呼ばれる手法があります。

今回の問題は、WebBrowser.Navigateメソッドとブックマークレットの手法で解決することが出来ました。

private void button1_Click(object sender, RoutedEventArgs e) {
    sb.Append("javascript:function foo(){");
    sb.Append("document.getElementById('username').value='hogehoge';");
    sb.Append("};");
    sb.Append("foo();");

    webBrowser.Navigate(new Uri(sb.ToString()));
}

また、一度でもブックマークレットを使用しておけば、表示中のコンテンツ内ではInvokeScriptを使用することが可能です。

Navigatedイベント内で何もしないブックマークレットを実行(フラグを持たせて一度だけ実行される)した後であれば、InvokeScriptメソッドでJavaScriptコードを実行することが出来るようになっています。

using System;
using System.Text;
using System.Windows;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;

namespace WebBrowserNoScriptTag {
    public partial class MainPage : PhoneApplicationPage {
        public MainPage() {
            InitializeComponent();

            // Navigatedイベントハンドラを追加
            webBrowser.Navigated += webBrowser_Navigated;
        }

        protected override void OnNavigatedTo(NavigationEventArgs e) {
            // テスト用ページへ遷移する
            var url = "http://ch3cooh.jp/files/webbrowser-tagno.html";
            webBrowser.Navigate(new Uri(url));
        }

        bool isRunScript = false;
        void webBrowser_Navigated(object sender, NavigationEventArgs e) {
            // 一度だけ何もしないブックマークレットを実行
            if (!isRunScript) {
                webBrowser.Navigate(new Uri("javascript:;"));
                isRunScript = true;
            }
        }

        private void button1_Click(object sender, RoutedEventArgs e) {
            webBrowser.InvokeScript("eval", "document.getElementById('username').value='hogehoge'");
        }

        //private void button1_Click(object sender, RoutedEventArgs e) {
        //    var sb = new StringBuilder();
        //    sb.Append("javascript:function foo(){");
        //    sb.Append("document.getElementById('username').value='hogehoge';");
        //    sb.Append("};");
        //    sb.Append("foo();");
        //    webBrowser.Navigate(new Uri(sb.ToString()));
        //}
    }
}

コンテンツによってInvokeScriptメソッドが使用出来ないという方がいましたら、この方法をお試しください。