From 19e88f0bf47a07419420f8bb58c180a47a469c7e Mon Sep 17 00:00:00 2001 From: Ehsan Akhgari Date: Tue, 9 Oct 2018 21:42:22 +0000 Subject: [PATCH] Bug 1491403 - Part 3: Propagate the user input event handling state to the promise resolve handlers in case the promise creator requests it r=smaug,arai,baku Depends on D7004 Differential Revision: https://phabricator.services.mozilla.com/D7005 --HG-- extra : moz-landing-system : lando --- dom/promise/Promise.cpp | 50 +++++++++++++++++++++----- dom/promise/Promise.h | 46 +++++++++++++++++++++--- xpcom/base/CycleCollectedJSContext.cpp | 34 ++++++++++++++---- 3 files changed, 110 insertions(+), 20 deletions(-) diff --git a/dom/promise/Promise.cpp b/dom/promise/Promise.cpp index f4480ebb53b5..02d529a22997 100644 --- a/dom/promise/Promise.cpp +++ b/dom/promise/Promise.cpp @@ -11,9 +11,11 @@ #include "mozilla/Atomics.h" #include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/EventStateManager.h" #include "mozilla/OwningNonNull.h" #include "mozilla/Preferences.h" #include "mozilla/ResultExtensions.h" +#include "mozilla/Unused.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/DOMException.h" @@ -89,24 +91,39 @@ Promise::~Promise() // static already_AddRefed -Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv) +Promise::Create(nsIGlobalObject* aGlobal, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction) { if (!aGlobal) { aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } RefPtr p = new Promise(aGlobal); - p->CreateWrapper(nullptr, aRv); + p->CreateWrapper(nullptr, aRv, aPropagateUserInteraction); if (aRv.Failed()) { return nullptr; } return p.forget(); } +bool +Promise::MaybePropagateUserInputEventHandling() +{ + JS::PromiseUserInputEventHandlingState state = + EventStateManager::IsHandlingUserInput() ? + JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation : + JS::PromiseUserInputEventHandlingState::DidntHaveUserInteractionAtCreation; + JS::Rooted p(RootingCx(), mPromiseObj); + return JS::SetPromiseUserInputEventHandlingState(p, state); +} + // static already_AddRefed Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, - JS::Handle aValue, ErrorResult& aRv) + JS::Handle aValue, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction) { JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject()); JS::Rooted p(aCx, @@ -116,7 +133,7 @@ Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, return nullptr; } - return CreateFromExisting(aGlobal, p); + return CreateFromExisting(aGlobal, p, aPropagateUserInteraction); } // static @@ -132,13 +149,18 @@ Promise::Reject(nsIGlobalObject* aGlobal, JSContext* aCx, return nullptr; } - return CreateFromExisting(aGlobal, p); + // This promise will never be resolved, so we pass + // eDontPropagateUserInteraction for aPropagateUserInteraction + // unconditionally. + return CreateFromExisting(aGlobal, p, eDontPropagateUserInteraction); } // static already_AddRefed Promise::All(JSContext* aCx, - const nsTArray>& aPromiseList, ErrorResult& aRv) + const nsTArray>& aPromiseList, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction) { JS::Rooted globalObj(aCx, JS::CurrentGlobalOrNull(aCx)); if (!globalObj) { @@ -174,7 +196,7 @@ Promise::All(JSContext* aCx, return nullptr; } - return CreateFromExisting(global, result); + return CreateFromExisting(global, result, aPropagateUserInteraction); } void @@ -271,7 +293,9 @@ Promise::ThenWithoutCycleCollection( } void -Promise::CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv) +Promise::CreateWrapper(JS::Handle aDesiredProto, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction) { AutoJSAPI jsapi; if (!jsapi.Init(mGlobal)) { @@ -285,6 +309,9 @@ Promise::CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv) aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } + if (aPropagateUserInteraction == ePropagateUserInteraction) { + Unused << MaybePropagateUserInputEventHandling(); + } } void @@ -491,12 +518,17 @@ Promise::HandleException(JSContext* aCx) // static already_AddRefed Promise::CreateFromExisting(nsIGlobalObject* aGlobal, - JS::Handle aPromiseObj) + JS::Handle aPromiseObj, + PropagateUserInteraction aPropagateUserInteraction) { MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) == js::GetObjectCompartment(aPromiseObj)); RefPtr p = new Promise(aGlobal); p->mPromiseObj = aPromiseObj; + if (aPropagateUserInteraction == ePropagateUserInteraction && + !p->MaybePropagateUserInputEventHandling()) { + return nullptr; + } return p.forget(); } diff --git a/dom/promise/Promise.h b/dom/promise/Promise.h index dddc45f93181..4d87df672b81 100644 --- a/dom/promise/Promise.h +++ b/dom/promise/Promise.h @@ -51,12 +51,24 @@ public: NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise) MOZ_DECLARE_WEAKREFERENCE_TYPENAME(Promise) + enum PropagateUserInteraction + { + eDontPropagateUserInteraction, + ePropagateUserInteraction + }; + // Promise creation tries to create a JS reflector for the Promise, so is // fallible. Furthermore, we don't want to do JS-wrapping on a 0-refcount // object, so we addref before doing that and return the addrefed pointer // here. + // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want + // the promise resolve handler to be called as if we were handling user + // input events in case we are currently handling user input events. static already_AddRefed - Create(nsIGlobalObject* aGlobal, ErrorResult& aRv); + Create(nsIGlobalObject* aGlobal, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction = + eDontPropagateUserInteraction); // Reports a rejected Promise by sending an error report. static void ReportRejectedPromise(JSContext* aCx, JS::HandleObject aPromise); @@ -116,9 +128,15 @@ public: // Do the equivalent of Promise.resolve in the compartment of aGlobal. The // compartment of aCx is ignored. Errors are reported on the ErrorResult; if // aRv comes back !Failed(), this function MUST return a non-null value. + // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want + // the promise resolve handler to be called as if we were handling user + // input events in case we are currently handling user input events. static already_AddRefed Resolve(nsIGlobalObject* aGlobal, JSContext* aCx, - JS::Handle aValue, ErrorResult& aRv); + JS::Handle aValue, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction = + eDontPropagateUserInteraction); // Do the equivalent of Promise.reject in the compartment of aGlobal. The // compartment of aCx is ignored. Errors are reported on the ErrorResult; if @@ -130,9 +148,14 @@ public: // Do the equivalent of Promise.all in the current compartment of aCx. Errors // are reported on the ErrorResult; if aRv comes back !Failed(), this function // MUST return a non-null value. + // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want + // the promise resolve handler to be called as if we were handling user + // input events in case we are currently handling user input events. static already_AddRefed All(JSContext* aCx, const nsTArray>& aPromiseList, - ErrorResult& aRv); + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction = + eDontPropagateUserInteraction); void Then(JSContext* aCx, @@ -193,9 +216,14 @@ public: // Create a dom::Promise from a given SpiderMonkey Promise object. // aPromiseObj MUST be in the compartment of aGlobal's global JS object. + // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want + // the promise resolve handler to be called as if we were handling user + // input events in case we are currently handling user input events. static already_AddRefed CreateFromExisting(nsIGlobalObject* aGlobal, - JS::Handle aPromiseObj); + JS::Handle aPromiseObj, + PropagateUserInteraction aPropagateUserInteraction = + eDontPropagateUserInteraction); enum class PromiseState { Pending, @@ -217,7 +245,13 @@ protected: // Do JS-wrapping after Promise creation. Passing null for aDesiredProto will // use the default prototype for the sort of Promise we have. - void CreateWrapper(JS::Handle aDesiredProto, ErrorResult& aRv); + // Pass ePropagateUserInteraction for aPropagateUserInteraction if you want + // the promise resolve handler to be called as if we were handling user + // input events in case we are currently handling user input events. + void CreateWrapper(JS::Handle aDesiredProto, + ErrorResult& aRv, + PropagateUserInteraction aPropagateUserInteraction = + eDontPropagateUserInteraction); private: template @@ -238,6 +272,8 @@ private: void HandleException(JSContext* aCx); + bool MaybePropagateUserInputEventHandling(); + RefPtr mGlobal; JS::Heap mPromiseObj; diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp index f409b5478b99..2a2168d2e226 100644 --- a/xpcom/base/CycleCollectedJSContext.cpp +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -9,6 +9,7 @@ #include "mozilla/ArrayUtils.h" #include "mozilla/AutoRestore.h" #include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/EventStateManager.h" #include "mozilla/Move.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Sprintf.h" @@ -20,7 +21,6 @@ #include "mozilla/dom/DOMJSClass.h" #include "mozilla/dom/DOMException.h" #include "mozilla/dom/ProfileTimelineMarkerBinding.h" -#include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseBinding.h" #include "mozilla/dom/PromiseDebugging.h" #include "mozilla/dom/ScriptSettings.h" @@ -35,6 +35,7 @@ #include "nsDOMJSUtils.h" #include "nsDOMMutationObserver.h" #include "nsJSUtils.h" +#include "nsPIDOMWindow.h" #include "nsWrapperCache.h" #include "nsStringBuffer.h" @@ -203,15 +204,24 @@ CycleCollectedJSContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const class PromiseJobRunnable final : public MicroTaskRunnable { public: - PromiseJobRunnable(JS::HandleObject aCallback, + PromiseJobRunnable(JS::HandleObject aPromise, + JS::HandleObject aCallback, JS::HandleObject aCallbackGlobal, JS::HandleObject aAllocationSite, nsIGlobalObject* aIncumbentGlobal) - :mCallback( + : mCallback( new PromiseJobCallback(aCallback, aCallbackGlobal, aAllocationSite, aIncumbentGlobal)) + , mPropagateUserInputEventHandling(false) { MOZ_ASSERT(js::IsFunctionObject(aCallback)); + + if (aPromise) { + JS::PromiseUserInputEventHandlingState state = + JS::GetPromiseUserInputEventHandlingState(aPromise); + mPropagateUserInputEventHandling = + state == JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation; + } } virtual ~PromiseJobRunnable() @@ -224,6 +234,16 @@ protected: JSObject* callback = mCallback->CallbackPreserveColor(); nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr; if (global && !global->IsDying()) { + // Propagate the user input event handling bit if needed. + nsCOMPtr win = do_QueryInterface(global); + nsCOMPtr doc; + if (win) { + doc = win->GetExtantDoc(); + } + AutoHandlingUserInputStatePusher userInpStatePusher(mPropagateUserInputEventHandling, + nullptr, + doc); + mCallback->Call("promise callback"); aAso.CheckForInterrupt(); } @@ -244,6 +264,7 @@ protected: private: RefPtr mCallback; + bool mPropagateUserInputEventHandling; }; /* static */ @@ -275,9 +296,10 @@ CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx, global = xpc::NativeGlobal(aIncumbentGlobal); } JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx)); - RefPtr runnable = new PromiseJobRunnable(aJob, jobGlobal, - aAllocationSite, - global); + RefPtr runnable = new PromiseJobRunnable(aPromise, aJob, + jobGlobal, + aAllocationSite, + global); self->DispatchToMicroTask(runnable.forget()); return true; }