Bug 1351148 Part3: Synthesize native input events with priority. f=kats,smaug. r=smaug.
☠☠ backed out by 53fc41f717a7 ☠ ☠
authorStone Shih <sshih@mozilla.com>
Fri, 19 May 2017 15:41:24 +0800
changeset 374065 07b66fb75c715a62beee27a13c6e4d0759b960cd
parent 374064 46d8f42863af4fc6f2948ed2ff74629d3bb7c5df
child 374066 de4929e39b7e85257ce9107455f288b7f076300b
push id93636
push usersshih@mozilla.com
push dateFri, 11 Aug 2017 03:21:29 +0000
treeherdermozilla-inbound@0059106aaa20 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1351148
milestone57.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1351148 Part3: Synthesize native input events with priority. f=kats,smaug. r=smaug. The test helper_touch_action_regions.html uses nsDOMWindowUtils to synthesize native input events and creates some runnables to trigger the test. It expects the runnables which synthesize native input events are processed first, then the runnables to continue the test, and finally the input events are forwarded from chrome process to content process. Enabling event prioritization may change the execution order. Wraps those runnables to synthesize native input events as priority=input and dispatches those runnables to continue the test with priority=input to make sure the execution order is as expected. MozReview-Commit-ID: 8hkaB1FRW9T
dom/base/nsDOMWindowUtils.cpp
gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
xpcom/threads/nsIRunnable.idl
xpcom/threads/nsIThreadManager.idl
xpcom/threads/nsThreadManager.cpp
xpcom/threads/nsThreadUtils.cpp
xpcom/threads/nsThreadUtils.h
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -170,16 +170,40 @@ private:
     return item;
   }
 
   static LinkedList<OldWindowSize> sList;
   nsWeakPtr mWindowRef;
   nsSize mSize;
 };
 
+namespace {
+
+class NativeInputRunnable final : public PrioritizableRunnable
+{
+  explicit NativeInputRunnable(already_AddRefed<nsIRunnable>&& aEvent);
+  ~NativeInputRunnable() {}
+public:
+  static already_AddRefed<nsIRunnable> Create(already_AddRefed<nsIRunnable>&& aEvent);
+};
+
+NativeInputRunnable::NativeInputRunnable(already_AddRefed<nsIRunnable>&& aEvent)
+  : PrioritizableRunnable(Move(aEvent), nsIRunnablePriority::PRIORITY_INPUT)
+{
+}
+
+/* static */ already_AddRefed<nsIRunnable>
+NativeInputRunnable::Create(already_AddRefed<nsIRunnable>&& aEvent)
+{
+  nsCOMPtr<nsIRunnable> event(new NativeInputRunnable(Move(aEvent)));
+  return event.forget();
+}
+
+} // unnamed namespace
+
 LinkedList<OldWindowSize> OldWindowSize::sList;
 
 NS_INTERFACE_MAP_BEGIN(nsDOMWindowUtils)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMWindowUtils)
   NS_INTERFACE_MAP_ENTRY(nsIDOMWindowUtils)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
