Chrome 首次启动引导页里触发 Pref 设置,为什么主进程收不到 IPC

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

在做浏览器首次启动引导功能时,我遇到一个非常典型、但又很容易误判的问题:

很多人第一反应会怀疑:

  • 是不是 base::BindOnce() 参数绑错了?
  • 是不是 proceed 参数位置不对?
  • 是不是首次启动回调没正确触发?

但真正的问题,通常不在回调绑定,而在页面所处的运行环境和消息通道根本不是一条链路。

这篇文章就把这个问题从架构层、消息链路层、对象生命周期层彻底拆开。


一、问题现象

先看启动阶段的核心代码。首次启动时,代码会走到类似下面这段逻辑:

if ((first_run::IsChromeFirstRun() ||
     command_line.HasSwitch("show-startup-devtools")) && first_run_) {
  base::OnceCallback<void(bool, std::vector<std::string>)> continue_startup =
      base::BindOnce(
          [](const base::CommandLine& command_line,
             Profile* profile,
             const base::FilePath& cur_dir,
             const std::vector<GURL>& urls,
             chrome::startup::IsProcessStartup process_startup,
             chrome::startup::IsFirstRun is_first_run,
             bool proceed_arg,
             std::vector<std::string> runtime_args) {
            StartupBrowserCreator creator;
            base::CommandLine local_cmd_line = command_line;
            for (const auto& arg : runtime_args) {
              local_cmd_line.AppendSwitch(arg);
            }
            OpenNewWindowForFirstRun(command_line, profile, cur_dir, urls,
                                     process_startup, is_first_run,
                                     proceed_arg);
          },
          command_line,
          profile,
          cur_dir,
          first_run_tabs_,
          process_startup,
          is_first_run);

  StartupGuideView::Show(profile, std::move(continue_startup));
  first_run_ = false;
  return;
}

这个逻辑的意图其实非常清晰:

  1. 如果是浏览器首次运行,先不要直接开主窗口;
  2. 先显示引导页 StartupGuideView::Show();
  3. 把真正“继续启动浏览器”的逻辑包装成一个 base::OnceCallback<void(bool, std::vector<std::string>)>;
  4. 等引导页完成后,再执行这个 callback 去继续启动主浏览器窗口。

而我遇到的问题是:

对应的消息接收代码长这样:

bool ExternalApisTabHelper::OnMessageReceived(const IPC::Message& message,
    content::RenderFrameHost* render_frame_host) {
  bool handled = true;
  IPC_BEGIN_MESSAGE_MAP(ExternalApisTabHelper, message)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppCmd, OnAppCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppCmdBuffer, OnAppCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_FileApiCmd, OnFileApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_FileApiCmdBuffer, OnFileApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_RegistryApiCmd, OnRegistryApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_RegistryApiCmdBuffer, OnRegistryApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_DesktopApiCmd, OnDesktopApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_DesktopApiCmdBuffer, OnDesktopApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_PrefApiCmd, OnPrefApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_PrefApiCmdBuffer, OnPrefApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_BrowserApiCmd, OnBrowserApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_BrowserApiCmdBuffer, OnBrowserApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_TabApiCmd, OnTabApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_TabApiCmdBuffer, OnTabApiCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExternalStat, OnExternalStat)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_WidgetApiCmd, OnWidgetCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_WidgetApiCmdBuffer, OnWidgetCmdBuffer)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_EventApiCmd, OnEventCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_SettingPanelApiCmd, OnSettingPanelApiCmd)
    IPC_MESSAGE_HANDLER(ExtensionHostMsg_SettingPanelApiCmdBuffer, OnSettingPanelApiCmdBuffer)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()

  return handled;
}

其中 pref 分支是:

