Chromium settings_private 机制深度解析:一个 Pref 未注册引发的血案

wsl2上运行带有Isaac Gym的程序报错: GPU Pipeline: disabled 显示GPU Pipeline:disabled,查找相关文档发现是WSL2目前不支持Isaac Gym,时,想要在wsl2上运行,最后报错如下。 阅读详情

背景

在 Chromium 的设置页面(chrome://settings)中,前端通过 chrome.settingsPrivate.setPref() API 来修改浏览器配置项(Pref)。本文以一个真实 Bug 为切入点,深度剖析这套机制的底层原理。

Bug 现象:前端调用 settingsPrivate.setPref('performance_tuning.high_efficiency_mode.aggressiveness', value) 后,C++ 层的 SettingsPrivateSetPrefFunction::Run() 断点没有触发,前端也没有收到任何回调。


一、整体架构:三层设计

Chromium 的设置系统由三层组成:

前端 WebUI (TypeScript/HTML)
        ↓  chrome.settingsPrivate.setPref()
Extension API 层 (C++)
        ↓  PrefsUtil::SetPref()
PrefService 层 (C++)

1.1 前端层

设置页面是一个 WebUI,运行在 chrome://settings。前端通过 chrome.settingsPrivate 这个 Extension API 与 C++ 通信:

// 前端调用示例
chrome.settingsPrivate.setPref(
  'performance_tuning.high_efficiency_mode.aggressiveness',
  2  // kAggressive
);

1.2 Extension API 层

入口在 chrome/browser/extensions/api/settings_private/settings_private_api.cc:

ExtensionFunction::ResponseAction SettingsPrivateSetPrefFunction::Run() {
  auto parameters = api::settings_private::SetPref::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(parameters);

  SettingsPrivateDelegate* delegate =
      SettingsPrivateDelegateFactory::GetForBrowserContext(browser_context());

  settings_private::SetPrefResult result =
      delegate->SetPref(parameters->name, &parameters->value);
  // ...
}

1.3 PrefService 层

Chromium 的配置系统核心,负责持久化存储和读取所有配置项。


二、关键机制:Allowlist(白名单)

settings_private 不是一个万能的 Pref 读写接口。它有严格的白名单机制,只有在 GetAllowlistedKeys() 中注册的 Pref 才能被前端访问。

文件位置:chrome/browser/extensions/api/settings_private/prefs_util.cc

const PrefsUtil::TypedPrefMap& PrefsUtil::GetAllowlistedKeys() {
  static PrefsUtil::TypedPrefMap* s_allowlist = nullptr;
  if (s_allowlist) {
    return *s_allowlist;
  }
  s_allowlist = new PrefsUtil::TypedPrefMap();

  // Performance settings.
  (*s_allowlist)
      [performance_manager::user_tuning::prefs::kMemorySaverModeState] =
          settings_api::PrefType::kNumber;
  (*s_allowlist)[performance_manager::user_tuning::prefs::
                     kMemorySaverModeAggressiveness] =
      settings_api::PrefType::kNumber;
  // ...
  return *s_allowlist;
}

白名单的作用:

  1. 安全隔离:防止恶意扩展或页面随意修改任意 Pref
  2. 类型声明:声明每个 Pref 的预期类型(kNumber/kBoolean/kString 等)
  3. 变更监听:settings_private_event_router 只监听白名单内的 Pref 变化

三、SetPref 完整调用链

chrome.settingsPrivate.setPref(name, value)
  → SettingsPrivateSetPrefFunction::Run()
    → SettingsPrivateDelegate::SetPref()
      → PrefsUtil::SetPref()
        ① GetAllowlistedPrefType(pref_name)  // 检查白名单
        ② FindServiceForPref(pref_name)       // 找到对应的 PrefService
        ③ IsPrefUserModifiable(pref_name)     // 检查是否可修改
        ④ pref_service->FindPreference(pref_name)  // 关键:查找 Pref 是否存在
        ⑤ pref_service->SetInteger(pref_name, value)  // 实际写入

3.1 步骤①:白名单检查

if (GetAllowlistedPrefType(pref_name) == settings_api::PrefType::kNone) {
  return settings_private::SetPrefResult::PREF_NOT_FOUND;
}

如果 Pref 不在白名单里,直接返回 PREF_NOT_FOUND。

3.2 步骤②:FindServiceForPref

PrefService* PrefsUtil::FindServiceForPref(const std::string& pref_name) {
  PrefService* user_prefs = profile_->GetPrefs();

  // 优先查 profile prefs
  if (user_prefs->FindPreference(pref_name)) {
    return user_prefs;
  }
  // 再查 local_state
  if (g_browser_process->local_state()->FindPreference(pref_name)) {
    return g_browser_process->local_state();
  }
  // 默认返回 user_prefs(此时 FindPreference 会返回 null)
  return user_prefs;
}

这里有个关键点:如果 Pref 没有被注册,两个 FindPreference 都返回 null,函数默认返回 user_prefs。

3.3 步骤④:致命的 FindPreference

const PrefService::Preference* pref = pref_service->FindPreference(pref_name);
if (!pref) {
  return settings_private::SetPrefResult::PREF_NOT_FOUND;
}

如果 Pref 没有被注册到任何 PrefService,这里就会返回 null,整个调用返回 PREF_NOT_FOUND。


四、PrefService 的两种存储:local_state vs profile prefs

Chromium 有两个主要的 PrefService 实例:

特性local_stateprofile prefs
作用域全局,所有用户共享每个用户 Profile 独立
注册函数RegisterLocalStatePrefs()RegisterProfilePrefs()
存储位置Local State 文件Preferences 文件
典型用途性能设置、系统级配置用户偏好、书签、历史

文件位置:components/performance_manager/user_tuning/prefs.cc

void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
  registry->RegisterIntegerPref(
      kMemorySaverModeState,
      static_cast<int>(MemorySaverModeState::kDisabled));

#ifdef USE_360HACK
  registry->RegisterIntegerPref(
      kMemorySaverModeAggressiveness,
      static_cast<int>(MemorySaverModeAggressiveness::kMedium));
#endif
  // ...
}

