原文地址:https://github.com/fanfeilong/cefutil/blob/master/doc/CEF_Close.md

=============================================================================

我来说windows下CEF3.2171的关闭流程,里面会引用一部分官方库的文档和个人的伪代码,为了辅助理解—— 以下是截取自cef_life_span_handler.h的头文件文档,所以一部分文档他还是写在头文件里的,根据他的流程,能很快的去梳理相关逻辑

 // The CefLifeSpanHandler::OnBeforeClose() method will be called immediately
// before the browser object is destroyed. The application should only exit
// after OnBeforeClose() has been called for all existing browsers.
//
// If the browser represents a modal window and a custom modal loop
// implementation was provided in CefLifeSpanHandler::RunModal() this callback
// should be used to restore the opener window to a usable state.
//
// By way of example consider what should happen during window close when the
// browser is parented to an application-provided top-level OS window.
// 1. User clicks the window close button which sends an OS close
// notification (e.g. WM_CLOSE on Windows, performClose: on OS-X and
// "delete_event" on Linux).
// 2. Application's top-level window receives the close notification and:
// A. Calls CefBrowserHost::CloseBrowser(false).
// B. Cancels the window close.
// 3. JavaScript 'onbeforeunload' handler executes and shows the close
// confirmation dialog (which can be overridden via
// CefJSDialogHandler::OnBeforeUnloadDialog()).
// 4. User approves the close.
// 5. JavaScript 'onunload' handler executes.
// 6. Application's DoClose() handler is called. Application will:
// A. Set a flag to indicate that the next close attempt will be allowed.
// B. Return false.
// 7. CEF sends an OS close notification.
// 8. Application's top-level window receives the OS close notification and
// allows the window to close based on the flag from #6B.
// 9. Browser OS window is destroyed.
// 10. Application's CefLifeSpanHandler::OnBeforeClose() handler is called and
// the browser object is destroyed.
// 11. Application exits by calling CefQuitMessageLoop() if no other browsers
// exist.

pre1. 如果你要管理CefBrowser的生命周期,意味者你必须实现相关 CefLifeSpanHandler接口,在OnAfterCreated里管理和获取CefBrowser的每一个browser,在DoClose和OnBeforeClose里管理关闭 pre2. 这里要注意整个流程对应开发者来说不是线性代码,都是基于消息、事件投递、接口切面的一种编程习惯,大家要对异步接口的开发有心理准备,你实现的一个接口都是等待框架或者别人调用。

  1. 用户发起了一个系统级别的关闭操作,比如CLOSE,window下会被WM_CLOSE和相关封装触发
  2. 系统的顶层窗口消息处理会获取到CLOSE消息   A. 调用 CefBrowserHost::CloseBrowser(false), 通知HOST开始关闭流程,参数force_close=false,我们不需要交互式关闭。   B. 取消系统级关闭,比如以下代码我"return 0; "拒接了系统窗口的关闭流程,进入了个人和CEF的流程
case WM_CLOSE:
if (client && !client->IsClosing()) {
CefRefPtr<CefBrowser> browser = client->GetBrowser();
if (browser.get()) {
// Notify the browser window that we would like to close it. This
// will result in a call to ClientHandler::DoClose() if the
// JavaScript 'onbeforeunload' event handler allows it.
browser->GetHost()->CloseBrowser(false); // Cancel the close.
return ;
}
} // Allow the close.
break;

这是单browser关闭

browser->GetHost()->CloseBrowser(false);

同样可以替换成多browser关闭

void ClientHandler::CloseAllBrowsers(bool force_close) {
if (!CefCurrentlyOn(TID_UI)) {
// Execute on the UI thread.
CefPostTask(TID_UI,
base::Bind(&ClientHandler::CloseAllBrowsers, this, force_close));
return;
} if (!popup_browsers_.empty()) {
// Request that any popup browsers close.
BrowserList::const_iterator it = popup_browsers_.begin();
for (; it != popup_browsers_.end(); ++it)
(*it)->GetHost()->CloseBrowser(force_close);
} if (browser_.get()) {
// Request that the main browser close.
browser_->GetHost()->CloseBrowser(force_close);
}
}

intermittent. 最终会触发一个或多个CloseBrowser => CefBrowserHostImpl::CloseBrowser => CefBrowserHostImpl::CloseContents

