Skip to content

Commit 8af1a11

Browse files
xnightskyTian Xia
authored andcommitted
add CEF_Close.md
1 parent 6896135 commit 8af1a11

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

doc/CEF_Close.md

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
我来说windows下CEF3.2171的关闭流程,里面会引用一部分官方库的文档和个人的伪代码,为了辅助理解——
2+
以下是截取自cef_life_span_handler.h的头文件文档,所以一部分文档他还是写在头文件里的,根据他的流程,能很快的去梳理相关逻辑
3+
// The CefLifeSpanHandler::OnBeforeClose() method will be called immediately
4+
// before the browser object is destroyed. The application should only exit
5+
// after OnBeforeClose() has been called for all existing browsers.
6+
//
7+
// If the browser represents a modal window and a custom modal loop
8+
// implementation was provided in CefLifeSpanHandler::RunModal() this callback
9+
// should be used to restore the opener window to a usable state.
10+
//
11+
// By way of example consider what should happen during window close when the
12+
// browser is parented to an application-provided top-level OS window.
13+
// 1. User clicks the window close button which sends an OS close
14+
// notification (e.g. WM_CLOSE on Windows, performClose: on OS-X and
15+
// "delete_event" on Linux).
16+
// 2. Application's top-level window receives the close notification and:
17+
// A. Calls CefBrowserHost::CloseBrowser(false).
18+
// B. Cancels the window close.
19+
// 3. JavaScript 'onbeforeunload' handler executes and shows the close
20+
// confirmation dialog (which can be overridden via
21+
// CefJSDialogHandler::OnBeforeUnloadDialog()).
22+
// 4. User approves the close.
23+
// 5. JavaScript 'onunload' handler executes.
24+
// 6. Application's DoClose() handler is called. Application will:
25+
// A. Set a flag to indicate that the next close attempt will be allowed.
26+
// B. Return false.
27+
// 7. CEF sends an OS close notification.
28+
// 8. Application's top-level window receives the OS close notification and
29+
// allows the window to close based on the flag from #6B.
30+
// 9. Browser OS window is destroyed.
31+
// 10. Application's CefLifeSpanHandler::OnBeforeClose() handler is called and
32+
// the browser object is destroyed.
33+
// 11. Application exits by calling CefQuitMessageLoop() if no other browsers
34+
// exist.
35+
36+
pre1. 如果你要管理CefBrowser的生命周期,意味者你必须实现相关 CefLifeSpanHandler接口,在OnAfterCreated里管理和获取CefBrowser的每一个browser,在DoClose和OnBeforeClose里管理关闭
37+
pre2. 这里要注意整个流程对应开发者来说不是线性代码,都是基于消息、事件投递、接口切面的一种编程习惯,大家要对异步接口的开发有心理准备,你实现的一个接口都是等待框架或者别人调用。
38+
39+
1. 用户发起了一个系统级别的关闭操作,比如CLOSE,window下会被WM_CLOSE和相关封装触发
40+
2. 系统的顶层窗口消息处理会获取到CLOSE消息
41+
  A. 调用 CefBrowserHost::CloseBrowser(false), 通知HOST开始关闭流程,参数force_close=false,我们不需要交互式关闭。
42+
  B. 取消系统级关闭,比如以下代码我"return 0; "拒接了系统窗口的关闭流程,进入了个人和CEF的流程
