Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is is possible to know for sure if a WebBrowser is navigating or not?

I'm trying to find a way for my program to know when a WebBrowser is navigating and when is not. This is because the program will interact with the loaded document via JavaScript that will be injected in the document. I don't have any other way to know when it starts navigating than handling the Navigating event since is not my program but the user who will navigate by interacting with the document. But then, when DocumentCompleted occurs doesn't necessarily mean that it have finished navigating. I've been googling a lot and found two pseudo-solutions:

  1. Check for WebBrowser's ReadyState property in the DocumentCompleted event. The problem with this is that if not the document but a frame in the document loads, the ReadyState will be Completed even if the main document is not completed.

  2. To prevent this, they advise to see if the Url parameter passed to DocumentCompleted matches the Url of the WebBrowser. This way I would know that DocumentCompleted is not being invoked by some other frame in the document.

The problem with 2 is that, as I said, the only way I have to know when a page is navigating is by handling the Navigating (or Navigated) event. So if, for instance, I'm in Google Maps and click Search, Navigating will be called, but just a frame is navigating; not the whole page (on the specific Google case, I could use the TargetFrameName property of WebBrowserNavigatingEventArgs to check if it's a frame the one that is navigating, but frames doesn't always have names). So after that, DocumentCompleted will be called, but not with the same Url as my WebBrowsers Url property because it was just a frame the one that navigated, so my program would thing that it's still navigating, forever.

Adding up calls to Navigating and subtracting calls to DocumentCompleted wont work either. They are not always the same. I haven't find a solution to this problem for months already; I've been using solutions 1 and 2 and hoping they will work for most cases. My plan was to use a timer in case some web page has errors or something but I don't think Google Maps has any errors. I could still use it but the only uglier solution would be to burn up my PC.

Edit: So far, this is the closest I've got to a solution:

partial class SafeWebBrowser
{
    private class SafeNavigationManager : INotifyPropertyChanged
    {
        private SafeWebBrowser Parent;
        private bool _IsSafeNavigating = false;
        private int AccumulatedNavigations = 0;
        private bool NavigatingCalled = false;

        public event PropertyChangedEventHandler PropertyChanged;

        public bool IsSafeNavigating
        {
            get { return _IsSafeNavigating; }
            private set { SetIsSafeNavigating(value); }
        }

        public SafeNavigationManager(SafeWebBrowser parent)
        {
            Parent = parent;
        }

        private void SetIsSafeNavigating(bool value)
        {
            if (_IsSafeNavigating != value)
            {
                _IsSafeNavigating = value;
                OnPropertyChanged(new PropertyChangedEventArgs("IsSafeNavigating"));
            }
        }

        private void UpdateIsSafeNavigating()
        {
            IsSafeNavigating = (AccumulatedNavigations != 0) || (NavigatingCalled == true);
        }

        private bool IsMainFrameCompleted(WebBrowserDocumentCompletedEventArgs e)
        {
            return Parent.ReadyState == WebBrowserReadyState.Complete && e.Url == Parent.Url;
        }

        protected void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null) PropertyChanged(this, e);
        }

        public void OnNavigating(WebBrowserNavigatingEventArgs e)
        {
            if (!e.Cancel) NavigatingCalled = true;
            UpdateIsSafeNavigating();
        }

        public void OnNavigated(WebBrowserNavigatedEventArgs e)
        {
            NavigatingCalled = false;
            AccumulatedNavigations++;
            UpdateIsSafeNavigating();
        }

        public void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
        {
            NavigatingCalled = false;
            AccumulatedNavigations--;
            if (AccumulatedNavigations < 0) AccumulatedNavigations = 0;
            if (IsMainFrameCompleted(e)) AccumulatedNavigations = 0;
            UpdateIsSafeNavigating();
        }
    }
}

SafeWebBrowser inherits WebBrowser. The methods OnNavigating, OnNavigated and OnDocumentCompleted are called on the corresponding WebBrowser overridden methods. The property IsSafeNavigating is the one that would let me know if it's navigating or not.

like image 298
Juan Avatar asked Nov 08 '10 19:11

Juan


1 Answers

Waiting till the document has loaded is a difficult problem, but you want to continually check for .ReadyState and .Busy (dont forget that). I will give you some general information you will need, then I will answer your specific question at the end.

BTW, NC = NavigateComplete and DC = DocumentComplete.

Also, if the page you are waiting for has frames, you need to get a ref to them and check their .busy and .readystate as well, and if the frames are nested, the nested frames .readystate and .busy as well, so you need to write a function that recursively retreives those references.

Now, regardless of how many frames it has, first fired NC event is always the top document, and last fired DC event is always that of the top (parent) document as well.

So you should check to see if its the first call and the pDisp Is WebBrowser1.object (literally thats what you type in your if statement) then you know its the NC for top level document, then you wait for this same object to appear in a DC event, so save the pDisp to a global variable, and wait until a DC is run and that DC's pDisp is equal to the Global pDisp you've saved during the first NC event (as in, the pDisp that you saved globally in the first NC event that fired). So once you know that pDisp was returned in a DC, you know the entire document is finished loading.

This will improve your currect method, however, to make it more fool proof, you need to do the frames checking as well, since even if you did all of the above, it's over 90% good but not 100% fool proof, need to do more for this.

In order to do successful NC/DC counting in a meaningful way (it is possible, trust me) you need to save the pDisp of each NC in an array or a collection, if and only if it doesn't already exist in that array/collection. The key to making this work is checking for the duplicate NC pDisp, and not adding it if it exists. Because what happens is, NC fires with a particular URL, then a server-side redirect or URL change occurs and when this happens, the NC is fired again, BUT it happens with the same pDisp object that was used for the old URL. So the same pDisp object is sent to the second NC event now occuring for the second time with a new URL but still all being done with the exact same pDisp object.

Now, because you have a count of all unique NC pDisp objects, you can (one by one) remove them as each DC event occurs, by doing the typical If pDisp Is pDispArray(i) Then (this is in VB) comparison wrapped in a For Loop, and for each one taken off, your array count will get closer to 0. This is the accurate way to do it, however, this alone is not enough, as another NC/DC pair can occur after your count reaches 0. Also, you got to remember to do the exact same For Loop pDisp checking in the NavigateError event as you do in the DC event, because when a navigation error occurs, a NavigateError event is fired INSTEAD of the DC event.

I know this was a lot to take, but it took me years of having to deal with this dreaded control to figure these things out, I've got other code & methods if you need, but some of the stuff I mentioned here in relation to WB Navigation being truely ready, haven't been published online before, so I really hope you find them useful and let me know how you go. Also, if you want/need clarification on some of this let me know, unfortunately, the above isn't everything if you want to be 100% sure that the webpage is done loading, cheers.

PS: Also, forgot to mention, relying on URL's to do any sort of counting is inaccurate and a very bad idea because several frames can have the same URL - as an example, the www.microsoft.com website does this, there are like 3 frames or so calling MS's main site that you see in the address bar. Don't use URL's for any counting method.

like image 77
Erx_VB.NExT.Coder Avatar answered Oct 12 '22 14:10

Erx_VB.NExT.Coder