File size: 6,349 Bytes
b931367
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
(function() {

    // Console 日志
    console.info('全局异常捕获脚本已加载,开始监听错误...');

    // 1. 初始化错误显示容器 (保留原有的 Visual Toast 功能)
    const errorContainer = document.createElement('div');
    errorContainer.id = 'global-error';
    // 设置样式:全屏、黑色背景、红色文字、置顶
    errorContainer.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        background-color: rgba(0, 0, 0, 0.9);
        color: #ff5555;
        z-index: 999999;
        overflow-y: auto;
        padding: 20px;
        font-family: 'Consolas', 'Monaco', monospace;
        font-size: 14px;
        white-space: pre-wrap;
        display: none; /* 默认隐藏,有错误时显示 */
        box-sizing: border-box;
    `;
    
    // 添加标题和关闭提示
    const header = document.createElement('div');
    header.style.borderBottom = '1px solid #555';
    header.style.paddingBottom = '10px';
    header.style.marginBottom = '10px';
    header.innerHTML = '<h2 style="margin:0; color:#fff;">🚨 全局异常捕获监控</h2><small style="color:#aaa">点击页面任意处可临时关闭蒙层</small>';
    errorContainer.appendChild(header);
    
    // 点击关闭功能
    errorContainer.addEventListener('click', () => {
        errorContainer.style.display = 'none';
    });

    // 确保 DOM 加载后插入 body,或者如果 body 存在直接插入
    if (document.body) {
        document.body.appendChild(errorContainer);
    } else {
        window.addEventListener('DOMContentLoaded', () => document.body.appendChild(errorContainer));
    }

    // 2. 核心处理函数:既显示在屏幕上,也发送给父窗口
    function handleError(type, details) {
        // A. 显示在屏幕上 (Visual Toast)
        logErrorToScreen(type, details);

        // B. 发送给父窗口 (iframe 通信)
        postErrorToParent(type, details);
    }

    // 显示在屏幕上的具体实现
    function logErrorToScreen(type, details) {
        // 显示蒙层
        errorContainer.style.display = 'block';

        const reportItem = document.createElement('div');
        reportItem.style.marginBottom = '20px';
        reportItem.style.borderBottom = '1px dashed #444';
        reportItem.style.paddingBottom = '10px';

        // 格式化时间
        const time = new Date().toLocaleTimeString();
        
        // 构建 HTML 内容
        reportItem.innerHTML = `
            <div style="color: #fff; font-weight: bold;">[${time}] <span style="background:#b00; padding:2px 5px; border-radius:3px;">${type}</span></div>
            <div style="margin-top:5px; color: #ffaaaa;">${details.message || '无错误信息'}</div>
            ${details.filename ? `<div style="color: #888;">Location: ${details.filename}:${details.lineno}:${details.colno}</div>` : ''}
            ${details.stack ? `<pre style="color: #aaa; background: #111; padding: 10px; overflow-x: auto; margin-top:5px;">${details.stack}</pre>` : ''}
            ${details.selector ? `<div style="color: #888;">Element: &lt;${details.selector}&gt; (src: ${details.src})</div>` : ''}
        `;
        
        // 插入到标题之后,内容的顶部
        header.after(reportItem);
    }

    // 发送给父窗口的具体实现
    function postErrorToParent(type, details) {
        if (window.parent && window.parent !== window) {
            const msg = {
                type: 'iframeError',
                payload: {
                    errorType: type,
                    message: details.message || 'Unknown Error',
                    stack: details.stack || '',
                    filename: details.filename || '',
                    lineno: details.lineno || 0,
                    colno: details.colno || 0,
                    selector: details.selector || '',
                    src: details.src || '',
                    href: window.location.href,
                    timestamp: new Date().toISOString()
                }
            };
            
            // 使用 '*' 允许跨域传递,或者根据需要指定特定 origin
            window.parent.postMessage(msg, '*');
            console.info('[CatchError] Posted error to parent:', payload);
        }
    }

    // 3. 监听器 A: 捕获 JS 运行时错误 + 资源加载错误 (img/script)
    // 注意:第三个参数 true (useCapture) 是捕获资源错误的关键
    window.addEventListener('error', (event) => {
        // 情况 1: 资源加载错误 (img, script, link)
        // 资源错误没有冒泡,但在捕获阶段可以拦截,且 target 是 DOM 元素
        if (event.target && (event.target instanceof HTMLElement)) {
            const target = event.target;
            handleError('Resource Error', {
                message: `资源加载失败 (${target.tagName})`,
                selector: target.tagName.toLowerCase(),
                src: target.src || target.href || 'unknown source',
                stack: 'N/A (Network Error)'
            });
        } 
        // 情况 2: 普通 JS 运行时错误
        else {
            handleError('Runtime Error', {
                message: event.message,
                filename: event.filename,
                lineno: event.lineno,
                colno: event.colno,
                stack: event.error ? event.error.stack : '无堆栈信息'
            });
        }
    }, true); // useCapture = true

    // 4. 监听器 B: 捕获未处理的 Promise Rejection
    window.addEventListener('unhandledrejection', (event) => {
        // 提取错误原因
        let reason = event.reason;
        let stack = '无堆栈信息';
        let message = '';

        if (reason instanceof Error) {
            message = reason.message;
            stack = reason.stack;
        } else {
            // 如果 reject 的不是 Error 对象(例如 reject("foo"))
            message = typeof reason === 'object' ? JSON.stringify(reason) : String(reason);
        }

        handleError('Unhandled Promise', {
            message: `Promise 被 Reject 且未被 Catch: ${message}`,
            stack: stack
        });
    });

    console.info('全局异常捕获脚本已初始化完成 (Visual + PostMessage)。');

})();