diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index eb1f7b63d..f549ed6a8 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -397,6 +397,7 @@ nsFrame::nsFrame(nsStyleContext* aContext) mState = NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY; mMayHaveRoundedCorners = false; + mReflowRequestedForCharDataChange = false; mStyleContext = aContext; mStyleContext->AddRef(); #ifdef DEBUG diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index e514caf27..eddf73067 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -3154,6 +3154,23 @@ protected: bool mMayHaveRoundedCorners : 1; + /** + * This bit is used in nsTextFrame::CharacterDataChanged() as an optimization + * to skip redundant reflow-requests when the character data changes multiple + * times between reflows. If this flag is set, then it implies that the + * NS_FRAME_IS_DIRTY state bit is also set (and that intrinsic sizes have + * been marked as dirty on our ancestor chain). + * + * XXXdholbert This bit is *only* used on nsTextFrame, but it lives here on + * nsIFrame simply because this is where we've got unused state bits + * available in a gap. If bits become more scarce, we should perhaps consider + * expanding the range of frame-specific state bits in nsFrameStateBits.h and + * moving this to be one of those (e.g. by swapping one of the adjacent + * general-purpose bits to take the place of this bool:1 here, so we can grow + * that range of frame-specific bits by 1). + */ + bool mReflowRequestedForCharDataChange : 1; + // Helpers /** * Can we stop inside this frame when we're skipping non-rendered whitespace? diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 819ffd1ee..00c7ed34a 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -4589,7 +4589,12 @@ nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) } int32_t endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength; - nsTextFrame* lastDirtiedFrame = nullptr; + + // Parent of the last frame that we passed to FrameNeedsReflow (or noticed + // had already received an earlier FrameNeedsReflow call). + // (For subsequent frames with this same parent, we can just set their + // dirty bit without bothering to call FrameNeedsReflow again.) + nsIFrame* lastDirtiedFrameParent = nullptr; nsIPresShell* shell = PresContext()->GetPresShell(); do { @@ -4597,17 +4602,39 @@ nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) // if this was a pure insertion). textFrame->mState &= ~TEXT_WHITESPACE_FLAGS; textFrame->ClearTextRuns(); - if (!lastDirtiedFrame || - lastDirtiedFrame->GetParent() != textFrame->GetParent()) { - // Ask the parent frame to reflow me. - shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange, - NS_FRAME_IS_DIRTY); - lastDirtiedFrame = textFrame; + + nsIFrame* parentOfTextFrame = textFrame->GetParent(); + bool areAncestorsAwareOfReflowRequest = false; + if (lastDirtiedFrameParent == parentOfTextFrame) { + // An earlier iteration of this loop already called + // FrameNeedsReflow for a sibling of |textFrame|. + areAncestorsAwareOfReflowRequest = true; } else { - // if the parent is a block, we're cheating here because we should - // be marking our line dirty, but we're not. nsTextFrame::SetLength - // will do that when it gets called during reflow. - textFrame->AddStateBits(NS_FRAME_IS_DIRTY); + lastDirtiedFrameParent = parentOfTextFrame; + } + + if (textFrame->mReflowRequestedForCharDataChange) { + // We already requested a reflow for this frame; nothing to do. + MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY), + "mReflowRequestedForCharDataChange should only be set " + "on dirty frames"); + } else { + // Make sure textFrame is queued up for a reflow. Also set a flag so we + // don't waste time doing this again in repeated calls to this method. + textFrame->mReflowRequestedForCharDataChange = true; + if (!areAncestorsAwareOfReflowRequest) { + // Ask the parent frame to reflow me. + shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + } else { + // We already called FrameNeedsReflow on behalf of an earlier sibling, + // so we can just mark this frame as dirty and don't need to bother + // telling its ancestors. + // Note: if the parent is a block, we're cheating here because we should + // be marking our line dirty, but we're not. nsTextFrame::SetLength will + // do that when it gets called during reflow. + textFrame->AddStateBits(NS_FRAME_IS_DIRTY); + } } textFrame->InvalidateFrame(); @@ -8514,8 +8541,10 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, // Clear out the reflow state flags in mState. We also clear the whitespace // flags because this can change whether the frame maps whitespace-only text - // or not. + // or not. We also clear the flag that tracks whether we had a pending + // reflow request from CharacterDataChanged (since we're reflowing now). RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); + mReflowRequestedForCharDataChange = false; // Temporarily map all possible content while we construct our new textrun. // so that when doing reflow our styles prevail over any part of the