зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
de1e0827c0
Коммит
a7df06446a
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче