cef/tests/cefclient/browser/root_window_mac.mm

1076 lines
35 KiB
Plaintext
Raw Normal View History

// 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"
Implement Views framework on Windows and Linux (issue #1749). - Add Views header files in a new include/views directory. - Add initial top-level window (CefWindow), control (CefBrowserView, CefLabelButton, CefMenuButton, CefPanel, CefScrollView, CefTextfield) and layout (CefBoxLayout, CefFlowLayout) support. See libcef/browser/views/view_impl.h comments for implementation details. - Add Views example usage in cefclient and cefsimple and Views unit tests in cef_unittests. Pass the `--use-views` command-line flag to cefclient, cefsimple and cef_unittests to run using the Views framework instead of platform APIs. For cefclient and cefsimple this will create the browser window and all related functionality using the Views framework. For cef_unittests this will run all tests (except OSR tests) in a Views-based browser window. Views- specific unit tests (`--gtest_filter=Views*`) will be run even if the the `--use-views` flag is not specified. - Pass the `--hide-frame` command-line flag to cefclient to demo a frameless Views-based browser window. - Pass the `--hide-controls` command-line flag to cefclient to demo a browser window without top controls. This also works in non-Views mode. - Pass the `--enable-high-dpi-support` command-line flag to cef_unittests on Windows to test high-DPI support on a display that supports it. - Add CefImage for reading/writing image file formats. - Add CefBrowser::DownloadImage() for downloading image URLs as a CefImage representation. This is primarily for loading favicons. - Add CefMenuModel::CreateMenuModel() and CefMenuModelDelegate for creating custom menus. This is primarily for use with CefMenuButton. - Add CefBrowser::TryCloseBrowser() helper for closing a browser. Also improve related documentation in cef_life_span_handler.h. - Rename cef_page_range_t to cef_range_t. It is now also used by CefTextfield. - Remove CefLifeSpanHandler::RunModal() which is never called. - Add draggable regions example to cefclient.
2016-01-19 21:09:01 +01:00
#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: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<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: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<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->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<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);
// 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<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(
Implement Views framework on Windows and Linux (issue #1749). - Add Views header files in a new include/views directory. - Add initial top-level window (CefWindow), control (CefBrowserView, CefLabelButton, CefMenuButton, CefPanel, CefScrollView, CefTextfield) and layout (CefBoxLayout, CefFlowLayout) support. See libcef/browser/views/view_impl.h comments for implementation details. - Add Views example usage in cefclient and cefsimple and Views unit tests in cef_unittests. Pass the `--use-views` command-line flag to cefclient, cefsimple and cef_unittests to run using the Views framework instead of platform APIs. For cefclient and cefsimple this will create the browser window and all related functionality using the Views framework. For cef_unittests this will run all tests (except OSR tests) in a Views-based browser window. Views- specific unit tests (`--gtest_filter=Views*`) will be run even if the the `--use-views` flag is not specified. - Pass the `--hide-frame` command-line flag to cefclient to demo a frameless Views-based browser window. - Pass the `--hide-controls` command-line flag to cefclient to demo a browser window without top controls. This also works in non-Views mode. - Pass the `--enable-high-dpi-support` command-line flag to cef_unittests on Windows to test high-DPI support on a display that supports it. - Add CefImage for reading/writing image file formats. - Add CefBrowser::DownloadImage() for downloading image URLs as a CefImage representation. This is primarily for loading favicons. - Add CefMenuModel::CreateMenuModel() and CefMenuModelDelegate for creating custom menus. This is primarily for use with CefMenuButton. - Add CefBrowser::TryCloseBrowser() helper for closing a browser. Also improve related documentation in cef_life_span_handler.h. - Rename cef_page_range_t to cef_range_t. It is now also used by CefTextfield. - Remove CefLifeSpanHandler::RunModal() which is never called. - Add draggable regions example to cefclient.
2016-01-19 21:09:01 +01:00
new window_test::WindowTestRunnerMac());
if (fullscreen)
Implement Views framework on Windows and Linux (issue #1749). - Add Views header files in a new include/views directory. - Add initial top-level window (CefWindow), control (CefBrowserView, CefLabelButton, CefMenuButton, CefPanel, CefScrollView, CefTextfield) and layout (CefBoxLayout, CefFlowLayout) support. See libcef/browser/views/view_impl.h comments for implementation details. - Add Views example usage in cefclient and cefsimple and Views unit tests in cef_unittests. Pass the `--use-views` command-line flag to cefclient, cefsimple and cef_unittests to run using the Views framework instead of platform APIs. For cefclient and cefsimple this will create the browser window and all related functionality using the Views framework. For cef_unittests this will run all tests (except OSR tests) in a Views-based browser window. Views- specific unit tests (`--gtest_filter=Views*`) will be run even if the the `--use-views` flag is not specified. - Pass the `--hide-frame` command-line flag to cefclient to demo a frameless Views-based browser window. - Pass the `--hide-controls` command-line flag to cefclient to demo a browser window without top controls. This also works in non-Views mode. - Pass the `--enable-high-dpi-support` command-line flag to cef_unittests on Windows to test high-DPI support on a display that supports it. - Add CefImage for reading/writing image file formats. - Add CefBrowser::DownloadImage() for downloading image URLs as a CefImage representation. This is primarily for loading favicons. - Add CefMenuModel::CreateMenuModel() and CefMenuModelDelegate for creating custom menus. This is primarily for use with CefMenuButton. - Add CefBrowser::TryCloseBrowser() helper for closing a browser. Also improve related documentation in cef_life_span_handler.h. - Rename cef_page_range_t to cef_range_t. It is now also used by CefTextfield. - Remove CefLifeSpanHandler::RunModal() which is never called. - Add draggable regions example to cefclient.
2016-01-19 21:09:01 +01:00
test_runner->Maximize(browser);
else
Implement Views framework on Windows and Linux (issue #1749). - Add Views header files in a new include/views directory. - Add initial top-level window (CefWindow), control (CefBrowserView, CefLabelButton, CefMenuButton, CefPanel, CefScrollView, CefTextfield) and layout (CefBoxLayout, CefFlowLayout) support. See libcef/browser/views/view_impl.h comments for implementation details. - Add Views example usage in cefclient and cefsimple and Views unit tests in cef_unittests. Pass the `--use-views` command-line flag to cefclient, cefsimple and cef_unittests to run using the Views framework instead of platform APIs. For cefclient and cefsimple this will create the browser window and all related functionality using the Views framework. For cef_unittests this will run all tests (except OSR tests) in a Views-based browser window. Views- specific unit tests (`--gtest_filter=Views*`) will be run even if the the `--use-views` flag is not specified. - Pass the `--hide-frame` command-line flag to cefclient to demo a frameless Views-based browser window. - Pass the `--hide-controls` command-line flag to cefclient to demo a browser window without top controls. This also works in non-Views mode. - Pass the `--enable-high-dpi-support` command-line flag to cef_unittests on Windows to test high-DPI support on a display that supports it. - Add CefImage for reading/writing image file formats. - Add CefBrowser::DownloadImage() for downloading image URLs as a CefImage representation. This is primarily for loading favicons. - Add CefMenuModel::CreateMenuModel() and CefMenuModelDelegate for creating custom menus. This is primarily for use with CefMenuButton. - Add CefBrowser::TryCloseBrowser() helper for closing a browser. Also improve related documentation in cef_life_span_handler.h. - Rename cef_page_range_t to cef_range_t. It is now also used by CefTextfield. - Remove CefLifeSpanHandler::RunModal() which is never called. - Add draggable regions example to cefclient.
2016-01-19 21:09:01 +01:00
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