Not part of the build. Update new text frame for textrun API changes. Also implements a textrun cache for the new text frame
authorroc+@cs.cmu.edu
Wed, 09 May 2007 15:04:56 -0700
changeset 1328 54505530d11b40761505e24487263f4e90bbce3a
parent 1327 3d714d9cd9d46e1cb0b135d41bf42adc6f02f2e0
child 1329 a13e49076f571b1834dcb67c432a798bf7aef790
push id1
push userbsmedberg@mozilla.com
push dateThu, 20 Mar 2008 16:49:24 +0000
treeherdermozilla-central@61007906a1f8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone1.9a5pre
Not part of the build. Update new text frame for textrun API changes. Also implements a textrun cache for the new text frame
layout/generic/nsTextFrameThebes.cpp
layout/generic/nsTextFrameUtils.cpp
layout/generic/nsTextFrameUtils.h
layout/generic/nsTextRunTransformations.cpp
layout/generic/nsTextRunTransformations.h
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -77,16 +77,18 @@
 #include "nsCSSColorUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsDisplayList.h"
 #include "nsFrame.h"
 #include "nsTextTransformer.h"
 #include "nsTextFrameUtils.h"
 #include "nsTextRunTransformations.h"
 #include "nsFrameManager.h"
+#include "nsTextFrameTextRunCache.h"
+#include "nsExpirationTracker.h"
 
 #include "nsTextFragment.h"
 #include "nsGkAtoms.h"
 #include "nsFrameSelection.h"
 #include "nsISelection.h"
 #include "nsIDOMRange.h"
 #include "nsILookAndFeel.h"
 #include "nsCSSRendering.h"
@@ -551,16 +553,167 @@ protected:
                                     PRInt32 textLength,
                                     PRBool isRTLChars,
                                     PRBool isOddLevel,
                                     PRBool isBidiSystem);
   
   void SetOffsets(PRInt32 start, PRInt32 end);
 };
 
+static void
+DestroyUserData(void* aUserData)
+{
+  TextRunUserData* userData = NS_STATIC_CAST(TextRunUserData*, aUserData);
+  if (userData) {
+    nsMemory::Free(userData);
+  }
+}
+
+static void
+ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun)
+{
+  aFrame->RemoveStateBits(TEXT_IS_RUN_OWNER);
+  while (aFrame) {
+    if (aFrame->GetTextRun() != aTextRun)
+      break;
+    aFrame->SetTextRun(nsnull);
+    aFrame = NS_STATIC_CAST(nsTextFrame*, aFrame->GetNextContinuation());
+  }
+}
+
+static void
+UnhookTextRunFromFrames(gfxTextRun* aTextRun)
+{
+  if (!aTextRun->GetUserData())
+    return;
+
+  // Kill all references to the textrun. It could be referenced by any of its
+  // owners, and all their in-flows.
+  if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
+    nsIFrame* firstInFlow = NS_STATIC_CAST(nsIFrame*, aTextRun->GetUserData());
+    ClearAllTextRunReferences(NS_STATIC_CAST(nsTextFrame*, firstInFlow), aTextRun);
+  } else {
+    TextRunUserData* userData =
+      NS_STATIC_CAST(TextRunUserData*, aTextRun->GetUserData());
+    PRInt32 i;
+    for (i = 0; i < userData->mMappedFlowCount; ++i) {
+      ClearAllTextRunReferences(userData->mMappedFlows[i].mStartFrame, aTextRun);
+    }
+    DestroyUserData(userData);
+  }
+  aTextRun->SetUserData(nsnull);  
+}
+
+class FrameTextRunCache;
+
+static FrameTextRunCache *gTextRuns = nsnull;
+
+/*
+ * Cache textruns and expire them after 3*10 seconds of no use.
+ * Our textruns are either:
+ * -- not tracked and not in the cache
+ * -- tracked but not in the cache and nsTextFrameUtils::TEXT_IS_UNCACHED is set
+ * -- tracked, in the cache, and nsTextFrameUtils::TEXT_IS_UNCACHED is not set
+ */
+class FrameTextRunCache : public nsExpirationTracker<gfxTextRun,3> {
+public:
+  enum { TIMEOUT_SECONDS = 10 };
+  FrameTextRunCache()
+      : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
+  ~FrameTextRunCache() {
+    AgeAllGenerations();
+  }
+
+  void RemoveFromCache(gfxTextRun* aTextRun) {
+    RemoveObject(aTextRun);
+    if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED)) {
+      mCache.RemoveTextRun(aTextRun);
+    }
+  }
+
+  // This gets called when the timeout has expired on a gfxTextRun
+  virtual void NotifyExpired(gfxTextRun* aTextRun) {
+    UnhookTextRunFromFrames(aTextRun);
+    RemoveFromCache(aTextRun);
+    delete aTextRun;
+  }
+
+  class InnerCache : public gfxTextRunCache {
+  public:
+    // If a textrun is removed from the cache, and has no associated frame(s),
+    // then we may as well destroy it now. We should stop tracking it
+    // in any case; even if it has an associated frame, there is no point
+    // in keeping it around after it's dropped from that frame.
+    virtual void NotifyRemovedFromCache(gfxTextRun* aTextRun) {
+      if (aTextRun->GetExpirationState()->IsTracked()) {
+        gTextRuns->RemoveObject(aTextRun);
+      }
+      if (aTextRun->GetUserData())                
+        return;
+      delete aTextRun;
+    }
+  };
+
+  InnerCache mCache;
+};
+
+static gfxTextRun *
+MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+            gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
+            PRUint32 aFlags)
+{
+    nsAutoPtr<gfxTextRun> textRun;
+    if (aLength == 0) {
+        textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
+    } else if (aLength == 1 && aText[0] == ' ') {
+        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
+    } else {
+        textRun = gTextRuns->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
+    }
+    if (!textRun)
+        return nsnull;
+    nsresult rv = gTextRuns->AddObject(textRun);
+    if (NS_FAILED(rv))
+        return nsnull;
+    return textRun.forget();
+}
+
+static gfxTextRun *
+MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+            gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
+            PRUint32 aFlags)
+{
+    nsAutoPtr<gfxTextRun> textRun;
+    if (aLength == 0) {
+        textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
+    } else if (aLength == 1 && aText[0] == ' ') {
+        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
+    } else {
+        textRun = gTextRuns->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
+    }
+    if (!textRun)
+        return nsnull;
+    nsresult rv = gTextRuns->AddObject(textRun);
+    if (NS_FAILED(rv))
+        return nsnull;
+    return textRun.forget();
+}
+
+nsresult
+nsTextFrameTextRunCache::Init() {
+    gTextRuns = new FrameTextRunCache();
+    return gTextRuns ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+void
+nsTextFrameTextRunCache::Shutdown() {
+    delete gTextRuns;
+    gTextRuns = nsnull;
+}
+
 PRInt32 nsTextFrame::GetInFlowContentLength() {
 #ifdef IBMBIDI
   nsTextFrame* nextBidi = nsnull;
   PRInt32      start = -1, end;
 
   if (mState & NS_FRAME_IS_BIDI) {
     nextBidi = NS_STATIC_CAST(nsTextFrame*, GetLastInFlow()->GetNextContinuation());
     if (nextBidi) {
@@ -687,18 +840,17 @@ public:
     mLineBreakBeforeFrames.Clear();
     mMaxTextLength = 0;
     mDoubleByteText = PR_FALSE;
   }
   void AccumulateRunInfo(nsTextFrame* aFrame);
   void BuildTextRunForFrames(void* aTextBuffer);
   void AssignTextRun(gfxTextRun* aTextRun);
   nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex);
-  void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aText, PRUint32 aLength,
-                                 PRBool aIs2b, PRBool aIsExistingTextRun);
+  void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun);
 
   PRBool StylesMatchForTextRun(nsIFrame* aFrame1, nsIFrame* aFrame2);
 
   // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
   // are a sequence of in-flow frames. There can be multiple MappedFlows per
   // content element; the frames in each MappedFlow all have the same style
   // context.
   struct MappedFlow {
@@ -846,113 +998,16 @@ ExpandBuffer(PRUnichar* aDest, PRUint8* 
     *aDest = *aSrc;
     ++aDest;
     ++aSrc;
     --aCount;
   }
   return aDest;
 }
 
