[go: nahoru, domu]

blob: 43bf8e8aef22638fb302837f702657b8d33d2341 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/remote_cocoa/app_shim/immersive_mode_tabbed_controller.h"
#include "base/apple/foundation_util.h"
#include "base/functional/callback_forward.h"
#import "components/remote_cocoa/app_shim/bridged_content_view.h"
#include "components/remote_cocoa/app_shim/immersive_mode_controller.h"
namespace remote_cocoa {
ImmersiveModeTabbedController::ImmersiveModeTabbedController(
NativeWidgetMacNSWindow* browser_window,
NativeWidgetMacNSWindow* overlay_window,
NativeWidgetMacNSWindow* tab_window)
: ImmersiveModeController(browser_window, overlay_window) {
tab_window_ = tab_window;
browser_window.titleVisibility = NSWindowTitleHidden;
tab_titlebar_view_controller_ =
[[NSTitlebarAccessoryViewController alloc] init];
tab_titlebar_view_controller_.view = [[NSView alloc] init];
// The view is pinned to the opposite side of the traffic lights. A view long
// enough is able to paint underneath the traffic lights. This also works with
// RTL setups.
tab_titlebar_view_controller_.layoutAttribute = NSLayoutAttributeTrailing;
}
ImmersiveModeTabbedController::~ImmersiveModeTabbedController() {
StopObservingChildWindows(tab_window_);
browser_window().toolbar = nil;
BridgedContentView* tab_content_view = tab_content_view_;
[tab_content_view removeFromSuperview];
tab_window_.contentView = tab_content_view;
[tab_titlebar_view_controller_ removeFromParentViewController];
tab_titlebar_view_controller_ = nil;
}
void ImmersiveModeTabbedController::Enable() {
ImmersiveModeController::Enable();
BridgedContentView* tab_content_view =
base::apple::ObjCCastStrict<BridgedContentView>(tab_window_.contentView);
[tab_content_view removeFromSuperview];
tab_content_view_ = tab_content_view;
// The ordering of resetting the `contentView` is important for macOS 12 and
// below. `tab_content_view_` needs to be removed from the
// `tab_window_.contentView` property before adding
// `tab_content_view_` to a new NSView tree. We will be left
// with a blank view if this ordering is not maintained.
tab_window_.contentView =
[[BridgedContentView alloc] initWithBridge:tab_content_view_.bridge
bounds:gfx::Rect()];
// This will allow the NSToolbarFullScreenWindow to become key when
// interacting with the tab strip.
// The `overlay_window_` is handled the same way in ImmersiveModeController.
// See the comment there for more details.
tab_window_.ignoresMouseEvents = YES;
[tab_titlebar_view_controller_.view addSubview:tab_content_view];
[tab_titlebar_view_controller_.view setFrameSize:tab_window_.frame.size];
tab_titlebar_view_controller_.fullScreenMinHeight =
tab_window_.frame.size.height;
// Keep the tab content view's size in sync with its parent view.
tab_content_view_.translatesAutoresizingMaskIntoConstraints = NO;
[tab_content_view_.heightAnchor
constraintEqualToAnchor:tab_content_view_.superview.heightAnchor]
.active = YES;
[tab_content_view_.widthAnchor
constraintEqualToAnchor:tab_content_view_.superview.widthAnchor]
.active = YES;
[tab_content_view_.centerXAnchor
constraintEqualToAnchor:tab_content_view_.superview.centerXAnchor]
.active = YES;
[tab_content_view_.centerYAnchor
constraintEqualToAnchor:tab_content_view_.superview.centerYAnchor]
.active = YES;
ObserveChildWindows(tab_window_);
// The presence of a visible NSToolbar causes the titlebar to be revealed.
NSToolbar* toolbar = [[NSToolbar alloc] init];
// Remove the baseline separator for macOS 10.15 and earlier. This has no
// effect on macOS 11 and above. See
// `-[ImmersiveModeTitlebarViewController separatorView]` for removing the
// separator on macOS 11+.
toolbar.showsBaselineSeparator = NO;
browser_window().toolbar = toolbar;
// `UpdateToolbarVisibility()` will make the toolbar visible as necessary.
UpdateToolbarVisibility(last_used_style());
}
void ImmersiveModeTabbedController::UpdateToolbarVisibility(
mojom::ToolbarVisibilityStyle style) {
// Don't make changes when a reveal lock is active. Do update the
// `last_used_style` so the style will be updated once all outstanding reveal
// locks are released.
if (reveal_lock_count() > 0) {
set_last_used_style(style);
return;
}
// TODO(https://crbug.com/1426944): A NSTitlebarAccessoryViewController hosted
// in the titlebar, as opposed to above or below it, does not hide/show when
// using the `hidden` property. Instead we must entirely remove the view
// controller to make the view hide. Switch to using the `hidden` property
// once Apple resolves this bug.
switch (style) {
case mojom::ToolbarVisibilityStyle::kAlways:
AddController();
TitlebarReveal();
break;
case mojom::ToolbarVisibilityStyle::kAutohide:
AddController();
TitlebarHide();
break;
case mojom::ToolbarVisibilityStyle::kNone:
RemoveController();
TitlebarHide();
break;
}
ImmersiveModeController::UpdateToolbarVisibility(style);
// During fullscreen restore or split screen restore tab window can be left
// without a parent, leading to the window being hidden which causes
// compositing to stop. This call ensures that tab window is parented to
// overlay window and is in the correct z-order.
OrderTabWindowZOrderOnTop();
// macOS 10.15 does not call `OnTitlebarFrameDidChange` as often as newer
// versions of macOS. Add a layout call here and in `RevealLock` and
// `RevealUnlock` to pickup the slack. There is no harm in extra layout calls
// on newer versions of macOS, -setFrameOrigin: is essentially a NOP when the
// frame size doesn't change.
LayoutWindowWithAnchorView(tab_window_, tab_content_view_);
}
void ImmersiveModeTabbedController::AddController() {
NSWindow* window = browser_window();
if (![window.titlebarAccessoryViewControllers
containsObject:tab_titlebar_view_controller_]) {
[window addTitlebarAccessoryViewController:tab_titlebar_view_controller_];
}
}
void ImmersiveModeTabbedController::RemoveController() {
[tab_titlebar_view_controller_ removeFromParentViewController];
}
void ImmersiveModeTabbedController::OnTopViewBoundsChanged(
const gfx::Rect& bounds) {
ImmersiveModeController::OnTopViewBoundsChanged(bounds);
NSRect frame = NSRectFromCGRect(bounds.ToCGRect());
[tab_titlebar_view_controller_.view
setFrameSize:NSMakeSize(
frame.size.width,
tab_titlebar_view_controller_.view.frame.size.height)];
}
void ImmersiveModeTabbedController::RevealLock() {
TitlebarReveal();
// Call after TitlebarReveal() for a proper layout.
ImmersiveModeController::RevealLock();
LayoutWindowWithAnchorView(tab_window_, tab_content_view_);
}
void ImmersiveModeTabbedController::RevealUnlock() {
// The reveal lock count will be updated in
// ImmersiveModeController::RevealUnlock(), count 1 or less here as unlocked.
if (reveal_lock_count() < 2 &&
last_used_style() == mojom::ToolbarVisibilityStyle::kAutohide) {
TitlebarHide();
}
// Call after TitlebarHide() for a proper layout.
ImmersiveModeController::RevealUnlock();
LayoutWindowWithAnchorView(tab_window_, tab_content_view_);
}
void ImmersiveModeTabbedController::TitlebarReveal() {
browser_window().toolbar.visible = YES;
}
void ImmersiveModeTabbedController::TitlebarHide() {
browser_window().toolbar.visible = NO;
}
void ImmersiveModeTabbedController::OnTitlebarFrameDidChange(NSRect frame) {
ImmersiveModeController::OnTitlebarFrameDidChange(frame);
LayoutWindowWithAnchorView(tab_window_, tab_content_view_);
}
void ImmersiveModeTabbedController::OnChildWindowAdded(NSWindow* child) {
// The `tab_window_` is a child of the `overlay_window_`. Ignore
// the `tab_window_`.
if (child == tab_window_) {
return;
}
OrderTabWindowZOrderOnTop();
ImmersiveModeController::OnChildWindowAdded(child);
}
void ImmersiveModeTabbedController::OnChildWindowRemoved(NSWindow* child) {
// The `tab_window_` is a child of the `overlay_window_`. Ignore
// the `tab_window_`.
if (child == tab_window_) {
return;
}
ImmersiveModeController::OnChildWindowRemoved(child);
}
bool ImmersiveModeTabbedController::ShouldObserveChildWindow(NSWindow* child) {
// Filter out the `tab_window_`.
if (child == tab_window_) {
return false;
}
return ImmersiveModeController::ShouldObserveChildWindow(child);
}
bool ImmersiveModeTabbedController::IsTabbed() {
return true;
}
void ImmersiveModeTabbedController::OrderTabWindowZOrderOnTop() {
// Keep the tab window on top of its siblings. This will allow children of tab
// window to always be z-order on top of overlay window children.
// Practically this allows for the tab preview hover card to be z-order on top
// of omnibox results popup.
if (overlay_window().childWindows.lastObject != tab_window_) {
[overlay_window() removeChildWindow:tab_window_];
[overlay_window() addChildWindow:tab_window_ ordered:NSWindowAbove];
}
}
} // namespace remote_cocoa