Bug 385423. Refactor textrun cache so that all textrun clients use a single global word-based cache. Responsibility for stripping out problematic characters (e.g. newlines) is given to the word cache. r=vlad,smontagu
authorroc+@cs.cmu.edu
Tue, 03 Jul 2007 20:39:01 -0700
changeset 3118 5b26a3e78cdb950ac6f6390da55a545cf20bb5cc
parent 3117 1076b130cc5aa3ad63ec7f4c4daf5584417195dd
child 3119 a00cc9ed6ea384bca5498933b7d0b67dec9c144b
push idunknown
push userunknown
push dateunknown
reviewersvlad, smontagu
bugs385423
milestone1.9a7pre
Bug 385423. Refactor textrun cache so that all textrun clients use a single global word-based cache. Responsibility for stripping out problematic characters (e.g. newlines) is given to the word cache. r=vlad,smontagu
gfx/src/thebes/nsThebesFontMetrics.h
gfx/thebes/public/gfxFont.h
gfx/thebes/public/gfxTextRunCache.h
gfx/thebes/public/gfxTextRunWordCache.h
gfx/thebes/src/gfxAtsuiFonts.cpp
gfx/thebes/src/gfxPangoFonts.cpp
gfx/thebes/src/gfxPlatform.cpp
gfx/thebes/src/gfxTextRunCache.cpp
gfx/thebes/src/gfxTextRunWordCache.cpp
gfx/thebes/src/gfxWindowsFonts.cpp
gfx/thebes/test/gfxFontSelectionTest.cpp
gfx/thebes/test/gfxWordCacheTest.cpp
layout/generic/nsTextFrameThebes.cpp
layout/generic/nsTextFrameUtils.h
layout/generic/nsTextRunTransformations.cpp
layout/svg/base/src/nsSVGGlyphFrame.cpp
layout/svg/base/src/nsSVGGlyphFrame.h
--- a/gfx/src/thebes/nsThebesFontMetrics.h
+++ b/gfx/src/thebes/nsThebesFontMetrics.h
@@ -154,36 +154,36 @@ public:
 protected:
 
     const gfxFont::Metrics& GetMetrics() const;
 
     class AutoTextRun {
     public:
         AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
                     const char* aString, PRInt32 aLength, PRBool aEnableSpacing) {
-            mTextRun = gfxGlobalTextRunCache::GetTextRun(
+            mTextRun = gfxTextRunCache::MakeTextRun(
                 NS_REINTERPRET_CAST(const PRUint8*, aString), aLength,
                 aMetrics->mFontGroup,
                 NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
                 aMetrics->mP2A,
                 ComputeFlags(aMetrics, aEnableSpacing));
         }
         AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC,
                     const PRUnichar* aString, PRInt32 aLength, PRBool aEnableSpacing) {
-            mTextRun = gfxGlobalTextRunCache::GetTextRun(
+            mTextRun = gfxTextRunCache::MakeTextRun(
                 aString, aLength, aMetrics->mFontGroup,
                 NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)),
                 aMetrics->mP2A,
                 ComputeFlags(aMetrics, aEnableSpacing));
         }
-        gfxTextRun* operator->() { return mTextRun; }
-        gfxTextRun* get() { return mTextRun; }
+        gfxTextRun* operator->() { return mTextRun.get(); }
+        gfxTextRun* get() { return mTextRun.get(); }
 
     private:
-        gfxTextRun* mTextRun;
+        gfxTextRunCache::AutoTextRun mTextRun;
         
         static PRUint32 ComputeFlags(nsThebesFontMetrics* aMetrics,
                                      PRBool aEnableSpacing) {
             PRUint32 flags = 0;
             if (aMetrics->GetRightToLeftTextRunMode()) {
                 flags |= gfxTextRunFactory::TEXT_IS_RTL;
             }
             if (aEnableSpacing) {
--- a/gfx/thebes/public/gfxFont.h
+++ b/gfx/thebes/public/gfxFont.h
@@ -424,19 +424,21 @@ protected:
 class THEBES_API gfxTextRunFactory {
     THEBES_INLINE_DECL_REFCOUNTING(gfxTextRunFactory)
 
 public:
     // Flags in the mask 0xFFFF0000 are reserved for textrun clients
     // Flags in the mask 0x0000F000 are reserved for per-platform fonts
     // Flags in the mask 0x00000FFF are set by the textrun creator.
     enum {
-        USER_TEXT_FLAGS     = 0xFFFF0000,
+        CACHE_TEXT_FLAGS    = 0xF0000000,
+        USER_TEXT_FLAGS     = 0x0FFF0000,
         PLATFORM_TEXT_FLAGS = 0x0000F000,
         TEXTRUN_TEXT_FLAGS  = 0x00000FFF,
+        SETTABLE_FLAGS      = CACHE_TEXT_FLAGS | USER_TEXT_FLAGS,
       
         /**
          * When set, the text string pointer used to create the text run
          * is guaranteed to be available during the lifetime of the text run.
          */
         TEXT_IS_PERSISTENT           = 0x0001,
         /**
          * When set, the text is known to be all-ASCII (< 128).
@@ -512,17 +514,17 @@ public:
 };
 
 /**
  * gfxTextRun is an abstraction for drawing and measuring substrings of a run
  * of text. It stores runs of positioned glyph data, each run having a single
  * gfxFont. The glyphs are associated with a string of source text, and the
  * gfxTextRun APIs take parameters that are offsets into that source text.
  * 
- * \r, \t and \n characters (for which gfxFontGroup::IsInvisibleChar returns
+ * \r, \t and \n characters (for which gfxFontGroup::IsInvalidChar returns
  * PR_TRUE) should be set to a CompressedGlyph with SetMissing() to make them
  * invisible and zero-width. 
  * 
  * gfxTextRuns are not refcounted. They should be deleted when no longer required.
  * 
  * gfxTextRuns are mostly immutable. The only things that can change are
  * inter-cluster spacing and line break placement. Spacing is always obtained
  * lazily by methods that need it, it is not cached. Line breaks are stored
@@ -790,22 +792,22 @@ public:
     // Utility getters
 
     PRBool IsRightToLeft() const { return (mFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0; }
     gfxFloat GetDirection() const { return (mFlags & gfxTextRunFactory::TEXT_IS_RTL) ? -1.0 : 1.0; }
     void *GetUserData() const { return mUserData; }
     void SetUserData(void *aUserData) { mUserData = aUserData; }
     PRUint32 GetFlags() const { return mFlags; }
     void SetFlagBits(PRUint32 aFlags) {
-      NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::USER_TEXT_FLAGS),
+      NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
                    "Only user flags should be mutable");
       mFlags |= aFlags;
     }
     void ClearFlagBits(PRUint32 aFlags) {
-      NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::USER_TEXT_FLAGS),
+      NS_ASSERTION(!(aFlags & ~gfxTextRunFactory::SETTABLE_FLAGS),
                    "Only user flags should be mutable");
       mFlags &= ~aFlags;
     }
     const gfxSkipChars& GetSkipChars() const { return mSkipChars; }
     PRUint32 GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; }
     gfxFontGroup *GetFontGroup() const { return mFontGroup; }
     const PRUint8 *GetText8Bit() const
     { return (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) ? mText.mSingle : nsnull; }
@@ -1163,20 +1165,19 @@ public:
             mStyle.Equals(other.mStyle);
     }
 
     const gfxFontStyle *GetStyle() const { return &mStyle; }
 
     virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle) = 0;
 
     /**
-     * Tabs, CRs and LFs should be zero-width and invisible. They should
-     * break up shaping.
+     * Tabs, CRs and LFs should not be passed in to MakeTextRun.
      */
-    static PRBool IsInvisibleChar(PRUnichar ch) {
+    static PRBool IsInvalidChar(PRUnichar ch) {
         return ch == '\t' || ch == '\r' || ch == '\n';
     }
 
     /**
      * Make a textrun for an empty string. This is fast; if you call it,
      * don't bother caching the result.
      */
     gfxTextRun *MakeEmptyTextRun(const Parameters *aParams, PRUint32 aFlags);
--- a/gfx/thebes/public/gfxTextRunCache.h
+++ b/gfx/thebes/public/gfxTextRunCache.h
@@ -34,163 +34,90 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef GFX_TEXT_RUN_CACHE_H
 #define GFX_TEXT_RUN_CACHE_H
 
 #include "gfxFont.h"
-#include "nsCheapSets.h"
 
 /**
- * A textrun cache object. A general textrun caching solution. If you use
- * this class to create a textrun cache, you are responsible for managing
- * textrun lifetimes. The full power of textrun creation is exposed; you can
- * set all textrun creation flags and parameters.
- */
-class THEBES_API gfxTextRunCache {
-public:
-    gfxTextRunCache() {
-        mCache.Init(100);
-    }
-    ~gfxTextRunCache() {
-        NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
-    }
-
-    /**
-     * Get a textrun from the cache, create one if necessary.
-     * @param aFlags the flags TEXT_IS_ASCII, TEXT_IS_8BIT and TEXT_HAS_SURROGATES
-     * are ignored; the cache sets them based on the string.
-     * @param aCallerOwns if this is null, the cache always creates a new
-     * textrun owned by the caller. If non-null, the cache may return a textrun
-     * that was previously created and is owned by some previous caller
-     * to GetOrMakeTextRun on this cache. If so, *aCallerOwns will be set
-     * to false.
-     */
-    gfxTextRun *GetOrMakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                 gfxFontGroup *aFontGroup,
-                                 const gfxFontGroup::Parameters *aParams,
-                                 PRUint32 aFlags, PRBool *aCallerOwns = nsnull);
-    /**
-     * Get a textrun from the cache, create one if necessary.
-     * @param aFlags the flags TEXT_IS_ASCII, TEXT_IS_8BIT and TEXT_HAS_SURROGATES
-     * are ignored; the cache sets them based on the string.
-     * @param aCallerOwns if this is null, the cache always creates a new
-     * textrun owned by the caller. If non-null, the cache may return a textrun
-     * that was previously created and is owned by some previous caller
-     * to GetOrMakeTextRun on this cache. If so, *aCallerOwns will be set
-     * to false.
-     */
-    gfxTextRun *GetOrMakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                 gfxFontGroup *aFontGroup,
-                                 const gfxFontGroup::Parameters *aParams,
-                                 PRUint32 aFlags, PRBool *aCallerOwns = nsnull);
-
-    /**
-     * Notify that a text run was hit in the cache, a new one created, and
-     * that the new one has replaced the old one in the cache.
-     */
-    virtual void NotifyRemovedFromCache(gfxTextRun *aTextRun) {}
-
-    /**
-     * Remove a textrun from the cache. This must be called before aTextRun
-     * is deleted!
-     */
-    void RemoveTextRun(gfxTextRun *aTextRun);
-
-    /** The following flags are part of the cache key: */
-    enum { FLAG_MASK =
-        gfxTextRunFactory::TEXT_IS_RTL |
-        gfxTextRunFactory::TEXT_ENABLE_SPACING |
-        gfxTextRunFactory::TEXT_ABSOLUTE_SPACING |
-        gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING |
-        gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS |
-        gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX
-    };
-
-protected:
-    struct THEBES_API CacheHashKey {
-        void       *mFontOrGroup;
-        const void *mString;
-        PRUint32    mLength;
-        PRUint32    mAppUnitsPerDevUnit;
-        PRUint32    mFlags;
-        PRUint32    mStringHash;
-
-        CacheHashKey(void *aFontOrGroup, const void *aString, PRUint32 aLength,
-                     PRUint32 aAppUnitsPerDevUnit, PRUint32 aFlags, PRUint32 aStringHash)
-            : mFontOrGroup(aFontOrGroup), mString(aString), mLength(aLength),
-              mAppUnitsPerDevUnit(aAppUnitsPerDevUnit), mFlags(aFlags),
-              mStringHash(aStringHash) {}
-    };
-
-    class THEBES_API CacheHashEntry : public PLDHashEntryHdr {
-    public:
-        typedef const CacheHashKey &KeyType;
-        typedef const CacheHashKey *KeyTypePointer;
-
-        // When constructing a new entry in the hashtable, mTextRuns will be
-        // blank. The caller of Put() will fill it in.
-        CacheHashEntry(KeyTypePointer aKey)  { }
-        CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
-        ~CacheHashEntry() { }
-
-        PRBool KeyEquals(const KeyTypePointer aKey) const;
-        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
-        static PLDHashNumber HashKey(const KeyTypePointer aKey);
-        enum { ALLOW_MEMMOVE = PR_TRUE };
-
-        gfxTextRun *mTextRun;
-    };
-
-    CacheHashKey GetKeyForTextRun(gfxTextRun *aTextRun);
-
-    nsTHashtable<CacheHashEntry> mCache;
-};
-
-/**
- * A simple global textrun cache for textruns that do not carry state
+ * A simple textrun cache for textruns that do not carry state
  * (e.g., actual or potential linebreaks) and do not need complex initialization.
  * The lifetimes of these textruns are managed by the cache (they are auto-expired
  * after a certain period of time).
  */
-class THEBES_API gfxGlobalTextRunCache {
+class THEBES_API gfxTextRunCache {
 public:
     /**
-     * Get a textrun for the given text, using a global cache. The returned
-     * textrun is valid until the next event loop. We own it, the caller
-     * must not free it.
+     * Get a textrun for the given text, using a global cache. The textrun
+     * must be released via ReleaseTextRun, not deleted.
      * Do not set any state in the textrun (e.g. actual or potential linebreaks).
      * Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set
      * appropriately.
      * Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live
      * forever.
+     * The string can contain any characters, invalid ones will be stripped
+     * properly.
      */
-    static gfxTextRun *GetTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                  gfxFontGroup *aFontGroup,
-                                  gfxContext *aRefContext,
-                                  PRUint32 aAppUnitsPerDevUnit,
-                                  PRUint32 aFlags);
+    static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                                   gfxFontGroup *aFontGroup,
+                                   gfxContext *aRefContext,
+                                   PRUint32 aAppUnitsPerDevUnit,
+                                   PRUint32 aFlags);
 
     /**
-     * Get a textrun for the given text, using a global cache. The returned
-     * textrun is valid until the next event loop. We own it, the caller
-     * must not free it.
+     * As above, but allows a full Parameters object to be passed in.
+     */
+    static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                                   gfxFontGroup *aFontGroup,
+                                   const gfxTextRunFactory::Parameters* aParams,
+                                   PRUint32 aFlags);
+
+    /**
+     * Get a textrun for the given text, using a global cache. The textrun
+     * must be released via ReleaseTextRun, not deleted.
      * Do not set any state in the textrun (e.g. actual or potential linebreaks).
      * Flags IS_8BIT, IS_ASCII and HAS_SURROGATES are automatically set
      * appropriately.
      * Flag IS_PERSISTENT must NOT be set unless aText is guaranteed to live
      * forever.
+     * The string can contain any characters, invalid ones will be stripped
+     * properly.
      */
-    static gfxTextRun *GetTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                  gfxFontGroup *aFontGroup,
-                                  gfxContext *aRefContext,
-                                  PRUint32 aAppUnitsPerDevUnit,
-                                  PRUint32 aFlags);
+    static gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+                                   gfxFontGroup *aFontGroup,
+                                   gfxContext *aRefContext,
+                                   PRUint32 aAppUnitsPerDevUnit,
+                                   PRUint32 aFlags);
+    
+    /**
+     * Release a previously acquired textrun. Consider using AutoTextRun
+     * instead of calling this.
+     */
+    static void ReleaseTextRun(gfxTextRun *aTextRun);
+
+    class AutoTextRun {
+    public:
+    	AutoTextRun(gfxTextRun *aTextRun) : mTextRun(aTextRun) {}
+    	AutoTextRun() : mTextRun(nsnull) {}
+    	AutoTextRun& operator=(gfxTextRun *aTextRun) {
+            gfxTextRunCache::ReleaseTextRun(mTextRun);
+            mTextRun = aTextRun;
+            return *this;
+        }
+        ~AutoTextRun() {
+            gfxTextRunCache::ReleaseTextRun(mTextRun);
+        }
+        gfxTextRun *get() { return mTextRun; }
+        gfxTextRun *operator->() { return mTextRun; }
+    private:
+        gfxTextRun *mTextRun;
+    };
 
 protected:
     friend class gfxPlatform;
 
     static nsresult Init();
     static void Shutdown();
 };
 
--- a/gfx/thebes/public/gfxTextRunWordCache.h
+++ b/gfx/thebes/public/gfxTextRunWordCache.h
@@ -41,106 +41,51 @@
 #include "gfxFont.h"
 
 /**
  * Cache individual "words" (strings delimited by white-space or white-space-like
  * characters that don't involve kerning or ligatures) in textruns.
   */
 class THEBES_API gfxTextRunWordCache {
 public:
-    gfxTextRunWordCache() {
-        mCache.Init(100);
-    }
-    ~gfxTextRunWordCache() {
-        NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
-    }
+    enum { TEXT_IN_CACHE = 0x10000000 };
 
     /**
      * Create a textrun using cached words.
+     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
+     * treated as invisible missing.
      * @param aFlags the flags TEXT_IS_ASCII and TEXT_HAS_SURROGATES must be set
-     * by the caller, if applicable
-     * @param aIsInCache if true is returned, then RemoveTextRun must be called
-     * before the textrun changes or dies.
+     * by the caller, if applicable; TEXT_IN_CACHE is added if we
+     * have a reference to the textrun in the cache and RemoveTextRun must
+     * be called when the textrun dies.
      */
-    gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                            gfxFontGroup *aFontGroup,
-                            const gfxFontGroup::Parameters *aParams,
-                            PRUint32 aFlags, PRBool *aIsInCache);
+    static gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                                   gfxFontGroup *aFontGroup,
+                                   const gfxFontGroup::Parameters *aParams,
+                                   PRUint32 aFlags);
     /**
-     * Create a textrun using cached words
-     * @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
-     * if applicable
-     * @param aIsInCache if true is returned, then RemoveTextRun must be called
-     * before the textrun changes or dies.
+     * Create a textrun using cached words.
+     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
+     * treated as invisible missing.
+     * @param aFlags the flags TEXT_IS_ASCII and TEXT_HAS_SURROGATES must be set
+     * by the caller, if applicable; TEXT_IN_CACHE is added if we
+     * have a reference to the textrun in the cache and RemoveTextRun must
+     * be called when the textrun dies.
      */
-    gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                            gfxFontGroup *aFontGroup,
-                            const gfxFontGroup::Parameters *aParams,
-                            PRUint32 aFlags, PRBool *aIsInCache);
+    static gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+                                   gfxFontGroup *aFontGroup,
+                                   const gfxFontGroup::Parameters *aParams,
+                                   PRUint32 aFlags);
 
     /**
      * Remove a textrun from the cache. This must be called before aTextRun
      * is deleted! The text in the textrun must still be valid.
      */
-    void RemoveTextRun(gfxTextRun *aTextRun);
+    static void RemoveTextRun(gfxTextRun *aTextRun);
 
 protected:
-    struct THEBES_API CacheHashKey {
-        void        *mFontOrGroup;
-        const void  *mString;
-        PRUint32     mLength;
-        PRUint32     mAppUnitsPerDevUnit;
-        PRUint32     mStringHash;
-        PRPackedBool mIsDoubleByteText;
-    };
-
-    class THEBES_API CacheHashEntry : public PLDHashEntryHdr {
-    public:
-        typedef const CacheHashKey &KeyType;
-        typedef const CacheHashKey *KeyTypePointer;
-
-        // When constructing a new entry in the hashtable, the caller of Put()
-        // will fill us in.
-        CacheHashEntry(KeyTypePointer aKey) : mTextRun(nsnull), mWordOffset(0),
-            mHashedByFont(PR_FALSE) { }
-        CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
-        ~CacheHashEntry() { }
-
-        PRBool KeyEquals(const KeyTypePointer aKey) const;
-        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
-        static PLDHashNumber HashKey(const KeyTypePointer aKey);
-        enum { ALLOW_MEMMOVE = PR_TRUE };
+    friend class gfxPlatform;
 
-        gfxTextRun *mTextRun;
-        // The offset of the start of the word in the textrun. The length of
-        // the word is not stored here because we can figure it out by
-        // looking at the textrun's text.
-        PRUint32    mWordOffset:31;
-        // This is set to true when the cache entry was hashed by the first
-        // font in mTextRun's fontgroup; it's false when the cache entry
-        // was hashed by the fontgroup itself.
-        PRUint32    mHashedByFont:1;
-    };
-    
-    // Used to track words that should be copied from one textrun to
-    // another during the textrun construction process
-    struct DeferredWord {
-        gfxTextRun *mSourceTextRun;
-        PRUint32    mSourceOffset;
-        PRUint32    mDestOffset;
-        PRUint32    mLength;
-        PRUint32    mHash;
-    };
-    
-    PRBool LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
-                      PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
-                      nsTArray<DeferredWord>* aDeferredWords);
-    void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
-                       gfxContext *aContext,
-                       const nsTArray<DeferredWord>& aDeferredWords,
-                       PRBool aSuccessful);
-    void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
-                    PRUint32 aEnd, PRUint32 aHash);    
-
-    nsTHashtable<CacheHashEntry> mCache;
+    static nsresult Init();
+    static void Shutdown();
 };
 
 #endif /* GFX_TEXT_RUN_WORD_CACHE_H */
--- a/gfx/thebes/src/gfxAtsuiFonts.cpp
+++ b/gfx/thebes/src/gfxAtsuiFonts.cpp
@@ -682,32 +682,28 @@ SetGlyphsForCharacterGroup(ATSLayoutReco
             ++regularGlyphCount;
             displayGlyph = glyph;
         }
         if (i > 0 && aRun->IsRightToLeft() != (offset < aGlyphs[i - 1].originalOffset)) {
             inOrder = PR_FALSE;
         }
     }
 
-    gfxTextRun::CompressedGlyph g;
-    if (gfxFontGroup::IsInvisibleChar(aString[firstOffset/2])) {
-        NS_ASSERTION(firstOffset == lastOffset,
-                     "Invisible character grouped with other characters?");
-        aRun->SetCharacterGlyph(aSegmentStart + firstOffset/2, g.SetMissing());
-        return;
-    }
+    NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aString[firstOffset/2]),
+                 "Invalid char passed in");
 
     if (!allMatched) {
         for (i = firstOffset; i <= lastOffset; ++i) {
             PRUint32 index = i/2;
             aRun->SetMissingGlyph(aSegmentStart + index, aString[index]);
         }
         return;
     }
 
