wxWebConnect Getting Started Guide and Code Examples

Getting Started Guide

Overview

wxWebConnect is a Web Browser Control library for wxWidgets that enables developers to quickly integrate advanced web browser capabilities into their own applications using the Mozilla Foundation's Gecko XULRunner runtime.

To access Gecko's functionality directly, developers need to know XPCOM, or "Cross(X) - Platform Component Object Model", which is an interface technology, similar to Microsoft's COM, but with cross-platform capabillities, that allows developers to call functions in Gecko, even though it exists as a standalone binary.  Mozilla has a lot of information to help developers do exactly this:

Unfortunately, this approach assumes a detailed knowledge of XPCOM, which can be particularly difficult to understand, preventing developers from fully utilizing Gecko's excellent capabilities.

Using wxWebConnect, developers can access Gecko's rich functionality through a set user-friendly classes that handle the complicated interaction with Gecko's XPCOM interfaces.

Getting Started

First, let's build the test application, which you can find here.  To do this, download the source, then:

On Windows:

  1. Unzip this archive into, for example, \webconnect
  2. Install wxWidgets into \webconnect\wxWidgets and make sure it's built
  3. Open the .sln file and build testapp
  4. Run testapp

On Linux:

  1. Unzip this archive into, for example, /home/user/webconnect
  2. Install wxWidgets into /home/user/webconnect/wxWidgets and make sure it's built
  3. Get the Linux version of xulrunner 1.9 from Mozilla and replace the existing xr directory with it.  The packaged XULRunner is the Win32 version, which will not work on Linux.
  4. Go to /home/user/webconnect/webconnect and type "make"
  5. Go to /home/user/webconnect/testapp and type "make"
  6. Type "./testapp"

As you can see, when we run this application, it opens up an embedded web control, and we're free to browse, print, download, and perform other web related operations.  If we look at /testapp/testapp.cpp, the implementation of the test application, we'll see that most of these features are implemented by calling functions on wxWebControl, which is largely implemented in the wxWebConnect library in /webconnect/webcontrol.h and /webconnect/webcontrol.cpp.  wxWebControl, in turn, passes the function call onto the Gecko engine, which actually performs the operation.

Looking at MyApp::OnInit() in the testapp.cpp file, we see that when we first start the application, the application looks for the XULRunner engine, which in this case is located in the "xr" folder.  If XULRunner is found, the application initializes the web control with the location of the engine so that calls to the web control pass the appropriate command through to the engine, which then performs the operation.

Then, when we open a URI, for example, the application calls wxWebControl::OpenURI(), which in turn calls the LoadURI() function on the nsIWebNavigationURI interface of the nsWebBrowser instance.

Class Overview

To give us a better sense of what we're working with, let's take a quick look at the basic class organization.  Or if you want to skip ahead, you can take a look at the full API here.

wxWebConnect divides into three basic areas, the control-related classes, content-related classes, and the DOM classes.

The control-related classes are the primary entry point for embedding the browser in your application, and contain the majority of the base functionality, such as displaying web pages, navigating, printing and other similar operations.  These classes are:

  • wxWebControl.  Main browser control, deriving from wxControl.  Used for rendering web pages.
  • wxWebFrame. 
  • wxWebDialog. 
  • wxWebEvent. Used to carry event information for browser-related events, such as state and status changes, title and location changes, and DOM events.

The content-related classes are supplementary classes that help to configure the behavior of the browser control, assist in issuing certain kinds of requests, assist in handling certain kinds of content, or reporting on the progress of certain requests.  These classes are:

  • wxWebPreferences.  Used for getting and setting web preferences, such as the user agent, cookies, and proxy settings.
  • wxWebPostData. Used for setting post values when issuing POST commands programmatically.
  • wxWebContentHandler. Used for intercepting specific content types and processing them manually.
  • wxWebProgressBase. Used to get download progress information; progress information receivers should derive from this class and override the desired methods.

