Bug 263359 part 4: resolve paragraph on encountering line breaks in preformatted elements. r=roc
authorSimon Montagu <smontagu@smontagu.org>
Mon, 11 Apr 2011 11:00:28 +0300
changeset 67834 cde1da5d8a8a108782aede8aa122f75d442b4234
parent 67833 85f334fb73b1b5fc60e993f9b9f6ac6320e75357
child 67835 1e2727bc8036a98f9c2c9acb791eba62b7ae7c3d
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs263359
milestone2.2a1pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 263359 part 4: resolve paragraph on encountering line breaks in preformatted elements. r=roc
layout/base/nsBidiPresUtils.cpp
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -51,16 +51,17 @@
 #include "nsCSSFrameConstructor.h"
 #include "nsHTMLContainerFrame.h"
 #include "nsInlineFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "nsContainerFrame.h"
 #include "nsFirstLetterFrame.h"
 #include "gfxUnicodeProperties.h"
 #include "nsIThebesFontMetrics.h"
+#include "nsTextFrame.h"
 
 #undef NOISY_BIDI
 #undef REALLY_NOISY_BIDI
 
 using namespace mozilla;
 
 static const PRUnichar kSpace            = 0x0020;
 static const PRUnichar kLineSeparator    = 0x2028;
@@ -160,87 +161,99 @@ SplitInlineAncestors(nsIFrame*     aFram
     
     frame = parent;
     parent = grandparent;
   }
   
   return NS_OK;
 }
 
-// Convert bidi continuations to fluid continuations for a frame and all of its
-// inline ancestors.
+static void
+MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext)
+{
+  NS_ASSERTION (!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext, 
+                "next-in-flow is not next continuation!");
+  aFrame->SetNextInFlow(aNext);
+
+  NS_ASSERTION (!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
+                "prev-in-flow is not prev continuation!");
+  aNext->SetPrevInFlow(aFrame);
+}
+
+// If aFrame is the last child of its parent, convert bidi continuations to
+// fluid continuations for all of its inline ancestors.
 static void
 JoinInlineAncestors(nsIFrame* aFrame)
 {
-  nsIFrame* frame = aFrame;
+  if (aFrame->GetNextSibling()) {
+    return;
+  }
+  nsIFrame* frame = aFrame->GetParent();
   while (frame && IsBidiSplittable(frame)) {
     nsIFrame* next = frame->GetNextContinuation();
     if (next) {
-      NS_ASSERTION (!frame->GetNextInFlow() || frame->GetNextInFlow() == next, 
-                    "next-in-flow is not next continuation!");
-      frame->SetNextInFlow(next);
-
-      NS_ASSERTION (!next->GetPrevInFlow() || next->GetPrevInFlow() == frame,
-                    "prev-in-flow is not prev continuation!");
-      next->SetPrevInFlow(frame);
+      MakeContinuationFluid(frame, next);
     }
     // Join the parent only as long as we're its last child.
     if (frame->GetNextSibling())
       break;
     frame = frame->GetParent();
   }
 }
 
 static nsresult
