Edited at

WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる その4


はじめに

久しぶりにネタが出来たので書いてみました。


問題点

お仕事では入力項目に入ったバーコードリーダーの値をEnterキーイベント時に取得するようにしていました。「その3のキー押下の処理」を利用しています。

ところが50回に数回くらいサーバーに送られた値と違う(桁数が足りない)現象が見受けられました。

※RS-232Cの入力データをキー入力に変換するソフトを使用していたり、処理能力が低いPCなどで発生しているので一般的なPCでは発生することはないと思われます。


原因

CefSharpのWebコンポーネント側での入力途中のタイミングで、先にEnterキーイベントで入力値を取得してしまうことが原因と思われます。

IEのWebブラウザコンポーネントではpreviewkeydownイベントが専用にあったため問題にならなったのかな、やっていることは一緒なはずなんだが・・・


対応

サーバーに送られた値は正しいのでリクエストの値を取得できればいいと考えました。

調べるとCefSharpのWebコンポーネントには、RequestHandlerがあることが分かりました。

サンプルは、「WinFormsでChromiumブラウザコンポーネント(CefSharp)を使ってみる その2」で使用した「Keisan 生活や実務に役立つ計算サイト - 体脂肪率」で、サイト読み込み完了直後に体脂肪率なら身長と体重を自動入力し、リクエストされた身長と体重と体脂肪率の結果を表示します。


VB版

' リクエストデータ格納用

Private _postData As Hashtable = New Hashtable()

' リクエストイベント処理登録
_webBrowser.RequestHandler = New RequestHandler(Me)

Private Sub OnLoadingStateChanged(sender As Object, e As LoadingStateChangedEventArgs)
If Not e.IsLoading Then
Invoke(New MethodInvoker(
Sub()
'_webBrowser.Focus()
SetFocus(_webBrowser.Handle)
End Sub))

' 読込完了処理を記述
Dim jsScript As String
jsScript = String.Format("document.getElementById('var_身長').focus();")
_webBrowser.ExecuteScriptAsync(jsScript)

Dim anser As String = ""
If _currentAddress.IndexOf("keisan") >= 0 Then
jsScript = "document.getElementById('ans1').innerText;"
_webBrowser.EvaluateScriptAsync(jsScript).ContinueWith(
Sub(x)
Dim response = x.Result

If response.Success AndAlso response.Result IsNot Nothing Then
anser = response.Result.ToString()
If anser.Trim = "" Then
jsScript = String.Format("document.getElementById('var_身長').value = '{0}';", 180)
jsScript &= String.Format("document.getElementById('var_体重').value = '{0}';", 54)
jsScript &= "document.getElementById('executebtn').click();"
_webBrowser.ExecuteScriptAsync(jsScript)
Else
MessageBox.Show(String.Format("身長={0} 体重={1} BMI={2}", _postData("var_身長"), _postData("var_体重"), anser))
End If
End If
End Sub)
End If
End If
End Sub

' リクエストのデータセット処理
Public Function SetRequestData(url As String, requestBody As String) As Boolean
Dim aaa As String

If url.IndexOf("/exec/system/1161228728") >= 0 Then
If requestBody.IndexOf("=") <> -1 Then
Dim ary As String() = requestBody.Split("&")
For i As Integer = 0 To ary.Length - 1
Dim param As String() = ary(i).Split("=")
_postData.Add(Web.HttpUtility.UrlDecode(param(0)), Web.HttpUtility.UrlDecode(param(1)))
Next

Return True
End If
End If

Return False
End Function



VB版

Public Class RequestHandler

Implements IRequestHandler
Private _frm As frmMain

' コンストラクタ
Public Sub New(frm As frmMain)
_frm = frm
End Sub

' リクエストが投げられる前に呼ばれる処理
Private Function OnBeforeResourceLoad(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, callback As IRequestCallback) As CefReturnValue Implements IRequestHandler.OnBeforeResourceLoad
If request.Method = "POST" Then
Using postData = request.PostData
Dim elements = postData.Elements
Dim charSet = request.GetCharSet()