The Document Object Model (DOM) classes are used for accessing and manipulating the DOM of a web control, and are the primary means for interacting with the web content.  These classes are:

  • wxDOMNode. 
  • wxDOMNodeList. 
  • wxDOMNamedNodeMap. 
  • wxDOMAttr.  Derives from wxDOMNode.
  • wxDOMElement.  Derives from wxDOMNode.
  • wxDOMText.  Derives from wxDOMNode.
  • wxDOMDocument.  Derives from wxDOMNode.
  • wxDOMHTMLElement.  Derives from wxDOMElement.
  • wxDOMHTMLAnchorElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLButtonElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLInputElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLLinkElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLOptionElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLParamElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLSelectElement.  Derives from wxDOMHTMLElement.
  • wxDOMHTMLTextAreaElement.  Derives from wxDOMHTMLElement.
  • wxDOMEvent. 
  • wxDOMMouseEvent.  Derives from wxDOMEvent.

Embedding the Browser

Now that we understand a little more about how wxWebConnect works, let's try to build our own application that uses it's capabilities.  First, let's construct a shell application:


    class MyApp : public wxApp
    {
    public:

        bool OnInit()
        {
            return true;
        }

        wxString FindXulRunner(const wxString& xulrunner_dirname)
        {
        }
    };

    DECLARE_APP(MyApp);
    IMPLEMENT_APP(MyApp);

To use the wxWebControl, we need to let it know where XULRunner is located.  To do this, we first need to find the path of XULRunner, then initialize wxWebControl with XULRunner's path.

To find XULRunner, we find the location with wxStandardPaths::Get().GetExecutablePath() as follows:

    wxString FindXulRunner(const wxString& xulrunner_dirname)
    {
        // get the location of this executable
        wxString exe_path = wxStandardPaths::Get().GetExecutablePath();
        wxString path_separator = wxFileName::GetPathSeparator();
        exe_path = exe_path.BeforeLast(path_separator[0]);
        exe_path += path_separator;
       
        wxString path;

        // check <exe_path>/<xulrunner_path>
        path = exe_path + xulrunner_dirname;
        if (wxDir::Exists(path))
            return path;

        // TODO: add the following paths
        // check <exe_path>/../<xulrunner_path>
        // check <exe_path>/../../<xulrunner_path>

        return wxEmptyString;
    }

Once we know where XULRunner is located, we then initialize wxWebControl with its location:

    bool OnInit()
    {
        wxString xulrunner_path = FindXulRunner(wxT("xr"));
        if (xulrunner_path.IsEmpty())
        {
            wxMessageBox(wxT("Could not find xulrunner directory"));
            return false;
        }
           
        wxWebControl::InitEngine(xulrunner_path);

        wxFrame* frame = new MyFrame(NULL,
                                     wxID_ANY,
                                     wxT("Gecko Embedding Test"),
                                     wxDefaultPosition,
                                     wxSize(1024, 768));
        SetTopWindow(frame);
        frame->Show();

        return true;
    }

In addition to the above, we need to actually create the application frame with the embedded control.  To do this, first create a shell wxFrame class:

    BEGIN_EVENT_TABLE(MyFrame, wxFrame)
        EVT_SIZE(MyFrame::OnSize)
    END_EVENT_TABLE()

    class MyFrame : public wxFrame
    {
    public:
        MyFrame(wxWindow* parent,
                wxWindowID id,
                const wxString& title,
                const wxPoint& pos = wxDefaultPosition,
                const wxSize& size = wxDefaultSize,
                long style = wxDEFAULT_FRAME_STYLE | wxSUNKEN_BORDER) :
                    wxFrame(parent, id, title, pos, size, style)
        {
        }

        ~MyFrame()
        {
        }

    private:

        void OnSize(wxSizeEvent& evt)
        {
        }

    private:

      wxWebControl* m_browser;
    };

Then, in the frame constructor, initialize the browser control, add it to a sizer, and open a default location:

    m_browser = new wxWebControl(this, -1, wxPoint(0,0), wxSize(800,600));

    wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
    sizer->Add(m_browser, 1, wxEXPAND);
    SetSizer(sizer);

    m_browser->OpenURI(wxT("https://www.kirix.com/labs"));

That's it!  If we run the application, we should the application open with a web page.

Adding Plugins and Preferences

Adding browser plugins is similar to initializing wxWebControl.  In the OnInit() function, we add the path for the plugin before calling wxWebControl::InitEngine():


// Locate some common paths and initialize the control with
// the plugin paths; add these common plugin directories to
// MOZ_PLUGIN_PATH
wxString program_files_dir;
::wxGetEnv(wxT("ProgramFiles"), &program_files_dir);
if (program_files_dir.Length() == 0 || program_files_dir.Last() != '\\')
program_files_dir += wxT("\\");

