Chromium 132→148 升级实战:Legacy IPC 消息丢失问题深度解析

一、问题背景

在将浏览器内核从 Chromium 132 升级到 148 的过程中,遇到了一个棘手的问题:Render 进程无法收到 Browser 进程发送的 Legacy IPC 消息。奇怪的是,反向的消息(Render → Browser)却能正常发送。

这个问题的本质是 Chromium 148 对进程内 IPC 路由架构进行了根本性的重构。

二、问题表现

升级到 148 后,所有依赖 Legacy IPC 通信的功能全部失效:

  • Browser 进程发出的 IPC 消息在 Render 端无法被 RenderFrame 接收

  • 日志显示消息已到达 Render 进程,但没有被分发到目标 RenderFrame

  • Mojo 通信的功能正常,只有走 IPC::Channel 的老消息受影响

  • Render 进程向 Browser 发送消息正常

三、根因分析:Chromium 148 架构变更

3.1 为什么 Chromium 要改这个架构?

Chromium 148 对渲染进程的 IPC 架构进行了根本性重构,主要驱动力来自三个方面:

1. Site Isolation 的深化

随着 Site Isolation(站点隔离)策略的全面推行,不同站点的 iframe 需要在逻辑上完全隔离。旧架构中,所有 RenderFrame 共享同一个全局路由表,这存在安全隐患------一个站点理论上可能通过某种方式获取其他站点 frame 的 routing_id

新架构引入了 AgentSchedulingGroup 作为按站点分组的调度单元:

text

复制代码
旧模型: RenderProcess → RenderThread (全局路由表) → 所有 RenderFrame
新模型: RenderProcess → AgentSchedulingGroup (按站点分组) → 同站点 RenderFrame
2. 全面向 Mojo 迁移

Chromium 的长期目标是废弃 Legacy IPC,全面转向 Mojo。Mojo 使用 message pipe 进行端到端通信,天然不需要全局路由表:

text

复制代码
Legacy IPC: Browser → Channel → 路由表查 routing_id → 目标 RenderFrame
Mojo:       Browser → MessagePipe → 目标 RenderFrame (直接绑定)

routing_id 是 Legacy IPC 时代的产物,是一个全局命名空间中的整数标识符。在 Mojo 体系中,这个设计已经过时。

3. 进程模型简化

旧架构中,RenderThread 承担了太多职责:持有 Channel、维护路由表、分发消息、处理控制消息等。新架构将这些职责分离到不同的组件中,使每个组件的职责更单一。

3.2 132 vs 148 架构对比

Chromium 132 架构

text

复制代码
┌─────────────────────────────────────────────────────────┐
│                    RenderProcess                         │
│  ┌───────────────────────────────────────────────────┐  │
│  │                  RenderThread (全局单例)            │  │
│  │                                                    │  │
│  │  职责:                                             │  │
│  │  1. 持有 IPC::Channel (与Browser通信的管道)       │  │
│  │  2. 维护全局路由表 routing_id → IPC::Listener      │  │
│  │  3. 分发所有收到的IPC消息                          │  │
│  │                                                    │  │
│  │  ┌─────────────────────────────────────────────┐  │  │
│  │  │           全局路由表 (routing_id_map_)       │  │  │
│  │  │                                             │  │  │
│  │  │  routing_id=1 → RenderFrameImpl (主frame)   │  │  │
│  │  │  routing_id=2 → RenderFrameImpl (iframe A)  │  │  │
│  │  │  routing_id=3 → RenderFrameImpl (iframe B)  │  │  │
│  │  │  MSG_ROUTING_CONTROL → RenderThread 自身     │  │  │
│  │  └─────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────┘  │
│                          │                               │
│         ┌────────────────┼────────────────┐              │
│         ▼                ▼                ▼              │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐         │
│  │RenderFrame │  │RenderFrame │  │RenderFrame │         │
│  │(routing=1) │  │(routing=2) │  │(routing=3) │         │
│  │            │  │            │  │            │         │
│  │ 实现:      │  │ 实现:      │  │ 实现:      │         │
│  │ IPC::Listen│  │ IPC::Listen│  │ IPC::Listen│         │
│  │ IPC::Sender│  │ IPC::Sender│  │ IPC::Sender│         │
│  └────────────┘  └────────────┘  └────────────┘         │
└─────────────────────────────────────────────────────────┘

消息接收流程(132):

text