+    gfxTextRun::CompressedGlyph g;
     PRUint32 offset;
     for (offset = firstOffset + 2; offset <= lastOffset; offset += 2) {
         PRUint32 index = offset/2;
         if (!inOrder) {
             // Because the characters in this group were not in the textrun's
             // required order, we must make the entire group an indivisible cluster
             aRun->SetCharacterGlyph(aSegmentStart + index, g.SetClusterContinuation());
         } else if (!aRun->GetCharacterGlyphs()[index].IsClusterContinuation()) {
--- a/gfx/thebes/src/gfxPangoFonts.cpp
+++ b/gfx/thebes/src/gfxPangoFonts.cpp
@@ -1057,21 +1057,19 @@ SetGlyphsForCharacterGroup(const PangoGl
         // Skip the CompressedGlyph that we have added, but check if the
         // character was supposed to be ignored. If it's supposed to be ignored,
         // overwrite the textrun entry with an invisible missing-glyph.
         gunichar ch = g_utf8_get_char(p);
         NS_ASSERTION(!IS_SURROGATE(ch), "surrogates should not appear in UTF8");
         if (ch >= 0x10000) {
             // Skip surrogate
             ++utf16Offset;
-        } else {
-            if (gfxFontGroup::IsInvisibleChar(PRUnichar(ch))) {
-                aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing());
-            }
         }
+        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(PRUnichar(ch)),
+                     "Invalid character detected");
         ++utf16Offset;
 
         // We produced this UTF8 so we don't need to worry about malformed stuff
         p = g_utf8_next_char(p);
         if (p >= end)
             break;
 
         if (utf16Offset >= textRunLength) {
@@ -1251,19 +1249,18 @@ gfxPangoFontGroup::CreateGlyphRunsXft(gf
         // g_unichar_validate(ch) may be mildly useful.
         gunichar ch = g_utf8_get_char(p);
         p = g_utf8_next_char(p);
         
         if (ch == 0) {
             // treat this null byte as a missing glyph. Pango
             // doesn't create glyphs for these, not even missing-glyphs.
             aTextRun->SetMissingGlyph(utf16Offset, 0);
-        } else if (ch < 0x10000 && IsInvisibleChar(PRUnichar(ch))) {
-            aTextRun->SetCharacterGlyph(utf16Offset, g.SetMissing());
         } else {
+            NS_ASSERTION(!IsInvalidChar(ch), "Invalid char detected");
             FT_UInt glyph = XftCharIndex(dpy, xfont, ch);
             XGlyphInfo info;
             XftGlyphExtents(dpy, xfont, &glyph, 1, &info);
             if (info.yOff > 0) {
                 NS_WARNING("vertical offsets not supported");
             }
 
             PRInt32 advance = info.xOff*appUnitsPerDevUnit;
--- a/gfx/thebes/src/gfxPlatform.cpp
+++ b/gfx/thebes/src/gfxPlatform.cpp
@@ -48,16 +48,17 @@
 #include "gfxBeOSPlatform.h"
 #elif defined(XP_OS2)
 #include "gfxOS2Platform.h"
 #endif
 
 #include "gfxContext.h"
 #include "gfxImageSurface.h"
 #include "gfxTextRunCache.h"
+#include "gfxTextRunWordCache.h"
 
 #include "nsIPref.h"
 #include "nsServiceManagerUtils.h"
 
 #ifdef MOZ_ENABLE_GLITZ
 #include <stdlib.h>
 #endif
 
@@ -103,32 +104,40 @@ gfxPlatform::Init()
 
     rv = gfxFontCache::Init();
     if (NS_FAILED(rv)) {
         NS_ERROR("Could not initialize gfxFontCache");
         Shutdown();
         return rv;
     }
 
-    rv = gfxGlobalTextRunCache::Init();
+    rv = gfxTextRunWordCache::Init();
     if (NS_FAILED(rv)) {
-        NS_ERROR("Could not initialize gfxGlobalTextRunCache");
+        NS_ERROR("Could not initialize gfxTextRunWordCache");
+        Shutdown();
+        return rv;
+    }
+
+    rv = gfxTextRunCache::Init();
+    if (NS_FAILED(rv)) {
+        NS_ERROR("Could not initialize gfxTextRunCache");
         Shutdown();
         return rv;
     }
 
     return NS_OK;
 }
 
 void
 gfxPlatform::Shutdown()
 {
     // These may be called before the corresponding subsystems have actually
     // started up. That's OK, they can handle it.
-    gfxGlobalTextRunCache::Shutdown();
+    gfxTextRunCache::Shutdown();
+    gfxTextRunWordCache::Shutdown();
     gfxFontCache::Shutdown();
 #if defined(XP_MACOSX)
     gfxQuartzFontCache::Shutdown();
 #endif
     delete gPlatform;
     gPlatform = nsnull;
 }
 
--- a/gfx/thebes/src/gfxTextRunCache.cpp
+++ b/gfx/thebes/src/gfxTextRunCache.cpp
@@ -31,351 +31,106 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "gfxTextRunCache.h"
+#include "gfxTextRunWordCache.h"
 
 #include "nsExpirationTracker.h"
 
-static inline PRUint32
-HashMix(PRUint32 aHash, PRUnichar aCh)
-{
-    return (aHash >> 28) ^ (aHash << 4) ^ aCh;
-}
-
-static PRUint32
-HashString(const PRUnichar *aText, PRUint32 aLength, PRUint32 *aFlags)
-{
-    *aFlags &= ~(gfxFontGroup::TEXT_HAS_SURROGATES | gfxFontGroup::TEXT_IS_ASCII);
-    PRUint32 i;
-    PRUint32 hashCode = 0;
-    PRUnichar allBits = 0;
-    for (i = 0; i < aLength; ++i) {
-        PRUnichar ch = aText[i];
-        hashCode = HashMix(hashCode, ch);
-        allBits |= ch;
-        if (IS_SURROGATE(ch)) {
-            *aFlags |= gfxFontGroup::TEXT_HAS_SURROGATES;
-        }
-    }
-    if (!(allBits & ~0x7F)) {
-        *aFlags |= gfxFontGroup::TEXT_IS_ASCII;
-    }
-    return hashCode;
-}
-
-static PRUint32
-HashString(const PRUint8 *aText, PRUint32 aLength, PRUint32 *aFlags)
-{
-    *aFlags &= ~(gfxFontGroup::TEXT_HAS_SURROGATES | gfxFontGroup::TEXT_IS_ASCII);
-    *aFlags |= gfxFontGroup::TEXT_IS_8BIT;
-    PRUint32 i;
-    PRUint32 hashCode = 0;
-    PRUint8 allBits = 0;
-    for (i = 0; i < aLength; ++i) {
-        PRUint8 ch = aText[i];
-        hashCode = HashMix(hashCode, ch);
-        allBits |= ch;
-    }
-    if (!(allBits & ~0x7F)) {
-        *aFlags |= gfxFontGroup::TEXT_IS_ASCII;
-    }
-    return hashCode;
-}
-
-static void *GetCacheKeyFontOrGroup(gfxTextRun *aTextRun)
-{
-    PRUint32 glyphRunCount;
-    const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&glyphRunCount);
-    gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
-    gfxFont *firstFont = fontGroup->GetFontAt(0);
-    return glyphRunCount == 1 && glyphRuns[0].mFont == firstFont
-           ? NS_STATIC_CAST(void *, firstFont)
-           : NS_STATIC_CAST(void *, fontGroup);
-}
-
-gfxTextRun *
-gfxTextRunCache::GetOrMakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                  gfxFontGroup *aFontGroup,
-                                  const gfxFontGroup::Parameters *aParams,
-                                  PRUint32 aFlags, PRBool *aCallerOwns)
-{
-    if (aCallerOwns) {
-        *aCallerOwns = PR_TRUE;
-    }
-    if (aLength == 0) {
-        aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
-    } else if (aLength == 1 && aText[0] == ' ') {
-        aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
-        static const PRUnichar space = ' ';
-        aText = &space;
-    }
-
-    PRUint32 hashCode = HashString(aText, aLength, &aFlags);
-    gfxFont *font = aFontGroup->GetFontAt(0);
-    CacheHashKey key(font, aText, aLength, aParams->mAppUnitsPerDevUnit, aFlags, hashCode);
-    CacheHashEntry *entry = nsnull;
-    if (font) {
-        entry = mCache.GetEntry(key);
-    }
-    if (!entry) {
-        key.mFontOrGroup = aFontGroup;
-        entry = mCache.GetEntry(key);
-    }
-    if (entry) {
-        gfxTextRun *textRun = entry->mTextRun;
-        if (aCallerOwns) {
-            *aCallerOwns = PR_FALSE;
-            return textRun;
-        }
-        gfxTextRun *newRun =
-            textRun->Clone(aParams, aText, aLength, aFontGroup, aFlags);
-        if (newRun) {
-            newRun->SetHashCode(hashCode);
-            entry->mTextRun = newRun;
-            NotifyRemovedFromCache(textRun);
-            return newRun;
-        }
-    }
-
-    gfxTextRun *newRun =
-        aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
-    if (newRun) {
-        newRun->SetHashCode(hashCode);
-        key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun);
-        entry = mCache.PutEntry(key);
-        if (entry) {
-            entry->mTextRun = newRun;
-        }
-        NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)),
-                     "Inconsistent hashing");
-    }
-    return newRun;
-}
-
-gfxTextRun *
-gfxTextRunCache::GetOrMakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                  gfxFontGroup *aFontGroup,
-                                  const gfxFontGroup::Parameters *aParams,
-                                  PRUint32 aFlags, PRBool *aCallerOwns)
-{
-    if (aCallerOwns) {
-        *aCallerOwns = PR_TRUE;
-    }
-    if (aLength == 0) {
-        aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
-    } else if (aLength == 1 && aText[0] == ' ') {
-        aFlags |= gfxFontGroup::TEXT_IS_PERSISTENT;
-        static const PRUint8 space = ' ';
-        aText = &space;
-    }
-
-    PRUint32 hashCode = HashString(aText, aLength, &aFlags);
-    gfxFont *font = aFontGroup->GetFontAt(0);
-    CacheHashKey key(font, aText, aLength, aParams->mAppUnitsPerDevUnit, aFlags, hashCode);
-    CacheHashEntry *entry = nsnull;
-    if (font) {
-        entry = mCache.GetEntry(key);
-    }
-    if (!entry) {
-        key.mFontOrGroup = aFontGroup;
-        entry = mCache.GetEntry(key);
-    }
-    if (entry) {
-        gfxTextRun *textRun = entry->mTextRun;
-        if (aCallerOwns) {
-            *aCallerOwns = PR_FALSE;
-            return textRun;
-        }
-
-        gfxTextRun *newRun =
-            textRun->Clone(aParams, aText, aLength,
-                           aFontGroup, aFlags);
-        if (newRun) {
-            newRun->SetHashCode(hashCode);
-            entry->mTextRun = newRun;
-            NotifyRemovedFromCache(textRun);
-            return newRun;
-        }
-    }
-
-    gfxTextRun *newRun =
-        aFontGroup->MakeTextRun(aText, aLength, aParams, aFlags);
-    if (newRun) {
-        newRun->SetHashCode(hashCode);
-        key.mFontOrGroup = GetCacheKeyFontOrGroup(newRun);
-        entry = mCache.PutEntry(key);
-        if (entry) {
-            entry->mTextRun = newRun;
-        }
-        NS_ASSERTION(!entry || entry == mCache.GetEntry(GetKeyForTextRun(newRun)),
-                     "Inconsistent hashing");
-    }
-    return newRun;
-}
-
-gfxTextRunCache::CacheHashKey
-gfxTextRunCache::GetKeyForTextRun(gfxTextRun *aTextRun)
-{
-    const void *text;
-    PRUint32 length = aTextRun->GetLength();
-    if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
-        text = aTextRun->GetText8Bit();
-    } else {
-        text = aTextRun->GetTextUnicode();
-    }
-    void *fontOrGroup = GetCacheKeyFontOrGroup(aTextRun);
-    return CacheHashKey(fontOrGroup, text, length, aTextRun->GetAppUnitsPerDevUnit(),
-                        aTextRun->GetFlags(), aTextRun->GetHashCode());
-}
-
-void
-gfxTextRunCache::RemoveTextRun(gfxTextRun *aTextRun)
-{
-    CacheHashKey key = GetKeyForTextRun(aTextRun);
-#ifdef DEBUG
-    CacheHashEntry *entry = mCache.GetEntry(key);
-    NS_ASSERTION(entry && entry->mTextRun == aTextRun,
-                 "Failed to find textrun in cache");
-#endif
-    mCache.RemoveEntry(key);
-}
-
-static PRBool
-CompareDifferentWidthStrings(const PRUint8 *aStr1, const PRUnichar *aStr2,
-                             PRUint32 aLength)
-{
-    PRUint32 i;
-    for (i = 0; i < aLength; ++i) {
-        if (aStr1[i] != aStr2[i])
-            return PR_FALSE;
-    }
-    return PR_TRUE;
-}
-
-PRBool
-gfxTextRunCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
-{
-    gfxTextRun *textRun = mTextRun;
-    if (!textRun)
-        return PR_FALSE;
-    PRUint32 length = textRun->GetLength();
-    if (aKey->mFontOrGroup != GetCacheKeyFontOrGroup(textRun) ||
-        aKey->mLength != length ||
-        aKey->mAppUnitsPerDevUnit != textRun->GetAppUnitsPerDevUnit() ||
-        ((aKey->mFlags ^ textRun->GetFlags()) & FLAG_MASK))
-        return PR_FALSE;
-
-    if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
-        if (aKey->mFlags & gfxFontGroup::TEXT_IS_8BIT)
-            return memcmp(textRun->GetText8Bit(), aKey->mString, length) == 0;
-        return CompareDifferentWidthStrings(textRun->GetText8Bit(),
-                                            NS_STATIC_CAST(const PRUnichar *, aKey->mString), length);
-    } else {
-        if (!(aKey->mFlags & gfxFontGroup::TEXT_IS_8BIT))
-            return memcmp(textRun->GetTextUnicode(), aKey->mString, length*sizeof(PRUnichar)) == 0;
-        return CompareDifferentWidthStrings(NS_STATIC_CAST(const PRUint8 *, aKey->mString),
-                                            textRun->GetTextUnicode(), length);
-    }
-}
-
-PLDHashNumber
-gfxTextRunCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
-{
-    return aKey->mStringHash + (long)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit +
-        (aKey->mFlags & FLAG_MASK);
-}
-
 /*
  * Cache textruns and expire them after 3*10 seconds of no use
  */
-class TextRunCache : public nsExpirationTracker<gfxTextRun,3> {
+class TextRunExpiringCache : public nsExpirationTracker<gfxTextRun,3> {
 public:
-    enum { TIMEOUT_SECONDS = 10 };
-    TextRunCache()
-        : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
-    ~TextRunCache() {
+    TextRunExpiringCache()
+        : nsExpirationTracker<gfxTextRun,3>(10*1000) {}
+    ~TextRunExpiringCache() {
         AgeAllGenerations();
     }
 
     // This gets called when the timeout has expired on a gfxTextRun
     virtual void NotifyExpired(gfxTextRun *aTextRun) {
         RemoveObject(aTextRun);
-        mCache.RemoveTextRun(aTextRun);
+        gfxTextRunWordCache::RemoveTextRun(aTextRun);
         delete aTextRun;
     }
-
-    gfxTextRunCache mCache;
 };
 
-static TextRunCache *gTextRunCache = nsnull;
+static TextRunExpiringCache *gTextRunCache = nsnull;
 
-static nsresult
-UpdateOwnership(gfxTextRun *aTextRun, PRBool aOwned)
+gfxTextRun *
+gfxTextRunCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                             gfxFontGroup *aFontGroup,
+                             const gfxTextRunFactory::Parameters* aParams,
+                             PRUint32 aFlags)
 {
-    if (!aTextRun)
+    if (!gTextRunCache)
         return nsnull;
-    if (aOwned)
-        return gTextRunCache->AddObject(aTextRun);
-    if (!aTextRun->GetExpirationState()->IsTracked())
-        return NS_OK;
-    return gTextRunCache->MarkUsed(aTextRun);
+    return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
+        aParams, aFlags);
 }
 
 gfxTextRun *
-gfxGlobalTextRunCache::GetTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                  gfxFontGroup *aFontGroup,
-                                  gfxContext *aRefContext,
-                                  PRUint32 aAppUnitsPerDevUnit,
-                                  PRUint32 aFlags)
+gfxTextRunCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                             gfxFontGroup *aFontGroup,
+                             gfxContext *aRefContext,
+                             PRUint32 aAppUnitsPerDevUnit,
+                             PRUint32 aFlags)
 {
     if (!gTextRunCache)
         return nsnull;
-    PRBool owned;
     gfxTextRunFactory::Parameters params = {
         aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
     };
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, &params, aFlags, &owned);
-    nsresult rv = UpdateOwnership(textRun, owned);
-    if (NS_FAILED(rv))
-        return nsnull;
-    return textRun.forget();
+    return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
+        &params, aFlags);
 }
 
 gfxTextRun *
