Bug 1302071 - Part 5: Bucket PresContext invalidations by transaction ID, and only deliver them when the associated composite has completed. r?tnikkel
This patch does a few things:
* Buckets invalidations by transaction ID, and sends MozAfterPaints events for them when the associated composite completes.
* Creates a separate EventualDidPaint timer for each transaction ID we have invalidations for rather than just using one.
* Removes NotifyDidPaintForSubtree(PAINT_LAYERS), as it was only necessary for the existing bucketing mechanism.
MozReview-Commit-ID: JERMsgxhPQd
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -6283,17 +6283,19 @@ class nsAutoNotifyDidPaint
{
public:
nsAutoNotifyDidPaint(PresShell* aShell, uint32_t aFlags)
: mShell(aShell), mFlags(aFlags)
{
}
~nsAutoNotifyDidPaint()
{
- mShell->GetPresContext()->NotifyDidPaintForSubtree(mFlags);
+ if (mFlags & nsIPresShell::PAINT_COMPOSITE) {
+ mShell->GetPresContext()->NotifyDidPaintForSubtree();
+ }
}
private:
PresShell* mShell;
uint32_t mFlags;
};
void
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -75,16 +75,17 @@
#include "gfxTextRun.h"
#include "nsFontFaceUtils.h"
#include "nsLayoutStylesheetCache.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/StyleSheetInlines.h"
#include "mozilla/Telemetry.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/layers/APZThreadUtils.h"
#if defined(MOZ_WIDGET_GTK)
#include "gfxPlatformGtk.h" // xxx - for UseFcFontList
#endif
// Needed for Start/Stop of Image Animation
#include "imgIContainer.h"
@@ -395,17 +396,17 @@ NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresContext)
NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsPresContext, LastRelease())
void
nsPresContext::LastRelease()
{
if (IsRoot()) {
- static_cast<nsRootPresContext*>(this)->CancelDidPaintTimer();
+ static_cast<nsRootPresContext*>(this)->CancelAllDidPaintTimers();
}
if (mMissingFonts) {
mMissingFonts->Clear();
}
}
NS_IMPL_CYCLE_COLLECTION_CLASS(nsPresContext)
@@ -1032,17 +1033,17 @@ nsPresContext::DetachShell()
if (IsRoot()) {
nsRootPresContext* thisRoot = static_cast<nsRootPresContext*>(this);
// Have to cancel our plugin geometry timer, because the
// callback for that depends on a non-null presshell.
thisRoot->CancelApplyPluginGeometryTimer();
// The did-paint timer also depends on a non-null pres shell.
- thisRoot->CancelDidPaintTimer();
+ thisRoot->CancelAllDidPaintTimers();
}
}
void
nsPresContext::DoChangeCharSet(const nsCString& aCharSet)
{
UpdateCharSet(aCharSet);
mDeviceContext->FlushFontCache();
@@ -2480,21 +2481,33 @@ nsPresContext::NotifyInvalidation(uint64
for (pc = this; pc; pc = pc->GetParentPresContext()) {
if (pc->mFireAfterPaintEvents)
break;
pc->mFireAfterPaintEvents = true;
}
if (!pc) {
nsRootPresContext* rpc = GetRootPresContext();
if (rpc) {
- rpc->EnsureEventualDidPaintEvent();
+ rpc->EnsureEventualDidPaintEvent(aTransactionId);
}
}
- mInvalidateRequestsSinceLastPaint.AppendElement(aRect);
+ TransactionInvalidations* transaction = nullptr;
+ for (TransactionInvalidations& t : mTransactions) {
+ if (t.mTransactionId == aTransactionId) {
+ transaction = &t;
+ break;
+ }
+ }
+ if (!transaction) {
+ transaction = mTransactions.AppendElement();
+ transaction->mTransactionId = aTransactionId;
+ }
+
+ transaction->mInvalidations.AppendElement(aRect);
}
/* static */ void
nsPresContext::NotifySubDocInvalidation(ContainerLayer* aContainer,
const nsIntRegion& aRegion)
{
ContainerLayerPresContext *data =
static_cast<ContainerLayerPresContext*>(
@@ -2525,33 +2538,32 @@ nsPresContext::SetNotifySubDocInvalidati
/* static */ void
nsPresContext::ClearNotifySubDocInvalidationData(ContainerLayer* aContainer)
{
aContainer->SetUserData(&gNotifySubDocInvalidationData, nullptr);
}
struct NotifyDidPaintSubdocumentCallbackClosure {
- uint32_t mFlags;
uint64_t mTransactionId;
const mozilla::TimeStamp& mTimeStamp;
bool mNeedsAnotherDidPaintNotification;
};
-static bool
-NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData)
+/* static */ bool
+nsPresContext::NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData)
{
NotifyDidPaintSubdocumentCallbackClosure* closure =
static_cast<NotifyDidPaintSubdocumentCallbackClosure*>(aData);
nsIPresShell* shell = aDocument->GetShell();
if (shell) {
nsPresContext* pc = shell->GetPresContext();
if (pc) {
- pc->NotifyDidPaintForSubtree(closure->mFlags, closure->mTransactionId,
+ pc->NotifyDidPaintForSubtree(closure->mTransactionId,
closure->mTimeStamp);
- if (pc->IsDOMPaintEventPending()) {
+ if (pc->mFireAfterPaintEvents) {
closure->mNeedsAnotherDidPaintNotification = true;
}
}
}
return true;
}
class DelayedFireDOMPaintEvent : public Runnable {
@@ -2580,59 +2592,67 @@ public:
RefPtr<nsPresContext> mPresContext;
uint64_t mTransactionId;
const mozilla::TimeStamp mTimeStamp;
nsTArray<nsRect> mList;
};
void
-nsPresContext::NotifyDidPaintForSubtree(uint32_t aFlags, uint64_t aTransactionId,
+nsPresContext::NotifyDidPaintForSubtree(uint64_t aTransactionId,
const mozilla::TimeStamp& aTimeStamp)
{
if (IsRoot()) {
- static_cast<nsRootPresContext*>(this)->CancelDidPaintTimer();
+ static_cast<nsRootPresContext*>(this)->CancelDidPaintTimers(aTransactionId);
if (!mFireAfterPaintEvents) {
return;
}
}
if (!PresShell()->IsVisible() && !mFireAfterPaintEvents) {
return;
}
// Non-root prescontexts fire MozAfterPaint to all their descendants
// unconditionally, even if no invalidations have been collected. This is
// because we don't want to eat the cost of collecting invalidations for
// every subdocument (which would require putting every subdocument in its
// own layer).
- if (aFlags & nsIPresShell::PAINT_LAYERS) {
- mUndeliveredInvalidateRequestsBeforeLastPaint.AppendElements(mozilla::Move(mInvalidateRequestsSinceLastPaint));
+ bool sent = false;
+ uint32_t i = 0;
+ while (i < mTransactions.Length()) {
+ if (mTransactions[i].mTransactionId <= aTransactionId) {
+ nsCOMPtr<nsIRunnable> ev =
+ new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations,
+ mTransactions[i].mTransactionId, aTimeStamp);
+ nsContentUtils::AddScriptRunner(ev);
+ sent = true;
+ mTransactions.RemoveElementAt(i);
+ } else {
+ i++;
+ }
}
- if (aFlags & nsIPresShell::PAINT_COMPOSITE) {
+
+ if (!sent) {
+ nsTArray<nsRect> dummy;
nsCOMPtr<nsIRunnable> ev =
- new DelayedFireDOMPaintEvent(this, &mUndeliveredInvalidateRequestsBeforeLastPaint,
+ new DelayedFireDOMPaintEvent(this, &dummy,
aTransactionId, aTimeStamp);
nsContentUtils::AddScriptRunner(ev);
}
- NotifyDidPaintSubdocumentCallbackClosure closure = { aFlags, aTransactionId, aTimeStamp, false };
- mDocument->EnumerateSubDocuments(NotifyDidPaintSubdocumentCallback, &closure);
+ NotifyDidPaintSubdocumentCallbackClosure closure = { aTransactionId, aTimeStamp, false };
+ mDocument->EnumerateSubDocuments(nsPresContext::NotifyDidPaintSubdocumentCallback, &closure);
if (!closure.mNeedsAnotherDidPaintNotification &&
- mInvalidateRequestsSinceLastPaint.IsEmpty() &&
- mUndeliveredInvalidateRequestsBeforeLastPaint.IsEmpty()) {
+ mTransactions.IsEmpty()) {
// Nothing more to do for the moment.
mFireAfterPaintEvents = false;
- } else {
- if (IsRoot()) {
- static_cast<nsRootPresContext*>(this)->EnsureEventualDidPaintEvent();
- }
}
}
bool
nsPresContext::HasCachedStyleData()
{
if (!mShell) {
return false;
@@ -2970,24 +2990,24 @@ nsRootPresContext::nsRootPresContext(nsI
mDOMGeneration(0)
{
}
nsRootPresContext::~nsRootPresContext()
{
NS_ASSERTION(mRegisteredPlugins.Count() == 0,
"All plugins should have been unregistered");
- CancelDidPaintTimer();
+ CancelAllDidPaintTimers();
CancelApplyPluginGeometryTimer();
}
/* virtual */ void
nsRootPresContext::Detach()
{
- CancelDidPaintTimer();
+ CancelAllDidPaintTimers();
// XXXmats maybe also CancelApplyPluginGeometryTimer(); ?
nsPresContext::Detach();
}
void
nsRootPresContext::RegisterPluginForGeometryUpdates(nsIContent* aPlugin)
{
mRegisteredPlugins.PutEntry(aPlugin);
@@ -3232,36 +3252,61 @@ nsRootPresContext::CollectPluginGeometry
SortConfigurations(&configurations);
if (clm) {
clm->StorePluginWidgetConfigurations(configurations);
}
PluginDidSetGeometry(mRegisteredPlugins);
#endif // #ifndef XP_MACOSX
}
-static void
-NotifyDidPaintForSubtreeCallback(nsITimer *aTimer, void *aClosure)
+void
+nsRootPresContext::EnsureEventualDidPaintEvent(uint64_t aTransactionId)
{
- nsPresContext* presContext = (nsPresContext*)aClosure;
- nsAutoScriptBlocker blockScripts;
- // This is a fallback if we don't get paint events for some reason
- // so we'll just pretend both layer painting and compositing happened.
- presContext->NotifyDidPaintForSubtree(
- nsIPresShell::PAINT_LAYERS | nsIPresShell::PAINT_COMPOSITE);
+ for (NotifyDidPaintTimer& t : mNotifyDidPaintTimers) {
+ if (t.mTransactionId == aTransactionId) {
+ return;
+ }
+ }
+
+ nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1");
+ if (timer) {
+ nsresult rv = timer->InitWithCallback(NewTimerCallback([=](){
+ nsAutoScriptBlocker blockScripts;
+ this->NotifyDidPaintForSubtree(aTransactionId);
+ }), 100, nsITimer::TYPE_ONE_SHOT);
+
+ if (NS_SUCCEEDED(rv)) {
+ NotifyDidPaintTimer* t = mNotifyDidPaintTimers.AppendElement();
+ t->mTransactionId = aTransactionId;
+ t->mTimer = timer;
+ }
+ }
}
void
-nsRootPresContext::EnsureEventualDidPaintEvent()
+nsRootPresContext::CancelDidPaintTimers(uint64_t aTransactionId)
{
- if (mNotifyDidPaintTimer)
- return;
-
- mNotifyDidPaintTimer = CreateTimer(NotifyDidPaintForSubtreeCallback,
- "NotifyDidPaintForSubtreeCallback",
- 100);
+ uint32_t i = 0;
+ while (i < mNotifyDidPaintTimers.Length()) {
+ if (mNotifyDidPaintTimers[i].mTransactionId <= aTransactionId) {
+ mNotifyDidPaintTimers[i].mTimer->Cancel();
+ mNotifyDidPaintTimers.RemoveElementAt(i);
+ } else {
+ i++;
+ }
+ }
+}
+
+void
+nsRootPresContext::CancelAllDidPaintTimers()
+{
+ for (uint32_t i = 0; i < mNotifyDidPaintTimers.Length(); i++) {
+ mNotifyDidPaintTimers[i].mTimer->Cancel();
+ }
+ mNotifyDidPaintTimers.Clear();
}
void
nsRootPresContext::AddWillPaintObserver(nsIRunnable* aRunnable)
{
if (!mWillPaintFallbackEvent.IsPending()) {
mWillPaintFallbackEvent = new RunWillPaintObservers(this);
NS_DispatchToMainThread(mWillPaintFallbackEvent.get());
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -957,18 +957,17 @@ public:
// Mark an area as invalidated, associated with a given transaction id (allocated
// by nsRefreshDriver::GetTransactionId).
// Invalidated regions will be dispatched to MozAfterPaint events when
// NotifyDidPaintForSubtree is called for the transaction id (or any higher id).
void NotifyInvalidation(uint64_t aTransactionId, const nsRect& aRect);
// aRect is in device pixels
void NotifyInvalidation(uint64_t aTransactionId, const nsIntRect& aRect);
- // aFlags are nsIPresShell::PAINT_ flags
- void NotifyDidPaintForSubtree(uint32_t aFlags, uint64_t aTransactionId = 0,
+ void NotifyDidPaintForSubtree(uint64_t aTransactionId = 0,
const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp());
void FireDOMPaintEvent(nsTArray<nsRect>* aList, uint64_t aTransactionId,
mozilla::TimeStamp aTimeStamp = mozilla::TimeStamp());
// Callback for catching invalidations in ContainerLayers
// Passed to LayerProperties::ComputeDifference
static void NotifySubDocInvalidation(mozilla::layers::ContainerLayer* aContainer,
const nsIntRegion& aRegion);
@@ -1175,16 +1174,18 @@ protected:
const LangGroupFontPrefs* GetFontPrefsForLang(nsIAtom *aLanguage) const
{
nsIAtom* lang = aLanguage ? aLanguage : mLanguage.get();
return StaticPresData::Get()->GetFontPrefsForLangHelper(lang, &mLangGroupFontPrefs);
}
void UpdateCharSet(const nsCString& aCharSet);
+ static bool NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData);
+
public:
void DoChangeCharSet(const nsCString& aCharSet);
/**
* Checks for MozAfterPaint listeners on the document
*/
bool MayHavePaintEventListener();
@@ -1286,18 +1287,21 @@ protected:
nsCOMPtr<nsITheme> mTheme;
nsCOMPtr<nsILanguageAtomService> mLangService;
nsCOMPtr<nsIPrintSettings> mPrintSettings;
nsCOMPtr<nsITimer> mPrefChangedTimer;
FramePropertyTable mPropertyTable;
- nsTArray<nsRect> mInvalidateRequestsSinceLastPaint;
- nsTArray<nsRect> mUndeliveredInvalidateRequestsBeforeLastPaint;
+ struct TransactionInvalidations {
+ uint64_t mTransactionId;
+ nsTArray<nsRect> mInvalidations;
+ };
+ AutoTArray<TransactionInvalidations, 4> mTransactions;
// text performance metrics
nsAutoPtr<gfxTextPerfMetrics> mTextPerf;
nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
nsRect mVisibleArea;
nsSize mPageSize;
@@ -1464,25 +1468,28 @@ public:
nsRootPresContext(nsIDocument* aDocument, nsPresContextType aType);
virtual ~nsRootPresContext();
virtual void Detach() override;
/**
* Ensure that NotifyDidPaintForSubtree is eventually called on this
* object after a timeout.
*/
- void EnsureEventualDidPaintEvent();
+ void EnsureEventualDidPaintEvent(uint64_t aTransactionId);
- void CancelDidPaintTimer()
- {
- if (mNotifyDidPaintTimer) {
- mNotifyDidPaintTimer->Cancel();
- mNotifyDidPaintTimer = nullptr;
- }
- }
+ /**
+ * Cancels any pending eventual did paint timer for transaction
+ * ids up to and including aTransactionId.
+ */
+ void CancelDidPaintTimers(uint64_t aTransactionId);
+
+ /**
+ * Cancel all pending eventual did paint timers.
+ */
+ void CancelAllDidPaintTimers();
/**
* Registers a plugin to receive geometry updates (position and clip
* region) so it can update its widget.
* Callers must call UnregisterPluginForGeometryUpdates before
* the aPlugin frame is destroyed.
*/
void RegisterPluginForGeometryUpdates(nsIContent* aPlugin);
@@ -1577,17 +1584,22 @@ protected:
return NS_OK;
}
// The lifetime of this reference is handled by an nsRevocableEventPtr
nsRootPresContext* MOZ_NON_OWNING_REF mPresContext;
};
friend class nsPresContext;
- nsCOMPtr<nsITimer> mNotifyDidPaintTimer;
+ struct NotifyDidPaintTimer {
+ uint64_t mTransactionId;
+ nsCOMPtr<nsITimer> mTimer;
+ };
+ AutoTArray<NotifyDidPaintTimer, 4> mNotifyDidPaintTimers;
+
nsCOMPtr<nsITimer> mApplyPluginGeometryTimer;
nsTHashtable<nsRefPtrHashKey<nsIContent> > mRegisteredPlugins;
nsTArray<nsCOMPtr<nsIRunnable> > mWillPaintObservers;
nsRevocableEventPtr<RunWillPaintObservers> mWillPaintFallbackEvent;
uint32_t mDOMGeneration;
};
#ifdef MOZ_REFLOW_PERF
--- a/view/nsView.cpp
+++ b/view/nsView.cpp
@@ -1078,18 +1078,17 @@ nsView::DidCompositeWindow(uint64_t aTra
{
nsIPresShell* presShell = mViewManager->GetPresShell();
if (presShell) {
nsAutoScriptBlocker scriptBlocker;
nsPresContext* context = presShell->GetPresContext();
nsRootPresContext* rootContext = context->GetRootPresContext();
MOZ_ASSERT(rootContext, "rootContext must be valid.");
- rootContext->NotifyDidPaintForSubtree(nsIPresShell::PAINT_COMPOSITE, aTransactionId,
- aCompositeEnd);
+ rootContext->NotifyDidPaintForSubtree(aTransactionId, aCompositeEnd);
// If the two timestamps are identical, this was likely a fake composite
// event which wouldn't be terribly useful to display.
if (aCompositeStart == aCompositeEnd) {
return;
}
nsIDocShell* docShell = context->GetDocShell();