Geckofxでのプログラム作成メモ その1

# はじめに

C#言語(.NET 4.7.2)とWindowsFormに、Geckofxを埋め込んで使うためのメモ。
NuGetでGeckofxをインストールすればFirefoxが扱えるようになり、いろんなことができるようになります。ただ、情報が少ないので作成している間に課題となってるところなどメモしておこうと思います。

## 用意する物

* Visual Studio 2017 Community または 開発用
* .NET 4以上のSDK
* NuGet（Geckofx45 64bitでも32bitでもどちらでも）

## ターゲット環境はご自由に...

WPFでも作れます。UWPはわかりません。簡単な仕組みだけにするならWindowsFormでも大丈夫。

## 継承クラスの作成

そのまま、GeckoWebBrowser のインスタンスを作ってあれこれするだけなのですが、やることが決まっているならば継承クラスとして作り、拡張する方が便利かもしれません。（猫はそうしてます）

```C#
public class WebBrowserExtension : GeckoWebBrowser
{
    public WebBrowserExtension()
    {
         // firefox のランタイムの場所
         var app = GetGeckoDllPath();
         // プロファイルを作る場所
         // 猫は、作ったプログラムをc:/program files等では動かさないので、その配下に作るようにしてる
         var prf = System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Profile", AppDomain.CurrentDomain.SetupInformation.ApplicationName);

         // プロフィールの場所を指定する
         Gecko.Xpcom.ProfileDirectory = prf;
         Gecko.Xpcom.Initialize(app);

         // プロンプト制御のため（後述）
         Gecko.PromptFactory.PromptServiceCreator = () => new WebBrowserExtensionAlertFilter();

         // デフォルトの設定を反映
         InitDefaultPreferences();
    }

    /// <summary>
    /// Firefoxライブラリのランタイム場所を返すメソッド
    /// 場所はお任せ
    /// x86 ... Firefox
    /// x64 ... Firefox64
    /// </summary>
    /// <returns></returns>
    private string GetGeckoDllPath() =>
        IntPtr.Size == 4 ?
            System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Firefox") :
            System.IO.Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, "Firefox64");

}
```

コンストラクタ内で、Xpcomを初期化させています。プロファイルの位置も、初期化時に決定していないとだめなので、ここはどのように実装するかは考えものです。
なお、プロセス終了するまでメモリーは食い続け、解放する手段もないですので注意が必要です。あと、情報の大半は共有されているので、プロセス内で複数のブラウザに分けたとしても、設定情報は共有されてしまいますので、もし分けたい場合は子プロセスとして動作させる必要がありそうです。

```C#
        private void InitDefaultPreferences()
        {
           // タッチイベントの有効化
            GeckoPreferences.Default["dom.w3c_touch_events.enabled"] = 1;

            // スクリプトの実行時間
            GeckoPreferences.Default["dom.max_script_run_time"] = 3;

            // プラグイン無効化
            GeckoPreferences.Default["plugins.click_to_play"] = true;
            GeckoPreferences.Default["plugin.scan.plid.all"] = false;
            GeckoPreferences.Default["plugin.default_plugin_disabled"] = false;

            // 言語情報
            GeckoPreferences.Default["intl.accept_languages"] = "ja-JP";

            // ディスクキャッシュの有無
            GeckoPreferences.Default["browser.cache.disk.enable"] = false;
            GeckoPreferences.Default["browser.cache.disk.capacity"] = 0;

            // メモリーキャッシュの有無
            GeckoPreferences.Default["browser.cache.memory.enable"] = false;
            GeckoPreferences.Default["browser.cache.memory.capacity"] = -1;

            // Javascript動作
            GeckoPreferences.Default["javascript.options.mem.max"] = 256;
            GeckoPreferences.Default["javascript.options.mem.mem.gc_frequency"] = 3;
            GeckoPreferences.Default["browser.sessionhistory.max_total_viewers"] = -1;
        }
```

何もせずに生成させると、素の情報ができてしまいます。ある程度、環境設定をこちらで作っておくといいと思います。なお、UserAgentも正規の情報は入っていないので、そのままYahoo!を表示させると期限切れ情報が表示されます。

```C#
GeckoPreferences.Default["general.useragent.override"]  = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0";
```
で偽装しておく必要があります。
なお、GeckoPreferencesには、Default プロパティの他に User もあります。Userは、以下のメソッドでプロファイルに保存できるのでしょう（未検証）。

```C#
GeckoPreferences.Save();
```

## プロンプトを防ぐ

```c#
new WebBrowserExtensionAlertFilter();
```

クラスを作ってプロンプト（ダイアログボックス）を制御できます。これらは、インターフェイスを継承してそれぞれを上書きすることになります。

```C#
public class WebBrowserExtensionAlertFilter : nsIPromptService2, nsIPrompt
{
    ... 以下略
}
```

インターフェイスの宣言を実装してあげてください。
数が多いので、ちょっと紹介以下略...です。
ブレイクポイント当てると、大概は引っかかるので、なんとか処理は出来そうです。

## ブラウジングする

```C#
Navigate("https://qiita.com");
```
のように使い、URLを指定します。
表示されたサイトのHTML等を操作するには、OnDocumentCompleted をオーバーライドします。(またはDocumentCompletedイベントを使います）

```c#
protected override async void OnDocumentCompleted(GeckoDocumentCompletedEventArgs e)
{
     base.OnDocumentCompleted(e);

     var dom = this.Document;

     ...
}
```
このメソッドで、出力されたDOMを Documentオブジェクトを経由して操作することが可能です。つまり、読み取って解析が可能ということです。
this.Documentにしているのは、eにもあるのですが、最近流行のJavascriptでの動的動作によって、e 側から返るオブジェクトが一致しない現象があったから。もしかすると、気のせいかもしれませんけどね。