wxString dir = program_files_dir;
dir += wxT("Mozilla Firefox\\plugins");
wxWebControl::AddPluginPath(dir);

Basic Navigation

Now that we've embedded the browser, let's implement some basic navigation.  This is straightforward; first, we add menu items and event handlers, then call the appropriate navigation functions in each of the handlers.

Next, let's implement a function to take us home:


void MyFrame::OnGoHome(wxCommandEvent& evt)
{
m_browser->OpenURI(wxT("https://www.kirix.com/labs"));
}

Finally, let's implement back, forward, stop, and reload:

    
void MyFrame::OnGoBack(wxCommandEvent& evt)
{
m_browser->GoBack();
}

void MyFrame::OnGoForward(wxCommandEvent& evt)
{
m_browser->GoForward();
}

void MyFrame::OnStop(wxCommandEvent& evt)
{
m_browser->Stop();
}

void MyFrame::OnReload(wxCommandEvent& evt)
{
m_browser->Reload();
}

Clipboard Operations

Let's also implement some clipboard operations, which are just as straightforward as the navigation functions:


void MyFrame::OnCut(wxCommandEvent& evt)
{
m_browser->CutSelection();
}

void MyFrame::OnCopy(wxCommandEvent& evt)
{
m_browser->CopySelection();
}

void MyFrame::OnCopyLink(wxCommandEvent& evt)
{
m_browser->CopyLinkLocation();
}

void MyFrame::OnPaste(wxCommandEvent& evt)
{
m_browser->Paste();
}

void MyFrame::OnSelectAll(wxCommandEvent& evt)
{
m_browser->SelectAll();
}

In the case of CopyLinkLocation(), this function is typically called from a right-click menu command event handler, so that the link that is copied is obvious from the user's perspective.

The clipboard functions are content sensitive, so we'll also implement enablers and disablers in a wxUpdateUIEvent event handler:

    
void MyFrame::OnUpdateUI(wxUpdateUIEvent& evt)
{
int id = evt.GetId();
switch (id)
{
case ID_Cut:
evt.Enable(m_browser->CanCutSelection());
break;

case ID_Copy:
evt.Enable(m_browser->CanCopySelection());
break;

case ID_CopyLink:
evt.Enable(m_browser->CanCopyLinkLocation());
break;

case ID_Paste:
evt.Enable(m_browser->CanPaste());
break;
}
}

Find

To implement find, we need to get the text to find, as well as the find settings, such as whether we should look forward or backward, match the case or not, or find the entire word or not.  Here's a simple implementation:


void MyFrame::OnFind(wxCommandEvent& evt)
{
wxTextEntryDialog dlg(this,
wxT("Please enter the text you wish to find:"),
wxT("Find Text"),
wxT(""));

int res = dlg.ShowModal();
if (res != wxID_OK)
return;

// set the find text
wxString find_text = dlg.GetValue();

// set the flags to use when finding the text
int find_flags = 0;
find_flags |= wxWEB_FIND_WRAP;
find_flags |= wxWEB_FIND_SEARCH_FRAMES;

// some additional flags:
// wxWEB_FIND_BACKWARDS
// wxWEB_FIND_MATCH_CASE
// wxWEB_FIND_ENTIRE_WORD

m_browser->Find(find_text, find_flags);
}

In typical use, we'd use an actual find-specific dialog that exposes the find flags so that we could set the find flags.  In addition, we'd save both the find text and the find flags so that we could call the find function repeatedly using the same parameters without having to open the find panel.

Printing

To implement printing, we only need three functions, one for getting the current page settings to initialize the wxPageSetupDialog, a second for setting the page settings from whatever the user enters into the wxPageSetupDialog, and the third for actually performing the printing.  Most of the extra work is just mediating between the units of measure and API of the data from the wxPageSetupDialogData, which is used by wxPageSetupDialog, and the browser control functions used for setting up the page and actually printing.

Here's a condensed version that shows the basic idea for setting the page settings:


void MyFrame::OnPageSetup(wxCommandEvent& evt)
{
// get the web control page settings;
// currently, units are in inches
double page_width = 0;
double page_height = 0;
double left_margin = 0;
double right_margin = 0;
double top_margin = 0;
double bottom_margin = 0;

m_browser->GetPageSettings(&page_width, &page_height,
&left_margin, &right_margin,
&top_margin, &bottom_margin);

// ... convert the page settings to millimeters, which are
// used by wxPageSetupDialog, and initialize an
// instance of the wxPageSetupDialog data class ...

// ... open a wxPageSetupDialog dialog, initilizing it with
// previously created dialog data

// ... if 'OK' is pressed in the dialog, save the data, and
// convert it back to inches

// save the page settings
m_browser->SetPageSettings(page_width, page_height,
left_margin,
right_margin,
top_margin,
bottom_margin);
}

Then, to print:

    
void MyFrame::OnPrint(wxCommandEvent& evt)
{
m_browser->Print();
}

Handling Events

At this point, we've implemented much of the basic functionality for a typical browser.  However, we still need to handle some of the most important cases, including updating the URL and status bars with the current locations, handling events from user interaction with the content, as well as handling certain types of web content in different ways.  To implement these, we need to handle the browser events.

First, let's take a look at the events that are available.

  • wxEVT_WEB_TITLECHANGE.  Dispatched when the title of a web page in the browser control changes; useful for setting the title bar in the application with the title of the webpage.
  • wxEVT_WEB_OPENURI.  Dispatched when a URL is opened.
  • wxEVT_WEB_LOCATIONCHANGE.  Dispatched when the location of a web page in the browser control changes.  This event is useful for updating the URL bar with the current location when a user clicks on a link on a web page.
  • wxEVT_WEB_DOMCONTENTLOADED.  Dispatched when the DOM content is entirely loaded, but before the page renders.  This event is important for manipulating the DOM reliably, since wxEVT_WEB_LOCATIONCHANGE is often fired before all the content finishes loaded, in which case any DOM manipulations made at this point will not be performed against a completely-loaded DOM and will therefore, depending on the DOM operation, will not perform properly.
  • wxEVT_WEB_STATECHANGE.  Dispatched when the browser control state changes, such as when a request is started, or a request is redirecting, transferring, negotiating, or stopped.  This event is useful for resetting the text in the status bar after a request has been made by checking to see if the state has both the wxWEB_STATE_STOP and the wxWEB_STATE_IS_REQUEST flags set to true.
  • wxEVT_WEB_STATUSCHANGE.  Dispatched when content is loading, and is useful for updating the status bar with the URL location that is currently being loaded.
  • wxEVT_WEB_STATUSTEXT.  Dispatched when a user howevers over a URL, and is useful for updating the status bar with the URL location.
  • wxEVT_WEB_SHOWCONTEXTMENU.  Dispatched when a user right-clicks on a web page element, and is useful for showing a context menu.
  • wxEVT_WEB_LEFTDOWN.  Dipatched when a user left-clicks on a web page element.
  • wxEVT_WEB_MIDDLEDOWN.  Dispatched when a user middle-clicks on a web page element.
  • wxEVT_WEB_RIGHTDOWN.  Dispatched when a user right-clicks on a web page element.
  • wxEVT_WEB_LEFTUP.  Dispatched when a user left-clicks on a web page element.
  • wxEVT_WEB_MIDDLEUP.  Dispatched when a user middle-clicks on a web page element.
  • wxEVT_WEB_RIGHTUP.  Dispatched when a user right-clicks on a web page element.
  • wxEVT_WEB_LEFTDCLICK.  Dispatched when a user left-double-clicks on a web page element.
  • wxEVT_WEB_CREATEBROWSER.  Dispatched when the browser control is created.
  • wxEVT_WEB_INITDOWNLOAD.  Dispatched when the browser control is ready to download content, such as when a user clicks on a file to download.  This event is useful for opening a dialog that let's the user choose whether to download the content, open the content in the browser, or cancel the request, each of which can be set in the event handler by calling SetDownloadAction() on the event, and passing either wxWEB_DOWNLOAD_OPEN, wxWEB_DOWNLOAD_SAVEAS, or wxWEB_DOWNLOAD_CANCEL.
  • wxEVT_WEB_SHOULDHANDLECONTENT.  Dispatched when the browser control is ready to handle a particular type of content, allowing the program to intercept the default handler for the content and implement custom behavior.  For example, if the program should handle XML data in a non-standard way, the default way the browser handles this content could be stopped by looking for the "application/xml" MIME type in the handler for this function, and if the MIME type matches this, calling SetShouldHandle(false) on the event.
  • wxEVT_WEB_FAVICONAVAILABLE.  Dispatched when the favicon should be changed.
  • wxEVT_WEB_DOMEVENT.  Dispatched for DOM events.

