commit e318afd7771d07ef1f18f64c2ab789a2b3b50df5
parent 81568ecfb7bfe264c334797d7edbe72c52762299
Author: agoloman <agoloman@mozilla.com>
Date: Tue, 25 Nov 2025 14:25:22 +0200
Revert "Bug 2000504 - Make PresShell drive more of painting. r=jwatt" for causing assertions.
This reverts commit 68078cc21e6792ffc2a7fa9a69bfecc62c1ca761.
Diffstat:
7 files changed, 291 insertions(+), 76 deletions(-)
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
@@ -69,7 +69,6 @@
#include "mozilla/ServoBindings.h"
#include "mozilla/ServoStyleSet.h"
#include "mozilla/Sprintf.h"
-#include "mozilla/StartupTimeline.h"
#include "mozilla/StaticAnalysisFunctions.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_dom.h"
@@ -4256,9 +4255,17 @@ bool PresShell::IsSafeToFlush() const {
if (mIsReflowing || mChangeNestCount || mIsDestroying) {
return false;
}
- // Not safe either if we're painting.
- // TODO(emilio): What could call us while painting now?
- return !mIsPainting;
+
+ // Not safe if we are painting
+ if (nsViewManager* viewManager = GetViewManager()) {
+ bool isPainting = false;
+ viewManager->IsPainting(isPainting);
+ if (isPainting) {
+ return false;
+ }
+ }
+
+ return true;
}
void PresShell::NotifyFontFaceSetOnRefresh() {
@@ -4475,6 +4482,12 @@ void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
// scrollable frame and the primary frame of the scroll container.
TriggerPendingScrollTimelineAnimations(mDocument);
}
+
+ if (flushType >= FlushType::Layout) {
+ if (!mIsDestroying) {
+ viewManager->UpdateWidgetGeometry();
+ }
+ }
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged(
@@ -12184,13 +12197,10 @@ void PresShell::ResetVisualViewportSize() {
}
void PresShell::SetNeedsWindowPropertiesSync() {
- if (XRE_IsContentProcess() || !IsRoot()) {
- // Window properties are only relevant to top level widgets in the parent
- // process
- return;
- }
mNeedsWindowPropertiesSync = true;
- SchedulePaint();
+ if (mViewManager) {
+ mViewManager->PostPendingUpdate();
+ }
}
bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
@@ -12468,42 +12478,8 @@ PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() {
return {minSize, maxSize};
}
-void PresShell::PaintSynchronously() {
- MOZ_ASSERT(!mIsPainting, "re-entrant paint?");
- if (IsNeverPainting() || IsPaintingSuppressed() || !IsVisible() ||
- MOZ_UNLIKELY(NS_WARN_IF(mIsPainting))) {
- return;
- }
- RefPtr widget = GetOwnWidget();
- if (NS_WARN_IF(!widget)) {
- // We were asked to paint a non-root pres shell, or an already-detached
- // shell.
- return;
- }
- MOZ_ASSERT(widget->IsTopLevelWidget());
- if (!widget->NeedsPaint()) {
- return;
- }
-
- // FIXME: This might not be needed now except for widget paints
- // (WillPaintWindow) and maybe FlushWillPaintObservers.
- WillPaint();
-
- if (MOZ_UNLIKELY(mIsDestroying)) {
- return;
- }
-
- mViewManager->FlushDelayedResize();
-
- mIsPainting = true;
- auto cleanUpPaintingBit = MakeScopeExit([&] { mIsPainting = false; });
- nsAutoScriptBlocker blocker;
- RefPtr<WindowRenderer> renderer = widget->GetWindowRenderer();
- PaintAndRequestComposite(GetRootFrame(), renderer, PaintFlags::None);
-}
-
-void PresShell::SyncWindowPropertiesIfNeeded() {
- if (!mNeedsWindowPropertiesSync) {
+void PresShell::SyncWindowProperties() {
+ if (!mNeedsWindowPropertiesSync || XRE_IsContentProcess()) {
return;
}
diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h
@@ -1293,10 +1293,7 @@ class PresShell final : public nsStubDocumentObserver,
return mNeedLayoutFlush || mNeedStyleFlush;
}
- void MOZ_CAN_RUN_SCRIPT PaintSynchronously();
- // Ensures the top-level window has the right size constraints /
- // color-scheme / etc.
- void SyncWindowPropertiesIfNeeded();
+ void SyncWindowProperties();
struct WindowSizeConstraints {
nsSize mMinSize;
nsSize mMaxSize;
@@ -3416,7 +3413,6 @@ class PresShell final : public nsStubDocumentObserver,
bool mDidInitialize : 1;
bool mIsDestroying : 1;
bool mIsReflowing : 1;
- bool mIsPainting : 1 = false;
bool mIsObservingDocument : 1;
// Whether we shouldn't ever get to FlushPendingNotifications. This flag is
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
@@ -2628,11 +2628,10 @@ bool nsRefreshDriver::PaintIfNeeded() {
}
mCompositionPayloads.Clear();
}
- RefPtr<PresShell> ps = mPresContext->PresShell();
+ RefPtr<nsViewManager> vm = mPresContext->PresShell()->GetViewManager();
{
PaintTelemetry::AutoRecordPaint record;
- ps->SyncWindowPropertiesIfNeeded();
- ps->PaintSynchronously();
+ vm->ProcessPendingUpdates();
// Paint our popups.
if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
pm->PaintPopups(this);
diff --git a/view/nsView.cpp b/view/nsView.cpp
@@ -263,9 +263,9 @@ void nsView::WillPaintWindow(nsIWidget* aWidget) {
vm->WillPaintWindow(aWidget);
}
-bool nsView::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion) {
+bool nsView::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion aRegion) {
RefPtr<nsViewManager> vm = mViewManager;
- vm->PaintWindow(aWidget);
+ vm->Refresh(this, aRegion);
return true;
}
diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp
@@ -54,7 +54,9 @@ uint32_t nsViewManager::gLastUserEventTime = 0;
nsViewManager::nsViewManager()
: mPresShell(nullptr),
mDelayedResize(NSCOORD_NONE, NSCOORD_NONE),
- mRootView(nullptr) {}
+ mRootView(nullptr),
+ mPainting(false),
+ mHasPendingWidgetGeometryChanges(false) {}
nsViewManager::~nsViewManager() {
if (mRootView) {
@@ -169,28 +171,170 @@ nsViewManager* nsViewManager::GetParentViewManager() const {
return nullptr;
}
-void nsViewManager::PaintWindow(nsIWidget* aWidget) {
- RefPtr ps = mPresShell;
- if (!ps) {
+/**
+ aRegion is given in device coordinates!!
+ aContext may be null, in which case layers should be used for
+ rendering.
+*/
+void nsViewManager::Refresh(nsView* aView,
+ const LayoutDeviceIntRegion& aRegion) {
+ NS_ASSERTION(aView->GetViewManager() == this, "wrong view manager");
+
+ if (mPresShell && mPresShell->IsNeverPainting()) {
return;
}
- RefPtr renderer = aWidget->GetWindowRenderer();
- if (renderer->NeedsWidgetInvalidation()) {
- renderer->FlushRendering(wr::RenderReasons::WIDGET);
- } else {
- ps->SyncPaintFallback(ps->GetRootFrame(), renderer);
+
+ if (aRegion.IsEmpty()) {
+ return;
+ }
+
+ nsIWidget* widget = aView->GetWidget();
+ if (!widget) {
+ return;
+ }
+
+ MOZ_ASSERT(!IsPainting(), "recursive painting not permitted");
+ if (NS_WARN_IF(IsPainting())) {
+ return;
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ SetPainting(true);
+
+ if (RefPtr<PresShell> presShell = mPresShell) {
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("--COMPOSITE-- %p\n", presShell.get());
+ }
+#endif
+ RefPtr<WindowRenderer> renderer = widget->GetWindowRenderer();
+ if (!renderer->NeedsWidgetInvalidation()) {
+ renderer->FlushRendering(wr::RenderReasons::WIDGET);
+ } else {
+ presShell->SyncPaintFallback(presShell->GetRootFrame(), renderer);
+ }
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("--ENDCOMPOSITE--\n");
+ }
+#endif
+ mozilla::StartupTimeline::RecordOnce(
+ mozilla::StartupTimeline::FIRST_PAINT);
+ }
+
+ SetPainting(false);
+ }
+}
+
+void nsViewManager::ProcessPendingUpdatesForView(nsView* aView,
+ bool aFlushDirtyRegion) {
+ NS_ASSERTION(IsRootVM(), "Updates will be missed");
+ if (!aView) {
+ return;
+ }
+
+ RefPtr<PresShell> rootPresShell = mPresShell;
+ AutoTArray<nsCOMPtr<nsIWidget>, 1> widgets;
+ aView->GetViewManager()->ProcessPendingUpdatesRecurse(aView, widgets);
+ for (nsIWidget* widget : widgets) {
+ MOZ_ASSERT(widget->IsTopLevelWidget());
+ if (RefPtr ps = widget->GetPresShell()) {
+ ps->SyncWindowProperties();
+ }
+ }
+ if (rootPresShell->GetViewManager() != this) {
+ return; // presentation might have been torn down
+ }
+ if (aFlushDirtyRegion) {
+ nsAutoScriptBlocker scriptBlocker;
+ SetPainting(true);
+ for (nsIWidget* widget : widgets) {
+ if (RefPtr ps = widget->GetPresShell()) {
+ RefPtr vm = ps->GetViewManager();
+ vm->ProcessPendingUpdatesPaint(MOZ_KnownLive(widget));
+ }
+ }
+ SetPainting(false);
+ }
+}
+
+void nsViewManager::ProcessPendingUpdatesRecurse(
+ nsView* aView, AutoTArray<nsCOMPtr<nsIWidget>, 1>& aWidgets) {
+ if (mPresShell && mPresShell->IsNeverPainting()) {
+ return;
+ }
+
+ if (nsIWidget* widget = aView->GetWidget()) {
+ aWidgets.AppendElement(widget);
+ }
+}
+
+void nsViewManager::ProcessPendingUpdatesPaint(nsIWidget* aWidget) {
+ if (!aWidget->NeedsPaint()) {
+ return;
+ }
+ // If an ancestor widget was hidden and then shown, we could
+ // have a delayed resize to handle.
+ if (mDelayedResize != nsSize(NSCOORD_NONE, NSCOORD_NONE) && mPresShell &&
+ mPresShell->IsVisible()) {
+ FlushDelayedResize();
+ }
+
+ if (!mRootView || !mPresShell) {
+ NS_ERROR("FlushDelayedResize destroyed the view?");
+ return;
+ }
+
+ nsIWidgetListener* previousListener =
+ aWidget->GetPreviouslyAttachedWidgetListener();
+
+ if (previousListener && previousListener != mRootView &&
+ mRootView->IsPrimaryFramePaintSuppressed()) {
+ return;
+ }
+
+ RefPtr ps = mPresShell;
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("---- PAINT START ----PresShell(%p), nsIWidget(%p)\n",
+ ps.get(), aWidget);
+ }
+#endif
+
+ ps->PaintAndRequestComposite(ps->GetRootFrame(), aWidget->GetWindowRenderer(),
+ PaintFlags::None);
+ mRootView->SetForcedRepaint(false);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("---- PAINT END ----\n");
+ }
+#endif
+}
+
+void nsViewManager::PostPendingUpdate() {
+ nsViewManager* rootVM = RootViewManager();
+ rootVM->mHasPendingWidgetGeometryChanges = true;
+ if (rootVM->mPresShell) {
+ rootVM->mPresShell->SetNeedLayoutFlush();
+ rootVM->mPresShell->SchedulePaint();
}
- mozilla::StartupTimeline::RecordOnce(mozilla::StartupTimeline::FIRST_PAINT);
}
void nsViewManager::WillPaintWindow(nsIWidget* aWidget) {
- WindowRenderer* renderer = aWidget->GetWindowRenderer();
- if (renderer->NeedsWidgetInvalidation()) {
+ if (!aWidget) {
return;
}
- if (RefPtr ps = mPresShell) {
- // FIXME(bug 2002232): Why is this needed?
- ps->PaintSynchronously();
+ WindowRenderer* renderer = aWidget->GetWindowRenderer();
+ if (mRootView &&
+ (mRootView->ForcedRepaint() || !renderer->NeedsWidgetInvalidation())) {
+ ProcessPendingUpdates();
+ // Re-get the view pointer here since the ProcessPendingUpdates might have
+ // destroyed it during CallWillPaintOnObservers.
+ if (mRootView) {
+ mRootView->SetForcedRepaint(false);
+ }
}
}
@@ -225,3 +369,63 @@ void nsViewManager::ResizeView(nsView* aView, const nsSize& aSize) {
// pointer, and this resize is implicitly changing the clip rect, it's OK
// because layout will change it back again if necessary.
}
+
+void nsViewManager::IsPainting(bool& aIsPainting) {
+ aIsPainting = IsPainting();
+}
+
+void nsViewManager::ProcessPendingUpdates() {
+ if (!IsRootVM()) {
+ RefPtr<nsViewManager> rootViewManager = RootViewManager();
+ rootViewManager->ProcessPendingUpdates();
+ return;
+ }
+
+ // Flush things like reflows by calling WillPaint on observer presShells.
+ if (mPresShell) {
+ RefPtr<nsViewManager> strongThis(this);
+ CallWillPaintOnObservers();
+
+ ProcessPendingUpdatesForView(mRootView, true);
+ }
+}
+
+void nsViewManager::UpdateWidgetGeometry() {
+ if (!IsRootVM()) {
+ RefPtr<nsViewManager> rootViewManager = RootViewManager();
+ rootViewManager->UpdateWidgetGeometry();
+ return;
+ }
+
+ if (mHasPendingWidgetGeometryChanges) {
+ mHasPendingWidgetGeometryChanges = false;
+ ProcessPendingUpdatesForView(mRootView, false);
+ }
+}
+
+/* static */ void nsViewManager::CollectVMsForWillPaint(
+ nsView* aView, nsViewManager* aParentVM,
+ nsTArray<RefPtr<nsViewManager>>& aVMs) {
+ nsViewManager* vm = aView->GetViewManager();
+ if (vm != aParentVM) {
+ aVMs.AppendElement(vm);
+ }
+}
+
+void nsViewManager::CallWillPaintOnObservers() {
+ MOZ_ASSERT(IsRootVM(), "Must be root VM for this to be called!");
+
+ if (!mRootView) {
+ return;
+ }
+
+ AutoTArray<RefPtr<nsViewManager>, 2> VMs;
+ CollectVMsForWillPaint(mRootView, nullptr, VMs);
+ for (const auto& vm : VMs) {
+ if (vm->GetRootView()) {
+ if (RefPtr<PresShell> presShell = vm->GetPresShell()) {
+ presShell->WillPaint();
+ }
+ }
+ }
+}
diff --git a/view/nsViewManager.h b/view/nsViewManager.h
@@ -101,6 +101,14 @@ class nsViewManager final {
public:
/**
+ * Indicate whether the viewmanager is currently painting
+ *
+ * @param aPainting true if the viewmanager is painting
+ * false otherwise
+ */
+ void IsPainting(bool& aIsPainting);
+
+ /**
* Retrieve the time of the last user event. User events
* include mouse and keyboard events. The viewmanager
* saves the time of the last user event.
@@ -114,9 +122,28 @@ class nsViewManager final {
*/
MOZ_CAN_RUN_SCRIPT void ProcessPendingUpdates();
+ /**
+ * Just update widget geometry without flushing the dirty region
+ */
+ MOZ_CAN_RUN_SCRIPT void UpdateWidgetGeometry();
+
+ // Call this when you need to let the viewmanager know that it now has
+ // pending updates.
+ void PostPendingUpdate();
+
private:
static uint32_t gLastUserEventTime;
+ void FlushPendingInvalidates();
+
+ MOZ_CAN_RUN_SCRIPT
+ void ProcessPendingUpdatesForView(nsView* aView,
+ bool aFlushDirtyRegion = true);
+ void ProcessPendingUpdatesRecurse(
+ nsView* aView, AutoTArray<nsCOMPtr<nsIWidget>, 1>& aWidgets);
+ MOZ_CAN_RUN_SCRIPT
+ void ProcessPendingUpdatesPaint(nsIWidget* aWidget);
+
/**
* Call WillPaint() on all view observers under this vm root.
*/
@@ -124,15 +151,24 @@ class nsViewManager final {
static void CollectVMsForWillPaint(nsView* aView, nsViewManager* aParentVM,
nsTArray<RefPtr<nsViewManager>>& aVMs);
+ // aView is the view for aWidget and aRegion is relative to aWidget.
+ MOZ_CAN_RUN_SCRIPT
+ void Refresh(nsView* aView, const LayoutDeviceIntRegion& aRegion);
+
MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoSetWindowDimensions(const nsSize&);
bool ShouldDelayResize() const;
+ bool IsPainting() const { return RootViewManager()->mPainting; }
+
+ void SetPainting(bool aPainting) { RootViewManager()->mPainting = aPainting; }
+
nsViewManager* RootViewManager() const;
nsViewManager* GetParentViewManager() const;
bool IsRootVM() const { return !GetParentViewManager(); }
MOZ_CAN_RUN_SCRIPT void WillPaintWindow(nsIWidget* aWidget);
- MOZ_CAN_RUN_SCRIPT void PaintWindow(nsIWidget* aWidget);
+ MOZ_CAN_RUN_SCRIPT
+ bool PaintWindow(nsIWidget* aWidget, const LayoutDeviceIntRegion& aRegion);
MOZ_CAN_RUN_SCRIPT void DidPaintWindow();
mozilla::PresShell* mPresShell;
@@ -142,6 +178,14 @@ class nsViewManager final {
nsSize mDelayedResize;
nsView* mRootView;
+
+ // The following members should not be accessed directly except by
+ // the root view manager. Some have accessor functions to enforce
+ // this, as noted.
+ // Use IsPainting() and SetPainting() to access mPainting.
+ bool mPainting;
+ bool mHasPendingWidgetGeometryChanges;
+
// from here to public should be static and locked... MMP
};
diff --git a/xpfe/appshell/AppWindow.cpp b/xpfe/appshell/AppWindow.cpp
@@ -1081,10 +1081,6 @@ void AppWindow::OnChromeLoaded() {
mChromeLoaded = true;
ApplyChromeFlags();
SyncAttributesToWidget();
- if (RefPtr ps = GetPresShell()) {
- // Sync window properties now, before showing the window.
- ps->SyncWindowPropertiesIfNeeded();
- }
if (mWindow) {
SizeShell();
if (mShowAfterLoad) {