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
This commit is contained in:
Ehsan Akhgari 2018-10-09 21:42:22 +00:00
Родитель bfde189b6d
Коммит 19e88f0bf4
3 изменённых файлов: 110 добавлений и 20 удалений

Просмотреть файл

@ -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>
Promise::Create(nsIGlobalObject* aGlobal, ErrorResult& aRv)
Promise::Create(nsIGlobalObject* aGlobal,
ErrorResult& aRv,
PropagateUserInteraction aPropagateUserInteraction)
{
if (!aGlobal) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
RefPtr<Promise> 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<JSObject*> p(RootingCx(), mPromiseObj);
return JS::SetPromiseUserInputEventHandlingState(p, state);
}
// static
already_AddRefed<Promise>
Promise::Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv)
JS::Handle<JS::Value> aValue,
ErrorResult& aRv,
PropagateUserInteraction aPropagateUserInteraction)
{
JSAutoRealm ar(aCx, aGlobal->GetGlobalJSObject());
JS::Rooted<JSObject*> 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>
Promise::All(JSContext* aCx,
const nsTArray<RefPtr<Promise>>& aPromiseList, ErrorResult& aRv)
const nsTArray<RefPtr<Promise>>& aPromiseList,
ErrorResult& aRv,
PropagateUserInteraction aPropagateUserInteraction)
{
JS::Rooted<JSObject*> 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<JSObject*> aDesiredProto, ErrorResult& aRv)
Promise::CreateWrapper(JS::Handle<JSObject*> aDesiredProto,
ErrorResult& aRv,
PropagateUserInteraction aPropagateUserInteraction)
{
AutoJSAPI jsapi;
if (!jsapi.Init(mGlobal)) {
@ -285,6 +309,9 @@ Promise::CreateWrapper(JS::Handle<JSObject*> 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>
Promise::CreateFromExisting(nsIGlobalObject* aGlobal,
JS::Handle<JSObject*> aPromiseObj)
JS::Handle<JSObject*> aPromiseObj,
PropagateUserInteraction aPropagateUserInteraction)
{
MOZ_ASSERT(js::GetObjectCompartment(aGlobal->GetGlobalJSObject()) ==
js::GetObjectCompartment(aPromiseObj));
RefPtr<Promise> p = new Promise(aGlobal);
p->mPromiseObj = aPromiseObj;
if (aPropagateUserInteraction == ePropagateUserInteraction &&
!p->MaybePropagateUserInputEventHandling()) {
return nullptr;
}
return p.forget();
}

Просмотреть файл

@ -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<Promise>
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<Promise>
Resolve(nsIGlobalObject* aGlobal, JSContext* aCx,
JS::Handle<JS::Value> aValue, ErrorResult& aRv);
JS::Handle<JS::Value> 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<Promise>
All(JSContext* aCx, const nsTArray<RefPtr<Promise>>& 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<Promise>
CreateFromExisting(nsIGlobalObject* aGlobal,
JS::Handle<JSObject*> aPromiseObj);
JS::Handle<JSObject*> 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<JSObject*> 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<JSObject*> aDesiredProto,
ErrorResult& aRv,
PropagateUserInteraction aPropagateUserInteraction =
eDontPropagateUserInteraction);
private:
template <typename T>
@ -238,6 +272,8 @@ private:
void HandleException(JSContext* aCx);
bool MaybePropagateUserInputEventHandling();
RefPtr<nsIGlobalObject> mGlobal;
JS::Heap<JSObject*> mPromiseObj;

Просмотреть файл

@ -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<nsPIDOMWindowInner> win = do_QueryInterface(global);
nsCOMPtr<nsIDocument> doc;
if (win) {
doc = win->GetExtantDoc();
}
AutoHandlingUserInputStatePusher userInpStatePusher(mPropagateUserInputEventHandling,
nullptr,
doc);
mCallback->Call("promise callback");
aAso.CheckForInterrupt();
}
@ -244,6 +264,7 @@ protected:
private:
RefPtr<PromiseJobCallback> mCallback;
bool mPropagateUserInputEventHandling;
};
/* static */
@ -275,9 +296,10 @@ CycleCollectedJSContext::EnqueuePromiseJobCallback(JSContext* aCx,
global = xpc::NativeGlobal(aIncumbentGlobal);
}
JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx));
RefPtr<MicroTaskRunnable> runnable = new PromiseJobRunnable(aJob, jobGlobal,
aAllocationSite,
global);
RefPtr<PromiseJobRunnable> runnable = new PromiseJobRunnable(aPromise, aJob,
jobGlobal,
aAllocationSite,
global);
self->DispatchToMicroTask(runnable.forget());
return true;
}