-CreateBidiContinuation(nsIFrame*       aFrame,
-                       nsIFrame**      aNewFrame)
+CreateContinuation(nsIFrame*       aFrame,
+                   nsIFrame**      aNewFrame,
+                   PRBool          aIsFluid)
 {
   NS_PRECONDITION(aNewFrame, "null OUT ptr");
   NS_PRECONDITION(aFrame, "null ptr");
 
   *aNewFrame = nsnull;
 
   nsPresContext *presContext = aFrame->PresContext();
   nsIPresShell *presShell = presContext->PresShell();
-  NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateBidiContinuation");
+  NS_ASSERTION(presShell, "PresShell must be set on PresContext before calling nsBidiPresUtils::CreateContinuation");
 
   nsIFrame* parent = aFrame->GetParent();
-  NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateBidiContinuation");
+  NS_ASSERTION(parent, "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
 
   nsresult rv = NS_OK;
   
   // Have to special case floating first letter frames because the continuation
   // doesn't go in the first letter frame. The continuation goes with the rest
   // of the text that the first letter frame was made out of.
   if (parent->GetType() == nsGkAtoms::letterFrame &&
       parent->GetStyleDisplay()->IsFloating()) {
     nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
     rv = letterFrame->CreateContinuationForFloatingParent(presContext, aFrame,
-                                                          aNewFrame, PR_FALSE);
+                                                          aNewFrame, aIsFluid);
     return rv;
   }
 
   rv = presShell->FrameConstructor()->
-    CreateContinuingFrame(presContext, aFrame, parent, aNewFrame, PR_FALSE);
+    CreateContinuingFrame(presContext, aFrame, parent, aNewFrame, aIsFluid);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // The list name nsGkAtoms::nextBidi would indicate we don't want reflow
   // XXXbz this needs higher-level framelist love
   nsFrameList temp(*aNewFrame, *aNewFrame);
   rv = parent->InsertFrames(nsGkAtoms::nextBidi, aFrame, temp);
   if (NS_FAILED(rv)) {
     return rv;
   }
   
-  // Split inline ancestor frames
-  rv = SplitInlineAncestors(aFrame);
-  if (NS_FAILED(rv)) {
-    return rv;
+  if (!aIsFluid) {
+    // Split inline ancestor frames
+    rv = SplitInlineAncestors(aFrame);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
   }
 
   return NS_OK;
 }
 
 static PRBool
 IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
                      nsIFrame* aPrevFrame, nsIFrame* aFrame)
@@ -281,17 +294,17 @@ AdvanceLineIteratorToFrame(nsIFrame* aFr
   aPrevFrame = child;
 }
 
 /*
  * Overview of the implementation of Resolve():
  *
  *  Walk through the descendants of aBlockFrame and build:
  *   * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
- *   * mBuffer: an nsAutoString containing a representation of
+ *   * mBuffer: an nsString containing a representation of
  *     the content of the frames.
  *     In the case of text frames, this is the actual text context of the
  *     frames, but some other elements are represented in a symbolic form which
  *     will make the Unicode Bidi Algorithm give the correct results.
  *     Bidi embeddings and overrides set by CSS or <bdo> elements are
  *     represented by the corresponding Unicode control characters.
  *     <br> elements are represented by U+2028 LINE SEPARATOR
  *     Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
@@ -383,16 +396,17 @@ nsBidiPresUtils::Resolve(nsBlockFrame* a
        block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
     block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
     TraverseFrames(aBlockFrame, block->GetFirstChild(nsnull));
   }
 
   if (ch != 0) {
     mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME);
     mBuffer.Append(kPDF);
+    NS_ASSERTION(mEmbeddingStack.Length(), "embedding/override underflow");
     mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1);
   }
 
   // Resolve final paragraph
   ResolveParagraph(aBlockFrame);
   return mSuccess;
 }
 
@@ -401,17 +415,16 @@ nsBidiPresUtils::ResolveParagraph(nsBloc
 {
   nsPresContext *presContext = aBlockFrame->PresContext();
   PRInt32 bufferLength = mBuffer.Length();
 
   if (bufferLength < 1) {
     mSuccess = NS_OK;
     return;
   }
-  // XXX: TODO: Handle preformatted text ('\n')
   mBuffer.ReplaceChar("\t\r\n", kSpace);
 
   PRInt32 runCount;
   PRUint8 embeddingLevel = mParaLevel;
 
   mSuccess = mBidiEngine->SetPara(mBuffer.get(), bufferLength, mParaLevel, nsnull);
   if (NS_FAILED(mSuccess) ) {
       return;
@@ -558,22 +571,27 @@ nsBidiPresUtils::ResolveParagraph(nsBloc
             /*
              * There is more text that belongs to this directional run in the next
              * text frame: make sure it is a fluid continuation of the current frame.
              * Do not advance frameIndex, because the next frame may contain
              * multi-directional text and need to be split
              */
             PRInt32 newIndex = frameIndex;
             do {
-            } while (mLogicalFrames[++newIndex] == NS_BIDI_CONTROL_FRAME);
-            RemoveBidiContinuation(frame, frameIndex, newIndex, lineOffset);
-          } else if (runLength == fragmentLength) {
+            } while (++newIndex < frameCount &&
+                     mLogicalFrames[newIndex] == NS_BIDI_CONTROL_FRAME);
+            if (newIndex < frameCount) {
+              RemoveBidiContinuation(frame, frameIndex, newIndex, lineOffset);
+            }
+          } else if (runLength == fragmentLength &&
+                     numRun + 1 < runCount) {
             /*
-             * The directional run ends at the end of the frame. Make sure that
-             * the next frame is a non-fluid continuation
+             * If the directional run ends at the end of the frame, and this is
+             * not the end of our paragraph, make sure that the next frame is a
+             * non-fluid continuation
              */
             nsIFrame* next = frame->GetNextInFlow();
             if (next) {
               frame->SetNextContinuation(next);
               next->SetPrevContinuation(frame);
             }
           }
           frame->AdjustOffsetsForBidi(contentOffset, contentOffset + fragmentLength);
@@ -616,22 +634,21 @@ nsBidiPresUtils::ResolveParagraph(nsBloc
             next->SetPrevContinuation(parent);
           }
           child = parent;
           parent = child->GetParent();
         }
         if (parent && IsBidiSplittable(parent))
           SplitInlineAncestors(child);
       }
-      else if (!frame->GetNextSibling()) {
-        // We're not at an end of a run, and |frame| is the last child of its parent.
-        // If its ancestors happen to have bidi continuations, convert them into
-        // fluid continuations.
-        nsIFrame* parent = frame->GetParent();
-        JoinInlineAncestors(parent);
+      else {
+        // We're not at an end of a run. If |frame| is the last child of its
+        // parent, and its ancestors happen to have bidi continuations, convert
+        // them into fluid continuations.
+        JoinInlineAncestors(frame);
       }
     }
   } // for
 
 #ifdef DEBUG
 #ifdef REALLY_NOISY_BIDI
   printf("---\nAfter Resolve(), frameTree =:\n");
   aBlockFrame->List(stdout, 0);
@@ -725,17 +742,104 @@ nsBidiPresUtils::TraverseFrames(nsBlockF
       }
       mLogicalFrames.AppendElement(frame);
 
       // Append the content of the frame to the paragraph buffer
       nsIAtom* frameType = frame->GetType();
       if (nsGkAtoms::textFrame == frameType) {
         if (content != mPrevContent) {
           mPrevContent = content;
-          content->AppendTextTo(mBuffer);
+          if (!frame->GetStyleContext()->GetStyleText()->NewlineIsSignificant()) {
+            content->AppendTextTo(mBuffer);
+          } else {
+            /*
+             * For preformatted text we have to do bidi resolution on each line
+             * separately. 
+             */
+            nsAutoString text;
+            content->AppendTextTo(text);
+            nsIFrame* next;
+            do {
+              next = nsnull;
+
+              PRInt32 start, end;
+              frame->GetOffsets(start, end);
+              PRInt32 endLine = text.FindCharInSet(NS_LITERAL_STRING("\n\r"),
+                                                   start);
+              if (endLine == -1) {
+                /*
+                 * If there is no newline in the frame, just save the text and
+                 * do bidi resolution later
+                 */
+                mBuffer.Append(Substring(text, start));
+                break;
+              }
+
+              /*
+               * If there is a newline in the frame, break the frame after the
+               * newline, do bidi resolution and repeat until the end of the
+               * element.
+               */
+              ++endLine;
+
+              /*
+               * If the frame ends before the new line, save the text and move
+               * into the next continuation
+               */
+              while (end < endLine) {
+                mBuffer.Append(Substring(text, start, end - start));
+                frame = frame->GetNextContinuation();
+                NS_ASSERTION(frame, "Premature end of continuation chain");
+                frame->GetOffsets(start, end);
+                mLogicalFrames.AppendElement(frame);
+
+                /*
+                 * If we have already overshot the saved next-sibling while
+                 * scanning the frame's continuations, advance it.
+                 */
+                if (frame == nextSibling) {
+                  nextSibling = frame->GetNextSibling();
+                }
+              }
+
+              mBuffer.Append(Substring(text, start, endLine - start));
+
+              if (PRUint32(endLine) < text.Length()) {
+                nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+                textFrame->SetLength(endLine - start, nsnull);
+                next = frame->GetNextInFlow();
+                if (!next) {
+                  // If the frame already has a bidi continuation, make it fluid
+                  next = frame->GetNextContinuation();
+                  if (next) {
+                    MakeContinuationFluid(frame, next);
+                    JoinInlineAncestors(frame);
+                  } else {
+                    // If the frame has no next in flow, create one
+                    CreateContinuation(frame, &next, PR_TRUE);
+                  }
+                }
+              }
+              ResolveParagraphWithinBlock(aBlockFrame);
+
+              if (next) {
+                frame = next;
+                mLogicalFrames.AppendElement(frame);
+              }
+
+              /*
+               * If we have already overshot the saved next-sibling while
+               * scanning the frame's continuations, advance it.
+               */
+              if (frame && frame == nextSibling) {
+                nextSibling = frame->GetNextSibling();
+              }
+
+            } while (next);
+          }
         }
       } else if (nsGkAtoms::brFrame == frameType) {
         // break frame -- append line separator
         mBuffer.Append(kLineSeparator);
         ResolveParagraphWithinBlock(aBlockFrame);
       } else { 
         // other frame type -- see the Unicode Bidi Algorithm:
         // "...inline objects (such as graphics) are treated as if they are ...
@@ -754,16 +858,17 @@ nsBidiPresUtils::TraverseFrames(nsBlockF
     }
 
     // If the element is attributed by dir, indicate direction pop (add PDF frame)
     if (ch != 0 && isLastFrame) {
       // Add a dummy frame pointer representing a bidi control code after the
       // last frame of an element specifying embedding or override
       mLogicalFrames.AppendElement(NS_BIDI_CONTROL_FRAME);
       mBuffer.Append(kPDF);
+      NS_ASSERTION(mEmbeddingStack.Length(), "embedding/override underflow");
       mEmbeddingStack.TruncateLength(mEmbeddingStack.Length() - 1);
     }
     childFrame = nextSibling;
   } while (childFrame);
 }
 
 void
 nsBidiPresUtils::ResolveParagraphWithinBlock(nsBlockFrame* aBlockFrame)
