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