Implementing a new CSS property
There are a few steps necessary to implement a CSS property.
Initially, we have a list of properties and values inside CSSPropertyNames.in and CSSValueKeywords.in. When building WebCore, there is a compilation step which runs the preprocessor and creates enums with all the properties & values in CSSPropertyNames.h and CSSValueKeywords.h. You use these enum values to refer to the property and values in code.
Next up, we have to parse the syntax for the new property. This is the syntax for the property which is described in the CSS spec.
In WebKit, we parse CSS in two steps. The first step involves a Bison parser which doesn’t know anything about the particular syntax of each individual property. Instead, it simply understands the syntax of CSS itself (like what the {} characters mean, the difference between strings and idents, what an @-rule is, etc.). The language this Bison parser accepts is defined in CSSGrammar.y.in.
Bison parsers work by issuing callbacks every time they apply a rule in their grammar. We implement these callbacks in CSSParser.cpp. Whenever the Bison parser invokes the grammar rule which represents a new CSS value, the callback constructs a CSSParserValue (This is actually done inside the Bison grammar directly). When the parser invokes the grammar rule which represents a collection of values for a particular property, the callback constructs a CSSParserValueList. These data structures are retained by CSSParser.
Then, when Bison has parsed an entire CSS declaration, it calls CSSParser::parseValue(), which turns the CSSParser data structures into CSSValues, and returns a boolean which represents if the values have correct form. This function switches over CSSPropertyIDs (to which you added a new one in CSSPropertyNames.in) to perform the required inspection and conversion. This is where you implement the property syntax described in the CSS spec. At the end of this function, it calls CSSParser::addProperty() which records the new CSSValues along with their appropriate CSSPropertyID. Note that a CSSValue can contain a list of other CSSValues within it.
CSSParser::parseValue() is one of the many places in WebKit where we have a bifurcation between a simple path and a complex path. The previous paragraph describes the complex path. The simple path is used in cases where the CSS grammar is of the form “ident | ident | ident | ident | …” This is a common case, and in this case, there is no need to duplicate all the inspection and translation logic. In particular, CSS value idents all have an id (which you added to in CSSValueKeywords.in) and CSSParserValues have the same ID. Therefore, translation from one to the other can be mechanical. We have a discrimination function, isKeywordPropertyID(), which we use to opt properties into this simple path. Then, isValidKeywordPropertyAndValue() switches over these simple properties, and for each one, just compares the specified value to a list of valid values. It returns true or false depending on whether or not the value is valid.
Okay, so now we’ve got some CSSValues which represent our CSS declaration. Next, all the CSS declarations have to cascade and be applied to all the right elements. The goal here is to associate each element with a RenderStyle which holds all of the style information pertaining to the element. You can customize this cascade behavior in a couple ways, all of which are triggered by CSSPropertyNames.in.
RenderStyle is the place which will eventually hold all the style information for each element. Each CSS property is assumed to have a getter, setter, and initial value method in RenderStyle. As we perform the cascade, we will “build” the style by calling these methods of RenderStyle. There are three operations when building a style: Setting the initial value of the RenderStyle, setting the inherit value of the RenderStyle (to a value of its parent style), and setting a value of the RenderStyle given a CSSValue.
The behavior of this building phase can be customized by setting attributes on your property in CSSPropertyNames.in. This is the place where you can customize the name of the associated method names of RenderStyle. A more common thing to do, however, is to specify the “Custom” attribute which means that you will write one or more of those three operations listed in the previous paragraph. One of the compilation steps of WebCore is to create a StyleBuilder.cpp, which switches over property values and invokes those three operations. The attributes in CSSPropertyNames.in change the code which is generated in this file. In particular, if you specify a custom operation, StyleBuilder.cpp will call into StyleBuilderCustom, where you can implement your operation.
There are actually three ways to convert between a CSSValue and state inside a RenderStyle.
The first is to specify Custom=Value in CSSPropertyNames.in, where you implement a callback in StyleBuilderCustom which receives a CSSValue, and you can do whatever you want with it.
The second is used when you have a single setter in RenderStyle which encapsulates all the state pertaining to the property. In this case, all you need is a single mapping function which maps from the CSSValue to your particular data type. In this case, you specify Converter in CSSPropertyNames.in, and write your mapping function in StyleBuilderConverter. The StyleBuilder doesn’t have to know the data type of this state because the generated code looks like setFoo(convertFoo(value));
The last case is used when you have a single setter and a unique scalar data type in RenderStyle which represents this new state. In this case, you simply perform the mapping in a cast operator, usually defined in CSSPrimitiveValueMappings.h. This operator casts between CSSPrimitiveValue and your new type. In this case, you don’t say anything special in CSSPropertyNames.in, and the generated code looks like setFoo(downcast<CSSPrimitiveValue>(value));
New data types which hold your new RenderState state are usually declared in RenderStyleConstants.h.
Then, at layout or paint time, you consult with the RenderStyle to do whatever your property is supposed to do. Every element has a reference to its RenderStyle.
There are a couple other follow-up pieces you will also need to do:
One extra piece that you have to do is to allow for getComputedStyle() to work. There is a function, ComputedStyleExtractor::propertyValue() which inspects a RenderStyle and constructs a CSSValue for a given property. Because of this, your property must be round-trippable through RenderStyle. This function switches over the property to extract, and each one has a stanza where the RenderStyle is inspected and the resulting CSSValue is built up.
Another extra piece comes into play when style changes. We have machinery which partitions all our properties into properties which require layout, properties which require repainting, properties which require recompositing, etc. When a property changes, we inspect this partitioning to decide which operations we need to perform as a result of the style change. You can add this partitioning information about your new property in RenderStyle::diff().