Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mac: Browser created before CefRunMessageLoop does not exit correctly #3810

Open
prashantkn94 opened this issue Oct 19, 2024 · 8 comments
Open
Labels
bug Bug report macos MacOS platform

Comments

@prashantkn94
Copy link

prashantkn94 commented Oct 19, 2024

Describe the bug

There are two approaches I can take to create a browser using the CefBrowserHost::CreateBrowser API:

  1. In the main method, after initializing CEF, I can create the view where the browser will load and then call CreateBrowser immediately before invoking CefRunMessageLoop.

  2. Alternatively, after initializing CEF, I can schedule the creation of the view and the subsequent CreateBrowser call using performSelectorOnMainThread.
    The issue arises when calling CloseBrowser. In approach 2, the OnBeforeClose callback is triggered as expected. However, in approach 1, the callback does not execute, and the renderer process remains active.

In both the cases, the view in which browser url is loaded is a child of parent main window. The CloseBrowser() is explicitly closed by my application.

I understand that this question is better suited for the CEF forum, but despite multiple attempts to register with different email addresses, I haven't received the activation link. I also reached out to [email protected] regarding the issue but haven't received any response.

Versions (please complete the following information):

  • OS: MAC OS 14.5
  • CEF Version: 116.0.0

Additional context

@prashantkn94 prashantkn94 added the bug Bug report label Oct 19, 2024
@magreenblatt
Copy link
Collaborator

Recommended usage is to call CreateBrowser from OnContextInitialized. However, this still sounds like a bug.

@magreenblatt magreenblatt changed the title [MAC OS] OnBeforeClose does not get called when browser is created without performSelectorOnMainThread mac: Browser created before CefRunMessageLoop does not exit correctly Oct 21, 2024
@magreenblatt magreenblatt added the macos MacOS platform label Oct 21, 2024
@magreenblatt
Copy link
Collaborator

CEF Version: [e.g. 111.2.2]

Please try with a supported CEF version (M130 or newer)

@magreenblatt magreenblatt added the needs user feedback Additional feedback required label Oct 21, 2024
@magreenblatt
Copy link
Collaborator

May be related to #3469

@prashantkn94
Copy link
Author

prashantkn94 commented Oct 21, 2024

Hi @magreenblatt Thanks for looking into it. We are using the cef version - 116.0.0, I have updated it in the description. Has it been fixed in 130 or newer versions of cef ?

@magreenblatt
Copy link
Collaborator

Has it been fixed in 130 or newer versions of cef ?

It may be, please test.

@prashantkn94
Copy link
Author

sure, I'll update you once we test it

@prashantkn94
Copy link
Author

prashantkn94 commented Oct 23, 2024

Hi @magreenblatt Seems like the bug is still present on the latest cef version. I was able to reproduce it by modifying the cefsimple app slightly. I am creating a couple of browsers inside my window, and closing the first browser after a certain timer. You'll observe that it has been removed from the view, but yet the OnBeforeClose will not be called and the corresponding renderer process remains active. Can you please help ?

Below is the cefsimple_mac.mm

#import <Cocoa/Cocoa.h>

#include "include/cef_application_mac.h"
#include "include/cef_command_line.h"
#include "include/wrapper/cef_helpers.h"
#include "include/wrapper/cef_library_loader.h"
#include "tests/cefsimple/simple_app.h"
#include "tests/cefsimple/simple_handler.h"

NSView* view1;

// Receives notifications from the application.
@interface SimpleAppDelegate : NSObject <NSApplicationDelegate>

- (void)createApplication:(id)object;
- (void)tryToTerminateApplication:(NSApplication*)app;
@end

// Provide the CefAppProtocol implementation required by CEF.
@interface SimpleApplication : NSApplication <CefAppProtocol> {
 @private
  BOOL handlingSendEvent_;
}
@end

@implementation SimpleApplication
- (BOOL)isHandlingSendEvent {
  return handlingSendEvent_;
}

- (void)setHandlingSendEvent:(BOOL)handlingSendEvent {
  handlingSendEvent_ = handlingSendEvent;
}

- (void)sendEvent:(NSEvent*)event {
  CefScopedSendingEvent sendingEventScoper;
  [super sendEvent:event];
}

- (void)terminate:(id)sender {
  SimpleAppDelegate* delegate =
      static_cast<SimpleAppDelegate*>([NSApp delegate]);
  [delegate tryToTerminateApplication:self];
  // Return, don't exit. The application is responsible for exiting on its own.
}
@end

@implementation SimpleAppDelegate

// Create the application on the UI thread.
- (void)createApplication:(id)object {
  [[NSBundle mainBundle] loadNibNamed:@"MainMenu"
                                owner:NSApp
                      topLevelObjects:nil];

  // Set the delegate for application events.
  [[NSApplication sharedApplication] setDelegate:self];
}

- (void)tryToTerminateApplication:(NSApplication*)app {
  SimpleHandler* handler = SimpleHandler::GetInstance();
  if (handler) {
    [view1 removeFromSuperview];
    handler->CloseFirstBrowsers(true);
  }
}

- (NSApplicationTerminateReply)applicationShouldTerminate:
    (NSApplication*)sender {
  return NSTerminateNow;
}

// Called when the user clicks the app dock icon while the application is
// already running.
- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApplication
                    hasVisibleWindows:(BOOL)flag {
  SimpleHandler* handler = SimpleHandler::GetInstance();
  if (handler && !handler->IsClosing()) {
    handler->ShowMainWindow();
  }
  return NO;
}
@end


void createWindow() {
    CEF_REQUIRE_UI_THREAD();
    // Specify CEF browser settings here.
    CefBrowserSettings browser_settings;

    // Information used when creating the native window.
    CefWindowInfo window_info;

    NSRect frame = NSMakeRect(350, 350, 700, 700);
    NSWindow* window  = [[NSWindow alloc] initWithContentRect:frame
                        styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable)
                        backing:NSBackingStoreBuffered
                        defer:NO];

    [window setBackgroundColor:[NSColor grayColor]];
    [window makeKeyAndOrderFront:NSApp];
    NSRect mainFrame = NSMakeRect(0, 0, 700, 700);
    NSView *mainView = [[NSView alloc] initWithFrame:mainFrame];
    [[window contentView] addSubview:mainView];
    
    NSRect frame1 = NSMakeRect(0, 350, 700, 350);
    view1 = [[NSView alloc] initWithFrame:frame1];

    [mainView addSubview:view1];
    CefRect bounds = CefRect(0, 0, NSWidth([view1 bounds]), NSHeight([view1 bounds]));
    CefWindowHandle cefView1 = (__bridge CefWindowHandle)view1;
    window_info.SetAsChild(cefView1, bounds);
    
    CefBrowserHost::CreateBrowser(window_info, SimpleHandler::GetInstance(), "http://www.wikipedia.org", browser_settings,nullptr, nullptr);
    
    NSRect frame2 = NSMakeRect(0, 0, 700, 350);
    NSView* view2 = [[NSView alloc] initWithFrame:frame2];

    [mainView addSubview:view2];
    bounds = CefRect(0, 0, NSWidth([view2 bounds]), NSHeight([view2 bounds]));
    CefWindowHandle cefView2 = (__bridge CefWindowHandle)view2;
    CefWindowInfo window_info1;
    window_info1.SetAsChild(cefView2, bounds);

    CefBrowserHost::CreateBrowser(window_info1, SimpleHandler::GetInstance(), "https://www.google.com", browser_settings,nullptr, nullptr);
}

