diff --git a/docshell/base/BrowsingContextGroup.cpp b/docshell/base/BrowsingContextGroup.cpp index 930d4fb6a265..b6cf50cf5d2c 100644 --- a/docshell/base/BrowsingContextGroup.cpp +++ b/docshell/base/BrowsingContextGroup.cpp @@ -7,6 +7,7 @@ #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/ClearOnShutdown.h" +#include "mozilla/InputTaskManager.h" #include "mozilla/dom/BrowsingContextBinding.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/ContentChild.h" @@ -319,6 +320,36 @@ void BrowsingContextGroup::FlushPostMessageEvents() { } } +void BrowsingContextGroup::IncInputEventSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled_AtStartup()); + if (!mInputEventSuspensionLevel && !InputTaskManager::Get()->IsSuspended()) { + InputTaskManager::Get()->SetIsSuspended(true); + } + ++mInputEventSuspensionLevel; +} + +void BrowsingContextGroup::DecInputEventSuspensionLevel() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled_AtStartup()); + --mInputEventSuspensionLevel; + if (!mInputEventSuspensionLevel && InputTaskManager::Get()->IsSuspended()) { + InputTaskManager::Get()->SetIsSuspended(false); + } +} + +void BrowsingContextGroup::UpdateInputTaskManagerIfNeeded() { + MOZ_ASSERT(StaticPrefs::dom_input_events_canSuspendInBCG_enabled_AtStartup()); + InputTaskManager* inputManager = InputTaskManager::Get(); + + if (mInputEventSuspensionLevel && !inputManager->IsSuspended()) { + inputManager->SetIsSuspended(true); + return; + } + + if (!mInputEventSuspensionLevel && inputManager->IsSuspended()) { + inputManager->SetIsSuspended(false); + } +} + /* static */ BrowsingContextGroup* BrowsingContextGroup::GetChromeGroup() { MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); diff --git a/docshell/base/BrowsingContextGroup.h b/docshell/base/BrowsingContextGroup.h index ca1b26b987af..e1dd0da8176e 100644 --- a/docshell/base/BrowsingContextGroup.h +++ b/docshell/base/BrowsingContextGroup.h @@ -126,6 +126,11 @@ class BrowsingContextGroup final : public nsWrapperCache { void FlushPostMessageEvents(); + // Update the status of the InputTaskManager based on + // 1.Its suspension status + // 2.Which BCG sets the suspension status. + void UpdateInputTaskManagerIfNeeded(); + static BrowsingContextGroup* GetChromeGroup(); void GetDocGroups(nsTArray& aDocGroups); @@ -149,6 +154,9 @@ class BrowsingContextGroup final : public nsWrapperCache { static void GetAllGroups(nsTArray>& aGroups); + void IncInputEventSuspensionLevel(); + void DecInputEventSuspensionLevel(); + private: friend class CanonicalBrowsingContext; @@ -206,8 +214,14 @@ class BrowsingContextGroup final : public nsWrapperCache { RefPtr mTimerEventQueue; RefPtr mWorkerEventQueue; -}; + // A counter to keep track of the input event suspension level of this BCG + // + // We use BrowsingContextGroup to emulate process isolation in Fission, so + // documents within the same the same BCG will behave like they share + // the same input task queue. + uint32_t mInputEventSuspensionLevel = 0; +}; } // namespace dom } // namespace mozilla diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index b4438c11349f..7ac0793e96d8 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -4836,6 +4836,18 @@ nsDocShell::SetIsActive(bool aIsActive) { } } + // When switching between tabs, there are always docShells that become + // active in the same process if they are same process tabs, so we just + // let the docShells that are becoming active to update the + // InputTaskManager if needed, because it's going to be duplicate works + // to ask both active and inactive docShells to do it. + // + // If the tabs are in different processes, we still don't need to call + // UpdateInputTaskManagerIfNeeded because the it's okay to keep + // the input events suspended for background tabs. + if (aIsActive && InputTaskManager::CanSuspendInputEvent()) { + mBrowsingContext->Group()->UpdateInputTaskManagerIfNeeded(); + } return NS_OK; } diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 2ebd6cefd36c..62b7c61b0440 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -70,6 +70,7 @@ #include "mozilla/HTMLEditor.h" #include "mozilla/HoldDropJSObjects.h" #include "mozilla/IdentifierMapEntry.h" +#include "mozilla/InputTaskManager.h" #include "mozilla/IntegerRange.h" #include "mozilla/InternalMutationEvent.h" #include "mozilla/Likely.h" @@ -15603,7 +15604,9 @@ static CallState MarkDocumentTreeToBeInSyncOperation( return CallState::Continue; } -nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc) { +nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc, + SyncOperationBehavior aSyncBehavior) + : mSyncBehavior(aSyncBehavior) { mMicroTaskLevel = 0; CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); if (ccjs) { @@ -15618,6 +15621,13 @@ nsAutoSyncOperation::nsAutoSyncOperation(Document* aDoc) { } } } + + mBrowsingContext = aDoc->GetBrowsingContext(); + if (mBrowsingContext && + mSyncBehavior == SyncOperationBehavior::eSuspendInput && + InputTaskManager::CanSuspendInputEvent()) { + mBrowsingContext->Group()->IncInputEventSuspensionLevel(); + } } } @@ -15632,6 +15642,12 @@ nsAutoSyncOperation::~nsAutoSyncOperation() { if (ccjs) { ccjs->SetMicroTaskLevel(mMicroTaskLevel); } + + if (mBrowsingContext && + mSyncBehavior == SyncOperationBehavior::eSuspendInput && + InputTaskManager::CanSuspendInputEvent()) { + mBrowsingContext->Group()->DecInputEventSuspensionLevel(); + } } gfxUserFontSet* Document::GetUserFontSet() { diff --git a/dom/base/Document.h b/dom/base/Document.h index 6ee07c9fa207..bc23271c1852 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -5243,14 +5243,19 @@ class MOZ_STACK_CLASS mozAutoSubtreeModified { RefPtr mSubtreeOwner; }; +enum class SyncOperationBehavior { eSuspendInput, eAllowInput }; + class MOZ_STACK_CLASS nsAutoSyncOperation { public: - explicit nsAutoSyncOperation(Document* aDocument); + explicit nsAutoSyncOperation(Document* aDocument, + SyncOperationBehavior aSyncBehavior); ~nsAutoSyncOperation(); private: nsTArray> mDocuments; uint32_t mMicroTaskLevel; + const SyncOperationBehavior mSyncBehavior; + RefPtr mBrowsingContext; }; class MOZ_RAII AutoSetThrowOnDynamicMarkupInsertionCounter final { diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 8dfb099bbfc0..23f3b7e41958 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -28,6 +28,7 @@ #include "mozilla/PendingAnimationTracker.h" #include "mozilla/ServoStyleSet.h" #include "mozilla/SharedStyleSheetCache.h" +#include "mozilla/InputTaskManager.h" #include "nsIObjectLoadingContent.h" #include "nsIFrame.h" #include "mozilla/layers/APZCCallbackHelper.h" @@ -1420,6 +1421,12 @@ nsDOMWindowUtils::GetIsMozAfterPaintPending(bool* aResult) { return NS_OK; } +NS_IMETHODIMP +nsDOMWindowUtils::GetIsInputTaskManagerSuspended(bool* aResult) { + *aResult = InputTaskManager::Get()->IsSuspended(); + return NS_OK; +} + NS_IMETHODIMP nsDOMWindowUtils::DisableNonTestMouseEvents(bool aDisable) { nsCOMPtr window = do_QueryReferent(mWindow); diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp index 5bd31420bd9f..96b93d8c3d11 100644 --- a/dom/base/nsGlobalWindowOuter.cpp +++ b/dom/base/nsGlobalWindowOuter.cpp @@ -4943,7 +4943,7 @@ bool nsGlobalWindowOuter::AlertOrConfirm(bool aAlert, const nsAString& aMessage, } bool result = false; - nsAutoSyncOperation sync(mDoc); + nsAutoSyncOperation sync(mDoc, SyncOperationBehavior::eSuspendInput); if (ShouldPromptToBlockDialogs()) { bool disallowDialog = false; nsAutoString label; @@ -5043,7 +5043,7 @@ void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage, nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label); } - nsAutoSyncOperation sync(mDoc); + nsAutoSyncOperation sync(mDoc, SyncOperationBehavior::eSuspendInput); bool ok; aError = prompt->Prompt(title.get(), fixedMessage.get(), &inoutValue, label.IsVoid() ? nullptr : label.get(), @@ -5287,7 +5287,7 @@ Nullable nsGlobalWindowOuter::Print( return nullptr; } - nsAutoSyncOperation sync(docToPrint); + nsAutoSyncOperation sync(docToPrint, SyncOperationBehavior::eAllowInput); EnterModalState(); auto exitModal = MakeScopeExit([&] { LeaveModalState(); }); diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 290e225f8db4..0a7c93cae952 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -821,6 +821,11 @@ interface nsIDOMWindowUtils : nsISupports { */ readonly attribute boolean isMozAfterPaintPending; + /** + * Returns true if the InputTaskManager is suspended. + */ + readonly attribute boolean isInputTaskManagerSuspended; + /** * Suppresses/unsuppresses user initiated event handling in window's document * and subdocuments. diff --git a/dom/xhr/XMLHttpRequestMainThread.cpp b/dom/xhr/XMLHttpRequestMainThread.cpp index bc7bb3af2b1a..78afb77dcb3e 100644 --- a/dom/xhr/XMLHttpRequestMainThread.cpp +++ b/dom/xhr/XMLHttpRequestMainThread.cpp @@ -3032,7 +3032,8 @@ nsresult XMLHttpRequestMainThread::SendInternal(const BodyExtractorBase* aBody, } if (NS_SUCCEEDED(rv)) { - nsAutoSyncOperation sync(mSuspendedDoc); + nsAutoSyncOperation sync(mSuspendedDoc, + SyncOperationBehavior::eSuspendInput); if (!SpinEventLoopUntil([&]() { return !mFlagSyncLooping; })) { rv = NS_ERROR_UNEXPECTED; } diff --git a/dom/xhr/tests/file_sync_xhr_event_handling_helper.html b/dom/xhr/tests/file_sync_xhr_event_handling_helper.html new file mode 100644 index 000000000000..3aacb242adf2 --- /dev/null +++ b/dom/xhr/tests/file_sync_xhr_event_handling_helper.html @@ -0,0 +1,40 @@ + + + + + + + diff --git a/dom/xhr/tests/file_sync_xhr_event_handling_switch_bcg_helper.html b/dom/xhr/tests/file_sync_xhr_event_handling_switch_bcg_helper.html new file mode 100644 index 000000000000..97dd4bb61b1a --- /dev/null +++ b/dom/xhr/tests/file_sync_xhr_event_handling_switch_bcg_helper.html @@ -0,0 +1,14 @@ + + + + + + diff --git a/dom/xhr/tests/file_sync_xhr_nested_helper.html b/dom/xhr/tests/file_sync_xhr_nested_helper.html new file mode 100644 index 000000000000..6194de33eaf9 --- /dev/null +++ b/dom/xhr/tests/file_sync_xhr_nested_helper.html @@ -0,0 +1,33 @@ + + + + + + + diff --git a/dom/xhr/tests/mochitest.ini b/dom/xhr/tests/mochitest.ini index 9743669c72bd..332f8c397d84 100644 --- a/dom/xhr/tests/mochitest.ini +++ b/dom/xhr/tests/mochitest.ini @@ -108,6 +108,18 @@ support-files = test_XHR_timeout.js [test_XHRSendData.html] [test_sync_xhr_document_write_with_iframe.html] skip-if = toolkit == "android" && debug && !is_fennec +[test_sync_xhr_event_handling.html] +support-files = + file_sync_xhr_event_handling_helper.html +fail-if = !e10s || release_or_beta +[test_sync_xhr_nested.html] +support-files = + file_sync_xhr_nested_helper.html +skip-if = !e10s || release_or_beta # Input event will be discarded during sync XHR, thus timeout +[test_sync_xhr_event_handling_switch_bcg.html] +support-files = + file_sync_xhr_event_handling_switch_bcg_helper.html +fail-if = !e10s || release_or_beta [test_nestedSyncXHR.html] [test_event_listener_leaks.html] skip-if = (os == "win" && processor == "aarch64") #bug 1535784 diff --git a/dom/xhr/tests/test_sync_xhr_event_handling.html b/dom/xhr/tests/test_sync_xhr_event_handling.html new file mode 100644 index 000000000000..8bdaafea6de6 --- /dev/null +++ b/dom/xhr/tests/test_sync_xhr_event_handling.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/dom/xhr/tests/test_sync_xhr_event_handling_switch_bcg.html b/dom/xhr/tests/test_sync_xhr_event_handling_switch_bcg.html new file mode 100644 index 000000000000..7d85155f27b4 --- /dev/null +++ b/dom/xhr/tests/test_sync_xhr_event_handling_switch_bcg.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/dom/xhr/tests/test_sync_xhr_nested.html b/dom/xhr/tests/test_sync_xhr_nested.html new file mode 100644 index 000000000000..8149807a875e --- /dev/null +++ b/dom/xhr/tests/test_sync_xhr_nested.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + diff --git a/dom/xslt/xml/txXMLParser.cpp b/dom/xslt/xml/txXMLParser.cpp index a4b827e42a26..b769d6f17e42 100644 --- a/dom/xslt/xml/txXMLParser.cpp +++ b/dom/xslt/xml/txXMLParser.cpp @@ -33,7 +33,8 @@ nsresult txParseDocumentFromURI(const nsAString& aHref, // Raw pointer, we want the resulting txXPathNode to hold a reference to // the document. Document* theDocument = nullptr; - nsAutoSyncOperation sync(loaderDocument); + nsAutoSyncOperation sync(loaderDocument, + SyncOperationBehavior::eSuspendInput); rv = nsSyncLoadService::LoadDocument( documentURI, nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST, loaderDocument->NodePrincipal(), diff --git a/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp index 5386a277d91f..55fcb15cbf72 100644 --- a/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp +++ b/dom/xslt/xslt/txMozillaStylesheetCompiler.cpp @@ -557,7 +557,8 @@ nsresult txSyncCompileObserver::loadURI(const nsAString& aUri, if (mProcessor) { source = mProcessor->GetSourceContentModel(); } - dom::nsAutoSyncOperation sync(source ? source->OwnerDoc() : nullptr); + dom::nsAutoSyncOperation sync(source ? source->OwnerDoc() : nullptr, + dom::SyncOperationBehavior::eSuspendInput); nsCOMPtr document; rv = nsSyncLoadService::LoadDocument( diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp index 3417830d2068..6166e8815672 100644 --- a/layout/base/PresShell.cpp +++ b/layout/base/PresShell.cpp @@ -200,6 +200,7 @@ #include "mozilla/ServoStyleSet.h" #include "mozilla/StyleSheet.h" #include "mozilla/StyleSheetInlines.h" +#include "mozilla/InputTaskManager.h" #include "mozilla/dom/ImageTracker.h" #include "nsIDocShellTreeOwner.h" #include "nsClassHashtable.h" @@ -7590,6 +7591,10 @@ bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent( return false; } + MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() && + aGUIEvent->mMessage != eMouseMove, + !InputTaskManager::Get()->IsSuspended()); + if (aGUIEvent->mMessage == eKeyDown) { mPresShell->mNoDelayedKeyEvents = true; } else if (!mPresShell->mNoDelayedKeyEvents) { @@ -7616,6 +7621,10 @@ bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent( return false; } + MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() && + aGUIEvent->mMessage != eMouseMove, + !InputTaskManager::Get()->IsSuspended()); + if (aGUIEvent->mMessage == eMouseDown) { mPresShell->mNoDelayedMouseEvents = true; } else if (!mPresShell->mNoDelayedMouseEvents && diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp index a7d984fabcc4..832b7f425068 100644 --- a/layout/base/nsDocumentViewer.cpp +++ b/layout/base/nsDocumentViewer.cpp @@ -1261,7 +1261,7 @@ nsDocumentViewer::PermitUnload(PermitUnloadAction aAction, return NS_OK; } - nsAutoSyncOperation sync(mDocument); + nsAutoSyncOperation sync(mDocument, SyncOperationBehavior::eSuspendInput); AutoSuppressEventHandlingAndSuspend seh(bc->Group()); mInPermitUnloadPrompt = true; diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 0932416927ff..9a1daa9a1e01 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -1951,6 +1951,12 @@ value: true mirror: always +# Whether we allow BrowsingContextGroup to suspend input events +- name: dom.input_events.canSuspendInBCG.enabled + type: bool + value: @IS_NIGHTLY_BUILD@ + mirror: once + # Enable not moving the cursor to end when a text input or textarea has .value # set to the value it already has. By default, enabled. - name: dom.input.skip_cursor_move_for_same_value_set diff --git a/xpcom/threads/IdlePeriodState.h b/xpcom/threads/IdlePeriodState.h index a21335ada8ba..58303b454bca 100644 --- a/xpcom/threads/IdlePeriodState.h +++ b/xpcom/threads/IdlePeriodState.h @@ -172,7 +172,7 @@ class IdlePeriodState { // If we're in a content process, we use mIdleScheduler to communicate with // the parent process for purposes of cross-process idle tracking. - RefPtr mIdleScheduler; + RefPtr mIdleScheduler; // Our cached idle deadline. This is set by UpdateCachedIdleDeadline() and // cleared by ClearCachedIdleDeadline(). Consumers should do the former while diff --git a/xpcom/threads/InputTaskManager.h b/xpcom/threads/InputTaskManager.h index 06665f2f0074..bab477178c45 100644 --- a/xpcom/threads/InputTaskManager.h +++ b/xpcom/threads/InputTaskManager.h @@ -9,6 +9,7 @@ #include "TaskController.h" #include "mozilla/StaticPtr.h" +#include "mozilla/StaticPrefs_dom.h" namespace mozilla { @@ -45,6 +46,34 @@ class InputTaskManager : public TaskManager { static void Cleanup() { gInputTaskManager = nullptr; } static void Init(); + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + MOZ_ASSERT(NS_IsMainThread()); + return mIsSuspended; + } + + bool IsSuspended() { + MOZ_ASSERT(NS_IsMainThread()); + return mIsSuspended; + } + + void SetIsSuspended(bool aIsSuspended) { + MOZ_ASSERT(NS_IsMainThread()); + mIsSuspended = aIsSuspended; + } + + static bool CanSuspendInputEvent() { + // Ensure it's content process because InputTaskManager only + // works in e10s. + // + // Input tasks will have nullptr as their task manager when the + // event queue state is STATE_DISABLED, so we can't suspend + // input events. + return XRE_IsContentProcess() && + StaticPrefs::dom_input_events_canSuspendInBCG_enabled_AtStartup() && + InputTaskManager::Get()->State() != + InputEventQueueState::STATE_DISABLED; + } + private: InputTaskManager() : mInputQueueState(STATE_DISABLED) {} @@ -53,6 +82,10 @@ class InputTaskManager : public TaskManager { AutoTArray mStartTimes; static StaticRefPtr gInputTaskManager; + + // Unlike mInputQueueState, mIsSuspended is used by TaskController to + // indicate its status + bool mIsSuspended = false; }; } // namespace mozilla