cef/tests/cefclient/browser/root_window_mac.mm

726 lines
22 KiB
Plaintext

// 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_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<NSWindowDelegate> {
@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<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 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<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;
}
}
}
// 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()
: 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,
const RootWindowConfig& config,
const CefBrowserSettings& settings) {
DCHECK(delegate);
DCHECK(!initialized_);
delegate_ = delegate;
with_controls_ = config.with_controls;
with_osr_ = config.with_osr;
with_extension_ = config.with_extension;
start_rect_ = config.bounds;
CreateBrowserWindow(config.url);
initialized_ = true;
// Create the native root window on the main thread.
if (CURRENTLY_ON_MAIN_THREAD()) {
CreateRootWindow(settings, config.initially_hidden);
} else {
MAIN_POST_CLOSURE(base::Bind(&RootWindowMac::CreateRootWindow, this,
settings, config.initially_hidden));
}
}
void RootWindowMac::InitAsPopup(RootWindow::Delegate* delegate,
bool with_controls,
bool with_osr,
const CefPopupFeatures& popupFeatures,
CefWindowInfo& windowInfo,
CefRefPtr<CefClient>& 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<int>(width);
content_rect.size.height =
static_cast<int>(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<RootWindowDelegate*>([window_ delegate]).force_close = force;
[window_ performClose:nil];
}
}
void RootWindowMac::SetDeviceScaleFactor(float device_scale_factor) {
REQUIRE_MAIN_THREAD();
if (browser_window_ && with_osr_)
browser_window_->SetDeviceScaleFactor(device_scale_factor);
}
float RootWindowMac::GetDeviceScaleFactor() const {
REQUIRE_MAIN_THREAD();
if (browser_window_ && with_osr_)
return browser_window_->GetDeviceScaleFactor();
NOTREACHED();
return 0.0f;
}
CefRefPtr<CefBrowser> 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];
}
bool RootWindowMac::WithWindowlessRendering() const {
REQUIRE_MAIN_THREAD();
return with_osr_;
}
bool RootWindowMac::WithExtension() const {
REQUIRE_MAIN_THREAD();
return with_extension_;
}
void RootWindowMac::WindowDestroyed() {
window_ = nil;
window_destroyed_ = true;
NotifyDestroyedIfDone();
}
void RootWindowMac::CreateBrowserWindow(const std::string& startup_url) {
if (with_osr_) {
OsrRendererSettings 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,
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?
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);
// 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);
window_ = [[window_class 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);
}
if (!initially_hidden) {
// Show the window.
Show(ShowNormal);
// Size the window.
SetBounds(x, y, width, height);
}
}
void RootWindowMac::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);
delegate_->OnBrowserCreated(this, browser);
}
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<CefDraggableRegion>& 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<CefBrowser> browser = GetBrowser();
if (browser) {
scoped_ptr<window_test::WindowTestRunnerMac> test_runner(
new window_test::WindowTestRunnerMac());
if (fullscreen)
test_runner->Maximize(browser);
else
test_runner->Restore(browser);
}
}
void RootWindowMac::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(ShowNormal);
}
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];
}
// 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 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> RootWindow::GetForNSWindow(NSWindow* window) {
RootWindowDelegate* delegate =
static_cast<RootWindowDelegate*>([window delegate]);
return [delegate root_window];
}
} // namespace client