// Entry point function for the browser process.
int main(int argc, char* argv[]) {
  // Load the CEF framework library at runtime instead of linking directly
  // as required by the macOS sandbox implementation.
  CefScopedLibraryLoader library_loader;
  if (!library_loader.LoadInMain()) {
    return 1;
  }

  // Provide CEF with command-line arguments.
  CefMainArgs main_args(argc, argv);

  @autoreleasepool {
    // Initialize the SimpleApplication instance.
    [SimpleApplication sharedApplication];

    // If there was an invocation to NSApp prior to this method, then the NSApp
    // will not be a SimpleApplication, but will instead be an NSApplication.
    // This is undesirable and we must enforce that this doesn't happen.
    CHECK([NSApp isKindOfClass:[SimpleApplication class]]);

    // Parse command-line arguments for use in this method.
    CefRefPtr<CefCommandLine> command_line =
        CefCommandLine::CreateCommandLine();
    command_line->InitFromArgv(argc, argv);

    // Specify CEF global settings here.
    CefSettings settings;

    // When generating projects with CMake the CEF_USE_SANDBOX value will be
    // defined automatically. Pass -DUSE_SANDBOX=OFF to the CMake command-line
    // to disable use of the sandbox.
#if !defined(CEF_USE_SANDBOX)
    settings.no_sandbox = true;
#endif
    if (!CefInitialize(main_args, settings, SimpleApp::GetInstance().get(), nullptr)) {
      return CefGetExitCode();
    }
      
    SimpleHandler *simple = new SimpleHandler(true);
    // below line avoids compilation error
    simple->IsClosing();

    // Create the application delegate.
    SimpleAppDelegate* delegate = [[SimpleAppDelegate alloc] init];
    // Set as the delegate for application events.
    NSApp.delegate = delegate;

    [NSTimer scheduledTimerWithTimeInterval:10.0
                                          repeats:NO
                                          block:^(NSTimer * _Nonnull timer) {
              [delegate performSelectorOnMainThread:@selector(tryToTerminateApplication:)
                                       withObject:nil
                                    waitUntilDone:NO];
    }];
    createWindow();

    /*
     [delegate performSelectorOnMainThread:@selector(createApplication:)
                               withObject:nil
                            waitUntilDone:NO];
     */

    // Run the CEF message loop. This will block until CefQuitMessageLoop() is
    // called.
    CefRunMessageLoop();

    // Shut down CEF.
    CefShutdown();

    // Release the delegate.
#if !__has_feature(objc_arc)
    [delegate release];
#endif  // !__has_feature(objc_arc)
    delegate = nil;
  }  // @autoreleasepool

  return 0;
}

And here is the simple_app.cpp

#include "tests/cefsimple/simple_app.h"

#include <string>

#include "include/cef_browser.h"
#include "include/cef_command_line.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_helpers.h"
#include "tests/cefsimple/simple_handler.h"

namespace {

// When using the Views framework this object provides the delegate
// implementation for the CefWindow that hosts the Views-based browser.
class SimpleWindowDelegate : public CefWindowDelegate {
 public:
  SimpleWindowDelegate(CefRefPtr<CefBrowserView> browser_view,
                       cef_runtime_style_t runtime_style,
                       cef_show_state_t initial_show_state)
      : browser_view_(browser_view),
        runtime_style_(runtime_style),
        initial_show_state_(initial_show_state) {}

  void OnWindowCreated(CefRefPtr<CefWindow> window) override {
    // Add the browser view and show the window.
    window->AddChildView(browser_view_);

    if (initial_show_state_ != CEF_SHOW_STATE_HIDDEN) {
      window->Show();
    }

    if (initial_show_state_ != CEF_SHOW_STATE_MINIMIZED &&
        initial_show_state_ != CEF_SHOW_STATE_HIDDEN) {
      // Give keyboard focus to the browser view.
      browser_view_->RequestFocus();
    }
  }

  void OnWindowDestroyed(CefRefPtr<CefWindow> window) override {
    browser_view_ = nullptr;
  }

