New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 776891 link

Starred by 9 users

Issue metadata

Status: Duplicate
Merged: issue 632009
Owner: ----
Closed: Nov 2017
Cc:
Components:
EstimatedDays: ----
NextAction: ----
OS: Windows
Pri: 2
Type: Bug-Regression



Sign in to add a comment

Priority for styles in extension injected stylesheets (insertCSS) has changed in a bad way

Reported by k...@luminance.org, Oct 20 2017

Issue description

UserAgent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0

Steps to reproduce the problem:
1. Inject an extension stylesheet that has rules that override rules in the document

What is the expected behavior?
1. Where reasonable, extension styles should override document styles.

What went wrong?
Extension styles no longer appear to override document styles unless you liberally apply !important to them. (I think; this is difficult because the Chrome devtools strip the !important modifier when displaying styles.) The overall ordering of the injected stylesheets shows them lower as if they have generally been deprioritized.

WebStore page: https://chrome.google.com/webstore/detail/viramate/fgpokpknehglcioijejfeebigdnbnokj?utm_source=chrome-app-launcher-info-dialog

Did this work before? Yes Last month

Chrome version: Version 64.0.3245.0 (Official Build) canary (64-bit)  Channel: canary  Channel: canary
OS Version: 10.0
Flash Version: 

Attaching screenshots of devtools that compare release channel and canary for the same element + loaded stylesheets.

If I manually move the rules I care about to inline styles they work, so there's nothing wrong with the rules. My other existing rules that don't conflict (in the same way?) with the document are still working.

I can "fix" this by applying !important to every single line in my CSS files but that seems suboptimal.
 
insertcss priority release.png
406 KB View Download
insertcss priority canary.png
429 KB View Download
As an additional data point, it's also affecting styles injected via the `manifest.json` rules.

Comment 2 by woxxom@gmail.com, Oct 22 2017

r509096 might be related
Cc: dgozman@chromium.org m.jeth...@eyeo.com
Labels: Needs-Triage-M64 TE-NeedsTriageHelp
As per comment comment #2 cc'ing to the file owner and reviewer for more updates on this issue.

Thanks!

Comment 4 by m.jeth...@eyeo.com, Oct 27 2017

The cascading order is correct:

https://www.w3.org/TR/css-cascade-3/#cascading

https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade#Cascading_order

What has changed is that style sheets injected by extensions are now treated as user style sheets.

DevTools must be modified to reflect the new priorities.

> I can "fix" this by applying !important to every single line in my CSS files but that seems suboptimal.

Why do you say that it's suboptimal?

If you're talking about performance of the browser itself, !important rules are no different than normal rules, they're just applied in the reverse order. It's not suboptimal at all.
I don't think anyone is arguing that user stylesheets should be higher priority than author stylesheets - I think the argument here is that treating extension stylesheets as user stylesheets is suboptimal for extension developers because extensions, by their nature, often involve modifying and tweaking existing code (including CSS styles) on existing sites.  Placing a typical extension CSS rule lower in priority than a typical rule on an existing site basically makes non-!important style rules useless for extensions.

The bigger issue here may be not how Chrome -should- treat extension stylesheets, but rather how it -has- in the past.  There are sure to be a great many extensions which will be negatively impacted by this change, including my own.  I'm fairly certain the only reason there hasn't been more pushback is because this change hasn't made it to a stable release channel.

Another interesting data point is tabs.insertCSS() in the WebExtensions API:
https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/insertCSS

See "cssOrigin" - Firefox supports specifying the origin of the injected CSS, and when not supplied, defaults to author (rather than user).

Just another thing to think about...

Comment 6 by m.jeth...@eyeo.com, Oct 27 2017

Even before this change non-!important rules in extensions did not override author rules [1]

[1]: https://crrev.com/e4488e2/third_party/WebKit/Source/core/css/DocumentStyleSheetCollection.cpp#91

Are you using tabs.insertCSS?
#6: I'm not familiar with Chromium internals, but it effectively worked at the same level as author rules, as if included as the last stylesheet in the page.

I can't comment on correctness or standards, but I'm certain this will break most page-altering extensions.

Comment 8 Deleted

Comment 9 Deleted

#6 - Seconding #7, I believe they were treated as author stylesheets, which put them at the same priority as the embedded stylesheets on the page - so the outcome came down to the specificity of the rules.  Typically that means the extension styles win, since (at least in my case) I'm applying specific classes to elements, whereas the styles already present on the page are typically based on inheritance.

