您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Fixes Bitwarden autofill on Zyxel router login pages (EX7710-B0, EX5601-T0, and similar).
// ==UserScript== // @name Zyxel Router: Bitwarden Autofill Fix // @namespace io.zyxel.autofill // @version 1.0.2 // @description Fixes Bitwarden autofill on Zyxel router login pages (EX7710-B0, EX5601-T0, and similar). // Prevents the router UI from deleting the password field and ensures the correct value is submitted. // @match http://192.168.1.1/* // @match https://192.168.1.1/* // @run-at document-idle // @noframes // @grant none // @license GPL-2.0-only; https://www.gnu.org/licenses/old-licenses/gpl-2.0.txt // ==/UserScript== (function () { 'use strict'; // CSS selectors for important elements on Zyxel login page const SEL = { user: '#username', passCandidates: '#userpassword, .maskPassword, .unmaskPassword', passMask: '.maskPassword', passUnmask: '.unmaskPassword', eye: '#userpassword_maskCheck', // toggle mask/unmask checkbox loginBtn: '#loginBtn', form: 'form.form-login' }; // Tracks latest captured username and password const state = { user: '', pw: '' }; // Helper shortcuts const $ = (s, root = document) => root.querySelector(s); const $$ = (s, root = document) => Array.from(root.querySelectorAll(s)); const fire = (el, type, opts = {}) => el && el.dispatchEvent(new Event(type, { bubbles: true, cancelable: true, ...opts })); const keyup = (el) => el && el.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, cancelable: true })); // Sets element value and fires events so the page detects the change const setVal = (el, v) => { if (!el || el.value === v) return; el.value = v; fire(el, 'input'); keyup(el); fire(el, 'change'); }; // Returns all candidate password fields currently in DOM const passInputs = () => $$(SEL.passCandidates).filter((el) => el instanceof HTMLInputElement); // Makes sure the unmasked password field is active instead of masked const preferUnmasked = () => { const eye = $(SEL.eye); const m = $(SEL.passMask); const u = $(SEL.passUnmask); if (!eye || !m || !u) return; const maskedVisible = m.style.display !== 'none'; const unmaskedVisible = u.style.display !== 'none'; if (maskedVisible && !unmaskedVisible) eye.click(); // simulate clicking eye icon }; // Capture value from username/password once and mark element to avoid duplicate binding const hookCaptureOnce = (el, isUser) => { if (!el || el.__zyCap) return; el.__zyCap = true; const update = () => { const v = el.value || ''; if (!v) return; if (isUser) state.user = v; else state.pw = v; }; el.addEventListener('input', update, true); el.addEventListener('change', update, true); update(); }; // Attach capture hooks to username and all password fields const wireCapture = () => { hookCaptureOnce($(SEL.user), true); passInputs().forEach((el) => hookCaptureOnce(el, false)); }; // Reapply captured values into fields (Bitwarden fix) const applyOnce = () => { const uEl = $(SEL.user); if (state.user && uEl) setVal(uEl, state.user); if (state.pw) passInputs().forEach((el) => setVal(el, state.pw)); }; // Ensure values are re-applied just before submit (button click, Enter key, or form submit) const installPreSubmit = () => { const pre = () => { preferUnmasked(); applyOnce(); }; // Clicks on login button document.addEventListener('pointerdown', (e) => { const btn = e.target.closest && e.target.closest(SEL.loginBtn); if (btn) pre(); }, true); document.addEventListener('mousedown', (e) => { const btn = e.target.closest && e.target.closest(SEL.loginBtn); if (btn) pre(); }, true); // Pressing Enter on form document.addEventListener('keydown', (e) => { if (e.key !== 'Enter') return; if ($(SEL.form)) pre(); }, true); // Submitting the form document.addEventListener('submit', (e) => { if (e.target.matches && e.target.matches(SEL.form)) pre(); }, true); }; // Continuously re-assert values against Zyxel's aggressive DOM rewrites const installGlobalCapture = () => { // Keeps applying values for a duration after an event const armKeepAlive = (() => { let rafId = null; let until = 0; const tick = () => { applyOnce(); if (performance.now() < until) { rafId = requestAnimationFrame(tick); } else { rafId = null; } }; return (ms) => { until = performance.now() + ms; if (!rafId) rafId = requestAnimationFrame(tick); }; })(); // Capture user typing into fields document.addEventListener('input', (e) => { const t = e.target; if (!(t instanceof HTMLInputElement)) return; if (t.matches(SEL.user) && t.value) { state.user = t.value; armKeepAlive(5000); } if (t.matches(SEL.passCandidates) && t.value) { state.pw = t.value; armKeepAlive(5000); } }, true); // Capture change events too document.addEventListener('change', (e) => { const t = e.target; if (!(t instanceof HTMLInputElement)) return; if (t.matches(SEL.user) && t.value) state.user = t.value; if (t.matches(SEL.passCandidates) && t.value) state.pw = t.value; }, true); // Watch DOM mutations (Zyxel rebuilds inputs often) const mo = new MutationObserver(() => { preferUnmasked(); wireCapture(); if (state.user || state.pw) armKeepAlive(1500); }); mo.observe(document.documentElement, { childList: true, subtree: true }); }; // Entry point: prepare fields and start listeners const init = () => { preferUnmasked(); wireCapture(); installGlobalCapture(); installPreSubmit(); }; // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init, { once: true }); } else { init(); } })();