// ==UserScript==
// @name Via 浏览器 404 和网络连接失败页面美化
// @namespace https://viayoo.com/
// @version 1.11
// @description 为 Via 浏览器错误页面(404 Not Found 和网络连接失败)注入美化,404 页面采用幻境风格,网络连接失败页面采用生命美学与科幻风格,支持日夜模式自动切换
// @author 是小白呀 & Grok
// @match *://*/*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 提取错误页面中的 URL(用于网络连接失败页面)
function extractErrorUrl() {
const bodyText = document.body ? document.body.innerText : '';
const urlMatch = bodyText.match(/(https?:\/\/[^\s]+)/i);
return urlMatch ? urlMatch[0] : '未知地址';
}
// 提取错误代码和服务器信息(用于 404 页面)
function extractErrorInfo() {
const bodyText = document.body ? document.body.innerText : '';
const errorCodeMatch = bodyText.match(/net::ERR_[A-Z_]+/) || document.title.match(/404 Not Found/) || ['404 Not Found'];
const serverMatch = bodyText.match(/nginx/i) ? 'nginx' : '';
return {
errorCode: errorCodeMatch[0],
server: serverMatch
};
}
// 检查是否为 404 错误页面(严格匹配“404 Not Found”和“nginx”)
function checkFor404Page() {
let has404 = false;
let hasNginx = false;
// 检查标题
if (/404 Not Found/i.test(document.title)) {
has404 = true;
}
// 检查 body 内容
if (document.body) {
const text = document.body.innerText || '';
if (/404 Not Found/i.test(text)) {
has404 = true;
}
if (/nginx/i.test(text)) {
hasNginx = true;
}
}
// 必须同时满足“404 Not Found”和“nginx”
return has404 && hasNginx;
}
// 检查是否为网络连接失败页面
function checkForConnectionErrorPage() {
// 排除 404 页面
if (checkFor404Page()) {
return false;
}
// 快速检查标题
if (/无法打开|Error/i.test(document.title)) {
return true;
}
// 检查 body 内容
if (document.body) {
const text = document.body.innerText || '';
if (/net::ERR_|网页无法打开|无法连接|ERR_NAME_NOT_RESOLVED/i.test(text)) {
return true;
}
// 空页面
if (document.body.innerHTML.trim() === '') {
return true;
}
}
return false;
}
// 替换 404 错误页面(幻境风格)
function replace404Page() {
const { errorCode, server } = extractErrorInfo();
document.documentElement.innerHTML = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>迷失幻境</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
position: relative;
margin: 0;
}
/* 夜间模式(幻境风格) */
@media (prefers-color-scheme: dark) {
body {
background: radial-gradient(ellipse at center, #1a1a3a 0%, #0a0a1a 70%, #000000 100%);
}
.fog-layer {
background: linear-gradient(45deg, rgba(74, 0, 224, 0.1), rgba(0, 210, 255, 0.1));
animation: fogFlow 20s infinite linear;
}
.mist {
background: radial-gradient(circle, rgba(74, 0, 224, 0.2) 0%, rgba(74, 0, 224, 0) 70%);
filter: blur(30px);
}
.fragment {
border: 1px solid rgba(100, 100, 255, 0.3);
box-shadow: 0 0 20px rgba(74, 0, 224, 0.4);
}
.star-dust {
background: #b8b8ff;
}
.ripple {
border: 2px solid rgba(100, 100, 255, 0.2);
box-shadow: 0 0 20px rgba(74, 0, 224, 0.3);
}
.btn-particle {
background: #b8b8ff;
}
h1, .icon, .error-code, .server {
background: linear-gradient(135deg, #8e2de2, #4a00e0, #00d2ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 15px rgba(74, 0, 224, 0.5);
}
p {
color: #b8b8ff;
text-shadow: 0 0 10px rgba(74, 0, 224, 0.3);
}
.btn {
background: linear-gradient(135deg, #4a00e0, #8e2de2);
box-shadow: 0 4px 15px rgba(74, 0, 224, 0.4);
}
.btn:hover {
box-shadow: 0 8px 20px rgba(74, 0, 224, 0.6);
}
}
/* 白天模式(幻境风格) */
@media (prefers-color-scheme: light) {
body {
background: radial-gradient(ellipse at center, #e6e6fa 0%, #f0f0ff 70%, #ffffff 100%);
}
.fog-layer {
background: linear-gradient(45deg, rgba(79, 209, 197, 0.1), rgba(246, 224, 94, 0.1));
animation: fogFlow 20s infinite linear;
}
.mist {
background: radial-gradient(circle, rgba(246, 224, 94, 0.3) 0%, rgba(246, 224, 94, 0) 70%);
filter: blur(25px);
}
.fragment {
border: 1px solid rgba(79, 209, 197, 0.3);
box-shadow: 0 0 20px rgba(246, 224, 94, 0.4);
}
.star-dust {
background: #f6e05e;
}
.ripple {
border: 2px solid rgba(79, 209, 197, 0.2);
box-shadow: 0 0 20px rgba(246, 224, 94, 0.3);
}
.btn-particle {
background: #f6e05e;
}
h1, .icon, .error-code, .server {
background: linear-gradient(135deg, #4fd1c5, #f6e05e, #63b3ed);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 0 15px rgba(246, 224, 94, 0.5);
}
p {
color: #2d3748;
text-shadow: 0 0 10px rgba(246, 224, 94, 0.3);
}
.btn {
background: linear-gradient(135deg, #4fd1c5, #f6e05e);
box-shadow: 0 4px 15px rgba(79, 209, 197, 0.3);
}
.btn:hover {
box-shadow: 0 8px 20px rgba(79, 209, 197, 0.5);
}
}
/* 幻境元素 */
.fog-layer {
position: absolute;
width: 200%;
height: 200%;
top: -50%;
left: -50%;
opacity: 0.3;
z-index: 0;
}
.mist {
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
animation: float 12s infinite ease-in-out;
opacity: 0.5;
z-index: 1;
}
.mist:nth-child(2) {
top: 20%;
left: 30%;
animation-delay: 0s;
}
.mist:nth-child(3) {
top: 50%;
left: 70%;
width: 150px;
height: 150px;
animation-delay: 4s;
}
.mist:nth-child(4) {
top: 80%;
left: 40%;
width: 180px;
height: 180px;
animation-delay: 8s;
}
.fragment {
position: absolute;
background: transparent;
opacity: 0.6;
animation: float 15s infinite ease-in-out, rotate 10s infinite linear;
z-index: 2;
}
.fragment.triangle {
width: 0;
height: 0;
border-left: 20px solid transparent;
border-right: 20px solid transparent;
border-bottom: 35px solid rgba(255, 255, 255, 0.2);
}
.fragment.pentagon {
width: 30px;
height: 30px;
clip-path: polygon(50% 0%, 100% 38%, 82% 100%, 18% 100%, 0% 38%);
background: rgba(255, 255, 255, 0.2);
}
.star-dust {
position: absolute;
width: 2px;
height: 2px;
border-radius: 50%;
animation: drift 6s infinite ease-in-out;
animation-delay: calc(var(--delay) * 1s);
}
.ripple {
position: absolute;
width: 100px;
height: 100px;
border-radius: 50%;
opacity: 0;
animation: ripple 5s infinite ease-out;
z-index: 1;
}
.ripple:nth-child(2) { animation-delay: 0s; }
.ripple:nth-child(3) { animation-delay: 1.5s; }
.ripple:nth-child(4) { animation-delay: 3s; }
.btn-particle {
position: absolute;
width: 3px;
height: 3px;
border-radius: 50%;
animation: orbit 4s infinite ease-in-out;
z-index: 3;
}
.content {
position: relative;
z-index: 3;
text-align: center;
animation: fadeIn 1s ease-out;
}
.icon-wrapper {
position: relative;
display: inline-block;
}
.error-code, .server {
font-size: 18px;
margin: 8px 0;
animation: float 6s infinite ease-in-out;
}
.server {
font-size: 14px;
}
h1 {
font-size: 28px;
font-weight: 600;
margin: 0 0 12px;
animation: gradientShift 8s ease infinite;
background-size: 200% 200%;
}
p {
font-size: 16px;
line-height: 1.5;
margin: 0 0 20px;
}
.btn-wrapper {
position: relative;
display: inline-block;
}
.btn {
padding: 10px 24px;
font-size: 14px;
font-weight: 500;
color: #ffffff;
border: none;
border-radius: 30px;
cursor: pointer;
transition: all 0.3s ease;
animation: pulse 2s infinite;
}
.btn:hover {
transform: translateY(-3px);
}
.icon {
font-size: 48px;
margin-bottom: 16px;
animation: breathe 3s infinite ease-in-out;
}
@keyframes fogFlow {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
}
@keyframes float {
0% { transform: translate(0, 0); }
33% { transform: translate(5px, 10px); }
66% { transform: translate(-5px, -10px); }
100% { transform: translate(0, 0); }
}
@keyframes rotate {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes drift {
0% { transform: translate(0, 0); opacity: 0.3; }
50% { transform: translate(20px, -10px); opacity: 1; }
100% { transform: translate(0, 0); opacity: 0.3; }
}
@keyframes ripple {
0% { width: 100px; height: 100px; opacity: 0.5; transform: translate(-50%, -50%); }
100% { width: 300px; height: 300px; opacity: 0; transform: translate(-50%, -50%); }
}
@keyframes orbit {
0% { transform: translate(0, 0); opacity: 0.5; }
50% { transform: translate(15px, 10px); opacity: 1; }
100% { transform: translate(0, 0); opacity: 0.5; }
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes breathe {
0% { opacity: 0.7; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
100% { opacity: 0.7; transform: scale(1); }
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@media (max-width: 600px) {
h1 {
font-size: 24px;
}
p {
font-size: 14px;
}
.btn {
padding: 8px 20px;
font-size: 13px;
}
.icon {
font-size: 40px;
}
.error-code {
font-size: 16px;
}
.server {
font-size: 12px;
}
.fragment.triangle {
border-left: 15px solid transparent;
border-right: 15px solid transparent;
border-bottom: 25px solid rgba(255, 255, 255, 0.2);
}
.fragment.pentagon {
width: 20px;
height: 20px;
}
.ripple {
width: 80px;
height: 80px;
}
@keyframes ripple {
0% { width: 80px; height: 80px; opacity: 0.5; transform: translate(-50%, -50%); }
100% { width: 200px; height: 200px; opacity: 0; transform: translate(-50%, -50%); }
}
}
</style>
</head>
<body>
<!-- 流动迷雾层 -->
<div class="fog-layer"></div>
<!-- 迷雾效果 -->
<div class="mist"></div>
<div class="mist"></div>
<div class="mist"></div>
<!-- 幻境碎片 -->
<div class="fragment triangle" style="top: 15%; left: 20%; animation-delay: 0s;"></div>
<div class="fragment pentagon" style="top: 25%; left: 80%; animation-delay: 2s;"></div>
<div class="fragment triangle" style="top: 70%; left: 30%; animation-delay: 4s;"></div>
<div class="fragment pentagon" style="top: 60%; left: 85%; animation-delay: 6s;"></div>
<div class="fragment triangle" style="top: 40%; left: 10%; animation-delay: 8s;"></div>
<div class="fragment pentagon" style="top: 20%; left: 50%; animation-delay: 10s;"></div>
<div class="fragment triangle" style="top: 80%; left: 70%; animation-delay: 12s;"></div>
<!-- 中心涟漪 -->
<div class="ripple" style="top: 50%; left: 50%;"></div>
<div class="ripple" style="top: 50%; left: 50%;"></div>
<div class="ripple" style="top: 50%; left: 50%;"></div>
<!-- 中心内容 -->
<div class="content">
<div class="icon-wrapper">
<div class="icon">✨</div>
<div class="btn-particle" style="animation-delay: 0s;"></div>
<div class="btn-particle" style="animation-delay: 1s;"></div>
<div class="btn-particle" style="animation-delay: 2s;"></div>
</div>
<h1>迷失幻境</h1>
<div class="error-code">${errorCode}</div>
<div class="server">${server}</div>
<p>路径已断,迷雾笼罩,尝试返回现实吧。</p>
<div class="btn-wrapper">
<button class="btn" onclick="history.back()">返回现实</button>
<div class="btn-particle" style="animation-delay: 0s;"></div>
<div class="btn-particle" style="animation-delay: 1s;"></div>
<div class="btn-particle" style="animation-delay: 2s;"></div>
</div>
</div>
<script>
// 随机星尘粒子
function addStarDust() {
for (let i = 0; i < 50; i++) {
const dust = document.createElement('div');
dust.className = 'star-dust';
dust.style.left = Math.random() * 100 + '%';
dust.style.top = Math.random() * 100 + '%';
dust.style.setProperty('--delay', Math.random() * 4);
document.body.appendChild(dust);
}
}
addStarDust();
// 监听主题切换
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
document.querySelectorAll('.star-dust').forEach(dust => dust.remove());
addStarDust();
});
</script>
</body>
</html>
`;
}
// 替换网络连接失败页面(科幻风格,生命美学日间模式)
function replaceConnectionErrorPage(errorUrl) {
document.documentElement.innerHTML = `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>连接中断</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
color: #e0e0ff;
overflow: hidden;
position: relative;
margin: 0;
}
/* 夜间模式(科幻风格) */
@media (prefers-color-scheme: dark) {
body {
background: radial-gradient(ellipse at center, #1a1a3a 0%, #0a0a1a 70%, #000000 100%);
}
.stars {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="0.5" fill="white" opacity="0.8"/></svg>') repeat;
z-index: 1;
}
.star {
position: absolute;
width: 2px;
height: 2px;
background: white;
border-radius: 50%;
animation: twinkle 4s infinite;
animation-delay: calc(var(--delay) * 1s);
}
.big-dipper {
position: fixed;
top: 10%;
left: 5%;
width: 100px;
height: 60px;
z-index: 1;
}
.big-dipper-star {
position: absolute;
width: 3px;
height: 3px;
background: #b8b8ff;
border-radius: 50%;
animation: twinkle 3s infinite;
}
.big-dipper-star:nth-child(1) { left: 20px; top: 10px; }
.big-dipper-star:nth-child(2) { left: 35px; top: 15px; }
.big-dipper-star:nth-child(3) { left: 50px; top: 20px; }
.big-dipper-star:nth-child(4) { left: 65px; top: 25px; }
.big-dipper-star:nth-child(5) { left: 55px; top: 40px; }
.big-dipper-star:nth-child(6) { left: 45px; top: 50px; }
.big-dipper-star:nth-child(7) { left: 35px; top: 60px; }
.tao-flow {
background: radial-gradient(circle, rgba(74, 0, 224, 0.3) 0%, rgba(74, 0, 224, 0) 70%);
filter: blur(20px);
}
.container {
background: rgba(10, 10, 30, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(100, 100, 255, 0.2);
box-shadow: 0 8px 30px rgba(74, 0, 224, 0.3);
}
h1, .icon {
background: linear-gradient(135deg, #8e2de2, #4a00e0, #00d2ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
p {
color: #b8b8ff;
}
.btn {
background: linear-gradient(135deg, #4a00e0, #8e2de2);
box-shadow: 0 4px 15px rgba(74, 0, 224, 0.4);
}
.btn:hover {
box-shadow: 0 8px 20px rgba(74, 0, 224, 0.6);
}
.failed-url {
background: rgba(20, 20, 50, 0.5);
color: #b8b8ff;
}
.error-code {
color: #8888cc;
}
}
/* 白天模式(生命美学风格,基于 1.8 版本) */
@media (prefers-color-scheme: light) {
body {
background: radial-gradient(ellipse at center, #e8f5e9 0%, #f1f8e9 70%, #ffffff 100%); /* 浅绿到白色,模拟自然光晕 */
}
.stars {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="0.5" fill="#c8e6c9" opacity="0.4"/></svg>') repeat; /* 淡绿星点 */
z-index: 1;
animation: twinkle 6s infinite alternate;
}
.tao-flow {
background: radial-gradient(circle, rgba(165, 214, 167, 0.3) 0%, rgba(255, 245, 157, 0) 70%); /* 淡绿到浅金色 */
filter: blur(15px);
}
.tao-flow:nth-child(2) {
top: 20%;
left: 50%;
width: 150px;
height: 150px;
animation-delay: 3s;
transform: translateX(-50%);
}
.tao-flow:nth-child(3) {
top: 50%;
left: 50%;
width: 180px;
height: 180px;
animation-delay: 6s;
transform: translateX(-50%);
}
.container {
background: rgba(255, 255, 255, 0.85); /* 更柔和的透明效果 */
backdrop-filter: blur(12px);
border: 1px solid rgba(165, 214, 167, 0.3); /* 淡绿边框 */
box-shadow: 0 8px 30px rgba(165, 214, 167, 0.2); /* 淡绿阴影 */
}
h1, .icon {
background: linear-gradient(135deg, #a5d6a7, #fff59d, #aed581); /* 淡绿到浅金色渐变 */
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
p {
color: #37474f; /* 深灰色文字 */
}
.btn {
background: linear-gradient(135deg, #a5d6a7, #fff59d); /* 淡绿到浅金色按钮 */
box-shadow: 0 4px 15px rgba(165, 214, 167, 0.3);
}
.btn:hover {
box-shadow: 0 8px 20px rgba(165, 214, 167, 0.5);
}
.failed-url {
background: rgba(240, 244, 195, 0.5); /* 浅金色背景 */
color: #455a64; /* 深灰色文字 */
}
.error-code {
color: #78909c; /* 浅灰色 */
}
}
/* 通用样式(科幻风格) */
.tao-flow {
position: absolute;
width: 200px;
height: 200px;
border-radius: 50%;
animation: float 12s infinite ease-in-out;
opacity: 0.6;
z-index: 2;
}
.container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 3;
text-align: center;
padding: 24px;
border-radius: 16px;
width: 90%;
max-width: 400px;
animation: fadeIn 0.6s ease-out;
}
h1 {
font-size: 26px;
font-weight: 600;
margin: 0 0 12px;
animation: gradientShift 8s ease infinite;
background-size: 200% 200%;
}
p {
font-size: 15px;
line-height: 1.5;
margin: 0 0 20px;
}
.btn {
display: inline-block;
padding: 10px 24px;
font-size: 14px;
font-weight: 500;
color: #ffffff;
border: none;
border-radius: 30px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn:hover {
transform: translateY(-3px);
}
.failed-url {
font-size: 13px;
margin: 16px 0;
padding: 8px 12px;
border-radius: 8px;
word-break: break-all;
}
.error-code {
font-size: 12px;
margin-top: 12px;
}
.icon {
font-size: 48px;
margin-bottom: 16px;
animation: pulse 2s infinite;
}
@keyframes twinkle {
0% { opacity: 0.3; }
50% { opacity: 1; }
100% { opacity: 0.3; }
}
@keyframes float {
0% { transform: translate(0, 0); }
33% { transform: translate(30px, 30px); }
66% { transform: translate(-30px, -20px); }
100% { transform: translate(0, 0); }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@media (max-width: 600px) {
.container {
padding: 16px;
width: 90%;
max-width: 340px;
left: 50%;
transform: translate(-50%, -50%);
}
h1 {
font-size: 22px;
}
p {
font-size: 14px;
}
.btn {
padding: 8px 20px;
font-size: 13px;
}
.icon {
font-size: 40px;
}
.big-dipper {
width: 80px;
height: 48px;
}
}
</style>
</head>
<body>
<div class="stars"></div>
<div class="big-dipper">
<div class="big-dipper-star"></div>
<div class="big-dipper-star"></div>
<div class="big-dipper-star"></div>
<div class="big-dipper-star"></div>
<div class="big-dipper-star"></div>
<div class="big-dipper-star"></div>
<div class="big-dipper-star"></div>
</div>
<div class="tao-flow"></div>
<div class="tao-flow"></div>
<div class="container">
<div class="icon">⚡️</div>
<h1>连接中断</h1>
<p>无法感知星辰脉动,请检查网络或稍后重试。</p>
<button class="btn" onclick="window.location.reload()">重新连接</button>
<div class="failed-url">目标地址: ${errorUrl}</div>
<div class="error-code">错误代码: net::ERR_CONNECTION_ABORTED</div>
</div>
<script>
// 随机星星闪烁
function addRandomStars() {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
for (let i = 0; i < 20; i++) {
const star = document.createElement('div');
star.className = 'star';
star.style.left = Math.random() * 100 + 'vw';
star.style.top = Math.random() * 100 + 'vh';
star.style.setProperty('--delay', Math.random() * 4);
document.body.appendChild(star);
}
}
}
addRandomStars();
// 监听主题切换
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
document.querySelectorAll('.star').forEach(star => star.remove());
addRandomStars();
});
</script>
</body>
</html>
`;
}
// 快速注入机制
function initInjection() {
// 优先检查 404 页面
if (checkFor404Page()) {
replace404Page();
return true;
}
// 再检查网络连接失败页面
if (checkForConnectionErrorPage()) {
const errorUrl = extractErrorUrl();
replaceConnectionErrorPage(errorUrl);
return true;
}
return false;
}
// 立即检查
initInjection();
// 早期高频检查
let attempts = 0;
const maxAttempts = 20; // 最多检查 1 秒(50ms x 20)
const earlyCheckInterval = setInterval(() => {
if (initInjection()) {
clearInterval(earlyCheckInterval);
} else if (attempts >= maxAttempts) {
clearInterval(earlyCheckInterval);
}
attempts++;
}, 50);
// 监听 DOM 变化
const observer = new MutationObserver((mutations, obs) => {
if (initInjection()) {
obs.disconnect();
}
});
// 尽早观察 documentElement
observer.observe(document.documentElement, { childList: true, subtree: true });
// 页面加载完成时再检查
document.addEventListener('DOMContentLoaded', () => {
initInjection();
}, { once: true });
})();