如果让你实现一个在线的 JavaScript 代码运行环境,要求用户代码不能对页面进行批改,以防止潜在的平安问题,你会怎么做?
应用with
?应用proxy
?OK,都能够,然而这两种办法都须要关注很多细节,否则用户仍旧有可乘之机,这样一来你的实现外面就会有一个很长长长长长长长的操作黑名单。
除此之外,咱们还能够专门部署一个页面,将代码提到服务端渲染成页面,再通过 iframe 去拜访,如果 iframe 与父页面之间是跨域的话能够达到很高的安全性——那么能不能不看后端的脸色,齐全应用浏览器来实现相似的沙箱呢?
当然能够——
1. iframe
对前端页面而言,跨域是页面与页面之间的鸿沟,但这并不意味着咱们必须从新关上一个页面来运行新的代码,因为咱们能够应用 <iframe>
标签:
<iframe src="www.xxxx.xxx"></iframe>
对于同域的 iframe,咱们能够间接通过 .contentWindow
拜访并操作它的全局对象,而后间接往里面执行 JavaScript:
document.querySelector('iframe')
.contentWindow
.eval('alert("hello world!");');
然而同域页面的子页面是能够与父页面进行互操作的,
2. data URL
你可能在一些页面里见过小图片不应用网络链接,而是采纳一个 data:image/png;base64 xxxxxxx
格调的 URL,这种 URL 就是 data URL。
除了 data URL 之外你可能还见过 blob://
结尾的 URL —— Object URL。不过 Object URL 与以后页面是同域的,而 data URL 与以后页面是跨域的。所以 咱们能够在 iframe 应用 data URL 来进行跨域隔离。
3. 将 JavaScript 代码变成 data URL
咱们能够间接将 JavaScript 片段变成 data:application/javascript,
的 URL,然而这样有一个问题:iframe 关上这样的 URL 的时候,会显示代码原文而不是执行代码,这个行为其实和你间接在浏览器地址栏输出 JS 的 URL 是一样的。
所以咱们须要将 JavaScript 代码拼接到 html
外面,再变成 data URL,而后交给 iframe 去加载:
const javaScriptFragment = `
alert('hello world');
`;
const htmlFragment = `
<!doctype html>
<html>
<head>
<meta chatset="utf-8" />
</head>
<body>
<script>${javaScriptFragment}</script>
</body>
</html>
`;
const dataUrl = `data:text/html,${htmlFragment}`;
// 留神,如果代码片段中含有中文的话,须要应用 encodeURIFragment 本义 htmlFragment
document.querySelector('iframe').src = dataUrl;
4. 如果须要获取执行后果的话,基于 postMessage 定制通信机制
如果咱们岂但要做沙箱隔离,还被要求获取运行后果的话,则能够做一个通信机制,让 iframe 获取到用户代码执行后果,iframe 与父页面之间最好的跨域通信办法莫过于 postMessage
。
除了获取后果之外,还能够将通信机制进一步扩大成为 RPC,这样能够实时批改页面里的代码来查看成果,相似于 codepen。
具体实现与主题不是强相干,这里就不写了。