Bug 1351148 Part2: Add a priority queue for input events. r=smaug.
authorStone Shih <sshih@mozilla.com>
Tue, 21 Mar 2017 15:44:12 +0800
changeset 374472 734914d289e0b0a58061d6fb85589c04645c22ee
parent 374471 50bfc06f2b2c71dccac9aa3925cb8c1fbbd8e32a
child 374473 91380fc40b7daf1fe5460d48c3448e244a5e9dfc
push id32327
push userarchaeopteryx@coole-files.de
push dateSun, 13 Aug 2017 23:30:48 +0000
treeherdermozilla-central@3bfcbdf5c6c3 [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 Part2: Add a priority queue for input events. r=smaug. MozReview-Commit-ID: 5ud1Ex9UNVo
dom/events/EventStateManager.cpp
dom/events/EventStateManager.h
dom/ipc/ContentBridgeParent.cpp
dom/ipc/ContentBridgeParent.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PBrowser.ipdl
dom/ipc/TabChild.cpp
dom/ipc/TabParent.cpp
dom/ipc/TabParent.h
dom/ipc/nsIContentChild.cpp
dom/ipc/nsIContentParent.cpp
dom/ipc/nsIContentParent.h
ipc/chromium/src/chrome/common/ipc_message.cc
ipc/chromium/src/chrome/common/ipc_message.h
ipc/glue/MessageChannel.cpp
ipc/ipdl/ipdl/ast.py
ipc/ipdl/ipdl/lower.py
ipc/ipdl/ipdl/parser.py
ipc/ipdl/test/cxx/PTestPriority.ipdl
layout/base/nsRefreshDriver.cpp
layout/base/nsRefreshDriver.h
modules/libpref/init/all.js
xpcom/threads/InputEventStatistics.cpp
xpcom/threads/InputEventStatistics.h
xpcom/threads/LazyIdleThread.cpp
xpcom/threads/SchedulerGroup.cpp
xpcom/threads/SchedulerGroup.h
xpcom/threads/moz.build
xpcom/threads/nsIRunnable.idl
xpcom/threads/nsIThread.idl
xpcom/threads/nsThread.cpp
xpcom/threads/nsThread.h
xpcom/threads/nsThreadManager.cpp
xpcom/threads/nsThreadManager.h
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2861,23 +2861,42 @@ NodeAllowsClickThrough(nsINode* aNode)
     aNode = nsContentUtils::GetCrossDocParentNode(aNode);
   }
   return true;
 }
 #endif
 
 void
 EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
+                                           nsIFrame* aTargetFrame,
                                            nsEventStatus& aStatus)
 {
   if (aStatus == nsEventStatus_eConsumeNoDefault) {
     return;
   }
 
   if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) {
+    if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) {
+      RefPtr<TabParent> remote = aTargetFrame ?
+        TabParent::GetFrom(aTargetFrame->GetContent()) : nullptr;
+      if (remote && !remote->IsReadyToHandleInputEvents()) {
+        // We need to dispatch the event to the browser element again if we were
+        // waiting for the key reply but the event wasn't sent to the content
+        // process due to the remote browser wasn't ready.
+        WidgetKeyboardEvent keyEvent(*aKeyboardEvent);
+        aKeyboardEvent->MarkAsHandledInRemoteProcess();
+        EventDispatcher::Dispatch(remote->GetOwnerElement(), mPresContext,
+                                  &keyEvent);
+        if (keyEvent.DefaultPrevented()) {
+          aKeyboardEvent->PreventDefault(!keyEvent.DefaultPreventedByContent());
+          aStatus = nsEventStatus_eConsumeNoDefault;
+          return;
+        }
+      }
+    }
     // The widget expects a reply for every keyboard event. If the event wasn't
     // dispatched to a content process (non-e10s or no content process
     // running), we need to short-circuit here. Otherwise, we need to wait for
     // the content process to handle the event.
     aKeyboardEvent->mWidget->PostHandleKeyEvent(aKeyboardEvent);
     if (aKeyboardEvent->DefaultPrevented()) {
       aStatus = nsEventStatus_eConsumeNoDefault;
       return;
@@ -3519,17 +3538,17 @@ EventStateManager::PostHandleEvent(nsPre
     break;
 
   case eKeyUp:
     break;
 
   case eKeyPress:
     {
       WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
-      PostHandleKeyboardEvent(keyEvent, *aStatus);
+      PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
     }
     break;
 
   case eMouseEnterIntoWidget:
     if (mCurrentTarget) {
       nsCOMPtr<nsIContent> targetContent;
       mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
       SetContentState(targetContent, NS_EVENT_STATE_HOVER);
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -104,17 +104,17 @@ public:
    * DOM and frame processing.
    */
   nsresult PostHandleEvent(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIFrame* aTargetFrame,
                            nsEventStatus* aStatus);
 
   void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
-                               nsEventStatus& aStatus);
+                               nsIFrame* aTargetFrame, nsEventStatus& aStatus);
 
   /**
    * DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll
    * event and eLegacyMousePixelScroll event for compatibility with old Gecko.
    */
   void DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame,
                                        WidgetWheelEvent* aEvent,
                                        nsEventStatus* aStatus);
--- a/dom/ipc/ContentBridgeParent.cpp
+++ b/dom/ipc/ContentBridgeParent.cpp
@@ -166,16 +166,34 @@ ContentBridgeParent::AllocPBrowserParent
 }
 
 bool
 ContentBridgeParent::DeallocPBrowserParent(PBrowserParent* aParent)
 {
   return nsIContentParent::DeallocPBrowserParent(aParent);
 }
 
+mozilla::ipc::IPCResult
+ContentBridgeParent::RecvPBrowserConstructor(PBrowserParent* actor,
+                                             const TabId& tabId,
+                                             const TabId& sameTabGroupAs,
+                                             const IPCTabContext& context,
+                                             const uint32_t& chromeFlags,
+                                             const ContentParentId& cpId,
+                                             const bool& isForBrowser)
+{
+  return nsIContentParent::RecvPBrowserConstructor(actor,
+                                                   tabId,
+                                                   sameTabGroupAs,
+                                                   context,
+                                                   chromeFlags,
+                                                   cpId,
+                                                   isForBrowser);
+}
+
 void
 ContentBridgeParent::NotifyTabDestroyed()
 {
   int32_t numLiveTabs = ManagedPBrowserParent().Count();
   if (numLiveTabs == 1) {
     MessageLoop::current()->PostTask(NewRunnableMethod(
       "dom::ContentBridgeParent::Close", this, &ContentBridgeParent::Close));
   }
--- a/dom/ipc/ContentBridgeParent.h
+++ b/dom/ipc/ContentBridgeParent.h
@@ -133,16 +133,25 @@ protected:
                       const TabId& aSameTabGroupAs,
                       const IPCTabContext &aContext,
                       const uint32_t& aChromeFlags,
                       const ContentParentId& aCpID,
                       const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserParent(PBrowserParent*) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvPBrowserConstructor(PBrowserParent* actor,
+                          const TabId& tabId,
+                          const TabId& sameTabGroupAs,
+                          const IPCTabContext& context,
+                          const uint32_t& chromeFlags,
+                          const ContentParentId& cpId,
+                          const bool& isForBrowser) override;
+
   virtual PIPCBlobInputStreamParent*
   SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
                                      const nsID& aID,
                                      const uint64_t& aSize) override;
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
                                  const uint64_t& aSize) override;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1171,16 +1171,19 @@ ContentChild::InitXPCOM(const XPCOMInitD
   nsLayoutStylesheetCache::SetUserContentCSSURL(ucsURL);
 
   // This will register cross-process observer.
   mozilla::dom::time::InitializeDateCacheCleaner();
 
   GfxInfoBase::SetFeatureStatus(aXPCOMInit.gfxFeatureStatus());
 
   DataStorage::SetCachedStorageEntries(aXPCOMInit.dataStorage());
+
+  // Enable input event prioritization.
+  nsThreadManager::get().EnableMainThreadEventPrioritization();
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvRequestMemoryReport(const uint32_t& aGeneration,
                                       const bool& aAnonymize,
                                       const bool& aMinimizeMemoryUsage,
                                       const MaybeFileDesc& aDMDFile)
 {
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2891,16 +2891,34 @@ ContentParent::AllocPBrowserParent(const
 }
 
 bool
 ContentParent::DeallocPBrowserParent(PBrowserParent* frame)
 {
   return nsIContentParent::DeallocPBrowserParent(frame);
 }
 
+mozilla::ipc::IPCResult
+ContentParent::RecvPBrowserConstructor(PBrowserParent* actor,
+                                       const TabId& tabId,
+                                       const TabId& sameTabGroupAs,
+                                       const IPCTabContext& context,
+                                       const uint32_t& chromeFlags,
+                                       const ContentParentId& cpId,
+                                       const bool& isForBrowser)
+{
+  return nsIContentParent::RecvPBrowserConstructor(actor,
+                                                   tabId,
+                                                   sameTabGroupAs,
+                                                   context,
+                                                   chromeFlags,
+                                                   cpId,
+                                                   isForBrowser);
+}
+
 PIPCBlobInputStreamParent*
 ContentParent::AllocPIPCBlobInputStreamParent(const nsID& aID,
                                               const uint64_t& aSize)
 {
   return nsIContentParent::AllocPIPCBlobInputStreamParent(aID, aSize);
 }
 
 bool
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -818,16 +818,25 @@ private:
                                               const TabId& aSameTabGroupAs,
                                               const IPCTabContext& aContext,
                                               const uint32_t& aChromeFlags,
                                               const ContentParentId& aCpId,
                                               const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserParent(PBrowserParent* frame) override;
 
+  virtual mozilla::ipc::IPCResult
+  RecvPBrowserConstructor(PBrowserParent* actor,
+                          const TabId& tabId,
+                          const TabId& sameTabGroupAs,
+                          const IPCTabContext& context,
+                          const uint32_t& chromeFlags,
+                          const ContentParentId& cpId,
+                          const bool& isForBrowser) override;
+
   virtual PIPCBlobInputStreamParent*
   SendPIPCBlobInputStreamConstructor(PIPCBlobInputStreamParent* aActor,
                                      const nsID& aID,
                                      const uint64_t& aSize) override;
 
   virtual PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID,
                                  const uint64_t& aSize) override;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -539,16 +539,22 @@ parent:
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
      */
     async RemotePaintIsReady();
 
     /**
+     * Child informs the parent that the content is ready to handle input
+     * events. This is sent when the TabChild is created.
+     */
+    async RemoteIsReadyToHandleInputEvents();
+
+    /**
      * Child informs the parent that the layer tree is already available.
      */
     async ForcePaintNoOp(uint64_t aLayerObserverEpoch);
 
     /**
      * Sent by the child to the parent to inform it that an update to the
      * dimensions has been requested, likely through win.moveTo or resizeTo
      */
@@ -641,39 +647,49 @@ child:
                      int32_t aClickCount,
                      int32_t aModifiers,
                      bool aIgnoreRootScrollFrame);
 
     /**
      * When two consecutive mouse move events would be added to the message queue,
      * they are 'compressed' by dumping the oldest one.
      */
