A large part of the current CSS Object Model sucks. More specifically, the CSSValue, CSSPrimitiveValue, CSSValueList, RGBColor, Rect and Counter interfaces are so poorly designed they're not implemented. I just tried to implement them for a project of mine and I must say the model is so weak and incoherent it cannot be implemented as is.

I have then tried to refine what's in the 2000-nov-13 spec of DOM Level 2 Style to reach something workable. I am NOT saying this has to be done or implemented. Call it a mental exercise I did just for fun, w/o caring about performance.

Let's first look at what's wrong:

Interface CSSValue

      interface CSSValue {
      
        // UnitTypes
        const unsigned short      CSS_INHERIT                    = 0;
        const unsigned short      CSS_PRIMITIVE_VALUE            = 1;
        const unsigned short      CSS_VALUE_LIST                 = 2;
        const unsigned short      CSS_CUSTOM                     = 3;
      
                 attribute DOMString        cssText;
                                              // raises(DOMException) on setting
      
        readonly attribute unsigned short   cssValueType;
      };

"inherit" is here considered as a special identifier despite of the fact a CSSPrimitiveValue can be a CSS_IDENT. There is no UnitType for "initial". A CSS_CUSTOM is, according to the spec, a "custom value"; but a custom value still has to be valid per CSS syntax so it should be representable with CSS_VALUE_LISTs and CSS_VALUEs.

Interface CSSValueList

interface CSSValueList : CSSValue {
  readonly attribute unsigned long    length;
  CSSValue           item(in unsigned long index);
};

All in all, this one is simple and should be quite ok. But one thing is missing: a property can accept a comma-separated list of whitespace-separated values. The current CSSValueList cannot express if the serialization of a CSSValueList should be whitespace- or comma-separated.

Interface CSSPrimitiveValue

interface CSSPrimitiveValue : CSSValue {

  // UnitTypes
  const unsigned short      CSS_UNKNOWN                    = 0;
  const unsigned short      CSS_NUMBER                     = 1;
  const unsigned short      CSS_PERCENTAGE                 = 2;
  const unsigned short      CSS_EMS                        = 3;
  const unsigned short      CSS_EXS                        = 4;
  const unsigned short      CSS_PX                         = 5;
  const unsigned short      CSS_CM                         = 6;
  const unsigned short      CSS_MM                         = 7;
  const unsigned short      CSS_IN                         = 8;
  const unsigned short      CSS_PT                         = 9;
  const unsigned short      CSS_PC                         = 10;
  const unsigned short      CSS_DEG                        = 11;
  const unsigned short      CSS_RAD                        = 12;
  const unsigned short      CSS_GRAD                       = 13;
  const unsigned short      CSS_MS                         = 14;
  const unsigned short      CSS_S                          = 15;
  const unsigned short      CSS_HZ                         = 16;
  const unsigned short      CSS_KHZ                        = 17;
  const unsigned short      CSS_DIMENSION                  = 18;
  const unsigned short      CSS_STRING                     = 19;
  const unsigned short      CSS_URI                        = 20;
  const unsigned short      CSS_IDENT                      = 21;
  const unsigned short      CSS_ATTR                       = 22;
  const unsigned short      CSS_COUNTER                    = 23;
  const unsigned short      CSS_RECT                       = 24;
  const unsigned short      CSS_RGBCOLOR                   = 25;

  readonly attribute unsigned short   primitiveType;
  void               setFloatValue(in unsigned short unitType, 
                                   in float floatValue)
                                        raises(DOMException);
  float              getFloatValue(in unsigned short unitType)
                                        raises(DOMException);
  void               setStringValue(in unsigned short stringType, 
                                    in DOMString stringValue)
                                        raises(DOMException);
  DOMString          getStringValue()
                                        raises(DOMException);
  Counter            getCounterValue()
                                        raises(DOMException);
  Rect               getRectValue()
                                        raises(DOMException);
  RGBColor           getRGBColorValue()
                                        raises(DOMException);
};