注意:kMemorySaverModeAggressiveness 被包在 #ifdef USE_360HACK 里!如果这个宏没有定义,这个 Pref 就不会被注册。


五、根因分析:Pref 注册 vs Allowlist 的区别

这是本次 Bug 的核心认知:

Allowlist(白名单)= 权限名单
PrefRegistry(注册)= 户口本

有权限但没上户口,API 也改不了。

具体来说:

  • Allowlist 只是告诉 settings_private 系统"这个 Pref 允许被前端访问"
  • PrefRegistry 才是真正让 Pref 在 PrefService 中"存在"的操作

两者缺一不可:

情况1:在 Allowlist,也在 Registry → ✅ 正常工作
情况2:在 Allowlist,不在 Registry → ❌ PREF_NOT_FOUND(本次 Bug)
情况3:不在 Allowlist,在 Registry → ❌ PREF_NOT_FOUND(第一道关卡就拦截)
情况4:都不在 → ❌ PREF_NOT_FOUND

六、变更通知机制:settings_private_event_router

当 Pref 被成功修改后,前端如何收到通知?

文件位置:chrome/browser/extensions/api/settings_private/settings_private_event_router.cc

// 启动时,对白名单中所有 Pref 注册监听
const PrefsUtil::TypedPrefMap& keys = prefs_util_->GetAllowlistedKeys();
for (const auto& it : keys) {
  // 找到对应的 PrefChangeRegistrar
  PrefChangeRegistrar* registrar = FindRegistrarForPref(it.first);
  if (registrar) {
    registrar->Add(it.first, base::BindRepeating(
        &SettingsPrivateEventRouter::OnPreferenceChanged, ...));
  }
}

当 Pref 值变化时,事件路由器会触发 onPrefsChanged 事件,前端监听这个事件来更新 UI:

chrome.settingsPrivate.onPrefsChanged.addListener((prefs) => {
  // 更新 UI
});

如果 Pref 没有注册,FindRegistrarForPref 也找不到对应的 registrar,监听就不会建立,前端自然收不到变更通知。


七、完整的 Bug 复现路径

1. 前端点击"最大限度"单选按钮
2. 触发 sendPrefChange()
3. 调用 chrome.settingsPrivate.setPref(
     'performance_tuning.high_efficiency_mode.aggressiveness', 2)
4. C++ 收到请求,进入 SettingsPrivateSetPrefFunction::Run()
   ↓ 断点在这里,但如果前端根本没发请求,这里就不会触发
5. PrefsUtil::SetPref() 被调用
6. GetAllowlistedPrefType() → kNumber(白名单有)
7. FindServiceForPref() → 两个 FindPreference 都返回 null → 返回 user_prefs
8. pref_service->FindPreference(pref_name) → null(没注册!)
9. 返回 PREF_NOT_FOUND
10. 前端收到 false 或 error,UI 状态不更新

八、修复方案

确保 kMemorySaverModeAggressiveness 被正确注册:

// components/performance_manager/user_tuning/prefs.cc

void RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
  // ...
  registry->RegisterIntegerPref(
      kMemorySaverModeAggressiveness,
      static_cast<int>(MemorySaverModeAggressiveness::kMedium));
  // ...
}

或者如果它应该是 Profile 级别的(跟随用户):

void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
  // ...
  registry->RegisterIntegerPref(
      kMemorySaverModeAggressiveness,
      static_cast<int>(MemorySaverModeAggressiveness::kMedium));
}

九、总结:新增一个 settings_private 可访问的 Pref 的完整步骤

  1. 定义 Pref 名称常量(prefs.h):

    inline constexpr char kMyNewPref[] = "my.new.pref";
    
  2. 注册 Pref(prefs.cc):

    // 根据作用域选择注册位置
    registry->RegisterIntegerPref(kMyNewPref, default_value);
    
  3. 加入 Allowlist(prefs_util.cc):

    (*s_allowlist)[my_component::prefs::kMyNewPref] =
        settings_api::PrefType::kNumber;
    
  4. 前端使用:

    chrome.settingsPrivate.setPref('my.new.pref', value);
    chrome.settingsPrivate.getPref('my.new.pref', (pref) => { ... });
    

缺少任何一步都会导致 API 调用失败。 这就是本次 Bug 的根本原因——步骤 2 在特定编译条件下被跳过了。

Kafka 入门篇(一) 初识 Kafka 什么是 Kafka Kafka 的基本术语 Kafka 的特性(设计原则) Kafka 的使用场景 Kafka 的消息队列 点对点模式的消息队列 发布-订阅模式的消息队列 Kafka 系统架构 核心 API Kafka 为何如此之快 broker 端配置 broker.id port zookeeper.connect log.dirs num.recovery.threads.per.data.dir auto.create.topics.enable topic默认配置 num.par 阅读详情

相关推荐

大模型时代必备技能:3种向量量化技术对比(PQ/AQ/RVQ)与选型指南

本文深入解析了大模型时代处理海量向量数据的关键技术——向量量化。通过对比Product Quantization (PQ)、Additive Quantization (AQ)和Residual Vector Quantization (RVQ)三种主流技术的核心原理、性能表现与适用场景,为工程师提供了从理论到实践的选型指南,旨在解决向量存储与检索的效率瓶颈,助力RAG等大模型应用高效落地。

silver的专栏 795

深入解析浏览器在线页与本地页的实现差异:从资源加载到安全机制

本文基于Chromium内核分析浏览器中在线页与本地页的实现差异。在线页通过HTTP请求远程资源,受网络影响较大,需遵循严格的安全策略;而本地页资源内置在浏览器安装包中,加载速度快,但依赖浏览器升级更新。两者的安全机制不同,本地页具有更高的浏览器API访问权限。现代浏览器趋向采用混合模式,框架资源本地化,动态内容远程获取,以兼顾性能与灵活性。理解这些差异有助于浏览器开发、扩展开发和安全研究。

John_ToStr的博客 1079

C# WinForms实战:5分钟用ScottPlot打造专业级示波器界面(附完整代码)

本文详细介绍了如何在C# WinForms应用中,利用ScottPlot.WinForms库快速构建一个专业级的软件示波器界面。通过从环境配置、静态波形绘制到动态数据实时更新的完整实战演示,帮助开发者高效实现高性能的波形显示功能,适用于工业监控、信号分析等多种场景。

weixin_29198045的博客 831

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

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

2401_84466325的博客 182

STM32F407标准库下ADC多通道DMA采集的配置与优化

本文详细介绍了STM32F407标准库下ADC多通道DMA采集的配置与优化方法。通过GPIO模拟输入配置、ADC时钟和模式设置、DMA传输参数配置等关键步骤,实现高效的多通道数据采集。文章还提供了性能优化技巧和常见问题排查指南,帮助开发者提升采样速率和稳定性。

weixin_29192141的博客 427

S7-1200中时钟功能设定和读写调用的具体方法

S7-1200中时钟功能设定和读写调用的具体方法

Robot_PLC_HMI_Automation 1万+

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

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

m0_74451345的博客 99

PCL 区域生长分割【2026最新版】

区域生长算法原理介绍、代码实现以及一个具体实验场景的应用案例进一步实现将分割结果的分类保存至本地文件夹。最近一次更新时间为:2026年5月4日。

点云侠的博客 40万+

【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

学Simulink--基础微电网场景实例:基于Simulink的微电网并网/孤岛无缝切换控制仿真

本文完成了基于 Simulink 的微电网并网/孤岛无缝切换控制仿真,实现了:✅ 构建含双模式逆变器的微电网模型✅ 实现预同步 + Stateflow 逻辑的智能切换✅ 验证计划与非计划切换的性能边界✅ 证明“无缝”在工程上是可实现的。

