浏览器
快来分享你的内容吧~
旧版 chromium 源码编译,以 86.0.4240.198 版本为例
<img src="https://pic.code-nav.cn/post_picture/1922184057994162177/tGT9sg21coJqAsvb.webp" alt="在这里插入图片描述" width="100%" /> ## chromium各版本发布日程 [chromium各版本发布日程](https://chromiumdash.appspot.com/schedule) ## chromium各版本git日志 [chromium各版本git日志](https://chromium.googlesource.com/chromium/src/+refs) 在编译之前我们需要下载好全量的 chromium 源码,之前文章中,我们下载的时候是加上了 --no-history 参数,是不会去拉取全量源码,只会拉取最新版本的源码。 ## 下载 depot_tools 工具 首先下载 depot_tools 工具,并将 depot_tools 目录添加到系统变量 path 中 ```sh git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ``` 下载好了之后,不要执行 gclient 命令和修改任何文件。在C盘根目录或者其他目录下新建 boto.cfg 文件,文件内容为以下内容:(proxy_port参数修改为自己设备的代理的端口) ```sh [Boto] proxy = 127.0.0.1 proxy_port = 10808 ``` 然后新增环境变量 NO_AUTH_BOTO_CONFIG ,值为boto.cfg文件路径,不会的可以参考之前的源码编译章节内容 ## 拉取 chromium 全量源码 然后新建一个 chromiumAll 目录,用来放置全量 chromium 的源码(此目录所在磁盘建议剩余存储空间要在 150GB 以上),进到你刚刚新建的chromiumAll目录,然后拉取代码。 打开powerShell 7,建议使用前面文章介绍的新版powershell,设置全局代理,开启vpn,执行以下代码进行拉取即可,一共大约 80GB 的源码,中途不可中断,中断直接重头再来,所以需要保证你的网络顺畅,并且你的代理服务流量额度要够,我跑了好多次才成功将他全部拉取下来,都是中途中断了,然后只能重头再来,浪费了接近 300GB 的vpn流量,**(如果你成功拉取了,可以将这个代码不做任何操作,备份一份到本地,然后用复制出来的代码去做项目,保证一直会有一份比较干净的源码)** ```sh $env:HTTP_PROXY="http://127.0.0.1:10808"; $env:HTTPS_PROXY="http://127.0.0.1:10808" fetch chromium ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/4a6fvIFTawg4xd1Q.webp" alt="在这里插入图片描述" width="446px" /> 下载好了之后,进到chromium各版本git日志中,地址在开头位置,全局搜索一下需要的版本 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/C8mHsibNx8fam2Uj.webp" alt="在这里插入图片描述" width="100%" /> 点击图中位置  <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/ckdgHC0eDT0ZHRqg.webp" alt="在这里插入图片描述" width="100%" /> 点击图中位置 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/BywyCeaLe1AyVFSG.webp" alt="在这里插入图片描述" width="100%" /> 进入之后,首先记住commit的提交编号,这个非常重要,然后找到 Checking Out and Building,然后找到对应系统,打开构建文档链接即可。 环境要求按照构建文档要求的环境来,例如Windows 10 SDK,Visual Studio这些,严格按照文档要求来就可以。 环境配好之后,就是切换版本,在powershell 7 中,src目录中执行一下命令,5f8e74b8f1dab431a1a57d84101185f0e8da33d4就是刚刚的commit编号 ```sh git checkout 5f8e74b8f1dab431a1a57d84101185f0e8da33d4 ``` 执行完之后,执行 git log -1,查看一下当前版本的状态,目前的状态就是回到了,也可以通过查看前面的chromium各版本发布日程链接查看每个版本的发布时间来验证对错,一般不会差太久。 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/DzCnW8Yq8HRjLsS4.webp" alt="在这里插入图片描述" width="100%" /> 在src的DESP文件中,搜索一下depot_tools,记住这个版本号即可  powershell 切换到depot_tools目录,执行以下命令,87a7605160fdc1370d6fbaa6347a36cbc5287546就是图片中的版本号 ```sh git checkout 87a7605160fdc1370d6fbaa6347a36cbc5287546 ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/MHvIzA9jB8MlXaoC.webp" alt="在这里插入图片描述" width="100%" /> 在执行gclient之前,可以先运行一次depot_tools下的 bootstrap\win_tools.bat 文件,会自动下载一个对应版本的python,执行完之后,就可以执行 gclient 了,在gclient运行成之后,切换到chromium的src目录下,跟之前一样开始执行编译所需要的步骤就可以。 在执行 gn gen out/Default 的时候,构建配置可以使用我这份,当然你也可以根据GN官网的配置来。 ```bash # 系统 target_os = "win" # 架构 target_cpu = "x64" # 是否开启调试 is_debug = false # 不允许远程调试 enable_remoting = false # 使用 Chrome 官方的编译优化建议 is_official_build = true # 分散链接编译 is_component_build = false # 不使用官方 API 密钥 use_official_google_api_keys = false google_api_key = "" google_default_client_secret = "" # 关闭支持 NaCl enable_nacl = false # 支持 H264 编码 proprietary_codecs = true # 解码器 ffmpeg_branding = "Chrome" # 启用调试符号以满足 GRIT 要求 symbol_level = 1 v8_symbol_level = 1 blink_symbol_level = 1 # 明确禁用 strip_debug_info strip_debug_info = false # 禁用 PGO 优化以避免配置文件缺失问题 chrome_pgo_phase = 0 ``` 最后构建的时候,用 **ninja -C out/Default chrome**,不要用 autoninja -C out/Default chrome 在编译过程中可能会报的错误: 如果你运行gclient sync 的时候报了gbk 编码之类的错误,可以尝试运行这个命令来重新执行 ```bash cmd /c "set PYTHONIOENCODING=utf-8 && gclient sync" ``` 如果在执行hooks阶段报以下类似错误: ```bash Running hooks: 46% (45/96) tools_traffic_annotation_windows ________ running 'vpython.bat src/third_party/depot_tools/download_from_google_storage.py --no_resume --no_auth --num_threads=4 --bucket chromium-tools-traffic_annotation -d src/tools/traffic_annotation/bin/win32' in 'J:\chromium' 1> File gs://chromium-tools-traffic_annotation/f983cccf0780bec2aa066a691f50e4c8a3a07153 for src/tools/traffic_annotation/bin/win32\traffic_annotation_auditor.exe does not exist, skipping. Downloading 1 files took 4.006000 second(s) File gs://chromium-tools-traffic_annotation/f983cccf0780bec2aa066a691f50e4c8a3a07153 for src/tools/traffic_annotation/bin/win32\traffic_annotation_auditor.exe does not exist. Error: Command 'vpython.bat src/third_party/depot_tools/download_from_google_storage.py --no_resume --no_auth --num_threads=4 --bucket chromium-tools-traffic_annotation -d src/tools/traffic_annotation/bin/win32' returned non-zero exit status 1 in J:\chromium ``` 可以在src目录中的DESP文件中,将此项注释,原因是谷歌存储桶中,tools_traffic_annotation_windows文件已经被删除不存在了,根据官方解释,这个东西的作用是用来记录你的流量用途,没有的话,不影响我们后面的编译   编译好了之后,就可以看到我们对应版本的chromium了。  要点总结: 对应历史版本的chromium在编译的时候,就要用DESP文件中记录的对应版本的depot_tools,将他们都回滚到历史版本,然后执行编译所需要的步骤即可,否则会发生很多奇怪的错误
魔改chromium源码——自定义浏览器启动参数
在进行以下操作之前,请确保已完成之前文章中提到的 [源码拉取及编译](https://blog.csdn.net/weixin_45307278/article/details/146828832) 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 --- ## 为什么需要自定义传参? >在讨论这个问题之前,我们需要引入一个重要的背景概念:环境持久化 和运行环境一致性 。这是许多现代浏览器应用场景中不可忽视的关键点。 > >众所周知,当浏览器启动时,其内部参数(如指纹、用户代理、硬件信息等)会被初始化并固定下来。例如,假设你登录了某个网站(比如A网站),第一次启动浏览器时,系统计算出的设备指纹是1111。但当你关闭浏览器后再次打开时,由于浏览器内部可能默认启用了指纹随机化机制 ,指纹可能会变成2222。对于A网站的风控系统来说,这种指纹的变化会被视为异常行为,进而将你的运行环境标记为可疑,甚至可能导致账户被限制或封禁。 >因此,在实际应用中,为了实现更灵活且可控的浏览器行为,我们需要解决以下几个核心问题: > 1. 明确随机化的控制点 :我们需要知道哪些参数是可以被随机化的,以及如何修改它们。 >2. 实现随机值的持久化:随机生成的值需要能够在多次启动之间保持一致,避免因频繁变化而触发风控。 >3. 支持外部干预:通过外部手段动态调整内部参数,以满足不同场景的需求。 > --- > >在这种情况下,自定义传参 就显得尤为重要。通过在浏览器启动时传递不同的参数,我们可以为每一个浏览器实例设置独立的配置,从而实现多样化的运行效果。例如: > - 数据隔离 :浏览器自带的--user-data-dir参数可以指定不同的用户数据目录,确保每个实例的数据完全隔离。 > - 指纹定制 :通过传参,我们可以为每个浏览器实例分配固定的指纹值,避免因随机化导致的风控问题。 > - 灵活扩展 :自定义传参不仅限于指纹,还可以用于调整其他关键参数(如分辨率、语言、时区等),从而更好地模拟真实用户环境。 > > 综上所述,自定义传参是实现环境持久化和运行环境一致性的基础工具之一。它不仅能够帮助我们规避风控系统的误判,还能为复杂的应用场景提供更高的灵活性和可控性。这正是我们在设计和使用浏览器自动化方案时需要重点关注的核心能力 以下为实现步骤以及验证方式: 验证方式我们会用到 [新增自定义变量到windows属性](https://blog.csdn.net/weixin_45307278/article/details/147150394) 这篇文章中的方法,将传参接收的值挂载到 window 全局变量。然后在devtool中输出,验证我们传值是否符合预期。 # 步骤 1:定义新的命令行开关 修改位置: ```bash src\chrome\common\chrome_switches.h ``` 在 namespace switches 中新增变量 ```cpp extern const char kMyCustomFlag[]; // 新增 ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/7kNgJfoG5TSlnoNk.webp" alt="在这里插入图片描述" width="100%" /> 修改位置: ```bash src\chrome\common\chrome_switches.cc ``` 在 namespace switches 中新增变量,my-custom-flag 就是我们要传的链接参数 ```cpp const char kMyCustomFlag[] = "my-custom-flag"; // 新增 ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/kEkWIyAMsaQAM0HD.webp" alt="在这里插入图片描述" width="100%" /> # 步骤 2:创建 JavaScript 绑定,用于测试验证 在 src\content\renderer\ 中新增文件 my_custom_flag.h ```cpp #ifndef CONTENT_RENDERER_MY_CODE_BINDING_H_ #define CONTENT_RENDERER_MY_CODE_BINDING_H_ #include "v8/include/v8.h" namespace content { class myCustomFlagBinding { public: static void Install(v8::Local<v8::Context> context); }; } #endif ``` 在 src\content\renderer\ 中新增文件 my_custom_flag.cc ```cpp #include "content/renderer/my_custom_flag.h" #include "base/logging.h" // NEW: For LOG(INFO) #include "v8/include/v8.h" #include "base/command_line.h" #include "chrome/common/chrome_switches.h" namespace content { void myCustomFlagBinding::Install(v8::Local<v8::Context> context) { v8::Isolate* isolate = context->GetIsolate(); v8::HandleScope handle_scope(isolate); v8::Local<v8::Object> global = context->Global(); std::string value; auto* command_line = base::CommandLine::ForCurrentProcess(); if (command_line->HasSwitch(switches::kMyCustomFlag)) { value = command_line->GetSwitchValueASCII(switches::kMyCustomFlag); LOG(INFO) << "Custom flag detected in renderer: --my-custom-flag=" << value; // NEW: Enable logging } else { LOG(INFO) << "No --my-custom-flag found in renderer"; // NEW: Log when switch is missing } // commandFlag 就是新增的全局变量,访问方式就是 window.commandFlag global->Set( context, v8::String::NewFromUtf8(isolate, "commandFlag").ToLocalChecked(), v8::String::NewFromUtf8(isolate, value.c_str()).ToLocalChecked()) .Check(); } } // namespace content ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/jrwYbKZSgzGo6p0J.webp" alt="在这里插入图片描述" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/q0mWnbfavoEodWUV.webp" alt="在这里插入图片描述" width="100%" /> # 步骤 3:绑定到 RenderFrameImpl,用于测试验证 修改位置: ```bash src\content\renderer\render_frame_impl.cc ``` 新增头文件 ```cpp #include "content/renderer/my_custom_flag.h" ``` 新增内容 ```cpp if (world_id == 0) { myCustomFlagBinding::Install(context); } ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/5VjC0W27QrdaqCqc.webp" alt="在这里插入图片描述" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/bILXbWx1SrI6eWVR.webp" alt="在这里插入图片描述" width="100%" /> # 步骤 4:确保开关传播到渲染进程 修改位置: ```bash src\content\browser\renderer_host\render_process_host_impl.cc ``` 新增头文件 ```cpp #include "chrome/common/chrome_switches.h" ``` 在void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer中新增 ```cpp switches::kMyCustomFlag, ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/05RFoHc92YKylyNm.webp" alt="在这里插入图片描述" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/v7KthHTf9evbu7pT.webp" alt="在这里插入图片描述" width="100%" /> 最后,在src目录下,执行 gn gen out/Default ,重新生成构建文件 构建成功之后运行一下命令进行编译 ```cpp autoninja -C out/Default chrome ``` 使用以下方式启动编译好的chrome.exe <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/a2KCZbkzmzpwuVRw.webp" alt="image.png" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/IcfWIjH4tA0sTr5a.webp" alt="image.png" width="712px" />
魔改chromium源码——新增自定义变量到windows属性
在进行以下操作之前,请确保已完成之前文章中提到的 [源码拉取及编译](https://blog.csdn.net/weixin_45307278/article/details/146828832) 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 --- ### 目标 在 Chromium 中添加一个全局变量 myCode,值为 "你好!我是来自C++的字符串",并通过 JavaScript 的 console.log(window.myCodeApi.myCode()) 直接访问。 ### 实现步骤 ## 步骤 1:定义全局变量 在 src/base 模块中定义全局变量 myCode,以便在整个 Chromium 项目中复用 在src/base目录下,创建文件:my_globals.h **文件内容:** ```cpp #ifndef BASE_MY_GLOBALS_H_ #define BASE_MY_GLOBALS_H_ #include <string> namespace base { extern const char* const kMyCode; } #endif ``` base目录下创建文件:my_globals.cc **文件内容:** ```cpp #include "base/my_globals.h" namespace base { const char* const kMyCode = "你好!我是来自C++的字符串"; } ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/UHvgxWc4Bd8MCqtv.webp" alt="在这里插入图片描述" width="100%" /> **修改 base/BUILD.gn** **文件路径:** src/base/BUILD.gn **操作:** 在 component("base") 的 sources 列表中添加新文件的文件名 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/KtOeff9HLpmEKEmY.webp" alt="在这里插入图片描述" width="100%" /> ## 步骤 2:创建 JavaScript 绑定 在 src/content/renderer 目录中,创建文件:my_code_binding.h **文件内容:** ```cpp #ifndef CONTENT_RENDERER_MY_CODE_BINDING_H_ #define CONTENT_RENDERER_MY_CODE_BINDING_H_ #include "v8/include/v8.h" namespace content { class MyCodeBinding { public: // 安装绑定到指定的 V8 上下文中 static void Install(v8::Local<v8::Context> context); private: // 获取 my_code 值的 JavaScript 函数 static void GetMyCode(const v8::FunctionCallbackInfo<v8::Value>& args); }; } #endif ``` 在 src/content/renderer 目录中,创建文件:my_code_binding.cc ```cpp #include "content/renderer/my_code_binding.h" #include "base/my_globals.h" #include "third_party/blink/public/web/blink.h" #include "v8/include/v8.h" namespace content { void MyCodeBinding::Install(v8::Local<v8::Context> context) { // 从 context 获取 Isolate v8::Isolate* isolate = context->GetIsolate(); v8::HandleScope handle_scope(isolate); v8::Local<v8::Object> global = context->Global(); v8::Local<v8::Object> my_code_api = v8::Object::New(isolate); my_code_api->Set( context, v8::String::NewFromUtf8(isolate, "myCode").ToLocalChecked(), v8::Function::New(context, &MyCodeBinding::GetMyCode).ToLocalChecked()) .Check(); global->Set( context, v8::String::NewFromUtf8(isolate, "myCodeApi").ToLocalChecked(), my_code_api) .Check(); } void MyCodeBinding::GetMyCode(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); v8::HandleScope handle_scope(isolate); args.GetReturnValue().Set( v8::String::NewFromUtf8(isolate, base::kMyCode) .ToLocalChecked()); } } ``` **修改 content/renderer/BUILD.gn** **文件路径:** src/content/renderer/BUILD.gn **操作:** 在 target(link_target_type, "renderer") 的 sources 列表中添加新文件 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/61XZJDRDM0UP9Nto.webp" alt="在这里插入图片描述" width="100%" /> ## 步骤 3:绑定到 RenderFrameImpl 在 RenderFrameImpl 中调用绑定逻辑,将 myCode 属性安装到脚本上下文中 修改文件路径:src/content/renderer/render_frame_impl.cc 在文件顶部添加 my_code_binding.h 头文件,可以按文件头字母顺序添加 ```cpp #include "content/renderer/my_code_binding.h" <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/lS7yrj8F0pciPuuk.webp" alt="在这里插入图片描述" width="100%" /> uuk.webp)  在RenderFrameImpl::DidCreateScriptContext中添加如下代码 ```cpp void RenderFrameImpl::DidCreateScriptContext(v8::Local<v8::Context> context, int world_id) { // 新增代码 if (world_id == 0) { MyCodeBinding::Install(context); } // 新增代码 TRACE_EVENT_WITH_FLOW0("navigation", "RenderFrameImpl::DidCreateScriptContext", TRACE_ID_LOCAL(this), TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT); v8::MicrotasksScope microtasks(GetAgentGroupScheduler().Isolate(), context->GetMicrotaskQueue(), v8::MicrotasksScope::kDoNotRunMicrotasks); if (((enabled_bindings_.Has(BindingsPolicyValue::kMojoWebUi)) || enable_mojo_js_bindings_) && IsMainFrame() && world_id == ISOLATED_WORLD_ID_GLOBAL) { // We only allow these bindings to be installed when creating the main // world context of the main frame. blink::WebV8Features::EnableMojoJS(context, true); if (mojo_js_features_) { if (mojo_js_features_->file_system_access) blink::WebV8Features::EnableMojoJSFileSystemAccessHelper(context, true); } } if (world_id == ISOLATED_WORLD_ID_GLOBAL && mojo_js_interface_broker_.is_valid()) { // MojoJS interface broker can be enabled on subframes, and will limit the // interfaces JavaScript can request to those provided in the broker. blink::WebV8Features::EnableMojoJSAndUseBroker( context, std::move(mojo_js_interface_broker_)); } for (auto& observer : observers_) observer.DidCreateScriptContext(context, world_id);    最后,在src目录下,执行 gn gen out/Default ,重新生成构建文件 构建成功之后运行一下命令进行编译 ```bash autoninja -C out/Default chrome ```  如果你想实现console.log(window.myCode);这样的效果 将 my_code_binding.h 修改为 ```cpp #ifndef CONTENT_RENDERER_MY_CODE_BINDING_H_ #define CONTENT_RENDERER_MY_CODE_BINDING_H_ #include "v8/include/v8.h" namespace content { class MyCodeBinding { public: static void Install(v8::Local<v8::Context> context); }; } #endif ``` 将前面的 my_code_binding.cc 修改为 ```cpp #include "content/renderer/my_code_binding.h" #include "base/my_globals.h" #include "v8/include/v8.h" namespace content { void MyCodeBinding::Install(v8::Local<v8::Context> context) { v8::Isolate* isolate = context->GetIsolate(); v8::HandleScope handle_scope(isolate); v8::Local<v8::Object> global = context->Global(); global->Set( context, v8::String::NewFromUtf8(isolate, "myCode").ToLocalChecked(), v8::String::NewFromUtf8(isolate, base::kMyCode).ToLocalChecked()) .Check(); } } ``` 
魔改chromium源码——canvas指纹修改 第一节
在进行以下操作之前,请确保已完成之前文章中提到的 [源码拉取及编译](https://www.codefather.cn/post/1922187045236019201) 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 --- # Canvas 指纹技术简介 Canvas 指纹 是一种基于浏览器的隐式用户标识技术,利用 HTML5 的 <canvas> 元素及其绘图功能生成设备或浏览器的唯一标识符。其核心原理是:不同设备、浏览器或操作系统在渲染相同的图形内容时,可能会产生细微的差异。这些差异可以被提取并转换为一个唯一的“指纹”,从而用于识别用户的设备或浏览器。 # Canvas 指纹的实现过程 以字节某音产品中的 Canvas 指纹生成代码为例,分析其实现过程: ```javascript function canvasFingerprint(e = 3735928559) { var urlStr; // 创建 Canvas 元素并设置尺寸 var canvasEl = document.createElement('canvas'); canvasEl.width = 100; canvasEl.height = 100; // 获取绘图上下文并绘制内容 var canvas = canvasEl.getContext('2d'); canvas.fillStyle = '#0078d4'; canvas.fillRect(0, 0, 100, 100); canvas.font = '14px serif'; // 设置字体样式 canvas.fillText('龘ฑภ경', 2, 12); // 绘制多语言字符 canvas.shadowBlur = 2; // 添加阴影效果 canvas.showOffsetX = 1; // 自定义属性(非标准) canvas.showColor = 'lime'; // 自定义属性(非标准) canvas.arc(8, 8, 8, 0, 2); // 绘制圆弧 canvas.stroke(); // 描边 // 将 Canvas 内容转换为 Base64 数据 URL urlStr = canvasEl.toDataURL(); // 基于数据 URL 计算哈希值 for (var i = 0; i < 32; i++) { e = (65599 * e + urlStr.charCodeAt(e % urlStr.length)) >>> 0; } return e; // 返回最终的指纹值 } // 调用函数生成指纹 canvasFingerprint(); ``` # 实现步骤解析 1. **创建 Canvas 元素:** 使用 document.createElement('canvas') 创建一个 <canvas> 元素,并设置其宽度为 100 像素,高度为 100 像素。 这些尺寸经过精心设计,既能保证足够的复杂性,又能避免过高的计算开销。 2. **绘制图形内容:** 设置字体样式为 14px serif,并在画布上绘制包含多种语言字符的文本 '龘ฑภ경'。这种多语言字符组合增加了渲染结果的多样性。 添加阴影效果(shadowBlur)和绘制一个绿色圆弧(arc),进一步丰富图形内容。 3. **提取渲染结果:** 使用 canvasEl.toDataURL() 方法将画布内容转换为 Base64 编码的数据 URL。这个 URL 包含了当前设备对图形内容的渲染结果。 4. **计算哈希值:** 遍历数据 URL 的字符序列,通过一个自定义的哈希算法(基于乘法因子 65599 和字符 ASCII 值)计算出一个 32 位整数作为最终的指纹值。 这个哈希值具有高区分度,能够唯一标识当前设备或浏览器。 在src\third_party\blink\renderer\modules\canvas\canvas2d\canvas_2d_recorder_context.cc所在目录中,新建一个random_hex_color.h文件。文件内容如下 ```cpp #ifndef RANDOM_COLOR_H #define RANDOM_COLOR_H #include <array> #include <random> #include <string> #include "base/time/time.h" // 用于动态种子 namespace content { // 声明并定义 getRandomColorCode 函数 inline std::string getRandomColorCode() { // 十六进制字符集合 const std::array<char, 17> hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '\0'}; // 静态字符数组,用于存储颜色代码('#' + 6个字符 + '\0') static thread_local std::array<char, 8> colorCode; // 初始化伪随机数生成器 static thread_local std::mt19937 gen(static_cast<unsigned>(base::Time::Now().ToTimeT())); static std::uniform_int_distribution<> dis(0, 15); // 生成 [0, 15] 范围内的随机数 // 构造颜色代码字符串 colorCode[0] = '#'; // 设置第c一个字符为 '#' for (int i = 1; i < 7; ++i) { colorCode[i] = hexChars[dis(gen)]; // 随机选择一个十六进制字符 } colorCode[7] = '\0'; // 添加字符串终止符 return colorCode.data(); // 返回指向静态数组的指针 } } #endif // RANDOM_COLOR_H ``` 在src\third_party\blink\renderer\modules\canvas\canvas2d\canvas_2d_recorder_context.cc目录中,找到底下这段函数的源码。 **我们以修改 canvas 的 fillStyle 值为例进行说明。对于其他属性的随机化处理,可以参考同样的思路:找到对应的代码位置,并按照类似的方式调整逻辑。这里不再逐一演示,但方法思路是通用的。** 如果在这个文件找不到这个代码,全局搜索一下canvas2d文件夹,不同版本的 chromium 源码结构会有些许差别,但功能是一致的 ```cpp #include "third_party/blink/renderer/modules/canvas/canvas2d/random_color.h" // 引入头文件 void Canvas2DRecorderContext::setFillStyle(v8::Isolate* isolate, v8::Local<v8::Value> value, ExceptionState& exception_state) { ValidateStateStack(); CanvasRenderingContext2DState& state = GetState(); // This block is similar to that in setStrokeStyle(), see comments there for // details on this. if (value->IsString()) { // v8::Local<v8::String> v8_string = value.As<v8::String>(); 这行注释 // 新增的 std::string color_code_str = content::getRandomColorCode(); const char* color_code = color_code_str.c_str(); // 新增的 v8::Local<v8::String> v8_string = v8::String::NewFromUtf8(isolate, color_code, v8::NewStringType::kNormal).ToLocalChecked(); UpdateIdentifiabilityStudyBeforeSettingStrokeOrFill( v8_string, CanvasOps::kSetFillStyle); if (state.IsUnparsedFillColor(v8_string)) { return; } Color parsed_color = Color::kTransparent; if (!ExtractColorFromV8StringAndUpdateCache( isolate, v8_string, exception_state, parsed_color)) { return; } if (state.FillStyle().IsEquivalentColor(parsed_color)) { state.SetUnparsedFillColor(isolate, v8_string); return; } state.SetFillColor(parsed_color); state.ClearUnparsedFillColor(); state.ClearResolvedFilter(); return; } V8CanvasStyle v8_style; if (!ExtractV8CanvasStyle(isolate, value, v8_style, exception_state)) { return; } UpdateIdentifiabilityStudyBeforeSettingStrokeOrFill(v8_style, CanvasOps::kSetFillStyle); switch (v8_style.type) { case V8CanvasStyleType::kCSSColorValue: state.SetFillColor(v8_style.css_color_value); break; case V8CanvasStyleType::kGradient: state.SetFillGradient(v8_style.gradient); break; case V8CanvasStyleType::kPattern: if (!origin_tainted_by_content_ && !v8_style.pattern->OriginClean()) { SetOriginTaintedByContent(); } state.SetFillPattern(v8_style.pattern); break; case V8CanvasStyleType::kString: { Color parsed_color = Color::kTransparent; if (ParseColorOrCurrentColor(v8_style.string, parsed_color) == ColorParseResult::kParseFailed) { return; } if (!state.FillStyle().IsEquivalentColor(parsed_color)) { state.SetFillColor(parsed_color); } break; } } state.ClearUnparsedFillColor(); state.ClearResolvedFilter(); } ``` 新增头文件 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/rnci4hUNDKfYQlq4.webp" alt="在这里插入图片描述" width="100%" /> 先将v8::Local<v8::String> v8_string = value.As<v8::String>();这行注释掉,然后新增底下两行代码。 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/zykAcU2GWg3Zfxt7.webp" alt="image.png" width="100%" /> 修改完了之后保存,然后执行以下命令编译 ```sh autoninja -C out/Default chrome ``` 创建一个index.html文件,文件内容如下: ```html <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"> <title>canvas</title> </head> <body> <canvas class="" id="myCanvas1" width="100" height="100"></canvas> <canvas class="" id="myCanvas2" width="100" height="100"></canvas> <script> function canvasFingerprint (urlStr) { var e = 3735928559 // 基于数据 URL 计算哈希值 for (var i = 0; i < 32; i++) { e = (65599 * e + urlStr.charCodeAt(e % urlStr.length)) >>> 0; } return e; // 返回最终的指纹值 } function cavasCreate (el) { // 同一段逻辑的canvas做多次渲染 const canvas = document.getElementById(el); const ctx = canvas.getContext('2d'); ctx.fillStyle = '#0078d4'; ctx.fillRect(0, 0, 100, 100); ctx.font = '14px Arial, sans-serif'; ctx.fillStyle = 'black'; ctx.shadowColor = 'rgba(0, 0, 0, 0.5)'; ctx.shadowBlur = 2; ctx.fillText('龘ฑภ경', 10, 50); const urlStr = canvas.toDataURL(); return urlStr } const urlStr1 = cavasCreate('myCanvas1') const urlStr2 = cavasCreate('myCanvas2') // 调用函数生成指纹 console.log(canvasFingerprint(urlStr1)); console.log(canvasFingerprint(urlStr2)); </script> </body> </html> ```  <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/yBDqTYCUzJrXLsMp.png" alt="在这里插入图片描述" width="641px" /> <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/CJpLiIS4thunozC8.png" alt="在这里插入图片描述" width="675px" /> --- # 特别说明 通过对上述随机化逻辑的分析和实现,可以发现一种简单而有效的检测方法: 对同一段 Canvas 逻辑代码多次执行,并对比每次生成的结果。如果所有结果完全一致,则说明浏览器环境未被篡改,处于正常状态;反之,如果结果存在不一致,则表明相关函数内容或者浏览器环境已被篡改。 **关于这一问题的解决方案,我们将在下一节详细探讨。**
魔改chromium源码——绕过无限debugger反调试
在进行以下操作之前,请确保已完成之前文章中提到的 [源码拉取及编译](https://www.codefather.cn/post/1922187045236019201) 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 --- 在浏览器中实现“无限 debugger”的反调试技术是一种常见的手段,用于防止他人通过开发者工具对网页进行调试或逆向分析。这种技术的核心是利用 JavaScript 的 debugger 语句,当开发者工具打开时,debugger 会触发断点,从而阻止代码继续执行。 以下是如何实现“无限 debugger”的几种方式 使用 setInterval ```javascript setInterval(() => { debugger; }, 100); ``` 递归调用 ```javascript function infiniteDebugger() { debugger; infiniteDebugger(); } infiniteDebugger(); ``` 事件监听 ```javascript document.addEventListener('keydown', (event) => { if (event.key === 'F12' || (event.ctrlKey && event.shiftKey && event.key === 'I')) { debugger; } }); document.addEventListener('contextmenu', (event) => { debugger; }); ``` 检测开发者工具状态 ```javascript setInterval(() => { const start = performance.now(); debugger; const end = performance.now(); // 如果执行时间过长,说明开发者工具可能已打开 if (end - start > 100) { console.clear(); // 清空控制台 debugger; // 再次触发断点 } }, 500); ``` 使用console.debug ```javascript const hook = new Error() let num = -1 Object.defineProperty(hook, "stack", { get: function () { num += 1 num ? debugger : '' return num; } }); function isOpenDevTool() { num = -1 console.debug(hook); hook.stack requestAnimationFrame(isOpenDevTool); } requestAnimationFrame(isOpenDevTool); ``` 在了解实现原理后,我们现在的思路是让 debugger 关键字像 JavaScript 中的其他独立关键字一样,无需特定上下文即可单独执行。以下是一些可以在 JavaScript 中独立执行且不需要特定上下文的关键字示例: ```javascript true; false; this; null; undefined; NaN; Infinity; -Infinity; ``` 这些关键字在 JavaScript 中具有独立的意义和用途,我们的目标是使 debugger 关键字也能以类似的方式运行。 以下为修改路径 ```sh \src\v8\src\parsing\keywords-gen.h ``` 我们将 debugger 关键字替换为与 null 等效的值。当然,也可以选择其他关键字,但需确保其能够独立运行,且不依赖任何参数或上下文环境。 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/0wbxk1aXcD2yl86H.webp" alt="在这里插入图片描述" width="100%" /> 修改保存之后,执行以下命编译 ```sh autoninja -C out/Default chrome ``` 经过验证可以发现,运行 debugger 语句的效果与返回 null 是等价的。这种情况下,程序既不会抛出错误,也不会因断点而中断执行。 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/FoIL3TKZmQ6VUQkZ.webp" alt="在这里插入图片描述" width="100%" />
魔改chromium源码——navigator.webdriver 检测
在进行以下操作之前,请确保已完成之前文章中提到的 [源码拉取及编译](https://www.codefather.cn/post/1922187045236019201) 部分。 如果已顺利完成相关配置,即可继续执行后续操作。 --- [chromium源码官网 https://source.chromium.org/chromium/chromium/src](https://source.chromium.org/chromium/chromium/src) 说下修改的chromium源码思路: 首先在修改源码过检测之前,我们要知道它是怎么检测的,找到他通过哪个JS的API来做的检测,只有知道了如何检测,我们才能想办法去绕过,俗话说,知己知彼百战百胜嘛! # 绕过 navigator.webdriver 检测 在浏览器中环境对抗的时候,会通过 navigator.webdriver 来检测是否为自动化在控制网站,如果返回为true,则表示是自动化在控制,否则不是,所以我们只需要将他的返回值永远返回false即可 自动化控制返回的值 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/qZbeJZ5lVBilRHPG.png" alt="在这里插入图片描述" width="499px" /> 正常浏览器返回的值  在chromium源码中,找到以下路径 ```sh \src\third_party\blink\renderer\core\frame\navigator.cc ``` 有以下两种修改方式,选其中一种即可。 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/1L0Sewqva24P0UZi.webp" alt="在这里插入图片描述" width="100%" />  <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/ONHMfDN1aLldhElH.webp" alt="在这里插入图片描述" width="100%" />  修改完之后保存代码即可,在修改的时候不要将原始代码删除,注释即可方便出错的时候查问题。 保存好了之后,运行以下命令,这个命令就是前面编译章节中的编译命令,你当时编译的时候用的是什么,这里就写什么,他不是重新编译,是增量编译,只对新增的内容进行编译,所以速度会快很多。 ```sh autoninja -C out/Default chrome <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/XMb2pHdURJPFdEtR.webp" alt="在这里插入图片描述" width="100%" />  编译好之后,通过自动化程序检测一下 我们这里使用 playwright 来测试 ```python from playwright.sync_api import sync_playwright def run(): with sync_playwright() as p: browser = p.chromium.launch( executable_path="E:\my_code\chromium\src\out\Default\chrome.exe", headless=False ) # 创建一个新的浏览器上下文 context = browser.new_context() # 打开一个新页面 page = context.new_page() # 访问目标网站 page.goto("https://www.baidu.com") # 等待用户输入后再关闭浏览器 input("Press Enter to close the browser...") # 关闭浏览器 browser.close() if __name__ == "__main__": run() ``` 可以看到是已经成功了  
魔改chromium源码——源码拉取及编译
[魔改chromium——基础环境搭建](https://juejin.cn/spost/7485614368596738100) 上一节在完成了基础环境搭建之后,这一节内容是将源码拉取到我们本地,为后续修改chromium源码做准备,这节内容非常漫长,速度快慢取决于你的网络和机器配置,如果网络和机器不好的话,编译和拉取代码的时间可能会超过十几个小时,在我自己的机器上拉取源码过程大约花了40分钟,机器上编译chromium过程花了6个多小时,才编译出chromium浏览器,所以,有一台好的机器和网络非常重要。 # 设置git全局环境变量 首先在新下载的powershell中执行以下命令,设置一下git全局环境变量,一行一行执行,本地代理端口号改成自己的即可 ```sh git config --global http.proxy http://127.0.0.1:10808 git config --global https.proxy http://127.0.0.1:10808 git config --global http.sslVerify false # 禁用 HTTPS 请求的 SSL 证书验证 git config --global core.autocrlf false # 禁用换行符的自动转换 git config --global core.filemode false # 禁用 Git 对文件权限模式(如可执行权限)的检测 git config --global branch.autosetuprebase always # 控制新分支的默认行为,使其在拉取(pull)时自动使用 rebase 而不是 merge 这两个可以选择性开启 git config --global core.fscache true # 启用文件系统缓存功能 git config --global core.preloadindex true # 启用索引预加载功能 ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/B0xcrGLkwKbcEUOa.webp" alt="在这里插入图片描述" width="692px" /> 设置好了之后,在depot\_tools同级目录中新建一个chromium目录 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/gutJJNcQng5YtKIm.webp" alt="在这里插入图片描述" width="505px" /> # 创建boto.cfg和设置boto.cfg 创建boto.cfg文件写入内容,本地代理的端口号proxy\_port参数改成自己的就可以 ```sh [Boto] proxy = 127.0.0.1 proxy_port = 10808 ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/x0pBFt3bQQzAFCfO.webp" alt="在这里插入图片描述" width="395px" /> 新增环境变量NO_AUTH BOTO_CONFIG,值为boto.cfg文件路径  # 拉取源码 设置好了之后,首先将powershell当前目录切换到我们新建的chromium目录,然后执行以下命令,就可以开始拉取源码了,本地代理的端口号记得改成自己的就可以 ```sh $env:HTTP_PROXY="http://127.0.0.1:10808"; $env:HTTPS_PROXY="http://127.0.0.1:10808" fetch --no-history chromium ```  拉取中的状态,如果你的网络不好,这将会是一个非常漫长的过程,需要耐心等待    <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/L8q8Wu6G4HdAEjtq.webp" alt="在这里插入图片描述" width="100%" /> 拉取完之后,还会自动执行Hooks   当这2个都是百分之百,就说明成功了,下一步就可以执行编译了。 以下是chromium每个文件夹大概作用的介绍 | 目录名称 | 描述 | |-----------------------|----------------------------------------------------------------------------------------| | android_webview | 集成 Android 平台的外观展示 | | apps | 参考如何构建 Chrome 应用程序 | | ash | 包含了 Chrome OS 的窗口管理器和其他用户界面相关的代码 | | base | Chromium 中的基础库,包含了许多通用的数据结构、工具函数等 | | build | 包含了构建 Chromium 时使用的脚本和配置文件 | | build_overrides | 用于存放特定配置或定制化构建所需的构建脚本和覆盖文件 | | buildtools | 用于构建和编译 Chromium 项目的工具和脚本 | | cc | Chromium 的渲染合成层 | | chrome | 这个目录包含了 Chrome 浏览器特定的功能和 UI,如书签、扩展程序、设置等 | | chromecast | 与 Google Chromecast 相关的代码和功能 | | chromeos | 负责 Chrome OS 相关的功能和特性 | | codelabs | 用于存储代码示例、教程和演示性的实验项目 | | components | 用于存放浏览器各种组件,如 PDF 渲染器、插件系统、媒体播放器 | | content | Chromium 内层的代码,包含了浏览器核心功能,例如网络通信、多媒体处理等 | | courgette | 数据压缩和差分算法相关的代码 | | crypto | 用于处理加密和密码学相关操作,包含各种加密算法的实现和安全相关的功能 | | dbus | 用于处理 D-Bus(Desktop Bus)相关功能的部分 | | device | 提供跨平台底层硬件 API 抽象层 | | docs | 用于存储各种文档、说明和指南,通常用于开发者、贡献者或者维护者参考 | | extensions | 用来存放浏览器扩展(Extensions)相关代码 | | fuchsia_web | 为 Fuchsia 操作系统提供支持的一部分 | | gin | 一个用于实现 JavaScript 和 C++ 之间的互操作的库 | | google_apis | 用于存储与 Google API 相关的代码和配置 | | google_update | 负责处理 Google 更新相关的功能 | | gpu | Chromium 的 GPU 加速相关的代码,用于提高浏览器的性能 | | headless | 实现 Chromium 的无头(headless)模式 | | infra | 存储与基础设施、自动化测试以及持续集成/持续部署(CI/CD)相关的代码和配置文件 | | internal | 存放一些内部工具、测试或者辅助代码,而不是直接与浏览器的核心功能相关 | | ios | 存储与 iOS 平台相关的代码和资源 | | ios_internal | 存储与 iOS 平台相关的内部实现代码和工具 | | ipc | 用于实现进程间通信(IPC)的关键组件之一 | | media | Chromium 的媒体框架,包括音频和视频播放等功能 | | mojo | 一个跨进程通信框架,用于在 Chromium 浏览器的不同进程之间进行高效的通信 | | native_client | 存放 Native Client(NaCl)相关代码的地方 | | native_client_sdk | Native Client 的软件开发工具包(SDK) | | net | Chromium 开发的网络库,使用 WebKit 下的 test_shell 时可以单独使用 | | out | 编译输出的目录 | | pdf | PDF 文档相关的代码和功能 | | ppapi | Pepper 插件 API(PPAPI)的实现 | | printing | 处理打印相关的功能 | | remoting | 包含与远程桌面相关的代码 | | rlz | 处理 RLZ(Referrer Language Zwiebelbrot)相关功能 | | sandbox | 包含了 Chromium 的沙盒功能,用于增强浏览器的安全性 | | services | 包含了 Chromium 的后台服务代码,处理一些后台任务和服务 | | signing_keys | 用于存储签名密钥相关的文件 | | skia | Chromium 浏览器对图形处理的基础支持,如 2D 图形渲染、图像处理等 | | sql | 提供 SQLite 相关的模块 | | storage | 数据存储和持久性相关的代码,提供了与本地数据交互的功能 | | styleguide | 存放代码风格指南和规范的相关文件 | | testing | 包含了 Chromium 的测试代码,用于自动化测试和测试框架 | | third_party | 包含了 Chromium 使用的第三方库和工具,如 Skia、WebRTC、SQLite 等 | | tools | 包含了用于开发、测试和调试 Chromium 的工具和脚本 | | ui | 包含了 Chromium 的用户界面相关的代码,包括窗口、UI 控件等 | | url | Google 开源的 URL 解析和规范库 | | v8 | Google 开发的一款基于 JavaScript 的解释引擎 | | webkit | 将网页内容转换为可视化的页面,并提供与用户交互的基础功能,用来解析 HTML/CSS 等 | # 生成配置 在chromium目录下执行以下命令,设置生成配置 ```sh cd src && gn gen out/Default   # 编辑配置 执行成功之后,运行一下命令编辑配置 ```sh gn args out/Default ``` 运行之后他会打开一个txt的文件,这里面就是写后面要编译的条件,这是官方的编译条件配置参考链接<https://www.chromium.org/developers/gn-build-configuration>  将这些内容粘贴进去,然后保存并关闭记事本,他就会自动执行接下来的操作 ```sh # 系统 target_os="win" # 架构 target_cpu="x64" # 是否开启调试 is_debug=false # 不允许远程调试 enable_remoting=false # 使用Chrome官方的编译优化建议 is_official_build=true # 分散链接编译 is_component_build=false #不使用官方API秘钥 use_official_google_api_keys=false #不使用官方API秘钥 google_api_key=false #不使用官方API秘钥 google_default_client_secret=false #关闭支持NACL,这是一种Chrome插件,因为安全性,稳定性存在问题,已经很少使用了 enable_nacl=false #支持H264编码 proprietary_codecs=true #解码器 ffmpeg_branding="Chrome" #使用无符号或最少符号实现更快的构建 symbol_level=0 #删除v8符号 v8_symbol_level=0 #删除WebCore符号 blink_symbol_level=0 #Chrome品牌标识和实验性标志 disable_fieldtrial_testing_config=true ```   记事本打开chromium目录下的 .gclient 文件  将内容新增到这个位置  修改保存之后,根据前面报错日志中的提示,再运行一次以下命令 ```sh gclient runhooks ``` <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/6d3gmyw6LUwdgIIA.webp" alt="在这里插入图片描述" width="100%" /> gclient runhooks运行成功之后,再从之前的编辑配置步骤重新执行一下   # 编译chromium 以上都正常之后,就可以开始编译了,执行以下命令即可,这是一个非常漫长的过程,所以如果你是笔记本,请连接好你的电源,保证你的网络一直是畅通的,保持屏幕常亮,关闭所有杀毒软件的目录扫描,关闭所有会让你电脑息屏的软件 ```sh autoninja -C out/Default chrome ```  大约53000多个组件要编译,时间的话,需要几个小时到几十个小时不等,具体看你的机器配置,全程几乎跑满CPU,编译好了之后结果会生成在 chromium目录下的 src/out/Default 目录。然后运行chrome.exe, 正常之后,说明我们编译配置和编译环境是ok的,就可以到下一节修改源码内容了 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/t06TBUi0sss0VMl3.webp" alt="在这里插入图片描述" width="100%" />    下一节就是修改chromium源码,实现一些我们想要实现的功能
魔改chromium源码——基础环境搭建
[谷歌chromium环境要求详细文档](https://chromium.googlesource.com/chromium/src.git/+/HEAD/docs/windows_build_instructions.md) # 软件和环境要求,必须安装,硬性要求 * 系统环境:Windows 10,内存最小8GB,推荐16GB+,NTFS格式磁盘最少100GB空间 * Git版本:安装最新版本即可,[Git桌面端下载地址](https://git-scm.com),会自动将Git添加到系统Path * Visual Studio版本:[Visual Studio 2022 IDE](https://visualstudio.microsoft.com/zh-hans/vs/) 不要下载错了版本,目前谷歌chromium文档的环境要求是Visual Studio 2022,后面随着chromium迭代可能会是其他版本,严格按照官方文档要求来就不会有错 * NodeJS:下载最新的TLS版本就可以,[NodeJS官网链接](https://nodejs.cn/download/) * python环境:python 10.0 版本即可,也可以装最新的 python 13.0 版本,但不建议 * PowerShell:安装最新版本即可,不要用电脑自带的,自带的PowerShell版本太低,后面拉取的时候会出问题,下载地址[PowerShell](https://github.com/PowerShell/PowerShell/releases) * 准备一个相对稳定的梯子,这个非常重要,没有梯子的话,可以不用往下看了 # 安装和设置Visual Studio Visual Studio安装和C++组件要求: <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/EJswLTaHxmERiOuw.webp" alt="在这里插入图片描述" width="100%" /> 图片中这些C++组件是最基础的要求,如果需要其他组件,可根据需要自行添加,然后是对windows 11 SDK版本要求,会有一个默认的,然后,根据谷歌chromium文档的要求,新添加谷歌要求版本的SDK即可,所以我这里会有2个win11的SDK <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/GkxiHl8OLaVaQNX2.webp" alt="在这里插入图片描述" width="100%" />   选择好组件之后,安装即可,如要要修改安装位置,可以在此处修改安装位置  安装好了之后,将vs2022\_install设置到环境变量中,变量值就是你安装的Visual Studio路径  # 安装Debugging Tools 在windows的应用列表中,添加Debugging Tools 选择修改  选择change <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/VesqTdH094LKZcTi.webp" alt="在这里插入图片描述" width="100%" /> 选择change,等待下载安装即可 <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/czj1qr5UPiNkXeJu.webp" alt="在这里插入图片描述" width="100%" /> <img src="https://pic.code-nav.cn/post_picture/1922184057994162177/RNTkHoR90uLZfr3r.webp" alt="在这里插入图片描述" width="100%" /> # 下载和设置depot_tools 下载depot\_tools捆绑文件 下载方式一:<https://storage.googleapis.com/chrome-infra/depot_tools.zip> 下载方式二: ```sh git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git ```  下载好之后将压缩包中的depot\_tools文件夹解压出来,随便解压到你自己能找到的地方就就可以,然后将这个depot\_tools目录设置到系统变量中  在系统变量中设置DEPOT\_TOOLS\_WIN\_TOOLCHAIN,值为0  # 修改depot_tools中的cipd.bat文件 打开cipd.bat文件,然后搜索关键字":CLEAN\_BOOTSTRAP" 将这个位置的powershell修改为下面的pwsh,修改完成保存即可    # 初始化依赖 以上都设置好了之后,就可以开始初始化依赖,运行以下命令,本地代理的端口号改成自己的就可以 ```sh $env:HTTP_PROXY="http://127.0.0.1:10808"; $env:HTTPS_PROXY="http://127.0.0.1:10808" gclient ``` 初始化完成之后,就会出现以下结果,说明环境已经基本搭建完成,下一节内容就是如何拉取chromium代码 
浏览器渲染页面过程
<html> <head></head> <body> <div class="content ql-editor"> <p><br></p> <h1><strong style="color: rgb(51, 51, 51);">浏览器是如何渲染页面的?</strong></h1> <p><br></p> <p>当浏览器的网络线程收到 HTML 文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列</p> <p>在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/m6y03pk6.jpeg"></p> <blockquote> <div class="blockquote-item"> HTML字符串 ---> 渲染 ---> 像素信息 </div> </blockquote> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="color: rgb(51, 51, 51);">渲染流程</strong></h2> <p><br></p> <p>整个渲染流程分为多个阶段,分别是:HTML 解析、样式计算、布局、分层、绘制、分块、光栅化、画、像素信息</p> <p>每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入</p> <p>这样,整个渲染流程就形成了一套组织严密的生产流水线</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/oedxla3i.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="color: rgb(51, 51, 51);">解析HTML</strong></h2> <p><br></p> <p>渲染的第一步是<strong>解析HTML</strong></p> <p><strong>Document Object Model</strong></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/kmfkfzve.jpeg"></p> <p><br></p> <p>解析过程中遇到 CSS 解析 CSS, 遇到 JS 执行 JS。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和外部的 JS 文件。</p> <p><br></p> <p>如果主线程解析到Link位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,继续解析后续的 HTML。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/v0sz0pxl.jpeg"></p> <p><br></p> <p>CSS解析如下</p> <p><strong>CSS Object Model</strong></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/tnxadbjd.jpeg"></p> <p>为什么有默认样式这东西就是因为提前设置了,这里拿chromium源码举例</p> <p><br></p> <p>我们都知道body有默认样式,就是因为浏览器提前设置了</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/buika6uk.jpeg"></p> <p><br></p> <p>我们可以通过上面形成的CSS树形成的StyleSheetList来操作,如何操作呢</p> <p><br></p> <p>我们在控制台输入,给第3个style树添加规则:每个div有个红色的边框</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/xj5c0rz9.jpeg"></p> <p>可以查看浏览器的情况,发现一大堆都被添加了</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/pgszqu34.jpeg"></p> <p>但你会发现在元素中没有像我们添加CSS一样把样式添加到div里面,也就是说这种添加是外部的,如<link...></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/xhsxftci.jpeg"></p> <p><br></p> <p><br></p> <p>如果主线程解析到script位置,会停止解析 HTML,转而等待 JS 文件下载好,并将全局代码解析执行完成后,才能继续解析 HTML.这是因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以 DOM 树的生成必须暂停。这就是 JS 会阻塞 HTML 解析的根本原因。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/pyw2bs4a.jpeg"></p> <p>第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。</p> <blockquote> <div class="blockquote-item"> 为什么要变为对象结构,因为如果为字符串不好操作 </div> <div class="blockquote-item"> 这里借用c的树结构图 </div> </blockquote> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/i8bil8up.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="color: rgb(51, 51, 51);">样式计算</strong></h2> <p><br></p> <p>渲染的下一步是<strong>样式计算</strong>。</p> <p><br></p> <p>主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style。</p> <p>在这一过程中,很多预设值会变成绝对值,比如red会变成rgb(255,0,0);相对单位会变成绝对单位,比如em会变成px</p> <p><br></p> <p>这一步完成后,会得到一棵带有样式的 DOM 树。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/ycmycku0.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="font-family: inherit;font-style;font-variant-ligatures;font-variant-caps; font-size: 1.17em; color: rgb(51, 51, 51);">布局</strong></h2> <p><br></p> <p>接下来是<strong>布局</strong>,布局完成后会得到布局树。</p> <p><br></p> <p>布局阶段会依次遍历 DOM 树的每一个节点,计算每个节点的几何信息。例如节点的宽高、相对包含块的位置。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/f5p6cl7b.jpeg"></p> <p>大部分时候,DOM 树和布局树并非一一对应。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/6zzi8aaw.jpeg"></p> <p>比如display:none的节点没有几何信息,因此不会生成到布局树;又比如使用了伪元素选择器,虽然 DOM 树中不存在这些伪元素节点,但它们拥有几何信息,所以会生成到布局树中。还有匿名行盒、匿名块盒等等都会导致 DOM 树和布局树无法一一对应。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/p4y445rv.jpeg"></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/kshzhbdm.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="font-family: inherit;font-style;font-variant-ligatures;font-variant-caps; font-size: 0.83em; color: rgb(51, 51, 51);">分层</strong></h2> <p><br></p> <p>下一步是<strong>分层</strong></p> <p><br></p> <p>主线程会使用一套复杂的策略对整个布局树中进行分层。</p> <p><br></p> <p>分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。</p> <p><br></p> <p>滚动条、堆叠上下文、transform、opacity 等样式都会或多或少的影响分层结果,也可以通过will-change属性更</p> <p>大程度的影响分层结果。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/j9s264hl.jpeg"></p> <p>我们可以通过开发者工具中的图层来查看当前页面的分层</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/rexcs2je.jpeg"></p> <p>可以移动或旋转查看当前页的分层</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/rka1bcq3.jpeg"></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="font-size: 0.83em;font-style;font-variant-ligatures;font-variant-caps; color: rgb(51, 51, 51);">绘制</strong></h2> <p><br></p> <p>再下一步是<strong>绘制</strong></p> <p><br></p> <p>主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/6yzltrjq.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="color: rgb(51, 51, 51);">分块</strong></h2> <p><br></p> <p>完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/r817xbje.jpeg"></p> <p>合成线程首先对每个图层进行分块,将其划分为更多的小区域。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/ugc0v529.jpeg"></p> <p>它会从线程池中拿取多个线程来完成分块工作。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/xaeuk05t.jpeg"></p> <p><br></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="font-size: 0.83em;font-style;font-variant-ligatures;font-variant-caps; color: rgb(51, 51, 51);">光栅化</strong></h2> <p><br></p> <p>分块完成后,进入<strong>光栅化</strong>阶段。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/pk74mjj1.jpeg"></p> <p>合成线程会将块信息交给 GPU 进程,以极高的速度完成光栅化。</p> <p><br></p> <p>GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。</p> <p><br></p> <p>光栅化的结果,就是一块一块的位图</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/im0nq358.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="font-family: inherit;font-style;font-variant-ligatures;font-variant-caps; font-size: 0.83em; color: rgb(51, 51, 51);">画</strong></h2> <p><br></p> <p>最后一个阶段就是<strong>画</strong>了</p> <p><br></p> <p>合成线程拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。</p> <p><br></p> <p>指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。</p> <p><br></p> <p>变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。</p> <p><br></p> <p>合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/wic2pc6f.jpeg"></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <h2><strong style="font-size: 0.83em;font-style;font-variant-ligatures;font-variant-caps; color: rgb(51, 51, 51);">完整过程</strong></h2> <p><br></p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/q6gnt5rp.jpeg"></p> <p><br></p> <p><br></p> <hr class="article-hr" style="height: 0px"> <p><br></p> <h2><strong style="color: rgb(51, 51, 51);">什么是 reflow?</strong></h2> <p><br></p> <p>reflow 的本质就是重新计算 layout 树。</p> <p><br></p> <p>当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。</p> <p><br></p> <p>为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当 JS 代码全部完成后再进行统一计算。所以,改动属性造成的 reflow 是异步完成的。</p> <p><br></p> <p>也同样因为如此,当 JS 获取布局属性时,就可能造成无法获取到最新的布局信息。</p> <p><br></p> <p>浏览器在反复权衡下,最终决定获取属性立即 reflow</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/fgwbn35j.jpeg"></p> <hr class="article-hr" style="height: 0px"> <p><br></p> <h2><strong style="color: rgb(51, 51, 51);">什么是 repaint?</strong></h2> <p><br></p> <p>repaint 的本质就是重新根据分层信息计算了绘制指令。</p> <p>当改动了可见样式后,就需要重新计算,会引发 repaint。</p> <p>由于元素的布局信息也属于可见样式,所以 reflow 一定会引起 repaint</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/2g0t0ree.jpeg"></p> <hr class="article-hr" style="height: 0px"> <p><br></p> <h2><strong style="color: rgb(51, 51, 51);">为什么 transform 的效率高?</strong></h2> <p><br></p> <p>因为 transform 既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个「draw」阶段</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/87zzz2x3.jpeg"></p> <p>由于 draw 阶段在合成线程中,所以 transform 的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响 transform 的变化。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/r67bhg41.jpeg"></p> <p>下面举个例子</p> <div class="ql-code-block-container"> <div class="ql-code-block"> <span class="ql-token hljs-meta"><!DOCTYPE html></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><html lang="en"></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><head></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><meta charset="UTF-8" /></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><meta http-equiv="X-UA-Compatible" content="IE=edge" /></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><meta name="viewport" content="width=device-width, initial-scale=1.0" /></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><title></span>Document<span class="ql-token hljs-tag"></title></span> </div> <div class="ql-code-block"> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><style></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-selector-class">.ball</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">width</span>: <span class="ql-token hljs-number">100px</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">height</span>: <span class="ql-token hljs-number">100px</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">background</span>: <span class="ql-token hljs-number">#f40</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">border-radius</span>: <span class="ql-token hljs-number">50%</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">margin</span>: <span class="ql-token hljs-number">30px</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-selector-class">.ball1</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">animation</span>: move1 <span class="ql-token hljs-number">1s</span> alternate infinite ease-in-out; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-selector-class">.ball2</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">position</span>: fixed; </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">left</span>: <span class="ql-token hljs-number">0</span>; </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">animation</span>: move2 <span class="ql-token hljs-number">1s</span> alternate infinite ease-in-out; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">@keyframes</span> move1 { </div> <div class="ql-code-block"> <span class="ql-token hljs-selector-tag">to</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">transform</span>: <span class="ql-token hljs-built_in">translate</span>(<span class="ql-token hljs-number">100px</span>); </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">@keyframes</span> move2 { </div> <div class="ql-code-block"> <span class="ql-token hljs-selector-tag">to</span> { </div> <div class="ql-code-block"> <span class="ql-token hljs-attribute">left</span>: <span class="ql-token hljs-number">100px</span>; </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"></style></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"></head></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><body></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><button id="btn"></span>死循环<span class="ql-token hljs-tag"></button></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><div class="ball ball1"></div></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><div class="ball ball2"></div></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"><script></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">function</span> <span class="ql-token hljs-title">delay</span>(<span class="ql-token hljs-params">duration</span>) { </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">var</span> start = <span class="ql-token hljs-title">Date</span>.<span class="ql-token hljs-title">now</span>(); </div> <div class="ql-code-block"> <span class="ql-token hljs-keyword">while</span> (<span class="ql-token hljs-title">Date</span>.<span class="ql-token hljs-title">now</span>() - start < duration) {} </div> <div class="ql-code-block"> } </div> <div class="ql-code-block"> btn.<span class="ql-token hljs-property">onclick</span> = <span class="ql-token hljs-keyword">function</span> () { </div> <div class="ql-code-block"> <span class="ql-token hljs-title">delay</span>(<span class="ql-token hljs-number">5000</span>); </div> <div class="ql-code-block"> }; </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"></script></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"></body></span> </div> <div class="ql-code-block"> <span class="ql-token hljs-tag"></html></span> </div> </div> <p>一个小球是用animation和transform做动画,一个是普通的,在滚动的时候点击死循环,会发现animation的小球依旧在动,而另一个已经停止,就是因为这个小球它不在主线程渲染,所以不会影响 transform 的变化。</p> <p><img src="https://pic.code-nav.cn/planet_post_image/1651569319508750338/weq6wj09.jpeg"></p> <p><br></p> </div> </body> </html>
#浏览器# 今天分享的是浏览器的历史。 notion地址:https://mblog.notion.site/03219f63124048d484912a388490e852 掘金地址(可能在审核中无法访问):https://juejin.cn/post/7203593574746095671