@@ -1115,76 +1139,77 @@ nsDOMWindowUtils::SendNativeKeyEvent(int
                                      const nsAString& aUnmodifiedCharacters,
                                      nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<int32_t,
                       int32_t,
                       uint32_t,
                       nsString,
                       nsString,
                       nsIObserver*>("nsIWidget::SynthesizeNativeKeyEvent",
                                     widget,
                                     &nsIWidget::SynthesizeNativeKeyEvent,
                                     aNativeKeyboardLayout,
                                     aNativeKeyCode,
                                     aModifiers,
                                     aCharacters,
                                     aUnmodifiedCharacters,
-                                    aObserver));
+                                    aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseEvent(int32_t aScreenX,
                                        int32_t aScreenY,
                                        int32_t aNativeMessage,
                                        int32_t aModifierFlags,
                                        nsIDOMElement* aElement,
                                        nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<LayoutDeviceIntPoint, int32_t, int32_t, nsIObserver*>(
       "nsIWidget::SynthesizeNativeMouseEvent",
       widget,
       &nsIWidget::SynthesizeNativeMouseEvent,
       LayoutDeviceIntPoint(aScreenX, aScreenY),
       aNativeMessage,
       aModifierFlags,
-      aObserver));
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseMove(int32_t aScreenX,
                                       int32_t aScreenY,
                                       nsIDOMElement* aElement,
                                       nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  NS_DispatchToMainThread(NewRunnableMethod<LayoutDeviceIntPoint, nsIObserver*>(
-    "nsIWidget::SynthesizeNativeMouseMove",
-    widget,
-    &nsIWidget::SynthesizeNativeMouseMove,
-    LayoutDeviceIntPoint(aScreenX, aScreenY),
-    aObserver));
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
+    NewRunnableMethod<LayoutDeviceIntPoint, nsIObserver*>(
+      "nsIWidget::SynthesizeNativeMouseMove",
+      widget,
+      &nsIWidget::SynthesizeNativeMouseMove,
+      LayoutDeviceIntPoint(aScreenX, aScreenY),
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseScrollEvent(int32_t aScreenX,
                                              int32_t aScreenY,
                                              uint32_t aNativeMessage,
                                              double aDeltaX,
@@ -1196,35 +1221,36 @@ nsDOMWindowUtils::SendNativeMouseScrollE
                                              nsIObserver* aObserver)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  NS_DispatchToMainThread(NewRunnableMethod<mozilla::LayoutDeviceIntPoint,
-                                            uint32_t,
-                                            double,
-                                            double,
-                                            double,
-                                            uint32_t,
-                                            uint32_t,
-                                            nsIObserver*>(
-    "nsIWidget::SynthesizeNativeMouseScrollEvent",
-    widget,
-    &nsIWidget::SynthesizeNativeMouseScrollEvent,
-    LayoutDeviceIntPoint(aScreenX, aScreenY),
-    aNativeMessage,
-    aDeltaX,
-    aDeltaY,
-    aDeltaZ,
-    aModifierFlags,
-    aAdditionalFlags,
-    aObserver));
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
+    NewRunnableMethod<mozilla::LayoutDeviceIntPoint,
+                      uint32_t,
+                      double,
+                      double,
+                      double,
+                      uint32_t,
+                      uint32_t,
+                      nsIObserver*>(
+      "nsIWidget::SynthesizeNativeMouseScrollEvent",
+      widget,
+      &nsIWidget::SynthesizeNativeMouseScrollEvent,
+      LayoutDeviceIntPoint(aScreenX, aScreenY),
+      aNativeMessage,
+      aDeltaX,
+      aDeltaY,
+      aDeltaZ,
+      aModifierFlags,
+      aAdditionalFlags,
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeTouchPoint(uint32_t aPointerId,
                                        uint32_t aTouchState,
                                        int32_t aScreenX,
                                        int32_t aScreenY,
@@ -1236,53 +1262,53 @@ nsDOMWindowUtils::SendNativeTouchPoint(u
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   if (aPressure < 0 || aPressure > 1 || aOrientation > 359) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<uint32_t,
                       nsIWidget::TouchPointerState,
                       LayoutDeviceIntPoint,
                       double,
                       uint32_t,
                       nsIObserver*>("nsIWidget::SynthesizeNativeTouchPoint",
                                     widget,
                                     &nsIWidget::SynthesizeNativeTouchPoint,
                                     aPointerId,
                                     (nsIWidget::TouchPointerState)aTouchState,
                                     LayoutDeviceIntPoint(aScreenX, aScreenY),
                                     aPressure,
                                     aOrientation,
-                                    aObserver));
+                                    aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeTouchTap(int32_t aScreenX,
                                      int32_t aScreenY,
                                      bool aLongTap,
                                      nsIObserver* aObserver)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<LayoutDeviceIntPoint, bool, nsIObserver*>(
       "nsIWidget::SynthesizeNativeTouchTap",
       widget,
       &nsIWidget::SynthesizeNativeTouchTap,
       LayoutDeviceIntPoint(aScreenX, aScreenY),
       aLongTap,
-      aObserver));
+      aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SuppressAnimation(bool aSuppress)
 {
   nsIWidget* widget = GetWidget();
   if (widget) {
@@ -1294,21 +1320,21 @@ nsDOMWindowUtils::SuppressAnimation(bool
 NS_IMETHODIMP
 nsDOMWindowUtils::ClearNativeTouchSequence(nsIObserver* aObserver)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  NS_DispatchToMainThread(
+  NS_DispatchToMainThread(NativeInputRunnable::Create(
     NewRunnableMethod<nsIObserver*>("nsIWidget::ClearNativeTouchSequence",
                                     widget,
                                     &nsIWidget::ClearNativeTouchSequence,
-                                    aObserver));
+                                    aObserver)));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
 {
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
--- a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
@@ -88,16 +88,21 @@ function waitFor(eventType, count) {
     if (Date.now() - start > 10000) {
       // It's taking too long, let's abort
       return false;
     }
   }
   return true;
 }
 
+function RunAfterProcessedQueuedInputEvents(aCallback) {
+  let tm = SpecialPowers.Services.tm;
+  tm.dispatchToMainThread(aCallback, SpecialPowers.Ci.nsIRunnablePriority.PRIORITY_INPUT);
+}
+
 function* test(testDriver) {
   // The main part of this test should run completely before the child process'
   // main-thread deals with the touch event, so check to make sure that happens.
   document.body.addEventListener('touchstart', failure, { passive: true });
 
   // What we want here is to synthesize all of the touch events (from this code in
   // the child process), and have the chrome process generate and process them,
   // but not allow the events to be dispatched back into the child process until
@@ -134,26 +139,29 @@ function* test(testDriver) {
   //     handle DOM touchstart  <-- touchstart goes at end of queue
   //
   // As we continue yielding one at a time, the synthesizations run, and the
   // touch events get added to the end of the queue. As we yield, we take
   // snapshots in the chrome process, to make sure that the APZ has started
   // scrolling even though we know we haven't yet processed the DOM touch events
   // in the child process yet.
   //
-  // Note that the "async callback" we use here is SpecialPowers.executeSoon,
-  // because nothing else does exactly what we want:
+  // Note that the "async callback" we use here is SpecialPowers.tm.dispatchToMainThread
+  // with priority = input, because nothing else does exactly what we want:
   // - setTimeout(..., 0) does not maintain ordering, because it respects the
   //   time delta provided (i.e. the callback can jump the queue to meet its
   //   deadline).
   // - SpecialPowers.spinEventLoop and SpecialPowers.executeAfterFlushingMessageQueue
   //   are not e10s friendly, and can get arbitrarily delayed due to IPC
   //   round-trip time.
   // - SimpleTest.executeSoon has a codepath that delegates to setTimeout, so
   //   is less reliable if it ever decides to switch to that codepath.
+  // - SpecialPowers.executeSoon dispatches a task to main thread. However,
+  //   normal runnables may be preempted by input events and be executed in an
+  //   unexpected order.
 
   // The other problem we need to deal with is the asynchronicity in the chrome
   // process. That is, we might request a snapshot before the chrome process has
   // actually synthesized the event and processed it. To guard against this, we
   // register a thing in the chrome process that counts the touch events that
   // have been dispatched, and poll that thing synchronously in order to make
   // sure we only snapshot after the event in question has been processed.
   // That's what the chromeTouchEventCounter business is all about. The sync
@@ -164,23 +172,23 @@ function* test(testDriver) {
   // So, here we go...
 
   // Set up the chrome process touch listener
   ok(chromeTouchEventCounter('start'), "Chrome touch counter registered");
 
   // Set up the child process events and callbacks
   var scroller = document.getElementById('scroller');
   synthesizeNativeTouch(scroller, 10, 110, SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
-  SpecialPowers.executeSoon(testDriver);
+  RunAfterProcessedQueuedInputEvents(testDriver);
   for (var i = 1; i < 10; i++) {
     synthesizeNativeTouch(scroller, 10, 110 - (i * 10), SpecialPowers.DOMWindowUtils.TOUCH_CONTACT, null, 0);
-    SpecialPowers.executeSoon(testDriver);
+    RunAfterProcessedQueuedInputEvents(testDriver);
   }
   synthesizeNativeTouch(scroller, 10, 10, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, null, 0);
-  SpecialPowers.executeSoon(testDriver);
+  RunAfterProcessedQueuedInputEvents(testDriver);
   ok(true, "Finished setting up event queue");
 
   // Get our baseline snapshot
   var rect = rectRelativeToScreen(scroller);
   var lastSnapshot = getSnapshot(rect);
   ok(true, "Got baseline snapshot");
   var numDifferentSnapshotPairs = 0;
 
--- a/xpcom/threads/nsIRunnable.idl
+++ b/xpcom/threads/nsIRunnable.idl
@@ -13,16 +13,16 @@
 interface nsIRunnable : nsISupports
 {
     /**
      * The function implementing the task to be run.
      */
     void run();
 };
 
-[uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
+[scriptable, uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
 interface nsIRunnablePriority : nsISupports
 {
     const unsigned short PRIORITY_NORMAL = 0;
     const unsigned short PRIORITY_INPUT = 1;
     const unsigned short PRIORITY_HIGH = 2;
     readonly attribute unsigned long priority;
 };
--- a/xpcom/threads/nsIThreadManager.idl
+++ b/xpcom/threads/nsIThreadManager.idl
@@ -88,17 +88,17 @@ interface nsIThreadManager : nsISupports
   /**
    * This queues a runnable to the main thread. It's a shortcut for JS callers
    * to be used instead of
    *   .mainThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
    * or
    *   .currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
    * C++ callers should instead use NS_DispatchToMainThread.
    */
-  void dispatchToMainThread(in nsIRunnable event);
+  void dispatchToMainThread(in nsIRunnable event, [optional] in uint32_t priority);
 
   /**
    * This queues a runnable to the main thread's idle queue.
    *
    * @param event
    *   The event to dispatch.
    * @param timeout
    *   The time in milliseconds until this event should be moved from the idle
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -390,26 +390,30 @@ nsThreadManager::GetSystemGroupEventTarg
 uint32_t
 nsThreadManager::GetHighestNumberOfThreads()
 {
   OffTheBooksMutexAutoLock lock(mLock);
   return mHighestNumberOfThreads;
 }
 
 NS_IMETHODIMP
-nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent)
+nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent, uint32_t aPriority)
 {
   // Note: C++ callers should instead use NS_DispatchToMainThread.
   MOZ_ASSERT(NS_IsMainThread());
 
   // Keep this functioning during Shutdown
   if (NS_WARN_IF(!mMainThread)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
-
+  if (aPriority != nsIRunnablePriority::PRIORITY_NORMAL) {
+    nsCOMPtr<nsIRunnable> event(aEvent);
+    return mMainThread->DispatchFromScript(
+             new PrioritizableRunnable(event.forget(), aPriority), 0);
+  }
   return mMainThread->DispatchFromScript(aEvent, 0);
 }
 
 void
 nsThreadManager::EnableMainThreadEventPrioritization()
 {
   static bool sIsInitialized = false;
   if (sIsInitialized) {
--- a/xpcom/threads/nsThreadUtils.cpp
+++ b/xpcom/threads/nsThreadUtils.cpp
@@ -83,16 +83,57 @@ namespace detail {
 already_AddRefed<nsITimer> CreateTimer()
 {
   nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID);
   return timer.forget();
 }
 } // namespace detail
 } // namespace mozilla
 
+NS_IMPL_ISUPPORTS_INHERITED(PrioritizableRunnable, Runnable,
+                            nsIRunnablePriority)
+
+PrioritizableRunnable::PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+                                             uint32_t aPriority)
+ // Real runnable name is managed by overridding the GetName function.
+ : Runnable("PrioritizableRunnable")
+ , mRunnable(Move(aRunnable))
+ , mPriority(aPriority)
+{
+#if DEBUG
+  nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable);
+  MOZ_ASSERT(!runnablePrio);
+#endif
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::GetName(nsACString& aName)
+{
+  // Try to get a name from the underlying runnable.
+  nsCOMPtr<nsINamed> named = do_QueryInterface(mRunnable);
+  if (named) {
+    named->GetName(aName);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::Run()
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  return mRunnable->Run();
+}
+
+NS_IMETHODIMP
+PrioritizableRunnable::GetPriority(uint32_t* aPriority)
+{
+  *aPriority = mPriority;
+  return NS_OK;
+}
+
 #endif  // XPCOM_GLUE_AVOID_NSPR
 
 //-----------------------------------------------------------------------------
 
 nsresult
 NS_NewNamedThread(const nsACString& aName,
                   nsIThread** aResult,
                   nsIRunnable* aEvent,
--- a/xpcom/threads/nsThreadUtils.h
+++ b/xpcom/threads/nsThreadUtils.h
@@ -481,16 +481,36 @@ public:
 protected:
   virtual ~IdleRunnable() {}
 private:
   IdleRunnable(const IdleRunnable&) = delete;
   IdleRunnable& operator=(const IdleRunnable&) = delete;
   IdleRunnable& operator=(const IdleRunnable&&) = delete;
 };
 
+// This class is designed to be a wrapper of a real runnable to support event
+// prioritizable.
+class PrioritizableRunnable : public Runnable, public nsIRunnablePriority
+{
+public:
+  PrioritizableRunnable(already_AddRefed<nsIRunnable>&& aRunnable,
+                        uint32_t aPriority);
+
+  NS_IMETHOD GetName(nsACString& aName) override;
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIRUNNABLE
+  NS_DECL_NSIRUNNABLEPRIORITY
+
+protected:
+  virtual ~PrioritizableRunnable() {};
+  nsCOMPtr<nsIRunnable> mRunnable;
+  uint32_t mPriority;
+};
+
 namespace detail {
 
 // An event that can be used to call a C++11 functions or function objects,
 // including lambdas. The function must have no required arguments, and must
 // return void.
 template<typename StoredFunction>
 class RunnableFunction : public Runnable
 {