CSS Typed OM Level 1

Editor’s Draft,

More details about this document
This version:
https://drafts.css-houdini.org/css-typed-om-1/
Latest published version:
https://www.w3.org/TR/css-typed-om-1/
Previous Versions:
Feedback:
public-houdini@w3.org with subject line “[css-typed-om] … message topic …” (archives)
GitHub
Inline In Spec
Editors:
Tab Atkins-Bittner (Google)
François Remy (Microsoft)
Former Editors:
(Google)

Abstract

Converting CSSOM value strings into meaningfully typed JavaScript representations and back can incur a significant performance overhead. This specification exposes CSS values as typed JavaScript objects, to make manipulating them both easier and more performant.

Status of this document

This is a public copy of the editors’ draft. It is provided for discussion only and may change at any moment. Its publication here does not imply endorsement of its contents by W3C. Don’t cite this document other than as work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-typed-om” in the title, like this: “[css-typed-om] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 03 November 2023 W3C Process Document.

1. Introduction

CSS stylesheets are parsed into abstract UA-internal data structures, the internal representations of CSS, which various specification algorithms manipulate.

Internal representations can’t be directly manipulated, as they are implementation-dependent; UAs have to agree on how to interpret the internal representations, but the representations themselves are purposely left undefined so that UAs can store and manipulate CSS in whatever way is most efficient for them.

Previously, the only way to read or write to the internal representations was via strings—​stylesheets or the CSSOM allowed authors to send strings to the UA, which were parsed into internal representations, and the CSSOM allowed authors to request that the UA serialize their internal representations back into strings.

This specification introduces a new way to interact with internal representations, by representing them with specialized JS objects that can be manipulated and understood more easily and more reliably than string parsing/concatenation. This new approach is both easier for authors (for example, numeric values are reflected with actual JS numbers, and have unit-aware mathematical operations defined for them) and in many cases are more performant, as values can be directly manipulated and then cheaply translated back into internal representations without having to build and then parse strings of CSS.

2. CSSStyleValue objects

[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface CSSStyleValue {
    stringifier;
    [Exposed=Window] static CSSStyleValue parse(USVString property, USVString cssText);
    [Exposed=Window] static sequence<CSSStyleValue> parseAll(USVString property, USVString cssText);
};

CSSStyleValue objects are the base class of all CSS values accessible via the Typed OM API.

The stringification behavior of CSSStyleValue objects is defined in § 6 CSSStyleValue Serialization.

The parse(property, cssText) method, when invoked, must parse a CSSStyleValue with property property, cssText cssText, and parseMultiple set to false, and return the result.

The parseAll(property, cssText) method, when invoked, must parse a CSSStyleValue with property property, cssText cssText, and parseMultiple set to true, and return the result.

To parse a CSSStyleValue given a string property, a string cssText, and a parseMultiple flag, run these steps:
  1. If property is not a custom property name string, set property to property ASCII lowercased.

  2. If property is not a valid CSS property, throw a TypeError.

  3. Attempt to parse cssText according to property’s grammar. If this fails, throw a TypeError. Otherwise, let whole value be the parsed result.

    The behavior of custom properties are different when modified via JavaScript than when defined in style sheets.

    When a custom property is defined with an invalid syntax in a style sheet, then the value is recorded as "unset", to avoid having to reparse every style sheet when a custom property is registered.

    Conversely, when a custom property is modified via the JavaScript API, any parse errors are propagated to the progamming environment via a TypeError. This allows more immediate feedback of errors to developers.

  4. Subdivide into iterations whole value, according to property, and let values be the result.

  5. For each value in values, replace it with the result of reifying value for property.

    Define the global.

  6. If parseMultiple is false, return values[0]. Otherwise, return values.

To subdivide into iterations a CSS value whole value for a property property, execute the following steps:
  1. If property is a single-valued property, return a list containing whole value.

  2. Otherwise, divide whole value into individual iterations, as appropriate for property, and return a list containing the iterations in order.

How to divide a list-valued property into iterations is intentionally undefined and hand-wavey at the moment. Generally, you just split it on top-level commas (corresponding to a top-level <foo># term in the grammar), but some legacy properties (such as counter-reset) don’t separate their iterations with commas.

It’s expected to be rigorously defined in the future, but at the moment is explicitly a "you know what we mean" thing.

2.1. Direct CSSStyleValue Objects

Values that can’t yet be directly supported by a more specialized CSSStyleValue subclass are instead represented as CSSStyleValue objects.

Each CSSStyleValue object is associated with a particular CSS property, via its [[associatedProperty]] internal slot, and a particular, immutable, internal representation. These objects are said to "represent" the particular internal representation they were reified from, such that if they are set back into a stylesheet for the same property, they reproduce an equivalent internal representation.

These CSSStyleValue objects are only considered valid for the property that they were parsed for. This is enforced by CSSStyleValue objects having a [[associatedProperty]] internal slot, which is either null (the default) or a string specifying a property name.

Note: This slot is checked by StylePropertyMap.set()/append()

3. The StylePropertyMap

[Exposed=(Window, Worker, PaintWorklet, LayoutWorklet)]
interface StylePropertyMapReadOnly {
    iterable<USVString, sequence<CSSStyleValue>>;
    (undefined or CSSStyleValue) get(USVString property);
    sequence<CSSStyleValue> getAll(USVString property);
    boolean has(USVString property);
    readonly attribute unsigned long size;
};

[Exposed=Window]
interface StylePropertyMap : StylePropertyMapReadOnly {
    undefined set(USVString property, (CSSStyleValue or USVString)... values);
    undefined append(USVString property, (CSSStyleValue or USVString)... values);
    undefined delete(USVString property);
    undefined clear();
};