-    async RealMouseMoveEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId) compress;
+    prio(input) async RealMouseMoveEvent(WidgetMouseEvent event,
+                                         ScrollableLayerGuid aGuid,
+                                         uint64_t aInputBlockId) compress;
     /**
      * Mouse move events with |reason == eSynthesized| are sent via a separate
      * message because they do not generate DOM 'mousemove' events, and the
      * 'compress' attribute on RealMouseMoveEvent() could result in a
      * |reason == eReal| event being dropped in favour of an |eSynthesized|
      * event, and thus a DOM 'mousemove' event to be lost.
      */
-    async SynthMouseMoveEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealMouseButtonEvent(WidgetMouseEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealKeyEvent(WidgetKeyboardEvent event);
-    async MouseWheelEvent(WidgetWheelEvent event, ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealTouchEvent(WidgetTouchEvent aEvent,
-                         ScrollableLayerGuid aGuid,
-                         uint64_t aInputBlockId,
-                         nsEventStatus aApzResponse);
-    async HandleTap(TapType aType, LayoutDevicePoint point, Modifiers aModifiers,
-                    ScrollableLayerGuid aGuid, uint64_t aInputBlockId);
-    async RealTouchMoveEvent(WidgetTouchEvent aEvent,
-                             ScrollableLayerGuid aGuid,
-                             uint64_t aInputBlockId,
-                             nsEventStatus aApzResponse);
-    async RealDragEvent(WidgetDragEvent aEvent, uint32_t aDragAction, uint32_t aDropEffect);
+    prio(input) async SynthMouseMoveEvent(WidgetMouseEvent event,
+                                          ScrollableLayerGuid aGuid,
+                                          uint64_t aInputBlockId);
+    prio(input) async RealMouseButtonEvent(WidgetMouseEvent event,
+                                           ScrollableLayerGuid aGuid,
+                                           uint64_t aInputBlockId);
+    prio(input) async RealKeyEvent(WidgetKeyboardEvent event);
+    prio(input) async MouseWheelEvent(WidgetWheelEvent event,
+                                      ScrollableLayerGuid aGuid,
+                                      uint64_t aInputBlockId);
+    prio(input) async RealTouchEvent(WidgetTouchEvent aEvent,
+                                     ScrollableLayerGuid aGuid,
+                                     uint64_t aInputBlockId,
+                                     nsEventStatus aApzResponse);
+    prio(input) async HandleTap(TapType aType, LayoutDevicePoint point,
+                                Modifiers aModifiers, ScrollableLayerGuid aGuid,
+                                uint64_t aInputBlockId);
+    prio(input) async RealTouchMoveEvent(WidgetTouchEvent aEvent,
+                                         ScrollableLayerGuid aGuid,
+                                         uint64_t aInputBlockId,
+                                         nsEventStatus aApzResponse);
+    prio(input) async RealDragEvent(WidgetDragEvent aEvent,
+                                    uint32_t aDragAction, uint32_t aDropEffect);
     async PluginEvent(WidgetPluginEvent aEvent);
 
     /**
      * @see nsIDOMWindowUtils sendKeyEvent.
      */
     async KeyEvent(nsString aType,
                    int32_t aKeyCode,
                    int32_t aCharCode,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -85,16 +85,17 @@
 #include "nsIWebBrowserFocus.h"
 #include "nsIWebBrowserSetup.h"
 #include "nsIWebProgress.h"
 #include "nsIXULRuntime.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsLayoutUtils.h"
 #include "nsPrintfCString.h"
+#include "nsThreadManager.h"
 #include "nsThreadUtils.h"
 #include "nsViewManager.h"
 #include "nsWeakReference.h"
 #include "nsWindowWatcher.h"
 #include "PermissionMessageUtils.h"
 #include "PuppetWidget.h"
 #include "StructuredCloneData.h"
 #include "nsViewportInfo.h"
@@ -316,17 +317,29 @@ private:
         MOZ_ASSERT(!mTabChild);
     }
 
     NS_IMETHOD
     Run() override
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(mTabChild);
-
+        // When enabling input event prioritization, we reserve limited time
+        // to process input events. We may handle the rest in the next frame
+        // when running out of time of the current frame. In that case, input
+        // events may be dispatched after ActorDestroy. Delay
+        // DelayedDeleteRunnable to avoid it to happen.
+        nsThread* thread = nsThreadManager::get().GetCurrentThread();
+        MOZ_ASSERT(thread);
+        bool eventPrioritizationEnabled = false;
+        thread->IsEventPrioritizationEnabled(&eventPrioritizationEnabled);
+        if (eventPrioritizationEnabled && thread->HasPendingInputEvents()) {
+          MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(this));
+          return NS_OK;
+        }
         // Check in case ActorDestroy was called after RecvDestroy message.
         if (mTabChild->IPCOpen()) {
             Unused << PBrowserChild::Send__delete__(mTabChild);
         }
 
         mTabChild = nullptr;
         return NS_OK;
     }
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -167,16 +167,17 @@ TabParent::TabParent(nsIContentParent* a
   , mHasContentOpener(false)
 #ifdef DEBUG
   , mActiveSupressDisplayportCount(0)
 #endif
   , mLayerTreeEpoch(0)
   , mPreserveLayers(false)
   , mHasPresented(false)
   , mHasBeforeUnload(false)
+  , mIsReadyToHandleInputEvents(false)
 {
   MOZ_ASSERT(aManager);
 }
 
 TabParent::~TabParent()
 {
 }
 
@@ -1076,27 +1077,27 @@ TabParent::SendMouseEvent(const nsAStrin
 
 void
 TabParent::SendKeyEvent(const nsAString& aType,
                         int32_t aKeyCode,
                         int32_t aCharCode,
                         int32_t aModifiers,
                         bool aPreventDefault)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   Unused << PBrowserParent::SendKeyEvent(nsString(aType), aKeyCode, aCharCode,
                                          aModifiers, aPreventDefault);
 }
 
 void
 TabParent::SendRealMouseEvent(WidgetMouseEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (widget) {
     // When we mouseenter the tab, the tab's cursor should
     // become the current cursor.  When we mouseexit, we stop.
@@ -1146,17 +1147,17 @@ TabParent::GetLayoutDeviceToCSSScale()
     ? (float)ctx->AppUnitsPerDevPixel() / nsPresContext::AppUnitsPerCSSPixel()
     : 0.0f);
 }
 
 void
 TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction,
                              uint32_t aDropEffect)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
   DebugOnly<bool> ret =
     PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
 }