复制代码
IPC::Channel 收到消息
  → RenderThread::OnMessageReceived(msg)
    → 提取 routing_id
      → routing_id_map_.find(routing_id) 找到目标 RenderFrame
        → render_frame->OnMessageReceived(msg) 直接分发

消息发送流程(132):

text

复制代码
RenderFrame::Send(msg)
  → msg->set_routing_id(routing_id_)
    → render_thread_->Send(msg)
      → channel_->Send(msg) 直接写入通道
Chromium 148 原生架构

text

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                        RenderProcess                             │
│                                                                  │
│  ┌────────────────────────────────────────────────────────────┐ │
│  │                  RenderThread (大幅简化)                     │ │
│  │                                                             │ │
│  │  职责:                                                      │ │
│  │  1. 持有 IPC::Channel (仅用于legacy IPC)                    │ │
│  │  2. 只处理 MSG_ROUTING_CONTROL 消息                        │ │
│  │  3. ❌ 不再维护 RenderFrame 的 routing_id 路由表            │ │
│  └────────────────────────────────────────────────────────────┘ │
│                          │                                       │
│          ┌───────────────┼───────────────┐                      │
│          ▼               ▼               ▼                      │
│  ┌─────────────────────────────────────────────────┐           │
│  │     AgentSchedulingGroup (site-a.example.com)    │           │
│  │                                                  │           │
│  │  职责:                                           │           │
│  │  1. 同站点帧的调度中心                           │           │
│  │  2. 维护 LocalFrameToken → RenderFrame 映射      │           │
│  │  3. ❌ 不维护 routing_id 映射 (原生148)          │           │
│  │  4. 管理 Mojo 接口 (Associated Interface)        │           │
│  │                                                  │           │
│  │  ┌────────────────────────────────────────────┐  │           │
│  │  │     listener_map_ (按LocalFrameToken)       │  │           │
│  │  │                                            │  │           │
│  │  │  frame_token_A → RenderFrameImpl (主frame) │  │           │
│  │  │  frame_token_B → RenderFrameImpl (iframe)  │  │           │
│  │  └────────────────────────────────────────────┘  │           │
│  └─────────────────────────────────────────────────┘           │
│                          │                                       │
│         ┌────────────────┼────────────────┐                     │
│         ▼                ▼                ▼                     │
│  ┌────────────┐  ┌─────────────────────────────────────┐        │
│  │RenderFrame │  │  AgentSchedulingGroup (site-b.com)   │        │
│  │            │  │  ┌────────────┐                      │        │
│  │ 实现:      │  │  │RenderFrame │                     │        │
│  │ ❌ 不继承  │  │  │            │                     │        │
│  │ IPC::Listen│  │  │ 实现:      │                     │        │
│  │ IPC::Sender│  │  │ ❌ 不继承  │                     │        │
│  └────────────┘  │  │ IPC::Listen│                     │        │
│                   │  └────────────┘                      │        │
│                   └─────────────────────────────────────┘        │
└─────────────────────────────────────────────────────────────────┘

消息接收流程(148 原生)------ 问题所在:

text

复制代码
IPC::Channel 收到消息
  → RenderThread::OnMessageReceived(msg)
    → 提取 routing_id
      → ❌ 没有 routing_id_map_,无法找到目标 RenderFrame
        → 消息被丢弃!

消息发送流程(148)------ 不受影响:

text

复制代码
RenderFrame::Send(msg)
  → agent_scheduling_group_->Send(msg)
    → channel_->Send(msg) 直接写入通道,不需要路由表

3.3 为什么只有接收受影响?

这个问题的关键点在于,接收消息依赖路由表,而发送消息不依赖

方向 路径 是否依赖 Render 端路由表 受影响
Browser → Render Channel → 路由表查 routing_id → RenderFrame
Render → Browser RenderFrame → Channel → Browser 路由表 (直接写 Channel)

发送方向:RenderFrame 直接持有 AgentSchedulingGroup(进而持有 Channel)的引用,可以直接写入。Browser 端的路由表仍然存在,所以 Browser 能正确接收。

接收方向:消息从 Channel 到达 Render 端后,需要根据 routing_id 找到对应的 RenderFrame。在 148 中,RenderThread 不再维护这个路由表,AgentSchedulingGroup 也只用 LocalFrameToken 做索引,routing_id 无法直接映射,导致消息无法分发。

四、解决方案

