Files
2026-07-20 10:18:56 +08:00

149 lines
4.9 KiB
JavaScript

/**
* 全局防浏览器自动填充登录账号/密码进业务输入框.
* 跳过真正的登录/改密字段;对划转数量等易中招框用 readonly 到聚焦.
*/
(function () {
"use strict";
var GUARD_ATTRS = {
autocomplete: "off",
autocorrect: "off",
autocapitalize: "off",
spellcheck: "false",
"data-lpignore": "true",
"data-1p-ignore": "true",
"data-bwignore": "true",
"data-form-type": "other",
};
function looksLikeUsername(v) {
return /^[a-z][a-z0-9._-]{1,31}$/i.test(String(v || "").trim());
}
function isAuthField(el) {
if (!el || !el.getAttribute) return true;
var t = String(el.type || "").toLowerCase();
if (t === "hidden" || t === "checkbox" || t === "radio" || t === "file" || t === "submit" || t === "button") {
return true;
}
if (el.getAttribute("aria-hidden") === "true") return true;
if (el.tabIndex === -1 && String(el.getAttribute("autocomplete") || "").toLowerCase() === "username") {
return true; // 诱饵账号框
}
var idName = String(el.id || "") + " " + String(el.name || "");
if (/^(pwd-|hub-pwd-|login-)/i.test(String(el.id || ""))) return true;
if (el.closest) {
if (el.closest(".login-form, #login-form, form.login-form, .password-settings, [data-password-settings]")) {
return true;
}
}
// env API Key 等 type=password 仍要防登录密码灌入,不在此跳过
if (t === "password" && /^(username|password)$/i.test(String(el.name || ""))) {
if (el.closest && el.closest("form[method='post'], form[method='POST']")) return true;
}
return false;
}
function isAmountLike(el) {
var key = String(el.id || "") + " " + String(el.name || "") + " " + String(el.placeholder || "");
return /amount|xfer|transfer|划转|数量|金额/i.test(key);
}
function wipeBad(el) {
if (!el || isAuthField(el)) return;
var v = String(el.value || "").trim();
if (!looksLikeUsername(v)) return;
var t = String(el.type || "text").toLowerCase();
if (t === "number" || isAmountLike(el) || /price|sheets|qty|sl|tp|target|entry|strike/i.test(String(el.id || "") + String(el.name || ""))) {
el.value = "";
}
}
function harden(el) {
if (!el || el.nodeType !== 1) return;
if (isAuthField(el)) return;
if (el.getAttribute("aria-hidden") === "true") return;
if (el.dataset && el.dataset.autofillGuarded === "1") {
wipeBad(el);
return;
}
if (el.dataset) el.dataset.autofillGuarded = "1";
Object.keys(GUARD_ATTRS).forEach(function (k) {
var cur = el.getAttribute(k);
if (k === "autocomplete" && cur && /^(username|current-password)/i.test(cur)) {
return;
}
// env 密钥框用 new-password 更抗登录密码灌入
if (k === "autocomplete" && String(el.type || "").toLowerCase() === "password") {
el.setAttribute(k, "new-password");
return;
}
if (!cur || cur === "on") el.setAttribute(k, GUARD_ATTRS[k]);
});
if (String(el.type || "").toLowerCase() === "password" || isAmountLike(el)) {
el.setAttribute("readonly", "readonly");
el.addEventListener("focus", function () {
el.removeAttribute("readonly");
});
el.addEventListener("blur", function () {
if (!el.value) el.setAttribute("readonly", "readonly");
});
}
wipeBad(el);
setTimeout(function () {
wipeBad(el);
}, 250);
setTimeout(function () {
wipeBad(el);
}, 900);
setTimeout(function () {
wipeBad(el);
}, 2000);
}
function scan(root) {
var scope = root && root.querySelectorAll ? root : document;
var list = scope.querySelectorAll(
'input[type="text"], input[type="number"], input[type="search"], input[type="url"], input[type="email"], input[type="tel"], input[type="password"], input:not([type]), textarea'
);
for (var i = 0; i < list.length; i++) harden(list[i]);
}
function boot() {
scan(document);
if (typeof MutationObserver === "undefined") return;
var obs = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var m = mutations[i];
if (m.type === "childList") {
for (var j = 0; j < m.addedNodes.length; j++) {
var n = m.addedNodes[j];
if (!n || n.nodeType !== 1) continue;
if (n.matches && n.matches("input, textarea")) harden(n);
else if (n.querySelectorAll) scan(n);
}
} else if (m.type === "attributes" && m.target) {
harden(m.target);
}
}
});
obs.observe(document.documentElement, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ["value"],
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", boot);
} else {
boot();
}
window.cmAutofillGuardScan = scan;
})();