mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-12 00:50:40 +00:00
Bug 1351148 Part2: Add a priority queue for input events. r=smaug.
MozReview-Commit-ID: 5ud1Ex9UNVo
This commit is contained in:
parent
64a5f2d51c
commit
9d1d77d849
@ -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<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
|
||||
@ -3524,7 +3543,7 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
|
||||
case eKeyPress:
|
||||
{
|
||||
WidgetKeyboardEvent* keyEvent = aEvent->AsKeyboardEvent();
|
||||
PostHandleKeyboardEvent(keyEvent, *aStatus);
|
||||
PostHandleKeyboardEvent(keyEvent, mCurrentTarget, *aStatus);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -109,7 +109,7 @@ public:
|
||||
nsEventStatus* aStatus);
|
||||
|
||||
void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
|
||||
nsEventStatus& aStatus);
|
||||
nsIFrame* aTargetFrame, nsEventStatus& aStatus);
|
||||
|
||||
/**
|
||||
* DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -2874,6 +2874,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)
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -102,7 +102,8 @@ nsIContentChild::RecvPBrowserConstructor(PBrowserChild* aActor,
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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<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 {
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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(
|
||||
|
@ -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()")
|
||||
|
||||
|
@ -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
|
||||
|
@ -257,6 +257,13 @@ public:
|
||||
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;
|
||||
@ -2404,6 +2411,17 @@ nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault)
|
||||
return sRegularRateTimer->GetIdleDeadlineHint(aDefault);
|
||||
}
|
||||
|
||||
/* static */ Maybe<TimeStamp>
|
||||
nsRefreshDriver::GetNextTickHint()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (!sRegularRateTimer) {
|
||||
return Nothing();
|
||||
}
|
||||
return sRegularRateTimer->GetNextTickHint();
|
||||
}
|
||||
|
||||
void
|
||||
nsRefreshDriver::Disconnect()
|
||||
{
|
||||
|
@ -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<mozilla::TimeStamp> GetNextTickHint();
|
||||
|
||||
static void DispatchIdleRunnableAfterTick(nsIRunnable* aRunnable,
|
||||
uint32_t aDelay);
|
||||
static void CancelIdleRunnable(nsIRunnable* aRunnable);
|
||||
|
@ -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
|
||||
|
68
xpcom/threads/InputEventStatistics.cpp
Normal file
68
xpcom/threads/InputEventStatistics.cpp
Normal file
@ -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
|
114
xpcom/threads/InputEventStatistics.h
Normal file
114
xpcom/threads/InputEventStatistics.h
Normal file
@ -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_
|
@ -513,6 +513,18 @@ 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)
|
||||
{
|
||||
|
@ -395,8 +395,17 @@ SchedulerGroup::Runnable::Run()
|
||||
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()
|
||||
|
@ -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<nsIRunnable>&& aRunnable,
|
||||
@ -95,6 +95,7 @@ public:
|
||||
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSIRUNNABLE
|
||||
NS_DECL_NSIRUNNABLEPRIORITY
|
||||
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID);
|
||||
|
||||
|
@ -71,6 +71,7 @@ UNIFIED_SOURCES += [
|
||||
'BlockingResourceBase.cpp',
|
||||
'HangAnnotations.cpp',
|
||||
'HangMonitor.cpp',
|
||||
'InputEventStatistics.cpp',
|
||||
'LazyIdleThread.cpp',
|
||||
'MainThreadIdlePeriod.cpp',
|
||||
'nsEnvironment.cpp',
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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<nsIRunnable> 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<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
|
||||
|
||||
@ -1165,6 +1277,24 @@ nsThread::IdleDispatch(already_AddRefed<nsIRunnable> 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<AutoTimeDurationHelper> timeDurationHelper;
|
||||
if (priority == nsIRunnablePriority::PRIORITY_INPUT) {
|
||||
timeDurationHelper.emplace();
|
||||
}
|
||||
event->Run();
|
||||
} else if (aMayWait) {
|
||||
MOZ_ASSERT(ShuttingDown(),
|
||||
|
@ -97,6 +97,16 @@ public:
|
||||
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);
|
||||
|
||||
@ -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<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)
|
||||
{
|
||||
@ -171,43 +197,82 @@ protected:
|
||||
}
|
||||
|
||||
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:
|
||||
mozilla::CondVar mEventsAvailable;
|
||||
mozilla::UniquePtr<nsEventQueue> mNormalQueue;
|
||||
mozilla::UniquePtr<nsEventQueue> 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<nsEventQueue> mNormalQueue;
|
||||
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
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -53,6 +53,7 @@ public:
|
||||
~nsThreadManager()
|
||||
{
|
||||
}
|
||||
void EnableMainThreadEventPrioritization();
|
||||
|
||||
private:
|
||||
nsThreadManager()
|
||||
|
Loading…
Reference in New Issue
Block a user