Question

I was studying the following piece of code from WatiN which handles launching and attaching to Internet Explorer:

    private static IEBrowser CreateIEPartiallyInitializedInNewProcess(Uri uri)
    {
        var m_Proc = CreateIExploreInNewProcess(uri);
        var helper = new AttachToIeHelper();

        var action = new TryFuncUntilTimeOut(TimeSpan.FromSeconds(Settings.AttachToBrowserTimeOut))
        {
            SleepTime = TimeSpan.FromMilliseconds(500)
        };

        var ie = action.Try(() =>
        {
            m_Proc.Refresh();
            var mainWindowHandle = m_Proc.MainWindowHandle;

            // return mainWindowHandle != IntPtr.Zero ? GetIWebBrowser2Directly(mainWindowHandle) : null;

            return mainWindowHandle != IntPtr.Zero
                ? helper.FindIEPartiallyInitialized(new AttributeConstraint("hwnd", mainWindowHandle.ToString()))
                : null;
        });

        if (ie != null) return ie._ieBrowser; 
        // if (ie != null) return new IEBrowser(ie);

        throw new BrowserNotFoundException("IE", "Timeout while waiting to attach to newly created instance of IE.", Settings.AttachToBrowserTimeOut);
    }

What WatiN does is that it launches the Internet Explorer and waits till it gets it's .MainWindowHandle (which is the handle of the "window" displaying content inside internet explorer). Once it gets a hold on this window handle, it then gets a list of all IWebBrowser2 windows that are up and running in the user's desktop and tries to match the .MainWindowHandle of the process with one (if any) of the window handles stemming from the IWebBrowser2 collection.

The most significant problem with this approach is that IWebBrowser2.HWND property (needed to compare against .MainWindowHandle) can be very problematic, erratic and temperamental in the sense that it throws InvalidCastException every other time when you try to access it (at least on the machines that I am running tests on). Then again there's the overhead of such an operation.

Here's my question to anyone who might be more knowledgeable that I am in windows programming: Since the HWNDs are gonna match anyways why don't we use the .MainWindowHandle value to retrieve the needed IWebBrowser2 right off the bat (see the commented out code above) by using the following method (inspired by the code WatiN itself uses inside ShellWindow2.cs):

    private static IWebBrowser2 GetIWebBrowser2Directly(IntPtr embeddedWebBrowserWindowHandle)
    {
        IHTMLDocument2 document2 = UtilityClass.TryFuncIgnoreException(() => IEUtils.IEDOMFromhWnd(embeddedWebBrowserWindowHandle));
        if (document2 == null) return null;

        IHTMLWindow2 parentWindow = UtilityClass.TryFuncIgnoreException(() => document2.parentWindow);
        if (parentWindow == null) return null;

        return UtilityClass.TryFuncIgnoreException(() => ShellWindows2.RetrieveIWebBrowser2FromIHtmlWindw2Instance(parentWindow));
    }

(as a sidenote, we can even make a proxy object, described in another post of mine, to cache the window handle so as to avoid asking IWebBrowser2.HWND for it).

This works for me just fine. I cannot see any conflict or mismatch between HWNDs - dunno if there's a corner case I might be missing out on. I am tempted to ask in WatiN forums about this but I thought to ask here at Programmers' Central first just in case I am missing something obvious.

Thank you all in advance. Any tip appreciated.

Cheers, Dominick

Était-ce utile?

La solution

I started digging into the internal window-structure of Internet Explorer and came up with the following hierarchy (irrelevant windows are ommited ofcourse):

IEFrame
|
-- TabWindowClass-1 --convert--> FirstIWebBrowser2
|
-- TabWindowClass-2 --convert--> SecondIWebBrowser2
|
...
|
-- TabWindowClass-Nth --convert--> Nth-IWebBrowser2

Through testing, I came up with the following findings in Windows7 + IE9(9.0.8112.16421)

  1. Interestingly (and counter-intuitively) IWebBrowser2.HWND is NEVER identical to the HWND of the TabWindowClass that it came from.

  2. IEFrame->HWND is the same as ANY of IWebBrowser2.HWND properties in the same internet explorer process. This is true even if we have multiple tabs open in the same internet explorer process.

  3. The Process.MainWindowHandle attribute of the internet explorer process (when we launch internet explorer programmatically) is identical to the IEFrame->HWND and thusly identical to those of IWebBrowser2 objects as well.

  4. The tab that is active can be retrieved by using IEFrame's HWND right off the bat (using the method I outlined in the original post).

My best educated guesses as to why the above hwnd-layout holds are that:

  1. Either folks over at Redmond made this hwnd-wiring between IEFrame and IWebBrowser2 because there's actually only 1 active tab window at a time (while the user is given the illusion of X number of tabs). Or ...

  2. Because there was a need to maintain backwards compatibility with pre-existing code targeted at previous versions of IE and that code was using IEFrame's HWND to get it's IWebBrowser2 object.

In either case I feel that there might be an internal bug in the implementation of this HWND wiring when it comes to accessing it from IWebBrowser2 interface which results in InvalidCastException.

Anyone with more insight into this issue please feel free to drop a line or two. Hope these help.

Cheers, Dominick

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top