sexta-feira, 17 de maio de 2013

Overriding Default Firefox Components - JavaScript

Introduction
Sometimes we want to add new features to Firefox. For that we can create simple Firefox extensions.
But in certain cases, we want to override a feature of Firefox with our own code to achieve our purposes.
In this post we will cover how to implement our own firefox component and install it, overriding the behavior implemented by default. The use case that will be used will be the creation of a password manager which always returns the same username-password pair. This component will be implemented in javascript, but a following post will give the same behavior in C++.
I start by introducing some concepts, but if you only want to read code jump to the code section or download the code.

Add-ons Nomenclature
The nomenclature for Firefox can be confusing but here is a summary:
Add-on : Anything that can be added to firefox
Extensions: A piece of code that can be used to expand the firefox features
Plugin : A shared library used to display a specific type not supported by default (e.g:flash)
Component : A piece (old or new) of the Firefox with a well-defined interface. The focus of this post.

Understanding Components
Firefox is a very good example of software architecture. One main focus of Firefox has been the uncoupling between different modules of Firefox and their implementation which is achieved by having an interface definition for these modules, and an implementation of this interface. Together, an implementation and its interface, are a component.
The main advantage of this approach is to be able to re-implement the behavior of any of these components without breaking the software, allowing a component to be replaced, almost in runtime, by a component with the same interface.
Other advantage is that since these modules have a well defined interface and goal, they can be re-used for other applications. That's why, when learning how to program for Firefox, you are also learning to program for Thunderbird, because Mozilla re-uses!

The technology used to achieve this uncoupling is Cross Platform Component Object Model (XPCOM).
On XPCOM the interfaces are defined in cross platform interface description language (XPIDL), defining the methods provided by the module including its parameters and return values.
The XPIDL file specifies:
Contract Name: Defines the name of the interface
Contract ID (UUID) : Defines an ID for the interface. Must be unique and change everytime the interface is changed

As the name implies, XPCOM is cross platform, enabling it to run in any platform (Windows, Linux, Mac). But other great property provided by XPCOM is that it is also programming language independent, and, as far as I know, you can implement components in javascript, c++ and python.
This brought a big advantage to firefox and a lot of code has been ported from c++ to javascript, turning it into real cross platform code.

Password Manager Architecture on Firefox

The module responsible for handling password management behaviour is nsILoginManager. Its interface can be found here.

In this post, we will not care about re-implementing this module because it already brings some usefull features such as form filling that we would like to keep.
This module delegates the responsibility of password storing and reading to the module nsILoginManagerStorage, which interface can be found here.
This is the module we want to replace!

The nsILoginManagerStorage module must return nsILoginInfo instances, which are "boxes" containing passwords, usernames, etc.

Finally, JavaScript!

Since we are developing a component for an already existing interface, we don't need to create the XPIDL file. If you want, you can consult the nsILoginManagerStorage xpidl in here.

To create our module, we must first create the prototype for our class:

 function SampleLoginManagerStorage() {}  
 SampleLoginManagerStorage.prototype = {  
 .  
 .  
 .  
 }  

Inside this prototype we insert some attributes as follows:


  classDescription: "Sample nsILoginManagerStorage implementation",  
  contractID: "@example.com/login-manager/storage/sample;1",  
  classID: Components.ID("{364a118c-747a-4f6d-ac63-2d2998e5a5c1}"),  
  QueryInterface: XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),  
  _xpcom_categories: [  
   {  
    category: "login-manager-storage",  
    entry: "nsILoginManagerStorage"  
   }  
  ]  
  var NSGetFactory = XPCOMUtils.generateNSGetFactory([SampleLoginManagerStorage]);  

The important attributes to understand are:

  • contractID - the name of the contract of our implementation
  • classID - the uuid of our implementation
  • QueryInterface - this attributes is setted by calling the generateQI with nsILoginManagerStrorage as argument. This means that the exported interface visible by XPCOM will be the same as the defined on the nsILoginManagerStorage xpidl.
  • _xpcom_categories - Tells firefox what components is our module implementing
  • NSGetFactory - Is an automatized way to tell Firefox how to build instances of our component.

The only thing that is missing are the methods implementaion, but since this is javascript we can run this without further code and our module will be loaded by Firefox without complaint, although no behavior exists.

Here is an interisting method to add to our code:
  findLogins: function SLMS_findLogins(count, hostname, formSubmitURL, httpRealm) {  
      var loginInfo = Components.classes["@mozilla.org/login-manager/loginInfo;1"]  
         .createInstance(Components.interfaces.nsILoginInfo);  
      loginInfo.init(hostname, formSubmitURL, httpRealm, "batata" , "potato", "email", "pass");  
      count.value = 1;  
      return [loginInfo];  
  }  

This method instantiates a nsILoginInfo object, initiates it with its username and password field as "batata" and "potato", and sets the count as 1, meaning there is only one object being returned.

Firefox Components Packaging and Registering 
To install the components (and any extension) they must be inserted inside a .xpi file, which is a zip file with a diferent name, and must obey to a certain folder structure as described here.

The most important file in our case is the chrome manifest in which we must add:
 component {364a118c-747a-4f6d-ac63-2d2998e5a5c1} components/passwordManager.js  
 contract @example.com/login-manager/storage/sample;1 {364a118c-747a-4f6d-ac63-2d2998e5a5c1}  
 category login-manager-storage nsILoginManagerStorage @example.com/login-manager/storage/sample;1  

1st Line - We are defining a component by its uuid and its source code location.
2nd Line - We are defining a new contract name and its uuid.
3rd Line - We are registering our component as an nsILoginManagerStorage interface implementer.

Conclusions
Although I wrote a lot about Firefox in general, overriding a Firefox built in component is as simple as a dozen of lines telling which component we want to replace, and implementing its methods. This process can be used with any other component that you can find useful to replace.
Doing our components in javascript allows use anything that javascript or xpcom already provide, such as writing into files, making http connections, etc.
Although, when in some scenarios it must be necessary to implement the component on another language such as c++ so we can access a specific library. I will focus on this case on my next post.

The working source code can be found in here:
https://github.com/NunoPinheiro/getters-setters/tree/master/firefox-password-manager-javascript

References
Building Firefox Addons -  https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons?redirectlocale=en-US&redirectslug=Addons
XPCOM - https://developer.mozilla.org/en-US/docs/XPCOM

Nenhum comentário:

Postar um comentário