43+
```c++
44+
case WM_CLOSE:
45+
if (client && !client->IsClosing()) {
46+
CefRefPtr<CefBrowser> browser = client->GetBrowser();
47+
if (browser.get()) {
48+
// Notify the browser window that we would like to close it. This
49+
// will result in a call to ClientHandler::DoClose() if the
50+
// JavaScript 'onbeforeunload' event handler allows it.
51+
browser->GetHost()->CloseBrowser(false);
52+
53+
// Cancel the close.
54+
return 0;
55+
}
56+
}
57+
58+
// Allow the close.
59+
break;
60+
```
61+
这是单browser关闭
62+
```C++
63+
browser->GetHost()->CloseBrowser(false);
64+
```
65+
同样可以替换成多browser关闭
66+
```C++
67+
void ClientHandler::CloseAllBrowsers(bool force_close) {
68+
if (!CefCurrentlyOn(TID_UI)) {
69+
// Execute on the UI thread.
70+
CefPostTask(TID_UI,
71+
base::Bind(&ClientHandler::CloseAllBrowsers, this, force_close));
72+
return;
73+
}
74+
75+
if (!popup_browsers_.empty()) {
76+
// Request that any popup browsers close.
77+
BrowserList::const_iterator it = popup_browsers_.begin();
78+
for (; it != popup_browsers_.end(); ++it)
79+
(*it)->GetHost()->CloseBrowser(force_close);
80+
}
81+
82+
if (browser_.get()) {
83+
// Request that the main browser close.
84+
browser_->GetHost()->CloseBrowser(force_close);
85+
}
86+
}
87+
```
88+
intermittent.
89+
最终会触发一个或多个CloseBrowser => CefBrowserHostImpl::CloseBrowser => CefBrowserHostImpl::CloseContents
90+
``` C++
91+
void CefBrowserHostImpl::CloseBrowser(bool force_close) {
92+
if (CEF_CURRENTLY_ON_UIT()) {
93+
// Exit early if a close attempt is already pending and this method is
94+
// called again from somewhere other than WindowDestroyed().
95+
if (destruction_state_ >= DESTRUCTION_STATE_PENDING &&
96+
(IsWindowless() || !window_destroyed_)) {
97+
if (force_close && destruction_state_ == DESTRUCTION_STATE_PENDING) {
98+
// Upgrade the destruction state.
99+
destruction_state_ = DESTRUCTION_STATE_ACCEPTED;
100+
}
101+
return;
102+
}
103+
104+
if (destruction_state_ < DESTRUCTION_STATE_ACCEPTED) {
105+
destruction_state_ = (force_close ? DESTRUCTION_STATE_ACCEPTED :
106+
DESTRUCTION_STATE_PENDING);
107+
}
108+
109+
content::WebContents* contents = web_contents();
110+
if (contents && contents->NeedToFireBeforeUnload()) {
111+
// Will result in a call to BeforeUnloadFired() and, if the close isn't
112+
// canceled, CloseContents().
113+
contents->DispatchBeforeUnload(false);
114+
} else {
115+
CloseContents(contents);
116+
}
117+
} else {
118+
CEF_POST_TASK(CEF_UIT,
119+
base::Bind(&CefBrowserHostImpl::CloseBrowser, this, force_close));
120+
}
121+
}
122+
```
123+
我们跳过其他细节( 我也没关注:) ),和相关CEF_POST_TASK把函数投递到UI线程上去处理的线程类型,最后会进入 CloseContents
124+
```C++
125+
void CefBrowserHostImpl::CloseContents(content::WebContents* source) {
126+
if (destruction_state_ == DESTRUCTION_STATE_COMPLETED)
127+
return;
128+
129+
bool close_browser = true;
130+
131+
// If this method is called in response to something other than
132+
// WindowDestroyed() ask the user if the browser should close.
133+
if (client_.get() && (IsWindowless() || !window_destroyed_)) {
134+
CefRefPtr<CefLifeSpanHandler> handler =
135+
client_->GetLifeSpanHandler();
136+
if (handler.get()) {
137+
close_browser = !handler->DoClose(this); // CefLifeSpanHandler::DoClose
138+
}
139+
}
140+
141+
if (close_browser) {
142+
if (destruction_state_ != DESTRUCTION_STATE_ACCEPTED)
143+
destruction_state_ = DESTRUCTION_STATE_ACCEPTED;
144+
145+
if (!IsWindowless() && !window_destroyed_) {
146+
// A window exists so try to close it using the platform method. Will
147+
// result in a call to WindowDestroyed() if/when the window is destroyed
148+
// via the platform window destruction mechanism.
149+
PlatformCloseWindow();
150+
} else {
151+
// Keep a reference to the browser while it's in the process of being
152+
// destroyed.
153+
CefRefPtr<CefBrowserHostImpl> browser(this);
154+
155+
// No window exists. Destroy the browser immediately.
156+
DestroyBrowser(); // CefLifeSpanHandler::OnBeforeClose
157+
if (!IsWindowless()) {
158+
// Release the reference added in PlatformCreateWindow().
159+
Release();
160+
}
161+
}
162+
} else if (destruction_state_ != DESTRUCTION_STATE_NONE) {
163+
destruction_state_ = DESTRUCTION_STATE_NONE;
164+
}
165+
}
166+
```
167+
在CloseContents里发现了我们的DoClose接口,其实这个函数里还有我们要处理的OnBeforeClose接口,DestroyBrowser包含了他
168+
```C++
169+
void CefBrowserHostImpl::DestroyBrowser() {
170+
CEF_REQUIRE_UIT();
171+
172+
destruction_state_ = DESTRUCTION_STATE_COMPLETED;
173+
174+
if (client_.get()) {
175+
CefRefPtr<CefLifeSpanHandler> handler = client_->GetLifeSpanHandler();
176+
if (handler.get()) {
177+
// Notify the handler that the window is about to be closed.
178+
handler->OnBeforeClose(this); //`CefLifeSpanHandler::OnBeforeClose
179+
}
180+
}
181+
// 省略
182+
}
183+
```
184+
到这里我们要有这样的调用栈初步概念(之后会修改..)
185+
```C++
186+
=> 表示同步调用 ->异步调用
187+
[os]window close =>
188+
[个人]browser(s) close =>
189+
[cef]CefBrowserHostImpl::CloseBrowser =>
190+
[cef]CefBrowserHostImpl::CloseContents =>
191+
[实现cef接口]CefLifeSpanHandler::DoClose =>
192+
[cef]CefLifeSpanHandler::DestroyBrowser =>
193+
[实现cef接口]CefLifeSpanHandler::OnBeforeClose =>
194+
[os]阻止window close
195+
```
196+
3-5. js和交互级的操作,略,其中有一点js onunload让你有机会在js层面去通知关闭,比如通知后台的native 线程去做自我清理动作,特别是cefQuery持久化以后的和JS交互的native线程
197+
6.DoClose接口被调用
198+
&ensp;&ensp;A. 允许设置一些状态表示browser正在关闭
199+
&ensp;&ensp;B. "return false;" 我们不需要紧急关闭(好像是预留接口,或者其他平台用)
200+
7.cef 发送一个系统级别的关闭动作发现之前的调用栈已经需要修改了,因为这里异步操作)
201+
CloseContents => PlatformCloseWindow => CefBrowserHostImpl::PlatformCloseWindow =>PostMessage WM_CLOSE
202+
```C++
203+
void CefBrowserHostImpl::PlatformCloseWindow() {
204+
if (window_info_.window != NULL) {
205+
HWND frameWnd = GetAncestor(window_info_.window, GA_ROOT);
206+
PostMessage(frameWnd, WM_CLOSE, 0, 0);
207+
}
208+
}
209+
```
210+
PS: host公共代码在这个文件browser_host_impl.cc里,平台特殊接口在browser_host_impl_xxx.cc里
211+
PlatformCloseWindow在browser_host_impl_win.cc里
212+
213+
允许关闭"if (client && !client->IsClosing()) " 之前我代码里IsClosing已经允许(
214+
```C++
215+
case WM_CLOSE:
216+
if (client && !client->IsClosing()) {
217+
// 这次不会进入IF分支
218+
```
219+
8.系统窗口已销毁
220+
```C++
221+
LRESULT CALLBACK CefBrowserHostImpl::WndProc(HWND hwnd, UINT message,
222+
WPARAM wParam, LPARAM lParam) {
223+
// 略
224+
case WM_DESTROY:
225+
if (browser) {
226+
// Clear the user data pointer.
227+
gfx::SetWindowUserData(hwnd, NULL);
228+
229+
// Force the browser to be destroyed and release the reference added in
230+
// PlatformCreateWindow().
231+
browser->WindowDestroyed();
232+
}
233+
return 0;
234+
// 略
235+
}
236+
```
237+
cef竟然在等系统群发销毁消息然后处理的代码,看到这里才发现CefBrowserHostImpl这个类是CefBrowser和系统整合的桥梁,不同平台对 CefBrowserHostImpl 的实现也不一样
238+
239+
9.框架发起销毁操作
240+
10. 调用OnBeforeClose
241+
WM_DESTROY => CefBrowserHostImpl::WindowDestroyed => ClientHandler::OnBeforeClose
242+
```C++
243+
void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
244+
CEF_REQUIRE_UI_THREAD();
245+
246+
message_router_->OnBeforeClose(browser);
247+
248+
if (GetBrowserId() == browser->GetIdentifier()) {
249+
{
250+
base::AutoLock lock_scope(lock_);
251+
// Free the browser pointer so that the browser can be destroyed
252+
browser_ = NULL;
253+
}
254+
255+
if (osr_handler_.get()) {
256+
osr_handler_->OnBeforeClose(browser);
257+
osr_handler_ = NULL;
258+
}
259+
} else if (browser->IsPopup()) {
260+
// Remove from the browser popup list.
261+
BrowserList::iterator bit = popup_browsers_.begin();
262+
for (; bit != popup_browsers_.end(); ++bit) {
263+
if ((*bit)->IsSame(browser)) {
264+
popup_browsers_.erase(bit);
265+
break;
266+
}
267+
}
268+
}
269+
270+
if (--browser_count_ == 0) {
271+
// All browser windows have closed.
272+
// Remove and delete message router handlers.
273+
MessageHandlerSet::const_iterator it = message_handler_set_.begin();
274+
for (; it != message_handler_set_.end(); ++it) {
275+
message_router_->RemoveHandler(*(it));
276+
delete *(it);
277+
}
278+
message_handler_set_.clear();
279+
message_router_ = NULL;
280+
281+
// Quit the application message loop.
282+
//AppQuitMessageLoop();
283+
XCefAppManage::Instance()->QuitMessageLoop();
284+
}
285+
}
286+
```
287+
我判断到 "--browser_count_ == 0" , 开始发起真实关闭。
288+
289+
11. 发起的真实关闭逻辑
290+
个人在OnBeforeClose里销毁所有browser和相关操作cefquery组件销毁以后,发起了总的关闭流程(关闭是异步的)
291+
```C++
292+
void XCefAppManage::QuitMessageLoop() {
293+
if (GetCefSettings().multi_threaded_message_loop)
294+
{
295+
// Running in multi-threaded message loop mode. Need to execute
296+
// PostQuitMessage on the main application thread.
297+
if (NULL == message_wnd__)
298+
{
299+
message_wnd__ = ::FindWindow(XWinUtil::GetMessageWindowClassName(XWinUtil::GetParentProcessID()), NULL);
300+
}
301+
DCHECK(message_wnd__);
302+
PostMessage(message_wnd__, WM_COMMAND, ID_QUIT, 0);
303+
}
304+
else {
305+
CefQuitMessageLoop();
306+
}
307+
}
308+
```
309+
如果CefRunMessageLoop创建的消息循环对应CefQuitMessageLoop去终结,其他方式的处理就千奇百怪了,我根据cefclient的例子用一个隐藏窗口来结束系统的消息循环
310+
```C++
311+
PostMessage(message_wnd__, WM_COMMAND, ID_QUIT, 0);
312+
```
313+
处理非常简单,调用了原生API PostQuitMessage退出循环
314+
```C++
315+
case ID_QUIT:
316+
PostQuitMessage(0);
317+
return 0;
318+
```
319+
PS: 如果需要把CEF混入其他框架的话,不同框架的处理都需要适配,切记。
320+
12. CefShutDown() 销毁Cef资源,如果有资源没关闭,比如browser的计数不为0,debug下会check assert。
321+
最后我再来修改一下之前的调用栈
322+
```C++
323+
=> 表示同步调用 ->异步调用
324+
[os]on window close WM_CLOSE =>
325+
[个人]browser(s) close =>
326+
[cef]CefBrowserHostImpl::CloseBrowser =>
327+
[cef]CefBrowserHostImpl::CloseContents =>
328+
[实现cef接口]CefLifeSpanHandler::DoClose =>
329+
[cef]CefBrowserHostImpl::PlatformCloseWindow =>
330+
[os] post window close -> WM_CLOSE
331+
[os]阻止window close
332+
333+
post被接收
334+
[os] 第二次on window close WM_CLOSE =>
335+
[个人] 允许 window close ->WM_DESTROY
336+
337+
[os] 系统群发WM_DESTROY
338+
[cef] CefBrowserHostImpl::WndProc =>
339+
[cef & os] 每个browser proc内的 WM_CLOSE 处理
340+
[cef]CefLifeSpanHandler::DestroyBrowser =>
341+
[实现cef接口]CefLifeSpanHandler::OnBeforeClose =>
342+
发起总关闭(个人使用XCefAppManage::QuitMessageLoop)->
343+
344+
退出消息循环
345+
CefShutDown
346+
```
347+
348+
这就一些对CEF CLOSE的个人理解的心得

0 commit comments

Comments
 (0)