1. 背景知识:script 标签有哪些属性
-
src: 脚本来源地址 -
type: 表示所代表的脚本类型,有如下取值:- 未设置(默认)/一个空字符串/一个 JavaScript MIME 类型:普通脚本/传统脚本
module:模块脚本- importmap:代表元素体内包含导入映射(importmap)表
- 任何其他值:所嵌入的内容被视为一个数据块,不会被浏览器处理
-
integrity:包含用户代理可用于验证所获取到资源的完整性的内联元数据 -
crossorigin:见下文详解 -
其他属性,比如
asyncdefer等,与本文关系不大,详见MDN
2. crossorigin 属性的作用
1. 【html 标准】文档中的解释
对于普通脚本,它控制是否公开错误信息。对于模块脚本,它控制用于跨域请求的凭据模式。(原文见此)

2. 个人总结
默认情况下,可以从任意位置加载 js 。
如果配置了该属性,会让浏览器启用CORS检查(判断响应头中Access-Control-Allow-Origin是否与当前文档同源),如果检查不通过,则浏览器拒绝执行代码。
什么情况下要启用检查?
- 需要捕获跨域脚本中的错误信息
type="module"并且需要发送凭据
只验证了 cookie,其他认证信息不知道怎么测试
- 需要校验跨域脚本的完整性(配置了
integrity的时候) - 文档中的脚本(并不单单指跨域脚本)需要使用 跨域隔离的 API(比如SharedArrayBuffer,原因见此)
3. 不设置 crossorigin 属性时的表现
不跨站就会发 cookie;跨站的话,只有满足
SameSite=None; Secure=true的 cookie 才会发送
-
如果设置了
type="module",则一定不发送 cookie -
收到响应后,浏览器会正常执行脚本代码
-
全局错误捕获方面
-
页面同源的脚本出错:
- promise 异常:可以捕获✅
- 其他普通异常:可以捕获✅
-
跨域脚本出错:
- promise 异常:无法捕获❌
- 其他普通异常:只能捕获到
Script error.的错误,没有详细信息
-

4. 设置了 crossorigin 属性时的表现
crossorigin 有 2 个值:use-credentials和anonymous。
只有指定use-credentials时才表现为use-credentials,其他任何不合法的值都视为 anonymous
1. anonymous
-
不管有没有设置
type="module",获取资源时,都不会发送 cookie -
收到响应后,只有满足 响应头中
Access-Control-Allow-Origin=== 请求头中的origin字段,浏览器才会执行脚本代码,否则会抛出 CORS 异常

-
如果设置了
integrity,就还会再校验一次资源完整性,不满足也会报错

-
全局错误捕获方面
-
页面同源的脚本出错:
- promise 异常:可以捕获✅
- 其他普通异常:可以捕获✅
-
跨域脚本出错:
- promise 异常:可以捕获✅
- 其他普通异常:可以捕获✅
-

2. use-credentials
-
不管有没有设置
type="module",发送请求时是否带上 cookie,都取决于 cookie 的同站策略 -
收到响应后,响应头中需要同时满足以下条件,浏览器才会执行脚本代码,否则会抛出 CORS 异常:
Access-Control-Allow-Origin=== 请求头中的origin字段Access-Control-Allow-Credentials为 true
-
如果设置了
integrity,就还会再校验一次资源完整性,不满足也会报错 -
错误捕获方面,与
anonymous相同
5. 深入错误捕获
1. 网络解释


2.【html 标准】文档中的说明
1. 普通异常为什么会抛出Script error.?
html 标准文档 中有这样一句话:

如果该脚本属于classic script且muted errors为 true,则普通异常就会抛出Script error.
- 什么是
classic script?
就是指普通脚本。
省略
type属性,或将其设置为空字符串,或将其设置为JavaScript 的MIME类型,意味着该脚本是classic script( 原文见此)
muted errors什么情况下会为 true?
跨域脚本就为 true,后面附带了一句极为简单的解释:“会泄漏私有信息”。(原文见此)

- 为什么设置了
crossorigin就可以暴露错误详情?
以下为个人理解,没有找到原文。
因为设置了之后,浏览器会校验响应头(Access-Control-Allow-Origin),发现服务器是允许页面内访问该脚本资源的,于是允许暴露错误信息。
2. 全局 Promise 异常为什么无法捕获?
- 全局 Promise 异常是
HostPromiseRejectionTracker进行处理的(见下图标注序号 1 处,原文见此)

HostPromiseRejectionTracker内部,判断如果是classic script,则直接 return (原文见此)

(个人理解同源的情况下应该走到第 5 小点中,就可以正常捕获)
- 回到 1 中截图标识 2 的位置,提到:
If a rejection is still not handled after this, then the rejection may be reported to a developer console.
由于HostPromiseRejectionTracker方法未进行任何处理,没有被全局监听所捕获,于是就在控制台抛出了异常。
3. 源码分析:跨域脚本抛出的普通异常为什么没有详细堆栈
1. v8 源码
首先,在 v8 源码中,script脚本默认就是跨域的。注释提到会在“主线程合并期间修复”(未研究对应代码)

上图中的注释下面调用了ScriptOriginOptions方法进行初始化script,其第一个参数就是表示是否为跨域脚本,传入的值是 false:

而异常信息的IsSharedCrossOrigin()方法就是直接调用script的IsSharedCrossOrigin()方法

因此,可以知道, 异常信息的IsSharedCrossOrigin()方法会默认返回 false.
2. chromium 源码
在chromium源码中,如果异常信息的IsSharedCrossOrigin()方法返回 false,会将sanitize_script_errors设为SanitizeScriptErrors::kSanitize

之后,判断到满足sanitize_script_errors == SanitizeScriptErrors::kSanitize,则调用CreateSanitizedError方法

就是这个方法里,会抛出Script error,并且没有给出堆栈信息。

以上可以解释普通异常为什么没有堆栈,至于 Promise 异常为什么不能捕获,还没有看懂。并且限于水平,未能调试该源码,不太好说这些没有错漏之处,颇为遗憾……
文章详细解释了script标签的crossorigin属性在处理跨域脚本时的作用,涉及错误处理、cookie发送、资源完整性验证等方面。重点讨论了anonymous和use-credentials模式,以及为何设置了crossorigin后能暴露更多错误信息。

1万+

被折叠的 条评论
为什么被折叠?