void ExternalApisTabHelper::OnPrefApiCmd(
    const ExtensionHostMsg_PrefApiCmd_Params& params) {
  int prefcmd_id = params.invoke_id;
  const std::string& module_name = params.s1;
  const std::string& function_name = params.s2;
  const std::string& p1 = params.s3;
  const std::string& p2 = params.s4;
  const std::string& p3 = params.s5;
  const std::string& p4 = params.s6;

  if (module_name == "prefOperation") {
    prefcmd_handler::OnPrefApiCmd(prefcmd_id, module_name, function_name, p1,
                                  web_contents(), nullptr);
  }
}

现象非常明确:这里根本没进来。


二、先把一个容易误判的点说清楚:BindOnce 没有问题

很多人看到上面的 lambda,会卡在这个地方:

bool proceed_arg,
std::vector<std::string> runtime_args

然后怀疑:

proceed 不应该被 bind,应该是运行时参数吧?

这个判断本身是对的。

在 Chromium 的 callback 模型中,base::BindOnce() 的行为是:

  • 前面传进去的实参,会被提前固化;
  • lambda 末尾剩下的形参,会变成 callback 在 Run() 时需要提供的运行时参数。

所以你现在这个写法里:

  • 已经 bind 的:
    • command_line
    • profile
    • cur_dir
    • urls
    • process_startup
    • is_first_run
  • 没有 bind 的:
    • proceed_arg
    • runtime_args

因此最后得到的 callback 类型正好就是:

base::OnceCallback<void(bool, std::vector<std::string>)>

也就是说:

  • proceed_arg 是运行时参数;
  • runtime_args 也是运行时参数;
  • 这和你想实现的语义一致。

所以,这段回调绑定不是 IPC 收不到的主因。

它最多只会影响“引导页结束后是否继续开浏览器窗口”,而不会决定“引导页里的 JS 能否发到 ExternalApisTabHelper”。

这两个问题是两个层次:


三、真正的关键:首次启动引导页不是普通浏览器标签页

这是整个问题最核心的地方。

1. 普通“外部 API 页面”的创建方式

我在代码里确认到,挂载 ExternalApisTabHelper 的地方并不是全局自动发生的,而是依赖特定的 WebContents 创建路径。

在 WebHostView::WebHostView() 里有这样一段:

web_contents_ = WebContents::Create(
    content::WebContents::CreateParams(browser_context, std::move(inset)));

PrefsTabHelper::CreateForWebContents(web_contents_.get());
extensions::SetViewType(web_contents_.get(),
    extensions::mojom::ViewType::kExtensionPopup);
web_contents_->SetDelegate(this);
web_contents_->SetZoomLevelDisable(true);
content::WebContentsObserver::Observe(web_contents_.get());
PrefsTabHelper::CreateForWebContents(web_contents_.get());
ExternalApisTabHelper::CreateForWebContents(web_contents_.get());
prefs_notify::PrefsNotifyTabHelper::CreateForWebContents(web_contents_.get());

注意其中最关键的三件事:

  1. 给 WebContents 挂了 PrefsTabHelper::CreateForWebContents()
  2. 挂了 ExternalApisTabHelper::CreateForWebContents()
  3. 挂了 prefs_notify::PrefsNotifyTabHelper::CreateForWebContents()

这说明:

这套 pref / browser / widget / external API 的消息接收能力,并不是 WebContents 天生自带,而是通过 helper 显式挂上去的。

也就是说,如果某个页面对应的 WebContents 没有执行这三步初始化,那么:

  • 它就没有 ExternalApisTabHelper;
  • 它也就不会接收 ExtensionHostMsg_PrefApiCmd;
  • 你的 OnPrefApiCmd() 自然永远进不去。

2. 首次启动引导页的创建方式

再看你的引导页 StartupGuideView 是怎么创建页面的:

web_view_ = AddChildView(std::make_unique<views::WebView>(profile));
web_view_->LoadInitialURL(GURL("chrome://startup-guide/"));

这是一个完全不同的创建路径。

它不是通过 WebHostView 来创建页面,而是通过 views::WebView 直接承载一个 chrome://startup-guide/ 页面。

