Proposal: Ability to insert CSS from content script context
Opened this issue · 5 comments
Background
I have been working on a MV3-compatible lite version of uBlock Origin, named uBlock Origin Lite, or uBOL. The goal with the extension is to be fully declarative, such that no service worker is needed for the filtering to occur. The service worker is required only when the user interacts with the extension settings (either from the popup panel or options page.
The fully declarative nature of the extension is fulfilled, but there is currently a negative side effect as a result: the inability to reliably enforce cosmetic filtering on web pages.
This issue arises because the CSS styles to inject in a web page do not have precedence over the webpage's own CSS styles. This CSS styles precedence issue is normally resolved using the scripting.insertCSS()
, but since uBOL is entirely declarative, it can't use this approach, as this would require the content script to send a message to the service worker, causing the service worker to wake up as a result. This would defeat the goal of being entirely declarative to avoid the cost of having to constantly wake up the service worker each time a webpage navigation occurs.
Solution
This issue would be solved if it was possible to call scripting.insertCSS()
(or equivalent) from the content script context. The API in the context of a content script could be made simpler, as its purpose would be always to inject CSS targeting the current document (so no need for tab id, frame id, etc.)
chrome.scripting.insertCSS(
injection: CSSContentScriptInjection
)
injection
: The details of the styles to insert.
CSSContentScriptInjection
css
: string: A string containing the CSS to inject.
origin?
: StyleOrigin
. The style origin for the injection. Defaults to AUTHOR
.
Notes
There are already a set of API methods available from content script contexts: https://developer.chrome.com/docs/extensions/mv3/content_scripts/#capabilities, so this is not a new approach.
Using the browser dev tools, I notice that there is already an instance of chrome.scripting
object in the context of the content script (possibly as a result of the content script being injected through scripting.registerContentScripts), so it would be a matter of adding insertCSS
method to it.
Essentially the API method would accomplish in a more efficient way what can be already accomplished by sending a message to the service worker, and security-wise, I cannot see a difference.
This is sort of related to Proposal: Declarative Cosmetic Rules, but adding API method scripting.insertCSS
in the context of a content script would not make the issues raised in #362 a roadblock while reaching the same goal of being fully declarative.
References
Related issue in uBOL repo: uBlockOrigin/uBOL-home#5 (comment)
uBOL summary description: https://github.com/gorhill/uBlock/blob/master/platform/mv3/description/en.md
Chrome Web Store: https://chrome.google.com/webstore/detail/ublock-origin-lite/ddkjiahejlhfcafbddmgiahcphecmpfh
Updates
- 2023-06-04: Also consider that sending messages to the service worker from a content script is currently unreliable, as this often results in
Could not establish connection. Receiving end does not exist.
when the service worker is no longer in memory.
The method can be inside chrome.dom namespace. It'd be synchronous for the proposed use case of a literal css
string.
there is already an instance of chrome.scripting object in the context of the content script
AFAIK it's an artifact from the currently abandoned implementation of an API method to configure parameters of the content script via chrome.scripting API in a full chrome-extension:// context, i.e. when it'll be finally implemented it may have a different namespace.
This issue was discussed during today's meeting; the meeting notes are pending publication at #408.
This issue arises because the CSS styles to inject in a web page do not have precedence over the webpage's own CSS styles. This CSS styles precedence issue is normally resolved using the
scripting.insertCSS()
,
I suppose that this is your main point of the feature request: as an extension you want the highest control over the appearance, and currently the only way to do that is by loading a stylesheet with "user" origin. While web pages (with "author") origin ordinarily take precedent over the "user" style sheets, the opposite is true when !important
is added: https://developer.mozilla.org/en-US/docs/Web/CSS/Cascade#cascading_order
there is already an instance of chrome.scripting object in the context of the content script
AFAIK it's an artifact from the currently abandoned implementation of an API method to configure parameters of the content script via chrome.scripting API in a full chrome-extension:// context, i.e. when it'll be finally implemented it may have a different namespace.
Specifically, globalParams.
you want the highest control over the appearance, and currently the only way to do that is by loading a stylesheet with "user" origin
Yes. There is no guarantee that a constructable stylesheet will override the styles we want to override, even more so when those styles to override are inlined using the style
attribute on an element.
Overriding using inline style is also problematic because this changes the style
attribute, and some cosmetic filters do use the style
attribute as a condition, for example:
##div[style="display:flex !important"] > div
And for reliability this also requires a mutation observer, so in the end beside being unreliable, it's inefficient.
Since this can already be accomplished by waking up the service worker so that it can inject the USER
styles using scripting.injectCSS
, the request is really for a way to do the same without having to wake up the service worker -- it does feel like an avoidable roundtrip to the service worker since the USER
styles are meant to be injected in the current context.
I did modify the current code in my extension in order to inject USER
styles through the service worker, and I have to admit this works rather well in Chromium, though I did take care to minimize initialization work performed by the service worker.
This doesn't work as well in Firefox though, there is a visible delay before the user styles are injected, and there seem to be other issues with reliability as the user styles are seemingly not always injected (reason unknown).
I had some discussions about this on the Chrome side. Given scripting.insertCSS supports a target property we wouldn't want to expose in content scripts, exposing the same API namespace seems confusing. That said, we would be supportive of adding something to the browser.dom namespace, which hasn't previously been used outside of Chrome but feels like a nice place for this sort of functionality.
We also discussed if we would want to support the files parameter, or if we would require CSS as a string. We didn't have a definite answer there but were leaning towards not supporting files as this could allow a compromised content script to access files it otherwise wouldn't be able to.
Adding
supportive: chrome