-static void*
-TransformTextToBuffer(nsTextFrame* aFrame, PRInt32 aContentLength,
-                      void* aBuffer, PRInt32 aCharSize, gfxSkipCharsBuilder* aBuilder,
-                      PRPackedBool* aIncomingWhitespace)
-{
-  const nsTextFragment* frag = aFrame->GetContent()->GetText();
-  PRInt32 contentStart = aFrame->GetContentOffset();
-  PRBool compressWhitespace = !aFrame->GetStyleText()->WhiteSpaceIsSignificant();
-  PRUint32 analysisFlags;
-
-  if (frag->Is2b()) {
-    NS_ASSERTION(aCharSize == 2, "Wrong size buffer!");
-    return nsTextFrameUtils::TransformText(
-        frag->Get2b() + contentStart, aContentLength, NS_STATIC_CAST(PRUnichar*, aBuffer),
-        compressWhitespace, aIncomingWhitespace, aBuilder, &analysisFlags);
-  } else {
-    if (aCharSize == 2) {
-      // Need to expand the text. First transform it into a temporary buffer,
-      // then expand.
-      nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> tempBuf;
-      if (!tempBuf.AppendElements(aContentLength))
-        return nsnull;
-      PRUint8* end = nsTextFrameUtils::TransformText(
-          NS_REINTERPRET_CAST(const PRUint8*, frag->Get1b()) + contentStart, aContentLength,
-          tempBuf.Elements(), compressWhitespace, aIncomingWhitespace, aBuilder, &analysisFlags);
-      return ExpandBuffer(NS_STATIC_CAST(PRUnichar*, aBuffer),
-                          tempBuf.Elements(), end - tempBuf.Elements());
-    } else {
-      return nsTextFrameUtils::TransformText(
-          NS_REINTERPRET_CAST(const PRUint8*, frag->Get1b()) + contentStart, aContentLength,
-          NS_STATIC_CAST(PRUint8*, aBuffer),
-          compressWhitespace, aIncomingWhitespace, aBuilder, &analysisFlags);
-    }
-  }
-}
-
-static void
-ReconstructTextForRun(gfxTextRun* aTextRun, PRBool aRememberText,
-                      BuildTextRunsScanner* aSetupBreaks,
-                      PRPackedBool* aIncomingWhitespace)
-{
-  gfxSkipCharsBuilder builder;
-  nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
-  PRUint32 charSize = (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_IS_8BIT) ? 1 : 2;
-  PRInt32 length;
-  void* bufEnd;
-  nsTextFrame* f;
-
-  if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
-    f = NS_STATIC_CAST(nsTextFrame*, aTextRun->GetUserData());
-    length = f->GetContentLength();
-    if (!buffer.AppendElements(length*charSize))
-      return;
-    bufEnd = TransformTextToBuffer(f, length, buffer.Elements(), charSize, &builder,
-                                   aIncomingWhitespace);
-    if (!bufEnd)
-      return;
-  } else {
-    TextRunUserData* userData = NS_STATIC_CAST(TextRunUserData*, aTextRun->GetUserData());
-    length = 0;
-    PRInt32 i;
-    for (i = 0; i < userData->mMappedFlowCount; ++i) {
-      TextRunMappedFlow* flow = &userData->mMappedFlows[i];
-      length += flow->mContentLength;
-    }
-    if (!buffer.AppendElements(length*charSize))
-      return;
-
-    bufEnd = buffer.Elements();
-    for (i = 0; i < userData->mMappedFlowCount; ++i) {
-      TextRunMappedFlow* flow = &userData->mMappedFlows[i];
-      bufEnd = TransformTextToBuffer(flow->mStartFrame, flow->mContentLength, bufEnd,
-                                     charSize, &builder, aIncomingWhitespace);
-      if (!bufEnd)
-        return;
-    }
-    f = userData->mMappedFlows[0].mStartFrame;
-  }
-  PRUint32 transformedLength = NS_STATIC_CAST(PRUint8*, bufEnd) - buffer.Elements();
-  if (charSize == 2) {
-    transformedLength >>= 1;
-  }
-
-  if (aRememberText) {
-    if (charSize == 2) {
-      aTextRun->RememberText(NS_REINTERPRET_CAST(PRUnichar*, buffer.Elements()), transformedLength);
-    } else {
-      aTextRun->RememberText(buffer.Elements(), transformedLength);
-    }
-  }
-
-  if (aSetupBreaks) {
-    aSetupBreaks->SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), transformedLength,
-                                            charSize == 2, PR_TRUE);
-  }
-}
-
 /**
  * This gets called when we need to make a text run for the current list of
  * frames.
  */
 void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks)
 {
   if (mMappedFlows.Length() == 0)
     return;
@@ -962,18 +1017,19 @@ void BuildTextRunsScanner::FlushFrames(P
       mCurrentRunTrimLeadingWhitespace) {
     // We do not need to (re)build the textrun.
     // Note that if the textrun included all these frames and more, and something
     // changed so that it can only cover these frames, then one of the frames
     // at the boundary would have detected the change and nuked the textrun.
 
     // Feed this run's text into the linebreaker to provide context. This also
     // updates mTrimNextRunLeadingWhitespace appropriately.
-    ReconstructTextForRun(mCurrentFramesAllSameTextRun, PR_FALSE, this,
-                          &mTrimNextRunLeadingWhitespace);
+    SetupBreakSinksForTextRun(mCurrentFramesAllSameTextRun, PR_TRUE);
+    mTrimNextRunLeadingWhitespace =
+      (mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) != 0;
   } else {
     nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
     if (!buffer.AppendElements(mMaxTextLength*(mDoubleByteText ? 2 : 1)))
       return;
     BuildTextRunForFrames(buffer.Elements());
   }
 
   if (aFlushLineBreaks) {
@@ -1110,25 +1166,16 @@ static nscoord StyleToCoord(const nsStyl
 {
   if (eStyleUnit_Coord == aCoord.GetUnit()) {
     return aCoord.GetCoordValue();
   } else {
     return 0;
   }
 }
 
-static void
-DestroyUserData(void* aUserData)
-{
-  TextRunUserData* userData = NS_STATIC_CAST(TextRunUserData*, aUserData);
-  if (userData) {
-    nsMemory::Free(userData);
-  }
-}
- 
 nsTextFrame*
 BuildTextRunsScanner::GetNextBreakBeforeFrame(PRUint32* aIndex)
 {
   PRUint32 index = *aIndex;
   if (index >= mLineBreakBeforeFrames.Length())
     return nsnull;
   *aIndex = index + 1;
   return NS_STATIC_CAST(nsTextFrame*, mLineBreakBeforeFrames.ElementAt(index));
@@ -1159,22 +1206,34 @@ GetFontGroupForFrame(nsIFrame* aFrame)
     return nsnull;
 
   nsIFontMetrics* metricsRaw = metrics;
   nsIThebesFontMetrics* fm = NS_STATIC_CAST(nsIThebesFontMetrics*, metricsRaw);
   return fm->GetThebesFontGroup();
 }
 
 static gfxTextRun*
-GetSpecialString(gfxFontGroup* aFontGroup, gfxFontGroup::SpecialString aSpecial,
-                 gfxTextRun* aTextRun)
+GetHyphenTextRun(gfxTextRun* aTextRun, nsIRenderingContext* aRefContext)
 {
-  if (!aFontGroup)
-    return nsnull;
-  return aFontGroup->GetSpecialStringTextRun(aSpecial, aTextRun);
+  gfxContext* ctx = NS_STATIC_CAST(gfxContext*,
+    aRefContext->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT));
+  gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
+  PRUint32 flags = gfxFontGroup::TEXT_IS_PERSISTENT;
+
+  static const PRUnichar unicodeHyphen = 0x2010;
+  gfxTextRun* textRun =
+    gfxGlobalTextRunCache::GetTextRun(&unicodeHyphen, 1, fontGroup, ctx,
+                                      aTextRun->GetAppUnitsPerDevUnit(), flags);
+  if (textRun && textRun->CountMissingGlyphs() == 0)
+    return textRun;
+
+  static const PRUint8 dash = '-';
+  return gfxGlobalTextRunCache::GetTextRun(&dash, 1, fontGroup, ctx,
+                                           aTextRun->GetAppUnitsPerDevUnit(),
+                                           flags);
 }
 
 static gfxFont::Metrics
 GetFontMetrics(gfxFontGroup* aFontGroup)
 {
   if (!aFontGroup)
     return gfxFont::Metrics();
   gfxFont* font = aFontGroup->GetFontAt(0);
@@ -1188,17 +1247,16 @@ BuildTextRunsScanner::BuildTextRunForFra
 {
   gfxSkipCharsBuilder builder;
 
   const void* textPtr = aTextBuffer;
   PRBool anySmallcapsStyle = PR_FALSE;
   PRBool anyTextTransformStyle = PR_FALSE;
   nsIContent* lastContent = nsnull;
   PRInt32 endOfLastContent = 0;
-  PRBool anyMixedStyleFlows = PR_FALSE;
   PRUint32 textFlags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
 
   if (mCurrentRunTrimLeadingWhitespace) {
     textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
   }
 
   nsAutoTArray<PRUint32,50> textBreakPoints;
   // We might have a final break offset for the end of the textrun
@@ -1260,17 +1318,16 @@ BuildTextRunsScanner::BuildTextRunForFra
 
     if (content == lastContent) {
       NS_ASSERTION(endOfLastContent >= contentStart,
                    "Gap in textframes mapping content?!"); 
       // Text frames can overlap (see comment in ScanFrame below)
       contentStart = PR_MAX(contentStart, endOfLastContent);
       if (contentStart >= contentEnd)
         continue;
-      anyMixedStyleFlows = PR_TRUE;
       userData->mMappedFlows[finalMappedFlowCount - 1].mContentLength += contentLength;
     } else {
       TextRunMappedFlow* newFlow = &userData->mMappedFlows[finalMappedFlowCount];
 
       newFlow->mStartFrame = mappedFlow->mStartFrame;
       newFlow->mDOMOffsetToBeforeTransformOffset = builder.GetCharCount() - mappedFlow->mContentOffset;
       newFlow->mContentLength = contentLength;
       ++finalMappedFlowCount;
@@ -1366,21 +1423,21 @@ BuildTextRunsScanner::BuildTextRunForFra
   if (!fontGroup) {
     DestroyUserData(userData);
     return;
   }
 
   // Setup factory chain
   nsAutoPtr<nsTransformingTextRunFactory> transformingFactory;
   if (anySmallcapsStyle) {
-    transformingFactory = new nsFontVariantTextRunFactory(fontGroup);
+    transformingFactory = new nsFontVariantTextRunFactory();
   }
   if (anyTextTransformStyle) {
     transformingFactory =
-      new nsCaseTransformTextRunFactory(fontGroup, transformingFactory.forget());
+      new nsCaseTransformTextRunFactory(transformingFactory.forget());
   }
   nsTArray<nsStyleContext*> styles;
   if (transformingFactory) {
     for (i = 0; i < mMappedFlows.Length(); ++i) {
       MappedFlow* mappedFlow = &mMappedFlows[i];
       PRUint32 end = i == mMappedFlows.Length() - 1 ? transformedLength :
           mMappedFlows[i + 1].mTransformedTextOffset;
       nsStyleContext* sc = mappedFlow->mStartFrame->GetStyleContext();
@@ -1392,22 +1449,22 @@ BuildTextRunsScanner::BuildTextRunForFra
   }
 
   if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
     textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
   }
   if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
     textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
   }
-  if (!(textFlags & nsTextFrameUtils::TEXT_HAS_NON_ASCII)) {
-    textFlags |= gfxTextRunFactory::TEXT_IS_ASCII;
-  }
   if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) {
     textFlags |= gfxTextRunFactory::TEXT_IS_RTL;
   }
+  if (mTrimNextRunLeadingWhitespace) {
+    textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE;
+  }
 
   gfxSkipChars skipChars;
   skipChars.TakeFrom(&builder);
   // Convert linebreak coordinates to transformed string offsets
   NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
                "Didn't find all the frames to break-before...");
   gfxSkipCharsIterator iter(skipChars);
   for (i = 0; i < nextBreakIndex; ++i) {
@@ -1415,110 +1472,100 @@ BuildTextRunsScanner::BuildTextRunForFra
     *breakPoint = iter.ConvertOriginalToSkipped(*breakPoint);
   }
   if (mStartOfLine) {
     textBreakPoints[nextBreakIndex] = transformedLength;
     ++nextBreakIndex;
   }
 
   gfxTextRunFactory::Parameters params =
-      { mContext, finalUserData, firstFrame->GetStyleVisibility()->mLangGroup, &skipChars,
+      { mContext, finalUserData, &skipChars,
         textBreakPoints.Elements(), nextBreakIndex,
-        firstFrame->PresContext()->AppUnitsPerDevPixel(), textFlags };
+        firstFrame->PresContext()->AppUnitsPerDevPixel() };
 
   gfxTextRun* textRun;
   if (mDoubleByteText) {
-    if (textFlags & gfxTextRunFactory::TEXT_IS_ASCII) {
-      NS_WARNING("Hmm ... why are we taking the Unicode path when the text is all ASCII?");
-    }
     const PRUnichar* text = NS_STATIC_CAST(const PRUnichar*, textPtr);
     if (transformingFactory) {
       textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
-                                                 styles.Elements());
+                                                 fontGroup, textFlags, styles.Elements());
       if (textRun) {
         transformingFactory.forget();
       }
     } else {
-      textRun = fontGroup->MakeTextRun(text, transformedLength, &params);
+      textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
     }
     if (!textRun) {
       DestroyUserData(userData);
       return;
     }
-    if (anyMixedStyleFlows) {
-      // Make sure we're not asked to recover the text, it's too hard to do later.
-      textRun->RememberText(text, transformedLength);
-    }
   } else {
     const PRUint8* text = NS_STATIC_CAST(const PRUint8*, textPtr);
+    textFlags |= gfxFontGroup::TEXT_IS_8BIT;
     if (transformingFactory) {
       textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
-                                                 styles.Elements());
+                                                 fontGroup, textFlags, styles.Elements());
       if (textRun) {
         transformingFactory.forget();
       }
     } else {
-      textRun = fontGroup->MakeTextRun(text, transformedLength, &params);
+      textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
     }
     if (!textRun) {
       DestroyUserData(userData);
       return;
     }