@@ -1236,17 +1341,17 @@ nsBidiPresUtils::EnsureBidiContinuation(
                                         PRInt32&        aFrameIndex,
                                         PRInt32         aStart,
                                         PRInt32         aEnd)
 {
   NS_PRECONDITION(aNewFrame, "null OUT ptr");
   NS_PRECONDITION(aFrame, "aFrame is null");
 
   aFrame->AdjustOffsetsForBidi(aStart, aEnd);
-  mSuccess = CreateBidiContinuation(aFrame, aNewFrame);
+  mSuccess = CreateContinuation(aFrame, aNewFrame, PR_FALSE);
 }
 
 void
 nsBidiPresUtils::RemoveBidiContinuation(nsIFrame*       aFrame,
                                         PRInt32         aFirstIndex,
                                         PRInt32         aLastIndex,
                                         PRInt32&        aOffset) const
 {
@@ -1268,24 +1373,17 @@ nsBidiPresUtils::RemoveBidiContinuation(
       frameProps.Set(nsIFrame::EmbeddingLevelProperty(),
                      NS_INT32_TO_PTR(embeddingLevel));
       frameProps.Set(nsIFrame::BaseLevelProperty(),
                      NS_INT32_TO_PTR(baseLevel));
       frame->AddStateBits(NS_FRAME_IS_BIDI);
       while (frame) {
         nsIFrame* prev = frame->GetPrevContinuation();
         if (prev) {
-          NS_ASSERTION (!frame->GetPrevInFlow() || frame->GetPrevInFlow() == prev, 
-                        "prev-in-flow is not prev continuation!");
-          frame->SetPrevInFlow(prev);
-
-          NS_ASSERTION (!prev->GetNextInFlow() || prev->GetNextInFlow() == frame,
-                        "next-in-flow is not next continuation!");
-          prev->SetNextInFlow(frame);
-
+          MakeContinuationFluid(prev, frame);
           frame = frame->GetParent();
         } else {
           break;
         }
       }
     }
   }
 }