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.