IntroductionWhy pay money for a popup window blocker when it's so easy to roll your own? Popup Blocker is implemented in ATL as a browser helper object (BHO). A BHO is a DLL that will attach itself to every new instance of Internet Explorer. In the BHO you can intercept Internet Explorer events, and access the browser window and document object model (DOM). This gives you a great deal of flexibility in modifying Internet Explorer behavior. There are several advantages to using a BHO over an application that continually scans open windows for keywords in the title. The BHO is event driven and does not run in a loop or use a timer, so it doesn't use any CPU cycles if nothing is happening. The BHO will prevent popups from opening and downloading their content, so you save bandwidth. The BHO can be written so as to eliminate the need for a keyword list or blacklist. Also included, but not discussed in this article, is a Windows Installer setup program. Creating the BHOA minimal BHO is a COM server DLL that implements The only method on // // IOleObjectWithSite Methods // STDMETHODIMP CPub::SetSite(IUnknown *pUnkSite) { if (!pUnkSite) { ATLTRACE(_T("SetSite(): pUnkSite is NULL\n")); } else { // Query pUnkSite for the IWebBrowser2 interface. m_spWebBrowser2 = pUnkSite; if (m_spWebBrowser2) { // Connect to the browser in order to handle events. HRESULT hr = ManageBrowserConnection(ConnType_Advise); if (FAILED(hr)) ATLTRACE(_T("Failure sinking events from IWebBrowser2\n")); } else { ATLTRACE(_T("QI for IWebBrowser2 failed\n")); } } return S_OK; } Once we have the pointer to // // Funnel web browser events through this class // HRESULT CPub::ManageBrowserConnection(ConnectType eConnectType) { if (eConnectType == ConnType_Unadvise && m_dwBrowserCookie == 0) return S_OK; // not advised, nothing to do ATLASSERT(m_spWebBrowser2); if (!m_spWebBrowser2) return S_OK; CComQIPtr To create the event handler we have to derive our class from class ATL_NO_VTABLE CPub : public IObjectWithSiteImpl<CPUB>, public IDispatchImpl Then add the Invoke method as follows: // // IDispatch Methods // STDMETHODIMP CPub::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pvarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { if (!pDispParams) return E_INVALIDARG; switch (dispidMember) { // // The parameters for this DISPID are as follows: // [0]: Cancel flag - VT_BYREF|VT_BOOL // [1]: IDispatch* - Pointer to an IDispatch interface. // // If you cancel here, ALL popups will be blocked. // case DISPID_NEWWINDOW2: // Set the cancel flag to block popups pDispParams->rgvarg[0].pvarVal->vt = VT_BOOL; pDispParams->rgvarg[0].pvarVal->boolVal = VARIANT_TRUE; break; default: break; } return S_OK; } Once you get this much to compile, to make IE load the BHO you need to add the CLSID to the registry. HKLM { SOFTWARE { Microsoft { Windows { CurrentVersion { Explorer { 'Browser Helper Objects' { {C68AE9C0-0909-4DDC-B661-C1AFB9F5AE53} } } } } } } If you've done everything right up to now, the BHO will load with every instance of IE and prevent all popup windows from opening. You can quickly test this by launching IE and selecting Open in New Window from the context menu. If the BHO is working properly the command should fail. One problem you may notice is that even with no open browser windows the linker will sometimes fail because some process is using the BHO, forcing you to reboot the computer to release it. Needless to say, this can quickly become annoying. What is happening is that Windows Explorer also uses IE for its GUI, and so also loads the BHO. Since we want our BHO to load only when we launch IE, we need to make a change to BOOL WINAPI DllMain(DWORD dwReason, LPVOID lpReserved) { if (dwReason == DLL_PROCESS_ATTACH) { // Don't attach to Windows Explorer TCHAR pszLoader[MAX_PATH]; GetModuleFileName(NULL, pszLoader, MAX_PATH); CString sLoader = pszLoader; sLoader.MakeLower(); if (sLoader.Find(_T("explorer.exe")) >= 0) return FALSE; g_hinstPub = _AtlBaseModule.m_hInst; } return __super::DllMain(dwReason, lpReserved); } So now that we have our basic BHO, it would be nice to be able to exert some kind of control over it. For starters, we need to be able to enable or disable it. Also, it would be nice if Open in New Window from the context menu worked normally. There are probably a number of ways to do this, but I chose to add my own menu to the normal IE context menu. To access the IE context menu we need to derive from the class ATL_NO_VTABLE CPub : public IObjectWithSiteImpl<CPUB>, public IDispatchImpl Add the HRESULT CPub::ShowContextMenu(DWORD dwID, POINT *ppt, IUnknown *pcmdTarget, IDispatch *pdispObject) { // Return S_OK to tell MSHTML not to display its own menu // Return S_FALSE displays default MSHTML menu return S_OK; } In order to get IE to call our // // The parameters for this DISPID: // [0]: URL navigated to - VT_BYREF|VT_VARIANT // [1]: An object that evaluates to the top-level or frame // WebBrowser object corresponding to the event. // // Fires after a navigation to a link is completed on either // a window or frameSet element. // case DISPID_NAVIGATECOMPLETE2: ATLTRACE(_T("(%ld) DISPID_NAVIGATECOMPLETE2\n"), ::GetCurrentThreadId()); { // Any new windows that might pop up after this // (due to script) should be blocked. m_bBlockNewWindow = TRUE; // Reset if (!m_pWBDisp) { // This is the IDispatch* of the top-level browser m_pWBDisp = pDispParams->rgvarg[1].pdispVal; } } break; case DISPID_DOCUMENTCOMPLETE: ATLTRACE(_T("(%ld) DISPID_DOCUMENTCOMPLETE\n"), ::GetCurrentThreadId()); if (m_pWBDisp && m_pWBDisp == pDispParams->rgvarg[1].pdispVal) { // If the LPDISPATCH are same, that means // it is the final DocumentComplete. Reset m_pWBDisp. ATLTRACE(_T("(%ld) DISPID_DOCUMENTCOMPLETE (final)\n"), ::GetCurrentThreadId()); m_pWBDisp = NULL; CComPtr If you compile and run now, you should see that the IE context menu is disabled since we are always returning S_OK from the // Insert our menu at the top of the context menu g_hPubMenu = LoadMenu(g_hinstPub, MAKEINTRESOURCE(IDR_PUBMENU)); if (g_hPubMenu) { ::InsertMenu(hSubMenu, 0, MF_POPUP | MF_BYPOSITION, (UINT_PTR) g_hPubMenu, _T("Popup Blocker")); ::InsertMenu(hSubMenu, 1, MF_BYPOSITION | MF_SEPARATOR, NULL, NULL); } The tricky part comes when you run the BHO and notice that the all the items on the context menu work as you expect EXCEPT for those items on your menu, which are disabled. This is because IE inconveniently disregards menu items it does not recognize and will not enable your menu items even if you tell it to do so with the LRESULT CALLBACK CtxMenuWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (uMsg == WM_INITMENUPOPUP) { if (wParam == (WPARAM) g_hPubMenu) { // This is our menu ::CheckMenuItem(g_hPubMenu, ID_ENABLEPOPUPBLOCKER, MF_BYCOMMAND | (g_bEnabled ? MF_CHECKED : MF_UNCHECKED)); ::CheckMenuItem(g_hPubMenu, ID_PLAYSOUND, MF_BYCOMMAND | (g_bPlaySound ? MF_CHECKED : MF_UNCHECKED)); return 0; } } return CallWindowProc(g_lpPrevWndProc, hwnd, uMsg, wParam, lParam); } If a message is not for our menu, we just pass it on to the original window procedure. Now that the menu is working, you can add switches and dialogs to control the behavior of the BHO. In the code included with this article I added a checked menu item to enable/disable the BHO. I also intercepted the normal Open in New Window command and set a flag temporarily disabling the BHO in order to restore normal behavior. So at this point we have a popup blocker that works pretty well, but there are still a couple of glitches. The first one that you will notice is that certain links seem to be disabled, that is nothing happens when they are clicked. These are links that pop up another window, usually through a bit of script. The BHO happily suppresses these windows with no indication to the user. To solve this problem, I added a way to temporarily override the BHO by holding down the CTRL key. You have to be careful here to not disable the SHIFT + Click shortcut used to open a new window (thanks to Matt Newman for pointing that one out!). BOOL CPub::IsHotkeyDown() { SHORT nState = GetAsyncKeyState(VK_CONTROL); BOOL bDown = (nState & 0x8000); if (!bDown) { // Allow shift+click to open a new window nState = GetAsyncKeyState(VK_SHIFT); bDown = (nState & 0x8000); } return bDown; } But how is the user supposed to know when to press the hot key? In my code I chose to play a sound every time a popup window is suppressed. That way the user has an indication that the BHO is in the act when a link appears to be broken. Then all the user needs to do is hold down the CTRL key while clicking the link, and the link will work normally. Now our popup blocker is working really well. It blocks all popup windows and we can shut it off if we need to. However, there are still a couple of problems to solve and you probably won't notice them right away. For one, when browsing the web you will start to see these IE Script Error dialogs popping up: This is even more annoying than the normal popup ads. They occur because some scripts expect to have the popup window available and generate an error when it is not. Because not all script writers add error handlers to their pages, IE acts as the default error handler and poups up its own cryptic dialog box. Another problem is that some options on the Save As dialog under "Save as type" have disappeared, and there are various other UI problems when hosting IE -- like scroll bars going away when viewing help inside Visual Studio. To solve script error problem, I implemented class ATL_NO_VTABLE CPub : public IObjectWithSiteImpl<CPUB>, public IDispatchImpl STDMETHOD(QueryStatus)( /*[in]*/ const GUID *pguidCmdGroup, /*[in]*/ ULONG cCmds, /*[in,out][size_is(cCmds)]*/ OLECMD *prgCmds, /*[in,out]*/ OLECMDTEXT *pCmdText) { return m_spDefaultOleCommandTarget->QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText); } STDMETHOD(Exec)( /*[in]*/ const GUID *pguidCmdGroup, /*[in]*/ DWORD nCmdID, /*[in]*/ DWORD nCmdExecOpt, /*[in]*/ VARIANTARG *pvaIn, /*[in,out]*/ VARIANTARG *pvaOut) { if (nCmdID == OLECMDID_SHOWSCRIPTERROR) { // Don't show the error dialog, but // continue running scripts on the page. (*pvaOut).vt = VT_BOOL; (*pvaOut).boolVal = VARIANT_TRUE; return S_OK; } return m_spDefaultOleCommandTarget->Exec(pguidCmdGroup, nCmdID, nCmdExecOpt, pvaIn, pvaOut); } With our I should mention that in my first implementation I was intercepting the The Save As and UI problems are solved by simply calling the default handler from within each STDMETHOD(GetHostInfo)(DOCHOSTUIINFO FAR *pInfo) { if (m_spDefaultDocHostUIHandler) return m_spDefaultDocHostUIHandler->GetHostInfo(pInfo); return S_OK; } It's not clear why these UI problems are not mentioned in MSDN documentation on BHOs (if they are, I missed them), but they definately should be. <shameless_plug>The latest version can be installed from the web at Osborn Technologies.</shameless_plug> ImprovementsThere is still room for a number of improvements:
Revision History
LicenseThis article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below. A list of licenses authors might use can be found here |
|
来自: doc360sir > 《webbrowser》