This is so completely crazy I don't know where to start...

  • CSS_UNKNOWN is supposed to represent a "value that is not a recognized CSS2 value". Then it should be thrown away by the parser as invalid and never reach the OM, right?
  • the list of units is long and not easily extensible
  • attr(), counter(), counters(), rect() and the more recent gradients or var() calls are all functions; adding a new setter and a new getter for each new type is overkill
  • attr() was extended by recent specs and can now take more than one argument. The above does not allow to individually modify those arguments.
  • "initial" and "inherit" are, as I already said above, covered by both CSSValue and CSS_IDENT here
  • let's suppose we have a CSSValue that is a CSSPrimitiveValue. Setting its cssText to "10px 10px" will then trigger an exception since a CSSPrimitiveValue cannot transmute magically into a CSSValueList...
  • I love how the spec prose says setStringValue() has "No Parameters"...

Interface Rect

interface Rect {
  readonly attribute CSSPrimitiveValue  top;
  readonly attribute CSSPrimitiveValue  right;
  readonly attribute CSSPrimitiveValue  bottom;
  readonly attribute CSSPrimitiveValue  left;
};

This looks and smells like a CSSValueList far too much.

Interface RGBColor

interface RGBColor {
  readonly attribute CSSPrimitiveValue  red;
  readonly attribute CSSPrimitiveValue  green;
  readonly attribute CSSPrimitiveValue  blue;
};

This cannot represent rgba(), hsl() and hsla() colors. We also have to use three CSSPrimitiveValue for the three color components because they can be a percentage or an integer...

Interface Counter

interface Counter {
  readonly attribute DOMString        identifier;
  readonly attribute DOMString        listStyle;
  readonly attribute DOMString        separator;
};

Again, something is missing here: nothing says if it's supposed to be a counter() or a counters() value. And no, the separator could not do the trick since it can be the empty string.

Requirements

To have a better OM for Values, i.e. an extensible OM that allows an application to deal with parsed values of all kinds, we need to change of perspective. First, the list of reserved idents, the list of units and the list of functions are not extensible. Secondly, we have cast issues between PrimitiveValues and ValueLists and we need a single interface. We can deal with all the issues with a single CSSValue interface:

New interface CSSValue

interface CSSValue {

  // ValueTypes
  const unsigned short      CSS_SYMBOL                     = 0;
  const unsigned short      CSS_NUMBER                     = 1;
  const unsigned short      CSS_UNIT                       = 2;
  const unsigned short      CSS_STRING                     = 3;
  const unsigned short      CSS_URI                        = 4;
  const unsigned short      CSS_IDENT                      = 5;
const unsigned short CSS_VALUE_LIST = 6; readonly attribute unsigned short type; attribute boolean commaSeparated;
readonly attribute unsigned long length;
CSSValue item(in unsigned long index);
raises(DOMException);

void setFloatValue(in float floatValue) raises(DOMException); float getFloatValue() raises(DOMException);
void setStringValue(in DOMString stringValue) raises(DOMException); DOMString getStringValue() raises(DOMException); };

Definition group ValueTypes

An integer indicating the type of the Value

CSS_SYMBOL
The value is a single character than cannot be interpreted otherwise. For instance the / character in the font shorthand property. The value can be obtained by the getStringValue() and set by the setStringValue() method.
CSS_NUMBER
The value is a simple number. The value can be obtained by using the getFloatValue() method and set through by setFloatValue() method.
CSS_UNIT
The value is a number followed by a unit. The number part of the value can be obtained by using the getFloatValue() method and set through by setFloatValue() method. The unit part of the value can be obtained by using the getUnit() method and set through by setUnit() method
CSS_STRING
The value is a string. The value can be obtained by the getStringValue() and set by the setStringValue() method.
CSS_URI
The value is a URI. The parameter of the url() function can be obtained by the getStringValue() and set by the setStringValue() method.
CSS_IDENT
The value is a CSS identifier. The value can be obtained by the getStringValue() and set by the setStringValue() method.
CSS_VALUE_LIST
The value is a list of values or a function. It is a function if the getStringValue() method does not reply the empty string. The list of values is whitespace-separated if the commaSeparated attribute is false and comma-separated otherwise.

Attributes

type of type unsigned short, readonly
The type of the value as defined by the constants specified above.
commaSeparated of type boolean
The separation type of the list of values. Meaningful only if the type attribute is CSS_VALUE_LIST. The list is whitespace-separated if the attribute is false and comma-separated otherwise.
length of type unsigned long, readonly
The number of CSSValue in the list. The range of valid values of the indices is 0 to length-1 inclusive.
Exceptions
INVALID_ACCESS_ERR: Raised if the CSS value is a not a CSS_VALUE_LIST.