这个差异非常关键:

  • WebHostView 是你们定制链路里的“宿主页面容器”;
  • views::WebView 则是 Chromium 通用的嵌入式 WebContents 视图控件;
  • 后者不会自动附带你们在前者里显式创建的 helper。

因此,首次启动引导页所在的 WebContents,默认并没有接入 ExternalApisTabHelper 这套消息接收体系。

这就是为什么你在引导页控制台里调 pref,主进程却收不到 ExtensionHostMsg_PrefApiCmd。

不是因为消息中途丢了,而是因为:

这条页面根本没有连接到那根线。


四、为什么“在控制台里触发 pref 设置”不等于“会走到 ExternalApisTabHelper”

这里还涉及第二个经常被忽略的技术点:页面类型不同,前后端通信机制也不同。

你的引导页加载的是:

chrome://startup-guide/

这说明它是一个 WebUI 页面,而不是普通扩展页,也不是普通网页。

1. WebUI 的标准通信方式

WebUI 的标准 JS -> Browser 通信,不是 ExtensionHostMsg_* 这套旧 IPC,而是:

你当前的 StartupGuideUI 里已经明确用了这套机制:

web_ui->RegisterMessageCallback(
    "finishSetup",
    base::BindRepeating(&StartupGuideUI::HandleFinishSetup,
                        base::Unretained(this)));

web_ui->RegisterMessageCallback(
    "showWindow",
    base::BindRepeating(&StartupGuideUI::HandleShowWindow,
                        base::Unretained(this)));

也就是说,这个页面天然应该走的是 WebUI message pipeline。

2. ExternalApisTabHelper 的通信方式

而 ExternalApisTabHelper::OnMessageReceived() 处理的则是另一套链路:

IPC_MESSAGE_HANDLER(ExtensionHostMsg_PrefApiCmd, OnPrefApiCmd)

这意味着它依赖的是:

  • Renderer 侧发送 ExtensionHostMsg_PrefApiCmd
  • Browser 侧有对应的 observer / listener / helper 挂在这个 WebContents 上
  • 最终进入 OnMessageReceived()

所以从架构上讲,这其实是两套完全不同的前后端桥接方式:

  1. chrome.send / RegisterMessageCallback:WebUI 体系
  2. ExtensionHostMsg_* / IPC_MESSAGE_HANDLER:扩展/旧 IPC 体系

你在一个 WebUI 页面里,期待它自动具备另一套体系的收发能力,本身就是不成立的,除非你显式给这个页面对应的 WebContents 补齐那套初始化。


五、从对象关系上看,消息到底卡在哪一层

我们把对象关系画清楚,问题会更容易理解。

场景 A:普通承载 External API 的页面

对象链路大概是:

WebHostView
  └── WebContents
        ├── PrefsTabHelper
        ├── ExternalApisTabHelper
        └── PrefsNotifyTabHelper

Renderer 发出 ExtensionHostMsg_PrefApiCmd 后:

Renderer
  -> Browser-side WebContents route
  -> ExternalApisTabHelper::OnMessageReceived()
  -> ExternalApisTabHelper::OnPrefApiCmd()
  -> prefcmd_handler::OnPrefApiCmd()

场景 B:首次启动引导页

对象链路大概是:

StartupGuideView
  └── views::WebView
        └── WebContents
              └── WebUI (StartupGuideUI)

这条链路里默认只有:

却没有:

所以消息会卡在什么地方?

答案是:

不是卡在 OnPrefApiCmd(),而是在更前面——接收器根本不存在。

即使你的前端代码“自认为”发出了某种 pref 指令,也不意味着 Browser 侧这条 WebContents 有对应的接收对象。


六、为什么这个问题在首次启动场景特别容易出现

因为首次启动往往有一种“错觉”:

看起来都是浏览器里显示的一个页面,应该和普通 tab 差不多。

但实际上首次启动往往是“浏览器 UI 初始化前”的一个临时宿主环境,它和正常标签页存在几个关键差异。

