解决 "Script Error" 的另类思路
本文由小芭乐发表
前端的同学如果用 window.onerror 事件做过监控,应该知道,跨域的脚本会给出 "Script Error." 提示,拿不到具体的错误信息和堆栈信息。
这里读者可以跟我一起做一个实验,来深入了解这个事情。先做一下实验准备:
app.js
创建一个 Node APP,只做静态服务器,提供两个端口用于做跨域实验。
const express = require('express');
const app = express();
app.use(express.static('./public'));
app.listen(3000);
app.listen(4000);
public/index.html
创建一个静态页面,监听 window.onerror 事件,并且输出事件的堆栈。同时分别加载两个域的 JS 文件。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Script Error Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<button id="btn-3000">3000</button>
<button id="btn-4000">4000</button>
<div>
<pre id="info"></pre>
</div>
</body>
<script>
window.addEventListener('error', evt => {
const info = evt.error ? evt.error.stack : evt.message;
document.querySelector('#info').textContent = info;
});
</script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>
</html>
public/at3000.js
创建一个在 3000 端口执行的脚本,监听 3000 按钮的点击事件,并且抛出一个异常:
const btn3k = document.querySelector('#btn-3000');
btn3k.addEventListener('click', () => {
throw new Error('Fail 3000');
});
public/at4000.js
同样的,创建一个在 4000 端口执行的脚本:
const btn4k = document.querySelector('#btn-4000');
btn4k.addEventListener('click', () => {
throw new Error('Fail 4000');
});
复现 Script Error
这个时候,我们启动 Node APP:node app.js,然后访问 http://127.0.0.1:3000。
分别点击按钮 3000 和 4000,我们发现,同域下面的 3000 按钮点击后,异常消息可以捕获到。而跨域的 4000 按钮,只有一个 Script Error。
点击 3000 按钮
点击 4000 按钮
我们复现了 "Script Error."!
有同学举手,我知道,只要加一个跨域头就可以了!
Access-Control-Allow-Origin
没错,我们可以给静态文件服务器加上跨域协议头:
app.use(express.static('./public', {
setHeaders(res) {
res.set('access-control-allow-origin', res.req.get('origin'));
res.set('access-control-allow-credentials', 'true');
}
}));
同时,加载 JS 的时候,加上跨域声明:
<script src="http://127.0.0.1:4000/at4000.js" crossorigin="anonymous"></script>
这样,无论 3000 还是 4000 按钮,我们点击都能获得异常信息。
但是,这个方案有两个致命的弱点:
- 如果 JS 声明了
crossorigin="anonymous"但是响应头没有正确,JS 会直接无法执行 - 我们并不总是有静态服务器的配置权限,跨域头不是想加就能加
声明了 crossorigin 但是没有响应跨域头的 JS
另类思路
如果我告诉你,可以不加跨域头,只是在 JS 文件加载之前加载一个「特别的」JS,一样可以达到目的,你信不信?
<script src="http://127.0.0.1:3000/inject-event-target.js"></script>
<script src="http://127.0.0.1:3000/at3000.js"></script>
<script src="http://127.0.0.1:4000/at4000.js"></script>
这个神奇的 inject-event-target.js 可以让我们在没有跨域头的情况下,拿到 4000 按钮事件处理器的执行异常信息。
点击 3000
点击 4000
如果你觉得神奇,请点赞后,继续往下阅读。这个魔法 JS,其实也很简单:
const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
const wrappedListener = function (...args) {
try {
return listener.apply(this, args);
}
catch (err) {
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}
原理也非笔者原创,而是从这篇文章学习而来。
简单解释一下:
- 改写了 EventTarget 的 addEventListener 方法;
- 对传入的 listener 进行包装,返回包装过的 listener,对其执行进行 try-catch;
- 浏览器不会对 try-catch 起来的异常进行跨域拦截,所以 catch 到的时候,是有堆栈信息的;
- 重新 throw 出来异常的时候,执行的是同域代码,所以 window.onerror 捕获的时候不会丢失堆栈信息;
实际上,利用包装 addEventListener,我们还可以达到「扩展堆栈」的效果:
堆栈扩展效果
我们不仅知道异常堆栈,而且还知道导致该异常的事件处理器,是在何处添加进去的。实现这个效果,也很简单:
(() => {
const originAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, listener, options) {
+ // 捕获添加事件时的堆栈
+ const addStack = new Error(`Event (${type})`).stack;
const wrappedListener = function (...args) {
try {
return listener.apply(this, args);
}
catch (err) {
+ // 异常发生时,扩展堆栈
+ err.stack += '\n' + addStack;
throw err;
}
}
return originAddEventListener.call(this, type, wrappedListener, options);
}
})();
同样的道理,我们也可以对 setTimeout、setInterval、requestAnimationFrame 甚至 XMLHttpRequest 做这样的拦截,得到一些我们本来得不到的信息。
此文已由作者授权腾讯云+社区发布,更多原文请点击
搜索关注公众号「云加社区」,第一时间获取技术干货,关注后回复1024 送你一份技术课程大礼包!
解决 "Script Error" 的另类思路的更多相关文章
- 怎样解决Script error报错问题
如果脚本网址与网页网址不在同一个域(比如使用了 CDN), 那如果这个脚本执行报错了, 就会报:Script error. 由于同源策略, 浏览器禁止向外部脚本泄漏信息, 因此不会提供完整的报错信息, ...
- Script error.全面解析
一些用户向我们反馈,Fundebug的[JavaScript](https://docs.fundebug.com/notifier/javascript/)监控插件抓到了很多**Script err ...
- Fundebug前端JavaScript插件更新至1.7.1,拆分录屏代码,还原部分Script error.
摘要: BUG监控插件压缩至18K. 1.7.1拆分了录屏代码,BUG监控插件压缩至18K,另外我们还原了部分Script error,帮助用户更方便地Debug.请大家及时更新哈~ 拆分录屏代码 从 ...
- Script error.深度测试
Script error.全面解析中我们介绍了Script error.的由来.这篇博客,我们将各种情况(不同浏览器.本地远程托管JS文件)考虑进去,进行一个深度的测试,为读者带来一个全面的了解. G ...
- Uncaught Error: Script error for "popper.js", needed by: bootstrap - require.js
Uncaught Error: Script error for "popper.js", needed by: bootstrap https://requirejs.org/d ...
- 解决 SQL 注入的另类方法
本文是翻译,版权归原作者所有 原文地址(original source):https://bitcoinrevolt.wordpress.com/2016/03/08/solving-the-prob ...
- 解决Unknown error: Unable to build: the file dx.jar was not loaded from the SDK folder!
解决Unknown error: to the dx.jar the SDK folder!最近渐渐迁移到Android Studio来了,更新过Android SDK Manager里的东西后,打开 ...
- script error总结
移动端的页面在控制台报出一个script error,通常的原因有一下几点: 1. 脚本引入错误 可能是脚本的地址不对,协议不对(http或https问题),本地host文件绑定的地址不对 2. 方法 ...
- ubuntu 16.04 + eigen3 安装(解决 fatal error: Eigen/Core: No such file or directory)
1.安装 sudo apt-get install libeigen3-dev 2. 解决 fatal error: Eigen/Core: No such file or directory 当调用 ...
随机推荐
- 第一次在线latex的使用
发现了一个神奇的网站,overleaf 以下是基于默认模板写的. \documentclass[a4paper]{article} %% Language and font encodings \us ...
- tornado框架学习及借用有道翻译api做自动翻译页面
趁着这几天有时间,就简单的学了一下tornado框架,简单做了个自动翻译的页面 仅为自己学习参考,不作其他用途 文件夹目录结构如下: . ├── server.py ├── static │ └─ ...
- js检测输入域的值是否变化
场景: 用户在新建或编辑表单数据时,操作关闭按钮,如果有输入项已经变动时,提示用户存在信息变更,是否放弃当前操作. 初始值情景: 1.通过原生的value指定,如: <input value=' ...
- # 2019-2020-3 《Java 程序设计》第三周总结
2019-2020-3 <Java 程序设计>第三周知识总结 1.类的定义 语法格式如下(加[]表示可选项): [修饰符] class 类名 { 属性定义(声明) 方法定义(声明)} 2. ...
- Linux学习--- C语言关键字、数据类型
关键字: sizeof为关键字,并不是函数. 作用:编译器给我们查看内存空间容量的一个工具. eg:int a: printf("the size is %d\n",sizeof ...
- UVa 11481 Arrange the Numbers (组合数学)
题意:给定 n,m,k,问你在 1 ~ n 的排列中,前 m 个恰好有 k 个不在自己位置的排列有多少个. 析:枚举 m+1 ~ n 中有多少个恰好在自己位置,这个是C(n-m, i),然后前面选出 ...
- 【慕课网实战】一、以慕课网日志分析为例 进入大数据 Spark SQL 的世界
课程整套CDH相关的软件下载地址:http://archive.cloudera.com/cdh5/cdh/5/ cdh-5.7.0 生产或者测试环境选择对应CDH版本时,一定要采用尾号是一样的版本 ...
- POJ - 3984 迷宫问题 dfs解法
#include<stdio.h> #include<string.h> #include<stack> #include<algorithm> usi ...
- Alpha冲刺 - (5/10)
Part.1 开篇 队名:彳艮彳亍团队 组长博客:戳我进入 作业博客:班级博客本次作业的链接 Part.2 成员汇报 过去两天完成了哪些任务 基于ssm框架的前后端交互测试,结合微信小程序demo 展 ...
- 注册Docker官网账号 注册按钮不能点
出现如下问题:注册按钮不能点,解决办法,如下 关于docker hub上不能注册dockeID的问题 注意的是,google访问助手,用在线安装,360安全浏览器,再重启下该浏览器,省得装插件.