-1

How can I detect when DevTools has finished loading?

In CefSharp you can bring up the DevTools window with the ShowDevTools() extension method. I want to direct the embedded browser to load a page only after DevTools has been loaded (specifically, after it has attached its JavaScript debugger). However, ShowDevTools() seems to execute asynchronously.

Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • 1
    Implent ILoadHandler and assign to the browser LoadHandler property, see if http://cefsharp.github.io/api/73.1.x/html/M_CefSharp_ILoadHandler_OnFrameLoadEnd.htm is called for devtools, if not then it's not possible. You'll have to check the url to determine if it's Devtools – amaitland Jun 19 '19 at 20:39
  • @amaitland I see the DevTools URL pass through `ILoadHandler.OnFrameLoadEnd`, yet DevTools isn't completely loaded at that point. I can tell because (a) `frameLoadEndArgs.Frame.Browser.IsLoading` is true while my ChromiumWebBrowser control's `IsLoading` is false, and (b) if I immediately load this HTML `` then the DevTools debugger doesn't catch that breakpoint. However, if I wait a little longer then it'll stop at that breakpoint. I can't poll `Frame.Browser.IsLoading` in `OnFrameLoadEnd` because `IFrame` gets disposed. [cont'd below] – Matt Thomas Jun 21 '19 at 15:51
  • @amaitland [cont'd] My next thought was to catch the frame's global ID, load the frame outside `OnFrameLoadEnd` and poll `Browser.IsLoading` there. But I'm running into a strange issue where the `IFrame` I get back from `Browser.GetBrowser().GetFrame(frameId)` has a different `Identifier` than the ID I passed into `GetFrame()`, and `Browser.GetBrowser().GetFrameIdentifiers()` is returning an empty list, and the `frame.EvaluateScriptAsync("1")` task never completes. Any suggestions? – Matt Thomas Jun 21 '19 at 15:53
  • ^ Just noticed that `frameLoadEndArgs.Frame.Browser.Identifier` is different than my ChromiumWebBrowser control's `GetBrowser().Identifier`. Where do I go to get the browser corresponding to `frameLoadEndArgs.Frame.Browser.Identifier` when I'm outside the `ILoadHandler.OnFrameLoadEnd` event? – Matt Thomas Jun 21 '19 at 16:02
  • 1
    You can keep a reference to the `IBrowser` instance passed in http://cefsharp.github.io/api/73.1.x/html/P_CefSharp_FrameLoadEndEventArgs_Browser.htm I cannot really read your code as there's no types and the names doesn't clearly convey the meaning. `FrameLoadEnd` roughly translates to all resources loaded and the DOM is in a ready state. I'm not familiar with the inner workings of `DevTools`, it may generate a large portion of the `UI` in `Javascript` for which there's no easy way to track the when it's fully loaded as that definition differs for every web page. – amaitland Jun 22 '19 at 10:37

1 Answers1

0

After some false starts, my ShowDevToolsAsync extension method shown below seems to be working reliably.

Note I'm using a modification of https://stackoverflow.com/a/7809413/3063273 to detect when DevTools has finished loading. I'm not sure what a more reliable alternative would be. Perhaps you could hook into ILoadHandler.OnFrameLoadEnd like the comments to the OP suggest and work from there. I tried that path but didn't get far enough down it before I was able to get this solution working which I think is more direct and obvious.

Important: your instance of ChromiumWebBrowser needs to already have loaded a web page (or at least started loading one), otherwise DevTools will never hook into it but will just sit there with this message:

DevTools was disconnected from the page

If you want to ensure that DevTools is up before loading your page (for example to guarantee that its Javascript debugger is ready to catch even the earliest errors), then make your ChromiumWebBrowser load a dummy page, like <html>Loading DevTools...</html>, then wait for DevTools to come up, then load your page.

using System;
using System.Threading;
using System.Threading.Tasks;
using CefSharp;
using CefSharp.WinForms;

public static class ChromiumWebBrowserExtensions
{
    public static async Task ShowDevToolsAsync(
        this ChromiumWebBrowser browser,
        CancellationToken cancellationToken)
    {
        browser.ShowDevTools();

        while (true)
        {
            cancellationToken.ThrowIfCancellationRequested();

            // Detect if console is open
            var result = await browser.EvaluateScriptAsync(@"
(function(){
    // Inspired by https://stackoverflow.com/a/7809413/3063273
    var detector = function(){};
    detector.isOpened = false;
    detector.toString = function(){
        this.isOpened = true;
    };
    console.log('%c', detector);
    return detector.isOpened;
})()
", timeout: TimeSpan.FromSeconds(1));
            if (!result.Success)
            {
                throw new Exception(result.Message);
            }
            if (!(result.Result is bool isOpened))
                continue;
            if (isOpened)
                break;
            await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken);
        }
    }
}

Example usage (using an additional extension method I don't show above, but you should get the gist):

public async Task DemonstrateAsync(ChromiumWebBrowser browser, CancellationToken cancellationToken)
{
    // Give DevTools something to hook into
    await browser.LoadDataUrlAsync("<html>Waiting for DevTools to load...</html>", cancellationToken);

    // Show DevTools and wait until it's up
    await browser.ShowDevToolsAsync(cancellationToken);

    // Give the Javascript debugger a breakpoint
    await browser.LoadDataUrlAsync("<html><script>debugger;</script></html>", cancellationToken);
}

Edit: added async delay to reduce the iteration count of the while loop. 100ms is an arbitrary number that for me reduced the iteration count from >300 to ~3.

Matt Thomas
  • 5,279
  • 4
  • 27
  • 59
  • Out of curiosity how many times does it loop before Devtools is opened? – amaitland Jun 26 '19 at 23:37
  • @amaitland A few hundred on my machine (measured three times to be 339, 332, and 334 iterations). If I introduce a 10ms delay in the loop then it's more than an order of magnitude fewer (measured 19, 18, 19); 100ms is (3, 4, 3). I edited to include an async delay – Matt Thomas Jun 27 '19 at 12:12