1. 生命周期不同

首次启动引导页在 StartupGuideView::Show() 中创建,展示结束后才触发真正的浏览器窗口启动。

这意味着:

  • 引导页先活着;
  • 真正的浏览器主窗口还没起来,或者还没完成正常初始化;
  • Browser*、TabStrip、普通页面 helper 链路可能都还不是稳定状态。

而普通 tab 的很多 Browser-side helper,天然是围绕“已存在的浏览器窗口”和“常规 tab 生命周期”设计的。

2. 宿主环境不同

首次启动页当前使用的是 views::WebView,不是 WebHostView。

这意味着:

  • 它少了 自定义宿主的初始化逻辑;
  • 少了 helper 挂载;
  • 少了某些 view type 标记;
  • 少了某些默认代理和委托。

3. 通信模型不同

首次启动页是 chrome://startup-guide/,天然属于 WebUI 页面。

WebUI 页面的推荐通信方式,本来就不应该优先走 ExtensionHostMsg_*。

所以这类问题非常容易在“首启页、设置页、引导页、气泡页、悬浮窗页”等非标准 tab 宿主环境里暴露出来。


七、如何验证问题确实出在 helper 没挂上

如果你想把这个问题彻底坐实,最有效的方式不是猜,而是分层打日志。

验证 1:引导页 WebContents 是否存在

在 StartupGuideView 处加日志:

auto* web_contents = web_view_->GetWebContents();
LOG(INFO) << "startup guide web_contents=" << web_contents;

验证 2:helper 是否被挂载

如果 ExternalApisTabHelper 提供了 FromWebContents(...),可以这样打:

LOG(INFO) << "external api helper="
          << ExternalApisTabHelper::FromWebContents(web_contents);

如果输出为空,问题就已经坐实了。

验证 3:消息接收函数是否有任何触发

在 ExternalApisTabHelper::OnMessageReceived() 开头打印:

LOG(INFO) << "ExternalApisTabHelper::OnMessageReceived type=" << message.type();

如果普通页面能看到日志、引导页看不到,那就说明两者根本不是同一接收链路。

验证 4:WebUI 自己的 callback 是否能收到

在 StartupGuideUI 注册一条测试消息:

web_ui->RegisterMessageCallback(
    "pingNative",
    base::BindRepeating(&StartupGuideUI::HandlePingNative,
                        base::Unretained(this)));

如果前端 chrome.send('pingNative') 能到达,而 pref IPC 到不了,就能进一步证明:

页面本身没问题,问题出在你走错了通信通道。


八、修复思路一:按 WebUI 体系重做 pref 设置(推荐)

这是我更推荐的方案,因为它和当前页面的架构天然一致。

既然 chrome://startup-guide/ 是一个 WebUI 页面,那就让它走 WebUI 的标准消息机制。

1. 在 StartupGuideUI 里注册消息

比如增加:

web_ui->RegisterMessageCallback(
    "setStartupPref",
    base::BindRepeating(&StartupGuideUI::HandleSetStartupPref,
                        base::Unretained(this)));

2. 在 C++ 里实现处理逻辑

void StartupGuideUI::HandleSetStartupPref(const base::Value::List& args) {
  AllowJavascript();

  if (args.size() < 2)
    return;

  const std::string* pref_name = args[0].GetIfString();
  const std::string* pref_value = args[1].GetIfString();
  if (!pref_name || !pref_value)
    return;

  Profile* profile = Profile::FromWebUI(web_ui());
  if (!profile)
    return;

  profile->GetPrefs()->SetString(*pref_name, *pref_value);
}

3. JS 侧直接调用

chrome.send('setStartupPref', ['my.pref.key', '1']);

这种方式的优点是:

  • 跟 StartupGuideUI 当前已有的消息机制一致;
  • 不依赖 ExternalApisTabHelper;
  • 不依赖 ExtensionHostMsg_*;
  • 更符合 Chromium 对 WebUI 页面的设计方式;
  • 维护成本更低。

