wicg / construct-stylesheets Goto Github PK
View Code? Open in Web Editor NEWAPI for constructing CSS stylesheet objects
License: Other
API for constructing CSS stylesheet objects
License: Other
The section on Using Constructed Stylesheets currently begins with the paragraph:
A CSSStyleSheet can only be applied to the Document it is constructed on (e.g. the document on which the factory function is called on), and any ShadowRoot in the Document it is constructed on, by assigning an array containing the sheet to their adoptedStyleSheets. So, a stylesheet can be used in multiple DocumentOrShadowRoots within the document it is constructed on.
This paragraph appears to be a sequence of statements of facts (see Hixie's post). In other words, it doesn't have any normative requirements. But it also doesn't cite any references for those statements of facts, which makes me suspect that it might be intending to impose normative requirements.
I think this should be reworded to either (a) more clearly impose the normative requirements that it intends or (b) cite sources (i.e., other specifications) for its statements of facts.
The link goes to the HTML spec, which refers to mimesniff, which does not define the concept.
I've been trying to find where the behavior of normal @import
and stylesheet loading is defined to reject cross-site non-text/css, but haven't found it yet. https://drafts.csswg.org/cssom/#fetch-a-css-style-sheet is a thing, but also uses "Content-Type metadata".
Most simply, what should happen for @import
from a constructed stylesheet if the server does not send a Content-Type
header?
This probably doesn't need to block this spec progressing, because of the mess the rest of stylesheet loading is, but it might be good to explicitly say the behavior needs to match https://drafts.csswg.org/cssom/#fetch-a-css-style-sheet and whatever HTML uses to load sheets, assuming this last is defined.
This is spun off from #25 (comment).
Should this be considered as style-inline for the purpose of CSP style-src directives? If not, what CSP style-src should we use/add?
This needs to be clarifed for construction from string, and @import
-ed style from it (once #25 resolves to allow it).
In cases where this API is used by custom elements, different subclasses may want to add a different stylesheet to adoptedStyleSheets
. In that case, it's better to have explicit add
and remove
methods rather than to have author scripts manipulate FrozenArray
.
If the sheet has @import rules, how can the API consumer tell when the sheet is done loading all the imports and is ready to be used?
At the moment the internal slots are described in the same style as the JavaScript constructor.
The spec currently has an empty section on applying styles in all contexts. This section should either have contents or it should be removed.
The algorithm for replace(text)
appears to leave the disallow modification flag set forever in the first part of step 7, where an @import
fails to load. It seems like that branch should also clear the disallow modification flag.
(I got here from w3ctag/design-reviews#308.)
Should adding the same constructed stylesheet in two different Documents be allowed? If it is, can it be used as a security attack vector?
Related to #57.
At TPAC we discussed what exactly ties stylesheets to documents, if anything. Our conclusion was that it's only the base URL, which is set at construction time. So in this sense document.createCSSStyleSheetSync()
is equivalent to a hypothetical window.createCSSStyleSheetSync({ baseURL: document.baseURI })
or new CSSStyleSheet({ baseURL: document.baseURI })
.
Given this, I suggest we just infer the base URL at construction time, in the same way many other web platform APIs do. Namely we can use the current settings object's API base URL.
The spec currently aims to prevent a constructed stylesheet being used across multiple documents. We should figure out what happens when a shadow host for a ShadowRoot
with constructed stylesheets attached is adopted into a different document.
From @calebdwilliams #25 (comment)
A follow up question is what happens when an @import fails? Does the rest of the stylesheet still parse? Say, your imported sheet is for CSS custom properties as I've outlined in #24 and the importer has fallbacks set up? For the record, this is my primary interest in @import for constructible stylesheets (composing constructed stylesheets).
https://github.com/whatwg/html/issues?q=is%3Aopen+is%3Aissue+label%3A%22topic%3A+style%22 has a number of issues, some of which are significant. It'd be good to clear that technical debt before adding to it.
At least, it seems to me that otherwise they end up being a little different from bytes coming in over the network in that they'd also support lone surrogates, which doesn't seem ideal.
I think the intent is yes, but it's not 100% clear.
TreeScope is a term used in Blink implementation. In other specs DocumentOrShadowRoot
interface is used to specify shared properties between Document and ShadowRoot.
This applies to @import, backgrounds, fonts, etc.
This is a bit more complicated than the <link>
case, because in that case there is only one plausible fetch group: the one for the document. With constructible sheets, there are at least two fetch groups involved: the one for the window the constructor came from (esp. for sheets not attached to anything) and the one for wherever the sheet is actually being used.
I guess a sheet can in fact be used in multiple places, so there could be any number of fetch groups involved....
As a concrete question, if I create a sheet from documen A's window, and am using it in documents B and C, and B matches a rule with one background image while C matches a rule with another, which load events, if any, are blocked by the resulting image loads.
Once it's easier to construct (or import, see WICG/webcomponents#759), they're more likely to be passed around to disparate parts of an application, which might try to independently mutate the stylesheet (say, to implement scoping via rewriting selectors).
Making it easy to clone a StyleSheet could help in code that wants to share/reuse StyleSheets safely. Clones could possibly be cheap if they allow for copy-on-write optimizations.
(instead of #3,)
Do we want to include manually-added sheets in document.styleSheets
, similar to how document.fonts
mixes OM-created and manually-created fonts?
Big difference is that ordering matters here, which makes dealing with the invariants much more annoying. (What happens if you manually add a sheet between two <link>
sheets, then insert another <link>
in the document between them? Does it go before or after your manually-added one? Or do we just make it illegal to manually add a sheet before an automatic sheet?)
For same-origin or CORS-enabled things one can fetch the data, then construct from data. What about non-same-origin non-CORS things?
I don't understand why we need a separate moreStyleSheets.
Ryosuke suggested adding a constructor that just takes a URL, so you can construct a stylesheet as easily as adding a link
, like document.loadCSSStyleSheet(...)
.
I thought that Fetch has a lot of options beyond just the URL, and I didn't want to duplicate those in the CSSStyleSheet() constructor (and keep it consistent as Fetch grows and changes). You can just do fetch(...).then(r=>document.CSSStyleSheet(r.text))
pretty easily.
Domenic pointed to WASM as an example where they have a constructor directly take a Response
object, so you can build something from a fetch()
response even more easily: document.CSSStyleSheet(fetch(...))
.
This sounds good to me.
We should add some way to compose stylesheets to support reuse over repetition. This is in relation to @annevk's comment on Web components issue 468. Consider adding an optional configuration argument to the CSSStyleSheet
constructor that would essentially @import
existing stylesheets into a the current object:
const sheet1 = new CSSStyleSheet(`
:root {
--primary: #004977;
--secondary: #011728;
--alert: #d03027;
}`);
const sheet2 = new CSSStyleSheet(`
h1 {
background: var(--secondary);
color: var(--primary);
}`, { import: [sheet1] });
const sheet3 = new CSSStylesheet(`
.error {
color: var(--alert);
}`, { import: [sheet1] });
This would solve for my concern in the aforementioned issue about only allowing one stylesheet to be attached to a single custom element in the registry, would not interfere with current CSS behavior and would require only minimal modifications to the current proposal.
I think the only question I would have is how does the config import play with any @import
in the sheet body (I would assume it is inserted before). Even if this syntax doesn't work, there needs to have a way to custom import constructed stylesheets into other constructed stylesheets to make the solution for 468 viable.
Copied from inline ISSUE 5 in the section "Applying Styles In All Contexts".
One of the major "misuses" of the
>>>
combinator is to apply "default styles" to a component wherever it lives in the tree, no matter how deeply nested it is inside of components. The use-case for this is to provide the equivalent of the user-agent stylesheet, but for custom elements (thus, the styles by necessity must come from the author).
Unfortunately, this is extremely slow, and there’s not a whole lot that can be done about that—>>>
combinators are slow by their nature. Note, though, that the UA and user stylesheets automatically apply in all shadows; it’s only the author stylesheet that is limited to the context it’s created in.(At this point, one might point out that this is already handled by just setting up styles during element construction. This doesn’t help for cases where a component is purposely authored to be styled by the end-user; forcing users of components to go muck around in their components' source code is a non-starter.)
One possible solution here is to add another origin, the "author default" origin, which sits between "user" and "author", and applies in all shadow roots automatically. We can add a list for these stylesheets, akin to document.styleSheets, and allow you to insert constructed stylesheets into it. Or maybe add an .origin attribute to CSSStyleSheet, defaulting to "author"?
Maybe it only applies to the context you’re in and descendant contexts? Need to investigate; probably bad to let a component apply automatic styles to the outer page via this mechanism.
By default, the added stylesheet via .moreStyleSheets
property would have "author" origin, but do we also need other ("user", or "author default") origins?
We may have other interfaces to add stylesheets to different origins, or have CSSStyleSheet
have .origin
property?
If someone does https://drafts.csswg.org/cssom/#dom-cssstylesheet-insertrule on a constructed stylesheet, or any of its imports (or their imports, etc) and the parsed rule is an @import
rule, we should probably throw. Especially if we want to be able to share the sheets across documents.
Need to define the value of https://drafts.csswg.org/cssom/#concept-css-style-sheet-origin-clean-flag on these sheets.
It's not clear to me why wrong MIME type on an @import
leads to TypeError but other failures (CSP, say) lead to a DOMException.
If we think the text/css thing really needs to be distinguishable from other load failures, doing it by the type of the exception is a bit odd.
Right now I believe it can't happen, but the spec draft here would allow sharing a mutable MediaList across multiple sheets. Is this desired? Would cloning the MediaList in the constructor be better?
The original proposal defines the constructor running in a synchronous manner, but can it return Promise<CSSStyleSheet>
instead, allowing the implementation to do lazy CSS parsing of the given text?
https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets defines an attribute that has a getter and a setter, but it seems pretty handwavy about (a) what the setter does and (b) what the getter does. Some of the relevant specification text seems to be above the description of the interface. But for things with a getter and a setter I think you'll often end up with a clearer spec if you explicitly describe the getter and setter behavior.
If we do, we don't need to introduce a constructor for StyleSheetList
, and can use array assignment and other functions directly on adoptedStyleSheets
.
cc @domenic who pointed this out.
It makes sense that createCSSStyleSheet
has to be async to wait for @import
rules.
Would it be possible to allow the creation of a no-import stylesheet synchronously, with a new factory method?
Otherwise I fear we are going to quickly see developers writing code like this:
function createCSSStyleSheetSync(text, options) {
const ss = document.createEmptyCSSStyleSheet(options);
for (const rule of text.split("}")) {
ss.insertRule(rule + "}");
}
return ss;
}
which I'm sure is very buggy, but will mostly work.
We switched away from constructors to instead use factory functions for two reasons:
Some of the loading details get easier if it's clear what document the stylesheet is associated with. While you can find an appropriate document to associate with a normal constructor (I forget what the right term is, but it's used elsewhere), it's a little weird. A factory function on document
makes this obvious.
We want at least some of the constructors to be async, and async constructors aren't going to be a thing.
@plinss suggested that we can get around both of those issues:
If we really need to associate it with a document, we can just require passing that as an argument to the constructor. In new CSSStyleSheet(document)
versus document.createCSSStyleSheet()
, the former is even slightly shorter!
The constructor can take some text (or whatever, there's a few forms), and create an inert stylesheet. You can then activate it with either .parse()
(which is sync, and which throws if there's an @import
) or .load()
(which is async and allows @import
). If we add a Response
-taking constructor like in #49, that particular stylesheet can only be .load()
ed; .parse()
would throw.
This is mostly a bikeshedding issue and I don't feel strongly. But IMO the following seems nicer:
const ss = await CSSStyleSheet.create(text, options);
const ss2 = CSSStyleSheet.createEmpty(options);
than the current spec's
const ss = await document.createCSSStyleSheet(text, options);
const ss2 = await document.createEmptyCSSStyleSheet(options);
It seems that https://drafts.csswg.org/css-scoping/#default-element-styles "Default Styles for Custom Elements" should go if we take this approach?
Seems nicer to just have one sync constructor, createCSSStyleSheetSync('')
. Maybe even make the string optional so you don't need to pass an empty string. (Although I guess passing nothing will be equivalent to passing 'undefined'
which will be treated as a no-op ^_^.)
This matters for base URL bits in the CSS and some DOM APIs. In general, need to define https://drafts.csswg.org/cssom/#concept-css-style-sheet-location
https://wicg.github.io/construct-stylesheets/#dom-document-createcssstylesheet claims to define an asynchronous method to create a style sheet, but it doesn't actually say anywhere to complete the rest of the steps in the list of steps at some later time. That implies that it's actually defining a synchronous method. It should probably do some (most?) of the steps later.
Apparently this spec (per verbal comments in today's CSS WG meeting) is solving something that's particularly important for shadow DOM. The introduction of the spec ought to explain what that is.
The original comment from @rniwa:
WICG/webcomponents#468 (comment)
This decision affects many other issues, so we'd like to get this resolved.
The spec draft at https://wicg.github.io/construct-stylesheets/#dom-document-createcssstylesheet doesn't have any link to the github repository, so it's not currently saying where to file issues.
Stylesheet parsing is affected by quirks mode. Should constructable stylesheets always be parsed in standards mode? Should they pick up the quirks mode of a document (which)?
https://wicg.github.io/construct-stylesheets/index.html#using-constructed-stylesheets says:
A CSSStyleSheet can only be applied to the Document it is constructed on (e.g. the document on which the factory function is called on), and any ShadowRoot in the Document it is constructed on, by assigning an array containing the sheet to their adoptedStyleSheets. So, a stylesheet can be used in multiple DocumentOrShadowRoots within the document it is constructed on.
Non-explicitly constructed stylesheets cannot be added to adoptedStyleSheets.
There are a few problems here:
The definition of which document a sheet is associated with is not clear. Is it the this
object of the factory method, or a document derived from the factory method itself (from its Realm)? Those can be different if .call
is used. Presumably it should just be the this
object of the factory method. Ideally this would be clearly specified in the factory methods by storing a reference to the "context object" Document
on the resulting sheet. This may require adding a state item ("pinned document", "unique document", "document", there are several options for naming and semantics here) to https://drafts.csswg.org/cssom/#css-style-sheets but that shouldn't be too bad, especially since CSSOM has a reasonably active editor now.
There is currently no state associated with a stylesheet that indicates whether it's an "explicitly constructed stylesheet". Presumably @imports
from constructed stylesheets are "non-explicitly constructed stylesheets", but right not that's not really defined. The Document
state from item 1 might work to identify "constructed stylesheets", depending on how it's defined.
The spec doesn't define what happens if you do try to assign a sheet in the wrong document. Presumably the adoptedStyleSheets
setter should throw in this case. This might be best addressed by writing down actual steps for that setter. Those steps would also do the throwing for non-explicitly constructed stylesheets. You will need some handwaving in terms of how you're getting your hands on the actual stylesheets from the FrozenArray
because that's slightly rocket-science at the moment; please talk to @domenic about that.
E.g. before all the @imports have finished. Does it throw? Does it allow mutation of the rule list while loads are still in progress? Something else?
If sheet contains one or more @import rules and sheet’s list of adopter documents contains any other Document than sheet’s constructor document, throw a "NotAllowedError" DOMException.
this should be "return a promise rejected with a "NotAllowedError" DOMException.
The spec, even after #40, does not have a clear processing model for the getter/setter of adoptedStylesheets. It should define them. Roughly something like
Every
DocumentOrShadowRoot
has an adopted stylesheetsFrozenArray<StyleSheet>
On getting,
adoptedStylesheets
returns thisDocumentOrShadowRoot
's adopted stylesheets.On setting,
adoptedStylesheets
performs the following steps:
- Let adopted be the result of converting the given value to a
FrozenArray<StyleSheet>
- Set this
DocumentOrShadowRoot
's adopted stylesheets to adopted.- ... perform actual styling updates ... (unsure on right spec text for this)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.