web-dev-qa-db-ja.com

CefSharpは、ドキュメントのロード/処理の前にJavascriptを挿入します

私が取り組んでいるプロジェクトの場合、Webページのドキュメント処理を開始する前にJavaScriptを挿入する必要があります。これはWebBrowserコンポーネントを介して簡単に実現できますが、CefSharpの使用で問題が発生しています。

これは問題の単純化です。ウェブページが機能するには「InjectedObject」が存在する必要があります。ドキュメントの最上部でインジェクションを行わずにWebページを呼び出すか、ドキュメントが処理される前に評価/実行されると、次のようになります。

=====失敗時のhtml出力例=====

isObjectPresent?

false

=====

表示するWebページが必要な場合:

=====成功時のhtml出力例=====

isObjectPresent?

true

=====

<html>
  <head>
    <script>
      isObjectPresent = typeof InjectedObject == "object";
    </script>
  </head>
  <body>
    <p>isObjectPresent?</p>
    <div id="result"></div>
    <script>
      document.getElementById("result").innerHTML = isObjectPresent;
    </script>
  </body>
</html>

利用可能なすべての提案を見ると、LoadingStateChanged()またはFrameLoadEnd()を使用してスクリプトを挿入する必要があることがわかります。

public void OnFrameLoadEnd(object sender, FrameLoadEndEventArgs args) {
  if (args.Frame.IsMain) {
    args.Frame.ExecuteJavascriptAsync("window.InjectedObject = {};");
  }
}

ただし、これを試したすべての反復で、FrameLoadStartを使用した場合でも、ドキュメントの処理が開始された後に挿入されたJavaScriptが発生しました。ドキュメント処理が開始される前に発生することを保証する真のJavaScriptインジェクションの例はありますか? (競合状態/タイミングの問題を回避するようにしてください)。

私が模倣しようとしているWebBrowserコンポーネントの動作の例として、次のようなものがあります。

private void uiWebBrowser_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e) 
{
  var browser = (WebBrowser)sender;
  var document = browser.Document as HTMLDocument;
  var head = document.getElementsByTagName("head").Cast<HTMLHeadElement>().First();
  if (head != null)
  {
    var script = document.createElement("script") as IHTMLScriptElement;
    script.text = "window.InjectedObject = {};"
    if (head.firstChild != null)
    {
      head.insertBefore((IHTMLDOMNode)script, head.firstChild);
    }
    else
    {
      head.appendChild((IHTMLDOMNode)script;
    }
  }
}

ヘルプや提案は大歓迎です。理想的には、インターネットリクエストの解析と挿入を介してページをダウンロードしてから、loadhtmlを使用することは避けたいと思います。これは、メインフレームに影響を与えたすべてのナビゲーションアクションに対してそれを行う必要があると予想されるためです。これはハックの仕事のように聞こえます。

コメントのフォローアップとして、上記のユースケースにはjavascriptV8エンジンコンテキストで十分であることが示唆されました。 IRenderProcessMessageHandlerインターフェイスからOnContextCreatedメソッドを実装しようとすると、同じ結果になります。

== MainWindow.xaml ==

<Window x:Class="ExampleCefSharp001.MainWindow"
    xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
    xmlns:cefSharp="clr-namespace:CefSharp.Wpf;Assembly=CefSharp.Wpf"
    xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ExampleCefSharp001"
    mc:Ignorable="d"
    Title="MainWindow" Height="1000" Width="1100">
  <Grid>
    <cefSharp:ChromiumWebBrowser x:Name="uiWebView"></cefSharp:ChromiumWebBrowser>
  </Grid>
</Window>

== MainWindow.xaml.cs ==

public partial class MainWindow : Window
{
  JavascriptManager jsmanager;

  public MainWindow()
  {
    InitializeComponent();

    jsmanager = new JavascriptManager(uiWebView);
  }
}

public class JavascriptManager : ILoadHandler, IRenderProcessMessageHandler
{
  string injection = "window.InjectedObject = {};";

  public JavascriptManager(ChromiumWebBrowser browser)
  {
    browser.LoadHandler = this;
    browser.RenderProcessMessageHandler = this;

    //  Lets just pretend this is a real url with the example html above.
    browser.Address = "https://www.example.com/timingtest.htm"
  }

  public void OnContextCreated(IWebBrowser browserControl, IBrowser browser, IFrame frame)
  {
    frame.ExecuteJavaScriptAsync(injection);
  }
}

コメントや提案に感謝します。足りないものがあれば教えてください!

9
Glorifundel

ついにこれに戻った。以下にある例に大きく基づいています: CefSharp.Example/Filters/FindReplaceResponseFilter.cs

iRequestHandlerおよびIResponseFilterインターフェースの実装:

== MainWindow.xaml ==

<Window x:Class="ExampleCefSharp001.MainWindow"
  xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
  xmlns:cefSharp="clr-namespace:CefSharp.Wpf;Assembly=CefSharp.Wpf"
  xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
  xmlns:d="http://schemas.Microsoft.com/expression/blend/2008"
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  xmlns:local="clr-namespace:ExampleCefSharp001"
  mc:Ignorable="d"
  Title="MainWindow" Height="1000" Width="1100">
  <Grid>
    <cefSharp:ChromiumWebBrowser x:Name="uiWebView"></cefSharp:ChromiumWebBrowser>
  </Grid>
</Window>

== MainWindow.xaml.cs ==

public partial class MainWindow : Window
{
  JavascriptManager jsmanager;

  public MainWindow()
  {
    InitializeComponent();

    jsmanager = new JavascriptManager(uiWebView);
  }
}

public class JavascriptManager : IRequestHandler
{
  string injection = "window.InjectedObject = {};";

  public JavascriptManager(ChromiumWebBrowser browser)
  {
    browser.RequestHandler = this;

    //  Lets just pretend this is a real url with the example html above.
    browser.Address = "https://www.example.com/timingtest.htm"
  }

  public IResponseFilter GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
    {
        if (frame.IsMain && request.ResourceType == ResourceType.MainFrame) 
        {
            return new JavascriptInjectionFilter(injection);
        }
        return null;
    }
}

public class JavascriptInjectionFilter : IResponseFilter
{
    /// <summary>
    /// Location to insert the javascript
    /// </summary>
    public enum Locations
    {
        /// <summary>
        /// Insert Javascript at the top of the header element
        /// </summary>
        head,
        /// <summary>
        /// Insert Javascript at the top of the body element
        /// </summary>
        body
    }

    string injection;
    string location;
    int offset = 0;
    List<byte> overflow = new List<byte>();

    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="injection"></param>
    /// <param name="location"></param>
    public JavascriptInjectionFilter(string injection, Locations location = Locations.head)
    {
        this.injection = "<script>" + injection + "</script>";
        switch (location)
        {
            case Locations.head:
                this.location = "<head>";
                break;

            case Locations.body:
                this.location = "<body>";
                break;

            default:
                this.location = "<head>";
                break;
        }
    }

    /// <summary>
    /// Disposal
    /// </summary>
    public void Dispose()
    {
        //
    }

    /// <summary>
    /// Filter Processing...  handles the injection
    /// </summary>
    /// <param name="dataIn"></param>
    /// <param name="dataInRead"></param>
    /// <param name="dataOut"></param>
    /// <param name="dataOutWritten"></param>
    /// <returns></returns>
    public FilterStatus Filter(Stream dataIn, out long dataInRead, Stream dataOut, out long dataOutWritten)
    {
        dataInRead = dataIn == null ? 0 : dataIn.Length;
        dataOutWritten = 0;

        if (overflow.Count > 0)
        {
            var buffersize = Math.Min(overflow.Count, (int)dataOut.Length);
            dataOut.Write(overflow.ToArray(), 0, buffersize);
            dataOutWritten += buffersize;

            if (buffersize < overflow.Count)
            {
                overflow.RemoveRange(0, buffersize - 1);
            }
            else
            {
                overflow.Clear();
            }
        }


        for (var i = 0; i < dataInRead; ++i)
        {
            var readbyte = (byte)dataIn.ReadByte();
            var readchar = Convert.ToChar(readbyte);
            var buffersize = dataOut.Length - dataOutWritten;

            if (buffersize > 0)
            {
                dataOut.WriteByte(readbyte);
                dataOutWritten++;
            }
            else
            {
                overflow.Add(readbyte);
            }

            if (char.ToLower(readchar) == location[offset])
            {
                offset++;
                if (offset >= location.Length)
                {
                    offset = 0;
                    buffersize = Math.Min(injection.Length, dataOut.Length - dataOutWritten);

                    if (buffersize > 0)
                    {
                        var data = Encoding.UTF8.GetBytes(injection);
                        dataOut.Write(data, 0, (int)buffersize);
                        dataOutWritten += buffersize;
                    }

                    if (buffersize < injection.Length)
                    {
                        var remaining = injection.Substring((int)buffersize, (int)(injection.Length - buffersize));
                        overflow.AddRange(Encoding.UTF8.GetBytes(remaining));
                    }

                }
            }
            else
            {
                offset = 0;
            }

        }

        if (overflow.Count > 0 || offset > 0)
        {
            return FilterStatus.NeedMoreData;
        }

        return FilterStatus.Done;
    }

    /// <summary>
    /// Initialization
    /// </summary>
    /// <returns></returns>
    public bool InitFilter()
    {
        return true;
    }

}

amaitland を正しい方向に向けてくれて、そして上記のコードの大部分が基づいているサンプルプログラムに感謝します。最終結果:

<html><head></head><body><script>window.InjectedObject = {}</script>
  <script>
    isObjectPresent = typeof InjectedObject == "object";
  </script>
  <p>isObjectPresent?</p>
  <div id="result"></div>
  <script>
    document.getElementById("result").innerHTML = isObjectPresent;
  </script>
</body></html>

これは、ヘッダーの上部にテキストを含むドキュメントを前処理するという私のニーズを満たし、挿入されたコードの前に既存のコードが実行される可能性があるタイミングの問題がないことを保証します。

editいくつかの小さな修正。メインフレームがロードされたときにのみ挿入する制御ロジックが追加されました。

5
Glorifundel

GetResourceResponseFilterの実装をオーバーライドする必要があるというあなたの答えは正しいですが、インターフェイスを正しい方法で実装しなかった場合、ブラウザがコンテンツをレンダリングしないことになり、代わりにDefaultRequestHandlerそしてGetResourceResponseFilter()をオーバーライドし、受け入れられた回答に記載されているようにカスタムフィルターを提供します。これは、この特定の機能をovverideするだけでよい場合に簡単になります。

public class CustomRequestHandler : DefaultRequestHandler
{
    string script = "alert('hello');";

    public override IResponseFilter GetResourceResponseFilter(CefSharp.IWebBrowser browserControl, IBrowser browser, IFrame frame, IRequest request, IResponse response)
    {
        if (frame.IsMain && request.ResourceType == ResourceType.MainFrame)
        {
            return new JavascriptInjectionFilter(script);
        }
        return null;
    }
}

次に、それをクロムブラウザに割り当てます。

CustomRequestHandler customRequestHandler = new CustomRequestHandler();
chromeBrowser.RequestHandler = customRequestHandler ;
1
Ali Ezzat Odeh