I'm using the IWebBrowser2 interface to render a page from an HTML string created at runtime. I have written a method (let's call it DisplayHtmlString) that takes an HTML string and renders it as shown in this example. The method also calls Navigate2 with "about:blank" first, to ensure a document is present, and it also calls close after calling write.
The first time I call DisplayHtmlString, the page is always rendered correctly, i.e. the browser displays HTML according to the string I pass. The problem is that subsequent calls sometimes do not work correctly, but render a blank page instead. What could be causing this?
I have found out that when the blank page is shown, this is the result of navigating to about:blank. This was determined by navigating to a local file instead, which is then shown (whereas the HTML string should be shown instead, due to the subsequent write/close). So the call to Navigate2 works, while the calls to write and close sometimes don't.
I considered IE-internal security checks as a possible cause (cross-domain checking?), but my gut feeling is that this isn't what's happening here.
It seems more likely to me that it is some kind of synchronization problem, along the lines of "IE hasn't finished rendering yet before the next call to DisplayHtmlString comes along". My code originally didn't check the browser's ready state (because the example doesn't). I added an experimental waiting loop with a call to get_readyState and observed that the state never got beyond "loading" before returning from the method - probably because rendering is asynchronous(?). I also notice that when successive calls to DisplayHtmlString work correctly, the program's main message loop has run (giving Windows a chance to process messages), which is not the case in the scenario where successive calls to DisplayHtmlString fail.
So I'm pretty sure I need to provide correct synchronization here, but how? I notice there's a method named onreadystatechange, but haven't experimented with that yet, due to the multitude of other things I tried while groping in the dark. Could that be the solution, and how does one use it correctly? Or, alternatively, shall I just process the message loop inside DisplayHtmlString until the ready state changes to "complete"?
UPDATE: Added message loop processing to DisplayHtmlString. In the first call (which works), the ready state gets to "interactive", but no further (which doesn't seem to be a problem). In the subsequent call (when it fails), the ready state stays at "loading", even though the message loop is processed.
You should handle readystatechange
event on the document
object. In JavaScript, it would look like this:
<body>
<body>Hi, this is going to be replaced!</body>
<script>
window.onload = function()
{
document.open("text/html");
document.onreadystatechange = function() {
if (document.readyState == "complete")
alert("Done!");
}
document.write("<b>Hello again!</b>");
document.close();
}
</script>
</body>
To get it done with C++ or C#, probably the easiest way would be to provide an implementation of IDispatch
interface to IHTMLDocument2::put_readystatechange
. Then IDispatch::Invoke(DISPID_VALUE)
will be called back upon readystatechange
event.
I might be able to help with a code sample if you specify the language in use.
[EDITED] You can grab the complete example (C++/ATL/VS2012) from here. The code does it asynchronously by posting a custom message to the main window. It's too long to cite here, below are the relevant parts.
IDispatch implementation, for onreadystatechange Event Sink:
class CEventSink:
public CComObjectRoot,
public IDispatch
{
private:
HWND m_hwnd;
UINT m_message;
public:
CEventSink()
{
m_hwnd = NULL;
m_message = NULL;
}
BEGIN_COM_MAP(CEventSink)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// Init
void Init(HWND hwnd, UINT message)
{
m_hwnd = hwnd;
m_message = message;
}
// IDispatch
STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) {
return E_NOTIMPL; }
STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo) {
return E_NOTIMPL; }
STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid) {
return E_NOTIMPL; }
STDMETHODIMP Invoke(
DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
DISPPARAMS* pDispParams, VARIANT* pvarResult,
EXCEPINFO* pExcepInfo, UINT* puArgErr)
{
if ( dispidMember != NULL )
return DISP_E_MEMBERNOTFOUND;
// Just post a message to notify the main window
::PostMessage(m_hwnd, m_message, 0, 0);
return S_OK;
}
};
Using it:
CComObject<CEventSink>* p = NULL;
hr = CComObject<CEventSink>::CreateInstance(&p);
if ( FAILED(hr) )
return 0;
p->Init(m_hWnd, WM_DOCUMENTREADYSTATECHANGE);
m_eventSink = p; // does AddRef
// ...
m_htmlDocument2->put_onreadystatechange(CComVariant(m_eventSink));
For more details, get the sources and look at WebOcHost.cpp
. The error checks are very basic, for brevity.