windows下CEF3的關閉流程《轉》

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

=============================================================================github

我來講windows下CEF3.2171的關閉流程,裏面會引用一部分官方庫的文檔和我的的僞代碼,爲了輔助理解—— 如下是截取自cef_life_span_handler.h的頭文件文檔,因此一部分文檔他仍是寫在頭文件裏的,根據他的流程,能很快的去梳理相關邏輯web

 // 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 0;
        }
      }

      // Allow the close.
      break;

這是單browser關閉windows

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

一樣能夠替換成多browser關閉app

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線程上去處理的線程類型,最後會進入 CloseContentsless

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
    }
  }
  // 省略
}

到這裏咱們要有這樣的調用棧初步概念(以後會修改..)ide

=> 表示同步調用 ->異步調用
[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, 0, 0);
  }
}

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 0;
//
  }

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_ == 0) {
    // 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, 0);
  }
  else {
    CefQuitMessageLoop();
  }
}

若是CefRunMessageLoop建立的消息循環對應CefQuitMessageLoop去終結,其餘方式的處理就千奇百怪了,我根據cefclient的例子用一個隱藏窗口來結束系統的消息循環

PostMessage(message_wnd__, WM_COMMAND, ID_QUIT, 0);

處理很是簡單,調用了原生API PostQuitMessage退出循環

case ID_QUIT:
      PostQuitMessage(0);
      return 0;

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

相關文章
相關標籤/搜索