blob: a7e8fba9cf33bdecb65c578d45305c3807008b44 [file] [log] [blame]
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/frame/immersive_mode_controller_ash.h"
#include "ash/public/cpp/immersive/immersive_revealed_lock.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/public/interfaces/window_state_type.mojom.h"
#include "base/macros.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/ui/ash/ash_util.h"
#include "chrome/browser/ui/ash/tablet_mode_client.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
#include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/web_contents.h"
#include "services/ui/public/cpp/property_type_converters.h"
#include "services/ui/public/interfaces/window_manager.mojom.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/mus/mus_types.h"
#include "ui/aura/mus/property_converter.h"
#include "ui/aura/mus/window_port_mus.h"
#include "ui/aura/window.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/paint_context.h"
#include "ui/compositor/paint_recorder.h"
#include "ui/views/background.h"
#include "ui/views/mus/mus_client.h"
#include "ui/views/view.h"
#include "ui/views/widget/native_widget_aura.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/non_client_view.h"
#include "ui/wm/core/window_util.h"
namespace {
// Converts from ImmersiveModeController::AnimateReveal to
// ash::ImmersiveFullscreenController::AnimateReveal.
ash::ImmersiveFullscreenController::AnimateReveal
ToImmersiveFullscreenControllerAnimateReveal(
ImmersiveModeController::AnimateReveal animate_reveal) {
switch (animate_reveal) {
case ImmersiveModeController::ANIMATE_REVEAL_YES:
return ash::ImmersiveFullscreenController::ANIMATE_REVEAL_YES;
case ImmersiveModeController::ANIMATE_REVEAL_NO:
return ash::ImmersiveFullscreenController::ANIMATE_REVEAL_NO;
}
NOTREACHED();
return ash::ImmersiveFullscreenController::ANIMATE_REVEAL_NO;
}
class ImmersiveRevealedLockAsh : public ImmersiveRevealedLock {
public:
explicit ImmersiveRevealedLockAsh(ash::ImmersiveRevealedLock* lock)
: lock_(lock) {}
private:
std::unique_ptr<ash::ImmersiveRevealedLock> lock_;
DISALLOW_COPY_AND_ASSIGN(ImmersiveRevealedLockAsh);
};
// View responsible for mirroring the content of the TopContainer. This is done
// by way of mirroring the actual layers.
class TopContainerMirrorView : public views::View {
public:
explicit TopContainerMirrorView(views::View* view) : view_(view) {
DCHECK(view_->layer());
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
// At this point we have no size. Wait for the first resize before we
// create the mirrored layer.
}
~TopContainerMirrorView() override {}
// views::View:
void OnBoundsChanged(const gfx::Rect& previous_bounds) override {
if (mirrored_layer_tree_owner_ &&
mirrored_layer_tree_owner_->root()->size() == size()) {
return;
}
mirrored_layer_tree_owner_.reset();
DCHECK(view_->layer()); // SetPaintToLayer() should have been called.
mirrored_layer_tree_owner_ = wm::MirrorLayers(view_, false);
mirrored_layer_tree_owner_->root()->SetBounds(gfx::Rect(size()));
layer()->Add(mirrored_layer_tree_owner_->root());
}
private:
views::View* view_;
std::unique_ptr<ui::LayerTreeOwner> mirrored_layer_tree_owner_;
DISALLOW_COPY_AND_ASSIGN(TopContainerMirrorView);
};
} // namespace
ImmersiveModeControllerAsh::ImmersiveModeControllerAsh()
: ImmersiveModeController(Type::ASH),
controller_(new ash::ImmersiveFullscreenController) {}
ImmersiveModeControllerAsh::~ImmersiveModeControllerAsh() = default;
void ImmersiveModeControllerAsh::Init(BrowserView* browser_view) {
browser_view_ = browser_view;
controller_->Init(this, browser_view_->frame(),
browser_view_->top_container());
}
void ImmersiveModeControllerAsh::SetEnabled(bool enabled) {
if (controller_->IsEnabled() == enabled)
return;
EnableWindowObservers(enabled);
controller_->SetEnabled(browser_view_->browser()->is_app() ?
ash::ImmersiveFullscreenController::WINDOW_TYPE_HOSTED_APP :
ash::ImmersiveFullscreenController::WINDOW_TYPE_BROWSER
, enabled);
}
bool ImmersiveModeControllerAsh::IsEnabled() const {
return controller_->IsEnabled();
}
bool ImmersiveModeControllerAsh::ShouldHideTopViews() const {
return controller_->IsEnabled() && !controller_->IsRevealed();
}
bool ImmersiveModeControllerAsh::IsRevealed() const {
return controller_->IsRevealed();
}
int ImmersiveModeControllerAsh::GetTopContainerVerticalOffset(
const gfx::Size& top_container_size) const {
if (!IsEnabled())
return 0;
return static_cast<int>(top_container_size.height() *
(visible_fraction_ - 1));
}
ImmersiveRevealedLock* ImmersiveModeControllerAsh::GetRevealedLock(
AnimateReveal animate_reveal) {
return new ImmersiveRevealedLockAsh(controller_->GetRevealedLock(
ToImmersiveFullscreenControllerAnimateReveal(animate_reveal)));
}
void ImmersiveModeControllerAsh::OnFindBarVisibleBoundsChanged(
const gfx::Rect& new_visible_bounds_in_screen) {
find_bar_visible_bounds_in_screen_ = new_visible_bounds_in_screen;
}
bool ImmersiveModeControllerAsh::ShouldStayImmersiveAfterExitingFullscreen() {
// TODO(crbug.com/760811): Support tablet mode in mash.
if (ash_util::IsRunningInMash())
return false;
return !browser_view_->IsBrowserTypeNormal() &&
TabletModeClient::Get()->tablet_mode_enabled() &&
TabletModeClient::Get()->auto_hide_title_bars();
}
views::Widget* ImmersiveModeControllerAsh::GetRevealWidget() {
return mash_reveal_widget_.get();
}
void ImmersiveModeControllerAsh::OnWidgetActivationChanged(
views::Widget* widget,
bool active) {
if (browser_view_->IsBrowserTypeNormal())
return;
// TODO(crbug.com/760811): Support tablet mode in mash.
if (ash_util::IsRunningInMash() ||
!(TabletModeClient::Get()->tablet_mode_enabled() &&
TabletModeClient::Get()->auto_hide_title_bars())) {
return;
}
// Enable immersive mode if the widget is activated. Do not disable immersive
// mode if the widget deactivates, but is not minimized.
controller_->SetEnabled(
browser_view_->browser()->is_app()
? ash::ImmersiveFullscreenController::WINDOW_TYPE_HOSTED_APP
: ash::ImmersiveFullscreenController::WINDOW_TYPE_BROWSER,
active || !widget->IsMinimized());
}
void ImmersiveModeControllerAsh::EnableWindowObservers(bool enable) {
if (observers_enabled_ == enable)
return;
observers_enabled_ = enable;
aura::Window* native_window = browser_view_->GetNativeWindow();
aura::Window* target_window = ash_util::IsRunningInMash()
? native_window->GetRootWindow()
: native_window;
content::Source<FullscreenController> source(browser_view_->browser()
->exclusive_access_manager()
->fullscreen_controller());
if (enable) {
target_window->AddObserver(this);
registrar_.Add(this, chrome::NOTIFICATION_FULLSCREEN_CHANGED, source);
} else {
target_window->RemoveObserver(this);
registrar_.Remove(this, chrome::NOTIFICATION_FULLSCREEN_CHANGED, source);
}
}
void ImmersiveModeControllerAsh::LayoutBrowserRootView() {
views::Widget* widget = browser_view_->frame();
// Update the window caption buttons.
widget->non_client_view()->frame_view()->ResetWindowControls();
widget->non_client_view()->frame_view()->InvalidateLayout();
browser_view_->InvalidateLayout();
widget->GetRootView()->Layout();
}
void ImmersiveModeControllerAsh::CreateMashRevealWidget() {
if (!ash_util::IsRunningInMash())
return;
DCHECK(!mash_reveal_widget_);
mash_reveal_widget_ = std::make_unique<views::Widget>();
views::Widget::InitParams init_params(views::Widget::InitParams::TYPE_POPUP);
init_params.mus_properties
[ui::mojom::WindowManager::kRenderParentTitleArea_Property] =
mojo::ConvertTo<std::vector<uint8_t>>(
static_cast<aura::PropertyConverter::PrimitiveType>(true));
init_params.mus_properties
[ui::mojom::WindowManager::kWindowIgnoredByShelf_InitProperty] =
mojo::ConvertTo<std::vector<uint8_t>>(true);
init_params.name = "ChromeImmersiveRevealWindow";
// We want events to fall through to the real views.
init_params.accept_events = false;
init_params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
init_params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
init_params.parent = browser_view_->GetNativeWindow()->GetRootWindow();
// The widget needs to be translucent so the frame decorations drawn by the
// window manager are visible.
init_params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
const gfx::Rect& top_container_bounds =
browser_view_->top_container()->bounds();
init_params.bounds =
gfx::Rect(0, -top_container_bounds.height(), top_container_bounds.width(),
top_container_bounds.height());
mash_reveal_widget_->Init(init_params);
mash_reveal_widget_->SetContentsView(
new TopContainerMirrorView(browser_view_->top_container()));
mash_reveal_widget_->StackAtTop();
mash_reveal_widget_->Show();
}
void ImmersiveModeControllerAsh::DestroyMashRevealWidget() {
mash_reveal_widget_.reset();
}
void ImmersiveModeControllerAsh::OnImmersiveRevealStarted() {
DestroyMashRevealWidget();
visible_fraction_ = 0;
browser_view_->top_container()->SetPaintToLayer();
browser_view_->top_container()->layer()->SetFillsBoundsOpaquely(false);
LayoutBrowserRootView();
CreateMashRevealWidget();
for (Observer& observer : observers_)
observer.OnImmersiveRevealStarted();
}
void ImmersiveModeControllerAsh::OnImmersiveRevealEnded() {
DestroyMashRevealWidget();
visible_fraction_ = 0;
browser_view_->top_container()->DestroyLayer();
LayoutBrowserRootView();
for (Observer& observer : observers_)
observer.OnImmersiveRevealEnded();
}
void ImmersiveModeControllerAsh::OnImmersiveFullscreenExited() {
DestroyMashRevealWidget();
browser_view_->top_container()->DestroyLayer();
LayoutBrowserRootView();
}
void ImmersiveModeControllerAsh::SetVisibleFraction(double visible_fraction) {
if (visible_fraction_ == visible_fraction)
return;
visible_fraction_ = visible_fraction;
browser_view_->Layout();
browser_view_->frame()->GetFrameView()->UpdateClientArea();
if (mash_reveal_widget_) {
gfx::Rect bounds = mash_reveal_widget_->GetNativeWindow()->bounds();
bounds.set_y(visible_fraction * bounds.height() - bounds.height());
mash_reveal_widget_->SetBounds(bounds);
}
}
std::vector<gfx::Rect>
ImmersiveModeControllerAsh::GetVisibleBoundsInScreen() const {
views::View* top_container_view = browser_view_->top_container();
gfx::Rect top_container_view_bounds = top_container_view->GetVisibleBounds();
// TODO(tdanderson): Implement View::ConvertRectToScreen().
gfx::Point top_container_view_bounds_in_screen_origin(
top_container_view_bounds.origin());
views::View::ConvertPointToScreen(top_container_view,
&top_container_view_bounds_in_screen_origin);
gfx::Rect top_container_view_bounds_in_screen(
top_container_view_bounds_in_screen_origin,
top_container_view_bounds.size());
std::vector<gfx::Rect> bounds_in_screen;
bounds_in_screen.push_back(top_container_view_bounds_in_screen);
bounds_in_screen.push_back(find_bar_visible_bounds_in_screen_);
return bounds_in_screen;
}
void ImmersiveModeControllerAsh::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK_EQ(chrome::NOTIFICATION_FULLSCREEN_CHANGED, type);
if (!controller_->IsEnabled())
return;
// Auto hide the shelf in immersive browser fullscreen.
bool in_tab_fullscreen = content::Source<FullscreenController>(source)->
IsWindowFullscreenForTabOrPending();
browser_view_->GetNativeWindow()->SetProperty(
ash::kHideShelfWhenFullscreenKey, in_tab_fullscreen);
}
void ImmersiveModeControllerAsh::OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) {
if (key == ash::kWindowStateTypeKey) {
ash::mojom::WindowStateType new_state =
window->GetProperty(ash::kWindowStateTypeKey);
ash::mojom::WindowStateType old_state = ash::mojom::WindowStateType(old);
// Disable immersive fullscreen when the user exits fullscreen without going
// through FullscreenController::ToggleBrowserFullscreenMode(). This is the
// case if the user exits fullscreen via the restore button.
if (controller_->IsEnabled() &&
new_state != ash::mojom::WindowStateType::FULLSCREEN &&
new_state != ash::mojom::WindowStateType::PINNED &&
new_state != ash::mojom::WindowStateType::TRUSTED_PINNED &&
new_state != ash::mojom::WindowStateType::MINIMIZED &&
old_state == ash::mojom::WindowStateType::FULLSCREEN) {
browser_view_->FullscreenStateChanged();
}
}
}
void ImmersiveModeControllerAsh::OnWindowDestroying(aura::Window* window) {
// Clean up observers here rather than in the destructor because the owning
// BrowserView has already destroyed the aura::Window.
EnableWindowObservers(false);
}