void CefBrowserHostImpl::CloseBrowser(bool force_close) {
if (CEF_CURRENTLY_ON_UIT()) {
// Exit early if a close attempt is already pending and this method is
// called again from somewhere other than WindowDestroyed().
if (destruction_state_ >= DESTRUCTION_STATE_PENDING &&
(IsWindowless() || !window_destroyed_)) {
if (force_close && destruction_state_ == DESTRUCTION_STATE_PENDING) {
// Upgrade the destruction state.
destruction_state_ = DESTRUCTION_STATE_ACCEPTED;
}
return;
} if (destruction_state_ < DESTRUCTION_STATE_ACCEPTED) {
destruction_state_ = (force_close ? DESTRUCTION_STATE_ACCEPTED :
DESTRUCTION_STATE_PENDING);
} content::WebContents* contents = web_contents();
if (contents && contents->NeedToFireBeforeUnload()) {
// Will result in a call to BeforeUnloadFired() and, if the close isn't
// canceled, CloseContents().
contents->DispatchBeforeUnload(false);
} else {
CloseContents(contents);
}
} else {
CEF_POST_TASK(CEF_UIT,
base::Bind(&CefBrowserHostImpl::CloseBrowser, this, force_close));
}
}

我们跳过其他细节( 我也没关注:) ),和相关CEF_POST_TASK把函数投递到UI线程上去处理的线程类型,最后会进入 CloseContents

void CefBrowserHostImpl::CloseContents(content::WebContents* source) {
if (destruction_state_ == DESTRUCTION_STATE_COMPLETED)
return; bool close_browser = true; // If this method is called in response to something other than
// WindowDestroyed() ask the user if the browser should close.
if (client_.get() && (IsWindowless() || !window_destroyed_)) {
CefRefPtr<CefLifeSpanHandler> handler =
client_->GetLifeSpanHandler();
if (handler.get()) {
close_browser = !handler->DoClose(this); // CefLifeSpanHandler::DoClose
}
} if (close_browser) {
if (destruction_state_ != DESTRUCTION_STATE_ACCEPTED)
destruction_state_ = DESTRUCTION_STATE_ACCEPTED; if (!IsWindowless() && !window_destroyed_) {
// A window exists so try to close it using the platform method. Will
// result in a call to WindowDestroyed() if/when the window is destroyed
// via the platform window destruction mechanism.
PlatformCloseWindow();
} else {
// Keep a reference to the browser while it's in the process of being
// destroyed.
CefRefPtr<CefBrowserHostImpl> browser(this); // No window exists. Destroy the browser immediately.
DestroyBrowser(); // CefLifeSpanHandler::OnBeforeClose
if (!IsWindowless()) {
// Release the reference added in PlatformCreateWindow().
Release();
}
}
} else if (destruction_state_ != DESTRUCTION_STATE_NONE) {
destruction_state_ = DESTRUCTION_STATE_NONE;
}
}

在CloseContents里发现了我们的DoClose接口,其实这个函数里还有我们要处理的OnBeforeClose接口,DestroyBrowser包含了他

void CefBrowserHostImpl::DestroyBrowser() {
CEF_REQUIRE_UIT(); destruction_state_ = DESTRUCTION_STATE_COMPLETED; if (client_.get()) {
CefRefPtr<CefLifeSpanHandler> handler = client_->GetLifeSpanHandler();
if (handler.get()) {
// Notify the handler that the window is about to be closed.
handler->OnBeforeClose(this); //`CefLifeSpanHandler::OnBeforeClose
}
}
// 省略
}

到这里我们要有这样的调用栈初步概念(之后会修改..)

=> 表示同步调用 ->异步调用
[os]window close =>
[个人]browser(s) close =>
[cef]CefBrowserHostImpl::CloseBrowser =>
[cef]CefBrowserHostImpl::CloseContents =>
[实现cef接口]CefLifeSpanHandler::DoClose =>
[cef]CefLifeSpanHandler::DestroyBrowser =>
[实现cef接口]CefLifeSpanHandler::OnBeforeClose =>
[os]阻止window close

3-5. js和交互级的操作,略,其中有一点js onunload让你有机会在js层面去通知关闭,比如通知后台的native 线程去做自我清理动作,特别是cefQuery持久化以后的和JS交互的native线程 6.DoClose接口被调用   A. 允许设置一些状态表示browser正在关闭   B. "return false;" 我们不需要紧急关闭(好像是预留接口,或者其他平台用) 7.cef 发送一个系统级别的关闭动作发现之前的调用栈已经需要修改了,因为这里异步操作) CloseContents => PlatformCloseWindow => CefBrowserHostImpl::PlatformCloseWindow =>PostMessage WM_CLOSE

void CefBrowserHostImpl::PlatformCloseWindow() {
if (window_info_.window != NULL) {
HWND frameWnd = GetAncestor(window_info_.window, GA_ROOT);
PostMessage(frameWnd, WM_CLOSE, , );
}
}

PS: host公共代码在这个文件browser_host_impl.cc里,平台特殊接口在browser_host_impl_xxx.cc里 PlatformCloseWindow在browser_host_impl_win.cc里

