Bluish Coder

Programming Languages, Martials Arts and Computers. The Weblog of Chris Double.


 

This article is currently a work in progress...As I learn more about Firefox and the source code I'll update this article. And although it demonstrates adding an element called 'video', the final specification that the WHATWG group uses for 'video' and what Firefox implements is likely to differ from what is shown here.

Have you ever wanted to add your own HTML element to a web browser? Probably not. But just in case, I'm going to describe how to add a new element to Firefox.

The part of the process I'm going to describe in this article is how to add the element to the HTML parser and the DOM object that gets created when the element is parsed. Later updates will go through accessing it from Javascript, adding a visual representation, etc.

Adding the DOM object is relatively easy. First you'll need a working build of Firefox. Follow the Mozilla documentation on getting the source from CVS and building it.

In the file content/base/src/nsGkAtomList.h add an atom for the name of the tag you are adding. For this example I'll use 'video':

GK_ATOM(video, "video")

Firefox uses IDL to describe the DOM object. This gets compiled into a header file for a class with pure virtual functions that needs to be implemented. This example will use the IDL for the Video element that Opera proposed to the WHATWG mailing list. Create a nsIDOMHTMLVideoElement.idl in the source directory dom/public/idl/html. It should have contents like the following:

#include "nsIDOMHTMLElement.idl"
[scriptable, uuid(6d7c3e9a-42d7-4ff5-8d2c-26873aaeec12)]
interface nsIDOMHTMLVideoElement : nsIDOMHTMLElement
{
  attribute DOMString src;
  attribute long height;
  attribute long width;

  void play();
  void pause();
  void stop();
};

The IDL should look very similar to that from the Opera specification. The 'uuid' was generated using 'uuidgen' to get a unique identifier. You can browse through the other IDL files in that directory to get an idea of how the other HTML DOM elements are specified.

The IDL compiler will be run on this file to generate the header file for the class we need to create. For that to happen, edit dom/public/idl/html/Makefile.in to contain the IDL file in the XPIDLSRCS section:

XPIDLSRCS =      \
  nsIDOMHTMLCanvasElement.idl  \
  nsIDOMHTMLVideoElement.idl  \
  ...

The generated header file, nsIDOMHTMLVideoElement.h, will live in the object directory specified in the .mozconfig build file, under the directory dist/include/dom/nsIDOMHTMLVideoElement.h. This helpfully provides a template of the code you'll need to write to implement it:

#if 0
/* Use the code below as a template for the implementation 
   class for this interface. */

/* Header file */
class nsDOMHTMLVideoElement : public nsIDOMHTMLVideoElement
{
public:
  NS_DECL_ISUPPORTS
  NS_DECL_NSIDOMHTMLVIDEOELEMENT

  nsDOMHTMLVideoElement();

private:
  ~nsDOMHTMLVideoElement();

protected:
  /* additional members */
};

/* Implementation file */
NS_IMPL_ISUPPORTS1(nsDOMHTMLVideoElement, nsIDOMHTMLVideoElement)

nsDOMHTMLVideoElement::nsDOMHTMLVideoElement()
{
  /* member initializers and constructor code */
}
...
/* attribute DOMString src; */
NS_IMETHODIMP nsDOMHTMLVideoElement::GetSrc(nsAString & aSrc)
{
    return NS_ERROR_NOT_IMPLEMENTED;
}
...
/* void stop (); */
NS_IMETHODIMP nsDOMHTMLVideoElement::Stop()
{
    return NS_ERROR_NOT_IMPLEMENTED;
}

/* End of implementation class template. */
#endif

The implementation should be created in a file in the directory content/html/content/src/. Again, you'll see the implementation of the other elements in there for examples. Following the template above as a guide as well as looking at the other implementations should make it fairly easy.

Rather than manually implement the getters and setters for the attributes, use the macro NS_IMPL_STRING_ATTR (or NS_IMPL_INT_ATTR and other related ones depending on the attribute type) to automatically define the setters and getters. This has the advantage of dealing with getting the values from attributes passed in the HTML. For example:

NS_IMPL_STRING_ATTR(nsHTMLVideoElement, Src, src)
NS_IMPL_INT_ATTR(nsHTMLVideoElement, Width, width)
NS_IMPL_INT_ATTR(nsHTMLVideoElement, Height, height)

You should also add a statement to generate a function that creates instances of the DOM object:

NS_IMPL_NS_NEW_HTML_ELEMENT(Video)

In content/html/content/src/nsGenericHTMLElement.h, add a call to the macro NS_DECLARE_NS_NEW_HTML_ELEMENT to provide a prototype for this creation function:

NS_DECLARE_NS_NEW_HTML_ELEMENT(Video)

You'll see the list of other elements there as a guide.

To get this implementation of the DOM object to compile, add it to Makefile.in from the directory content/html/content/src/, in the CPPSRCS section:

