Greasy Fork is available in English.

星号密码显示助手

当鼠标停留在密码框时显示星号密码。再也不担心忘记密码和输错密码了。

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name              星号密码显示助手
// @namespace         https://github.com/syhyz1990/starpassword
// @version           1.0.8
// @author            YouXiaoHou
// @description       当鼠标停留在密码框时显示星号密码。再也不担心忘记密码和输错密码了。
// @match             *://*/*
// @license           MIT
// @homepage          https://www.youxiaohou.com/tool/install-starpassword.html
// @supportURL        https://github.com/syhyz1990/starpassword
// @require           https://registry.npmmirror.com/sweetalert2/10.16.6/files/dist/sweetalert2.min.js
// @resource          swalStyle https://registry.npmmirror.com/sweetalert2/10.16.6/files/dist/sweetalert2.min.css
// @run-at            document-start
// @grant             GM_setValue
// @grant             GM_getValue
// @grant             GM_registerMenuCommand
// @grant             GM_getResourceText
// @icon              data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjggMTI4Ij48cGF0aCBkPSJNMTAzLjkgNTEuMkg0Ni4xYy0xLjIgMC0yLjEtLjktMi4xLTIuMVYyOS44YzAtNy4xIDguOS0xNC45IDIwLTE0LjkgMTEgMCAyMCA3LjggMjAgMTQuOXY2LjRjLjYgMy42IDMuOCA2LjQgNy43IDYuNHM3LjItMi44IDcuNy02LjRoLjF2LTYuNEM5OS41IDEzLjQgODMuNiAwIDY0IDBTMjguNSAxMy40IDI4LjUgMjkuOFY0OWMwIDEuMi0uOSAyLjEtMi4xIDIuMUgyNGMtNy40IDAtMTMuMyA1LjctMTMuMyAxMi44djUxLjJjMCA3LjEgNiAxMi44IDEzLjMgMTIuOGg4MGM3LjQgMCAxMy4zLTUuNyAxMy4zLTEyLjh2LTUxYy0uMS03LjEtNi4xLTEyLjktMTMuNC0xMi45eiIgZmlsbD0iIzQ0NCIvPjxwYXRoIGQ9Ik02Ni44IDY2LjRsNCAxMi40Yy40IDEuMiAxLjUgMiAyLjggMmgxM2MyLjkgMCA0LjEgMy43IDEuNyA1LjRsLTEwLjUgNy42Yy0xIC44LTEuNSAyLjEtMS4xIDMuM2w0IDEyLjRjLjkgMi43LTIuMiA1LTQuNiAzLjNsLTEwLjUtNy42Yy0xLS44LTIuNC0uOC0zLjUgMGwtMTAuNSA3LjZjLTIuMyAxLjctNS41LS42LTQuNi0zLjNsNC0xMi40Yy40LTEuMiAwLTIuNi0xLjEtMy4zbC0xMC41LTcuNmMtMi4zLTEuNy0xLjEtNS40IDEuNy01LjRoMTNjMS4zIDAgMi40LS44IDIuOC0ybDQtMTIuNGMxLjItMi43IDUtMi43IDUuOSAweiIgZmlsbD0iI2ZmZiIvPjwvc3ZnPg==
// ==/UserScript==

