酢ろぐ!

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

Windows PhoneでJavaScriptを使ってWebBrowserコントロールを制御する

WebBrowserコントロールを使って自動ログイン機能を実装する」では、JavaScriptを使ってYahoo!Japanへのログイン機能を実装しました。何故JavaScriptのコードが実行出来るのか説明不足でしたので、後出しになってしまいましたが改めて説明したいと思います。

WebBrowser.InvokeScriptメソッドについて

その前にWebBrowser.InvokeScriptメソッドの使い方について説明しておきます。WebBrowser.InvokeScriptメソッドは、現在表示されているページにて定義されているスクリプトの名前を指定して実行するメソッドです。

InvokeScript(String) スクリプトの名前を指定して実行する
InvokeScript(String, String[]) スクリプトの名前を指定して引数を与えて実行する

以下のようなHTMLファイルの場合は、「funcWithoutPram」の部分がスクリプトの名前になります。

HTML

実行するHTMLファイルを準備します。あらかじめJavaScriptで引数無しメソッド、1つだけ引数を受け取るメソッド、2つ引数を受け取るメソッドを用意しておきます。いずれもコールされると「None Data.」の部分を書き換えます。

<html>
    <head>
        <script type="text/javascript">
            function funcWithoutPram() {
              result.innerHTML = "called funcWithoutPram";
            }
            function funcWithPram1(parm1) {
              result.innerHTML = "called funcWithPram1(" + parm1 + ")";
            }
            function funcWithPram2(parm1,parm2) {
              result.innerHTML = "called funcWithPram2("
                + parm1 + "," + parm2 + ")";
            }
        </script>
    </head>
    <body>
    <div id="result">
        None Data.
    </div>
    </body>
</html>

このHTMLファイルを単純に表示しているだけの状態です。

None Data.が表示されています。下に並んでいるボタンを押すと指定したスクリプトを実行するようにします。

C#

コードです。OnNavigatedToメソッドで先ほどのHTMLページへ遷移させています。

WebBrowser.InvokeScriptメソッドを使う前には、XAMLかコード上かのいづれかで必ずIsScriptEnabledプロパティがTrueに設定していることを確認してください。

IsScriptEnabledプロパティが無効のままInvokeScriptメソッドを実行しようとすると、例外(System.SystemException)が発生してしまいます。「An unknown error has occurred. Error: 80020006.」としかメッセージが出ないので原因に気付きにくいので気を付けてください。

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

namespace WebBrowserScriptTest {
    public partial class MainPage : PhoneApplicationPage {
        // コンストラクター
        public MainPage() {
            InitializeComponent();
        }

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

        private void button1_Click(object sender, System.Windows.RoutedEventArgs e) {
            // 引数無しのJavaScriptを実行
            webBrowser.InvokeScript("funcWithoutPram");
        }

        private void button2_Click(object sender, System.Windows.RoutedEventArgs e) {
            // 引数が1つのJavaScriptを実行
            webBrowser.InvokeScript("funcWithPram1", "引数1");
        }

        private void button3_Click(object sender, System.Windows.RoutedEventArgs e) {
            // 引数が2つのJavaScriptを実行
            webBrowser.InvokeScript("funcWithPram2", "引数1", "引数2");
        }
    }
}

ボタンをタップすると定義されたJavaScriptのメソッドを実行します。

下図は、HTMLファイルに定義されたfuncWithPram1メソッドとfuncWithPram2メソッドに引数を渡して実行してみたスクリーンショットです。


何故JavaScriptのコードが実行出来るのか

WebBrowser.InvokeScriptメソッドは、既に定義済みのスクリプトを実行するメソッドであるということを理解して頂けたと思います。本題の何故JavaScriptのコードが実行出来るのかを説明したいと思います。

前回のログイン機能でのコードをひとつピックアップしてみました。

  webBrowser1.InvokeScript("eval", "document.forms['login_form'].submit();");

JavaScriptにはeval関数というものがあります。eval関数は引数として実行したいJavaScriptのコードを文字列で受けます。

上記のInvokeScriptメソッドは、「eval」というスクリプトに対して、「document.forms['login_form'].submit();」というJavaScriptコードを引数として渡して実行します。

このevalメソッドを使うことでどんなことをが出来るのか?ざっと書き出してみました。

WebBrowserコントロールで表示しているページのタイトルを取得する
// 表示しているタイトルを取得する
var title = webBrowser.InvokeScript("eval", "document.title");
WebBrowserコントロールで表示しているページのURLを取得する
// 表示しているURLを取得する
var strURL = webBrowser.InvokeScript("eval", "document.URL");
WebBrowserコントロールで表示しているテキストボックスの値を取得/設定する

以下のようなパスワードボックス(パスワード入力欄)を持つHTMLがあるとします。

<input id="password" type="password" />

このパスワード入力欄は「password」というIDを持っています。IDから要素を取得するには、JavaScriptのdocument.getElementByIdメソッドを使います。取得した要素のvalueプロパティを取得することで、現在何が入力されているのかを知ることが出来ます。

// テキストボックスの値を取得する
var value = webBrowser.InvokeScript("eval", "document.getElementById('password').value");

逆に設定したい場合もまずは対象となる要素を取得します。document.getElementByIdメソッドを使って、取得した要素のvalueプロパティに値を設定します。

    // テキストボックスに値を設定する(戻り値は設定した値)
    var result = webBrowser.InvokeScript("eval", "document.getElementById('password').value = 'hogehgoe'");

上記のコードを任意のタイミングで実行すると「hogehoge」がテキストボックスに反映されます。正しく実行されていれば、InvokeScriptメソッドの戻り値を受けた変数resultも「hogehoge」を含んでいると思います。

追記

今回の記事ではXAMLはたぶん要らないと思うけど、一応掲載しておきます。

<phone:PhoneApplicationPage 
    x:Class="WebBrowserScriptTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True">

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock x:Name="ApplicationTitle" Text="SOFTBUILD" 
                       Style="{StaticResource PhoneTextNormalStyle}"/>
            <TextBlock x:Name="PageTitle" Text="Script Test" Margin="9,-7,0,0" 
                       Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
        	<phone:WebBrowser Margin="0,0,0,100" Name="webBrowser" IsScriptEnabled="True" />
        	<Grid Height="100" VerticalAlignment="Bottom" >
        		<Grid.ColumnDefinitions>
        			<ColumnDefinition Width="0.33*"/>
        			<ColumnDefinition Width="0.33*"/>
        			<ColumnDefinition Width="0.34*"/>
        		</Grid.ColumnDefinitions>
                <Button x:Name="button1" Content="Parm無し" Grid.Column="0" 
                        d:LayoutOverrides="Width" FontSize="21.333" Click="button1_Click"/>
        		<Button x:Name="button2" Content="Parm有り1" Grid.Column="1" 
                        d:LayoutOverrides="Width" FontSize="21.333" Click="button2_Click"/>
        		<Button x:Name="button3" Content="Parm有り2" Grid.Column="2" 
                        d:LayoutOverrides="Width" FontSize="21.333" Click="button3_Click"/>
        	</Grid></Grid>
    </Grid>    
</phone:PhoneApplicationPage>