diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -231,16 +231,20 @@ typedef uint64_t nsFrameState;
 // Bits 20-31 and 60-63 of the frame state are reserved for implementations.
 #define NS_FRAME_IMPL_RESERVED                      nsFrameState(0xF0000000FFF00000)
 #define NS_FRAME_RESERVED                           ~NS_FRAME_IMPL_RESERVED
 
 // This bit is set on floats whose parent does not contain their
 // placeholder.  This can happen for two reasons:  (1) the float was
 // split, and this piece is the continuation, or (2) the entire float
 // didn't fit on the page.
+// Note that this bit is also shared by text frames for
+// TEXT_FORCE_TRIM_WHITESPACE.  That's OK because we only check the
+// NS_FRAME_IS_PUSHED_FLOAT bit on frames which we already know are
+// out-of-flow.
 #define NS_FRAME_IS_PUSHED_FLOAT                    NS_FRAME_STATE_BIT(32)
 
 // This bit acts as a loop flag for recursive paint server drawing.
 #define NS_FRAME_DRAWING_AS_PAINTSERVER             NS_FRAME_STATE_BIT(33)
 
 // Frame or one of its (cross-doc) descendants may have the
 // NS_FRAME_HAS_CONTAINER_LAYER bit.
 #define NS_FRAME_HAS_CONTAINER_LAYER_DESCENDANT     NS_FRAME_STATE_BIT(34)
diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -16,16 +16,20 @@
 
 class nsTextPaintStyle;
 class PropertyProvider;
 
 // This state bit is set on frames that have some non-collapsed characters after
 // reflow
 #define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
 
+// This state bit is set on frames which are forced to trim their leading and
+// trailing whitespaces
+#define TEXT_FORCE_TRIM_WHITESPACE       NS_FRAME_STATE_BIT(32)
+
 #define TEXT_HAS_FONT_INFLATION          NS_FRAME_STATE_BIT(61)
 
 typedef nsFrame nsTextFrameBase;
 
 class nsTextFrame : public nsTextFrameBase {
 public:
   NS_DECL_QUERYFRAME_TARGET(nsTextFrame)
   NS_DECL_FRAMEARENA_HELPERS
diff --git a/layout/generic/nsTextFrameThebes.cpp b/layout/generic/nsTextFrameThebes.cpp
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -191,16 +191,19 @@ NS_DECLARE_FRAME_PROPERTY(FontSizeInflat
 #define TEXT_BLINK_ON              NS_FRAME_STATE_BIT(29)
 
 // Set when this text frame is mentioned in the userdata for mTextRun
 #define TEXT_IN_TEXTRUN_USER_DATA  NS_FRAME_STATE_BIT(30)
 
 // nsTextFrame.h has
 // #define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
 
+// nsTextFrame.h has
+// #define TEXT_FORCE_TRIM_WHITESPACE       NS_FRAME_STATE_BIT(32)
+
 // Set when this text frame is mentioned in the userdata for the
 // uninflated textrun property
 #define TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA NS_FRAME_STATE_BIT(60)
 
 // nsTextFrame.h has
 // #define TEXT_HAS_FONT_INFLATION          NS_FRAME_STATE_BIT(61)
 
 // If true, then this frame is being removed due to a SetLength() on a
@@ -7607,17 +7610,18 @@ nsTextFrame::ReflowText(nsLineLayout& aL
         (newLineOffset will remain -1), but we will still cache it in mContent
       */
       newLineOffset = contentNewLineOffset;
     }
     if (newLineOffset >= 0) {
       length = newLineOffset + 1 - offset;
     }
   }
-  if (atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) {
+  if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
+      (GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE)) {
     // Skip leading whitespace. Make sure we don't skip a 'pre-line'
     // newline if there is one.
     int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
     int32_t whitespaceCount =
       GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
     offset += whitespaceCount;
     length -= whitespaceCount;
   }
@@ -7765,17 +7769,18 @@ nsTextFrame::ReflowText(nsLineLayout& aL
     gfxSkipCharsIterator iter(provider.GetStart());
     iter.SetOriginalOffset(offset + limitLength);
     transformedLength = iter.GetSkippedOffset() - transformedOffset;
   }
   uint32_t transformedLastBreak = 0;
   bool usedHyphenation;
   gfxFloat trimmedWidth = 0;
   gfxFloat availWidth = aAvailableWidth;
-  bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
+  bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
+                                   (GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE);
   int32_t unusedOffset;  
   gfxBreakPriority breakPriority;
   aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
   uint32_t transformedCharsFit =
     mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
                                   (GetStateBits() & TEXT_START_OF_LINE) != 0,
                                   availWidth,
                                   &provider, !aLineLayout.LineIsBreakable(),
@@ -7834,21 +7839,22 @@ nsTextFrame::ReflowText(nsLineLayout& aL
   bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
   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 (brokeText) {
+    if (brokeText ||
+        (GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE)) {
       // We're definitely going to break so our trailing whitespace should
-      // definitely be timmed. Record that we've already done it.
+      // definitely be trimmed. Record that we've already done it.
       AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
-    } else {
+    } else if (!(GetStateBits() & TEXT_FORCE_TRIM_WHITESPACE)) {
       // 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
diff --git a/layout/mathml/nsMathMLTokenFrame.cpp b/layout/mathml/nsMathMLTokenFrame.cpp
--- a/layout/mathml/nsMathMLTokenFrame.cpp
+++ b/layout/mathml/nsMathMLTokenFrame.cpp
@@ -6,16 +6,17 @@
 #include "nsCOMPtr.h"
 #include "nsFrame.h"
 #include "nsPresContext.h"
 #include "nsStyleContext.h"
 #include "nsStyleConsts.h"
 #include "nsContentUtils.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsMathMLTokenFrame.h"
+#include "nsTextFrame.h"
 
 nsIFrame*
 NS_NewMathMLTokenFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsMathMLTokenFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsMathMLTokenFrame)
@@ -63,16 +64,17 @@ nsMathMLTokenFrame::GetMathMLFrameType()
       style.EqualsLiteral("script") || style.EqualsLiteral("bold-script") ||
       style.EqualsLiteral("sans-serif-italic") ||
       style.EqualsLiteral("sans-serif-bold-italic")) {
     return eMathMLFrameType_ItalicIdentifier;
   }
   else if(style.EqualsLiteral("invariant")) {
     nsAutoString data;
     nsContentUtils::GetNodeTextContent(mContent, false, data);
+    data.CompressWhitespace();
     eMATHVARIANT variant = nsMathMLOperators::LookupInvariantChar(data);
 
     switch (variant) {
     case eMATHVARIANT_italic:
     case eMATHVARIANT_bold_italic:
     case eMATHVARIANT_script:
     case eMATHVARIANT_bold_script:
     case eMATHVARIANT_sans_serif_italic:
@@ -80,59 +82,73 @@ nsMathMLTokenFrame::GetMathMLFrameType()
       return eMathMLFrameType_ItalicIdentifier;
     default:
       ; // fall through to upright
     }
   }
   return eMathMLFrameType_UprightIdentifier;
 }
 
-static void
-CompressWhitespace(nsIContent* aContent)
+void
+nsMathMLTokenFrame::ForceTrimChildTextFrames()
 {
-  uint32_t numKids = aContent->GetChildCount();
-  for (uint32_t kid = 0; kid < numKids; kid++) {
-    nsIContent* cont = aContent->GetChildAt(kid);
-    if (cont && cont->IsNodeOfType(nsINode::eTEXT)) {
-      nsAutoString text;
-      cont->AppendTextTo(text);
-      text.CompressWhitespace();
-      cont->SetText(text, false); // not meant to be used if notify is needed
+  // Set flags on child text frames to force them to trim their leading and
+  // trailing whitespaces.
+  for (nsIFrame* childFrame = GetFirstPrincipalChild(); childFrame;
+       childFrame = childFrame->GetNextSibling()) {
+    if (childFrame->GetType() == nsGkAtoms::textFrame) {
+      childFrame->AddStateBits(TEXT_FORCE_TRIM_WHITESPACE);
     }
   }
 }
 
 NS_IMETHODIMP
-nsMathMLTokenFrame::Init(nsIContent*      aContent,
-                         nsIFrame*        aParent,
-                         nsIFrame*        aPrevInFlow)
-{
-  // leading and trailing whitespace doesn't count -- bug 15402
-  // brute force removal for people who do <mi> a </mi> instead of <mi>a</mi>
-  // XXX the best fix is to skip these in nsTextFrame
-  CompressWhitespace(aContent);
-
-  // let the base class do its Init()
-  return nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
-}
-
-NS_IMETHODIMP
 nsMathMLTokenFrame::SetInitialChildList(ChildListID     aListID,
                                         nsFrameList&    aChildList)
 {
   // First, let the base class do its work
   nsresult rv = nsMathMLContainerFrame::SetInitialChildList(aListID, aChildList);
   if (NS_FAILED(rv))
     return rv;
 
+  ForceTrimChildTextFrames();
+
   SetQuotes(false);
   ProcessTextData();
   return rv;
 }
 
+NS_IMETHODIMP
+nsMathMLTokenFrame::AppendFrames(ChildListID aListID,
+                                 nsFrameList& aChildList)
+{
+  nsresult rv = nsMathMLContainerFrame::AppendFrames(aListID, aChildList);
+  if (NS_FAILED(rv))
+    return rv;
+
+  ForceTrimChildTextFrames();
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsMathMLTokenFrame::InsertFrames(ChildListID aListID,
+                                 nsIFrame* aPrevFrame,
+                                 nsFrameList& aChildList)
+{
+  nsresult rv = nsMathMLContainerFrame::InsertFrames(aListID, aPrevFrame,
+                                                     aChildList);
+  if (NS_FAILED(rv))
+    return rv;
+
+  ForceTrimChildTextFrames();
+
+  return rv;
+}
+
 nsresult
 nsMathMLTokenFrame::Reflow(nsPresContext*          aPresContext,
                            nsHTMLReflowMetrics&     aDesiredSize,
                            const nsHTMLReflowState& aReflowState,
                            nsReflowStatus&          aStatus)
 {
   nsresult rv = NS_OK;
 
@@ -292,16 +308,17 @@ nsMathMLTokenFrame::SetTextStyle()
     return false;
 
   if (!mFrames.FirstChild())
     return false;
 
   // Get the text content that we enclose and its length
   nsAutoString data;
   nsContentUtils::GetNodeTextContent(mContent, false, data);
+  data.CompressWhitespace();
   int32_t length = data.Length();
   if (!length)
     return false;
 
   nsAutoString fontstyle;
   bool isSingleCharacter =
     length == 1 ||
     (length == 2 && NS_IS_HIGH_SURROGATE(data[0]));
diff --git a/layout/mathml/nsMathMLTokenFrame.h b/layout/mathml/nsMathMLTokenFrame.h
--- a/layout/mathml/nsMathMLTokenFrame.h
+++ b/layout/mathml/nsMathMLTokenFrame.h
@@ -30,23 +30,27 @@ public:
   }
 
   NS_IMETHOD
   InheritAutomaticData(nsIFrame* aParent);
 
   virtual eMathMLFrameType GetMathMLFrameType();
 
   NS_IMETHOD
-  Init(nsIContent*      aContent,
-       nsIFrame*        aParent,
-       nsIFrame*        aPrevInFlow);
+  SetInitialChildList(ChildListID     aListID,
+                      nsFrameList&    aChildList);
 
   NS_IMETHOD
-  SetInitialChildList(ChildListID     aListID,
-                      nsFrameList&    aChildList);
+  AppendFrames(ChildListID            aListID,
+               nsFrameList&           aChildList);
+
+  NS_IMETHOD
+  InsertFrames(ChildListID            aListID,
+               nsIFrame*              aPrevFrame,
+               nsFrameList&           aChildList);
 
   NS_IMETHOD
   Reflow(nsPresContext*          aPresContext,
          nsHTMLReflowMetrics&     aDesiredSize,
          const nsHTMLReflowState& aReflowState,
          nsReflowStatus&          aStatus);
 
   virtual nsresult
@@ -78,11 +82,13 @@ protected:
   virtual void ProcessTextData();
 
   // helper to set the style of <mi> which has to be italic or normal
   // depending on its textual content
   bool SetTextStyle();
 
   // helper to set the quotes of <ms>
   void SetQuotes(bool aNotify);
+
+  void ForceTrimChildTextFrames();
 };
 
 #endif /* nsMathMLTokentFrame_h___ */
diff --git a/layout/mathml/nsMathMLmoFrame.cpp b/layout/mathml/nsMathMLmoFrame.cpp
--- a/layout/mathml/nsMathMLmoFrame.cpp
+++ b/layout/mathml/nsMathMLmoFrame.cpp
@@ -117,16 +117,17 @@ nsMathMLmoFrame::BuildDisplayList(nsDisp
 // get the text that we enclose and setup our nsMathMLChar
 void
 nsMathMLmoFrame::ProcessTextData()
 {
   mFlags = 0;
 
   nsAutoString data;
   nsContentUtils::GetNodeTextContent(mContent, false, data);
+  data.CompressWhitespace();
   int32_t length = data.Length();
   PRUnichar ch = (length == 0) ? kNullCh : data[0];
 
   if ((length == 1) && 
       (ch == kInvisibleComma || 
        ch == kApplyFunction  || 
        ch == kInvisibleTimes)) {
     mFlags |= NS_MATHML_OPERATOR_INVISIBLE;