Bug 1830820 - Introduce some delays to user input handling r=smaug
In some edge cases, we may start handling user input events that are not supposed to be handled by the current document. This patch introduces some delay to handle user input events. Differential Revision: https://phabricator.services.mozilla.com/D180976
This commit is contained in:
@@ -1314,6 +1314,7 @@ void nsDocShell::FirePageHideShowNonRecursive(bool aShow) {
|
||||
mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
|
||||
if (mBrowsingContext->IsTop()) {
|
||||
doc->NotifyPossibleTitleChange(false);
|
||||
doc->SetLoadingOrRestoredFromBFCacheTimeStampToNow();
|
||||
if (inner) {
|
||||
// Now that we have found the inner window of the page restored
|
||||
// from the history, we have to make sure that
|
||||
|
||||
@@ -12220,7 +12220,7 @@ void Document::SetReadyStateInternal(ReadyState aReadyState,
|
||||
}
|
||||
|
||||
if (aUpdateTimingInformation && READYSTATE_LOADING == aReadyState) {
|
||||
mLoadingTimeStamp = TimeStamp::Now();
|
||||
SetLoadingOrRestoredFromBFCacheTimeStampToNow();
|
||||
}
|
||||
NotifyLoading(mAncestorIsLoading, mReadyState, aReadyState);
|
||||
mReadyState = aReadyState;
|
||||
@@ -13564,8 +13564,9 @@ nsresult Document::GetStateObject(JS::MutableHandle<JS::Value> aState) {
|
||||
|
||||
void Document::SetNavigationTiming(nsDOMNavigationTiming* aTiming) {
|
||||
mTiming = aTiming;
|
||||
if (!mLoadingTimeStamp.IsNull() && mTiming) {
|
||||
mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(), mLoadingTimeStamp);
|
||||
if (!mLoadingOrRestoredFromBFCacheTimeStamp.IsNull() && mTiming) {
|
||||
mTiming->SetDOMLoadingTimeStamp(GetDocumentURI(),
|
||||
mLoadingOrRestoredFromBFCacheTimeStamp);
|
||||
}
|
||||
|
||||
// If there's already the DocumentTimeline instance, tell it since the
|
||||
|
||||
@@ -1045,6 +1045,13 @@ class Document : public nsINode,
|
||||
|
||||
void SetLoadedAsData(bool aLoadedAsData, bool aConsiderForMemoryReporting);
|
||||
|
||||
TimeStamp GetLoadingOrRestoredFromBFCacheTimeStamp() const {
|
||||
return mLoadingOrRestoredFromBFCacheTimeStamp;
|
||||
}
|
||||
void SetLoadingOrRestoredFromBFCacheTimeStampToNow() {
|
||||
mLoadingOrRestoredFromBFCacheTimeStamp = TimeStamp::Now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Normally we assert if a runnable labeled with one DocGroup touches data
|
||||
* from another DocGroup. Calling IgnoreDocGroupMismatches() on a document
|
||||
@@ -5124,8 +5131,9 @@ class Document : public nsINode,
|
||||
|
||||
RefPtr<nsDOMNavigationTiming> mTiming;
|
||||
|
||||
// Recorded time of change to 'loading' state.
|
||||
TimeStamp mLoadingTimeStamp;
|
||||
// Recorded time of change to 'loading' state
|
||||
// or time of the page gets restored from BFCache.
|
||||
TimeStamp mLoadingOrRestoredFromBFCacheTimeStamp;
|
||||
|
||||
// Decided to use nsTObserverArray because it allows us to
|
||||
// remove candidates while iterating them and this is what
|
||||
|
||||
@@ -1983,6 +1983,26 @@ bool PresShell::SimpleResizeReflow(nscoord aWidth, nscoord aHeight) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PresShell::CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent) {
|
||||
if (XRE_IsParentProcess()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aGUIEvent->mFlags.mIsSynthesizedForTests) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!aGUIEvent->IsUserAction()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nsPresContext* rootPresContext = mPresContext->GetRootPresContext()) {
|
||||
return rootPresContext->UserInputEventsAllowed();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresShell::AddResizeEventFlushObserverIfNeeded() {
|
||||
if (!mIsDestroying && !mResizeEventPending &&
|
||||
MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
|
||||
@@ -6882,6 +6902,17 @@ nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell,
|
||||
aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Here we are granting some delays to ensure that user input events are
|
||||
// created while the page content may not be visible to the user are not
|
||||
// processed.
|
||||
// The main purpose of this is to avoid user inputs are handled in the
|
||||
// new document where as the user inputs were originally targeting some
|
||||
// content in the old document.
|
||||
if (!CanHandleUserInputEvents(aGUIEvent)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
EventHandler eventHandler(*this);
|
||||
return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent,
|
||||
aDontRetargetEvents, aEventStatus);
|
||||
@@ -9304,6 +9335,10 @@ void PresShell::Freeze(bool aIncludeSubDocuments) {
|
||||
if (presContext->RefreshDriver()->GetPresContext() == presContext) {
|
||||
presContext->RefreshDriver()->Freeze();
|
||||
}
|
||||
|
||||
if (nsPresContext* rootPresContext = presContext->GetRootPresContext()) {
|
||||
rootPresContext->ResetUserInputEventsAllowed();
|
||||
}
|
||||
}
|
||||
|
||||
mFrozen = true;
|
||||
@@ -9362,6 +9397,16 @@ void PresShell::Thaw(bool aIncludeSubDocuments) {
|
||||
UpdateImageLockingState();
|
||||
|
||||
UnsuppressPainting();
|
||||
|
||||
// In case the above UnsuppressPainting call didn't start the
|
||||
// refresh driver, we manually start the refresh driver to
|
||||
// ensure nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading
|
||||
// can be called for user input events handling.
|
||||
if (presContext && presContext->IsRoot()) {
|
||||
if (!presContext->RefreshDriver()->HasPendingTick()) {
|
||||
presContext->RefreshDriver()->InitializeTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------
|
||||
|
||||
@@ -385,6 +385,8 @@ class PresShell final : public nsStubDocumentObserver,
|
||||
*/
|
||||
bool SimpleResizeReflow(nscoord aWidth, nscoord aHeight);
|
||||
|
||||
bool CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Updates pending layout, assuming reasonable (up-to-date, or mid-update for
|
||||
|
||||
@@ -249,6 +249,7 @@ nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType)
|
||||
mAnimationTriggeredRestyles(0),
|
||||
mInterruptChecksToSkip(0),
|
||||
mNextFrameRateMultiplier(0),
|
||||
mMeasuredTicksSinceLoading(0),
|
||||
mViewportScrollStyles(StyleOverflow::Auto, StyleOverflow::Auto),
|
||||
// mImageAnimationMode is initialised below, in constructor body
|
||||
mImageAnimationModePref(imgIContainer::kNormalAnimMode),
|
||||
@@ -285,6 +286,7 @@ nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType)
|
||||
mHadFirstContentfulPaint(false),
|
||||
mHadNonTickContentfulPaint(false),
|
||||
mHadContentfulPaintComposite(false),
|
||||
mUserInputEventsAllowed(false),
|
||||
#ifdef DEBUG
|
||||
mInitialized(false),
|
||||
#endif
|
||||
@@ -1236,6 +1238,69 @@ nsRootPresContext* nsPresContext::GetRootPresContext() const {
|
||||
return pc->IsRoot() ? static_cast<nsRootPresContext*>(pc) : nullptr;
|
||||
}
|
||||
|
||||
bool nsPresContext::UserInputEventsAllowed() {
|
||||
MOZ_ASSERT(IsRoot());
|
||||
if (mUserInputEventsAllowed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Special document
|
||||
if (Document()->IsInitialDocument()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mMeasuredTicksSinceLoading <
|
||||
StaticPrefs::dom_input_events_security_minNumTicks()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StaticPrefs::dom_input_events_security_minTimeElapsedInMS()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
dom::Document* doc = Document();
|
||||
|
||||
MOZ_ASSERT_IF(StaticPrefs::dom_input_events_security_minNumTicks(),
|
||||
doc->GetReadyStateEnum() >= Document::READYSTATE_LOADING);
|
||||
|
||||
TimeStamp loadingOrRestoredFromBFCacheTime =
|
||||
doc->GetLoadingOrRestoredFromBFCacheTimeStamp();
|
||||
MOZ_ASSERT(!loadingOrRestoredFromBFCacheTime.IsNull());
|
||||
|
||||
TimeDuration elapsed = TimeStamp::Now() - loadingOrRestoredFromBFCacheTime;
|
||||
if (elapsed.ToMilliseconds() >=
|
||||
StaticPrefs::dom_input_events_security_minTimeElapsedInMS()) {
|
||||
mUserInputEventsAllowed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading() {
|
||||
MOZ_ASSERT(IsRoot());
|
||||
if (mMeasuredTicksSinceLoading >=
|
||||
StaticPrefs::dom_input_events_security_minNumTicks()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We consider READYSTATE_LOADING is the point when the page
|
||||
// becomes interactive
|
||||
if (Document()->GetReadyStateEnum() >= Document::READYSTATE_LOADING ||
|
||||
Document()->IsInitialDocument()) {
|
||||
++mMeasuredTicksSinceLoading;
|
||||
}
|
||||
|
||||
if (mMeasuredTicksSinceLoading <
|
||||
StaticPrefs::dom_input_events_security_minNumTicks()) {
|
||||
// Here we are forcing refresh driver to run because we can't always
|
||||
// guarantee refresh driver will run enough times to meet the minNumTicks
|
||||
// requirement. i.e. about:blank.
|
||||
if (!RefreshDriver()->HasPendingTick()) {
|
||||
RefreshDriver()->InitializeTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Helper function for setting Anim Mode on image
|
||||
static void SetImgAnimModeOnImgReq(imgIRequest* aImgReq, uint16_t aMode) {
|
||||
if (aImgReq) {
|
||||
|
||||
@@ -257,7 +257,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
*/
|
||||
nsRootPresContext* GetRootPresContext() const;
|
||||
|
||||
virtual bool IsRoot() { return false; }
|
||||
virtual bool IsRoot() const { return false; }
|
||||
|
||||
mozilla::dom::Document* Document() const {
|
||||
#ifdef DEBUG
|
||||
@@ -512,6 +512,16 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
nsDeviceContext* DeviceContext() const { return mDeviceContext; }
|
||||
mozilla::EventStateManager* EventStateManager() { return mEventManager; }
|
||||
|
||||
bool UserInputEventsAllowed();
|
||||
|
||||
void MaybeIncreaseMeasuredTicksSinceLoading();
|
||||
|
||||
void ResetUserInputEventsAllowed() {
|
||||
MOZ_ASSERT(IsRoot());
|
||||
mMeasuredTicksSinceLoading = 0;
|
||||
mUserInputEventsAllowed = false;
|
||||
}
|
||||
|
||||
// Get the text zoom factor in use.
|
||||
float TextZoom() const { return mTextZoom; }
|
||||
|
||||
@@ -1254,6 +1264,8 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
// During page load we use slower frame rate.
|
||||
uint32_t mNextFrameRateMultiplier;
|
||||
|
||||
uint32_t mMeasuredTicksSinceLoading;
|
||||
|
||||
nsTArray<RefPtr<mozilla::ManagedPostRefreshObserver>>
|
||||
mManagedPostRefreshObservers;
|
||||
|
||||
@@ -1347,6 +1359,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
// Has NotifyDidPaintForSubtree been called for a contentful paint?
|
||||
unsigned mHadContentfulPaintComposite : 1;
|
||||
|
||||
unsigned mUserInputEventsAllowed : 1;
|
||||
#ifdef DEBUG
|
||||
unsigned mInitialized : 1;
|
||||
#endif
|
||||
@@ -1379,7 +1392,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
|
||||
class nsRootPresContext final : public nsPresContext {
|
||||
public:
|
||||
nsRootPresContext(mozilla::dom::Document* aDocument, nsPresContextType aType);
|
||||
virtual bool IsRoot() override { return true; }
|
||||
virtual bool IsRoot() const override { return true; }
|
||||
|
||||
/**
|
||||
* Add a runnable that will get called before the next paint. They will get
|
||||
|
||||
@@ -2248,6 +2248,12 @@ void nsRefreshDriver::FlushAutoFocusDocuments() {
|
||||
}
|
||||
}
|
||||
|
||||
void nsRefreshDriver::MaybeIncreaseMeasuredTicksSinceLoading() {
|
||||
if (mPresContext && mPresContext->IsRoot()) {
|
||||
mPresContext->MaybeIncreaseMeasuredTicksSinceLoading();
|
||||
}
|
||||
}
|
||||
|
||||
void nsRefreshDriver::CancelFlushAutoFocus(Document* aDocument) {
|
||||
mAutoFocusFlushDocuments.RemoveElement(aDocument);
|
||||
}
|
||||
@@ -2693,6 +2699,7 @@ void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
|
||||
DispatchAnimationEvents();
|
||||
RunFullscreenSteps();
|
||||
RunFrameRequestCallbacks(aNowTime);
|
||||
MaybeIncreaseMeasuredTicksSinceLoading();
|
||||
|
||||
if (mPresContext && mPresContext->GetPresShell()) {
|
||||
AutoTArray<PresShell*, 16> observers;
|
||||
|
||||
@@ -492,6 +492,7 @@ class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
|
||||
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
|
||||
void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime);
|
||||
void UpdateRelevancyOfContentVisibilityAutoFrames();
|
||||
void MaybeIncreaseMeasuredTicksSinceLoading();
|
||||
void EvaluateMediaQueriesAndReportChanges();
|
||||
|
||||
enum class IsExtraTick {
|
||||
|
||||
@@ -2799,6 +2799,20 @@
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# The minimum number of ticks after page navigation
|
||||
# that need to occur before user input events are allowed to be handled.
|
||||
- name: dom.input_events.security.minNumTicks
|
||||
type: uint32_t
|
||||
value: 3
|
||||
mirror: always
|
||||
|
||||
# The minimum elapsed time (in milliseconds) after page navigation
|
||||
# for user input events are allowed to be handled.
|
||||
- name: dom.input_events.security.minTimeElapsedInMS
|
||||
type: uint32_t
|
||||
value: 100
|
||||
mirror: always
|
||||
|
||||
# The maximum time (milliseconds) we reserve for handling input events in each
|
||||
# frame.
|
||||
- name: dom.input_event_queue.duration.max
|
||||
|
||||
@@ -6,3 +6,5 @@
|
||||
/* globals user_pref */
|
||||
// ensure webrender is set (and we don't need MOZ_WEBRENDER env variable)
|
||||
user_pref("gfx.webrender.all", true);
|
||||
user_pref("dom.input_events.security.minNumTicks", 0);
|
||||
user_pref("dom.input_events.security.minTimeElapsedInMS", 0);
|
||||
|
||||
Reference in New Issue
Block a user