Preemptive popup killer

 

Oh no ! not yet another Popup killer !!

Well, sort of. The remainder of this article presents a technique to block popup windows when surfing the Internet. What makes it so unique is that it has the ability to block those popup windows before they get created on screen. This makes the technique quite different in nature with other articles in code project such like C# Popup killer (mine by the way, huh!) and C++ Popup Killer.

And because I wanted to share it as efficiently as possible, I have included both C++ and C# implementation for it.

 

What is Preemptive Popup killer

Standard (shareware) Popup killers usually search for banned window names on a regular time interval basis. It means that these tools cannot do anything as long as a known banned window has not been shown on screen. Those banned windows somewhat continue to bother.
Besides that, Popup windows are mostly ads whose names are dynamic URLs, hence need to be declared again and again as new banned windows, because no banned windows dictionnary can be broad enough to anticipate all possible ad names. As a consequence, it requires users to manually add entries in the dictionnary of banned windows. Even if this can be eased with the help of windows hotkeys, the user is required some kind of interaction.

The panacea is to kill banned windows without being seen.

 

Taking advantage of Internet Explorer events

The starting point for the development is the events fired by Internet Explorer while surfing the net. There is one particularly interesting, whose handler is OnNewWindow2([out] IDispatch*, [out] BOOL *bCancel). This event is fired anytime a new window is about to be created, in other words :
  • anytime the user right-clicks on a hyperlink (or image, ...) and selects Open In New Window
  • anytime javascript code window.open("http://doubleshit.com/...","ad window", ...) is executed
Out of these 2 options, the developer may decide to manage the state of right clicks through time, but that would be a shortcoming because not only right clicks show a contextual menu, as the context menu key does this too actually. In fact, a smarter idea is to take advantage of the fact that ad popups are windows open while the HTML code is being parsed by the browser, before the HTML document gets the completed state. If we can get our hands on the document state when processing this event, then we may just end up with a trivial decision : as long as the OnNewWindow2 event is triggered while the document is not in completed state, that's an annoying popup window. In this case, what we simply do is *bCancel = true, and good bye !
Popup windows are thus finely filtered out, and don't need any repository of known banned windows.


Let's begin with the C++ implementation.

 

Banning popups using C++

