深度解析 Chromium WebUI 的生命周期与 IsJavascriptAllowed 崩溃之谜

C++中的Thunk技术和它的使用场景 阅读详情

在 Chromium (或基于其二次开发的浏览器等内核) 开发中,WebUI 是我们最常打交道的模块之一。诸如 chrome://settings、chrome://history 这些内置页面,都是通过 WebUI 机制实现前端 (HTML/JS) 与底层 C++ 的双向通信。

然而,在处理后台异步任务向前端推送状态时,我们很容易踩到一个极其经典的深坑——导致浏览器直接触发 int 3 指令,出现类似下面的宕机堆栈:

00 chrome!base::ImmediateCrash [inlined]
01 chrome!logging::CheckFailure [inlined]
02 chrome!content::WebUIMessageHandler::CallJavascriptFunction<...>(...)
03 chrome!content::WebUIMessageHandler::FireWebUIListener(...)
...
04 chrome!settings::AboutHandler::UpdateManualUpgradeStatus(...)
05 chrome!UpgradeService::UpdateManualUpgradeStatus(...) // 后台异步服务触发

为什么一个看似普通的 FireWebUIListener 调用,会直接把浏览器搞崩溃?这背后隐藏着 Chromium 团队对生命周期与状态同步极其严苛的设计哲学。

崩溃的元凶:CHECK(IsJavascriptAllowed())

顺着堆栈找到源码,我们会发现崩溃发生在 WebUIMessageHandler 的一段模板代码中:

template <typename... Values>
void CallJavascriptFunction(std::string_view function_name, const Values&... values) {
  // 致命的断言!如果为 false,直接 base::ImmediateCrash 宕机!
  CHECK(IsJavascriptAllowed()) << "Cannot CallJavascriptFunction before explicitly allowing JavaScript.";

  web_ui()->CallJavascriptFunctionUnsafe(function_name, values...);
}

触发崩溃的原因非常直接:IsJavascriptAllowed() 返回了 false。

这引发了三个核心问题:

  1. 这个状态位是什么?
  2. 为什么它会变成 false?
  3. 为什么 Chromium 不选择默默 return 忽略这个调用,而是非要用 CHECK 把浏览器弄崩溃?

为什么需要状态锁?WebUI 的三大通信痛点

WebUI 的前端运行在渲染进程(Renderer),后端 C++ 运行在浏览器进程(Browser)。这种跨进程的异步通信面临着严重的“时序错乱”挑战:

  1. 过早调用(JS 未就绪): 页面刚打开,DOM 和 JS 都还没加载完。如果后台 C++ 此时立刻推送数据,Renderer 进程会因为找不到对应的 JS 回调而报错或丢失状态。
  2. 过晚调用(幽灵鞭尸): 用户刷新了页面或关掉了标签页,旧的 JavaScript Context 已经被销毁。如果 C++ 的某个异步后台任务(如网络请求或下载进度)几秒钟后才完成,它还傻乎乎地去调用 CallJavascriptFunction 更新 UI,相当于在向一个不存在的“幽灵”喊话。
  3. 安全风险(跨站信息泄露): 如果发生导航切换到其他普通网页,残留的后台 Handler 还在发特权数据,极易引发安全漏洞。

解决方案:强制“握手”与“拔网线”机制

为了解决这些痛点,Chromium 在 WebUIMessageHandler 中设计了一套极其严格的生命周期握手机制。

阶段一:前端主动“举手”(Allow Javascript)

当一个 C++ Handler 被创建时,它的麦克风是静音的(IsJavascriptAllowed() == false)。 只有当前端的 JS 彻底加载完毕,并通过 cr.sendWithPromise('aboutPageReady') 等消息告诉 C++:“我准备好了”时,后端的对应方法必须显式调用:

void AboutHandler::HandlePageReady(const base::Value::List& args) {
  AllowJavascript(); // 解除封印,允许向前端发送消息
}

阶段二:框架强制“拔网线”(Disallow Javascript)

一旦页面发生刷新、导航离开或标签页被关闭,底层的 WebUIImpl 会监听到 RenderFrameHost 的销毁事件。 它会立刻遍历挂载在这个页面上的所有 Handler,强制调用虚函数:

void WebUIMessageHandler::DisallowJavascript() {
  javascript_allowed_ = false; // 强行拔掉网线
  OnJavascriptDisallowed();    // 通知子类做清理工作
}

此时,IsJavascriptAllowed() 会立刻变回 false。

为什么非要用 CHECK 崩溃?

如果 C++ 代码在 IsJavascriptAllowed() == false 的情况下依然试图调用 CallJavascriptFunction,这意味着什么?

这意味着开发者的生命周期管理存在严重的逻辑 Bug。

想象这样一个场景(正如导致我们崩溃的那个 Bug):

  1. 页面打开,前端就绪,后台全局单例服务(如 UpgradeService)保存了 AboutHandler 的指针(Observer)。
  2. 用户关掉了设置页面。底层框架敏锐地“拔掉了网线”。
  3. 但全局服务还在后台勤勤恳恳地下载更新,并且在进度更新时,试图调用那个还未被析构但已失效的 AboutHandler 去通知 UI。

如果 Chromium 仅仅是在底层 return 忽略这个调用,开发者可能永远不会知道:

  • 他的后台任务正在对一个失效的页面做无用功。
  • 他的全局 Observer 列表里可能堆积了大量无效的指针(潜在的内存泄漏或 Use-After-Free)。

“Fail Fast(尽早失败)” 是 C/C++ 底层系统编程的黄金法则。让程序在不该发生的事情发生时立刻触发 int 3 宕机,带上完整的调用栈,逼着开发者去修复隐藏在异步回调里的生命周期管理漏洞。

正确的防御姿势

理解了这个机制,修复方案就呼之欲出了。在 WebUI Handler 中处理后台异步回调时,必须在向前端喊话之前,先看看麦克风还在不在自己手里:

void AboutHandler::UpdateManualUpgradeStatus(const std::string& status, int progress) {
  // 【关键防御】如果页面已刷新或关闭,底层框架已禁止 JS 通信,直接返回
  if (!IsJavascriptAllowed()) {
    return;
  }

  // 确认安全后,再向前端推送消息
  base::Value::Dict event;
  event.Set("status", status);
  event.Set("progress", progress);
  FireWebUIListener("update-status-changed", event);
}

或者,更彻底的做法是:重写 OnJavascriptDisallowed(),在网线被拔掉的瞬间,主动注销所有的全局监听器:

void AboutHandler::OnJavascriptDisallowed() {
  // 页面失效时,主动告诉后台服务:别再给我发消息了!
  UpgradeService::GetInstance()->RemoveManualUpgradeObserver(this);
}

结语

Chromium 的代码虽然庞大复杂,但每一处看似“不讲理”的 CHECK 崩溃背后,往往都藏着精心设计的防御体系。理解 IsJavascriptAllowed 的底层逻辑,不仅能帮我们修掉棘手的崩溃,更能让我们在编写异步 C++ 代码时,时刻对对象的生命周期与状态机保持敬畏。

深入解析 Chromium Mojo IPC:跨进程通信原理与源码实战 本文深入解析Chromium浏览器中的Mojo IPC机制,这一高性能跨进程通信框架支撑着现代浏览器多进程架构的运行。文章从Mojo的五个核心概念(MessagePipe、Handle、Interface、Receiver/Remote、Bindings)切入,详细剖析了其数据流、线程模型和异步机制。通过源码分析展示了MessagePipe的底层实现、SimpleWatcher的事件监听机制,以及接口自动生成类的运作原理。最后提供了实战示例和性能优化建议,包括使用SharedBuffer减少拷贝、合理绑定线 阅读详情

相关推荐

深入理解浏览器渲染引擎:底层机制与性能优化实战

本文深入探讨了现代浏览器渲染引擎的工作原理,特别是Blink和V8引擎的协作机制。文章首先概述了浏览器的多进程架构,包括浏览器进程、渲染进程、GPU进程和网络进程的功能。接着,详细解析了渲染引擎的核心步骤,从HTML和CSS的解析到DOM和CSSOM的构建,再到渲染树的形成、布局、绘制和最终的合成阶段。文章还探讨了JavaScript引擎与渲染引擎的交互方式,以及如何通过优化策略减少重排和重绘,提升页面渲染性能。最后,提供了Chromium源码的调试路径和实用建议,帮助开发者更好地理解和优化渲染流程。

John_ToStr的博客 1777

贪心算法——最大整数问题详解

贪心算法——最大整数问题详解: [最大整数]设有n个正整数,将它们连接成一排,组成一个最大的多位整数。 例如:n=3时,3个整数13,312,343,连成的最大整数为34331213。 又如:n=4时,4个整数7,13,4,246,连成的最大整数为7424613。 输入:n N个数 输出:连成的多位数 算法分析:此题很容易想到使用贪心法,在考试时有很多

John_ToStr的博客 7773

深入解构 Chromium 升级流程与常见问题解决方案

本文详细记录了从老旧Chromium版本升级至114版本的完整流程,涵盖版本评估、代码迁移、构建适配等功能环节。重点分析了升级过程中面临的技术挑战,包括API接口变更、Mojo架构调整、Blink层重构及沙箱策略强化等问题,并提供了针对性的解决方案。文章特别强调了模块解耦设计、沙箱权限审查和性能优化等关键点,分享了对齐工具链、回归测试和灰度发布等实践经验。最后总结了定期升级、维护兼容层和利用社区资源等核心建议,为浏览器内核升级提供了实用技术参考。

John_ToStr的博客 1453

Windows Hook案例分析与技术探索

