Search code examples
c#cefsharp

Intercept calls to window.frameElement in cefsharp


In my WinForms project, I am opening a CefSharp browser and loading an external url that is not in my control. Normally, this URL is opened as a iframe within external application.

The page interacts with its parent window using window.frameElement and calls some function on window.frameElement.

In CefSharp, I am not able to stub out window.frameElement.

Is there anyother way to know when the page has called some function on window.frameElement.


Solution

  • This is going to be pretty hacky, but I'm sharing regardless. I created a simple example which worked for me, I hope it will help you as well.

    The JavaScript side is just a plain HTML with a button which invokes an event handler on click:

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Test</title>
    
        <script src="app.js"></script>
    </head>
    <body>
        <button onclick="onClick()">Click me</button>
    </body>
    </html>
    

    The event handler just invokes a random function (doesn't even have to exist on frameElement) with some arguments:

    app.js

    function onClick() {
        var x = window.frameElement.greet("Hello", "world");
    }
    

    The C# side has a ChromiumWebBrowser pointing to the address of my page:

    Form1.Designer.cs

    this.browser = new CefSharp.WinForms.ChromiumWebBrowser("http://localhost:3000/index.html");
    

    I subscribe to the FrameLoadEnd event:

    this.browser.FrameLoadEnd += BrowserOnFrameLoadEnd;
    

    The handler injects some JavaScript code into the main/top frame (our main page):

    Form1.cs

    private void BrowserOnFrameLoadEnd(object sender, FrameLoadEndEventArgs frameLoadEndEventArgs)
    {
        if (frameLoadEndEventArgs.Frame.IsMain)
        {
            frameLoadEndEventArgs.Frame.ExecuteJavaScriptAsync(@"
    (function() {
        Object.defineProperty(window, 'frameElement', {
            writable: true
        });
    
        window.frameElement = {};
    
        window.frameElement = new Proxy(window.frameElement, {
            get: function (target, propKey, receiver) {
                return function(...args) {
                    window.frameElementInterceptor.intercept(propKey, args);
                };
            }
        });
    })();");
        }
    }
    

    The injected code modifies the window.frameElement to capture property getters using an ES6 Proxy. This way when the original page does window.frameElement.someMethod(arg1, arg2) I can inject my custom logic.

    The custom logic I'm injecting is to call the frameElementInterceptor's intercept method with the name of the function that was called and the arguments that were passed in.

    Going back to the C# side, we register a JS object with the CEFSharp browser and make it available as frameElementInterceptor on the global scope in JS:

    [ComVisible(true)]
    public class FrameElementInterceptor
    {
        public void Intercept(string propKey, object[] args)
        {
            MessageBox.Show($@"Function '{propKey}' was called with arguments: [{string.Join(", ", args)}]",
    @"Function call intercepted");
        }
    }
    
    public Form1()
    {
        InitializeComponent();
    
        // .....
    
        browser.RegisterJsObject("frameElementInterceptor", new FrameElementInterceptor());
    }
    

    Now all the pieces are in place and whenever I click the button in the page, my C# interceptor gets the message:

    MessageBox from C#

    Caveat: although this simple example works, but I'm sure there are all kinds of edge cases not considered here, so I would be very wary with putting this in production. Also I think a better solution would involve code change from CefSharp's side. They could enable registering of types that derive from DynamicObject and enable overriding existing objects on window (like frameElement). As far as I know there is no progress on this since this question.