Let's add an event to set the application frame title based on the title of the currently loaded web page.  First, we register the event with the event table:


// note: wxID_WEB should be defined elsewhere
EVT_WEB_TITLECHANGE(wxID_WEB, MyFrame::OnTitleChange)

Next, we create an event handler, in which we set the title:

    
void MyFrame::OnTitleChange(wxWebEvent& evt)
{
SetTitle(evt.GetString());
}

As a result, when a web page title changes, such as when the user navigates from web page to web page, the title in the frame updates with the current page title.

Now, let's add an event to create a right-click menu with command IDs for the various navigation and clipboard commands we created earlier.  Again, we register the event with the event table:

    
// note: wxID_WEB should be defined elsewhere
EVT_WEB_SHOWCONTEXTMENU(wxID_WEB, MyFrame::OnShowContextMenu)

Assuming we've also registered event handlers in the event table for the various navigation and clipboard events, we have:

    
void MyFrame::OnShowContextMenu(wxWebEvent& evt)
{
wxMenu menuPopup;

wxString href = evt.GetHref();
if (!href.IsEmpty())
{
menuPopup.Append(ID_OpenHref, _("&Open"));
menuPopup.AppendSeparator();

// note: here, m_uri_href is a wxString member variable
// that's a private member in the class; we simply store
// the href, then, when the command handler for ID_OpenHref
// calls m_browser->OpenURI(m_uri_href), the appropriate
// URL will open
m_uri_href = href;
}
else
{
menuPopup.Append(ID_GoBack, _("&Back"));
menuPopup.Append(ID_GoForward, _("&Forward"));
menuPopup.Append(ID_Reload, _("&Reload"));
menuPopup.Append(ID_Stop, _("&Stop"));
menuPopup.AppendSeparator();
}

menuPopup.Append(ID_Cut, _("Cu&t"));
menuPopup.Append(ID_Copy, _("&Copy"));
menuPopup.Append(ID_CopyLink, _("Copy &Link"));
menuPopup.Append(ID_Paste, _("&Paste"));
menuPopup.Append(ID_SelectAll, _("Select &All"));

wxPoint pt_mouse = ::wxGetMousePosition();
pt_mouse = m_browser->ScreenToClient(pt_mouse);
PopupMenu(&menuPopup, pt_mouse);
}

Interacting with the DOM

For some features, we might need to interact with the DOM.  For example, if we want to show all links in the web page, we need to access the DOM so we can get the links.

To access the DOM, we first need to wait till the DOM is loaded for a particular page, which we can determine by waiting for the wxEVT_WEB_DOMCONTENTLOADED event.  Then, once the DOM is loaded, we use wxWebConnect::GetDOMDocument() to get the DOM document, from which we iteratively search through the child elements.

For example, here's how we can get the links on a page:


void MyFrame::ShowLinks()
{
// ... make sure the DOM content is loaded ...

// get the DOM Document
wxArrayString arr;
wxDOMDocument doc = m_browser->GetDOMDocument();

// get the child links
GetChildLinks(doc, arr);

// ... do something with the links ...
}

void MyFrame::GetChildLinks(wxDOMNode node, wxArrayString& arr)
{
// make sure we have a valid node; if we don't, we're done
if (!node.IsOk())
return;

// get the node's children
wxDOMNodeList nodelist = node.GetChildNodes();

// iterate through the children
int i, count = nodelist.GetLength();
for (i = 0; i < count; ++i)
{
// get a particular child node and see
// if it's an anchor element
wxDOMNode node = nodelist.Item(i);
wxDOMHTMLAnchorElement anchor = node;

// if the node is an anchor element, add it
// to the list of links
if (anchor.IsOk())
arr.Add(anchor.GetHref(););

// get any links from this particular child node
GetChildLinks(node, arr);
}
}

API

Here's the API for the wxWebConnect classes, organized by category and name: