| // 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 <Cocoa/Cocoa.h> |
| |
| #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 <NSWindowDelegate> { |
| @private |
| NSWindow* window_; |
| client::RootWindowMac* root_window_; |
| std::optional<CefRect> last_visible_bounds_; |
| bool force_close_; |
| } |
| |
| @property(nonatomic, readonly) client::RootWindowMac* root_window; |
| @property(nonatomic, readwrite) std::optional<CefRect> 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:NSBezelStyleSmallSquare]; |
| [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<CefRect> 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<int>(bounds.origin.x), |
| static_cast<int>(bounds.origin.y), |
| static_cast<int>(bounds.size.width), |
| static_cast<int>(bounds.size.height)}; |
| |
| // Convert from macOS coordinates (bottom-left origin) to DIP coordinates |
| // (top-left origin). |
| dip_bounds.y = static_cast<int>(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:content_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<RootWindowMacImpl, DeleteOnMainThread> { |
| 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<RootWindowConfig> config, |
| const CefBrowserSettings& settings); |
| void InitAsPopup(RootWindow::Delegate* delegate, |
| bool with_controls, |
| bool with_osr, |
| const CefPopupFeatures& popupFeatures, |
| CefWindowInfo& windowInfo, |
| CefRefPtr<CefClient>& 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<CefBrowser> GetBrowser() const; |
| ClientWindowHandle GetWindowHandle() const; |
| bool WithWindowlessRendering() const; |
| bool WithExtension() const; |
| |
| // BrowserWindow::Delegate methods. |
| void OnBrowserCreated(CefRefPtr<CefBrowser> 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<CefDraggableRegion>& 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<BrowserWindow> 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<RootWindowConfig> config, |
| const CefBrowserSettings& settings) { |
| DCHECK(!initialized_); |
| |
| with_controls_ = config->with_controls; |
| with_osr_ = config->with_osr; |
| with_extension_ = config->window_type == WindowType::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<CefRect> 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<CefClient>& 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<int>(width), |
| static_cast<int>(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<RootWindowDelegate*>([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<CefBrowser> 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); |
| |
| // Create the main window. |
| window_ = [[NSWindow 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<CefBrowser> 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<CefDraggableRegion>& 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<CefBrowser> browser = GetBrowser(); |
| if (browser) { |
| std::unique_ptr<window_test::WindowTestRunnerMac> 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<int>(new_size.width); |
| content_rect.size.height = |
| static_cast<int>(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<RootWindowConfig> 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<CefClient>& 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<CefBrowser> 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<CefBrowser> 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<CefDraggableRegion>& 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<CefBrowser> browser = root_window_->GetBrowser(); |
| if (browser.get()) { |
| browser->GoBack(); |
| } |
| } |
| |
| - (IBAction)goForward:(id)sender { |
| CefRefPtr<CefBrowser> browser = root_window_->GetBrowser(); |
| if (browser.get()) { |
| browser->GoForward(); |
| } |
| } |
| |
| - (IBAction)reload:(id)sender { |
| CefRefPtr<CefBrowser> browser = root_window_->GetBrowser(); |
| if (browser.get()) { |
| browser->Reload(); |
| } |
| } |
| |
| - (IBAction)stopLoading:(id)sender { |
| CefRefPtr<CefBrowser> browser = root_window_->GetBrowser(); |
| if (browser.get()) { |
| browser->StopLoad(); |
| } |
| } |
| |
| - (IBAction)takeURLStringValueFrom:(NSTextField*)sender { |
| CefRefPtr<CefBrowser> 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<CefBrowser> 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<CefRect> 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 |