diff options
Diffstat (limited to 'mojo/services/window_manager/focus_controller_unittest.cc')
-rw-r--r-- | mojo/services/window_manager/focus_controller_unittest.cc | 1197 |
1 files changed, 1197 insertions, 0 deletions
diff --git a/mojo/services/window_manager/focus_controller_unittest.cc b/mojo/services/window_manager/focus_controller_unittest.cc new file mode 100644 index 0000000..ea60ac7 --- /dev/null +++ b/mojo/services/window_manager/focus_controller_unittest.cc @@ -0,0 +1,1197 @@ +// Copyright (c) 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 "mojo/services/window_manager/focus_controller.h" + +#include "mojo/converters/geometry/geometry_type_converters.h" +#include "mojo/services/window_manager/basic_focus_rules.h" +#include "mojo/services/window_manager/capture_controller.h" +#include "mojo/services/window_manager/focus_controller_observer.h" +#include "mojo/services/window_manager/view_event_dispatcher.h" +#include "mojo/services/window_manager/view_targeter.h" +#include "mojo/services/window_manager/window_manager_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/events/event_utils.h" +#include "ui/gfx/geometry/rect.h" + +using mojo::View; + +namespace window_manager { + +// Counts the number of events that occur. +class FocusNotificationObserver : public FocusControllerObserver { + public: + FocusNotificationObserver() + : activation_changed_count_(0), + focus_changed_count_(0), + reactivation_count_(0), + reactivation_requested_view_(NULL), + reactivation_actual_view_(NULL) {} + ~FocusNotificationObserver() override {} + + void ExpectCounts(int activation_changed_count, int focus_changed_count) { + EXPECT_EQ(activation_changed_count, activation_changed_count_); + EXPECT_EQ(focus_changed_count, focus_changed_count_); + } + int reactivation_count() const { return reactivation_count_; } + View* reactivation_requested_view() const { + return reactivation_requested_view_; + } + View* reactivation_actual_view() const { + return reactivation_actual_view_; + } + + protected: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + ++activation_changed_count_; + } + + void OnFocused(View* gained_focus) override { ++focus_changed_count_; } + + void OnAttemptToReactivateView(View* request_active, + View* actual_active) override { + ++reactivation_count_; + reactivation_requested_view_ = request_active; + reactivation_actual_view_ = actual_active; + } + + private: + int activation_changed_count_; + int focus_changed_count_; + int reactivation_count_; + View* reactivation_requested_view_; + View* reactivation_actual_view_; + + DISALLOW_COPY_AND_ASSIGN(FocusNotificationObserver); +}; + +class ViewDestroyer { + public: + virtual View* GetDestroyedView() = 0; + + protected: + virtual ~ViewDestroyer() {} +}; + +// FocusNotificationObserver that keeps track of whether it was notified about +// activation changes or focus changes with a destroyed view. +class RecordingFocusNotificationObserver : public FocusNotificationObserver { + public: + RecordingFocusNotificationObserver(FocusController* focus_controller, + ViewDestroyer* destroyer) + : focus_controller_(focus_controller), + destroyer_(destroyer), + active_(nullptr), + focus_(nullptr), + was_notified_with_destroyed_view_(false) { + focus_controller_->AddObserver(this); + } + ~RecordingFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + bool was_notified_with_destroyed_view() const { + return was_notified_with_destroyed_view_; + } + + // Overridden from FocusNotificationObserver: + void OnActivated(View* gained_active) override { + if (active_ && active_ == destroyer_->GetDestroyedView()) + was_notified_with_destroyed_view_ = true; + active_ = gained_active; + } + + void OnFocused(View* gained_focus) override { + if (focus_ && focus_ == destroyer_->GetDestroyedView()) + was_notified_with_destroyed_view_ = true; + focus_ = gained_focus; + } + + private: + FocusController* focus_controller_; + + // Not owned. + ViewDestroyer* destroyer_; + View* active_; + View* focus_; + + // Whether the observer was notified about the loss of activation or the + // loss of focus with a view already destroyed by |destroyer_| as the + // |lost_active| or |lost_focus| parameter. + bool was_notified_with_destroyed_view_; + + DISALLOW_COPY_AND_ASSIGN(RecordingFocusNotificationObserver); +}; + +class DestroyOnLoseActivationFocusNotificationObserver + : public FocusNotificationObserver, + public ViewDestroyer { + public: + DestroyOnLoseActivationFocusNotificationObserver( + FocusController* focus_controller, + View* view_to_destroy, + View* initial_active) + : focus_controller_(focus_controller), + view_to_destroy_(view_to_destroy), + active_(initial_active), + did_destroy_(false) { + focus_controller_->AddObserver(this); + } + ~DestroyOnLoseActivationFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + // Overridden from FocusNotificationObserver: + void OnActivated(View* gained_active) override { + if (view_to_destroy_ && active_ == view_to_destroy_) { + view_to_destroy_->Destroy(); + did_destroy_ = true; + } + active_ = gained_active; + } + + // Overridden from ViewDestroyer: + View* GetDestroyedView() override { + return did_destroy_ ? view_to_destroy_ : nullptr; + } + + private: + FocusController* focus_controller_; + View* view_to_destroy_; + View* active_; + bool did_destroy_; + + DISALLOW_COPY_AND_ASSIGN(DestroyOnLoseActivationFocusNotificationObserver); +}; + +class ScopedFocusNotificationObserver : public FocusNotificationObserver { + public: + ScopedFocusNotificationObserver(FocusController* focus_controller) + : focus_controller_(focus_controller) { + focus_controller_->AddObserver(this); + } + ~ScopedFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + private: + FocusController* focus_controller_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFocusNotificationObserver); +}; + +// Only responds to events if a message contains |target| as a parameter. +class ScopedFilteringFocusNotificationObserver + : public FocusNotificationObserver { + public: + ScopedFilteringFocusNotificationObserver(FocusController* focus_controller, + View* target, + View* initial_active, + View* initial_focus) + : focus_controller_(focus_controller), + target_(target), + active_(initial_active), + focus_(initial_focus) { + focus_controller_->AddObserver(this); + } + ~ScopedFilteringFocusNotificationObserver() override { + focus_controller_->RemoveObserver(this); + } + + private: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + if (gained_active == target_ || active_ == target_) + FocusNotificationObserver::OnActivated(gained_active); + active_ = gained_active; + } + + void OnFocused(View* gained_focus) override { + if (gained_focus == target_ || focus_ == target_) + FocusNotificationObserver::OnFocused(gained_focus); + focus_ = gained_focus; + } + + void OnAttemptToReactivateView(View* request_active, + View* actual_active) override { + if (request_active == target_ || actual_active == target_) { + FocusNotificationObserver::OnAttemptToReactivateView(request_active, + actual_active); + } + } + + FocusController* focus_controller_; + View* target_; + View* active_; + View* focus_; + + DISALLOW_COPY_AND_ASSIGN(ScopedFilteringFocusNotificationObserver); +}; + +// Used to fake the handling of events in the pre-target phase. +class SimpleEventHandler : public ui::EventHandler { + public: + SimpleEventHandler() {} + ~SimpleEventHandler() override {} + + // Overridden from ui::EventHandler: + void OnMouseEvent(ui::MouseEvent* event) override { + event->SetHandled(); + } + void OnGestureEvent(ui::GestureEvent* event) override { + event->SetHandled(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(SimpleEventHandler); +}; + +class FocusShiftingActivationObserver + : public FocusControllerObserver { + public: + explicit FocusShiftingActivationObserver(FocusController* focus_controller, + View* activated_view) + : focus_controller_(focus_controller), + activated_view_(activated_view), + shift_focus_to_(NULL) {} + ~FocusShiftingActivationObserver() override {} + + void set_shift_focus_to(View* shift_focus_to) { + shift_focus_to_ = shift_focus_to; + } + + private: + // Overridden from FocusControllerObserver: + void OnActivated(View* gained_active) override { + // Shift focus to a child. This should prevent the default focusing from + // occurring in FocusController::FocusView(). + if (gained_active == activated_view_) + focus_controller_->FocusView(shift_focus_to_); + } + + void OnFocused(View* gained_focus) override {} + + FocusController* focus_controller_; + View* activated_view_; + View* shift_focus_to_; + + DISALLOW_COPY_AND_ASSIGN(FocusShiftingActivationObserver); +}; + +// BasicFocusRules subclass that allows basic overrides of focus/activation to +// be tested. This is intended more as a test that the override system works at +// all, rather than as an exhaustive set of use cases, those should be covered +// in tests for those FocusRules implementations. +class TestFocusRules : public BasicFocusRules { + public: + TestFocusRules(View* root) + : BasicFocusRules(root), focus_restriction_(NULL) {} + + // Restricts focus and activation to this view and its child hierarchy. + void set_focus_restriction(View* focus_restriction) { + focus_restriction_ = focus_restriction; + } + + // Overridden from BasicFocusRules: + bool SupportsChildActivation(View* view) const override { + // In FocusControllerTests, only the Root has activatable children. + return view->GetRoot() == view; + } + bool CanActivateView(View* view) const override { + // Restricting focus to a non-activatable child view means the activatable + // parent outside the focus restriction is activatable. + bool can_activate = + CanFocusOrActivate(view) || view->Contains(focus_restriction_); + return can_activate ? BasicFocusRules::CanActivateView(view) : false; + } + bool CanFocusView(View* view) const override { + return CanFocusOrActivate(view) ? BasicFocusRules::CanFocusView(view) + : false; + } + View* GetActivatableView(View* view) const override { + return BasicFocusRules::GetActivatableView( + CanFocusOrActivate(view) ? view : focus_restriction_); + } + View* GetFocusableView(View* view) const override { + return BasicFocusRules::GetFocusableView( + CanFocusOrActivate(view) ? view : focus_restriction_); + } + View* GetNextActivatableView(View* ignore) const override { + View* next_activatable = BasicFocusRules::GetNextActivatableView(ignore); + return CanFocusOrActivate(next_activatable) + ? next_activatable + : GetActivatableView(focus_restriction_); + } + + private: + bool CanFocusOrActivate(View* view) const { + return !focus_restriction_ || focus_restriction_->Contains(view); + } + + View* focus_restriction_; + + DISALLOW_COPY_AND_ASSIGN(TestFocusRules); +}; + +// Common infrastructure shared by all FocusController test types. +class FocusControllerTestBase : public testing::Test { + protected: + // Hierarchy used by all tests: + // root_view + // +-- w1 + // | +-- w11 + // | +-- w12 + // +-- w2 + // | +-- w21 + // | +-- w211 + // +-- w3 + FocusControllerTestBase() + : root_view_(TestView::Build(0, gfx::Rect(0, 0, 800, 600))), + v1(TestView::Build(1, gfx::Rect(0, 0, 50, 50), root_view())), + v11(TestView::Build(11, gfx::Rect(5, 5, 10, 10), v1)), + v12(TestView::Build(12, gfx::Rect(15, 15, 10, 10), v1)), + v2(TestView::Build(2, gfx::Rect(75, 75, 50, 50), root_view())), + v21(TestView::Build(21, gfx::Rect(5, 5, 10, 10), v2)), + v211(TestView::Build(211, gfx::Rect(1, 1, 5, 5), v21)), + v3(TestView::Build(3, gfx::Rect(125, 125, 50, 50), root_view())) {} + + // Overridden from testing::Test: + void SetUp() override { + testing::Test::SetUp(); + + test_focus_rules_ = new TestFocusRules(root_view()); + focus_controller_.reset( + new FocusController(scoped_ptr<FocusRules>(test_focus_rules_))); + SetFocusController(root_view(), focus_controller_.get()); + + capture_controller_.reset(new CaptureController); + SetCaptureController(root_view(), capture_controller_.get()); + + ViewTarget* root_target = root_view_->target(); + root_target->SetEventTargeter(scoped_ptr<ViewTargeter>(new ViewTargeter())); + view_event_dispatcher_.reset(new ViewEventDispatcher()); + view_event_dispatcher_->SetRootViewTarget(root_target); + + GetRootViewTarget()->AddPreTargetHandler(focus_controller_.get()); + } + + void TearDown() override { + GetRootViewTarget()->RemovePreTargetHandler(focus_controller_.get()); + view_event_dispatcher_.reset(); + + root_view_->Destroy(); + + capture_controller_.reset(); + test_focus_rules_ = nullptr; // Owned by FocusController. + focus_controller_.reset(); + + testing::Test::TearDown(); + } + + void FocusView(View* view) { focus_controller_->FocusView(view); } + View* GetFocusedView() { return focus_controller_->GetFocusedView(); } + int GetFocusedViewId() { + View* focused_view = GetFocusedView(); + return focused_view ? focused_view->id() : -1; + } + void ActivateView(View* view) { focus_controller_->ActivateView(view); } + void DeactivateView(View* view) { focus_controller_->DeactivateView(view); } + View* GetActiveView() { return focus_controller_->GetActiveView(); } + int GetActiveViewId() { + View* active_view = GetActiveView(); + return active_view ? active_view->id() : -1; + } + + View* GetViewById(int id) { return root_view_->GetChildById(id); } + + void ClickLeftButton(View* view) { + // Get the center bounds of |target| in |root_view_| coordinate space. + gfx::Point center = + gfx::Rect(view->bounds().To<gfx::Rect>().size()).CenterPoint(); + ViewTarget::ConvertPointToTarget(ViewTarget::TargetFromView(view), + root_view_->target(), ¢er); + + ui::MouseEvent button_down(ui::ET_MOUSE_PRESSED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_NONE); + ui::EventDispatchDetails details = + view_event_dispatcher_->OnEventFromSource(&button_down); + CHECK(!details.dispatcher_destroyed); + + ui::MouseEvent button_up(ui::ET_MOUSE_RELEASED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_NONE); + details = view_event_dispatcher_->OnEventFromSource(&button_up); + CHECK(!details.dispatcher_destroyed); + } + + ViewTarget* GetRootViewTarget() { + return ViewTarget::TargetFromView(root_view()); + } + + View* root_view() { return root_view_; } + TestFocusRules* test_focus_rules() { return test_focus_rules_; } + FocusController* focus_controller() { return focus_controller_.get(); } + CaptureController* capture_controller() { return capture_controller_.get(); } + + // Test functions. + virtual void BasicFocus() = 0; + virtual void BasicActivation() = 0; + virtual void FocusEvents() = 0; + virtual void DuplicateFocusEvents() {} + virtual void ActivationEvents() = 0; + virtual void ReactivationEvents() {} + virtual void DuplicateActivationEvents() {} + virtual void ShiftFocusWithinActiveView() {} + virtual void ShiftFocusToChildOfInactiveView() {} + virtual void ShiftFocusToParentOfFocusedView() {} + virtual void FocusRulesOverride() = 0; + virtual void ActivationRulesOverride() = 0; + virtual void ShiftFocusOnActivation() {} + virtual void ShiftFocusOnActivationDueToHide() {} + virtual void NoShiftActiveOnActivation() {} + virtual void ChangeFocusWhenNothingFocusedAndCaptured() {} + virtual void DontPassDestroyedView() {} + // TODO(erg): Also, void FocusedTextInputClient() once we build the IME. + + private: + TestView* root_view_; + scoped_ptr<FocusController> focus_controller_; + TestFocusRules* test_focus_rules_; + scoped_ptr<CaptureController> capture_controller_; + // TODO(erg): The aura version of this class also keeps track of WMState. Do + // we need something analogous here? + + scoped_ptr<ViewEventDispatcher> view_event_dispatcher_; + + TestView* v1; + TestView* v11; + TestView* v12; + TestView* v2; + TestView* v21; + TestView* v211; + TestView* v3; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerTestBase); +}; + +// Test base for tests where focus is directly set to a target view. +class FocusControllerDirectTestBase : public FocusControllerTestBase { + protected: + FocusControllerDirectTestBase() {} + + // Different test types shift focus in different ways. + virtual void FocusViewDirect(View* view) = 0; + virtual void ActivateViewDirect(View* view) = 0; + virtual void DeactivateViewDirect(View* view) = 0; + + // Input events do not change focus if the view can not be focused. + virtual bool IsInputEvent() = 0; + + void FocusViewById(int id) { + View* view = root_view()->GetChildById(id); + DCHECK(view); + FocusViewDirect(view); + } + void ActivateViewById(int id) { + View* view = root_view()->GetChildById(id); + DCHECK(view); + ActivateViewDirect(view); + } + + // Overridden from FocusControllerTestBase: + void BasicFocus() override { + EXPECT_EQ(nullptr, GetFocusedView()); + FocusViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + } + void BasicActivation() override { + EXPECT_EQ(nullptr, GetActiveView()); + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + // Verify that attempting to deactivate NULL does not crash and does not + // change activation. + DeactivateView(nullptr); + EXPECT_EQ(2, GetActiveViewId()); + DeactivateView(GetActiveView()); + EXPECT_EQ(1, GetActiveViewId()); + } + void FocusEvents() override { + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + { + SCOPED_TRACE("initial state"); + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + } + + FocusViewById(1); + { + SCOPED_TRACE("FocusViewById(1)"); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(0, 0); + } + + FocusViewById(2); + { + SCOPED_TRACE("FocusViewById(2)"); + root_observer.ExpectCounts(2, 2); + observer1.ExpectCounts(2, 2); + observer2.ExpectCounts(1, 1); + } + } + void DuplicateFocusEvents() override { + // Focusing an existing focused view should not resend focus events. + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + + FocusViewById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + + FocusViewById(1); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + } + void ActivationEvents() override { + ActivateViewById(1); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + void ReactivationEvents() override { + ActivateViewById(1); + ScopedFocusNotificationObserver root_observer(focus_controller()); + EXPECT_EQ(0, root_observer.reactivation_count()); + GetViewById(2)->SetVisible(false); + // When we attempt to activate "2", which cannot be activated because it + // is not visible, "1" will be reactivated. + ActivateViewById(2); + EXPECT_EQ(1, root_observer.reactivation_count()); + EXPECT_EQ(GetViewById(2), + root_observer.reactivation_requested_view()); + EXPECT_EQ(GetViewById(1), + root_observer.reactivation_actual_view()); + } + void DuplicateActivationEvents() override { + ActivateViewById(1); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer1( + focus_controller(), GetViewById(1), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + + root_observer.ExpectCounts(0, 0); + observer1.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + + // Activating an existing active view should not resend activation events. + ActivateViewById(2); + root_observer.ExpectCounts(1, 1); + observer1.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + } + void ShiftFocusWithinActiveView() override { + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + FocusViewById(12); + EXPECT_EQ(12, GetFocusedViewId()); + } + void ShiftFocusToChildOfInactiveView() override { + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(11, GetFocusedViewId()); + } + void ShiftFocusToParentOfFocusedView() override { + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + FocusViewById(1); + // Focus should _not_ shift to the parent of the already-focused view. + EXPECT_EQ(11, GetFocusedViewId()); + } + void FocusRulesOverride() override { + EXPECT_EQ(NULL, GetFocusedView()); + FocusViewById(11); + EXPECT_EQ(11, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(GetViewById(211)); + FocusViewById(12); + // Input events leave focus unchanged; direct API calls will change focus + // to the restricted view. + int focused_view = IsInputEvent() ? 11 : 211; + EXPECT_EQ(focused_view, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + FocusViewById(12); + EXPECT_EQ(12, GetFocusedViewId()); + } + void ActivationRulesOverride() override { + ActivateViewById(1); + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + View* v3 = GetViewById(3); + test_focus_rules()->set_focus_restriction(v3); + + ActivateViewById(2); + // Input events leave activation unchanged; direct API calls will activate + // the restricted view. + int active_view = IsInputEvent() ? 1 : 3; + EXPECT_EQ(active_view, GetActiveViewId()); + EXPECT_EQ(active_view, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateViewById(2); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + } + void ShiftFocusOnActivation() override { + // When a view is activated, by default that view is also focused. + // An ActivationChangeObserver may shift focus to another view within the + // same activatable view. + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + ActivateViewById(2); + + View* target = GetViewById(1); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(focus_controller(), target)); + observer->set_shift_focus_to(target->GetChildById(11)); + focus_controller()->AddObserver(observer.get()); + + ActivateViewById(1); + + // w1's ActivationChangeObserver shifted focus to this child, pre-empting + // FocusController's default setting. + EXPECT_EQ(11, GetFocusedViewId()); + + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + + // Simulate a focus reset by the ActivationChangeObserver. This should + // trigger the default setting in FocusController. + observer->set_shift_focus_to(nullptr); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + focus_controller()->RemoveObserver(observer.get()); + + ActivateViewById(2); + EXPECT_EQ(2, GetFocusedViewId()); + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + } + void ShiftFocusOnActivationDueToHide() override { + // Similar to ShiftFocusOnActivation except the activation change is + // triggered by hiding the active view. + ActivateViewById(1); + EXPECT_EQ(1, GetFocusedViewId()); + + // Removes view 3 as candidate for next activatable view. + root_view()->GetChildById(3)->SetVisible(false); + EXPECT_EQ(1, GetFocusedViewId()); + + View* target = root_view()->GetChildById(2); + + scoped_ptr<FocusShiftingActivationObserver> observer( + new FocusShiftingActivationObserver(focus_controller(), target)); + observer->set_shift_focus_to(target->GetChildById(21)); + focus_controller()->AddObserver(observer.get()); + + // Hide the active view. + root_view()->GetChildById(1)->SetVisible(false); + + EXPECT_EQ(21, GetFocusedViewId()); + + focus_controller()->RemoveObserver(observer.get()); + } + void NoShiftActiveOnActivation() override { + // When a view is activated, we need to prevent any change to activation + // from being made in response to an activation change notification. + } + + // Verifies focus change is honored while capture held. + void ChangeFocusWhenNothingFocusedAndCaptured() override { + View* v1 = root_view()->GetChildById(1); + capture_controller()->SetCapture(v1); + + EXPECT_EQ(-1, GetActiveViewId()); + EXPECT_EQ(-1, GetFocusedViewId()); + + FocusViewById(1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + capture_controller()->ReleaseCapture(v1); + } + + // Verifies if a view that loses activation or focus is destroyed during + // observer notification we don't pass the destroyed view to other observers. + void DontPassDestroyedView() override { + FocusViewById(1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + { + View* to_destroy = root_view()->GetChildById(1); + DestroyOnLoseActivationFocusNotificationObserver observer1( + focus_controller(), to_destroy, GetActiveView()); + RecordingFocusNotificationObserver observer2(focus_controller(), + &observer1); + + FocusViewById(2); + + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + + EXPECT_EQ(to_destroy, observer1.GetDestroyedView()); + EXPECT_FALSE(observer2.was_notified_with_destroyed_view()); + } + + { + View* to_destroy = root_view()->GetChildById(2); + DestroyOnLoseActivationFocusNotificationObserver observer1( + focus_controller(), to_destroy, GetActiveView()); + RecordingFocusNotificationObserver observer2(focus_controller(), + &observer1); + + FocusViewById(3); + + EXPECT_EQ(3, GetActiveViewId()); + EXPECT_EQ(3, GetFocusedViewId()); + + EXPECT_EQ(to_destroy, observer1.GetDestroyedView()); + EXPECT_FALSE(observer2.was_notified_with_destroyed_view()); + } + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDirectTestBase); +}; + +// Focus and Activation changes via the FocusController API. +class FocusControllerApiTest : public FocusControllerDirectTestBase { + public: + FocusControllerApiTest() {} + + private: + // Overridden from FocusControllerTestBase: + void FocusViewDirect(View* view) override { FocusView(view); } + void ActivateViewDirect(View* view) override { ActivateView(view); } + void DeactivateViewDirect(View* view) override { DeactivateView(view); } + bool IsInputEvent() override { return false; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerApiTest); +}; + +// Focus and Activation changes via input events. +class FocusControllerMouseEventTest : public FocusControllerDirectTestBase { + public: + FocusControllerMouseEventTest() {} + + // Tests that a handled mouse event does not trigger a view activation. + void IgnoreHandledEvent() { + EXPECT_EQ(NULL, GetActiveView()); + View* v1 = root_view()->GetChildById(1); + SimpleEventHandler handler; + GetRootViewTarget()->PrependPreTargetHandler(&handler); + ClickLeftButton(v1); + EXPECT_EQ(NULL, GetActiveView()); + // TODO(erg): Add gesture testing when we get gestures working. + GetRootViewTarget()->RemovePreTargetHandler(&handler); + ClickLeftButton(v1); + EXPECT_EQ(1, GetActiveViewId()); + } + + private: + // Overridden from FocusControllerTestBase: + void FocusViewDirect(View* view) override { ClickLeftButton(view); } + void ActivateViewDirect(View* view) override { ClickLeftButton(view); } + void DeactivateViewDirect(View* view) override { + View* next_activatable = test_focus_rules()->GetNextActivatableView(view); + ClickLeftButton(next_activatable); + } + bool IsInputEvent() override { return true; } + + DISALLOW_COPY_AND_ASSIGN(FocusControllerMouseEventTest); +}; + +// TODO(erg): Add a FocusControllerGestureEventTest once we have working +// gesture forwarding and handling. + +// Test base for tests where focus is implicitly set to a window as the result +// of a disposition change to the focused window or the hierarchy that contains +// it. +class FocusControllerImplicitTestBase : public FocusControllerTestBase { + protected: + explicit FocusControllerImplicitTestBase(bool parent) : parent_(parent) {} + + View* GetDispositionView(View* view) { + return parent_ ? view->parent() : view; + } + + // Change the disposition of |view| in such a way as it will lose focus. + virtual void ChangeViewDisposition(View* view) = 0; + + // Allow each disposition change test to add additional post-disposition + // change expectations. + virtual void PostDispostionChangeExpectations() {} + + // Overridden from FocusControllerTestBase: + void BasicFocus() override { + EXPECT_EQ(NULL, GetFocusedView()); + + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + EXPECT_EQ(211, GetFocusedViewId()); + + ChangeViewDisposition(w211); + // BasicFocusRules passes focus to the parent. + EXPECT_EQ(parent_ ? 2 : 21, GetFocusedViewId()); + } + + void BasicActivation() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + EXPECT_EQ(NULL, GetActiveView()); + + View* w2 = root_view()->GetChildById(2); + ActivateView(w2); + EXPECT_EQ(2, GetActiveViewId()); + + ChangeViewDisposition(w2); + EXPECT_EQ(3, GetActiveViewId()); + PostDispostionChangeExpectations(); + } + + void FocusEvents() override { + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer211( + focus_controller(), GetViewById(211), GetActiveView(), + GetFocusedView()); + + { + SCOPED_TRACE("first"); + root_observer.ExpectCounts(0, 0); + observer211.ExpectCounts(0, 0); + } + + ChangeViewDisposition(w211); + { + SCOPED_TRACE("second"); + { + SCOPED_TRACE("root_observer"); + root_observer.ExpectCounts(0, 1); + } + { + SCOPED_TRACE("observer211"); + observer211.ExpectCounts(0, 1); + } + } + } + + void ActivationEvents() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + View* w2 = root_view()->GetChildById(2); + ActivateView(w2); + + ScopedFocusNotificationObserver root_observer(focus_controller()); + ScopedFilteringFocusNotificationObserver observer2( + focus_controller(), GetViewById(2), GetActiveView(), GetFocusedView()); + ScopedFilteringFocusNotificationObserver observer3( + focus_controller(), GetViewById(3), GetActiveView(), GetFocusedView()); + root_observer.ExpectCounts(0, 0); + observer2.ExpectCounts(0, 0); + observer3.ExpectCounts(0, 0); + + ChangeViewDisposition(w2); + root_observer.ExpectCounts(1, 1); + observer2.ExpectCounts(1, 1); + observer3.ExpectCounts(1, 1); + } + + void FocusRulesOverride() override { + EXPECT_EQ(NULL, GetFocusedView()); + View* w211 = root_view()->GetChildById(211); + FocusView(w211); + EXPECT_EQ(211, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(root_view()->GetChildById(11)); + ChangeViewDisposition(w211); + // Normally, focus would shift to the parent (w21) but the override shifts + // it to 11. + EXPECT_EQ(11, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + } + + void ActivationRulesOverride() override { + DCHECK(!parent_) << "Activation tests don't support parent changes."; + + View* w1 = root_view()->GetChildById(1); + ActivateView(w1); + + EXPECT_EQ(1, GetActiveViewId()); + EXPECT_EQ(1, GetFocusedViewId()); + + View* w3 = root_view()->GetChildById(3); + test_focus_rules()->set_focus_restriction(w3); + + // Normally, activation/focus would move to w2, but since we have a focus + // restriction, it should move to w3 instead. + ChangeViewDisposition(w1); + EXPECT_EQ(3, GetActiveViewId()); + EXPECT_EQ(3, GetFocusedViewId()); + + test_focus_rules()->set_focus_restriction(NULL); + ActivateView(root_view()->GetChildById(2)); + EXPECT_EQ(2, GetActiveViewId()); + EXPECT_EQ(2, GetFocusedViewId()); + } + + private: + // When true, the disposition change occurs to the parent of the window + // instead of to the window. This verifies that changes occurring in the + // hierarchy that contains the window affect the window's focus. + bool parent_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerImplicitTestBase); +}; + +// Focus and Activation changes in response to window visibility changes. +class FocusControllerHideTest : public FocusControllerImplicitTestBase { + public: + FocusControllerHideTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerHideTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + GetDispositionView(view)->SetVisible(false); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerHideTest); +}; + +// Focus and Activation changes in response to window parent visibility +// changes. +class FocusControllerParentHideTest : public FocusControllerHideTest { + public: + FocusControllerParentHideTest() : FocusControllerHideTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentHideTest); +}; + +// Focus and Activation changes in response to window destruction. +class FocusControllerDestructionTest : public FocusControllerImplicitTestBase { + public: + FocusControllerDestructionTest() : FocusControllerImplicitTestBase(false) {} + + protected: + FocusControllerDestructionTest(bool parent) + : FocusControllerImplicitTestBase(parent) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + GetDispositionView(view)->Destroy(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerDestructionTest); +}; + +// Focus and Activation changes in response to window removal. +class FocusControllerRemovalTest : public FocusControllerImplicitTestBase { + public: + FocusControllerRemovalTest() + : FocusControllerImplicitTestBase(false), + window_to_destroy_(nullptr) {} + + protected: + FocusControllerRemovalTest(bool parent) + : FocusControllerImplicitTestBase(parent), + window_to_destroy_(nullptr) {} + + // Overridden from FocusControllerImplicitTestBase: + void ChangeViewDisposition(View* view) override { + View* disposition_view = GetDispositionView(view); + disposition_view->parent()->RemoveChild(disposition_view); + window_to_destroy_ = disposition_view; + } + void TearDown() override { + if (window_to_destroy_) + window_to_destroy_->Destroy(); + + FocusControllerImplicitTestBase::TearDown(); + } + + private: + View* window_to_destroy_; + + DISALLOW_COPY_AND_ASSIGN(FocusControllerRemovalTest); +}; + +// Focus and Activation changes in response to window parent removal. +class FocusControllerParentRemovalTest : public FocusControllerRemovalTest { + public: + FocusControllerParentRemovalTest() : FocusControllerRemovalTest(true) {} + + private: + DISALLOW_COPY_AND_ASSIGN(FocusControllerParentRemovalTest); +}; + +#define FOCUS_CONTROLLER_TEST(TESTCLASS, TESTNAME) \ + TEST_F(TESTCLASS, TESTNAME) { TESTNAME(); } + +// Runs direct focus change tests (input events and API calls). +// +// TODO(erg): Enable gesture events in the future. +#define DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerApiTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target. +#define IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerDestructionTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerRemovalTest, TESTNAME) + +// Runs implicit focus change tests for disposition changes to target's parent +// hierarchy. +#define IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentHideTest, TESTNAME) \ + FOCUS_CONTROLLER_TEST(FocusControllerParentRemovalTest, TESTNAME) +// TODO(erg): FocusControllerParentDestructionTest were commented out in the +// aura version of this file, and don't work when I tried porting things over. + +// Runs all implicit focus change tests (changes to the target and target's +// parent hierarchy) +#define IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_PARENT_TESTS(TESTNAME) + +// Runs all possible focus change tests. +#define ALL_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TESTS(TESTNAME) + +// Runs focus change tests that apply only to the target. For example, +// implicit activation changes caused by window disposition changes do not +// occur when changes to the containing hierarchy happen. +#define TARGET_FOCUS_TESTS(TESTNAME) \ + DIRECT_FOCUS_CHANGE_TESTS(TESTNAME) \ + IMPLICIT_FOCUS_CHANGE_TARGET_TESTS(TESTNAME) + +// - Focuses a window, verifies that focus changed. +ALL_FOCUS_TESTS(BasicFocus); + +// - Activates a window, verifies that activation changed. +TARGET_FOCUS_TESTS(BasicActivation); + +// - Focuses a window, verifies that focus events were dispatched. +ALL_FOCUS_TESTS(FocusEvents); + +// - Focuses or activates a window multiple times, verifies that events are only +// dispatched when focus/activation actually changes. +DIRECT_FOCUS_CHANGE_TESTS(DuplicateFocusEvents); +DIRECT_FOCUS_CHANGE_TESTS(DuplicateActivationEvents); + +// - Activates a window, verifies that activation events were dispatched. +TARGET_FOCUS_TESTS(ActivationEvents); + +// - Attempts to active a hidden view, verifies that current view is +// attempted to be reactivated and the appropriate event dispatched. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, ReactivationEvents); + +// - Input events/API calls shift focus between focusable views within the +// active view. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusWithinActiveView); + +// - Input events/API calls to a child view of an inactive view shifts +// activation to the activatable parent and focuses the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToChildOfInactiveView); + +// - Input events/API calls to focus the parent of the focused view do not +// shift focus away from the child. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusToParentOfFocusedView); + +// - Verifies that FocusRules determine what can be focused. +ALL_FOCUS_TESTS(FocusRulesOverride); + +// - Verifies that FocusRules determine what can be activated. +TARGET_FOCUS_TESTS(ActivationRulesOverride); + +// - Verifies that attempts to change focus or activation from a focus or +// activation change observer are ignored. +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivation); +DIRECT_FOCUS_CHANGE_TESTS(ShiftFocusOnActivationDueToHide); +DIRECT_FOCUS_CHANGE_TESTS(NoShiftActiveOnActivation); + +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, + ChangeFocusWhenNothingFocusedAndCaptured); + +// See description above DontPassDestroyedView() for details. +FOCUS_CONTROLLER_TEST(FocusControllerApiTest, DontPassDestroyedView); + +// TODO(erg): Add the TextInputClient tests here. + +// If a mouse event was handled, it should not activate a view. +FOCUS_CONTROLLER_TEST(FocusControllerMouseEventTest, IgnoreHandledEvent); + +} // namespace window_manager |