Unfortunately I can't provide a way to reproduce the issue, since the site in question on my end requires credentials, but I can whip up an example if it's needed.  Thanks for looking in to this.

Comment 11 by m.jeth...@eyeo.com, Oct 27 2017

#10: I'll be very happy if you provide an example.
#11: I've attached a dead-simple extension ["ChromeStyleTest.zip"] which is intended to turn the background on https://www.google.com red.

On Chrome 61 (stable), it behaves as expected ["Chrome 61 Stable.png"] because the CSS rule we're applying is more specific than the Google-applied rule ("html body" vs "body").  However, on Chrome 64 (canary), our rule is ignored ["Chrome 64 Canary.png"] since it's now lower-priority (user) than the Google rule (author).  The real trouble here is that even if we set an ID on the body tag and use that ID in the CSS rule, it would still be ignored, even though that's about as specific as you can get.  The -only- way to get our rule applied is to use !important.

I've read through a lot of the discussion and documentation that was build around this change, and I generally agree that having the -option- to inject styles as user-origin rather than author-origin is a good thing, but changing the default behavior yanks the rug out from under extension developers.  That's only made worse by the fact that we don't have a choice in that change - there's no way to switch things back to the way they were before this change.  I just fear that the "hopefully minimal breakage" will be more than minimal.

Let me know if there's anything else I can do to assist - thanks!
Chrome 61 Stable.PNG
15.7 KB View Download
Chrome 64 Canary.PNG
15.4 KB View Download
ChromeStyleTest.zip
449 bytes Download

Comment 13 by m.jeth...@eyeo.com, Oct 27 2017

#11: Thanks!

I see what the problem is now.

Why does the extension use a specificity hack rather than !important in the first place?

Comment 14 by woxxom@gmail.com, Oct 27 2017

Using specificity isn't a hack. It's a proper use of cascading. Using !important is rather a hack by definition. 
#13: This is a rather contrived example, designed to show the issue with changing the priority.  In the actual extension I work on, it's not a "specificity hack" like adding "html" to the front of the selector - rather, we apply styles through CSS classes to elements we're adding to the web page.

The styles already present on the page include some very broad "reset" styles, which set margins/padding/borders/etc. to 0 for basically every HTML tag (div, span, table, etc.)  This works exactly as expected, where our styles are applied to our elements because the CSS class is a more specific selector than a tag-based one, but the reset styles are used where we don't specify a style.  That's the way CSS is supposed to be used, with more specific styles building on more general ones, as #14 mentioned.  However, with this change, these reset styles now override our class-based styles entirely, meaning we basically can't add margins, padding, borders, or most other styles to any element on the screen without also adding !important.

Basically, we're trying to cooperate and build on the CSS already in the page, rather than replace it entirely - if a style specified in the page is marked as !important, that SHOULD take priority over our CSS, unless we want to further override that with our own !important declaration.

Hopefully that clarifies the situation - thanks!

Comment 16 by k...@luminance.org, Oct 27 2017

My extension also provides specific per-element overrides and additional styles in content that has lots of broad "reset" styles. I use !important a handful of times where it's required, but adding it to every style rule - I have thousands - would be a real mess and would prevent me from using !important when I actually need it.

In practice if this bug persists I would just stop using insertCSS and manually insert raw style elements into the page, using a content script. In my experience this is a bit slower and creates flashes of unstyled content, which isn't great.

Comment 17 by m.jeth...@eyeo.com, Oct 27 2017

#15: Thanks for the clarification, this really helps.

I've uploaded a modified version of ChromeStyleTest.zip here that is a little more reflective of the issue.
ChromeStyleTest.zip
1020 bytes Download

Comment 18 Deleted