amy_mhd的博客 101

个人顶级AI助手OpenClaw(Clawdbot) - 新手快速入门指南

本文详细介绍了个人顶级AI助手OpenClaw(Clawdbot) 的快速入门指南,包括安装部署和使用配置及注意事项

program哲学 5301

Chrome 扩展安全攻防实录:从权限滥用到供应链投毒

汇总开发者自查清单与使用者风险识别要点,给出可执行的落地建议。

FJ39SfgYS的博客 205

第16课:动手实战中文句法依存分析

句法分析是自然语言处理(NLP)中的关键技术之一,其基本任务是确定句子的句法结构或者句子中词汇之间的依存关系。主要包括两方面的内容:一是确定语言的语法体系,即对语言中合法句子的语法结构给予形式化的定义;另一方面是句法分析技术,即根据给定的语法体系,自动推导出句子的句法结构,分析句子所包含的句法单位和这些句法单位之间的关系。 句法分析被用在很多场景中,比如搜索引擎用户日志分析和关键词识别,比如信息抽...

春华秋实 2435

墨墨助记 Moji v0.3.0 升级|Chrome 网页离线查词,收藏生词也能留下原句

墨墨助记 Moji v0.3.0 为桌面 Chrome 带来网页离线查词:双击生词或划选短语,点一下放大镜,就能在原文旁看释义、助记和例句。连接墨墨后,可把生词、网页原句和中文译文一起收藏,还能分别设置参与高亮的复习词本和新词的收藏去向。文中用 abandon 演示完整流程。

长路漫漫的歇脚处 219

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Chrome 内存泄漏诊断指南:从现象到根因的完整排查路径

诊断流程回顾:现象 → 基线 → 趋势 → 快照 → 引用链 → 修复 → 验证工具选择建议与学习资源。

qq_44304677的博客 290

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Chrome 内存泄漏诊断指南:从现象定位到根因排查

本文系统解析前端内存泄漏的成因、诊断与预防,涵盖堆栈内存模型、V8垃圾回收机制及常见泄漏场景(如未解绑事件、定时器、闭包滥用等)。通过Chrome DevTools性能面板、堆快照对比、分配时间线等工具,结合“五步定位法”实战排查,剖析路由切换、虚拟列表、WebSocket缓存等典型案例。强调编码规范、生命周期管理与工程化监控,提出“先确认、再定位、后验证”的方法论,助力开发者提升应用稳定性与性能。

2401_84488651的博客 304

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Chrome内存占用过高:2026年完整优化指南

Chrome 开几个标签页内存就飙到90%以上,风扇狂转、系统卡顿?本文从标签管理、进程策略、内存回收、后台限制到实验性开关,一步步教你把 Chrome 内存占用压下来,多开页面也能保持流畅。

the3clipse的博客 309

Linux Bash 常用操作与历史参数复用

……

u013669912的博客 332

命令行参数、环境变量与虚拟地址空间:从 main 的形参说起

本文详解命令行参数、环境变量和虚拟地址空间。程序启动时,内核通过exec将命令行参数(argc、argv)和环境变量(envp)加载至栈顶,由main函数接收。argv以NULL结尾,envp同理,可通过environ或getenv访问。环境变量由父进程继承,通过export传递给子进程。这些数据位于进程地址空间的栈顶,按低到高顺序排列:argc、argv数组、NULL、envp数组、NULL、辅助向量及字符串内容。理解此结构有助于掌握程序启动机制与内存管理原理。

dev_jin的博客 122

【Chrome 开发者工具】如何使用 Network 下载网页中的 HTML 文件?

在日常学习或开发过程中,我们经常会遇到这样的情况:打开一个网页,里面嵌入了 HTML 格式的课件、幻灯片或者其他在线资源,但网页本身没有提供明显的下载按钮。如果直接使用浏览器的Ctrl + S保存网页,有时只能保存当前页面的 HTML,而无法准确获取嵌入其中的资源。那么,如何找到网页真正加载的 HTML 文件,并将其下载到本地呢?利用 Chrome 浏览器开发者工具(DevTools)中的 Network 面板,定位 HTML 资源并下载。整个过程无需安装额外软件,也不需要编写代码。

ShawnLiaoking的博客 261
上一篇: 从“会调用”到“稳得住”:Agent工具使用与MCP安全交互深度剖析
下一篇: 深入 Chromium 底层:异步任务中的生命周期魔法与引用计数解密
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

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

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

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

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

打赏作者

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

抵扣说明:

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

余额充值