Inject stylus in Shadow DOM

inject stylus to shadowRoot

// ==UserScript==
// @name            Inject stylus in Shadow DOM
// @namespace       https://greasyfork.org/users/821661
// @match           https://*/*
// @run-at          document-start
// @version         0.2
// @author          hdyzen
// @description     inject stylus to shadowRoot
// @license         MIT
// ==/UserScript==
"use strict";

const shadows = [];

const stylesMap = new Map();

const isStylus = node => node.nodeName === "STYLE" && node.id && /^stylus-[0-9]+$/.test(node.id);

const hasHostPseudoSelector = rule => rule.selectorText.startsWith(":host");

const deleteRuleWithoutHost = styleSheet => {
    const rules = styleSheet.cssRules;

    for (let i = rules.length - 1; i >= 0; i--) {
        const rule = rules[i];

        if (rule instanceof CSSStyleRule && hasHostPseudoSelector(rule)) {
            continue;
        }

        if (rule instanceof CSSMediaRule) {
            deleteRuleWithoutHost(rule);

            continue;
        }

        styleSheet.deleteRule(i);
    }
};

const cssStyleSheetToText = sheet => {
    let cssText = "";

    if (sheet.cssRules) {
        for (let rule of sheet.cssRules) {
            cssText += rule.cssText + "\n";
        }
    }

    return cssText;
};

const createSheet = text => {
    const styleSheet = new CSSStyleSheet();
    styleSheet.replaceSync(text);
    deleteRuleWithoutHost(styleSheet);

    return cssStyleSheetToText(styleSheet);
};

const addUpdateStyleToMap = styleNode => {
    const sheet = createSheet(styleNode.innerHTML);

    if (sheet) {
        stylesMap.set(styleNode.id, sheet);
    }
};

const removeStyleInMap = id => {
    stylesMap.delete(id);
};

const createStyleElement = (id, text) => {
    const style = document.createElement("style");
    style.id = id;
    style.innerHTML = text;

    return style;
};

const updateStyleInShadows = () => {
    for (const shadow of shadows) {
        for (const [key, value] of stylesMap) {
            const styleInShadow = shadow.querySelector(`#${key}`);

            if (styleInShadow && styleInShadow !== value) {
                styleInShadow.innerHTML = value;
            }
        }
    }
};

const mutationsHandler = mutations => {
    for (const mutation of mutations) {
        for (const node of mutation.addedNodes) {
            if (isStylus(node)) {
                addUpdateStyleToMap(node);
                updateStyleInShadows();
                // console.log("Style adicionado", node, stylesMap);
            }
        }

        for (const removed of mutation.removedNodes) {
            if (isStylus(removed)) {
                removeStyleInMap(removed.id);
                updateStyleInShadows();
                // console.log("Style removido", removed, stylesMap);
            }
        }

        if (mutation.type === "characterData" && isStylus(mutation.target.parentElement)) {
            addUpdateStyleToMap(mutation.target.parentElement);
            updateStyleInShadows();
            // console.log('Style alterado', mutation.target.parentElement, stylesMap);
        }
    }
};

const observer = new MutationObserver(mutationsHandler);

observer.observe(document.documentElement, { childList: true, characterData: true, subtree: true });

const originalAttachShadow = Element.prototype.attachShadow;

Element.prototype.attachShadow = function (options) {
    const shadowRoot = originalAttachShadow.call(this, options);

    shadows.push(shadowRoot);

    for (const [key, value] of stylesMap) {
        shadowRoot.appendChild(createStyleElement(key, value));
    }

    return shadowRoot;
};