We derive the CHtmlView MFC class, a simple wrapper for the web browser control (Internet Explorer ActiveX). The OnNewWindow2 event handler does not even need be declared in a DISPATCH map or whatever, because the CHtmlView base implementation provides all of this already. What follows is the code for the event handler :
void CHtmlViewEx::OnNewWindow2( LPDISPATCH* ppDisp, BOOL* Cancel )
{
    // GetBrowserInstance() is a member method of MFC's CHtmlView

    if ( m_bFilterPopups && GetBrowserInstance() )
    {
        // check if we are trying to open a new window though document state is not complete
        //typedef enum tagREADYSTATE{
        //    READYSTATE_UNINITIALIZED = 0,
        //    READYSTATE_LOADING = 1,
        //    READYSTATE_LOADED = 2,
        //    READYSTATE_INTERACTIVE = 3,
        //    READYSTATE_COMPLETE = 4
        //} READYSTATE;

        READYSTATE nReadyState;
        GetBrowserInstance()->get_ReadyState(&nReadyState);
        if (nReadyState!=READYSTATE_COMPLETE)
        {
            *Cancel = TRUE;
            return;
        }
    }

    // otherwise, it is ok. New window creation is allowed.
    // in this sample, we are in a MDI environment, 
    //  so let's ask the framework to create a new document instance
    ((CWwApp*)AfxGetApp())->NewDocument();

    // now that the new empty document is created, let's get the new web browser instance 
    // and send it back to the event caller.
    CMDIFrameWnd *pFrame = (CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
    CMDIChildWnd *pChild = (CMDIChildWnd *) pFrame->GetActiveFrame();
    CWwView *pNewView = (CWwView*) pChild->GetActiveView();
    *ppDisp = pNewView->GetBrowserInstance();
}

 

Banning popups using C#

Let's start with a default Windows C# Form Application.

Insert the web browser control by going in the .Net Toolbox window, right-clicking on Customize Toolbox, and browse for the COM component named : "Microsoft Web Browser" (shdocvw.dll), and drop it onto your form :


Adding the web browser control to your Toolbox

 

Doing this, VS.Net does the marshalling stuff. It uses aximp (from the VS.Net tool directory) to import the web browser ActiveX control and expose it as a managed component made of 2 files in your project obj directory : Interop.SHDocVw.dll and AxInterop.SHDocVw.dll. With the help of a tool like ildasm (also from the VS.Net tool directory), it is possible to know by advance how marshalling will be done. Let's check this out :


From C++ to intermediate : IDL shdocvw.dll interface reexposed in Interop.SHDocVw.dll

 

From the screen capture below, is it possible to see that Invoke([in][out] object& marshal( idispatch) ppDisp, [in][out] bool& Cancel) is a rewritten form of the event handler.
Then, what is left to do is to search a reference in the other file (AxInterop.SHDocVw.dll) for our symbolic name : DWebBrowserEvents2_NewWindow2EventHandler :


intermediate to managed code : Managed AxInterop.SHDocVw.dll interface exposed to the developer

 

What's exposed at the AxInterop.SHDocVw.dll level is exactly what is available to us C# developers. To close this short insight in this marshalling knightmare we may just remember that, using C#, when we are going to be provided an event handler for the NewWindow2 event, this one will have the following signature :
    void OnNewWindow2EventHandler(object sender, class AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e);
And it will be marshalled back to the following low-level code :
    WebBrowser::IDispatch::Invoke([in][out] object&  marshal( idispatch) ppDisp, [in][out] bool& Cancel)
that will in turn do the following C++ code execution :
    Invoke_vtable[DISPID=14](IDispatch* pDisp, bool*)
thanks to the AxSHDocVw.DWebBrowserEvents2_NewWindow2Event map definition shown below :


AxSHDocVw.DWebBrowserEvents2_NewWindow2Event definition : bridge between managed and low-level

 

 

Ok Stephane, give me that fucking C# code before I vomit

Let's get the code skeleton for the OnNewWindow2 event handler : Select the web browser in the Form, show the Properties Window, and show the events tab. From here, just type OnNewWindow2 in front of NewWindow2 (the marshalled name for the AxSHDocVw.DWebBrowserEvents2_NewWindow2Event event).


Adding support for the NewWindow2 event

 

The code is the same than its C++ counter part :
// description : NewWindow2 event handler
//
// purpose : handle popup windows triggered by right-clicks or javascript (windows.open)
//
private void OnNewWindow2EventHandler(object sender, AxSHDocVw.DWebBrowserEvents2_NewWindow2Event e)
{
    // is document ready state complete ? if not, that's an ad popup --> don't allow it
    if ( axWebBrowser1.ReadyState != SHDocVw.tagREADYSTATE.READYSTATE_COMPLETE)
    {
        e.ppDisp = null;
        e.cancel = true;
        return;
    }

    // otherwise, this window is legitimate. Let's create a new form instance and 
    //  send the new web browser instance back to the caller.
    Form1 newwindow = new Form1();
    newwindow.Text = "(new browser window)";
    e.ppDisp = newwindow.axWebBrowser1.Application;

    // and finally show the new window
    newwindow.Show();
}

 

The navigation applogic itself is a breeze, and is as follows :

// command handlers
//

private void button_GoBack(object sender, System.EventArgs e)
{
    axWebBrowser1.GoBack();
}

private void button_Refresh(object sender, System.EventArgs e)
{
    axWebBrowser1.CtlRefresh();
}

// called when the user hits VK_ENTER in the address bar
private void OnNewUrl(object sender, System.Windows.Forms.KeyEventArgs e)
{
    if (e.KeyCode==Keys.Enter)
        Navigate( textBox1.Text );
}



// applogic
//

protected void SyncUI(String sURL)
{
    textBox1.Text = sURL; // update UI
}

public void Navigate(string sURL)
{
    SyncUI(sURL);

    object o = new object();
    object oURL = (object) sURL;

    this.axWebBrowser1.Navigate2( ref oURL, 
                                  ref o/*ref object flags*/, 
                                  ref o/*ref object targetframe*/,
                                  ref o/*ref object postdata*/,
                                  ref o/*ref object headers*/);
}

 

 

 

Stephane Rodriguez-
August 21, 2002.

 


Home
Blog