electron/shell/browser/native_window_mac.mm

1809 lines
60 KiB
Text
Raw Normal View History

// Copyright (c) 2013 GitHub, Inc.
2014-04-25 09:49:37 +00:00
// Use of this source code is governed by the MIT license that can be
2013-04-12 07:04:46 +00:00
// found in the LICENSE file.
#include "shell/browser/native_window_mac.h"
2013-04-12 07:04:46 +00:00
2017-10-05 22:57:27 +00:00
#include <AvailabilityMacros.h>
2018-05-02 11:43:45 +00:00
#include <objc/objc-runtime.h>
#include <algorithm>
2016-10-26 00:55:34 +00:00
#include <string>
#include <utility>
#include <vector>
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/numerics/ranges.h"
#include "base/strings/sys_string_conversions.h"
#include "base/task/post_task.h"
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
2015-04-13 12:10:51 +00:00
#include "content/public/browser/browser_accessibility_state.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "shell/browser/native_browser_view_mac.h"
#include "shell/browser/ui/cocoa/electron_native_widget_mac.h"
#include "shell/browser/ui/cocoa/electron_ns_window.h"
#include "shell/browser/ui/cocoa/electron_ns_window_delegate.h"
#include "shell/browser/ui/cocoa/electron_preview_item.h"
#include "shell/browser/ui/cocoa/electron_touch_bar.h"
#include "shell/browser/ui/cocoa/root_view_mac.h"
#include "shell/browser/ui/inspectable_web_contents.h"
#include "shell/browser/ui/inspectable_web_contents_view.h"
#include "shell/browser/window_list.h"
#include "shell/common/gin_converters/gfx_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/options_switches.h"
#include "shell/common/process_util.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/webrtc/modules/desktop_capture/mac/window_list_utils.h"
#include "ui/gfx/skia_util.h"
#include "ui/gl/gpu_switching_manager.h"
2018-04-24 10:00:44 +00:00
#include "ui/views/background.h"
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
2018-04-19 06:26:34 +00:00
#include "ui/views/widget/widget.h"
2013-04-12 07:04:46 +00:00
// This view would inform Chromium to resize the hosted views::View.
//
// The overrided methods should behave the same with BridgedContentView.
@interface ElectronAdaptedContentView : NSView {
@private
views::NativeWidgetMacNSWindowHost* bridge_host_;
}
@end
@implementation ElectronAdaptedContentView
- (id)initWithShell:(electron::NativeWindowMac*)shell {
if ((self = [self init])) {
bridge_host_ = views::NativeWidgetMacNSWindowHost::GetFromNativeWindow(
shell->GetNativeWindow());
}
return self;
}
- (void)viewDidMoveToWindow {
// When this view is added to a window, AppKit calls setFrameSize before it is
// added to the window, so the behavior in setFrameSize is not triggered.
NSWindow* window = [self window];
if (window)
[self setFrameSize:NSZeroSize];
}
- (void)setFrameSize:(NSSize)newSize {
// The size passed in here does not always use
// -[NSWindow contentRectForFrameRect]. The following ensures that the
// contentView for a frameless window can extend over the titlebar of the new
// window containing it, since AppKit requires a titlebar to give frameless
// windows correct shadows and rounded corners.
NSWindow* window = [self window];
if (window && [window contentView] == self) {
newSize = [window contentRectForFrameRect:[window frame]].size;
// Ensure that the window geometry be updated on the host side before the
// view size is updated.
bridge_host_->GetInProcessNSWindowBridge()->UpdateWindowGeometry();
}
[super setFrameSize:newSize];
// The OnViewSizeChanged is marked private in derived class.
static_cast<remote_cocoa::mojom::NativeWidgetNSWindowHost*>(bridge_host_)
->OnViewSizeChanged(gfx::Size(newSize.width, newSize.height));
}
@end
// This view always takes the size of its superview. It is intended to be used
// as a NSWindow's contentView. It is needed because NSWindow's implementation
// explicitly resizes the contentView at inopportune times.
@interface FullSizeContentView : NSView
@end
@implementation FullSizeContentView
// This method is directly called by NSWindow during a window resize on OSX
// 10.10.0, beta 2. We must override it to prevent the content view from
// shrinking.
- (void)setFrameSize:(NSSize)size {
if ([self superview])
size = [[self superview] bounds].size;
[super setFrameSize:size];
}
// The contentView gets moved around during certain full-screen operations.
// This is less than ideal, and should eventually be removed.
- (void)viewDidMoveToSuperview {
[self setFrame:[[self superview] bounds]];
}
@end
// Custom Quit, Minimize and Full Screen button container for frameless
// windows.
@interface CustomWindowButtonView : NSView {
@private
BOOL mouse_inside_;
}
@end
@implementation CustomWindowButtonView
- (id)initWithFrame:(NSRect)frame {
2017-06-05 18:08:05 +00:00
self = [super initWithFrame:frame];
NSButton* close_button =
[NSWindow standardWindowButton:NSWindowCloseButton
forStyleMask:NSWindowStyleMaskTitled];
[close_button setTag:1];
NSButton* miniaturize_button =
[NSWindow standardWindowButton:NSWindowMiniaturizeButton
forStyleMask:NSWindowStyleMaskTitled];
[miniaturize_button setTag:2];
CGFloat x = 0;
const CGFloat space_between = 20;
[close_button setFrameOrigin:NSMakePoint(x, 0)];
x += space_between;
[self addSubview:close_button];
[miniaturize_button setFrameOrigin:NSMakePoint(x, 0)];
x += space_between;
[self addSubview:miniaturize_button];
const auto last_button_frame = miniaturize_button.frame;
[self setFrameSize:NSMakeSize(last_button_frame.origin.x +
last_button_frame.size.width,
last_button_frame.size.height)];
mouse_inside_ = NO;
2017-06-06 22:19:53 +00:00
[self setNeedsDisplayForButtons];
2017-06-05 18:08:05 +00:00
return self;
}
- (void)viewDidMoveToWindow {
if (!self.window) {
return;
}
// Stay in upper left corner.
const CGFloat top_margin = 3;
const CGFloat left_margin = 7;
[self setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin];
[self setFrameOrigin:NSMakePoint(left_margin, self.window.frame.size.height -
self.frame.size.height -
top_margin)];
}
2017-06-05 18:08:05 +00:00
- (BOOL)_mouseInGroup:(NSButton*)button {
return mouse_inside_;
}
- (void)updateTrackingAreas {
auto tracking_area = [[[NSTrackingArea alloc]
initWithRect:NSZeroRect
options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways |
NSTrackingInVisibleRect
owner:self
userInfo:nil] autorelease];
[self addTrackingArea:tracking_area];
}
2017-06-05 18:08:05 +00:00
- (void)mouseEntered:(NSEvent*)event {
[super mouseEntered:event];
mouse_inside_ = YES;
2017-06-05 18:08:05 +00:00
[self setNeedsDisplayForButtons];
}
2017-06-05 18:08:05 +00:00
- (void)mouseExited:(NSEvent*)event {
[super mouseExited:event];
mouse_inside_ = NO;
2017-06-05 18:08:05 +00:00
[self setNeedsDisplayForButtons];
}
- (void)setNeedsDisplayForButtons {
2017-06-05 18:08:05 +00:00
for (NSView* subview in self.subviews) {
[subview setHidden:!mouse_inside_];
2017-06-05 18:08:05 +00:00
[subview setNeedsDisplay:YES];
}
}
@end
@interface ElectronProgressBar : NSProgressIndicator
2014-09-17 07:58:08 +00:00
@end
@implementation ElectronProgressBar
2014-09-17 07:58:08 +00:00
- (void)drawRect:(NSRect)dirtyRect {
if (self.style != NSProgressIndicatorBarStyle)
return;
// Draw edges of rounded rect.
NSRect rect = NSInsetRect([self bounds], 1.0, 1.0);
2014-09-18 08:17:49 +00:00
CGFloat radius = rect.size.height / 2;
2018-04-20 18:47:04 +00:00
NSBezierPath* bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
2014-09-18 08:17:49 +00:00
[bezier_path setLineWidth:2.0];
[[NSColor grayColor] set];
[bezier_path stroke];
2014-09-17 07:58:08 +00:00
// Fill the rounded rect.
2014-09-18 08:17:49 +00:00
rect = NSInsetRect(rect, 2.0, 2.0);
radius = rect.size.height / 2;
2018-04-20 18:47:04 +00:00
bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
2014-09-18 08:17:49 +00:00
[bezier_path setLineWidth:1.0];
[bezier_path addClip];
2014-09-17 07:58:08 +00:00
// Calculate the progress width.
2018-04-20 18:47:04 +00:00
rect.size.width =
floor(rect.size.width * ([self doubleValue] / [self maxValue]));
2014-09-17 07:58:08 +00:00
// Fill the progress bar with color blue.
[[NSColor colorWithSRGBRed:0.2 green:0.6 blue:1 alpha:1] set];
NSRectFill(rect);
}
@end
namespace gin {
2018-04-20 18:47:04 +00:00
template <>
struct Converter<electron::NativeWindowMac::TitleBarStyle> {
2018-04-20 18:47:04 +00:00
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
electron::NativeWindowMac::TitleBarStyle* out) {
using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
std::string title_bar_style;
if (!ConvertFromV8(isolate, val, &title_bar_style))
return false;
if (title_bar_style == "hidden") {
*out = TitleBarStyle::HIDDEN;
} else if (title_bar_style == "hiddenInset") {
*out = TitleBarStyle::HIDDEN_INSET;
} else if (title_bar_style == "customButtonsOnHover") {
*out = TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER;
} else {
return false;
}
return true;
}
};
template <>
struct Converter<electron::NativeWindowMac::VisualEffectState> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
electron::NativeWindowMac::VisualEffectState* out) {
using VisualEffectState = electron::NativeWindowMac::VisualEffectState;
std::string visual_effect_state;
if (!ConvertFromV8(isolate, val, &visual_effect_state))
return false;
if (visual_effect_state == "followWindow") {
*out = VisualEffectState::FOLLOW_WINDOW;
} else if (visual_effect_state == "active") {
*out = VisualEffectState::ACTIVE;
} else if (visual_effect_state == "inactive") {
*out = VisualEffectState::INACTIVE;
} else {
return false;
}
return true;
}
};
} // namespace gin
namespace electron {
2013-04-12 07:04:46 +00:00
2018-05-02 11:43:45 +00:00
namespace {
bool IsFramelessWindow(NSView* view) {
NSWindow* nswindow = [view window];
if (![nswindow respondsToSelector:@selector(shell)])
return false;
NativeWindow* window = [static_cast<ElectronNSWindow*>(nswindow) shell];
2018-05-02 11:43:45 +00:00
return window && !window->has_frame();
}
IMP original_set_frame_size = nullptr;
IMP original_view_did_move_to_superview = nullptr;
// This method is directly called by NSWindow during a window resize on OSX
// 10.10.0, beta 2. We must override it to prevent the content view from
// shrinking.
void SetFrameSize(NSView* self, SEL _cmd, NSSize size) {
if (!IsFramelessWindow(self)) {
auto original =
reinterpret_cast<decltype(&SetFrameSize)>(original_set_frame_size);
return original(self, _cmd, size);
}
// For frameless window, resize the view to cover full window.
if ([self superview])
size = [[self superview] bounds].size;
auto super_impl = reinterpret_cast<decltype(&SetFrameSize)>(
[[self superclass] instanceMethodForSelector:_cmd]);
super_impl(self, _cmd, size);
}
// The contentView gets moved around during certain full-screen operations.
// This is less than ideal, and should eventually be removed.
void ViewDidMoveToSuperview(NSView* self, SEL _cmd) {
if (!IsFramelessWindow(self)) {
// [BridgedContentView viewDidMoveToSuperview];
auto original = reinterpret_cast<decltype(&ViewDidMoveToSuperview)>(
original_view_did_move_to_superview);
if (original)
original(self, _cmd);
return;
2018-05-02 11:43:45 +00:00
}
[self setFrame:[[self superview] bounds]];
}
} // namespace
NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
2018-04-08 11:20:43 +00:00
NativeWindow* parent)
: NativeWindow(options, parent), root_view_(new RootViewMac(this)) {
ui::NativeTheme::GetInstanceForNativeUi()->AddObserver(this);
int width = 800, height = 600;
options.Get(options::kWidth, &width);
options.Get(options::kHeight, &height);
2013-04-12 07:04:46 +00:00
NSRect main_screen_rect = [[[NSScreen screens] firstObject] frame];
2018-04-24 08:23:08 +00:00
gfx::Rect bounds(round((NSWidth(main_screen_rect) - width) / 2),
round((NSHeight(main_screen_rect) - height) / 2), width,
2018-04-24 08:23:08 +00:00
height);
2018-04-24 10:00:44 +00:00
options.Get(options::kResizable, &resizable_);
options.Get(options::kTitleBarStyle, &title_bar_style_);
options.Get(options::kZoomToPageWidth, &zoom_to_page_width_);
options.Get(options::kFullscreenWindowTitle, &fullscreen_window_title_);
options.Get(options::kSimpleFullScreen, &always_simple_fullscreen_);
options.Get(options::kTrafficLightPosition, &traffic_light_position_);
options.Get(options::kVisualEffectState, &visual_effect_state_);
bool minimizable = true;
options.Get(options::kMinimizable, &minimizable);
2016-01-23 07:51:52 +00:00
2016-01-22 21:24:33 +00:00
bool maximizable = true;
options.Get(options::kMaximizable, &maximizable);
bool closable = true;
options.Get(options::kClosable, &closable);
2017-03-30 20:47:11 +00:00
std::string tabbingIdentifier;
options.Get(options::kTabbingIdentifier, &tabbingIdentifier);
2017-03-27 14:15:17 +00:00
std::string windowType;
options.Get(options::kType, &windowType);
bool useStandardWindow = true;
// eventually deprecate separate "standardWindow" option in favor of
// standard / textured window types
options.Get(options::kStandardWindow, &useStandardWindow);
if (windowType == "textured") {
useStandardWindow = false;
}
NSUInteger styleMask = NSWindowStyleMaskTitled;
bool customOnHover =
title_bar_style_ == TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER;
if (customOnHover && (!useStandardWindow || transparent() || !has_frame()))
styleMask = NSWindowStyleMaskFullSizeContentView;
if (minimizable)
styleMask |= NSMiniaturizableWindowMask;
if (closable)
styleMask |= NSWindowStyleMaskClosable;
if (resizable_)
styleMask |= NSResizableWindowMask;
// The window without titlebar is treated the same with frameless window.
if (title_bar_style_ != TitleBarStyle::NORMAL)
set_has_frame(false);
if (!useStandardWindow || transparent() || !has_frame())
styleMask |= NSTexturedBackgroundWindowMask;
2018-04-24 08:23:08 +00:00
// Create views::Widget and assign window_ with it.
// TODO(zcbenz): Get rid of the window_ in future.
views::Widget::InitParams params;
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
params.bounds = bounds;
params.delegate = this;
params.type = views::Widget::InitParams::TYPE_WINDOW;
params.native_widget = new ElectronNativeWidgetMac(this, styleMask, widget());
widget()->Init(std::move(params));
window_ = static_cast<ElectronNSWindow*>(
widget()->GetNativeWindow().GetNativeNSWindow());
2018-04-24 08:23:08 +00:00
[window_ setEnableLargerThanScreen:enable_larger_than_screen()];
window_delegate_.reset([[ElectronNSWindowDelegate alloc] initWithShell:this]);
[window_ setDelegate:window_delegate_];
// Only use native parent window for non-modal windows.
if (parent && !is_modal()) {
SetParentWindow(parent);
2016-06-19 03:06:08 +00:00
}
if (transparent()) {
// Setting the background color to clear will also hide the shadow.
2014-12-23 19:17:56 +00:00
[window_ setBackgroundColor:[NSColor clearColor]];
}
2015-04-25 02:35:28 +00:00
if (windowType == "desktop") {
[window_ setLevel:kCGDesktopWindowLevel - 1];
2015-11-20 05:06:42 +00:00
[window_ setDisableKeyOrMainWindow:YES];
2018-04-20 18:47:04 +00:00
[window_ setCollectionBehavior:(NSWindowCollectionBehaviorCanJoinAllSpaces |
NSWindowCollectionBehaviorStationary |
NSWindowCollectionBehaviorIgnoresCycle)];
}
2016-06-13 08:10:28 +00:00
bool focusable;
if (options.Get(options::kFocusable, &focusable) && !focusable)
[window_ setDisableKeyOrMainWindow:YES];
if (transparent() || !has_frame()) {
// Don't show title bar.
[window_ setTitlebarAppearsTransparent:YES];
[window_ setTitleVisibility:NSWindowTitleHidden];
// Remove non-transparent corners, see http://git.io/vfonD.
[window_ setOpaque:NO];
}
2014-12-23 19:17:56 +00:00
// Create a tab only if tabbing identifier is specified and window has
// a native title bar.
2017-03-30 20:47:11 +00:00
if (tabbingIdentifier.empty() || transparent() || !has_frame()) {
if (@available(macOS 10.12, *)) {
[window_ setTabbingMode:NSWindowTabbingModeDisallowed];
}
} else {
if (@available(macOS 10.12, *)) {
2017-03-30 20:47:11 +00:00
[window_ setTabbingIdentifier:base::SysUTF8ToNSString(tabbingIdentifier)];
2017-03-27 14:15:17 +00:00
}
}
// Hide the title bar background
if (title_bar_style_ != TitleBarStyle::NORMAL) {
[window_ setTitlebarAppearsTransparent:YES];
}
// Hide the title bar.
if (title_bar_style_ == TitleBarStyle::HIDDEN_INSET) {
base::scoped_nsobject<NSToolbar> toolbar(
[[NSToolbar alloc] initWithIdentifier:@"titlebarStylingToolbar"]);
[toolbar setShowsBaselineSeparator:NO];
[window_ setToolbar:toolbar];
}
2018-04-24 10:00:44 +00:00
// Resize to content bounds.
bool use_content_size = false;
options.Get(options::kUseContentSize, &use_content_size);
2018-04-24 10:00:44 +00:00
if (!has_frame() || use_content_size)
SetContentSize(gfx::Size(width, height));
// Enable the NSView to accept first mouse event.
bool acceptsFirstMouse = false;
options.Get(options::kAcceptFirstMouse, &acceptsFirstMouse);
[window_ setAcceptsFirstMouse:acceptsFirstMouse];
2013-04-12 07:04:46 +00:00
2015-04-27 04:08:22 +00:00
// Disable auto-hiding cursor.
bool disableAutoHideCursor = false;
options.Get(options::kDisableAutoHideCursor, &disableAutoHideCursor);
2015-04-27 04:08:22 +00:00
[window_ setDisableAutoHideCursor:disableAutoHideCursor];
2016-01-25 07:02:43 +00:00
// Use an NSEvent monitor to listen for the wheel event.
BOOL __block began = NO;
wheel_event_monitor_ = [NSEvent
2018-04-20 18:47:04 +00:00
addLocalMonitorForEventsMatchingMask:NSScrollWheelMask
handler:^(NSEvent* event) {
if ([[event window] windowNumber] !=
[window_ windowNumber])
return event;
if (!began && (([event phase] ==
NSEventPhaseMayBegin) ||
([event phase] ==
NSEventPhaseBegan))) {
this->NotifyWindowScrollTouchBegin();
began = YES;
} else if (began &&
(([event phase] ==
NSEventPhaseEnded) ||
([event phase] ==
NSEventPhaseCancelled))) {
this->NotifyWindowScrollTouchEnd();
began = NO;
}
return event;
}];
// Set maximizable state last to ensure zoom button does not get reset
// by calls to other APIs.
2016-08-04 23:47:03 +00:00
SetMaximizable(maximizable);
2018-04-10 08:30:46 +00:00
// Default content view.
SetContentView(new views::View());
AddContentViewLayers(minimizable, closable);
original_frame_ = [window_ frame];
original_level_ = [window_ level];
2018-04-08 11:20:43 +00:00
}
NativeWindowMac::~NativeWindowMac() {}
void NativeWindowMac::Cleanup() {
DCHECK(!IsClosed());
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
[NSEvent removeMonitor:wheel_event_monitor_];
}
void NativeWindowMac::RedrawTrafficLights() {
// Ensure maximizable options retain pre-existing state.
SetMaximizable(maximizable_);
if (!traffic_light_position_.x() && !traffic_light_position_.y()) {
return;
}
if (IsFullscreen())
return;
NSWindow* window = window_;
NSButton* close = [window standardWindowButton:NSWindowCloseButton];
NSButton* miniaturize =
[window standardWindowButton:NSWindowMiniaturizeButton];
NSButton* zoom = [window standardWindowButton:NSWindowZoomButton];
// Safety check just in case apple changes the view structure in a macOS
// update
DCHECK(close.superview);
DCHECK(close.superview.superview);
if (!close.superview || !close.superview.superview)
return;
NSView* titleBarContainerView = close.superview.superview;
// Hide the container when exiting fullscreen, otherwise traffic light buttons
// jump
if (exiting_fullscreen_) {
[titleBarContainerView setHidden:YES];
return;
}
[titleBarContainerView setHidden:NO];
CGFloat buttonHeight = [close frame].size.height;
CGFloat titleBarFrameHeight = buttonHeight + traffic_light_position_.y();
CGRect titleBarRect = titleBarContainerView.frame;
CGFloat titleBarWidth = NSWidth(titleBarRect);
titleBarRect.size.height = titleBarFrameHeight;
titleBarRect.origin.y = window.frame.size.height - titleBarFrameHeight;
[titleBarContainerView setFrame:titleBarRect];
BOOL isRTL = [titleBarContainerView userInterfaceLayoutDirection] ==
NSUserInterfaceLayoutDirectionRightToLeft;
NSArray* windowButtons = @[ close, miniaturize, zoom ];
const CGFloat space_between =
[miniaturize frame].origin.x - [close frame].origin.x;
for (NSUInteger i = 0; i < windowButtons.count; i++) {
NSView* view = [windowButtons objectAtIndex:i];
CGRect rect = [view frame];
if (isRTL) {
CGFloat buttonWidth = NSWidth(rect);
// origin is always top-left, even in RTL
rect.origin.x = titleBarWidth - traffic_light_position_.x() +
(i * space_between) - buttonWidth;
} else {
rect.origin.x = traffic_light_position_.x() + (i * space_between);
}
rect.origin.y = (titleBarFrameHeight - rect.size.height) / 2;
[view setFrameOrigin:rect.origin];
}
}
void NativeWindowMac::SetContentView(views::View* view) {
views::View* root_view = GetContentsView();
if (content_view())
root_view->RemoveChildView(content_view());
set_content_view(view);
root_view->AddChildView(content_view());
if (buttons_view_) {
// Ensure the buttons view are always floated on the top.
[buttons_view_ removeFromSuperview];
[[window_ contentView] addSubview:buttons_view_];
}
root_view->Layout();
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::Close() {
if (!IsClosable()) {
WindowList::WindowCloseCancelled(this);
return;
}
2014-04-11 04:45:48 +00:00
[window_ performClose:nil];
// Closing a sheet doesn't trigger windowShouldClose,
// so we need to manually call it ourselves here.
if (is_modal() && parent() && IsVisible()) {
NotifyWindowCloseButtonClicked();
}
2013-04-12 07:04:46 +00:00
}
2013-05-01 08:12:00 +00:00
void NativeWindowMac::CloseImmediately() {
// Retain the child window before closing it. If the last reference to the
// NSWindow goes away inside -[NSWindow close], then bad stuff can happen.
// See e.g. http://crbug.com/616701.
base::scoped_nsobject<NSWindow> child_window(window_,
base::scoped_policy::RETAIN);
2014-04-11 04:45:48 +00:00
[window_ close];
2013-05-01 08:12:00 +00:00
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::Focus(bool focus) {
if (!IsVisible())
return;
if (focus) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:NO];
2014-04-11 04:45:48 +00:00
[window_ makeKeyAndOrderFront:nil];
} else {
2014-04-11 04:45:48 +00:00
[window_ orderBack:nil];
}
2013-04-12 07:04:46 +00:00
}
2013-05-16 14:56:52 +00:00
bool NativeWindowMac::IsFocused() {
2014-04-11 04:45:48 +00:00
return [window_ isKeyWindow];
2013-05-16 14:56:52 +00:00
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::Show() {
if (is_modal() && parent()) {
NSWindow* window = parent()->GetNativeWindow().GetNativeNSWindow();
2016-09-29 18:49:24 +00:00
if ([window_ sheetParent] == nil)
[window beginSheet:window_
completionHandler:^(NSModalResponse){
}];
return;
}
2017-09-14 05:43:22 +00:00
// Reattach the window to the parent to actually show it.
if (parent())
InternalSetParentWindow(parent(), true);
// This method is supposed to put focus on window, however if the app does not
// have focus then "makeKeyAndOrderFront" will only show the window.
[NSApp activateIgnoringOtherApps:YES];
2014-10-17 14:51:20 +00:00
[window_ makeKeyAndOrderFront:nil];
}
void NativeWindowMac::ShowInactive() {
2017-05-01 21:01:48 +00:00
// Reattach the window to the parent to actually show it.
if (parent())
InternalSetParentWindow(parent(), true);
2017-09-14 05:43:22 +00:00
[window_ orderFrontRegardless];
2013-04-12 07:04:46 +00:00
}
void NativeWindowMac::Hide() {
if (is_modal() && parent()) {
[window_ orderOut:nil];
[parent()->GetNativeWindow().GetNativeNSWindow() endSheet:window_];
return;
}
2017-09-14 05:43:22 +00:00
// Deattach the window from the parent before.
if (parent())
InternalSetParentWindow(parent(), false);
2014-04-11 04:45:48 +00:00
[window_ orderOut:nil];
2013-04-12 07:04:46 +00:00
}
2013-10-03 00:27:59 +00:00
bool NativeWindowMac::IsVisible() {
bool occluded = [window_ occlusionState] == NSWindowOcclusionStateVisible;
// For a window to be visible, it must be visible to the user in the
// foreground of the app, which means that it should not be minimized or
// occluded
return [window_ isVisible] && !occluded && !IsMinimized();
2013-10-03 00:27:59 +00:00
}
void NativeWindowMac::SetExitingFullScreen(bool flag) {
exiting_fullscreen_ = flag;
}
void NativeWindowMac::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) {
base::PostTask(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&NativeWindow::RedrawTrafficLights, GetWeakPtr()));
}
bool NativeWindowMac::IsEnabled() {
return [window_ attachedSheet] == nil;
}
void NativeWindowMac::SetEnabled(bool enable) {
2018-02-06 13:30:33 +00:00
if (enable) {
[window_ endSheet:[window_ attachedSheet]];
} else {
2018-04-20 18:47:04 +00:00
[window_ beginSheet:window_
completionHandler:^(NSModalResponse returnCode) {
NSLog(@"modal enabled");
return;
}];
}
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::Maximize() {
if (IsMaximized())
return;
// Take note of the current window size
if (IsNormal())
original_frame_ = [window_ frame];
2014-04-11 04:45:48 +00:00
[window_ zoom:nil];
2013-04-12 07:04:46 +00:00
}
void NativeWindowMac::Unmaximize() {
if (!IsMaximized())
return;
2014-04-11 04:45:48 +00:00
[window_ zoom:nil];
2013-04-12 07:04:46 +00:00
}
2014-06-28 01:17:37 +00:00
bool NativeWindowMac::IsMaximized() {
if (([window_ styleMask] & NSWindowStyleMaskResizable) != 0) {
return [window_ isZoomed];
} else {
NSRect rectScreen = [[NSScreen mainScreen] visibleFrame];
NSRect rectWindow = [window_ frame];
return (rectScreen.origin.x == rectWindow.origin.x &&
rectScreen.origin.y == rectWindow.origin.y &&
rectScreen.size.width == rectWindow.size.width &&
rectScreen.size.height == rectWindow.size.height);
}
2014-05-14 21:58:49 +00:00
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::Minimize() {
if (IsMinimized())
return;
// Take note of the current window size
if (IsNormal())
original_frame_ = [window_ frame];
2014-04-11 04:45:48 +00:00
[window_ miniaturize:nil];
2013-04-12 07:04:46 +00:00
}
void NativeWindowMac::Restore() {
2014-04-11 04:45:48 +00:00
[window_ deminiaturize:nil];
2013-04-12 07:04:46 +00:00
}
2014-07-26 05:58:26 +00:00
bool NativeWindowMac::IsMinimized() {
return [window_ isMiniaturized];
}
2014-11-25 06:34:14 +00:00
void NativeWindowMac::SetFullScreen(bool fullscreen) {
if (fullscreen == IsFullscreen())
2013-04-12 07:04:46 +00:00
return;
// Take note of the current window size
if (IsNormal())
original_frame_ = [window_ frame];
[window_ toggleFullScreenMode:nil];
2013-04-12 07:04:46 +00:00
}
2015-04-21 13:35:36 +00:00
bool NativeWindowMac::IsFullscreen() const {
return [window_ styleMask] & NSWindowStyleMaskFullScreen;
2013-04-12 07:04:46 +00:00
}
2016-01-15 04:54:12 +00:00
void NativeWindowMac::SetBounds(const gfx::Rect& bounds, bool animate) {
// Do nothing if in fullscreen mode.
if (IsFullscreen())
return;
2016-07-07 11:02:18 +00:00
// Check size constraints since setFrame does not check it.
gfx::Size size = bounds.size();
size.SetToMax(GetMinimumSize());
gfx::Size max_size = GetMaximumSize();
if (!max_size.IsEmpty())
size.SetToMin(max_size);
NSRect cocoa_bounds = NSMakeRect(bounds.x(), 0, size.width(), size.height());
// Flip coordinates based on the primary screen.
NSScreen* screen = [[NSScreen screens] firstObject];
2018-04-20 18:47:04 +00:00
cocoa_bounds.origin.y = NSHeight([screen frame]) - size.height() - bounds.y();
2016-01-15 04:54:12 +00:00
[window_ setFrame:cocoa_bounds display:YES animate:animate];
}
gfx::Rect NativeWindowMac::GetBounds() {
NSRect frame = [window_ frame];
gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
NSScreen* screen = [[NSScreen screens] firstObject];
bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
return bounds;
}
bool NativeWindowMac::IsNormal() {
return NativeWindow::IsNormal() && !IsSimpleFullScreen();
}
gfx::Rect NativeWindowMac::GetNormalBounds() {
if (IsNormal()) {
return GetBounds();
}
NSRect frame = original_frame_;
gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame));
NSScreen* screen = [[NSScreen screens] firstObject];
bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame));
return bounds;
// Works on OS_WIN !
// return widget()->GetRestoredBounds();
}
void NativeWindowMac::SetContentSizeConstraints(
const extensions::SizeConstraints& size_constraints) {
auto convertSize = [this](const gfx::Size& size) {
// Our frameless window still has titlebar attached, so setting contentSize
// will result in actual content size being larger.
if (!has_frame()) {
NSRect frame = NSMakeRect(0, 0, size.width(), size.height());
NSRect content = [window_ originalContentRectForFrameRect:frame];
return content.size;
} else {
return NSMakeSize(size.width(), size.height());
}
};
2014-04-11 04:45:48 +00:00
NSView* content = [window_ contentView];
if (size_constraints.HasMinimumSize()) {
NSSize min_size = convertSize(size_constraints.GetMinimumSize());
[window_ setContentMinSize:[content convertSize:min_size toView:nil]];
}
if (size_constraints.HasMaximumSize()) {
NSSize max_size = convertSize(size_constraints.GetMaximumSize());
[window_ setContentMaxSize:[content convertSize:max_size toView:nil]];
}
NativeWindow::SetContentSizeConstraints(size_constraints);
2013-04-18 07:38:04 +00:00
}
bool NativeWindowMac::MoveAbove(const std::string& sourceId) {
const content::DesktopMediaID id = content::DesktopMediaID::Parse(sourceId);
if (id.type != content::DesktopMediaID::TYPE_WINDOW)
return false;
// Check if the window source is valid.
const CGWindowID window_id = id.id;
if (!webrtc::GetWindowOwnerPid(window_id))
return false;
[window_ orderWindow:NSWindowAbove relativeTo:id.id];
return true;
}
2018-04-20 18:47:04 +00:00
void NativeWindowMac::MoveTop() {
[window_ orderWindow:NSWindowAbove relativeTo:0];
}
2018-04-24 10:00:44 +00:00
2013-04-12 07:04:46 +00:00
void NativeWindowMac::SetResizable(bool resizable) {
SetStyleMask(resizable, NSWindowStyleMaskResizable);
2013-04-12 07:04:46 +00:00
}
2013-04-18 07:38:04 +00:00
bool NativeWindowMac::IsResizable() {
return [window_ styleMask] & NSWindowStyleMaskResizable;
2013-04-18 07:38:04 +00:00
}
void NativeWindowMac::SetAspectRatio(double aspect_ratio,
2016-05-23 01:36:05 +00:00
const gfx::Size& extra_size) {
2016-07-11 07:43:01 +00:00
NativeWindow::SetAspectRatio(aspect_ratio, extra_size);
2016-07-11 07:43:01 +00:00
// Reset the behaviour to default if aspect_ratio is set to 0 or less.
if (aspect_ratio > 0.0)
[window_ setAspectRatio:NSMakeSize(aspect_ratio, 1.0)];
else
[window_ setResizeIncrements:NSMakeSize(1.0, 1.0)];
}
2016-10-26 00:47:22 +00:00
void NativeWindowMac::PreviewFile(const std::string& path,
const std::string& display_name) {
preview_item_.reset([[ElectronPreviewItem alloc]
initWithURL:[NSURL fileURLWithPath:base::SysUTF8ToNSString(path)]
title:base::SysUTF8ToNSString(display_name)]);
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
2016-10-12 01:08:01 +00:00
}
2016-11-21 18:30:13 +00:00
void NativeWindowMac::CloseFilePreview() {
if ([QLPreviewPanel sharedPreviewPanelExists]) {
[[QLPreviewPanel sharedPreviewPanel] close];
}
}
void NativeWindowMac::SetMovable(bool movable) {
[window_ setMovable:movable];
}
bool NativeWindowMac::IsMovable() {
return [window_ isMovable];
}
void NativeWindowMac::SetMinimizable(bool minimizable) {
2016-01-23 11:12:19 +00:00
SetStyleMask(minimizable, NSMiniaturizableWindowMask);
}
bool NativeWindowMac::IsMinimizable() {
return [window_ styleMask] & NSMiniaturizableWindowMask;
}
2016-01-22 21:24:33 +00:00
void NativeWindowMac::SetMaximizable(bool maximizable) {
maximizable_ = maximizable;
2016-01-22 21:24:33 +00:00
[[window_ standardWindowButton:NSWindowZoomButton] setEnabled:maximizable];
}
bool NativeWindowMac::IsMaximizable() {
return [[window_ standardWindowButton:NSWindowZoomButton] isEnabled];
}
2016-01-23 07:47:37 +00:00
void NativeWindowMac::SetFullScreenable(bool fullscreenable) {
2018-04-20 18:47:04 +00:00
SetCollectionBehavior(fullscreenable,
NSWindowCollectionBehaviorFullScreenPrimary);
2016-01-23 11:12:19 +00:00
// On EL Capitan this flag is required to hide fullscreen button.
2018-04-20 18:47:04 +00:00
SetCollectionBehavior(!fullscreenable,
NSWindowCollectionBehaviorFullScreenAuxiliary);
2016-01-22 21:24:33 +00:00
}
2016-01-23 07:47:37 +00:00
bool NativeWindowMac::IsFullScreenable() {
2016-01-23 11:12:19 +00:00
NSUInteger collectionBehavior = [window_ collectionBehavior];
return collectionBehavior & NSWindowCollectionBehaviorFullScreenPrimary;
2016-01-22 21:24:33 +00:00
}
void NativeWindowMac::SetClosable(bool closable) {
SetStyleMask(closable, NSWindowStyleMaskClosable);
}
bool NativeWindowMac::IsClosable() {
return [window_ styleMask] & NSWindowStyleMaskClosable;
}
void NativeWindowMac::SetAlwaysOnTop(ui::ZOrderLevel z_order,
const std::string& level_name,
int relative_level) {
if (z_order == ui::ZOrderLevel::kNormal) {
SetWindowLevel(NSNormalWindowLevel);
return;
2016-09-22 16:22:28 +00:00
}
int level = NSNormalWindowLevel;
if (level_name == "floating") {
level = NSFloatingWindowLevel;
} else if (level_name == "torn-off-menu") {
level = NSTornOffMenuWindowLevel;
} else if (level_name == "modal-panel") {
level = NSModalPanelWindowLevel;
} else if (level_name == "main-menu") {
level = NSMainMenuWindowLevel;
} else if (level_name == "status") {
level = NSStatusWindowLevel;
} else if (level_name == "pop-up-menu") {
level = NSPopUpMenuWindowLevel;
} else if (level_name == "screen-saver") {
level = NSScreenSaverWindowLevel;
} else if (level_name == "dock") {
// Deprecated by macOS, but kept for backwards compatibility
level = NSDockWindowLevel;
}
SetWindowLevel(level + relative_level);
}
void NativeWindowMac::SetWindowLevel(int unbounded_level) {
int level = std::min(
std::max(unbounded_level, CGWindowLevelForKey(kCGMinimumWindowLevelKey)),
CGWindowLevelForKey(kCGMaximumWindowLevelKey));
ui::ZOrderLevel z_order_level = level == NSNormalWindowLevel
? ui::ZOrderLevel::kNormal
: ui::ZOrderLevel::kFloatingWindow;
bool did_z_order_level_change = z_order_level != GetZOrderLevel();
was_maximizable_ = IsMaximizable();
// We need to explicitly keep the NativeWidget up to date, since it stores the
// window level in a local variable, rather than reading it from the NSWindow.
// Unfortunately, it results in a second setLevel call. It's not ideal, but we
// don't expect this to cause any user-visible jank.
widget()->SetZOrderLevel(z_order_level);
[window_ setLevel:level];
// Set level will make the zoom button revert to default, probably
// a bug of Cocoa or macOS.
SetMaximizable(was_maximizable_);
// This must be notified at the very end or IsAlwaysOnTop
// will not yet have been updated to reflect the new status
if (did_z_order_level_change)
NativeWindow::NotifyWindowAlwaysOnTopChanged();
2013-04-12 07:04:46 +00:00
}
ui::ZOrderLevel NativeWindowMac::GetZOrderLevel() {
return widget()->GetZOrderLevel();
2013-04-18 07:38:04 +00:00
}
void NativeWindowMac::Center() {
2014-04-11 04:45:48 +00:00
[window_ center];
2013-04-12 07:04:46 +00:00
}
void NativeWindowMac::Invalidate() {
[window_ flushWindow];
2017-02-14 19:09:15 +00:00
[[window_ contentView] setNeedsDisplay:YES];
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::SetTitle(const std::string& title) {
[window_ setTitle:base::SysUTF8ToNSString(title)];
if (title_bar_style_ == TitleBarStyle::HIDDEN) {
RedrawTrafficLights();
}
2013-04-12 07:04:46 +00:00
}
2013-04-18 06:30:05 +00:00
std::string NativeWindowMac::GetTitle() {
2018-04-20 18:47:04 +00:00
return base::SysNSStringToUTF8([window_ title]);
2013-04-18 06:30:05 +00:00
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::FlashFrame(bool flash) {
if (flash) {
attention_request_id_ = [NSApp requestUserAttention:NSInformationalRequest];
} else {
[NSApp cancelUserAttentionRequest:attention_request_id_];
attention_request_id_ = 0;
}
}
2018-04-20 18:47:04 +00:00
void NativeWindowMac::SetSkipTaskbar(bool skip) {}
bool NativeWindowMac::IsExcludedFromShownWindowsMenu() {
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
return [window isExcludedFromWindowsMenu];
}
void NativeWindowMac::SetExcludedFromShownWindowsMenu(bool excluded) {
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
[window setExcludedFromWindowsMenu:excluded];
}
void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
if (simple_fullscreen && !is_simple_fullscreen_) {
is_simple_fullscreen_ = true;
// Take note of the current window size and level
if (IsNormal()) {
original_frame_ = [window_ frame];
original_level_ = [window_ level];
}
simple_fullscreen_options_ = [NSApp currentSystemPresentationOptions];
simple_fullscreen_mask_ = [window styleMask];
2018-04-20 18:47:04 +00:00
// We can simulate the pre-Lion fullscreen by auto-hiding the dock and menu
// bar
NSApplicationPresentationOptions options =
NSApplicationPresentationAutoHideDock |
NSApplicationPresentationAutoHideMenuBar;
[NSApp setPresentationOptions:options];
was_maximizable_ = IsMaximizable();
was_movable_ = IsMovable();
NSRect fullscreenFrame = [window.screen frame];
// If our app has dock hidden, set the window level higher so another app's
// menu bar doesn't appear on top of our fullscreen app.
if ([[NSRunningApplication currentApplication] activationPolicy] !=
NSApplicationActivationPolicyRegular) {
window.level = NSPopUpMenuWindowLevel;
}
2018-04-20 18:47:04 +00:00
if (!fullscreen_window_title()) {
// Hide the titlebar
SetStyleMask(false, NSWindowStyleMaskTitled);
2020-01-29 09:42:25 +00:00
// Resize the window to accommodate the _entire_ screen size
2018-04-20 18:47:04 +00:00
fullscreenFrame.size.height -=
[[[NSApplication sharedApplication] mainMenu] menuBarHeight];
} else if (!window_button_visibility_.has_value()) {
// Lets keep previous behaviour - hide window controls in titled
// fullscreen mode when not specified otherwise.
[[window standardWindowButton:NSWindowZoomButton] setHidden:YES];
[[window standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
[[window standardWindowButton:NSWindowCloseButton] setHidden:YES];
}
2018-04-20 18:47:04 +00:00
[window setFrame:fullscreenFrame display:YES animate:YES];
// Fullscreen windows can't be resized, minimized, maximized, or moved
SetMinimizable(false);
SetResizable(false);
SetMaximizable(false);
SetMovable(false);
} else if (!simple_fullscreen && is_simple_fullscreen_) {
is_simple_fullscreen_ = false;
2018-04-20 18:47:04 +00:00
if (!fullscreen_window_title()) {
// Restore the titlebar
SetStyleMask(true, NSWindowStyleMaskTitled);
}
// Restore window controls visibility state
const bool window_button_hidden = !window_button_visibility_.value_or(true);
[[window standardWindowButton:NSWindowZoomButton]
setHidden:window_button_hidden];
[[window standardWindowButton:NSWindowMiniaturizeButton]
setHidden:window_button_hidden];
[[window standardWindowButton:NSWindowCloseButton]
setHidden:window_button_hidden];
2018-04-20 18:47:04 +00:00
[window setFrame:original_frame_ display:YES animate:YES];
window.level = original_level_;
[NSApp setPresentationOptions:simple_fullscreen_options_];
// Restore original style mask
ScopedDisableResize disable_resize;
[window_ setStyleMask:simple_fullscreen_mask_];
// Restore window manipulation abilities
SetMaximizable(was_maximizable_);
SetMovable(was_movable_);
}
}
bool NativeWindowMac::IsSimpleFullScreen() {
return is_simple_fullscreen_;
}
2013-04-12 07:04:46 +00:00
void NativeWindowMac::SetKiosk(bool kiosk) {
if (kiosk && !is_kiosk_) {
kiosk_options_ = [NSApp currentSystemPresentationOptions];
2013-04-12 07:04:46 +00:00
NSApplicationPresentationOptions options =
NSApplicationPresentationHideDock |
NSApplicationPresentationHideMenuBar |
NSApplicationPresentationDisableAppleMenu |
NSApplicationPresentationDisableProcessSwitching |
NSApplicationPresentationDisableForceQuit |
NSApplicationPresentationDisableSessionTermination |
2013-04-12 07:04:46 +00:00
NSApplicationPresentationDisableHideApplication;
[NSApp setPresentationOptions:options];
is_kiosk_ = true;
2017-01-13 23:04:51 +00:00
was_fullscreen_ = IsFullscreen();
2018-04-20 18:47:04 +00:00
if (!was_fullscreen_)
SetFullScreen(true);
} else if (!kiosk && is_kiosk_) {
is_kiosk_ = false;
2018-04-20 18:47:04 +00:00
if (!was_fullscreen_)
SetFullScreen(false);
[NSApp setPresentationOptions:kiosk_options_];
2013-04-12 07:04:46 +00:00
}
}
bool NativeWindowMac::IsKiosk() {
return is_kiosk_;
}
void NativeWindowMac::SetBackgroundColor(SkColor color) {
2018-04-24 11:08:14 +00:00
base::ScopedCFTypeRef<CGColorRef> cgcolor(
skia::CGColorCreateFromSkColor(color));
[[[window_ contentView] layer] setBackgroundColor:cgcolor];
2015-10-23 03:35:33 +00:00
}
SkColor NativeWindowMac::GetBackgroundColor() {
return skia::CGColorRefToSkColor(
[[[window_ contentView] layer] backgroundColor]);
}
void NativeWindowMac::SetHasShadow(bool has_shadow) {
[window_ setHasShadow:has_shadow];
}
bool NativeWindowMac::HasShadow() {
return [window_ hasShadow];
}
2017-09-29 02:26:02 +00:00
void NativeWindowMac::SetOpacity(const double opacity) {
const double boundedOpacity = base::ClampToRange(opacity, 0.0, 1.0);
[window_ setAlphaValue:boundedOpacity];
2017-09-29 02:26:02 +00:00
}
double NativeWindowMac::GetOpacity() {
return [window_ alphaValue];
}
void NativeWindowMac::SetRepresentedFilename(const std::string& filename) {
[window_ setRepresentedFilename:base::SysUTF8ToNSString(filename)];
}
std::string NativeWindowMac::GetRepresentedFilename() {
return base::SysNSStringToUTF8([window_ representedFilename]);
}
void NativeWindowMac::SetDocumentEdited(bool edited) {
[window_ setDocumentEdited:edited];
}
bool NativeWindowMac::IsDocumentEdited() {
return [window_ isDocumentEdited];
}
void NativeWindowMac::SetIgnoreMouseEvents(bool ignore, bool forward) {
[window_ setIgnoresMouseEvents:ignore];
if (!ignore) {
SetForwardMouseMessages(NO);
} else {
SetForwardMouseMessages(forward);
}
}
void NativeWindowMac::SetContentProtection(bool enable) {
2018-04-20 18:47:04 +00:00
[window_
setSharingType:enable ? NSWindowSharingNone : NSWindowSharingReadOnly];
}
void NativeWindowMac::SetFocusable(bool focusable) {
// No known way to unfocus the window if it had the focus. Here we do not
// want to call Focus(false) because it moves the window to the back, i.e.
// at the bottom in term of z-order.
[window_ setDisableKeyOrMainWindow:!focusable];
}
void NativeWindowMac::AddBrowserView(NativeBrowserView* view) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
if (!view) {
[CATransaction commit];
Implement initial, experimental BrowserView API Right now, `<webview>` is the only way to embed additional content in a `BrowserWindow`. Unfortunately `<webview>` suffers from a [number of problems](https://github.com/electron/electron/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Awebview%20). To make matters worse, many of these are upstream Chromium bugs instead of Electron-specific bugs. For us at [Figma](https://www.figma.com), the main issue is very slow performance. Despite the upstream improvements to `<webview>` through the OOPIF work, it is probable that there will continue to be `<webview>`-specific bugs in the future. Therefore, this introduces a `<webview>` alternative to called `BrowserView`, which... - is a thin wrapper around `api::WebContents` (so bugs in `BrowserView` will likely also be bugs in `BrowserWindow` web contents) - is instantiated in the main process like `BrowserWindow` (and unlike `<webview>`, which lives in the DOM of a `BrowserWindow` web contents) - needs to be added to a `BrowserWindow` to display something on the screen This implements the most basic API. The API is expected to evolve and change in the near future and has consequently been marked as experimental. Please do not use this API in production unless you are prepared to deal with breaking changes. In the future, we will want to change the API to support multiple `BrowserView`s per window. We will also want to consider z-ordering auto-resizing, and possibly even nested views.
2017-04-11 17:47:30 +00:00
return;
}
add_browser_view(view);
if (view->GetInspectableWebContentsView()) {
auto* native_view = view->GetInspectableWebContentsView()
->GetNativeView()
.GetNativeNSView();
[[window_ contentView] addSubview:native_view
positioned:NSWindowAbove
relativeTo:nil];
native_view.hidden = NO;
}
[CATransaction commit];
Implement initial, experimental BrowserView API Right now, `<webview>` is the only way to embed additional content in a `BrowserWindow`. Unfortunately `<webview>` suffers from a [number of problems](https://github.com/electron/electron/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20label%3Awebview%20). To make matters worse, many of these are upstream Chromium bugs instead of Electron-specific bugs. For us at [Figma](https://www.figma.com), the main issue is very slow performance. Despite the upstream improvements to `<webview>` through the OOPIF work, it is probable that there will continue to be `<webview>`-specific bugs in the future. Therefore, this introduces a `<webview>` alternative to called `BrowserView`, which... - is a thin wrapper around `api::WebContents` (so bugs in `BrowserView` will likely also be bugs in `BrowserWindow` web contents) - is instantiated in the main process like `BrowserWindow` (and unlike `<webview>`, which lives in the DOM of a `BrowserWindow` web contents) - needs to be added to a `BrowserWindow` to display something on the screen This implements the most basic API. The API is expected to evolve and change in the near future and has consequently been marked as experimental. Please do not use this API in production unless you are prepared to deal with breaking changes. In the future, we will want to change the API to support multiple `BrowserView`s per window. We will also want to consider z-ordering auto-resizing, and possibly even nested views.
2017-04-11 17:47:30 +00:00
}
void NativeWindowMac::RemoveBrowserView(NativeBrowserView* view) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
if (!view) {
[CATransaction commit];
return;
}
if (view->GetInspectableWebContentsView())
[view->GetInspectableWebContentsView()->GetNativeView().GetNativeNSView()
removeFromSuperview];
remove_browser_view(view);
[CATransaction commit];
}
void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
InternalSetParentWindow(parent, IsVisible());
}
2017-05-23 09:41:59 +00:00
gfx::NativeView NativeWindowMac::GetNativeView() const {
return [window_ contentView];
}
2017-09-14 05:43:22 +00:00
gfx::NativeWindow NativeWindowMac::GetNativeWindow() const {
2014-04-11 04:45:48 +00:00
return window_;
}
2017-05-23 09:41:59 +00:00
gfx::AcceleratedWidget NativeWindowMac::GetAcceleratedWidget() const {
return [window_ windowNumber];
}
content::DesktopMediaID NativeWindowMac::GetDesktopMediaID() const {
return content::DesktopMediaID(content::DesktopMediaID::TYPE_WINDOW,
GetAcceleratedWidget());
2016-01-07 20:38:35 +00:00
}
NativeWindowHandle NativeWindowMac::GetNativeWindowHandle() const {
return [window_ contentView];
}
2018-04-20 18:47:04 +00:00
void NativeWindowMac::SetProgressBar(double progress,
const NativeWindow::ProgressState state) {
2014-09-17 07:58:08 +00:00
NSDockTile* dock_tile = [NSApp dockTile];
// Sometimes macOS would install a default contentView for dock, we must
// verify whether NSProgressIndicator has been installed.
bool first_time = !dock_tile.contentView ||
[[dock_tile.contentView subviews] count] == 0 ||
![[[dock_tile.contentView subviews] lastObject]
isKindOfClass:[NSProgressIndicator class]];
2018-04-20 18:47:04 +00:00
// For the first time API invoked, we need to create a ContentView in
// DockTile.
if (first_time) {
NSImageView* image_view = [[[NSImageView alloc] init] autorelease];
2014-09-17 07:58:08 +00:00
[image_view setImage:[NSApp applicationIconImage]];
[dock_tile setContentView:image_view];
NSRect frame = NSMakeRect(0.0f, 0.0f, dock_tile.size.width, 15.0);
NSProgressIndicator* progress_indicator =
[[[ElectronProgressBar alloc] initWithFrame:frame] autorelease];
2014-09-17 07:58:08 +00:00
[progress_indicator setStyle:NSProgressIndicatorBarStyle];
[progress_indicator setIndeterminate:NO];
[progress_indicator setBezeled:YES];
[progress_indicator setMinValue:0];
[progress_indicator setMaxValue:1];
[progress_indicator setHidden:NO];
[dock_tile.contentView addSubview:progress_indicator];
2014-09-17 07:58:08 +00:00
}
2018-04-20 18:47:04 +00:00
NSProgressIndicator* progress_indicator = static_cast<NSProgressIndicator*>(
[[[dock_tile contentView] subviews] lastObject]);
2014-09-17 07:58:08 +00:00
if (progress < 0) {
[progress_indicator setHidden:YES];
} else if (progress > 1) {
[progress_indicator setHidden:NO];
2014-09-17 07:58:08 +00:00
[progress_indicator setIndeterminate:YES];
[progress_indicator setDoubleValue:1];
2014-09-17 07:58:08 +00:00
} else {
[progress_indicator setHidden:NO];
2014-09-17 07:58:08 +00:00
[progress_indicator setDoubleValue:progress];
}
[dock_tile display];
}
2015-02-11 01:14:26 +00:00
void NativeWindowMac::SetOverlayIcon(const gfx::Image& overlay,
2018-04-20 18:47:04 +00:00
const std::string& description) {}
2015-02-07 19:56:03 +00:00
void NativeWindowMac::SetVisibleOnAllWorkspaces(bool visible,
bool visibleOnFullScreen) {
// In order for NSWindows to be visible on fullscreen we need to functionally
// mimic app.dock.hide() since Apple changed the underlying functionality of
// NSWindows starting with 10.14 to disallow NSWindows from floating on top of
// fullscreen apps.
ProcessSerialNumber psn = {0, kCurrentProcess};
if (visibleOnFullScreen) {
[window_ setCanHide:NO];
TransformProcessType(&psn, kProcessTransformToUIElementApplication);
} else {
[window_ setCanHide:YES];
TransformProcessType(&psn, kProcessTransformToForegroundApplication);
}
2016-01-23 11:12:19 +00:00
SetCollectionBehavior(visible, NSWindowCollectionBehaviorCanJoinAllSpaces);
SetCollectionBehavior(visibleOnFullScreen,
NSWindowCollectionBehaviorFullScreenAuxiliary);
}
bool NativeWindowMac::IsVisibleOnAllWorkspaces() {
2015-03-27 11:41:07 +00:00
NSUInteger collectionBehavior = [window_ collectionBehavior];
return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces;
}
void NativeWindowMac::SetAutoHideCursor(bool auto_hide) {
[window_ setDisableAutoHideCursor:!auto_hide];
}
void NativeWindowMac::SelectPreviousTab() {
if (@available(macOS 10.12, *)) {
[window_ selectPreviousTab:nil];
}
}
void NativeWindowMac::SelectNextTab() {
if (@available(macOS 10.12, *)) {
[window_ selectNextTab:nil];
}
}
void NativeWindowMac::MergeAllWindows() {
if (@available(macOS 10.12, *)) {
[window_ mergeAllWindows:nil];
}
}
void NativeWindowMac::MoveTabToNewWindow() {
if (@available(macOS 10.12, *)) {
[window_ moveTabToNewWindow:nil];
}
}
void NativeWindowMac::ToggleTabBar() {
if (@available(macOS 10.12, *)) {
[window_ toggleTabBar:nil];
}
}
bool NativeWindowMac::AddTabbedWindow(NativeWindow* window) {
if (window_ == window->GetNativeWindow().GetNativeNSWindow()) {
return false;
} else {
if (@available(macOS 10.12, *))
[window_ addTabbedWindow:window->GetNativeWindow().GetNativeNSWindow()
ordered:NSWindowAbove];
}
return true;
}
bool NativeWindowMac::SetWindowButtonVisibility(bool visible) {
if (title_bar_style_ == TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER) {
return false;
}
window_button_visibility_ = visible;
[[window_ standardWindowButton:NSWindowCloseButton] setHidden:!visible];
[[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:!visible];
[[window_ standardWindowButton:NSWindowZoomButton] setHidden:!visible];
return true;
}
2016-11-07 20:22:41 +00:00
void NativeWindowMac::SetVibrancy(const std::string& type) {
NSView* vibrant_view = [window_ vibrantView];
if (type.empty()) {
if (background_color_before_vibrancy_) {
[window_ setBackgroundColor:background_color_before_vibrancy_];
[window_ setTitlebarAppearsTransparent:transparency_before_vibrancy_];
}
if (vibrant_view == nil)
return;
[vibrant_view removeFromSuperview];
[window_ setVibrantView:nil];
return;
}
background_color_before_vibrancy_.reset([[window_ backgroundColor] retain]);
transparency_before_vibrancy_ = [window_ titlebarAppearsTransparent];
2016-11-07 20:22:41 +00:00
if (title_bar_style_ != TitleBarStyle::NORMAL) {
[window_ setTitlebarAppearsTransparent:YES];
[window_ setBackgroundColor:[NSColor clearColor]];
}
2016-11-07 20:22:41 +00:00
NSVisualEffectView* effect_view = (NSVisualEffectView*)vibrant_view;
if (effect_view == nil) {
effect_view = [[[NSVisualEffectView alloc]
initWithFrame:[[window_ contentView] bounds]] autorelease];
[window_ setVibrantView:(NSView*)effect_view];
[effect_view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[effect_view setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
if (visual_effect_state_ == VisualEffectState::ACTIVE) {
[effect_view setState:NSVisualEffectStateActive];
} else if (visual_effect_state_ == VisualEffectState::INACTIVE) {
[effect_view setState:NSVisualEffectStateInactive];
} else {
[effect_view setState:NSVisualEffectStateFollowsWindowActiveState];
}
// Make frameless Vibrant windows have rounded corners.
if (!has_frame() && !is_modal()) {
CGFloat radius = 5.0f; // default corner radius
CGFloat dimension = 2 * radius + 1;
NSSize size = NSMakeSize(dimension, dimension);
NSImage* maskImage = [NSImage imageWithSize:size
flipped:NO
drawingHandler:^BOOL(NSRect rect) {
NSBezierPath* bezierPath = [NSBezierPath
bezierPathWithRoundedRect:rect
xRadius:radius
yRadius:radius];
[[NSColor blackColor] set];
[bezierPath fill];
return YES;
}];
[maskImage setCapInsets:NSEdgeInsetsMake(radius, radius, radius, radius)];
[maskImage setResizingMode:NSImageResizingModeStretch];
[effect_view setMaskImage:maskImage];
[window_ setCornerMask:maskImage];
}
[[window_ contentView] addSubview:effect_view
positioned:NSWindowBelow
relativeTo:nil];
}
2016-11-07 20:22:41 +00:00
std::string dep_warn = " has been deprecated and removed as of macOS 10.15.";
node::Environment* env =
node::Environment::GetCurrent(v8::Isolate::GetCurrent());
NSVisualEffectMaterial vibrancyType;
if (type == "appearance-based") {
EmitWarning(env, "NSVisualEffectMaterialAppearanceBased" + dep_warn,
"electron");
vibrancyType = NSVisualEffectMaterialAppearanceBased;
} else if (type == "light") {
EmitWarning(env, "NSVisualEffectMaterialLight" + dep_warn, "electron");
vibrancyType = NSVisualEffectMaterialLight;
} else if (type == "dark") {
EmitWarning(env, "NSVisualEffectMaterialDark" + dep_warn, "electron");
vibrancyType = NSVisualEffectMaterialDark;
} else if (type == "titlebar") {
vibrancyType = NSVisualEffectMaterialTitlebar;
}
if (@available(macOS 10.11, *)) {
if (type == "selection") {
vibrancyType = NSVisualEffectMaterialSelection;
} else if (type == "menu") {
vibrancyType = NSVisualEffectMaterialMenu;
} else if (type == "popover") {
vibrancyType = NSVisualEffectMaterialPopover;
} else if (type == "sidebar") {
vibrancyType = NSVisualEffectMaterialSidebar;
} else if (type == "medium-light") {
EmitWarning(env, "NSVisualEffectMaterialMediumLight" + dep_warn,
"electron");
vibrancyType = NSVisualEffectMaterialMediumLight;
} else if (type == "ultra-dark") {
EmitWarning(env, "NSVisualEffectMaterialUltraDark" + dep_warn,
"electron");
vibrancyType = NSVisualEffectMaterialUltraDark;
}
}
if (@available(macOS 10.14, *)) {
if (type == "header") {
vibrancyType = NSVisualEffectMaterialHeaderView;
} else if (type == "sheet") {
vibrancyType = NSVisualEffectMaterialSheet;
} else if (type == "window") {
vibrancyType = NSVisualEffectMaterialWindowBackground;
} else if (type == "hud") {
vibrancyType = NSVisualEffectMaterialHUDWindow;
} else if (type == "fullscreen-ui") {
vibrancyType = NSVisualEffectMaterialFullScreenUI;
} else if (type == "tooltip") {
vibrancyType = NSVisualEffectMaterialToolTip;
} else if (type == "content") {
vibrancyType = NSVisualEffectMaterialContentBackground;
} else if (type == "under-window") {
vibrancyType = NSVisualEffectMaterialUnderWindowBackground;
} else if (type == "under-page") {
vibrancyType = NSVisualEffectMaterialUnderPageBackground;
}
}
if (vibrancyType)
[effect_view setMaterial:vibrancyType];
2016-11-07 20:22:41 +00:00
}
void NativeWindowMac::SetTrafficLightPosition(const gfx::Point& position) {
traffic_light_position_ = position;
RedrawTrafficLights();
}
gfx::Point NativeWindowMac::GetTrafficLightPosition() const {
return traffic_light_position_;
}
void NativeWindowMac::SetTouchBar(
std::vector<gin_helper::PersistentDictionary> items) {
2018-04-20 10:23:21 +00:00
if (@available(macOS 10.12.2, *)) {
touch_bar_.reset([[ElectronTouchBar alloc]
2018-04-20 10:23:21 +00:00
initWithDelegate:window_delegate_.get()
window:this
settings:std::move(items)]);
2018-04-20 10:23:21 +00:00
[window_ setTouchBar:nil];
}
}
2017-03-01 00:08:12 +00:00
void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) {
2018-04-20 10:23:21 +00:00
if (@available(macOS 10.12.2, *)) {
if (touch_bar_ && [window_ touchBar])
[touch_bar_ refreshTouchBarItem:[window_ touchBar] id:item_id];
}
}
void NativeWindowMac::SetEscapeTouchBarItem(
gin_helper::PersistentDictionary item) {
2018-04-20 10:23:21 +00:00
if (@available(macOS 10.12.2, *)) {
if (touch_bar_ && [window_ touchBar])
[touch_bar_ setEscapeTouchBarItem:std::move(item)
forTouchBar:[window_ touchBar]];
2018-04-20 10:23:21 +00:00
}
}
gfx::Rect NativeWindowMac::ContentBoundsToWindowBounds(
const gfx::Rect& bounds) const {
if (has_frame()) {
gfx::Rect window_bounds(
[window_ frameRectForContentRect:bounds.ToCGRect()]);
int frame_height = window_bounds.height() - bounds.height();
window_bounds.set_y(window_bounds.y() - frame_height);
return window_bounds;
} else {
return bounds;
}
}
gfx::Rect NativeWindowMac::WindowBoundsToContentBounds(
const gfx::Rect& bounds) const {
if (has_frame()) {
gfx::Rect content_bounds(
[window_ contentRectForFrameRect:bounds.ToCGRect()]);
int frame_height = bounds.height() - content_bounds.height();
content_bounds.set_y(content_bounds.y() + frame_height);
return content_bounds;
} else {
return bounds;
}
}
2018-04-24 10:00:44 +00:00
bool NativeWindowMac::CanResize() const {
return resizable_;
}
views::View* NativeWindowMac::GetContentsView() {
return root_view_.get();
}
void NativeWindowMac::AddContentViewLayers(bool minimizable, bool closable) {
// Make sure the bottom corner is rounded for non-modal windows:
// http://crbug.com/396264.
if (!is_modal()) {
// For normal window, we need to explicitly set layer for contentView to
// make setBackgroundColor work correctly.
// There is no need to do so for frameless window, and doing so would make
// titleBarStyle stop working.
if (has_frame()) {
base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]);
[background_layer
setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
[[window_ contentView] setLayer:background_layer];
}
[[window_ contentView] setWantsLayer:YES];
}
if (!has_frame()) {
// In OSX 10.10, adding subviews to the root view for the NSView hierarchy
// produces warnings. To eliminate the warnings, we resize the contentView
// to fill the window, and add subviews to that.
// http://crbug.com/380412
if (!original_set_frame_size) {
Class cl = [[window_ contentView] class];
original_set_frame_size = class_replaceMethod(
cl, @selector(setFrameSize:), (IMP)SetFrameSize, "v@:{_NSSize=ff}");
original_view_did_move_to_superview =
class_replaceMethod(cl, @selector(viewDidMoveToSuperview),
(IMP)ViewDidMoveToSuperview, "v@:");
[[window_ contentView] viewDidMoveToWindow];
}
// The fullscreen button should always be hidden for frameless window.
[[window_ standardWindowButton:NSWindowFullScreenButton] setHidden:YES];
if (title_bar_style_ == TitleBarStyle::CUSTOM_BUTTONS_ON_HOVER) {
buttons_view_.reset(
[[CustomWindowButtonView alloc] initWithFrame:NSZeroRect]);
// NSWindowStyleMaskFullSizeContentView does not work with zoom button
SetFullScreenable(false);
if (!minimizable)
[[buttons_view_ viewWithTag:2] removeFromSuperview];
if (!closable)
[[buttons_view_ viewWithTag:1] removeFromSuperview];
[[window_ contentView] addSubview:buttons_view_];
} else {
if (title_bar_style_ != TitleBarStyle::NORMAL)
return;
// Hide the window buttons.
[[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES];
[[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
[[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES];
}
// Some third-party macOS utilities check the zoom button's enabled state to
// determine whether to show custom UI on hover, so we disable it here to
// prevent them from doing so in a frameless app window.
SetMaximizable(false);
}
}
2018-04-20 18:47:04 +00:00
void NativeWindowMac::InternalSetParentWindow(NativeWindow* parent,
bool attach) {
2017-09-14 05:43:22 +00:00
if (is_modal())
return;
NativeWindow::SetParentWindow(parent);
// Do not remove/add if we are already properly attached.
if (attach && parent &&
[window_ parentWindow] == parent->GetNativeWindow().GetNativeNSWindow())
2017-09-14 05:43:22 +00:00
return;
// Remove current parent window.
if ([window_ parentWindow])
[[window_ parentWindow] removeChildWindow:window_];
// Set new parent window.
// Note that this method will force the window to become visible.
if (parent && attach)
[parent->GetNativeWindow().GetNativeNSWindow()
addChildWindow:window_
ordered:NSWindowAbove];
2017-09-14 05:43:22 +00:00
}
void NativeWindowMac::SetForwardMouseMessages(bool forward) {
[window_ setAcceptsMouseMovedEvents:forward];
}
void NativeWindowMac::OverrideNSWindowContentView() {
// When using `views::Widget` to hold WebContents, Chromium would use
// `BridgedContentView` as content view, which does not support draggable
// regions. In order to make draggable regions work, we have to replace the
// content view with a simple NSView.
if (has_frame()) {
container_view_.reset(
[[ElectronAdaptedContentView alloc] initWithShell:this]);
} else {
container_view_.reset([[FullSizeContentView alloc] init]);
[container_view_ setFrame:[[[window_ contentView] superview] bounds]];
}
[container_view_
setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[window_ setContentView:container_view_];
AddContentViewLayers(IsMinimizable(), IsClosable());
}
2016-01-23 11:12:19 +00:00
void NativeWindowMac::SetStyleMask(bool on, NSUInteger flag) {
// Changing the styleMask of a frameless windows causes it to change size so
// we explicitly disable resizing while setting it.
ScopedDisableResize disable_resize;
2016-08-04 23:47:03 +00:00
bool was_maximizable = IsMaximizable();
2016-01-23 11:12:19 +00:00
if (on)
[window_ setStyleMask:[window_ styleMask] | flag];
else
[window_ setStyleMask:[window_ styleMask] & (~flag)];
// Change style mask will make the zoom button revert to default, probably
2016-06-18 13:26:26 +00:00
// a bug of Cocoa or macOS.
2016-08-04 23:47:03 +00:00
SetMaximizable(was_maximizable);
2016-01-23 11:12:19 +00:00
}
void NativeWindowMac::SetCollectionBehavior(bool on, NSUInteger flag) {
2016-08-04 23:47:03 +00:00
bool was_maximizable = IsMaximizable();
2016-01-23 11:12:19 +00:00
if (on)
[window_ setCollectionBehavior:[window_ collectionBehavior] | flag];
else
[window_ setCollectionBehavior:[window_ collectionBehavior] & (~flag)];
// Change collectionBehavior will make the zoom button revert to default,
2016-06-18 13:26:26 +00:00
// probably a bug of Cocoa or macOS.
2016-08-04 23:47:03 +00:00
SetMaximizable(was_maximizable);
2016-01-23 11:12:19 +00:00
}
2013-04-12 07:04:46 +00:00
// static
NativeWindow* NativeWindow::Create(const gin_helper::Dictionary& options,
2018-04-08 11:20:43 +00:00
NativeWindow* parent) {
return new NativeWindowMac(options, parent);
2013-04-12 07:04:46 +00:00
}
} // namespace electron