Executing JavaScript on C# with CefSharp WPF causes Error
Asked Answered
T

2

12

Whenever I try to execute JavaScript through C# using CefSharp (Stable 57.0), I get an error. I am simply trying to execute the alert function, so I can make sure that works and later test it out with my own function. However, I seem to be getting errors trying to do so.

public partial class WebBrowserWindow : Window
{
    public WebBrowserWindow()
    {
        InitializeComponent();
        webBrowser.MenuHandler = new ContextMenuHandler();
        webBrowser.RequestHandler = new RequestHandler();
    }

    //Trying to execute this with either method gives me an error.
    public void ExecuteJavaScript()
    {
        //webBrowser.GetMainFrame().ExecuteJavaScriptAsync("alert('test')");

        //webBrowser.ExecuteScriptAsync("alert('test');");
    }
}

I have tried both ways of executing the script.

The first one:

webBrowser.GetMainFrame().ExecuteJavaScriptAsync("alert('test')");

Gives me this error:

enter image description here

The second:

webBrowser.ExecuteScriptAsync("alert('test');");

Gives me this error:

enter image description here

My objective is to create a C# function that can execute a JavaScript function in my CefSharp Browser.

I tried many links/references and there weren't that many on stack overflow. I also read The FAQ for CefSharp and couldn't find any simple examples that allow me to execute JavaScript at will through C#.

In addition, I've verified the events where the Frame is loaded (it finishes loading), and unloaded (it does not unload), and if the webbrowser is null (which it's not), and the message from the:

webBrowser.GetMainFrame().ExecuteJavaScriptAsync("alert('test')");

still causes the first error to occur.

I tested for GetMainFrame(). It always returns null. ALWAYS. Doesn't matter how long I wait, or what conditions I check for.

IMPORTANT

I forgot to add one crucial piece of information, I have 2 assemblies in my project. Both of them compile into separate executables:

Helper.exe Main.exe

main.exe has a window "CallUI" that, when a button gets clicked, it executes the method I created "ExecuteJavaScript()", which is inside of my window "BrowserWindow". The CallUI window is declared and initialized in Helper.exe.

So basically I am trying to use a separate program to open a window, click a button that calls the method and execute javascript. So I think because they are different processes, it tells me the browser is null. However, when I do it all in Main.exe it works fine. Is there a workaround that allows me to use the separate process to create the window from Helper.exe and execute the Javascript from Main.exe?

Tound answered 2/6, 2017 at 15:53 Comment(2)
Can you please create minimum reproducible example (source code) about this Main.exe and Helper.exe interaction? Without it it's not so easy to provide you solution and be sure that it works.Methanol
Hey, I got my problem resolved, turns out I had to implement a way for the processes to talk to each other (see my long answer below). I also had an extra question about my solution (in the comment section of my answer).Tound
T
5

It has come to my attention that I was handling the problem the wrong way.

My problem, in fact, doesn't exist if it's just a single process holding all the code together. However, the fact that my project has an executable that was trying to communicate with another was the problem. I actually never had a way for my helper.exe to talk to my main.exe appropriately.

What I learned from this is that the processes were trying to talk to each other without any sort of shared address access. They live in separate address spaces, so whenever my helper.exe tried to execute that javascript portion that belonged in Main.exe, it was trying to execute the script in an uninitialized version of a browser that belonged in its own address space and not main.exe.

So how did I solve that problem? I had to include an important piece that allowed the helper.exe process to talk to the main.exe process. As I googled how processes can talk to each other, I found out about MemoryMappedFiles. So I decided to implement a simple example into my program that allows Helper.exe to send messages to Main.exe.

Here is the example. This is a file I created called "MemoryMappedHandler.cs"

public class MemoryMappedHandler
{
    MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen("mmf1", 512);
    MemoryMappedViewStream stream;
    MemoryMappedViewAccessor accessor;
    BinaryReader reader;
    public static Message message = new Message();

    public MemoryMappedHandler()
    {
        stream = mmf.CreateViewStream();
        accessor = mmf.CreateViewAccessor();
        reader = new BinaryReader(stream);

        new Thread(() =>
        {
            while (stream.CanRead)
            {
                Thread.Sleep(500);
                message.MyStringWithEvent = reader.ReadString();
                accessor.Write(0, 0);
                stream.Position = 0;
            }
        }).Start();

    }

    public static void PassMessage(string message)
    {
        try
        {
            using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("mmf1"))
            {
                using (MemoryMappedViewStream stream = mmf.CreateViewStream(0, 512))
                {
                    BinaryWriter writer = new BinaryWriter(stream);
                    writer.Write(message);
                }
            }
        }
        catch (FileNotFoundException)
        {
            MessageBox.Show("Cannot Send a Message. Please open Main.exe");
        }
    }
}

This is compiled into a dll that both Main.exe and Helper.exe can use.

Helper.exe uses the method PassMessage() to send the message to a Memory Mapped File called "mmf1". Main.exe, which must be open at all times, takes care of creating that file that can receive the messages from Helper.exe. I sends that Message to a class that holds that message and every time it receives it, it activates an event.

Here is what the Message class looks like:

[Serializable]
public class Message
{
    public event EventHandler HasMessage;

    public string _myStringWithEvent;

    public string MyStringWithEvent
    {
        get { return _myStringWithEvent; }
        set
        {
            _myStringWithEvent = value;
            if (value != null && value != String.Empty)
            {
                if (HasMessage != null)
                    HasMessage(this, EventArgs.Empty);
            }
        }
    }
}

Finally, I had to initialize Message in my WebBrowserWindow class like this:

public partial class WebBrowserWindow : Window
{
    public WebBrowserWindow()
    {
        InitializeComponent();
        webBrowser.MenuHandler = new ContextMenuHandler();
        webBrowser.RequestHandler = new RequestHandler();
        MemoryMappedHandler.message.HasMessage += Message_HasMessage;
    }

    private void Message_HasMessage(object sender, EventArgs e)
    {
        ExecuteJavaScript(MemoryMappedHandler.message.MyStringWithEvent);
    }

    public void ExecuteJavaScript(string message)
    {
        //webBrowser.GetMainFrame().ExecuteJavaScriptAsync("alert('test')");

        //webBrowser.ExecuteScriptAsync("alert('test');");
    }
}

And now it allows me to execute the javascript I need by sending a message from the Helper.exe to the Main.exe.

Tound answered 9/6, 2017 at 12:53 Comment(2)
One more thing: I want to make my MemoryMappedFile better. Right now it has a bug, where if you send the same message from Helper.exe, it will only execute it the first time (due to how I set the Message Property). How can I clear the receiving stream (MemoryMappedViewStream) (inside of MemoryMappedHandler constructor)? I tried many things like "Flush()", writing an empty message to mmf1 (it only deletes the first character of the message), etc.Tound
After a bit of thinking, I resolved the problem stated in my comment above this one.Tound
S
3

Have you tried this link? Contains a snippet that checks if the browser is initialised first.

cefsharp execute javascript

private void OnIsBrowserInitializedChanged(object sender, IsBrowserInitializedChangedEventArgs args)
{
    if(args.IsBrowserInitialized)
    {
        browser.ExecuteScriptAsync("alert('test');");
    }
}
Superannuate answered 4/6, 2017 at 16:15 Comment(1)
Yes I have tried that already. The version 57 of CefSharp doesn't use IsBrowserInitializedChangedEventArgs on IsBrowserInitializedChanged event, it uses a dependency property. Not only that, I still am not able to execute my method independently after the browser is confirmed initialized/not null, giving the same error message listed above.Tound

© 2022 - 2024 — McMap. All rights reserved.