网页插件或嵌入自定义图表都要运行不安全的第三方代码,本文探究如何让第三方代码在 web 网页中高效且安全运行。
iframe 元素表示嵌套的浏览上下文。每个嵌入式浏览上下文都有其自己的会话历史记录和文档。
1 2 3
| window === frame.contentWindow window.document === frame.contentDocument window.Array === frame.contentWindow.Array
|
注: 由于每个浏览上下文都是完整的文档环境,页面中的每个<iframe>
都需要增加内存和其他计算资源。从理论上讲,您可以根据需要使用任意多个<iframe>
,但请检查性能问题。
iframe问题:
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
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>shadowDom</title> <style> .chart-title { color: blue !important; } </style> </head> <body> <div id="custom_chart"></div>
<script>
var elementRef = document.getElementById("custom_chart"); var shadow = elementRef.attachShadow({ mode: 'open' }); var title = document.createElement('h2'); title.textContent = '我是影子DOM'; title.classList.add('chart-title'); var content = document.createElement('div'); content.classList.add('chart-content'); var style = document.createElement('style'); style.textContent = ` .chart-title { color: red; } .chart-content { width: 200px; height: 100px; background: silver; } `; var script = document.createElement('script'); script.textContent = ` var chart = 1; console.log(window); `; shadow.appendChild(style); shadow.appendChild(script); shadow.appendChild(title); shadow.appendChild(content); </script> </body> </html>
|
问题: shadowDom 并不隔离 js。
在主线程中如何隔离 js ?
1 2 3
| eval('1+1;'); eval('function(){return 123;}'); eval('window.document.write("<script src=https://cdnjs.cloudflare.com/ajax/libs/echarts/4.6.0/echarts.min.js></script>")');
|
使用示例:
1 2 3 4 5 6 7 8 9
| let g = window; let r = Realm.makeRootRealm(); let f = r.evaluate("(function() { return 17 })"); f() === 17 Reflect.getPrototypeOf(f) === g.Function.prototype Reflect.getPrototypeOf(f) === r.global.Function.prototype
|
核心原理:
1 2 3 4 5 6
| function simplifiedEval(scopeProxy, userCode) { 'use strict' with (scopeProxy) { eval(userCode) } }
|
with 语句【限制作用域】
with 语句接收的对象会添加到作用域链的前端并在代码执行完之后移除。
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
|
var obj = { name: 'xiaoming' }; function sayName() { var name = 'xiaoqiang'; with(obj) { console.log(name); } console.log(name); } sayName();
var obj = { name: 'xiaoming' }; function sayName() { var name = 'xiaoqiang'; with(obj) { name = 'xiaohua'; age = 30; } console.log(name); console.log(age); } sayName(); console.log(obj.name); console.log(obj.age);
|
Proxy 对象【访问拦截】
1 2 3 4 5 6 7 8 9
| const scopeProxy = new Proxy(whitelist, { get(target, prop) { if (prop in target) { return target[prop]; } return undefined; } });
|
最终方案
Real + shadowDom
优点:
n
个自定义图表,仅需2
个 iframe 解决,大大减少了iframe
数量。
- 主线程中运行速度很快。
- 共享公共资源,节省内存和网络请求开支。
- 代码
安全性
比原来更高。
缺点和改进:
- 兼容性:
Proxy
IE11不支持,Edge支持,chrome 49以上支持。
- 加载多个
第三方库冲突
问题。
- 实践中的
安全性
和性能
问题仍有待进一步验证。
参考文档
如何安全的运行第三方 JavaScript 代码(上)?
如何安全的运行第三方 JavaScript 代码(中)?
如何安全的运行第三方 JavaScript 代码(下)?
原文-How to build a plugin system on the web and also sleep well at night
JS中的沙箱个人见解
iframe异步加载技术及性能
Realm-shim
Reflect