From de7f705042637d4f7254153d10ac3698eb20e6a8 Mon Sep 17 00:00:00 2001 From: Stone Shih Date: Tue, 21 Mar 2017 15:44:12 +0800 Subject: [PATCH] Bug 1351148 Part2: Add a priority queue for input events. r=smaug. MozReview-Commit-ID: 5ud1Ex9UNVo --- dom/events/EventStateManager.cpp | 21 +- dom/events/EventStateManager.h | 2 +- dom/ipc/ContentBridgeParent.cpp | 18 ++ dom/ipc/ContentBridgeParent.h | 9 + dom/ipc/ContentChild.cpp | 3 + dom/ipc/ContentParent.cpp | 18 ++ dom/ipc/ContentParent.h | 9 + dom/ipc/PBrowser.ipdl | 48 +++-- dom/ipc/TabChild.cpp | 15 +- dom/ipc/TabParent.cpp | 27 ++- dom/ipc/TabParent.h | 8 + dom/ipc/nsIContentChild.cpp | 3 +- dom/ipc/nsIContentParent.cpp | 19 ++ dom/ipc/nsIContentParent.h | 9 + ipc/chromium/src/chrome/common/ipc_message.cc | 3 +- ipc/chromium/src/chrome/common/ipc_message.h | 34 ++-- ipc/glue/MessageChannel.cpp | 16 +- ipc/ipdl/ipdl/ast.py | 3 +- ipc/ipdl/ipdl/lower.py | 3 +- ipc/ipdl/ipdl/parser.py | 3 +- ipc/ipdl/test/cxx/PTestPriority.ipdl | 9 +- layout/base/nsRefreshDriver.cpp | 18 ++ layout/base/nsRefreshDriver.h | 6 + modules/libpref/init/all.js | 20 ++ xpcom/threads/InputEventStatistics.cpp | 68 +++++++ xpcom/threads/InputEventStatistics.h | 114 +++++++++++ xpcom/threads/LazyIdleThread.cpp | 12 ++ xpcom/threads/SchedulerGroup.cpp | 9 + xpcom/threads/SchedulerGroup.h | 3 +- xpcom/threads/moz.build | 1 + xpcom/threads/nsIRunnable.idl | 3 +- xpcom/threads/nsIThread.idl | 3 + xpcom/threads/nsThread.cpp | 181 +++++++++++++++--- xpcom/threads/nsThread.h | 119 +++++++++--- xpcom/threads/nsThreadManager.cpp | 21 ++ xpcom/threads/nsThreadManager.h | 1 + 36 files changed, 750 insertions(+), 109 deletions(-) create mode 100644 xpcom/threads/InputEventStatistics.cpp create mode 100644 xpcom/threads/InputEventStatistics.h diff --git a/dom/events/EventStateManager.cpp b/dom/events/EventStateManager.cpp index 20d388490f9f..d28f225bd271 100644 --- a/dom/events/EventStateManager.cpp +++ b/dom/events/EventStateManager.cpp @@ -2866,6 +2866,7 @@ NodeAllowsClickThrough(nsINode* aNode) void EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent, + nsIFrame* aTargetFrame, nsEventStatus& aStatus) { if (aStatus == nsEventStatus_eConsumeNoDefault) { @@ -2873,6 +2874,24 @@ EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent, } if (!aKeyboardEvent->HasBeenPostedToRemoteProcess()) { + if (aKeyboardEvent->IsWaitingReplyFromRemoteProcess()) { + RefPtr 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 @@ -3524,7 +3543,7 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext, case eKeyPress: { WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent(); - PostHandleKeyboardEvent(keyEvent, *aStatus); + PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus); } break; diff --git a/dom/events/EventStateManager.h b/dom/events/EventStateManager.h index f4e148af9b95..888e4aeda6d2 100644 --- a/dom/events/EventStateManager.h +++ b/dom/events/EventStateManager.h @@ -109,7 +109,7 @@ public: nsEventStatus* aStatus); void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent, - nsEventStatus& aStatus); + nsIFrame* aTargetFrame, nsEventStatus& aStatus); /** * DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll diff --git a/dom/ipc/ContentBridgeParent.cpp b/dom/ipc/ContentBridgeParent.cpp index 4cbc78f51545..72778807a149 100644 --- a/dom/ipc/ContentBridgeParent.cpp +++ b/dom/ipc/ContentBridgeParent.cpp @@ -171,6 +171,24 @@ 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() { diff --git a/dom/ipc/ContentBridgeParent.h b/dom/ipc/ContentBridgeParent.h index 03a975bb0c04..cf5d96daac20 100644 --- a/dom/ipc/ContentBridgeParent.h +++ b/dom/ipc/ContentBridgeParent.h @@ -138,6 +138,15 @@ protected: 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, diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 1e9f670da42a..2bbf599922a5 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -1176,6 +1176,9 @@ ContentChild::InitXPCOM(const XPCOMInitData& aXPCOMInit, GfxInfoBase::SetFeatureStatus(aXPCOMInit.gfxFeatureStatus()); DataStorage::SetCachedStorageEntries(aXPCOMInit.dataStorage()); + + // Enable input event prioritization. + nsThreadManager::get().EnableMainThreadEventPrioritization(); } mozilla::ipc::IPCResult diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index 825fe3d2340d..3667d1a9e073 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -2896,6 +2896,24 @@ 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) diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index 254798a98dab..c10eb4c41c9a 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -823,6 +823,15 @@ private: 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, diff --git a/dom/ipc/PBrowser.ipdl b/dom/ipc/PBrowser.ipdl index c9ce571a8d79..10d408cebb90 100644 --- a/dom/ipc/PBrowser.ipdl +++ b/dom/ipc/PBrowser.ipdl @@ -543,6 +543,12 @@ parent: */ 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. */ @@ -646,7 +652,9 @@ child: * 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 @@ -654,21 +662,29 @@ child: * |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); /** diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp index 5a14fd9dc0ff..40360e1015c7 100644 --- a/dom/ipc/TabChild.cpp +++ b/dom/ipc/TabChild.cpp @@ -90,6 +90,7 @@ #include "nsPIWindowRoot.h" #include "nsLayoutUtils.h" #include "nsPrintfCString.h" +#include "nsThreadManager.h" #include "nsThreadUtils.h" #include "nsViewManager.h" #include "nsWeakReference.h" @@ -321,7 +322,19 @@ private: { 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); diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index 96defc63e782..b9a2feb9be5a 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -172,6 +172,7 @@ TabParent::TabParent(nsIContentParent* aManager, , mPreserveLayers(false) , mHasPresented(false) , mHasBeforeUnload(false) + , mIsReadyToHandleInputEvents(false) { MOZ_ASSERT(aManager); } @@ -1081,7 +1082,7 @@ TabParent::SendKeyEvent(const nsAString& aType, int32_t aModifiers, bool aPreventDefault) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } Unused << PBrowserParent::SendKeyEvent(nsString(aType), aKeyCode, aCharCode, @@ -1091,7 +1092,7 @@ TabParent::SendKeyEvent(const nsAString& aType, void TabParent::SendRealMouseEvent(WidgetMouseEvent& aEvent) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } aEvent.mRefPoint += GetChildProcessOffset(); @@ -1151,7 +1152,7 @@ void TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction, uint32_t aDropEffect) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } aEvent.mRefPoint += GetChildProcessOffset(); @@ -1170,7 +1171,7 @@ TabParent::AdjustTapToChildWidget(const LayoutDevicePoint& aPoint) void TabParent::SendMouseWheelEvent(WidgetWheelEvent& aEvent) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } @@ -1449,7 +1450,7 @@ TabParent::RecvClearNativeTouchSequence(const uint64_t& aObserverId) void TabParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } aEvent.mRefPoint += GetChildProcessOffset(); @@ -1470,7 +1471,7 @@ TabParent::SendRealKeyEvent(WidgetKeyboardEvent& aEvent) void TabParent::SendRealTouchEvent(WidgetTouchEvent& aEvent) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return; } @@ -1531,7 +1532,7 @@ TabParent::SendHandleTap(TapType aType, const ScrollableLayerGuid& aGuid, uint64_t aInputBlockId) { - if (mIsDestroyed) { + if (mIsDestroyed || !mIsReadyToHandleInputEvents) { return false; } if ((aType == TapType::eSingleTap || aType == TapType::eSecondTap) && @@ -2942,6 +2943,18 @@ TabParent::RecvRemotePaintIsReady() 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() { diff --git a/dom/ipc/TabParent.h b/dom/ipc/TabParent.h index f4375463c6e2..f52a7d03b555 100644 --- a/dom/ipc/TabParent.h +++ b/dom/ipc/TabParent.h @@ -603,6 +603,9 @@ public: void LiveResizeStarted() override; void LiveResizeStopped() override; + void SetReadyToHandleInputEvents() { mIsReadyToHandleInputEvents = true; } + bool IsReadyToHandleInputEvents() { return mIsReadyToHandleInputEvents; } + protected: bool ReceiveMessage(const nsString& aMessage, bool aSync, @@ -628,6 +631,8 @@ protected: 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, @@ -778,6 +783,9 @@ private: // 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); }; diff --git a/dom/ipc/nsIContentChild.cpp b/dom/ipc/nsIContentChild.cpp index dafe8bc5bea0..b73f4e9569c0 100644 --- a/dom/ipc/nsIContentChild.cpp +++ b/dom/ipc/nsIContentChild.cpp @@ -102,7 +102,8 @@ nsIContentChild::RecvPBrowserConstructor(PBrowserChild* aActor, if (os) { os->NotifyObservers(static_cast(tabChild), "tab-child-created", nullptr); } - + // Notify parent that we are ready to handle input events. + tabChild->SendRemoteIsReadyToHandleInputEvents(); return IPC_OK(); } diff --git a/dom/ipc/nsIContentParent.cpp b/dom/ipc/nsIContentParent.cpp index 2b531abef548..ba59f02ce2a2 100644 --- a/dom/ipc/nsIContentParent.cpp +++ b/dom/ipc/nsIContentParent.cpp @@ -206,6 +206,25 @@ nsIContentParent::DeallocPBrowserParent(PBrowserParent* aFrame) 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) diff --git a/dom/ipc/nsIContentParent.h b/dom/ipc/nsIContentParent.h index be690861f72f..f27f59da7f30 100644 --- a/dom/ipc/nsIContentParent.h +++ b/dom/ipc/nsIContentParent.h @@ -117,6 +117,15 @@ protected: // IPDL methods 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); diff --git a/ipc/chromium/src/chrome/common/ipc_message.cc b/ipc/chromium/src/chrome/common/ipc_message.cc index eeaa57de1d12..413a946edc4f 100644 --- a/ipc/chromium/src/chrome/common/ipc_message.cc +++ b/ipc/chromium/src/chrome/common/ipc_message.cc @@ -67,8 +67,7 @@ Message::Message(int32_t routing_id, 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) diff --git a/ipc/chromium/src/chrome/common/ipc_message.h b/ipc/chromium/src/chrome/common/ipc_message.h index efb430237036..1752210f9e44 100644 --- a/ipc/chromium/src/chrome/common/ipc_message.h +++ b/ipc/chromium/src/chrome/common/ipc_message.h @@ -46,8 +46,9 @@ class Message : public Pickle { }; enum PriorityValue { - NORMAL_PRIORITY, - HIGH_PRIORITY, + NORMAL_PRIORITY = 0, + INPUT_PRIORITY = 1, + HIGH_PRIORITY = 2, }; enum MessageCompression { @@ -91,17 +92,12 @@ class Message : public Pickle { } PriorityValue priority() const { - if (header()->flags & PRIO_BIT) { - return HIGH_PRIORITY; - } - return NORMAL_PRIORITY; + return static_cast((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 { @@ -315,16 +311,16 @@ class Message : public Pickle { // 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 }; diff --git a/ipc/glue/MessageChannel.cpp b/ipc/glue/MessageChannel.cpp index f17ea1bdabbb..1ab730823186 100644 --- a/ipc/glue/MessageChannel.cpp +++ b/ipc/glue/MessageChannel.cpp @@ -1971,8 +1971,20 @@ MessageChannel::MessageTask::Clear() 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; } diff --git a/ipc/ipdl/ipdl/ast.py b/ipc/ipdl/ipdl/ast.py index 451df44c1b08..f21df2aa331b 100644 --- a/ipc/ipdl/ipdl/ast.py +++ b/ipc/ipdl/ipdl/ast.py @@ -9,7 +9,8 @@ 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): diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py index 1e6e7cf2087f..6769231fd705 100644 --- a/ipc/ipdl/ipdl/lower.py +++ b/ipc/ipdl/ipdl/lower.py @@ -1725,8 +1725,9 @@ def _generateMessageConstructor(clsname, msgid, segmentSize, nested, prio, prett 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( diff --git a/ipc/ipdl/ipdl/parser.py b/ipc/ipdl/ipdl/parser.py index 7ba8f810dbd5..da03a94a5b75 100644 --- a/ipc/ipdl/ipdl/parser.py +++ b/ipc/ipdl/ipdl/parser.py @@ -503,7 +503,8 @@ def p_Nested(p): 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()") diff --git a/ipc/ipdl/test/cxx/PTestPriority.ipdl b/ipc/ipdl/test/cxx/PTestPriority.ipdl index edb98365b7ca..c9bc3d3aa75e 100644 --- a/ipc/ipdl/test/cxx/PTestPriority.ipdl +++ b/ipc/ipdl/test/cxx/PTestPriority.ipdl @@ -3,11 +3,14 @@ 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 diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index 5b84b3504bf8..d4d7455d2d18 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -257,6 +257,13 @@ public: return idleEnd < aDefault ? idleEnd : aDefault; } + Maybe 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; @@ -2404,6 +2411,17 @@ nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault) return sRegularRateTimer->GetIdleDeadlineHint(aDefault); } +/* static */ Maybe +nsRefreshDriver::GetNextTickHint() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!sRegularRateTimer) { + return Nothing(); + } + return sRegularRateTimer->GetNextTickHint(); +} + void nsRefreshDriver::Disconnect() { diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h index 159a1b7804cc..095dd94fcb80 100644 --- a/layout/base/nsRefreshDriver.h +++ b/layout/base/nsRefreshDriver.h @@ -345,6 +345,12 @@ public: */ 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 GetNextTickHint(); + static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable, uint32_t aDelay); static void CancelIdleRunnable(nsIRunnable* aRunnable); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index f786d7702869..ac430fd7f392 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -3120,6 +3120,26 @@ 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 diff --git a/xpcom/threads/InputEventStatistics.cpp b/xpcom/threads/InputEventStatistics.cpp new file mode 100644 index 000000000000..84426fa9ce45 --- /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(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 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 diff --git a/xpcom/threads/InputEventStatistics.h b/xpcom/threads/InputEventStatistics.h new file mode 100644 index 000000000000..4c2a60b3a243 --- /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 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 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_ diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp index 6456f192444a..ae91ede4e109 100644 --- a/xpcom/threads/LazyIdleThread.cpp +++ b/xpcom/threads/LazyIdleThread.cpp @@ -513,6 +513,18 @@ LazyIdleThread::IdleDispatch(already_AddRefed 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 aIdlePeriod) { diff --git a/xpcom/threads/SchedulerGroup.cpp b/xpcom/threads/SchedulerGroup.cpp index 80971415e6a8..c7eee0c54032 100644 --- a/xpcom/threads/SchedulerGroup.cpp +++ b/xpcom/threads/SchedulerGroup.cpp @@ -395,8 +395,17 @@ SchedulerGroup::Runnable::Run() return result; } +NS_IMETHODIMP +SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority) +{ + *aPriority = nsIRunnablePriority::PRIORITY_NORMAL; + nsCOMPtr 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() diff --git a/xpcom/threads/SchedulerGroup.h b/xpcom/threads/SchedulerGroup.h index f0e9eb941272..967ef8b1fc26 100644 --- a/xpcom/threads/SchedulerGroup.h +++ b/xpcom/threads/SchedulerGroup.h @@ -81,7 +81,7 @@ public: MOZ_ASSERT(IsSafeToRun()); } - class Runnable final : public mozilla::Runnable + class Runnable final : public mozilla::Runnable, public nsIRunnablePriority { public: Runnable(already_AddRefed&& aRunnable, @@ -95,6 +95,7 @@ public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIRUNNABLE + NS_DECL_NSIRUNNABLEPRIORITY NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID); diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build index bd153dc844d8..12713b2a2691 100644 --- a/xpcom/threads/moz.build +++ b/xpcom/threads/moz.build @@ -74,6 +74,7 @@ UNIFIED_SOURCES += [ 'BlockingResourceBase.cpp', 'HangAnnotations.cpp', 'HangMonitor.cpp', + 'InputEventStatistics.cpp', 'LazyIdleThread.cpp', 'MainThreadIdlePeriod.cpp', 'nsEnvironment.cpp', diff --git a/xpcom/threads/nsIRunnable.idl b/xpcom/threads/nsIRunnable.idl index 4d26f72d913c..44b71e516127 100644 --- a/xpcom/threads/nsIRunnable.idl +++ b/xpcom/threads/nsIRunnable.idl @@ -22,6 +22,7 @@ interface nsIRunnable : nsISupports 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; }; diff --git a/xpcom/threads/nsIThread.idl b/xpcom/threads/nsIThread.idl index 618051f3be19..f49f7d605972 100644 --- a/xpcom/threads/nsIThread.idl +++ b/xpcom/threads/nsIThread.idl @@ -152,6 +152,9 @@ interface nsIThread : nsISerialEventTarget */ [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 diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp index e33f492ee340..94567f8b922e 100644 --- a/xpcom/threads/nsThread.cpp +++ b/xpcom/threads/nsThread.cpp @@ -41,6 +41,7 @@ #include "nsThreadSyncDispatch.h" #include "LeakRefPtr.h" #include "GeckoProfiler.h" +#include "InputEventStatistics.h" #ifdef MOZ_CRASHREPORTER #include "nsServiceManagerUtils.h" @@ -815,47 +816,158 @@ nsThread::DispatchInternal(already_AddRefed aEvent, uint32_t aFlags 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; - } - mProcessSecondaryQueueRunnable = false; + SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_HIGH); + mInputHandlingStartTime = TimeStamp(); + mProcessHighPriorityQueueRunnable = false; return retVal; } + mProcessHighPriorityQueueRunnable = + mHighQueue->HasPendingEvent(aProofOfLock); - // 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; + uint32_t pendingInputCount = mInputQueue->Count(aProofOfLock); + if (pendingInputCount > 0) { + if (mInputHandlingStartTime.IsNull()) { + mInputHandlingStartTime = + InputEventStatistics::Get() + .GetInputHandlingStartTime(mInputQueue->Count(aProofOfLock)); + } + if (TimeStamp::Now() > mInputHandlingStartTime) { + retVal = mInputQueue->GetEvent(false, aEvent, aProofOfLock); + MOZ_ASSERT(*aEvent); + SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_INPUT); + return retVal; + } } - // Let's see if we should next time process an event from the secondary - // queue. - mProcessSecondaryQueueRunnable = - mSecondaryQueue->HasPendingEvent(aProofOfLock); + // 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; + retVal = mNormalQueue->GetEvent(reallyMayWait, aEvent, aProofOfLock); if (*aEvent) { // We got an event, return early. + SetPriorityIfNotNull(aPriority, nsIRunnablePriority::PRIORITY_NORMAL); return retVal; } - } while(aMayWait || mProcessSecondaryQueueRunnable); - + 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; + } + 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; +} + +void +nsThread::nsChainedEventQueue::PutEvent(already_AddRefed aEvent, + MutexAutoLock& aProofOfLock) +{ + RefPtr event(aEvent); + nsCOMPtr 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 @@ -1165,6 +1277,24 @@ nsThread::IdleDispatch(already_AddRefed aEvent) 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); @@ -1442,7 +1572,10 @@ nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) sMainThreadRunnableName[length] = '\0'; } #endif - + Maybe timeDurationHelper; + if (priority == nsIRunnablePriority::PRIORITY_INPUT) { + timeDurationHelper.emplace(); + } event->Run(); } else if (aMayWait) { MOZ_ASSERT(ShuttingDown(), diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h index 9330df346bb5..aee3cd83079f 100644 --- a/xpcom/threads/nsThread.h +++ b/xpcom/threads/nsThread.h @@ -97,6 +97,16 @@ public: static const uint32_t kRunnableNameBufSize = 1000; static mozilla::Array 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); @@ -142,27 +152,43 @@ protected: 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(mEventsAvailable, nsEventQueue::eSharedCondVarQueue); - // Both queues need to use the same CondVar! - mSecondaryQueue = + // All queues need to use the same CondVar! + mInputQueue = + mozilla::MakeUnique(mEventsAvailable, + nsEventQueue::eSharedCondVarQueue); + mHighQueue = mozilla::MakeUnique(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) { @@ -171,43 +197,82 @@ protected: } void PutEvent(already_AddRefed aEvent, - mozilla::MutexAutoLock& aProofOfLock) - { - RefPtr event(aEvent); - nsCOMPtr 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 mEventTarget; private: - mozilla::CondVar mEventsAvailable; - mozilla::UniquePtr mNormalQueue; - mozilla::UniquePtr mSecondaryQueue; + 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 mNormalQueue; + mozilla::UniquePtr mInputQueue; + mozilla::UniquePtr 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 diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp index f5f0f4a5c508..95eb14535322 100644 --- a/xpcom/threads/nsThreadManager.cpp +++ b/xpcom/threads/nsThreadManager.cpp @@ -11,6 +11,7 @@ #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 @@ -19,6 +20,7 @@ #endif #include "MainThreadIdlePeriod.h" +#include "InputEventStatistics.h" using namespace mozilla; @@ -406,6 +408,25 @@ nsThreadManager::DispatchToMainThread(nsIRunnable *aEvent) 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) { diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h index 64ccc9bc946a..da9e35578490 100644 --- a/xpcom/threads/nsThreadManager.h +++ b/xpcom/threads/nsThreadManager.h @@ -53,6 +53,7 @@ public: ~nsThreadManager() { } + void EnableMainThreadEventPrioritization(); private: nsThreadManager()