-gfxGlobalTextRunCache::GetTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                  gfxFontGroup *aFontGroup,
-                                  gfxContext *aRefContext,
-                                  PRUint32 aAppUnitsPerDevUnit,
-                                  PRUint32 aFlags)
+gfxTextRunCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+                             gfxFontGroup *aFontGroup,
+                             gfxContext *aRefContext,
+                             PRUint32 aAppUnitsPerDevUnit,
+                             PRUint32 aFlags)
 {
     if (!gTextRunCache)
         return nsnull;
-    PRBool owned;
     gfxTextRunFactory::Parameters params = {
         aRefContext, nsnull, nsnull, nsnull, 0, aAppUnitsPerDevUnit
-    };     
-    nsAutoPtr<gfxTextRun> textRun;
-    textRun = gTextRunCache->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, &params, aFlags, &owned);
-    nsresult rv = UpdateOwnership(textRun, owned);
-    if (NS_FAILED(rv))
-        return nsnull;
-    return textRun.forget();
+    };
+    return gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
+        &params, aFlags);
+}
+
+void
+gfxTextRunCache::ReleaseTextRun(gfxTextRun *aTextRun)
+{
+    if (!aTextRun)
+        return;
+    if (!(aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) {
+        delete aTextRun;
+        return;
+    }
+    nsresult rv = gTextRunCache->AddObject(aTextRun);
+    if (NS_FAILED(rv)) {
+        delete aTextRun;
+        return;
+    }
 }
 
 nsresult
-gfxGlobalTextRunCache::Init()
+gfxTextRunCache::Init()
 {
-    gTextRunCache = new TextRunCache();
+    gTextRunCache = new TextRunExpiringCache();
     return gTextRunCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
 void
-gfxGlobalTextRunCache::Shutdown()
+gfxTextRunCache::Shutdown()
 {
     delete gTextRunCache;
     gTextRunCache = nsnull;
 }
--- a/gfx/thebes/src/gfxTextRunWordCache.cpp
+++ b/gfx/thebes/src/gfxTextRunWordCache.cpp
@@ -32,16 +32,124 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "gfxTextRunWordCache.h"
 
+
+/**
+ * Cache individual "words" (strings delimited by white-space or white-space-like
+ * characters that don't involve kerning or ligatures) in textruns.
+  */
+class TextRunWordCache {
+public:
+    TextRunWordCache() {
+        mCache.Init(100);
+    }
+    ~TextRunWordCache() {
+        NS_ASSERTION(mCache.Count() == 0, "Textrun cache not empty!");
+    }
+
+    /**
+     * Create a textrun using cached words.
+     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
+     * treated as invisible missing.
+     * @param aFlags the flags TEXT_IS_ASCII and TEXT_HAS_SURROGATES must be set
+     * by the caller, if applicable
+     * @param aIsInCache if true is returned, then RemoveTextRun must be called
+     * before the textrun changes or dies.
+     */
+    gfxTextRun *MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                            gfxFontGroup *aFontGroup,
+                            const gfxFontGroup::Parameters *aParams,
+                            PRUint32 aFlags);
+    /**
+     * Create a textrun using cached words.
+     * Invalid characters (see gfxFontGroup::IsInvalidChar) will be automatically
+     * treated as invisible missing.
+     * @param aFlags the flag TEXT_IS_ASCII must be set by the caller,
+     * if applicable
+     * @param aIsInCache if true is returned, then RemoveTextRun must be called
+     * before the textrun changes or dies.
+     */
+    gfxTextRun *MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+                            gfxFontGroup *aFontGroup,
+                            const gfxFontGroup::Parameters *aParams,
+                            PRUint32 aFlags);
+
+    /**
+     * Remove a textrun from the cache. This must be called before aTextRun
+     * is deleted! The text in the textrun must still be valid.
+     */
+    void RemoveTextRun(gfxTextRun *aTextRun);
+
+protected:
+    struct CacheHashKey {
+        void        *mFontOrGroup;
+        const void  *mString;
+        PRUint32     mLength;
+        PRUint32     mAppUnitsPerDevUnit;
+        PRUint32     mStringHash;
+        PRPackedBool mIsDoubleByteText;
+    };
+
+    class CacheHashEntry : public PLDHashEntryHdr {
+    public:
+        typedef const CacheHashKey &KeyType;
+        typedef const CacheHashKey *KeyTypePointer;
+
+        // When constructing a new entry in the hashtable, the caller of Put()
+        // will fill us in.
+        CacheHashEntry(KeyTypePointer aKey) : mTextRun(nsnull), mWordOffset(0),
+            mHashedByFont(PR_FALSE) { }
+        CacheHashEntry(const CacheHashEntry& toCopy) { NS_ERROR("Should not be called"); }
+        ~CacheHashEntry() { }
+
+        PRBool KeyEquals(const KeyTypePointer aKey) const;
+        static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+        static PLDHashNumber HashKey(const KeyTypePointer aKey);
+        enum { ALLOW_MEMMOVE = PR_TRUE };
+
+        gfxTextRun *mTextRun;
+        // The offset of the start of the word in the textrun. The length of
+        // the word is not stored here because we can figure it out by
+        // looking at the textrun's text.
+        PRUint32    mWordOffset:31;
+        // This is set to true when the cache entry was hashed by the first
+        // font in mTextRun's fontgroup; it's false when the cache entry
+        // was hashed by the fontgroup itself.
+        PRUint32    mHashedByFont:1;
+    };
+    
+    // Used to track words that should be copied from one textrun to
+    // another during the textrun construction process
+    struct DeferredWord {
+        gfxTextRun *mSourceTextRun;
+        PRUint32    mSourceOffset;
+        PRUint32    mDestOffset;
+        PRUint32    mLength;
+        PRUint32    mHash;
+    };
+    
+    PRBool LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
+                      PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
+                      nsTArray<DeferredWord>* aDeferredWords);
+    void FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
+                       gfxContext *aContext,
+                       const nsTArray<DeferredWord>& aDeferredWords,
+                       PRBool aSuccessful);
+    void RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
+                    PRUint32 aEnd, PRUint32 aHash);    
+
+    nsTHashtable<CacheHashEntry> mCache;
+};
+
 static PRLogModuleInfo *gWordCacheLog = PR_NewLogModule("wordCache");
 
 static inline PRUint32
 HashMix(PRUint32 aHash, PRUnichar aCh)
 {
     return (aHash >> 28) ^ (aHash << 4) ^ aCh;
 }
 
@@ -74,17 +182,17 @@ static PRBool
 IsBoundarySpace(PRUnichar aChar)
 {
     return aChar == ' ' || aChar == UNICODE_NBSP;
 }
 
 static PRBool
 IsWordBoundary(PRUnichar aChar)
 {
-    return IsBoundarySpace(aChar) || gfxFontGroup::IsInvisibleChar(aChar);
+    return IsBoundarySpace(aChar) || gfxFontGroup::IsInvalidChar(aChar);
 }
 
 /**
  * Looks up a word in the cache. If the word is found in the cache
  * (which could be from an existing textrun or an earlier word in the same
  * textrun), we copy the glyphs from the word into the textrun, unless
  * aDeferredWords is non-null (meaning that all words from now on must be
  * copied later instead of now), in which case we add the word to be copied
@@ -99,19 +207,19 @@ IsWordBoundary(PRUnichar aChar)
  * 2) we need to record words that appear in the string in some kind
  * of hash table so we can detect and use them if they appear later in the
  * (in general the string might be huge and contain many repeated words).
  * We might as well use the master hash table for this.
  * 
  * @return true if the word was found in the cache, false otherwise.
  */
 PRBool
-gfxTextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
-                                PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
-                                nsTArray<DeferredWord>* aDeferredWords)
+TextRunWordCache::LookupWord(gfxTextRun *aTextRun, gfxFont *aFirstFont,
+                             PRUint32 aStart, PRUint32 aEnd, PRUint32 aHash,
+                             nsTArray<DeferredWord>* aDeferredWords)
 {
     if (aEnd <= aStart)
         return PR_TRUE;
 
     PRUint32 length = aEnd - aStart;
     CacheHashKey key =
         { aFirstFont, aTextRun->GetTextAt(aStart),
           length, aTextRun->GetAppUnitsPerDevUnit(), aHash,
@@ -167,21 +275,23 @@ gfxTextRunWordCache::LookupWord(gfxTextR
  * with that font in the key, and add a new entry keyed with the fontgroup
  * instead.
  * 
  * @param aSuccessful if false, then we don't do any word copies and we don't
  * add anything to the cache, but we still remove all the optimistic cache
  * entries.
  */
 void
-gfxTextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
-                                   gfxContext *aContext,
-                                   const nsTArray<DeferredWord>& aDeferredWords,
-                                   PRBool aSuccessful)
+TextRunWordCache::FinishTextRun(gfxTextRun *aTextRun, gfxTextRun *aNewRun,
+                                gfxContext *aContext,
+                                const nsTArray<DeferredWord>& aDeferredWords,
+                                PRBool aSuccessful)
 {
+    aTextRun->SetFlagBits(gfxTextRunWordCache::TEXT_IN_CACHE);
+
     PRUint32 i;
     gfxFontGroup *fontGroup = aTextRun->GetFontGroup();
     gfxFont *font = fontGroup->GetFontAt(0);
     // copy deferred words from various sources into destination textrun
     for (i = 0; i < aDeferredWords.Length(); ++i) {
         const DeferredWord *word = &aDeferredWords[i];
         gfxTextRun *source = word->mSourceTextRun;
         if (!source) {
@@ -236,20 +346,20 @@ gfxTextRunWordCache::FinishTextRun(gfxTe
                     aTextRun->SetSpaceGlyph(font, aContext, charIndex);
                 }
             }
         }
     }
 }
 
 gfxTextRun *
-gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
-                                 gfxFontGroup *aFontGroup,
-                                 const gfxFontGroup::Parameters *aParams,
-                                 PRUint32 aFlags, PRBool *aIsInCache)
+TextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                              gfxFontGroup *aFontGroup,
+                              const gfxFontGroup::Parameters *aParams,
+                              PRUint32 aFlags)
 {
     nsAutoPtr<gfxTextRun> textRun;
     textRun = new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
     if (!textRun || !textRun->GetCharacterGlyphs())
         return nsnull;   
 
     gfxFont *font = aFontGroup->GetFontAt(0);
     nsresult rv = textRun->AddGlyphRun(font, 0);
@@ -293,37 +403,35 @@ gfxTextRunWordCache::MakeTextRun(const P
             hash = HashMix(hash, ch);
         }
     }
 
     if (deferredWords.Length() == 0) {
         // We got everything from the cache, so we're done. No point in calling
         // FinishTextRun.
         // This textrun is not referenced by the cache.
-        *aIsInCache = PR_FALSE;
         return textRun.forget();
     }
-    *aIsInCache = PR_TRUE;
 
     // create textrun for unknown words
     gfxTextRunFactory::Parameters params =
         { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
     nsAutoPtr<gfxTextRun> newRun;
     newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(),
                                      &params, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
 
     FinishTextRun(textRun, newRun, aParams->mContext, deferredWords, newRun != nsnull);
     return textRun.forget();
 }
 
 gfxTextRun *
-gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
-                                 gfxFontGroup *aFontGroup,
-                                 const gfxFontGroup::Parameters *aParams,
-                                 PRUint32 aFlags, PRBool *aIsInCache)
+TextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+                              gfxFontGroup *aFontGroup,
+                              const gfxFontGroup::Parameters *aParams,
+                              PRUint32 aFlags)
 {
     aFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
     nsAutoPtr<gfxTextRun> textRun;
     textRun = new gfxTextRun(aParams, aText, aLength, aFontGroup, aFlags);
     if (!textRun || !textRun->GetCharacterGlyphs())
         return nsnull;
 
     gfxFont *font = aFontGroup->GetFontAt(0);
@@ -368,35 +476,33 @@ gfxTextRunWordCache::MakeTextRun(const P
             hash = HashMix(hash, ch);
         }
     }
 
     if (deferredWords.Length() == 0) {
         // We got everything from the cache, so we're done. No point in calling
         // FinishTextRun.
         // This textrun is not referenced by the cache.
-        *aIsInCache = PR_FALSE;
         return textRun.forget();
     }
-    *aIsInCache = PR_TRUE;
 
     // create textrun for unknown words
     gfxTextRunFactory::Parameters params =
         { aParams->mContext, nsnull, nsnull, nsnull, 0, aParams->mAppUnitsPerDevUnit };
     nsAutoPtr<gfxTextRun> newRun;
     newRun = aFontGroup->MakeTextRun(tempString.Elements(), tempString.Length(),
                                      &params, aFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT);
 
     FinishTextRun(textRun, newRun, aParams->mContext, deferredWords, newRun != nsnull);
     return textRun.forget();
 }
 
 void
-gfxTextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
-                                PRUint32 aEnd, PRUint32 aHash)
+TextRunWordCache::RemoveWord(gfxTextRun *aTextRun, PRUint32 aStart,
+                             PRUint32 aEnd, PRUint32 aHash)
 {
     if (aEnd <= aStart)
         return;
 
     PRUint32 length = aEnd - aStart;
     CacheHashKey key =
         { GetWordFontOrGroup(aTextRun, aStart, length), aTextRun->GetTextAt(aStart),
           length, aTextRun->GetAppUnitsPerDevUnit(), aHash,
@@ -409,17 +515,17 @@ gfxTextRunWordCache::RemoveWord(gfxTextR
         PR_LOG(gWordCacheLog, PR_LOG_DEBUG, ("%p(%d-%d,%d): removed using %s",
             aTextRun, aStart, aEnd - aStart, aHash,
             key.mFontOrGroup == aTextRun->GetFontGroup() ? "fontgroup" : "font"));
     }
 }
 
 // Remove a textrun from the cache by looking up each word and removing it
 void
-gfxTextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
+TextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
 {
     PRUint32 i;
     PRUint32 wordStart = 0;
     PRUint32 hash = 0;
     for (i = 0; i < aTextRun->GetLength(); ++i) {
         PRUnichar ch = aTextRun->GetChar(i);
         if (IsWordBoundary(ch)) {
             RemoveWord(aTextRun, wordStart, i, hash);
@@ -459,17 +565,17 @@ static void *
 GetFontOrGroup(gfxFontGroup *aFontGroup, PRBool aUseFont)
 {
     return aUseFont
         ? NS_STATIC_CAST(void *, aFontGroup->GetFontAt(0))
         : NS_STATIC_CAST(void *, aFontGroup);
 }
 
 PRBool
-gfxTextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
+TextRunWordCache::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
 {
     if (!mTextRun)
         return PR_FALSE;
 
     PRUint32 length = aKey->mLength;
     gfxFontGroup *fontGroup = mTextRun->GetFontGroup();
     if (!IsWordEnd(mTextRun, mWordOffset + length) ||
         GetFontOrGroup(fontGroup, mHashedByFont) != aKey->mFontOrGroup ||
@@ -487,13 +593,59 @@ gfxTextRunWordCache::CacheHashEntry::Key
         if (aKey->mIsDoubleByteText)
             return memcmp(text, aKey->mString, length*sizeof(PRUnichar)) == 0;
         return CompareDifferentWidthStrings(NS_STATIC_CAST(const PRUint8 *, aKey->mString),
                                             text, length);
     }
 }
 
 PLDHashNumber
-gfxTextRunWordCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
+TextRunWordCache::CacheHashEntry::HashKey(const KeyTypePointer aKey)
 {
     return aKey->mStringHash + (long)aKey->mFontOrGroup + aKey->mAppUnitsPerDevUnit +
         aKey->mIsDoubleByteText;
 }
+
+static TextRunWordCache *gTextRunWordCache = nsnull;
+
+nsresult
+gfxTextRunWordCache::Init()
+{
+    gTextRunWordCache = new TextRunWordCache();
+    return gTextRunWordCache ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
+void
+gfxTextRunWordCache::Shutdown()
+{
+    delete gTextRunWordCache;
+    gTextRunWordCache = nsnull;
+}
+
+gfxTextRun *
+gfxTextRunWordCache::MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
+                                 gfxFontGroup *aFontGroup,
+                                 const gfxFontGroup::Parameters *aParams,
+                                 PRUint32 aFlags)
+{
+    if (!gTextRunWordCache)
+        return nsnull;
+    return gTextRunWordCache->MakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
+}
+
+gfxTextRun *
+gfxTextRunWordCache::MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
+                                 gfxFontGroup *aFontGroup,
+                                 const gfxFontGroup::Parameters *aParams,
+                                 PRUint32 aFlags)
+{
+    if (!gTextRunWordCache)
+        return nsnull;
+    return gTextRunWordCache->MakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
+}
+
+void
+gfxTextRunWordCache::RemoveTextRun(gfxTextRun *aTextRun)
+{
+    if (!gTextRunWordCache)
+        return;
+    gTextRunWordCache->RemoveTextRun(aTextRun);
+}
--- a/gfx/thebes/src/gfxWindowsFonts.cpp
+++ b/gfx/thebes/src/gfxWindowsFonts.cpp
@@ -640,19 +640,19 @@ SetupTextRunFromGlyphs(gfxTextRun *aRun,
     PRUint32 i;
     PRInt32 lastWidth = 0;
     PRUint32 appUnitsPerDevPixel = aRun->GetAppUnitsPerDevUnit();
     for (i = 0; i < length; ++i) {
         PRInt32 advancePixels = partialWidthArray[i] - lastWidth;
         lastWidth = partialWidthArray[i];
         PRInt32 advanceAppUnits = advancePixels*appUnitsPerDevPixel;
         WCHAR glyph = aGlyphs[i];
-        if (gfxFontGroup::IsInvisibleChar(aRun->GetChar(i))) {
-            aRun->SetCharacterGlyph(i, g.SetMissing());
-        } else if (advanceAppUnits >= 0 &&
+        NS_ASSERTION(!gfxFontGroup::IsInvalidChar(aRun->GetChar(i)),
+                     "Invalid character detected!");
+        if (advanceAppUnits >= 0 &&
             gfxTextRun::CompressedGlyph::IsSimpleAdvance(advanceAppUnits) &&
             gfxTextRun::CompressedGlyph::IsSimpleGlyphID(glyph)) {
             aRun->SetCharacterGlyph(i, g.SetSimpleGlyph(advanceAppUnits, glyph));
         } else {
             gfxTextRun::DetailedGlyph details;
             details.mIsLastGlyph = PR_TRUE;
             details.mGlyphID = glyph;
             details.mAdvance = advanceAppUnits;
--- a/gfx/thebes/test/gfxFontSelectionTest.cpp
+++ b/gfx/thebes/test/gfxFontSelectionTest.cpp
@@ -294,26 +294,25 @@ RunTest (TestEntry *test, gfxContext *ct
     gfxTextRunFactory::Parameters params = {
       ctx, nsnull, nsnull, nsnull, 0, 60
     };
     PRUint32 flags = gfxTextRunFactory::TEXT_IS_PERSISTENT;
     if (test->isRTL) {
         flags |= gfxTextRunFactory::TEXT_IS_RTL;
     }
     PRUint32 length;
-    PRBool isInCache;
     if (test->stringType == S_ASCII) {
         flags |= gfxTextRunFactory::TEXT_IS_ASCII | gfxTextRunFactory::TEXT_IS_8BIT;
         length = strlen(test->string);
-        textRun = gTextRunCache->MakeTextRun(NS_REINTERPRET_CAST(PRUint8*, test->string), length, fontGroup, &params, flags, &isInCache);
+        textRun = gfxTextRunWordCache::MakeTextRun(NS_REINTERPRET_CAST(PRUint8*, test->string), length, fontGroup, &params, flags);
     } else {
         flags |= gfxTextRunFactory::TEXT_HAS_SURROGATES; // just in case
         NS_ConvertUTF8toUTF16 str(nsDependentCString(test->string));
         length = str.Length();
-        textRun = gTextRunCache->MakeTextRun(str.get(), length, fontGroup, &params, flags, &isInCache);
+        textRun = gfxTextRunWordCache::MakeTextRun(str.get(), length, fontGroup, &params, flags);
     }
 
     gfxFontTestStore::NewStore();
     textRun->Draw(ctx, gfxPoint(0,0), 0, length, nsnull, nsnull, nsnull);
     gfxFontTestStore *s = gfxFontTestStore::CurrentStore();
 
     gTextRunCache->RemoveTextRun(textRun);
 
--- a/gfx/thebes/test/gfxWordCacheTest.cpp
+++ b/gfx/thebes/test/gfxWordCacheTest.cpp
@@ -77,44 +77,39 @@ public:
  ~FrameTextRunCache() {
    AgeAllGenerations();
  }
 
  void RemoveFromCache(gfxTextRun* aTextRun) {
    if (aTextRun->GetExpirationState()->IsTracked()) {
      RemoveObject(aTextRun);
    }
-   mCache.RemoveTextRun(aTextRun);
+   gfxTextRunWordCache::RemoveTextRun(aTextRun);
  }
 
  // This gets called when the timeout has expired on a gfxTextRun
  virtual void NotifyExpired(gfxTextRun* aTextRun) {
    RemoveFromCache(aTextRun);
    delete aTextRun;
  }
-
- gfxTextRunWordCache 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);
    } else if (aLength == 1 && aText[0] == ' ') {
        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
    } else {
-       PRBool isInCache;
-       textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
-           aParams, aFlags, &isInCache);
-       if (!isInCache && textRun) {
-       }
+       textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
+           aParams, aFlags);
    }
    if (!textRun)
        return nsnull;
    nsresult rv = gTextRuns->AddObject(textRun);
    if (NS_FAILED(rv)) {
        gTextRuns->RemoveFromCache(textRun);
        return nsnull;
    }
@@ -176,23 +171,23 @@ main (int argc, char **argv) {
 
        PRUint32 flags = gfxTextRunFactory::TEXT_IS_PERSISTENT;
 
        // First load an Arabic word into the cache
        const char cString[] = "\xd8\xaa\xd9\x85";
        nsDependentCString cStr(cString);
        NS_ConvertUTF8toUTF16 str(cStr);
        gfxTextRun *tr = MakeTextRun(str.get(), str.Length(), fontGroup, &params, flags);
+       tr->GetAdvanceWidth(0, str.Length(), nsnull);
 
        // Now try to trigger an assertion with a word cache bug. The first
        // word is in the cache so it gets added to the new textrun directly.
        // The second word is not in the cache 
        const char cString2[] = "\xd8\xaa\xd9\x85\n\xd8\xaa\xd8\x85 ";
        nsDependentCString cStr2(cString2);
        NS_ConvertUTF8toUTF16 str2(cStr2);
        gfxTextRun *tr2 = MakeTextRun(str2.get(), str2.Length(), fontGroup, &params, flags);
-
        tr2->GetAdvanceWidth(0, str2.Length(), nsnull);
    }
 
    fflush (stderr);
    fflush (stdout);
 }
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -133,26 +133,20 @@
 // This bit is set on frames that are logically adjacent to the end of the
 // line (i.e. no following on line with actual displayed in-flow content).
 #define TEXT_END_OF_LINE     0x00400000
 // This bit is set on frames that end with a hyphenated break.
 #define TEXT_HYPHEN_BREAK    0x00800000
 // This bit is set on frames that trimmed trailing whitespace characters when
 // calculating their width during reflow.
 #define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000
-// Set when the frame's text may have a different style from its in-flow
-// brethren (e.g. the frame is in first-letter or first-line), so text runs
-// may need to be reconstructed when the frame's content offset/length changes.
-// We set this on the frame that has in first-letter or first-line, but not
-// in-flow siblings outside first-letter or first-line.
-#define TEXT_RUN_LAYOUT_DEPENDENT  0x02000000
 
 #define TEXT_REFLOW_FLAGS    \
   (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
-   TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_RUN_LAYOUT_DEPENDENT)
+   TEXT_TRIMMED_TRAILING_WHITESPACE)
 
 // Cache bits for IsEmpty().
 // Set this bit if the textframe is known to be only collapsible whitespace.
 #define TEXT_IS_ONLY_WHITESPACE    0x08000000
 // Set this bit if the textframe is known to be not only collapsible whitespace.
 #define TEXT_ISNOT_ONLY_WHITESPACE 0x10000000
 
 #define TEXT_WHITESPACE_FLAGS      0x18000000
@@ -499,16 +493,25 @@ public:
 #ifdef DEBUG
   void ToCString(nsString& aBuf, PRInt32* aTotalContentLength) const;
 #endif
 
   PRInt32 GetContentOffset() const { return mContentOffset; }
   PRInt32 GetContentLength() const { return mContentLength; }
   PRInt32 GetContentEnd() const { return mContentOffset + mContentLength; }
 
+  /**
+   * Fix up the content offsets for all next-in-flows so that they do not
+   * overlap this frame's content.
+   * @param aClearTextRuns if true, then any next-in-flows whose content
+   * offsets changed have their textruns cleared (as would be necessary
+   * if this frame could have a different style to those frames)
+   */
+  void AdjustNextInFlowContentOffsetsForGrowth(PRBool aClearTextRuns);
+
   // Compute the length of the content mapped by this frame
   // and all its in-flow siblings. Basically this means starting at mContentOffset
   // and going to the end of the text node or the next bidi continuation
   // boundary.
   PRInt32 GetInFlowContentLength();
 
   // Clears out mTextRun from this frame and all other frames that hold a reference
   // to it, then deletes the textrun.
@@ -554,18 +557,16 @@ protected:
 
   SelectionDetails* GetSelectionDetails();
   
   void AdjustSelectionPointsForBidi(SelectionDetails *sdptr,
                                     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);
@@ -627,48 +628,42 @@ public:
   ~FrameTextRunCache() {
     AgeAllGenerations();
   }
 
   void RemoveFromCache(gfxTextRun* aTextRun) {
     if (aTextRun->GetExpirationState()->IsTracked()) {
       RemoveObject(aTextRun);
     }
-    if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED)) {
-      mCache.RemoveTextRun(aTextRun);
+    if (aTextRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE) {
+      gfxTextRunWordCache::RemoveTextRun(aTextRun);
     }
   }
 
   // This gets called when the timeout has expired on a gfxTextRun
   virtual void NotifyExpired(gfxTextRun* aTextRun) {
     UnhookTextRunFromFrames(aTextRun);
     RemoveFromCache(aTextRun);
     delete aTextRun;
   }
-
-  gfxTextRunWordCache 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);
+        textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags);
     } else if (aLength == 1 && aText[0] == ' ') {
-        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
+        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
     } else {
-        PRBool isInCache;
-        textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
-            aParams, aFlags, &isInCache);
-        if (!isInCache && textRun) {
-            textRun->SetFlagBits(nsTextFrameUtils::TEXT_IS_UNCACHED);
-        }
+        textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
+            aParams, aFlags);
     }
     if (!textRun)
         return nsnull;
     nsresult rv = gTextRuns->AddObject(textRun);
     if (NS_FAILED(rv)) {
         gTextRuns->RemoveFromCache(textRun);
         return nsnull;
     }
