Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Should adoptedStyleSheets be ordered before other style sheets in the tree, instead of after? #93

Open
bicknellr opened this issue Apr 29, 2019 · 24 comments · Fixed by w3c/csswg-drafts#6431
Labels
needs resolution Needs consensus/resolution before shipping

Comments

@bicknellr
Copy link

From https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets:

The user agent must include all style sheets in the DocumentOrShadowRoot's adopted stylesheets inside its document or shadow root CSS style sheets.

These adopted stylesheets are ordered after all the other style sheets (i.e. those derived from styleSheets).

Is there a particular reason that style sheets in adoptedStyleSheets are ordered after style sheets from <style>s in the associated tree? AFAICT, adoptedStyleSheets is being designed primarily as a mechanism for sharing style sheets amongst many elements, but it feels strange that shared styles would take precedence over styles that are certainly only applicable to a particular instance (i.e. <style> in the associated tree).

p.s. Yes, you could just add your instance-specific style sheets to the end of the instance's adoptedStyleSheets instead of inserting them into the tree, but why is that necessary?

@tabatkins
Copy link
Contributor

They have to be put somewhere, and putting them before all the stylesheets coming from link/etc seems odder than putting them after.

@bicknellr
Copy link
Author

Sounds like this is a subjective issue, so here's my preferred color for the bike shed:

I conceptually think of the style sheets in a single root as if they were concatenated into a single style sheet since they pretty much work this way. Adding a style sheet to adoptedStyleSheets seems akin to adding @import statements to the concatenated style sheet. When importing a style sheet with @import, I'd usually put it at the top so that my inline rulesets will override anything that I'm getting from the import.

So, if we use this example:

const sheet1 = new CSSStyleSheet();
sheet1.replaceSync(".foo { color: red; }");

const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.adoptedStyleSheets = [sheet1];
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;

It feels more like it should be:

@import 'data:text/css,.foo { color: red; }';
.foo { color: blue; }

than:

.foo { color: blue; }
@import 'data:text/css,.foo { color: red; }';

I think this sentiment becomes even more applicable if CSS Modules become a thing. But I totally recognize that it's not critical since I could just stop adding inline <style> elements and switch everything over to using adoptedStyleSheets to get the same effect.

@tabatkins
Copy link
Contributor

I see where you're coming from within shadow roots, but consider the issue on the outer page, where .styleSheets will usually only include stylesheets coming from <link> in the <head>. It seems a lot weirder (to me, at least) for the JS-created adopted stylesheet to default to being earlier in the cascade than the <link rel=stylesheet>.

If we could somehow put it in the middle, where everything in the head came first, then adopted stylesheets, then style in body, I think that would be the best solution. But all markup-based stylesheets are bodged together into .styleSheets, so we instead have to choose to put the adopted sheets before or after all of them. :/

Putting JS-managed stuff after HTML-managed stuff is the general pattern for this sort of thing, so that's what we went with.

@tabatkins
Copy link
Contributor

