引言
在現代瀏覽器的開發中,前端頁面和 C++ 內核之間的通信是一項核心功能。無論是本地設置頁(chrome:// 內置 H5)還是在線活動頁,前端都可能需要調用瀏覽器底層 API,實現諸如“設置默認瀏覽器”、“更改壁紙”、“讀取用戶配置”等操作。
本文將以 Chromium 內核及其派生瀏覽器為例,詳細解析 SetDefaultView
的創建、OnAppCmd
消息轉發機制以及 delegate 承載業務邏輯的設計思路,并討論這種機制在本地頁和在線頁的通用性。
一、SetDefaultView 的創建與初始化
瀏覽器彈窗(如“設置默認瀏覽器”對話框)通常是一個獨立的 View 對象,承載前端頁面并接收消息。核心創建入口是 SetDefaultView::CreateSetDefaultView
:
SetDefaultView* SetDefaultView::CreateSetDefaultView( gfx::NativeWindow parent, const GURL& url, Delegate* delegate, Browser* browser) { Browser* browser_active = BrowserList::GetInstance()->GetLastActive(); if (!browser_active) browser_active = browser; gfx::NativeWindow parent_active = parent; SeBrowserView* browser_view_active = SeBrowserView::GetBrowserViewForBrowser(browser_active); if (browser_view_active) parent_active = browser_view_active->frame()->GetNativeWindow(); SetDefaultView* set_view = new SetDefaultView(url, browser_active, delegate, true); if (nullptr == set_view) return nullptr; set_view->set_parent_window(parent_active); set_view->SetBrowserView(browser_view_active); set_view->set_use_focusless(true); set_view->set_close_on_deactivate(false); views::SeBubbleDelegateView::CreateBubble(set_view); return set_view; }
關鍵解析
選擇活躍 Browser
使用BrowserList::GetInstance()->GetLastActive()
獲取當前活躍的瀏覽器實例,如果沒有,則使用傳入的browser
。
這樣彈窗總能掛載到一個活躍瀏覽器上下文。確定父窗口
利用SeBrowserView::GetBrowserViewForBrowser
獲取 BrowserView,并將其 Frame 的原生窗口作為父窗口。
父窗口的存在保證彈窗能正確附著在瀏覽器 UI 上。實例化 SetDefaultView
new SetDefaultView(url, browser_active, delegate, true);
url
:要加載的頁面,可以是本地頁或在線頁。delegate
:后端業務邏輯的承載者。
配置 View 屬性
set_use_focusless(true)
:無焦點模式。set_close_on_deactivate(false)
:失去焦點不關閉。
這些屬性保證彈窗在用戶操作時穩定顯示。
注冊 Bubble
views::SeBubbleDelegateView::CreateBubble(set_view);
這一行是關鍵,它將
SetDefaultView
掛載到 UI 樹中,同時初始化內部WebHostView
,確保前端 JS 能發消息到后端 C++。
二、JS 消息到 C++ 的轉發機制
在 SetDefaultView
中,核心消息入口是 OnAppCmd
:
void SetDefaultView::OnAppCmd(WebHostView* sender, int invoke_id, const std::string& module_name, const std::string& function_name, const std::string& p1, const std::string& p2) { if (delegate_) delegate_->OnAppCmd(invoke_id, function_name, p1, p2); }
調用流程解析
前端 JS 發起調用
頁面中 JS 調用appcmd(...)
或window.external.invoke(...)
,傳入函數名和參數:
appcmd("defaultModule", "setAsDefaultBrowser", "", "");
WebHostView 接收消息
WebHostView
負責攔截前端調用,將參數解析成:invoke_id
:調用 IDmodule_name
:模塊名function_name
:函數名p1
、p2
:參數
SetDefaultView::OnAppCmd 轉發
消息到達OnAppCmd
,但這里并不處理具體業務,而是轉發給 delegate。delegate 處理業務
由delegate_->OnAppCmd(...)
根據function_name
執行具體邏輯,例如調用系統 API 設置默認瀏覽器或查詢狀態。
三、Delegate 的業務邏輯實現示例
典型的 delegate 實現如下:
class SetDefaultController : public SetDefaultView::Delegate { public: void OnAppCmd(int invoke_id, const std::string& function_name, const std::string& p1, const std::string& p2) override { if (function_name == "setAsDefaultBrowser") { HandleSetAsDefaultBrowser(invoke_id); } else if (function_name == "checkDefaultBrowser") { HandleCheckDefaultBrowser(invoke_id); } else { LOG(WARNING) << "Unknown command: " << function_name; } } private: void HandleSetAsDefaultBrowser(int invoke_id) { bool success = ShellIntegration::SetAsDefaultBrowser(); SendResponseToJs(invoke_id, success ? "ok" : "fail"); } void HandleCheckDefaultBrowser(int invoke_id) { bool is_default = ShellIntegration::IsDefaultBrowser(); SendResponseToJs(invoke_id, is_default ? "yes" : "no"); } void SendResponseToJs(int invoke_id, const std::string& result) { base::Value::Dict dict; dict.Set("id", invoke_id); dict.Set("result", result); web_ui()->CallJavascriptFunctionUnsafe("onAppCmdResponse", dict); } };
說明
delegate 根據
function_name
分發不同業務邏輯。執行業務后通過
web_ui()->CallJavascriptFunctionUnsafe
回調前端,完成 JS 的響應。
四、調用鏈總結
完整流程如下:
前端 JS ↓ appcmd / window.external.invoke WebHostView ↓ SetDefaultView::OnAppCmd ↓ delegate_->OnAppCmd ↓ 系統 API 或業務邏輯 ↓ WebUI 回調前端顯示結果
五、本地頁與在線頁的適用性
1. 本地頁(chrome:// 或內置 H5)
URL:
chrome://settings
或pak
中的 H5 頁面優點:
響應快,無網絡依賴
前端與后端接口完全可控
應用場景:
設置默認瀏覽器
修改瀏覽器主題
內置配置頁
2. 在線頁(https:// 或遠程 H5)
URL:遠程服務器托管頁面
優點:
UI 可熱更新
可以進行動態內容、A/B 測試
應用場景:
登錄/綁定頁
活動推廣頁
數據統計/上報
注意:
依賴網絡,安全性需校驗
消息仍通過
OnAppCmd
轉發,不受 URL 來源影響
3. 通用性分析
核心機制只依賴 WebHostView + OnAppCmd + delegate
不論頁面來源本地還是遠程,前端調用均可安全到達 delegate 執行邏輯
六、總結
SetDefaultView 是瀏覽器彈窗消息通道的承載者。
CreateSetDefaultView 負責實例化 view、掛載 UI、初始化 WebHostView。
OnAppCmd 作為 JS 消息入口,負責轉發給 delegate。
delegate 承載業務邏輯,實現真正的功能操作。
本地頁和在線頁都可使用同一機制,差別只在頁面 URL 和資源管理。
這種設計具有以下優點:
UI 與業務邏輯解耦
消息分發統一,可擴展性強
支持本地和在線頁面,便于前端迭代
七、附錄:博客擴展建議
源碼截圖:展示
CreateSetDefaultView
、OnAppCmd
、delegate 代碼片段調用鏈圖:前端 JS → WebHostView → SetDefaultView → delegate → 系統 API
實際案例:設置默認瀏覽器、壁紙設置頁面
注意事項:
delegate 必須在 view 創建前綁定
在線頁調用需要考慮跨域和安全
失去焦點或父窗口關閉時的行為設置
通過本文,你可以清晰理解瀏覽器彈窗從創建到 JS 消息處理的完整閉環,無論本地頁還是在線頁,都能使用同樣的機制實現前端調用本地業務邏輯。