Changes between Initial Version and Version 1 of ImplementingCSSProperty


Ignore:
Timestamp:
Aug 27, 2015 6:51:52 PM (9 years ago)
Author:
mmaxfield@apple.com
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • ImplementingCSSProperty

    v1 v1  
     1= Implementing a new CSS property =
     2
     3There are a few steps necessary to implement a CSS property.
     4
     5Initially, 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 two 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.
     6
     7Next 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.
     8
     9In WebKit, we parse CSS this in two pieces. 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.
     10
     11Bison 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.
     12
     13Then, 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.
     14
     15CSSParser::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.
     16
     17Okay, 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.
     18
     19RenderStyle 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.
     20
     21The 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.
     22
     23There are actually three ways to convert between a CSSValue and state inside a RenderStyle.
     24
     25The 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.
     26
     27The 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));
     28
     29The 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));
     30
     31New data types which hold your new RenderState state are usually declared in RenderStyleConstants.h.
     32
     33Then, 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.
     34
     35One last 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.