CPPSRCS  = \
  ...
  nsHTMLVideoElement.cpp \
  \$(NULL)

To be able to access the DOM element from Javascript, call methods, modify properties, etc, a structure needs to be filled in detailing what interfaces we implement. This can be done with the following macro in nsDOMHTMLVideoElement.cpp:

NS_HTML_CONTENT_INTERFACE_MAP_BEGIN(nsHTMLVideoElement, nsGenericElement)
  NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLVideoElement)
  NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(HTMLVideoElement)
NS_HTML_CONTENT_INTERFACE_MAP_END

Notice that it provides an interface mapping for the interface we implement, nsIDOMHTMLVideoElement, and it has a CLASSINFO line which is used, I think, for interfacing with Javascript. For this CLASSINFO line to work, add an entry to the file dom/public/nsDOMClassInfoId.h, which is an enumeration entry for the DOM class:

  eDOMClassInfo_XULCommandEvent_id,
  eDOMClassInfo_CommandEvent_id,
  ...
  // WhatWG Video Element
  eDOMClassInfo_HTMLVideoElement_id,
  ...

Once nsDOMClassInfoId.h has been modified, make a corresponding change to dom/src/base/nsDOMClassInfo.cpp. First, the include for the video header file needs to be added:

...
#include "nsIDOMHTMLVideoElement.h"
...

In the nsClassInfoData array, add an entry for our new element:

  NS_DEFINE_CLASSINFO_DATA(HTMLVideoElement, nsHTMLElementSH,
                           ELEMENT_SCRIPTABLE_FLAGS)

In nsDOMClassInfo::Init add a map entry:

  DOM_CLASSINFO_MAP_BEGIN(HTMLVideoElement, nsIDOMHTMLVideoElement)
    DOM_CLASSINFO_MAP_ENTRY(nsIDOMHTMLVideoElement)
    DOM_CLASSINFO_GENERIC_HTML_MAP_ENTRIES
  DOM_CLASSINFO_MAP_END

That's the DOM object side of things dealt with, now to add the element to the parser. In parser/htmlparser/public/nsHTMLTagList.h, add an HTML_TAG for the element:

HTML_TAG(video, Video)

That include file is used in an 'enum' for each possible HTML tag. A corresponding array must be modified to contain some information on the tag in the exact same ordering as the enum. The array is in the file parser/htmlparser/src/nsElementTable.cpp and is called gHTMLElements.

If you don't change that array, but do have the tag in the enum you'll get an assertion warning when running Firefox. The video entry is:

{
    /*tag*/                             eHTMLTag_video,
    /*req-parent excl-parent*/          eHTMLTag_unknown,eHTMLTag_unknown,
    /*rootnodes,endrootnodes*/          &gRootTags,&gRootTags,
    /*autoclose starttags and endtags*/ 0,0,0,0,
    /*parent,incl,exclgroups*/          kSpecial, (kFlowEntity|kSelf), kNone,
    /*special props, prop-range*/       0,kDefaultPropRange,
    /*special parents,kids,skip*/       0,0,eHTMLTag_unknown,
    /*contain-func*/                    0
},

The eHTMLTag_video identifier used above is automatically generated by the HTML_TAG macro we defined before.

Modify parser/htmlparser/src/nsHTMLTags.cpp. Add an entry for the unicode name of the tag:

static const PRUnichar sHTMLTagUnicodeName_video[] =
   {'v', 'i', 'd', 'e', 'o', '\0'};

Lastly, Firefox includes an HTML editor component. This is used in 'Rich Editor Widgets' like the rich email composition window in GMail. This needs a modification to a table to prevent assertion errors about not having enough entries due to our newly added element.

Modify editor/libeditor/html/nsHTMLEditUtils.cpp to add an entry to the kElements array:

...
  ELEM(var, PR_TRUE, PR_TRUE, GROUP_PHRASE, GROUP_INLINE_ELEMENT),
  ELEM(video, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
  ELEM(wbr, PR_FALSE, PR_FALSE, GROUP_NONE, GROUP_NONE),
...

Build Firefox with these changes and run it. It should now be able to handle the new HTML element. For example, the following HTML file:

<html>
  <body>
    <video id="v" src='test.ogg'/>
  </body>
</html>

It will parse the HTML and create the IDOMHTMLVideoElement object. If you put debugging in the constructor of the object you'll see it was created. Browsing the DOM tree using the DOM inspector or similar tool will show the 'video' element. Using Javascript Shell you can get access to the object from Javascript:

document.getElementById('v')
 => [object HTMLVideoElement @ 0x4d6ff68 (native @ 0x4cd2750)]

document.getElementById('v').src
 => "test.ogg"

You can't do much with it yet. The next article on this topic should cover how to have a visual representation.

Links