Reddit Login Autofill helper

Make login button go to /login page, then create autofill-compliant dummy form to let Firefox autofill credentials.

// ==UserScript==
// @name         Reddit Login Autofill helper
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Make login button go to /login page, then create autofill-compliant dummy form to let Firefox autofill credentials.
// @author       bedweb
// @match        https://www.reddit.com/*
// @grant        none
// @license      GNU GPLv3
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        loginUrl: 'https://www.reddit.com/login',
        mobileBreakpoint: 768,
        autofillDelay: 1500,
        styles: {
            dummyForm: {
                position: 'fixed',
                top: '0px',
                left: '0px',
                //width: '1px',
                //height: '1px',
                //overflow: 'hidden',
                //'z-index':'999',
                opacity: '1'
            }
        }
    };

    /* Shared Utilities */
    const DomUtils = {
        waitForElement: (selector) => new Promise(resolve => {
            if (document.querySelector(selector)) return resolve();
            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve();
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });
        }),

        traverseShadow: (root = document, selector) => {
            const walker = document.createTreeWalker(
                root,
                NodeFilter.SHOW_ELEMENT,
                { acceptNode: node => node.shadowRoot ?
                    NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP }
            );

            while (walker.nextNode()) {
                const shadowRoot = walker.currentNode.shadowRoot;
                const element = shadowRoot.querySelector(selector);
                if (element) return element;
                const nested = DomUtils.traverseShadow(shadowRoot, selector);
                if (nested) return nested;
            }
            return null;
        }
    };
function cloneToLoseEventsAndIntercepts(el){
    const elClone = el.cloneNode(true);
    el.parentNode.replaceChild(elClone, el);
}
    /* Login Button Modification */
    const ButtonModifier = {
        init: function() {
            if (window.location.href.startsWith(CONFIG.loginUrl)) return;

            const observer = new MutationObserver(() => {
                const loginButton = DomUtils.traverseShadow(document, '[href*="login"]');
                if (loginButton && !loginButton.href) this.convertToLink(loginButton);
            });
            document.querySelectorAll('faceplate-tracker[noun="login"]').forEach(tracker => {
                cloneToLoseEventsAndIntercepts(tracker);
            });
            observer.observe(document.documentElement, {
                childList: true,
                subtree: true
            });
        },

        convertToLink: function(element) {
            const link = document.createElement('a');
            link.href = CONFIG.loginUrl;
            link.innerHTML = element.innerHTML;
            link.className = element.className;
            link.style.cssText = element.style.cssText;
            element.parentNode.replaceChild(link, element);


        }
    };

    /* Autofill Manager */
    const AutofillManager = {
        init: function() {
            if (!window.location.href.startsWith(CONFIG.loginUrl)) return;

            this.createDummyForm();
            this.setupObservers();
            this.triggerAutofill();
        },

        createDummyForm: function() {
            const form = document.createElement('form');
            Object.assign(form.style, CONFIG.styles.dummyForm);

            this.dummyUser = this.createInput('text', 'username');
            this.dummyPass = this.createInput('password', 'current-password');
            form.appendChild(this.dummyUser);
            form.appendChild(this.dummyPass);
            document.querySelector(".bg-ui-modalbackground").appendChild(form);
        },

        createInput: (type, autocomplete) => {
            const input = document.createElement('input');
            input.id = "dummy" + autocomplete;
            input.type = type;
            input.autocomplete = autocomplete;
            //input.autocapitalize = 'none';
            input.style = 'background-color:white;color:black';
            return input;
        },

        setupObservers: function() {
            this.observer = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    //if (mutation.attributeName === 'value') {
                        this.transferValues();
                    //}
                });
            });

            this.observer.observe(this.dummyUser, { attributes: true });
            this.observer.observe(this.dummyPass, { attributes: true });
        },

        transferValues: function() {
            const transferField = (dummy, name) => {
                const real = DomUtils.traverseShadow(document, `input[name="${name}"]`);
                if (real && dummy.value) {
                    real.value = dummy.value;
                    real.dispatchEvent(new Event('input', { bubbles: true }));
                    real.dispatchEvent(new Event('change', { bubbles: true }));
                }
            };

            transferField(this.dummyUser, 'username');
            transferField(this.dummyPass, 'password');
        },

        triggerAutofill: function() {
            const isMobile = window.innerWidth <= CONFIG.mobileBreakpoint;
            setTimeout(() => {
                this.dummyUser.focus();
                setTimeout(() => this.dummyPass.focus(), isMobile ? 100 : 50);
            }, isMobile ? 1000 : 500);
        }
    };

    /* Main Execution Flow */
    window.addEventListener('load', () => {
        const isMobile = window.innerWidth <= CONFIG.mobileBreakpoint;
        setTimeout(() => {
            ButtonModifier.init();
            AutofillManager.init();
        }, isMobile ? 1000 : 500);
    }, { once: true });

})();