mirror of
https://bitbucket.org/chromiumembedded/cef
synced 2025-01-28 01:59:25 +01:00
25954e872e
The cefclient sample app on macOS will persist window state across application restart if run with cache_path and persist_user_references enabled. To test: 1. Run `cefclient --cache-path=/path/to/cache --persist-user-preferences` 2. Move or resize the window, maximize, minimize, etc. 3. Exit cefclient. 4. Run cefclient again with the same arguments. The previous window state will be restored.
1076 lines
35 KiB
Plaintext
1076 lines
35 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_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(
|
|
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
|