浏览器进程隔离&V8隔离应用
😁😁😁孬话说在前头
微前端中隔离是个非常重要的概念,通常称之为沙箱隔离,就是将各个微应用隔离开,防止变量污染,样式污染等,这篇文章从浏览器原理的角度理解微前端的设计基础.这里其实用进程隔离这个词不合适,但是我想体现出,沙箱隔离策略的颗粒度实在进程上,有错误内容欢迎大家指正。
浏览器多进程架构下的隔离
众所周知,浏览器是一个多进程的架构设计,其中包含一个浏览器(Browser)主进程,一个GPU进程,1 个网络(NetWork)进程、多个渲染进程和多个插件进程。多个进程独立运行,可以避免某个进程阻塞影响其他进程或者浏览器的工作,多进程也可以运用多个CPU核心,更好的利用硬件资源,加快网页的打开和渲染,这大概就是多核CPU推动了浏览器的发展。
以 Chrome 浏览器为例,在运行时会常驻 Browser 主进程,而打开新标签页时会动态创建对应的 Renderer 进程,
-
Browser 主进程:主要负责处理网络资源请求、用户的输入输出 UI 事件、地址栏 URL 管理、书签管理、回退与前进按钮、文件访问、Cookie 数据存储等。Browser 进程是一个常驻的主进程,它也被称为代理进程,会派生进程并监督它们的活动情况。除此之外,Browser 进程会对派生的进程进行沙箱隔离,具备沙箱策略引擎服务。Browser 进程通过内部的 I/O 线程与其他进程通信,通信的方式是 [IPC]
-
Renderer 进程:主要负责标签页和 iframe 所在 Web 应用的 UI 渲染和 JavaScript 执行。Renderer 进程由 Browser 主进程派生,每次手动新开标签页时,Browser 进程会创建一个新的 Renderer 进程
所以Brower主进程和Renderer是一对多的关系,浏览器在进行沙箱设计的启发来源于windows操作系统的沙箱,都是在进程上进行控制,由一个主线程统一做派生、管理进程的沙箱策略,代理Target进程执行策略的操作,进程间的通信则是通过IPC.
在微前端的基础方案中,iframe就是利用了浏览器进程管理的策略,形成天然的沙箱。如果想要查看 Chrome 浏览器的进程运行情况,可以通过右上角的设置 / 更多工具 / 任务管理器打开,此时可以在正在开发的项目通过iframe引入一个正常的网页,就可以看到iframe单独占了一个进程
如上面两张图所示:这里我用iframe引入的是elementUI的官网地址于是单开一个渲染进程,这就是浏览器的站点隔离策略,不同站点会隔离出两个进程.那同站的呢,这里有个小插曲,关于iframe的应用,我们做了一个视频播放插件集成的平台,以iframe的形式引入多个项目,再有两个视频同时播放的情况下,就可以看到iframe同站的表现
那么问题又来了,这同站的iframe在同一个进程中如何隔离的呢?接下来就要说说V8隔离了.
V8引擎下的隔离
// V8 隔离实例需要的参数
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
// V8 隔离实例,通常情况下一个线程对应一个隔离实例
v8::Isolate* isolate = v8::Isolate::New(create_params);
上图是v8的部分源码,其中Isolate是隔离的 V8 运行时实例,在 V8 中使用 Isolate 来实现 Web 页面、Web Workder 以及 Chrome 插件中的 JavaScript 运行时环境隔离。在上文提到的Renderder进程中还有一个Blink渲染引擎,在这个引擎中,通常Isolate和线程是1:1的关系.从代码我们可以得到,每次需要创建一个v8隔离环境,就会创建一个Isolate实例。那意思是:同站的每个iframe都创建了一个Isolate实例吗,我一开始也是这么以为的。除了Isolate可以座位隔离,还有一个概念叫上下文,iframe的上下我们都清楚,独立的iframe对应独立的documnet对象和 window,v8的C++源码对上下文context也做了处理。
v8::Isolate::Scope isolate_scope(isolate);
// 分配一个栈空间用于存储 handle scope
// HandleScope 在使用的时候必须绑定一个 Isolate,因为 Isolate 管理内存相关信息
v8::HandleScope handle_scope(isolate);
// 接下来所有的 Local Handle 都归该 HandleScope 管理
// .............................................
// Local<SomeType> 用于表明是一个 Local Handle
// 在 HandleScope 开辟的栈中放入一个 Local Handle,指向 v8 的 context
v8::Local<v8::Context> context = v8::Context::New(isolate);
翻译一下上述代码,内存空间分配了一个栈,用来管理Isolate实例下的Context实例,这里Isolate 和 Context 的关系是 1:N。也就是说同站的iframe实际上是在同一个Isolate实例下,由于 JavaScript 可以通过代码更改内置的 window 全局变量(例如通过原型链来更改内置对象的方法),因此不同的应用程序必须隔离 window。Context 可以简单理解为 JavaScript 的全局上下文,通过创建不同的 Context,可以使各个站点应用对应的 JavaScript 隔离彼此的 window 全局变量。
V8 在运行时隔离方面,主要包括了 Isolate 隔离和 Context 隔离。Isolate 在安全上用于物理空间的隔离,可以防止跨站攻击,有自己的堆内存和垃圾回收器等资源,不同的 Isolate 之间的内存和资源相互隔离,它们之间无法共享数据,是非常安全可靠的隔离。而 Context 隔离是指在同一个 Isolate 实例中,可以创建不同的 Context,这些 Context 有自己的全局变量、函数和对象等,默认情况下不同 Context 对应的 JavaScript 全局上下文无法访问其他全局上下文。
总结下,在微前端的隔离方案中并没有webAPI可以直接创建Isolate或者Context,所以微前端方案中一般只能通过间接的手段比如iframe或者手动隔离,后面会继续深入学习手动隔离的芝士:JS隔离、CSS隔离、快照隔离等。