  bool CanClose(CefRefPtr<CefWindow> window) override {
    // Allow the window to close if the browser says it's OK.
    CefRefPtr<CefBrowser> browser = browser_view_->GetBrowser();
    if (browser) {
      return browser->GetHost()->TryCloseBrowser();
    }
    return true;
  }

  CefSize GetPreferredSize(CefRefPtr<CefView> view) override {
    return CefSize(800, 600);
  }

  cef_show_state_t GetInitialShowState(CefRefPtr<CefWindow> window) override {
    return initial_show_state_;
  }

  cef_runtime_style_t GetWindowRuntimeStyle() override {
    return runtime_style_;
  }

 private:
  CefRefPtr<CefBrowserView> browser_view_;
  const cef_runtime_style_t runtime_style_;
  const cef_show_state_t initial_show_state_;

  IMPLEMENT_REFCOUNTING(SimpleWindowDelegate);
  DISALLOW_COPY_AND_ASSIGN(SimpleWindowDelegate);
};

class SimpleBrowserViewDelegate : public CefBrowserViewDelegate {
 public:
  explicit SimpleBrowserViewDelegate(cef_runtime_style_t runtime_style)
      : runtime_style_(runtime_style) {}

  bool OnPopupBrowserViewCreated(CefRefPtr<CefBrowserView> browser_view,
                                 CefRefPtr<CefBrowserView> popup_browser_view,
                                 bool is_devtools) override {
    // Create a new top-level Window for the popup. It will show itself after
    // creation.
    CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(
        popup_browser_view, runtime_style_, CEF_SHOW_STATE_NORMAL));

    // We created the Window.
    return true;
  }

  cef_runtime_style_t GetBrowserRuntimeStyle() override {
    return runtime_style_;
  }

 private:
  const cef_runtime_style_t runtime_style_;

  IMPLEMENT_REFCOUNTING(SimpleBrowserViewDelegate);
  DISALLOW_COPY_AND_ASSIGN(SimpleBrowserViewDelegate);
};

}  // namespace

SimpleApp::SimpleApp() = default;

void SimpleApp::OnContextInitialized() {
}

CefRefPtr<SimpleApp> SimpleApp::GetInstance() {
    static CefRefPtr<SimpleApp> simple = nullptr;
    if (!simple) {
        simple = new SimpleApp();
    }
    return simple;
}

CefRefPtr<CefClient> SimpleApp::GetDefaultClient() {
  // Called when a new browser window is created via Chrome style UI.
  return SimpleHandler::GetInstance();
}

and
simple_handler.cc

#include "tests/cefsimple/simple_handler.h"

#include <sstream>
#include <string>

#include "include/base/cef_callback.h"
#include "include/cef_app.h"
#include "include/cef_parser.h"
#include "include/views/cef_browser_view.h"
#include "include/views/cef_window.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_helpers.h"

namespace {

SimpleHandler* g_instance = nullptr;

// Returns a data: URI with the specified contents.
std::string GetDataURI(const std::string& data, const std::string& mime_type) {
  return "data:" + mime_type + ";base64," +
         CefURIEncode(CefBase64Encode(data.data(), data.size()), false)
             .ToString();
}

}  // namespace

SimpleHandler::SimpleHandler(bool is_alloy_style)
    : is_alloy_style_(is_alloy_style) {
  DCHECK(!g_instance);
  g_instance = this;
}

SimpleHandler::~SimpleHandler() {
  g_instance = nullptr;
}

// static
SimpleHandler* SimpleHandler::GetInstance() {
  return g_instance;
}

void SimpleHandler::OnTitleChange(CefRefPtr<CefBrowser> browser,
                                  const CefString& title) {
  CEF_REQUIRE_UI_THREAD();

  if (auto browser_view = CefBrowserView::GetForBrowser(browser)) {
    // Set the title of the window using the Views framework.
    CefRefPtr<CefWindow> window = browser_view->GetWindow();
    if (window) {
      window->SetTitle(title);
    }
  } else if (is_alloy_style_) {
    // Set the title of the window using platform APIs.
    PlatformTitleChange(browser, title);
  }
}

