1

I am trying to create a method to, using the WebBrowser control, navigate to a webpage and block execution until the page has fully loaded.

Unfortunately knowing if the page has fully loaded or not is not that simple and just based on the events Navigating and DocumentCompleted.

Together they make a pair: when Navigating fires, a DocumentCompleted will always fire after that.

Unfortunately DocumentCompleted fires for every frame that has loaded, so it could fire multiple times per page, meaning it's not unusual to see the following sequence: Navigating - DocumentCompleted - Navigating - DocumentCompleted.

As a result, I've defined "page fully loaded" as the following: if DocumentCompleted has fired and a second has elapsed without Navigating firing again during that time.

Anyway, I've got the following as my Rx code to try to do the above:

_pageLoaded = navigating.Select(_ => documentCompleted.Delay(TimeSpan.FromSeconds(1))).Switch().FirstAsync();

Then somewhere else I do an await _pageLoaded before continuing.

However, for some bizarre reason, this does not work the first time around. So when the application loads up and the WebBrowser navigates somewhere, it gets stuck on the await _pageLoaded until both Navigating and DocumentCompleted fire again (i.e., I go to another URL - or the same one).

I did a quick project just to test this out and the events Navigating and DocumentCompleted fire as expected, here's my full code:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private IObservable<object> _pageLoaded; 

    private void Form1_Load(object sender, EventArgs e)
    {
        var navigating = Observable.FromEventPattern(webBrowser, "Navigating");
        var documentCompleted = Observable.FromEventPattern(webBrowser, "DocumentCompleted");
        _pageLoaded = navigating.Select(_ => documentCompleted.Delay(TimeSpan.FromSeconds(1))).Switch().FirstAsync();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        webBrowser.Navigate(textBox1.Text);
        await _pageLoaded;
        MessageBox.Show("DoneLoading");
    }

    private void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        MessageBox.Show("DocumentCompleted fired");
    }

    private void webBrowser_Navigating(object sender, WebBrowserNavigatingEventArgs e)
    {
        MessageBox.Show("Navigating fired");
    }
}

The result when someone enters a URL into the TextBox (I tested with google.com) and hits the Button is:

  • Navigating fired
  • DocumentCompleted fired
  • ... ... nothing

If then on the WebBrowser I click on a link, say images at the top for example, the following happens:

  • Navigating fired
  • DocumentCompleted fired
  • 1 sec later ... DoneLoading

If I hit the Button again what then happens is:

  • Navigating fired
  • DocumentCompleted fired
  • 1 sec later ... DoneLoading
  • And obviously if I haven't clicked any link within the page before, then I get a second DoneLoading MessageBox.

So, my question is simple: what does it not work the first time around and how can I fix it?

EDIT: I just did a test with a different website where the events fire multiple times, and it works fine on those websites. So it's literally websites where it only fires the one time, and it appears to only be an issue the first time around.

JohnUbuntu
  • 699
  • 6
  • 19

1 Answers1

4

The problem is that you are waiting after you've called Navigate - so depending whether the Navigate event is raised synchronously or not, either the Rx never sees the first navigate event or it is a race.

Try this:

    private async void button1_Click_1(object sender, EventArgs e)
    {
        var waiter = _pageLoaded.GetAwaiter();
        webBrowser.Navigate(textBox1.Text);
        await waiter;
        MessageBox.Show("DoneLoading");
    }

Incidentally, for just this reason its a very common pattern in asynchronous code to start waiting for an event to complete and then initiate it - so this is a good one to remember.

James World
  • 29,019
  • 9
  • 86
  • 120
  • 2
    That was exactly the problem. This comes from thinking of Observables as events and thinking that doing `navigating = Observable.FromEventPattern(webBrowser, "Navigating");` subscribed me to any event generated by it, that it would just put them in the "stream" waiting to be picked up by whatever I wanted, whenever I wanted. Now I know better ;) Thank you! – JohnUbuntu Mar 31 '16 at 16:03
  • You might find this answer helpful too: http://stackoverflow.com/questions/19895373/how-to-use-observable-fromevent-instead-of-fromeventpattern-and-avoid-string-lit/19896246#19896246 – James World Mar 31 '16 at 16:16