// Copyright (c) 2015 The Chromium Embedded Framework Authors. All rights // reserved. Use of this source code is governed by a BSD-style license that // can be found in the LICENSE file. #include "tests/cefclient/browser/root_window_mac.h" #include #include "include/base/cef_callback.h" #include "include/cef_app.h" #include "include/cef_application_mac.h" #include "include/views/cef_display.h" #include "tests/cefclient/browser/browser_window_osr_mac.h" #include "tests/cefclient/browser/browser_window_std_mac.h" #include "tests/cefclient/browser/client_prefs.h" #include "tests/cefclient/browser/main_context.h" #include "tests/cefclient/browser/temp_window.h" #include "tests/cefclient/browser/window_test_runner_mac.h" #include "tests/shared/browser/main_message_loop.h" #include "tests/shared/common/client_switches.h" // Receives notifications from controls and the browser window. Will delete // itself when done. @interface RootWindowDelegate : NSObject { @private NSWindow* window_; client::RootWindowMac* root_window_; std::optional last_visible_bounds_; bool force_close_; } @property(nonatomic, readonly) client::RootWindowMac* root_window; @property(nonatomic, readwrite) std::optional last_visible_bounds; @property(nonatomic, readwrite) bool force_close; - (id)initWithWindow:(NSWindow*)window andRootWindow:(client::RootWindowMac*)root_window; - (IBAction)goBack:(id)sender; - (IBAction)goForward:(id)sender; - (IBAction)reload:(id)sender; - (IBAction)stopLoading:(id)sender; - (IBAction)takeURLStringValueFrom:(NSTextField*)sender; @end // @interface RootWindowDelegate namespace client { namespace { // Sizes for URL bar layout. #define BUTTON_HEIGHT 22 #define BUTTON_WIDTH 72 #define BUTTON_MARGIN 8 #define URLBAR_HEIGHT 32 NSButton* MakeButton(NSRect* rect, NSString* title, NSView* parent) { NSButton* button = [[NSButton alloc] initWithFrame:*rect]; #if !__has_feature(objc_arc) [button autorelease]; #endif // !__has_feature(objc_arc) [button setTitle:title]; [button setBezelStyle:NSSmallSquareBezelStyle]; [button setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)]; [parent addSubview:button]; rect->origin.x += BUTTON_WIDTH; return button; } // Returns the current DIP screen bounds for a visible window in the // restored position, or nullopt if the window is currently minimized or // fullscreen. std::optional GetWindowBoundsInScreen(NSWindow* window) { if ([window isMiniaturized] or [window isZoomed]) { return std::nullopt; } auto screen = [window screen]; if (screen == nil) screen = [NSScreen mainScreen]; const auto bounds = [window frame]; const auto screen_bounds = [screen frame]; if (NSEqualRects(bounds, screen_bounds)) { // Don't include windows that are transitioning to fullscreen. return std::nullopt; } CefRect dip_bounds{static_cast(bounds.origin.x), static_cast(bounds.origin.y), static_cast(bounds.size.width), static_cast(bounds.size.height)}; // Convert from macOS coordinates (bottom-left origin) to DIP coordinates // (top-left origin). dip_bounds.y = static_cast(screen_bounds.size.height) - dip_bounds.height - dip_bounds.y; return dip_bounds; } // Keep the frame bounds inside the display work area. NSRect ClampNSBoundsToWorkArea(const NSRect& frame_bounds, const CefRect& display_bounds, const CefRect& work_area) { NSRect bounds = frame_bounds; // Convert from DIP coordinates (top-left origin) to macOS coordinates // (bottom-left origin). const int work_area_y = display_bounds.height - work_area.height - work_area.y; if (bounds.size.width > work_area.width) { bounds.size.width = work_area.width; } if (bounds.size.height > work_area.height) { bounds.size.height = work_area.height; } if (bounds.origin.x < work_area.x) { bounds.origin.x = work_area.x; } else if (bounds.origin.x + bounds.size.width >= work_area.x + work_area.width) { bounds.origin.x = work_area.x + work_area.width - bounds.size.width; } if (bounds.origin.y < work_area_y) { bounds.origin.y = work_area_y; } else if (bounds.origin.y + bounds.size.height >= work_area_y + work_area.height) { bounds.origin.y = work_area_y + work_area.height - bounds.size.height; } return bounds; } // Get frame and content area rects matching the input DIP screen bounds. The // resulting window frame will be kept inside the closest display work area. If // |input_content_bounds| is true the input size is used for the content area // and the input origin is used for the frame. Otherwise, both input size and // origin are used for the frame. void GetNSBoundsInDisplay(const CefRect& dip_bounds, bool input_content_bounds, NSWindowStyleMask style_mask, NSRect& frame_rect, NSRect& content_rect) { // Identify the closest display. auto display = CefDisplay::GetDisplayMatchingBounds(dip_bounds, /*input_pixel_coords=*/false); const auto display_bounds = display->GetBounds(); const auto display_work_area = display->GetWorkArea(); // Convert from DIP coordinates (top-left origin) to macOS coordinates // (bottom-left origin). NSRect requested_rect = NSMakeRect(dip_bounds.x, dip_bounds.y, dip_bounds.width, dip_bounds.height); requested_rect.origin.y = display_bounds.height - requested_rect.size.height - requested_rect.origin.y; // Calculate the equivalent frame and content bounds. if (input_content_bounds) { // Compute frame rect from content rect. Keep the requested origin. content_rect = requested_rect; frame_rect = [NSWindow frameRectForContentRect:frame_rect styleMask:style_mask]; frame_rect.origin = requested_rect.origin; } else { // Compute content rect from frame rect. frame_rect = requested_rect; content_rect = [NSWindow contentRectForFrameRect:frame_rect styleMask:style_mask]; } // Keep the frame inside the display work area. const NSRect new_frame_rect = ClampNSBoundsToWorkArea(frame_rect, display_bounds, display_work_area); if (!NSEqualRects(frame_rect, new_frame_rect)) { frame_rect = new_frame_rect; content_rect = [NSWindow contentRectForFrameRect:frame_rect styleMask:style_mask]; } } } // namespace class RootWindowMacImpl : public base::RefCountedThreadSafe { public: RootWindowMacImpl(RootWindowMac& root_window); ~RootWindowMacImpl(); // Called by RootWindowDelegate after the associated NSWindow has been // closed. void OnNativeWindowClosed(); void CreateBrowserWindow(const std::string& startup_url); void CreateRootWindow(const CefBrowserSettings& settings, bool initially_hidden); // RootWindow methods. void Init(RootWindow::Delegate* delegate, std::unique_ptr config, const CefBrowserSettings& settings); void InitAsPopup(RootWindow::Delegate* delegate, bool with_controls, bool with_osr, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings); void Show(RootWindow::ShowMode mode); void Hide(); void SetBounds(int x, int y, size_t width, size_t height); void Close(bool force); void SetDeviceScaleFactor(float device_scale_factor); float GetDeviceScaleFactor() const; CefRefPtr GetBrowser() const; ClientWindowHandle GetWindowHandle() const; bool WithWindowlessRendering() const; bool WithExtension() const; // BrowserWindow::Delegate methods. void OnBrowserCreated(CefRefPtr browser); void OnBrowserWindowDestroyed(); void OnSetAddress(const std::string& url); void OnSetTitle(const std::string& title); void OnSetFullscreen(bool fullscreen); void OnAutoResize(const CefSize& new_size); void OnSetLoadingState(bool isLoading, bool canGoBack, bool canGoForward); void OnSetDraggableRegions(const std::vector& regions); void NotifyDestroyedIfDone(); // After initialization all members are only accessed on the main thread. // Members set during initialization. RootWindowMac& root_window_; bool with_controls_ = false; bool with_osr_ = false; bool with_extension_ = false; bool is_popup_ = false; CefRect initial_bounds_; cef_show_state_t initial_show_state_ = CEF_SHOW_STATE_NORMAL; std::unique_ptr browser_window_; bool initialized_ = false; // Main window. NSWindow* window_ = nil; RootWindowDelegate* window_delegate_ = nil; // Buttons. NSButton* back_button_ = nil; NSButton* forward_button_ = nil; NSButton* reload_button_ = nil; NSButton* stop_button_ = nil; // URL text field. NSTextField* url_textfield_ = nil; bool window_destroyed_ = false; bool browser_destroyed_ = false; }; RootWindowMacImpl::RootWindowMacImpl(RootWindowMac& root_window) : root_window_(root_window) {} RootWindowMacImpl::~RootWindowMacImpl() { REQUIRE_MAIN_THREAD(); // The window and browser should already have been destroyed. DCHECK(window_destroyed_); DCHECK(browser_destroyed_); } void RootWindowMacImpl::Init(RootWindow::Delegate* delegate, std::unique_ptr config, const CefBrowserSettings& settings) { DCHECK(!initialized_); with_controls_ = config->with_controls; with_osr_ = config->with_osr; with_extension_ = config->with_extension; if (!config->bounds.IsEmpty()) { // Initial state was specified via the config object. initial_bounds_ = config->bounds; initial_show_state_ = config->show_state; } else { // Initial state may be specified via the command-line or global // preferences. std::optional bounds; if (prefs::LoadWindowRestorePreferences(initial_show_state_, bounds) && bounds) { initial_bounds_ = *bounds; } } CreateBrowserWindow(config->url); initialized_ = true; CreateRootWindow(settings, config->initially_hidden); } void RootWindowMacImpl::InitAsPopup(RootWindow::Delegate* delegate, bool with_controls, bool with_osr, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings) { DCHECK(delegate); DCHECK(!initialized_); with_controls_ = with_controls; with_osr_ = with_osr; is_popup_ = true; if (popupFeatures.xSet) initial_bounds_.x = popupFeatures.x; if (popupFeatures.ySet) initial_bounds_.y = popupFeatures.y; if (popupFeatures.widthSet) initial_bounds_.width = popupFeatures.width; if (popupFeatures.heightSet) initial_bounds_.height = popupFeatures.height; CreateBrowserWindow(std::string()); initialized_ = true; // The new popup is initially parented to a temporary window. The native root // window will be created after the browser is created and the popup window // will be re-parented to it at that time. browser_window_->GetPopupConfig(TempWindow::GetWindowHandle(), windowInfo, client, settings); } void RootWindowMacImpl::Show(RootWindow::ShowMode mode) { REQUIRE_MAIN_THREAD(); if (!window_) return; const bool is_visible = [window_ isVisible]; const bool is_minimized = [window_ isMiniaturized]; const bool is_maximized = [window_ isZoomed]; if ((mode == RootWindow::ShowMinimized && is_minimized) || (mode == RootWindow::ShowMaximized && is_maximized) || (mode == RootWindow::ShowNormal && is_visible)) { // The window is already in the desired state. return; } // Undo the previous state since it's not the desired state. if (is_minimized) [window_ deminiaturize:nil]; else if (is_maximized) [window_ performZoom:nil]; // Window visibility may change after (for example) deminiaturizing the // window. if (![window_ isVisible]) [window_ makeKeyAndOrderFront:nil]; if (mode == RootWindow::ShowMinimized) [window_ performMiniaturize:nil]; else if (mode == RootWindow::ShowMaximized) [window_ performZoom:nil]; } void RootWindowMacImpl::Hide() { REQUIRE_MAIN_THREAD(); if (!window_) return; // Undo miniaturization, if any, so the window will actually be hidden. if ([window_ isMiniaturized]) [window_ deminiaturize:nil]; // Hide the window. [window_ orderOut:nil]; } void RootWindowMacImpl::SetBounds(int x, int y, size_t width, size_t height) { REQUIRE_MAIN_THREAD(); if (!window_) return; const CefRect dip_bounds(x, y, static_cast(width), static_cast(height)); // Calculate the equivalent frame and content area bounds. NSRect frame_rect, content_rect; GetNSBoundsInDisplay(dip_bounds, /*input_content_bounds=*/true, [window_ styleMask], frame_rect, content_rect); [window_ setFrame:frame_rect display:YES]; } void RootWindowMacImpl::Close(bool force) { REQUIRE_MAIN_THREAD(); if (window_) { static_cast([window_ delegate]).force_close = force; [window_ performClose:nil]; window_destroyed_ = true; } } void RootWindowMacImpl::SetDeviceScaleFactor(float device_scale_factor) { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) browser_window_->SetDeviceScaleFactor(device_scale_factor); } float RootWindowMacImpl::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); if (browser_window_ && with_osr_) return browser_window_->GetDeviceScaleFactor(); NOTREACHED(); return 0.0f; } CefRefPtr RootWindowMacImpl::GetBrowser() const { REQUIRE_MAIN_THREAD(); if (browser_window_) return browser_window_->GetBrowser(); return nullptr; } ClientWindowHandle RootWindowMacImpl::GetWindowHandle() const { REQUIRE_MAIN_THREAD(); return CAST_NSVIEW_TO_CEF_WINDOW_HANDLE([window_ contentView]); } bool RootWindowMacImpl::WithWindowlessRendering() const { REQUIRE_MAIN_THREAD(); return with_osr_; } bool RootWindowMacImpl::WithExtension() const { REQUIRE_MAIN_THREAD(); return with_extension_; } void RootWindowMacImpl::OnNativeWindowClosed() { window_ = nil; window_destroyed_ = true; NotifyDestroyedIfDone(); } void RootWindowMacImpl::CreateBrowserWindow(const std::string& startup_url) { if (with_osr_) { OsrRendererSettings settings = {}; MainContext::Get()->PopulateOsrSettings(&settings); browser_window_.reset(new BrowserWindowOsrMac(&root_window_, with_controls_, startup_url, settings)); } else { browser_window_.reset( new BrowserWindowStdMac(&root_window_, with_controls_, startup_url)); } } void RootWindowMacImpl::CreateRootWindow(const CefBrowserSettings& settings, bool initially_hidden) { REQUIRE_MAIN_THREAD(); DCHECK(!window_); // TODO(port): If no x,y position is specified the window will always appear // in the upper-left corner. Maybe there's a better default place to put it? CefRect dip_bounds = initial_bounds_; // TODO(port): Also, maybe there's a better way to choose the default size. if (dip_bounds.width <= 0) dip_bounds.width = 800; if (dip_bounds.height <= 0) dip_bounds.height = 600; // For popups, the requested bounds are for the content area and the requested // origin is for the window. if (is_popup_ && with_controls_) { dip_bounds.height += URLBAR_HEIGHT; } const NSWindowStyleMask style_mask = (NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable); // Calculate the equivalent frame and content area bounds. NSRect frame_rect, content_rect; GetNSBoundsInDisplay(dip_bounds, /*input_content_bounds=*/is_popup_, style_mask, frame_rect, content_rect); // The CEF framework library is loaded at runtime so we need to use this // mechanism for retrieving the class. Class window_class = NSClassFromString(@"UnderlayOpenGLHostingWindow"); CHECK(window_class); // Create the main window. window_ = [[window_class alloc] initWithContentRect:content_rect styleMask:style_mask backing:NSBackingStoreBuffered defer:NO]; [window_ setTitle:@"cefclient"]; // No dark mode, please window_.appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; // Create the delegate for control and browser window events. window_delegate_ = [[RootWindowDelegate alloc] initWithWindow:window_ andRootWindow:&root_window_]; if (!initial_bounds_.IsEmpty()) { // Remember the bounds from the previous application run in case the user // does not move or resize the window during this application run. window_delegate_.last_visible_bounds = initial_bounds_; } // Rely on the window delegate to clean us up rather than immediately // releasing when the window gets closed. We use the delegate to do // everything from the autorelease pool so the window isn't on the stack // during cleanup (ie, a window close from javascript). [window_ setReleasedWhenClosed:NO]; const cef_color_t background_color = MainContext::Get()->GetBackgroundColor(); [window_ setBackgroundColor:[NSColor colorWithCalibratedRed:float(CefColorGetR( background_color)) / 255.0f green:float(CefColorGetG( background_color)) / 255.0f blue:float(CefColorGetB( background_color)) / 255.0f alpha:1.f]]; NSView* contentView = [window_ contentView]; NSRect contentBounds = [contentView bounds]; if (!with_osr_) { // Make the content view for the window have a layer. This will make all // sub-views have layers. This is necessary to ensure correct layer // ordering of all child views and their layers. [contentView setWantsLayer:YES]; } if (with_controls_) { // Reduce the browser height by the URL bar height. contentBounds.size.height -= URLBAR_HEIGHT; // Create the buttons. NSRect button_rect = contentBounds; button_rect.origin.y = contentBounds.size.height + (URLBAR_HEIGHT - BUTTON_HEIGHT) / 2; button_rect.size.height = BUTTON_HEIGHT; button_rect.origin.x += BUTTON_MARGIN; button_rect.size.width = BUTTON_WIDTH; back_button_ = MakeButton(&button_rect, @"Back", contentView); [back_button_ setTarget:window_delegate_]; [back_button_ setAction:@selector(goBack:)]; [back_button_ setEnabled:NO]; forward_button_ = MakeButton(&button_rect, @"Forward", contentView); [forward_button_ setTarget:window_delegate_]; [forward_button_ setAction:@selector(goForward:)]; [forward_button_ setEnabled:NO]; reload_button_ = MakeButton(&button_rect, @"Reload", contentView); [reload_button_ setTarget:window_delegate_]; [reload_button_ setAction:@selector(reload:)]; [reload_button_ setEnabled:NO]; stop_button_ = MakeButton(&button_rect, @"Stop", contentView); [stop_button_ setTarget:window_delegate_]; [stop_button_ setAction:@selector(stopLoading:)]; [stop_button_ setEnabled:NO]; // Create the URL text field. button_rect.origin.x += BUTTON_MARGIN; button_rect.size.width = [contentView bounds].size.width - button_rect.origin.x - BUTTON_MARGIN; url_textfield_ = [[NSTextField alloc] initWithFrame:button_rect]; [contentView addSubview:url_textfield_]; [url_textfield_ setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)]; [url_textfield_ setTarget:window_delegate_]; [url_textfield_ setAction:@selector(takeURLStringValueFrom:)]; [url_textfield_ setEnabled:NO]; [[url_textfield_ cell] setWraps:NO]; [[url_textfield_ cell] setScrollable:YES]; } // Place the window at the target point. This is required for proper placement // if the point is on a secondary display. [window_ setFrameOrigin:frame_rect.origin]; if (!is_popup_) { // Create the browser window. browser_window_->CreateBrowser( CAST_NSVIEW_TO_CEF_WINDOW_HANDLE(contentView), CefRect(0, 0, contentBounds.size.width, contentBounds.size.height), settings, nullptr, root_window_.delegate_->GetRequestContext(&root_window_)); } else { // With popups we already have a browser window. Parent the browser window // to the root window and show it in the correct location. browser_window_->ShowPopup(CAST_NSVIEW_TO_CEF_WINDOW_HANDLE(contentView), 0, 0, contentBounds.size.width, contentBounds.size.height); } if (!initially_hidden) { auto mode = RootWindow::ShowNormal; if (initial_show_state_ == CEF_SHOW_STATE_MAXIMIZED) { mode = RootWindow::ShowMaximized; } else if (initial_show_state_ == CEF_SHOW_STATE_MINIMIZED) { mode = RootWindow::ShowMinimized; } // Show the window. Show(mode); } } void RootWindowMacImpl::OnBrowserCreated(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); // For popup browsers create the root window once the browser has been // created. if (is_popup_) CreateRootWindow(CefBrowserSettings(), false); root_window_.delegate_->OnBrowserCreated(&root_window_, browser); } void RootWindowMacImpl::OnBrowserWindowDestroyed() { REQUIRE_MAIN_THREAD(); browser_window_.reset(); if (!window_destroyed_) { // The browser was destroyed first. This could be due to the use of // off-screen rendering or execution of JavaScript window.close(). // Close the RootWindow. Close(true); } browser_destroyed_ = true; NotifyDestroyedIfDone(); } void RootWindowMacImpl::OnSetAddress(const std::string& url) { REQUIRE_MAIN_THREAD(); if (url_textfield_) { std::string urlStr(url); NSString* str = [NSString stringWithUTF8String:urlStr.c_str()]; [url_textfield_ setStringValue:str]; } } void RootWindowMacImpl::OnSetDraggableRegions( const std::vector& regions) { REQUIRE_MAIN_THREAD(); // TODO(cef): Implement support for draggable regions on this platform. } void RootWindowMacImpl::OnSetTitle(const std::string& title) { REQUIRE_MAIN_THREAD(); if (window_) { std::string titleStr(title); NSString* str = [NSString stringWithUTF8String:titleStr.c_str()]; [window_ setTitle:str]; } } void RootWindowMacImpl::OnSetFullscreen(bool fullscreen) { REQUIRE_MAIN_THREAD(); CefRefPtr browser = GetBrowser(); if (browser) { std::unique_ptr test_runner( new window_test::WindowTestRunnerMac()); if (fullscreen) test_runner->Maximize(browser); else test_runner->Restore(browser); } } void RootWindowMacImpl::OnAutoResize(const CefSize& new_size) { REQUIRE_MAIN_THREAD(); if (!window_) return; // Desired content rectangle. NSRect content_rect; content_rect.size.width = static_cast(new_size.width); content_rect.size.height = static_cast(new_size.height) + (with_controls_ ? URLBAR_HEIGHT : 0); // Convert to a frame rectangle. NSRect frame_rect = [window_ frameRectForContentRect:content_rect]; // Don't change the origin. frame_rect.origin = window_.frame.origin; [window_ setFrame:frame_rect display:YES]; // Make sure the window is visible. Show(RootWindow::ShowNormal); } void RootWindowMacImpl::OnSetLoadingState(bool isLoading, bool canGoBack, bool canGoForward) { REQUIRE_MAIN_THREAD(); if (with_controls_) { [url_textfield_ setEnabled:YES]; [reload_button_ setEnabled:!isLoading]; [stop_button_ setEnabled:isLoading]; [back_button_ setEnabled:canGoBack]; [forward_button_ setEnabled:canGoForward]; } // After Loading is done, check if voiceover is running and accessibility // should be enabled. if (!isLoading) { Boolean keyExists = false; // On OSX there is no API to query if VoiceOver is active or not. The value // however is stored in preferences that can be queried. if (CFPreferencesGetAppBooleanValue(CFSTR("voiceOverOnOffKey"), CFSTR("com.apple.universalaccess"), &keyExists)) { GetBrowser()->GetHost()->SetAccessibilityState(STATE_ENABLED); } } } void RootWindowMacImpl::NotifyDestroyedIfDone() { // Notify once both the window and the browser have been destroyed. if (window_destroyed_ && browser_destroyed_) root_window_.delegate_->OnRootWindowDestroyed(&root_window_); } RootWindowMac::RootWindowMac() { impl_ = new RootWindowMacImpl(*this); } RootWindowMac::~RootWindowMac() {} BrowserWindow* RootWindowMac::browser_window() const { return impl_->browser_window_.get(); } RootWindow::Delegate* RootWindowMac::delegate() const { return delegate_; } void RootWindowMac::Init(RootWindow::Delegate* delegate, std::unique_ptr config, const CefBrowserSettings& settings) { DCHECK(delegate); delegate_ = delegate; impl_->Init(delegate, std::move(config), settings); } void RootWindowMac::InitAsPopup(RootWindow::Delegate* delegate, bool with_controls, bool with_osr, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings) { DCHECK(delegate); delegate_ = delegate; impl_->InitAsPopup(delegate, with_controls, with_osr, popupFeatures, windowInfo, client, settings); } void RootWindowMac::Show(ShowMode mode) { impl_->Show(mode); } void RootWindowMac::Hide() { impl_->Hide(); } void RootWindowMac::SetBounds(int x, int y, size_t width, size_t height) { impl_->SetBounds(x, y, width, height); } void RootWindowMac::Close(bool force) { impl_->Close(force); } void RootWindowMac::SetDeviceScaleFactor(float device_scale_factor) { impl_->SetDeviceScaleFactor(device_scale_factor); } float RootWindowMac::GetDeviceScaleFactor() const { return impl_->GetDeviceScaleFactor(); } CefRefPtr RootWindowMac::GetBrowser() const { return impl_->GetBrowser(); } ClientWindowHandle RootWindowMac::GetWindowHandle() const { return impl_->GetWindowHandle(); } bool RootWindowMac::WithWindowlessRendering() const { return impl_->WithWindowlessRendering(); } bool RootWindowMac::WithExtension() const { return impl_->WithExtension(); } void RootWindowMac::OnBrowserCreated(CefRefPtr browser) { impl_->OnBrowserCreated(browser); } void RootWindowMac::OnBrowserWindowDestroyed() { impl_->OnBrowserWindowDestroyed(); } void RootWindowMac::OnSetAddress(const std::string& url) { impl_->OnSetAddress(url); } void RootWindowMac::OnSetTitle(const std::string& title) { impl_->OnSetTitle(title); } void RootWindowMac::OnSetFullscreen(bool fullscreen) { impl_->OnSetFullscreen(fullscreen); } void RootWindowMac::OnAutoResize(const CefSize& new_size) { impl_->OnAutoResize(new_size); } void RootWindowMac::OnSetLoadingState(bool isLoading, bool canGoBack, bool canGoForward) { impl_->OnSetLoadingState(isLoading, canGoBack, canGoForward); } void RootWindowMac::OnSetDraggableRegions( const std::vector& regions) { impl_->OnSetDraggableRegions(regions); } void RootWindowMac::OnNativeWindowClosed() { impl_->OnNativeWindowClosed(); } } // namespace client @implementation RootWindowDelegate @synthesize root_window = root_window_; @synthesize last_visible_bounds = last_visible_bounds_; @synthesize force_close = force_close_; - (id)initWithWindow:(NSWindow*)window andRootWindow:(client::RootWindowMac*)root_window { if (self = [super init]) { window_ = window; [window_ setDelegate:self]; root_window_ = root_window; force_close_ = false; // Register for application hide/unhide notifications. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidHide:) name:NSApplicationDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidUnhide:) name:NSApplicationDidUnhideNotification object:nil]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; #if !__has_feature(objc_arc) [super dealloc]; #endif // !__has_feature(objc_arc) } - (IBAction)goBack:(id)sender { CefRefPtr browser = root_window_->GetBrowser(); if (browser.get()) browser->GoBack(); } - (IBAction)goForward:(id)sender { CefRefPtr browser = root_window_->GetBrowser(); if (browser.get()) browser->GoForward(); } - (IBAction)reload:(id)sender { CefRefPtr browser = root_window_->GetBrowser(); if (browser.get()) browser->Reload(); } - (IBAction)stopLoading:(id)sender { CefRefPtr browser = root_window_->GetBrowser(); if (browser.get()) browser->StopLoad(); } - (IBAction)takeURLStringValueFrom:(NSTextField*)sender { CefRefPtr browser = root_window_->GetBrowser(); if (!browser.get()) return; NSString* url = [sender stringValue]; // if it doesn't already have a prefix, add http. If we can't parse it, // just don't bother rather than making things worse. NSURL* tempUrl = [NSURL URLWithString:url]; if (tempUrl && ![tempUrl scheme]) url = [@"http://" stringByAppendingString:url]; std::string urlStr = [url UTF8String]; browser->GetMainFrame()->LoadURL(urlStr); } // Called when we are activated (when we gain focus). - (void)windowDidBecomeKey:(NSNotification*)notification { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window) browser_window->SetFocus(true); root_window_->delegate()->OnRootWindowActivated(root_window_); } // Called when we are deactivated (when we lose focus). - (void)windowDidResignKey:(NSNotification*)notification { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window) browser_window->SetFocus(false); } // Called when we have been minimized. - (void)windowDidMiniaturize:(NSNotification*)notification { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window) browser_window->Hide(); } // Called when we have been unminimized. - (void)windowDidDeminiaturize:(NSNotification*)notification { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window) browser_window->Show(); } // Called when we have been resized. - (void)windowDidResize:(NSNotification*)notification { // Track the last visible bounds for window restore purposes. const auto dip_bounds = client::GetWindowBoundsInScreen(window_); if (dip_bounds) { last_visible_bounds_ = dip_bounds; } } // Called when we have been moved. - (void)windowDidMove:(NSNotification*)notification { // Track the last visible bounds for window restore purposes. const auto dip_bounds = client::GetWindowBoundsInScreen(window_); if (dip_bounds) { last_visible_bounds_ = dip_bounds; } } // Called when the application has been hidden. - (void)applicationDidHide:(NSNotification*)notification { // If the window is miniaturized then nothing has really changed. if (![window_ isMiniaturized]) { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window) browser_window->Hide(); } } // Called when the application has been unhidden. - (void)applicationDidUnhide:(NSNotification*)notification { // If the window is miniaturized then nothing has really changed. if (![window_ isMiniaturized]) { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window) browser_window->Show(); } } // Called when the window is about to close. Perform the self-destruction // sequence by getting rid of the window. By returning YES, we allow the window // to be removed from the screen. - (BOOL)windowShouldClose:(NSWindow*)window { if (!force_close_) { client::BrowserWindow* browser_window = root_window_->browser_window(); if (browser_window && !browser_window->IsClosing()) { CefRefPtr browser = browser_window->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 NO; } } } // Save window restore position. std::optional dip_bounds; cef_show_state_t show_state = CEF_SHOW_STATE_NORMAL; if ([window_ isMiniaturized]) { show_state = CEF_SHOW_STATE_MINIMIZED; } else if ([window_ isZoomed]) { show_state = CEF_SHOW_STATE_MAXIMIZED; } else { dip_bounds = client::GetWindowBoundsInScreen(window_); } if (!dip_bounds) { dip_bounds = last_visible_bounds_; } client::prefs::SaveWindowRestorePreferences(show_state, dip_bounds); // Clean ourselves up after clearing the stack of anything that might have the // window on it. [self cleanup]; // Allow the close. return YES; } // Deletes itself. - (void)cleanup { // Don't want any more delegate callbacks after we destroy ourselves. window_.delegate = nil; window_.contentView = [[NSView alloc] initWithFrame:NSZeroRect]; // Delete the window. #if !__has_feature(objc_arc) [window_ autorelease]; #endif // !__has_feature(objc_arc) window_ = nil; root_window_->OnNativeWindowClosed(); } @end // @implementation RootWindowDelegate