How to automate an IE webapp that pops a modal HTML dialog?
Asked Answered
O

1

11

[Revised yet again for clarity]

I have a C++ program which interacts with a website. The site is IE-specific, and so is my program.

I'm connecting to the running instance of IE in an ordinary way (out of process -- see code). Once I get the IWebBrowser2, I have no problem getting the IHTMLDocument2 and interacting with the individual IHTMLElement objects, filling in fields and clicking buttons.

But if the web page has javascript that calls window.showModalDialog, I'm stuck: I need to interact with the HTML elements in the popup, just like the other pages; but I can't seem to get its IWebBrowser2.

The popup is always titled "Web Page Dialog", and is a window of type Internet Explorer_TridentDlgFrame containing an Internet Explorer_Server. But I can't get the IWebBrowser2 from the Internet Explorer_Server window the way I can when it's a normal IE instance.

I can get the IHTMLDocument2Ptr, but when I try to get the IWebBrowser2 I get an HRESULT of E_NOINTERFACE.

The code is pretty standard stuff, and works fine if it's a 'normal' IE window

IHTMLDocument2Ptr pDoc;
LRESULT lRes;

/* hWndChild is an instance of class "Internet Explorer_Server" */

UINT nMsg = ::RegisterWindowMessage( "WM_HTML_GETOBJECT" );
::SendMessageTimeout( hWndChild, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, 
    (DWORD*)&lRes );

LPFNOBJECTFROMLRESULT pfObjectFromLresult = 
    (LPFNOBJECTFROMLRESULT)::GetProcAddress( hInst, "ObjectFromLresult" );
if ( pfObjectFromLresult != NULL )
{
    HRESULT hr;
    hr = (*pfObjectFromLresult)( lRes, IID_IHTMLDocument, 0, (void**)&pDoc );
    if ( SUCCEEDED(hr) ) {
        IServiceProvider *pService;
        hr = pDoc->QueryInterface(IID_IServiceProvider, (void **) &pService);
        if ( SUCCEEDED(hr) )
        {
            hr = pService->QueryService(SID_SWebBrowserApp,
                IID_IWebBrowser2, (void **) &pBrowser);

            // This is where the problem occurs:
            // hr == E_NOINTERFACE
         }
    }
}

In case it matters, this is Vista and IE8. (I emphasize this because both of these introduced breaking changes in my codebase, which had worked fine with XP/IE7.)

Once again, my goal is to get each IHTMLElement and interact with it. I don't have access to the source code of the application which I'm automating.

I'm considering sending keystrokes blindly to the Internet Explorer_Server window, but would rather not.

Edited to add:

Someone suggested getting the child windows and sending them messages, but I'm pretty sure that doesn't work with Internet Explorer_Server; according to Spy++, there's aren't any child windows. (This is not IE-specific. Java applets don't seem to have child windows, either.)

Update

In the comments, Simon Maurer said the above code worked for him, and just to make sure there were no typos he very generously posted a complete standalone app on pastebin. When I used his code, it failed in the same way in the same place, and I realized he thought I wanted to connect to the underlying page, not the popup. So I've edited the text above to remove that ambiguity.

Ober answered 17/4, 2013 at 9:29 Comment(7)
What's the exception? Does pDoc look valid when you call pDoc->QueryInterface?Heartstrings
@NateHekman : I've substantially revised the question.Ober
Can you confirm the c++ app is out-of-process? What's a "webpage dialog"? Is it the IE window that pops up when a script calls showModalDialog?Mccahill
@SimonMourier - I've revised the question. Yes, it's definitely out of process. (An earlier version of the app worked in-process, but that stopped working with IE8.) Yes, I assume the app is calling showModalDialog.Ober
FYI, I have tested this code with IE 10 on Windows 8 and it works, even with a modal dialog displayed.Mccahill
@SimonMourier - Just to make sure: you mean you've tested my code with IE10, yes? (I haven't - the customer uses IE8.)Ober
In fact, I also tested this on IE8, Windows 7. My code is here pastebin.com/uj1nXG4S and the HTML file used is simply this <html><input type="button" onclick="window.showModalDialog('test.htm');" /></html>Mccahill
T
4

I don't know why you want to get the IServiceProvider or IWebBrowser2 if you just want the IHTMLElement's. You can get them by calling the IHTMLDocument's get_all() method.

This code snippet shows you how this works:

#include <Windows.h>
#include <mshtml.h>
#include <Exdisp.h>
#include <atlbase.h>
#include <SHLGUID.h>
#include <oleacc.h>
#include <comdef.h>
#include <tchar.h>

HRESULT EnumElements(HINSTANCE hOleAccInst, HWND child)
{
    HRESULT hr;

    UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));
    LRESULT lRes = 0;
    ::SendMessageTimeout(child, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (PDWORD)&lRes);

    LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress(hOleAccInst, "ObjectFromLresult");
    if (pfObjectFromLresult == NULL)
        return S_FALSE;

    CComPtr<IHTMLDocument2> spDoc;
    hr = (*pfObjectFromLresult)(lRes, IID_IHTMLDocument2, 0, (void**)&spDoc);
    if (FAILED(hr)) return hr;

    CComPtr<IHTMLElementCollection> spElementCollection;
    hr = spDoc->get_all(&spElementCollection);
    if (FAILED(hr)) return hr;

    CComBSTR url;
    spDoc->get_URL(&url);
    printf("URL: %ws\n", url);

    long lElementCount;
    hr = spElementCollection->get_length(&lElementCount);
    if (FAILED(hr)) return hr;
    printf("Number of elements: %d", lElementCount);

    VARIANT vIndex; vIndex.vt = VT_I4;
    VARIANT vSubIndex; vSubIndex.vt = VT_I4; vSubIndex.lVal = 0;
    for (vIndex.lVal = 0; vIndex.lVal < lElementCount; vIndex.lVal++)
    {
        CComPtr<IDispatch> spDispatchElement;
        if (FAILED(spElementCollection->item(vIndex, vSubIndex, &spDispatchElement)))
            continue;
        CComPtr<IHTMLElement> spElement;
        if (FAILED(spDispatchElement->QueryInterface(IID_IHTMLElement, (void**)&spElement)))
            continue;
        CComBSTR tagName;
        if (SUCCEEDED(spElement->get_tagName(&tagName)))
        {
            printf("%ws\n", tagName);
        }
    }
    return S_OK;
}

int _tmain(int argc, _TCHAR* argv[])
{
    ::CoInitialize(NULL);
    HINSTANCE hInst = ::LoadLibrary(_T("OLEACC.DLL"));
    if (hInst != NULL)
    {
        HRESULT hr = EnumElements(hInst, (HWND)0x000F05E4);    // Handle to Internet Explorer_Server determined with Spy++ :)
        ::FreeLibrary(hInst);
    }
    ::CoUninitialize();
    return 0;
}

Above code works on both: a normal window or a modal window, just pass the correct HWND to the SendMessageTimeout function.

WARNING I use a hard-coded HWND value in this example, if you want to test it you should start an IE instance and get the HWND of the Internet Explorer_Server window using Spy++.

I also advise you to use CComPtr to avoid memory leaks.

Tyranny answered 25/4, 2013 at 12:11 Comment(2)
I'll try this. What version(s) of IE are you using?Ober
YES this seems to work! It's true that in this application I only need the spElement, from there I can derive everything else.Ober

© 2022 - 2024 — McMap. All rights reserved.