Workaround
Thanks for all others, who pointed me to the right direction. Basically, there is no easy way, but you can:
- inject a content script and iterate
document.styleSheets
, where all parsed style sheets are already loaded. That's the hard task.
However, this is all read-only, so you cannot modify it directly.
- Then you need to send that result back to your background script (as content scripts do not have access to the required API) and apply the CSS manually via
browser.tabs.insertCSS
.
As for the first task, here is the code snippet (one time written in functional way and one time just structural) that returns all the CSS, given a media query.
You e.g. can call getCssForMediaQueryFunc("(prefers-color-scheme: dark)")
and get all the CSS applied for the dark color scheme.
/**
* Return CSS from the website for a specific query string.
*
* (functional implementation)
*
* @private
* @param {string} queryString
* @returns {string}
*/
function getCssForMediaQueryFunc(queryString) {
return Array.from(document.styleSheets).reduce((prev, styleSheet) => {
/* workaround for crazy HTML spec throwing an SecurityError here,
* see https://discourse.mozilla.org/t/accessing-some-fonts-css-style-sheet-via-stylesheet/38717?u=rugkx
* and https://stackoverflow.com/questions/21642277/security-error-the-operation-is-insecure-in-firefox-document-stylesheets */
try {
styleSheet.cssRules; // eslint-disable-line no-unused-expressions
} catch (e) {
return prev;
}
return Array.from(styleSheet.cssRules).reduce((prev, cssRule) => {
if (cssRule instanceof CSSMediaRule) {
if (cssRule.conditionText === queryString) {
return Array.from(cssRule.cssRules).reduce((prev, subCssRule) => {
return prev + subCssRule.cssText;
}, prev);
}
}
return prev;
}, prev);
}, "");
}
/**
* Return CSS from the website for a specific query string.
*
* @private
* @param {string} queryString
* @returns {string}
*/
function getCssForMediaQuery(queryString) { // eslint-disable-line no-unused-vars
let cssRules = "";
for (const styleSheet of document.styleSheets) {
/* workaround for crazy HTML spec throwing an SecurityError here,
* see https://discourse.mozilla.org/t/accessing-some-fonts-css-style-sheet-via-stylesheet/38717?u=rugkx
* and https://stackoverflow.com/questions/21642277/security-error-the-operation-is-insecure-in-firefox-document-stylesheets */
try {
styleSheet.cssRules; // eslint-disable-line no-unused-expressions
} catch (e) {
continue;
}
for (const cssRule of styleSheet.cssRules) {
if (cssRule instanceof CSSMediaRule) {
if (cssRule.conditionText === queryString) {
for (const subCssRule of cssRule.cssRules) {
cssRules = cssRules + subCssRule.cssText;
}
}
}
}
}
return cssRules;
}
(up-to-date code should be accessible here)
- As mentioned before, you also need to overwrite
window.matchMedia()
to also fake the result of the websites that could use JS for the detection. However, this is also it's own non-trivial task and requires exporting this function from the content script to the website. (Also faking that is hard.)
Proof of concept
I've implemented this whole thing as a more or less proof-of-concept in an add-on here, also available on addons.mozilla.org (AMO). (I've used permalinks here, but the add-on may of course be updated in the future.)
Future
Obviously, this is not a nice method, so I've created a new Bugzilla issue to find a better solution for that, e.g. a special Firefox API.