Refresh and DISPID_DOCUMENTCOMPLETE

Waterfall

One of the great unsolved mysteries of BHOs is how to catch refresh events. When the user refreshes a page, DISPID_DOCUMENTCOMPLETE and DISPID_NAVIGATECOMPLETE are not sent, as they are with a normal load. Add-ons rely on that to know when they can start changing the DOM, so it’s a pretty serious problem.

There are some suggested solutions that use the DISPID_DOWNLOADCOMPLETE event, which is sent on a refresh, but only after all images have been downloaded. Since you get one of those for every document complete, you can do some jiggery-pokery to spot if you get one of those without a corresponding navigate complete to spot refreshes.

It seemed like there had to be a more reliable way than this, so I’ve cooked up a solution that detects refresh events directly. You can download example source code here.

It works by attaching a hook to IE’s main window procedure using
SetWindowsHookEx(WH_CALLWNDPROCRET, …);
This calls back to the function we specify, after every call to IE’s message loop. By looking at what happened during a refresh, I spotted that a WM_COMMAND message is always passed, with a LOWORD(wparam) of 0x1799 for a refresh caused by pressing F50xa220 for one triggered from the main menu, and 0x179a for the context menu. These values are consistent across both IE 6 and 7.

I’ve set up my hook function to get called after the app’s message handler, so when I see a refresh command has just gone through, I override the default refresh behavior by explicitly calling IWebBrowser2::Navigate() to the current page’s URL. This causes IE to go through the normal loading events, so BHO’s now receive the usual document complete call.

Here’s the pros and cons of this new approach:

Pros:

Simplicity. Compared to the book-keeping needed for the event counting approach, it’s a lot easier to code.
Identical to normal loading. A refresh now triggers exactly the same page-loading events as loading a new page.
Right time. The event counting approach only detects a refresh when it sees a download complete event, which can be some time after the document is actually ready.

Cons:
Voodoo-esque. Relying on details of IE’s internal implementation is risky. The fact that it’s been consistent for several years makes it less scary.

Redundancy. If more BHOs use this approach, you could have the code called multiple
times for a single refresh. In practice this doesn’t seem to cause any noticeable problems.
Misses script refreshes. If a script calls window.location.Reload(), this won’t be detected.
Subverts
IE’s refresh behavior
. This is probably the most serious issue, since I
know there’s multiple meanings for refresh, depending on whether shift
is held down, etc. In production code, I’ll probably limit the forced
reload to pages where I needed it (eg search results for PeteSearch)

Overall, this seems better than the alternatives for my purposes. I’d prefer to avoid the whole thing, but since the bug’s been a known issue for at least four years, I can’t rely on it being fixed soon.

I also looked into some techniques that relied on adding a handler to the document or window’s onload event. Unfortunately using attachEvent(), there was no call back to the handler on a refresh, even though script handlers within the page do get called back. It’s possible that setting the onload handler for the window explicitly would have worked, but this can be overwritten by scripts, and so isn’t reliable.

More posts on porting Firefox add-ons to IE

6 responses

  1. buddy , this approach crashes perfect keyboard pro (mtwbho.dll) . im also going bit crazy with hooking it 😉
    have fun
    Nirmal

  2. Thanks for the heads-up! Have you got some steps I can follow to reproduce the crash?
    I just installed perfect keyboard pro on my vista machine, and it crashed my task bar with no intervention from my BHO.
    Digging through the info on PKP, it looks like they do some pretty radical hacking to get their macro stuff feeding into other apps. I presume there’s some issues in their code, since mine’s fairly simple and well-behaved, but I’d like to hear more! 🙂
    Pete

  3. Hi, let me thank you first for your effort.
    I’ve applied your solution to my project with limited success.
    Limited in that it works for the refresh menu and hot key(F5) but misses the context menu and refresh button (of IE7 in VS 2005 on WinXP pro with SP2).
    Any updates I missed?
    Thanks.

  4. Thanks Emjay. That looks like an interesting approach. John mentions that it doesn’t work with IE8, and that makes me wonder if my hooking code would either?

  5. Hello,

    Thank you for sharing this topic. I want to download your example , but that link does not work. Please fix it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: