本篇讲解如何通过消息路由,将 JS 发送的请求路由到浏览器进程来处理。
消息路由
消息路由发生在浏览器进程和渲染进程之间,通过消息路由,CEF将运行在渲染进程的 JS 消息发送到浏览器进程,并在浏览器进程中通过执行 C++ 代码做出响应。整个过程都是异步进行的。
实现步骤
CEF 抽象出了两个类来分别表示路由的两端:
- CefMessageRouterBrowserSide 表示路由的浏览器进程端
- CefMessageRouterRendererSide 表示路由的渲染进程端
在实现消息路由的具体实现中,需要在浏览器进程端创建 CefMessageRouterBrowserSide 对象,在渲染进程端创建 CefMessageRouterRendererSide。具体的实现步骤如下:
浏览器端:
1.1. 实现 RouterHandler,重写它的 OnQuery 方法以处理 JS 请求。
1.2. 在 CefClient 中声明 CefMessageRouterBrowserSide 对象,在 CefLifeSpanHandler::OnAfterCreated() 中创建它,并向它注册第1步中实现的处理类。
1.3. 在 CefLifeSpanHandler::OnBeforeClose() 中销毁 CefMessageRouterBrowserSide 对象和处理对象。
1.4. 重写 CefClient::OnProcessMessageReceived(),CefRequestHandler::OnBeforeBrowse(), CefRequestHandler::OnRenderProcessTerminated(),在这三个方法中调用 CefMessageRouterBrowserSide 对象同名的方法。渲染端:
2.1. 在 CefApp 中声明 CefMessageRouterRendererSide 对象,在 CefRenderProcessHandler::OnWebKitInitialized() 中创建它。
2.2. 重写 CefRenderProcessHandler::OnContextCreated(),CefRenderProcessHandler::OnContextReleased(),CefRenderProcessHandler::OnProcessMessageReceived(),在这三个方法中调用 CefMessageRouterRendererSide 对象同名的方法。
示例代码
#include "include/cef_app.h"
#include "include/cef_browser.h"
#include "include/cef_client.h"
#include "include/wrapper/cef_message_router.h"
#include "include/wrapper/cef_helpers.h"
#include <Windows.h>
// 浏览器客户端
class BrowserClient : public CefClient, public CefLifeSpanHandler, public CefRequestHandler
{
public:
// 1.1
class RouterHandler : public CefMessageRouterBrowserSide::Handler
{
public:
virtual bool OnQuery(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
int64 query_id, const CefString& request, bool persistent,
CefRefPtr<Callback> callback) override
{
CEF_REQUIRE_UI_THREAD();
// 处理JS请求
if (request == "giveMeFive")
{
callback->Success("OK! Give you 5!");
return true;
}
return false;
}
};
// Constructor & Destructor
public:
BrowserClient() {}
virtual ~BrowserClient() {}
// CefClient methods:
public:
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override { return this; }
virtual CefRefPtr<CefRequestHandler> GetRequestHandler() override { return this; }
// 1.4
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process, CefRefPtr<CefProcessMessage> message) override
{
return m_messageRouter->OnProcessMessageReceived(browser, source_process, message);
}
// CefLifeSpanHandler methods:
public:
// 1.2
virtual void OnAfterCreated(CefRefPtr<CefBrowser> browser)
{
CEF_REQUIRE_UI_THREAD();
if (!m_messageRouter)
{
CefMessageRouterConfig config;
m_messageRouter = CefMessageRouterBrowserSide::Create(config);
m_messageRouter->AddHandler(m_routerHandler.get(), true);
}
}
// 1.3
virtual void OnBeforeClose(CefRefPtr<CefBrowser> browser) override
{
CEF_REQUIRE_UI_THREAD();
m_messageRouter->RemoveHandler(m_routerHandler.get());
m_routerHandler.reset();
m_messageRouter = nullptr;
CefQuitMessageLoop();
}
// CefRequestHandler methods:
public:
// 1.4
virtual bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request, bool is_redirect) override
{
CEF_REQUIRE_UI_THREAD();
m_messageRouter->OnBeforeBrowse(browser, frame);
return false;
}
// 1.4
virtual void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
TerminationStatus status) override
{
CEF_REQUIRE_UI_THREAD();
m_messageRouter->OnRenderProcessTerminated(browser);
}
private:
std::unique_ptr<RouterHandler> m_routerHandler{ new RouterHandler };
// 1.2
CefRefPtr<CefMessageRouterBrowserSide> m_messageRouter{ nullptr };
private:
IMPLEMENT_REFCOUNTING(BrowserClient);
DISALLOW_COPY_AND_ASSIGN(BrowserClient);
};
// 浏览器进程 App
class AppBrowser : public CefApp, public CefBrowserProcessHandler
{
public:
AppBrowser() {}
virtual ~AppBrowser() {}
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; }
// CefBrowserProcessHandler methods:
public:
virtual void OnContextInitialized() override
{
CEF_REQUIRE_UI_THREAD();
CefWindowInfo window_info;
CefRefPtr<BrowserClient> client(new BrowserClient());
window_info.SetAsPopup(NULL, "cefsimple");
CefBrowserSettings browser_settings;
CefString url = "file:///G:/projects/vs2015/SimpleBrowser/JSTest/test.html";
CefBrowserHost::CreateBrowser(window_info, client, url, browser_settings, NULL);
}
private:
IMPLEMENT_REFCOUNTING(AppBrowser);
DISALLOW_COPY_AND_ASSIGN(AppBrowser);
};
// 渲染进程 App
class AppRenderer : public CefApp, public CefRenderProcessHandler
{
public:
AppRenderer() {}
virtual ~AppRenderer() {}
// CefApp methods:
public:
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() override { return this; }
// CefRenderProcessHandler methods;
public:
// 2.1
virtual void OnWebKitInitialized() override
{
CEF_REQUIRE_RENDERER_THREAD();
CefMessageRouterConfig config;
m_messageRouter = CefMessageRouterRendererSide::Create(config);
}
// 2.2
virtual void OnContextCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) override
{
CEF_REQUIRE_RENDERER_THREAD();
m_messageRouter->OnContextCreated(browser, frame, context);
}
// 2.2
virtual void OnContextReleased(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame, CefRefPtr<CefV8Context> context) override
{
CEF_REQUIRE_RENDERER_THREAD();
m_messageRouter->OnContextReleased(browser, frame, context);
}
// 2.2
virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
CefProcessId source_process, CefRefPtr<CefProcessMessage> message) override
{
CEF_REQUIRE_RENDERER_THREAD();
return m_messageRouter->OnProcessMessageReceived(browser, source_process, message);
}
private:
// 2.1
CefRefPtr<CefMessageRouterRendererSide> m_messageRouter{ nullptr };
private:
IMPLEMENT_REFCOUNTING(AppRenderer);
DISALLOW_COPY_AND_ASSIGN(AppRenderer);
};
// 除浏览器进程,渲染进程外的其他进程 App
class AppOther : public CefApp
{
public:
AppOther() {}
virtual ~AppOther() {}
private:
IMPLEMENT_REFCOUNTING(AppOther);
DISALLOW_COPY_AND_ASSIGN(AppOther);
};
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
// 获取进程启动参数
CefMainArgs main_args(hInstance);
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW());
// 根据不同的参数,创建不同的 App
CefRefPtr<CefApp> app;
if (!command_line->HasSwitch("type"))
{
app = new AppBrowser();
}
else
{
const std::string &processType = command_line->GetSwitchValue("type");
if (processType == "renderer")
{
app = new AppRenderer();
}
else
{
app = new AppOther();
}
}
// 执行子进程
int exitCode = CefExecuteProcess(main_args, app.get(), NULL);
if (exitCode >= 0)
{
return exitCode;
}
// 执行浏览器进程
CefSettings settings;
settings.no_sandbox = true;
CefInitialize(main_args, settings, app.get(), NULL);
CefRunMessageLoop();
CefShutdown();
return 0;
}
JS端:
<html>
<head>
<title>测试消息路由</title>
<script language="JavaScript">
function giveMeFive() {
window.cefQuery({
request: 'giveMeFive',
onSuccess: function(response) { alert(response); },
onFailure: function(error_code, error_message) {}
});
}
</script>
</head>
<body>
<form>
<input type="button" onclick="giveMeFive();" value="Give Me Five!">
</form>
</body>
</html>