diff options
author | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-14 02:27:50 +0000 |
---|---|---|
committer | pkotwicz@chromium.org <pkotwicz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-14 02:27:50 +0000 |
commit | 97decb5726a169fe67a70e1a3842ee7c6d331e18 (patch) | |
tree | b20a676eb6a906e7308b5363d3fed7f13d01a606 | |
parent | e49c390d04cc09b4d37811af741e6029e45d0db7 (diff) | |
download | chromium_src-97decb5726a169fe67a70e1a3842ee7c6d331e18.zip chromium_src-97decb5726a169fe67a70e1a3842ee7c6d331e18.tar.gz chromium_src-97decb5726a169fe67a70e1a3842ee7c6d331e18.tar.bz2 |
1) Fix repaint glitches wrt to the solo window header when window docking is enabled. The glitches were occurring because windows move to the kShellWindowId_DockedContainer for the duration of a drag and windows were not properly repainted
2) Change the behavior of the solo header such that:
- Docked windows never use the solo window header
- Windows which are not docked cannot use the solo window header when there is a docked window (There must be exactly one window onscreen in order to use the solo window header)
- A window can use the solo window header when it is being dragged even though it is in the kShellWindowId_DockedContainer
BUG=317439
TEST=SoloWindowTrackerTest.DockedWindow
R=jamescook
TBR=sky (For trivial change to chrome/browser/chromeos/login/login_display_host_impl.cc)
Review URL: https://codereview.chromium.org/68373002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235020 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | ash/ash.gyp | 3 | ||||
-rw-r--r-- | ash/root_window_controller.cc | 15 | ||||
-rw-r--r-- | ash/root_window_controller.h | 8 | ||||
-rw-r--r-- | ash/root_window_settings.cc | 3 | ||||
-rw-r--r-- | ash/root_window_settings.h | 4 | ||||
-rw-r--r-- | ash/wm/header_painter.cc | 176 | ||||
-rw-r--r-- | ash/wm/header_painter.h | 29 | ||||
-rw-r--r-- | ash/wm/header_painter_unittest.cc | 488 | ||||
-rw-r--r-- | ash/wm/solo_window_tracker.cc | 210 | ||||
-rw-r--r-- | ash/wm/solo_window_tracker.h | 81 | ||||
-rw-r--r-- | ash/wm/solo_window_tracker_unittest.cc | 423 | ||||
-rw-r--r-- | ash/wm/workspace/workspace_layout_manager.cc | 2 | ||||
-rw-r--r-- | chrome/browser/chromeos/login/login_display_host_impl.cc | 6 |
13 files changed, 748 insertions, 700 deletions
diff --git a/ash/ash.gyp b/ash/ash.gyp index 388b822..46213cf 100644 --- a/ash/ash.gyp +++ b/ash/ash.gyp @@ -518,6 +518,8 @@ 'wm/screen_dimmer.h', 'wm/session_state_animator.cc', 'wm/session_state_animator.h', + 'wm/solo_window_tracker.cc', + 'wm/solo_window_tracker.h', 'wm/stacking_controller.cc', 'wm/stacking_controller.h', 'wm/status_area_layout_manager.cc', @@ -829,6 +831,7 @@ 'wm/partial_screenshot_view_unittest.cc', 'wm/resize_shadow_and_cursor_unittest.cc', 'wm/screen_dimmer_unittest.cc', + 'wm/solo_window_tracker_unittest.cc', 'wm/stacking_controller_unittest.cc', 'wm/sticky_keys_unittest.cc', 'wm/system_gesture_event_filter_unittest.cc', diff --git a/ash/root_window_controller.cc b/ash/root_window_controller.cc index d66f169..de8c327 100644 --- a/ash/root_window_controller.cc +++ b/ash/root_window_controller.cc @@ -36,6 +36,7 @@ #include "ash/wm/panels/panel_window_event_handler.h" #include "ash/wm/root_window_layout_manager.h" #include "ash/wm/screen_dimmer.h" +#include "ash/wm/solo_window_tracker.h" #include "ash/wm/stacking_controller.h" #include "ash/wm/status_area_layout_manager.h" #include "ash/wm/system_background_controller.h" @@ -396,6 +397,13 @@ void RootWindowController::OnWallpaperAnimationFinished(views::Widget* widget) { void RootWindowController::CloseChildWindows() { mouse_event_target_.reset(); + // |solo_window_tracker_| must be shut down before windows are destroyed. + if (solo_window_tracker_) { + if (docked_layout_manager_) + docked_layout_manager_->RemoveObserver(solo_window_tracker_.get()); + solo_window_tracker_.reset(); + } + // Deactivate keyboard container before closing child windows and shutting // down associated layout managers. DeactivateKeyboard(Shell::GetInstance()->keyboard_controller()); @@ -563,7 +571,6 @@ void RootWindowController::DeactivateKeyboard( } } - //////////////////////////////////////////////////////////////////////////////// // RootWindowController, private: @@ -617,10 +624,14 @@ void RootWindowController::Init(RootWindowType root_window_type, root_window_->window()); root_window_->ShowRootWindow(); - // Create a launcher if a user is already logged. + // Create a launcher if a user is already logged in. if (shell->session_state_delegate()->NumberOfLoggedInUsers()) shelf()->CreateLauncher(); } + + solo_window_tracker_.reset(new SoloWindowTracker(root_window_.get())); + if (docked_layout_manager_) + docked_layout_manager_->AddObserver(solo_window_tracker_.get()); } void RootWindowController::InitLayoutManagers() { diff --git a/ash/root_window_controller.h b/ash/root_window_controller.h index 606cc50..076bfe6 100644 --- a/ash/root_window_controller.h +++ b/ash/root_window_controller.h @@ -43,8 +43,9 @@ class KeyboardController; } namespace ash { -class StackingController; class ShelfWidget; +class SoloWindowTracker; +class StackingController; class SystemTray; class ToplevelWindowEventHandler; @@ -150,6 +151,10 @@ class ASH_EXPORT RootWindowController : public ShellObserver { } void SetAnimatingWallpaperController(AnimatingDesktopController* controller); + SoloWindowTracker* solo_window_tracker() { + return solo_window_tracker_.get(); + } + // Access the shelf layout manager associated with this root // window controller, NULL if no such shelf exists. ShelfLayoutManager* GetShelfLayoutManager(); @@ -306,6 +311,7 @@ class ASH_EXPORT RootWindowController : public ShellObserver { scoped_ptr<DesktopBackgroundWidgetController> wallpaper_controller_; scoped_ptr<AnimatingDesktopController> animating_wallpaper_controller_; scoped_ptr<views::corewm::ScopedCaptureClient> capture_client_; + scoped_ptr<SoloWindowTracker> solo_window_tracker_; DISALLOW_COPY_AND_ASSIGN(RootWindowController); }; diff --git a/ash/root_window_settings.cc b/ash/root_window_settings.cc index 33880ed..b6e3b91 100644 --- a/ash/root_window_settings.cc +++ b/ash/root_window_settings.cc @@ -17,8 +17,7 @@ DEFINE_OWNED_WINDOW_PROPERTY_KEY(RootWindowSettings, kRootWindowSettingsKey, NULL); RootWindowSettings::RootWindowSettings() - : solo_window_header(false), - display_id(gfx::Display::kInvalidDisplayID), + : display_id(gfx::Display::kInvalidDisplayID), controller(NULL) { } diff --git a/ash/root_window_settings.h b/ash/root_window_settings.h index e49b0f2..f187949 100644 --- a/ash/root_window_settings.h +++ b/ash/root_window_settings.h @@ -23,10 +23,6 @@ class RootWindowController; struct RootWindowSettings { RootWindowSettings(); - // Indicate if the window in the active workspace should - // use the transparent "solo-window" header style. - bool solo_window_header; - // ID of the display associated with the root window. int64 display_id; diff --git a/ash/wm/header_painter.cc b/ash/wm/header_painter.cc index 3f8f2fe..fb0b2c4 100644 --- a/ash/wm/header_painter.cc +++ b/ash/wm/header_painter.cc @@ -6,25 +6,18 @@ #include <vector> -#include "ash/ash_constants.h" #include "ash/root_window_controller.h" -#include "ash/root_window_settings.h" -#include "ash/shell.h" -#include "ash/shell_window_ids.h" #include "ash/wm/caption_buttons/frame_caption_button_container_view.h" +#include "ash/wm/solo_window_tracker.h" #include "ash/wm/window_state.h" -#include "ash/wm/window_util.h" #include "base/logging.h" // DCHECK #include "grit/ash_resources.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkPaint.h" #include "third_party/skia/include/core/SkPath.h" -#include "ui/aura/client/aura_constants.h" -#include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/base/hit_test.h" -#include "ui/base/layout.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/theme_provider.h" #include "ui/gfx/animation/slide_animation.h" @@ -36,7 +29,6 @@ #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" -using aura::RootWindow; using aura::Window; using views::Widget; @@ -71,9 +63,6 @@ const int kActivationCrossfadeDurationMs = 200; // Alpha/opacity value for fully-opaque headers. const int kFullyOpaque = 255; -// A flag to enable/disable solo window header. -bool solo_window_header_enabled = true; - // Tiles an image into an area, rounding the top corners. Samples |image| // starting |image_inset_x| pixels from the left of the image. void TileRoundRect(gfx::Canvas* canvas, @@ -143,53 +132,6 @@ void PaintFrameImagesInRoundRect(gfx::Canvas* canvas, } } -// Returns true if |child| and all ancestors are visible. Useful to ensure that -// a window is individually visible and is not part of a hidden workspace. -bool IsVisibleToRoot(Window* child) { - for (Window* window = child; window; window = window->parent()) { - // We must use TargetVisibility() because windows animate in and out and - // IsVisible() also tracks the layer visibility state. - if (!window->TargetVisibility()) - return false; - } - return true; -} - -// Returns true if |window| is a "normal" window for purposes of solo window -// computations. Returns false for windows that are: -// * Not drawn (for example, DragDropTracker uses one for mouse capture) -// * Modal alerts (it looks odd for headers to change when an alert opens) -// * Constrained windows (ditto) -bool IsSoloWindowHeaderCandidate(aura::Window* window) { - return window && - window->type() == aura::client::WINDOW_TYPE_NORMAL && - window->layer() && - window->layer()->type() != ui::LAYER_NOT_DRAWN && - window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE && - !window->GetProperty(ash::kConstrainedWindowKey); -} - -// Returns a list of windows in |root_window|| that potentially could have -// a transparent solo-window header. -std::vector<Window*> GetWindowsForSoloHeaderUpdate(Window* root_window) { - std::vector<Window*> windows; - // Avoid memory allocations for typical window counts. - windows.reserve(16); - // Collect windows from the desktop. - Window* desktop = ash::Shell::GetContainer( - root_window, ash::internal::kShellWindowId_DefaultContainer); - windows.insert(windows.end(), - desktop->children().begin(), - desktop->children().end()); - // Collect "always on top" windows. - Window* top_container = - ash::Shell::GetContainer( - root_window, ash::internal::kShellWindowId_AlwaysOnTopContainer); - windows.insert(windows.end(), - top_container->children().begin(), - top_container->children().end()); - return windows; -} } // namespace namespace ash { @@ -268,18 +210,6 @@ void HeaderPainter::Init( } // static -void HeaderPainter::SetSoloWindowHeadersEnabled(bool enabled) { - solo_window_header_enabled = enabled; -} - -// static -void HeaderPainter::UpdateSoloWindowHeader(Window* root_window) { - // Use a separate function here so callers outside of HeaderPainter don't need - // to know about "ignorable_window". - UpdateSoloWindowInRoot(root_window, NULL /* ignorable_window */); -} - -// static gfx::Rect HeaderPainter::GetBoundsForClientView( int header_height, const gfx::Rect& window_bounds) { @@ -571,17 +501,6 @@ void HeaderPainter::OnTrackedByWorkspaceChanged(wm::WindowState* window_state, /////////////////////////////////////////////////////////////////////////////// // aura::WindowObserver overrides: -void HeaderPainter::OnWindowVisibilityChanged(aura::Window* window, - bool visible) { - // OnWindowVisibilityChanged can be called for the child windows of |window_|. - if (window != window_) - return; - - // Window visibility change may trigger the change of window solo-ness in a - // different window. - UpdateSoloWindowInRoot(window_->GetRootWindow(), visible ? NULL : window_); -} - void HeaderPainter::OnWindowDestroying(aura::Window* destroying) { DCHECK_EQ(window_, destroying); @@ -590,10 +509,6 @@ void HeaderPainter::OnWindowDestroying(aura::Window* destroying) { window_->RemoveObserver(this); wm::GetWindowState(window_)->RemoveObserver(this); - // If we have two or more windows open and we close this one, we might trigger - // the solo window appearance for another window. - UpdateSoloWindowInRoot(window_->GetRootWindow(), window_); - window_ = NULL; } @@ -609,19 +524,6 @@ void HeaderPainter::OnWindowBoundsChanged(aura::Window* window, } } -void HeaderPainter::OnWindowAddedToRootWindow(aura::Window* window) { - // Needs to trigger the window appearance change if the window moves across - // root windows and a solo window is already in the new root. - UpdateSoloWindowInRoot(window->GetRootWindow(), NULL /* ignore_window */); -} - -void HeaderPainter::OnWindowRemovingFromRootWindow(aura::Window* window) { - // Needs to trigger the window appearance change if the window moves across - // root windows and only one window is left in the previous root. Because - // |window| is not yet moved, |window| has to be ignored. - UpdateSoloWindowInRoot(window->GetRootWindow(), window); -} - /////////////////////////////////////////////////////////////////////////////// // gfx::AnimationDelegate overrides: @@ -673,9 +575,13 @@ int HeaderPainter::GetHeaderOpacity( if (ShouldUseMinimalHeaderStyle(THEMED_NO)) return kFullyOpaque; - // Single browser window is very transparent. - if (UseSoloWindowHeader()) + // Solo header is very transparent. + ash::SoloWindowTracker* solo_window_tracker = + internal::RootWindowController::ForWindow(window_)->solo_window_tracker(); + if (solo_window_tracker && + solo_window_tracker->GetWindowWithSoloHeader() == window_) { return kSoloWindowOpacity; + } // Otherwise, change transparency based on window activation status. if (header_mode == ACTIVE) @@ -683,74 +589,6 @@ int HeaderPainter::GetHeaderOpacity( return kInactiveWindowOpacity; } -bool HeaderPainter::UseSoloWindowHeader() const { - if (!solo_window_header_enabled) - return false; - // Don't use transparent headers for panels, pop-ups, etc. - if (!IsSoloWindowHeaderCandidate(window_)) - return false; - aura::Window* root = window_->GetRootWindow(); - // Don't recompute every time, as it would require many window property - // lookups. - return internal::GetRootWindowSettings(root)->solo_window_header; -} - -// static -bool HeaderPainter::UseSoloWindowHeaderInRoot(Window* root_window, - Window* ignore_window) { - int visible_window_count = 0; - std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root_window); - for (std::vector<Window*>::const_iterator it = windows.begin(); - it != windows.end(); - ++it) { - Window* window = *it; - // Various sorts of windows "don't count" for this computation. - if (ignore_window == window || - !IsSoloWindowHeaderCandidate(window) || - !IsVisibleToRoot(window)) - continue; - if (wm::GetWindowState(window)->IsMaximized()) - return false; - ++visible_window_count; - if (visible_window_count > 1) - return false; - } - // Count must be tested because all windows might be "don't count" windows - // in the loop above. - return visible_window_count == 1; -} - -// static -void HeaderPainter::UpdateSoloWindowInRoot(Window* root, - Window* ignore_window) { -#if defined(OS_WIN) - // Non-Ash Windows doesn't do solo-window counting for transparency effects, - // as the desktop background and window frames are managed by the OS. - if (!ash::Shell::HasInstance()) - return; -#endif - if (!root) - return; - internal::RootWindowSettings* root_window_settings = - internal::GetRootWindowSettings(root); - bool old_solo_header = root_window_settings->solo_window_header; - bool new_solo_header = UseSoloWindowHeaderInRoot(root, ignore_window); - if (old_solo_header == new_solo_header) - return; - root_window_settings->solo_window_header = new_solo_header; - - // Invalidate all the window frames in the desktop. There should only be - // a few. - std::vector<Window*> windows = GetWindowsForSoloHeaderUpdate(root); - for (std::vector<Window*>::const_iterator it = windows.begin(); - it != windows.end(); - ++it) { - Widget* widget = Widget::GetWidgetForNativeWindow(*it); - if (widget && widget->non_client_view()) - widget->non_client_view()->SchedulePaint(); - } -} - void HeaderPainter::SchedulePaintForHeader() { int top_left_height = top_left_corner_->height(); int top_right_height = top_right_corner_->height(); diff --git a/ash/wm/header_painter.h b/ash/wm/header_painter.h index bdc5dbf7..ea326af 100644 --- a/ash/wm/header_painter.h +++ b/ash/wm/header_painter.h @@ -63,13 +63,6 @@ class ASH_EXPORT HeaderPainter : public aura::WindowObserver, views::View* window_icon, FrameCaptionButtonContainerView* caption_button_container); - // Enable/Disable the solo-window transparent header appearance feature. - static void SetSoloWindowHeadersEnabled(bool enabled); - - // Updates the solo-window transparent header appearance for all windows - // using frame painters in |root_window|. - static void UpdateSoloWindowHeader(aura::Window* root_window); - // Returns the bounds of the client view for a window with |header_height| // and |window_bounds|. The return value and |window_bounds| are in the // views::NonClientView's coordinates. @@ -138,14 +131,10 @@ class ASH_EXPORT HeaderPainter : public aura::WindowObserver, void OnThemeChanged(); // aura::WindowObserver overrides: - virtual void OnWindowVisibilityChanged(aura::Window* window, - bool visible) OVERRIDE; virtual void OnWindowDestroying(aura::Window* window) OVERRIDE; virtual void OnWindowBoundsChanged(aura::Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) OVERRIDE; - virtual void OnWindowAddedToRootWindow(aura::Window* window) OVERRIDE; - virtual void OnWindowRemovingFromRootWindow(aura::Window* window) OVERRIDE; // ash::WindowStateObserver override: virtual void OnTrackedByWorkspaceChanged(wm::WindowState* window_state, @@ -190,24 +179,6 @@ class ASH_EXPORT HeaderPainter : public aura::WindowObserver, // Returns the radius of the header's top corners. int GetHeaderCornerRadius() const; - // Returns true if |window_->GetRootWindow()| should be drawing transparent - // window headers. - bool UseSoloWindowHeader() const; - - // Returns true if |root_window| has exactly one visible, normal-type window. - // It ignores |ignore_window| while calculating the number of windows. - // Pass NULL for |ignore_window| to consider all windows. - static bool UseSoloWindowHeaderInRoot(aura::Window* root_window, - aura::Window* ignore_window); - - // Updates the solo-window transparent header appearance for all windows in - // |root_window|. If |ignore_window| is not NULL it is ignored for when - // counting visible windows. This is useful for updates when a window is about - // to be closed or is moving to another root. If the solo window status - // changes it schedules paints as necessary. - static void UpdateSoloWindowInRoot(aura::Window* root_window, - aura::Window* ignore_window); - // Schedules a paint for the header. Used when transitioning from no header to // a header (or other way around). void SchedulePaintForHeader(); diff --git a/ash/wm/header_painter_unittest.cc b/ash/wm/header_painter_unittest.cc index 55f0ff7..551658a 100644 --- a/ash/wm/header_painter_unittest.cc +++ b/ash/wm/header_painter_unittest.cc @@ -4,28 +4,14 @@ #include "ash/wm/header_painter.h" -#include "ash/ash_constants.h" -#include "ash/root_window_settings.h" #include "ash/shell.h" -#include "ash/shell_window_ids.h" #include "ash/test/ash_test_base.h" #include "ash/wm/caption_buttons/frame_caption_button_container_view.h" #include "ash/wm/window_state.h" -#include "ash/wm/window_util.h" #include "base/memory/scoped_ptr.h" -#include "base/message_loop/message_loop.h" #include "grit/ash_resources.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "ui/aura/client/aura_constants.h" -#include "ui/aura/client/window_tree_client.h" -#include "ui/aura/root_window.h" -#include "ui/aura/window_observer.h" -#include "ui/base/hit_test.h" #include "ui/gfx/font.h" -#include "ui/gfx/screen.h" #include "ui/views/widget/widget.h" -#include "ui/views/widget/widget_delegate.h" -#include "ui/views/widget/widget_observer.h" #include "ui/views/window/non_client_view.h" using ash::HeaderPainter; @@ -34,56 +20,6 @@ using views::Widget; namespace { -class ResizableWidgetDelegate : public views::WidgetDelegate { - public: - ResizableWidgetDelegate(views::Widget* widget) { - widget_ = widget; - } - - virtual bool CanResize() const OVERRIDE { return true; } - // Implementations of the widget class. - virtual views::Widget* GetWidget() OVERRIDE { return widget_; } - virtual const views::Widget* GetWidget() const OVERRIDE { return widget_; } - virtual void DeleteDelegate() OVERRIDE { - delete this; - } - - private: - views::Widget* widget_; - - DISALLOW_COPY_AND_ASSIGN(ResizableWidgetDelegate); -}; - -class WindowRepaintChecker : public aura::WindowObserver { - public: - explicit WindowRepaintChecker(aura::Window* window) - : is_paint_scheduled_(false) { - window->AddObserver(this); - } - virtual ~WindowRepaintChecker() { - } - - bool IsPaintScheduledAndReset() { - bool result = is_paint_scheduled_; - is_paint_scheduled_ = false; - return result; - } - - private: - // aura::WindowObserver overrides: - virtual void OnWindowPaintScheduled(aura::Window* window, - const gfx::Rect& region) OVERRIDE { - is_paint_scheduled_ = true; - } - virtual void OnWindowDestroying(aura::Window* window) OVERRIDE { - window->RemoveObserver(this); - } - - bool is_paint_scheduled_; - - DISALLOW_COPY_AND_ASSIGN(WindowRepaintChecker); -}; - // Modifies the values of kInactiveWindowOpacity, kActiveWindowOpacity, and // kSoloWindowOpacity for the lifetime of the class. This is useful so that // the constants each have different values. @@ -129,33 +65,6 @@ HeaderPainter* CreateTestPainter(Widget* widget) { return painter; } -// Self-owned manager of the frame painter which deletes the painter and itself -// when its widget is closed. -class HeaderPainterOwner : views::WidgetObserver { - public: - explicit HeaderPainterOwner(Widget* widget) - : header_painter_(CreateTestPainter(widget)) { - widget->AddObserver(this); - } - - virtual ~HeaderPainterOwner() {} - - HeaderPainter* header_painter() { return header_painter_.get(); } - - private: - virtual void OnWidgetDestroying(Widget* widget) OVERRIDE { - widget->RemoveObserver(this); - // Do not delete directly here, since the task of HeaderPainter causing - // the crash of crbug.com/273310 may run after this class handles this - // event. - base::MessageLoop::current()->DeleteSoon(FROM_HERE, this); - } - - scoped_ptr<HeaderPainter> header_painter_; - - DISALLOW_COPY_AND_ASSIGN(HeaderPainterOwner); -}; - } // namespace namespace ash { @@ -171,343 +80,8 @@ class HeaderPainterTest : public ash::test::AshTestBase { widget->Init(params); return widget; } - - Widget* CreateAlwaysOnTopWidget() { - Widget* widget = new Widget; - Widget::InitParams params; - params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - params.context = CurrentContext(); - params.keep_on_top = true; - widget->Init(params); - return widget; - } - - Widget* CreatePanelWidget() { - Widget* widget = new Widget; - Widget::InitParams params; - params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - params.context = CurrentContext(); - params.type = Widget::InitParams::TYPE_PANEL; - widget->Init(params); - return widget; - } - - Widget* CreateResizableWidget() { - Widget* widget = new Widget; - Widget::InitParams params; - params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - params.context = CurrentContext(); - params.keep_on_top = true; - params.delegate = new ResizableWidgetDelegate(widget); - params.type = Widget::InitParams::TYPE_WINDOW; - widget->Init(params); - return widget; - } }; -TEST_F(HeaderPainterTest, CreateAndDeleteSingleWindow) { - // Ensure that creating/deleting a window works well and doesn't cause - // crashes. See crbug.com/155634 - aura::Window* root = Shell::GetTargetRootWindow(); - - scoped_ptr<Widget> widget(CreateTestWidget()); - scoped_ptr<HeaderPainter> painter(CreateTestPainter(widget.get())); - widget->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(painter->UseSoloWindowHeader()); - EXPECT_TRUE(internal::GetRootWindowSettings(root)->solo_window_header); - - // Close the window. - widget.reset(); - EXPECT_FALSE(internal::GetRootWindowSettings(root)->solo_window_header); - - // Recreate another window again. - widget.reset(CreateTestWidget()); - painter.reset(CreateTestPainter(widget.get())); - widget->Show(); - EXPECT_TRUE(painter->UseSoloWindowHeader()); - EXPECT_TRUE(internal::GetRootWindowSettings(root)->solo_window_header); -} - -TEST_F(HeaderPainterTest, UseSoloWindowHeader) { - // Create a widget and a painter for it. - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Create a second widget and painter. - scoped_ptr<Widget> w2(CreateTestWidget()); - scoped_ptr<HeaderPainter> p2(CreateTestPainter(w2.get())); - w2->Show(); - - // Now there are two windows, so we should not use solo headers. This only - // needs to test |p1| because "solo window headers" are a per-root-window - // property. - EXPECT_FALSE(p1->UseSoloWindowHeader()); - - // Hide one window. Solo should be enabled. - w2->Hide(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Show that window. Solo should be disabled. - w2->Show(); - EXPECT_FALSE(p1->UseSoloWindowHeader()); - - // Minimize the second window. Solo should be enabled. - w2->Minimize(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Close the minimized window. - w2.reset(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Open an always-on-top widget (which lives in a different container). - scoped_ptr<Widget> w3(CreateAlwaysOnTopWidget()); - scoped_ptr<HeaderPainter> p3(CreateTestPainter(w3.get())); - w3->Show(); - EXPECT_FALSE(p3->UseSoloWindowHeader()); - - // Close the always-on-top widget. - w3.reset(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); -} - -// An open V2 app window should cause browser windows not to use the -// solo window header. -TEST_F(HeaderPainterTest, UseSoloWindowHeaderWithApp) { - // Create a widget and a painter for it. - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Simulate a V2 app window, which is part of the active workspace but does - // not have a frame painter. - scoped_ptr<Widget> w2(CreateTestWidget()); - w2->Show(); - - // Now there are two windows, so we should not use solo headers. - EXPECT_FALSE(p1->UseSoloWindowHeader()); - - // Minimize the app window. The first window should go solo again. - w2->Minimize(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Restoring the app window turns off solo headers. - w2->Restore(); - EXPECT_FALSE(p1->UseSoloWindowHeader()); -} - -// Panels should not "count" for computing solo window headers, and the panel -// itself should always have an opaque header. -TEST_F(HeaderPainterTest, UseSoloWindowHeaderWithPanel) { - // Create a widget and a painter for it. - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Create a panel and a painter for it. - scoped_ptr<Widget> w2(CreatePanelWidget()); - scoped_ptr<HeaderPainter> p2(CreateTestPainter(w2.get())); - w2->Show(); - - // Despite two windows, the first window should still be considered "solo" - // because panels aren't included in the computation. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // The panel itself is not considered solo. - EXPECT_FALSE(p2->UseSoloWindowHeader()); - - // Even after closing the first window, the panel is still not considered - // solo. - w1.reset(); - EXPECT_FALSE(p2->UseSoloWindowHeader()); -} - -// Modal dialogs should not use solo headers. -TEST_F(HeaderPainterTest, UseSoloWindowHeaderModal) { - // Create a widget and a painter for it. - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Create a fake modal window. - scoped_ptr<Widget> w2(CreateTestWidget()); - scoped_ptr<HeaderPainter> p2(CreateTestPainter(w2.get())); - w2->GetNativeWindow()->SetProperty(aura::client::kModalKey, - ui::MODAL_TYPE_WINDOW); - w2->Show(); - - // Despite two windows, the first window should still be considered "solo" - // because modal windows aren't included in the computation. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // The modal window itself is not considered solo. - EXPECT_FALSE(p2->UseSoloWindowHeader()); -} - -// Constrained windows should not use solo headers. -TEST_F(HeaderPainterTest, UseSoloWindowHeaderConstrained) { - // Create a widget and a painter for it. - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // Create a fake constrained window. - scoped_ptr<Widget> w2(CreateTestWidget()); - scoped_ptr<HeaderPainter> p2(CreateTestPainter(w2.get())); - w2->GetNativeWindow()->SetProperty(ash::kConstrainedWindowKey, true); - w2->Show(); - - // Despite two windows, the first window should still be considered "solo" - // because constrained windows aren't included in the computation. - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - // The constrained window itself is not considered solo. - EXPECT_FALSE(p2->UseSoloWindowHeader()); -} - -// Non-drawing windows should not affect the solo computation. -TEST_F(HeaderPainterTest, UseSoloWindowHeaderNotDrawn) { - // Create a widget and a painter for it. - scoped_ptr<Widget> widget(CreateTestWidget()); - scoped_ptr<HeaderPainter> painter(CreateTestPainter(widget.get())); - widget->Show(); - - // We only have one window, so it should use a solo header. - EXPECT_TRUE(painter->UseSoloWindowHeader()); - - // Create non-drawing window similar to DragDropTracker. - scoped_ptr<aura::Window> window(new aura::Window(NULL)); - window->SetType(aura::client::WINDOW_TYPE_NORMAL); - window->Init(ui::LAYER_NOT_DRAWN); - aura::client::ParentWindowWithContext(window.get(), widget->GetNativeWindow(), - gfx::Rect()); - window->Show(); - - // Despite two windows, the first window should still be considered "solo" - // because non-drawing windows aren't included in the computation. - EXPECT_TRUE(painter->UseSoloWindowHeader()); -} - -#if defined(OS_WIN) -// Multiple displays are not supported on Windows Ash. http://crbug.com/165962 -#define MAYBE_UseSoloWindowHeaderMultiDisplay \ - DISABLED_UseSoloWindowHeaderMultiDisplay -#else -#define MAYBE_UseSoloWindowHeaderMultiDisplay \ - UseSoloWindowHeaderMultiDisplay -#endif - -TEST_F(HeaderPainterTest, MAYBE_UseSoloWindowHeaderMultiDisplay) { - if (!SupportsMultipleDisplays()) - return; - - UpdateDisplay("1000x600,600x400"); - - // Create two widgets and painters for them. - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->SetBounds(gfx::Rect(0, 0, 100, 100)); - w1->Show(); - WindowRepaintChecker checker1(w1->GetNativeWindow()); - scoped_ptr<Widget> w2(CreateTestWidget()); - scoped_ptr<HeaderPainter> p2(CreateTestPainter(w2.get())); - w2->SetBounds(gfx::Rect(0, 0, 100, 100)); - w2->Show(); - WindowRepaintChecker checker2(w2->GetNativeWindow()); - - // Now there are two windows in the same display, so we should not use solo - // headers. - EXPECT_FALSE(p1->UseSoloWindowHeader()); - EXPECT_FALSE(p2->UseSoloWindowHeader()); - EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); - - // Moves the second window to the secondary display. Both w1/w2 should be - // solo. - w2->SetBounds(gfx::Rect(1200, 0, 100, 100)); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - EXPECT_TRUE(p2->UseSoloWindowHeader()); - EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); - EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); - - // Open two more windows in the primary display. - scoped_ptr<Widget> w3(CreateTestWidget()); - scoped_ptr<HeaderPainter> p3(CreateTestPainter(w3.get())); - w3->SetBounds(gfx::Rect(0, 0, 100, 100)); - w3->Show(); - scoped_ptr<Widget> w4(CreateTestWidget()); - scoped_ptr<HeaderPainter> p4(CreateTestPainter(w4.get())); - w4->SetBounds(gfx::Rect(0, 0, 100, 100)); - w4->Show(); - - // Because the primary display has two windows w1 and w3, they shouldn't be - // solo. w2 should be solo. - EXPECT_FALSE(p1->UseSoloWindowHeader()); - EXPECT_TRUE(p2->UseSoloWindowHeader()); - EXPECT_FALSE(p3->UseSoloWindowHeader()); - EXPECT_FALSE(p4->UseSoloWindowHeader()); - EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); - - // Moves the w4 to the secondary display. Now the w2 shouldn't be solo - // anymore. - w4->SetBounds(gfx::Rect(1200, 0, 100, 100)); - EXPECT_FALSE(p1->UseSoloWindowHeader()); - EXPECT_FALSE(p2->UseSoloWindowHeader()); - EXPECT_FALSE(p3->UseSoloWindowHeader()); - EXPECT_FALSE(p4->UseSoloWindowHeader()); - EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); - - // Moves the w3 to the secondary display too. Now w1 should be solo again. - w3->SetBounds(gfx::Rect(1200, 0, 100, 100)); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - EXPECT_FALSE(p2->UseSoloWindowHeader()); - EXPECT_FALSE(p3->UseSoloWindowHeader()); - EXPECT_FALSE(p4->UseSoloWindowHeader()); - EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); - - // Change the w3 state to maximize. Doesn't affect to w1. - wm::GetWindowState(w3->GetNativeWindow())->Maximize(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - EXPECT_FALSE(p2->UseSoloWindowHeader()); - EXPECT_FALSE(p3->UseSoloWindowHeader()); - EXPECT_FALSE(p4->UseSoloWindowHeader()); - - // Close the w3 and w4. - w3.reset(); - w4.reset(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - EXPECT_TRUE(p2->UseSoloWindowHeader()); - EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); - - // Move w2 back to the primary display. - w2->SetBounds(gfx::Rect(0, 0, 100, 100)); - EXPECT_FALSE(p1->UseSoloWindowHeader()); - EXPECT_FALSE(p2->UseSoloWindowHeader()); - EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); - EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); - - // Close w2. - w2.reset(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); -} - TEST_F(HeaderPainterTest, GetHeaderOpacity) { // Create a widget and a painter for it. scoped_ptr<Widget> w1(CreateTestWidget()); @@ -601,66 +175,4 @@ TEST_F(HeaderPainterTest, TitleIconAlignment) { short_header_title_bounds.CenterPoint().y()); } -TEST_F(HeaderPainterTest, ChildWindowVisibility) { - scoped_ptr<Widget> w1(CreateTestWidget()); - scoped_ptr<HeaderPainter> p1(CreateTestPainter(w1.get())); - w1->Show(); - - // Solo active window has solo window opacity. - EXPECT_EQ(HeaderPainter::kSoloWindowOpacity, - p1->GetHeaderOpacity(HeaderPainter::ACTIVE, - IDR_AURA_WINDOW_HEADER_BASE_ACTIVE, - 0)); - - // Create a child window which doesn't affect the solo header. - scoped_ptr<Widget> w2(new Widget); - Widget::InitParams params(Widget::InitParams::TYPE_CONTROL); - params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - params.parent = w1->GetNativeView(); - w2->Init(params); - w2->Show(); - - // Still has solo header if child window is added. - EXPECT_EQ(HeaderPainter::kSoloWindowOpacity, - p1->GetHeaderOpacity(HeaderPainter::ACTIVE, - IDR_AURA_WINDOW_HEADER_BASE_ACTIVE, - 0)); - - // Change the visibility of w2 and verifies w1 still has solo header. - w2->Hide(); - EXPECT_EQ(HeaderPainter::kSoloWindowOpacity, - p1->GetHeaderOpacity(HeaderPainter::ACTIVE, - IDR_AURA_WINDOW_HEADER_BASE_ACTIVE, - 0)); -} - -TEST_F(HeaderPainterTest, NoCrashShutdownWithAlwaysOnTopWindow) { - // Create normal window and an always-on-top window, and leave it as is - // and finish the test, then verify it doesn't cause a crash. See - // crbug.com/273310. Note that those widgets will be deleted at - // RootWindowController::CloseChildWindows(), so this code is memory-safe. - Widget* w1 = new Widget; - Widget::InitParams params1; - params1.context = CurrentContext(); - w1->Init(params1); - HeaderPainterOwner* o1 = new HeaderPainterOwner(w1); - HeaderPainter* p1 = o1->header_painter(); - w1->SetBounds(gfx::Rect(0, 0, 100, 100)); - w1->Show(); - EXPECT_TRUE(p1->UseSoloWindowHeader()); - - Widget* w2 = new Widget; - Widget::InitParams params2; - params2.context = CurrentContext(); - params2.keep_on_top = true; - w2->Init(params2); - HeaderPainterOwner* o2 = new HeaderPainterOwner(w2); - HeaderPainter* p2 = o2->header_painter(); - w2->Show(); - EXPECT_FALSE(p1->UseSoloWindowHeader()); - EXPECT_FALSE(p2->UseSoloWindowHeader()); - - // Exit with no resource release. They'll be released at shutdown. -} - } // namespace ash diff --git a/ash/wm/solo_window_tracker.cc b/ash/wm/solo_window_tracker.cc new file mode 100644 index 0000000..71299d2 --- /dev/null +++ b/ash/wm/solo_window_tracker.cc @@ -0,0 +1,210 @@ +// Copyright 2013 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 "ash/wm/solo_window_tracker.h" + +#include <algorithm> + +#include "ash/ash_constants.h" +#include "ash/root_window_controller.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/wm/window_state.h" +#include "ash/wm/window_state_observer.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/window.h" + +namespace ash { + +namespace { + +// A flag to enable/disable the solo window header across all root windows. +bool g_solo_header_enabled = true; + +// Returns the containers from which a solo window is chosen. +std::vector<aura::Window*> GetContainers(aura::RootWindow* root_window) { + int kContainerIds[] = { + internal::kShellWindowId_DefaultContainer, + internal::kShellWindowId_AlwaysOnTopContainer, + // Docked windows never use the solo header, but regular windows move to the + // docked container when dragged. + internal::kShellWindowId_DockedContainer, + }; + std::vector<aura::Window*> containers; + for (size_t i = 0; i < arraysize(kContainerIds); ++i) { + containers.push_back( + Shell::GetContainer(root_window->window(), kContainerIds[i])); + } + return containers; +} + +// Returns true if |child| and all of its ancestors are visible and neither +// |child| nor any its ancestors is animating hidden. +bool GetTargetVisibility(aura::Window* child) { + for (aura::Window* window = child; window; window = window->parent()) { + if (!window->TargetVisibility()) + return false; + } + return true; +} + +// Returns true if |window| can use the solo window header. Returns false for +// windows that are: +// * Not drawn (for example, DragDropTracker uses one for mouse capture) +// * Modal alerts (it looks odd for headers to change when an alert opens) +// * Constrained windows (ditto) +bool IsValidCandidate(aura::Window* window) { + return window->type() == aura::client::WINDOW_TYPE_NORMAL && + window->layer() && + window->layer()->type() != ui::LAYER_NOT_DRAWN && + window->GetProperty(aura::client::kModalKey) == ui::MODAL_TYPE_NONE && + !window->GetProperty(ash::kConstrainedWindowKey); +} + +// Schedule's a paint of the window's entire bounds. +void SchedulePaint(aura::Window* window) { + window->SchedulePaintInRect(gfx::Rect(window->bounds().size())); +} + +} // namespace + + +// Class which triggers a repaint of the window which is passed to the +// constructor whenever the window's show type changes. The window's non client +// view is responsible for updating whether it uses the solo header as part of +// the repaint by querying GetWindowWithSoloHeader(). +class SoloWindowTracker::SoloWindowObserver + : public ash::wm::WindowStateObserver { + public: + explicit SoloWindowObserver(aura::Window* window) : window_(window) { + wm::GetWindowState(window_)->AddObserver(this); + } + + virtual ~SoloWindowObserver() { + wm::GetWindowState(window_)->RemoveObserver(this); + } + + private: + // ash::wm::WindowStateObserver override. + virtual void OnWindowShowTypeChanged( + ash::wm::WindowState* window_state, + ash::wm::WindowShowType old_type) OVERRIDE { + SchedulePaint(window_); + } + + aura::Window* window_; + + DISALLOW_COPY_AND_ASSIGN(SoloWindowObserver); +}; + +SoloWindowTracker::SoloWindowTracker(aura::RootWindow* root_window) + : containers_(GetContainers(root_window)), + solo_window_(NULL) { + for (size_t i = 0; i < containers_.size(); ++i) + containers_[i]->AddObserver(this); +} + +SoloWindowTracker::~SoloWindowTracker() { + for (size_t i = 0; i < containers_.size(); ++i) + containers_[i]->RemoveObserver(this); +} + +// static +void SoloWindowTracker::SetSoloHeaderEnabled(bool enabled) { + g_solo_header_enabled = enabled; + std::vector<aura::Window*> root_windows = + Shell::GetInstance()->GetAllRootWindows(); + for (size_t i = 0; i < root_windows.size(); ++i) { + SoloWindowTracker* tracker = + internal::GetRootWindowController(root_windows[i])-> + solo_window_tracker(); + if (tracker) + tracker->UpdateSoloWindow(NULL); + } +} + +aura::Window* SoloWindowTracker::GetWindowWithSoloHeader() { + bool use_solo_header = solo_window_ && + !wm::GetWindowState(solo_window_)->IsMaximizedOrFullscreen(); + return use_solo_header ? solo_window_ : NULL; +} + +void SoloWindowTracker::UpdateSoloWindow(aura::Window* ignore_window) { + std::vector<aura::Window*> candidates; + // Avoid memory allocations for typical window counts. + candidates.reserve(16); + for (size_t i = 0; i < containers_.size(); ++i) { + candidates.insert(candidates.end(), + containers_[i]->children().begin(), + containers_[i]->children().end()); + } + + aura::Window* old_solo_window = solo_window_; + solo_window_ = NULL; + if (g_solo_header_enabled && !AnyVisibleWindowDocked()) { + for (size_t i = 0; i < candidates.size(); ++i) { + aura::Window* candidate = candidates[i]; + // Various sorts of windows "don't count" for this computation. + if (candidate == ignore_window || + !IsValidCandidate(candidate) || + !GetTargetVisibility(candidate)) { + continue; + } + + if (solo_window_) { + // A window can only use the solo header if it is the only visible valid + // candidate (and there are no visible docked windows). + solo_window_ = NULL; + break; + } else { + solo_window_ = candidate; + } + } + } + + if (solo_window_ == old_solo_window) + return; + + solo_window_observer_.reset(solo_window_ ? + new SoloWindowObserver(solo_window_) : NULL); + if (old_solo_window) + SchedulePaint(old_solo_window); + if (solo_window_) + SchedulePaint(solo_window_); +} + +bool SoloWindowTracker::AnyVisibleWindowDocked() const { + // For the purpose of SoloWindowTracker, there is a visible docked window if + // it causes the dock to have non-empty bounds. This is intentionally + // different from: + // DockedWindowLayoutManager::IsAnyWindowDocked() and + // DockedWindowLayoutManager::is_dragged_window_docked(). + return !dock_bounds_.IsEmpty(); +} + +void SoloWindowTracker::OnWindowAdded(aura::Window* new_window) { + UpdateSoloWindow(NULL); +} + +void SoloWindowTracker::OnWillRemoveWindow(aura::Window* window) { + UpdateSoloWindow(window); +} + +void SoloWindowTracker::OnWindowVisibilityChanged(aura::Window* window, + bool visible) { + // |window| may be a grandchild of |containers_|. + std::vector<aura::Window*>::const_iterator it = std::find( + containers_.begin(), containers_.end(), window->parent()); + if (it != containers_.end()) + UpdateSoloWindow(NULL); +} + +void SoloWindowTracker::OnDockBoundsChanging(const gfx::Rect& new_bounds, + Reason reason) { + dock_bounds_ = new_bounds; + UpdateSoloWindow(NULL); +} + +} // namespace ash diff --git a/ash/wm/solo_window_tracker.h b/ash/wm/solo_window_tracker.h new file mode 100644 index 0000000..829af01 --- /dev/null +++ b/ash/wm/solo_window_tracker.h @@ -0,0 +1,81 @@ +// Copyright 2013 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. + +#ifndef ASH_WM_SOLO_WINDOW_TRACKER_H_ +#define ASH_WM_SOLO_WINDOW_TRACKER_H_ + +#include <vector> + +#include "ash/ash_export.h" +#include "ash/wm/dock/docked_window_layout_manager_observer.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "ui/aura/window_observer.h" +#include "ui/gfx/rect.h" + +namespace aura { +class RootWindow; +class Window; +} + +namespace ash { + +// Class which keeps track of the window (if any) which should use the solo +// window header. The solo window header is very transparent and is used when +// there is only one visible window and the window is not maximized or +// fullscreen. The solo window header is not used for either panels or docked +// windows. +class ASH_EXPORT SoloWindowTracker + : public aura::WindowObserver, + public internal::DockedWindowLayoutManagerObserver { + public: + explicit SoloWindowTracker(aura::RootWindow* root_window); + virtual ~SoloWindowTracker(); + + // Enable/Disable solo headers. + static void SetSoloHeaderEnabled(bool enabled); + + // Returns the window, if any, which should use the solo window header. + aura::Window* GetWindowWithSoloHeader(); + + private: + // Updates the window which would use the solo header if the window were not + // maximized or fullscreen. If |ignore_window| is not NULL, it is ignored for + // counting valid candidates. This is useful when there is a window which is + // about to be moved to a different root window or about to be closed. + void UpdateSoloWindow(aura::Window* ignore_window); + + // Returns true if there is a visible docked window. + bool AnyVisibleWindowDocked() const; + + // aura::WindowObserver overrides: + virtual void OnWindowAdded(aura::Window* new_window) OVERRIDE; + virtual void OnWillRemoveWindow(aura::Window* window) OVERRIDE; + virtual void OnWindowVisibilityChanged(aura::Window* window, + bool visible) OVERRIDE; + + // ash::internal::DockedWindowLayoutManagerObserver override: + virtual void OnDockBoundsChanging(const gfx::Rect& new_bounds, + Reason reason) OVERRIDE; + + // The containers whose children can use the solo header. + std::vector<aura::Window*> containers_; + + // The dock's bounds. + gfx::Rect dock_bounds_; + + // The window which would use the solo header if it were not maximized or + // fullscreen. + aura::Window* solo_window_; + + // Class which observes changes in |solo_window_|'s show type. + class SoloWindowObserver; + scoped_ptr<SoloWindowObserver> solo_window_observer_; + + DISALLOW_COPY_AND_ASSIGN(SoloWindowTracker); +}; + +} // namespace ash + +#endif // ASH_WM_SOLO_WINDOW_TRACKER_H_ diff --git a/ash/wm/solo_window_tracker_unittest.cc b/ash/wm/solo_window_tracker_unittest.cc new file mode 100644 index 0000000..f682fc8 --- /dev/null +++ b/ash/wm/solo_window_tracker_unittest.cc @@ -0,0 +1,423 @@ +// Copyright 2013 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 "ash/wm/solo_window_tracker.h" + +#include "ash/ash_constants.h" +#include "ash/ash_switches.h" +#include "ash/root_window_controller.h" +#include "ash/screen_ash.h" +#include "ash/shell.h" +#include "ash/shell_window_ids.h" +#include "ash/test/ash_test_base.h" +#include "ash/wm/window_resizer.h" +#include "ash/wm/window_state.h" +#include "base/memory/scoped_ptr.h" +#include "ui/aura/client/aura_constants.h" +#include "ui/aura/root_window.h" +#include "ui/aura/test/event_generator.h" +#include "ui/aura/window.h" +#include "ui/aura/window_observer.h" +#include "ui/base/hit_test.h" +#include "ui/gfx/screen.h" + +namespace ash { + +namespace { + +class WindowRepaintChecker : public aura::WindowObserver { + public: + explicit WindowRepaintChecker(aura::Window* window) + : is_paint_scheduled_(false) { + window->AddObserver(this); + } + virtual ~WindowRepaintChecker() { + } + + bool IsPaintScheduledAndReset() { + bool result = is_paint_scheduled_; + is_paint_scheduled_ = false; + return result; + } + + private: + // aura::WindowObserver overrides: + virtual void OnWindowPaintScheduled(aura::Window* window, + const gfx::Rect& region) OVERRIDE { + is_paint_scheduled_ = true; + } + virtual void OnWindowDestroying(aura::Window* window) OVERRIDE { + window->RemoveObserver(this); + } + + bool is_paint_scheduled_; + + DISALLOW_COPY_AND_ASSIGN(WindowRepaintChecker); +}; + +} // namespace + +class SoloWindowTrackerTest : public test::AshTestBase { + public: + SoloWindowTrackerTest() { + } + virtual ~SoloWindowTrackerTest() { + } + + // Helpers methods to create test windows in the primary root window. + aura::Window* CreateWindowInPrimary() { + aura::Window* window = new aura::Window(NULL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + window->SetBounds(gfx::Rect(100, 100)); + ParentWindowInPrimaryRootWindow(window); + return window; + } + aura::Window* CreateAlwaysOnTopWindowInPrimary() { + aura::Window* window = new aura::Window(NULL); + window->SetType(aura::client::WINDOW_TYPE_NORMAL); + window->Init(ui::LAYER_TEXTURED); + window->SetBounds(gfx::Rect(100, 100)); + window->SetProperty(aura::client::kAlwaysOnTopKey, true); + ParentWindowInPrimaryRootWindow(window); + return window; + } + aura::Window* CreatePanelWindowInPrimary() { + aura::Window* window = new aura::Window(NULL); + window->SetType(aura::client::WINDOW_TYPE_PANEL); + window->Init(ui::LAYER_TEXTURED); + window->SetBounds(gfx::Rect(100, 100)); + ParentWindowInPrimaryRootWindow(window); + return window; + } + + // Drag |window| to the dock. + void DockWindow(aura::Window* window) { + // Because the tests use windows without delegates, + // aura::test::EventGenerator cannot be used. + gfx::Point drag_to = + ash::ScreenAsh::GetDisplayBoundsInParent(window).top_right(); + scoped_ptr<WindowResizer> resizer(CreateWindowResizer( + window, + window->bounds().origin(), + HTCAPTION, + aura::client::WINDOW_MOVE_SOURCE_MOUSE)); + resizer->Drag(drag_to, 0); + resizer->CompleteDrag(0); + EXPECT_EQ(internal::kShellWindowId_DockedContainer, + window->parent()->id()); + } + + // Drag |window| out of the dock. + void UndockWindow(aura::Window* window) { + gfx::Point drag_to = + ash::ScreenAsh::GetDisplayWorkAreaBoundsInParent(window).top_right() - + gfx::Vector2d(10, 0); + scoped_ptr<WindowResizer> resizer(CreateWindowResizer( + window, + window->bounds().origin(), + HTCAPTION, + aura::client::WINDOW_MOVE_SOURCE_MOUSE)); + resizer->Drag(drag_to, 0); + resizer->CompleteDrag(0); + EXPECT_NE(internal::kShellWindowId_DockedContainer, + window->parent()->id()); + } + + // Returns the primary display. + gfx::Display GetPrimaryDisplay() { + return ash::Shell::GetInstance()->GetScreen()->GetPrimaryDisplay(); + } + + // Returns the secondary display. + gfx::Display GetSecondaryDisplay() { + return ScreenAsh::GetSecondaryDisplay(); + } + + // Returns the window which uses the solo header, if any, on the primary + // display. + aura::Window* GetWindowWithSoloHeaderInPrimary() { + return GetWindowWithSoloHeader(Shell::GetPrimaryRootWindow()); + } + + // Returns the window which uses the solo header, if any, in |root|. + aura::Window* GetWindowWithSoloHeader(aura::Window* root) { + SoloWindowTracker* solo_window_tracker = + internal::GetRootWindowController(root)->solo_window_tracker(); + return solo_window_tracker ? + solo_window_tracker->GetWindowWithSoloHeader() : NULL; + } + + private: + DISALLOW_COPY_AND_ASSIGN(SoloWindowTrackerTest); +}; + +TEST_F(SoloWindowTrackerTest, Basic) { + scoped_ptr<aura::Window> w1(CreateWindowInPrimary()); + w1->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + // Create a second window. + scoped_ptr<aura::Window> w2(CreateWindowInPrimary()); + w2->Show(); + + // Now there are two windows, so we should not use solo headers. + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + // Hide one window. Solo should be enabled. + w2->Hide(); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + // Show that window. Solo should be disabled. + w2->Show(); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + // Minimize the first window. Solo should be enabled. + wm::GetWindowState(w1.get())->Minimize(); + EXPECT_EQ(w2.get(), GetWindowWithSoloHeaderInPrimary()); + + // Close the minimized window. + w1.reset(); + EXPECT_EQ(w2.get(), GetWindowWithSoloHeaderInPrimary()); + + // Open an always-on-top window (which lives in a different container). + scoped_ptr<aura::Window> w3(CreateAlwaysOnTopWindowInPrimary()); + w3->Show(); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + // Close the always-on-top window. + w3.reset(); + EXPECT_EQ(w2.get(), GetWindowWithSoloHeaderInPrimary()); +} + +// Test that docked windows never use the solo header and that the presence of a +// docked window prevents all other windows from the using the solo window +// header. +TEST_F(SoloWindowTrackerTest, DockedWindow) { + if (!switches::UseDockedWindows()) + return; + + scoped_ptr<aura::Window> w1(CreateWindowInPrimary()); + w1->Show(); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + DockWindow(w1.get()); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + UndockWindow(w1.get()); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + scoped_ptr<aura::Window> w2(CreateWindowInPrimary()); + w2->Show(); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + DockWindow(w2.get()); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + wm::GetWindowState(w2.get())->Minimize(); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); +} + +// Panels should not "count" for computing solo window headers, and the panel +// itself should never use the solo header. +TEST_F(SoloWindowTrackerTest, Panel) { + scoped_ptr<aura::Window> w1(CreateWindowInPrimary()); + w1->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + // Create a panel window. + scoped_ptr<aura::Window> w2(CreatePanelWindowInPrimary()); + w2->Show(); + + // Despite two windows, the first window should still be considered "solo" + // because panels aren't included in the computation. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + // Even after closing the first window, the panel is still not considered + // solo. + w1.reset(); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); +} + +// Modal dialogs should not use solo headers. +TEST_F(SoloWindowTrackerTest, Modal) { + scoped_ptr<aura::Window> w1(CreateWindowInPrimary()); + w1->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + // Create a fake modal window. + scoped_ptr<aura::Window> w2(CreateWindowInPrimary()); + w2->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_WINDOW); + w2->Show(); + + // Despite two windows, the first window should still be considered "solo" + // because modal windows aren't included in the computation. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); +} + +// Constrained windows should not use solo headers. +TEST_F(SoloWindowTrackerTest, Constrained) { + scoped_ptr<aura::Window> w1(CreateWindowInPrimary()); + w1->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + + // Create a fake constrained window. + scoped_ptr<aura::Window> w2(CreateWindowInPrimary()); + w2->SetProperty(ash::kConstrainedWindowKey, true); + w2->Show(); + + // Despite two windows, the first window should still be considered "solo" + // because constrained windows aren't included in the computation. + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); +} + +// Non-drawing windows should not affect the solo computation. +TEST_F(SoloWindowTrackerTest, NotDrawn) { + aura::Window* w = CreateWindowInPrimary(); + w->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary()); + + // Create non-drawing window similar to DragDropTracker. + aura::Window* not_drawn = new aura::Window(NULL); + not_drawn->SetType(aura::client::WINDOW_TYPE_NORMAL); + not_drawn->Init(ui::LAYER_NOT_DRAWN); + ParentWindowInPrimaryRootWindow(not_drawn); + not_drawn->Show(); + + // Despite two windows, the first window should still be considered "solo" + // because non-drawing windows aren't included in the computation. + EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary()); +} + +TEST_F(SoloWindowTrackerTest, MultiDisplay) { + if (!SupportsMultipleDisplays()) + return; + + UpdateDisplay("1000x600,600x400"); + + scoped_ptr<aura::Window> w1(CreateWindowInPrimary()); + w1->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay()); + w1->Show(); + WindowRepaintChecker checker1(w1.get()); + scoped_ptr<aura::Window> w2(CreateWindowInPrimary()); + w2->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay()); + w2->Show(); + WindowRepaintChecker checker2(w2.get()); + + // Now there are two windows in the same display, so we should not use solo + // headers. + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); + + // Moves the second window to the secondary display. Both w1/w2 should be + // solo. + w2->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100), + ScreenAsh::GetSecondaryDisplay()); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + EXPECT_EQ(w2.get(), GetWindowWithSoloHeader(w2->GetRootWindow())); + EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); + EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); + + // Open two more windows in the primary display. + scoped_ptr<aura::Window> w3(CreateWindowInPrimary()); + w3->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay()); + w3->Show(); + scoped_ptr<aura::Window> w4(CreateWindowInPrimary()); + w4->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay()); + w4->Show(); + + // Because the primary display has three windows w1, w3, and w4, they + // shouldn't be solo. w2 should be solo. + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + EXPECT_EQ(w2.get(), GetWindowWithSoloHeader(w2->GetRootWindow())); + EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); + + // Move w4 to the secondary display. Now w2 shouldn't be solo anymore. + w4->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100), GetSecondaryDisplay()); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + EXPECT_EQ(NULL, GetWindowWithSoloHeader(w2->GetRootWindow())); + EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); + + // Moves w3 to the secondary display too. Now w1 should be solo again. + w3->SetBoundsInScreen(gfx::Rect(1200, 0, 100, 100), GetSecondaryDisplay()); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + EXPECT_EQ(NULL, GetWindowWithSoloHeader(w2->GetRootWindow())); + EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); + + // Change w3's state to maximize. Doesn't affect w1. + wm::GetWindowState(w3.get())->Maximize(); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + EXPECT_EQ(NULL, GetWindowWithSoloHeader(w2->GetRootWindow())); + + // Close w3 and w4. + w3.reset(); + w4.reset(); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + EXPECT_EQ(w2.get(), GetWindowWithSoloHeader(w2->GetRootWindow())); + EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); + + // Move w2 back to the primary display. + w2->SetBoundsInScreen(gfx::Rect(0, 0, 100, 100), GetPrimaryDisplay()); + EXPECT_EQ(w1->GetRootWindow(), w2->GetRootWindow()); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); + EXPECT_TRUE(checker2.IsPaintScheduledAndReset()); + + // Close w2. + w2.reset(); + EXPECT_EQ(w1.get(), GetWindowWithSoloHeaderInPrimary()); + EXPECT_TRUE(checker1.IsPaintScheduledAndReset()); +} + +TEST_F(SoloWindowTrackerTest, ChildWindowVisibility) { + aura::Window* w = CreateWindowInPrimary(); + w->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary()); + + // Create a child window. This should not affect the solo-ness of |w1|. + aura::Window* child = new aura::Window(NULL); + child->SetType(aura::client::WINDOW_TYPE_CONTROL); + child->Init(ui::LAYER_TEXTURED); + child->SetBounds(gfx::Rect(100, 100)); + w->AddChild(child); + child->Show(); + EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary()); + + // Changing the visibility of |child| should not affect the solo-ness of |w1|. + child->Hide(); + EXPECT_EQ(w, GetWindowWithSoloHeaderInPrimary()); +} + +TEST_F(SoloWindowTrackerTest, CreateAndDeleteSingleWindow) { + // Ensure that creating/deleting a window works well and doesn't cause + // crashes. See crbug.com/155634 + scoped_ptr<aura::Window> w(CreateWindowInPrimary()); + w->Show(); + + // We only have one window, so it should use a solo header. + EXPECT_EQ(w.get(), GetWindowWithSoloHeaderInPrimary()); + + // Close the window. + w.reset(); + EXPECT_EQ(NULL, GetWindowWithSoloHeaderInPrimary()); + + // Recreate another window again. + w.reset(CreateWindowInPrimary()); + w->Show(); + EXPECT_EQ(w.get(), GetWindowWithSoloHeaderInPrimary()); +} + +} // namespace ash diff --git a/ash/wm/workspace/workspace_layout_manager.cc b/ash/wm/workspace/workspace_layout_manager.cc index 513ea19..a0ae8cc 100644 --- a/ash/wm/workspace/workspace_layout_manager.cc +++ b/ash/wm/workspace/workspace_layout_manager.cc @@ -11,7 +11,6 @@ #include "ash/shell.h" #include "ash/wm/always_on_top_controller.h" #include "ash/wm/base_layout_manager.h" -#include "ash/wm/header_painter.h" #include "ash/wm/window_animations.h" #include "ash/wm/window_positioner.h" #include "ash/wm/window_properties.h" @@ -289,7 +288,6 @@ void WorkspaceLayoutManager::AdjustWindowBoundsWhenAdded( void WorkspaceLayoutManager::UpdateDesktopVisibility() { if (shelf_) shelf_->UpdateVisibilityState(); - HeaderPainter::UpdateSoloWindowHeader(window_->GetRootWindow()); } void WorkspaceLayoutManager::UpdateBoundsFromShowState( diff --git a/chrome/browser/chromeos/login/login_display_host_impl.cc b/chrome/browser/chromeos/login/login_display_host_impl.cc index ed33875..173eebe 100644 --- a/chrome/browser/chromeos/login/login_display_host_impl.cc +++ b/chrome/browser/chromeos/login/login_display_host_impl.cc @@ -10,7 +10,7 @@ #include "ash/desktop_background/user_wallpaper_delegate.h" #include "ash/shell.h" #include "ash/shell_window_ids.h" -#include "ash/wm/header_painter.h" +#include "ash/wm/solo_window_tracker.h" #include "base/bind.h" #include "base/command_line.h" #include "base/debug/trace_event.h" @@ -932,7 +932,7 @@ void LoginDisplayHostImpl::StartPostponedWebUI() { void LoginDisplayHostImpl::InitLoginWindowAndView() { if (login_window_) return; - ash::HeaderPainter::SetSoloWindowHeadersEnabled(false); + ash::SoloWindowTracker::SetSoloHeaderEnabled(false); if (system::keyboard_settings::ForceKeyboardDrivenUINavigation()) { views::FocusManager::set_arrow_key_traversal_enabled(true); @@ -986,7 +986,7 @@ void LoginDisplayHostImpl::InitLoginWindowAndView() { void LoginDisplayHostImpl::ResetLoginWindowAndView() { if (!login_window_) return; - ash::HeaderPainter::SetSoloWindowHeadersEnabled(true); + ash::SoloWindowTracker::SetSoloHeaderEnabled(true); login_window_->Close(); login_window_ = NULL; login_view_ = NULL; |