1. Motivation
Most web components uses Shadow DOM. For a style sheet to take effect within the Shadow DOM, it currently must be specified using a style element within each shadow root. As a web page may contain tens of thousands of web components, this can easily have a large time and memory cost if user agents force the style sheet rules to be parsed and stored once for every style element. However, the duplications are actually not needed as the web components will most likely use the same styling, perhaps one for each component library.
Some user agents might attempt to optimize by sharing internal style sheet representations across different instances of the style element. However, component libraries may use JavaScript to modify the style sheet rules, which will thwart style sheet sharing and have large costs in performance and memory.
2. Proposed Solution
We are proposing to provide an API for creating stylesheet objects from script, without needing style elements, and also a way to reuse them in multiple places. Script can optionally add, remove, or replace rules from a stylesheet object. Each stylesheet object can be added directly to any number of shadow roots (and/or the top level document).
const myElementSheet= new CSSStyleSheet(); class MyElementextends HTMLElement{ constructor() { super (); const shadowRoot= this . attachShadow({ mode: "open" }); shadowRoot. adoptedStyleSheets= [ myElementSheet]; } connectedCallback() { // Only actually parse the stylesheet when the first instance is connected. if ( myElementSheet. cssRules. length== 0 ) { myElementSheet. replaceSync( styleText); } } }
3. Constructing Stylesheets
partial interface CSSStyleSheet {constructor (optional CSSStyleSheetInit = {});options Promise <CSSStyleSheet >replace (USVString );text void replaceSync (USVString ); };text dictionary { (CSSStyleSheetInit MediaList or DOMString )= "";media DOMString = "";title boolean =alternate false ;boolean =disabled false ; };
-
CSSStyleSheet( options ) -
When
called,
execute
these
steps:
-
Construct a new
CSSStyleSheetobject sheet with the following properties:-
location set to the base URL of the associated Document for the current global object
-
No owner node .
-
No owner CSS rule .
-
Set alternate flag if the
alternateattribute of options is true, otherwise unset the alternate flag . -
Unset origin-clean flag .
-
Set constructed flag .
-
Constructor document set to the associated Document for the current global object .
-
-
If the
mediaattribute of options is a string, create a MediaList object from the string and assign it as sheet ’s media . Otherwise, serialize a media query list from the attribute and then create a MediaList object from the resulting string and set it as sheet ’s media . -
If the
disabledattribute of options is true, set sheet ’s disabled flag . -
Return sheet .
-
CSSStyleSheet instances have the following associated states:
- constructed flag
- Specified when created. Either set or unset. Unset by default. Signifies whether this stylesheet was created by invoking the IDL-defined constructor.
- disallow modification flag
- Either set or unset. Unset by default. If set, modification of the stylesheet’s rules is not allowed.
- constructor document
-
Specified
when
created.
The
Documenta constructed stylesheet is associated with. Null by default. Only non-null for stylesheets that have constructed flag set.
4. Modifying Constructed Stylesheets
After
construction,
constructed
stylesheets
can
be
modified
using
rule
modification
methods
like
insertRule()
or
deleteRule()
,
or
replace(text)
and
replaceSync(text)
.
The
behavior
of
those
methods
are
affected
by
the
disallow
modification
flag
and
constructed
flag
as
described
below.
Note that we’re explicitly monkeypatching CSSOM. We are working on merging this spec into CSSOM soon.
-
insertRule( rule , index ) -
-
Let sheet be this
CSSStyleSheetobject. -
If sheet ’s disallow modification flag is set, throw a "
NotAllowedError"DOMException. -
Parse a rule from rule . If the result is an @import rule and sheet ’s constructed flag is set, or sheet ’s parent CSS style sheet 's constructed flag is set, or that sheet’s parent CSS style sheet 's constructed flag is set (and so on), throw a "
NotAllowedError"DOMException. -
(The rest of the algorithm remains as in CSSOM)
-
-
deleteRule( index ) -
-
Let sheet be this
CSSStyleSheetobject. -
If sheet ’s disallow modification flag is set, throw a "
NotAllowedError"DOMException. -
(The rest of the algorithm remains as in CSSOM)
-
-
replace( text ) -
-
Let sheet be this
CSSStyleSheetobject. -
Let promise be a promise.
-
If sheet ’s constructed flag is not set, or sheet ’s disallow modification flag is set, reject promise with a "
NotAllowedError"DOMExceptionand return promise . -
Set sheet ’s disallow modification flag .
-
In parallel , do these steps:
-
Let rules be the result of running parse a list of rules from text . If rules is not a list of rules (i.e. an error occurred during parsing), set rules to an empty list.
-
Wait for loading of @import rules in rules and any nested @import s from those rules (and so on).
-
If any of them failed to load, terminate fetching of the remaining @import rules, and queue a task on the networking task source to perform the following steps:
-
Unset sheet ’s disallow modification flag .
-
Reject promise with a "
NetworkError"DOMException.
-
-
Otherwise, once all of them have finished loading, queue a task on the networking task source to perform the following steps:
-
Set sheet ’s CSS rules to rules .
-
Unset sheet ’s disallow modification flag .
-
Resolve promise with sheet .
-
Note: Loading of @import rules should follow the rules used for fetching style sheets for @import rules of stylesheets from <link> elements, in regard to what counts as success, CSP, and Content-Type header checking.
Note: We will use the fetch group of sheet ’s constructor document 's relevant settings object for @import rules and other (fonts, etc) loads.
Note: The rules regarding loading mentioned above are currently not specified rigorously anywhere.
-
-
-
Return promise .
-
-
replaceSync( text ) -
When
called,
execute
these
steps:
-
Let sheet be this
CSSStyleSheetobject. -
If sheet ’s constructed flag is not set, or sheet ’s disallow modification flag is set, throw a "
NotAllowedError"DOMException. -
Let rules be the result of running parse a list of rules from text . If rules is not a list of rules (i.e. an error occurred during parsing), set rules to an empty list.
-
If rules contains one or more @import rules, throw a "
NotAllowedError"DOMException. -
Set sheet ’s CSS rules to rules .
-
5. Using Constructed Stylesheets
partial interface DocumentOrShadowRoot {
attribute ObservableArray<CSSStyleSheet> adoptedStyleSheets;
};
The
set
an
indexed
value
algorithm
for
adoptedStyleSheets
,
of
type
FrozenArray<
CSSStyleSheet
>
On
getting,
adoptedStyleSheets
returns
this
DocumentOrShadowRoot
's
adopted
stylesheets
.
On
setting,
adoptedStyleSheets
performs
the
following
steps:
Let
given
adopted
value
be
the
result
of
converting
and
index
,
is
the
given
value
to
a
FrozenArray<CSSStyleSheet>
following:
-
If
any entry ofadoptedvaluehas its’s constructed flag is not set, or its constructor document is not equal to thisDocumentOrShadowRoot's node document , throw a "NotAllowedError"DOMException. -
SetInclude value in thisDocumentOrShadowRoot'sadopted stylesheetsdocument or shadow root CSS style sheets , ordered after all style sheets derived fromstyleSheets, and positioned relative to other constructed style sheets according toadoptedindex .
Every
The
delete
an
indexed
value
algorithm
for
,
given
value
and
index
,
is
the
following:
DocumentOrShadowRoot
adoptedStyleSheets
has
adopted
stylesheets
.
-
The user agent must include all style sheets in theRemove value from thisDocumentOrShadowRoot'sadopted stylesheets inside itsdocument or shadow root CSS style sheets. These adopted stylesheetsare ordered after allat theother style sheets (i.e. those derived from styleSheetsappropriate location, according to index .).value may appear more than once, which is why index is used to distinguish.
This
specification
defines
the
following
adopting
steps
for
ShadowRoot
nodes,
given
node
and
oldDocument
:
-
SetClear node ’sadoptedStyleSheets.to the empty list.