Bug 393096. Allow an element containing breakable whitespace to introduce a break opportunity no matter what the context. Also cleans up some trimming stuff and adds comprehensive whitespace breaking and trimming reftests. r+sr=dbaron
authorroc+@cs.cmu.edu
Sat, 20 Oct 2007 00:30:26 -0700
changeset 7069 11771c2119e38337ce443966bf2692e787925cc1
parent 7068 a1d9535e7d20e05eb4bd684b1b8517135eb902db
child 7070 1dab508b02c6ef79870693c116142d2e2b6a3079
push idunknown
push userunknown
push dateunknown
bugs393096
milestone1.9a9pre
Bug 393096. Allow an element containing breakable whitespace to introduce a break opportunity no matter what the context. Also cleans up some trimming stuff and adds comprehensive whitespace breaking and trimming reftests. r+sr=dbaron
content/base/public/nsLineBreaker.h
content/base/src/nsLineBreaker.cpp
layout/generic/nsLineLayout.cpp
layout/generic/nsLineLayout.h
layout/generic/nsTextFrameThebes.cpp
layout/reftests/text/reftest.list
layout/reftests/text/white-space-1-ref.html
layout/reftests/text/white-space-1a.html
layout/reftests/text/white-space-1b.html
layout/reftests/text/white-space-2-ref.html
layout/reftests/text/white-space-2.html
--- a/content/base/public/nsLineBreaker.h
+++ b/content/base/public/nsLineBreaker.h
@@ -68,19 +68,19 @@ public:
  * with each text chunk, which might happen during the corresponding AppendText
  * call, or might happen during a later AppendText call or even a Reset()
  * call.
  * 
  * The linebreak results MUST NOT depend on how the text is broken up
  * into AppendText calls.
  * 
  * The current strategy is that we break the overall text into
- * whitespace-delimited "words". Then for words that contain a "complex" 
- * character (currently CJK or Thai), we break within the word using complex
- * rules (JISx4051 or Pango).
+ * whitespace-delimited "words". Then those words are passed to the nsILineBreaker
+ * service for deeper analysis if they contain a "complex" character as described
+ * below.
  */
 class nsLineBreaker {
 public:
   nsLineBreaker();
   ~nsLineBreaker();
   
   static inline PRBool IsSpace(PRUnichar u) { return NS_IsSpace(u); }
 
@@ -97,44 +97,60 @@ public:
            NS_NeedsPlatformNativeHandling(u) ||
            (0x1100 <= u && u <= 0x11ff) || // Hangul Jamo
            (0x2000 <= u && u <= 0x21ff) || // Punctuations and Symbols
            (0x2e80 <= u && u <= 0xd7ff) || // several CJK blocks
            (0xf900 <= u && u <= 0xfaff) || // CJK Compatibility Idographs
            (0xff00 <= u && u <= 0xffef);   // Halfwidth and Fullwidth Forms
   }
 
-  // Normally, break opportunities exist at the end of each run of whitespace
-  // (see IsSpace above). Break opportunities can also exist inside runs of
-  // non-whitespace, as determined by nsILineBreaker. We pass a whitespace-
+  // Break opportunities exist at the end of each run of breakable whitespace
+  // (see IsSpace above). Break opportunities can also exist between pairs of
+  // non-whitespace characters, as determined by nsILineBreaker. We pass a whitespace-
   // delimited word to nsILineBreaker if it contains at least one character
   // matching IsComplexChar.
   // We provide flags to control on a per-chunk basis where breaks are allowed.
   // At any character boundary, exactly one text chunk governs whether a
   // break is allowed at that boundary.
   //
   // We operate on text after whitespace processing has been applied, so
   // other characters (e.g. tabs and newlines) may have been converted to
   // spaces.
+
+  /**
+   * Flags passed with each chunk of text.
+   */
   enum {
-    /**
-     * Allow a break opportunity at the start of this chunk of text.
+    /*
+     * Do not introduce a break opportunity at the start of this chunk of text.
      */
-    BREAK_ALLOW_INITIAL = 0x01,
+    BREAK_SUPPRESS_INITIAL = 0x01,
+    /**
+     * Do not introduce a break opportunity in the interior of this chunk of text.
+     * Also, whitespace in this chunk is treated as non-breakable.
+     */
+    BREAK_SUPPRESS_INSIDE = 0x02,
     /**
-     * Allow a break opportunity in the interior of this chunk of text.
+     * The sink currently is already set up to have no breaks in it;
+     * if no breaks are possible, nsLineBreaker does not need to call
+     * SetBreaks on it. This is useful when handling large quantities of
+     * preformatted text; the textruns will never have any breaks set on them,
+     * and there is no need to ever actually scan the text for breaks, except
+     * at the end of textruns in case context is needed for following breakable
+     * text.
      */
-    BREAK_ALLOW_INSIDE = 0x02
+    BREAK_SKIP_SETTING_NO_BREAKS = 0x04
   };
 
   /**
    * Append "invisible whitespace". This acts like whitespace, but there is
-   * no actual text associated with it.
+   * no actual text associated with it. Only the BREAK_SUPPRESS_INSIDE flag
+   * is relevant here.
    */
-  nsresult AppendInvisibleWhitespace();
+  nsresult AppendInvisibleWhitespace(PRUint32 aFlags);
 
   /**
    * Feed Unicode text into the linebreaker for analysis. aLength must be
    * nonzero.
    * @param aSink can be null if the breaks are not actually needed (we may
    * still be setting up state for later breaks)
    */
   nsresult AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32 aLength,
@@ -179,13 +195,16 @@ private:
   // appropriate sink(s). Then we clear the current word state.
   nsresult FlushCurrentWord();
 
   nsAutoTArray<PRUnichar,100> mCurrentWord;
   // All the items that contribute to mCurrentWord
   nsAutoTArray<TextItem,2>    mTextItems;
   PRPackedBool                mCurrentWordContainsComplexChar;
 
-  // True if the previous character was whitespace
-  PRPackedBool                mAfterSpace;
+  // True if the previous character was breakable whitespace
+  PRPackedBool                mAfterBreakableSpace;
+  // True if a break must be allowed at the current position because
+  // a run of breakable whitespace ends here
+  PRPackedBool                mBreakHere;
 };
 
 #endif /*NSLINEBREAKER_H_*/
--- a/content/base/src/nsLineBreaker.cpp
+++ b/content/base/src/nsLineBreaker.cpp
@@ -37,17 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsLineBreaker.h"
 #include "nsContentUtils.h"
 #include "nsILineBreaker.h"
 
 nsLineBreaker::nsLineBreaker()
   : mCurrentWordContainsComplexChar(PR_FALSE),
-    mAfterSpace(PR_FALSE)
+    mAfterBreakableSpace(PR_FALSE), mBreakHere(PR_FALSE)
 {
 }
 
 nsLineBreaker::~nsLineBreaker()
 {
   NS_ASSERTION(mCurrentWord.Length() == 0, "Should have Reset() before destruction!");
 }
 
@@ -67,20 +67,20 @@ nsLineBreaker::FlushCurrentWord()
   }
 
   PRUint32 i;
   PRUint32 offset = 0;
   for (i = 0; i < mTextItems.Length(); ++i) {
     TextItem* ti = &mTextItems[i];
     NS_ASSERTION(ti->mLength > 0, "Zero length word contribution?");
 
-    if (!(ti->mFlags & BREAK_ALLOW_INITIAL) && ti->mSinkOffset == 0) {
+    if ((ti->mFlags & BREAK_SUPPRESS_INITIAL) && ti->mSinkOffset == 0) {
       breakState[offset] = PR_FALSE;
     }
-    if (!(ti->mFlags & BREAK_ALLOW_INSIDE)) {
+    if (ti->mFlags & BREAK_SUPPRESS_INSIDE) {
       PRUint32 exclude = ti->mSinkOffset == 0 ? 1 : 0;
       memset(breakState.Elements() + offset + exclude, PR_FALSE, ti->mLength - exclude);
     }
 
     // Don't set the break state for the first character of the word, because
     // it was already set correctly earlier and we don't know what the true
     // value should be.
     PRUint32 skipSet = i == 0 ? 1 : 0;
@@ -102,17 +102,17 @@ nsLineBreaker::AppendText(nsIAtom* aLang
                           PRUint32 aFlags, nsILineBreakSink* aSink)
 {
   NS_ASSERTION(aLength > 0, "Appending empty text...");
 
   PRUint32 offset = 0;
 
   // Continue the current word
   if (mCurrentWord.Length() > 0) {
-    NS_ASSERTION(!mAfterSpace, "These should not be set");
+    NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere, "These should not be set");
 
     while (offset < aLength && !IsSpace(aText[offset])) {
       mCurrentWord.AppendElement(aText[offset]);
       if (!mCurrentWordContainsComplexChar && IsComplexChar(aText[offset])) {
         mCurrentWordContainsComplexChar = PR_TRUE;
       }
       ++offset;
     }
@@ -132,41 +132,48 @@ nsLineBreaker::AppendText(nsIAtom* aLang
 
   nsAutoTArray<PRPackedBool,4000> breakState;
   if (aSink) {
     if (!breakState.AppendElements(aLength))
       return NS_ERROR_OUT_OF_MEMORY;
   }
 
   PRUint32 start = offset;
-  if (!aSink && !aFlags) {
-    // Skip to the space before the last word, since we don't need the breaks
+  PRBool noBreaksNeeded = !aSink ||
+    ((aFlags & BREAK_SUPPRESS_INITIAL) && (aFlags & BREAK_SUPPRESS_INSIDE) &&
+     !mBreakHere && !mAfterBreakableSpace && (aFlags & BREAK_SKIP_SETTING_NO_BREAKS));
+  if (noBreaksNeeded) {
+    // Skip to the space before the last word, since either the break data
+    // here is not needed, or no breaks are set in the sink and there cannot
+    // be any breaks in this chunk; all we need is the context for the next
+    // chunk (if any)
     offset = aLength;
     while (offset > start) {
       --offset;
       if (IsSpace(aText[offset]))
         break;
     }
   }
   PRUint32 wordStart = offset;
   PRBool wordHasComplexChar = PR_FALSE;
 
   for (;;) {
     PRUnichar ch = aText[offset];
     PRBool isSpace = IsSpace(ch);
+    PRBool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
 
     if (aSink) {
-      breakState[offset] = mAfterSpace && !isSpace &&
-        (aFlags & (offset == 0 ? BREAK_ALLOW_INITIAL : BREAK_ALLOW_INSIDE));
+      breakState[offset] = mBreakHere || (mAfterBreakableSpace && !isBreakableSpace);
     }
-    mAfterSpace = isSpace;
+    mBreakHere = PR_FALSE;
+    mAfterBreakableSpace = isBreakableSpace;
 
     if (isSpace) {
       if (offset > wordStart && wordHasComplexChar) {
-        if (aSink && (aFlags & BREAK_ALLOW_INSIDE)) {
+        if (aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
           // Save current start-of-word state because GetJISx4051Breaks will
           // set it to false
           PRPackedBool currentStart = breakState[wordStart];
           nsContentUtils::LineBreaker()->
             GetJISx4051Breaks(aText + wordStart, offset - wordStart,
                               breakState.Elements() + wordStart);
           breakState[wordStart] = currentStart;
         }
@@ -193,33 +200,33 @@ nsLineBreaker::AppendText(nsIAtom* aLang
         mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
         // Ensure that the break-before for this word is written out
         offset = wordStart + 1;
         break;
       }
     }
   }
 
-  if (aSink) {
+  if (!noBreaksNeeded) {
     aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
   }
   return NS_OK;
 }
 
 nsresult
 nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aLength,
                           PRUint32 aFlags, nsILineBreakSink* aSink)
 {
   NS_ASSERTION(aLength > 0, "Appending empty text...");
 
   PRUint32 offset = 0;
 
   // Continue the current word
   if (mCurrentWord.Length() > 0) {
-    NS_ASSERTION(!mAfterSpace, "These should not be set");
+    NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere, "These should not be set");
 
     while (offset < aLength && !IsSpace(aText[offset])) {
       mCurrentWord.AppendElement(aText[offset]);
       if (!mCurrentWordContainsComplexChar &&
           IsComplexASCIIChar(aText[offset])) {
         mCurrentWordContainsComplexChar = PR_TRUE;
       }
       ++offset;
@@ -242,41 +249,48 @@ nsLineBreaker::AppendText(nsIAtom* aLang
 
   nsAutoTArray<PRPackedBool,4000> breakState;
   if (aSink) {
     if (!breakState.AppendElements(aLength))
       return NS_ERROR_OUT_OF_MEMORY;
   }
 
   PRUint32 start = offset;
-  if (!aSink && !aFlags) {
-    // Skip to the space before the last word, since we don't need the breaks
+  PRBool noBreaksNeeded = !aSink ||
+    ((aFlags & BREAK_SUPPRESS_INITIAL) && (aFlags & BREAK_SUPPRESS_INSIDE) &&
+     !mBreakHere && !mAfterBreakableSpace && (aFlags & BREAK_SKIP_SETTING_NO_BREAKS));
+  if (noBreaksNeeded) {
+    // Skip to the space before the last word, since either the break data
+    // here is not needed, or no breaks are set in the sink and there cannot
+    // be any breaks in this chunk; all we need is the context for the next
+    // chunk (if any)
     offset = aLength;
     while (offset > start) {
       --offset;
       if (IsSpace(aText[offset]))
         break;
     }
   }
   PRUint32 wordStart = offset;
   PRBool wordHasComplexChar = PR_FALSE;
 
   for (;;) {
     PRUint8 ch = aText[offset];
     PRBool isSpace = IsSpace(ch);
+    PRBool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
 
     if (aSink) {
-      breakState[offset] = mAfterSpace && !isSpace &&
-        (aFlags & (offset == 0 ? BREAK_ALLOW_INITIAL : BREAK_ALLOW_INSIDE));
+      breakState[offset] = mBreakHere || (mAfterBreakableSpace && !isBreakableSpace);
     }
-    mAfterSpace = isSpace;
+    mBreakHere = PR_FALSE;
+    mAfterBreakableSpace = isBreakableSpace;
 
     if (isSpace) {
       if (offset > wordStart && wordHasComplexChar) {
-        if (aSink && (aFlags & BREAK_ALLOW_INSIDE)) {
+        if (aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
           // Save current start-of-word state because GetJISx4051Breaks will
           // set it to false
           PRPackedBool currentStart = breakState[wordStart];
           nsContentUtils::LineBreaker()->
             GetJISx4051Breaks(aText + wordStart, offset - wordStart,
                               breakState.Elements() + wordStart);
           breakState[wordStart] = currentStart;
         }
@@ -306,23 +320,27 @@ nsLineBreaker::AppendText(nsIAtom* aLang
         mTextItems.AppendElement(TextItem(aSink, wordStart, len, aFlags));
         // Ensure that the break-before for this word is written out
         offset = wordStart + 1;
         break;
       }
     }
   }
 
-  if (aSink) {
+  if (!noBreaksNeeded) {
     aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
   }
   return NS_OK;
 }
 
 nsresult
-nsLineBreaker::AppendInvisibleWhitespace() {
-  // Treat as "invisible whitespace"
+nsLineBreaker::AppendInvisibleWhitespace(PRUint32 aFlags) {
   nsresult rv = FlushCurrentWord();
   if (NS_FAILED(rv))
     return rv;
-  mAfterSpace = PR_TRUE;
+
+  PRBool isBreakableSpace = !(aFlags & BREAK_SUPPRESS_INSIDE);
+  if (mAfterBreakableSpace && !isBreakableSpace) {
+    mBreakHere = PR_TRUE;
+  }
+  mAfterBreakableSpace = isBreakableSpace;
   return NS_OK;  
 }
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -113,16 +113,17 @@ nsLineLayout::nsLineLayout(nsPresContext
   // Stash away some style data that we need
   mStyleText = aOuterReflowState->frame->GetStyleText();
   mTextAlign = mStyleText->mTextAlign;
   mLineNumber = 0;
   mFlags = 0; // default all flags to false except those that follow here...
   mPlacedFloats = 0;
   mTotalPlacedFrames = 0;
   mTopEdge = 0;
+  mTrimmableWidth = 0;
 
   // Instead of always pre-initializing the free-lists for frames and
   // spans, we do it on demand so that situations that only use a few
   // frames and spans won't waste a lot of time in unneeded
   // initialization.
   PL_INIT_ARENA_POOL(&mArena, "nsLineLayout", 1024);
   mFrameFreeList = nsnull;
   mSpanFreeList = nsnull;
@@ -1029,17 +1030,19 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
       if (span) {
         // The frame we just finished reflowing is an inline
         // container.  It needs its child frames vertically aligned,
         // so do most of it now.
         VerticalAlignFrames(span);
       }
       
       if (!continuingTextRun) {
-        SetHasTrailingTextFrame(PR_FALSE);
+        if (!pfd->GetFlag(PFD_SKIPWHENTRIMMINGWHITESPACE)) {
+          mTrimmableWidth = 0;
+        }
         if (!psd->mNoWrap && (!CanPlaceFloatNow() || placedFloat)) {
           // record soft break opportunity after this content that can't be
           // part of a text run. This is not a text frame so we know
           // that offset PR_INT32_MAX means "after the content".
           if (NotifyOptionalBreakPosition(aFrame->GetContent(), PR_INT32_MAX, PR_TRUE)) {
             // If this returns true then we are being told to actually break here.
             aReflowStatus = NS_INLINE_LINE_BREAK_AFTER(aReflowStatus);
           }
@@ -1178,17 +1181,17 @@ nsLineLayout::CanPlaceFrame(PerFrameData
   } 
   printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak ? "true" : "false");
   nsFrame::ListTag(stdout, pfd->mFrame);
   printf(" frameWidth=%d\n", pfd->mBounds.XMost() + endMargin - psd->mX);
 #endif
 
   // Set outside to PR_TRUE if the result of the reflow leads to the
   // frame sticking outside of our available area.
-  PRBool outside = pfd->mBounds.XMost() + endMargin > psd->mRightEdge;
+  PRBool outside = pfd->mBounds.XMost() - mTrimmableWidth + endMargin > psd->mRightEdge;
   if (!outside) {
     // If it fits, it fits
 #ifdef NOISY_CAN_PLACE_FRAME
     printf("   ==> inside\n");
 #endif
     return PR_TRUE;
   }
 
--- a/layout/generic/nsLineLayout.h
+++ b/layout/generic/nsLineLayout.h
@@ -138,20 +138,19 @@ public:
 protected:
 #define LL_FIRSTLETTERSTYLEOK          0x00000008
 #define LL_ISTOPOFPAGE                 0x00000010
 #define LL_UPDATEDBAND                 0x00000020
 #define LL_IMPACTEDBYFLOATS            0x00000040
 #define LL_LASTFLOATWASLETTERFRAME     0x00000080
 #define LL_CANPLACEFLOAT               0x00000100
 #define LL_LINEENDSINBR                0x00000200
-#define LL_HASTRAILINGTEXTFRAME        0x00000400
-#define LL_NEEDBACKUP                  0x00000800
-#define LL_INFIRSTLINE                 0x00002000
-#define LL_GOTLINEBOX                  0x00004000
+#define LL_NEEDBACKUP                  0x00000400
+#define LL_INFIRSTLINE                 0x00000800
+#define LL_GOTLINEBOX                  0x00001000
 #define LL_LASTFLAG                    LL_GOTLINEBOX
 
   PRUint16 mFlags;
 
   void SetFlag(PRUint32 aFlag, PRBool aValue)
   {
     NS_ASSERTION(aFlag<=LL_LASTFLAG, "bad flag");
     NS_ASSERTION(aValue==PR_FALSE || aValue==PR_TRUE, "bad value");
@@ -198,27 +197,18 @@ public:
   PRBool InitFloat(nsPlaceholderFrame* aFrame, nsReflowStatus& aReflowStatus) {
     return mBlockRS->InitFloat(*this, aFrame, aReflowStatus);
   }
 
   PRBool AddFloat(nsPlaceholderFrame* aFrame, nsReflowStatus& aReflowStatus) {
     return mBlockRS->AddFloat(*this, aFrame, PR_FALSE, aReflowStatus);
   }
 
-  /**
-   * If the last content placed on the line (not counting inline containers)
-   * was text, and can form a contiguous text flow with the next content to be
-   * placed, and is not just a frame of all-skipped whitespace, this flag is
-   * true.
-   */
-  PRBool HasTrailingTextFrame() const {
-    return GetFlag(LL_HASTRAILINGTEXTFRAME);
-  }
-  void SetHasTrailingTextFrame(PRBool aHasTrailingTextFrame) { 
-    SetFlag(LL_HASTRAILINGTEXTFRAME, aHasTrailingTextFrame);
+  void SetTrimmableWidth(nscoord aTrimmableWidth) {
+    mTrimmableWidth = aTrimmableWidth;
   }
 
   //----------------------------------------
 
   PRBool GetFirstLetterStyleOK() const {
     return GetFlag(LL_FIRSTLETTERSTYLEOK);
   }
 
@@ -372,16 +362,19 @@ protected:
 
   nscoord mTopEdge;
   nscoord mMaxTopBoxHeight;
   nscoord mMaxBottomBoxHeight;
 
   // Final computed line-height value after VerticalAlignFrames for
   // the block has been called.
   nscoord mFinalLineHeight;
+  
+  // Amount of trimmable whitespace width for the trailing text frame, if any
+  nscoord mTrimmableWidth;
 
   // Per-frame data recorded by the line-layout reflow logic. This
   // state is the state needed to post-process the line after reflow
   // has completed (vertical alignment, horizontal alignment,
   // justification and relative positioning).
 
   struct PerSpanData;
   struct PerFrameData;
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -649,16 +649,18 @@ public:
                 mOffsetIntoTextRun(aOffsetIntoTextRun),
                 mChangedBreaks(PR_FALSE), mExistingTextRun(aExistingTextRun) {}
 
     virtual void SetBreaks(PRUint32 aOffset, PRUint32 aLength,
                            PRPackedBool* aBreakBefore) {
       if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
                                            aBreakBefore, mContext)) {
         mChangedBreaks = PR_TRUE;
+        // Be conservative and assume that some breaks have been set
+        mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
       }
     }
 
     gfxTextRun*  mTextRun;
     gfxContext*  mContext;
     PRUint32     mOffsetIntoTextRun;
     PRPackedBool mChangedBreaks;
     PRPackedBool mExistingTextRun;
@@ -1622,48 +1624,39 @@ BuildTextRunsScanner::SetupBreakSinksFor
       return;
     PRUint32 offset = mappedFlow->mTransformedTextOffset;
 
     PRUint32 length =
       (i == mMappedFlows.Length() - 1 ? aTextRun->GetLength()
        : mMappedFlows[i + 1].mTransformedTextOffset)
       - offset;
 
+    PRUint32 flags = 0;
+    nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
+    if (!initialBreakController) {
+      initialBreakController = mLineContainer;
+    }
+    if (!initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
+      flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
+    }
     nsTextFrame* startFrame = mappedFlow->mStartFrame;
+    const nsStyleText* textStyle = startFrame->GetStyleText();
+    if (!textStyle->WhiteSpaceCanWrap()) {
+      flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
+    }
+    if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
+      flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
+    }
+
     if (HasCompressedLeadingWhitespace(startFrame, mappedFlow->GetContentEnd(), iter)) {
-      mLineBreaker.AppendInvisibleWhitespace();
+      mLineBreaker.AppendInvisibleWhitespace(flags);
     }
 
     if (length > 0) {
-      PRUint32 flags = 0;
-      nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
-      if (!initialBreakController) {
-        initialBreakController = mLineContainer;
-      }
-      if (initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
-        flags |= nsLineBreaker::BREAK_ALLOW_INITIAL;
-      }
-      const nsStyleText* textStyle = startFrame->GetStyleText();
-      if (textStyle->WhiteSpaceCanWrap()) {
-        // If white-space is preserved, then the only break opportunity is at
-        // the end of whitespace runs; otherwise there is a break opportunity before
-        // and after each whitespace character
-        flags |= nsLineBreaker::BREAK_ALLOW_INSIDE;
-      }
-      
-      BreakSink* sink = *breakSink;
-      if (aSuppressSink) {
-        sink = nsnull;
-      } else if (flags) {
-        aTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
-      } else if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
-        // Don't bother setting breaks on a textrun that can't be broken
-        // and currently has no breaks set...
-        sink = nsnull;
-      }
+      BreakSink* sink = aSuppressSink ? nsnull : (*breakSink).get();
       if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
         mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset,
                                 length, flags, sink);
       } else {
         mLineBreaker.AppendText(lang, aTextRun->GetTextUnicode() + offset,
                                 length, flags, sink);
       }
     }
@@ -1807,18 +1800,17 @@ nsTextFrame::GetTrimmedOffsets(const nsT
 
   if (GetStateBits() & TEXT_START_OF_LINE) {
     PRInt32 whitespaceCount =
       GetTrimmableWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
     offsets.mStart += whitespaceCount;
     offsets.mLength -= whitespaceCount;
   }
 
-  if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE) &&
-      textStyle->WhiteSpaceCanWrap()) {
+  if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) {
     PRInt32 whitespaceCount =
       GetTrimmableWhitespaceCount(aFrag, offsets.GetEnd() - 1,
                                   offsets.mLength, -1);
     offsets.mLength -= whitespaceCount;
   }
   return offsets;
 }
 
@@ -5211,18 +5203,16 @@ nsTextFrame::Reflow(nsPresContext*      
 
   // The metrics for the text go in here
   gfxTextRun::Metrics textMetrics;
   PRBool needTightBoundingBox = (GetStateBits() & TEXT_FIRST_LETTER) != 0;
 #ifdef MOZ_MATHML
   NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
                "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
 #endif
-  PRBool suppressInitialBreak = !lineLayout.LineIsBreakable() ||
-    !lineLayout.HasTrailingTextFrame();
 
   PRInt32 limitLength = length;
   PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent);
   if (forceBreak >= offset + length) {
     // The break is not within the text considered for this textframe.
     forceBreak = -1;
   }
   if (forceBreak >= 0) {
@@ -5244,23 +5234,22 @@ nsTextFrame::Reflow(nsPresContext*      
     gfxSkipCharsIterator iter(provider.GetStart());
     iter.SetOriginalOffset(offset + limitLength);
     transformedLength = iter.GetSkippedOffset() - transformedOffset;
   }
   PRUint32 transformedLastBreak = 0;
   PRBool usedHyphenation;
   gfxFloat trimmedWidth = 0;
   gfxFloat availWidth = aReflowState.availableWidth;
-  PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() &&
-    textStyle->WhiteSpaceCanWrap();
+  PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
   PRUint32 transformedCharsFit =
     mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
                                   (GetStateBits() & TEXT_START_OF_LINE) != 0,
                                   availWidth,
-                                  &provider, suppressInitialBreak,
+                                  &provider, !lineLayout.LineIsBreakable(),
                                   canTrimTrailingWhitespace ? &trimmedWidth : nsnull,
                                   &textMetrics, needTightBoundingBox, ctx,
                                   &usedHyphenation, &transformedLastBreak);
   // The "end" iterator points to the first character after the string mapped
   // by this frame. Basically, its original-string offset is offset+charsFit
   // after we've computed charsFit.
   gfxSkipCharsIterator end(provider.GetEndHint());
   end.SetSkippedOffset(transformedOffset + transformedCharsFit);
@@ -5288,36 +5277,48 @@ nsTextFrame::Reflow(nsPresContext*      
     gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, ctx, this));
     if (hyphenTextRun.get()) {
       AddCharToMetrics(hyphenTextRun.get(),
                        mTextRun, &textMetrics, needTightBoundingBox, ctx);
     }
     AddStateBits(TEXT_HYPHEN_BREAK);
   }
 
-  // If everything fits including trimmed whitespace, then we should add the
-  // trimmed whitespace to our metrics now because it probably won't be trimmed
-  // and we need to position subsequent frames correctly...
-  if (forceBreak < 0 && textMetrics.mAdvanceWidth + trimmedWidth <= availWidth) {
-    textMetrics.mAdvanceWidth += trimmedWidth;
-    if (mTextRun->IsRightToLeft()) {
-      // Space comes before text, so the bounding box is moved to the
-      // right by trimmdWidth
-      textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
+  gfxFloat trimmableWidth = 0;
+  if (canTrimTrailingWhitespace) {
+    // Optimization: if we trimmed trailing whitespace, and we can be sure
+    // this frame will be at the end of the line, then leave it trimmed off.
+    // Otherwise we have to undo the trimming, in case we're not at the end of
+    // the line. (If we actually do end up at the end of the line, we'll have
+    // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
+    // having to re-do it.)
+    if (forceBreak >= 0 || transformedCharsFit < transformedLength) {
+      // We're definitely going to break so our trailing whitespace should
+      // definitely be timmed. Record that we've already done it.
+      AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
+    } else {
+      // We might not be at the end of the line. (Note that even if this frame
+      // ends in breakable whitespace, it might not be at the end of the line
+      // because it might be followed by breakable, but preformatted, whitespace.)
+      // Undo the trimming.
+      textMetrics.mAdvanceWidth += trimmedWidth;
+      trimmableWidth = trimmedWidth;
+      if (mTextRun->IsRightToLeft()) {
+        // Space comes before text, so the bounding box is moved to the
+        // right by trimmdWidth
+        textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
+      }
+
+      // Since everything fit and no break was forced,
+      // record the last break opportunity
+      if (lastBreak >= 0) {
+        lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak,
+            textMetrics.mAdvanceWidth <= aReflowState.availableWidth);
+      }
     }
-    
-    if (lastBreak >= 0) {
-      lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak,
-          textMetrics.mAdvanceWidth <= aReflowState.availableWidth);
-    }
-  } else {
-    // We're definitely going to break and our whitespace will definitely
-    // be trimmed.
-    // Record that whitespace has already been trimmed.
-    AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
   }
   PRInt32 contentLength = offset + charsFit - GetContentOffset();
   
   /////////////////////////////////////////////////////////////////////
   // Compute output metrics
   /////////////////////////////////////////////////////////////////////
 
   // first-letter frames should use the tight bounding box metrics for ascent/descent
@@ -5342,35 +5343,29 @@ nsTextFrame::Reflow(nsPresContext*      
     ConvertGfxRectOutward(textMetrics.mBoundingBox + gfxPoint(0, textMetrics.mAscent));
   aMetrics.mOverflowArea.UnionRect(boundingBox,
                                    nsRect(0, 0, aMetrics.width, aMetrics.height));
 
   /////////////////////////////////////////////////////////////////////
   // Clean up, update state
   /////////////////////////////////////////////////////////////////////
 
-  if (charsFit > 0) {
-    lineLayout.SetHasTrailingTextFrame(PR_TRUE);
-    if (charsFit == length) {
-      if (textStyle->WhiteSpaceCanWrap() &&
-          IsTrimmableSpace(frag, offset + charsFit - 1)) {
-        // Record a potential break after final breakable whitespace
-        lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
-            textMetrics.mAdvanceWidth <= aReflowState.availableWidth);
-      } else if (HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
-        // Record a potential break after final soft hyphen
-        lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
-            textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth);
-      }
-    }
-  } else {
-    // Don't allow subsequent text frame to break-before. All our text is       
-    // being skipped (usually whitespace, could be discarded Unicode control    
-    // characters).
-    lineLayout.SetHasTrailingTextFrame(PR_FALSE);
+  // If all our characters are discarded or collapsed, then trimmable width
+  // from the last textframe should be preserved. Otherwise the trimmable width
+  // from this textframe overrides. (Currently in CSS trimmable width can be
+  // at most one space so there's no way for trimmable width from a previous
+  // frame to accumulate with trimmable width from this frame.)
+  if (transformedCharsFit > 0) {
+    lineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
+  }
+  if (charsFit > 0 && charsFit == length &&
+      HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
+    // Record a potential break after final soft hyphen
+    lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
+        textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth);
   }
   if (completedFirstLetter) {
     lineLayout.SetFirstLetterStyleOK(PR_FALSE);
   }
 
   // Compute reflow status
   aStatus = contentLength == maxContentLength
     ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE;
--- a/layout/reftests/text/reftest.list
+++ b/layout/reftests/text/reftest.list
@@ -1,4 +1,7 @@
 == long-1.html long-ref.html
 == soft-hyphens-1a.html soft-hyphens-1-ref.html
 == soft-hyphens-1b.html soft-hyphens-1-ref.html
 == soft-hyphens-1c.html soft-hyphens-1-ref.html
+== white-space-1a.html white-space-1-ref.html
+== white-space-1b.html white-space-1-ref.html
+== white-space-2.html white-space-2-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/white-space-1-ref.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Reference -->
+<style>
+div { border:1px solid black; }
+.container { float:left; width:20%; border-color:cyan; }
+p { width:0; }
+b { font-weight:normal; background-color:yellow; white-space:pre; }
+</style>
+</head>
+<body>
+
+<div>
+<p><b>Hello
+Kitty</b>
+<p><b>Hello Kitty</b>
+<p><b>Hello Kitty</b>
+<p><b>Hello 
+Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello
+Kitty</b>
+<p><b>Hello 
+Kitty</b>
+<p><b>Hello
+Kitty</b>
+<p><b>Hello 
+Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello
+ Kitty</b>
+<p><b>Hello  Kitty</b>
+<p><b>Hello  Kitty</b>
+<p><b>Hello 
+ Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello
+Kitty</b>
+<p><b>Hello  Kitty</b>
+<p><b>Hello Kitty</b>
+<p><b>Hello 
+Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello  
+Kitty</b>
+<p><b>Hello  
+Kitty</b>
+<p><b>Hello  
+Kitty</b>
+<p><b>Hello  
+Kitty</b>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/white-space-1a.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Testing all combinations of pre/nowrap/pre-wrap/normal space pairs -->
+<style>
+.nowrap { white-space:nowrap; }
+.pre { white-space:pre; }
+.prewrap { white-space:-moz-pre-wrap; }
+div { border:1px solid black; }
+.container { float:left; width:20%; border-color:cyan; }
+p { width:0; }
+b { font-weight:normal; background-color:yellow; }
+</style>
+</head>
+<body>
+
+<div>
+<p><b>Hello Kitty</b>
+<p><b>Hello<span class="pre"> </span>Kitty</b>
+<p><b>Hello<span class="nowrap"> </span>Kitty</b>
+<p><b>Hello<span class="prewrap"> </span>Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello  Kitty</b>
+<p><b>Hello<span class="pre"> </span> Kitty</b>
+<p><b>Hello<span class="nowrap"> </span> Kitty</b>
+<p><b>Hello<span class="prewrap"> </span> Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello <span class="pre"> </span>Kitty</b>
+<p><b>Hello<span class="pre"> </span><span class="pre"> </span>Kitty</b>
+<p><b>Hello<span class="nowrap"> </span><span class="pre"> </span>Kitty</b>
+<p><b>Hello<span class="prewrap"> </span><span class="pre"> </span>Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello <span class="nowrap"> </span>Kitty</b>
+<p><b>Hello<span class="pre"> </span><span class="nowrap"> </span>Kitty</b>
+<p><b>Hello<span class="nowrap"> </span><span class="nowrap"> </span>Kitty</b>
+<p><b>Hello<span class="prewrap"> </span><span class="nowrap"> </span>Kitty</b>
+</div>
+
+<div class="container">
+<p><b>Hello <span class="prewrap"> </span>Kitty</b>
+<p><b>Hello<span class="pre"> </span><span class="prewrap"> </span>Kitty</b>
+<p><b>Hello<span class="nowrap"> </span><span class="prewrap"> </span>Kitty</b>
+<p><b>Hello<span class="prewrap"> </span><span class="prewrap"> </span>Kitty</b>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/white-space-1b.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Adding extra span boundaries -->
+<style>
+.nowrap { white-space:nowrap; }
+.pre { white-space:pre; }
+.prewrap { white-space:-moz-pre-wrap; }
+div { border:1px solid black; }
+.container { float:left; width:20%; border-color:cyan; }
+p { width:0; }
+b { font-weight:normal; background-color:yellow; }
+</style>
+</head>
+<body>
+
+<div>
+<p><b><span>Hello<span> <span>Kitty</span></b>
+<p><b><span>Hello<span><span class="pre"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="nowrap"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="prewrap"> </span><span>Kitty</span></b>
+</div>
+
+<div class="container">
+<p><b><span>Hello<span>  <span>Kitty</span></b>
+<p><b><span>Hello<span><span class="pre"> </span> <span>Kitty</span></b>
+<p><b><span>Hello<span><span class="nowrap"> </span> <span>Kitty</span></b>
+<p><b><span>Hello<span><span class="prewrap"> </span> <span>Kitty</span></b>
+</div>
+
+<div class="container">
+<p><b><span>Hello<span> <span class="pre"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="pre"> </span><span class="pre"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="nowrap"> </span><span class="pre"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="prewrap"> </span><span class="pre"> </span><span>Kitty</span></b>
+</div>
+
+<div class="container">
+<p><b><span>Hello<span> <span class="nowrap"> </span>Kitty</b>
+<p><b><span>Hello<span><span class="pre"> </span><span class="nowrap"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="nowrap"> </span><span class="nowrap"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="prewrap"> </span><span class="nowrap"> </span><span>Kitty</span></b>
+</div>
+
+<div class="container">
+<p><b><span>Hello<span> <span class="prewrap"> </span>Kitty</b>
+<p><b><span>Hello<span><span class="pre"> </span><span class="prewrap"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="nowrap"> </span><span class="prewrap"> </span><span>Kitty</span></b>
+<p><b><span>Hello<span><span class="prewrap"> </span><span class="prewrap"> </span><span>Kitty</span></b>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/white-space-2-ref.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Reference -->
+<style>
+div { border:1px solid black; }
+.container { float:left; width:20%; border-color:cyan; }
+p { width:0; }
+b { font-weight:normal; background-color:yellow; white-space:pre; }
+.cell { display:table-cell; border:1px solid green; }
+</style>
+</head>
+<body>
+
+<div>
+<p><span class="cell"><b>Hello
+Kitty</b></span>
+<p><span class="cell"><b>Hello Kitty</b></span>
+<p><span class="cell"><b>Hello Kitty</b></span>
+<p><span class="cell"><b>Hello 
+Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello
+Kitty</b></span>
+<p><span class="cell"><b>Hello 
+Kitty</b></span>
+<p><span class="cell"><b>Hello
+Kitty</b></span>
+<p><span class="cell"><b>Hello 
+Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello
+ Kitty</b></span>
+<p><span class="cell"><b>Hello  Kitty</b></span>
+<p><span class="cell"><b>Hello  Kitty</b></span>
+<p><span class="cell"><b>Hello 
+ Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello
+Kitty</b></span>
+<p><span class="cell"><b>Hello  Kitty</b></span>
+<p><span class="cell"><b>Hello Kitty</b></span>
+<p><span class="cell"><b>Hello 
+Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello  
+Kitty</b></span>
+<p><span class="cell"><b>Hello  
+Kitty</b></span>
+<p><span class="cell"><b>Hello  
+Kitty</b></span>
+<p><span class="cell"><b>Hello  
+Kitty</b></span>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text/white-space-2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<!-- Wrapping tests in table-cell to test min-width computation -->
+<style>
+div { border:1px solid black; }
+.container { float:left; width:20%; border-color:cyan; }
+p { width:0; }
+b { font-weight:normal; background-color:yellow; }
+.cell { display:table-cell; border:1px solid green; }
+.nowrap { white-space:nowrap; }
+.pre { white-space:pre; }
+.prewrap { white-space:-moz-pre-wrap; }
+</style>
+</head>
+<body>
+
+<div>
+<p><span class="cell"><b>Hello Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="pre"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="nowrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="prewrap"> </span>Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello  <span>Kitty</span></b></span>
+<p><span class="cell"><b>Hello<span class="pre"> </span> Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="nowrap"> </span> Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="prewrap"> </span> Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello <span class="pre"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="pre"> </span><span class="pre"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="nowrap"> </span><span class="pre"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="prewrap"> </span><span class="pre"> </span>Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello <span class="nowrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="pre"> </span><span class="nowrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="nowrap"> </span><span class="nowrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="prewrap"> </span><span class="nowrap"> </span>Kitty</b></span>
+</div>
+
+<div class="container">
+<p><span class="cell"><b>Hello <span class="prewrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="pre"> </span><span class="prewrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="nowrap"> </span><span class="prewrap"> </span>Kitty</b></span>
+<p><span class="cell"><b>Hello<span class="prewrap"> </span><span class="prewrap"> </span>Kitty</b></span>
+</div>
+
+</body>
+</html>