@@ -677,26 +672,22 @@ MakeTextRun(const PRUnichar *aText, PRUi
 
 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);
+        textRun = aFontGroup->MakeEmptyTextRun(aParams, aFlags);
     } else if (aLength == 1 && aText[0] == ' ') {
-        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
+        textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags);
     } else {
-        PRBool isInCache;
-        textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
-            aParams, aFlags, &isInCache);
-        if (!isInCache && textRun) {
-            textRun->SetFlagBits(nsTextFrameUtils::TEXT_IS_UNCACHED);
-        }
+        textRun = gfxTextRunWordCache::MakeTextRun(aText, aLength, aFontGroup,
+            aParams, aFlags);
     }
     if (!textRun)
         return nsnull;
     nsresult rv = gTextRuns->AddObject(textRun);
     if (NS_FAILED(rv)) {
         gTextRuns->RemoveFromCache(textRun);
         return nsnull;
     }
@@ -1345,23 +1336,19 @@ void BuildTextRunsScanner::ScanFrame(nsI
 
       // Don't do this optimization if mLastFrame has a terminal newline...
       // it's quite likely preformatted and we might want to end the textrun here.
       // This is almost always true:
       if (mLastFrame->GetStyleContext() == aFrame->GetStyleContext() &&
           !HasTerminalNewline(mLastFrame)) {
         nsTextFrame* frame = NS_STATIC_CAST(nsTextFrame*, aFrame);
         mappedFlow->mEndFrame = NS_STATIC_CAST(nsTextFrame*, frame->GetNextInFlow());
-        // Frames in the same flow can overlap at least temporarily
-        // (e.g. when first-line builds its textrun, we need to have it suck
-        // up all the in-flow content because we don't know how long the line
-        // is going to be).
-        mappedFlow->mContentEndOffset =
-          PR_MAX(mappedFlow->mContentEndOffset,
-                 frame->GetContentOffset() + frame->GetContentLength());
+        NS_ASSERTION(mappedFlow->mContentEndOffset <= frame->GetContentOffset(),
+                     "frame offsets overlap!");
+        mappedFlow->mContentEndOffset = frame->GetContentEnd();
         AccumulateRunInfo(frame);
         return;
       }
     }
   }
 
   // Now see if we can add a new set of frames to the current textrun
   if (aFrame->GetType() == nsGkAtoms::textFrame) {
@@ -1374,18 +1361,18 @@ void BuildTextRunsScanner::ScanFrame(nsI
     MappedFlow* mappedFlow = mMappedFlows.AppendElement();
     if (!mappedFlow)
       return;
 
     mappedFlow->mStartFrame = frame;
     mappedFlow->mEndFrame = NS_STATIC_CAST(nsTextFrame*, frame->GetNextInFlow());
     mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
     mappedFlow->mContentOffset = frame->GetContentOffset();
-    mappedFlow->mContentEndOffset =
-      frame->GetContentOffset() + frame->GetContentLength();
+    mappedFlow->mContentEndOffset = frame->GetContentEnd();
+    // This is temporary: it's overwritten in BuildTextRunForFrames
     mappedFlow->mTransformedTextOffset = 0;
     mLastFrame = frame;
 
     AccumulateRunInfo(frame);
     if (mMappedFlows.Length() == 1) {
       mCurrentFramesAllSameTextRun = frame->GetTextRun();
       mCurrentRunTrimLeadingWhitespace = mTrimNextRunLeadingWhitespace;
     } else {
@@ -1471,25 +1458,25 @@ GetHyphenTextRun(gfxTextRun* aTextRun, n
   }
   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);
+    gfxTextRunCache::MakeTextRun(&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);
+  return gfxTextRunCache::MakeTextRun(&dash, 1, fontGroup, ctx,
+                                      aTextRun->GetAppUnitsPerDevUnit(),
+                                      flags);
 }
 
 static gfxFont::Metrics
 GetFontMetrics(gfxFontGroup* aFontGroup)
 {
   if (!aFontGroup)
     return gfxFont::Metrics();
   gfxFont* font = aFontGroup->GetFontAt(0);
@@ -1569,22 +1556,18 @@ BuildTextRunsScanner::BuildTextRunForFra
     // Figure out what content is included in this flow.
     nsIContent* content = f->GetContent();
     const nsTextFragment* frag = content->GetText();
     PRInt32 contentStart = mappedFlow->mContentOffset;
     PRInt32 contentEnd = mappedFlow->mContentEndOffset;
     PRInt32 contentLength = contentEnd - contentStart;
 
     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;
+      NS_ASSERTION(endOfLastContent == contentStart,
+                   "Gap in textframes mapping content, or overlap?!"); 
       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;
@@ -1706,17 +1689,16 @@ BuildTextRunsScanner::BuildTextRunForFra
       PRUint32 end = i == mMappedFlows.Length() - 1 ? transformedLength :
           mMappedFlows[i + 1].mTransformedTextOffset;
       nsStyleContext* sc = mappedFlow->mStartFrame->GetStyleContext();
       PRUint32 j;
       for (j = mappedFlow->mTransformedTextOffset; j < end; ++j) {
         styles.AppendElement(sc);
       }
     }
-    textFlags |= nsTextFrameUtils::TEXT_IS_UNCACHED;
   }
 
   if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
     textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
   }
   if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
     textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
   }
@@ -3278,43 +3260,45 @@ NS_IMETHODIMP
 nsContinuingTextFrame::Init(nsIContent* aContent,
                             nsIFrame*   aParent,
                             nsIFrame*   aPrevInFlow)
 {
   NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
   // NOTE: bypassing nsTextFrame::Init!!!
   nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow);
 
-  nsIFrame* nextContinuation = aPrevInFlow->GetNextContinuation();
   // Hook the frame into the flow
-  SetPrevInFlow(aPrevInFlow);
-  aPrevInFlow->SetNextInFlow(this);
   nsTextFrame* prev = NS_STATIC_CAST(nsTextFrame*, aPrevInFlow);
+  nsTextFrame* nextContinuation = NS_STATIC_CAST(nsTextFrame*,
+      prev->GetNextContinuation());
+  SetPrevInFlow(prev);
+  prev->SetNextInFlow(this);
+
   mTextRun = prev->GetTextRun();
-  mContentOffset = prev->GetContentOffset() + prev->GetContentLength();
+  mContentOffset = prev->GetContentEnd();
+  mContentLength = mContent->TextLength() - mContentOffset;
 #ifdef IBMBIDI
-  if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
-    PRInt32 start, end;
-    aPrevInFlow->GetOffsets(start, mContentOffset);
-
+  if (prev->GetStateBits() & NS_FRAME_IS_BIDI) {
     nsPropertyTable *propTable = PresContext()->PropertyTable();
     propTable->SetProperty(this, nsGkAtoms::embeddingLevel,
           propTable->GetProperty(aPrevInFlow, nsGkAtoms::embeddingLevel),
                            nsnull, nsnull);
     propTable->SetProperty(this, nsGkAtoms::baseLevel,
               propTable->GetProperty(aPrevInFlow, nsGkAtoms::baseLevel),
                            nsnull, nsnull);
     propTable->SetProperty(this, nsGkAtoms::charType,
                propTable->GetProperty(aPrevInFlow, nsGkAtoms::charType),
                            nsnull, nsnull);
+
     if (nextContinuation) {
       SetNextContinuation(nextContinuation);
       nextContinuation->SetPrevContinuation(this);
-      nextContinuation->GetOffsets(start, end);
-      mContentLength = PR_MAX(1, start - mContentOffset);
+      NS_ASSERTION(mContentOffset <= nextContinuation->GetContentOffset(),
+                   "Overlapping text ranges!");
+      mContentLength = nextContinuation->GetContentOffset() - mContentOffset;
     }
     mState |= NS_FRAME_IS_BIDI;
   } // prev frame is bidi
 #endif // IBMBIDI
 
   return rv;
 }
 
@@ -3511,17 +3495,17 @@ nsTextFrame::ClearTextRun()
 //    // so we'd better kill this textrun now.
 //    if (textRun->GetExpirationState()->IsTracked()) {
 //      gTextRuns->RemoveFromCache(textRun);
 //    }
 //    delete textRun;
 //    return;
 //  }
 
-  if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED) {
+  if (!(textRun->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE)) {
     // Remove it now because it's not doing anything useful
     gTextRuns->RemoveFromCache(textRun);
     delete textRun;
   }
 }
 
 static void
 ClearTextRunsInFlowChain(nsTextFrame* aFrame)
