Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / MFC

A C++ Firefox component intercepting/operating HTML DOM

3.51/5 (15 votes)
10 Oct 2007CPOL4 min read 7   1.5K  
This article uses a simple example to show how to build a Firefox component intercepting/operating DOM

Background

I have been working on BHO for a few months now. It's neat to develop BHO components. On the contrary, Firefox seems really frustrating. I guess the first reason is that there are never enough examples and codes for you to follow. Secondly, Mozilla does not provide enough documents like Microsoft does. Well, I don't blame them for that. It's a small company and it's free source after all.

In the following sections, I will use an example to show you how to intercept Firefox's DOM elements and operate them like in BHO.

There are two articles I strongly recommend you to read before you scroll down this page.

The first one teaches you how to build a component; the second teaches you how to make an extension.

The example here is based on the example in the first article.

It took me two whole weeks to learn how Firefox as well as XPCOM operate. I will use this simple graph to demonstrate the structure:

UI--> XPConnect--> XPCOM

Most of the add-ons you have seen and you can download are extensions such as a toolbar etc. The majority of them are written with JavaScript. It's alright and neat if you don't want to touch some advanced components; for instance, a toolbar only navigates the window, etc. But if you want to go further, you have to call components built under XPConnect or XPCOM.

The following code is a simple cookie manager from Mozilla's official tutorial.

JavaScript
var cookiemanager = Components.classes["@mozilla.org/cookiemanager;1"].getService();
cookiemanager = cookiemanager.QueryInterface(Components.interfaces.nsICookieManager);
// called as part of a largerDeleteAllCookies() function 
function FinalizeCookieDeletions() {
  for (var c=0; c<deletedCookies.length; c++) {
    cookiemanager.remove(deletedCookies[c].host, deletedCookies[c].name,
    deletedCookies[c].path);
  }
  deletedCookies.length = 0;
}

It is quite easy to understand the procedure.

  • Get Service of Components
  • QI the components
  • Call the methods under the components

There isn't too much technology in this part. But, how the components work is another issue.

Firefox is based on the Gecko SDK. Basically, all those APIs that start with "ns" such as nsIWebBrowser (similar to IWebBrowser in BHO) are from the Gecko SDK. You can find more information on www.xulplanet.com.

Let's get to the point. Now, I want to build a FormFiller for Firefox. Meanwhile, I do not want to use JavaScript to do the job due to the following reasons:

  • Javascript will expose your code.
  • Javascript has very limited functionality.

So, here are two solutions:

  • Build a Firefox extension with C++
  • Build a Firefox component with C++

I go for the second. Although registering a component is far more complicated than registering an extension on Firefox. But it's much more secure when calling core components and operates the data.

How to do it

Like BHO, there is a nsIWebBrowser that provides the APIs of any XPCOM based browser such as SeaMonkey or NetScape. Through nsIWebBrowser, we can get nsIDOMWindow->nsIDOMDocument->nsIDOMElement then fill in the information we want. However, this does not work in Firefox. The reason is very simple: Firefox does not use nsIWebBrowser. It will return some random useless stuff when you call nsIWebBrowser via Component Manager.

I have no idea why they do that. Maybe it's because of the multi-tab structure of Firefox. So our problem is here. We must initialize nsIDOMWindow before we move further.

To save your time on Googling all the information, please let me present this simple function prototype:

JavaScript
var res = obj.Nothing(window);

where Nothing is the method called for filling in forms. "window" is the browser window object that JavaScript naturally has.

So inside your code, you have the method declared as:

C++
NS_IMETHOD Nothing(nsIDOMWindow *domWindow); //.h file

NS_IMETHODIMP MyComponent::Nothing(nsIDOMWindow *domWindow) //cpp file

When JavaScript passed the window into the method, it will be automatically converted into nsIDOMWindow.

OK, half of the job has been done.

The other half is easy. Look at the following code and you will understand.

C++
NS_IMETHODIMP MyComponent::Nothing(nsIDOMWindow *domWindow)
{
    nsEmbedString temp(L"test");
    nsEmbedString attribute(L"value");
    nsEmbedString id(L"id");
    nsEmbedString value(L"value");

    //The above declare some strings. 
    /* 
    Please note that the string in XPCOM is declared as either nsAString 
    or nsString or nsEmbedString Please refer to 
    http://developer.mozilla.org/en/docs/XPCOM:Strings for more information
    */

    nsIDOMWindow* window; //you can use the parameter directly
    window=domWindow;
    nsCOMPtr<nsIDOMDocument> domDocument; 
    nsCOMPtr<nsIDOMElement> element;
    window->GetDocument(getter_AddRefs(domDocument)); //Get document
    domDocument->GetElementById(temp,getter_AddRefs(element)); 
    //Find target element, you also can use GetElementByTagName
    //to get a node list. Then loop through the node list.
    //Please refer to XULPlanet for more info
    //about nsIDOMDocument
    element->GetAttribute(id,value);
    //I get the value of "id" attribute
    //and fill it into "value attribute"
    element->SetAttribute(attribute,value);
    return NS_OK;
}

OK, above is the all the code you will need.

How to build the project

I strongly recommend http://www.iosart.com/firefox/xpcom/. I learned most of the things about XPCOM components there.

To start your project, you need to have a .idl file.

C++
#include "nsISupports.idl"
#include "nsIDOMWindow.idl"
[scriptable, uuid(_YOUR_INTERFACE_GUID_)]
interface IMyComponent : nsISupports
{
  void Nothing(in nsIDOMWindow domWindow);
};

Then download XPIDL.exe to generate the .h file:

Quote from the article:

  • xpidl -m header -I_DIR_ IMyComponent.idl will create the IMyComponent.h header file.
  • xpidl -m typelib -I_DIR_ IMyComponent.idl will create the IMyComponent.xpt typelib file.

Then follow http://www.iosart.com/firefox/xpcom/.

Calling the component

There are two ways to call the component:

  • Through an extension
  • Call from JavaScript on a page

It's the same. An extension is a XUL with JavaScript. Please refer to http://www.borngeek.com/firefox/toolbar-tutorial/ to find out how it works.

The following code on the HTML page calls the component:

XML
<HTML>
<SCRIPT  type="text/javascript">
function MyComponentTestGo() {
    try {
        netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
        const cid = "@mydomain.com/XPCOMSample/MyComponent;1";
        obj = Components.classes[cid].createInstance();
        obj = obj.QueryInterface(Components.interfaces.IMyComponent);
    } catch (err) {
        alert(err);
        return; 
    }
    var res = obj.Nothing(window);}
</SCRIPT>
<BODY>
<BUTTON ONCLICK="MyComponentTestGo();" id="go">Go</BUTTON>
<input id="test" value="" />
</BODY>
</HTML>

Conclusion

The biggest problem of developing Firefox components is the lack of documents and code. Even XPCOM itself has the same problem. But I think the situation will get better and better. Usually, there are some skilled people on Mozillzine you can ask for help.

I hope this simple article is helpful to those who are frustrated developing for Firefox. If you have any comments or questions, please leave them here. I will reply to you as much as I can.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)