mirror of
https://github.com/electron/electron.git
synced 2026-01-07 22:54:25 -05:00
* chore: bump chromium in DEPS to 145.0.7562.0 * fix(patch-conflict): update code cache patch for PersistentCache refactor Upstream refactored code cache to use PersistentCache with new class-based implementation (NoopCodeCacheHost, LocalCodeCacheHost, CodeCacheWithPersistentCacheHost). Updated patch to integrate custom scheme support into the new structure while preserving ProcessLockURLIsCodeCacheScheme checks for embedder-registered schemes. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7044986 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-conflict): update dialog patch for RequestXdgDesktopPortal API Upstream changed from SetSystemdScopeUnitNameForXdgPortal to RequestXdgDesktopPortal API pattern. Updated OnServiceStarted signature and kept OnSystemdUnitStarted callback that calls Electron's file_dialog::StartPortalAvailabilityTestInBackground(). Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7204285 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-conflict): remove reference to deleted AbortByPlaceholderLayout flag Upstream removed the AbortByPlaceholderLayout runtime flag from runtime_enabled_features.json5. Updated patch to only add ElectronCSSCornerSmoothing without the removed flag reference. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7226494 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * chore: update patch hunk headers Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): guard media_file_system_registry for ChromeOS only Upstream CL https://chromium-review.googlesource.com/c/chromium/src/+/7100719 moved media_file_system_registry to be ChromeOS-only since Media Galleries is a Chrome Apps API and Chrome Apps are only available on Chrome OS now. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): update VideoPixelFormat API for SharedImageFormat Upstream CL https://chromium-review.googlesource.com/c/chromium/src/+/7207153 removed VideoPixelFormatToGfxBufferFormat as part of migration to SharedImageFormat. Update to use VideoPixelFormatToSharedImageFormat which directly returns the SharedImageFormat. Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): extend profile methods patch for ShouldEnableXfaForms The ShouldEnableXfaForms function uses Profile::FromBrowserContext() which is not available in Electron. Wrap the profile-dependent code in #if 0 to fall through to the feature flag default. Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump chromium in DEPS to 145.0.7563.0 * chore: bump chromium in DEPS to 145.0.7565.0 * chore: bump chromium in DEPS to 145.0.7567.0 * chore: bump chromium in DEPS to 145.0.7568.0 * fix(patch-conflict): update content_main_delegate.h context for IsInitFeatureListEarly Upstream added a new IsInitFeatureListEarly() virtual method to ContentMainDelegate just before where our GetBrowserV8SnapshotFilename() method is added. Updated patch context to account for this new method. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7092856 Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * chore: update patch hunk headers Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-update): include v8-cppgc.h for CppHeap complete type The std::unique_ptr<v8::CppHeap> default argument in node.h requires the complete CppHeap type definition for the destructor. Added the v8-cppgc.h include to provide the full type definition. Ref: Unable to locate CL - libc++ unique_ptr requires complete type for destructor Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * chore: update patch hunk headers Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(build): move NativeAppWindowFrameViewMacClient before constructor The std::unique_ptr<NativeAppWindowFrameViewMacClient> member requires the complete type definition to be visible at the point of the constructor because the unique_ptr destructor may be instantiated during exception handling. Moved the class definition before the NativeWindowMac constructor. Ref: Unable to locate CL - libc++ unique_ptr requires complete type for destructor Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com> * fix(patch-conflict): update create_browser_v8_snapshot_file_name_fuse context for IsInitFeatureListEarly The upstream added IsInitFeatureListEarly() virtual method declaration to ContentMainDelegate class. Updated the patch context to account for this new function being present before the GetBrowserV8SnapshotFilename() declaration we add. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7092856 Co-Authored-By: Claude <noreply@anthropic.com> * chore: update patch hunk headers Co-Authored-By: Claude <noreply@anthropic.com> * fix(patch-update): remove reverted IsInitFeatureListEarly from v8 snapshot patch The upstream added IsInitFeatureListEarly() was reverted, so the patch should not include this declaration. Only GetBrowserV8SnapshotFilename() should be added by the create_browser_v8_snapshot_file_name_fuse patch. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7230430 Co-Authored-By: Claude <noreply@anthropic.com> * 6171655: include single_thread_task_runner.h for complete type Added include for base/task/single_thread_task_runner.h in osr_converter.cc to resolve incomplete type error when using base::SingleThreadTaskRunner::GetCurrentDefault(). Ref: https://chromium-review.googlesource.com/c/chromium/src/+/6171655 Co-Authored-By: Claude <noreply@anthropic.com> * 7224136: use CHROMIUM_GIT_REVISION directly instead of removed function Upstream removed GetChromiumGitRevision() function from embedder_support. Updated to use CHROMIUM_GIT_REVISION macro directly via build/util/chromium_git_revision.h as recommended in the Chromium CL. Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7224136 Co-Authored-By: Claude <noreply@anthropic.com> * fixup! 7224136: use CHROMIUM_GIT_REVISION directly instead of removed function * fix(build): add missing include `components/dbus/xdg/systemd.h` for `void OnSystemdUnitStarted(dbus_xdg::SystemdUnitStatus)` in the same patch. * fix(build): adapt to string-view-ification change in windows jump_list.cc 7186922: Fix unsafe buffer usage in base/win/win_util.cc https://chromium-review.googlesource.com/c/chromium/src/+/7186922 * chore: update libc++ filenames * fixup! fix(build): add missing include * fixup! fix(build): extend profile methods patch for ShouldEnableXfaForms * fixup! fix(build): guard media_file_system_registry for ChromeOS only * fixup! fixup! fix(build): extend profile methods patch for ShouldEnableXfaForms --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: Keeley Hammond <khammond@slack-corp.com> Co-authored-by: Claude <svc-devxp-claude@slack-corp.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: clavin <clavin@electronjs.org>
1885 lines
62 KiB
Plaintext
1885 lines
62 KiB
Plaintext
// Copyright (c) 2013 GitHub, Inc.
|
||
// Use of this source code is governed by the MIT license that can be
|
||
// found in the LICENSE file.
|
||
|
||
#include "shell/browser/native_window_mac.h"
|
||
|
||
#include <AvailabilityMacros.h>
|
||
#include <objc/objc-runtime.h>
|
||
|
||
#include <algorithm>
|
||
#include <memory>
|
||
#include <string>
|
||
#include <utility>
|
||
#include <vector>
|
||
|
||
#include "base/apple/scoped_cftyperef.h"
|
||
#include "base/mac/mac_util.h"
|
||
#include "base/strings/sys_string_conversions.h"
|
||
#include "components/remote_cocoa/app_shim/native_widget_ns_window_bridge.h"
|
||
#include "components/remote_cocoa/browser/scoped_cg_window_id.h"
|
||
#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/browser.h"
|
||
#include "shell/browser/javascript_environment.h"
|
||
#include "shell/browser/ui/cocoa/electron_native_widget_mac.h"
|
||
#include "shell/browser/ui/cocoa/electron_ns_panel.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/cocoa/window_buttons_proxy.h"
|
||
#include "shell/browser/ui/drag_util.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/node_util.h"
|
||
#include "shell/common/options_switches.h"
|
||
#include "skia/ext/skia_utils_mac.h"
|
||
#include "third_party/webrtc/modules/desktop_capture/mac/window_list_utils.h"
|
||
#include "ui/base/hit_test.h"
|
||
#include "ui/display/screen.h"
|
||
#include "ui/gl/gpu_switching_manager.h"
|
||
#include "ui/views/background.h"
|
||
#include "ui/views/cocoa/native_widget_mac_ns_window_host.h"
|
||
#include "ui/views/widget/widget.h"
|
||
#include "ui/views/window/native_frame_view_mac.h"
|
||
|
||
@interface ElectronProgressBar : NSProgressIndicator
|
||
@end
|
||
|
||
@implementation ElectronProgressBar
|
||
|
||
- (void)drawRect:(NSRect)dirtyRect {
|
||
if (self.style != NSProgressIndicatorStyleBar)
|
||
return;
|
||
// Draw edges of rounded rect.
|
||
NSRect rect = NSInsetRect([self bounds], 1.0, 1.0);
|
||
CGFloat radius = rect.size.height / 2;
|
||
NSBezierPath* bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect
|
||
xRadius:radius
|
||
yRadius:radius];
|
||
[bezier_path setLineWidth:2.0];
|
||
[[NSColor grayColor] set];
|
||
[bezier_path stroke];
|
||
|
||
// Fill the rounded rect.
|
||
rect = NSInsetRect(rect, 2.0, 2.0);
|
||
radius = rect.size.height / 2;
|
||
bezier_path = [NSBezierPath bezierPathWithRoundedRect:rect
|
||
xRadius:radius
|
||
yRadius:radius];
|
||
[bezier_path setLineWidth:1.0];
|
||
[bezier_path addClip];
|
||
|
||
// Calculate the progress width.
|
||
rect.size.width =
|
||
floor(rect.size.width * ([self doubleValue] / [self maxValue]));
|
||
|
||
// Fill the progress bar with color blue.
|
||
[[NSColor colorWithSRGBRed:0.2 green:0.6 blue:1 alpha:1] set];
|
||
NSRectFill(rect);
|
||
}
|
||
|
||
@end
|
||
|
||
namespace gin {
|
||
|
||
template <>
|
||
struct Converter<electron::NativeWindowMac::VisualEffectState> {
|
||
static bool FromV8(v8::Isolate* isolate,
|
||
v8::Local<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::kFollowWindow;
|
||
} else if (visual_effect_state == "active") {
|
||
*out = VisualEffectState::kActive;
|
||
} else if (visual_effect_state == "inactive") {
|
||
*out = VisualEffectState::kInactive;
|
||
} else {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
};
|
||
|
||
} // namespace gin
|
||
|
||
namespace electron {
|
||
|
||
class NativeAppWindowFrameViewMacClient
|
||
: public views::NativeFrameViewMacClient {
|
||
public:
|
||
NativeAppWindowFrameViewMacClient(views::Widget* frame,
|
||
NativeWindowMac* window)
|
||
: frame_(frame), native_app_window_(window) {}
|
||
|
||
NativeAppWindowFrameViewMacClient(const NativeAppWindowFrameViewMacClient&) =
|
||
delete;
|
||
NativeAppWindowFrameViewMacClient& operator=(
|
||
const NativeAppWindowFrameViewMacClient&) = delete;
|
||
|
||
~NativeAppWindowFrameViewMacClient() override = default;
|
||
|
||
std::optional<int> NonClientHitTest(const gfx::Point& point) override {
|
||
if (frame_->IsFullscreen()) {
|
||
return HTCLIENT;
|
||
}
|
||
|
||
// Check for possible draggable region in the client area for the frameless
|
||
// window.
|
||
int contents_hit_test = native_app_window_->NonClientHitTest(point);
|
||
if (contents_hit_test != HTNOWHERE)
|
||
return contents_hit_test;
|
||
|
||
return HTCLIENT;
|
||
}
|
||
|
||
private:
|
||
const raw_ptr<views::Widget> frame_;
|
||
// Weak. Owned by extensions::AppWindow (which manages our Widget via its
|
||
// WebContents).
|
||
const raw_ptr<NativeWindowMac, DanglingUntriaged> native_app_window_;
|
||
};
|
||
|
||
NativeWindowMac::~NativeWindowMac() = default;
|
||
|
||
NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
|
||
NativeWindow* parent)
|
||
: NativeWindow(options, parent), root_view_(new RootViewMac(this)) {
|
||
ui::NativeTheme::GetInstanceForNativeUi()->AddObserver(this);
|
||
display::Screen::Get()->AddObserver(this);
|
||
|
||
int width = options.ValueOrDefault(options::kWidth, 800);
|
||
int height = options.ValueOrDefault(options::kHeight, 600);
|
||
|
||
NSRect main_screen_rect = [[[NSScreen screens] firstObject] frame];
|
||
gfx::Rect bounds(round((NSWidth(main_screen_rect) - width) / 2),
|
||
round((NSHeight(main_screen_rect) - height) / 2), width,
|
||
height);
|
||
|
||
const bool resizable = options.ValueOrDefault(options::kResizable, true);
|
||
options.Get(options::kZoomToPageWidth, &zoom_to_page_width_);
|
||
options.Get(options::kSimpleFullscreen, &always_simple_fullscreen_);
|
||
options.GetOptional(options::kTrafficLightPosition, &traffic_light_position_);
|
||
options.Get(options::kVisualEffectState, &visual_effect_state_);
|
||
|
||
const bool minimizable = options.ValueOrDefault(options::kMinimizable, true);
|
||
|
||
const bool maximizable = options.ValueOrDefault(options::kMaximizable, true);
|
||
|
||
const bool closable = options.ValueOrDefault(options::kClosable, true);
|
||
|
||
const std::string tabbingIdentifier =
|
||
options.ValueOrDefault(options::kTabbingIdentifier, std::string{});
|
||
|
||
const std::string windowType =
|
||
options.ValueOrDefault(options::kType, std::string{});
|
||
|
||
const bool hiddenInMissionControl =
|
||
options.ValueOrDefault(options::kHiddenInMissionControl, false);
|
||
|
||
const bool paint_when_initially_hidden =
|
||
options.ValueOrDefault(options::kPaintWhenInitiallyHidden, true);
|
||
|
||
NSUInteger styleMask = NSWindowStyleMaskTitled;
|
||
|
||
if (minimizable)
|
||
styleMask |= NSWindowStyleMaskMiniaturizable;
|
||
if (closable)
|
||
styleMask |= NSWindowStyleMaskClosable;
|
||
if (resizable)
|
||
styleMask |= NSWindowStyleMaskResizable;
|
||
|
||
// TODO: remove NSWindowStyleMaskTexturedBackground.
|
||
// https://github.com/electron/electron/issues/43125
|
||
#pragma clang diagnostic push
|
||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||
if (windowType == "textured" && (transparent() || !has_frame())) {
|
||
util::EmitDeprecationWarning(
|
||
"The 'textured' window type is deprecated and will be removed");
|
||
styleMask |= NSWindowStyleMaskTexturedBackground;
|
||
}
|
||
#pragma clang diagnostic pop
|
||
|
||
// Create views::Widget and assign window_ with it.
|
||
// TODO(zcbenz): Get rid of the window_ in future.
|
||
views::Widget::InitParams params(
|
||
views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
|
||
views::Widget::InitParams::TYPE_WINDOW);
|
||
params.bounds = bounds;
|
||
params.delegate = this;
|
||
params.type = views::Widget::InitParams::TYPE_WINDOW;
|
||
// Possibly allow painting before shown - later disabled in ElectronNSWindow.
|
||
params.headless_mode = paint_when_initially_hidden;
|
||
if (IsTranslucent()) {
|
||
params.opacity = views::Widget::InitParams::WindowOpacity::kTranslucent;
|
||
}
|
||
params.native_widget =
|
||
new ElectronNativeWidgetMac(this, windowType, styleMask, widget());
|
||
widget()->Init(std::move(params));
|
||
widget()->SetNativeWindowProperty(kNativeWindowKey.c_str(), this);
|
||
SetCanResize(resizable);
|
||
window_ = static_cast<ElectronNSWindow*>(
|
||
widget()->GetNativeWindow().GetNativeNSWindow());
|
||
|
||
RegisterDeleteDelegateCallback(RegisterDeleteCallbackPassKey(),
|
||
base::BindOnce(
|
||
[](NativeWindowMac* window) {
|
||
if (window->window_)
|
||
window->window_ = nil;
|
||
if (window->buttons_proxy_)
|
||
window->buttons_proxy_ = nil;
|
||
},
|
||
this));
|
||
|
||
[window_ setEnableLargerThanScreen:enable_larger_than_screen()];
|
||
|
||
window_delegate_ = [[ElectronNSWindowDelegate alloc] initWithShell:this];
|
||
[window_ setDelegate:window_delegate_];
|
||
|
||
// Only use native parent window for non-modal windows.
|
||
if (parent && !is_modal()) {
|
||
SetParentWindow(parent);
|
||
}
|
||
|
||
const bool rounded_corner =
|
||
options.ValueOrDefault(options::kRoundedCorners, true);
|
||
if (!rounded_corner && !has_frame())
|
||
SetBorderless(!rounded_corner);
|
||
|
||
if (transparent()) {
|
||
// Setting the background color to clear will also hide the shadow.
|
||
[window_ setBackgroundColor:[NSColor clearColor]];
|
||
}
|
||
|
||
if (windowType == "desktop") {
|
||
[window_ setLevel:kCGDesktopWindowLevel - 1];
|
||
[window_ setDisableKeyOrMainWindow:YES];
|
||
[window_ setCollectionBehavior:(NSWindowCollectionBehaviorCanJoinAllSpaces |
|
||
NSWindowCollectionBehaviorStationary |
|
||
NSWindowCollectionBehaviorIgnoresCycle)];
|
||
}
|
||
|
||
if (windowType == "panel") {
|
||
[window_ setLevel:NSFloatingWindowLevel];
|
||
}
|
||
|
||
if (bool val; options.Get(options::kFocusable, &val) && !val)
|
||
[window_ setDisableKeyOrMainWindow:YES];
|
||
|
||
if (transparent() || !has_frame()) {
|
||
// Don't show title bar.
|
||
[window_ setTitlebarAppearsTransparent:YES];
|
||
[window_ setTitleVisibility:NSWindowTitleHidden];
|
||
// Remove non-transparent corners, see
|
||
// https://github.com/electron/electron/issues/517.
|
||
[window_ setOpaque:NO];
|
||
// Show window buttons if titleBarStyle is not "normal".
|
||
if (title_bar_style() == TitleBarStyle::kNormal) {
|
||
InternalSetWindowButtonVisibility(false);
|
||
} else {
|
||
buttons_proxy_ = [[WindowButtonsProxy alloc] initWithWindow:window_];
|
||
[buttons_proxy_ setHeight:titlebar_overlay_height()];
|
||
if (traffic_light_position_) {
|
||
[buttons_proxy_ setMargin:*traffic_light_position_];
|
||
} else if (title_bar_style() == TitleBarStyle::kHiddenInset) {
|
||
// For macOS >= 11, while this value does not match official macOS apps
|
||
// like Safari or Notes, it matches titleBarStyle's old implementation
|
||
// before Electron <= 12.
|
||
[buttons_proxy_ setMargin:gfx::Point(12, 11)];
|
||
}
|
||
if (title_bar_style() == TitleBarStyle::kCustomButtonsOnHover) {
|
||
[buttons_proxy_ setShowOnHover:YES];
|
||
} else {
|
||
// customButtonsOnHover does not show buttons initially.
|
||
InternalSetWindowButtonVisibility(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Create a tab only if tabbing identifier is specified and window has
|
||
// a native title bar.
|
||
if (tabbingIdentifier.empty() || transparent() || !has_frame()) {
|
||
[window_ setTabbingMode:NSWindowTabbingModeDisallowed];
|
||
} else {
|
||
[window_ setTabbingIdentifier:base::SysUTF8ToNSString(tabbingIdentifier)];
|
||
}
|
||
|
||
// Resize to content bounds.
|
||
// NOTE(@mlaurencin) Spec requirements can be found here:
|
||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open#width
|
||
constexpr int kMinSizeReqdBySpec = 100;
|
||
const int inner_width = options.ValueOrDefault(options::kinnerWidth, 0);
|
||
const int inner_height = options.ValueOrDefault(options::kinnerHeight, 0);
|
||
bool use_content_size =
|
||
options.ValueOrDefault(options::kUseContentSize, false);
|
||
if (inner_width || inner_height) {
|
||
use_content_size = true;
|
||
if (inner_width)
|
||
width = std::max(kMinSizeReqdBySpec, inner_width);
|
||
if (inner_height)
|
||
height = std::max(kMinSizeReqdBySpec, inner_height);
|
||
}
|
||
|
||
if (!has_frame() || use_content_size)
|
||
SetContentSize(gfx::Size(width, height));
|
||
|
||
// Enable the NSView to accept first mouse event.
|
||
const bool acceptsFirstMouse =
|
||
options.ValueOrDefault(options::kAcceptFirstMouse, false);
|
||
[window_ setAcceptsFirstMouse:acceptsFirstMouse];
|
||
|
||
// Disable auto-hiding cursor.
|
||
const bool disableAutoHideCursor =
|
||
options.ValueOrDefault(options::kDisableAutoHideCursor, false);
|
||
[window_ setDisableAutoHideCursor:disableAutoHideCursor];
|
||
|
||
SetHiddenInMissionControl(hiddenInMissionControl);
|
||
|
||
// Set maximizable state last to ensure zoom button does not get reset
|
||
// by calls to other APIs.
|
||
SetMaximizable(maximizable);
|
||
|
||
// Default content view.
|
||
SetContentView(new views::View());
|
||
AddContentViewLayers();
|
||
|
||
UpdateWindowOriginalFrame();
|
||
original_level_ = [window_ level];
|
||
}
|
||
|
||
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->AddChildViewRaw(content_view());
|
||
|
||
root_view->DeprecatedLayoutImmediately();
|
||
}
|
||
|
||
void NativeWindowMac::Close() {
|
||
if (!IsClosable()) {
|
||
WindowList::WindowCloseCancelled(this);
|
||
return;
|
||
}
|
||
|
||
if (is_transitioning_fullscreen()) {
|
||
SetHasDeferredWindowClose(true);
|
||
return;
|
||
}
|
||
|
||
// Ensure we're detached from the parent window before closing.
|
||
RemoveChildFromParentWindow();
|
||
|
||
while (!child_windows_.empty()) {
|
||
auto* child = child_windows_.back();
|
||
child->RemoveChildFromParentWindow();
|
||
}
|
||
|
||
// If a sheet is attached to the window when we call
|
||
// [window_ performClose:nil], the window won't close properly
|
||
// even after the user has ended the sheet.
|
||
// Ensure it's closed before calling [window_ performClose:nil].
|
||
// If multiple sheets are open, they must all be closed.
|
||
while ([window_ attachedSheet]) {
|
||
[window_ endSheet:[window_ attachedSheet]];
|
||
}
|
||
DCHECK_EQ([[window_ sheets] count], 0UL);
|
||
|
||
// window_ could be nil after performClose.
|
||
bool should_notify = is_modal() && parent() && IsVisible();
|
||
|
||
[window_ performClose:nil];
|
||
|
||
// Closing a sheet doesn't trigger windowShouldClose,
|
||
// so we need to manually call it ourselves here.
|
||
if (should_notify) {
|
||
NotifyWindowCloseButtonClicked();
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::CloseImmediately() {
|
||
// Ensure we're detached from the parent window before closing.
|
||
RemoveChildFromParentWindow();
|
||
|
||
while (!child_windows_.empty()) {
|
||
auto* child = child_windows_.back();
|
||
child->RemoveChildFromParentWindow();
|
||
}
|
||
|
||
[window_ close];
|
||
}
|
||
|
||
void NativeWindowMac::Focus(bool focus) {
|
||
if (!IsVisible())
|
||
return;
|
||
|
||
if (focus) {
|
||
// If we're a panel window, we do not want to activate the app,
|
||
// which enables Electron-apps to build Spotlight-like experiences.
|
||
if (!IsPanel()) {
|
||
[[NSApplication sharedApplication] activateIgnoringOtherApps:NO];
|
||
}
|
||
[window_ makeKeyAndOrderFront:nil];
|
||
} else {
|
||
[window_ orderOut:nil];
|
||
[window_ orderBack:nil];
|
||
}
|
||
}
|
||
|
||
bool NativeWindowMac::IsFocused() const {
|
||
return [window_ isKeyWindow];
|
||
}
|
||
|
||
void NativeWindowMac::Show() {
|
||
if (is_modal() && parent()) {
|
||
NSWindow* window = parent()->GetNativeWindow().GetNativeNSWindow();
|
||
if ([window_ sheetParent] == nil)
|
||
[window beginSheet:window_
|
||
completionHandler:^(NSModalResponse){
|
||
}];
|
||
return;
|
||
}
|
||
|
||
set_wants_to_be_visible(true);
|
||
|
||
// Reattach the window to the parent to actually show it.
|
||
if (parent())
|
||
InternalSetParentWindow(parent(), true);
|
||
|
||
// Panels receive key focus when shown but should not activate the app.
|
||
if (!IsPanel()) {
|
||
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
|
||
}
|
||
[window_ makeKeyAndOrderFront:nil];
|
||
}
|
||
|
||
void NativeWindowMac::ShowInactive() {
|
||
set_wants_to_be_visible(true);
|
||
|
||
// Reattach the window to the parent to actually show it.
|
||
if (parent())
|
||
InternalSetParentWindow(parent(), true);
|
||
|
||
// Triggers `NativeWidgetMacNSWindowHost::OnVisibilityChanged`.
|
||
widget()->ShowInactive();
|
||
// `Widget::ShowInactive` is not sufficient to bring window to front.
|
||
[window_ orderFrontRegardless];
|
||
// Above calls do not trigger `orderWindow: relativeTo:` in which headless
|
||
// mode is being disabled.
|
||
[window_ disableHeadlessMode];
|
||
}
|
||
|
||
void NativeWindowMac::Hide() {
|
||
// If a sheet is attached to the window when we call [window_ orderOut:nil],
|
||
// the sheet won't be able to show again on the same window.
|
||
// Ensure it's closed before calling [window_ orderOut:nil].
|
||
if ([window_ attachedSheet])
|
||
[window_ endSheet:[window_ attachedSheet]];
|
||
|
||
if (is_modal() && parent()) {
|
||
[window_ orderOut:nil];
|
||
[parent()->GetNativeWindow().GetNativeNSWindow() endSheet:window_];
|
||
return;
|
||
}
|
||
|
||
// If the window wants to be visible and has a parent, then the parent may
|
||
// order it back in (in the period between orderOut: and close).
|
||
set_wants_to_be_visible(false);
|
||
|
||
DetachChildren();
|
||
|
||
// Detach the window from the parent before.
|
||
if (parent())
|
||
InternalSetParentWindow(parent(), false);
|
||
|
||
[window_ orderOut:nil];
|
||
}
|
||
|
||
bool NativeWindowMac::IsVisible() const {
|
||
bool occluded = [window_ occlusionState] == NSWindowOcclusionStateVisible;
|
||
return [window_ isVisible] && !occluded && !IsMinimized();
|
||
}
|
||
|
||
bool NativeWindowMac::IsEnabled() const {
|
||
return [window_ attachedSheet] == nil;
|
||
}
|
||
|
||
void NativeWindowMac::SetEnabled(bool enable) {
|
||
if (!enable) {
|
||
NSRect frame = [window_ frame];
|
||
NSWindow* window =
|
||
[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, frame.size.width,
|
||
frame.size.height)
|
||
styleMask:NSWindowStyleMaskTitled
|
||
backing:NSBackingStoreBuffered
|
||
defer:NO];
|
||
[window setAlphaValue:0.5];
|
||
|
||
[window_ beginSheet:window
|
||
completionHandler:^(NSModalResponse returnCode) {
|
||
NSLog(@"main window disabled");
|
||
return;
|
||
}];
|
||
} else if ([window_ attachedSheet]) {
|
||
[window_ endSheet:[window_ attachedSheet]];
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::Maximize() {
|
||
const bool is_visible = [window_ isVisible];
|
||
|
||
if (IsMaximized()) {
|
||
if (!is_visible)
|
||
ShowInactive();
|
||
return;
|
||
}
|
||
|
||
// Take note of the current window size
|
||
if (IsNormal())
|
||
UpdateWindowOriginalFrame();
|
||
[window_ zoom:nil];
|
||
|
||
if (!is_visible) {
|
||
ShowInactive();
|
||
NotifyWindowMaximize();
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::Unmaximize() {
|
||
// Bail if the last user set bounds were the same size as the window
|
||
// screen (e.g. the user set the window to maximized via setBounds)
|
||
//
|
||
// Per docs during zoom:
|
||
// > If there’s no saved user state because there has been no previous
|
||
// > zoom,the size and location of the window don’t change.
|
||
//
|
||
// However, in classic Apple fashion, this is not the case in practice,
|
||
// and the frame inexplicably becomes very tiny. We should prevent
|
||
// zoom from being called if the window is being unmaximized and its
|
||
// unmaximized window bounds are themselves functionally maximized.
|
||
if (!IsMaximized() || user_set_bounds_maximized_)
|
||
return;
|
||
|
||
[window_ zoom:nil];
|
||
}
|
||
|
||
bool NativeWindowMac::IsMaximized() const {
|
||
// It's possible for [window_ isZoomed] to be true
|
||
// when the window is minimized or fullscreened.
|
||
if (IsMinimized() || IsFullscreen())
|
||
return false;
|
||
|
||
if (HasStyleMask(NSWindowStyleMaskResizable) != 0)
|
||
return [window_ isZoomed];
|
||
|
||
NSRect rectScreen = aspect_ratio() > 0.0
|
||
? default_frame_for_zoom()
|
||
: [[NSScreen mainScreen] visibleFrame];
|
||
|
||
return NSEqualRects([window_ frame], rectScreen);
|
||
}
|
||
|
||
void NativeWindowMac::Minimize() {
|
||
if (IsMinimized())
|
||
return;
|
||
|
||
// Take note of the current window size
|
||
if (IsNormal())
|
||
UpdateWindowOriginalFrame();
|
||
[window_ miniaturize:nil];
|
||
}
|
||
|
||
void NativeWindowMac::Restore() {
|
||
[window_ deminiaturize:nil];
|
||
}
|
||
|
||
bool NativeWindowMac::IsMinimized() const {
|
||
return [window_ isMiniaturized];
|
||
}
|
||
|
||
bool NativeWindowMac::IsPanel() {
|
||
return [window_ isKindOfClass:[ElectronNSPanel class]];
|
||
}
|
||
|
||
bool NativeWindowMac::HandleDeferredClose() {
|
||
if (has_deferred_window_close_) {
|
||
SetHasDeferredWindowClose(false);
|
||
Close();
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void NativeWindowMac::RemoveChildWindow(NativeWindow* child) {
|
||
child_windows_.remove_if([&child](NativeWindow* w) { return (w == child); });
|
||
|
||
[window_ removeChildWindow:child->GetNativeWindow().GetNativeNSWindow()];
|
||
}
|
||
|
||
void NativeWindowMac::RemoveChildFromParentWindow() {
|
||
if (parent() && !is_modal()) {
|
||
parent()->RemoveChildWindow(this);
|
||
NativeWindow::SetParentWindow(nullptr);
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::AttachChildren() {
|
||
for (auto* child : child_windows_) {
|
||
if (!static_cast<NativeWindowMac*>(child)->wants_to_be_visible())
|
||
continue;
|
||
|
||
auto* child_nswindow = child->GetNativeWindow().GetNativeNSWindow();
|
||
if ([child_nswindow parentWindow] == window_)
|
||
continue;
|
||
|
||
// Attaching a window as a child window resets its window level, so
|
||
// save and restore it afterwards.
|
||
NSInteger level = window_.level;
|
||
[window_ addChildWindow:child_nswindow ordered:NSWindowAbove];
|
||
[window_ setLevel:level];
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::DetachChildren() {
|
||
// Hide all children before hiding/minimizing the window.
|
||
// NativeWidgetNSWindowBridge::NotifyVisibilityChangeDown()
|
||
// will DCHECK otherwise.
|
||
for (auto* child : child_windows_) {
|
||
[child->GetNativeWindow().GetNativeNSWindow() orderOut:nil];
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::SetFullScreen(bool fullscreen) {
|
||
// [NSWindow -toggleFullScreen] is an asynchronous operation, which means
|
||
// that it's possible to call it while a fullscreen transition is currently
|
||
// in process. This can create weird behavior (incl. phantom windows),
|
||
// so we want to schedule a transition for when the current one has completed.
|
||
if (is_transitioning_fullscreen()) {
|
||
if (!pending_transitions_.empty()) {
|
||
bool last_pending = pending_transitions_.back();
|
||
// Only push new transitions if they're different than the last transition
|
||
// in the queue.
|
||
if (last_pending != fullscreen)
|
||
pending_transitions_.push(fullscreen);
|
||
} else {
|
||
pending_transitions_.push(fullscreen);
|
||
}
|
||
return;
|
||
}
|
||
|
||
if (fullscreen == IsFullscreen() || !IsFullScreenable())
|
||
return;
|
||
|
||
// Take note of the current window size
|
||
if (IsNormal())
|
||
UpdateWindowOriginalFrame();
|
||
|
||
// This needs to be set here because it can be the case that
|
||
// SetFullScreen is called by a user before windowWillEnterFullScreen
|
||
// or windowWillExitFullScreen are invoked, and so a potential transition
|
||
// could be dropped.
|
||
set_is_transitioning_fullscreen(true);
|
||
|
||
if (![window_ toggleFullScreenMode:nil])
|
||
set_is_transitioning_fullscreen(false);
|
||
}
|
||
|
||
bool NativeWindowMac::IsFullscreen() const {
|
||
return HasStyleMask(NSWindowStyleMaskFullScreen);
|
||
}
|
||
|
||
void NativeWindowMac::SetBounds(const gfx::Rect& bounds, bool animate) {
|
||
// Do nothing if in fullscreen mode.
|
||
if (IsFullscreen())
|
||
return;
|
||
|
||
// 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];
|
||
cocoa_bounds.origin.y = NSHeight([screen frame]) - size.height() - bounds.y();
|
||
|
||
[window_ setFrame:cocoa_bounds display:YES animate:animate];
|
||
user_set_bounds_maximized_ = IsMaximized() ? true : false;
|
||
UpdateWindowOriginalFrame();
|
||
}
|
||
|
||
gfx::Rect NativeWindowMac::GetBounds() const {
|
||
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() const {
|
||
return NativeWindow::IsNormal() && !IsSimpleFullScreen();
|
||
}
|
||
|
||
gfx::Rect NativeWindowMac::GetNormalBounds() const {
|
||
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::SetSizeConstraints(
|
||
const extensions::SizeConstraints& window_constraints) {
|
||
// Apply the size constraints to NSWindow.
|
||
if (window_constraints.HasMinimumSize())
|
||
[window_ setMinSize:window_constraints.GetMinimumSize().ToCGSize()];
|
||
if (window_constraints.HasMaximumSize())
|
||
[window_ setMaxSize:window_constraints.GetMaximumSize().ToCGSize()];
|
||
NativeWindow::SetSizeConstraints(window_constraints);
|
||
}
|
||
|
||
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());
|
||
}
|
||
};
|
||
|
||
// Apply the size constraints to NSWindow.
|
||
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);
|
||
}
|
||
|
||
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_ orderWindowByShuffling:NSWindowAbove relativeTo:window_id];
|
||
return true;
|
||
}
|
||
|
||
void NativeWindowMac::MoveTop() {
|
||
[window_ orderWindowByShuffling:NSWindowAbove relativeTo:0];
|
||
}
|
||
|
||
void NativeWindowMac::SetBorderless(bool borderless) {
|
||
SetStyleMask(!borderless, NSWindowStyleMaskTitled);
|
||
SetStyleMask(borderless, NSWindowStyleMaskBorderless);
|
||
}
|
||
|
||
void NativeWindowMac::SetResizable(bool resizable) {
|
||
ScopedDisableResize disable_resize;
|
||
SetStyleMask(resizable, NSWindowStyleMaskResizable);
|
||
|
||
bool was_fullscreenable = IsFullScreenable();
|
||
|
||
// Right now, resizable and fullscreenable are decoupled in
|
||
// documentation and on Windows/Linux. Chromium disables
|
||
// fullscreen collection behavior as well as the maximize traffic
|
||
// light in SetCanResize if resizability is false on macOS unless
|
||
// the window is both resizable and maximizable. We want consistent
|
||
// cross-platform behavior, so if resizability is disabled we disable
|
||
// the maximize button and ensure fullscreenability matches user setting.
|
||
SetCanResize(resizable);
|
||
SetFullScreenable(was_fullscreenable);
|
||
UpdateZoomButton();
|
||
}
|
||
|
||
bool NativeWindowMac::IsResizable() const {
|
||
const bool in_fs_transition = is_transitioning_fullscreen();
|
||
bool has_rs_mask = HasStyleMask(NSWindowStyleMaskResizable);
|
||
return has_rs_mask && !IsFullscreen() && !in_fs_transition;
|
||
}
|
||
|
||
void NativeWindowMac::SetMovable(bool movable) {
|
||
[window_ setMovable:movable];
|
||
}
|
||
|
||
bool NativeWindowMac::IsMovable() const {
|
||
return [window_ isMovable];
|
||
}
|
||
|
||
void NativeWindowMac::SetMinimizable(bool minimizable) {
|
||
SetStyleMask(minimizable, NSWindowStyleMaskMiniaturizable);
|
||
}
|
||
|
||
bool NativeWindowMac::IsMinimizable() const {
|
||
return HasStyleMask(NSWindowStyleMaskMiniaturizable);
|
||
}
|
||
|
||
void NativeWindowMac::SetMaximizable(bool maximizable) {
|
||
maximizable_ = maximizable;
|
||
UpdateZoomButton();
|
||
}
|
||
|
||
bool NativeWindowMac::IsMaximizable() const {
|
||
return [[window_ standardWindowButton:NSWindowZoomButton] isEnabled];
|
||
}
|
||
|
||
void NativeWindowMac::UpdateZoomButton() {
|
||
[[window_ standardWindowButton:NSWindowZoomButton]
|
||
setEnabled:HasStyleMask(NSWindowStyleMaskResizable) &&
|
||
(CanMaximize() || IsFullScreenable())];
|
||
}
|
||
|
||
void NativeWindowMac::SetFullScreenable(bool fullscreenable) {
|
||
SetCollectionBehavior(fullscreenable,
|
||
NSWindowCollectionBehaviorFullScreenPrimary);
|
||
// On EL Capitan this flag is required to hide fullscreen button.
|
||
SetCollectionBehavior(!fullscreenable,
|
||
NSWindowCollectionBehaviorFullScreenAuxiliary);
|
||
|
||
UpdateZoomButton();
|
||
}
|
||
|
||
bool NativeWindowMac::IsFullScreenable() const {
|
||
NSUInteger collectionBehavior = [window_ collectionBehavior];
|
||
return collectionBehavior & NSWindowCollectionBehaviorFullScreenPrimary;
|
||
}
|
||
|
||
void NativeWindowMac::SetClosable(bool closable) {
|
||
SetStyleMask(closable, NSWindowStyleMaskClosable);
|
||
}
|
||
|
||
bool NativeWindowMac::IsClosable() const {
|
||
return HasStyleMask(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;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
SetWindowLevel(level + relative_level);
|
||
}
|
||
|
||
std::string NativeWindowMac::GetAlwaysOnTopLevel() const {
|
||
std::string level_name = "normal";
|
||
|
||
int level = [window_ level];
|
||
if (level == NSFloatingWindowLevel) {
|
||
level_name = "floating";
|
||
} else if (level == NSTornOffMenuWindowLevel) {
|
||
level_name = "torn-off-menu";
|
||
} else if (level == NSModalPanelWindowLevel) {
|
||
level_name = "modal-panel";
|
||
} else if (level == NSMainMenuWindowLevel) {
|
||
level_name = "main-menu";
|
||
} else if (level == NSStatusWindowLevel) {
|
||
level_name = "status";
|
||
} else if (level == NSPopUpMenuWindowLevel) {
|
||
level_name = "pop-up-menu";
|
||
} else if (level == NSScreenSaverWindowLevel) {
|
||
level_name = "screen-saver";
|
||
}
|
||
|
||
return level_name;
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
ui::ZOrderLevel NativeWindowMac::GetZOrderLevel() const {
|
||
return widget()->GetZOrderLevel();
|
||
}
|
||
|
||
void NativeWindowMac::Center() {
|
||
[window_ center];
|
||
}
|
||
|
||
void NativeWindowMac::Invalidate() {
|
||
[[window_ contentView] setNeedsDisplay:YES];
|
||
}
|
||
|
||
void NativeWindowMac::OnTitleChanged() {
|
||
[window_ setTitle:base::SysUTF8ToNSString(GetTitle())];
|
||
|
||
if (buttons_proxy_)
|
||
[buttons_proxy_ redraw];
|
||
}
|
||
|
||
void NativeWindowMac::FlashFrame(bool flash) {
|
||
if (flash) {
|
||
attention_request_id_ = [NSApp requestUserAttention:NSCriticalRequest];
|
||
} else {
|
||
[NSApp cancelUserAttentionRequest:attention_request_id_];
|
||
attention_request_id_ = 0;
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::SetSkipTaskbar(bool skip) {}
|
||
|
||
bool NativeWindowMac::IsExcludedFromShownWindowsMenu() const {
|
||
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
|
||
return [window isExcludedFromWindowsMenu];
|
||
}
|
||
|
||
void NativeWindowMac::SetExcludedFromShownWindowsMenu(bool excluded) {
|
||
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
|
||
[window setExcludedFromWindowsMenu:excluded];
|
||
}
|
||
|
||
void NativeWindowMac::OnDisplayMetricsChanged(const display::Display& display,
|
||
uint32_t changed_metrics) {
|
||
// We only want to force screen recalibration if we're in simpleFullscreen
|
||
// mode.
|
||
if (!is_simple_fullscreen_)
|
||
return;
|
||
|
||
content::GetUIThreadTaskRunner({})->PostTask(
|
||
FROM_HERE, base::BindOnce(&NativeWindow::UpdateFrame, GetWeakPtr()));
|
||
}
|
||
|
||
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()) {
|
||
UpdateWindowOriginalFrame();
|
||
original_level_ = [window_ level];
|
||
}
|
||
|
||
simple_fullscreen_options_ = [NSApp currentSystemPresentationOptions];
|
||
simple_fullscreen_mask_ = [window styleMask];
|
||
|
||
// 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;
|
||
}
|
||
|
||
// Always hide the titlebar in simple fullscreen mode.
|
||
//
|
||
// Note that we must remove the NSWindowStyleMaskTitled style instead of
|
||
// using the [window_ setTitleVisibility:], as the latter would leave the
|
||
// window with rounded corners.
|
||
SetStyleMask(false, NSWindowStyleMaskTitled);
|
||
|
||
if (!window_button_visibility_.has_value()) {
|
||
// Lets keep previous behaviour - hide window controls in titled
|
||
// fullscreen mode when not specified otherwise.
|
||
InternalSetWindowButtonVisibility(false);
|
||
}
|
||
|
||
[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;
|
||
|
||
[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_);
|
||
|
||
// Restore default window controls visibility state.
|
||
if (!window_button_visibility_.has_value()) {
|
||
bool visibility;
|
||
if (has_frame())
|
||
visibility = true;
|
||
else
|
||
visibility = title_bar_style() != TitleBarStyle::kNormal;
|
||
InternalSetWindowButtonVisibility(visibility);
|
||
}
|
||
|
||
if (buttons_proxy_)
|
||
[buttons_proxy_ redraw];
|
||
}
|
||
}
|
||
|
||
bool NativeWindowMac::IsSimpleFullScreen() const {
|
||
return is_simple_fullscreen_;
|
||
}
|
||
|
||
void NativeWindowMac::SetKiosk(bool kiosk) {
|
||
if (kiosk && !is_kiosk_) {
|
||
kiosk_options_ = [NSApp currentSystemPresentationOptions];
|
||
NSApplicationPresentationOptions options =
|
||
NSApplicationPresentationHideDock |
|
||
NSApplicationPresentationHideMenuBar |
|
||
NSApplicationPresentationDisableAppleMenu |
|
||
NSApplicationPresentationDisableProcessSwitching |
|
||
NSApplicationPresentationDisableForceQuit |
|
||
NSApplicationPresentationDisableSessionTermination |
|
||
NSApplicationPresentationDisableHideApplication;
|
||
[NSApp setPresentationOptions:options];
|
||
is_kiosk_ = true;
|
||
fullscreen_before_kiosk_ = IsFullscreen();
|
||
if (!fullscreen_before_kiosk_)
|
||
SetFullScreen(true);
|
||
} else if (!kiosk && is_kiosk_) {
|
||
is_kiosk_ = false;
|
||
if (!fullscreen_before_kiosk_)
|
||
SetFullScreen(false);
|
||
|
||
// Set presentation options *after* asynchronously exiting
|
||
// fullscreen to ensure they take effect.
|
||
[NSApp setPresentationOptions:kiosk_options_];
|
||
}
|
||
}
|
||
|
||
bool NativeWindowMac::IsKiosk() const {
|
||
return is_kiosk_;
|
||
}
|
||
|
||
void NativeWindowMac::SetBackgroundColor(SkColor color) {
|
||
base::apple::ScopedCFTypeRef<CGColorRef> cgcolor(
|
||
skia::CGColorCreateFromSkColor(color));
|
||
[[[window_ contentView] layer] setBackgroundColor:cgcolor.get()];
|
||
}
|
||
|
||
SkColor NativeWindowMac::GetBackgroundColor() const {
|
||
CGColorRef color = [[[window_ contentView] layer] backgroundColor];
|
||
if (!color)
|
||
return SK_ColorTRANSPARENT;
|
||
return skia::CGColorRefToSkColor(color);
|
||
}
|
||
|
||
void NativeWindowMac::SetHasShadow(bool has_shadow) {
|
||
[window_ setHasShadow:has_shadow];
|
||
}
|
||
|
||
bool NativeWindowMac::HasShadow() const {
|
||
return [window_ hasShadow];
|
||
}
|
||
|
||
void NativeWindowMac::InvalidateShadow() {
|
||
[window_ invalidateShadow];
|
||
}
|
||
|
||
void NativeWindowMac::SetOpacity(const double opacity) {
|
||
const double boundedOpacity = std::clamp(opacity, 0.0, 1.0);
|
||
[window_ setAlphaValue:boundedOpacity];
|
||
}
|
||
|
||
double NativeWindowMac::GetOpacity() const {
|
||
return [window_ alphaValue];
|
||
}
|
||
|
||
void NativeWindowMac::SetRepresentedFilename(const std::string& filename) {
|
||
[window_ setRepresentedFilename:base::SysUTF8ToNSString(filename)];
|
||
if (buttons_proxy_)
|
||
[buttons_proxy_ redraw];
|
||
}
|
||
|
||
std::string NativeWindowMac::GetRepresentedFilename() const {
|
||
return base::SysNSStringToUTF8([window_ representedFilename]);
|
||
}
|
||
|
||
void NativeWindowMac::SetDocumentEdited(bool edited) {
|
||
[window_ setDocumentEdited:edited];
|
||
if (buttons_proxy_)
|
||
[buttons_proxy_ redraw];
|
||
}
|
||
|
||
bool NativeWindowMac::IsDocumentEdited() const {
|
||
return [window_ isDocumentEdited];
|
||
}
|
||
|
||
bool NativeWindowMac::IsHiddenInMissionControl() const {
|
||
NSUInteger collectionBehavior = [window_ collectionBehavior];
|
||
return collectionBehavior & NSWindowCollectionBehaviorTransient;
|
||
}
|
||
|
||
void NativeWindowMac::SetHiddenInMissionControl(bool hidden) {
|
||
SetCollectionBehavior(hidden, NSWindowCollectionBehaviorTransient);
|
||
}
|
||
|
||
void NativeWindowMac::SetIgnoreMouseEvents(bool ignore, bool forward) {
|
||
[window_ setIgnoresMouseEvents:ignore];
|
||
if (!ignore) {
|
||
SetForwardMouseMessages(NO);
|
||
} else {
|
||
SetForwardMouseMessages(forward);
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::SetContentProtection(bool enable) {
|
||
[window_
|
||
setSharingType:enable ? NSWindowSharingNone : NSWindowSharingReadOnly];
|
||
}
|
||
|
||
bool NativeWindowMac::IsContentProtected() const {
|
||
return [window_ sharingType] == NSWindowSharingNone;
|
||
}
|
||
|
||
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];
|
||
}
|
||
|
||
bool NativeWindowMac::IsFocusable() const {
|
||
return ![window_ disableKeyOrMainWindow];
|
||
}
|
||
|
||
void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
|
||
InternalSetParentWindow(parent, IsVisible());
|
||
}
|
||
|
||
gfx::NativeView NativeWindowMac::GetNativeView() const {
|
||
return gfx::NativeView([window_ contentView]);
|
||
}
|
||
|
||
gfx::NativeWindow NativeWindowMac::GetNativeWindow() const {
|
||
return gfx::NativeWindow(window_);
|
||
}
|
||
|
||
gfx::AcceleratedWidget NativeWindowMac::GetAcceleratedWidget() const {
|
||
return [window_ windowNumber];
|
||
}
|
||
|
||
content::DesktopMediaID NativeWindowMac::GetDesktopMediaID() const {
|
||
auto desktop_media_id = content::DesktopMediaID(
|
||
content::DesktopMediaID::TYPE_WINDOW, GetAcceleratedWidget());
|
||
// c.f.
|
||
// https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/media/webrtc/native_desktop_media_list.cc;l=775-780;drc=79502ab47f61bff351426f57f576daef02b1a8dc
|
||
// Refs https://github.com/electron/electron/pull/30507
|
||
// TODO(deepak1556): Match upstream for `kWindowCaptureMacV2`
|
||
#if 0
|
||
if (remote_cocoa::ScopedCGWindowID::Get(desktop_media_id.id)) {
|
||
desktop_media_id.window_id = desktop_media_id.id;
|
||
}
|
||
#endif
|
||
return desktop_media_id;
|
||
}
|
||
|
||
NativeWindowHandle NativeWindowMac::GetNativeWindowHandle() const {
|
||
return GetNativeView();
|
||
}
|
||
|
||
void NativeWindowMac::SetProgressBar(double progress,
|
||
const NativeWindow::ProgressState state) {
|
||
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]];
|
||
|
||
// For the first time API invoked, we need to create a ContentView in
|
||
// DockTile.
|
||
if (first_time) {
|
||
NSImageView* image_view = [[NSImageView alloc] init];
|
||
[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];
|
||
[progress_indicator setStyle:NSProgressIndicatorStyleBar];
|
||
[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];
|
||
}
|
||
|
||
NSProgressIndicator* progress_indicator = static_cast<NSProgressIndicator*>(
|
||
[[[dock_tile contentView] subviews] lastObject]);
|
||
if (progress < 0) {
|
||
[progress_indicator setHidden:YES];
|
||
} else if (progress > 1) {
|
||
[progress_indicator setHidden:NO];
|
||
[progress_indicator setIndeterminate:YES];
|
||
[progress_indicator setDoubleValue:1];
|
||
} else {
|
||
[progress_indicator setHidden:NO];
|
||
[progress_indicator setDoubleValue:progress];
|
||
}
|
||
[dock_tile display];
|
||
}
|
||
|
||
void NativeWindowMac::SetOverlayIcon(const gfx::Image& overlay,
|
||
const std::string& description) {}
|
||
|
||
void NativeWindowMac::SetVisibleOnAllWorkspaces(bool visible,
|
||
bool visibleOnFullScreen,
|
||
bool skipTransformProcessType) {
|
||
// In order for NSWindows to be visible on fullscreen we need to invoke
|
||
// 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.
|
||
if (!skipTransformProcessType) {
|
||
if (visibleOnFullScreen) {
|
||
Browser::Get()->DockHide();
|
||
} else {
|
||
Browser::Get()->DockShow(JavascriptEnvironment::GetIsolate());
|
||
}
|
||
}
|
||
|
||
SetCollectionBehavior(visible, NSWindowCollectionBehaviorCanJoinAllSpaces);
|
||
SetCollectionBehavior(visibleOnFullScreen,
|
||
NSWindowCollectionBehaviorFullScreenAuxiliary);
|
||
}
|
||
|
||
bool NativeWindowMac::IsVisibleOnAllWorkspaces() const {
|
||
NSUInteger collectionBehavior = [window_ collectionBehavior];
|
||
return collectionBehavior & NSWindowCollectionBehaviorCanJoinAllSpaces;
|
||
}
|
||
|
||
void NativeWindowMac::SetAutoHideCursor(bool auto_hide) {
|
||
[window_ setDisableAutoHideCursor:!auto_hide];
|
||
}
|
||
|
||
void NativeWindowMac::UpdateVibrancyRadii(bool fullscreen) {
|
||
NSVisualEffectView* vibrantView = [window_ vibrantView];
|
||
|
||
if (vibrantView != nil && !vibrancy_type_.empty()) {
|
||
const bool no_rounded_corner = !HasStyleMask(NSWindowStyleMaskTitled);
|
||
|
||
// If the window is modal, its corners are rounded unless
|
||
// the user has explicitly passed |roundedCorners: false|.
|
||
bool should_round_modal = !no_rounded_corner && is_modal();
|
||
// If the window is nonmodal, its corners are rounded if it is frameless and
|
||
// the user hasn't passed |roundedCorners: false|.
|
||
bool should_round_nonmodal =
|
||
!no_rounded_corner && !is_modal() && !has_frame();
|
||
|
||
if (should_round_nonmodal || should_round_modal) {
|
||
CGFloat radius = fullscreen ? 0.0f : 9.0f;
|
||
CGFloat dimension = 2 * radius + 1;
|
||
NSImage* maskImage =
|
||
[NSImage imageWithSize:NSMakeSize(dimension, dimension)
|
||
flipped:NO
|
||
drawingHandler:^BOOL(NSRect rect) {
|
||
[[NSBezierPath bezierPathWithRoundedRect:rect
|
||
xRadius:radius
|
||
yRadius:radius] fill];
|
||
return YES;
|
||
}];
|
||
|
||
maskImage.capInsets = NSEdgeInsetsMake(radius, radius, radius, radius);
|
||
maskImage.resizingMode = NSImageResizingModeStretch;
|
||
[vibrantView setMaskImage:maskImage];
|
||
}
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::UpdateWindowOriginalFrame() {
|
||
original_frame_ = [window_ frame];
|
||
}
|
||
|
||
void NativeWindowMac::SetVibrancy(const std::string& type, int duration) {
|
||
NativeWindow::SetVibrancy(type, duration);
|
||
|
||
NSVisualEffectView* vibrantView = [window_ vibrantView];
|
||
views::View* rootView = GetContentsView();
|
||
bool animate = duration > 0;
|
||
|
||
if (type.empty()) {
|
||
vibrancy_type_ = type;
|
||
|
||
auto cleanupHandler = ^{
|
||
if (vibrant_native_view_host_ != nullptr) {
|
||
// Transfers ownership back to caller in the form of a unique_ptr which
|
||
// is subsequently deleted.
|
||
rootView->RemoveChildViewT(vibrant_native_view_host_);
|
||
vibrant_native_view_host_ = nullptr;
|
||
}
|
||
|
||
if (vibrantView != nil) {
|
||
[window_ setVibrantView:nil];
|
||
}
|
||
};
|
||
|
||
if (animate) {
|
||
__weak ElectronNSWindowDelegate* weak_delegate = window_delegate_;
|
||
[NSAnimationContext
|
||
runAnimationGroup:^(NSAnimationContext* context) {
|
||
context.duration = duration / 1000.0f;
|
||
vibrantView.animator.alphaValue = 0.0;
|
||
}
|
||
completionHandler:^{
|
||
if (!weak_delegate)
|
||
return;
|
||
|
||
cleanupHandler();
|
||
}];
|
||
} else {
|
||
cleanupHandler();
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
NSVisualEffectMaterial vibrancyType{};
|
||
if (type == "titlebar") {
|
||
vibrancyType = NSVisualEffectMaterialTitlebar;
|
||
} else 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 == "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) {
|
||
vibrancy_type_ = type;
|
||
|
||
if (vibrantView == nil) {
|
||
vibrantView = [[NSVisualEffectView alloc]
|
||
initWithFrame:[[window_ contentView] bounds]];
|
||
[window_ setVibrantView:vibrantView];
|
||
|
||
[vibrantView
|
||
setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
|
||
[vibrantView setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
|
||
|
||
if (visual_effect_state_ == VisualEffectState::kActive) {
|
||
[vibrantView setState:NSVisualEffectStateActive];
|
||
} else if (visual_effect_state_ == VisualEffectState::kInactive) {
|
||
[vibrantView setState:NSVisualEffectStateInactive];
|
||
} else {
|
||
[vibrantView setState:NSVisualEffectStateFollowsWindowActiveState];
|
||
}
|
||
|
||
// Vibrant view is inserted into the root view hierarchy underneath all
|
||
// other views.
|
||
vibrant_native_view_host_ = rootView->AddChildViewAt(
|
||
std::make_unique<views::NativeViewHost>(), 0);
|
||
vibrant_native_view_host_->Attach(gfx::NativeView(vibrantView));
|
||
|
||
rootView->DeprecatedLayoutImmediately();
|
||
|
||
UpdateVibrancyRadii(IsFullscreen());
|
||
}
|
||
|
||
if (animate) {
|
||
[vibrantView setAlphaValue:0.0];
|
||
[NSAnimationContext
|
||
runAnimationGroup:^(NSAnimationContext* context) {
|
||
context.duration = duration / 1000.0f;
|
||
vibrantView.animator.alphaValue = 1.0;
|
||
}
|
||
completionHandler:nil];
|
||
}
|
||
|
||
[vibrantView setMaterial:vibrancyType];
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::SetWindowButtonVisibility(bool visible) {
|
||
window_button_visibility_ = visible;
|
||
if (buttons_proxy_) {
|
||
if (visible)
|
||
[buttons_proxy_ redraw];
|
||
[buttons_proxy_ setVisible:visible];
|
||
}
|
||
|
||
if (title_bar_style() != TitleBarStyle::kCustomButtonsOnHover)
|
||
InternalSetWindowButtonVisibility(visible);
|
||
|
||
NotifyLayoutWindowControlsOverlay();
|
||
}
|
||
|
||
bool NativeWindowMac::GetWindowButtonVisibility() const {
|
||
return ![window_ standardWindowButton:NSWindowZoomButton].hidden ||
|
||
![window_ standardWindowButton:NSWindowMiniaturizeButton].hidden ||
|
||
![window_ standardWindowButton:NSWindowCloseButton].hidden;
|
||
}
|
||
|
||
void NativeWindowMac::SetWindowButtonPosition(
|
||
std::optional<gfx::Point> position) {
|
||
traffic_light_position_ = std::move(position);
|
||
if (buttons_proxy_) {
|
||
[buttons_proxy_ setMargin:traffic_light_position_];
|
||
NotifyLayoutWindowControlsOverlay();
|
||
}
|
||
}
|
||
|
||
std::optional<gfx::Point> NativeWindowMac::GetWindowButtonPosition() const {
|
||
return traffic_light_position_;
|
||
}
|
||
|
||
void NativeWindowMac::RedrawTrafficLights() {
|
||
if (buttons_proxy_ && !IsFullscreen())
|
||
[buttons_proxy_ redraw];
|
||
}
|
||
|
||
// In simpleFullScreen mode, update the frame for new bounds.
|
||
void NativeWindowMac::UpdateFrame() {
|
||
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
|
||
NSRect fullscreenFrame = [window.screen frame];
|
||
[window setFrame:fullscreenFrame display:YES animate:YES];
|
||
}
|
||
|
||
void NativeWindowMac::SetTouchBar(
|
||
std::vector<gin_helper::PersistentDictionary> items) {
|
||
touch_bar_ = [[ElectronTouchBar alloc] initWithDelegate:window_delegate_
|
||
window:this
|
||
settings:std::move(items)];
|
||
[window_ setTouchBar:nil];
|
||
}
|
||
|
||
void NativeWindowMac::RefreshTouchBarItem(const std::string& item_id) {
|
||
if (touch_bar_ && [window_ touchBar])
|
||
[touch_bar_ refreshTouchBarItem:[window_ touchBar] id:item_id];
|
||
}
|
||
|
||
void NativeWindowMac::SetEscapeTouchBarItem(
|
||
gin_helper::PersistentDictionary item) {
|
||
if (touch_bar_ && [window_ touchBar])
|
||
[touch_bar_ setEscapeTouchBarItem:std::move(item)
|
||
forTouchBar:[window_ touchBar]];
|
||
}
|
||
|
||
void NativeWindowMac::SelectPreviousTab() {
|
||
[window_ selectPreviousTab:nil];
|
||
}
|
||
|
||
void NativeWindowMac::SelectNextTab() {
|
||
[window_ selectNextTab:nil];
|
||
}
|
||
|
||
void NativeWindowMac::ShowAllTabs() {
|
||
[window_ toggleTabOverview:nil];
|
||
}
|
||
|
||
void NativeWindowMac::MergeAllWindows() {
|
||
[window_ mergeAllWindows:nil];
|
||
}
|
||
|
||
void NativeWindowMac::MoveTabToNewWindow() {
|
||
[window_ moveTabToNewWindow:nil];
|
||
}
|
||
|
||
void NativeWindowMac::ToggleTabBar() {
|
||
[window_ toggleTabBar:nil];
|
||
}
|
||
|
||
bool NativeWindowMac::AddTabbedWindow(NativeWindow* window) {
|
||
if (window_ == window->GetNativeWindow().GetNativeNSWindow()) {
|
||
return false;
|
||
} else {
|
||
[window_ addTabbedWindow:window->GetNativeWindow().GetNativeNSWindow()
|
||
ordered:NSWindowAbove];
|
||
}
|
||
return true;
|
||
}
|
||
|
||
std::optional<std::string> NativeWindowMac::GetTabbingIdentifier() const {
|
||
if ([window_ tabbingMode] == NSWindowTabbingModeDisallowed)
|
||
return std::nullopt;
|
||
|
||
return base::SysNSStringToUTF8([window_ tabbingIdentifier]);
|
||
}
|
||
|
||
void NativeWindowMac::SetAspectRatio(double aspect_ratio,
|
||
const gfx::Size& extra_size) {
|
||
NativeWindow::SetAspectRatio(aspect_ratio, extra_size);
|
||
|
||
// Reset the behaviour to default if aspect_ratio is set to 0 or less.
|
||
if (aspect_ratio > 0.0) {
|
||
NSSize aspect_ratio_size = NSMakeSize(aspect_ratio, 1.0);
|
||
if (has_frame())
|
||
[window_ setContentAspectRatio:aspect_ratio_size];
|
||
else
|
||
[window_ setAspectRatio:aspect_ratio_size];
|
||
} else {
|
||
[window_ setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::PreviewFile(const std::string& path,
|
||
const std::string& display_name) {
|
||
preview_item_ = [[ElectronPreviewItem alloc]
|
||
initWithURL:[NSURL fileURLWithPath:base::SysUTF8ToNSString(path)]
|
||
title:base::SysUTF8ToNSString(display_name)];
|
||
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
|
||
}
|
||
|
||
void NativeWindowMac::CloseFilePreview() {
|
||
// Need to be careful about checking [QLPreviewPanel sharedPreviewPanel] as
|
||
// simply accessing it will cause it to reinitialize and reappear.
|
||
if ([QLPreviewPanel sharedPreviewPanelExists] && preview_item_) {
|
||
[[QLPreviewPanel sharedPreviewPanel] close];
|
||
preview_item_ = nil;
|
||
}
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::NotifyWindowEnterFullScreen() {
|
||
NativeWindow::NotifyWindowEnterFullScreen();
|
||
// Restore the window title under fullscreen mode.
|
||
if (buttons_proxy_)
|
||
[window_ setTitleVisibility:NSWindowTitleVisible];
|
||
|
||
if (transparent() || !has_frame())
|
||
[window_ setTitlebarAppearsTransparent:NO];
|
||
}
|
||
|
||
void NativeWindowMac::NotifyWindowLeaveFullScreen() {
|
||
NativeWindow::NotifyWindowLeaveFullScreen();
|
||
// Restore window buttons.
|
||
if (buttons_proxy_ && window_button_visibility_.value_or(true)) {
|
||
[buttons_proxy_ redraw];
|
||
[buttons_proxy_ setVisible:YES];
|
||
}
|
||
|
||
if (transparent() || !has_frame())
|
||
[window_ setTitlebarAppearsTransparent:YES];
|
||
}
|
||
|
||
void NativeWindowMac::NotifyWindowWillEnterFullScreen() {
|
||
UpdateVibrancyRadii(true);
|
||
}
|
||
|
||
void NativeWindowMac::NotifyWindowDidFailToEnterFullScreen() {
|
||
UpdateVibrancyRadii(false);
|
||
|
||
if (buttons_proxy_)
|
||
[buttons_proxy_ redraw];
|
||
}
|
||
|
||
void NativeWindowMac::NotifyWindowWillLeaveFullScreen() {
|
||
if (buttons_proxy_) {
|
||
// Hide window title when leaving fullscreen.
|
||
[window_ setTitleVisibility:NSWindowTitleHidden];
|
||
// Hide the container otherwise traffic light buttons jump.
|
||
[buttons_proxy_ setVisible:NO];
|
||
}
|
||
UpdateVibrancyRadii(false);
|
||
}
|
||
|
||
void NativeWindowMac::SetActive(bool is_key) {
|
||
is_active_ = is_key;
|
||
}
|
||
|
||
bool NativeWindowMac::IsActive() const {
|
||
return is_active_;
|
||
}
|
||
|
||
void NativeWindowMac::Cleanup() {
|
||
DCHECK(!IsClosed());
|
||
ui::NativeTheme::GetInstanceForNativeUi()->RemoveObserver(this);
|
||
display::Screen::Get()->RemoveObserver(this);
|
||
[window_ cleanup];
|
||
}
|
||
|
||
std::unique_ptr<views::FrameView> NativeWindowMac::CreateFrameView(
|
||
views::Widget* widget) {
|
||
CHECK(!frame_view_client_);
|
||
frame_view_client_ =
|
||
std::make_unique<NativeAppWindowFrameViewMacClient>(widget, this);
|
||
return std::make_unique<views::NativeFrameViewMac>(widget,
|
||
frame_view_client_.get());
|
||
}
|
||
|
||
bool NativeWindowMac::HasStyleMask(NSUInteger flag) const {
|
||
return [window_ styleMask] & flag;
|
||
}
|
||
|
||
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;
|
||
|
||
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
|
||
// a bug of Cocoa or macOS.
|
||
SetMaximizable(maximizable_);
|
||
}
|
||
|
||
void NativeWindowMac::SetCollectionBehavior(bool on, NSUInteger flag) {
|
||
if (on)
|
||
[window_ setCollectionBehavior:[window_ collectionBehavior] | flag];
|
||
else
|
||
[window_ setCollectionBehavior:[window_ collectionBehavior] & (~flag)];
|
||
|
||
// Change collectionBehavior will make the zoom button revert to default,
|
||
// probably a bug of Cocoa or macOS.
|
||
SetMaximizable(maximizable_);
|
||
}
|
||
|
||
views::View* NativeWindowMac::GetContentsView() {
|
||
return root_view_.get();
|
||
}
|
||
|
||
bool NativeWindowMac::CanMaximize() const {
|
||
return maximizable_;
|
||
}
|
||
|
||
void NativeWindowMac::OnNativeThemeUpdated(ui::NativeTheme* observed_theme) {
|
||
content::GetUIThreadTaskRunner({})->PostTask(
|
||
FROM_HERE,
|
||
base::BindOnce(&NativeWindow::RedrawTrafficLights, GetWeakPtr()));
|
||
}
|
||
|
||
void NativeWindowMac::AddContentViewLayers() {
|
||
// 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()) {
|
||
CALayer* background_layer = [[CALayer alloc] init];
|
||
[background_layer
|
||
setAutoresizingMask:kCALayerWidthSizable | kCALayerHeightSizable];
|
||
[[window_ contentView] setLayer:background_layer];
|
||
}
|
||
[[window_ contentView] setWantsLayer:YES];
|
||
}
|
||
}
|
||
|
||
void NativeWindowMac::InternalSetWindowButtonVisibility(bool visible) {
|
||
[[window_ standardWindowButton:NSWindowCloseButton] setHidden:!visible];
|
||
[[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:!visible];
|
||
[[window_ standardWindowButton:NSWindowZoomButton] setHidden:!visible];
|
||
}
|
||
|
||
void NativeWindowMac::InternalSetParentWindow(NativeWindow* new_parent,
|
||
bool attach) {
|
||
if (is_modal())
|
||
return;
|
||
|
||
// Do not remove/add if we are already properly attached.
|
||
if (attach && new_parent &&
|
||
[window_ parentWindow] ==
|
||
new_parent->GetNativeWindow().GetNativeNSWindow())
|
||
return;
|
||
|
||
// Remove current parent window.
|
||
RemoveChildFromParentWindow();
|
||
|
||
// Set new parent window.
|
||
if (new_parent) {
|
||
new_parent->add_child_window(this);
|
||
if (attach)
|
||
new_parent->AttachChildren();
|
||
}
|
||
|
||
NativeWindow::SetParentWindow(new_parent);
|
||
}
|
||
|
||
void NativeWindowMac::SetForwardMouseMessages(bool forward) {
|
||
[window_ setAcceptsMouseMovedEvents:forward];
|
||
}
|
||
|
||
std::optional<gfx::Rect> NativeWindowMac::GetWindowControlsOverlayRect() {
|
||
if (!titlebar_overlay_)
|
||
return std::nullopt;
|
||
|
||
// On macOS, when in fullscreen mode, window controls (the menu bar, title
|
||
// bar, and toolbar) are attached to a separate NSView that slides down from
|
||
// the top of the screen, independent of, and overlapping the WebContents.
|
||
// Disable WCO when in fullscreen, because this space is inaccessible to
|
||
// WebContents. https://crbug.com/915110.
|
||
if (IsFullscreen())
|
||
return gfx::Rect();
|
||
|
||
if (buttons_proxy_ && window_button_visibility_.value_or(true)) {
|
||
NSRect buttons = [buttons_proxy_ getButtonsContainerBounds];
|
||
gfx::Rect overlay;
|
||
overlay.set_width(GetContentSize().width() - NSWidth(buttons));
|
||
overlay.set_height([buttons_proxy_ useCustomHeight]
|
||
? titlebar_overlay_height()
|
||
: NSHeight(buttons));
|
||
|
||
if (!base::i18n::IsRTL())
|
||
overlay.set_x(NSMaxX(buttons));
|
||
|
||
return overlay;
|
||
}
|
||
|
||
return std::nullopt;
|
||
}
|
||
|
||
void NativeWindowMac::OnWidgetInitialized() {
|
||
// |window_| is not yet assigned when this function is called, so we need to
|
||
// get the window from the widget.
|
||
NSWindow* window = widget()->GetNativeWindow().GetNativeNSWindow();
|
||
|
||
// In |NativeWidgetNSWindowBridge::InitCompositorView| in Chromium,
|
||
// translucent windows are assigned a clear background color. This causes
|
||
// undesirable side effects, such as a window with vibrancy losing its natural
|
||
// system border highlight. We undo that behavior here.
|
||
//
|
||
// Ref:
|
||
// https://source.chromium.org/chromium/chromium/src/+/main:components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm;l=1341-1342;drc=e7c8be576285195257b0813326b3bab154dc7e73
|
||
if (!transparent()) {
|
||
if ([[window backgroundColor] isEqual:NSColor.clearColor]) {
|
||
[window setBackgroundColor:NSColor.windowBackgroundColor];
|
||
}
|
||
if (![window isOpaque]) {
|
||
[window setOpaque:YES];
|
||
}
|
||
}
|
||
}
|
||
|
||
// static
|
||
std::unique_ptr<NativeWindow> NativeWindow::Create(
|
||
const gin_helper::Dictionary& options,
|
||
NativeWindow* parent) {
|
||
return std::make_unique<NativeWindowMac>(options, parent);
|
||
}
|
||
|
||
} // namespace electron
|