
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 F5, 0xa220 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