-    if (anyMixedStyleFlows) {
-      // Make sure we're not asked to recover the text, it's too hard to do later.
-      textRun->RememberText(text, transformedLength);
-    }
   }
   // We have to set these up after we've created the textrun, because
   // the breaks may be stored in the textrun during this very call.
   // This is a bit annoying because it requires another loop over the frames
   // making up the textrun, but I don't see a way to avoid this.
-  SetupBreakSinksForTextRun(textRun, textPtr, transformedLength, mDoubleByteText,
-                            PR_FALSE);
+  SetupBreakSinksForTextRun(textRun, PR_FALSE);
 
   // Actually wipe out the textruns associated with the mapped frames and associate
   // those frames with this text run.
   AssignTextRun(textRun);
 }
 
 void
-BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aText, PRUint32 aLength,
-                                                PRBool aIs2b, PRBool aIsExistingTextRun)
+BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, 
+                                                PRBool aIsExistingTextRun)
 {
   // textruns have uniform language
   nsIAtom* lang = mMappedFlows[0].mStartFrame->GetStyleVisibility()->mLangGroup;
   PRUint32 i;
   for (i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
     nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
       new BreakSink(aTextRun, mappedFlow->mTransformedTextOffset, aIsExistingTextRun));
     if (!breakSink || !*breakSink)
       return;
     PRUint32 offset = mappedFlow->mTransformedTextOffset;
 
     PRUint32 length =
-      (i == mMappedFlows.Length() - 1 ? aLength : mMappedFlows[i + 1].mTransformedTextOffset)
+      (i == mMappedFlows.Length() - 1 ? aTextRun->GetLength()
+       : mMappedFlows[i + 1].mTransformedTextOffset)
       - offset;
 
     PRUint32 flags = 0;
     if (!mappedFlow->mAncestorControllingInitialBreak ||
         mappedFlow->mAncestorControllingInitialBreak->GetStyleText()->WhiteSpaceCanWrap()) {
       flags |= nsLineBreaker::BREAK_NONWHITESPACE_BEFORE;
     }
     if (mappedFlow->mStartFrame->GetStyleText()->WhiteSpaceCanWrap()) {
       flags |= nsLineBreaker::BREAK_WHITESPACE | nsLineBreaker::BREAK_NONWHITESPACE_INSIDE;
     }
     // If length is zero and BREAK_WHITESPACE is active, this will notify
     // the linebreaker to insert a break opportunity before the next character.
     // Thus runs of entirely-skipped whitespace can still induce breaks.
-    if (aIs2b) {
-      mLineBreaker.AppendText(lang, NS_STATIC_CAST(const PRUnichar*, aText) + offset,
+    if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
+      mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset,
                               length, flags, *breakSink);
     } else {
-      mLineBreaker.AppendText(lang, NS_STATIC_CAST(const PRUint8*, aText) + offset,
+      mLineBreaker.AppendText(lang, aTextRun->GetTextUnicode() + offset,
                               length, flags, *breakSink);
     }
   }
 }
 
 void
 BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun)
 {
@@ -1556,32 +1603,45 @@ BuildTextRunsScanner::AssignTextRun(gfxT
     nsIContent* content = startFrame->GetContent();
     if (content != lastContent) {
       startFrame->AddStateBits(TEXT_IS_RUN_OWNER);
       lastContent = content;
     }    
   }
 }
 
+static already_AddRefed<nsIRenderingContext>
+GetReferenceRenderingContext(nsTextFrame* aTextFrame, nsIRenderingContext* aRC)
+{
+  if (aRC) {
+    NS_ADDREF(aRC);
+    return aRC;
+  }
+
+  nsIRenderingContext* result;      
+  nsresult rv = aTextFrame->PresContext()->PresShell()->
+    CreateRenderingContext(aTextFrame, &result);
+  if (NS_FAILED(rv))
+    return nsnull;
+  return result;      
+}
+
 gfxSkipCharsIterator
 nsTextFrame::EnsureTextRun(nsIRenderingContext* aRC, nsBlockFrame* aBlock,
                            const nsLineList::iterator* aLine,
                            PRUint32* aFlowEndInTextRun)
 {
-  if (!mTextRun) {
-    if (!aRC) {
-      nsCOMPtr<nsIRenderingContext> rendContext;      
-      nsresult rv = PresContext()->PresShell()->
-        CreateRenderingContext(this, getter_AddRefs(rendContext));
-      if (NS_SUCCEEDED(rv)) {
-        BuildTextRuns(rendContext, this, aBlock, aLine);
-      }
-    } else {
-      BuildTextRuns(aRC, this, aBlock, aLine);
-    }      
+  if (mTextRun) {
+    if (mTextRun->GetExpirationState()->IsTracked()) {
+      gTextRuns->MarkUsed(mTextRun);
+    }
+  } else {
+    nsCOMPtr<nsIRenderingContext> rendContext =
+      GetReferenceRenderingContext(this, aRC);     
+    BuildTextRuns(rendContext, this, aBlock, aLine);
     if (!mTextRun) {
       // A text run was not constructed for this frame. This is bad. The caller
       // will check mTextRun.
       static const gfxSkipChars emptySkipChars;
       return gfxSkipCharsIterator(emptySkipChars, 0);
     }
   }
 
@@ -1797,22 +1857,16 @@ public:
       mHyphenWidth(-1)
   {
     NS_ASSERTION(mTextRun, "Textrun not initialized!");
   }
 
   // Call this after construction if you're not going to reflow the text
   void InitializeForDisplay(PRBool aTrimAfter);
 
-  virtual void ForceRememberText() {
-    PRPackedBool incomingWhitespace =
-      (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0;
-    ReconstructTextForRun(mTextRun, PR_TRUE, nsnull, &incomingWhitespace);
-  }
-
   virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing);
   virtual gfxFloat GetHyphenWidth();
   virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
                                     PRPackedBool* aBreakBefore);
 
   /**
    * Count the number of justifiable characters in the given DOM range
    */
@@ -1995,22 +2049,18 @@ PropertyProvider::GetSpacing(PRUint32 aS
   }
 
   // Now add tab spacing, if there is any
   if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) {
     // ComputeTabSpaceCount() will tell us where spaces need to be inserted
     // for tabs, and how many.
     // ComputeTabSpaceCount takes transformed string offsets.
     PRUint8* tabSpaceList = ComputeTabSpaceCount(aStart, aLength);
-    gfxTextRun* spaceTextRun =
-      GetSpecialString(GetFontGroup(), gfxFontGroup::STRING_SPACE, mTextRun);
-    gfxFloat spaceWidth = mLetterSpacing + mWordSpacing;
-    if (spaceTextRun) {
-      spaceWidth += spaceTextRun->GetAdvanceWidth(0,  spaceTextRun->GetLength(), nsnull);
-    }
+    gfxFloat spaceWidth = mLetterSpacing + mWordSpacing +
+        mTextRun->GetFontGroup()->GetFontAt(0)->GetMetrics().spaceWidth*mTextRun->GetAppUnitsPerDevUnit();
     for (index = 0; index < aLength; ++index) {
       PRInt32 tabSpaces = tabSpaceList[index];
       aSpacing[index].mAfter += spaceWidth*tabSpaces;
     }
   }
 
   // Now add in justification spacing
   if (mJustificationSpacing) {
@@ -2059,18 +2109,18 @@ PropertyProvider::GetTabExpansionCount(P
   }
   return sum;
 }
 
 gfxFloat
 PropertyProvider::GetHyphenWidth()
 {
   if (mHyphenWidth < 0) {
-    gfxTextRun* hyphenTextRun =
-      GetSpecialString(GetFontGroup(), gfxFontGroup::STRING_HYPHEN, mTextRun);
+    nsCOMPtr<nsIRenderingContext> rc = GetReferenceRenderingContext(mFrame, nsnull);
+    gfxTextRun* hyphenTextRun = GetHyphenTextRun(mTextRun, rc);
     mHyphenWidth = mLetterSpacing;
     if (hyphenTextRun) {
       mHyphenWidth += hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull);
     }
   }
   return mHyphenWidth;
 }
 
@@ -2165,21 +2215,21 @@ PropertyProvider::SetupJustificationSpac
     // justification space assigned
     return;
   }
 
   gfxFloat naturalWidth =
     mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(),
                               GetSkippedDistance(mStart, realEnd), this);
   if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
-    gfxTextRun* specialTextRun =
-      GetSpecialString(GetFontGroup(), gfxFontGroup::STRING_HYPHEN, mTextRun);
-    if (specialTextRun) {
+    nsCOMPtr<nsIRenderingContext> rc = GetReferenceRenderingContext(mFrame, nsnull);
+    gfxTextRun* hyphenTextRun = GetHyphenTextRun(mTextRun, rc);
+    if (hyphenTextRun) {
       naturalWidth +=
-        specialTextRun->GetAdvanceWidth(0, specialTextRun->GetLength(), nsnull);
+        hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull);
     }
   }
   gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth;
   if (totalJustificationSpace <= 0) {
     // No space available
     return;
   }
   
@@ -3037,52 +3087,41 @@ nsTextFrame::GetLastContinuation() const
   nsTextFrame* lastInFlow = NS_CONST_CAST(nsTextFrame*, this);
   while (lastInFlow->mNextContinuation)  {
     lastInFlow = NS_STATIC_CAST(nsTextFrame*, lastInFlow->mNextContinuation);
   }
   NS_POSTCONDITION(lastInFlow, "illegal state in continuation chain.");
   return lastInFlow;
 }
 
-static void
-ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun)
-{
-  aFrame->RemoveStateBits(TEXT_IS_RUN_OWNER);
-  while (aFrame) {
-    if (aFrame->GetTextRun() != aTextRun)
-      break;
-    aFrame->SetTextRun(nsnull);
-    aFrame = NS_STATIC_CAST(nsTextFrame*, aFrame->GetNextContinuation());
-  }
-}
-
 void
 nsTextFrame::ClearTextRun()
 {
   // save textrun because ClearAllTextRunReferences will clear ours
   gfxTextRun* textRun = mTextRun;
   
   if (!textRun || !(GetStateBits() & TEXT_IS_RUN_OWNER))
     return;
 
-  // Kill all references to the textrun. It could be referenced by any of its
-  // owners, and all their in-flows.
-  if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
-    nsIFrame* firstInFlow = NS_STATIC_CAST(nsIFrame*, textRun->GetUserData());
-    ClearAllTextRunReferences(NS_STATIC_CAST(nsTextFrame*, firstInFlow), textRun);
-  } else {
-    TextRunUserData* userData =
-      NS_STATIC_CAST(TextRunUserData*, textRun->GetUserData());
-    PRInt32 i;
-    for (i = 0; i < userData->mMappedFlowCount; ++i) {
-      ClearAllTextRunReferences(userData->mMappedFlows[i].mStartFrame, textRun);
+  UnhookTextRunFromFrames(textRun);
+  if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
+    // the textrun's text may be referencing a DOM node that's changing,
+    // or that will change, so we'd better kill this textrun now.
+    if (textRun->GetExpirationState()->IsTracked()) {
+      gTextRuns->RemoveFromCache(textRun);
     }
-    DestroyUserData(userData);
-  }
-  delete textRun;
+    delete textRun;
+    return;
+  }
+
+  // If it's not tracked (i.e. in the cache), then delete it now.
+  // Otherwise it stays alive in case it gets hit in the cache.
+  if (!textRun->GetExpirationState()->IsTracked()) {
+    delete textRun;
+  }
 }
 
 NS_IMETHODIMP
 nsTextFrame::CharacterDataChanged(nsPresContext* aPresContext,
                                   nsIContent*     aChild,
                                   PRBool          aAppend)
 {
   ClearTextRun();
@@ -3614,18 +3653,20 @@ nsTextFrame::PaintTextWithSelectionColor
     // Draw text segment
     aCtx->SetColor(gfxRGBA(foreground));
     gfxFloat advance;
     mTextRun->Draw(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y), offset, length,
                    &aDirtyRect, &aProvider, &advance);
     if (hyphenWidth) {
       // Draw the hyphen
       gfxFloat hyphenBaselineX = aFramePt.x + xOffset + mTextRun->GetDirection()*advance;
-      gfxTextRun* hyphenTextRun =
-        GetSpecialString(aProvider.GetFontGroup(), gfxFontGroup::STRING_HYPHEN, mTextRun);
+      // Get a reference rendering context because aCtx might not have the
+      // reference matrix currently set
+      nsCOMPtr<nsIRenderingContext> rc = GetReferenceRenderingContext(this, nsnull);
+      gfxTextRun* hyphenTextRun = GetHyphenTextRun(mTextRun, rc);
       if (hyphenTextRun) {
         hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
                             0, hyphenTextRun->GetLength(), &aDirtyRect, nsnull, nsnull);
       }
       advance += hyphenWidth;
     }
     iterator.UpdateWithAdvance(advance);
   }
@@ -3766,18 +3807,18 @@ nsTextFrame::PaintText(nsIRenderingConte
   ctx->SetColor(gfxRGBA(textPaintStyle.GetTextColor()));
   
   mTextRun->Draw(ctx, textBaselinePt,
                  provider.GetStart().GetSkippedOffset(),
                  ComputeTransformedLength(provider),
                  &dirtyRect, &provider, needAdvanceWidth);
   if (GetStateBits() & TEXT_HYPHEN_BREAK) {
     gfxFloat hyphenBaselineX = textBaselinePt.x + mTextRun->GetDirection()*advanceWidth;
-    gfxTextRun* hyphenTextRun =
-      GetSpecialString(provider.GetFontGroup(), gfxFontGroup::STRING_HYPHEN, mTextRun);
+    nsCOMPtr<nsIRenderingContext> rc = GetReferenceRenderingContext(this, nsnull);
+    gfxTextRun* hyphenTextRun = GetHyphenTextRun(mTextRun, rc);
     if (hyphenTextRun) {
       hyphenTextRun->Draw(ctx, gfxPoint(hyphenBaselineX, textBaselinePt.y),
                           0, hyphenTextRun->GetLength(), &dirtyRect, nsnull, nsnull);
     }
   }
   PaintTextDecorations(ctx, dirtyRect, framePt, textPaintStyle, provider);
 }
 
@@ -4536,46 +4577,41 @@ nsTextFrame::ComputeSize(nsIRenderingCon
                          nsSize aMargin, nsSize aBorder, nsSize aPadding,
                          PRBool aShrinkWrap)
 {
   // Inlines and text don't compute size before reflow.
   return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 }
 
 static void
-AddCharToMetrics(gfxFloat aWidth, PropertyProvider* aProvider,
-                 gfxFontGroup::SpecialString aSpecial, gfxTextRun* aTextRun,
+AddCharToMetrics(gfxTextRun* aCharTextRun, gfxTextRun* aBaseTextRun,
                  gfxTextRun::Metrics* aMetrics, PRBool aTightBoundingBox)
 {
   gfxRect charRect;
+  // assume char does not overflow font metrics!!!
+  gfxFloat width = aCharTextRun->GetAdvanceWidth(0, aCharTextRun->GetLength(), nsnull);
   if (aTightBoundingBox) {
-    gfxTextRun* specialTextRun =
-      GetSpecialString(aProvider->GetFontGroup(), aSpecial, aTextRun);
-    gfxTextRun::Metrics charMetrics;
-    if (specialTextRun) {
-      charMetrics =
-        specialTextRun->MeasureText(0, specialTextRun->GetLength(), PR_TRUE, nsnull);
-    }
+    gfxTextRun::Metrics charMetrics =
+        aCharTextRun->MeasureText(0, aCharTextRun->GetLength(), PR_TRUE, nsnull);
     charRect = charMetrics.mBoundingBox;
   } else {
-    // assume char does not overflow font metrics!!!
-    charRect = gfxRect(0, -aMetrics->mAscent, aWidth,
+    charRect = gfxRect(0, -aMetrics->mAscent, width,
                        aMetrics->mAscent + aMetrics->mDescent);
   }
-  if (aTextRun->IsRightToLeft()) {
+  if (aBaseTextRun->IsRightToLeft()) {
     // Char comes before text, so the bounding box is moved to the
     // right by aWidth
-    aMetrics->mBoundingBox.MoveBy(gfxPoint(aWidth, 0));
+    aMetrics->mBoundingBox.MoveBy(gfxPoint(width, 0));
   } else {
     // char is moved to the right by mAdvanceWidth
-    charRect.MoveBy(gfxPoint(aMetrics->mAdvanceWidth, 0));
+    charRect.MoveBy(gfxPoint(width, 0));
   }
   aMetrics->mBoundingBox = aMetrics->mBoundingBox.Union(charRect);
 
-  aMetrics->mAdvanceWidth += aWidth;
+  aMetrics->mAdvanceWidth += width;
 }
 
 NS_IMETHODIMP
 nsTextFrame::Reflow(nsPresContext*           aPresContext,
                     nsHTMLReflowMetrics&     aMetrics,
                     const nsHTMLReflowState& aReflowState,
                     nsReflowStatus&          aStatus)
 {
@@ -4784,17 +4820,18 @@ nsTextFrame::Reflow(nsPresContext*      
       // This may set lastBreak greater than 'length', but that's OK
       lastBreak = end.GetOriginalOffset();
       // Reset 'end' to the correct offset.
       end.SetOriginalOffset(offset + charsFit);
     }
   }
   if (usedHyphenation) {
     // Fix up metrics to include hyphen
-    AddCharToMetrics(provider.GetHyphenWidth(), &provider, gfxFontGroup::STRING_HYPHEN,
+    gfxTextRun* hyphenTextRun = GetHyphenTextRun(mTextRun, aReflowState.rendContext);
+    AddCharToMetrics(hyphenTextRun,
                      mTextRun, &textMetrics, needTightBoundingBox);
     AddStateBits(TEXT_HYPHEN_BREAK);
   }
 
   // If it's only whitespace that didn't fit, then we will include
   // the whitespace in this frame, but we will eagerly trim all trailing
   // whitespace from our width calculations. Basically we do
   // TrimTrailingWhitespace early so that line layout sees that we fit on
@@ -4976,51 +5013,52 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPr
     return NS_ERROR_FAILURE;
   PRUint32 trimmedStart = iter.GetSkippedOffset();
 
   const nsTextFragment* frag = mContent->GetText();
   TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE);
   PRUint32 trimmedEnd = iter.ConvertOriginalToSkipped(trimmed.mStart + trimmed.mLength);
   const nsStyleText* textStyle = GetStyleText();
   gfxFloat delta = 0;
-  PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength);
 
   if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
     aLastCharIsJustifiable = PR_TRUE;
   } else if (trimmed.mStart + trimmed.mLength < mContentOffset + mContentLength) {
     gfxSkipCharsIterator end = iter;
     PRUint32 endOffset = end.ConvertOriginalToSkipped(mContentOffset + mContentLength);
     if (trimmedEnd < endOffset) {
+      PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength);
       delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
       // non-compressed whitespace being skipped at end of line -> justifiable
       // XXX should we actually *count* justifiable characters that should be
       // removed from the overall count? I think so...
       aLastCharIsJustifiable = PR_TRUE;
     }
   }
 
   if (!aLastCharIsJustifiable &&
       NS_STYLE_TEXT_ALIGN_JUSTIFY == textStyle->mTextAlign) {
     // Check if any character in the last cluster is justifiable
+    PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength);
     PRBool isCJK = IsChineseJapaneseLangGroup(this);
     gfxSkipCharsIterator justificationEnd(iter);
     provider.FindEndOfJustificationRange(&justificationEnd);
 
     PRInt32 i;
     for (i = justificationEnd.GetOriginalOffset(); i < trimmed.mStart + trimmed.mLength; ++i) {
       if (IsJustifiableCharacter(frag, i, isCJK)) {
         aLastCharIsJustifiable = PR_TRUE;
       }
     }
   }
 
   gfxFloat advanceDelta;
   mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
                           (GetStateBits() & TEXT_START_OF_LINE) != 0, PR_TRUE,
-                          &provider, &advanceDelta);
+                          &advanceDelta);
 
   // aDeltaWidth is *subtracted* from our width.
   // If advanceDelta is positive then setting the line break made us longer,
   // so aDeltaWidth could go negative.
   aDeltaWidth = NSToCoordFloor(delta - advanceDelta);
   // XXX if aDeltaWidth goes negative, that means this frame might not actually fit
   // anymore!!! We need higher level line layout to recover somehow. This can
   // really only happen when we have glyphs with special shapes at the end of
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -98,18 +98,17 @@ static PRBool IsDiscardable(PRUint8 ch, 
 PRUnichar*
 nsTextFrameUtils::TransformText(const PRUnichar* aText, PRUint32 aLength,
                                 PRUnichar* aOutput,
                                 PRBool aCompressWhitespace,
                                 PRPackedBool* aIncomingWhitespace,
                                 gfxSkipCharsBuilder* aSkipChars,
                                 PRUint32* aAnalysisFlags)
 {
-  // We're just going to assume this!
-  PRUint32 flags = TEXT_HAS_NON_ASCII;
+  PRUint32 flags = 0;
   PRUnichar* outputStart = aOutput;
 
   if (!aCompressWhitespace) {
     // Convert tabs and formfeeds to spaces and skip discardables.
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       PRUnichar ch = *aText++;
       if (ch == '\t') {
@@ -118,18 +117,16 @@ nsTextFrameUtils::TransformText(const PR
         *aOutput++ = ' ';
       } else if (IsDiscardable(ch, &flags)) {
         aSkipChars->SkipChar();
       } else {
         aSkipChars->KeepChar();
         if (ch == CH_NBSP) {
           ch = ' ';
           flags |= TEXT_WAS_TRANSFORMED;
-        } else if (IS_SURROGATE(ch)) {
-          flags |= gfxTextRunFactory::TEXT_HAS_SURROGATES;
         }
         *aOutput++ = ch;
       }
     }
     *aIncomingWhitespace = PR_FALSE;
   } else {
     PRBool inWhitespace = *aIncomingWhitespace;
     PRUint32 i;
@@ -156,18 +153,16 @@ nsTextFrameUtils::TransformText(const PR
       if (!nowInWhitespace) {
         if (IsDiscardable(ch, &flags)) {
           aSkipChars->SkipChar();
           nowInWhitespace = inWhitespace;
         } else {
           if (ch == CH_NBSP) {
             ch = ' ';
             flags |= TEXT_WAS_TRANSFORMED;
-          } else if (IS_SURROGATE(ch)) {
-            flags |= gfxTextRunFactory::TEXT_HAS_SURROGATES;
           }
           *aOutput++ = ch;
           aSkipChars->KeepChar();
         }
       } else {
         if (inWhitespace) {
           aSkipChars->SkipChar();
         } else {
@@ -194,25 +189,23 @@ PRUint8*
 nsTextFrameUtils::TransformText(const PRUint8* aText, PRUint32 aLength,
                                 PRUint8* aOutput,
                                 PRBool aCompressWhitespace,
                                 PRPackedBool* aIncomingWhitespace,
                                 gfxSkipCharsBuilder* aSkipChars,
                                 PRUint32* aAnalysisFlags)
 {
   PRUint32 flags = 0;
-  PRUint8 allBits = 0;
   PRUint8* outputStart = aOutput;
 
   if (!aCompressWhitespace) {
     // Convert tabs to spaces and skip discardables.
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       PRUint8 ch = *aText++;
-      allBits |= ch;
       if (ch == '\t') {
         flags |= TEXT_HAS_TAB|TEXT_WAS_TRANSFORMED;
         aSkipChars->KeepChar();
         *aOutput++ = ' ';
       } else if (IsDiscardable(ch, &flags)) {
         aSkipChars->SkipChar();
       } else {
         aSkipChars->KeepChar();
@@ -224,17 +217,16 @@ nsTextFrameUtils::TransformText(const PR
       }
     }
     *aIncomingWhitespace = PR_FALSE;
   } else {
     PRBool inWhitespace = *aIncomingWhitespace;
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       PRUint8 ch = *aText++;
-      allBits |= ch;
       PRBool nowInWhitespace = ch == ' ' || ch == '\t' || ch == '\n' || ch == '\f';
       if (!nowInWhitespace) {
         if (IsDiscardable(ch, &flags)) {
           aSkipChars->SkipChar();
           nowInWhitespace = inWhitespace;
         } else {
           if (ch == CH_NBSP) {
             ch = ' ';
@@ -257,19 +249,16 @@ nsTextFrameUtils::TransformText(const PR
       inWhitespace = nowInWhitespace;
     }
     *aIncomingWhitespace = inWhitespace;
   }
 
   if (outputStart + aLength != aOutput) {
     flags |= TEXT_WAS_TRANSFORMED;
   }
-  if (allBits & 0x80) {
-    flags |= TEXT_HAS_NON_ASCII;
-  }
   *aAnalysisFlags = flags;
   return aOutput;
 }
 
 // TODO The wordbreaker needs to be fixed. It's buggy, for example, it doesn't
 // handle diacriticals combined with spaces
 enum SimpleCharClass {
   CLASS_ALNUM,
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -35,37 +35,39 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef NSTEXTFRAMEUTILS_H_
 #define NSTEXTFRAMEUTILS_H_
 
 #include "gfxFont.h"
 #include "gfxSkipChars.h"
+#include "gfxTextRunCache.h"
 #include "nsTextFragment.h"
 
 #define BIG_TEXT_NODE_SIZE 4096
 
 class nsTextFrameUtils {
 public:
   // These constants are used as textrun flags for textframe textruns.
   enum {
     // The following flags are set by TransformText
 
     // the text has at least one untransformed tab character
     TEXT_HAS_TAB             = 0x010000,
     // the original text has at least one soft hyphen character
     TEXT_HAS_SHY             = 0x020000,
-    TEXT_HAS_NON_ASCII       = 0x040000,
-    TEXT_WAS_TRANSFORMED     = 0x080000,
+    TEXT_WAS_TRANSFORMED     = 0x040000,
 
     // The following flags are set by nsTextFrame
 
     TEXT_IS_SIMPLE_FLOW      = 0x100000,
-    TEXT_INCOMING_WHITESPACE = 0x200000
+    TEXT_INCOMING_WHITESPACE = 0x200000,
+    TEXT_TRAILING_WHITESPACE = 0x400000,
+    TEXT_IS_UNCACHED         = 0x800000
   };
 
   static PRBool
   IsPunctuationMark(PRUnichar aChar);
 
   /**
    * Returns PR_TRUE if aChars/aLength are something that make a space
    * character not be whitespace when they follow the space character.
@@ -81,16 +83,19 @@ public:
    * Create a text run from a run of Unicode text. The text may have whitespace
    * compressed. A preformatted tab is sent to the text run as a single space.
    * (Tab spacing must be performed by textframe later.) Certain other
    * characters are discarded.
    * 
    * @param aCompressWhitespace runs of consecutive whitespace (spaces not
    * followed by a diacritical mark, tabs, and newlines) are compressed to a
    * single space character.
+   * @param aIncomingWhitespace a flag indicating whether there was whitespace
+   * preceding this text. We set it to indicate if there's whitespace
+   * preceding the end of this text.
    */
   static PRUnichar* TransformText(const PRUnichar* aText, PRUint32 aLength,
                                   PRUnichar* aOutput,
                                   PRBool aCompressWhitespace,
                                   PRPackedBool* aIncomingWhitespace,
                                   gfxSkipCharsBuilder* aSkipChars,
                                   PRUint32* aAnalysisFlags);
 
@@ -127,17 +132,17 @@ public:
   static PRInt32
   FindWordBoundary(const nsTextFragment* aText,
                    gfxTextRun* aTextRun,
                    gfxSkipCharsIterator* aIterator,
                    PRInt32 aOffset, PRInt32 aLength,
                    PRInt32 aPosition, PRInt32 aDirection,
                    PRBool aBreakBeforePunctuation,
                    PRBool aBreakAfterPunctuation,
-                   PRBool* aWordIsWhitespace);                
+                   PRBool* aWordIsWhitespace);
 };
 
 class nsSkipCharsRunIterator {
 public:
   enum LengthMode {
     LENGTH_UNSKIPPED_ONLY   = PR_FALSE,
     LENGTH_INCLUDES_SKIPPED = PR_TRUE
   };
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -49,23 +49,23 @@
 #define SZLIG 0x00DF
 
 /**
  * So that we can reshape as necessary, we store enough information
  * to fully rebuild the textrun contents.
  */
 class nsTransformedTextRun : public gfxTextRun {
 public:
-  nsTransformedTextRun(gfxTextRunFactory::Parameters* aParams,
+  nsTransformedTextRun(const gfxTextRunFactory::Parameters* aParams,
                        nsTransformingTextRunFactory* aFactory,
+                       gfxFontGroup* aFontGroup,
                        const PRUnichar* aString, PRUint32 aLength,
-                       nsStyleContext** aStyles)
-    : gfxTextRun(aParams, aLength), mString(aString, aLength),
-      mFactory(aFactory), mRefContext(aParams->mContext),
-      mLangGroup(aParams->mLangGroup)
+                       const PRUint32 aFlags, nsStyleContext** aStyles)
+    : gfxTextRun(aParams, aString, aLength, aFontGroup, aFlags),
+      mFactory(aFactory), mRefContext(aParams->mContext)
   {
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       mStyles.AppendElement(aStyles[i]);
     }
     for (i = 0; i < aParams->mInitialBreakCount; ++i) {
       mLineBreaks.AppendElement(aParams->mInitialBreaks[i]);
     }
@@ -75,31 +75,27 @@ public:
                                         PRPackedBool* aBreakBefore)
   {
     PRBool changed = gfxTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore);
     mFactory->RebuildTextRun(this);
     return changed;
   }
   virtual PRBool SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
                                PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                               PropertyProvider* aProvider,
                                gfxFloat* aAdvanceWidthDelta);
 
-  nsString                                mString;
   nsAutoPtr<nsTransformingTextRunFactory> mFactory;
   nsRefPtr<gfxContext>                    mRefContext;
-  nsCOMPtr<nsIAtom>                       mLangGroup;
   nsTArray<PRUint32>                      mLineBreaks;
   nsTArray<nsRefPtr<nsStyleContext> >     mStyles;
 };
 
 PRBool
 nsTransformedTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
                                     PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                                    PropertyProvider* aProvider,
                                     gfxFloat* aAdvanceWidthDelta)
 {
   nsTArray<PRUint32> newBreaks;
   PRUint32 i;
   PRBool changed = PR_FALSE;
   for (i = 0; i < mLineBreaks.Length(); ++i) {
     PRUint32 pos = mLineBreaks[i];
     if (pos >= aStart)
@@ -130,48 +126,50 @@ nsTransformedTextRun::SetLineBreaks(PRUi
       *aAdvanceWidthDelta = 0;
     }
     return PR_FALSE;
   }
 
   newBreaks.AppendElements(mLineBreaks.Elements() + i, mLineBreaks.Length() - i);
   mLineBreaks.SwapElements(newBreaks);
 
-  gfxFloat currentAdvance = GetAdvanceWidth(aStart, aLength, aProvider);
+  gfxFloat currentAdvance = GetAdvanceWidth(aStart, aLength, nsnull);
   mFactory->RebuildTextRun(this);
   if (aAdvanceWidthDelta) {
-    *aAdvanceWidthDelta = GetAdvanceWidth(aStart, aLength, aProvider) - currentAdvance;
+    *aAdvanceWidthDelta = GetAdvanceWidth(aStart, aLength, nsnull) - currentAdvance;
   }
   return PR_TRUE;
 }
 
 gfxTextRun*
 nsTransformingTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                                          gfxTextRunFactory::Parameters* aParams,
+                                          const gfxTextRunFactory::Parameters* aParams,
+                                          gfxFontGroup* aFontGroup, PRUint32 aFlags,
                                           nsStyleContext** aStyles)
 {
   nsTransformedTextRun* textRun =
-    new nsTransformedTextRun(aParams, this, aString, aLength, aStyles);
+    new nsTransformedTextRun(aParams, this, aFontGroup, aString, aLength, aFlags, aStyles);
   if (!textRun)
     return nsnull;
   RebuildTextRun(textRun);
   return textRun;
 }
 
 gfxTextRun*
 nsTransformingTextRunFactory::MakeTextRun(const PRUint8* aString, PRUint32 aLength,
-                                          gfxTextRunFactory::Parameters* aParams,
+                                          const gfxTextRunFactory::Parameters* aParams,
+                                          gfxFontGroup* aFontGroup, PRUint32 aFlags,
                                           nsStyleContext** aStyles)
 {
   // We'll only have a Unicode code path to minimize the amount of code needed
   // for these rarely used features
   NS_ConvertASCIItoUTF16 unicodeString(NS_REINTERPRET_CAST(const char*, aString), aLength);
-  gfxTextRunFactory::Parameters params = *aParams;
-  params.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT;
-  return MakeTextRun(unicodeString.get(), aLength, &params, aStyles);
+  return MakeTextRun(unicodeString.get(), aLength, aParams, aFontGroup,
+                     aFlags & ~(gfxFontGroup::TEXT_IS_PERSISTENT | gfxFontGroup::TEXT_IS_8BIT),
+                     aStyles);
 }
 
 static PRUint32
 CountGlyphs(const gfxTextRun::DetailedGlyph* aDetails) {
   PRUint32 glyphCount;
   for (glyphCount = 0; !aDetails[glyphCount].mIsLastGlyph; ++glyphCount) {
   }
   return glyphCount + 1;
@@ -296,52 +294,53 @@ MergeCharactersInTextRun(gfxTextRun* aDe
       }
       ++offset;
     }
   }
   NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
 }
 
 static gfxTextRunFactory::Parameters
-GetParametersForInner(nsTransformedTextRun* aTextRun,
-                      gfxSkipChars* aDummy)
+GetParametersForInner(nsTransformedTextRun* aTextRun, PRUint32* aFlags)
 {
   gfxTextRunFactory::Parameters params =
-    { aTextRun->mRefContext, nsnull, aTextRun->mLangGroup, aDummy,
-      nsnull, nsnull, PRUint32(aTextRun->GetAppUnitsPerDevUnit()),
-      aTextRun->GetFlags() };
+    { aTextRun->mRefContext, nsnull, nsnull,
+      nsnull, nsnull, PRUint32(aTextRun->GetAppUnitsPerDevUnit())
+    };
+  *aFlags = aTextRun->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT;
   return params;
 }
 
 void
 nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun)
 {
   nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
   if (!converter)
     return;
 
-  gfxFontStyle fontStyle = *mFontGroup->GetStyle();
+  gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
+  gfxFontStyle fontStyle = *fontGroup->GetStyle();
   fontStyle.size *= 0.8;
-  nsRefPtr<gfxFontGroup> smallFont = mFontGroup->Copy(&fontStyle);
+  nsRefPtr<gfxFontGroup> smallFont = fontGroup->Copy(&fontStyle);
   if (!smallFont)
     return;
 
-  gfxSkipChars dummy;
-  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(aTextRun, &dummy);
+  PRUint32 flags;
+  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(aTextRun, &flags);
 
   PRUint32 length = aTextRun->GetLength();
-  const PRUnichar* str = aTextRun->mString.BeginReading();
+  const PRUnichar* str = aTextRun->GetTextUnicode();
   nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
   // Create a textrun so we can check cluster-start properties
   nsAutoPtr<gfxTextRun> inner;
-  inner = mFontGroup->MakeTextRun(str, length, &innerParams);
+  inner = fontGroup->MakeTextRun(str, length, &innerParams, flags);
   if (!inner)
     return;
 
-  nsCaseTransformTextRunFactory uppercaseFactory(smallFont, nsnull, PR_TRUE);
+  nsCaseTransformTextRunFactory uppercaseFactory(nsnull, PR_TRUE);
 
   aTextRun->ResetGlyphRuns();
 
   PRUint32 runStart = 0;
   PRPackedBool runIsLowercase = PR_FALSE;
   nsAutoTArray<nsStyleContext*,50> styleArray;
   nsAutoTArray<PRPackedBool,50> canBreakBeforeArray;
   nsAutoTArray<PRUint32,10> lineBreakBeforeArray;
@@ -374,19 +373,21 @@ nsFontVariantTextRunFactory::RebuildText
 
     if ((i == length || runIsLowercase != isLowercase) && runStart < i) {
       nsAutoPtr<gfxTextRun> child;
       // Setup actual line break data for child (which may affect shaping)
       innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
       innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
       if (runIsLowercase) {
         child = uppercaseFactory.MakeTextRun(str + runStart, i - runStart,
-                                             &innerParams, styleArray.Elements());
+                                             &innerParams, smallFont, flags,
+                                             styleArray.Elements());
       } else {
-        child = mFontGroup->MakeTextRun(str + runStart, i - runStart, &innerParams);
+        child = fontGroup->
+            MakeTextRun(str + runStart, i - runStart, &innerParams, flags);
       }
       if (!child)
         return;
       // Copy potential linebreaks into child so they're preserved
       // (and also child will be shaped appropriately)
       NS_ASSERTION(canBreakBeforeArray.Length() == i - runStart,
                    "lost some break-before values?");
       child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(), canBreakBeforeArray.Elements());
@@ -414,17 +415,17 @@ nsFontVariantTextRunFactory::RebuildText
 void
 nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun)
 {
   nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
   if (!converter)
     return;
 
   PRUint32 length = aTextRun->GetLength();
-  const PRUnichar* str = aTextRun->mString.BeginReading();
+  const PRUnichar* str = aTextRun->GetTextUnicode();
   nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
 
   nsAutoString convertedString;
   nsAutoTArray<PRPackedBool,50> charsToMergeArray;
   nsAutoTArray<nsStyleContext*,50> styleArray;
   nsAutoTArray<PRPackedBool,50> canBreakBeforeArray;
   nsAutoTArray<PRUint32,10> lineBreakBeforeArray;
   PRUint32 nextLineBreak = 0;
@@ -486,30 +487,32 @@ nsCaseTransformTextRunFactory::RebuildTe
   if (nextLineBreak < aTextRun->mLineBreaks.Length() &&
       aTextRun->mLineBreaks[nextLineBreak] == length) {
     lineBreakBeforeArray.AppendElement(length + extraCharsCount);
     ++nextLineBreak;
   }
   NS_ASSERTION(nextLineBreak == aTextRun->mLineBreaks.Length(),
                "lost track of line breaks somehow");
 
-  gfxSkipChars dummy;
-  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(aTextRun, &dummy);
+  PRUint32 flags;
+  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(aTextRun, &flags);
+  gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
 
   nsAutoPtr<gfxTextRun> child;
   // Setup actual line break data for child (which may affect shaping)
   innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
   innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
   if (mInnerTransformingTextRunFactory) {
     child = mInnerTransformingTextRunFactory->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
-        &innerParams, styleArray.Elements());
+        &innerParams, fontGroup, flags, styleArray.Elements());
   } else {
-    child = mFontGroup->MakeTextRun(
-        convertedString.BeginReading(), convertedString.Length(), &innerParams);
+    child = fontGroup->MakeTextRun(
+        convertedString.BeginReading(), convertedString.Length(), &innerParams,
+        flags);
   }
   if (!child)
     return;
   // Copy potential linebreaks into child so they're preserved
   // (and also child will be shaped appropriately)
   NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
                "Dropped characters or break-before values somewhere!");
   child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(), canBreakBeforeArray.Elements());