@@ -3669,18 +3653,18 @@ nsTextFrame::GetSelectionDetails()
     return nsnull;
 
   SelectionDetails* details =
     GetFrameSelection()->LookUpSelection(owner->GetContent(),
         isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, PR_FALSE);
   SelectionDetails* sd;
   for (sd = details; sd; sd = sd->mNext) {
     // The entire text is selected!
-    sd->mStart = mContentOffset;
-    sd->mEnd = mContentOffset + mContentLength;
+    sd->mStart = GetContentOffset();
+    sd->mEnd = GetContentEnd();
   }
   return details;
 }
 
 static void
 FillClippedRect(gfxContext* aCtx, nsPresContext* aPresContext,
                 nscolor aColor, const gfxRect& aDirtyRect, const gfxRect& aRect)
 {
@@ -4276,18 +4260,18 @@ nsTextFrame::IsVisibleInSelection(nsISel
     return PR_FALSE;
     
   SelectionDetails* details = GetSelectionDetails();
   PRBool found = PR_FALSE;
     
   // where are the selection points "really"
   SelectionDetails *sdptr = details;
   while (sdptr) {
-    if (sdptr->mEnd > mContentOffset &&
-        sdptr->mStart < mContentOffset + mContentLength &&
+    if (sdptr->mEnd > GetContentOffset() &&
+        sdptr->mStart < GetContentEnd() &&
         sdptr->mType == nsISelectionController::SELECTION_NORMAL) {
       found = PR_TRUE;
       break;
     }
     sdptr = sdptr->mNext;
   }
   DestroySelectionDetails(details);
 
@@ -4404,17 +4388,17 @@ nsTextFrame::SetSelected(nsPresContext* 
     aRange->GetEndContainer(getter_AddRefs(endNode));
     aRange->GetEndOffset(&endOffset);
     aRange->GetStartContainer(getter_AddRefs(startNode));
     aRange->GetStartOffset(&startOffset);
     nsCOMPtr<nsIDOMNode> thisNode = do_QueryInterface(GetContent());
 
     if (thisNode == startNode)
     {
-      if ((mContentOffset + mContentLength) >= startOffset)
+      if (GetContentEnd() >= startOffset)
       {
         found = PR_TRUE;
         if (thisNode == endNode)
         { //special case
           if (endOffset == startOffset) //no need to redraw since drawing takes place with cursor
             found = PR_FALSE;
 
           if (mContentOffset > endOffset)
@@ -4496,22 +4480,22 @@ nsTextFrame::GetPointFromOffset(nsPresCo
   if (!mTextRun)
     return NS_ERROR_FAILURE;
 
   PropertyProvider properties(this, iter);
   // Don't trim trailing whitespace, we want the caret to appear in the right
   // place if it's positioned there
   properties.InitializeForDisplay(PR_FALSE);  
 
-  if (inOffset < mContentOffset){
+  if (inOffset < GetContentOffset()){
     NS_WARNING("offset before this frame's content");
-    inOffset = mContentOffset;
-  } else if (inOffset > mContentOffset + mContentLength) {
+    inOffset = GetContentOffset();
+  } else if (inOffset > GetContentEnd()) {
     NS_WARNING("offset after this frame's content");
-    inOffset = mContentOffset + mContentLength;
+    inOffset = GetContentEnd();
   }
   PRInt32 trimmedOffset = properties.GetStart().GetOriginalOffset();
   PRInt32 trimmedEnd = trimmedOffset + properties.GetOriginalLength();
   inOffset = PR_MAX(inOffset, trimmedOffset);
   inOffset = PR_MIN(inOffset, trimmedEnd);
 
   iter.SetOriginalOffset(inOffset);
 
@@ -4854,27 +4838,34 @@ nsTextFrame::CheckVisibility(nsPresConte
 
   *aRetval = PR_FALSE;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const
 {
-  start = mContentOffset;
-  end = mContentOffset+mContentLength;
+  start = GetContentOffset();
+  end = GetContentEnd();
   return NS_OK;
 }
 
-// returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
-// if it does not contain a true "letter"
-// if returns PR_TRUE, then it also updates aLength to cover just the first-letter
-// text
-// XXX :first-letter should be handled during frame construction
-// (and it has a good bit in common with nextBidi)
+/**
+ * Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
+ * if it does not contain a true "letter".
+ * If returns PR_TRUE, then it also updates aLength to cover just the first-letter
+ * text.
+ *
+ * XXX :first-letter should be handled during frame construction
+ * (and it has a good bit in common with nextBidi)
+ * 
+ * @param aLength an in/out parameter: on entry contains the maximum length to
+ * return, on exit returns length of the first-letter fragment (which may
+ * include leading punctuation, for example)
+ */
 static PRBool
 FindFirstLetterRange(const nsTextFragment* aFrag,
                      gfxTextRun* aTextRun,
                      PRInt32 aOffset, PRInt32* aLength)
 {
   // Find first non-whitespace, non-punctuation cluster, and stop after it
   PRInt32 i;
   PRInt32 length = *aLength;
@@ -5165,16 +5156,36 @@ HasSoftHyphenBefore(const nsTextFragment
     if (!iter.IsOriginalCharSkipped())
       break;
     if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
       return PR_TRUE;
   }
   return PR_FALSE;
 }
 
+void
+nsTextFrame::AdjustNextInFlowContentOffsetsForGrowth(PRBool aClearTextRuns)
+{
+  PRInt32 end = GetContentEnd();
+  nsTextFrame* f = this;
+  NS_ASSERTION(!GetNextInFlow() ||
+               NS_STATIC_CAST(nsTextFrame*, GetNextInFlow())->GetContentOffset() <= GetContentEnd(),
+               "We shrunk, this should not be called");
+  while (PR_TRUE) {
+    f = NS_STATIC_CAST(nsTextFrame*, f->GetNextInFlow());
+    if (!f || f->GetContentOffset() >= end)
+      break;
+    f->mContentLength = PR_MAX(end, f->GetContentEnd()) - end;
+    f->mContentOffset = end;
+    if (aClearTextRuns) {
+      f->ClearTextRun();
+    }
+  }
+}
+
 NS_IMETHODIMP
 nsTextFrame::Reflow(nsPresContext*           aPresContext,
                     nsHTMLReflowMetrics&     aMetrics,
                     const nsHTMLReflowState& aReflowState,
                     nsReflowStatus&          aStatus)
 {
   DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
@@ -5188,35 +5199,26 @@ nsTextFrame::Reflow(nsPresContext*      
   // Set up flags and clear out state
   /////////////////////////////////////////////////////////////////////
 
   // Clear out the reflow state flags in mState (without destroying
   // the TEXT_BLINK_ON bit). We also clear the whitespace flags because this
   // can change whether the frame maps whitespace-only text or not.
   RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
 
-  nsTextFrame* prevInFlow = NS_STATIC_CAST(nsTextFrame*, GetPrevInFlow());
-  if (prevInFlow) {
-    // Our mContentOffset may be out of date due to our prev-in-flow growing or
-    // shrinking. Update it.
-    mContentOffset = prevInFlow->GetContentOffset() + prevInFlow->GetContentLength();
-  }
-
-  // Temporarily map all possible content while we construct our new textrun.
-  // so that when doing reflow our styles prevail over any part of the
-  // textrun we look at. Note that next-in-flows may be mapping the same
-  // content; gfxTextRun construction logic will ensure that we take priority.
+  NS_ASSERTION(!GetPrevInFlow() ||
+               NS_STATIC_CAST(nsTextFrame*, GetPrevInFlow())->GetContentEnd() == mContentOffset,
+               "Discontinuous content offsets!");
   PRInt32 maxContentLength = GetInFlowContentLength();
-  mContentLength = maxContentLength;
 
   // XXX If there's no line layout, we shouldn't even have created this
   // frame. This may happen if, for example, this is text inside a table
   // but not inside a cell. For now, just don't reflow. We also don't need to
   // reflow if there is no content.
-  if (!aReflowState.mLineLayout || !mContentLength) {
+  if (!aReflowState.mLineLayout || !maxContentLength) {
     ClearMetrics(aMetrics);
     aStatus = NS_FRAME_COMPLETE;
     return NS_OK;
   }
 
   nsLineLayout& lineLayout = *aReflowState.mLineLayout;
 
   if (aPresContext->BidiEnabled()) {
@@ -5245,21 +5247,25 @@ nsTextFrame::Reflow(nsPresContext*      
     AddStateBits(TEXT_START_OF_LINE);
   }
 
   // Layout dependent styles are a problem because we need to reconstruct
   // the gfxTextRun based on our layout.
   PRBool layoutDependentTextRun =
     lineLayout.GetFirstLetterStyleOK() || lineLayout.GetInFirstLine();
   if (layoutDependentTextRun) {
-    AddStateBits(TEXT_RUN_LAYOUT_DEPENDENT);
-  }
-  if (layoutDependentTextRun ||
-      (prevInFlow && (prevInFlow->GetStateBits() & TEXT_RUN_LAYOUT_DEPENDENT))) {
     ClearTextRun();
+    // Temporarily map all possible content while we construct our new textrun.
+    // so that when doing reflow our styles prevail over any part of the
+    // textrun we look at.
+    mContentLength = maxContentLength;
+    // The following loop is going to traverse all in-flow frames, which could
+    // be kinda slow, but we're going to have to rebuild all their text runs
+    // anyway so this shouldn't be any worse
+    AdjustNextInFlowContentOffsetsForGrowth(PR_TRUE);
   }
 
   PRUint32 flowEndInTextRun;
   nsIFrame* lineContainer = lineLayout.GetLineContainerFrame();
   gfxSkipCharsIterator iter =
     EnsureTextRun(aReflowState.rendContext, lineContainer,
                   lineLayout.GetLine(), &flowEndInTextRun);
 
@@ -5268,17 +5274,17 @@ nsTextFrame::Reflow(nsPresContext*      
     aStatus = NS_FRAME_COMPLETE;
     return NS_OK;
   }
 
   const nsTextFragment* frag = mContent->GetText();
   // DOM offsets of the text range we need to measure, after trimming
   // whitespace, restricting to first-letter, and restricting preformatted text
   // to nearest newline
-  PRInt32 length = mContentLength;
+  PRInt32 length = maxContentLength;
   PRInt32 offset = mContentOffset;
 
   // Restrict preformatted text to the nearest newline
   PRInt32 newLineOffset = -1;
   if (textStyle->WhiteSpaceIsSignificant()) {
     newLineOffset = FindChar(frag, offset, length, '\n');
     if (newLineOffset >= 0) {
       length = newLineOffset + 1 - offset;
@@ -5423,16 +5429,37 @@ nsTextFrame::Reflow(nsPresContext*      
   } 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);
   }
   mContentLength = offset + charsFit - mContentOffset;
 
+  // Now fix up content offsets/lengths for in-flows
+  nsTextFrame* f = NS_STATIC_CAST(nsTextFrame*, GetNextInFlow());
+  if (f) {
+    if (f->GetContentOffset() > GetContentEnd()) {
+      // We must have shrunk. Add the leftover text to the start of f.
+      f->mContentLength = f->GetContentEnd() - GetContentEnd();
+      f->mContentOffset = GetContentEnd();
+      if (layoutDependentTextRun) {
+        // f's textrun may need to change since the text style may be
+        // different.
+        f->ClearTextRun();
+      }
+    } else if (f->GetContentOffset() < GetContentEnd()) {
+      // We must have grown. Remove the text from f and possibly its
+      // continuations.
+      NS_ASSERTION(!layoutDependentTextRun,
+                   "We should have grown up above and be shrinking here!");
+      AdjustNextInFlowContentOffsetsForGrowth(layoutDependentTextRun);
+    }
+  }
+  
   /////////////////////////////////////////////////////////////////////
   // Compute output metrics
   /////////////////////////////////////////////////////////////////////
 
   // first-letter frames should use the tight bounding box metrics for ascent/descent
   // for good drop-cap effects
   if (GetStateBits() & TEXT_FIRST_LETTER) {
     textMetrics.mAscent = PR_MAX(0, -textMetrics.mBoundingBox.Y());
@@ -5590,19 +5617,19 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPr
   TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE);
   gfxSkipCharsIterator iter = start;
   PRUint32 trimmedEnd = iter.ConvertOriginalToSkipped(trimmed.mStart + trimmed.mLength);
   const nsStyleText* textStyle = GetStyleText();
   gfxFloat delta = 0;
 
   if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
     aLastCharIsJustifiable = PR_TRUE;
-  } else if (trimmed.mStart + trimmed.mLength < mContentOffset + mContentLength) {
+  } else if (trimmed.mStart + trimmed.mLength < GetContentEnd()) {
     gfxSkipCharsIterator end = iter;
-    PRUint32 endOffset = end.ConvertOriginalToSkipped(mContentOffset + mContentLength);
+    PRUint32 endOffset = end.ConvertOriginalToSkipped(GetContentEnd());
     if (trimmedEnd < endOffset) {
       // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
       // OK to pass null for the line container.
       PropertyProvider provider(mTextRun, textStyle, frag, this, start, mContentLength,
                                 nsnull, 0);
       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
@@ -5758,17 +5785,17 @@ nsTextFrame::List(FILE* out, PRInt32 aIn
     fprintf(out, " [view=%p]", NS_STATIC_CAST(void*, GetView()));
   }
 
   PRInt32 totalContentLength;
   nsAutoString tmp;
   ToCString(tmp, &totalContentLength);
 
   // Output the first/last content offset and prev/next in flow info
-  PRBool isComplete = (mContentOffset + mContentLength) == totalContentLength;
+  PRBool isComplete = GetContentEnd() == totalContentLength;
   fprintf(out, "[%d,%d,%c] ", 
           mContentOffset, mContentLength,
           isComplete ? 'T':'F');
   
   if (nsnull != mNextSibling) {
     fprintf(out, " next=%p", NS_STATIC_CAST(void*, mNextSibling));
   }
   nsIFrame* prevContinuation = GetPrevContinuation();
@@ -5867,30 +5894,34 @@ void nsTextFrame::AdjustSelectionPointsF
   
   return;
 }
 
 void
 nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart, PRInt32 aEnd)
 {
   AddStateBits(NS_FRAME_IS_BIDI);
-  SetOffsets(aStart, aEnd);
-  /*
-   * After Bidi resolution we may need to reassign text runs.
-   * This is called during bidi resolution from the block container, so we
-   * shouldn't be holding a local reference to a textrun anywhere.
-   */
-  ClearTextRunsInFlowChain(this);
-}
-
-void
-nsTextFrame::SetOffsets(PRInt32 aStart, PRInt32 aEnd)
-{
   mContentOffset = aStart;
   mContentLength = aEnd - aStart;
+  nsTextFrame* f = this;
+  // We were just assigned all the content for this in-flow run, so unmap all
+  // next-in-flows
+  while (PR_TRUE) {
+    /*
+     * After Bidi resolution we may need to reassign text runs.
+     * This is called during bidi resolution from the block container, so we
+     * shouldn't be holding a local reference to a textrun anywhere.
+     */
+    f->ClearTextRun();
+    f = NS_STATIC_CAST(nsTextFrame*, f->GetNextInFlow());
+    if (!f)
+      break;
+    f->mContentOffset = aEnd;
+    f->mContentLength = 0;
+  }
 }
 
 /**
  * @return PR_TRUE if this text frame ends with a newline character.  It should return
  * PR_FALSE if it is not a text frame.
  */
 PRBool
 nsTextFrame::HasTerminalNewline() const
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -67,18 +67,17 @@ public:
     TEXT_WAS_TRANSFORMED     = 0x040000,
 
     // The following flags are set by nsTextFrame
 
     TEXT_IS_SIMPLE_FLOW      = 0x100000,
     TEXT_INCOMING_WHITESPACE = 0x200000,
     TEXT_TRAILING_WHITESPACE = 0x400000,
     TEXT_COMPRESSED_LEADING_WHITESPACE = 0x800000,
-    TEXT_IS_UNCACHED         = 0x1000000,
-    TEXT_NO_BREAKS           = 0x2000000
+    TEXT_NO_BREAKS           = 0x1000000
   };
 
   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.
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -184,51 +184,16 @@ static PRUint32
 CountGlyphs(const gfxTextRun::DetailedGlyph* aDetails) {
   PRUint32 glyphCount;
   for (glyphCount = 0; !aDetails[glyphCount].mIsLastGlyph; ++glyphCount) {
   }
   return glyphCount + 1;
 }
 
 /**
- * Concatenate textruns together just by copying their glyphrun data
- */
-static void
-AppendTextRun(gfxTextRun* aDest, gfxTextRun* aSrc, PRUint32 aOffset)
-{
-  PRUint32 numGlyphRuns;
-  const gfxTextRun::GlyphRun* glyphRuns = aSrc->GetGlyphRuns(&numGlyphRuns);
-  PRUint32 j;
-  PRUint32 offset = aOffset;
-  for (j = 0; j < numGlyphRuns; ++j) {
-    PRUint32 runOffset = glyphRuns[j].mCharacterOffset;
-    PRUint32 len =
-      (j + 1 < numGlyphRuns ? glyphRuns[j + 1].mCharacterOffset : aSrc->GetLength()) -
-      runOffset;
-    nsresult rv = aDest->AddGlyphRun(glyphRuns[j].mFont, offset);
-    if (NS_FAILED(rv))
-      return;
-
-    PRUint32 k;
-    for (k = 0; k < len; ++k) {
-      gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[runOffset + k];
-      if (g.IsComplexCluster()) {
-        const gfxTextRun::DetailedGlyph* details = aSrc->GetDetailedGlyphs(runOffset + k);
-        aDest->SetDetailedGlyphs(offset, details, CountGlyphs(details));
-      } else {
-        aDest->SetCharacterGlyph(offset, g);
-      }
-      ++offset;
-    }
-  }
-  NS_ASSERTION(offset - aOffset == aSrc->GetLength(),
-               "Something went wrong in our length calculations...");
-}
-
-/**
  * Copy a given textrun, but merge certain characters into a single logical
  * character. Glyphs for a character are added to the glyph list for the previous
  * character and then the merged character is eliminated. Visually the results
  * are identical.
  * 
  * This is used for text-transform:uppercase when we encounter a SZLIG,
  * whose uppercase form is "SS".
  * 
@@ -312,17 +277,17 @@ MergeCharactersInTextRun(gfxTextRun* aDe
 }
 
 static gfxTextRunFactory::Parameters
 GetParametersForInner(nsTransformedTextRun* aTextRun, PRUint32* aFlags,
     gfxContext* aRefContext)
 {
   gfxTextRunFactory::Parameters params =
     { aRefContext, nsnull, nsnull,
-      nsnull, nsnull, PRUint32(aTextRun->GetAppUnitsPerDevUnit())
+      nsnull, nsnull, aTextRun->GetAppUnitsPerDevUnit()
     };
   *aFlags = aTextRun->GetFlags() & ~gfxFontGroup::TEXT_IS_PERSISTENT;
   return params;
 }
 
 void
 nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun,
     gfxContext* aRefContext)
@@ -336,27 +301,24 @@ nsFontVariantTextRunFactory::RebuildText
   fontStyle.size *= 0.8;
   nsRefPtr<gfxFontGroup> smallFont = fontGroup->Copy(&fontStyle);
   if (!smallFont)
     return;
 
   PRUint32 flags;
   gfxTextRunFactory::Parameters innerParams =
       GetParametersForInner(aTextRun, &flags, aRefContext);
-  // The text outlives the child and inner textruns
-  flags |= gfxFontGroup::TEXT_IS_PERSISTENT;
 
   PRUint32 length = aTextRun->GetLength();
   const PRUnichar* str = aTextRun->GetTextUnicode();
   nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
   // Create a textrun so we can check cluster-start properties
-  nsAutoPtr<gfxTextRun> inner;
-  // This text is going to outlive the inner text run
-  inner = fontGroup->MakeTextRun(str, length, &innerParams, flags);
-  if (!inner)
+  gfxTextRunCache::AutoTextRun inner(
+      gfxTextRunCache::MakeTextRun(str, length, fontGroup, &innerParams, flags));
+  if (!inner.get())
     return;
 
   nsCaseTransformTextRunFactory uppercaseFactory(nsnull, PR_TRUE);
 
   aTextRun->ResetGlyphRuns();
 
   PRUint32 runStart = 0;
   PRBool runIsLowercase = PR_FALSE;
@@ -387,36 +349,41 @@ nsFontVariantTextRunFactory::RebuildText
           isLowercase = ch != ch2 || ch == SZLIG;
         } else {
           // Don't transform the character! I.e., pretend that it's not lowercase
         }
       }
     }
 
     if ((i == length || runIsLowercase != isLowercase) && runStart < i) {
-      nsAutoPtr<gfxTextRun> child;
+      nsAutoPtr<gfxTextRun> transformedChild;
+      gfxTextRunCache::AutoTextRun cachedChild;
+      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,
+        transformedChild = uppercaseFactory.MakeTextRun(str + runStart, i - runStart,
             &innerParams, smallFont, flags, styleArray.Elements(), PR_FALSE);
+        child = transformedChild;
       } else {
-        child = fontGroup->
-            MakeTextRun(str + runStart, i - runStart, &innerParams, flags);
+        cachedChild =
+          gfxTextRunCache::MakeTextRun(str + runStart, i - runStart, fontGroup,
+              &innerParams, flags);
+        child = cachedChild.get();
       }
       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(), aRefContext);
-      AppendTextRun(aTextRun, child, runStart);
+      aTextRun->CopyGlyphDataFrom(child, 0, child->GetLength(), runStart, PR_FALSE);
 
       runStart = i;
       styleArray.Clear();
       canBreakBeforeArray.Clear();
       lineBreakBeforeArray.Clear();
       if (nextLineBreak > 0 && aTextRun->mLineBreaks[nextLineBreak - 1] == i) {
         lineBreakBeforeArray.AppendElement(0);
       }
@@ -513,30 +480,32 @@ nsCaseTransformTextRunFactory::RebuildTe
   NS_ASSERTION(nextLineBreak == aTextRun->mLineBreaks.Length(),
                "lost track of line breaks somehow");
 
   PRUint32 flags;
   gfxTextRunFactory::Parameters innerParams =
       GetParametersForInner(aTextRun, &flags, aRefContext);
   gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
 
-  nsAutoPtr<gfxTextRun> child;
+  nsAutoPtr<gfxTextRun> transformedChild;
+  gfxTextRunCache::AutoTextRun cachedChild;
+  gfxTextRun* child;
   // Setup actual line break data for child (which may affect shaping)
   innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
   innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
-  // The text outlives 'child'
-  flags |= gfxFontGroup::TEXT_IS_PERSISTENT;
   if (mInnerTransformingTextRunFactory) {
-    child = mInnerTransformingTextRunFactory->MakeTextRun(
+    transformedChild = mInnerTransformingTextRunFactory->MakeTextRun(
         convertedString.BeginReading(), convertedString.Length(),
         &innerParams, fontGroup, flags, styleArray.Elements(), PR_FALSE);
+    child = transformedChild.get();
   } else {
-    child = fontGroup->MakeTextRun(
-        convertedString.BeginReading(), convertedString.Length(), &innerParams,
-        flags);
+    cachedChild = gfxTextRunCache::MakeTextRun(
+        convertedString.BeginReading(), convertedString.Length(), fontGroup,
+        &innerParams, flags);
+    child = cachedChild.get();
   }
   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(),
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -49,16 +49,17 @@
 #include "nsSVGTextPathFrame.h"
 #include "nsSVGPathElement.h"
 #include "nsSVGPoint.h"
 #include "nsSVGRect.h"
 #include "nsDOMError.h"
 #include "gfxContext.h"
 #include "gfxMatrix.h"
 #include "gfxPlatform.h"
+#include "gfxTextRunCache.h"
 
 // XXX: This initial straightforward conversion from accessing cairo
 // directly to Thebes doesn't handle clusters.  Pretty much all code
 // that measures or draws single characters (textPath code and some
 // DOM accessors) will need to be reworked.
 
 //----------------------------------------------------------------------
 // Implementation
@@ -210,19 +211,19 @@ nsSVGGlyphFrame::GetType() const
 //----------------------------------------------------------------------
 // nsISVGChildFrame methods
 
 void
 nsSVGGlyphFrame::LoopCharacters(gfxContext *aCtx, const nsString &aText,
                                 const nsSVGCharacterPosition *aCP,
                                 FillOrStroke aFillOrStroke)
 {
-  nsAutoPtr<gfxTextRun> textRun(GetTextRun(aCtx, aText));
+  gfxTextRunCache::AutoTextRun textRun = GetTextRun(aCtx, aText);
 
-  if (!textRun)
+  if (!textRun.get())
     return;
 
   if (!aCP) {
     if (aFillOrStroke == STROKE) {
       textRun->DrawToPath(aCtx, mPosition, 0, aText.Length(), nsnull, nsnull);
     } else {
       textRun->Draw(aCtx, mPosition, 0, aText.Length(),
                     nsnull, nsnull, nsnull);
@@ -601,18 +602,18 @@ nsSVGGlyphFrame::GetCharacterPosition(gf
 
   /* textPath frame, but invalid target */
   if (!data)
     return NS_ERROR_FAILURE;
 
   gfxFloat length = data->GetLength();
   PRUint32 strLength = aText.Length();
 
-  nsAutoPtr<gfxTextRun> textRun(GetTextRun(aContext, aText));
-  if (!textRun)
+  gfxTextRunCache::AutoTextRun textRun = GetTextRun(aContext, aText);
+  if (!textRun.get())
     return NS_ERROR_OUT_OF_MEMORY;
 
   nsSVGCharacterPosition *cp = new nsSVGCharacterPosition[strLength];
 
   for (PRUint32 k = 0; k < strLength; k++)
     cp[k].draw = PR_FALSE;
 
   gfxFloat x = mPosition.x;
@@ -1342,27 +1343,21 @@ nsSVGGlyphFrame::GetTextRun(gfxContext *
 {
   // XXX: should really pass in GetPresContext()->AppUnitsPerDevPixel()
   // instead of "1" and do the appropriate unit conversions when sending
   // coordinates into thebes and pulling metrics out.
   //
   // References:
   //   https://bugzilla.mozilla.org/show_bug.cgi?id=375141
   //   http://weblogs.mozillazine.org/roc/archives/2007/03/text_text_text.html
-
-  gfxTextRunFactory::Parameters params =
-    { aCtx, nsnull, nsnull,
-      nsnull, nsnull,
-      1 // see note above
-      };
-
   if (!mFontGroup)
     return nsnull;
 
-  return mFontGroup->MakeTextRun(aText.get(), aText.Length(), &params, 0);
+  return gfxTextRunCache::MakeTextRun(aText.get(), aText.Length(),
+      mFontGroup, aCtx, 1, 0);
 }
 
 //----------------------------------------------------------------------
 // helper class
 
 nsSVGGlyphFrame::nsSVGAutoGlyphHelperContext::nsSVGAutoGlyphHelperContext(
     nsSVGGlyphFrame *aSource,
     const nsString &aText,
--- a/layout/svg/base/src/nsSVGGlyphFrame.h
+++ b/layout/svg/base/src/nsSVGGlyphFrame.h
@@ -39,16 +39,17 @@
 #ifndef __NS_SVGGLYPHFRAME_H__
 #define __NS_SVGGLYPHFRAME_H__
 
 #include "nsSVGGeometryFrame.h"
 #include "nsISVGGlyphFragmentLeaf.h"
 #include "nsISVGChildFrame.h"
 #include "gfxContext.h"
 #include "gfxFont.h"
+#include "gfxTextRunCache.h"
 
 struct nsSVGCharacterPosition;
 class nsSVGTextFrame;
 class nsSVGGlyphFrame;
 
 typedef nsSVGGeometryFrame nsSVGGlyphFrameBase;
 
 class nsSVGGlyphFrame : public nsSVGGlyphFrameBase,
@@ -159,40 +160,41 @@ protected:
   };
 
   // VC6 does not allow the inner class to access protected members
   // of the outer class
   class nsSVGAutoGlyphHelperContext;
   friend class nsSVGAutoGlyphHelperContext;
 
   // A helper class to deal with gfxTextRuns and temporary thebes
-  // contexts.  It destroys them when it goes out of scope.
+  // contexts.
   class nsSVGAutoGlyphHelperContext
   {
   public:
     nsSVGAutoGlyphHelperContext(nsSVGGlyphFrame *aSource,
                                 const nsString &aText)
     {
       Init(aSource, aText);
     }
 
     nsSVGAutoGlyphHelperContext(nsSVGGlyphFrame *aSource,
                                 const nsString &aText,
                                 nsSVGCharacterPosition **cp);
 
     gfxContext *GetContext() { return mCT; }
-    gfxTextRun *GetTextRun() { return mTextRun; }
+    gfxTextRun *GetTextRun() { return mTextRun.get(); }
 
   private:
     void Init(nsSVGGlyphFrame *aSource, const nsString &aText);
 
-    nsRefPtr<gfxContext> mCT;
-    nsAutoPtr<gfxTextRun> mTextRun;
+    nsRefPtr<gfxContext>         mCT;
+    gfxTextRunCache::AutoTextRun mTextRun;
   };
 
+  // The textrun must be released via gfxTextRunCache::AutoTextRun
   gfxTextRun *GetTextRun(gfxContext *aCtx,
                          const nsString &aText);
 
   PRBool GetCharacterData(nsAString & aCharacterData);
   nsresult GetCharacterPosition(gfxContext *aContext,
                                 const nsString &aText,
                                 nsSVGCharacterPosition **aCharacterPosition);