#include "stdafx.h" #include "cef_manager.h" #include "include/wrapper/cef_closure_task.h" #include "include/base/cef_bind.h" #include "cef_control/app/client_app.h" #include "cef_control/handler/browser_handler.h" namespace nim_comp { BOOL CefMessageLoopDispatcher::IsIdleMessage(const MSG* pMsg) { switch (pMsg->message) { case WM_MOUSEMOVE: case WM_NCMOUSEMOVE: case WM_PAINT: return FALSE; } return TRUE; } bool CefMessageLoopDispatcher::Dispatch(const MSG &msg) { static BOOL bDoIdle = TRUE; TranslateMessage(&msg); DispatchMessage(&msg); if (IsIdleMessage(&msg)) { bDoIdle = TRUE; } while (bDoIdle && !::PeekMessage(const_cast(&msg), NULL, 0, 0, PM_NOREMOVE)) { CefDoMessageLoopWork(); bDoIdle = FALSE; } return true; } /////////////////////////////////////////////////////////////////////////////////// // 发现一个非常奇葩的bug,离屏渲染+多线程消息循环模式下,在浏览器对象上右击弹出菜单,是无法正常关闭的 // 翻cef源码后发现菜单是用TrackPopupMenu函数创建的,在MSDN资料上查看后发现调用TrackPopupMenu前 // 需要给其父窗口调用SetForegroundWindow。但是在cef源码中没有调用 // 最终翻cef源码后得到的解决方法是在cef的UI线程创建一个窗口,这个窗体的父窗口必须是在主程序UI线程创建的 // 这样操作之后就不会出现菜单无法关闭的bug了,虽然不知道为什么但是bug解决了 void FixContextMenuBug(HWND hwnd) { CreateWindow(L"Static", L"", WS_CHILD, 0, 0, 0, 0, hwnd, NULL, NULL, NULL); PostMessage(hwnd, WM_CLOSE, 0, 0); } /////////////////////////////////////////////////////////////////////////////////// CefManager::CefManager() { browser_count_ = 0; is_enable_offset_render_ = true; } void CefManager::AddCefDllToPath() { #if !defined(SUPPORT_CEF) return; #endif TCHAR path_envirom[4096] = { 0 }; GetEnvironmentVariable(L"path", path_envirom, 4096); std::wstring cef_path = nbase::win32::GetCurrentModuleDirectory(); #ifdef _DEBUG //cef_path += L"cef_debug"; // 现在即使在debug模式下也使用cef release版本的dll,为了屏蔽掉cef退出时的中断,如果不需要调试cef的功能不需要使用debug版本的dll cef_path += L"cef"; #else cef_path += L"cef"; #endif if (!nbase::FilePathIsExist(cef_path, true)) { MessageBox(NULL, L"请解压Cef.rar压缩包", L"提示", MB_OK); exit(0); } std::wstring new_envirom(cef_path); new_envirom.append(L";").append(path_envirom); SetEnvironmentVariable(L"path", new_envirom.c_str()); // 解决播放flash弹出黑框的问题 // https://blog.csdn.net/zhuhongshu/article/details/77482985 std::wstring cmd_path = cef_path + L"\\dummy_cmd.exe"; SetEnvironmentVariable(L"ComSpec", cmd_path.c_str()); } // Cef的初始化接口,同时备注了使用各个版本的Cef时遇到的各种坑 // Cef1916版本较稳定,各个功能使用正常,但是某些在debug模式网页打开时会出中断警告(但并不是错误),可能是因为对新html标准支持不够,但是在release模式下正常使用 // Cef2357版本无法使用,当程序处理重定向信息并且重新加载页面后,渲染进程会崩掉 // Cef2526、2623版本对各种新页面都支持,唯一的坑就是debug模式在多线程消息循环开启下,程序退出时会中断,但是release模式正常。 // (PS:如果开发者不使用负责Cef功能的开发,可以切换到release模式的cef dll文件,这样即使在deubg下也不会报错,修改AddCefDllToPath代码可以切换到release目录) bool CefManager::Initialize(const std::wstring& app_data_dir, CefSettings &settings, bool is_enable_offset_render /*= true*/) { #if !defined(SUPPORT_CEF) return true; #endif is_enable_offset_render_ = is_enable_offset_render; CefMainArgs main_args(GetModuleHandle(NULL)); CefRefPtr app(new ClientApp); // 如果是在子进程中调用,会堵塞直到子进程退出,并且exit_code返回大于等于0 // 如果在Browser进程中调用,则立即返回-1 int exit_code = CefExecuteProcess(main_args, app.get(), NULL); if (exit_code >= 0) return false; GetCefSetting(app_data_dir, settings); bool bRet = CefInitialize(main_args, settings, app.get(), NULL); if (is_enable_offset_render_) { HWND hwnd = CreateWindow(L"Static", L"", WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL, NULL); CefPostTask(TID_UI, base::Bind(&FixContextMenuBug, hwnd)); } return bRet; } void CefManager::UnInitialize() { #if !defined(SUPPORT_CEF) return; #endif CefShutdown(); } bool CefManager::IsEnableOffsetRender() const { return is_enable_offset_render_; } nbase::Dispatcher* CefManager::GetMessageDispatcher() { return &message_dispatcher_; } void CefManager::AddBrowserCount() { browser_count_++; } void CefManager::SubBrowserCount() { browser_count_--; ASSERT(browser_count_ >= 0); } int CefManager::GetBrowserCount() { return browser_count_; } void CefManager::PostQuitMessage(int nExitCode) { #if !defined(SUPPORT_CEF) ::PostQuitMessage(nExitCode); return; #endif // 当我们需要结束进程时,千万不要直接调用::PostQuitMessage,这是可能还有浏览器对象没有销毁 // 应该等所有浏览器对象都销毁后再调用::PostQuitMessage if (browser_count_ == 0) { nbase::ThreadManager::PostTask(kThreadUI, [nExitCode]() { ::PostQuitMessage(nExitCode); }); } else { auto cb = [nExitCode]() { CefManager::GetInstance()->PostQuitMessage(nExitCode); }; nbase::ThreadManager::PostDelayedTask(kThreadUI, cb, nbase::TimeDelta::FromMilliseconds(500)); } } void CefManager::GetCefSetting(const std::wstring& app_data_dir, CefSettings &settings) { if (false == nbase::FilePathIsExist(app_data_dir, true)) nbase::CreateDirectory(app_data_dir); settings.no_sandbox = true; // 设置localstorage,不要在路径末尾加"\\",否则运行时会报错 CefString(&settings.cache_path) = app_data_dir + L"CefLocalStorage"; // 设置debug log文件位置 CefString(&settings.log_file) = app_data_dir + L"cef.log"; // 调试模型下使用单进程,但是千万不要在release发布版本中使用,官方已经不推荐使用单进程模式 // cef1916版本debug模式:在单进程模式下程序退出时会触发中断 #ifdef _DEBUG settings.single_process = true; #else settings.single_process = false; #endif // cef2623、2526版本debug模式:在使用multi_threaded_message_loop时退出程序会触发中断 // 加入disable-extensions参数可以修复这个问题,但是会导致一些页面打开时报错 // 开启Cef多线程消息循环,兼容nbase库消息循环 settings.multi_threaded_message_loop = true; // 开启离屏渲染 settings.windowless_rendering_enabled = is_enable_offset_render_; } }