允许关闭"if (client && !client->IsClosing()) " 之前我代码里IsClosing已经允许(

case WM_CLOSE:
if (client && !client->IsClosing()) {
// 这次不会进入IF分支

8.系统窗口已销毁

LRESULT CALLBACK CefBrowserHostImpl::WndProc(HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam) {
// 略
case WM_DESTROY:
if (browser) {
// Clear the user data pointer.
gfx::SetWindowUserData(hwnd, NULL); // Force the browser to be destroyed and release the reference added in
// PlatformCreateWindow().
browser->WindowDestroyed();
}
return ;
// 略
}

cef竟然在等系统群发销毁消息然后处理的代码,看到这里才发现CefBrowserHostImpl这个类是CefBrowser和系统整合的桥梁,不同平台对 CefBrowserHostImpl 的实现也不一样

9.框架发起销毁操作 10. 调用OnBeforeClose WM_DESTROY => CefBrowserHostImpl::WindowDestroyed => ClientHandler::OnBeforeClose

void ClientHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD(); message_router_->OnBeforeClose(browser); if (GetBrowserId() == browser->GetIdentifier()) {
{
base::AutoLock lock_scope(lock_);
// Free the browser pointer so that the browser can be destroyed
browser_ = NULL;
} if (osr_handler_.get()) {
osr_handler_->OnBeforeClose(browser);
osr_handler_ = NULL;
}
} else if (browser->IsPopup()) {
// Remove from the browser popup list.
BrowserList::iterator bit = popup_browsers_.begin();
for (; bit != popup_browsers_.end(); ++bit) {
if ((*bit)->IsSame(browser)) {
popup_browsers_.erase(bit);
break;
}
}
} if (--browser_count_ == ) {
// All browser windows have closed.
// Remove and delete message router handlers.
MessageHandlerSet::const_iterator it = message_handler_set_.begin();
for (; it != message_handler_set_.end(); ++it) {
message_router_->RemoveHandler(*(it));
delete *(it);
}
message_handler_set_.clear();
message_router_ = NULL; // Quit the application message loop.
//AppQuitMessageLoop();
XCefAppManage::Instance()->QuitMessageLoop();
}
}

我判断到 "--browser_count_ == 0" , 开始发起真实关闭。

  1. 发起的真实关闭逻辑 个人在OnBeforeClose里销毁所有browser和相关操作cefquery组件销毁以后,发起了总的关闭流程(关闭是异步的)
void            XCefAppManage::QuitMessageLoop() {
if (GetCefSettings().multi_threaded_message_loop)
{
// Running in multi-threaded message loop mode. Need to execute
// PostQuitMessage on the main application thread.
if (NULL == message_wnd__)
{
message_wnd__ = ::FindWindow(XWinUtil::GetMessageWindowClassName(XWinUtil::GetParentProcessID()), NULL);
}
DCHECK(message_wnd__);
PostMessage(message_wnd__, WM_COMMAND, ID_QUIT, );
}
else {
CefQuitMessageLoop();
}
}

如果CefRunMessageLoop创建的消息循环对应CefQuitMessageLoop去终结,其他方式的处理就千奇百怪了,我根据cefclient的例子用一个隐藏窗口来结束系统的消息循环

PostMessage(message_wnd__, WM_COMMAND, ID_QUIT, );

处理非常简单,调用了原生API PostQuitMessage退出循环

case ID_QUIT:
PostQuitMessage();
return ;

PS: 如果需要把CEF混入其他框架的话,不同框架的处理都需要适配,切记。 12. CefShutDown() 销毁Cef资源,如果有资源没关闭,比如browser的计数不为0,debug下会check assert。 最后我再来修改一下之前的调用栈

=> 表示同步调用 ->异步调用
[os]on window close WM_CLOSE =>
[个人]browser(s) close =>
[cef]CefBrowserHostImpl::CloseBrowser =>
[cef]CefBrowserHostImpl::CloseContents =>
[实现cef接口]CefLifeSpanHandler::DoClose =>
[cef]CefBrowserHostImpl::PlatformCloseWindow =>
[os] post window close -> WM_CLOSE
[os]阻止window close post被接收
[os] 第二次on window close WM_CLOSE =>
[个人] 允许 window close ->WM_DESTROY [os] 系统群发WM_DESTROY
[cef] CefBrowserHostImpl::WndProc =>
[cef & os] 每个browser proc内的 WM_CLOSE 处理
[cef]CefLifeSpanHandler::DestroyBrowser =>
[实现cef接口]CefLifeSpanHandler::OnBeforeClose =>
发起总关闭(个人使用XCefAppManage::QuitMessageLoop)-> 退出消息循环
CefShutDown

这就一些对CEF CLOSE的个人理解的心得

原文连接:https://github.com/fanfeilong/cefutil/blob/master/doc/CEF_Close.md

windows下CEF3的关闭流程《转》的更多相关文章

  1. Windows下快速启动/关闭orcl服务

    大家都知道windows下绝大部分都是图形操作化,很少用命令来执行,例如启动.关闭orcl数据库服务时,一般情况都是在任务管理器(taskmgr.ctrl+shift+esc)或服务(services ...

  2. 7步学会在Windows下上架iOS APP流程

    之前用跨平台开发工具做了一个应用,平台可以同时生成安卓版和苹果版,想着也把这应用上架到App Store试试,于是找同学借了个苹果开发者账号,但没那么简单,还要用到Mac电脑的钥匙串申请发布证书和上传 ...

  3. windows 下命令行关闭进程。

    使用 进程名关闭 taskkill /im mspaint.exe /f 使用 进程id 关闭 taskkill /im 12555 /f

  4. Windows下Django项目搭建流程

    1.创建虚拟环境 virtualenv test 2.进入虚拟环境 source ./项目名/Scripts/activate 3.创建项目 django-admin startproject 项目名 ...

  5. Windows下编译YouCompleteMe流程

    废话 生命在于折腾. 之前不用这个插件的原因: 因为要使这个插件起作用,前前后后需要下载几百MB(win下更是超过了1GB)的东西,包括了Clang编译器,ycmd的c艹源码还有ycm本身的vim s ...

  6. windows下启动/关闭Sybase数据库服务器

    启动.关闭Sybase数据库服务器 一.启动Sybase服务器 在windows下介绍两种方法启动Sybase数据库服务器. 1.通过服务器管理器 依次打开控制面板>管理工具>服务 管理窗 ...

  7. Linux和windows 平台下启动和关闭mysql服务

    Linux平台下启动和关闭mysql服务 一.linux下查看mysql服务的两种方式: 方式一: [root@localhost bin]ps -ef|grep mysql 方式二: [root@l ...

  8. windows下如何查看端口,关闭端口,开启端口

    如何查看端口 在Windows 2000/XP/Server 2003中要查看端口,可以使用NETSTAT命令: “开始">"运行”>“cmd”,打开命令提示符窗口.在 ...

  9. windows下快速启动或关闭系统服务方法

    在windows下有些后台服务会开机自动启动. 用命令行方式启动关闭应用服务 使用sc.exe命令功能列表 修改服务启动类型的命令行格式为(特别注意start=后面有一个空格) sc config 服 ...

随机推荐

  1. AOP初识

    AOP是Aspect Oriented Programing的简称,面向切面编程.AOP适合于那些具有横切逻辑的应用:如性能监测,访问控制,事务管理.缓存.对象池管理以及日志记录.AOP将这些分散在各 ...

  2. spring的Autowired、Resource、Inject的使用

    基本知识:spring最底层使用的是Map,id是bean的id,value是bean的class或者是实例. 1:bean的加载顺序. @Bean("testDao") publ ...

  3. 虚拟机NAS存储的安装

    1.下载一款免费的NAS系统fressNAS 下载网址;https://www.freenas.org/download/ 2.选择配置好虚拟机(内存设置8G最好,选择桥接) 不要做任何操作等待进入安 ...

  4. Windows下学习C语言有哪些集成开发软件?

    前言 初学者学习C语言遇到的最大困难想必就是搭建环境了,相当多的初学者就是被搭建环境导致放弃了学习编程,就我自己的经验而言,初学编程不应该受限于环境,使用成熟好用的环境就可以了,之后熟悉一些可以在慢慢 ...

  5. XSL-FO知识点【一】

    XSL-FO 用于格式化供输出的 XML 数据. 什么是 XSL-FO? XSL-FO 是用于格式化 XML 数据的语言 XSL-FO 指可扩展样式表语言格式化对象(Extensible Styles ...

  6. mysql_safe和mysql_multi

    1 mysql_safe 原理 mysqld_safe其实为一个shell脚本(封装mysqld),启动时需要调用server和database(即/bin和/data目录),因此需要满足下述条件之一 ...

  7. git rebase 版本。。变基

    git rebase,顾名思义,就是重新定义(re)起点(base)的作用,即重新定义分支的版本库状态.要搞清楚这个东西,要先看看版本库状态切换的两种情况: 我们知道,在某个分支上,我们可以通过git ...

  8. python 私有和保护成员变量如何实现?—— "单下划线 " 开始的成员变量叫做保护变量,意思是只有类实例和子类实例能访问到这些变量;" 双下划线 " 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据

    默认情况下,Python中的成员函数和成员变量都是公开的(public),在python中没有类似public,private等关键词来修饰成员函数和成员变量.在python中定义私有变量只需要在变量 ...

  9. 二次封装Response类 | 视图类传递参数给序列化类context

    二次封装Response类 源码: class Response(SimpleTemplateResponse): """ An HttpResponse that al ...

  10. 017_Python3 数据结构

    本章节我们主要结合前面所学的知识点来介绍Python数据结构.   *****************************   1.列表 Python中列表是可变的,这是它区别于字符串和元组的最重 ...