核心思路:在 148 的新架构上,手动重建 routing_id 路由表,让 Legacy IPC 消息可以正确分发。

4.1 修改 RenderFrame 接口

RenderFrame 重新成为 IPC 通信端点:

cpp

复制代码
// content/public/renderer/render_frame.h
// 修改前 (148原生):
class CONTENT_EXPORT RenderFrame : public base::SupportsUserData {
  // 不继承 IPC 接口
};

// 修改后:
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
#include "ipc/ipc_listener.h"
#include "ipc/ipc_sender.h"
#endif

class CONTENT_EXPORT RenderFrame :
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
    public IPC::Listener,      // 重新继承
    public IPC::Sender,        // 重新继承
#endif
    public base::SupportsUserData {
  // ...
};

4.2 在 AgentSchedulingGroup 中重建路由表

这是最关键的一步------在 AgentSchedulingGroup 中维护 routing_id → RenderFrame 的映射:

cpp

复制代码
// content/renderer/agent_scheduling_group.h
class AgentSchedulingGroup {
 private:
  // 原有的 LocalFrameToken 映射(Mojo 用)
  absl::flat_hash_map<blink::LocalFrameToken, 
                      raw_ptr<RenderFrameImpl>> listener_map_;

  // 新增: routing_id 映射(Legacy IPC 用)
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
  std::map<int32_t, raw_ptr<RenderFrameImpl>> routing_id_map_;
#endif
};

4.3 修改 Frame 注册/注销流程

cpp

复制代码
// content/renderer/agent_scheduling_group.cc

void AgentSchedulingGroup::AddFrameRoute(
    const blink::LocalFrameToken& frame_token,
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
    int routing_id,       // 新增参数
#endif
    RenderFrameImpl* render_frame,
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  
  // 保留原有的 LocalFrameToken 映射
  listener_map_.insert({frame_token, render_frame});
  
  // 新增 routing_id 映射和路由注册
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
  DCHECK(!base::Contains(routing_id_map_, routing_id));
  routing_id_map_.insert({routing_id, render_frame});
  render_thread_->AddRoute(routing_id, render_frame);
  render_thread_->AttachTaskRunnerToRoute(routing_id, std::move(task_runner));
#endif
}

void AgentSchedulingGroup::RemoveFrameRoute(
    const blink::LocalFrameToken& frame_token
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
    , int routing_id
#endif
) {
  listener_map_.erase(frame_token);
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
  DCHECK(base::Contains(routing_id_map_, routing_id));
  routing_id_map_.erase(routing_id);
  render_thread_->RemoveRoute(routing_id);
#endif
}

4.4 实现消息接收分发

cpp

复制代码
// content/renderer/agent_scheduling_group.cc

#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
bool AgentSchedulingGroup::OnMessageReceived(const IPC::Message& message) {
  DCHECK_NE(message.routing_id(), MSG_ROUTING_CONTROL);
  
  // 根据 routing_id 找到目标 RenderFrame
  auto* listener = GetListener(message.routing_id());
  if (!listener)
    return false;
  
  // 分发给目标 RenderFrame
  return listener->OnMessageReceived(message);
}

bool AgentSchedulingGroup::Send(IPC::Message* message) {
  std::unique_ptr<IPC::Message> msg(message);
  
  if (GetMBIMode() == features::MBIMode::kLegacy)
    return render_thread_->Send(msg.release());
  
  DCHECK_NE(message->routing_id(), MSG_ROUTING_CONTROL);
  DCHECK(channel_);
  return channel_->Send(msg.release());
}
#endif

// routing_id 查找辅助方法
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
RenderFrameImpl* AgentSchedulingGroup::GetListener(int32_t routing_id) {
  return base::FindPtrOrNull(routing_id_map_, routing_id);
}
#endif

4.5 适配 OnBadMessageReceived 签名变更

148 中 IPC::Listener::OnBadMessageReceived 的签名从无参变为带 const IPC::Message& 参数:

cpp

复制代码
// 修改前 (132):
void OnBadMessageReceived() override;

// 修改后 (148):
void AgentSchedulingGroup::OnBadMessageReceived(const IPC::Message& message) {
  return ToImpl(*render_thread_).OnBadMessageReceived(message);
}

4.6 适配 RenderFrameImpl 消息处理

cpp

复制代码
// content/renderer/render_frame_impl.cc

// 构造时传入 routing_id
agent_scheduling_group_->AddFrameRoute(
    frame_token_,
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
    routing_id_,
#endif
    this, GetTaskRunner(blink::TaskType::kInternalNavigationAssociated));

// 析构时传入 routing_id
agent_scheduling_group_->RemoveFrameRoute(
    frame_token_
#if BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
    , routing_id_
#endif
);

// 消息处理中适配 observer 调用
bool RenderFrameImpl::OnMessageReceived(const IPC::Message& msg) {
  for (auto& observer : observers_) {
#if defined(USE_CUSTOM_HACK) && BUILDFLAG(CONTENT_ENABLE_LEGACY_IPC)
    // 优先调用 OnMessageReceived
    if (observer.OnMessageReceived(msg))
      return true;
#endif
#ifdef USE_CUSTOM_HACK
    // 兼容旧的回调路径
    if (observer.OnMessageReceivedFromWidget(msg, nullptr))
      return true;
#endif
  }
  // ... 其他消息处理
}

4.7 修复后的完整消息流

text

复制代码
修复后:
IPC::Channel 收到消息
  → RenderThread::OnMessageReceived(msg)
    → AgentSchedulingGroup::OnMessageReceived(msg)
      → 提取 routing_id
        → routing_id_map_.find(routing_id) ✅ 找到了!
          → render_frame->OnMessageReceived(msg) 成功分发

五、踩坑总结

5.1 关键教训

  1. 理解架构变更的本质 :不要只看 API 变化,要理解 Chromium 为什么改。148 的目标是废弃 Legacy IPC 转向 Mojo,routing_id 路由被主动移除。

  2. 消息收发的不对称性:IPC 通信中,发送路径和接收路径的依赖不同。发送端直接持有 Channel 引用即可,接收端依赖路由表。这就是为什么只有接收受影响。

  3. Legacy IPC 的保护 :如果你的产品还大量使用 Legacy IPC(通过自定义宏控制),升核时必须重点关注 IPC::ListenerIPC::Sender 接口和路由机制的变更。

5.2 检查清单

升级 Chromium 大版本时,检查以下内容:

  • RenderFrame 是否还继承 IPC::ListenerIPC::Sender

  • RenderThread 是否还维护 routing_id_map_

  • AgentSchedulingGroup 的消息路由机制

  • OnBadMessageReceived 的签名是否变更

  • 自定义 RenderFrameObserver 的消息回调路径

  • AddRoute / RemoveRoute 的调用是否还存在

5.3 架构演进趋势

text

复制代码
Chromium 版本        IPC 机制                     路由方式
───────────         ────────                     ────────
132 及之前          Legacy IPC 为主              RenderThread 全局路由表
148                 Mojo 为主, Legacy IPC 废弃    AgentSchedulingGroup Token 路由
未来                Mojo 完全替代                 无全局路由

对于仍有 Legacy IPC 需求的产品,需要在每次升级时关注 Chromium 对旧 IPC 基础设施的裁剪,及时做兼容性修复。长期来看,逐步将 Legacy IPC 迁移到 Mojo 才是根本解决方案。


本文基于真实的浏览器内核升级工程经验编写,代码示例已脱敏处理,仅保留架构相关的核心逻辑。

相关推荐
笨蛋©1 小时前
[实战] 2026年制造业数字化质量审核 (Quality Audit) 深度解析
ai·数字化·质量管理·制造业·fai
FBI HackerHarry浩2 小时前
Ollama如何安装到D盘
python·ai
wuminyu2 小时前
Java世界中StringTable源码剖析
java·linux·c语言·jvm·c++
humors2212 小时前
AI案例:头脑风暴创作-正反论证-报告撰写-摘要总结
人工智能·ai·写作·总结·案例·论证
Sam09272 小时前
OpenClaw 和 Hermes 怎么结合:从聊天入口到隔离执行器的 Agent 工程实践
人工智能·ai
恼书:-(空寄2 小时前
接口乱改直接炸线上!微服务接口版本控制全方案:URL_请求头版本+接口兼容原则,老旧系统无痛迭代
微服务·架构
虎妞05002 小时前
多模态大模型应用指南:从 GPT-4V 到开源方案
ai·多模态·视觉·gpt-4v·llava
happyprince2 小时前
08_verl-Workers模块详解
人工智能·架构·强化学习
啾啾Fun2 小时前
【LLM应用可靠性】2-RAG 生产失败模式:如何避免检索生成系统的性能退化
ai·llm·系统设计·rag