Reddit Enhancement Suite [1] (2.5MM users, if it matters) is also significantly affected by this change (disclosure: I'm one of the developers). Per #16 we'll just resort to injecting <style> elements if this change goes stable.

We're in a unique(ly bad) position here, as we want our styles to sit between two separate "categories" of author styles: above styles which are common across the site (which generally have low specificity), but below custom styles written by users of the site (which generally have high specificity). I realize this is a niche use-case, but I don't believe it's possible after this change.

Also, Firefox and Edge still treat extension stylesheets as author stylesheets (try ChromeStyleTest.zip), so extensions which inject CSS are less portable.

[1]: https://chrome.google.com/webstore/detail/reddit-enhancement-suite/kbmfpngjjgdllneeigpgjifpgocmfgmb

(deleted and reposted since I used the wrong account)
CanaryOnTheRight.PNG
221 KB View Download

Comment 20 by m.jeth...@eyeo.com, Oct 28 2017

I think this should be discussed with the Chromium team.

Please include some of the folks who were involved in the decision-making over  crbug.com/632009 

Speaking only for myself, I do see now that there are seemingly valid use cases for author style sheets in extensions and "!important" is no substitute. As things stand though the only option I see for extension developers is to inject the CSS directly into the page by adding a <style> element containing the code. This may not work on all pages though, especially those that prevent inline styles using CSP [1], but in those cases one could resort to using a data URI [2] or even loading the style sheet from a remote location (possibly served by the extension anyway by intercepting the request). In adversarial cases where the page tries to remove the injected CSS, there is the option of using Shadow DOM v0 similar to the way Adblock Plus does it [3].

[1]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
[2]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
[3]: https://github.com/adblockplus/adblockpluschrome/blob/9f593a8c9def5f3aac906e4fbc44370779b3352c/include.preload.js#L358

Comment 21 by k...@luminance.org, Oct 28 2017

I previously tried loading my styles inside a shadow root but had problems with the styles not properly applying to the containing document. I got the impression style elements inside a shadow root are supposed to only apply to elements within the shadow DOM. Is that incorrect?

Comment 22 by woxxom@gmail.com, Oct 28 2017

The proper solution would be to allow extensions to specify the origin - "author" or "user" - in chrome.tabs.insertCSS and in manifest.json.
BTW page CSP that forbids inline styles does not apply to style elements injected by an extension in Chrome, but does in Firefox.

Comment 23 by m.jeth...@eyeo.com, Oct 28 2017

Adblock Plus calls Element.createShadowRoot on document.documentElement (<html>) and then inserts a <shadow> element into the returned shadow root. It then adds a "::content" prefix to all the selectors so the styles actually apply to the host element.

Comment 24 by k...@luminance.org, Oct 28 2017

I admit I'm still not very good at understanding how Shadow DOM works, but if I create a shadow root on the document element or the body element the viewport becomes black because shadow DOM suppresses rendering of the host element. Should I really be using the <shadow> element? I see now from messing around that adding one to the shadow root causes the host element to reappear (?) but MDN says this element is deprecated and shouldn't be used.

Would I need to apply ::content to all of my selectors? I have something like 60KB of css so while adding a new prefix to all of my selectors isn't as bad as adding !important to every rule, it's still kinda awful.

Comment 25 by m.jeth...@eyeo.com, Oct 28 2017

#24: A lot of the v0 stuff is deprecated [1]

You would have to use the "::content" prefix if you want the styles to apply to the "shadow host" (in this case <html>)

[1]: https://hayato.io/2016/shadowdomv1/

Comment 26 by k...@luminance.org, Oct 28 2017

Closed shadow roots are nice (thanks!) but you can't attach a shadow root to the document element. The spec doesn't permit it on <html> so it seems like the Adblock Plus method is spec-violating and could break later. I could try to attach to <body> but there is no body element when my content script runs (I need to run as early as possible to inject script into the document before the document runs its own scripts).

Comment 27 by woxxom@gmail.com, Oct 28 2017

#26, FWIW you can wait for <body> using a nonrecursive MutationObserver on <html> element:
  new MutationObserver(mutations => {
    if (mutations.some(m => [...m.addedNodes].includes(document.body))) {
      console.log('hey');
    }
  }).observe(document.documentElement, {childList: true});
The only nuisance is that MutationObserver coalesces mutations so by the time its callback is invoked, the body may have a [small] bunch of elements inside.

Comment 28 by woxxom@gmail.com, Nov 3 2017

Why is the issue status still "unconfirmed"? Isn't it like absolutely obvious that the new behavior is not universally acceptable and should be reverted in favor of implementing a user-specified "origin" that can be "author" (default) or "user"?
Mergedinto: 632009
Status: Duplicate (was: Unconfirmed)
Moved this to original  issue 632009 .
> I don't think anyone is arguing that user stylesheets should be higher priority than author stylesheets

Why do you inject stylesheets just to be overridden? Extension are privileged, webpages are not, extensions should be able to force styles onto webpages without leaving webpages a way to resist. Currently, there isn't a way to enforce element hiding from extension without patching page context JavaScript API. 

Sign in to add a comment