Adding a new CSS property

This page describes how to add a new CSS property to the style system. The style system is the part of the code in Gecko that is responsible for producing a computed value for every property for every element. See the Gecko Overview for more information about the style system.

This document assumes that you have a specifcation for the property available. Issues about how to write such a specification (including things such as whether it is appropriate to use prefixes or when properties should be inherited by default) are not covered here.

If you need more details on any of the points mentioned here, a good place to find them is by looking at other changes in the version control history of the files mentioned.

Getting started

First, you'll want to add an appropriate test entry to property_database.js. See the documentation at the top of the file and the other entries for examples. This will cause tests of your new property to be added to many of the mochitests in layout/style/test, which can be run with the command "./mach mochitest -f plain layout/style/".

Then, unless you're implementing a shorthand property, you need to decide which style struct the computed value of the property should go in. (Again, see the Gecko Overview for more information.) It should generally be grouped with related properties. Remember that each style struct contains only properties that inherit by default or only properties that do not. (Which set the property is in is given in the specification, which says "Inherited: yes" or "Inherited: no" in the property's definition.) Also note that some of the style structs intentionally contain only properties set/reset by a particular common shorthand property; this improves the effectiveness of some of the performance and memory optimizations done with the rule tree, and thus we should avoid adding a property not reset by that shorthand to such a struct.

Once you know which struct you're going to use (or that the property is a shorthand), you can add appropriate entry to nsCSSPropList.h. Again, see the documentation at the top of the file and the other entries for examples, and also see the next section for details about parsing.

Parsing

To start implementing the parsing, first read the Syntax line in the property's specification (whose syntax is in turn defined in the CSS Values and Units specification) and any prose in the specification that adds additional restrictions to that syntax.

Every property in nsCSSPropList.h must have in its flags field exactly one of the four CSS_PROPERTY_PARSE_* flags defined in nsCSSProps.h. Given the syntax for the property, you can determine which one to use. If the property's values can be expressed as a single value, or as a space-separated or comma-separated list of values that can be described by the VARIANT_* flags (also in nsCSSProps.h), you should use CSS_PROPERTY_PARSE_VALUE or CSS_PROPERTY_PARSE_VALUE_LIST, and set the appropriate VARIANT_* flags in the entry in nsCSSPropList.h (and maybe also CSS_PROPERTY_VALUE_LIST_USES_COMMAS). Otherwise, set the variant entry in the nsCSSPropList entry to 0. If you need custom parsing code, this can be inserted at one of two different places: you can override the entire property value parsing by using CSS_PROPERTY_PARSE_FUNCTION, which is generally the right thing to do for shorthand properties. Or, preferably, you can still use CSS_PROPERTY_PARSE_VALUE or ..._VALUE_LIST and just override the value parsing by setting CSS_PROPERTY_VALUE_PARSER_FUNCTION. You must take the latter approach for properties that are part of shorthands if you want the shorthand parser to be able to reuse the longhand parser. (Note that when the longhand property CSS_PROPERTY_PARSE_VALUE_LIST, the shorthand property parser would be assumed to be reusing the longhand parser once per item, not for a whole list, as for properties like background-image or transform-timing-function.)

If the property takes a list of keywords other than inherit/initial/etc., auto, none, or normal (which can be expressed using the VARIANT_* flags), you should use VARIANT_KEYWORD and add a keyword table to the nsCSSProps class. The name of this keyword table needs to go in the nsCSSPropList entry; otherwise that keyword table should be null. If you need a keyword table, you should include auto, normal, or none (if needed) in that table rather than combining VARIANT_KEYWORD with VARIANT_AUTO, VARIANT_NORMAL, or VARIANT_NONE.

If you add a shorthand property, you'll need to use the CSS_PROP_SHORTHAND macro. You'll need to use CSS_PROPERTY_PARSE_FUNCTION as described above; you can't use the other options for shorthands. And for shorthands you also need to include a subproperty table in nsCSSProps, whose name must match the "method" argument in the CSS_PROP_SHORTHAND macro. When writing the custom parsing code for a shorthand, you'll need to ensure that any successful parse sets all of the subproperties of the shorthand (since that's the way shorthands work in CSS).

Some common mistakes to watch out for when writing custom parsing code (which might go away if we redesign the parser along the lines described in css3-syntax):

  • make sure to call SkipUntil() to look for the matching close parentheses, braces, or brackets whenever you hit an error inside of them.
  • make sure to call UngetToken() if you have called GetToken() but the result is unexpected. This is particularly important in case the unexpected token is a parenthesis or something else that requires matching the other half of a pair.
  • if you're adding a preference-controlled property to a non-preference-controlled shorthand, you need to call AppendValue for that property if and only if its preference is enabled (and see next point)
  • if you need to check a preference in custom parsing code (e.g., because you're adding a new property to a shorthand, but only conditionally on that property's preference), call nsCSSProps::IsEnabled(), which is faster than calling into the preferences code

For further understanding of how the parsing code works, you should read and understand the code in nsCSSParser.cpp.

Computed Style

When adding a new longhand property, you also need to implement the computed style side of things. (None of this applies to shorthand properties.)

Start by reading and understanding the "Computed Value:" line in the specification's definition of the property, and any associated prose. This line describes the conceptual representation (but not syntax) of the computed value of the property. The data structures in Gecko that store the computed value must correspond to this concept, or inheritance won't work as described by the specification.

So first, you need to add a member variable (or variables) to the chosen style struct in nsStyleStruct.h and fix the default constructor and the copy constructor of that struct in nsStyleStruct.cpp. If this variable needs to be a tagged union, use nsStyleCoord. But don't use nsStyleCoord if you only need a single type that nsStyleCoord provides. The default constructor must initialize the variable to match the initial value of the property, as described in the "Initial Value:" line in the specification's definition of the property.

You then need to add code to nsRuleNode::Compute*Data (where the * is for the style struct) to transform a specified value of the property into a computed value of the property. This needs to match the specification's definition of the "Computed value" (and, for the 'initial' value, it's definition of "Initial Value"). For examples of how to write this computation code, see other examples in nsRuleNode.cpp. In any case where the computation you do might vary between elements that match the same list of style rules, you need to set the canStoreInRuleTree variable to false. A common example of this is 'em' units, which depend on the font size. Note that use of SetCoord (if the resulting value needs to be a tagged union) or SetDiscrete is preferred when it's possible to use those functions. Also, it's very important not to touch the computed style data at all when there's no specified data provided (eCSSUnit_Null); touching the computed style data in this case would break partial computation based on a start struct, which is when we computed style data on the basis of computed data we've already computed from a subset of the style rules that we're currently computing from.

Then you need to change nsStyle*::CalcDifference (in nsStyleStruct.cpp) to describe how dynamic changes of your new CSS property need to be handled. See nsChangeHint.h for documentation (or if that's not sufficient, see RestyleManager::ProcessRestyledFrames).

Then you need to change nsComputedDOMStyle.cpp to implement the getComputedStyle API. You'll need to add an entry in nsComputedDOMStylePropertyList.h and then add a method (matching the method name in the CSS property list) to implement the API. While we implement the string API in terms of the value API, we generally don't care much about the deprecated primitive value API, so in cases where there's some new structure, you should generally create the simplest structure of primitive values that will produce the right string. (We should also make this part of adding properties much more automated and make the code smaller.)

Animation

If the existing animation behaviors in nsStyleAnimation aren't sufficient for how this new property is interpolated (as its specification should describe), then you'll need to implement a new behavior type in nsStyleAnimation and reference it from nsCSSPropList.h. This should probably be in a separate patch from the basic addition of the property.

Finishing up

Then you'll need to rebuild and run the mochitests in layout/style (./mach mochitest -f plain layout/style/). If you've done everything right, they should pass; if they don't, you need to fix something.

Then you should get review on the work you've done so far and move on to implementing whatever the property does (which should be in a separate patch or patches).