Bug 1806905 - Tweak reflow callback set-up to deal with container query reentrancy. r=smaug

Differential Revision: https://phabricator.services.mozilla.com/D165611
This commit is contained in:
Emilio Cobos Álvarez
2022-12-28 17:46:44 +00:00
parent a0f22c39e3
commit e947639b46
3 changed files with 85 additions and 60 deletions

View File

@@ -853,7 +853,9 @@ PresShell::PresShell(Document* aDocument)
mForceUseLegacyNonPrimaryDispatch(false),
mInitializedWithClickEventDispatchingBlacklist(false),
mMouseLocationWasSetBySynthesizedMouseEventForTests(false),
mHasTriedFastUnsuppress(false) {
mHasTriedFastUnsuppress(false),
mProcessingReflowCommands(false),
mPendingDidDoReflow(false) {
MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
MOZ_ASSERT(aDocument);
@@ -2151,6 +2153,7 @@ bool PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
// Now, we may have been destroyed by the destructor of
// `nsAutoCauseReflowNotifier`.
mPendingDidDoReflow = true;
DidDoReflow(true);
// the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE,
@@ -4200,10 +4203,7 @@ void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) {
FlushType flushType =
aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
if (shouldFlush && !mIsDestroying && nsContentUtils::IsSafeToRunScript()) {
// We don't want to flush when not allowed to run script (e.g., like when
// running container query updates), since that trivially executes script.
// We'll flush layout again at the end of that process if necessary.
if (shouldFlush && !mIsDestroying) {
FlushPendingNotifications(flushType);
}
}
@@ -9485,9 +9485,22 @@ void PresShell::WillDoReflow() {
}
void PresShell::DidDoReflow(bool aInterruptible) {
MOZ_ASSERT(mPendingDidDoReflow);
if (!nsContentUtils::IsSafeToRunScript()) {
// If we're reflowing while script-blocked (e.g. from container query
// updates), defer our reflow callbacks until the end of our next layout
// flush.
SetNeedLayoutFlush();
return;
}
auto clearPendingDidDoReflow =
MakeScopeExit([&] { mPendingDidDoReflow = false; });
mHiddenContentInForcedLayout.Clear();
HandlePostedReflowCallbacks(aInterruptible);
if (mIsDestroying) {
return;
}
@@ -9825,12 +9838,18 @@ void PresShell::DoVerifyReflow() {
#define NS_LONG_REFLOW_TIME_MS 5000
bool PresShell::ProcessReflowCommands(bool aInterruptible) {
if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting) {
if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting &&
!mPendingDidDoReflow) {
// Nothing to do; bail out
return true;
}
mozilla::TimeStamp timerStart = mozilla::TimeStamp::Now();
const bool wasProcessingReflowCommands = mProcessingReflowCommands;
auto restoreProcessingReflowCommands = MakeScopeExit(
[&] { mProcessingReflowCommands = wasProcessingReflowCommands; });
mProcessingReflowCommands = true;
auto timerStart = mozilla::TimeStamp::Now();
bool interrupted = false;
if (!mDirtyRoots.IsEmpty()) {
#ifdef DEBUG
@@ -9846,68 +9865,68 @@ bool PresShell::ProcessReflowCommands(bool aInterruptible) {
: (PRIntervalTime)0;
// Scope for the reflow entry point
{
nsAutoScriptBlocker scriptBlocker;
WillDoReflow();
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
nsAutoScriptBlocker scriptBlocker;
WillDoReflow();
AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
OverflowChangedTracker overflowTracker;
OverflowChangedTracker overflowTracker;
do {
// Send an incremental reflow notification to the target frame.
nsIFrame* target = mDirtyRoots.PopShallowestRoot();
do {
// Send an incremental reflow notification to the target frame.
nsIFrame* target = mDirtyRoots.PopShallowestRoot();
if (!target->IsSubtreeDirty()) {
// It's not dirty anymore, which probably means the notification
// was posted in the middle of a reflow (perhaps with a reflow
// root in the middle). Don't do anything.
continue;
}
interrupted = !DoReflow(target, aInterruptible, &overflowTracker);
// Keep going until we're out of reflow commands, or we've run
// past our deadline, or we're interrupted.
} while (!interrupted && !mDirtyRoots.IsEmpty() &&
(!aInterruptible || PR_IntervalNow() < deadline));
interrupted = !mDirtyRoots.IsEmpty();
overflowTracker.Flush();
if (!interrupted) {
// We didn't get interrupted. Go ahead and perform scroll anchor
// adjustments.
FlushPendingScrollAnchorAdjustments();
if (!target->IsSubtreeDirty()) {
// It's not dirty anymore, which probably means the notification
// was posted in the middle of a reflow (perhaps with a reflow
// root in the middle). Don't do anything.
continue;
}
}
// Exiting the scriptblocker might have killed us
if (!mIsDestroying) {
DidDoReflow(aInterruptible);
}
interrupted = !DoReflow(target, aInterruptible, &overflowTracker);
// DidDoReflow might have killed us
if (!mIsDestroying) {
// Keep going until we're out of reflow commands, or we've run
// past our deadline, or we're interrupted.
} while (!interrupted && !mDirtyRoots.IsEmpty() &&
(!aInterruptible || PR_IntervalNow() < deadline));
interrupted = !mDirtyRoots.IsEmpty();
overflowTracker.Flush();
if (!interrupted) {
// We didn't get interrupted. Go ahead and perform scroll anchor
// adjustments.
FlushPendingScrollAnchorAdjustments();
}
mPendingDidDoReflow = true;
}
// Exiting the scriptblocker might have killed us. If we were processing
// scroll commands, let the outermost call deal with it.
if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) {
DidDoReflow(aInterruptible);
}
// DidDoReflow might have killed us
if (!mIsDestroying) {
#ifdef DEBUG
if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
(void*)this);
}
DoVerifyReflow();
if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
(void*)this);
}
DoVerifyReflow();
#endif
// If any new reflow commands were enqueued during the reflow, schedule
// another reflow event to process them. Note that we want to do this
// after DidDoReflow(), since that method can change whether there are
// dirty roots around by flushing, and there's no point in posting a
// reflow event just to have the flush revoke it.
if (!mDirtyRoots.IsEmpty()) {
MaybeScheduleReflow();
// And record that we might need flushing
SetNeedLayoutFlush();
}
// If any new reflow commands were enqueued during the reflow, schedule
// another reflow event to process them. Note that we want to do this
// after DidDoReflow(), since that method can change whether there are
// dirty roots around by flushing, and there's no point in posting a
// reflow event just to have the flush revoke it.
if (!mDirtyRoots.IsEmpty()) {
MaybeScheduleReflow();
// And record that we might need flushing
SetNeedLayoutFlush();
}
}