从 NPAPI、PPAPI 到 Extension,再到 IE 兼容模式:浏览器内核架构是如何演进的(结合 Chromium 理解)

📅 2026/6/25 14:23:16
从 NPAPI、PPAPI 到 Extension,再到 IE 兼容模式:浏览器内核架构是如何演进的(结合 Chromium 理解)
好的这是一份基于你提供的框架进行了深度扩充和润色的技术演进文档。我保留了你的原意和核心结构同时补充了技术细节、历史背景和逻辑衔接使其更像一篇正式的技术博客或内部培训材料。浏览器能力演进史从 NPAPI 到 Mojo从插件到平台做浏览器内核开发时经常会看到这些名字NPAPI、PPAPI、Extension、content::、Mojo、MSHTML、IE Mode。刚接触时很容易混淆NPAPI 和 PPAPI 到底是什么关系为什么浏览器后来不再推荐插件现代浏览器为什么转向 Extension为什么还有厂商保留 NPAPI为什么还有浏览器保留 IE 内核Chromium 里的content::又是什么本文把这些概念串成一条完整的演进链揭示浏览器如何从一个“网页加载器”演进成一个“完整的应用运行平台”。一、浏览器最早为什么需要插件最初浏览器的能力非常有限。它本质上是一个声明式文档查看器只能处理HTML描述文档结构CSS定义视觉样式JS提供基本的页面交互然而用户的需求远比“查看文档”复杂得多。随着互联网的商业化涌现出大量超出浏览器原生能力的需求富媒体播放视频、音频、流媒体文档处理PDF 阅读、Office 文档预览高级交互3D 在线游戏、复杂的文件上传与下载管理安全与硬件交互网银 U 盾、USB Key、摄像头、麦克风、数字签名浏览器自身的标准制定和功能实现速度远远跟不上这些需求的爆发。为了让网页能够承载这些“重型”应用浏览器必须开放一个“能力后门”允许第三方原生代码在浏览器中运行。这就是插件诞生的背景。其核心架构非常简单直接网页 (HTML/JS) ↓ 调用 插件 (NPAPI/PPAPI) ↓ 直接调用 操作系统 (Windows/macOS/Linux)这个时代最早也是最成功的标准就是NPAPI。二、NPAPI浏览器与 DLL 直接通信的“野蛮时代”NPAPI全称Netscape Plugin Application Programming Interface始于 Netscape 浏览器。它的设计哲学简单而粗暴将浏览器进程变成一个原生代码的宿主。架构模型chrome.exe (浏览器进程) ├─ Browser (界面) ├─ Renderer (网页渲染) └─ plugin.dll (插件作为动态库直接加载进进程空间)插件本质上就是一个操作系统级别的动态链接库Windows 上是.dllLinux 上是.somacOS 上是.plugin。生命周期与调用链页面嵌入网页通过object或embed标签声明插件内容。加载浏览器解析到标签通过LoadLibrary()或dlopen()将插件 DLL 加载到自己的内存空间。初始化调用NP_Initialize()交换浏览器和插件的函数指针表然后调用NPP_New()创建插件实例。运行与交互事件循环触发NPP_HandleEvent()脚本调用触发NPObject的调用。销毁页面关闭时调用NPP_Destroy()最终卸载 DLL。完整的调用链是HTML object/embed ↓ PluginDocument (浏览器内部的插件文档节点) ↓ WebPlugin (浏览器对插件的封装层) ↓ NPAPI (C接口层) ↓ DLL (插件原生代码)NPAPI 的“超能力”因为插件和浏览器共享同一个进程空间插件代码拥有与浏览器本身完全同等的权限。它可以做任何事CreateFile()/ReadFile()随意读写用户磁盘OpenProcess()/ReadProcessMemory()操作其他进程EnumWindows()枚举系统所有窗口send()/recv()发起任意网络连接加载其他 DLLHook 系统 API这种能力模型虽然极其强大但也埋下了灾难的种子。NPAPI 的致命问题这种“无边界”的架构导致了严重且系统性的问题。假设一个插件里写了Sleep(30000)整个调用链会如何插件执行 Sleep(30000) ↓ 阻塞 消息循环卡死 ↓ 浏览器UI线程阻塞 ↓ 整个浏览器窗口失去响应无法操作、无法滚动、无法关闭这只是一个缩影典型问题如下表所示问题结果原因插件崩溃整个浏览器崩溃插件和浏览器在同一进程一个内存越界野指针直接破坏整个进程。死锁/死循环UI 卡死、无响应插件占用了浏览器的主线程UI线程导致事件循环无法处理用户输入。内存泄漏浏览器内存持续飙高最终崩溃插件内部忘记释放内存且该内存属于浏览器进程的堆。严重安全风险用户系统被完全控制插件可以执行任意系统调用、安装恶意软件、窃取文件浏览器沙箱对其完全无效。跨平台困难一个插件只能用于特定OSDLL 是二进制文件内含 x86/x64 机器码和特定OS的系统调用必须为每个平台单独编译。随着浏览器在性能和稳定性上的要求越来越高这种“插件亡、浏览器亡”的共生关系变得不可接受。浏览器厂商在维护崩溃报告时发现绝大多数崩溃都指向第三方插件。变革势在必行。三、PPAPI插件进程隔离与沙箱化时代Google 在接手 Chromium 项目后深刻认识到多进程架构是浏览器的未来。他们提出了一项革命性原则插件不能再和浏览器共进程。这个原则催生了PPAPI (Pepper Plugin API)。核心理念将插件从浏览器主进程中剥离放入一个独立、低权限的沙箱进程。浏览器和插件之间通过高效的IPC (进程间通信)机制进行交互。架构模型Browser Process (浏览器主进程) ↓ 发现 Pepper 插件 ↓ 启动 Plugin Process (独立的插件进程) ↓ 加载并运行 pepflashplayer.dll (Pepper 插件)调用链网页 JS/HTML ↓ Renderer Process (渲染进程处理普通网页内容) ↓ Pepper Host (在渲染进程或浏览器进程中封装PPAPI的C接口) ↓ IPC (通过 Mojo 或 Chromium IPC 通道) Plugin Process (插件进程) ↓ PPAPI 实现 (处理 IPC 调用执行沙箱允许的操作)PPAPI 的能力边界PPAPI 不再提供“无限制的系统调用”。相反它提供了一系列封装的、受控的接口所有能力必须通过IPC向浏览器申请和代理执行。插件的能力从CreateFile()send()变成了PPB_URLLoader发起网络请求由浏览器网络栈代理PPB_FileIO受限的文件读写只能访问沙箱临时目录或用户指定的文件PPB_Graphics2D/PPB_Graphics3D使用 GPU 加速的图形渲染通过 CommandBuffer 提交给 GPU 进程PPB_Audio播放音频由浏览器音频栈代理PPB_Instance管理插件实例的生命周期PPAPI 带来的进步与代价优点稳定性隔离插件崩溃仅其自身的 Plugin Process 终止浏览器主程序和其标签页毫发无伤。用户可以刷新页面重载插件。安全沙箱插件进程运行在高度受限的环境中无法直接访问文件系统、注册表或其他进程。所有敏感操作必须经过浏览器审核和代理。GPU 硬件加速通过Graphics3DAPIPepper Flash 可以高效使用 OpenGL/DirectX 进行视频解码和渲染性能远超 NPAPI。真正的多进程浏览器架构变得更加健壮和模块化。缺点IPC 性能开销每次绘图调用、文件读写都需要跨进程通信对延迟敏感的应用如游戏有一定影响。生命周期与状态同步极其复杂浏览器崩溃了插件进程怎么办插件进程无响应浏览器如何判断和重启多个标签页共享一个插件进程时如何处理这些边界情况使得实现异常困难。API 设计耦合度高PPAPI 的 C API 与 Chromium 内部机制如 Compositor、CommandBuffer绑定较深版本兼容性差。其最著名、也几乎是唯一的典型应用就是Pepper Flash。Google 与 Adobe 合作将 Flash Player 移植到 PPAPI 之上使其能在 Chrome 的安全沙箱中运行。四、为什么 PPAPI 最终也消失了PPAPI 的消失并非因为它是一个失败的技术恰恰相反它在过渡期完美地完成了历史使命。它退役的根本原因是浏览器自身变得足够强大吞噬了插件的核心应用场景。这场“功能吞噬”的演进路径非常清晰视频播放Flash Video→HTML5video标签。浏览器原生支持 H.264/VP9 编解码。矢量图形与动画Flash SWF→CSS3 Animations / SVG。2D/3D 游戏Stage3D→WebGL / WebGL 2.0。浏览器直接封装 OpenGL ES提供原生级 GPU 渲染。高性能计算C 插件计算→WebAssembly (WASM)。允许在浏览器中安全地运行接近原生速度的二进制代码。PDF 阅读Adobe Reader 插件→内建 PDF 引擎(Chrome/Edge 用 PDFium)。实时通信RTMP via Flash→WebRTC。浏览器原生支持点对点音视频和数据通信。整个能力替代路线图NPAPI 插件 ↓ 隔离 沙箱 PPAPI 插件 ↓ 浏览器原生功能替代 扩展 (Extensions) ↓ 深化与开放 功能强大的 Web API (W3C标准) ↓ 将浏览器内核服务化 Mojo 服务化架构当 Web 平台本身可以做几乎所有事情并且做得更安全、更流畅时一个需要用户额外安装、存在安全风险、难以跨平台维护的第三方插件框架退出历史舞台就是必然。五、现代浏览器为什么转向 Extension现代浏览器的扩展Extension模型其设计哲学与插件截然不同。插件是在浏览器上“开洞”而扩展是在浏览器之上“添砖加瓦”。它不是一个二进制程序的加载器而是一个能力受限的标准化 Web 应用。现代模型网页 (Web Content) ↓ 隔离 扩展 (Extension) - 由 HTML/JS/CSS 组成 ↓ 通过受限 API 调用 Browser (浏览器核心) - 作为能力的唯一中介和代理 ↓ 通过内部机制访问 操作系统能力 (读写文件、管理下载、网络请求等)扩展的能力边界声明与权限扩展通过manifest.json声明自己需要的权限用户在安装时进行授权。chrome.tabs访问标签页信息chrome.storage同步/本地存储数据chrome.cookies访问和管理 Cookiechrome.downloads管理文件下载chrome.webRequest拦截和修改网络请求nativeMessaging唯一的“后门”允许扩展与一个用户预先安装的原生程序进行标准输入输出通信。内部调用链路一个看似简单的扩展 API 调用背后是复杂的进程间通信。例如chrome.tabs.query({active: true, currentWindow: true})其内部链路是Extension Context (扩展的JS代码运行环境) ↓ 调用 chrome.tabs.query API Extensions API (C 实现运行在 Browser Process) ↓ 访问内部数据结构 Browser (TabStripModel, Window管理等) ↓ 通过 Mojo 与渲染进程通信 Renderer (WebContents查找对应标签页状态) ↓ 返回结果 最终将 Tab 对象返回给扩展的 JS 代码在这个模型中浏览器成为了所有系统能力的统一、受控的入口。它不再是简单地加载插件 DLL而是精心的在扩展和系统之间加了一层代理和审计。六、既然淘汰了为什么还有厂商保留 NPAPI尽管主流浏览器Chrome, Firefox, Edge, Safari早已全面禁用 NPAPI但在某些特定领域尤其在中国仍有大量基于 Chromium 的国产浏览器保留了 NPAPI 支持。这通常不是技术优劣的问题而是无法逃避的历史兼容成本。这些系统往往是多年前基于 ActiveX (IE) 或 NPAPI (Firefox/Chrome) 开发的关键业务系统。典型的遗留系统依赖链业务网页 ↓ 调用 plugin.Sign(合同数据) 或 plugin.ReadUSB() ↓ 插件通过 中间件 DLL (PKI 库、驱动接口) ↓ 与 硬件证书 (U盾) / 系统驱动 ↓ 绑定 后台业务逻辑受影响的核心行业银行企业网银、银企直连政务电子税务局、工商注册、政府采购平台OA 办公公文处理、电子签章如iSignature、红头文件工控与医疗基于浏览器的 SCADA 监控界面、PACS 医学影像系统教育老旧的在线考试系统、虚拟实验平台如果贸然删除 NPAPI 支持结果是灾难性的功能直接失效登录按钮点击无反应、签章无法调用、U盾无法识别、文件无法上传。业务停摆企业每月报税、发送公文、处理订单的基础操作都无法完成。厂商的常见兼容方案为了在升级 Chromium 内核的同时保证业务连续性厂商通常采用以下几种方案方案一内部维护 NPAPI 层硬扛直接在 Chromium 代码中恢复被删除的 NPAPI 基础设施。需要恢复的组件PluginService、PluginProcessHost、WebPlugin、NPObject绑定等。代价带来极高的 Chromium 升级成本每次合并上游代码都会发生剧烈冲突并需要自行承担安全风险。方案二伪插件Native Messaging 桥接对外表现像一个插件但内部是扩展 原生程序的组合。业务网页 (仍可调用 window.plugin.method()) ↓ 通过页面注入JS进行拦截和转发 Content Script (Extension) ↓ chrome.runtime.connectNative() Native Host (一个独立的 host.exe) ↓ 加载并调用 旧插件的核心 DLL (usbkey.dll)优点符合现代安全模型不需要维护整个 NPAPI 栈。缺点需要分发和安装一个额外的host.exe并且页面需要适配JS桥接层。方案三双内核切换最普遍的中国方案这也是我们下一节要重点讨论的内容。对于 *.bank.com, *.gov.cn ↓ 自动切换 使用 IE 内核MSHTML加载原生支持 ActiveX/NPAPI ↓ 对于其他标准网站 ↓ 使用 Chromium 内核加载这完美地引出了下一个问题。七、很多浏览器说支持 IE 内核本质上到底做了什么很多人可能简单地理解为LoadLibrary(mshtml.dll)就完成了。实际上这背后的工程复杂度堪比在飞机飞行时更换引擎。7.1 IE 浏览器 ≠ IE 内核IE 浏览器iexplore.exe只是一个外壳它真正的核心是一系列以mshtml.dll为首的 COM 组件集合。真正的 IE 内核组件核心 DLL职责mshtml.dll排版与渲染引擎负责 HTML/CSS 解析、DOM 树构建、布局和绘制。jscript.dllJavaScript 引擎(非 Chakra 模式)负责解析和执行 ES3 标准的 JS。vbscript.dllVBScript 引擎负责解析和执行 VBS这是很多老式 ASP 系统的核心。urlmon.dllURL 监视器与安全域管理负责 URL 解析、安全区域判断、异步可插拔协议。wininet.dll网络与缓存负责 HTTP/HTTPS 请求和响应以及 Cookie 和缓存管理。ole32.dllCOM 基础结构所有组件都以 COM 形式交互是 IE 内核的“神经系统”。浏览器通常通过以下 COM 调用获取 IE 的核心能力// 创建 WebBrowser 控件的实例CoCreateInstance(CLSID_WebBrowser,NULL,CLSCTX_INPROC_SERVER,IID_IWebBrowser2,(void**)pBrowser);// 控制其导航到指定URLpBrowser-Navigate(Lhttp://old-system.com);7.2 浏览器真正做的是构建一个“IE 容器”现代浏览器并不是“变成”IE而是在自己的一个标签页或区域中作为容器宿主Host去承载一个 IE 内核的实例。宏观架构现代浏览器 (Browser Process) ├─ Chromium 标签页栈 └─ IE 容器宿主 ├─ IEHostView (一个自定义的窗口类) │ ↓ 创建子窗口管理布局和消息 └─ WebBrowser Control (ActiveX 控件实例) ↓ 加载和运行 ├─ mshtml.dll, jscript.dll ... └─ 业务 ActiveX 控件 (网银U盾)容器内部的典型实现classIEHostView{public:// 创建一个可以嵌入 ActiveX 控件的容器窗口HWNDCreate(HWND parent){// AtlAxCreateControl 会加载并初始化 Shell.Explorer (即WebBrowser控件)AtlAxCreateControl(LShell.Explorer,parent,NULL,m_spBrowser);// 获取 IWebBrowser2 接口进行操作m_spBrowser-Navigate(url);}private:CComPtrIWebBrowser2m_spBrowser;};7.3 真正困难的是“统一体验”加载 IE 内核只是第一步最困难的是让用户感觉不到他在使用两个完全不同的引擎。这需要在多个层面进行深度同步。(1) 统一的 Tab 生命周期管理需要一个抽象基类来封装所有类型的网页宿主classIWebPage{public:virtualvoidLoadURL(constGURLurl)0;virtualvoidClose()0;virtualstd::stringGetTitle()0;// ...};// 一个实现是标准 Chromium 页面classChromeView:publicIWebPage{/* 包含 content::WebContents */};// 另一个实现是 IE 容器页面classIEView:publicIWebPage{/* 包含 IEHostView */};(2) 同步导航状态监听 IE 的一系列 COM 事件并转换成浏览器统一的状态。NavigateComplete2- 更新地址栏 URLDocumentComplete- 通知 Tab 页面加载完毕停止刷新动画TitleChange- 更新 Tab 标题CommandStateChange- 更新前进后退按钮的可用状态(3) 同步 Cookie 与登录态这是最棘手的问题之一。许多旧系统使用wininet.dll的 Cookie 容器而 Chromium 使用自己的CookieMonster。两者默认是隔离的。登录丢失问题用户在 IE 内核中登录银行切换到 Chrome 内核又变成未登录。解决方案需要实现一个CookieSyncService通过InternetGetCookieEx等 API 定期将 WinINET Cookie 与 Chromium 的 Cookie 数据库进行双向同步或者强制所有请求走同一套网络栈。(4) 接管下载IE 弹出原生的下载对话框会破坏统一体验。实现监听DownloadBegin、FileDownload等事件取消 IE 的原生下载获取文件的 URL、Cookie等信息传递给浏览器自己的DownloadManager处理。(5) JS 与浏览器通信桥接老旧系统大量使用window.external作为 JS 与浏览器外壳通信的渠道。window.external.Login(user, pass)实现浏览器需要实现一个IDispatch接口的 COM 对象并将其挂载到 IE 容器的window.external上。这样IE 内的 JS COM 调用就能被转换成 C 函数调用进入浏览器的逻辑。(6) 智能切核自动切换规则不可能让用户手动选择哪个网站用哪个内核。需要一套复杂的规则引擎。规则示例*.bank.com、*.gov.cn、*.taobao.com仅限某些特定路径。逻辑当一个导航请求发起时先匹配规则引擎。- 命中 IE 规则CreateViewForIE()- 创建IEView。- 未命中CreateViewForChrome()- 创建ChromeView。7.4 为什么越来越少浏览器保留 IE 了这个“IE 容器”方案的维护成本是天文数字Chromium 代码库本身已经极其庞大和复杂。MSHTML 同步层 兼容层这是一套对稳定性、性能和安全性要求都极高的胶水代码。微软的态度微软已放弃 IE转向基于 Chromium 的 Edge。Windows 11 中已移除 IE。继续依赖一个不再更新的系统组件风险极高。现代替代方案微软主推Chromium 内核浏览器 ↓ 如果遇到老旧系统 开启 IE 模式标签页 (实际上就是之前的容器方案但由微软官方维护) ↓ 或者 使用 WebView2引导客户将系统前端逐步重构为现代 Web 技术八、那 Chromium 的content::API 又是什么当你在 Chromium 源码中看到满屏的content::时它不是指“网页内容”。它是 Chromium 项目最核心、最基础的**“浏览器抽象层”**。可以这样理解 Chromium 的分层架构----------------- -- 产品层面向用户 | chrome/ | Chrome, Edge, Opera 等浏览器的个性化功能 (书签, 同步, 历史, UI主题) ----------------- ^ | ----------------- -- 内容层核心抽象 | content/ | 一个多进程的、沙箱化的 Web 平台托管层。它是所有浏览器功能的基础。 ----------------- 暴露 content:: 公共 API。 ^ | ----------------- -- 引擎层 | blink/ v8/ | 渲染引擎 (Blink) 和 JavaScript 引擎 (V8)。 ----------------- ^ | ----------------- -- 系统层 | //base, //net | 基础库、网络栈、图形、GPU 命令缓冲等。 -----------------content::层的核心职责多进程架构的实现管理 Browser, GPU, Utility, Plugin, Renderer 等各种进程的启动、通信和生命周期。沙箱隔离为每个渲染进程以及其他子进程配置沙箱策略。站点隔离实现现代浏览器的站点隔离Site Isolation安全特性。核心 Web 平台功能提供一个最小化的集合来加载、渲染和交互网页并暴露必要的 API 供上层使用。content::命名空间下的核心类核心类职责与代表意义content::WebContents一个网页页面实例的抽象。它是content/层最核心的类。代表了从导航开始到渲染、显示、事件处理的全过程。content::RenderProcessHost渲染进程的宿主。负责管理一个独立的渲染器进程的生命周期和通信。content::RenderFrameHost一个 RenderFrame 的宿主。一个WebContents可能包含多个 frame主 frame 子 iframe每个都由一个RenderFrameHost代理。content::BrowserContext浏览器上下文。通常对应一个用户 Profile管理该用户下的 Cookie、缓存、存储权限等数据。content::NavigationHandle一次导航请求的处理对象。从发起 URL 请求到提交渲染的全过程。当你在chrome/层想要打开一个新标签页时最终会调用content::WebContents::Create()等方法在content/层创建并管理这个页面实例。九、现代 Chromium 的真实能力链路今天浏览器内部的能力调用早已不是一条单向直线而是一个服务化的、多层解耦的复杂系统。能力链路全景图网页 JavaScript ↓ 调用 标准 Web API (如 navigator.geolocation, navigator.mediaDevices) ↓ 实现于 Blink 渲染引擎 (在沙箱化的 Renderer Process 中运行) ↓ 如果该 API 需要系统权限则通过 Mojo 接口发起 IPC 调用 Browser Process 中的对应 Mojo 服务实现 (如 GeolocationServiceImpl) ↓ 该服务验证权限、管理生命周期并调用真正的系统接口 操作系统 API (如 Windows Location API, CoreAudio, Win32 file I/O)对比过去的 NPAPI 链路网页 JS ↓ 直接调用 Plugin DLL (在浏览器进程中) ↓ 直接调用 Windows API核心变化在于调用从直接函数调用变为跨进程 IPC保证了安全与稳定。能力提供者从第三方 DLL 变为浏览器自己的服务浏览器从平台的使用者变成了平台能力的定义者和提供者。一切皆服务化Mojo地理定位、文件系统、网络、音频、GPU 等都变成了独立的 Mojo 服务。这使得架构极度模块化可以独立开发、测试甚至替换。十、总结浏览器能力的四个时代浏览器能力模型的演进本质上是一段不断强化安全与规范逐步收回“系统权限后门”的历史。第一代NPAPI 时代野蛮共生网页 ↓ 无限制调用 NPAPI 插件 (DLL) ↓ 直接调用 操作系统关键词高性能、无权限控制、稳定性极差、严重安全风险。代表Flash, Silverlight, 各种网银控件。第二代PPAPI 时代隔离沙箱网页 ↓ 声明式调用 PPAPI 插件进程 (沙箱运行) ↓ 通过代理访问 操作系统关键词进程隔离、沙箱安全、过渡技术。代表Pepper Flash。第三代Extension 时代能力封装网页 (受限环境) ↓ 通过 Event/API 通信 扩展 (Extension) ↓ 通过受限的 JS API 调用 浏览器核心 (权限代理)关键词跨平台、权限声明、浏览器即中介。代表Chrome Web Store 中的各类扩展。第四代Web API Mojo 服务化时代平台即服务网页 (JS) ↓ 调用标准 API Web 平台 (Blink Mojo 服务) ↓ 高度模块化、可审计 操作系统能力关键词标准化、服务化、高模块化、安全纵深防御。代表现代浏览器本身。如果你今天还在 Chromium 代码里看到PluginService、PluginProcessHost、NPObject、WebPlugin等代码它们通常意味着历史兼容为了不破坏某些极端边缘的旧页面。企业定制某些发行版如企业浏览器或国产浏览器为特定业务保留的能力。代码遗迹在彻底移除前暂时保留的僵尸代码。现代浏览器的真正主路径已经全面迁移到了content/负责核心的多进程 Web 平台托管。extensions/提供安全、跨平台的扩展系统。services/网络、设备、媒体等核心系统服务的集合。components/可复用的浏览器功能组件如自动填充、密码管理。浏览器已经不再是那个简单的“网页加载器”。它已经蜕变成一个功能强大、安全严密的通用应用运行平台是事实上的新一代操作系统用户态。