(function () {
    'use strict';

    let MutationObserverNew = null;

    let util = {
        getValue(name) {
            return GM_getValue(name);
        },

        setValue(name, value) {
            GM_setValue(name, value);
        },

        addStyle(id, tag, css) {
            tag = tag || 'style';
            let doc = document, styleDom = doc.getElementById(id);
            if (styleDom) return;
            let style = doc.createElement(tag);
            style.rel = 'stylesheet';
            style.id = id;
            tag === 'style' ? style.innerHTML = css : style.href = css;
            document.head.appendChild(style);
        },
    };

    let main = {
        /**
         * 配置默认值
         */
        initValue() {
            let value = [{
                name: 'setting_wait_time',
                value: 300
            }, {
                name: 'setting_show_method',
                value: 0
            }];

            value.forEach((v) => {
                util.getValue(v.name) === undefined && util.setValue(v.name, v.value);
            });
        },

        /**
         * 保存原始的MutationObserver,防止被覆盖
         */
        observer() {
            MutationObserverNew = window.MutationObserver;
        },

        registerMenuCommand() {
            GM_registerMenuCommand('⚙️ 设置', () => {
                let html = `<div style="font-size: 1em;">
                              <label class="starpassword-setting-label">显示密码方式
                              <select id="S-starpassword-show-method" class="starpassword-select">
                                <option value="0" ${util.getValue('setting_show_method') == 0 ? 'selected' : ''}>鼠标悬浮在密码框上时</option>
                                <option value="1" ${util.getValue('setting_show_method') == 1 ? 'selected' : ''}>双击密码框时</option>
                                <option value="2" ${util.getValue('setting_show_method') == 2 ? 'selected' : ''}>单击密码框时</option>
                                <option value="3" ${util.getValue('setting_show_method') == 3 ? 'selected' : ''}>按下Ctrl并单击密码框时</option>
                              </select>
                              </label>
                              <label class="starpassword-setting-label"><span>等待时间 <span id="S-starpassword-wait-time-label">(${util.getValue('setting_wait_time')}毫秒)</span></span><input type="range" id="S-starpassword-wait-time" min="0" max="1000" step="50" value="${util.getValue('setting_wait_time')}" style="width: 180px;"></label>
                            </div>`;
                Swal.fire({
                    title: '星号密码显示助手',
                    html,
                    icon: 'info',
                    showCloseButton: true,
                    confirmButtonText: '保存',
                    footer: '<div style="text-align: center;font-size: 1em;">Powered by <a href="https://www.youxiaohou.com">油小猴</a></div>',
                    customClass: {
                        container: 'starpassword-container',
                        popup: 'starpassword-popup'
                    }
                }).then((res) => {
                    res.isConfirmed && history.go(0);
                });

                document.getElementById('S-starpassword-show-method').addEventListener('change', (e) => {
                    util.setValue('setting_show_method', e.currentTarget.value);
                });
                document.getElementById('S-starpassword-wait-time').addEventListener('change', (e) => {
                    util.setValue('setting_wait_time', e.target.value);
                    document.getElementById('S-starpassword-wait-time-label').innerText = `(${e.target.value}毫秒)`;
                });
            });
        },

        addPluginStyle() {
            let style = `
                .starpassword-container { z-index: 999999!important }
                .starpassword-popup { font-size: 14px!important }
                .starpassword-setting-label { display:flex; align-items: center; justify-content: space-between; padding-top: 18px; }
                .starpassword-select { background: #f3fcff; height: 28px; width: 180px; line-height: 28px; border: 1px solid #9bc0dd; border-radius: 2px;}
            `;

            if (document.head) {
                util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
                util.addStyle('starpassword-style', 'style', style);
            }

            const headObserver = new MutationObserver(() => {
                util.addStyle('swal-pub-style', 'style', GM_getResourceText('swalStyle'));
                util.addStyle('starpassword-style', 'style', style);
            });
            headObserver.observe(document.head, {childList: true, subtree: true});
        },

        isTopWindow() {
            return window.self === window.top;
        },

        showPassword() {
            const KEY_ENTER = 13;
            const KEY_CTRL = 17;
            let behave = util.getValue('setting_show_method');
            let wait = util.getValue('setting_wait_time');

            function mouseOver(tar) {
                tar.addEventListener('mouseover', () => {
                    tar.isMouseOver = true;
                    setTimeout(() => {
                        if (tar.isMouseOver) {
                            tar.type = 'text';
                        }
                    }, wait);
                }, false);

                tar.addEventListener('mouseout', () => {
                    tar.isMouseOver = false;
                    tar.type = 'password';
                }, false);

                tar.addEventListener('blur', () => {
                    tar.type = 'password';
                }, false);

                tar.addEventListener('keydown', e => {
                    if (e.keyCode === KEY_ENTER) {
                        tar.type = 'password';
                    }
                }, false);
            }

            function mouseDblClick(tar) {
                tar.addEventListener('dblclick', () => {
                    tar.type = tar.type === 'password' ? 'text' : 'password';
                }, false);

                tar.addEventListener('blur', () => {
                    tar.type = 'password';
                }, false);

                tar.addEventListener('keydown', e => {
                    if (e.keyCode === KEY_ENTER) {
                        tar.type = 'password';
                    }
                }, false);
            }

            function mouseFocus(tar) {
                tar.addEventListener('focus', () => {
                    tar.type = 'text';
                }, false);

                tar.addEventListener('blur', () => {
                    tar.type = 'password';
                }, false);

                tar.addEventListener('keydown', e => {
                    if (e.keyCode === KEY_ENTER) {
                        tar.type = 'password';
                    }
                }, false);
            }

            function ctrlKeyShift(tar) {
                let isHide = true;
                let notPressCtrl = true;
                let onlyCtrl = true;

                tar.addEventListener('blur', () => {
                    tar.type = 'password';
                    isHide = true;
                    notPressCtrl = true;
                    onlyCtrl = true;
                }, false);

                tar.addEventListener('keyup', e => {
                    if (e.keyCode === KEY_CTRL) {
                        if (onlyCtrl) {
                            isHide = !isHide;
                        } else {
                            isHide = false;
                        }

                        if (isHide) {
                            tar.type = 'password';
                        } else {
                            tar.type = 'text';
                        }
                        notPressCtrl = true;
                        onlyCtrl = true;
                    }
                }, false);

                tar.addEventListener('keydown', e => {
                    if (e.keyCode === KEY_ENTER) {
                        tar.type = 'password';
                        isHide = true;
                        notPressCtrl = true;
                        onlyCtrl = true;
                    } else if (e.keyCode === KEY_CTRL) {
                        if (notPressCtrl) {
                            tar.type = 'text';
                            notPressCtrl = false;
                            onlyCtrl = true;
                        }
                    } else {
                        onlyCtrl = notPressCtrl;
                    }
                }, false);
            }

            const actionsArr = [mouseOver, mouseDblClick, mouseFocus, ctrlKeyShift];
            const doc = window.document;
            const modified = new WeakSet();

            function modifyAllInputs() {
                const passwordInputs = doc.querySelectorAll('input[type=password]');
                passwordInputs.forEach(input => {
                    if (!modified.has(input)) {
                        actionsArr[behave](input);
                        modified.add(input);
                    }
                });
            }

            function modifyWeb() {
                modifyAllInputs();
            }

            modifyWeb();

            const docObserver = new MutationObserverNew(() => {
                // NOTE: Despite we can recursively check element from addNodes.
                // Benchmark shows that it is much fast to just use `querySelectorAll` to find password inputs
                modifyWeb();
            });

            docObserver.observe(doc.documentElement, {
                childList: true,
                subtree: true,
                // Some website add input with text type at first, then change its type to password.
                attributes: true,
                attributeFilter: ['type']
            });
        },

        init() {
            this.observer();
            this.initValue();
            this.showPassword();
            this.addPluginStyle();
            this.isTopWindow() && this.registerMenuCommand();
        }
    };
    main.init();
})();