如果你的引导页只需要做少量 pref 写入,这是最干净的方案。


九、修复思路二:在引导页的 WebContents 上补挂 External API helper

如果你的目标不是“简单改几个 pref”,而是必须复用现有整套 prefOperation JS 封装,那可以考虑把首次启动页也接入同一条 helper 链路。

在 StartupGuideView::StartupGuideView() 中,拿到 WebContents 后,参考 WebHostView::WebHostView() 的初始化方式补挂 helper。

伪代码如下:

auto* web_contents = web_view_->GetWebContents();
if (web_contents) {
  PrefsTabHelper::CreateForWebContents(web_contents);
  ExternalApisTabHelper::CreateForWebContents(web_contents);
  prefs_notify::PrefsNotifyTabHelper::CreateForWebContents(web_contents);
}

必要时还可能要补:

extensions::SetViewType(web_contents,
    extensions::mojom::ViewType::kExtensionPopup);

但这里有几个风险要注意。

风险 1:你只是“挂了 helper”,不代表 Renderer 一定会发同样的 IPC

ExternalApisTabHelper::OnMessageReceived() 是 Browser 侧接收器,但前提是 Renderer 侧也得有对应发送逻辑。

而 WebUI 页面和扩展页的 renderer 环境差异很大:

  • JS 注入方式不同;
  • 暴露给页面的 native bridge 不同;
  • 安全策略不同;
  • 是否允许某些 API 注入也不同。

所以你不能只看 Browser 侧挂没挂 helper,还要确认:

引导页前端实际调用的 prefOperation,到底是不是在发送 ExtensionHostMsg_PrefApiCmd。

风险 2:helper 挂载顺序可能影响行为

某些 helper 依赖:

  • WebContentsDelegate
  • ViewType
  • profile / browser context
  • 甚至特定 navigation state

因此照抄 WebHostView::WebHostView() 也不一定 100% 等价。

风险 3:架构污染

如果一个页面本质上是 WebUI 页面,却为了复用旧 API 硬接 ExtensionHostMsg_*,长期看可能把架构越搞越乱:

  • WebUI 逻辑一部分走 chrome.send
  • 一部分走 ExtensionHostMsg_*
  • 后期排查问题会非常痛苦

所以除非你必须复用成熟的 external api 体系,否则我仍然建议优先走 WebUI 原生消息。


十、为什么 WebHostView::OnMessageReceived() 只处理 AppCmd 也值得关注

还有一个容易被忽略的细节:在 WebHostView 里,OnMessageReceived 其实只处理了:

IPC_MESSAGE_HANDLER(ExtensionHostMsg_AppCmd, OnAppCmd)

而你贴出来的 pref 处理是在 ExternalApisTabHelper::OnMessageReceived() 里。

这进一步说明:

  • WebHostView 自己并不负责所有 External API 消息;
  • 更完整的处理分发逻辑是在 ExternalApisTabHelper 里;
  • 因此光有 WebContents 本身还不够,必须把 helper 挂上去,才能接到 PrefApiCmd 这类消息。

这个点从侧面再次证明了前面的判断:

引导页如果没挂 ExternalApisTabHelper,就不可能走到 OnPrefApiCmd()。


十一、从 Chromium 架构角度看,这个问题本质是什么

如果把这件事抽象一下,它本质上是一个非常典型的 Chromium/Browser 架构问题:

1. WebContents 只是页面容器,不等于“功能完备页面”

很多人会误以为:

只要我有一个 WebContents,前端发过来的所有东西 Browser 都能收到。

其实不是。

WebContents 更像是一个“浏览内容载体”。真正的业务能力,往往来自挂在它身上的各种 helper / observer / delegate:

所以“同样都是页面”,功能是否可用,取决于它是不是走了同一套组装路径。

2. Browser-side capability 是“组装出来的”

首次启动页与普通 tab 最大的区别,不在于 UI 长得不同,而在于:

  • 它们的 WebContents 创建者不同;
  • 初始化流程不同;
  • 挂载 helper 不同;
  • 所属消息通道不同。

也就是说:

Chromium 里很多能力不是“页面自带”,而是“宿主装配出来的”。

3. 控制流与消息链路是两套正交系统

这次问题很有代表性的一点在于:

  • 你一开始看的是 BindOnce 和 continue_startup;
  • 但真正的问题发生在 WebContents 的消息接收装配层。

这说明在浏览器开发中,必须把两个维度分开看:

  1. 控制流:谁先创建、谁后显示、谁回调谁
  2. 通信流:谁能给谁发消息、谁在接收、谁具备处理能力

很多“看起来像回调有问题”的 bug,最后本质都是“对象根本不在同一个消息平面上”。


十二、我最终给出的结论

把这次问题压缩成一句话就是:

首次启动引导页 StartupGuideView 里使用的是 views::WebView + WebUI 机制创建的 chrome://startup-guide/ 页面,而不是走 WebHostView 那条会自动挂载 ExternalApisTabHelper 的创建链路。因此,引导页中触发的 pref 操作并不会自然进入 ExternalApisTabHelper::OnPrefApiCmd()。

换句话说:

  • base::BindOnce() 参数位置不是主要问题;
  • proceed 作为运行时参数保留在 callback 末尾是正确的;
  • 真正的问题是:首启引导页的 WebContents 没有接入你预期的 External API IPC 接收体系。

十三、实践建议

如果你未来还会做类似页面,我建议按下面原则处理:

建议 1:先定义页面类型,再决定通信方式

  • 如果页面是 WebUI,优先用 chrome.send + RegisterMessageCallback
  • 如果页面是扩展页 / 特殊宿主页,再考虑 ExtensionHostMsg_*

不要混用,除非非常清楚两条链路都已经被正确装配。

建议 2:不要假设所有 WebContents 默认等价

每创建一个新页面,都问自己三个问题:

  1. 它是谁创建的?
  2. 它挂了哪些 helper?
  3. 它的 JS -> Browser 通信走哪条桥?

建议 3:遇到“收不到消息”,先检查接收器是否存在

排查顺序建议固定成:

  1. 页面有没有对应的 WebContents
  2. helper 有没有挂上
  3. renderer 是否真的发了这条消息
  4. Browser 侧是否有 handler
  5. handler 是否因为条件判断被短路

而不是一上来就怀疑 callback 参数。


十四、一个更直白的总结

如果把整个问题类比成现实世界:

  • BindOnce 决定的是“什么时候出发、谁来开门”;
  • ExternalApisTabHelper 决定的是“这栋楼里有没有前台收信”;
  • 你的引导页问题并不是“门
深入解析 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

【强化学习】11 —— Double DQN算法与Dueling DQN算法

可以看到由于数据点的分布,使得拟合出的价值函数无法与真实的价值函数吻合,甚至在部分区域产生了明显的偏差。Dueling DQN 是 DQN 另一种的改进算法,它在传统 DQN 的基础上只进行了微小的改动,但却能大幅提升 DQN 的表现。这样,即使其中一套神经网络的某个动作存在比较严重的过高估计问题,由于另一套神经网络的存在,这个动作最终使用的值不会存在很大的过高估计问题。.在同一个状态下,所有动作的优势值之和为 0,因为所有动作的动作价值的期望就是这个状态的状态价值。环境的状态包括倒立摆角度的正弦值。

sinat_52032317的博客 1万+

拆解歌尔2718硅麦:2.75mm超小封装背后的MEMS技术解析

本文深度解析歌尔2718硅麦的2.75mm超小封装技术,探讨MEMS麦克风在微型化过程中的声学性能与结构可靠性平衡。通过ASIC芯片堆叠、纳米级应力控制等创新技术,歌尔2718在TWS耳机和智能手机中实现卓越性能,同时克服微型化带来的声学挑战。

