2

I'm having trouble understanding the flow of this function I'm building.

    public void PortalLogin(AutoResetEvent signal)
            {
                // Navigate to portal
                string portalUrl = "website_name";
                string portalEmail = "email@email.com";
                string portalPassword = "password";
                Action action2 = () =>
                {
                    webBrowser2.Tag = signal;
                    webBrowser2.Navigate(portalUrl);
                    webBrowser2.DocumentCompleted -= WebBrowserDocumentCompleted;
                    webBrowser2.DocumentCompleted += WebBrowserDocumentCompleted;
                };
                webBrowser2.Invoke(action2);
                signal.WaitOne();

                // Login to O365 portal
                webBrowser2.Invoke(new Action(() =>
                {
                    HtmlElement head = webBrowser2.Document.GetElementsByTagName("head")[0];
                    HtmlElement testScript = webBrowser2.Document.CreateElement("script");
                    IHTMLScriptElement element = (IHTMLScriptElement)testScript.DomElement;
                    element.text = "function PortalLogin() { document.getElementById('userid').value = '" + portalEmail + "'; document.getElementById('password').value = '" + portalPassword + "';  document.getElementById('login').submit(); }";
                    head.AppendChild(testScript);
                    webBrowser2.Document.InvokeScript("PortalLogin");
                }));
            }

... more functions after this

When I step through it, it doesn't seem to be invoking the document.getElementById('login').submit(); part of the script "in time". How can I make sure nothing happens until the InvokeScript has fully completed?

Also- if you see any superfluous code or stuff that can be cleaned up, that's awesome too.

EDIT: Here is DocumentCompleted function.

private void WebBrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs Url)
        {
            ((AutoResetEvent)((WebBrowser)sender).Tag).Set();
        }
sir_thursday
  • 5,270
  • 12
  • 64
  • 118
  • Show your `WebBrowserDocumentCompleted`. I assume you're raising `signal` from there? Also, is `PortalLogin` running on a separate thread (from the `webBrowser2`'s parent thread)? – noseratio Aug 16 '13 at 21:26
  • Yes, it is running on a separate thread. – sir_thursday Aug 16 '13 at 21:45

1 Answers1

4

A few points:

You can add DocumentCompleted event handler once outside PortalLogin and reuse the same handler. You are using AutoResetEvent which automatically resets into non-signaled state after signal.WaitOne(), so you should be fine with just one permanent handler for DocumentCompleted.

Are you sure document.getElementById('login') returns a valid element with submit method available? Verify that before calling InvokeScript. You could do the login in two steps, e.g.:

            element.text = "function PortalLogin() { document.getElementById('userid').value = '" + portalEmail + "'; document.getElementById('password').value = '" + portalPassword + "';  }" +
                "function ExecuteLogin() { document.getElementById('login').submit(); }";
            head.AppendChild(testScript);
            webBrowser2.Document.InvokeScript("PortalLogin");
            // verify document.getElementById('login') here
            webBrowser2.Document.InvokeScript("ExecuteLogin");

Note: if successful, the submit would eventually trigger another DocumentCompleted event.

I would refactor this code using single thread and await/async pattern. DocumentCompleted can be wrapped as a task with TaskCompletionSource (here's how).

Below is how it might look like using async/await. In places where there's MessageBox.Show you could do your DOM manipulations. Note, it's all done on the main UI thread (asynchronously, of course). Looks quite a bit easier to me.

void Form1_Load(object sender, EventArgs e)
{
    var task = DoNavigationAsync();
    task.ContinueWith((t) =>
    {
        MessageBox.Show("Navigation done!");
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

struct Void {}; // use an empty struct as parameter to generic TaskCompletionSource

async Task DoNavigationAsync()
{
    Void v;
    TaskCompletionSource<Void> tcs = null; 
    WebBrowserDocumentCompletedEventHandler documentComplete = null;

    documentComplete = new WebBrowserDocumentCompletedEventHandler((s, e) =>
    {
        // more of DocumentCompleted can possibly be fired due to dynamic navigation inside the web page, we don't want them!
        this.WB.DocumentCompleted -= documentComplete;              
        tcs.SetResult(v); // continue from where awaited
    });

    // navigate to www.bing.com
    tcs = new TaskCompletionSource<Void>();
    this.WB.DocumentCompleted += documentComplete;
    this.WB.Navigate("http://www.bing.com");
    await tcs.Task;
    // do whatever you want with this instance of WB.Document
    MessageBox.Show(this.WB.Document.Url.ToString());

    // navigate to www.google.com
    tcs = new TaskCompletionSource<Void>();
    this.WB.DocumentCompleted += documentComplete;
    this.WB.Navigate("http://www.google.com");
    await tcs.Task;
    // do whatever you want with this instance of WB.Document
    MessageBox.Show(this.WB.Document.Url.ToString());

    // navigate to www.yahoo.com
    tcs = new TaskCompletionSource<Void>();
    this.WB.DocumentCompleted += documentComplete;
    this.WB.Navigate("http://www.yahoo.com");
    await tcs.Task;
    // do whatever you want with this instance of WB.Document
    MessageBox.Show(this.WB.Document.Url.ToString());

    return;
}
Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486