Methods

getFloatValue
Retrieves the value of a CSS_NUMBER or the number part of the value of a CSS_UNIT. If this CSS value is not a CSS_NUMBER or a CSS_UNIT, a DOMException is raised.
Return Value
float The float value of this CSS_NUMBER or CSS_UNIT
Exceptions
INVALID_ACCESS_ERR: Raised if the CSS value isn't a CSS_NUMBER nor a CSS_UNIT.
getStringValue
For a CSS_SYMBOL, retrieves the single character used as a symbol.
For a CSS_STRING, retrieves the string. Enclosing quotes or double-quotes are NOT included.
For a CSS_UNIT, retrieves the unit of the value.
For a CSS_URI, retrieves the argument of the url(...) notation. Enclosing quotes or double-quotes are NOT includedt.
For a CSS_IDENT, retrieves the identifier.
For a CSS_VALUE_LIST and if that list of values is passed as the parameters of a function, retrieves the function name. Retrieves the empty string otherwise.
For a CSS_NUMBER and CSS_UNIT, a DOMException is raised.
No Parameters
Return Value
DOMString The float value of this CSS_NUMBER or CSS_UNIT
Exceptions
INVALID_ACCESS_ERR: Raised if the CSS value is a CSS_NUMBER or a CSS_UNIT.
item
For a CSS_VALUE_LIST, Used to retrieve a CSSValue by ordinal index. The order in this collection represents the order of thevalues in the CSS style property. If index is greater than or equal to the number of values in the list, this returnsnull.
For all other value types, a DOMException is raised.
Parameter
index of type unsigned long: index into the collection.
Return value
CSSValue The CSSValue at the index position in the CSSValueList, or null if that is not a valid index.
Exceptions
INVALID_ACCESS_ERR: Raised if the CSS value is a not a CSS_VALUE_LIST.
setFloatValue
Sets the value of a CSS_NUMBER or the number part of the value of a CSS_UNIT. If this CSS value is not a CSS_NUMBER or a CSS_UNIT, a DOMException is raised.
Parameter
floatValue of type float;
No Return Value
Exceptions
INVALID_ACCESS_ERR: Raised if the CSS value isn't a CSS_NUMBER nor a CSS_UNIT or if the attached property doesn't support the float value or the unit type.
NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.
setStringValue
For a CSS_SYMBOL, sets the single character used as a symbol.
For a CSS_STRING, sets the string.
For a CSS_UNIT, sets the unit of the value.
For a CSS_URI, sets the argument of the url(...) notation.
For a CSS_IDENT, sets the identifier.
For a CSS_VALUE_LIST and if the parameter is not the empty string, make the list of values become a function and sets the function name. Make the list become a plain list of values if the parameter is the empty string.
For a CSS_NUMBER and CSS_UNIT, a DOMException is raised.
Parameter
stringValue of type DOMString
No Return Value
Exceptions
INVALID_ACCESS_ERR: Raised if the CSS value is a CSS_NUMBER or a CSS_UNIT, if the type of the value is CSS_SYMBOL and the string can be parsed as an other type of value, if the type of the value is CSS_UNIT and the string is not a valid CSS unit, if the type of the value is CSS_URI and the string is not a valid URI, if the type of the value is CSS_IDENT and the string is not a valid CSS identifier, if the type of the value is CSS_VALUE_LIST and the string is not a valid CSS identifier or the empty string.
NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly.

Conclusion

The above should be enough to describe any CSS value, specified or computed. The model will become a bit complex for complex values but it ensures any web application can have access to parsed values, deal with their types and modify them. Let's take an example:

background-image: linear-gradient(to bottom, yellow 0%, blue 100%), url(foo.png);

This will result in the following OM (click on the image to enlarge it):

OM example

Again, I'm not saying the above is the thing to do or implement. It can certainly be improved, for instance for colors. A totally different perspective is also perfectly possible. I am only saying that making a better CSS OM allowing a full representation of parsed values in stylesheets and computed values is feasible. I hope the CSS OM will offer such power in the future.

UPDATE: the new CSSValue interface above lacks one thing, the ubiquitous cssText for parsing and serialization. Sorry for that.