@@ -1165,17 +1166,17 @@ LayoutDevicePoint
 TabParent::AdjustTapToChildWidget(const LayoutDevicePoint& aPoint)
 {
   return aPoint + LayoutDevicePoint(GetChildProcessOffset());
 }
 
 void
 TabParent::SendMouseWheelEvent(WidgetWheelEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
 
   ScrollableLayerGuid guid;
   uint64_t blockId;
   ApzAwareEventRoutingToChild(&guid, &blockId, nullptr);
   aEvent.mRefPoint += GetChildProcessOffset();
   DebugOnly<bool> ret =
@@ -1444,17 +1445,17 @@ TabParent::RecvClearNativeTouchSequence(
     widget->ClearNativeTouchSequence(responder.GetObserver());
   }
   return IPC_OK();
 }
 
 void
 TabParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   aEvent.mRefPoint += GetChildProcessOffset();
 
   if (aEvent.mMessage == eKeyPress) {
     // XXX Should we do this only when input context indicates an editor having
     //     focus and the key event won't cause inputting text?
     aEvent.InitAllEditCommands();
@@ -1465,17 +1466,17 @@ TabParent::SendRealKeyEvent(WidgetKeyboa
   DebugOnly<bool> ret = PBrowserParent::SendRealKeyEvent(aEvent);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealKeyEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
 }
 
 void
 TabParent::SendRealTouchEvent(WidgetTouchEvent& aEvent)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
 
   // PresShell::HandleEventInternal adds touches on touch end/cancel.  This
   // confuses remote content and the panning and zooming logic into thinking
   // that the added touches are part of the touchend/cancel, when actually
   // they're not.
   if (aEvent.mMessage == eTouchEnd || aEvent.mMessage == eTouchCancel) {
@@ -1526,17 +1527,17 @@ TabParent::SendPluginEvent(WidgetPluginE
 
 bool
 TabParent::SendHandleTap(TapType aType,
                          const LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId)
 {
-  if (mIsDestroyed) {
+  if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return false;
   }
   if ((aType == TapType::eSingleTap || aType == TapType::eSecondTap) &&
       GetRenderFrame()) {
     GetRenderFrame()->TakeFocusForClickFromTap();
   }
   LayoutDeviceIntPoint offset = GetChildProcessOffset();
   return PBrowserParent::SendHandleTap(aType, aPoint + offset, aModifiers,
@@ -2937,16 +2938,28 @@ TabParent::RecvRemotePaintIsReady()
   event->InitEvent(NS_LITERAL_STRING("MozAfterRemotePaint"), false, false);
   event->SetTrusted(true);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
   bool dummy;
   mFrameElement->DispatchEvent(event, &dummy);
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+TabParent::RecvRemoteIsReadyToHandleInputEvents()
+{
+  // When enabling input event prioritization, input events may preempt other
+  // normal priority IPC messages. To prevent the input events preempt
+  // PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to
+  // notify the parent that TabChild is created and ready to handle input
+  // events.
+  SetReadyToHandleInputEvents();
+  return IPC_OK();
+}
+
 mozilla::plugins::PPluginWidgetParent*
 TabParent::AllocPPluginWidgetParent()
 {
 #ifdef XP_WIN
   return new mozilla::plugins::PluginWidgetParent();
 #else
   MOZ_ASSERT_UNREACHABLE();
   return nullptr;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -598,16 +598,19 @@ public:
                           uint64_t* aLayersId);
 
   mozilla::ipc::IPCResult RecvEnsureLayersConnected(CompositorOptions* aCompositorOptions) override;
 
   // LiveResizeListener implementation
   void LiveResizeStarted() override;
   void LiveResizeStopped() override;
 
+  void SetReadyToHandleInputEvents() { mIsReadyToHandleInputEvents = true; }
+  bool IsReadyToHandleInputEvents() { return mIsReadyToHandleInputEvents; }
+
 protected:
   bool ReceiveMessage(const nsString& aMessage,
                       bool aSync,
                       ipc::StructuredCloneData* aData,
                       mozilla::jsipc::CpowHolder* aCpows,
                       nsIPrincipal* aPrincipal,
                       nsTArray<ipc::StructuredCloneData>* aJSONRetVal = nullptr);
 
@@ -623,16 +626,18 @@ protected:
   nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow;
 
   virtual PRenderFrameParent* AllocPRenderFrameParent() override;
 
   virtual bool DeallocPRenderFrameParent(PRenderFrameParent* aFrame) override;
 
   virtual mozilla::ipc::IPCResult RecvRemotePaintIsReady() override;
 
+  virtual mozilla::ipc::IPCResult RecvRemoteIsReadyToHandleInputEvents() override;
+
   virtual mozilla::ipc::IPCResult RecvForcePaintNoOp(const uint64_t& aLayerObserverEpoch) override;
 
   virtual mozilla::ipc::IPCResult RecvSetDimensions(const uint32_t& aFlags,
                                                     const int32_t& aX, const int32_t& aY,
                                                     const int32_t& aCx, const int32_t& aCy) override;
 
   virtual mozilla::ipc::IPCResult RecvGetTabCount(uint32_t* aValue) override;
 
@@ -773,16 +778,19 @@ private:
   // True if this TabParent has had its layer tree sent to the compositor
   // at least once.
   bool mHasPresented;
 
   // True if at least one window hosted in the TabChild has added a
   // beforeunload event listener.
   bool mHasBeforeUnload;
 
+  // True when the remote browser is created and ready to handle input events.
+  bool mIsReadyToHandleInputEvents;
+
 public:
   static TabParent* GetTabParentFromLayersId(uint64_t aLayersId);
 };
 
 struct MOZ_STACK_CLASS TabParent::AutoUseNewTab final
 {
 public:
   AutoUseNewTab(TabParent* aNewTab, nsCString* aURLToLoad)
--- a/dom/ipc/nsIContentChild.cpp
+++ b/dom/ipc/nsIContentChild.cpp
@@ -97,17 +97,18 @@ nsIContentChild::RecvPBrowserConstructor
   if (NS_WARN_IF(NS_FAILED(tabChild->Init()))) {
     return IPC_FAIL(tabChild, "TabChild::Init failed");
   }
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->NotifyObservers(static_cast<nsITabChild*>(tabChild), "tab-child-created", nullptr);
   }
-
+  // Notify parent that we are ready to handle input events.
+  tabChild->SendRemoteIsReadyToHandleInputEvents();
   return IPC_OK();
 }
 
 PIPCBlobInputStreamChild*
 nsIContentChild::AllocPIPCBlobInputStreamChild(const nsID& aID,
                                                const uint64_t& aSize)
 {
   // IPCBlobInputStreamChild is refcounted. Here it's created and in
--- a/dom/ipc/nsIContentParent.cpp
+++ b/dom/ipc/nsIContentParent.cpp
@@ -201,16 +201,35 @@ nsIContentParent::AllocPBrowserParent(co
 bool
 nsIContentParent::DeallocPBrowserParent(PBrowserParent* aFrame)
 {
   TabParent* parent = TabParent::GetFrom(aFrame);
   NS_RELEASE(parent);
   return true;
 }
 
+mozilla::ipc::IPCResult
+nsIContentParent::RecvPBrowserConstructor(PBrowserParent* actor,
+                                          const TabId& tabId,
+                                          const TabId& sameTabGroupAs,
+                                          const IPCTabContext& context,
+                                          const uint32_t& chromeFlags,
+                                          const ContentParentId& cpId,
+                                          const bool& isForBrowser)
+{
+  TabParent* parent = TabParent::GetFrom(actor);
+  // When enabling input event prioritization, input events may preempt other
+  // normal priority IPC messages. To prevent the input events preempt
+  // PBrowserConstructor, we use an IPC 'RemoteIsReadyToHandleInputEvents' to
+  // notify parent that TabChild is created. In this case, PBrowser is initiated
+  // from content so that we can set TabParent as ready to handle input events.
+  parent->SetReadyToHandleInputEvents();
+  return IPC_OK();
+}
+
 PIPCBlobInputStreamParent*
 nsIContentParent::AllocPIPCBlobInputStreamParent(const nsID& aID,
                                                  const uint64_t& aSize)
 {
   MOZ_CRASH("PIPCBlobInputStreamParent actors should be manually constructed!");
   return nullptr;
 }
 
--- a/dom/ipc/nsIContentParent.h
+++ b/dom/ipc/nsIContentParent.h
@@ -112,16 +112,25 @@ protected: // IPDL methods
   virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
                                               const TabId& aSameTabGroupsAs,
                                               const IPCTabContext& aContext,
                                               const uint32_t& aChromeFlags,
                                               const ContentParentId& aCpId,
                                               const bool& aIsForBrowser);
   virtual bool DeallocPBrowserParent(PBrowserParent* frame);
 
+  virtual mozilla::ipc::IPCResult
+  RecvPBrowserConstructor(PBrowserParent* actor,
+                          const TabId& tabId,
+                          const TabId& sameTabGroupAs,
+                          const IPCTabContext& context,
+                          const uint32_t& chromeFlags,
+                          const ContentParentId& cpId,
+                          const bool& isForBrowser);
+
   virtual mozilla::ipc::PIPCBlobInputStreamParent*
   AllocPIPCBlobInputStreamParent(const nsID& aID, const uint64_t& aSize);
 
   virtual bool
   DeallocPIPCBlobInputStreamParent(mozilla::ipc::PIPCBlobInputStreamParent* aActor);
 
   virtual mozilla::ipc::PFileDescriptorSetParent*
   AllocPFileDescriptorSetParent(const mozilla::ipc::FileDescriptor& aFD);
--- a/ipc/chromium/src/chrome/common/ipc_message.cc
+++ b/ipc/chromium/src/chrome/common/ipc_message.cc
@@ -62,18 +62,17 @@ Message::Message(int32_t routing_id,
                  MessageCompression compression,
                  const char* const aName,
                  bool recordWriteLatency)
     : Pickle(MSG_HEADER_SZ, segment_capacity) {
   MOZ_COUNT_CTOR(IPC::Message);
   header()->routing = routing_id;
   header()->type = type;
   header()->flags = nestedLevel;
-  if (priority == HIGH_PRIORITY)
-    header()->flags |= PRIO_BIT;
+  set_priority(priority);
   if (compression == COMPRESSION_ENABLED)
     header()->flags |= COMPRESS_BIT;
   else if (compression == COMPRESSION_ALL)
     header()->flags |= COMPRESSALL_BIT;
 #if defined(OS_POSIX)
   header()->num_fds = 0;
 #endif
   header()->interrupt_remote_stack_depth_guess = static_cast<uint32_t>(-1);
--- a/ipc/chromium/src/chrome/common/ipc_message.h
+++ b/ipc/chromium/src/chrome/common/ipc_message.h
@@ -41,18 +41,19 @@ class Message : public Pickle {
 
   enum NestedLevel {
     NOT_NESTED = 1,
     NESTED_INSIDE_SYNC = 2,
     NESTED_INSIDE_CPOW = 3
   };
 
   enum PriorityValue {
-    NORMAL_PRIORITY,
-    HIGH_PRIORITY,
+    NORMAL_PRIORITY = 0,
+    INPUT_PRIORITY = 1,
+    HIGH_PRIORITY = 2,
   };
 
   enum MessageCompression {
     COMPRESSION_NONE,
     COMPRESSION_ENABLED,
     COMPRESSION_ALL
   };
 
@@ -86,27 +87,22 @@ class Message : public Pickle {
   }
 
   void set_nested_level(NestedLevel nestedLevel) {
     DCHECK((nestedLevel & ~NESTED_MASK) == 0);
     header()->flags = (header()->flags & ~NESTED_MASK) | nestedLevel;
   }
 
   PriorityValue priority() const {
-    if (header()->flags & PRIO_BIT) {
-      return HIGH_PRIORITY;
-    }
-    return NORMAL_PRIORITY;
+    return static_cast<PriorityValue>((header()->flags & PRIO_MASK) >> 2);
   }
 
   void set_priority(PriorityValue prio) {
-    header()->flags &= ~PRIO_BIT;
-    if (prio == HIGH_PRIORITY) {
-      header()->flags |= PRIO_BIT;
-    }
+    DCHECK(((prio << 2) & ~PRIO_MASK) == 0);
+    header()->flags = (header()->flags & ~PRIO_MASK) | (prio << 2);
   }
 
   bool is_constructor() const {
     return (header()->flags & CONSTRUCTOR_BIT) != 0;
   }
 
   void set_constructor() {
     header()->flags |= CONSTRUCTOR_BIT;
@@ -310,26 +306,26 @@ class Message : public Pickle {
 
 #if !defined(OS_MACOSX)
  protected:
 #endif
 
   // flags
   enum {
     NESTED_MASK     = 0x0003,
-    PRIO_BIT        = 0x0004,
-    SYNC_BIT        = 0x0008,
-    REPLY_BIT       = 0x0010,
-    REPLY_ERROR_BIT = 0x0020,
-    INTERRUPT_BIT   = 0x0040,
-    COMPRESS_BIT    = 0x0080,
-    COMPRESSALL_BIT = 0x0100,
-    CONSTRUCTOR_BIT = 0x0200,
+    PRIO_MASK       = 0x000C,
+    SYNC_BIT        = 0x0010,
+    REPLY_BIT       = 0x0020,
+    REPLY_ERROR_BIT = 0x0040,
+    INTERRUPT_BIT   = 0x0080,
+    COMPRESS_BIT    = 0x0100,
+    COMPRESSALL_BIT = 0x0200,
+    CONSTRUCTOR_BIT = 0x0400,
 #ifdef MOZ_TASK_TRACER
-    TASKTRACER_BIT  = 0x0400,
+    TASKTRACER_BIT  = 0x0800,
 #endif
   };
 
   struct Header : Pickle::Header {
     int32_t routing;  // ID of the view that this message is destined for
     msgid_t type;   // specifies the user-defined message type
     uint32_t flags;   // specifies control flags for the message
 #if defined(OS_POSIX)
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -1966,18 +1966,30 @@ MessageChannel::MessageTask::Clear()
     mChannel->AssertWorkerThread();
 
     mChannel = nullptr;
 }
 
 NS_IMETHODIMP
 MessageChannel::MessageTask::GetPriority(uint32_t* aPriority)
 {
-  *aPriority = mMessage.priority() == Message::HIGH_PRIORITY ?
-               PRIORITY_HIGH : PRIORITY_NORMAL;
+  switch (mMessage.priority()) {
+  case Message::NORMAL_PRIORITY:
+    *aPriority = PRIORITY_NORMAL;
+    break;
+  case Message::INPUT_PRIORITY:
+    *aPriority = PRIORITY_INPUT;
+    break;
+  case Message::HIGH_PRIORITY:
+    *aPriority = PRIORITY_HIGH;
+    break;
+  default:
+    MOZ_ASSERT(false);
+    break;
+  }
   return NS_OK;
 }
 
 void
 MessageChannel::DispatchMessage(Message &&aMsg)
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
--- a/ipc/ipdl/ipdl/ast.py
+++ b/ipc/ipdl/ipdl/ast.py
@@ -4,17 +4,18 @@
 
 import sys
 
 NOT_NESTED = 1
 INSIDE_SYNC_NESTED = 2
 INSIDE_CPOW_NESTED = 3
 
 NORMAL_PRIORITY = 1
-HIGH_PRIORITY = 2
+INPUT_PRIORITY = 2
+HIGH_PRIORITY = 3
 
 class Visitor:
     def defaultVisit(self, node):
         raise Exception, "INTERNAL ERROR: no visitor for node type `%s'"% (
             node.__class__.__name__)
 
     def visitTranslationUnit(self, tu):
         for cxxInc in tu.cxxIncludes:
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -1720,18 +1720,19 @@ def _generateMessageConstructor(clsname,
     elif nested == ipdl.ast.INSIDE_SYNC_NESTED:
         nestedEnum = 'IPC::Message::NESTED_INSIDE_SYNC'
     else:
         assert nested == ipdl.ast.INSIDE_CPOW_NESTED
         nestedEnum = 'IPC::Message::NESTED_INSIDE_CPOW'
 
     if prio == ipdl.ast.NORMAL_PRIORITY:
         prioEnum = 'IPC::Message::NORMAL_PRIORITY'
+    elif prio == ipdl.ast.INPUT_PRIORITY:
+        prioEnum = 'IPC::Message::INPUT_PRIORITY'
     else:
-        assert prio == ipdl.ast.HIGH_PRIORITY
         prioEnum = 'IPC::Message::HIGH_PRIORITY'
 
     func.addstmt(
         StmtReturn(ExprNew(Type('IPC::Message'),
                            args=[ routingId,
                                   ExprVar(msgid),
                                   ExprLiteral.Int(int(segmentSize)),
                                   ExprVar(nestedEnum),
--- a/ipc/ipdl/ipdl/parser.py
+++ b/ipc/ipdl/ipdl/parser.py
@@ -498,17 +498,18 @@ def p_Nested(p):
     if p[1] not in kinds:
         _error(locFromTok(p, 1), "Expected not, inside_sync, or inside_cpow for nested()")
 
     p[0] = { 'nested': kinds[p[1]] }
 
 def p_Priority(p):
     """Priority : ID"""
     kinds = {'normal': 1,
-             'high': 2}
+             'input': 2,
+             'high': 3}
     if p[1] not in kinds:
         _error(locFromTok(p, 1), "Expected normal or high for prio()")
 
     p[0] = { 'prio': kinds[p[1]] }
 
 def p_SendQualifier(p):
     """SendQualifier : NESTED '(' Nested ')'
                      | PRIO '(' Priority ')'"""
--- a/ipc/ipdl/test/cxx/PTestPriority.ipdl
+++ b/ipc/ipdl/test/cxx/PTestPriority.ipdl
@@ -1,14 +1,17 @@
 namespace mozilla {
 namespace _ipdltest {
 
 sync protocol PTestPriority {
 parent:
-    prio(high) async Msg1();
-    prio(high) sync Msg2();
+    prio(input) async PMsg1();
+    prio(input) sync PMsg2();
+    prio(high) async PMsg3();
+    prio(high) sync PMsg4();
 
 child:
-    prio(high) async Msg3();
+    prio(input) async CMsg1();
+    prio(high) async CMsg2();
 };
 
 } // namespace _ipdltest
 } // namespace mozilla
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -252,16 +252,23 @@ public:
       return aDefault;
     }
 
     idleEnd = idleEnd - TimeDuration::FromMilliseconds(
       nsLayoutUtils::IdlePeriodDeadlineLimit());
     return idleEnd < aDefault ? idleEnd : aDefault;
   }
 
+  Maybe<TimeStamp> GetNextTickHint()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    TimeStamp nextTick = MostRecentRefresh() + GetTimerRate();
+    return nextTick < TimeStamp::Now() ? Nothing() : Some(nextTick);
+  }
+
 protected:
   virtual void StartTimer() = 0;
   virtual void StopTimer() = 0;
   virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
 
   bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
   {
     nsPresContext* pc = aDriver->GetPresContext();
@@ -2399,16 +2406,27 @@ nsRefreshDriver::GetIdleDeadlineHint(Tim
   // sRegularRateTimer, since we consider refresh drivers attached to
   // sThrottledRateTimer to be inactive. This implies that tasks
   // resulting from a tick on the sRegularRateTimer counts as being
   // busy but tasks resulting from a tick on sThrottledRateTimer
   // counts as being idle.
   return sRegularRateTimer->GetIdleDeadlineHint(aDefault);
 }
 
+/* static */ Maybe<TimeStamp>
+nsRefreshDriver::GetNextTickHint()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!sRegularRateTimer) {
+    return Nothing();
+  }
+  return sRegularRateTimer->GetNextTickHint();
+}
+
 void
 nsRefreshDriver::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   StopTimer();
 
   if (mPresContext) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -340,16 +340,22 @@ public:
    * expected deadline. If the next expected deadline is later than
    * the default value, the default value is returned.
    *
    * If we're animating and we have skipped paints a time in the past
    * is returned.
    */
   static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault);
 
+  /**
+   * It returns the expected timestamp of the next tick or nothing if the next
+   * tick is missed.
+   */
+  static mozilla::Maybe<mozilla::TimeStamp> GetNextTickHint();
+
   static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
                                             uint32_t aDelay);
   static void CancelIdleRunnable(nsIRunnable* aRunnable);
 
   bool SkippedPaints() const
   {
     return mSkippedPaints;
   }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3115,16 +3115,36 @@ pref("dom.max_script_run_time", 10);
 pref("dom.global_stop_script", true);
 
 // Time (milliseconds) between throttled idle callbacks.
 pref("dom.idle_period.throttled_length", 10000);
 
 // The amount of idle time (milliseconds) reserved for a long idle period
 pref("idle_queue.long_period", 50);
 
+// Control the event prioritization on content main thread
+#ifdef NIGHTLY_BUILD
+pref("prioritized_input_events.enabled", true);
+#else
+pref("prioritized_input_events.enabled", false);
+#endif
+
+// The maximum and minimum time (milliseconds) we reserve for handling input
+// events in each frame.
+pref("prioritized_input_events.duration.max", 8);
+pref("prioritized_input_events.duration.min", 1);
+
+// The default amount of time (milliseconds) required for handling a input
+// event.
+pref("prioritized_input_events.default_duration_per_event", 1);
+
+// The number of processed input events we use to predict the amount of time
+// required to process the following input events.
+pref("prioritized_input_events.count_for_prediction", 9);
+
 // The minimum amount of time (milliseconds) required for an idle
 // period to be scheduled on the main thread. N.B. that
 // layout.idle_period.time_limit adds padding at the end of the idle
 // period, which makes the point in time that we expect to become busy
 // again be:
 // now + idle_queue.min_period + layout.idle_period.time_limit
 pref("idle_queue.min_period", 3);
 
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/InputEventStatistics.cpp
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "InputEventStatistics.h"
+
+#include "nsRefreshDriver.h"
+
+namespace mozilla {
+
+TimeDuration
+InputEventStatistics::TimeDurationCircularBuffer::GetMean()
+{
+  return mTotal / (int64_t)mSize;
+}
+
+InputEventStatistics::InputEventStatistics()
+  : mEnable(false)
+{
+  MOZ_ASSERT(Preferences::IsServiceAvailable());
+  uint32_t inputDuration =
+    Preferences::GetUint("prioritized_input_events.default_duration_per_event",
+                         sDefaultInputDuration);
+
+  TimeDuration defaultDuration = TimeDuration::FromMilliseconds(inputDuration);
+
+  uint32_t count =
+    Preferences::GetUint("prioritized_input_events.count_for_prediction",
+                         sInputCountForPrediction);
+
+  mLastInputDurations =
+    MakeUnique<TimeDurationCircularBuffer>(count, defaultDuration);
+
+  uint32_t maxDuration =
+    Preferences::GetUint("prioritized_input_events.duration.max",
+                         sMaxReservedTimeForHandlingInput);
+
+  uint32_t minDuration =
+    Preferences::GetUint("prioritized_input_events.duration.min",
+                         sMinReservedTimeForHandlingInput);
+
+  mMaxInputDuration = TimeDuration::FromMilliseconds(maxDuration);
+  mMinInputDuration = TimeDuration::FromMilliseconds(minDuration);
+}
+
+TimeStamp
+InputEventStatistics::GetInputHandlingStartTime(uint32_t aInputCount)
+{
+  MOZ_ASSERT(mEnable);
+  Maybe<TimeStamp> nextTickHint = nsRefreshDriver::GetNextTickHint();
+
+  if (nextTickHint.isNothing()) {
+    // Return a past time to process input events immediately.
+    return TimeStamp::Now() - TimeDuration::FromMilliseconds(1);
+  }
+  TimeDuration inputCost = mLastInputDurations->GetMean() * aInputCount;
+  inputCost = inputCost > mMaxInputDuration
+              ? mMaxInputDuration
+              : inputCost < mMinInputDuration
+              ? mMinInputDuration
+              : inputCost;
+
+  return nextTickHint.value() - inputCost;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/threads/InputEventStatistics.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#if !defined(InputEventStatistics_h_)
+#define InputEventStatistics_h_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+class InputEventStatistics
+{
+  // The default amount of time (milliseconds) required for handling a input
+  // event.
+  static const uint16_t sDefaultInputDuration = 1;
+
+  // The number of processed input events we use to predict the amount of time
+  // required to process the following input events.
+  static const uint16_t sInputCountForPrediction = 9;
+
+  // The default maximum and minimum time (milliseconds) we reserve for handling
+  // input events in each frame.
+  static const uint16_t sMaxReservedTimeForHandlingInput = 8;
+  static const uint16_t sMinReservedTimeForHandlingInput = 1;
+
+  class TimeDurationCircularBuffer
+  {
+    int16_t mSize;
+    int16_t mCurrentIndex;
+    nsTArray<TimeDuration> mBuffer;
+    TimeDuration mTotal;
+
+  public:
+    TimeDurationCircularBuffer(uint32_t aSize, TimeDuration& aDefaultValue)
+      : mSize(aSize)
+      , mCurrentIndex(0)
+    {
+      mSize = mSize == 0 ? sInputCountForPrediction : mSize;
+      for (int16_t index = 0; index < mSize; ++index) {
+        mBuffer.AppendElement(aDefaultValue);
+        mTotal += aDefaultValue;
+      }
+    }
+
+    void Insert(TimeDuration& aDuration)
+    {
+      mTotal += (aDuration - mBuffer[mCurrentIndex]);
+      mBuffer[mCurrentIndex++] = aDuration;
+      if (mCurrentIndex == mSize) {
+        mCurrentIndex = 0;
+      }
+    }
+
+    TimeDuration GetMean();
+  };
+
+  UniquePtr<TimeDurationCircularBuffer> mLastInputDurations;
+  TimeDuration mMaxInputDuration;
+  TimeDuration mMinInputDuration;
+  bool mEnable;
+
+  InputEventStatistics();
+  ~InputEventStatistics()
+  {
+  }
+
+public:
+  static InputEventStatistics& Get()
+  {
+    static InputEventStatistics sInstance;
+    return sInstance;
+  }
+
+  void UpdateInputDuration(TimeDuration aDuration)
+  {
+    if (!mEnable) {
+      return;
+    }
+    mLastInputDurations->Insert(aDuration);
+  }
+
+  TimeStamp GetInputHandlingStartTime(uint32_t aInputCount);
+
+  void SetEnable(bool aEnable)
+  {
+    mEnable = aEnable;
+  }
+};
+
+class MOZ_RAII AutoTimeDurationHelper final
+{
+public:
+  AutoTimeDurationHelper()
+  {
+    mStartTime = TimeStamp::Now();
+  }
+
+  ~AutoTimeDurationHelper()
+  {
+    InputEventStatistics::Get().UpdateInputDuration(TimeStamp::Now() - mStartTime);
+  }
+
+private:
+  TimeStamp mStartTime;
+};
+
+} // namespace mozilla
+
+#endif // InputEventStatistics_h_
--- a/xpcom/threads/LazyIdleThread.cpp
+++ b/xpcom/threads/LazyIdleThread.cpp
@@ -509,16 +509,28 @@ LazyIdleThread::HasPendingEvents(bool* a
 
 NS_IMETHODIMP
 LazyIdleThread::IdleDispatch(already_AddRefed<nsIRunnable> aEvent)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
+LazyIdleThread::EnableEventPrioritization()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+LazyIdleThread::IsEventPrioritizationEnabled(bool* aResult)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
 LazyIdleThread::RegisterIdlePeriod(already_AddRefed<nsIIdlePeriod> aIdlePeriod)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP
 LazyIdleThread::ProcessNextEvent(bool aMayWait,
                                  bool* aEventWasProcessed)
--- a/xpcom/threads/SchedulerGroup.cpp
+++ b/xpcom/threads/SchedulerGroup.cpp
@@ -390,18 +390,27 @@ SchedulerGroup::Runnable::Run()
   // The runnable's destructor can have side effects, so try to execute it in
   // the scope of the TabGroup.
   mRunnable = nullptr;
 
   mGroup->SetValidatingAccess(EndValidation);
   return result;
 }
 
+NS_IMETHODIMP
+SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority)
+{
+  *aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
+  nsCOMPtr<nsIRunnablePriority> runnablePrio = do_QueryInterface(mRunnable);
+  return runnablePrio ? runnablePrio->GetPriority(aPriority) : NS_OK;
+}
+
 NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable,
                             mozilla::Runnable,
+                            nsIRunnablePriority,
                             SchedulerGroup::Runnable)
 
 SchedulerGroup::AutoProcessEvent::AutoProcessEvent()
  : mPrevRunningDispatcher(SchedulerGroup::sRunningDispatcher)
 {
   SchedulerGroup* prev = sRunningDispatcher;
   if (prev) {
     MOZ_ASSERT(prev->mAccessValid);
--- a/xpcom/threads/SchedulerGroup.h
+++ b/xpcom/threads/SchedulerGroup.h
@@ -76,30 +76,31 @@ public:
   }
 
   // Ensure that it's valid to access the TabGroup at this time.
   void ValidateAccess() const
   {
     MOZ_ASSERT(IsSafeToRun());
   }
 
-  class Runnable final : public mozilla::Runnable
+  class Runnable final : public mozilla::Runnable, public nsIRunnablePriority
   {
   public:
     Runnable(already_AddRefed<nsIRunnable>&& aRunnable,
              SchedulerGroup* aGroup);
 
     SchedulerGroup* Group() const { return mGroup; }
 
     NS_IMETHOD GetName(nsACString& aName) override;
 
     bool IsBackground() const { return mGroup->IsBackground(); }
 
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIRUNNABLE
+    NS_DECL_NSIRUNNABLEPRIORITY
 
     NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID);
 
  private:
     friend class SchedulerGroup;
 
     ~Runnable() = default;
 
--- a/xpcom/threads/moz.build
+++ b/xpcom/threads/moz.build
@@ -69,16 +69,17 @@ SOURCES += [
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
     'BackgroundHangMonitor.cpp',
     'BlockingResourceBase.cpp',
     'HangAnnotations.cpp',
     'HangMonitor.cpp',
+    'InputEventStatistics.cpp',
     'LazyIdleThread.cpp',
     'MainThreadIdlePeriod.cpp',
     'nsEnvironment.cpp',
     'nsEventQueue.cpp',
     'nsMemoryPressure.cpp',
     'nsProcessCommon.cpp',
     'nsProxyRelease.cpp',
     'nsThread.cpp',
--- a/xpcom/threads/nsIRunnable.idl
+++ b/xpcom/threads/nsIRunnable.idl
@@ -17,11 +17,12 @@ interface nsIRunnable : nsISupports
      */
     void run();
 };
 
 [uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)]
 interface nsIRunnablePriority : nsISupports
 {
     const unsigned short PRIORITY_NORMAL = 0;
-    const unsigned short PRIORITY_HIGH = 1;
+    const unsigned short PRIORITY_INPUT = 1;
+    const unsigned short PRIORITY_HIGH = 2;
     readonly attribute unsigned long priority;
 };
--- a/xpcom/threads/nsIThread.idl
+++ b/xpcom/threads/nsIThread.idl
@@ -147,16 +147,19 @@ interface nsIThread : nsISerialEventTarg
    * @throws NS_ERROR_INVALID_ARG
    *   Indicates that event is null.
    * @throws NS_ERROR_UNEXPECTED
    *   Indicates that the thread is shutting down and has finished processing
    * events, so this event would never run and has not been dispatched.
    */
   [noscript] void idleDispatch(in alreadyAddRefed_nsIRunnable event);
 
+  [noscript] void enableEventPrioritization();
+  [noscript] bool isEventPrioritizationEnabled();
+
   /**
    * Use this attribute to dispatch runnables to the thread. Eventually, the
    * eventTarget attribute will be the only way to dispatch events to a
    * thread--nsIThread will no longer inherit from nsIEventTarget.
    */
   readonly attribute nsIEventTarget eventTarget;
 
   /**
--- a/xpcom/threads/nsThread.cpp
+++ b/xpcom/threads/nsThread.cpp
@@ -36,16 +36,17 @@
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsIIdlePeriod.h"
 #include "nsIIdleRunnable.h"
 #include "nsThreadSyncDispatch.h"
 #include "LeakRefPtr.h"
 #include "GeckoProfiler.h"
+#include "InputEventStatistics.h"
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsServiceManagerUtils.h"
 #include "nsICrashReporter.h"
 #include "mozilla/dom/ContentChild.h"
 #endif
 
 #ifdef XP_LINUX
@@ -810,55 +811,166 @@ nsThread::DispatchInternal(already_AddRe
     return NS_OK;
   }
 
   NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL ||
                aFlags == NS_DISPATCH_AT_END, "unexpected dispatch flags");
   return PutEvent(event.take(), aTarget);
 }
 
+NS_IMPL_ISUPPORTS(nsThread::nsChainedEventQueue::EnablePrioritizationRunnable,
+                  nsIRunnable)
+
+void
+nsThread::nsChainedEventQueue::EnablePrioritization(MutexAutoLock& aProofOfLock)
+{
+  MOZ_ASSERT(!mIsInputPrioritizationEnabled);
+  // When enabling event prioritization, there may be some pending events with
+  // different priorities in the normal queue. Create an event in the normal
+  // queue to consume all pending events in the time order to make sure we won't
+  // preempt a pending event (e.g. input) in the normal queue by another newly
+  // created event with the same priority.
+  mNormalQueue->PutEvent(new EnablePrioritizationRunnable(this), aProofOfLock);
+  mInputHandlingStartTime = TimeStamp();
+  mIsInputPrioritizationEnabled = true;
+}
+
 bool
-nsThread::nsChainedEventQueue::GetEvent(bool aMayWait, nsIRunnable** aEvent,
-                                        unsigned short* aPriority,
-                                        mozilla::MutexAutoLock& aProofOfLock)
+nsThread::nsChainedEventQueue::
+GetNormalOrInputOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
+                                    unsigned short* aPriority,
+                                    MutexAutoLock& aProofOfLock)
 {
   bool retVal = false;
   do {
-    if (mProcessSecondaryQueueRunnable) {
-      MOZ_ASSERT(mSecondaryQueue->HasPendingEvent(aProofOfLock));
-      retVal = mSecondaryQueue->GetEvent(aMayWait, aEvent, aProofOfLock);
+    // Use mProcessHighPriorityQueueRunnable to prevent the high priority events
+    // from consuming all cpu time and causing starvation.
+    if (mProcessHighPriorityQueueRunnable) {
+      MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
+      retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
       MOZ_ASSERT(*aEvent);
-      if (aPriority) {
-        *aPriority = nsIRunnablePriority::PRIORITY_HIGH;
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
+      mInputHandlingStartTime = TimeStamp();
+      mProcessHighPriorityQueueRunnable = false;
+      return retVal;
+    }
+    mProcessHighPriorityQueueRunnable =
+      mHighQueue->HasPendingEvent(aProofOfLock);
+
+    uint32_t pendingInputCount = mInputQueue->Count(aProofOfLock);
+    if (pendingInputCount > 0) {
+      if (mInputHandlingStartTime.IsNull()) {
+        mInputHandlingStartTime =
+          InputEventStatistics::Get()
+            .GetInputHandlingStartTime(mInputQueue->Count(aProofOfLock));
       }
-      mProcessSecondaryQueueRunnable = false;
-      return retVal;
+      if (TimeStamp::Now() > mInputHandlingStartTime) {
+        retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
+        MOZ_ASSERT(*aEvent);
+        SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
+        return retVal;
+      }
     }
 
-    // We don't want to wait if mSecondaryQueue has some events.
-    bool reallyMayWait =
-      aMayWait && !mSecondaryQueue->HasPendingEvent(aProofOfLock);
-    retVal =
-      mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
-    if (aPriority) {
-      *aPriority = nsIRunnablePriority::PRIORITY_NORMAL;
-    }
+    // We don't want to wait if there are some high priority events or input
+    // events in the queues.
+    bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable &&
+                         pendingInputCount == 0;
 
-    // Let's see if we should next time process an event from the secondary
-    // queue.
-    mProcessSecondaryQueueRunnable =
-      mSecondaryQueue->HasPendingEvent(aProofOfLock);
-
+    retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
     if (*aEvent) {
       // We got an event, return early.
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
+      return retVal;
+    }
+    if (pendingInputCount > 0 && !mProcessHighPriorityQueueRunnable) {
+      // Handle input events if we have time for them.
+      MOZ_ASSERT(mInputQueue->HasPendingEvent(aProofOfLock));
+      retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock);
+      MOZ_ASSERT(*aEvent);
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT);
+      return retVal;
+    }
+  } while (aMayWait || mProcessHighPriorityQueueRunnable);
+  return retVal;
+}
+
+bool
+nsThread::nsChainedEventQueue::
+GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
+                             unsigned short* aPriority,
+                             MutexAutoLock& aProofOfLock)
+{
+  bool retVal = false;
+  do {
+    // Use mProcessHighPriorityQueueRunnable to prevent the high priority events
+    // from consuming all cpu time and causing starvation.
+    if (mProcessHighPriorityQueueRunnable) {
+      MOZ_ASSERT(mHighQueue->HasPendingEvent(aProofOfLock));
+      retVal = mHighQueue->GetEvent(false, aEvent, aProofOfLock);
+      MOZ_ASSERT(*aEvent);
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH);
+      mProcessHighPriorityQueueRunnable = false;
       return retVal;
     }
-  } while(aMayWait || mProcessSecondaryQueueRunnable);
+    mProcessHighPriorityQueueRunnable =
+      mHighQueue->HasPendingEvent(aProofOfLock);
+
+    // We don't want to wait if there are some events in the high priority
+    // queue.
+    bool reallyMayWait = aMayWait && !mProcessHighPriorityQueueRunnable;
+
+    retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock);
+    if (*aEvent) {
+      // We got an event, return early.
+      SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL);
+      return retVal;
+    }
+  } while (aMayWait || mProcessHighPriorityQueueRunnable);
+  return retVal;
+}
 
-  return retVal;
+void
+nsThread::nsChainedEventQueue::PutEvent(already_AddRefed<nsIRunnable> aEvent,
+                                        MutexAutoLock& aProofOfLock)
+{
+  RefPtr<nsIRunnable> event(aEvent);
+  nsCOMPtr<nsIRunnablePriority> runnablePrio(do_QueryInterface(event));
+  uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
+  if (runnablePrio) {
+    runnablePrio->GetPriority(&prio);
+  }
+  switch (prio) {
+  case nsIRunnablePriority::PRIORITY_NORMAL:
+    mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+    break;
+  case nsIRunnablePriority::PRIORITY_INPUT:
+    if (mIsInputPrioritizationEnabled) {
+      mInputQueue->PutEvent(event.forget(), aProofOfLock);
+    } else {
+      mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+    }
+    break;
+  case nsIRunnablePriority::PRIORITY_HIGH:
+    if (mIsInputPrioritizationEnabled) {
+      mHighQueue->PutEvent(event.forget(), aProofOfLock);
+    } else {
+      // During startup, ContentParent sends SetXPCOMProcessAttributes to
+      // initialize ContentChild and enable input event prioritization. After
+      // that, ContentParent sends PBrowserConstructor to create PBrowserChild.
+      // To prevent PBrowserConstructor preempt SetXPCOMProcessAttributes and
+      // cause problems, we have to put high priority events in mNormalQueue to
+      // keep the correct order of initialization.
+      mNormalQueue->PutEvent(event.forget(), aProofOfLock);
+    }
+    break;
+  default:
+    MOZ_ASSERT(false);
+    break;
+  }
 }
 
 //-----------------------------------------------------------------------------
 // nsIEventTarget
 
 NS_IMETHODIMP
 nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags)
 {
@@ -1160,16 +1272,34 @@ nsThread::IdleDispatch(already_AddRefed<
     NS_WARNING("An idle event was posted to a thread that will never run it (rejected)");
     return NS_ERROR_UNEXPECTED;
   }
 
   mIdleEvents.PutEvent(event.take(), lock);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsThread::EnableEventPrioritization()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MutexAutoLock lock(mLock);
+  // Only support event prioritization for main event queue.
+  mEventsRoot.EnablePrioritization(lock);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsThread::IsEventPrioritizationEnabled(bool* aResult)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  *aResult = mEventsRoot.IsPrioritizationEnabled();
+  return NS_OK;
+}
+
 #ifdef MOZ_CANARY
 void canary_alarm_handler(int signum);
 
 class Canary
 {
   //XXX ToDo: support nested loops
 public:
   Canary()
@@ -1437,17 +1567,20 @@ nsThread::ProcessNextEvent(bool aMayWait
         // Copy the name into sMainThreadRunnableName's buffer, and append a
         // terminating null.
         uint32_t length = std::min((uint32_t) kRunnableNameBufSize - 1,
                                    (uint32_t) name.Length());
         memcpy(sMainThreadRunnableName.begin(), name.BeginReading(), length);
         sMainThreadRunnableName[length] = '\0';
       }
 #endif
-
+      Maybe<AutoTimeDurationHelper> timeDurationHelper;
+      if (priority == nsIRunnablePriority::PRIORITY_INPUT) {
+        timeDurationHelper.emplace();
+      }
       event->Run();
     } else if (aMayWait) {
       MOZ_ASSERT(ShuttingDown(),
                  "This should only happen when shutting down");
       rv = NS_ERROR_UNEXPECTED;
     }
   }
 
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -92,16 +92,26 @@ public:
   };
 
   static bool SaveMemoryReportNearOOM(ShouldSaveMemoryReport aShouldSave);
 #endif
 
   static const uint32_t kRunnableNameBufSize = 1000;
   static mozilla::Array<char, kRunnableNameBufSize> sMainThreadRunnableName;
 
+  // Query whether there are some pending input events in the queue. This method
+  // is supposed to be called on main thread with input event prioritization
+  // enabled.
+  bool HasPendingInputEvents()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    mozilla::MutexAutoLock lock(mLock);
+    return mEventsRoot.HasPendingEventsInInputQueue(lock);
+  }
+
 private:
   void DoMainThreadSpecificProcessing(bool aReallyWait);
 
   // Returns a null TimeStamp if we're not in the idle period.
   mozilla::TimeStamp GetIdleDeadline();
   void GetIdleEvent(nsIRunnable** aEvent, mozilla::MutexAutoLock& aProofOfLock);
   void GetEvent(bool aWait, nsIRunnable** aEvent,
                 unsigned short* aPriority,
@@ -137,82 +147,137 @@ protected:
   nsresult PutEvent(already_AddRefed<nsIRunnable> aEvent,
                     nsNestedEventTarget* aTarget);
 
   nsresult DispatchInternal(already_AddRefed<nsIRunnable> aEvent,
                             uint32_t aFlags, nsNestedEventTarget* aTarget);
 
   struct nsThreadShutdownContext* ShutdownInternal(bool aSync);
 
-  // Wrapper for nsEventQueue that supports chaining.
+  // Wrapper for nsEventQueue that supports chaining and prioritization.
   class nsChainedEventQueue
   {
   public:
     explicit nsChainedEventQueue(mozilla::Mutex& aLock)
       : mNext(nullptr)
       , mEventsAvailable(aLock, "[nsChainedEventQueue.mEventsAvailable]")
-      , mProcessSecondaryQueueRunnable(false)
+      , mIsInputPrioritizationEnabled(false)
+      , mIsReadyToPrioritizeEvents(false)
+      , mProcessHighPriorityQueueRunnable(false)
     {
       mNormalQueue =
         mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
                                           nsEventQueue::eSharedCondVarQueue);
-      // Both queues need to use the same CondVar!
-      mSecondaryQueue =
+      // All queues need to use the same CondVar!
+      mInputQueue =
+        mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
+                                          nsEventQueue::eSharedCondVarQueue);
+      mHighQueue =
         mozilla::MakeUnique<nsEventQueue>(mEventsAvailable,
                                           nsEventQueue::eSharedCondVarQueue);
     }
 
+    void EnablePrioritization(mozilla::MutexAutoLock& aProofOfLock);
+
+    bool IsPrioritizationEnabled()
+    {
+      return mIsInputPrioritizationEnabled;
+    }
+
     bool GetEvent(bool aMayWait, nsIRunnable** aEvent,
                   unsigned short* aPriority,
-                  mozilla::MutexAutoLock& aProofOfLock);
+                  mozilla::MutexAutoLock& aProofOfLock) {
+      return mIsReadyToPrioritizeEvents
+        ? GetNormalOrInputOrHighPriorityEvent(aMayWait, aEvent, aPriority, aProofOfLock)
+        : GetNormalOrHighPriorityEvent(aMayWait, aEvent, aPriority, aProofOfLock);
+    }
 
     void PutEvent(nsIRunnable* aEvent, mozilla::MutexAutoLock& aProofOfLock)
     {
       RefPtr<nsIRunnable> event(aEvent);
       PutEvent(event.forget(), aProofOfLock);
     }
 
     void PutEvent(already_AddRefed<nsIRunnable> aEvent,
-                  mozilla::MutexAutoLock& aProofOfLock)
-    {
-      RefPtr<nsIRunnable> event(aEvent);
-      nsCOMPtr<nsIRunnablePriority> runnablePrio =
-        do_QueryInterface(event);
-      uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL;
-      if (runnablePrio) {
-        runnablePrio->GetPriority(&prio);
-      }
-      MOZ_ASSERT(prio == nsIRunnablePriority::PRIORITY_NORMAL ||
-                 prio == nsIRunnablePriority::PRIORITY_HIGH);
-      if (prio == nsIRunnablePriority::PRIORITY_NORMAL) {
-        mNormalQueue->PutEvent(event.forget(), aProofOfLock);
-      } else {
-        mSecondaryQueue->PutEvent(event.forget(), aProofOfLock);
-      }
-    }
+                  mozilla::MutexAutoLock& aProofOfLock);
 
     bool HasPendingEvent(mozilla::MutexAutoLock& aProofOfLock)
     {
       return mNormalQueue->HasPendingEvent(aProofOfLock) ||
-             mSecondaryQueue->HasPendingEvent(aProofOfLock);
+             mInputQueue->HasPendingEvent(aProofOfLock) ||
+             mHighQueue->HasPendingEvent(aProofOfLock);
+    }
+
+    bool HasPendingEventsInInputQueue(mozilla::MutexAutoLock& aProofOfLock)
+    {
+      MOZ_ASSERT(mIsInputPrioritizationEnabled);
+      return mInputQueue->HasPendingEvent(aProofOfLock);
     }
 
     nsChainedEventQueue* mNext;
     RefPtr<nsNestedEventTarget> mEventTarget;
 
   private:
+    bool GetNormalOrInputOrHighPriorityEvent(bool aMayWait,
+                                             nsIRunnable** aEvent,
+                                             unsigned short* aPriority,
+                                             mozilla::MutexAutoLock& aProofOfLock);
+
+    bool GetNormalOrHighPriorityEvent(bool aMayWait, nsIRunnable** aEvent,
+                                      unsigned short* aPriority,
+                                      mozilla::MutexAutoLock& aProofOfLock);
+
+    // This is used to flush pending events in nsChainedEventQueue::mNormalQueue
+    // before starting event prioritization.
+    class EnablePrioritizationRunnable final : public nsIRunnable
+    {
+      nsChainedEventQueue* mEventQueue;
+
+    public:
+      NS_DECL_ISUPPORTS
+
+      explicit EnablePrioritizationRunnable(nsChainedEventQueue* aQueue)
+        : mEventQueue(aQueue)
+      {
+      }
+
+      NS_IMETHOD Run() override
+      {
+        mEventQueue->mIsReadyToPrioritizeEvents = true;
+        return NS_OK;
+      }
+    private:
+      ~EnablePrioritizationRunnable()
+      {
+      }
+    };
+
+    static void SetPriorityIfNotNull(unsigned short* aPriority, short aValue)
+    {
+      if (aPriority) {
+        *aPriority = aValue;
+      }
+    }
     mozilla::CondVar mEventsAvailable;
+    mozilla::TimeStamp mInputHandlingStartTime;
     mozilla::UniquePtr<nsEventQueue> mNormalQueue;
-    mozilla::UniquePtr<nsEventQueue> mSecondaryQueue;
+    mozilla::UniquePtr<nsEventQueue> mInputQueue;
+    mozilla::UniquePtr<nsEventQueue> mHighQueue;
+    bool mIsInputPrioritizationEnabled;
 
+    // When enabling input event prioritization, there may be some events in the
+    // queue. We have to process all of them before the new coming events to
+    // prevent the queued events are preempted by the newly ones with the same
+    // priority.
+    bool mIsReadyToPrioritizeEvents;
     // Try to process one high priority runnable after each normal
     // priority runnable. This gives the processing model HTML spec has for
     // 'Update the rendering' in the case only vsync messages are in the
     // secondary queue and prevents starving the normal queue.
-    bool mProcessSecondaryQueueRunnable;
+    bool mProcessHighPriorityQueueRunnable;
   };
 
   class nsNestedEventTarget final : public nsIEventTarget
   {
   public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIEVENTTARGET_FULL
 
--- a/xpcom/threads/nsThreadManager.cpp
+++ b/xpcom/threads/nsThreadManager.cpp
@@ -6,24 +6,26 @@
 
 #include "nsThreadManager.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "mozilla/AbstractThread.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/SystemGroup.h"
 #include "mozilla/ThreadLocal.h"
 #ifdef MOZ_CANARY
 #include <fcntl.h>
 #include <unistd.h>
 #endif
 
 #include "MainThreadIdlePeriod.h"
+#include "InputEventStatistics.h"
 
 using namespace mozilla;
 
 static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread;
 
 bool
 NS_IsMainThread()
 {
@@ -401,16 +403,35 @@ nsThreadManager::DispatchToMainThread(ns
   // Keep this functioning during Shutdown
   if (NS_WARN_IF(!mMainThread)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   return mMainThread->DispatchFromScript(aEvent, 0);
 }
 
+void
+nsThreadManager::EnableMainThreadEventPrioritization()
+{
+  static bool sIsInitialized = false;
+  if (sIsInitialized) {
+    return;
+  }
+  sIsInitialized = true;
+  MOZ_ASSERT(Preferences::IsServiceAvailable());
+  bool enable =
+    Preferences::GetBool("prioritized_input_events.enabled", false);
+
+  if (!enable) {
+    return;
+  }
+  InputEventStatistics::Get().SetEnable(true);
+  mMainThread->EnableEventPrioritization();
+}
+
 NS_IMETHODIMP
 nsThreadManager::IdleDispatchToMainThread(nsIRunnable *aEvent, uint32_t aTimeout)
 {
   // Note: C++ callers should instead use NS_IdleDispatchToThread or
   // NS_IdleDispatchToCurrentThread.
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIRunnable> event(aEvent);
--- a/xpcom/threads/nsThreadManager.h
+++ b/xpcom/threads/nsThreadManager.h
@@ -48,16 +48,17 @@ public:
   // simultaneously during the execution of the thread manager.
   uint32_t GetHighestNumberOfThreads();
 
   // This needs to be public in order to support static instantiation of this
   // class with older compilers (e.g., egcs-2.91.66).
   ~nsThreadManager()
   {
   }
+  void EnableMainThreadEventPrioritization();
 
 private:
   nsThreadManager()
     : mCurThreadIndex(0)
     , mMainPRThread(nullptr)
     , mLock("nsThreadManager.mLock")
     , mInitialized(false)
     , mCurrentNumberOfThreads(1)