Bug 1351148 Part2: Add a priority queue for input events. r=smaug.

MozReview-Commit-ID: 5ud1Ex9UNVo
This commit is contained in:
Stone Shih 2017-03-21 15:44:12 +08:00
parent 1467938d67
commit de7f705042
36 changed files with 750 additions and 109 deletions

View File

@ -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;

View File

@ -109,7 +109,7 @@ public:
nsEventStatus* aStatus);
void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
nsEventStatus& aStatus);
nsIFrame* aTargetFrame, nsEventStatus& aStatus);
/**
* DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll

View File

@ -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()
{

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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);
/**

View File

@ -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);

View File

@ -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()
{

View File

@ -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);
};

View File

@ -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();
}

View File

@ -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)

View File

@ -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);

View File

@ -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)

View File

@ -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
};

View File

@ -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;
}

View File

@ -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):

View File

@ -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(

View File

@ -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()")

View File

@ -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

View File

@ -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()
{

View File

@ -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);

View File

@ -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

View 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

View 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_

View File

@ -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)
{

View File

@ -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()

View File

@ -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);

View File

@ -74,6 +74,7 @@ UNIFIED_SOURCES += [
'BlockingResourceBase.cpp',
'HangAnnotations.cpp',
'HangMonitor.cpp',
'InputEventStatistics.cpp',
'LazyIdleThread.cpp',
'MainThreadIdlePeriod.cpp',
'nsEnvironment.cpp',

View File

@ -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;
};

View File

@ -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

View File

@ -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(),

View File

@ -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

View File

@ -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)
{

View File

@ -53,6 +53,7 @@ public:
~nsThreadManager()
{
}
void EnableMainThreadEventPrioritization();
private:
nsThreadManager()