If the original CSS isn't in the CSSOM, where is it?
Getting all CSS style rules
<style>
<link rel="stylesheet" href="...">
export const getPageStyles = () => {
// Query the document for any element that could have styles.
var styleElements =
[...**document.querySelectorAll('style, link[rel="stylesheet"]')**];
// Fetch all styles and ensure the results are in document order.
// Resolve with a single string of CSS text.
return Promise.all(styleElements.map((el) => {
if (el.href) {
return **fetch(el.href).then((response) => response.text());**
} else {
return **el.innerHTML;**
}
})).then((stylesArray) => stylesArray.join('\n'));
}
export const replacePageStyles = (css) => {
// Get a reference to all existing style elements.
const styles = document.querySelectorAll('style, link[rel="stylesheet"]');
// Create a new <style> tag with all the polyfilled styles.
const polyfillStyles = document.createElement('style');
polyfillStyles.innerHTML = css;
**document.head.appendChild(polyfillStyles);**
// Remove the old styles once the new styles have been added.
styles.forEach((el) => el.parentElement.removeChild(el));
};
Putting the polyfill together
import postcss from './third-party/postcss.js';
import {getPageStyles} from './get-page-styles.js';
import {randomFunctionPlugin} from './random-function-plugin.js';
import {replacePageStyles} from './replace-page-styles.js';
getPageStyles()
.then((css) => postcss([**randomFunctionPlugin**]).process(css))
.then((result) => replacePageStyles(result.css));
Check the rest of the CSS for matching rules, and then only replace the random() function with a random number as an inline style if it's the last matching rule.Wait, that won't work, because we have to account for specificity, so we'll have to manually parse each selector to calculate its specificity. Then we can sort the matching rules in specificity order from low to high, and only apply the declarations from the most specific selector.Oh and then there's @media rules, so we'll have to manually check for matches there as well.And speaking of at-rules, there's also @supports—can't forget about that.And lastly we'll have to account for property inheritance, so for each element we'll have to traverse up the DOM tree and inspect all its ancestors to get the full set of computed properties.Oh, sorry, one more thing: we'll also have to account for !important, which is calculated per-declaration instead of per-rule, so we'll have to maintain a separate mapping for that to figure out which declaration will ultimately win.
Hold up, didn't you just describe
the cascade?
Option #3
Rewrite all selectors that contain the random() function in such a way that instead of one selector matching many elements, you have many selectors each only matching one element.
// ...
// Clone the current rule and update the selector.
rule.parent.insertBefore(rule, rule.clone({
selector: appendToSelectors(rule.selector, **':not(.z)'**)
}))
// Insert all the new rules before the current rule.
for (const id of Object.keys(newRules)) {
rule.parent.insertBefore(rule, newRules[id]);
}
// Remove the current rule and continue iterating.
rule.remove();
});
});
The final plugin code
import postcss from './third-party/postcss.js';
import {randomFunctionPlugin} from './random-function-plugin';
import {getPageStyles} from './get-page-styles';
import {replacePageStyles} from './replace-page-styles';
getPageStyles()
.then((css) => postcss([randomFunctionPlugin]).process(css))
.then((result) => replacePageStyles(result.css));