diff options
author | mohsen <mohsen@chromium.org> | 2016-03-09 11:35:52 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-03-09 19:41:07 +0000 |
commit | ae35edbf864d215d1dc699225a1b0e0ece08b3be (patch) | |
tree | cf08ec3144647725369a4bffd9406696343d1bf1 | |
parent | aae535ca8e8087942b4716f92cf833f84a38f1a9 (diff) | |
download | chromium_src-ae35edbf864d215d1dc699225a1b0e0ece08b3be.zip chromium_src-ae35edbf864d215d1dc699225a1b0e0ece08b3be.tar.gz chromium_src-ae35edbf864d215d1dc699225a1b0e0ece08b3be.tar.bz2 |
mus: Server-side implementation of modal windows
This patch adds server-side implementation for window modal windows,
i.e. windows that are modal to their transient parent. System modal
windows will be added later.
BUG=548402
Review URL: https://codereview.chromium.org/1759523002
Cr-Commit-Position: refs/heads/master@{#380177}
20 files changed, 583 insertions, 40 deletions
diff --git a/components/mus/public/cpp/tests/test_window_tree.cc b/components/mus/public/cpp/tests/test_window_tree.cc index 398a661..ba9ebed 100644 --- a/components/mus/public/cpp/tests/test_window_tree.cc +++ b/components/mus/public/cpp/tests/test_window_tree.cc @@ -91,6 +91,8 @@ void TestWindowTree::RemoveTransientWindowFromParent( uint32_t change_id, uint32_t transient_window_id) {} +void TestWindowTree::SetModal(uint32_t change_id, uint32_t window_id) {} + void TestWindowTree::ReorderWindow(uint32_t change_id, uint32_t window_id, uint32_t relative_window_id, diff --git a/components/mus/public/cpp/tests/test_window_tree.h b/components/mus/public/cpp/tests/test_window_tree.h index d392341..8165c70 100644 --- a/components/mus/public/cpp/tests/test_window_tree.h +++ b/components/mus/public/cpp/tests/test_window_tree.h @@ -65,6 +65,7 @@ class TestWindowTree : public mojom::WindowTree { uint32_t transient_window_id) override; void RemoveTransientWindowFromParent(uint32_t change_id, uint32_t window_id) override; + void SetModal(uint32_t change_id, uint32_t window_id) override; void ReorderWindow(uint32_t change_id, uint32_t window_id, uint32_t relative_window_id, diff --git a/components/mus/public/interfaces/window_tree.mojom b/components/mus/public/interfaces/window_tree.mojom index 5114297..6ad3990 100644 --- a/components/mus/public/interfaces/window_tree.mojom +++ b/components/mus/public/interfaces/window_tree.mojom @@ -179,6 +179,13 @@ interface WindowTree { // This does not change transient window's position in the window hierarchy. RemoveTransientWindowFromParent(uint32 change_id, uint32 transient_window_id); + // Sets |window_id| to be modal to its transient parent. + // This fails if |window_id| does not identify a valid window. + // TODO(mohsen): If |window_id| does not have a transient parent, this will + // have no effect. Plan is to make a window modal to system if it does not + // have a transient parent. + SetModal(uint32 change_id, uint32 window_id); + // Reorders a window in its parent, relative to |relative_window_id| according // to |direction|. Only the connection that created the window's parent can // reorder its children. diff --git a/components/mus/ws/access_policy.h b/components/mus/ws/access_policy.h index 81e2d3b..4ce1540 100644 --- a/components/mus/ws/access_policy.h +++ b/components/mus/ws/access_policy.h @@ -32,6 +32,7 @@ class AccessPolicy { const ServerWindow* child) const = 0; virtual bool CanRemoveTransientWindowFromParent( const ServerWindow* window) const = 0; + virtual bool CanSetModal(const ServerWindow* window) const = 0; virtual bool CanReorderWindow(const ServerWindow* window, const ServerWindow* relative_window, mojom::OrderDirection direction) const = 0; diff --git a/components/mus/ws/connection_manager.cc b/components/mus/ws/connection_manager.cc index 4ef8461..f67713b 100644 --- a/components/mus/ws/connection_manager.cc +++ b/components/mus/ws/connection_manager.cc @@ -461,6 +461,11 @@ void ConnectionManager::OnWindowHierarchyChanged(ServerWindow* window, if (in_destructor_) return; + WindowManagerState* wms = + display_manager_->GetWindowManagerAndDisplay(window).window_manager_state; + if (wms) + wms->ReleaseCaptureBlockedByAnyModalWindow(); + ProcessWindowHierarchyChanged(window, new_parent, old_parent); // TODO(beng): optimize. @@ -526,6 +531,16 @@ void ConnectionManager::OnWillChangeWindowVisibility(ServerWindow* window) { } } +void ConnectionManager::OnWindowVisibilityChanged(ServerWindow* window) { + if (in_destructor_) + return; + + WindowManagerState* wms = + display_manager_->GetWindowManagerAndDisplay(window).window_manager_state; + if (wms) + wms->ReleaseCaptureBlockedByModalWindow(window); +} + void ConnectionManager::OnWindowPredefinedCursorChanged(ServerWindow* window, int32_t cursor_id) { if (in_destructor_) diff --git a/components/mus/ws/connection_manager.h b/components/mus/ws/connection_manager.h index fe3bc95..40c270a 100644 --- a/components/mus/ws/connection_manager.h +++ b/components/mus/ws/connection_manager.h @@ -240,6 +240,7 @@ class ConnectionManager : public ServerWindowDelegate, ServerWindow* relative, mojom::OrderDirection direction) override; void OnWillChangeWindowVisibility(ServerWindow* window) override; + void OnWindowVisibilityChanged(ServerWindow* window) override; void OnWindowSharedPropertyChanged( ServerWindow* window, const std::string& name, diff --git a/components/mus/ws/default_access_policy.cc b/components/mus/ws/default_access_policy.cc index d55c860..7c8c062 100644 --- a/components/mus/ws/default_access_policy.cc +++ b/components/mus/ws/default_access_policy.cc @@ -51,6 +51,11 @@ bool DefaultAccessPolicy::CanRemoveTransientWindowFromParent( WasCreatedByThisConnection(window->transient_parent())); } +bool DefaultAccessPolicy::CanSetModal(const ServerWindow* window) const { + return delegate_->HasRootForAccessPolicy(window) || + WasCreatedByThisConnection(window); +} + bool DefaultAccessPolicy::CanReorderWindow( const ServerWindow* window, const ServerWindow* relative_window, diff --git a/components/mus/ws/default_access_policy.h b/components/mus/ws/default_access_policy.h index da8db32..af93e78 100644 --- a/components/mus/ws/default_access_policy.h +++ b/components/mus/ws/default_access_policy.h @@ -31,6 +31,7 @@ class DefaultAccessPolicy : public AccessPolicy { const ServerWindow* child) const override; bool CanRemoveTransientWindowFromParent( const ServerWindow* window) const override; + bool CanSetModal(const ServerWindow* window) const override; bool CanReorderWindow(const ServerWindow* window, const ServerWindow* relative_window, mojom::OrderDirection direction) const override; diff --git a/components/mus/ws/event_dispatcher.cc b/components/mus/ws/event_dispatcher.cc index c7eca9e..57efcc1 100644 --- a/components/mus/ws/event_dispatcher.cc +++ b/components/mus/ws/event_dispatcher.cc @@ -235,14 +235,18 @@ void EventDispatcher::SetMousePointerScreenLocation( UpdateCursorProviderByLastKnownLocation(); } -void EventDispatcher::SetCaptureWindow(ServerWindow* window, +bool EventDispatcher::SetCaptureWindow(ServerWindow* window, bool in_nonclient_area) { if (window == capture_window_) - return; + return true; + + // A window that is blocked by a modal window cannot gain capture. + if (window && window->IsBlockedByModalWindow()) + return false; if (capture_window_) { // Stop observing old capture window. |pointer_targets_| are cleared on - // intial setting of a capture window. + // initial setting of a capture window. delegate_->OnServerWindowCaptureLost(capture_window_); capture_window_->RemoveObserver(this); } else { @@ -264,7 +268,7 @@ void EventDispatcher::SetCaptureWindow(ServerWindow* window, pair.second.is_mouse_event ? ui::EventPointerType::POINTER_TYPE_MOUSE : ui::EventPointerType::POINTER_TYPE_TOUCH; // TODO(jonross): Track previous location in PointerTarget for sending - // cancels + // cancels. ui::PointerEvent event(event_type, pointer_type, gfx::Point(), gfx::Point(), ui::EF_NONE, pair.first, ui::EventTimeForNow()); @@ -286,6 +290,7 @@ void EventDispatcher::SetCaptureWindow(ServerWindow* window, capture_window_ = window; capture_window_in_nonclient_area_ = in_nonclient_area; + return true; } void EventDispatcher::UpdateCursorProviderByLastKnownLocation() { @@ -470,10 +475,12 @@ EventDispatcher::PointerTarget EventDispatcher::PointerTargetForEvent( const ui::PointerEvent& event) const { PointerTarget pointer_target; gfx::Point location(event.location()); - pointer_target.window = + ServerWindow* target_window = FindDeepestVisibleWindowForEvents(root_, surface_id_, &location); + pointer_target.window = target_window->GetModalTarget(); pointer_target.is_mouse_event = event.IsMousePointerEvent(); pointer_target.in_nonclient_area = + target_window != pointer_target.window || IsLocationInNonclientArea(pointer_target.window, location); pointer_target.is_pointer_down = event.type() == ui::ET_POINTER_DOWN; return pointer_target; diff --git a/components/mus/ws/event_dispatcher.h b/components/mus/ws/event_dispatcher.h index 800ba8a..30868e8 100644 --- a/components/mus/ws/event_dispatcher.h +++ b/components/mus/ws/event_dispatcher.h @@ -57,7 +57,9 @@ class EventDispatcher : public ServerWindowObserver { // details. ServerWindow* capture_window() { return capture_window_; } const ServerWindow* capture_window() const { return capture_window_; } - void SetCaptureWindow(ServerWindow* capture_window, bool in_nonclient_area); + // Setting capture can fail if the window is blocked by a modal window + // (indicated by returning |false|). + bool SetCaptureWindow(ServerWindow* capture_window, bool in_nonclient_area); // Retrieves the ServerWindow of the last mouse move. ServerWindow* mouse_cursor_source_window() const { diff --git a/components/mus/ws/event_dispatcher_unittest.cc b/components/mus/ws/event_dispatcher_unittest.cc index ea34f1c..3273c3c 100644 --- a/components/mus/ws/event_dispatcher_unittest.cc +++ b/components/mus/ws/event_dispatcher_unittest.cc @@ -183,9 +183,10 @@ class EventDispatcherTest : public testing::Test { bool AreAnyPointersDown() const; // Deletes everything created during SetUp() void ClearSetup(); - // Creates a window which is a child of |root_window_|. It is not owned by - // EventDispatcherTest. - ServerWindow* CreateChildWindow(const WindowId& id); + scoped_ptr<ServerWindow> CreateChildWindowWithParent(const WindowId& id, + ServerWindow* parent); + // Creates a window which is a child of |root_window_|. + scoped_ptr<ServerWindow> CreateChildWindow(const WindowId& id); bool IsMouseButtonDown() const; bool IsWindowPointerTarget(ServerWindow* window) const; int NumberPointerTargetsForWindow(ServerWindow* window) const; @@ -214,14 +215,21 @@ void EventDispatcherTest::ClearSetup() { event_dispatcher_.reset(); } -ServerWindow* EventDispatcherTest::CreateChildWindow(const WindowId& id) { - ServerWindow* child = new ServerWindow(window_delegate_.get(), id); - root_window_->Add(child); +scoped_ptr<ServerWindow> EventDispatcherTest::CreateChildWindowWithParent( + const WindowId& id, + ServerWindow* parent) { + scoped_ptr<ServerWindow> child(new ServerWindow(window_delegate_.get(), id)); + parent->Add(child.get()); child->SetVisible(true); - EnableHitTest(child); + EnableHitTest(child.get()); return child; } +scoped_ptr<ServerWindow> EventDispatcherTest::CreateChildWindow( + const WindowId& id) { + return CreateChildWindowWithParent(id, root_window_.get()); +} + bool EventDispatcherTest::IsMouseButtonDown() const { return EventDispatcherTestApi(event_dispatcher_.get()).is_mouse_button_down(); } @@ -253,7 +261,7 @@ void EventDispatcherTest::SetUp() { } TEST_F(EventDispatcherTest, ProcessEvent) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -377,7 +385,7 @@ TEST_F(EventDispatcherTest, PostTargetAccelerator) { TEST_F(EventDispatcherTest, Capture) { ServerWindow* root = root_window(); - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -419,7 +427,7 @@ TEST_F(EventDispatcherTest, Capture) { } TEST_F(EventDispatcherTest, CaptureMultipleMouseButtons) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -462,7 +470,7 @@ TEST_F(EventDispatcherTest, CaptureMultipleMouseButtons) { } TEST_F(EventDispatcherTest, ClientAreaGoesToOwner) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -531,7 +539,7 @@ TEST_F(EventDispatcherTest, ClientAreaGoesToOwner) { } TEST_F(EventDispatcherTest, AdditionalClientArea) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -557,8 +565,8 @@ TEST_F(EventDispatcherTest, AdditionalClientArea) { } TEST_F(EventDispatcherTest, DontFocusOnSecondDown) { - scoped_ptr<ServerWindow> child1(CreateChildWindow(WindowId(1, 3))); - scoped_ptr<ServerWindow> child2(CreateChildWindow(WindowId(1, 4))); + scoped_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child1->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -592,8 +600,8 @@ TEST_F(EventDispatcherTest, DontFocusOnSecondDown) { } TEST_F(EventDispatcherTest, TwoPointersActive) { - scoped_ptr<ServerWindow> child1(CreateChildWindow(WindowId(1, 3))); - scoped_ptr<ServerWindow> child2(CreateChildWindow(WindowId(1, 4))); + scoped_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child1->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -651,7 +659,7 @@ TEST_F(EventDispatcherTest, TwoPointersActive) { } TEST_F(EventDispatcherTest, DestroyWindowWhileGettingEvents) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -681,7 +689,7 @@ TEST_F(EventDispatcherTest, DestroyWindowWhileGettingEvents) { TEST_F(EventDispatcherTest, MouseInExtendedHitTestRegion) { ServerWindow* root = root_window(); - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -731,8 +739,8 @@ TEST_F(EventDispatcherTest, MouseInExtendedHitTestRegion) { // TODO(moshayedi): crbug.com/590226. Enable this after we support wheel events // in mus event dispatcher. TEST_F(EventDispatcherTest, DISABLED_WheelWhileDown) { - scoped_ptr<ServerWindow> child1(CreateChildWindow(WindowId(1, 3))); - scoped_ptr<ServerWindow> child2(CreateChildWindow(WindowId(1, 4))); + scoped_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child1->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -761,7 +769,7 @@ TEST_F(EventDispatcherTest, DISABLED_WheelWhileDown) { // appropriate target window. TEST_F(EventDispatcherTest, SetExplicitCapture) { ServerWindow* root = root_window(); - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -855,7 +863,7 @@ TEST_F(EventDispatcherTest, SetExplicitCapture) { // capture. TEST_F(EventDispatcherTest, ExplicitCaptureOverridesImplicitCapture) { ServerWindow* root = root_window(); - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -977,8 +985,8 @@ TEST_F(EventDispatcherTest, CaptureUpdatesActivePointerTargets) { // Tests that when explicit capture is changed, that the previous window with // capture is no longer being observed. TEST_F(EventDispatcherTest, UpdatingCaptureStopsObservingPreviousCapture) { - scoped_ptr<ServerWindow> child1(CreateChildWindow(WindowId(1, 3))); - scoped_ptr<ServerWindow> child2(CreateChildWindow(WindowId(1, 4))); + scoped_ptr<ServerWindow> child1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> child2 = CreateChildWindow(WindowId(1, 4)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child1->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -1000,7 +1008,7 @@ TEST_F(EventDispatcherTest, UpdatingCaptureStopsObservingPreviousCapture) { // Tests that destroying a window with explicit capture clears the capture // state. TEST_F(EventDispatcherTest, DestroyingCaptureWindowRemovesExplicitCapture) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); EventDispatcher* dispatcher = event_dispatcher(); @@ -1042,7 +1050,7 @@ TEST_F(EventDispatcherTest, CaptureInNonClientAreaOverridesActualPoint) { } TEST_F(EventDispatcherTest, ProcessPointerEvents) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -1089,7 +1097,7 @@ TEST_F(EventDispatcherTest, ProcessPointerEvents) { } TEST_F(EventDispatcherTest, ResetClearsPointerDown) { - scoped_ptr<ServerWindow> child(CreateChildWindow(WindowId(1, 3))); + scoped_ptr<ServerWindow> child = CreateChildWindow(WindowId(1, 3)); root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); child->SetBounds(gfx::Rect(10, 10, 20, 20)); @@ -1125,6 +1133,179 @@ TEST_F(EventDispatcherTest, ResetClearsCapture) { EXPECT_EQ(nullptr, event_dispatcher()->capture_window()); } +// Tests that events on a modal parent target the modal child. +TEST_F(EventDispatcherTest, ModalWindowEventOnModalParent) { + scoped_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w1|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(15, 15), gfx::Point(15, 15), + base::TimeDelta(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + scoped_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w2.get(), details->window); + EXPECT_TRUE(details->in_nonclient_area); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(15, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(-35, 5), dispatched_event->location()); +} + +// Tests that events on a modal child target the modal child itself. +TEST_F(EventDispatcherTest, ModalWindowEventOnModalChild) { + scoped_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w2|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(55, 15), gfx::Point(55, 15), + base::TimeDelta(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + scoped_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w2.get(), details->window); + EXPECT_FALSE(details->in_nonclient_area); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(55, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location()); +} + +// Tests that events on an unrelated window are not affected by the modal +// window. +TEST_F(EventDispatcherTest, ModalWindowEventOnUnrelatedWindow) { + scoped_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + scoped_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 6)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w3|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(75, 15), gfx::Point(75, 15), + base::TimeDelta(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + scoped_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w3.get(), details->window); + EXPECT_FALSE(details->in_nonclient_area); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(75, 15), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(5, 5), dispatched_event->location()); +} + +// Tests that events events on a descendant of a modal parent target the modal +// child. +TEST_F(EventDispatcherTest, ModalWindowEventOnDescendantOfModalParent) { + scoped_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> w11 = + CreateChildWindowWithParent(WindowId(1, 4), w1.get()); + scoped_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + // Send event that is over |w11|. + const ui::PointerEvent mouse_pressed(ui::MouseEvent( + ui::ET_MOUSE_PRESSED, gfx::Point(25, 25), gfx::Point(25, 25), + base::TimeDelta(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + event_dispatcher()->ProcessEvent(mouse_pressed); + + scoped_ptr<DispatchedEventDetails> details = + test_event_dispatcher_delegate()->GetAndAdvanceDispatchedEventDetails(); + ASSERT_TRUE(details); + EXPECT_EQ(w2.get(), details->window); + EXPECT_TRUE(details->in_nonclient_area); + + ASSERT_TRUE(details->event); + ASSERT_TRUE(details->event->IsPointerEvent()); + + ui::PointerEvent* dispatched_event = details->event->AsPointerEvent(); + EXPECT_EQ(gfx::Point(25, 25), dispatched_event->root_location()); + EXPECT_EQ(gfx::Point(-25, 15), dispatched_event->location()); +} + + +// Tests that setting capture to a descendant of a modal parent fails. +TEST_F(EventDispatcherTest, ModalWindowSetCaptureDescendantOfModalParent) { + scoped_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> w11 = + CreateChildWindowWithParent(WindowId(1, 4), w1.get()); + scoped_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + EXPECT_FALSE(event_dispatcher()->SetCaptureWindow(w11.get(), false)); + EXPECT_EQ(nullptr, event_dispatcher()->capture_window()); +} + +// Tests that setting capture to a window unrelated to a modal parent works. +TEST_F(EventDispatcherTest, ModalWindowSetCaptureUnrelatedWindow) { + scoped_ptr<ServerWindow> w1 = CreateChildWindow(WindowId(1, 3)); + scoped_ptr<ServerWindow> w2 = CreateChildWindow(WindowId(1, 5)); + scoped_ptr<ServerWindow> w3 = CreateChildWindow(WindowId(1, 6)); + + root_window()->SetBounds(gfx::Rect(0, 0, 100, 100)); + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + + w1->AddTransientWindow(w2.get()); + w2->SetModal(); + + EXPECT_TRUE(event_dispatcher()->SetCaptureWindow(w3.get(), false)); + EXPECT_EQ(w3.get(), event_dispatcher()->capture_window()); +} + } // namespace test } // namespace ws } // namespace mus diff --git a/components/mus/ws/server_window.cc b/components/mus/ws/server_window.cc index 518c064..8c546a1 100644 --- a/components/mus/ws/server_window.cc +++ b/components/mus/ws/server_window.cc @@ -18,6 +18,28 @@ namespace mus { namespace ws { +namespace { + +const ServerWindow* GetModalChildForWindowAncestor(const ServerWindow* window) { + for (const ServerWindow* ancestor = window; ancestor; + ancestor = ancestor->parent()) { + for (const auto& transient_child : ancestor->transient_children()) { + if (transient_child->is_modal() && transient_child->IsDrawn()) + return transient_child; + } + } + return nullptr; +} + +const ServerWindow* GetModalTargetForWindow(const ServerWindow* window) { + const ServerWindow* modal_window = GetModalChildForWindowAncestor(window); + if (!modal_window) + return window; + return GetModalTargetForWindow(modal_window); +} + +} // namespace + ServerWindow::ServerWindow(ServerWindowDelegate* delegate, const WindowId& id) : ServerWindow(delegate, id, Properties()) {} @@ -29,6 +51,7 @@ ServerWindow::ServerWindow(ServerWindowDelegate* delegate, parent_(nullptr), stacking_target_(nullptr), transient_parent_(nullptr), + is_modal_(false), visible_(false), cursor_id_(mojom::Cursor::CURSOR_NULL), opacity_(1), @@ -241,6 +264,18 @@ void ServerWindow::RemoveTransientWindow(ServerWindow* child) { OnTransientWindowRemoved(this, child)); } +void ServerWindow::SetModal() { + is_modal_ = true; +} + +bool ServerWindow::IsBlockedByModalWindow() const { + return !!GetModalChildForWindowAncestor(this); +} + +const ServerWindow* ServerWindow::GetModalTarget() const { + return GetModalTargetForWindow(this); +} + bool ServerWindow::Contains(const ServerWindow* window) const { for (const ServerWindow* parent = window; parent; parent = parent->parent_) { if (parent == this) diff --git a/components/mus/ws/server_window.h b/components/mus/ws/server_window.h index 46a1b21b..6987f91 100644 --- a/components/mus/ws/server_window.h +++ b/components/mus/ws/server_window.h @@ -108,6 +108,20 @@ class ServerWindow { const Windows& transient_children() const { return transient_children_; } + bool is_modal() const { return is_modal_; } + void SetModal(); + + bool IsBlockedByModalWindow() const; + + // Returns the window that events targeted to this window should be retargeted + // to; according to modal windows. If any modal window is blocking this + // window, returns the topmost one; otherwise, returns this window. + const ServerWindow* GetModalTarget() const; + ServerWindow* GetModalTarget() { + return const_cast<ServerWindow*>( + static_cast<const ServerWindow*>(this)->GetModalTarget()); + } + // Returns true if this contains |window| or is |window|. bool Contains(const ServerWindow* window) const; @@ -197,6 +211,7 @@ class ServerWindow { ServerWindow* transient_parent_; Windows transient_children_; + bool is_modal_; bool visible_; gfx::Rect bounds_; gfx::Insets client_area_; diff --git a/components/mus/ws/window_manager_access_policy.cc b/components/mus/ws/window_manager_access_policy.cc index e31bac5..d8be6ac 100644 --- a/components/mus/ws/window_manager_access_policy.cc +++ b/components/mus/ws/window_manager_access_policy.cc @@ -42,6 +42,11 @@ bool WindowManagerAccessPolicy::CanRemoveTransientWindowFromParent( return true; } +bool WindowManagerAccessPolicy::CanSetModal( + const ServerWindow* window) const { + return true; +} + bool WindowManagerAccessPolicy::CanReorderWindow( const ServerWindow* window, const ServerWindow* relative_window, diff --git a/components/mus/ws/window_manager_access_policy.h b/components/mus/ws/window_manager_access_policy.h index bed716b..67f0e73 100644 --- a/components/mus/ws/window_manager_access_policy.h +++ b/components/mus/ws/window_manager_access_policy.h @@ -30,6 +30,7 @@ class WindowManagerAccessPolicy : public AccessPolicy { const ServerWindow* child) const override; bool CanRemoveTransientWindowFromParent( const ServerWindow* window) const override; + bool CanSetModal(const ServerWindow* window) const override; bool CanReorderWindow(const ServerWindow* window, const ServerWindow* relative_window, mojom::OrderDirection direction) const override; diff --git a/components/mus/ws/window_manager_state.cc b/components/mus/ws/window_manager_state.cc index 2a6d983..2ae1a24 100644 --- a/components/mus/ws/window_manager_state.cc +++ b/components/mus/ws/window_manager_state.cc @@ -106,15 +106,36 @@ void WindowManagerState::SetFrameDecorationValues( ->OnFrameDecorationValuesChanged(this); } -void WindowManagerState::SetCapture(ServerWindow* window, +bool WindowManagerState::SetCapture(ServerWindow* window, bool in_nonclient_area) { // TODO(sky): capture should be a singleton. Need to route to // ConnectionManager so that all other EventDispatchers are updated. DCHECK(IsActive()); if (capture_window() == window) - return; + return true; DCHECK(!window || root_->Contains(window)); - event_dispatcher_.SetCaptureWindow(window, in_nonclient_area); + return event_dispatcher_.SetCaptureWindow(window, in_nonclient_area); +} + +void WindowManagerState::ReleaseCaptureBlockedByModalWindow( + const ServerWindow* modal_window) { + if (!capture_window() || !modal_window->is_modal() || + !modal_window->IsDrawn()) + return; + + if (modal_window->transient_parent() && + !modal_window->transient_parent()->Contains(capture_window())) { + return; + } + + SetCapture(nullptr, false); +} + +void WindowManagerState::ReleaseCaptureBlockedByAnyModalWindow() { + if (!capture_window() || !capture_window()->IsBlockedByModalWindow()) + return; + + SetCapture(nullptr, false); } mojom::DisplayPtr WindowManagerState::ToMojomDisplay() const { diff --git a/components/mus/ws/window_manager_state.h b/components/mus/ws/window_manager_state.h index af6f389..b93cbfd 100644 --- a/components/mus/ws/window_manager_state.h +++ b/components/mus/ws/window_manager_state.h @@ -66,12 +66,20 @@ class WindowManagerState : public EventDispatcherDelegate { return got_frame_decoration_values_; } - void SetCapture(ServerWindow* window, bool in_nonclient_area); + bool SetCapture(ServerWindow* window, bool in_nonclient_area); ServerWindow* capture_window() { return event_dispatcher_.capture_window(); } const ServerWindow* capture_window() const { return event_dispatcher_.capture_window(); } + // Checks if |modal_window| is a visible modal window that blocks current + // capture window and if that's the case, releases the capture. + void ReleaseCaptureBlockedByModalWindow(const ServerWindow* modal_window); + + // Checks if the current capture window is blocked by any visible modal window + // and if that's the case, releases the capture. + void ReleaseCaptureBlockedByAnyModalWindow(); + // Returns true if this is the WindowManager of the active user. bool IsActive() const; diff --git a/components/mus/ws/window_tree.cc b/components/mus/ws/window_tree.cc index 90e5c57..824cb0f 100644 --- a/components/mus/ws/window_tree.cc +++ b/components/mus/ws/window_tree.cc @@ -196,8 +196,7 @@ bool WindowTree::SetCapture(const ClientWindowId& client_window_id) { (!current_capture_window || access_policy_->CanSetCapture(current_capture_window)) && event_ack_id_) { - wms->SetCapture(window, !HasRoot(window)); - return true; + return wms->SetCapture(window, !HasRoot(window)); } return false; } @@ -244,6 +243,18 @@ bool WindowTree::AddTransientWindow(const ClientWindowId& window_id, return false; } +bool WindowTree::SetModal(const ClientWindowId& window_id) { + ServerWindow* window = GetWindowByClientId(window_id); + if (window && access_policy_->CanSetModal(window)) { + window->SetModal(); + WindowManagerState* wms = GetWindowManagerState(window); + if (wms) + wms->ReleaseCaptureBlockedByModalWindow(window); + return true; + } + return false; +} + std::vector<const ServerWindow*> WindowTree::GetWindowTree( const ClientWindowId& window_id) const { const ServerWindow* window = GetWindowByClientId(window_id); @@ -1008,6 +1019,10 @@ void WindowTree::RemoveTransientWindowFromParent(uint32_t change_id, client()->OnChangeCompleted(change_id, success); } +void WindowTree::SetModal(uint32_t change_id, Id window_id) { + client()->OnChangeCompleted(change_id, SetModal(ClientWindowId(window_id))); +} + void WindowTree::ReorderWindow(uint32_t change_id, Id window_id, Id relative_window_id, @@ -1048,7 +1063,7 @@ void WindowTree::ReleaseCapture(uint32_t change_id, Id window_id) { window == current_capture_window; if (success) { Operation op(this, connection_manager_, OperationType::RELEASE_CAPTURE); - wms->SetCapture(nullptr, false); + success = wms->SetCapture(nullptr, false); } client()->OnChangeCompleted(change_id, success); } diff --git a/components/mus/ws/window_tree.h b/components/mus/ws/window_tree.h index 4dcb880..90dbdd9 100644 --- a/components/mus/ws/window_tree.h +++ b/components/mus/ws/window_tree.h @@ -128,6 +128,7 @@ class WindowTree : public mojom::WindowTree, const ClientWindowId& child_id); bool AddTransientWindow(const ClientWindowId& window_id, const ClientWindowId& transient_window_id); + bool SetModal(const ClientWindowId& window_id); std::vector<const ServerWindow*> GetWindowTree( const ClientWindowId& window_id) const; bool SetWindowVisibility(const ClientWindowId& window_id, bool visible); @@ -312,6 +313,7 @@ class WindowTree : public mojom::WindowTree, Id transient_window) override; void RemoveTransientWindowFromParent(uint32_t change_id, Id transient_window_id) override; + void SetModal(uint32_t change_id, Id window_id) override; void ReorderWindow(uint32_t change_Id, Id window_id, Id relative_window_id, diff --git a/components/mus/ws/window_tree_unittest.cc b/components/mus/ws/window_tree_unittest.cc index 61b1d66..9ad18b2 100644 --- a/components/mus/ws/window_tree_unittest.cc +++ b/components/mus/ws/window_tree_unittest.cc @@ -665,6 +665,224 @@ TEST_F(WindowTreeTest, ExplicitSetCapture) { EXPECT_EQ(nullptr, GetCaptureWindow(display)); } +// Tests that showing a modal window releases the capture if the capture is on a +// descendant of the modal parent. +TEST_F(WindowTreeTest, ShowModalWindowWithDescendantCapture) { + TestWindowTreeClient* embed_connection = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_connection, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w11| as a child of |w1| and make it visible. + ClientWindowId w11_id = BuildClientWindowId(tree, 11); + ASSERT_TRUE(tree->NewWindow(w11_id, ServerWindow::Properties())); + ServerWindow* w11 = tree->GetWindowByClientId(w11_id); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(w1_id, w11_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w11_id, true)); + + // Create |w2| as a child of |root_window| and modal to |w1| and leave it + // hidden. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Set capture to |w11|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w11_id)); + EXPECT_EQ(w11, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w2| visible. This should release capture as capture is set to a + // descendant of the modal parent. + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that setting a visible window as modal releases the capture if the +// capture is on a descendant of the modal parent. +TEST_F(WindowTreeTest, VisibleWindowToModalWithDescendantCapture) { + TestWindowTreeClient* embed_connection = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_connection, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w11| as a child of |w1| and make it visible. + ClientWindowId w11_id = BuildClientWindowId(tree, 11); + ASSERT_TRUE(tree->NewWindow(w11_id, ServerWindow::Properties())); + ServerWindow* w11 = tree->GetWindowByClientId(w11_id); + w11->SetBounds(gfx::Rect(10, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(w1_id, w11_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w11_id, true)); + + // Create |w2| as a child of |root_window| and make it visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + // Set capture to |w11|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w11_id)); + EXPECT_EQ(w11, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Set |w2| modal to |w1|. This should release the capture as the capture is + // set to a descendant of the modal parent. + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + +// Tests that showing a modal window does not change capture if the capture is +// not on a descendant of the modal parent. +TEST_F(WindowTreeTest, ShowModalWindowWithNonDescendantCapture) { + TestWindowTreeClient* embed_connection = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_connection, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| as a child of |root_window| and modal to |w1| and leave it + // hidden.. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Create |w3| as a child of |root_window| and make it visible. + ClientWindowId w3_id = BuildClientWindowId(tree, 3); + ASSERT_TRUE(tree->NewWindow(w3_id, ServerWindow::Properties())); + ServerWindow* w3 = tree->GetWindowByClientId(w3_id); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w3_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w3_id, true)); + + // Set capture to |w3|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w3_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w2| visible. This should not change the capture as the capture is not + // set to a descendant of the modal parent. + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + EXPECT_EQ(w3, GetCaptureWindow(display)); +} + +// Tests that setting a visible window as modal does not change the capture if +// the capture is not set to a descendant of the modal parent. +TEST_F(WindowTreeTest, VisibleWindowToModalWithNonDescendantCapture) { + TestWindowTreeClient* embed_connection = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_connection, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| and |w3| as children of |root_window| and make them visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + ClientWindowId w3_id = BuildClientWindowId(tree, 3); + ASSERT_TRUE(tree->NewWindow(w3_id, ServerWindow::Properties())); + ServerWindow* w3 = tree->GetWindowByClientId(w3_id); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w3_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w3_id, true)); + + // Set capture to |w3|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w3_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Set |w2| modal to |w1|. This should not release the capture as the capture + // is not set to a descendant of the modal parent. + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); +} + +// Tests that moving the capture window to a modal parent releases the capture +// as capture cannot be blocked by a modal window. +TEST_F(WindowTreeTest, MoveCaptureWindowToModalParent) { + TestWindowTreeClient* embed_connection = nullptr; + WindowTree* tree = nullptr; + ServerWindow* w1 = nullptr; + EXPECT_NO_FATAL_FAILURE(SetupEventTargeting(&embed_connection, &tree, &w1)); + + w1->SetBounds(gfx::Rect(10, 10, 30, 30)); + const ServerWindow* root_window = *tree->roots().begin(); + ClientWindowId root_window_id = ClientWindowIdForWindow(tree, root_window); + ClientWindowId w1_id = ClientWindowIdForWindow(tree, w1); + Display* display = tree->GetDisplay(w1); + + // Create |w2| and |w3| as children of |root_window| and make them visible. + ClientWindowId w2_id = BuildClientWindowId(tree, 2); + ASSERT_TRUE(tree->NewWindow(w2_id, ServerWindow::Properties())); + ServerWindow* w2 = tree->GetWindowByClientId(w2_id); + w2->SetBounds(gfx::Rect(50, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w2_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w2_id, true)); + + ClientWindowId w3_id = BuildClientWindowId(tree, 3); + ASSERT_TRUE(tree->NewWindow(w3_id, ServerWindow::Properties())); + ServerWindow* w3 = tree->GetWindowByClientId(w3_id); + w3->SetBounds(gfx::Rect(70, 10, 10, 10)); + ASSERT_TRUE(tree->AddWindow(root_window_id, w3_id)); + ASSERT_TRUE(tree->SetWindowVisibility(w3_id, true)); + + // Set |w2| modal to |w1|. + ASSERT_TRUE(tree->AddTransientWindow(w1_id, w2_id)); + ASSERT_TRUE(tree->SetModal(w2_id)); + + // Set capture to |w3|. + DispatchEventWithoutAck(CreatePointerDownEvent(25, 25)); + ASSERT_TRUE(tree->SetCapture(w3_id)); + EXPECT_EQ(w3, GetCaptureWindow(display)); + AckPreviousEvent(); + + // Make |w3| child of |w1|. This should release capture as |w3| is now blocked + // by a modal window. + ASSERT_TRUE(tree->AddWindow(w1_id, w3_id)); + EXPECT_EQ(nullptr, GetCaptureWindow(display)); +} + } // namespace test } // namespace ws } // namespace mus |