tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 68078cc21e6792ffc2a7fa9a69bfecc62c1ca761
parent 96b287d65272251e2392c3e63e58840ad83be220
Author: Emilio Cobos Álvarez <emilio@crisal.io>
Date:   Tue, 25 Nov 2025 11:39:45 +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:
Mlayout/base/PresShell.cpp | 68++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mlayout/base/PresShell.h | 6+++++-
Mlayout/base/nsRefreshDriver.cpp | 5+++--
Mview/nsView.cpp | 4++--
Mview/nsViewManager.cpp | 234++++++--------------------------------------------------------------------------
Mview/nsViewManager.h | 46+---------------------------------------------
Mxpfe/appshell/AppWindow.cpp | 4++++
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) {