commit dc2e8729cff00f406992fed91ce7e8f15d74f999
parent c2ba40a3a04ec59a4d36dc2085836420bab91f47
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date: Tue, 25 Nov 2025 13:18:55 +0000
Bug 2000504 - Make PresShell drive more of painting. r=jwatt
This changes the widget size constraints to be applied once per frame
before painting (like popups), but that seems acceptable.
Differential Revision: https://phabricator.services.mozilla.com/D272831
Diffstat:
7 files changed, 76 insertions(+), 291 deletions(-)
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
@@ -69,6 +69,7 @@
#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"
@@ -4255,17 +4256,9 @@ bool PresShell::IsSafeToFlush() const {
if (mIsReflowing || mChangeNestCount || mIsDestroying) {
return false;
}
-
- // Not safe if we are painting
- if (nsViewManager* viewManager = GetViewManager()) {
- bool isPainting = false;
- viewManager->IsPainting(isPainting);
- if (isPainting) {
- return false;
- }
- }
-
- return true;
+ // Not safe either if we're painting.
+ // TODO(emilio): What could call us while painting now?
+ return !mIsPainting;
}
void PresShell::NotifyFontFaceSetOnRefresh() {
@@ -4482,12 +4475,6 @@ 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(
@@ -12197,10 +12184,13 @@ void PresShell::ResetVisualViewportSize() {
}
void PresShell::SetNeedsWindowPropertiesSync() {
- mNeedsWindowPropertiesSync = true;
- if (mViewManager) {
- mViewManager->PostPendingUpdate();
+ if (XRE_IsContentProcess() || !IsRoot()) {
+ // Window properties are only relevant to top level widgets in the parent
+ // process
+ return;
}
+ mNeedsWindowPropertiesSync = true;
+ SchedulePaint();
}
bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
@@ -12478,8 +12468,42 @@ PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() {
return {minSize, maxSize};
}
-void PresShell::SyncWindowProperties() {
- if (!mNeedsWindowPropertiesSync || XRE_IsContentProcess()) {
+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) {
return;
}
diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h
@@ -1293,7 +1293,10 @@ class PresShell final : public nsStubDocumentObserver,
return mNeedLayoutFlush || mNeedStyleFlush;
}
- void SyncWindowProperties();
+ void MOZ_CAN_RUN_SCRIPT PaintSynchronously();
+ // Ensures the top-level window has the right size constraints /
+ // color-scheme / etc.
+ void SyncWindowPropertiesIfNeeded();
struct WindowSizeConstraints {
nsSize mMinSize;
nsSize mMaxSize;
@@ -3413,6 +3416,7 @@ 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,10 +2628,11 @@ bool nsRefreshDriver::PaintIfNeeded() {
}
mCompositionPayloads.Clear();
}
- RefPtr<nsViewManager> vm = mPresContext->PresShell()->GetViewManager();
+ RefPtr<PresShell> ps = mPresContext->PresShell();
{
PaintTelemetry::AutoRecordPaint record;
- vm->ProcessPendingUpdates();
+ ps->SyncWindowPropertiesIfNeeded();
+ ps->PaintSynchronously();
// 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 aRegion) {
+bool nsView::PaintWindow(nsIWidget* aWidget, LayoutDeviceIntRegion) {
RefPtr<nsViewManager> vm = mViewManager;
- vm->Refresh(this, aRegion);
+ vm->PaintWindow(aWidget);
return true;
}
diff --git a/view/nsViewManager.cpp b/view/nsViewManager.cpp
@@ -54,9 +54,7 @@ uint32_t nsViewManager::gLastUserEventTime = 0;
nsViewManager::nsViewManager()
: mPresShell(nullptr),
mDelayedResize(NSCOORD_NONE, NSCOORD_NONE),
- mRootView(nullptr),
- mPainting(false),
- mHasPendingWidgetGeometryChanges(false) {}
+ mRootView(nullptr) {}
nsViewManager::~nsViewManager() {
if (mRootView) {
@@ -171,170 +169,28 @@ nsViewManager* nsViewManager::GetParentViewManager() const {
return nullptr;
}
-/**
- 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;
- }
-
- 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;
- }
-
+void nsViewManager::PaintWindow(nsIWidget* aWidget) {
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");
+ if (!ps) {
+ return;
}
-#endif
-}
-
-void nsViewManager::PostPendingUpdate() {
- nsViewManager* rootVM = RootViewManager();
- rootVM->mHasPendingWidgetGeometryChanges = true;
- if (rootVM->mPresShell) {
- rootVM->mPresShell->SetNeedLayoutFlush();
- rootVM->mPresShell->SchedulePaint();
+ RefPtr renderer = aWidget->GetWindowRenderer();
+ if (!renderer->NeedsWidgetInvalidation()) {
+ renderer->FlushRendering(wr::RenderReasons::WIDGET);
+ } else {
+ ps->SyncPaintFallback(ps->GetRootFrame(), renderer);
}
+ mozilla::StartupTimeline::RecordOnce(mozilla::StartupTimeline::FIRST_PAINT);
}
void nsViewManager::WillPaintWindow(nsIWidget* aWidget) {
- if (!aWidget) {
+ WindowRenderer* renderer = aWidget->GetWindowRenderer();
+ if (renderer->NeedsWidgetInvalidation()) {
return;
}
- 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);
- }
+ if (RefPtr ps = mPresShell) {
+ // FIXME(bug 2002232): Why is this needed?
+ ps->PaintSynchronously();
}
}
@@ -369,63 +225,3 @@ 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,14 +101,6 @@ 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.
@@ -122,28 +114,9 @@ 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.
*/
@@ -151,24 +124,15 @@ 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
- bool PaintWindow(nsIWidget* aWidget, const LayoutDeviceIntRegion& aRegion);
+ MOZ_CAN_RUN_SCRIPT void PaintWindow(nsIWidget* aWidget);
MOZ_CAN_RUN_SCRIPT void DidPaintWindow();
mozilla::PresShell* mPresShell;
@@ -178,14 +142,6 @@ 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,6 +1081,10 @@ 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) {