Hook是Windows中提供的一种用以替换DOS下“中断“的系统机制,中文译为“挂钩”或“钩子”。在对 特定的系统事件进行Hook后,一旦发生已Hook事件,对该事件进行Hook的程序就会收到系统的通知, 这时程序就能在第一时间对该事件做出响应。 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有 到达目的程序前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理 (改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。..

John_ToStr的博客 7128

微信小游戏过审实战:JS混淆与马甲包规避技巧

本文详细解析了微信小游戏过审的实战技巧,重点介绍了JS代码混淆与马甲包规避策略。通过代码混淆、资源文件处理和分阶段提审等方法,有效应对微信机审系统的代码包侵权检测,提升过审成功率。文章还分享了常见问题的解决方案和实战经验,为开发者提供实用指导。

weixin_29234423的博客 279

知识图谱中的 Ontology(本体论)

本文的部分图片和内容来自中国科学院自动化研究所刘康老师的课件 Ontology(本体论) Ontology 是(特定领域)信息组织的一种形式,是领域知识规范的抽象和描述,是表达、共享、重用知识的方法。 Ontology 是知识体系构建的关键技术,知识图谱是一种人工智能技术,它的关键在于让计算机能够处理人类的知识。然而,人类脑海中的知识通常是直觉性的,我们无法将这种直觉性的知识直接输入给计算机,On...

P01son的博客 2万+

浏览器无痕模式机制解析:它与正常模式究竟有何不同?

在日常浏览网页时,很多用户会注意到浏览器提供了“无痕模式”、“隐私窗口”或“InPrivate模式”等选项。这个功能常常被认为是“匿名上网”,但从技术实现角度来看,无痕模式到底和普通浏览模式有什么不同?它真的能完全不留痕迹吗?又有哪些场景下开发者要注意两种模式的行为差异? 本文将从浏览器实现机制出发,系统地讲解无痕模式的原理、与普通模式的区别、以及它在开发调试中的表现差异和典型使用场景。

John_ToStr的博客 6026

虚拟机器人平台CoppeliaSim(Vrep)的逆运动学教学

相信看这篇博客的人都知道CoppeliaSim是做什么的,因此这里不多赘述。CoppeliaSim从最早的Vrep到现在已经更新了N个版本,从过去Vrep3.x开始使用的我这里小小地吐槽一下这个软件。CoppeliaSim每一个版本都有更新,问题是这些更新有时候不能向前兼容,而且时不时更新地让人不知所措(对于有紧急任务的时候真的很火大啊)。 以本文所展示地逆运动学来说,CoppeliaSim在4.0.0之后又大变,推翻了过往所有的拖动模块链接之类的构建方式,转而采用直接通过代码来实现逆运动学。 简单版逆运动

qq_39243015的博客 6698

CPU 占用升高 ≠ 卡顿:浏览器硬件加速的真正价值

浏览器开启硬件加速后CPU占用升高但运行更流畅的现象属于正常优化:硬件加速将图形任务从CPU主线程转移到GPU处理,虽然GPU调度会消耗部分CPU资源,但显著减轻了主线程负担,带来更流畅的体验。建议多数设备开启该功能,仅在驱动不兼容或低功耗场景下关闭。通过chrome://gpu可验证加速是否生效,开发者还能追踪关键渲染事件进行深度优化。

John_ToStr的博客 2134

从零构建:openEuler WSL下的ARM交叉编译工具链自动化部署方案

本文详细介绍了在openEuler WSL环境下使用Ansible自动化部署ARM交叉编译工具链的方案。通过编写Ansible角色和Playbook,实现从基础环境配置到工具链安装、环境变量设置的一键部署,大幅提升嵌入式开发效率。方案支持多种ARM架构,并扩展了QEMU仿真、VS Code远程开发等实用功能,是嵌入式开发者的高效解决方案。

h3i4j的博客 382

workbench拓扑优化教程_拓扑优化实例-轮毂的优化

拓扑优化实例-轮毂的优化ANSYS Workbench的新版本集成了topology optimization模块,为用户的拓扑优化提供的很好的应用。通过拓扑优化topology optimization模块的使用可以对拓扑有较好的理解,该模块拓扑优化可以简单概括为一句话:如何在合适的位置去除确定的材料使用量,而相应的刚度变化影响最小。本例以汽车轮毂为例,来确定轮毂的辐条形状。1.建立模型DM中可...

weixin_39581739的博客 3043

揭秘浏览器中 Cookie 的真正作用与性能影响

本文深入解析了Cookie在Web开发中的真实作用与性能影响。文章首先澄清了"Cookie能加速请求"的常见误解,指出Cookie不仅不会提升性能,反而可能因增加请求体积和降低缓存命中率而拖慢加载速度。通过分析Cookie的定义、核心作用(状态保持、个性化、行为跟踪)和现代浏览器对其的限制(SameSite、分区存储等),作者强调Cookie是状态管理工具而非性能优化手段。文章提供了实用建议:合理设置作用域、避免存储大数据、为静态资源使用独立域名等,并推荐采用现代缓存策略替代过度依赖Co

John_ToStr的博客 1627

【车载以太网测试从入门到精通】系列文章目录汇总

物理层测试、数据链路层测试、以太网交换机测试、网络层测试、传输层测试、以太网TCP/IP协议自动化测试、以太网网络管理测试、以太网网络管理自动化测试、鲁棒性测试、SOME/IP测试、SOMEI/IP服务接口自动化测试、DoIP诊断测试、DoIP诊断自动化测试、DoIP BootLoader刷写、以太网网关路由测试、以太网网关路由自动化测试、AVB测试。

测试领域技术分享 3154

关于回调函数跟this指针的一些理解

在C里面,经常需要提供一个函数地址,注册到结构里,然后在程序执行到特定阶段时,回调该函数。创建线程,注册线程运行的主函数就是一个典型的例子。这里以简单的回调实例,说明C++中回调函数为成员函数时有关this指针的问题。由于C++对C的继承关系,C++没有自己的线程封装技术,一般而言我们创建线程时,还是用C的回调函数机制。类似的例子也挺多的。在Java等纯粹的面向对象语言,则不一样,不光有自己的独立

John_ToStr的博客 1543

华为OD机试 - 城市聚集度(Python/JS/C/C++ 2025 A卷 200分)

华为OD机试 2025A卷题库疯狂收录中,刷题。

学Java,找哪吒 1129

SAP_FICO模块-资产减值功能对折旧和残值的影响

SAP_FICO模块-资产减值操作对资产历史折旧以及后续折旧的影响,包括减值操作的配置前提

风沙的博客 2802

从 base 出发:深入理解 Chromium 自研基础库的体系结构与设计哲学(兼谈为什么不直接用 STL)

Chromium基础库base解析:浏览器内核的底层基石 作为Chromium内核最底层的公共库,base库为整个浏览器架构提供了跨平台一致性的基础支撑。它通过重新封装智能指针、线程模型、容器等核心组件,解决了标准C++库在大型跨平台项目中面临的性能不可控、行为不一致等问题。base库包含内存管理(RefCounted/WeakPtr)、任务调度(TaskRunner/ThreadPool)、事件循环(MessageLoop)、自研容器(flat_map)等核心模块,采用统一抽象层确保不同操作系统表现一致。

John_ToStr的博客 1523

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

背包问题——动态规划算法详解与实例

动态规划算法详解与实例 一、基本概念     动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。 二、基本思想与策略     基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息

John_ToStr的博客 1512

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

make_shared、shared_ptr、enable_shared_from_this

make_shared 和 正常的 shared_ptr 构造函数异同!

John_ToStr的博客 1480

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Release 与 Debug 的区别及 Release 版本调试指南

摘要: Debug与Release模式在软件开发中存在显著差异:Debug关闭优化、保留调试信息,便于调试但性能差;Release开启优化、去除冗余检查,性能好但调试困难。Release版本易因未初始化变量、越界访问等问题出现Bug,尤其在线程竞争下更明显。调试Release版本需保留符号信息(如.pdb文件),临时禁用优化,利用日志、Dump文件及工具(如VS/WinDbg/gdb)定位问题。开发者应兼顾Debug阶段的严格测试与Release版本的符号保留,以快速修复线上问题。

John_ToStr的博客 1473

chrome源码中WeakPtr 跨线程使用详解:原理、风险与最佳实践

WeakPtr 跨线程使用详解:原理、风险与最佳实践

John_ToStr的博客 1471

Chrome 扩展安全攻防实录:从攻击面到防御实践

本文系统剖析Chrome扩展安全风险,涵盖其架构组成、权限模型与通信机制,梳理权限滥用、脚本注入、消息信任漏洞等攻击面,结合XSS提权、伪造消息、供应链攻击等典型案例,提出最小权限、输入校验、CSP策略、依赖管理等防御体系,并提供静态检查、动态调试等审计实践。展望Manifest V3下安全演进方向,为开发者与安全研究者提供全面防护指南。

2401_84466325的博客 182

Chrome 诡异问题|地址栏输入[baidu.com](https://baidu.com),页面却显示 Google 主页,无痕模式、设置页空白故障解决

今天遇到一个很奇怪的 Chrome 故障:地址栏明明输入baidu.com,打开页面却是 Google 首页,chrome://settings设置页面空白,无痕窗口访问百度同样黑屏,最终通过管理员 PowerShell 执行ipconfig /flushdns、netsh winsock reset并重启电脑修复。

m0_74451345的博客 99

【AI工具集】搭建Chrome Use替换Trae/CodeBuddy的Browser Use/浏览器控制/agent-browser

# 【AI工具集】搭建Chrome Use支持小龙虾/Trae/CodeBuddy的Browser Use/浏览器控制/agent-browser替换 ## 结论先行 **目的:** 在 Chrome 浏览器安装 chrome-use 扩展插件,打通浏览器和agent工具的桥联。可以用在openclaw 小龙虾,Trae , CodeBuddy(Work版本一样),稳定 & 深度 控制页面元素级内容,不受后台 / 浏览器缩放 / 桌面分辨率变化影响 / 兼容本地和云端LLM模型 ## 背景 最近在做AI自

刻痕 206
上一篇: 深入理解 V8 引擎:C++ 与 JavaScript 的跨界传送门
下一篇: Chromium 并发编程:base::WeakPtr 与 base::WrapRefCounted深度解析
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ปรัชญา แค้วคำมูล

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值