(Really, what we want is the ability to tag the adopted stylesheet as part of a specific Cascade Origin, so you could set things up as being "user-agent" level, and thus automatically overridden by anything at author-level, such as style contents. But that's not currently possible; perhaps in a v2?)

@bicknellr
Copy link
Author

bicknellr commented Apr 30, 2019

What if adoptedStyleSheets was moved from DocumentOrShadowRoot to HTMLStyleElement? Instead of attaching style sheet objects to a single bucket that applies to the entire root and requires making the 'before vs. after' decision, you would attach them to a specific node instead and inherently be able to decide the placement yourself.

Main document:

const externalStyles = document.createElement('style');
externalStyles.adoptedStyleSheets = [a, b, c];
// If you want adopted styles after everything else you've imported:
document.head.appendChild(externalStyles);

In a ShadowRoot:

const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
const externalStyles = document.createElement('style');
externalStyles.adoptedStyleSheets = [a, b, c];
// If you want adopted styles before everything in the root:
shadowRoot.insertBefore(externalStyles, shadowRoot.firstChild);

You could put them in multiple places if that's what you needed:

<style id="alwaysLoses"></style>
<style>.foo { color: red; }</style>
<style id="alwaysWins"></style>
<script>
document.getElementById('alwaysLoses').adoptedStyleSheets = [a, b];
document.getElementById('alwaysWins').adoptedStyleSheets = [c, d];
</script>

edit: Removed an incorrect line in the second example.

@domenic
Copy link
Contributor

domenic commented Apr 30, 2019

If you're going to give them before/after locations in the DOM tree, they should just be DOM nodes. For example, HTMLStyleElement.

@bicknellr
Copy link
Author

If you're going to give them before/after locations in the DOM tree, they should just be DOM nodes. For example, HTMLStyleElement.

CSSStyleSheet could have a method that produces a new HTMLStyleElement associated with the style sheet. Using createLinkedStyleElement as the name, then this example (that I updated to use prepend)

const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
const externalStyles = document.createElement('style');
externalStyles.adoptedStyleSheets = [a, b, c];
shadowRoot.prepend(externalStyles);

would become

const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
shadowRoot.adoptedStyleSheets = [sheet1];
shadowRoot.innerHTML = `
<style>.foo { color: blue; }</style>
<span class="foo">Hello!</span>
`;
shadowRoot.prepend(...[a, b, c].map(sheet => sheet.createLinkedStyleElement()));

Is that kind of what you were thinking?

@domenic
Copy link
Contributor

domenic commented May 2, 2019

Yes, although I'm not sure there's too much value in indirecting through CSSStyleSheet instead of just creating a HTMLStyleElement directly.

@bicknellr
Copy link
Author

I was thinking that the node created by createLinkedStyleSheet would continue to be associated with the CSSStyleSheet rather than containing a copy of the style sheet at the time they were created - replace, etc. would 'update' all of the associated nodes.

@domenic
Copy link
Contributor

domenic commented May 2, 2019

I was thinking even simpler: just use HTMLStyleElement only, don't ever use a CSSStyleSheet apart from htmlEl.sheet.

@bicknellr
Copy link
Author

I'm not sure I follow - could you give an example?

@manucorporat
Copy link

manucorporat commented Jun 17, 2019

document.adoptedStylesheet will be used most of the time by web component authors to provide the default styles of components, leaving the users the ability to customizable and override the styles using link or inlined <style>. This is pretty much broken, when they are applied after.

In fact, I thought it behaved like user agent's stylesheets.

While this problem is more important in the document case, it's too for shadow-dom, where developers should be able to add a <style> to override certain styles, it will not work.

@manucorporat
Copy link

I think something like document.adoptedStylesheetBefore (or similar) could also solve the problem, and in fact be the API that most developers use, given the use case of Constructable Stylesheets today.

@tabatkins
Copy link
Contributor

Yeah, the reasoning in this thread seems fairly convincing. Like I said in my earlier comment, I think the current placement (page, then adopted) makes the most sense for the outermost page, but within a shadow root the reasoning is different.

(Specifically, style in shadow roots seem more similar to style in body, which in my earlier comment I admit would probably make sense to place after the adopted sheets.)

So I support some way of having adopted sheets go before the inline sheets. Whether that's by shifting where they go in general, or by adding a secondary attribute that places them in a different spot, I don't have a real opinion on.

@rakina, @domenic ?

@rakina
Copy link
Member

rakina commented Jun 18, 2019

Sounds reasonable to have overridable styles. What if instead of changing/adding another type of adoptedStyleSheets, we make the positioning up to each stylesheet?

So you would have a new option overridable or something in the CSSStyleSheet constructor, where if it's true we would put it before all the other styles, and if false/unset we put it after.
(though all of them are overridable by the inline style attribute so I guess we need a better name/term).

I don't have any strong preference though, probably having two kinds of adoptedStyleSheets will be less confusing?

cc @mfreed7 @chrishtr

@domenic
Copy link
Contributor

domenic commented Jun 18, 2019

I don't have strong feelings, but am confused by this use case. Why would a web component ever modify a shared document-level resource? Put them in the shadowRoot.adoptedStyleSheets. Then they will behave similar to UA styles.

@manucorporat
Copy link

manucorporat commented Jun 19, 2019

So you would have a new option overridable or something in the CSSStyleSheet constructor, where if it's true we would put it before all the other styles, and if false/unset we put it after.
(though all of them are overridable by the inline style attribute so I guess we need a better name/term).

Also no personal preference, maybe something like order (analogous to css order) can cover this and other use cases better.

I don't have strong feelings, but am confused by this use case. Why would a web component ever modify a shared document-level resource? Put them in the shadowRoot.adoptedStyleSheets. Then they will behave similar to UA styles.

Ideally all components would use shadow-dom, but in practice it’s not possible. Believe me, we have tried really hard. The lack of :host-context(), style any descendant (slotted only styles direct children), integration with form (I know it will get better), developers might want some flexibility.

In addition apps (not isolated components) components usually work better without shadow-dom, otherwise all pages content is inside the shadow-dom. For SEO reasons is still useful to build web components without shadow-dom enabled.

In any case, for anything that is more complex than a simple web component, there are many good reasons to avoid it.

In stencil, developers can use native shadow-dom (recommended), scoped (simulated shadow-dom using normal css) and normal.

In both scoped and normal, and polyfilling native shadow-dom, the stylesheet is attached to the document.

However, in the shadow-dom use case I would also expect a inlined <style> to be applied after, and override the adopted styles. Notice that constructable stylesheets might be used most of the times by frameworks or libraries. But developers (users) will still use inlined styles to override certain default because the ergonomic are a little better.

@domenic
Copy link
Contributor

domenic commented Jun 19, 2019

Thanks for clarifying!

The lack of :host-context(), style any descendant (slotted only styles direct children), integration with form (I know it will get better)

I worry that at least two of these are working around unimplemented features in some browsers. Adding new features to the spec will not help with that, as those browsers will not implement the new features either.

Could you expand on the "style any descendant" problem, and how using document-level stylesheets solves it?

@bicknellr
Copy link
Author

bicknellr commented Jun 20, 2019

So you would have a new option overridable or something in the CSSStyleSheet constructor, where if it's true we would put it before all the other styles, and if false/unset we put it after.
(though all of them are overridable by the inline style attribute so I guess we need a better name/term).

Why would a web component ever modify a shared document-level resource?

I also think modifying or picking a single behavior for a shared style sheet is kind of strange and would be a problem because that chosen behavior would apply to all users of that style sheet. For example, if a package distributes modules exporting CSSStyleSheets, then that package has to decide the single behavior that all of its users use. One workaround would be to make the package export a function that generates a CSSStyleSheet with the desired options, but that starts cutting into the value of sharing them because you need a copy for each distinct set of options requested.

@manucorporat
Copy link

I worry that at least two of these are working around unimplemented features in some browsers. Adding new features to the spec will not help with that, as those browsers will not implement the new features either.

I guess that's fine, we support Constructable Stylesheets because they actually help the performance for most of our users (Chrome and Android). We fallback to the old slower behaiour in other browsers. The problem would be to not even use Constructable Stylesheets in Chrome because it does not cover our use case.

Could you expand on the "style any descendant" problem, and how using document-level stylesheets solves it?

So withing shadow-dom, ::slotted() is the only way to style direct children, it's not possible as long as i know to style any descendant h1 for example, in "normal" CSS it's as easy as doing:

my-cmp h1 {}

but in shadow-dom, ::slotted(h1) will not target a nested h1 element.

@rakina rakina added the needs resolution Needs consensus/resolution before shipping label Jan 20, 2020
@css-meeting-bot
Copy link

The CSS Working Group just discussed Should adoptedStyleSheets be ordered before other style sheets in the tree, instead of after?, and agreed to the following:

  • RESOLUTION: constructed style sheets to always go after
The full IRC log of that discussion <stantonm> topic: Should adoptedStyleSheets be ordered before other style sheets in the tree, instead of after?
<astearns> github: https://github.com//issues/93
<stantonm> github: https://github.com//issues/93
<stantonm> heycam: spec says orderering of stylesheets should be ?, but actually should it be other way around?
<heycam> https://github.com//issues/93#issuecomment-487772869
<bkardell_> q+
<stantonm> TabAtkins: my comment says make sense to put after
<stantonm> emilio: don't think there's a strong reason for one or other
<stantonm> hober: agree, but is there consistency arguments?
<stantonm> TabAtkins: maybe related to @font-face, which is after
<astearns> ack bkardell_
<stantonm> bkardell_: talking about shadow root and document styles, strangely related - some things bleed through, some blocked
<stantonm> ... not sure I get what ordering means
<stantonm> ... would like them to come before
<stantonm> ... different use cases, adopt styles from outside
<stantonm> TabAtkins: all sheets that come from markup come before adopted style sheets
<stantonm> bkardell_: we want to use these for UA equivilent, seems like not the right move
<stantonm> TabAtkins: adopted style sheets help when people put things directly in shadow dom
<stantonm> ... component usage will move to adopted style sheets, gives full control
<stantonm> ... if you use style inline, it's baked into the template
<stantonm> ... similar to link style sheet in head, where you override with adopted
<stantonm> ... both ordering can make sense, no strong argument
<stantonm> bkardell_: we don't know which is correct
<stantonm> hober: if we don't know, ask the author
<stantonm> ... implies two sets of adopted style sheets, seems complex
<stantonm> ... not sure if additional complexity is worth it
<tantek> present++
<stantonm> TabAtkins: don't need two sets, just move from shadow root to adopted
<stantonm> bkardell_: can you do adopted style sheets outside shadow dom
<stantonm> TabAtkins: yes
<stantonm> hober: summarizing = after, if they want before have to specify
<stantonm> RESOLUTION: constructed style sheets to always go after
<TabAtkins> astearns: And if we realize that authors do ahve common need to put the adopted ones first, we're free to add a knob for that.

@pygy
Copy link

pygy commented Feb 1, 2020

I'm coming late here (apologies, I've been living under a rock for the past year).

