mus: Fix handled status in UI event ack, add MessageLoop::NestingObserver
mus applications receive their events from the window server, which runs
in another process.
Currently all input events to a mus application are immediately acked as
handled, because it's possible the event will start a nested message loop
(e.g. by spawning a context menu) and we don't want the event delivery
message to time out.
With this CL we observe for a message loop starting and if we see one we
immediately ack the event to avoid a timeout. Otherwise we process the
event normally and return a correct handled status.
BUG=604069
TEST=added to base_unittests and views_mus_unittests
Review-Url: https://codereview.chromium.org/1891233006
Cr-Commit-Position: refs/heads/master@{#390259}
diff --git a/base/message_loop/message_loop.cc b/base/message_loop/message_loop.cc
index 01e7512..f189637 100644
--- a/base/message_loop/message_loop.cc
+++ b/base/message_loop/message_loop.cc
@@ -121,6 +121,8 @@
MessageLoop::DestructionObserver::~DestructionObserver() {
}
+MessageLoop::NestingObserver::~NestingObserver() {}
+
//------------------------------------------------------------------------------
MessageLoop::MessageLoop(Type type)
@@ -263,6 +265,16 @@
destruction_observers_.RemoveObserver(destruction_observer);
}
+void MessageLoop::AddNestingObserver(NestingObserver* observer) {
+ DCHECK_EQ(this, current());
+ nesting_observers_.AddObserver(observer);
+}
+
+void MessageLoop::RemoveNestingObserver(NestingObserver* observer) {
+ DCHECK_EQ(this, current());
+ nesting_observers_.RemoveObserver(observer);
+}
+
void MessageLoop::PostTask(
const tracked_objects::Location& from_here,
const Closure& task) {
@@ -576,6 +588,11 @@
#endif
}
+void MessageLoop::NotifyBeginNestedLoop() {
+ FOR_EACH_OBSERVER(NestingObserver, nesting_observers_,
+ OnBeginNestedMessageLoop());
+}
+
bool MessageLoop::DoWork() {
if (!nestable_tasks_allowed_) {
// Task can't be executed right now.
diff --git a/base/message_loop/message_loop.h b/base/message_loop/message_loop.h
index 802fae0..24d5b46 100644
--- a/base/message_loop/message_loop.h
+++ b/base/message_loop/message_loop.h
@@ -133,6 +133,7 @@
// Creates the default MessagePump based on |type|. Caller owns return
// value.
static std::unique_ptr<MessagePump> CreateMessagePumpForType(Type type);
+
// A DestructionObserver is notified when the current MessageLoop is being
// destroyed. These observers are notified prior to MessageLoop::current()
// being changed to return NULL. This gives interested parties the chance to
@@ -157,6 +158,19 @@
// DestructionObserver is receiving a notification callback.
void RemoveDestructionObserver(DestructionObserver* destruction_observer);
+ // A NestingObserver is notified when a nested message loop begins. The
+ // observers are notified before the first task is processed.
+ class BASE_EXPORT NestingObserver {
+ public:
+ virtual void OnBeginNestedMessageLoop() = 0;
+
+ protected:
+ virtual ~NestingObserver();
+ };
+
+ void AddNestingObserver(NestingObserver* observer);
+ void RemoveNestingObserver(NestingObserver* observer);
+
// NOTE: Deprecated; prefer task_runner() and the TaskRunner interfaces.
// TODO(skyostil): Remove these functions (crbug.com/465354).
//
@@ -473,6 +487,9 @@
// If message_histogram_ is NULL, this is a no-op.
void HistogramEvent(int event);
+ // Notify observers that a nested message loop is starting.
+ void NotifyBeginNestedLoop();
+
// MessagePump::Delegate methods:
bool DoWork() override;
bool DoDelayedWork(TimeTicks* next_delayed_work_time) override;
@@ -507,6 +524,8 @@
ObserverList<DestructionObserver> destruction_observers_;
+ ObserverList<NestingObserver> nesting_observers_;
+
// A recursion block that prevents accidentally running additional tasks when
// insider a (accidentally induced?) nested message pump.
bool nestable_tasks_allowed_;
diff --git a/base/message_loop/message_loop_test.cc b/base/message_loop/message_loop_test.cc
index c0e6481c..0f1e924 100644
--- a/base/message_loop/message_loop_test.cc
+++ b/base/message_loop/message_loop_test.cc
@@ -378,6 +378,66 @@
EXPECT_EQ(depth, 0);
}
+// A NestingObserver that tracks the number of nested message loop starts it
+// has seen.
+class TestNestingObserver : public MessageLoop::NestingObserver {
+ public:
+ TestNestingObserver() {}
+ ~TestNestingObserver() override {}
+
+ int begin_nested_loop_count() const { return begin_nested_loop_count_; }
+
+ // MessageLoop::NestingObserver:
+ void OnBeginNestedMessageLoop() override { begin_nested_loop_count_++; }
+
+ private:
+ int begin_nested_loop_count_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(TestNestingObserver);
+};
+
+void ExpectOneBeginNestedLoop(TestNestingObserver* observer) {
+ EXPECT_EQ(1, observer->begin_nested_loop_count());
+}
+
+// Starts a nested message loop.
+void RunNestedLoop(TestNestingObserver* observer,
+ const Closure& quit_outer_loop) {
+ // The nested loop hasn't started yet.
+ EXPECT_EQ(0, observer->begin_nested_loop_count());
+
+ MessageLoop::ScopedNestableTaskAllower allow(MessageLoop::current());
+ RunLoop nested_loop;
+ // Verify that by the time the first task is run the observer has seen the
+ // message loop begin.
+ MessageLoop::current()->PostTask(FROM_HERE,
+ Bind(&ExpectOneBeginNestedLoop, observer));
+ MessageLoop::current()->PostTask(FROM_HERE, nested_loop.QuitClosure());
+ nested_loop.Run();
+
+ // Quitting message loops doesn't change the begin count.
+ EXPECT_EQ(1, observer->begin_nested_loop_count());
+
+ quit_outer_loop.Run();
+}
+
+// Tests that a NestingObserver is notified when a nested message loop begins.
+void RunTest_NestingObserver(MessagePumpFactory factory) {
+ std::unique_ptr<MessagePump> pump(factory());
+ MessageLoop outer_loop(std::move(pump));
+
+ // Observe the outer loop for nested message loops beginning.
+ TestNestingObserver nesting_observer;
+ outer_loop.AddNestingObserver(&nesting_observer);
+
+ // Post a task that runs a nested message loop.
+ outer_loop.PostTask(FROM_HERE, Bind(&RunNestedLoop, &nesting_observer,
+ outer_loop.QuitWhenIdleClosure()));
+ outer_loop.Run();
+
+ outer_loop.RemoveNestingObserver(&nesting_observer);
+}
+
enum TaskType {
MESSAGEBOX,
ENDDIALOG,
diff --git a/base/message_loop/message_loop_test.h b/base/message_loop/message_loop_test.h
index 3d9889c..200232e87 100644
--- a/base/message_loop/message_loop_test.h
+++ b/base/message_loop/message_loop_test.h
@@ -28,6 +28,7 @@
void RunTest_EnsureDeletion(MessagePumpFactory factory);
void RunTest_EnsureDeletion_Chain(MessagePumpFactory factory);
void RunTest_Nesting(MessagePumpFactory factory);
+void RunTest_NestingObserver(MessagePumpFactory factory);
void RunTest_RecursiveDenial1(MessagePumpFactory factory);
void RunTest_RecursiveDenial3(MessagePumpFactory factory);
void RunTest_RecursiveSupport1(MessagePumpFactory factory);
diff --git a/base/run_loop.cc b/base/run_loop.cc
index af2c568d..4e425c9 100644
--- a/base/run_loop.cc
+++ b/base/run_loop.cc
@@ -68,6 +68,9 @@
run_depth_ = previous_run_loop_? previous_run_loop_->run_depth_ + 1 : 1;
loop_->run_loop_ = this;
+ if (run_depth_ > 1)
+ loop_->NotifyBeginNestedLoop();
+
running_ = true;
return true;
}
diff --git a/ui/views/mus/BUILD.gn b/ui/views/mus/BUILD.gn
index 7fc833c..187cd8f4 100644
--- a/ui/views/mus/BUILD.gn
+++ b/ui/views/mus/BUILD.gn
@@ -151,6 +151,7 @@
"../widget/widget_unittest.cc",
"native_widget_mus_unittest.cc",
"platform_test_helper_mus.cc",
+ "platform_window_mus_unittest.cc",
"run_all_unittests_mus.cc",
"screen_mus_unittest.cc",
]
diff --git a/ui/views/mus/native_widget_mus.h b/ui/views/mus/native_widget_mus.h
index 3636769..5dd2aca 100644
--- a/ui/views/mus/native_widget_mus.h
+++ b/ui/views/mus/native_widget_mus.h
@@ -73,6 +73,7 @@
static void NotifyFrameChanged(mus::WindowTreeConnection* connection);
mus::Window* window() { return window_; }
+ WindowTreeHostMus* window_tree_host() { return window_tree_host_.get(); }
aura::Window* GetRootWindow();
diff --git a/ui/views/mus/platform_window_mus.cc b/ui/views/mus/platform_window_mus.cc
index 4b72979..fcf21a1 100644
--- a/ui/views/mus/platform_window_mus.cc
+++ b/ui/views/mus/platform_window_mus.cc
@@ -4,6 +4,7 @@
#include "ui/views/mus/platform_window_mus.h"
+#include "base/message_loop/message_loop.h"
#include "build/build_config.h"
#include "components/bitmap_uploader/bitmap_uploader.h"
#include "components/mus/public/cpp/property_type_converters.h"
@@ -14,11 +15,52 @@
#include "ui/platform_window/platform_window_delegate.h"
#include "ui/views/mus/window_manager_connection.h"
+using mus::mojom::EventResult;
+
namespace views {
namespace {
+
static uint32_t accelerated_widget_count = 1;
+// Handles acknowledgement of an input event, either immediately when a nested
+// message loop starts, or upon destruction.
+class EventAckHandler : public base::MessageLoop::NestingObserver {
+ public:
+ explicit EventAckHandler(
+ std::unique_ptr<base::Callback<void(EventResult)>> ack_callback)
+ : ack_callback_(std::move(ack_callback)) {
+ DCHECK(ack_callback_);
+ base::MessageLoop::current()->AddNestingObserver(this);
+ }
+
+ ~EventAckHandler() override {
+ base::MessageLoop::current()->RemoveNestingObserver(this);
+ if (ack_callback_) {
+ ack_callback_->Run(handled_ ? EventResult::HANDLED
+ : EventResult::UNHANDLED);
+ }
+ }
+
+ void set_handled(bool handled) { handled_ = handled; }
+
+ // base::MessageLoop::NestingObserver:
+ void OnBeginNestedMessageLoop() override {
+ // Acknowledge the event immediately if a nested message loop starts.
+ // Otherwise we appear unresponsive for the life of the nested message loop.
+ if (ack_callback_) {
+ ack_callback_->Run(EventResult::HANDLED);
+ ack_callback_.reset();
+ }
+ }
+
+ private:
+ std::unique_ptr<base::Callback<void(EventResult)>> ack_callback_;
+ bool handled_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(EventAckHandler);
+};
+
} // namespace
PlatformWindowMus::PlatformWindowMus(ui::PlatformWindowDelegate* delegate,
@@ -222,17 +264,17 @@
void PlatformWindowMus::OnWindowInputEvent(
mus::Window* view,
- const ui::Event& event,
- std::unique_ptr<base::Callback<void(mus::mojom::EventResult)>>*
- ack_callback) {
- // It's possible dispatching the event will spin a nested message loop. Ack
- // the callback now, otherwise we appear unresponsive for the life of the
- // nested message loop.
- (*ack_callback)->Run(mus::mojom::EventResult::HANDLED);
- ack_callback->reset();
- // TODO(moshayedi): Avoid cloning after updating PlatformWindowDelegate to
- // accept constant pointers.
- delegate_->DispatchEvent(ui::Event::Clone(event).get());
+ const ui::Event& event_in,
+ std::unique_ptr<base::Callback<void(EventResult)>>* ack_callback) {
+ // Take ownership of the callback, indicating that we will handle it.
+ EventAckHandler ack_handler(std::move(*ack_callback));
+
+ std::unique_ptr<ui::Event> event = ui::Event::Clone(event_in);
+ delegate_->DispatchEvent(event.get());
+ // NOTE: |this| may be deleted.
+
+ ack_handler.set_handled(event->handled());
+ // |ack_handler| acks the event on destruction if necessary.
}
} // namespace views
diff --git a/ui/views/mus/platform_window_mus.h b/ui/views/mus/platform_window_mus.h
index 9364239..235108b 100644
--- a/ui/views/mus/platform_window_mus.h
+++ b/ui/views/mus/platform_window_mus.h
@@ -64,6 +64,8 @@
ui::PlatformImeController* GetPlatformImeController() override;
private:
+ friend class PlatformWindowMusTest;
+
void SetShowState(mus::mojom::ShowState show_state);
// mus::WindowObserver:
diff --git a/ui/views/mus/platform_window_mus_unittest.cc b/ui/views/mus/platform_window_mus_unittest.cc
new file mode 100644
index 0000000..e4d5a7b9
--- /dev/null
+++ b/ui/views/mus/platform_window_mus_unittest.cc
@@ -0,0 +1,154 @@
+// Copyright 2016 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 "ui/views/mus/platform_window_mus.h"
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/events/event.h"
+#include "ui/gfx/geometry/rect.h"
+#include "ui/views/mus/native_widget_mus.h"
+#include "ui/views/mus/window_tree_host_mus.h"
+#include "ui/views/test/views_test_base.h"
+#include "ui/views/widget/widget.h"
+#include "ui/views/widget/widget_delegate.h"
+
+using mus::mojom::EventResult;
+
+namespace views {
+namespace {
+
+// A view that reports any mouse press as handled.
+class HandleMousePressView : public View {
+ public:
+ HandleMousePressView() {}
+ ~HandleMousePressView() override {}
+
+ // View:
+ bool OnMousePressed(const ui::MouseEvent& event) override { return true; }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HandleMousePressView);
+};
+
+// A view that deletes a widget on mouse press.
+class DeleteWidgetView : public View {
+ public:
+ explicit DeleteWidgetView(std::unique_ptr<Widget>* widget_ptr)
+ : widget_ptr_(widget_ptr) {}
+ ~DeleteWidgetView() override {}
+
+ // View:
+ bool OnMousePressed(const ui::MouseEvent& event) override {
+ widget_ptr_->reset();
+ return true;
+ }
+
+ private:
+ std::unique_ptr<Widget>* widget_ptr_;
+ DISALLOW_COPY_AND_ASSIGN(DeleteWidgetView);
+};
+
+} // namespace
+
+class PlatformWindowMusTest : public ViewsTestBase {
+ public:
+ PlatformWindowMusTest() {}
+ ~PlatformWindowMusTest() override {}
+
+ int ack_callback_count() { return ack_callback_count_; }
+
+ void AckCallback(mus::mojom::EventResult result) {
+ ack_callback_count_++;
+ EXPECT_EQ(mus::mojom::EventResult::HANDLED, result);
+ }
+
+ // testing::Test:
+ void SetUp() override {
+ ViewsTestBase::SetUp();
+ widget_.reset(new Widget);
+ Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
+ params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+ params.bounds = gfx::Rect(0, 0, 100, 100);
+ widget_->Init(params);
+ widget_->Show();
+ native_widget_ =
+ static_cast<NativeWidgetMus*>(widget_->native_widget_private());
+ platform_window_ = native_widget_->window_tree_host()->platform_window();
+ ASSERT_TRUE(platform_window_);
+ }
+
+ // Returns a mouse pressed event in the middle of the widget.
+ std::unique_ptr<ui::MouseEvent> CreateMouseEvent() {
+ return base::WrapUnique(new ui::MouseEvent(
+ ui::ET_MOUSE_PRESSED, gfx::Point(50, 50), gfx::Point(50, 50),
+ base::TimeDelta(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON));
+ }
+
+ // Simulates an input event to the PlatformWindow.
+ void OnWindowInputEvent(
+ const ui::Event& event,
+ std::unique_ptr<base::Callback<void(mus::mojom::EventResult)>>*
+ ack_callback) {
+ platform_window_->OnWindowInputEvent(native_widget_->window(), event,
+ ack_callback);
+ }
+
+ protected:
+ std::unique_ptr<Widget> widget_;
+
+ private:
+ NativeWidgetMus* native_widget_ = nullptr;
+ PlatformWindowMus* platform_window_ = nullptr;
+ int ack_callback_count_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(PlatformWindowMusTest);
+};
+
+// Tests that an incoming UI event is acked with the handled status.
+TEST_F(PlatformWindowMusTest, EventAcked) {
+ View* content = new HandleMousePressView;
+ content->SetBounds(0, 0, 100, 100);
+ widget_->GetContentsView()->AddChildView(content);
+
+ // Dispatch an input event to the window and view.
+ std::unique_ptr<ui::MouseEvent> event = CreateMouseEvent();
+ std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
+ new base::Callback<void(EventResult)>(base::Bind(
+ &PlatformWindowMusTest::AckCallback, base::Unretained(this))));
+ OnWindowInputEvent(*event, &ack_callback);
+
+ // The platform window took ownership of the callback and called it.
+ EXPECT_FALSE(ack_callback);
+ EXPECT_EQ(1, ack_callback_count());
+}
+
+// Tests that a window that is deleted during event handling properly acks the
+// event.
+TEST_F(PlatformWindowMusTest, EventAckedWithWindowDestruction) {
+ View* content = new DeleteWidgetView(&widget_);
+ content->SetBounds(0, 0, 100, 100);
+ widget_->GetContentsView()->AddChildView(content);
+
+ // Dispatch an input event to the window and view.
+ std::unique_ptr<ui::MouseEvent> event = CreateMouseEvent();
+ std::unique_ptr<base::Callback<void(EventResult)>> ack_callback(
+ new base::Callback<void(EventResult)>(base::Bind(
+ &PlatformWindowMusTest::AckCallback, base::Unretained(this))));
+ OnWindowInputEvent(*event, &ack_callback);
+
+ // The widget was deleted.
+ EXPECT_FALSE(widget_);
+
+ // The platform window took ownership of the callback and called it.
+ EXPECT_FALSE(ack_callback);
+ EXPECT_EQ(1, ack_callback_count());
+}
+
+} // namespace views