Inject stylus in Shadow DOM

inject stylus to shadowRoot

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name            Inject stylus in Shadow DOM
// @namespace       https://greasyfork.org/users/821661
// @match           https://www.example.com/*
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_registerMenuCommand
// @run-at          document-start
// @version         0.1.2b
// @author          hdyzen
// @description     inject stylus to shadowRoot
// @license         MIT
// ==/UserScript==
'use strict';

// State of liveUpdate
const liveUpdate = GM_getValue('liveUpdate', true);

// Store the shadowRoots
const shadowRoots = new Set();

// Styles of stylus
const styles = new Map();

// Stylus selector
const stylusSelector = 'style.stylus[id^="stylus-"]:not(#stylus-transition-patch)';

// Menu for enable/disable live update
GM_registerMenuCommand(`LIVE UPDATE: ${liveUpdate ? 'ON' : 'OFF'}`, e => {
    GM_setValue('liveUpdate', !liveUpdate);
    location.reload();
});

// Store the original attachShadow
const originalAttachShadow = Element.prototype.attachShadow;

// Patch attachShadow and add stylus
Element.prototype.attachShadow = function () {
    const shadowRoot = originalAttachShadow.apply(this, arguments);

    styles.forEach(style => {
        const clonedStyle = style.cloneNode(true);
        shadowRoot.appendChild(clonedStyle);
    });

    shadowRoots.add(shadowRoot);
    return shadowRoot;
};

// Handle styles added/removed/modified
function handleStyle(elStyle, remove = false) {
    const id = elStyle.id;

    if (!remove) styles.set(id, elStyle);

    shadowRoots.forEach(shadow => {
        const oldStyle = shadow.querySelector(`#${id}`);

        // Removed
        if (oldStyle && remove) {
            styles.delete(id);
            oldStyle.remove();
            return;
        }

        // Modified & Added
        if (oldStyle) {
            oldStyle.innerHTML = elStyle.innerHTML;
        } else {
            const clonedStyle = elStyle.cloneNode(true);
            shadow.appendChild(clonedStyle);
        }
    });
}

// Observe changes
const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
        // Added styles of stylus
        mutation.addedNodes.forEach(addedNode => {
            if (addedNode.nodeName === 'STYLE' && addedNode.matches(stylusSelector)) {
                // console.log('Style added with stylus:', addedNode); // Log added styles of stylus
                handleStyle(addedNode);
            }
        });
        // Removed styles of stylus
        mutation.removedNodes.forEach(removedNode => {
            if (removedNode.nodeName === 'STYLE' && removedNode.matches(stylusSelector)) {
                // console.log('Style removed of stylus', removedNode); // Log removed styles of stylus
                handleStyle(removedNode, true);
            }
        });
        // Change within the stylus
        if (mutation.type === 'characterData' && mutation.target.parentElement.matches(stylusSelector)) {
            // console.log('Change within the stylus/style:', mutation.target.parentElement); // Log changes in styles of stylus
            handleStyle(mutation.target.parentNode);
        }
    });
});

if (liveUpdate) {
    // Start observing
    observer.observe(document.documentElement, {
        childList: true,
        subtree: true,
        characterData: true,
    });
}