--- a/layout/generic/nsTextRunTransformations.h
+++ b/layout/generic/nsTextRunTransformations.h
@@ -44,58 +44,53 @@ class nsTransformedTextRun;
 class nsStyleContext;
 
 class nsTransformingTextRunFactory {
 public:
   virtual ~nsTransformingTextRunFactory() {}
 
   // Default 8-bit path just transforms to Unicode and takes that path
   gfxTextRun* MakeTextRun(const PRUint8* aString, PRUint32 aLength,
-                          gfxFontGroup::Parameters* aParams, nsStyleContext** aStyles);
+                          const gfxFontGroup::Parameters* aParams,
+                          gfxFontGroup* aFontGroup, PRUint32 aFlags,
+                          nsStyleContext** aStyles);
   gfxTextRun* MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                          gfxFontGroup::Parameters* aParams, nsStyleContext** aStyles);
+                          const gfxFontGroup::Parameters* aParams,
+                          gfxFontGroup* aFontGroup, PRUint32 aFlags,
+                          nsStyleContext** aStyles);
 
   virtual void RebuildTextRun(nsTransformedTextRun* aTextRun) = 0;
 };
 
 /**
  * Builds textruns that render their text using a font-variant (i.e.,
  * smallcaps).
  */
 class nsFontVariantTextRunFactory : public nsTransformingTextRunFactory {
 public:
-  nsFontVariantTextRunFactory(gfxFontGroup* aFontGroup)
-    : mFontGroup(aFontGroup) {}
-    
   virtual void RebuildTextRun(nsTransformedTextRun* aTextRun);
-
-protected:
-  nsRefPtr<gfxFontGroup> mFontGroup;
 };
 
 /**
  * Builds textruns that transform the text in some way (e.g., capitalize)
  * and then render the text using some other textrun implementation.
  */
 class nsCaseTransformTextRunFactory : public nsTransformingTextRunFactory {
 public:
   // We could add an optimization here so that when there is no inner
   // factory, no title-case conversion, and no upper-casing of SZLIG, we override
   // MakeTextRun (after making it virtual in the superclass) and have it
   // just convert the string to uppercase or lowercase and create the textrun
   // via the fontgroup.
   
-  nsCaseTransformTextRunFactory(gfxFontGroup* aFontGroup,
-                                nsTransformingTextRunFactory* aInnerTransformingTextRunFactory,
+  nsCaseTransformTextRunFactory(nsTransformingTextRunFactory* aInnerTransformingTextRunFactory,
                                 PRBool aAllUppercase = PR_FALSE)
-    : mFontGroup(aFontGroup),
-      mInnerTransformingTextRunFactory(aInnerTransformingTextRunFactory),
+    : mInnerTransformingTextRunFactory(aInnerTransformingTextRunFactory),
       mAllUppercase(aAllUppercase) {}
 
   virtual void RebuildTextRun(nsTransformedTextRun* aTextRun);
 
 protected:
-  nsRefPtr<gfxFontGroup>                  mFontGroup;
   nsAutoPtr<nsTransformingTextRunFactory> mInnerTransformingTextRunFactory;
   PRPackedBool                            mAllUppercase;
 };
 
 #endif /*NSTEXTRUNTRANSFORMATIONS_H_*/