void SimpleHandler::OnAfterCreated(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();

  // Sanity-check the configured runtime style.
  CHECK_EQ(is_alloy_style_ ? CEF_RUNTIME_STYLE_ALLOY : CEF_RUNTIME_STYLE_CHROME,
           browser->GetHost()->GetRuntimeStyle());

  // Add to the list of existing browsers.
  browser_list_.push_back(browser);
}

bool SimpleHandler::DoClose(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();

  // Closing the main window requires special handling. See the DoClose()
  // documentation in the CEF header for a detailed destription of this
  // process.
  if (browser_list_.size() == 1) {
    // Set a flag to indicate that the window close should be allowed.
    is_closing_ = true;
  }

  // Allow the close. For windowed browsers this will result in the OS close
  // event being sent.
  return false;
}

void SimpleHandler::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
  CEF_REQUIRE_UI_THREAD();

  // Remove from the list of existing browsers.
  BrowserList::iterator bit = browser_list_.begin();
  for (; bit != browser_list_.end(); ++bit) {
    if ((*bit)->IsSame(browser)) {
      browser_list_.erase(bit);
      break;
    }
  }

  if (browser_list_.empty()) {
    // All browser windows have closed. Quit the application message loop.
    CefQuitMessageLoop();
  }
}

void SimpleHandler::OnLoadError(CefRefPtr<CefBrowser> browser,
                                CefRefPtr<CefFrame> frame,
                                ErrorCode errorCode,
                                const CefString& errorText,
                                const CefString& failedUrl) {
  CEF_REQUIRE_UI_THREAD();

  // Allow Chrome to show the error page.
  if (!is_alloy_style_) {
    return;
  }

  // Don't display an error for downloaded files.
  if (errorCode == ERR_ABORTED) {
    return;
  }

  // Display a load error message using a data: URI.
  std::stringstream ss;
  ss << "<html><body bgcolor=\"white\">"
        "<h2>Failed to load URL "
     << std::string(failedUrl) << " with error " << std::string(errorText)
     << " (" << errorCode << ").</h2></body></html>";

  frame->LoadURL(GetDataURI(ss.str(), "text/html"));
}

void SimpleHandler::ShowMainWindow() {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute on the UI thread.
    CefPostTask(TID_UI, base::BindOnce(&SimpleHandler::ShowMainWindow, this));
    return;
  }

  if (browser_list_.empty()) {
    return;
  }

  auto main_browser = browser_list_.front();

  if (auto browser_view = CefBrowserView::GetForBrowser(main_browser)) {
    // Show the window using the Views framework.
    if (auto window = browser_view->GetWindow()) {
      window->Show();
    }
  } else if (is_alloy_style_) {
    PlatformShowWindow(main_browser);
  }
}

void SimpleHandler::CloseFirstBrowsers(bool force_close) {
  if (!CefCurrentlyOn(TID_UI)) {
    // Execute on the UI thread.
    CefPostTask(TID_UI, base::BindOnce(&SimpleHandler::CloseFirstBrowsers, this,
                                       force_close));
    return;
  }

  if (browser_list_.empty()) {
    return;
  }

  BrowserList::const_iterator it = browser_list_.begin();
  (*it)->GetHost()->CloseBrowser(force_close);
}

#if !defined(OS_MAC)
void SimpleHandler::PlatformShowWindow(CefRefPtr<CefBrowser> browser) {
  NOTIMPLEMENTED();
}
#endif

@magreenblatt magreenblatt removed the needs user feedback Additional feedback required label Oct 23, 2024
@magreenblatt
Copy link
Collaborator

Thanks for the update. I suggest you use one of the working options until this issue is addressed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Bug report macos MacOS platform
Projects
None yet
Development

No branches or pull requests

2 participants