whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
7.96k stars 2.6k forks source link

Add <link disabled> stylesheets to document.styleSheets #9977

Open jfbrennan opened 8 months ago

jfbrennan commented 8 months ago

What problem are you trying to solve?

I am trying to disable a stylesheet on page load with HTML - <link rel="stylesheet" href="..." disabled> - but once a condition is met at runtime, enable that stylesheet with JavaScript via document.styleSheets.item(i).disabled = false.

Currently, <link disabled> prevents the stylesheet from inclusion in document.styleSheets. I find this odd. Developers must resort to interacting with the <link> elements directly instead of the CSSStyleSheet objects, like they can with stylesheets that weren't initially disabled in the document.

What solutions exist today?

Disable a stylesheet on page load with HTML <link disabled> then use a selector API, like getElementById, to remove the disabled attribute.

How would you solve it?

My intuition was that I could disable a stylesheet with HTML - <link disabled> - and it would become a CSSStyleSheet object with its disabled property set to true and still be included in document.styleSheets and its styles NOT applied to the document.

Setting sheet.disabled = false would then apply the styles. When the browser first downloads a disabled stylesheet is not a concern as sheet.cssRules could be null or already set when the stylesheet is enabled. Always downloading could avoid a delay when first enabling, but then again delaying the download may be desirable as well.

Anything else?

No response

flavi1 commented 8 months ago

Do you realy need a CSSStyleSheet object for disabled stylesheets ? Or are you just looking for a simple way to list enabled/disabled stylesheets?

const AllStyleSheets = document.querySelectorAll('link[rel~="stylesheet"], style')
AllStyleSheets.forEach((sEl) => {
    console.log(sEl.disabled) // changeable boolean value
    console.log(sEl.sheet)  // CSSStyleSheet or null
})

If it's a performance issue, you probably wants to disable some stylesheets after their load. Then you don't have to download them again. Ex with a custom attribute data-to-disable :

const AllStyleSheets = document.querySelectorAll('link[rel~="stylesheet"][data-to-disable], style[data-to-disable]')
AllStyleSheets.forEach((sEl) => {
    sEl.addEventListener('load', (ev)=>{
        ev.target.disabled = true;
    })
})

Not tested, but should work...

flavi1 commented 8 months ago

There is maybe another alternative. LinkHTMLElement is designed to load external ressources (not only CSS). I don't know how browsers caches them, and make it reusable later, but you can try to put the "stylesheet" on the rel attribute later.

A last idea : Try to use rel="preload", then add the as="style" attribute later, or remove a disabled attribute later. see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel/preload

I hope it will help you.

jfbrennan commented 8 months ago

@flavi1 thank you, there are many ways to do what we needed to do and we did. I opened this issue because of the inconsistent behavior, which I believe is incorrect and unintuitive for a developer.

For example, a developer includes this at the top of their document:

<link rel="stylesheet" href="https://assets.com/a.css">
<link rel="stylesheet" href="https://assets.com/b.css" disabled>
<link rel="stylesheet" href="https://assets.com/c.css" media="screen and (max-width: 600px)">

That results in:

// document.styleSheets
{
  0: {
    type: 'text/css', 
    href: 'https://assets.com/a.css', 
    media: {mediaText: "", ...}, 
    disabled: false, 
    ...
  },
  1: {
    type: 'text/css', 
    href: 'https://assets.com/c.css', 
    media: {mediaText: "screen and (max-width: 600px)", ...}, 
    disabled: false, 
    ...
  }
}

Note that b.css is not there at all and the others have defaulted to disabled: false.

Two of my three <link> elements were parsed and constructed into CSSStyleSheet objects and added to document.styleSheets. Feels like a bug that b.css was not. I 100% expected to find:

{
  0: {
    type: 'text/css', 
    href: 'https://assets.com/a.css', 
    media: {mediaText: "", ...}, 
    disabled: false, 
    ...
  },
  1: {
    type: 'text/css', 
    href: 'https://assets.com/b.css', 
    media: {mediaText: "", ...}, 
    disabled: true, // <-- In the disabled state, but <link> was still parsed and constructed like the others 
    ...
  },
  2: {
    type: 'text/css', 
    href: 'https://assets.com/c.css', 
    media: {mediaText: "screen and (max-width: 600px)", ...}, 
    disabled: false, 
    ...
  }
}

That seems very much like the way it should be.

Now, does a browser download a disabled link stylesheet or not? Well, no, browsers do not. Ok, I agree with the reasoning for that, but why does that mean we shouldn't include b.css as a disabled CSSStyleSheet in document.styleSheets? There are three reasons why I think that behavior is wrong and unintuitive:

flavi1 commented 8 months ago

Yes, I agree. Since a CSSStyleSheet can effectively refer to a not yet downloaded CSS, and the disabled property exists, it seems more consistant to have an instance for each StyleSheets, including disabled StylesSheets.