Bug 129115. Change the reflow path from a list structure to a tree structure to amortize state recovery and damage propagation costs. Eliminate table timeout reflows. r=kin,karnaze,rods,rbs; sr=attinasi
git-svn-id: svn://10.0.0.236/trunk@121265 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -54,7 +54,7 @@
|
||||
#include "nsIFrameManager.h"
|
||||
#include "nsIPresContext.h"
|
||||
#include "nsIPresShell.h"
|
||||
#include "nsHTMLReflowCommand.h"
|
||||
#include "nsReflowPath.h"
|
||||
#include "nsIStyleContext.h"
|
||||
#include "nsIView.h"
|
||||
#include "nsIFontMetrics.h"
|
||||
@@ -698,17 +698,18 @@ nsBlockFrame::Reflow(nsIPresContext* aPresContext,
|
||||
reflow.Append(nsHTMLReflowState::ReasonToString(aReflowState.reason));
|
||||
|
||||
if (aReflowState.reason == eReflowReason_Incremental) {
|
||||
reflow += " (";
|
||||
nsHTMLReflowCommand *command = aReflowState.path->mReflowCommand;
|
||||
|
||||
nsReflowType type;
|
||||
aReflowState.reflowCommand->GetType(type);
|
||||
reflow += kReflowCommandType[type];
|
||||
if (command) {
|
||||
// We're the target.
|
||||
reflow += " (";
|
||||
|
||||
nsIFrame* target;
|
||||
aReflowState.reflowCommand->GetTarget(target);
|
||||
reflow += nsPrintfCString("@%p", target);
|
||||
nsReflowType type;
|
||||
command->GetType(type);
|
||||
reflow += kReflowCommandType[type];
|
||||
|
||||
reflow += ")";
|
||||
reflow += ")";
|
||||
}
|
||||
}
|
||||
|
||||
IndentBy(stdout, gNoiseIndent);
|
||||
@@ -772,7 +773,7 @@ nsBlockFrame::Reflow(nsIPresContext* aPresContext,
|
||||
#else
|
||||
nsHTMLReflowState reflowState(aReflowState);
|
||||
reflowState.reason = eReflowReason_Resize;
|
||||
reflowState.reflowCommand = nsnull;
|
||||
reflowState.path = nsnull;
|
||||
nsBlockFrame::Reflow(aPresContext, aMetrics, reflowState, aStatus);
|
||||
#endif
|
||||
|
||||
@@ -848,7 +849,6 @@ nsBlockFrame::Reflow(nsIPresContext* aPresContext,
|
||||
|
||||
nsresult rv = NS_OK;
|
||||
|
||||
nsIFrame* target;
|
||||
switch (aReflowState.reason) {
|
||||
case eReflowReason_Initial:
|
||||
#ifdef NOISY_REFLOW_REASON
|
||||
@@ -864,14 +864,18 @@ nsBlockFrame::Reflow(nsIPresContext* aPresContext,
|
||||
// Do nothing; the dirty lines will already have been marked.
|
||||
break;
|
||||
|
||||
case eReflowReason_Incremental:
|
||||
aReflowState.reflowCommand->GetTarget(target);
|
||||
if (this == target) {
|
||||
nsReflowType type;
|
||||
aReflowState.reflowCommand->GetType(type);
|
||||
case eReflowReason_Incremental: {
|
||||
#ifdef NOISY_REFLOW_REASON
|
||||
ListTag(stdout);
|
||||
printf(": reflow=incremental type=%d\n", type);
|
||||
ListTag(stdout);
|
||||
printf(": reflow=incremental ");
|
||||
#endif
|
||||
nsReflowPath *path = aReflowState.path;
|
||||
nsHTMLReflowCommand *command = path->mReflowCommand;
|
||||
if (command) {
|
||||
nsReflowType type;
|
||||
command->GetType(type);
|
||||
#ifdef NOISY_REFLOW_REASON
|
||||
printf("type=%s ", kReflowCommandType[type]);
|
||||
#endif
|
||||
switch (type) {
|
||||
case eReflowType_StyleChanged:
|
||||
@@ -880,28 +884,44 @@ nsBlockFrame::Reflow(nsIPresContext* aPresContext,
|
||||
case eReflowType_ReflowDirty:
|
||||
// Do nothing; the dirty lines will already have been marked.
|
||||
break;
|
||||
case eReflowType_ContentChanged:
|
||||
// Perform a full reflow.
|
||||
rv = PrepareResizeReflow(state);
|
||||
break;
|
||||
default:
|
||||
// Map any other incremental operations into full reflows
|
||||
// We shouldn't get here. But, if we do, perform a full reflow.
|
||||
NS_ERROR("unexpected reflow type");
|
||||
rv = PrepareResizeReflow(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Get next frame in reflow command chain
|
||||
aReflowState.reflowCommand->GetNext(state.mNextRCFrame);
|
||||
|
||||
if (path->FirstChild() != path->EndChildren()) {
|
||||
// We're along the reflow path, but not necessarily the target
|
||||
// of the reflow.
|
||||
#ifdef NOISY_REFLOW_REASON
|
||||
ListTag(stdout);
|
||||
printf(": reflow=incremental");
|
||||
if (state.mNextRCFrame) {
|
||||
printf(" next=");
|
||||
nsFrame::ListTag(stdout, state.mNextRCFrame);
|
||||
printf(" next={ ");
|
||||
|
||||
for (nsReflowPath::iterator iter = path->FirstChild();
|
||||
iter != path->EndChildren();
|
||||
++iter) {
|
||||
nsFrame::ListTag(stdout, *iter);
|
||||
printf(" ");
|
||||
}
|
||||
printf("\n");
|
||||
|
||||
printf("}");
|
||||
#endif
|
||||
|
||||
rv = PrepareChildIncrementalReflow(state);
|
||||
}
|
||||
|
||||
#ifdef NOISY_REFLOW_REASON
|
||||
printf("\n");
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case eReflowReason_StyleChange:
|
||||
DrainOverflowLines(aPresContext);
|
||||
@@ -1522,71 +1542,76 @@ nsBlockFrame::PrepareInitialReflow(nsBlockReflowState& aState)
|
||||
nsresult
|
||||
nsBlockFrame::PrepareChildIncrementalReflow(nsBlockReflowState& aState)
|
||||
{
|
||||
// Determine the line being impacted
|
||||
line_iterator line = FindLineFor(aState.mNextRCFrame);
|
||||
if (line == end_lines()) {
|
||||
// This assertion actually fires on lots of pages
|
||||
// (e.g., bugzilla, bugzilla query page), so limit it
|
||||
// to a few people until we fix the problem causing it.
|
||||
//
|
||||
// I think waterson explained once why it was happening -- I think
|
||||
// it has something to do with the interaction of the unconstrained
|
||||
// reflow in multi-pass reflow with the reflow command's chain, but
|
||||
// I don't remember the details.
|
||||
#if defined(DEBUG_dbaron) || defined(DEBUG_waterson)
|
||||
NS_NOTREACHED("We don't have a line for the target of the reflow. "
|
||||
"Being inefficient");
|
||||
#endif
|
||||
// This can't happen, but just in case it does...
|
||||
return PrepareResizeReflow(aState);
|
||||
}
|
||||
// XXXwaterson this is non-optimal. We'd rather do this in
|
||||
// ReflowDirtyLines; however, I'm not quite ready to figure out how
|
||||
// to deal with reflow path retargeting yet.
|
||||
nsReflowPath *path = aState.mReflowState.path;
|
||||
|
||||
if (line->IsInline()) {
|
||||
if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) {
|
||||
// We've been asked to compute the maximum width of the block
|
||||
// frame, which ReflowLine() will handle this by performing an
|
||||
// unconstrained reflow on the line. If this incremental reflow
|
||||
// is targeted at a continuing frame, we may have to retarget
|
||||
// it, as the unconstrained reflow can destroy some of the
|
||||
// continuations.
|
||||
nsReflowPath::iterator iter = path->FirstChild();
|
||||
nsReflowPath::iterator end = path->EndChildren();
|
||||
|
||||
for ( ; iter != end; ++iter) {
|
||||
// Determine the line being impacted
|
||||
line_iterator line = FindLineFor(*iter);
|
||||
if (line == end_lines()) {
|
||||
// This assertion actually fires on lots of pages
|
||||
// (e.g., bugzilla, bugzilla query page), so limit it
|
||||
// to a few people until we fix the problem causing it.
|
||||
//
|
||||
// XXXwaterson if we implement some sort of ``reflow root''
|
||||
// frame, rather than requiring incremental reflows to always
|
||||
// walk from the real root frame, this code may not be needed
|
||||
// anymore. Or will it?
|
||||
NS_ASSERTION(aState.mNextRCFrame, "aState has no mNextRCFrame");
|
||||
|
||||
nsIFrame *prevInFlow;
|
||||
aState.mNextRCFrame->GetPrevInFlow(&prevInFlow);
|
||||
if (prevInFlow)
|
||||
RetargetInlineIncrementalReflow(aState, line, prevInFlow);
|
||||
// I think waterson explained once why it was happening -- I think
|
||||
// it has something to do with the interaction of the unconstrained
|
||||
// reflow in multi-pass reflow with the reflow command's chain, but
|
||||
// I don't remember the details.
|
||||
#if defined(DEBUG_dbaron) || defined(DEBUG_waterson)
|
||||
NS_NOTREACHED("We don't have a line for the target of the reflow. "
|
||||
"Being inefficient");
|
||||
#endif
|
||||
// This can't happen, but just in case it does...
|
||||
PrepareResizeReflow(aState);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line->IsInline()) {
|
||||
if (aState.GetFlag(BRS_COMPUTEMAXWIDTH)) {
|
||||
// We've been asked to compute the maximum width of the block
|
||||
// frame, which ReflowLine() will handle by performing an
|
||||
// unconstrained reflow on the line. If this incremental
|
||||
// reflow is targeted at a continuing frame, we may have to
|
||||
// retarget it, as the unconstrained reflow can destroy some
|
||||
// of the continuations. This will allow the incremental
|
||||
// reflow to arrive at the target frame during the first-pass
|
||||
// unconstrained reflow.
|
||||
nsIFrame *prevInFlow;
|
||||
(*iter)->GetPrevInFlow(&prevInFlow);
|
||||
if (prevInFlow)
|
||||
RetargetInlineIncrementalReflow(iter, line, prevInFlow);
|
||||
}
|
||||
}
|
||||
|
||||
// Just mark this line dirty. We never need to mark the
|
||||
// previous line dirty since either:
|
||||
// * the line is a block, and there would never be a chance to pull
|
||||
// something up
|
||||
// * It's an incremental reflow to something within an inline, which
|
||||
// we know must be very limited.
|
||||
line->MarkDirty();
|
||||
}
|
||||
|
||||
// Just mark this line dirty. We never need to mark the
|
||||
// previous line dirty since either:
|
||||
// * the line is a block, and there would never be a chance to pull
|
||||
// something up
|
||||
// * It's an incremental reflow to something within an inline, which
|
||||
// we know must be very limited.
|
||||
line->MarkDirty();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsBlockFrame::RetargetInlineIncrementalReflow(nsBlockReflowState &aState,
|
||||
nsBlockFrame::RetargetInlineIncrementalReflow(nsReflowPath::iterator &aTarget,
|
||||
line_iterator &aLine,
|
||||
nsIFrame *aPrevInFlow)
|
||||
{
|
||||
// To retarget the reflow, we'll walk back through the continuations
|
||||
// until we reach the primary frame, or we reach a continuation that
|
||||
// is preceded by a ``hard'' line break.
|
||||
NS_ASSERTION(aLine->Contains(aState.mNextRCFrame),
|
||||
NS_ASSERTION(aLine->Contains(*aTarget),
|
||||
"line doesn't contain the target of the incremental reflow");
|
||||
|
||||
// Now fix aState's mNextRCFrame, keeping track of how many lines we
|
||||
// walk back through.
|
||||
// Now fix the iterator, keeping track of how many lines we walk
|
||||
// back through.
|
||||
PRInt32 lineCount = 0;
|
||||
do {
|
||||
// XXX this might happen if the block is split; e.g.,
|
||||
@@ -1601,13 +1626,13 @@ nsBlockFrame::RetargetInlineIncrementalReflow(nsBlockReflowState &aState,
|
||||
if (aLine->GetBreakType() == NS_STYLE_CLEAR_LINE)
|
||||
break;
|
||||
|
||||
aState.mNextRCFrame = aPrevInFlow;
|
||||
aState.mNextRCFrame->GetPrevInFlow(&aPrevInFlow);
|
||||
*aTarget = aPrevInFlow;
|
||||
aPrevInFlow->GetPrevInFlow(&aPrevInFlow);
|
||||
|
||||
#ifdef DEBUG
|
||||
// Paranoia. Ensure that the prev-in-flow is really in the
|
||||
// previous line.
|
||||
line_iterator check = FindLineFor(aState.mNextRCFrame);
|
||||
line_iterator check = FindLineFor(*aTarget);
|
||||
NS_ASSERTION(check == aLine, "prev-in-flow not in previous linebox");
|
||||
#endif
|
||||
|
||||
@@ -1616,6 +1641,15 @@ nsBlockFrame::RetargetInlineIncrementalReflow(nsBlockReflowState &aState,
|
||||
|
||||
if (lineCount > 0) {
|
||||
// Fix any frames deeper in the reflow path.
|
||||
#if 0
|
||||
// XXXwaterson fix me! we've got to recurse through the iterator's
|
||||
// kids. This really shouldn't matter unless we want to implement
|
||||
// `display: inline-block' or do XBL form controls. Why, you ask?
|
||||
// Because what will happen is that inline frames will get flowed
|
||||
// with a resize reflow, which will be sufficient to mask any
|
||||
// glitches that would otherwise occur. However, as soon as boxes
|
||||
// or blocks end up in the flow here (and aren't explicit reflow
|
||||
// roots), they may optimize away the resize reflow.
|
||||
|
||||
// Get the reflow path, which is stored as a stack (i.e., the next
|
||||
// frame in the reflow is at the _end_ of the array).
|
||||
@@ -1640,6 +1674,9 @@ nsBlockFrame::RetargetInlineIncrementalReflow(nsBlockReflowState &aState,
|
||||
|
||||
path->ReplaceElementAt(frame, i);
|
||||
}
|
||||
#else
|
||||
NS_WARNING("blowing an incremental reflow targeted at a nested inline");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2048,12 +2085,16 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState)
|
||||
#ifdef DEBUG
|
||||
if (gNoisyReflow) {
|
||||
if (aState.mReflowState.reason == eReflowReason_Incremental) {
|
||||
nsReflowType type;
|
||||
aState.mReflowState.reflowCommand->GetType(type);
|
||||
IndentBy(stdout, gNoiseIndent);
|
||||
ListTag(stdout);
|
||||
printf(": incrementally reflowing dirty lines: type=%s(%d)",
|
||||
kReflowCommandType[type], type);
|
||||
printf(": incrementally reflowing dirty lines");
|
||||
|
||||
nsHTMLReflowCommand *command = aState.mReflowState.path->mReflowCommand;
|
||||
if (command) {
|
||||
nsReflowType type;
|
||||
command->GetType(type);
|
||||
printf(": type=%s(%d)", kReflowCommandType[type], type);
|
||||
}
|
||||
}
|
||||
else {
|
||||
IndentBy(stdout, gNoiseIndent);
|
||||
@@ -3031,7 +3072,6 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
|
||||
nsBlockReflowContext brc(aState.mPresContext, aState.mReflowState,
|
||||
aState.GetFlag(BRS_COMPUTEMAXELEMENTSIZE),
|
||||
aState.GetFlag(BRS_COMPUTEMAXWIDTH));
|
||||
brc.SetNextRCFrame(aState.mNextRCFrame);
|
||||
|
||||
// See if we should apply the top margin. If the block frame being
|
||||
// reflowed is a continuation (non-null prev-in-flow) then we don't
|
||||
@@ -3069,14 +3109,16 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
|
||||
// correct available space (there might be a floater that's
|
||||
// already been placed below the aState.mPrevBottomMargin
|
||||
|
||||
// Setup a reflowState to get the style computed margin-top value
|
||||
// Setup a reflowState to get the style computed margin-top
|
||||
// value. We'll use a reason of `resize' so that we don't fudge
|
||||
// any incremental reflow state.
|
||||
|
||||
// The availSpace here is irrelevant to our needs - all we want
|
||||
// out if this setup is the margin-top value which doesn't depend
|
||||
// on the childs available space.
|
||||
nsSize availSpace(aState.mContentArea.width, NS_UNCONSTRAINEDSIZE);
|
||||
nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState,
|
||||
frame, availSpace);
|
||||
frame, availSpace, eReflowReason_Resize);
|
||||
|
||||
// Now compute the collapsed margin-top value into aState.mPrevBottomMargin
|
||||
nsCollapsingMargin oldPrevBottomMargin = aState.mPrevBottomMargin;
|
||||
@@ -3126,11 +3168,10 @@ nsBlockFrame::ReflowBlockFrame(nsBlockReflowState& aState,
|
||||
Invalidate(aState.mPresContext, mRect);
|
||||
}
|
||||
|
||||
if (frame == aState.mNextRCFrame) {
|
||||
// NULL out mNextRCFrame so if we reflow it again we don't think it's still
|
||||
// an incremental reflow
|
||||
aState.mNextRCFrame = nsnull;
|
||||
}
|
||||
// Remove the frame from the reflow tree.
|
||||
if (aState.mReflowState.path)
|
||||
aState.mReflowState.path->RemoveChild(frame);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
@@ -3613,13 +3654,18 @@ nsBlockFrame::ReflowInlineFrame(nsBlockReflowState& aState,
|
||||
// Reflow the inline frame
|
||||
nsReflowStatus frameReflowStatus;
|
||||
PRBool pushedFrame;
|
||||
nsresult rv = aLineLayout.ReflowFrame(aFrame, &aState.mNextRCFrame,
|
||||
frameReflowStatus, nsnull, pushedFrame);
|
||||
if (aFrame == aState.mNextRCFrame) {
|
||||
// NULL out mNextRCFrame so if we reflow it again we don't think it's still
|
||||
// an incremental reflow
|
||||
aState.mNextRCFrame = nsnull;
|
||||
}
|
||||
nsresult rv = aLineLayout.ReflowFrame(aFrame, frameReflowStatus,
|
||||
nsnull, pushedFrame);
|
||||
|
||||
// If this is an incremental reflow, prune the child from the path
|
||||
// so we don't incrementally reflow it again.
|
||||
// XXX note that we don't currently have any incremental reflows
|
||||
// that trace a path through an inline frame (thanks to reflow roots
|
||||
// dealing with text inputs), but we may need to deal with this
|
||||
// again, someday.
|
||||
if (aState.mReflowState.path)
|
||||
aState.mReflowState.path->RemoveChild(aFrame);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
@@ -5049,8 +5095,6 @@ nsBlockFrame::ReflowFloater(nsBlockReflowState& aState,
|
||||
computeMaxElementSize,
|
||||
aState.GetFlag(BRS_COMPUTEMAXWIDTH));
|
||||
|
||||
brc.SetNextRCFrame(aState.mNextRCFrame);
|
||||
|
||||
// Reflow the floater
|
||||
PRBool isAdjacentWithTop = aState.IsAdjacentWithTop();
|
||||
|
||||
@@ -5077,11 +5121,9 @@ nsBlockFrame::ReflowFloater(nsBlockReflowState& aState,
|
||||
Invalidate(aState.mPresContext, mRect);
|
||||
}
|
||||
|
||||
if (floater == aState.mNextRCFrame) {
|
||||
// Null out mNextRCFrame so if we reflow it again, we don't think
|
||||
// it's still an incremental reflow
|
||||
aState.mNextRCFrame = nsnull;
|
||||
}
|
||||
// Remove the floater from the reflow tree.
|
||||
if (aState.mReflowState.path)
|
||||
aState.mReflowState.path->RemoveChild(floater);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
@@ -6233,8 +6275,32 @@ nsBlockFrame::ReflowBullet(nsBlockReflowState& aState,
|
||||
nsSize availSize;
|
||||
availSize.width = NS_UNCONSTRAINEDSIZE;
|
||||
availSize.height = NS_UNCONSTRAINEDSIZE;
|
||||
nsHTMLReflowState reflowState(aState.mPresContext, aState.mReflowState,
|
||||
mBullet, availSize);
|
||||
|
||||
// Get the reason right.
|
||||
// XXXwaterson Should this look just like the logic in
|
||||
// nsBlockReflowContext::ReflowBlock and nsLineLayout::ReflowFrame?
|
||||
const nsHTMLReflowState &rs = aState.mReflowState;
|
||||
nsReflowReason reason = rs.reason;
|
||||
if (reason == eReflowReason_Incremental) {
|
||||
if (! rs.path->HasChild(mBullet)) {
|
||||
// An incremental reflow not explicitly destined to (or through)
|
||||
// the child should be treated as a resize...
|
||||
reason = eReflowReason_Resize;
|
||||
}
|
||||
|
||||
// ...unless its an incremental `style changed' reflow targeted at
|
||||
// the block, in which case, we propagate that to its children.
|
||||
nsHTMLReflowCommand *command = rs.path->mReflowCommand;
|
||||
if (command) {
|
||||
nsReflowType type;
|
||||
command->GetType(type);
|
||||
if (type == eReflowType_StyleChanged)
|
||||
reason = eReflowReason_StyleChange;
|
||||
}
|
||||
}
|
||||
|
||||
nsHTMLReflowState reflowState(aState.mPresContext, rs,
|
||||
mBullet, availSize, reason);
|
||||
nsReflowStatus status;
|
||||
mBullet->WillReflow(aState.mPresContext);
|
||||
mBullet->Reflow(aState.mPresContext, aMetrics, reflowState, status);
|
||||
|
||||
Reference in New Issue
Block a user