For Each element In elements
If element.Type = PostDataElementType.Bytes Then
If _frm.SetRequestData(browserControl.Address, element.GetBody(charSet)) Then
Exit For
End If
End If
Next
End Using
End If

Return CefReturnValue.[Continue]
End Function

Private Function OnOpenUrlFromTab(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, targetUrl As String, targetDisposition As WindowOpenDisposition, userGesture As Boolean) As Boolean Implements IRequestHandler.OnOpenUrlFromTab
Return False
End Function

Private Function OnCertificateError(browserControl As IWebBrowser, browser As IBrowser, errorCode As CefErrorCode, requestUrl As String, sslInfo As ISslInfo, callback As IRequestCallback) As Boolean Implements IRequestHandler.OnCertificateError
Return False
End Function

Private Sub OnPluginCrashed(browserControl As IWebBrowser, browser As IBrowser, pluginPath As String) Implements IRequestHandler.OnPluginCrashed
End Sub

Private Function GetAuthCredentials(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, isProxy As Boolean, host As String, port As Integer,
realm As String, scheme As String, callback As IAuthCallback) As Boolean Implements IRequestHandler.GetAuthCredentials
callback.Dispose()
Return False
End Function
Private Sub OnRenderProcessTerminated(browserControl As IWebBrowser, browser As IBrowser, status As CefTerminationStatus) Implements IRequestHandler.OnRenderProcessTerminated
End Sub
Private Sub OnRenderViewReady(browserControl As IWebBrowser, browser As IBrowser) Implements IRequestHandler.OnRenderViewReady
End Sub

Private Function OnQuotaRequest(browserControl As IWebBrowser, browser As IBrowser, originUrl As String, newSize As Long, callback As IRequestCallback) As Boolean Implements IRequestHandler.OnQuotaRequest
Return False
End Function

Private Function OnProtocolExecution(browserControl As IWebBrowser, browser As IBrowser, url As String) As Boolean Implements IRequestHandler.OnProtocolExecution
Return False
End Function

Private Function OnResourceResponse(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse) As Boolean Implements IRequestHandler.OnResourceResponse
Return False
End Function

Private Sub OnResourceLoadComplete(browserControl As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse, status As UrlRequestStatus,
receivedContentLength As Long) Implements IRequestHandler.OnResourceLoadComplete
End Sub

Private Function GetResourceResponseFilter(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse) As IResponseFilter Implements IRequestHandler.GetResourceResponseFilter
Return Nothing
End Function

Private Sub OnResourceRedirect(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, response As IResponse, ByRef newUrl As String) Implements IRequestHandler.OnResourceRedirect
End Sub

Private Function OnBeforeBrowse(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, userGesture As Boolean, isRedirect As Boolean) As Boolean Implements IRequestHandler.OnBeforeBrowse
Return False
End Function

Private Function OnSelectClientCertificate(chromiumWebBrowser As IWebBrowser, browser As IBrowser, isProxy As Boolean, host As String, port As Integer, certificates As X509Certificate2Collection, callback As ISelectClientCertificateCallback) As Boolean Implements IRequestHandler.OnSelectClientCertificate
Return True
End Function

Private Function CanGetCookies(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest) As Boolean Implements IRequestHandler.CanGetCookies
Return True
End Function

Private Function CanSetCookie(chromiumWebBrowser As IWebBrowser, browser As IBrowser, frame As IFrame, request As IRequest, cookie As Cookie) As Boolean Implements IRequestHandler.CanSetCookie
Return True
End Function

End Class


C#版はSharpDevelopでVBから変換したものです。


C#版

// リクエストデータ格納用

private Hashtable _postData = new Hashtable();

// リクエストイベント処理登録
_webBrowser.RequestHandler = new RequestHandler(this);

private void OnLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
{
if (!e.IsLoading) {
//_webBrowser.Focus()
Invoke(new MethodInvoker(() => { SetFocus(_webBrowser.Handle); }));

// 読込完了処理を記述
string jsScript = null;
jsScript = string.Format("document.getElementById('var_身長').focus();");
_webBrowser.ExecuteScriptAsync(jsScript);

string anser = "";
if (_currentAddress.IndexOf("keisan") >= 0) {
jsScript = "document.getElementById('ans1').innerText;";
_webBrowser.EvaluateScriptAsync(jsScript).ContinueWith(x =>
{
dynamic response = x.Result;

if (response.Success && response.Result != null) {
anser = response.Result.ToString();
if (string.IsNullOrEmpty(anser.Trim())) {
jsScript = string.Format("document.getElementById('var_身長').value = '{0}';", 180);
jsScript += string.Format("document.getElementById('var_体重').value = '{0}';", 54);
jsScript += "document.getElementById('executebtn').click();";
_webBrowser.ExecuteScriptAsync(jsScript);
} else {
MessageBox.Show(string.Format("身長={0} 体重={1} BMI={2}", _postData("var_身長"), _postData("var_体重"), anser));
}
}
});
}
}
}

// リクエストのデータセット処理
public bool SetRequestData(string url, string requestBody)
{
string aaa = null;

if (url.IndexOf("/exec/system/1161228728") >= 0) {
if (requestBody.IndexOf("=") != -1) {
string[] ary = requestBody.Split("&");
for (int i = 0; i <= ary.Length - 1; i++) {
string[] param = ary[i].Split("=");
_postData.Add(Web.HttpUtility.UrlDecode(param[0]), Web.HttpUtility.UrlDecode(param[1]));
}

return true;
}
}

return false;
}



C#版

// リクエストハンドラークラス

public class RequestHandler : IRequestHandler
{
private frmMain _frm;

// コンストラクタ
public RequestHandler(frmMain frm)
{
_frm = frm;
}

// リクエストが投げられる前に呼ばれる処理
CefReturnValue IRequestHandler.OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IRequestCallback callback)
{
if (request.Method == "POST")
{
using (var postData = request.PostData)
{
var elements = postData.Elements;
var charSet = request.GetCharSet();

foreach (var element in elements)
{
if (element.Type == PostDataElementType.Bytes)
{
if (_frm.SetRequestData(browserControl.Address, element.GetBody(charSet)))
break;
}
}
}
}

return CefReturnValue.Continue;
}

bool IRequestHandler.OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame, string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
{
return false;
}

bool IRequestHandler.OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode, string requestUrl, ISslInfo sslInfo, IRequestCallback callback)
{
return false;
}

void IRequestHandler.OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath)
{
}

bool IRequestHandler.GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame, bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
{
callback.Dispose();
return false;
}

void IRequestHandler.OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status)
{
}

void IRequestHandler.OnRenderViewReady(IWebBrowser browserControl, IBrowser browser)
{
}

bool IRequestHandler.OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl, long newSize, IRequestCallback callback)
{
return false;
}

bool IRequestHandler.OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url)
{
return false;
}

bool IRequestHandler.OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
return false;
}

void IRequestHandler.OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength)
{
}

public void OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response, ref string newUrl)
{
}

public IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
{
return null;
}

public bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect)
{
return false;
}

public bool OnSelectClientCertificate(IWebBrowser chromiumWebBrowser, IBrowser browser, bool isProxy, string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
{
return true;
}

public bool CanGetCookies(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request)
{
return true;
}

public bool CanSetCookie(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, CefSharp.Cookie cookie)
{
return true;
}
}



結果

リクエストした身長と体重を取得することが出来ています。


最後に

リクエストした値をクライアント側で取得することはあまりない用途だと思います。この値をシリアルポートに送信して機器を動かすということをしています。

一般的にOnBeforeResourceLoadの用途はリクエストヘッダーの値を変更するとかくらいでしょうかね。