summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormohsen <mohsen@chromium.org>2016-03-09 11:35:52 -0800
committerCommit bot <commit-bot@chromium.org>2016-03-09 19:41:07 +0000
commitae35edbf864d215d1dc699225a1b0e0ece08b3be (patch)
treecf08ec3144647725369a4bffd9406696343d1bf1
parentaae535ca8e8087942b4716f92cf833f84a38f1a9 (diff)
downloadchromium_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}
-rw-r--r--components/mus/public/cpp/tests/test_window_tree.cc2
-rw-r--r--components/mus/public/cpp/tests/test_window_tree.h1
-rw-r--r--components/mus/public/interfaces/window_tree.mojom7
-rw-r--r--components/mus/ws/access_policy.h1
-rw-r--r--components/mus/ws/connection_manager.cc15
-rw-r--r--components/mus/ws/connection_manager.h1
-rw-r--r--components/mus/ws/default_access_policy.cc5
-rw-r--r--components/mus/ws/default_access_policy.h1
-rw-r--r--components/mus/ws/event_dispatcher.cc17
-rw-r--r--components/mus/ws/event_dispatcher.h4
-rw-r--r--components/mus/ws/event_dispatcher_unittest.cc235
-rw-r--r--components/mus/ws/server_window.cc35
-rw-r--r--components/mus/ws/server_window.h15
-rw-r--r--components/mus/ws/window_manager_access_policy.cc5
-rw-r--r--components/mus/ws/window_manager_access_policy.h1
-rw-r--r--components/mus/ws/window_manager_state.cc27
-rw-r--r--components/mus/ws/window_manager_state.h10
-rw-r--r--components/mus/ws/window_tree.cc21
-rw-r--r--components/mus/ws/window_tree.h2
-rw-r--r--components/mus/ws/window_tree_unittest.cc218
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