Commit a276ec54 authored by skuhne@chromium.org's avatar skuhne@chromium.org

Avoiding double resize of maximized windows upon user switch due to showing and hiding of the shelf

BUG=385992
TEST=MultiUserWindowManagerChromeOSTest.TestBlackBarCover & visual inspection

Review URL: https://codereview.chromium.org/448493002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@287880 0039d316-1c4b-4281-b951-d872f2087c98
parent a375ec49
......@@ -325,6 +325,7 @@ class ShelfWidget::DelegateView : public views::WidgetDelegate,
FocusCycler* focus_cycler() { return focus_cycler_; }
ui::Layer* opaque_background() { return &opaque_background_; }
ui::Layer* opaque_foreground() { return &opaque_foreground_; }
// Set if the shelf area is dimmed (eg when a window is maximized).
void SetDimmed(bool dimmed);
......@@ -383,7 +384,12 @@ class ShelfWidget::DelegateView : public views::WidgetDelegate,
scoped_ptr<views::Widget> dimmer_;
FocusCycler* focus_cycler_;
int alpha_;
// A black background layer which is shown when a maximized window is visible.
ui::Layer opaque_background_;
// A black foreground layer which is shown while transitioning between users.
// Note: Since the back- and foreground layers have different functions they
// can be used simultaneously - so no repurposing possible.
ui::Layer opaque_foreground_;
// The view which does the dimming.
DimmerView* dimmer_view_;
......@@ -399,12 +405,16 @@ ShelfWidget::DelegateView::DelegateView(ShelfWidget* shelf)
focus_cycler_(NULL),
alpha_(0),
opaque_background_(ui::LAYER_SOLID_COLOR),
opaque_foreground_(ui::LAYER_SOLID_COLOR),
dimmer_view_(NULL),
disable_dimming_animations_for_test_(false) {
set_allow_deactivate_on_esc(true);
opaque_background_.SetColor(SK_ColorBLACK);
opaque_background_.SetBounds(GetLocalBounds());
opaque_background_.SetOpacity(0.0f);
opaque_foreground_.SetColor(SK_ColorBLACK);
opaque_foreground_.SetBounds(GetLocalBounds());
opaque_foreground_.SetOpacity(0.0f);
}
ShelfWidget::DelegateView::~DelegateView() {
......@@ -450,6 +460,7 @@ bool ShelfWidget::DelegateView::GetDimmed() const {
void ShelfWidget::DelegateView::SetParentLayer(ui::Layer* layer) {
layer->Add(&opaque_background_);
layer->Add(&opaque_foreground_);
ReorderLayers();
}
......@@ -556,10 +567,12 @@ void ShelfWidget::DelegateView::Layout() {
void ShelfWidget::DelegateView::ReorderChildLayers(ui::Layer* parent_layer) {
views::View::ReorderChildLayers(parent_layer);
parent_layer->StackAtBottom(&opaque_background_);
parent_layer->StackAtTop(&opaque_foreground_);
}
void ShelfWidget::DelegateView::OnBoundsChanged(const gfx::Rect& old_bounds) {
opaque_background_.SetBounds(GetLocalBounds());
opaque_foreground_.SetBounds(GetLocalBounds());
if (dimmer_)
dimmer_->SetBounds(GetBoundsInScreen());
}
......@@ -678,6 +691,27 @@ ShelfBackgroundType ShelfWidget::GetBackgroundType() const {
return SHELF_BACKGROUND_DEFAULT;
}
void ShelfWidget::HideShelfBehindBlackBar(bool hide, int animation_time_ms) {
if (IsShelfHiddenBehindBlackBar() == hide)
return;
ui::Layer* opaque_foreground = delegate_view_->opaque_foreground();
float target_opacity = hide ? 1.0f : 0.0f;
scoped_ptr<ui::ScopedLayerAnimationSettings> opaque_foreground_animation;
opaque_foreground_animation.reset(new ui::ScopedLayerAnimationSettings(
opaque_foreground->GetAnimator()));
opaque_foreground_animation->SetTransitionDuration(
base::TimeDelta::FromMilliseconds(animation_time_ms));
opaque_foreground_animation->SetPreemptionStrategy(
ui::LayerAnimator::REPLACE_QUEUED_ANIMATIONS);
opaque_foreground->SetOpacity(target_opacity);
}
bool ShelfWidget::IsShelfHiddenBehindBlackBar() const {
return delegate_view_->opaque_foreground()->GetTargetOpacity() != 0.0f;
}
// static
bool ShelfWidget::ShelfAlignmentAllowed() {
user::LoginStatus login_status =
......
......@@ -45,6 +45,11 @@ class ASH_EXPORT ShelfWidget : public views::Widget,
BackgroundAnimatorChangeType change_type);
ShelfBackgroundType GetBackgroundType() const;
// Hide the shelf behind a black bar during e.g. a user transition when |hide|
// is true. The |animation_time_ms| will be used as animation duration.
void HideShelfBehindBlackBar(bool hide, int animation_time_ms);
bool IsShelfHiddenBehindBlackBar() const;
// Causes shelf items to be slightly dimmed (e.g. when a window is maximized).
void SetDimsShelf(bool dimming);
bool GetDimsShelf() const;
......
......@@ -197,6 +197,48 @@ std::string GetPrefForRootWindow(PrefService* pref_service,
return default_string;
}
// Gets the shelf auto hide behavior from prefs for a root window.
ash::ShelfAutoHideBehavior GetShelfAutoHideBehaviorFromPrefs(
Profile* profile,
aura::Window* root_window) {
// Don't show the shelf in app mode.
if (chrome::IsRunningInAppMode())
return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
// See comment in |kShelfAlignment| as to why we consider two prefs.
const std::string behavior_value(
GetPrefForRootWindow(profile->GetPrefs(),
root_window,
prefs::kShelfAutoHideBehaviorLocal,
prefs::kShelfAutoHideBehavior));
// Note: To maintain sync compatibility with old images of chrome/chromeos
// the set of values that may be encountered includes the now-extinct
// "Default" as well as "Never" and "Always", "Default" should now
// be treated as "Never" (http://crbug.com/146773).
if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
}
// Gets the shelf alignment from prefs for a root window.
ash::ShelfAlignment GetShelfAlignmentFromPrefs(Profile* profile,
aura::Window* root_window) {
// See comment in |kShelfAlignment| as to why we consider two prefs.
const std::string alignment_value(
GetPrefForRootWindow(profile->GetPrefs(),
root_window,
prefs::kShelfAlignmentLocal,
prefs::kShelfAlignment));
if (alignment_value == ash::kShelfAlignmentLeft)
return ash::SHELF_ALIGNMENT_LEFT;
else if (alignment_value == ash::kShelfAlignmentRight)
return ash::SHELF_ALIGNMENT_RIGHT;
else if (alignment_value == ash::kShelfAlignmentTop)
return ash::SHELF_ALIGNMENT_TOP;
return ash::SHELF_ALIGNMENT_BOTTOM;
}
// If prefs have synced and no user-set value exists at |local_path|, the value
// from |synced_path| is copied to |local_path|.
void MaybePropagatePrefToLocal(PrefServiceSyncable* pref_service,
......@@ -920,24 +962,7 @@ Profile* ChromeLauncherController::profile() {
ash::ShelfAutoHideBehavior ChromeLauncherController::GetShelfAutoHideBehavior(
aura::Window* root_window) const {
// Don't show the shelf in app mode.
if (chrome::IsRunningInAppMode())
return ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN;
// See comment in |kShelfAlignment| as to why we consider two prefs.
const std::string behavior_value(
GetPrefForRootWindow(profile_->GetPrefs(),
root_window,
prefs::kShelfAutoHideBehaviorLocal,
prefs::kShelfAutoHideBehavior));
// Note: To maintain sync compatibility with old images of chrome/chromeos
// the set of values that may be encountered includes the now-extinct
// "Default" as well as "Never" and "Always", "Default" should now
// be treated as "Never" (http://crbug.com/146773).
if (behavior_value == ash::kShelfAutoHideBehaviorAlways)
return ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS;
return ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER;
return GetShelfAutoHideBehaviorFromPrefs(profile_, root_window);
}
bool ChromeLauncherController::CanUserModifyShelfAutoHideBehavior(
......@@ -1438,6 +1463,25 @@ bool ChromeLauncherController::IsBrowserFromActiveUser(Browser* browser) {
return multi_user_util::IsProfileFromActiveUser(browser->profile());
}
bool ChromeLauncherController::ShelfBoundsChangesProbablyWithUser(
aura::Window* root_window,
const std::string& user_id) const {
Profile* other_profile = multi_user_util::GetProfileFromUserID(user_id);
DCHECK_NE(other_profile, profile_);
// Note: The Auto hide state from preferences is not the same as the actual
// visibility of the shelf. Depending on all the various states (full screen,
// no window on desktop, multi user, ..) the shelf could be shown - or not.
bool currently_shown = ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER ==
GetShelfAutoHideBehaviorFromPrefs(profile_, root_window);
bool other_shown = ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER ==
GetShelfAutoHideBehaviorFromPrefs(other_profile, root_window);
return currently_shown != other_shown ||
GetShelfAlignmentFromPrefs(profile_, root_window) !=
GetShelfAlignmentFromPrefs(other_profile, root_window);
}
void ChromeLauncherController::LauncherItemClosed(ash::ShelfID id) {
IDToItemControllerMap::iterator iter = id_to_item_controller_map_.find(id);
CHECK(iter != id_to_item_controller_map_.end());
......@@ -1696,20 +1740,8 @@ void ChromeLauncherController::SetShelfAlignmentFromPrefs() {
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
// See comment in |kShelfAlignment| as to why we consider two prefs.
const std::string alignment_value(
GetPrefForRootWindow(profile_->GetPrefs(),
*iter,
prefs::kShelfAlignmentLocal,
prefs::kShelfAlignment));
ash::ShelfAlignment alignment = ash::SHELF_ALIGNMENT_BOTTOM;
if (alignment_value == ash::kShelfAlignmentLeft)
alignment = ash::SHELF_ALIGNMENT_LEFT;
else if (alignment_value == ash::kShelfAlignmentRight)
alignment = ash::SHELF_ALIGNMENT_RIGHT;
else if (alignment_value == ash::kShelfAlignmentTop)
alignment = ash::SHELF_ALIGNMENT_TOP;
ash::Shell::GetInstance()->SetShelfAlignment(alignment, *iter);
ash::Shell::GetInstance()->SetShelfAlignment(
GetShelfAlignmentFromPrefs(profile_, *iter), *iter);
}
}
......
......@@ -384,6 +384,13 @@ class ChromeLauncherController : public ash::ShelfDelegate,
// Returns true if |browser| is owned by the active user.
bool IsBrowserFromActiveUser(Browser* browser);
// Check if the shelf visibility (location, visibility) will change with a new
// user profile or not. However, since the full visibility calculation of the
// shelf cannot be performed here, this is only a probability used for
// animation predictions.
bool ShelfBoundsChangesProbablyWithUser(aura::Window* root_window,
const std::string& user_id) const;
// Access to the BrowserStatusMonitor for tests.
BrowserStatusMonitor* browser_status_monitor_for_test() {
return browser_status_monitor_.get();
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/test/ash_test_base.h"
......@@ -768,6 +770,9 @@ TEST_F(MultiUserWindowManagerChromeOSTest, AnimationSteps) {
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(
window(0)->GetRootWindow()));
EXPECT_EQ(1.0f, window(0)->layer()->GetTargetOpacity());
ash::ShelfWidget* shelf = ash::RootWindowController::ForWindow(
window(0))->shelf();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
// Start the animation and see that the old window is becoming invisible, the
// new one visible, the background starts transitionining and the shelf hides.
......@@ -779,6 +784,7 @@ TEST_F(MultiUserWindowManagerChromeOSTest, AnimationSteps) {
EXPECT_EQ(ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(
window(0)->GetRootWindow()));
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
// Staring the next step should show the shelf again, but there are many
// subsystems missing (preferences system, ChromeLauncherController, ...)
......@@ -790,9 +796,11 @@ TEST_F(MultiUserWindowManagerChromeOSTest, AnimationSteps) {
EXPECT_EQ(ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(
window(0)->GetRootWindow()));
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
// After the finalize the animation of the wallpaper should be finished.
AdvanceUserTransitionAnimation();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
EXPECT_EQ("B", GetWallaperUserIdForTest());
}
......@@ -990,5 +998,86 @@ TEST_F(MultiUserWindowManagerChromeOSTest, ShowForUserSwitchesDesktop) {
EXPECT_EQ("H[a,b], H[b], S[c]", GetStatus());
}
class TestWindowObserver : public aura::WindowObserver {
public:
TestWindowObserver(): resize_calls_(0) {}
virtual ~TestWindowObserver() {};
virtual void OnWindowBoundsChanged(aura::Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) OVERRIDE {
resize_calls_++;
}
int resize_calls() { return resize_calls_; }
private:
int resize_calls_;
DISALLOW_COPY_AND_ASSIGN(TestWindowObserver);
};
// Test that switching between users with the shelf in the same place, the shelf
// will get covered with a black bar instead being hidden and re-shown.
TEST_F(MultiUserWindowManagerChromeOSTest, TestBlackBarCover) {
SetUpForThisManyWindows(2);
multi_user_window_manager()->SetWindowOwner(window(0), "A");
multi_user_window_manager()->SetWindowOwner(window(1), "B");
aura::Window* root_window = ash::Shell::GetInstance()->GetPrimaryRootWindow();
// Turn the use of delays and animation on.
multi_user_window_manager()->SetAnimationSpeedForTest(
chrome::MultiUserWindowManagerChromeOS::ANIMATION_SPEED_FAST);
EXPECT_NE(ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(root_window));
ash::ShelfWidget* shelf = ash::RootWindowController::ForWindow(
root_window)->shelf();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
// First test that with no maximized window we show/hide the shelf.
StartUserTransitionAnimation("B");
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
EXPECT_EQ(ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(root_window));
// Staring the next step should show the shelf again.
AdvanceUserTransitionAnimation();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
AdvanceUserTransitionAnimation();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
ash::Shell::GetInstance()->SetShelfAutoHideBehavior(
ash::SHELF_AUTO_HIDE_BEHAVIOR_NEVER, root_window);
// Now we maximize the windows which will cause the black overlay to show up.
wm::GetWindowState(window(0))->Maximize();
wm::GetWindowState(window(1))->Maximize();
// We set a window observer on both windows to see that no resize is performed
// on our test windows.
TestWindowObserver window_observer;
window(0)->AddObserver(&window_observer);
window(1)->AddObserver(&window_observer);
// Start the animation and see that the shelf gets hidden by the black bar,
// and the AutoHide behavior remains as it was.
StartUserTransitionAnimation("A");
EXPECT_TRUE(shelf->IsShelfHiddenBehindBlackBar());
EXPECT_NE(ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(root_window));
// Staring the next step should show the shelf again.
AdvanceUserTransitionAnimation();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
EXPECT_NE(ash::SHELF_AUTO_HIDE_ALWAYS_HIDDEN,
ash::Shell::GetInstance()->GetShelfAutoHideBehavior(root_window));
AdvanceUserTransitionAnimation();
EXPECT_FALSE(shelf->IsShelfHiddenBehindBlackBar());
window(0)->RemoveObserver(&window_observer);
window(1)->RemoveObserver(&window_observer);
// No resize should have been done to the window.
EXPECT_EQ(0, window_observer.resize_calls());
}
} // namespace test
} // namespace ash
......@@ -7,6 +7,7 @@
#include "ash/desktop_background/user_wallpaper_delegate.h"
#include "ash/root_window_controller.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/wm/mru_window_tracker.h"
#include "ash/wm/window_positioner.h"
......@@ -53,7 +54,7 @@ UserSwichAnimatorChromeOS::UserSwichAnimatorChromeOS(
new_user_id_(new_user_id),
animation_speed_ms_(animation_speed_ms),
animation_step_(ANIMATION_STEP_HIDE_OLD_USER),
screen_cover_(GetScreenCover()) {
screen_cover_(GetScreenCover(NULL)) {
AdvanceUserTransitionAnimation();
if (!animation_speed_ms_) {
......@@ -156,21 +157,33 @@ void UserSwichAnimatorChromeOS::TransitionWallpaper(
void UserSwichAnimatorChromeOS::TransitionUserShelf(
AnimationStep animation_step) {
ChromeLauncherController* chrome_launcher_controller =
ChromeLauncherController::instance();
// The shelf animation duration override.
int duration_override = animation_speed_ms_;
// Handle the shelf order of items. This is done once the old user is hidden.
if (animation_step == ANIMATION_STEP_SHOW_NEW_USER) {
// Some unit tests have no ChromeLauncherController.
if (ChromeLauncherController::instance())
ChromeLauncherController::instance()->ActiveUserChanged(new_user_id_);
// We kicked off the shelf animation in the command above. As such we can
// disable the override now again.
if (chrome_launcher_controller)
chrome_launcher_controller->ActiveUserChanged(new_user_id_);
// Hide the black rectangle on top of each shelf again.
aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
ash::ShelfWidget* shelf =
ash::RootWindowController::ForWindow(*iter)->shelf();
shelf->HideShelfBehindBlackBar(false, duration_override);
}
// We kicked off the shelf animation above and the override can be
// removed.
duration_override = 0;
}
if (!animation_speed_ms_ || animation_step == ANIMATION_STEP_FINALIZE)
return;
// Note: The animation duration override will be set before the old user gets
// hidden and reset after the animations for the new user got kicked off.
ash::Shell::RootWindowControllerList controller =
ash::Shell::GetInstance()->GetAllRootWindowControllers();
for (ash::Shell::RootWindowControllerList::iterator iter = controller.begin();
......@@ -179,11 +192,28 @@ void UserSwichAnimatorChromeOS::TransitionUserShelf(
duration_override);
}
if (animation_step != ANIMATION_STEP_HIDE_OLD_USER)
return;
// For each root window hide the shelf.
if (animation_step == ANIMATION_STEP_HIDE_OLD_USER) {
aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
aura::Window::Windows root_windows = ash::Shell::GetAllRootWindows();
for (aura::Window::Windows::const_iterator iter = root_windows.begin();
iter != root_windows.end(); ++iter) {
// Hiding the shelf will cause a resize on a maximized window.
// If the shelf is then shown for the following user in the same location,
// the window gets resized again. Since each resize can cause a considerable
// CPU usage and therefore effect jank, we should avoid hiding the shelf if
// the start and end location are the same and cover the shelf instead with
// a black rectangle on top.
if (GetScreenCover(*iter) != NO_USER_COVERS_SCREEN &&
(!chrome_launcher_controller ||
!chrome_launcher_controller->ShelfBoundsChangesProbablyWithUser(
*iter, new_user_id_))) {
ash::ShelfWidget* shelf =
ash::RootWindowController::ForWindow(*iter)->shelf();
shelf->HideShelfBehindBlackBar(true, duration_override);
} else {
// This shelf change is only part of the animation and will be updated by
// ChromeLauncherController::ActiveUserChanged() to the new users value.
// Note that the user preference will not be changed.
......@@ -300,13 +330,15 @@ void UserSwichAnimatorChromeOS::TransitionWindows(
}
UserSwichAnimatorChromeOS::TransitioningScreenCover
UserSwichAnimatorChromeOS::GetScreenCover() {
UserSwichAnimatorChromeOS::GetScreenCover(aura::Window* root_window) {
TransitioningScreenCover cover = NO_USER_COVERS_SCREEN;
for (MultiUserWindowManagerChromeOS::WindowToEntryMap::const_iterator it_map =
owner_->window_to_entry().begin();
it_map != owner_->window_to_entry().end();
++it_map) {
aura::Window* window = it_map->first;
if (root_window && window->GetRootWindow() != root_window)
continue;
if (window->IsVisible() && CoversScreen(window)) {
if (cover == NEW_USER_COVERS_SCREEN)
return BOTH_USERS_COVER_SCREEN;
......
......@@ -84,8 +84,9 @@ class UserSwichAnimatorChromeOS {
void TransitionWindows(AnimationStep animation_step);
// Check if a window is maximized / fullscreen / covering the entire screen.
// TODO(skuhne): We might want to do this on a per screen basis.
TransitioningScreenCover GetScreenCover();
// If a |root_window| is given, the screen coverage of that root_window is
// tested, otherwise all screens.
TransitioningScreenCover GetScreenCover(aura::Window* root_window);
// The owning window manager.
MultiUserWindowManagerChromeOS* owner_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment