// 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_bind.h" #include "include/cef_app.h" #include "include/cef_application_mac.h" #include "tests/cefclient/browser/browser_window_osr_mac.h" #include "tests/cefclient/browser/browser_window_std_mac.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_; bool force_close_; } @property (nonatomic, readonly) client::RootWindowMac* root_window; @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 @implementation RootWindowDelegate @synthesize root_window = root_window_; @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]; [super dealloc]; } - (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); } // 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 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:(id)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; } } } // Try to make the window go away. [window autorelease]; // Clean ourselves up after clearing the stack of anything that might have the // window on it. [self performSelectorOnMainThread:@selector(cleanup:) withObject:window waitUntilDone:NO]; // Allow the close. return YES; } // Deletes itself. - (void)cleanup:(id)window { root_window_->WindowDestroyed(); // Don't want any more delegate callbacks after we destroy ourselves. [window setDelegate:nil]; [self release]; } @end 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] autorelease]; [button setTitle:title]; [button setBezelStyle:NSSmallSquareBezelStyle]; [button setAutoresizingMask:(NSViewMaxXMargin | NSViewMinYMargin)]; [parent addSubview:button]; rect->origin.x += BUTTON_WIDTH; return button; } NSRect GetScreenRectForWindow(NSWindow* window) { NSScreen* screen = [window screen]; if (screen == nil) screen = [NSScreen mainScreen]; return [screen visibleFrame]; } } // namespace RootWindowMac::RootWindowMac() : delegate_(NULL), with_controls_(false), with_osr_(false), is_popup_(false), initialized_(false), window_(nil), back_button_(nil), forward_button_(nil), reload_button_(nil), stop_button_(nil), url_textfield_(nil), window_destroyed_(false), browser_destroyed_(false) { } RootWindowMac::~RootWindowMac() { REQUIRE_MAIN_THREAD(); // The window and browser should already have been destroyed. DCHECK(window_destroyed_); DCHECK(browser_destroyed_); } void RootWindowMac::Init(RootWindow::Delegate* delegate, bool with_controls, bool with_osr, const CefRect& bounds, const CefBrowserSettings& settings, const std::string& url) { DCHECK(delegate); DCHECK(!initialized_); delegate_ = delegate; with_controls_ = with_controls; with_osr_ = with_osr; start_rect_ = bounds; CreateBrowserWindow(url); initialized_ = true; // Create the native root window on the main thread. if (CURRENTLY_ON_MAIN_THREAD()) { CreateRootWindow(settings); } else { MAIN_POST_CLOSURE( base::Bind(&RootWindowMac::CreateRootWindow, this, settings)); } } void RootWindowMac::InitAsPopup(RootWindow::Delegate* delegate, bool with_controls, bool with_osr, const CefPopupFeatures& popupFeatures, CefWindowInfo& windowInfo, CefRefPtr& client, CefBrowserSettings& settings) { DCHECK(delegate); DCHECK(!initialized_); delegate_ = delegate; with_controls_ = with_controls; with_osr_ = with_osr; is_popup_ = true; if (popupFeatures.xSet) start_rect_.x = popupFeatures.x; if (popupFeatures.ySet) start_rect_.y = popupFeatures.y; if (popupFeatures.widthSet) start_rect_.width = popupFeatures.width; if (popupFeatures.heightSet) start_rect_.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 RootWindowMac::Show(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 == ShowMinimized && is_minimized) || (mode == ShowMaximized && is_maximized) || (mode == 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 == ShowMinimized) [window_ performMiniaturize:nil]; else if (mode == ShowMaximized) [window_ performZoom:nil]; } void RootWindowMac::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 RootWindowMac::SetBounds(int x, int y, size_t width, size_t height) { REQUIRE_MAIN_THREAD(); if (!window_) return; NSRect screen_rect = GetScreenRectForWindow(window_); // Desired content rectangle. NSRect content_rect; content_rect.size.width = static_cast(width); content_rect.size.height = static_cast(height) + (with_controls_ ? URLBAR_HEIGHT : 0); // Convert to a frame rectangle. NSRect frame_rect = [window_ frameRectForContentRect:content_rect]; frame_rect.origin.x = x; frame_rect.origin.y = screen_rect.size.height - y; [window_ setFrame:frame_rect display:YES]; } void RootWindowMac::Close(bool force) { REQUIRE_MAIN_THREAD(); if (window_) { static_cast([window_ delegate]).force_close = force; [window_ performClose:nil]; } } void RootWindowMac::SetDeviceScaleFactor(float device_scale_factor) { REQUIRE_MAIN_THREAD(); if (browser_window_) browser_window_->SetDeviceScaleFactor(device_scale_factor); } float RootWindowMac::GetDeviceScaleFactor() const { REQUIRE_MAIN_THREAD(); if (browser_window_) return browser_window_->GetDeviceScaleFactor(); return 1.0f; } CefRefPtr RootWindowMac::GetBrowser() const { REQUIRE_MAIN_THREAD(); if (browser_window_) return browser_window_->GetBrowser(); return NULL; } ClientWindowHandle RootWindowMac::GetWindowHandle() const { REQUIRE_MAIN_THREAD(); return [window_ contentView]; } void RootWindowMac::WindowDestroyed() { window_ = nil; window_destroyed_ = true; NotifyDestroyedIfDone(); } void RootWindowMac::CreateBrowserWindow(const std::string& startup_url) { if (with_osr_) { OsrRenderer::Settings settings = {}; MainContext::Get()->PopulateOsrSettings(&settings); browser_window_.reset(new BrowserWindowOsrMac(this, startup_url, settings)); } else { browser_window_.reset(new BrowserWindowStdMac(this, startup_url)); } } void RootWindowMac::CreateRootWindow(const CefBrowserSettings& settings) { 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? int x = start_rect_.x; int y = start_rect_.y; int width, height; if (start_rect_.IsEmpty()) { // TODO(port): Also, maybe there's a better way to choose the default size. width = 800; height = 600; } else { width = start_rect_.width; height = start_rect_.height; } // Create the main window. NSRect screen_rect = [[NSScreen mainScreen] visibleFrame]; NSRect window_rect = NSMakeRect(x, screen_rect.size.height - y, width, height); window_ = [[UnderlayOpenGLHostingWindow alloc] initWithContentRect:window_rect styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask | NSUnifiedTitleAndToolbarWindowMask ) backing:NSBackingStoreBuffered defer:NO]; [window_ setTitle:@"cefclient"]; // Create the delegate for control and browser window events. RootWindowDelegate* delegate = [[RootWindowDelegate alloc] initWithWindow:window_ andRootWindow:this]; // 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_) { // Create the buttons. NSRect button_rect = contentBounds; button_rect.origin.y = window_rect.size.height - URLBAR_HEIGHT + (URLBAR_HEIGHT - BUTTON_HEIGHT) / 2; button_rect.size.height = BUTTON_HEIGHT; button_rect.origin.x += BUTTON_MARGIN; button_rect.size.width = BUTTON_WIDTH; contentBounds.size.height -= URLBAR_HEIGHT; back_button_ = MakeButton(&button_rect, @"Back", contentView); [back_button_ setTarget:delegate]; [back_button_ setAction:@selector(goBack:)]; [back_button_ setEnabled:NO]; forward_button_ = MakeButton(&button_rect, @"Forward", contentView); [forward_button_ setTarget:delegate]; [forward_button_ setAction:@selector(goForward:)]; [forward_button_ setEnabled:NO]; reload_button_ = MakeButton(&button_rect, @"Reload", contentView); [reload_button_ setTarget:delegate]; [reload_button_ setAction:@selector(reload:)]; [reload_button_ setEnabled:NO]; stop_button_ = MakeButton(&button_rect, @"Stop", contentView); [stop_button_ setTarget: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:delegate]; [url_textfield_ setAction:@selector(takeURLStringValueFrom:)]; [url_textfield_ setEnabled:NO]; [[url_textfield_ cell] setWraps:NO]; [[url_textfield_ cell] setScrollable:YES]; } if (!is_popup_) { // Create the browser window. browser_window_->CreateBrowser(contentView, CefRect(0, 0, width, height), settings, delegate_->GetRequestContext(this)); } 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(contentView, 0, 0, contentBounds.size.width, contentBounds.size.height); } // Show the window. Show(ShowNormal); // Size the window. SetBounds(x, y, width, height); } void RootWindowMac::OnBrowserCreated(CefRefPtr browser) { REQUIRE_MAIN_THREAD(); // For popup browsers create the root window once the browser has been // created. if (is_popup_) CreateRootWindow(CefBrowserSettings()); } void RootWindowMac::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 RootWindowMac::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 RootWindowMac::OnSetDraggableRegions( const std::vector& regions) { REQUIRE_MAIN_THREAD(); // TODO(cef): Implement support for draggable regions on this platform. } void RootWindowMac::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 RootWindowMac::OnSetFullscreen(bool fullscreen) { REQUIRE_MAIN_THREAD(); CefRefPtr browser = GetBrowser(); if (browser) { scoped_ptr test_runner( new window_test::WindowTestRunnerMac()); if (fullscreen) test_runner->Maximize(browser); else test_runner->Restore(browser); } } void RootWindowMac::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]; } } void RootWindowMac::NotifyDestroyedIfDone() { // Notify once both the window and the browser have been destroyed. if (window_destroyed_ && browser_destroyed_) delegate_->OnRootWindowDestroyed(this); } // static scoped_refptr RootWindow::GetForNSWindow(NSWindow* window) { RootWindowDelegate* delegate = static_cast([window delegate]); return [delegate root_window]; } } // namespace client