Friday, October 28, 2011

How to implement an attribute in WebKit


In a previous article, I explained how to build a custom WebKit for MacOSX.
In this article, I will explain how to implement a custom attribute in WebKit.
Please keep in mind that these articles reflect the current state of my explorations in the WebKit code so do not hesitate to share your thoughts and/or correct me if I'm wrong somewhere.

The context


Imagine that for an obscure but good reason we want an attribute which set a custom attribute on a per frame / iframe tag basis.

This myattribute attribute is expected to be an url. An example use would be:

<iframe src="http://greatwebsite.com" myattribute="http://anothergreatwebsite.com" />

How can we achieve this?


WebKit is made of three major projects

  • WebKit: The UI project;
  • JavaScriptCore: Which contains the JavaScript engine code;
  • WebCore: The backend library of WebKit. DOM related code is located here. 

Basically, we want to add an attribute to the DOM so we will work on the WebCore project.

The very first thing to do is to keep in mind the following sketch:
http://www.webkit.org/coding/major-objects.html

It is also important to know that during its build process, WebKit generates a certain amount of source code files among which an HTMLNames.[h|cpp] which is where constants for our new attributes will be.

Let's write some code!

Register for the code generation

To implement our new attribute to the source code generation, we must add it in the html/HTMLAttributeNames.in file which is a big flat file of all available attributes. One per line.

At compile time, a constant myattributeAttr will be added to the HTMLNames class thus allowing you to use it in other places of your code.

Update the IDL

Then, have a look into the html directory. You will find one .idl file per HTML tag.

IDL files define what is exposed by WebCore to JavaScript and other bindings. For more informations, you can read http://trac.webkit.org/wiki/IdlAttributes or you can also have a look at the W3C specification here: http://www.w3.org/TR/WebIDL/.

So let's take the two .idl related to our tags HTMLFrameElement.idl and HTMLIFrameElement.idl and add the following line into then:
    attribute [Reflect, URL] DOMString myattribute;

Write the custom setter to implement the custom behaviour

Now it is time to write some code that will be executed when the myattribute attribute will be set.

Both HTMLFrameElement and HTMLIFrameElement classes have a common ancestor, HTMLFrameElementBase. This information is not in the .idl in which both inherits from HTMLElement but in the header files of both classes.

Since we want our attribute to be available to both classes, let's implement our setter in HTMLFrameElementBase!

To do it, add the following method declaration to HTMLFrameElementBase.h:
    void setMyattribute(const AtomicString&);

And in HTMLFrameElementBase.cpp, put the implementation of the method:

void HTMLFrameElementBase::setMyattribute(const AtomicString& str)
{
    // Create a URL out of the referrer parameter and set it as the referrer of the current frame.
    KURL url = str.isEmpty() ? KURL() : KURL(ParsedURLString, str.string());
    // Do your stuff..
}

What kind of stuff can we do here?
Well, almost anything you want!
Looking at the HTMLFrameElementBase class header ancestors, you can do several things:

Control the FrameLoader

You have access to the Frame by which you have access to the FrameLoader (as shown in the the sketch at: http://www.webkit.org/coding/major-objects.html).
So for example, you could do the following:
contentFrame()->loader()->setOutgoingReferrer(url);
in order to set a custom referrer.

Control the DOMWindow

You can also control the DOMWindow of the Frame or even the Document... You only have to look at what is available in these classes and use it!
So for example you could do the following:
contentWindow()->setUrl(url);

Control another element in the Document

You can also access to another element in the DOM and then modify it.
So for example you could do the following:

Element *element = contentDocument()-> getElementById(str);
element->setIdAttribute("newId");

All of this look good. But if you run WebKit at this point, your custom setter will never be called. Why?
Because we need to tell the parser that setting the myattribute attributes means calling our custom setter. This is done through the following method:
    virtual void parseMappedAttribute(Attribute*);

Add the following piece of code to parseMappedAttribute(Attribute*) in HTMLFrameElementBase.cpp:

if (attr->name() == myattributeAttr) {
  setReferrer(attr->value());                
}

This will cause your setter to be called when the attribute name will be the value of myattributeAttr.

Ok, you are ready to go! Compile your code, you can now load a webpage with your shiny new referrer attribute on an iframe tag!

Conclusion

Although WebKit might seems intimidating at first glance, in practice, it is mainly a great piece of software that is relatively easy to digg into… At least for such simple modifications.
I'll try to keep you aware as I am discovering new existing stuffs about WebKit in my next articles.
In the meantime, feel free to give me some feedback!