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

Proposal: Ability to insert CSS from content script context #403

Open
gorhill opened this issue Jun 4, 2023 · 5 comments
Open

Proposal: Ability to insert CSS from content script context #403

gorhill opened this issue Jun 4, 2023 · 5 comments
Labels
proposal Proposal for a change or new feature supportive: chrome Supportive from Chrome supportive: firefox Supportive from Firefox supportive: safari Supportive from Safari

Comments

@gorhill
Copy link

gorhill commented Jun 4, 2023

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.
gorhill added a commit to gorhill/uBlock that referenced this issue Jun 4, 2023
Related issues:
- uBlockOrigin/uBOL-home#5 (comment)
- w3c/webextensions#403

Currently, there is no other way to inject CSS user styles than to
wake up the service worker, so that it can inject the CSS styles
itself using the `scripting.insertCSS()` method.

If ever the MV3 API supports injecting CSS user styles directly
from a content script, uBOL will be back to be fully declarative.

At this point the service worker is very lightweight since the
filtering is completely  declarative, so this is not too much of
an issue performance-wise except for the fact that waking up the
service worker for the sole purpose of injecting CSS user styles
and nothing else introduces a pointless overhead.

Hopefully the MV3 API will mature to address such inefficiency.
@tophf
Copy link

tophf commented Jun 4, 2023

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.

@xeenon xeenon added supportive: safari Supportive from Safari proposal Proposal for a change or new feature and removed needs-triage labels Jun 8, 2023
@Rob--W Rob--W added follow-up: chrome Needs a response from a Chrome representative supportive: firefox Supportive from Firefox labels Jun 8, 2023
@Rob--W
Copy link
Member

Rob--W commented Jun 8, 2023

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.

@gorhill
Copy link
Author

gorhill commented Jun 8, 2023

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).

@oliverdunk
Copy link
Member

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.

@oliverdunk oliverdunk removed the follow-up: chrome Needs a response from a Chrome representative label Jul 28, 2023
@oliverdunk oliverdunk added the supportive: chrome Supportive from Chrome label Aug 3, 2023
@oliverdunk
Copy link
Member

Adding supportive: chrome Supportive from Chrome with the context above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal Proposal for a change or new feature supportive: chrome Supportive from Chrome supportive: firefox Supportive from Firefox supportive: safari Supportive from Safari
Projects
None yet
Development

No branches or pull requests

5 participants