weixin_28292761的博客 607

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

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

John_ToStr的博客 6026

开发基于GDL235KBQ6开发板的IAP升级程序(HEX协议+串口空闲中断 + DMA接收数据)

使用GDL235kB开发板,实现串口iap升级程序的bootloader代码编写,app为led灯闪烁,并完成通信c代码

zhaozijuncc的博客 642

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

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

John_ToStr的博客 2134

cs231n作业 assignment 1 q1 q2 q3

请先看课程作业地址。有两种做作业的方法,一种是在google Colab上做 (需魔法),另一种就是下载到本地,但是我懒得在本地配置环境,太麻烦了,还得修改一些基础代码,我就直接在google colab上做了google colab 配置环境只需要跟着教程走就好了最后还有一题解答题懒得做的,这个题目做的没啥意思。让我们用简单写法来实现softmax的loss函数与梯度计算。

Leezed525的博客 9277

ESP32 BLE Mesh Provisioner实战:手把手教你用ESP-IDF配网智能灯泡(附常见错误排查)

本文提供了一份基于ESP-IDF的ESP32 BLE Mesh Provisioner实战指南,详细解析了配网器的核心角色与工作流程。文章手把手教你从工程搭建、代码初始化到核心回调解析,并重点针对常见的‘Model not bound to AppKey’等错误提供了深度排查思路与解决方案,帮助你构建一个稳定可靠的智能照明网关。

desk3的博客 512

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

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

John_ToStr的博客 1627

Truncated SVD for faster dection

Truncated SVD方法最先被提出在《Fast R-CNN》论文中,用于降低全连接层的运算量,提升模型的速度。在卷积神经网络的分类任务中,FC层的运算速度远远大于CONV层,而在Fast R-CNN的目标检测任务中,大多数的运算时间发生在FC 的运算中,是因为针对每张输入图片,只进行一次卷积操作,而要对生成的Region Proposal,大致两千个,进行全连接操作进行分类与Bounding...

weixin_35747666的博客 1535

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

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

John_ToStr的博客 1543

MIMO-OFDM通信系统性能仿真及MATLAB实现

将MIMO和OFDM技术结合起来,可以充分利用两者的优点,进一步提高无线通信系统的性能。在MIMO-OFDM系统中,多个天线同时发射多个子载波,称为多天线多载波(MIMO-OFDM)系统。随着无线通信技术的不断发展,MIMO-OFDM技术因其高效、可靠和适应性等优点已被广泛应用于新一代通信系统中。本篇文章旨在介绍MIMO-OFDM通信系统的基本原理及其性能评估方法,并结合MATLAB提供相关代码实现。本篇文章介绍了MIMO-OFDM通信系统的基本原理和性能评估方法,并结合MATLAB提供了相关的实现代码。

学习使你进步。 2215

【Python搞定车载自动化测试】——Python实现车载以太网DoIP刷写(含Python源码)

随着智能电动汽车行业的发展,汽车=智能终端+四个轮子,而人工智能也发展越来越快,Python语言使用越来越广,本章节主要介绍如何使用Python语言基于Pytest自动化框架来实现DoIP诊断自动化测试。首先回顾一下DoIP诊断基础知识:DoIP(Diagnosis over Internet Protocol)是一种用于车辆诊断的网络通信协议。它基于现代互联网技术,允许通过以太网或IP网络进行车辆诊断和通信。

测试领域技术分享 3791

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

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

John_ToStr的博客 1523

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

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

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

John_ToStr的博客 1512

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

make_shared、shared_ptr、enable_shared_from_this

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

John_ToStr的博客 1480

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

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
上一篇: 深入剖析 Chromium 的 WebContentsUserData:页面级能力注入的核心基石
下一篇: Chromium 页面类型与 IPC 通信机制深度解析
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

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

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

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

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

打赏作者

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

抵扣说明:

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

余额充值