Bug 1434376 - Introduce ChromeOnly window.promiseDocumentFlushed to detect when refresh driver ticks have completed. r=bz

This is particularly useful for knowing when it's safe to query for style and
layout information for a window without causing a synchronous style or layout
flush.

Note that promiseDocumentFlushed was chosen over promiseDidRefresh or promiseRefreshed
to avoid potential confusion with the actual network-level refresh of browsers or
documents.

MozReview-Commit-ID: Am3G9yvSgdN

--HG--
extra : rebase_source : 20bdd2d6f624767d919d95a6601fc1c890aadf10
This commit is contained in:
Mike Conley 2018-02-11 20:14:49 -05:00
Родитель 19c880a480
Коммит 24b3c1ade3
5 изменённых файлов: 255 добавлений и 4 удалений

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

@ -856,6 +856,41 @@ nsGlobalWindowInner::IsBackgroundInternal() const
return !mOuterWindow || mOuterWindow->IsBackground();
}
class PromiseDocumentFlushedResolver final {
public:
PromiseDocumentFlushedResolver(Promise* aPromise,
PromiseDocumentFlushedCallback& aCallback)
: mPromise(aPromise)
, mCallback(&aCallback)
{
}
virtual ~PromiseDocumentFlushedResolver() = default;
void Call()
{
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
ErrorResult error;
JS::Rooted<JS::Value> returnVal(RootingCx());
mCallback->Call(&returnVal, error);
if (error.Failed()) {
mPromise->MaybeReject(error);
} else {
mPromise->MaybeResolve(returnVal);
}
}
void Cancel()
{
mPromise->MaybeReject(NS_ERROR_ABORT);
}
RefPtr<Promise> mPromise;
RefPtr<PromiseDocumentFlushedCallback> mCallback;
};
//*****************************************************************************
//*** nsGlobalWindowInner: Object Management
//*****************************************************************************
@ -889,6 +924,8 @@ nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter *aOuterWindow)
mCleanedUp(false),
mDialogAbuseCount(0),
mAreDialogsEnabled(true),
mObservingDidRefresh(false),
mIteratingDocumentFlushedResolvers(false),
mCanSkipCCGeneration(0),
mBeforeUnloadListenerCount(0)
{
@ -1293,6 +1330,13 @@ nsGlobalWindowInner::FreeInnerObjects()
while (mDoc->EventHandlingSuppressed()) {
mDoc->UnsuppressEventHandlingAndFireEvents(false);
}
if (mObservingDidRefresh) {
nsIPresShell* shell = mDoc->GetShell();
if (shell) {
Unused << shell->RemovePostRefreshObserver(this);
}
}
}
// Remove our reference to the document and the document principal.
@ -1334,6 +1378,11 @@ nsGlobalWindowInner::FreeInnerObjects()
}
mBeforeUnloadListenerCount = 0;
}
// If we have any promiseDocumentFlushed callbacks, fire them now so
// that the Promises can resolve.
CallDocumentFlushedResolvers();
mObservingDidRefresh = false;
}
//*****************************************************************************
@ -1489,6 +1538,12 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPromises)
for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mPromise);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mCallback);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
@ -1583,6 +1638,11 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPromises)
for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mPromise);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mCallback);
}
tmp->mDocumentFlushedResolvers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
@ -7323,6 +7383,125 @@ nsGlobalWindowInner::BeginWindowMove(Event& aMouseDownEvent, Element* aPanel,
aError = widget->BeginMoveDrag(mouseEvent);
}
already_AddRefed<Promise>
nsGlobalWindowInner::PromiseDocumentFlushed(PromiseDocumentFlushedCallback& aCallback,
ErrorResult& aError)
{
MOZ_RELEASE_ASSERT(IsChromeWindow());
if (!IsCurrentInnerWindow()) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (mIteratingDocumentFlushedResolvers) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!mDoc) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
nsIPresShell* shell = mDoc->GetShell();
if (!shell) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// We need to associate the lifetime of the Promise to the lifetime
// of the caller's global. That way, if the window we're observing
// refresh driver ticks on goes away before our observer is fired,
// we can still resolve the Promise.
nsIGlobalObject* global = GetIncumbentGlobal();
if (!global) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<Promise> resultPromise = Promise::Create(global, aError);
if (aError.Failed()) {
return nullptr;
}
UniquePtr<PromiseDocumentFlushedResolver> flushResolver(
new PromiseDocumentFlushedResolver(resultPromise, aCallback));
if (!shell->NeedFlush(FlushType::Style)) {
flushResolver->Call();
return resultPromise.forget();
}
if (!mObservingDidRefresh) {
bool success = shell->AddPostRefreshObserver(this);
if (!success) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
mObservingDidRefresh = true;
}
mDocumentFlushedResolvers.AppendElement(Move(flushResolver));
return resultPromise.forget();
}
void
nsGlobalWindowInner::CallDocumentFlushedResolvers()
{
MOZ_ASSERT(!mIteratingDocumentFlushedResolvers);
mIteratingDocumentFlushedResolvers = true;
for (const auto& documentFlushedResolver : mDocumentFlushedResolvers) {
documentFlushedResolver->Call();
}
mDocumentFlushedResolvers.Clear();
mIteratingDocumentFlushedResolvers = false;
}
void
nsGlobalWindowInner::CancelDocumentFlushedResolvers()
{
MOZ_ASSERT(!mIteratingDocumentFlushedResolvers);
mIteratingDocumentFlushedResolvers = true;
for (const auto& documentFlushedResolver : mDocumentFlushedResolvers) {
documentFlushedResolver->Cancel();
}
mDocumentFlushedResolvers.Clear();
mIteratingDocumentFlushedResolvers = false;
}
void
nsGlobalWindowInner::DidRefresh()
{
auto rejectionGuard = MakeScopeExit([&] {
CancelDocumentFlushedResolvers();
mObservingDidRefresh = false;
});
MOZ_ASSERT(mDoc);
nsIPresShell* shell = mDoc->GetShell();
MOZ_ASSERT(shell);
if (shell->NeedStyleFlush() || shell->HasPendingReflow()) {
// By the time our observer fired, something has already invalidated
// style and maybe layout. We'll wait until the next refresh driver
// tick instead.
rejectionGuard.release();
return;
}
bool success = shell->RemovePostRefreshObserver(this);
if (!success) {
return;
}
rejectionGuard.release();
CallDocumentFlushedResolvers();
mObservingDidRefresh = false;
}
already_AddRefed<nsWindowRoot>
nsGlobalWindowInner::GetWindowRoot(mozilla::ErrorResult& aError)
{

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

@ -57,6 +57,7 @@
#include "nsCheapSets.h"
#include "mozilla/dom/ImageBitmapSource.h"
#include "mozilla/UniquePtr.h"
#include "nsRefreshDriver.h"
class nsIArray;
class nsIBaseWindow;
@ -89,6 +90,8 @@ class IdleRequestExecutor;
class DialogValueHolder;
class PromiseDocumentFlushedResolver;
namespace mozilla {
class AbstractThread;
class ThrottledEventQueue;
@ -211,7 +214,8 @@ class nsGlobalWindowInner : public mozilla::dom::EventTarget,
public nsIScriptObjectPrincipal,
public nsSupportsWeakReference,
public nsIInterfaceRequestor,
public PRCListStr
public PRCListStr,
public nsAPostRefreshObserver
{
public:
typedef mozilla::TimeStamp TimeStamp;
@ -944,6 +948,12 @@ public:
mozilla::dom::Element* aPanel,
mozilla::ErrorResult& aError);
already_AddRefed<mozilla::dom::Promise>
PromiseDocumentFlushed(mozilla::dom::PromiseDocumentFlushedCallback& aCallback,
mozilla::ErrorResult& aError);
void DidRefresh() override;
void GetDialogArgumentsOuter(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval,
nsIPrincipal& aSubjectPrincipal,
mozilla::ErrorResult& aError);
@ -1269,6 +1279,9 @@ private:
mChromeFields.mGroupMessageManagers.Clear();
}
void CallDocumentFlushedResolvers();
void CancelDocumentFlushedResolvers();
public:
// Dispatch a runnable related to the global.
virtual nsresult Dispatch(mozilla::TaskCategory aCategory,
@ -1415,6 +1428,14 @@ protected:
// currently enabled on this window.
bool mAreDialogsEnabled;
// This flag keeps track of whether this window is currently
// observing DidRefresh notifications from the refresh driver.
bool mObservingDidRefresh;
// This flag keeps track of whether or not we're going through
// promiseDocumentFlushed resolvers. When true, promiseDocumentFlushed
// cannot be called.
bool mIteratingDocumentFlushedResolvers;
nsTArray<uint32_t> mEnabledSensors;
#if defined(MOZ_WIDGET_ANDROID)
@ -1441,6 +1462,8 @@ protected:
nsTArray<RefPtr<mozilla::dom::Promise>> mPendingPromises;
nsTArray<mozilla::UniquePtr<PromiseDocumentFlushedResolver>> mDocumentFlushedResolvers;
static InnerWindowByIdTable* sInnerWindowsById;
// Members in the mChromeFields member should only be used in chrome windows.

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

@ -372,6 +372,8 @@ partial interface Window {
};
#endif
callback PromiseDocumentFlushedCallback = any ();
// Mozilla extensions for Chrome windows.
partial interface Window {
// The STATE_* constants need to match the corresponding enum in nsGlobalWindow.cpp.
@ -444,6 +446,47 @@ partial interface Window {
[Throws, Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
void beginWindowMove(Event mouseDownEvent, optional Element? panel = null);
/**
* Calls the given function as soon as a style or layout flush for the
* top-level document is not necessary, and returns a Promise which
* resolves to the callback's return value after it executes.
*
* In the event that the window goes away before a flush can occur, the
* callback will still be called and the Promise resolved as the window
* tears itself down.
*
* Note that the callback can be called either synchronously or asynchronously
* depending on whether or not flushes are pending:
*
* The callback will be called synchronously when calling
* promiseDocumentFlushed when NO flushes are already pending. This is
* to ensure that no script has a chance to dirty the DOM before the callback
* is called.
*
* The callback will be called asynchronously if a flush is pending.
*
* The expected execution order is that all pending callbacks will
* be fired first (and in the order that they were queued) and then the
* Promise resolution handlers will all be invoked later on during the
* next microtask checkpoint.
*
* promiseDocumentFlushed does not support re-entrancy - so calling it from
* within a promiseDocumentFlushed callback will result in the inner call
* throwing an NS_ERROR_FAILURE exception, and the outer Promise rejecting
* with that exception.
*
* The callback function *must not make any changes which would require
* a style or layout flush*.
*
* Also throws NS_ERROR_FAILURE if the window is not in a state where flushes
* can be waited for (for example, the PresShell has not yet been created).
*
* @param {function} callback
* @returns {Promise}
*/
[Throws, Func="nsGlobalWindowInner::IsPrivilegedChromeWindow"]
Promise<any> promiseDocumentFlushed(PromiseDocumentFlushedCallback callback);
[Func="IsChromeOrXBL"]
readonly attribute boolean isChromeWindow;
};

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

@ -2330,13 +2330,13 @@ public:
mFillRule = aFillRule;
}
Position& GetPosition() {
mozilla::Position& GetPosition() {
MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
mType == StyleBasicShapeType::Ellipse,
"expected circle or ellipse");
return mPosition;
}
const Position& GetPosition() const {
const mozilla::Position& GetPosition() const {
MOZ_ASSERT(mType == StyleBasicShapeType::Circle ||
mType == StyleBasicShapeType::Ellipse,
"expected circle or ellipse");
@ -2396,7 +2396,7 @@ private:
// (top, right, bottom, left) for inset
nsTArray<nsStyleCoord> mCoordinates;
// position of center for ellipse or circle
Position mPosition;
mozilla::Position mPosition;
// corner radii for inset (0 if not set)
nsStyleCorners mRadius;
};

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

@ -10,6 +10,12 @@
#include "mozilla/TimeStamp.h"
#include "mozilla/TypeTraits.h"
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount().
#ifdef GetCurrentTime
#undef GetCurrentTime
#endif
namespace mozilla {
// Utility class that converts time values represented as an unsigned integral