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

Support user styles through scripting.registerContentScripts via a code property #615

Open
tophf opened this issue May 15, 2024 · 9 comments
Labels
supportive: chrome Supportive from Chrome supportive: firefox Supportive from Firefox supportive: safari Supportive from Safari

Comments

@tophf
Copy link

tophf commented May 15, 2024

Currently there's no support for userstyles in Chrome and it's become a big problem in ManifestV3 as it removes persistent background scripts, meaning that the style is often applied after 100-200ms necessary to spin up the background script environment, read the database of styles, inject the CSS. It produces a random and frequent FOUC (flash of unstyled content) while opening a page. Even an occasional FOUC is unacceptable as many styles completely change the appearance of web sites, so even a 16ms glimpse of unstyled content may be painful if it's white instead of the expected dark.

Previously, using a persistent background script a ManifestV2 extension was fast enough to apply the styles consistently without FOUC in most cases using extension messaging, the rest was covered by webRequestBlocking + URL.createObjectURL + XMLHttpRequest workaround that is impossible now in Chrome (webRequestBlocking is only available in a policy-installed ManifestV3 extension and URL.createObjectURL is still not exposed to service workers, so it can't be used even in a policy-installed extension as it requires asynchronous offscreen document communication while webRequestBlocking listener can only be synchronous in Chrome).

The only workaround in Chrome's MV3 is userScripts API, but it's a heavy hammer, which has the potential of XSS if the extension doesn't properly embed the CSS into the JS code string.

Firefox has browser.contentScripts.register that supports literal CSS code.

Primary goals to achieve parity with extensions like Stylus or Stylish (millions of users, ~100K userstyles):

  1. Register/update a literal CSS code string.

    Probably in chrome.scripting.registerContentScripts/updateContentScripts using cssCode: 'string' and a mandatory id.

  2. Support id in scripting.insertCSS/removeCSS to allow the user of the extension to toggle the styles on-the-fly.

  3. Inject at document_start when loading/prerendering the page, i.e. ignore runAt.

  4. Place the styles after all the other styling mechanisms (document.styleSheets, document.adoptedStyleSheets, styles added to the DOM root after document.body) to ensure they override page selectors with the same specificity in any origin mode (author/user).

    This is not the case in Chrome, where insertCSS injects before adoptedStyleSheets last time I checked.

  5. Don't apply CSP of the page to data: inside the style, ideally to any URL for which the extension has a host permission.

    These are used for custom backgrounds/icons.

    If any URL exemption is undesirable, then consider allowing blob:chrome-extension://.... so that the extension can replace the external links in CSS with URLs to blobs it downloaded when installing the style.

  6. Match the #-hash part of URLs.

    Many styles augment SPA sites with hash-fragment routing. Currently extension matching patterns completely ignore it. May be an optional mode.

  7. Add a method to query the matched ids for tabId/frameId/documentId so that the extension can show the number in its icon's badge and display the names of the styles in its popup, which allows the user to manager the styles (edit, configure, remove, toggle, reorder).

  8. Support JS regexp matches/excludes or at least RE2.

    This is a primary goal because unlike a userscript, a userstyle is not JS so it can't perform regex matching. An extension can't do it separately just for such styles, because it will be too late in many cases, i.e. FOUC.

    Many userstyles use regexps as it's the only way to avoid targeting the wrong page that has the same CSS selectors inside.

    If this is not implemented, extensions need a way to set the order in scripting.insertCSS e.g. before: 'id', because users often have several/many styles applied to a page and the order is important in CSS.

Secondary goals:

  1. Automatically apply the matching userstyles and remove the no-longer-matching userstyles at a URL change due to a soft navigation like history.pushState/replaceState, hash-navigation due to a click on a link, back/forward navigation.

    Although this can be done by an extension, but it'll have to read all the styles and match them manually. Styles often have a lot of matching patterns, many of them regexps.

  2. Allow re-ordering of the styles similarly to userScripts API: re-order without re-registering everything #606 by specifying before: 'id' or priority when registering/updating.

Alternative solution

#536 is the bare minimum to solve FOUC, but still wasteful due to the lack of full regexp matches used by many popular styles, so megabytes of CSS would be injected into every tab/frame and 1-100 regexp expressions (many styles have dozens) would be re-compiled and re-executed. It might be even better to extend chrome.declarativeContent with SetContentData({....}) because it's more flexible and already supports regexp (RE2).

@github-actions github-actions bot added needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time needs-triage: safari Safari needs to assess this issue for the first time labels May 15, 2024
@erosman
Copy link

erosman commented May 15, 2024

@tophf
Copy link
Author

tophf commented May 24, 2024

#536 is the bare minimum to solve FOUC, but still wasteful due to the lack of full regexp matches used by many popular styles, so megabytes of CSS would be injected into every tab/frame and 1-100 regexp expressions (many styles have dozens) would be re-compiled and re-executed. It might be even better to extend chrome.declarativeContent with SetContentData({....}) because it's more flexible and already supports regexp (RE2).

@xeenon
Copy link
Collaborator

xeenon commented Sep 27, 2024

At TPAC we are all supportive of supporting arbitrary stylesheets through scripting.registerContentScripts, but not in the userScripts API. Retitling to reflect that.

@xeenon xeenon changed the title userstyles support Support user styles through scripting.registerContentScripts via a code property Sep 27, 2024
@tophf
Copy link
Author

tophf commented Sep 27, 2024

but not in the userScripts API

To clarify, userstyles aren't userscripts, so there was never any dependency on the userScripts API.

@tophf tophf changed the title Support user styles through scripting.registerContentScripts via a code property Support user styles through scripting.registerContentScripts via a code property or a dedicated API Sep 27, 2024
@xeenon
Copy link
Collaborator

xeenon commented Sep 27, 2024

We resolved to put this in scripting and not a new dedicated API. (I wish userScripts was also part of scripting and not a similar but separate API.)

@xeenon xeenon changed the title Support user styles through scripting.registerContentScripts via a code property or a dedicated API Support user styles through scripting.registerContentScripts via a code property Sep 27, 2024
@tophf
Copy link
Author

tophf commented Sep 27, 2024

Your first comment was mentioning you resolved not to put it in the "userScripts API" and not a "dedicated API". Are you sure the latter is not just your interpretation? Given the amount of special casing for userstyles, arguably more than for userscripts, using a separate dedicated API might be a better choice.

@erosman
Copy link

erosman commented Sep 27, 2024

(I wish userScripts was also part of scripting and not a similar but separate API.)

As I have been suggesting (#489) ...... Better late than never ;)

@Rob--W Rob--W added supportive: chrome Supportive from Chrome supportive: safari Supportive from Safari supportive: firefox Supportive from Firefox and removed neutral: safari Not opposed or supportive from Safari needs-triage: chrome Chrome needs to assess this issue for the first time needs-triage: firefox Firefox needs to assess this issue for the first time labels Sep 28, 2024
@Rob--W
Copy link
Member

Rob--W commented Sep 28, 2024

I'm double-checking the accuracy of issue labels and statuses as part of preparing the publication of notes from the TPAC 2024 (#659) meeting. As Timothy mentioned above, the consensus is "we are all supportive of supporting arbitrary stylesheets through scripting.registerContentScripts, but not in the userScripts API."

Your first comment was mentioning you resolved not to put it in the "userScripts API" and not a "dedicated API". Are you sure the latter is not just your interpretation?

This kind of feature request has come up before and linked to userScripts. We wanted to explicitly assert that the functionality would not be added to the userScripts API, but the scripting API.

@tophf
Copy link
Author

tophf commented Oct 5, 2024

Judging by the published minutes there was no detailed discussion of those peculiarities in userstyles I've listed in the description that arguably make scripting API just as bad a fit as userScripts, even if we disregard the fact that userstyles have nothing to do with scripting.

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

No branches or pull requests

4 participants