Couldn't the adoptedStyleSheets be placed in the cascade as if they were part of style elements in the the root's ::before and ::after pseudo-elements? (...or, for the document, part of the body's ::before and ::after... Alternatively, give the body its own adoptedStyleSheets set)?

That would give authors some flexibility regarding their overidability, and well defined, non-arbitrary semantics.

As an author, being able to put styles between the head and the body makes a lot of sense from an overridability standpoint.

You could also make them visible (but read-only, or even opaque) in the .styleSheets list though that's probably a different issue.

@pygy
Copy link

pygy commented Feb 3, 2020

Adding some more thoughts: while the after position makes sense for app writers, the before slot would seem a natural fit for library authors.

@marijnh
Copy link

marijnh commented Jan 4, 2021

Another vote for having these appear before style tags (or, alternatively, giving client code control over the ordering). My use case is a library that injects styles but expects users to occasionally want to override those with regular old style sheets.

jkomoros added a commit to jkomoros/card-web that referenced this issue May 25, 2022
The problem was caused because when we switched to using static styles, those are adopted stylesheets, which have higher precedence than style blocks in the shadowRoot (see WICG/construct-stylesheets#93). That meant the style block wasn't actually overriding the default styles for card-renderer.

card-renderer container auto-adds a class of the current card type. This allows a pattern of `.${CARD_TYPE_mytype} ` preprended in front of style selectors to increase specificity.

That machinery is also used for CARD_TYPE_SECTION_HEAD and CARD_TYPE_WORKING_NOTES, the only two users of styleBlock. (Note that working-notes styling also broke in c28375e but I hadn't noticed before now).

Part of #453.
happysmilecode added a commit to happysmilecode/React_stylemod that referenced this issue Jul 25, 2023
Since that'll give our injected styles a higher precedence than regular
style sheets (WICG/construct-stylesheets#93).

Issue codemirror/dev#360
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs resolution Needs consensus/resolution before shipping
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants