layout/generic/nsTextFrame.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /* rendering object for textual content of elements */
     8 #include "nsTextFrame.h"
    10 #include "mozilla/Attributes.h"
    11 #include "mozilla/DebugOnly.h"
    12 #include "mozilla/Likely.h"
    13 #include "mozilla/MathAlgorithms.h"
    14 #include "mozilla/TextEvents.h"
    16 #include "nsCOMPtr.h"
    17 #include "nsBlockFrame.h"
    18 #include "nsCRT.h"
    19 #include "nsSplittableFrame.h"
    20 #include "nsLineLayout.h"
    21 #include "nsString.h"
    22 #include "nsUnicharUtils.h"
    23 #include "nsPresContext.h"
    24 #include "nsIContent.h"
    25 #include "nsStyleConsts.h"
    26 #include "nsStyleContext.h"
    27 #include "nsStyleStruct.h"
    28 #include "nsStyleStructInlines.h"
    29 #include "SVGTextFrame.h"
    30 #include "nsCoord.h"
    31 #include "nsRenderingContext.h"
    32 #include "nsIPresShell.h"
    33 #include "nsTArray.h"
    34 #include "nsCSSPseudoElements.h"
    35 #include "nsCSSFrameConstructor.h"
    36 #include "nsCompatibility.h"
    37 #include "nsCSSColorUtils.h"
    38 #include "nsLayoutUtils.h"
    39 #include "nsDisplayList.h"
    40 #include "nsFrame.h"
    41 #include "nsIMathMLFrame.h"
    42 #include "nsPlaceholderFrame.h"
    43 #include "nsTextFrameUtils.h"
    44 #include "nsTextRunTransformations.h"
    45 #include "MathMLTextRunFactory.h"
    46 #include "nsExpirationTracker.h"
    47 #include "nsUnicodeProperties.h"
    49 #include "nsTextFragment.h"
    50 #include "nsGkAtoms.h"
    51 #include "nsFrameSelection.h"
    52 #include "nsRange.h"
    53 #include "nsCSSRendering.h"
    54 #include "nsContentUtils.h"
    55 #include "nsLineBreaker.h"
    56 #include "nsIWordBreaker.h"
    57 #include "nsGenericDOMDataNode.h"
    58 #include "nsIFrameInlines.h"
    60 #include <algorithm>
    61 #ifdef ACCESSIBILITY
    62 #include "nsAccessibilityService.h"
    63 #endif
    64 #include "nsAutoPtr.h"
    66 #include "nsPrintfCString.h"
    68 #include "gfxFont.h"
    69 #include "gfxContext.h"
    71 #include "mozilla/dom/Element.h"
    72 #include "mozilla/LookAndFeel.h"
    74 #include "GeckoProfiler.h"
    76 #ifdef DEBUG
    77 #undef NOISY_REFLOW
    78 #undef NOISY_TRIM
    79 #else
    80 #undef NOISY_REFLOW
    81 #undef NOISY_TRIM
    82 #endif
    84 #ifdef DrawText
    85 #undef DrawText
    86 #endif
    88 using namespace mozilla;
    89 using namespace mozilla::dom;
    91 struct TabWidth {
    92   TabWidth(uint32_t aOffset, uint32_t aWidth)
    93     : mOffset(aOffset), mWidth(float(aWidth))
    94   { }
    96   uint32_t mOffset; // DOM offset relative to the current frame's offset.
    97   float    mWidth;  // extra space to be added at this position (in app units)
    98 };
   100 struct TabWidthStore {
   101   TabWidthStore(int32_t aValidForContentOffset)
   102     : mLimit(0)
   103     , mValidForContentOffset(aValidForContentOffset)
   104   { }
   106   // Apply tab widths to the aSpacing array, which corresponds to characters
   107   // beginning at aOffset and has length aLength. (Width records outside this
   108   // range will be ignored.)
   109   void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
   110                     uint32_t aOffset, uint32_t aLength);
   112   // Offset up to which tabs have been measured; positions beyond this have not
   113   // been calculated yet but may be appended if needed later.  It's a DOM
   114   // offset relative to the current frame's offset.
   115   uint32_t mLimit;
   117   // Need to recalc tab offsets if frame content offset differs from this.
   118   int32_t mValidForContentOffset;
   120   // A TabWidth record for each tab character measured so far.
   121   nsTArray<TabWidth> mWidths;
   122 };
   124 void
   125 TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
   126                             uint32_t aOffset, uint32_t aLength)
   127 {
   128   uint32_t i = 0, len = mWidths.Length();
   130   // If aOffset is non-zero, do a binary search to find where to start
   131   // processing the tab widths, in case the list is really long. (See bug
   132   // 953247.)
   133   // We need to start from the first entry where mOffset >= aOffset.
   134   if (aOffset > 0) {
   135     uint32_t lo = 0, hi = len;
   136     while (lo < hi) {
   137       i = (lo + hi) / 2;
   138       const TabWidth& tw = mWidths[i];
   139       if (tw.mOffset < aOffset) {
   140         // mWidths[i] precedes the target range; new search range
   141         // will be [i+1, hi)
   142         lo = ++i;
   143         continue;
   144       }
   145       if (tw.mOffset > aOffset) {
   146         // mWidths[i] is within (or beyond) the target range;
   147         // new search range is [lo, i). If it turns out that
   148         // mWidths[i] was the first entry within the range,
   149         // we'll never move hi any further, and end up exiting
   150         // when i == lo == this value of hi.
   151         hi = i;
   152         continue;
   153       }
   154       // Found an exact match for aOffset, so end search now
   155       break;
   156     }
   157   }
   159   uint32_t limit = aOffset + aLength;
   160   while (i < len) {
   161     const TabWidth& tw = mWidths[i];
   162     if (tw.mOffset >= limit) {
   163       break;
   164     }
   165     aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
   166     i++;
   167   }
   168 }
   170 static void DestroyTabWidth(void* aPropertyValue)
   171 {
   172   delete static_cast<TabWidthStore*>(aPropertyValue);
   173 }
   175 NS_DECLARE_FRAME_PROPERTY(TabWidthProperty, DestroyTabWidth)
   177 NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nullptr)
   179 // text runs are destroyed by the text run cache
   180 NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nullptr)
   182 NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr)
   184 class GlyphObserver : public gfxFont::GlyphChangeObserver {
   185 public:
   186   GlyphObserver(gfxFont* aFont, nsTextFrame* aFrame)
   187     : gfxFont::GlyphChangeObserver(aFont), mFrame(aFrame) {}
   188   virtual void NotifyGlyphsChanged() MOZ_OVERRIDE;
   189 private:
   190   nsTextFrame* mFrame;
   191 };
   193 static void DestroyGlyphObserverList(void* aPropertyValue)
   194 {
   195   delete static_cast<nsTArray<nsAutoPtr<GlyphObserver> >*>(aPropertyValue);
   196 }
   198 /**
   199  * This property is set on text frames with TEXT_IN_TEXTRUN_USER_DATA set that
   200  * have potentially-animated glyphs.
   201  * The only reason this list is in a property is to automatically destroy the
   202  * list when the frame is deleted, unregistering the observers.
   203  */
   204 NS_DECLARE_FRAME_PROPERTY(TextFrameGlyphObservers, DestroyGlyphObserverList);
   206 #define TEXT_REFLOW_FLAGS    \
   207   (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
   208    TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
   209    TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
   211 #define TEXT_WHITESPACE_FLAGS      (TEXT_IS_ONLY_WHITESPACE | \
   212                                     TEXT_ISNOT_ONLY_WHITESPACE)
   214 /*
   215  * Some general notes
   216  * 
   217  * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
   218  * transforms text to positioned glyphs. It can report the geometry of the
   219  * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
   220  * spacing, language, and other information.
   221  * 
   222  * A gfxTextRun can cover more than one DOM text node. This is necessary to
   223  * get kerning, ligatures and shaping for text that spans multiple text nodes
   224  * but is all the same font. The userdata for a gfxTextRun object is a
   225  * TextRunUserData* or an nsIFrame*.
   226  * 
   227  * We go to considerable effort to make sure things work even if in-flow
   228  * siblings have different style contexts (i.e., first-letter and first-line).
   229  * 
   230  * Our convention is that unsigned integer character offsets are offsets into
   231  * the transformed string. Signed integer character offsets are offsets into
   232  * the DOM string.
   233  * 
   234  * XXX currently we don't handle hyphenated breaks between text frames where the
   235  * hyphen occurs at the end of the first text frame, e.g.
   236  *   <b>Kit&shy;</b>ty
   237  */
   239 /**
   240  * We use an array of these objects to record which text frames
   241  * are associated with the textrun. mStartFrame is the start of a list of
   242  * text frames. Some sequence of its continuations are covered by the textrun.
   243  * A content textnode can have at most one TextRunMappedFlow associated with it
   244  * for a given textrun.
   245  * 
   246  * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
   247  * the offset into the before-transformation text of the textrun. It can be
   248  * positive (when a text node starts in the middle of a text run) or
   249  * negative (when a text run starts in the middle of a text node). Of course
   250  * it can also be zero.
   251  */
   252 struct TextRunMappedFlow {
   253   nsTextFrame* mStartFrame;
   254   int32_t      mDOMOffsetToBeforeTransformOffset;
   255   // The text mapped starts at mStartFrame->GetContentOffset() and is this long
   256   uint32_t     mContentLength;
   257 };
   259 /**
   260  * This is our user data for the textrun, when textRun->GetFlags() does not
   261  * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is
   262  * just one flow, the textrun's user data pointer is a pointer to mStartFrame
   263  * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength
   264  * is the length of the text node.
   265  */
   266 struct TextRunUserData {
   267   TextRunMappedFlow* mMappedFlows;
   268   uint32_t           mMappedFlowCount;
   269   uint32_t           mLastFlowIndex;
   270 };
   272 /**
   273  * This helper object computes colors used for painting, and also IME
   274  * underline information. The data is computed lazily and cached as necessary.
   275  * These live for just the duration of one paint operation.
   276  */
   277 class nsTextPaintStyle {
   278 public:
   279   nsTextPaintStyle(nsTextFrame* aFrame);
   281   void SetResolveColors(bool aResolveColors) {
   282     NS_ASSERTION(mFrame->IsSVGText() || aResolveColors,
   283                  "must resolve colors is frame is not for SVG text");
   284     mResolveColors = aResolveColors;
   285   }
   287   nscolor GetTextColor();
   288   /**
   289    * Compute the colors for normally-selected text. Returns false if
   290    * the normal selection is not being displayed.
   291    */
   292   bool GetSelectionColors(nscolor* aForeColor,
   293                             nscolor* aBackColor);
   294   void GetHighlightColors(nscolor* aForeColor,
   295                           nscolor* aBackColor);
   296   void GetURLSecondaryColor(nscolor* aForeColor);
   297   void GetIMESelectionColors(int32_t  aIndex,
   298                              nscolor* aForeColor,
   299                              nscolor* aBackColor);
   300   // if this returns false, we don't need to draw underline.
   301   bool GetSelectionUnderlineForPaint(int32_t  aIndex,
   302                                        nscolor* aLineColor,
   303                                        float*   aRelativeSize,
   304                                        uint8_t* aStyle);
   306   // if this returns false, we don't need to draw underline.
   307   static bool GetSelectionUnderline(nsPresContext* aPresContext,
   308                                       int32_t aIndex,
   309                                       nscolor* aLineColor,
   310                                       float* aRelativeSize,
   311                                       uint8_t* aStyle);
   313   // if this returns false, no text-shadow was specified for the selection
   314   // and the *aShadow parameter was not modified.
   315   bool GetSelectionShadow(nsCSSShadowArray** aShadow);
   317   nsPresContext* PresContext() const { return mPresContext; }
   319   enum {
   320     eIndexRawInput = 0,
   321     eIndexSelRawText,
   322     eIndexConvText,
   323     eIndexSelConvText,
   324     eIndexSpellChecker
   325   };
   327   static int32_t GetUnderlineStyleIndexForSelectionType(int32_t aSelectionType)
   328   {
   329     switch (aSelectionType) {
   330       case nsISelectionController::SELECTION_IME_RAWINPUT:
   331         return eIndexRawInput;
   332       case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
   333         return eIndexSelRawText;
   334       case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
   335         return eIndexConvText;
   336       case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
   337         return eIndexSelConvText;
   338       case nsISelectionController::SELECTION_SPELLCHECK:
   339         return eIndexSpellChecker;
   340       default:
   341         NS_WARNING("non-IME selection type");
   342         return eIndexRawInput;
   343     }
   344   }
   346 protected:
   347   nsTextFrame*   mFrame;
   348   nsPresContext* mPresContext;
   349   bool           mInitCommonColors;
   350   bool           mInitSelectionColorsAndShadow;
   351   bool           mResolveColors;
   353   // Selection data
   355   int16_t      mSelectionStatus; // see nsIDocument.h SetDisplaySelection()
   356   nscolor      mSelectionTextColor;
   357   nscolor      mSelectionBGColor;
   358   nsRefPtr<nsCSSShadowArray> mSelectionShadow;
   359   bool                       mHasSelectionShadow;
   361   // Common data
   363   int32_t mSufficientContrast;
   364   nscolor mFrameBackgroundColor;
   366   // selection colors and underline info, the colors are resolved colors if
   367   // mResolveColors is true (which is the default), i.e., the foreground color
   368   // and background color are swapped if it's needed. And also line color will
   369   // be resolved from them.
   370   struct nsSelectionStyle {
   371     bool    mInit;
   372     nscolor mTextColor;
   373     nscolor mBGColor;
   374     nscolor mUnderlineColor;
   375     uint8_t mUnderlineStyle;
   376     float   mUnderlineRelativeSize;
   377   };
   378   nsSelectionStyle mSelectionStyle[5];
   380   // Color initializations
   381   void InitCommonColors();
   382   bool InitSelectionColorsAndShadow();
   384   nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
   385   void InitSelectionStyle(int32_t aIndex);
   387   bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor);
   389   nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
   390                                nscolor aBackColor);
   391 };
   393 static void
   394 DestroyUserData(void* aUserData)
   395 {
   396   TextRunUserData* userData = static_cast<TextRunUserData*>(aUserData);
   397   if (userData) {
   398     nsMemory::Free(userData);
   399   }
   400 }
   402 /**
   403  * Remove |aTextRun| from the frame continuation chain starting at
   404  * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
   405  * Unmark |aFrame| as a text run owner if it's the frame we start at.
   406  * Return true if |aStartContinuation| is non-null and was found
   407  * in the next-continuation chain of |aFrame|.
   408  */
   409 static bool
   410 ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
   411                           nsTextFrame* aStartContinuation,
   412                           nsFrameState aWhichTextRunState)
   413 {
   414   NS_PRECONDITION(aFrame, "");
   415   NS_PRECONDITION(!aStartContinuation ||
   416                   (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
   417                    aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) ||
   418                   (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
   419                    aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun),
   420                   "wrong aStartContinuation for this text run");
   422   if (!aStartContinuation || aStartContinuation == aFrame) {
   423     aFrame->RemoveStateBits(aWhichTextRunState);
   424   } else {
   425     do {
   426       NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
   427       aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
   428     } while (aFrame && aFrame != aStartContinuation);
   429   }
   430   bool found = aStartContinuation == aFrame;
   431   while (aFrame) {
   432     NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
   433     if (!aFrame->RemoveTextRun(aTextRun)) {
   434       break;
   435     }
   436     aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
   437   }
   438   NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?");
   439   return found;
   440 }
   442 /**
   443  * Kill all references to |aTextRun| starting at |aStartContinuation|.
   444  * It could be referenced by any of its owners, and all their in-flows.
   445  * If |aStartContinuation| is null then process all userdata frames
   446  * and their continuations.
   447  * @note the caller is expected to take care of possibly destroying the
   448  * text run if all userdata frames were reset (userdata is deallocated
   449  * by this function though). The caller can detect this has occured by
   450  * checking |aTextRun->GetUserData() == nullptr|.
   451  */
   452 static void
   453 UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation)
   454 {
   455   if (!aTextRun->GetUserData())
   456     return;
   458   if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
   459     nsTextFrame* userDataFrame = static_cast<nsTextFrame*>(
   460       static_cast<nsIFrame*>(aTextRun->GetUserData()));
   461     nsFrameState whichTextRunState =
   462       userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
   463         ? TEXT_IN_TEXTRUN_USER_DATA
   464         : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
   465     DebugOnly<bool> found =
   466       ClearAllTextRunReferences(userDataFrame, aTextRun,
   467                                 aStartContinuation, whichTextRunState);
   468     NS_ASSERTION(!aStartContinuation || found,
   469                  "aStartContinuation wasn't found in simple flow text run");
   470     if (!(userDataFrame->GetStateBits() & whichTextRunState)) {
   471       aTextRun->SetUserData(nullptr);
   472     }
   473   } else {
   474     TextRunUserData* userData =
   475       static_cast<TextRunUserData*>(aTextRun->GetUserData());
   476     int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
   477     for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
   478       nsTextFrame* userDataFrame = userData->mMappedFlows[i].mStartFrame;
   479       nsFrameState whichTextRunState =
   480         userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
   481           ? TEXT_IN_TEXTRUN_USER_DATA
   482           : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
   483       bool found =
   484         ClearAllTextRunReferences(userDataFrame, aTextRun,
   485                                   aStartContinuation, whichTextRunState);
   486       if (found) {
   487         if (userDataFrame->GetStateBits() & whichTextRunState) {
   488           destroyFromIndex = i + 1;
   489         }
   490         else {
   491           destroyFromIndex = i;
   492         }
   493         aStartContinuation = nullptr;
   494       }
   495     }
   496     NS_ASSERTION(destroyFromIndex >= 0,
   497                  "aStartContinuation wasn't found in multi flow text run");
   498     if (destroyFromIndex == 0) {
   499       DestroyUserData(userData);
   500       aTextRun->SetUserData(nullptr);
   501     }
   502     else {
   503       userData->mMappedFlowCount = uint32_t(destroyFromIndex);
   504       if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
   505         userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
   506       }
   507     }
   508   }
   509 }
   511 void
   512 GlyphObserver::NotifyGlyphsChanged()
   513 {
   514   nsIPresShell* shell = mFrame->PresContext()->PresShell();
   515   for (nsIFrame* f = mFrame; f;
   516        f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
   517     if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) {
   518       // f will have its own GlyphObserver (if needed) so we can stop here.
   519       break;
   520     }
   521     f->InvalidateFrame();
   522     // Theoretically we could just update overflow areas, perhaps using
   523     // OverflowChangedTracker, but that would do a bunch of work eagerly that
   524     // we should probably do lazily here since there could be a lot
   525     // of text frames affected and we'd like to coalesce the work. So that's
   526     // not easy to do well.
   527     shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
   528   }
   529 }
   531 class FrameTextRunCache;
   533 static FrameTextRunCache *gTextRuns = nullptr;
   535 /*
   536  * Cache textruns and expire them after 3*10 seconds of no use.
   537  */
   538 class FrameTextRunCache MOZ_FINAL : public nsExpirationTracker<gfxTextRun,3> {
   539 public:
   540   enum { TIMEOUT_SECONDS = 10 };
   541   FrameTextRunCache()
   542       : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
   543   ~FrameTextRunCache() {
   544     AgeAllGenerations();
   545   }
   547   void RemoveFromCache(gfxTextRun* aTextRun) {
   548     if (aTextRun->GetExpirationState()->IsTracked()) {
   549       RemoveObject(aTextRun);
   550     }
   551   }
   553   // This gets called when the timeout has expired on a gfxTextRun
   554   virtual void NotifyExpired(gfxTextRun* aTextRun) {
   555     UnhookTextRunFromFrames(aTextRun, nullptr);
   556     RemoveFromCache(aTextRun);
   557     delete aTextRun;
   558   }
   559 };
   561 // Helper to create a textrun and remember it in the textframe cache,
   562 // for either 8-bit or 16-bit text strings
   563 template<typename T>
   564 gfxTextRun *
   565 MakeTextRun(const T *aText, uint32_t aLength,
   566             gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
   567             uint32_t aFlags)
   568 {
   569     nsAutoPtr<gfxTextRun> textRun(aFontGroup->MakeTextRun(aText, aLength,
   570                                                           aParams, aFlags));
   571     if (!textRun) {
   572         return nullptr;
   573     }
   574     nsresult rv = gTextRuns->AddObject(textRun);
   575     if (NS_FAILED(rv)) {
   576         gTextRuns->RemoveFromCache(textRun);
   577         return nullptr;
   578     }
   579 #ifdef NOISY_BIDI
   580     printf("Created textrun\n");
   581 #endif
   582     return textRun.forget();
   583 }
   585 void
   586 nsTextFrameTextRunCache::Init() {
   587     gTextRuns = new FrameTextRunCache();
   588 }
   590 void
   591 nsTextFrameTextRunCache::Shutdown() {
   592     delete gTextRuns;
   593     gTextRuns = nullptr;
   594 }
   596 int32_t nsTextFrame::GetContentEnd() const {
   597   nsTextFrame* next = static_cast<nsTextFrame*>(GetNextContinuation());
   598   return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
   599 }
   601 struct FlowLengthProperty {
   602   int32_t mStartOffset;
   603   // The offset of the next fixed continuation after mStartOffset, or
   604   // of the end of the text if there is none
   605   int32_t mEndFlowOffset;
   606 };
   608 int32_t nsTextFrame::GetInFlowContentLength() {
   609   if (!(mState & NS_FRAME_IS_BIDI)) {
   610     return mContent->TextLength() - mContentOffset;
   611   }
   613   FlowLengthProperty* flowLength =
   614     static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength));
   616   /**
   617    * This frame must start inside the cached flow. If the flow starts at
   618    * mContentOffset but this frame is empty, logically it might be before the
   619    * start of the cached flow.
   620    */
   621   if (flowLength && 
   622       (flowLength->mStartOffset < mContentOffset ||
   623        (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) &&
   624       flowLength->mEndFlowOffset > mContentOffset) {
   625 #ifdef DEBUG
   626     NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
   627 		 "frame crosses fixed continuation boundary");
   628 #endif
   629     return flowLength->mEndFlowOffset - mContentOffset;
   630   }
   632   nsTextFrame* nextBidi = static_cast<nsTextFrame*>(LastInFlow()->GetNextContinuation());
   633   int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
   635   if (!flowLength) {
   636     flowLength = new FlowLengthProperty;
   637     if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
   638                                         nsINode::DeleteProperty<FlowLengthProperty>))) {
   639       delete flowLength;
   640       flowLength = nullptr;
   641     }
   642   }
   643   if (flowLength) {
   644     flowLength->mStartOffset = mContentOffset;
   645     flowLength->mEndFlowOffset = endFlow;
   646   }
   648   return endFlow - mContentOffset;
   649 }
   651 // Smarter versions of dom::IsSpaceCharacter.
   652 // Unicode is really annoying; sometimes a space character isn't whitespace ---
   653 // when it combines with another character
   654 // So we have several versions of IsSpace for use in different contexts.
   656 static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos)
   657 {
   658   NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
   659   if (!aFrag->Is2b())
   660     return false;
   661   return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
   662     aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
   663 }
   665 // Check whether aPos is a space for CSS 'word-spacing' purposes
   666 static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag,
   667                                     uint32_t aPos, const nsStyleText* aStyleText)
   668 {
   669   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
   671   char16_t ch = aFrag->CharAt(aPos);
   672   switch (ch) {
   673   case ' ':
   674   case CH_NBSP:
   675     return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
   676   case '\r':
   677   case '\t': return !aStyleText->WhiteSpaceIsSignificant();
   678   case '\n': return !aStyleText->NewlineIsSignificant() &&
   679                     !aStyleText->NewlineIsDiscarded();
   680   default: return false;
   681   }
   682 }
   684 // Check whether the string aChars/aLength starts with space that's
   685 // trimmable according to CSS 'white-space:normal/nowrap'. 
   686 static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength)
   687 {
   688   NS_ASSERTION(aLength > 0, "No text for IsSpace!");
   690   char16_t ch = *aChars;
   691   if (ch == ' ')
   692     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
   693   return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
   694 }
   696 // Check whether the character aCh is trimmable according to CSS
   697 // 'white-space:normal/nowrap'
   698 static bool IsTrimmableSpace(char aCh)
   699 {
   700   return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
   701 }
   703 static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
   704                                const nsStyleText* aStyleText)
   705 {
   706   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
   708   switch (aFrag->CharAt(aPos)) {
   709   case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
   710                    !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
   711   case '\n': return !aStyleText->NewlineIsSignificant() &&
   712                     !aStyleText->NewlineIsDiscarded();
   713   case '\t':
   714   case '\r':
   715   case '\f': return !aStyleText->WhiteSpaceIsSignificant();
   716   default: return false;
   717   }
   718 }
   720 static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos)
   721 {
   722   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
   723   char16_t ch = aFrag->CharAt(aPos);
   724   if (ch == ' ' || ch == CH_NBSP)
   725     return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
   726   return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
   727 }
   729 // Count the amount of trimmable whitespace (as per CSS
   730 // 'white-space:normal/nowrap') in a text fragment. The first
   731 // character is at offset aStartOffset; the maximum number of characters
   732 // to check is aLength. aDirection is -1 or 1 depending on whether we should
   733 // progress backwards or forwards.
   734 static uint32_t
   735 GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
   736                             int32_t aStartOffset, int32_t aLength,
   737                             int32_t aDirection)
   738 {
   739   int32_t count = 0;
   740   if (aFrag->Is2b()) {
   741     const char16_t* str = aFrag->Get2b() + aStartOffset;
   742     int32_t fragLen = aFrag->GetLength() - aStartOffset;
   743     for (; count < aLength; ++count) {
   744       if (!IsTrimmableSpace(str, fragLen))
   745         break;
   746       str += aDirection;
   747       fragLen -= aDirection;
   748     }
   749   } else {
   750     const char* str = aFrag->Get1b() + aStartOffset;
   751     for (; count < aLength; ++count) {
   752       if (!IsTrimmableSpace(*str))
   753         break;
   754       str += aDirection;
   755     }
   756   }
   757   return count;
   758 }
   760 static bool
   761 IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline)
   762 {
   763   if (aFrag->Is2b())
   764     return false;
   765   int32_t len = aFrag->GetLength();
   766   const char* str = aFrag->Get1b();
   767   for (int32_t i = 0; i < len; ++i) {
   768     char ch = str[i];
   769     if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
   770       continue;
   771     return false;
   772   }
   773   return true;
   774 }
   776 static bool
   777 IsAllNewlines(const nsTextFragment* aFrag)
   778 {
   779   if (aFrag->Is2b())
   780     return false;
   781   int32_t len = aFrag->GetLength();
   782   const char* str = aFrag->Get1b();
   783   for (int32_t i = 0; i < len; ++i) {
   784     char ch = str[i];
   785     if (ch != '\n')
   786       return false;
   787   }
   788   return true;
   789 }
   791 static void
   792 CreateObserverForAnimatedGlyphs(nsTextFrame* aFrame, const nsTArray<gfxFont*>& aFonts)
   793 {
   794   if (!(aFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) {
   795     // Maybe the textrun was created for uninflated text.
   796     return;
   797   }
   799   nsTArray<nsAutoPtr<GlyphObserver> >* observers =
   800     new nsTArray<nsAutoPtr<GlyphObserver> >();
   801   for (uint32_t i = 0, count = aFonts.Length(); i < count; ++i) {
   802     observers->AppendElement(new GlyphObserver(aFonts[i], aFrame));
   803   }
   804   aFrame->Properties().Set(TextFrameGlyphObservers(), observers);
   805   // We are lazy and don't try to remove a property value that might be
   806   // obsolete due to style changes or font selection changes. That is
   807   // likely to be rarely needed, and we don't want to eat the overhead of
   808   // doing it for the overwhelmingly common case of no property existing.
   809   // (And we're out of state bits to conveniently use for a fast property
   810   // existence check.) The only downside is that in some rare cases we might
   811   // keep fonts alive for longer than necessary, or unnecessarily invalidate
   812   // frames.
   813 }
   815 static void
   816 CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun)
   817 {
   818   if (!aTextRun->GetUserData()) {
   819     return;
   820   }
   821   nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
   822   uint32_t numGlyphRuns;
   823   const gfxTextRun::GlyphRun* glyphRuns =
   824     aTextRun->GetGlyphRuns(&numGlyphRuns);
   825   for (uint32_t i = 0; i < numGlyphRuns; ++i) {
   826     gfxFont* font = glyphRuns[i].mFont;
   827     if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
   828       fontsWithAnimatedGlyphs.AppendElement(font);
   829     }
   830   }
   831   if (fontsWithAnimatedGlyphs.IsEmpty()) {
   832     return;
   833   }
   835   if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
   836     CreateObserverForAnimatedGlyphs(static_cast<nsTextFrame*>(
   837       static_cast<nsIFrame*>(aTextRun->GetUserData())), fontsWithAnimatedGlyphs);
   838   } else {
   839     TextRunUserData* userData =
   840       static_cast<TextRunUserData*>(aTextRun->GetUserData());
   841     for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
   842       CreateObserverForAnimatedGlyphs(userData->mMappedFlows[i].mStartFrame,
   843                                       fontsWithAnimatedGlyphs);
   844     }
   845   }
   846 }
   848 /**
   849  * This class accumulates state as we scan a paragraph of text. It detects
   850  * textrun boundaries (changes from text to non-text, hard
   851  * line breaks, and font changes) and builds a gfxTextRun at each boundary.
   852  * It also detects linebreaker run boundaries (changes from text to non-text,
   853  * and hard line breaks) and at each boundary runs the linebreaker to compute
   854  * potential line breaks. It also records actual line breaks to store them in
   855  * the textruns.
   856  */
   857 class BuildTextRunsScanner {
   858 public:
   859   BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext,
   860       nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) :
   861     mCurrentFramesAllSameTextRun(nullptr),
   862     mContext(aContext),
   863     mLineContainer(aLineContainer),
   864     mBidiEnabled(aPresContext->BidiEnabled()),
   865     mSkipIncompleteTextRuns(false),
   866     mWhichTextRun(aWhichTextRun),
   867     mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
   868     mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
   869     ResetRunInfo();
   870   }
   871   ~BuildTextRunsScanner() {
   872     NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
   873     NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared");
   874     NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
   875     NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
   876   }
   878   void SetAtStartOfLine() {
   879     mStartOfLine = true;
   880     mCanStopOnThisLine = false;
   881   }
   882   void SetSkipIncompleteTextRuns(bool aSkip) {
   883     mSkipIncompleteTextRuns = aSkip;
   884   }
   885   void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
   886     mCommonAncestorWithLastFrame = aFrame;
   887   }
   888   bool CanStopOnThisLine() {
   889     return mCanStopOnThisLine;
   890   }
   891   nsIFrame* GetCommonAncestorWithLastFrame() {
   892     return mCommonAncestorWithLastFrame;
   893   }
   894   void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
   895     if (mCommonAncestorWithLastFrame &&
   896         mCommonAncestorWithLastFrame->GetParent() == aFrame) {
   897       mCommonAncestorWithLastFrame = aFrame;
   898     }
   899   }
   900   void ScanFrame(nsIFrame* aFrame);
   901   bool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun);
   902   void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
   903   void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
   904   void ResetRunInfo() {
   905     mLastFrame = nullptr;
   906     mMappedFlows.Clear();
   907     mLineBreakBeforeFrames.Clear();
   908     mMaxTextLength = 0;
   909     mDoubleByteText = false;
   910   }
   911   void AccumulateRunInfo(nsTextFrame* aFrame);
   912   /**
   913    * @return null to indicate either textrun construction failed or
   914    * we constructed just a partial textrun to set up linebreaker and other
   915    * state for following textruns.
   916    */
   917   gfxTextRun* BuildTextRunForFrames(void* aTextBuffer);
   918   bool SetupLineBreakerContext(gfxTextRun *aTextRun);
   919   void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
   920   nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
   921   enum SetupBreakSinksFlags {
   922     SBS_DOUBLE_BYTE =      (1 << 0),
   923     SBS_EXISTING_TEXTRUN = (1 << 1),
   924     SBS_SUPPRESS_SINK    = (1 << 2)
   925   };
   926   void SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
   927                                  const void* aTextPtr,
   928                                  uint32_t    aFlags);
   929   struct FindBoundaryState {
   930     nsIFrame*    mStopAtFrame;
   931     nsTextFrame* mFirstTextFrame;
   932     nsTextFrame* mLastTextFrame;
   933     bool mSeenTextRunBoundaryOnLaterLine;
   934     bool mSeenTextRunBoundaryOnThisLine;
   935     bool mSeenSpaceForLineBreakingOnThisLine;
   936   };
   937   enum FindBoundaryResult {
   938     FB_CONTINUE,
   939     FB_STOPPED_AT_STOP_FRAME,
   940     FB_FOUND_VALID_TEXTRUN_BOUNDARY
   941   };
   942   FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
   944   bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
   946   // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
   947   // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
   948   // continuations starting from mStartFrame are a sequence of in-flow frames).
   949   struct MappedFlow {
   950     nsTextFrame* mStartFrame;
   951     nsTextFrame* mEndFrame;
   952     // When we consider breaking between elements, the nearest common
   953     // ancestor of the elements containing the characters is the one whose
   954     // CSS 'white-space' property governs. So this records the nearest common
   955     // ancestor of mStartFrame and the previous text frame, or null if there
   956     // was no previous text frame on this line.
   957     nsIFrame*    mAncestorControllingInitialBreak;
   959     int32_t GetContentEnd() {
   960       return mEndFrame ? mEndFrame->GetContentOffset()
   961           : mStartFrame->GetContent()->GetText()->GetLength();
   962     }
   963   };
   965   class BreakSink MOZ_FINAL : public nsILineBreakSink {
   966   public:
   967     BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, uint32_t aOffsetIntoTextRun,
   968               bool aExistingTextRun) :
   969                 mTextRun(aTextRun), mContext(aContext),
   970                 mOffsetIntoTextRun(aOffsetIntoTextRun),
   971                 mChangedBreaks(false), mExistingTextRun(aExistingTextRun) {}
   973     virtual void SetBreaks(uint32_t aOffset, uint32_t aLength,
   974                            uint8_t* aBreakBefore) MOZ_OVERRIDE {
   975       if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
   976                                            aBreakBefore, mContext)) {
   977         mChangedBreaks = true;
   978         // Be conservative and assume that some breaks have been set
   979         mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
   980       }
   981     }
   983     virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength,
   984                                    bool* aCapitalize) MOZ_OVERRIDE {
   985       NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED,
   986                    "Text run should be transformed!");
   987       nsTransformedTextRun* transformedTextRun =
   988         static_cast<nsTransformedTextRun*>(mTextRun);
   989       transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
   990                                             aCapitalize, mContext);
   991     }
   993     void Finish() {
   994       NS_ASSERTION(!(mTextRun->GetFlags() &
   995                      (gfxTextRunFactory::TEXT_UNUSED_FLAGS |
   996                       nsTextFrameUtils::TEXT_UNUSED_FLAG)),
   997                    "Flag set that should never be set! (memory safety error?)");
   998       if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
   999         nsTransformedTextRun* transformedTextRun =
  1000           static_cast<nsTransformedTextRun*>(mTextRun);
  1001         transformedTextRun->FinishSettingProperties(mContext);
  1003       // The way nsTransformedTextRun is implemented, its glyph runs aren't
  1004       // available until after nsTransformedTextRun::FinishSettingProperties()
  1005       // is called. So that's why we defer checking for animated glyphs to here.
  1006       CreateObserversForAnimatedGlyphs(mTextRun);
  1009     gfxTextRun*  mTextRun;
  1010     gfxContext*  mContext;
  1011     uint32_t     mOffsetIntoTextRun;
  1012     bool mChangedBreaks;
  1013     bool mExistingTextRun;
  1014   };
  1016 private:
  1017   nsAutoTArray<MappedFlow,10>   mMappedFlows;
  1018   nsAutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
  1019   nsAutoTArray<nsAutoPtr<BreakSink>,10> mBreakSinks;
  1020   nsAutoTArray<gfxTextRun*,5>   mTextRunsToDelete;
  1021   nsLineBreaker                 mLineBreaker;
  1022   gfxTextRun*                   mCurrentFramesAllSameTextRun;
  1023   gfxContext*                   mContext;
  1024   nsIFrame*                     mLineContainer;
  1025   nsTextFrame*                  mLastFrame;
  1026   // The common ancestor of the current frame and the previous leaf frame
  1027   // on the line, or null if there was no previous leaf frame.
  1028   nsIFrame*                     mCommonAncestorWithLastFrame;
  1029   // mMaxTextLength is an upper bound on the size of the text in all mapped frames
  1030   // The value UINT32_MAX represents overflow; text will be discarded
  1031   uint32_t                      mMaxTextLength;
  1032   bool                          mDoubleByteText;
  1033   bool                          mBidiEnabled;
  1034   bool                          mStartOfLine;
  1035   bool                          mSkipIncompleteTextRuns;
  1036   bool                          mCanStopOnThisLine;
  1037   nsTextFrame::TextRunType      mWhichTextRun;
  1038   uint8_t                       mNextRunContextInfo;
  1039   uint8_t                       mCurrentRunContextInfo;
  1040 };
  1042 static nsIFrame*
  1043 FindLineContainer(nsIFrame* aFrame)
  1045   while (aFrame && aFrame->CanContinueTextRun()) {
  1046     aFrame = aFrame->GetParent();
  1048   return aFrame;
  1051 static bool
  1052 IsLineBreakingWhiteSpace(char16_t aChar)
  1054   // 0x0A (\n) is not handled as white-space by the line breaker, since
  1055   // we break before it, if it isn't transformed to a normal space.
  1056   // (If we treat it as normal white-space then we'd only break after it.)
  1057   // However, it does induce a line break or is converted to a regular
  1058   // space, and either way it can be used to bound the region of text
  1059   // that needs to be analyzed for line breaking.
  1060   return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
  1063 static bool
  1064 TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength,
  1065                                   bool aIsDoubleByte)
  1067   if (aIsDoubleByte) {
  1068     const char16_t* chars = static_cast<const char16_t*>(aText);
  1069     for (uint32_t i = 0; i < aLength; ++i) {
  1070       if (IsLineBreakingWhiteSpace(chars[i]))
  1071         return true;
  1073     return false;
  1074   } else {
  1075     const uint8_t* chars = static_cast<const uint8_t*>(aText);
  1076     for (uint32_t i = 0; i < aLength; ++i) {
  1077       if (IsLineBreakingWhiteSpace(chars[i]))
  1078         return true;
  1080     return false;
  1084 struct FrameTextTraversal {
  1085   // These fields identify which frames should be recursively scanned
  1086   // The first normal frame to scan (or null, if no such frame should be scanned)
  1087   nsIFrame*    mFrameToScan;
  1088   // The first overflow frame to scan (or null, if no such frame should be scanned)
  1089   nsIFrame*    mOverflowFrameToScan;
  1090   // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
  1091   bool mScanSiblings;
  1093   // These identify the boundaries of the context required for
  1094   // line breaking or textrun construction
  1095   bool mLineBreakerCanCrossFrameBoundary;
  1096   bool mTextRunCanCrossFrameBoundary;
  1098   nsIFrame* NextFrameToScan() {
  1099     nsIFrame* f;
  1100     if (mFrameToScan) {
  1101       f = mFrameToScan;
  1102       mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
  1103     } else if (mOverflowFrameToScan) {
  1104       f = mOverflowFrameToScan;
  1105       mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
  1106     } else {
  1107       f = nullptr;
  1109     return f;
  1111 };
  1113 static FrameTextTraversal
  1114 CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType)
  1116   NS_ASSERTION(aType == aFrame->GetType(), "Wrong type");
  1118   FrameTextTraversal result;
  1120   bool continuesTextRun = aFrame->CanContinueTextRun();
  1121   if (aType == nsGkAtoms::placeholderFrame) {
  1122     // placeholders are "invisible", so a text run should be able to span
  1123     // across one. But don't descend into the out-of-flow.
  1124     result.mLineBreakerCanCrossFrameBoundary = true;
  1125     result.mOverflowFrameToScan = nullptr;
  1126     if (continuesTextRun) {
  1127       // ... Except for first-letter floats, which are really in-flow
  1128       // from the point of view of capitalization etc, so we'd better
  1129       // descend into them. But we actually need to break the textrun for
  1130       // first-letter floats since things look bad if, say, we try to make a
  1131       // ligature across the float boundary.
  1132       result.mFrameToScan =
  1133         (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
  1134       result.mScanSiblings = false;
  1135       result.mTextRunCanCrossFrameBoundary = false;
  1136     } else {
  1137       result.mFrameToScan = nullptr;
  1138       result.mTextRunCanCrossFrameBoundary = true;
  1140   } else {
  1141     if (continuesTextRun) {
  1142       result.mFrameToScan = aFrame->GetFirstPrincipalChild();
  1143       result.mOverflowFrameToScan =
  1144         aFrame->GetFirstChild(nsIFrame::kOverflowList);
  1145       NS_WARN_IF_FALSE(!result.mOverflowFrameToScan,
  1146                        "Scanning overflow inline frames is something we should avoid");
  1147       result.mScanSiblings = true;
  1148       result.mTextRunCanCrossFrameBoundary = true;
  1149       result.mLineBreakerCanCrossFrameBoundary = true;
  1150     } else {
  1151       result.mFrameToScan = nullptr;
  1152       result.mOverflowFrameToScan = nullptr;
  1153       result.mTextRunCanCrossFrameBoundary = false;
  1154       result.mLineBreakerCanCrossFrameBoundary = false;
  1157   return result;
  1160 BuildTextRunsScanner::FindBoundaryResult
  1161 BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
  1163   nsIAtom* frameType = aFrame->GetType();
  1164   nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame
  1165     ? static_cast<nsTextFrame*>(aFrame) : nullptr;
  1166   if (textFrame) {
  1167     if (aState->mLastTextFrame &&
  1168         textFrame != aState->mLastTextFrame->GetNextInFlow() &&
  1169         !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
  1170       aState->mSeenTextRunBoundaryOnThisLine = true;
  1171       if (aState->mSeenSpaceForLineBreakingOnThisLine)
  1172         return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
  1174     if (!aState->mFirstTextFrame) {
  1175       aState->mFirstTextFrame = textFrame;
  1177     aState->mLastTextFrame = textFrame;
  1180   if (aFrame == aState->mStopAtFrame)
  1181     return FB_STOPPED_AT_STOP_FRAME;
  1183   if (textFrame) {
  1184     if (!aState->mSeenSpaceForLineBreakingOnThisLine) {
  1185       const nsTextFragment* frag = textFrame->GetContent()->GetText();
  1186       uint32_t start = textFrame->GetContentOffset();
  1187       const void* text = frag->Is2b()
  1188           ? static_cast<const void*>(frag->Get2b() + start)
  1189           : static_cast<const void*>(frag->Get1b() + start);
  1190       if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(),
  1191                                             frag->Is2b())) {
  1192         aState->mSeenSpaceForLineBreakingOnThisLine = true;
  1193         if (aState->mSeenTextRunBoundaryOnLaterLine)
  1194           return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
  1197     return FB_CONTINUE; 
  1200   FrameTextTraversal traversal =
  1201     CanTextCrossFrameBoundary(aFrame, frameType);
  1202   if (!traversal.mTextRunCanCrossFrameBoundary) {
  1203     aState->mSeenTextRunBoundaryOnThisLine = true;
  1204     if (aState->mSeenSpaceForLineBreakingOnThisLine)
  1205       return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
  1208   for (nsIFrame* f = traversal.NextFrameToScan(); f;
  1209        f = traversal.NextFrameToScan()) {
  1210     FindBoundaryResult result = FindBoundaries(f, aState);
  1211     if (result != FB_CONTINUE)
  1212       return result;
  1215   if (!traversal.mTextRunCanCrossFrameBoundary) {
  1216     aState->mSeenTextRunBoundaryOnThisLine = true;
  1217     if (aState->mSeenSpaceForLineBreakingOnThisLine)
  1218       return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
  1221   return FB_CONTINUE;
  1224 // build text runs for the 200 lines following aForFrame, and stop after that
  1225 // when we get a chance.
  1226 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
  1228 /**
  1229  * General routine for building text runs. This is hairy because of the need
  1230  * to build text runs that span content nodes.
  1232  * @param aContext The gfxContext we're using to construct this text run.
  1233  * @param aForFrame The nsTextFrame for which we're building this text run.
  1234  * @param aLineContainer the line container containing aForFrame; if null,
  1235  *        we'll walk the ancestors to find it.  It's required to be non-null
  1236  *        when aForFrameLine is non-null.
  1237  * @param aForFrameLine the line containing aForFrame; if null, we'll figure
  1238  *        out the line (slowly)
  1239  * @param aWhichTextRun The type of text run we want to build. If font inflation
  1240  *        is enabled, this will be eInflated, otherwise it's eNotInflated.
  1241  */
  1242 static void
  1243 BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame,
  1244               nsIFrame* aLineContainer,
  1245               const nsLineList::iterator* aForFrameLine,
  1246               nsTextFrame::TextRunType aWhichTextRun)
  1248   NS_ASSERTION(aForFrame || aLineContainer,
  1249                "One of aForFrame or aLineContainer must be set!");
  1250   NS_ASSERTION(!aForFrameLine || aLineContainer,
  1251                "line but no line container");
  1253   nsIFrame* lineContainerChild = aForFrame;
  1254   if (!aLineContainer) {
  1255     if (aForFrame->IsFloatingFirstLetterChild()) {
  1256       lineContainerChild = aForFrame->PresContext()->PresShell()->
  1257         GetPlaceholderFrameFor(aForFrame->GetParent());
  1259     aLineContainer = FindLineContainer(lineContainerChild);
  1260   } else {
  1261     NS_ASSERTION(!aForFrame ||
  1262                  (aLineContainer == FindLineContainer(aForFrame) ||
  1263                   (aLineContainer->GetType() == nsGkAtoms::letterFrame &&
  1264                    aLineContainer->IsFloating())),
  1265                  "Wrong line container hint");
  1268   if (aForFrame) {
  1269     if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
  1270       aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
  1271       if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
  1272         aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
  1275     if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
  1276       aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
  1280   nsPresContext* presContext = aLineContainer->PresContext();
  1281   BuildTextRunsScanner scanner(presContext, aContext, aLineContainer,
  1282                                aWhichTextRun);
  1284   nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer);
  1286   if (!block) {
  1287     NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
  1288                  "Breakable non-block line containers not supported");
  1289     // Just loop through all the children of the linecontainer ... it's really
  1290     // just one line
  1291     scanner.SetAtStartOfLine();
  1292     scanner.SetCommonAncestorWithLastFrame(nullptr);
  1293     nsIFrame* child = aLineContainer->GetFirstPrincipalChild();
  1294     while (child) {
  1295       scanner.ScanFrame(child);
  1296       child = child->GetNextSibling();
  1298     // Set mStartOfLine so FlushFrames knows its textrun ends a line
  1299     scanner.SetAtStartOfLine();
  1300     scanner.FlushFrames(true, false);
  1301     return;
  1304   // Find the line containing 'lineContainerChild'.
  1306   bool isValid = true;
  1307   nsBlockInFlowLineIterator backIterator(block, &isValid);
  1308   if (aForFrameLine) {
  1309     backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
  1310   } else {
  1311     backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
  1312     NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
  1313     NS_ASSERTION(backIterator.GetContainer() == block,
  1314                  "Someone lied to us about the block");
  1316   nsBlockFrame::line_iterator startLine = backIterator.GetLine();
  1318   // Find a line where we can start building text runs. We choose the last line
  1319   // where:
  1320   // -- there is a textrun boundary between the start of the line and the
  1321   // start of aForFrame
  1322   // -- there is a space between the start of the line and the textrun boundary
  1323   // (this is so we can be sure the line breaks will be set properly
  1324   // on the textruns we construct).
  1325   // The possibly-partial text runs up to and including the first space
  1326   // are not reconstructed. We construct partial text runs for that text ---
  1327   // for the sake of simplifying the code and feeding the linebreaker ---
  1328   // but we discard them instead of assigning them to frames.
  1329   // This is a little awkward because we traverse lines in the reverse direction
  1330   // but we traverse the frames in each line in the forward direction.
  1331   nsBlockInFlowLineIterator forwardIterator = backIterator;
  1332   nsIFrame* stopAtFrame = lineContainerChild;
  1333   nsTextFrame* nextLineFirstTextFrame = nullptr;
  1334   bool seenTextRunBoundaryOnLaterLine = false;
  1335   bool mayBeginInTextRun = true;
  1336   while (true) {
  1337     forwardIterator = backIterator;
  1338     nsBlockFrame::line_iterator line = backIterator.GetLine();
  1339     if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
  1340       mayBeginInTextRun = false;
  1341       break;
  1344     BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr,
  1345       bool(seenTextRunBoundaryOnLaterLine), false, false };
  1346     nsIFrame* child = line->mFirstChild;
  1347     bool foundBoundary = false;
  1348     for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
  1349       BuildTextRunsScanner::FindBoundaryResult result =
  1350           scanner.FindBoundaries(child, &state);
  1351       if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
  1352         foundBoundary = true;
  1353         break;
  1354       } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
  1355         break;
  1357       child = child->GetNextSibling();
  1359     if (foundBoundary)
  1360       break;
  1361     if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
  1362         !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) {
  1363       // Found a usable textrun boundary at the end of the line
  1364       if (state.mSeenSpaceForLineBreakingOnThisLine)
  1365         break;
  1366       seenTextRunBoundaryOnLaterLine = true;
  1367     } else if (state.mSeenTextRunBoundaryOnThisLine) {
  1368       seenTextRunBoundaryOnLaterLine = true;
  1370     stopAtFrame = nullptr;
  1371     if (state.mFirstTextFrame) {
  1372       nextLineFirstTextFrame = state.mFirstTextFrame;
  1375   scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
  1377   // Now iterate over all text frames starting from the current line. First-in-flow
  1378   // text frames will be accumulated into textRunFrames as we go. When a
  1379   // text run boundary is required we flush textRunFrames ((re)building their
  1380   // gfxTextRuns as necessary).
  1381   bool seenStartLine = false;
  1382   uint32_t linesAfterStartLine = 0;
  1383   do {
  1384     nsBlockFrame::line_iterator line = forwardIterator.GetLine();
  1385     if (line->IsBlock())
  1386       break;
  1387     line->SetInvalidateTextRuns(false);
  1388     scanner.SetAtStartOfLine();
  1389     scanner.SetCommonAncestorWithLastFrame(nullptr);
  1390     nsIFrame* child = line->mFirstChild;
  1391     for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
  1392       scanner.ScanFrame(child);
  1393       child = child->GetNextSibling();
  1395     if (line.get() == startLine.get()) {
  1396       seenStartLine = true;
  1398     if (seenStartLine) {
  1399       ++linesAfterStartLine;
  1400       if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) {
  1401         // Don't flush frames; we may be in the middle of a textrun
  1402         // that we can't end here. That's OK, we just won't build it.
  1403         // Note that we must already have finished the textrun for aForFrame,
  1404         // because we've seen the end of a textrun in a line after the line
  1405         // containing aForFrame.
  1406         scanner.FlushLineBreaks(nullptr);
  1407         // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
  1408         // silences assertions in the scanner destructor.
  1409         scanner.ResetRunInfo();
  1410         return;
  1413   } while (forwardIterator.Next());
  1415   // Set mStartOfLine so FlushFrames knows its textrun ends a line
  1416   scanner.SetAtStartOfLine();
  1417   scanner.FlushFrames(true, false);
  1420 static char16_t*
  1421 ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount)
  1423   while (aCount) {
  1424     *aDest = *aSrc;
  1425     ++aDest;
  1426     ++aSrc;
  1427     --aCount;
  1429   return aDest;
  1432 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun)
  1434   if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW)
  1435     return mMappedFlows.Length() == 1 &&
  1436       mMappedFlows[0].mStartFrame == static_cast<nsTextFrame*>(aTextRun->GetUserData()) &&
  1437       mMappedFlows[0].mEndFrame == nullptr;
  1439   TextRunUserData* userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
  1440   if (userData->mMappedFlowCount != mMappedFlows.Length())
  1441     return false;
  1442   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
  1443     if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
  1444         int32_t(userData->mMappedFlows[i].mContentLength) !=
  1445             mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
  1446       return false;
  1448   return true;
  1451 /**
  1452  * This gets called when we need to make a text run for the current list of
  1453  * frames.
  1454  */
  1455 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak)
  1457   gfxTextRun* textRun = nullptr;
  1458   if (!mMappedFlows.IsEmpty()) {
  1459     if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
  1460         ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
  1461         ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) &&
  1462         ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0) ==
  1463         ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) &&
  1464         IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
  1465       // Optimization: We do not need to (re)build the textrun.
  1466       textRun = mCurrentFramesAllSameTextRun;
  1468       // Feed this run's text into the linebreaker to provide context.
  1469       if (!SetupLineBreakerContext(textRun)) {
  1470         return;
  1473       // Update mNextRunContextInfo appropriately
  1474       mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
  1475       if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) {
  1476         mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
  1478       if (textRun->GetFlags() & gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR) {
  1479         mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
  1481     } else {
  1482       AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
  1483       uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
  1484       if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
  1485           !buffer.AppendElements(bufferSize)) {
  1486         return;
  1488       textRun = BuildTextRunForFrames(buffer.Elements());
  1492   if (aFlushLineBreaks) {
  1493     FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun);
  1496   mCanStopOnThisLine = true;
  1497   ResetRunInfo();
  1500 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun)
  1502   bool trailingLineBreak;
  1503   nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
  1504   // textRun may be null for various reasons, including because we constructed
  1505   // a partial textrun just to get the linebreaker and other state set up
  1506   // to build the next textrun.
  1507   if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
  1508     aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
  1511   for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
  1512     if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
  1513       // TODO cause frames associated with the textrun to be reflowed, if they
  1514       // aren't being reflowed already!
  1516     mBreakSinks[i]->Finish();
  1518   mBreakSinks.Clear();
  1520   for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) {
  1521     gfxTextRun* deleteTextRun = mTextRunsToDelete[i];
  1522     gTextRuns->RemoveFromCache(deleteTextRun);
  1523     delete deleteTextRun;
  1525   mTextRunsToDelete.Clear();
  1528 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
  1530   if (mMaxTextLength != UINT32_MAX) {
  1531     NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow");
  1532     if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
  1533       mMaxTextLength = UINT32_MAX;
  1534     } else {
  1535       mMaxTextLength += aFrame->GetContentLength();
  1538   mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
  1539   mLastFrame = aFrame;
  1540   mCommonAncestorWithLastFrame = aFrame->GetParent();
  1542   MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
  1543   NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
  1544                mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
  1545                "Overlapping or discontiguous frames => BAD");
  1546   mappedFlow->mEndFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
  1547   if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
  1548     mCurrentFramesAllSameTextRun = nullptr;
  1551   if (mStartOfLine) {
  1552     mLineBreakBeforeFrames.AppendElement(aFrame);
  1553     mStartOfLine = false;
  1557 static nscoord StyleToCoord(const nsStyleCoord& aCoord)
  1559   if (eStyleUnit_Coord == aCoord.GetUnit()) {
  1560     return aCoord.GetCoordValue();
  1561   } else {
  1562     return 0;
  1566 static bool
  1567 HasTerminalNewline(const nsTextFrame* aFrame)
  1569   if (aFrame->GetContentLength() == 0)
  1570     return false;
  1571   const nsTextFragment* frag = aFrame->GetContent()->GetText();
  1572   return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
  1575 static nscoord
  1576 LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
  1578   if (aFrame->IsSVGText()) {
  1579     return 0;
  1581   if (!aStyleText) {
  1582     aStyleText = aFrame->StyleText();
  1584   return StyleToCoord(aStyleText->mLetterSpacing);
  1587 static nscoord
  1588 WordSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
  1590   if (aFrame->IsSVGText()) {
  1591     return 0;
  1593   if (!aStyleText) {
  1594     aStyleText = aFrame->StyleText();
  1596   return aStyleText->mWordSpacing;
  1599 bool
  1600 BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
  1602   // We don't need to check font size inflation, since
  1603   // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
  1604   // ensures that text runs never cross block boundaries.  This means
  1605   // that the font size inflation on all text frames in the text run is
  1606   // already guaranteed to be the same as each other (and for the line
  1607   // container).
  1608   if (mBidiEnabled &&
  1609       (NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2) ||
  1610        NS_GET_PARAGRAPH_DEPTH(aFrame1) != NS_GET_PARAGRAPH_DEPTH(aFrame2)))
  1611     return false;
  1613   nsStyleContext* sc1 = aFrame1->StyleContext();
  1614   const nsStyleText* textStyle1 = sc1->StyleText();
  1615   // If the first frame ends in a preformatted newline, then we end the textrun
  1616   // here. This avoids creating giant textruns for an entire plain text file.
  1617   // Note that we create a single text frame for a preformatted text node,
  1618   // even if it has newlines in it, so typically we won't see trailing newlines
  1619   // until after reflow has broken up the frame into one (or more) frames per
  1620   // line. That's OK though.
  1621   if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1))
  1622     return false;
  1624   if (aFrame1->GetContent() == aFrame2->GetContent() &&
  1625       aFrame1->GetNextInFlow() != aFrame2) {
  1626     // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
  1627     // sometimes when the unicode-bidi property is used; the bidi resolver
  1628     // breaks text into different frames even though the text has the same
  1629     // direction. We can't allow these two frames to share the same textrun
  1630     // because that would violate our invariant that two flows in the same
  1631     // textrun have different content elements.
  1632     return false;
  1635   nsStyleContext* sc2 = aFrame2->StyleContext();
  1636   const nsStyleText* textStyle2 = sc2->StyleText();
  1637   if (sc1 == sc2)
  1638     return true;
  1640   const nsStyleFont* fontStyle1 = sc1->StyleFont();
  1641   const nsStyleFont* fontStyle2 = sc2->StyleFont();
  1642   nscoord letterSpacing1 = LetterSpacing(aFrame1);
  1643   nscoord letterSpacing2 = LetterSpacing(aFrame2);
  1644   return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) &&
  1645     sc1->StyleFont()->mLanguage == sc2->StyleFont()->mLanguage &&
  1646     textStyle1->mTextTransform == textStyle2->mTextTransform &&
  1647     nsLayoutUtils::GetTextRunFlagsForStyle(sc1, fontStyle1, textStyle1, letterSpacing1) ==
  1648       nsLayoutUtils::GetTextRunFlagsForStyle(sc2, fontStyle2, textStyle2, letterSpacing2);
  1651 void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
  1653   // First check if we can extend the current mapped frame block. This is common.
  1654   if (mMappedFlows.Length() > 0) {
  1655     MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
  1656     if (mappedFlow->mEndFrame == aFrame &&
  1657         (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) {
  1658       NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
  1659                    "Flow-sibling of a text frame is not a text frame?");
  1661       // Don't do this optimization if mLastFrame has a terminal newline...
  1662       // it's quite likely preformatted and we might want to end the textrun here.
  1663       // This is almost always true:
  1664       if (mLastFrame->StyleContext() == aFrame->StyleContext() &&
  1665           !HasTerminalNewline(mLastFrame)) {
  1666         AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
  1667         return;
  1672   nsIAtom* frameType = aFrame->GetType();
  1673   // Now see if we can add a new set of frames to the current textrun
  1674   if (frameType == nsGkAtoms::textFrame) {
  1675     nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
  1677     if (mLastFrame) {
  1678       if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
  1679         FlushFrames(false, false);
  1680       } else {
  1681         if (mLastFrame->GetContent() == frame->GetContent()) {
  1682           AccumulateRunInfo(frame);
  1683           return;
  1688     MappedFlow* mappedFlow = mMappedFlows.AppendElement();
  1689     if (!mappedFlow)
  1690       return;
  1692     mappedFlow->mStartFrame = frame;
  1693     mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
  1695     AccumulateRunInfo(frame);
  1696     if (mMappedFlows.Length() == 1) {
  1697       mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
  1698       mCurrentRunContextInfo = mNextRunContextInfo;
  1700     return;
  1703   FrameTextTraversal traversal =
  1704     CanTextCrossFrameBoundary(aFrame, frameType);
  1705   bool isBR = frameType == nsGkAtoms::brFrame;
  1706   if (!traversal.mLineBreakerCanCrossFrameBoundary) {
  1707     // BR frames are special. We do not need or want to record a break opportunity
  1708     // before a BR frame.
  1709     FlushFrames(true, isBR);
  1710     mCommonAncestorWithLastFrame = aFrame;
  1711     mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
  1712     mStartOfLine = false;
  1713   } else if (!traversal.mTextRunCanCrossFrameBoundary) {
  1714     FlushFrames(false, false);
  1717   for (nsIFrame* f = traversal.NextFrameToScan(); f;
  1718        f = traversal.NextFrameToScan()) {
  1719     ScanFrame(f);
  1722   if (!traversal.mLineBreakerCanCrossFrameBoundary) {
  1723     // Really if we're a BR frame this is unnecessary since descendInto will be
  1724     // false. In fact this whole "if" statement should move into the descendInto.
  1725     FlushFrames(true, isBR);
  1726     mCommonAncestorWithLastFrame = aFrame;
  1727     mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
  1728   } else if (!traversal.mTextRunCanCrossFrameBoundary) {
  1729     FlushFrames(false, false);
  1732   LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
  1735 nsTextFrame*
  1736 BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex)
  1738   uint32_t index = *aIndex;
  1739   if (index >= mLineBreakBeforeFrames.Length())
  1740     return nullptr;
  1741   *aIndex = index + 1;
  1742   return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
  1745 static uint32_t
  1746 GetSpacingFlags(nscoord spacing)
  1748   return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0;
  1751 static gfxFontGroup*
  1752 GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation,
  1753                      nsFontMetrics** aOutFontMetrics = nullptr)
  1755   if (aOutFontMetrics)
  1756     *aOutFontMetrics = nullptr;
  1758   nsRefPtr<nsFontMetrics> metrics;
  1759   nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics),
  1760                                         aFontSizeInflation);
  1762   if (!metrics)
  1763     return nullptr;
  1765   if (aOutFontMetrics) {
  1766     *aOutFontMetrics = metrics;
  1767     NS_ADDREF(*aOutFontMetrics);
  1769   // XXX this is a bit bogus, we're releasing 'metrics' so the
  1770   // returned font-group might actually be torn down, although because
  1771   // of the way the device context caches font metrics, this seems to
  1772   // not actually happen. But we should fix this.
  1773   return metrics->GetThebesFontGroup();
  1776 static already_AddRefed<gfxContext>
  1777 CreateReferenceThebesContext(nsTextFrame* aTextFrame)
  1779   nsRefPtr<nsRenderingContext> tmp =
  1780     aTextFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
  1782   nsRefPtr<gfxContext> ctx = tmp->ThebesContext();
  1783   return ctx.forget();
  1786 /**
  1787  * The returned textrun must be deleted when no longer needed.
  1788  */
  1789 static gfxTextRun*
  1790 GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame)
  1792   nsRefPtr<gfxContext> ctx = aContext;
  1793   if (!ctx) {
  1794     ctx = CreateReferenceThebesContext(aTextFrame);
  1796   if (!ctx)
  1797     return nullptr;
  1799   return aTextRun->GetFontGroup()->
  1800     MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit());
  1803 static gfxFont::Metrics
  1804 GetFirstFontMetrics(gfxFontGroup* aFontGroup)
  1806   if (!aFontGroup)
  1807     return gfxFont::Metrics();
  1808   gfxFont* font = aFontGroup->GetFontAt(0);
  1809   if (!font)
  1810     return gfxFont::Metrics();
  1811   return font->GetMetrics();
  1814 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
  1815 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
  1816 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
  1817 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
  1818 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
  1819 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES == 5);
  1821 static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] =
  1823   nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal
  1824   nsTextFrameUtils::COMPRESS_NONE,               // pre
  1825   nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap
  1826   nsTextFrameUtils::COMPRESS_NONE,               // pre-wrap
  1827   nsTextFrameUtils::COMPRESS_WHITESPACE,         // pre-line
  1828   nsTextFrameUtils::DISCARD_NEWLINE              // -moz-pre-discard-newlines
  1829 };
  1831 gfxTextRun*
  1832 BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
  1834   gfxSkipChars skipChars;
  1836   const void* textPtr = aTextBuffer;
  1837   bool anySmallcapsStyle = false;
  1838   bool anyTextTransformStyle = false;
  1839   bool anyMathMLStyling = false;
  1840   uint8_t sstyScriptLevel = 0;
  1841   uint32_t textFlags = nsTextFrameUtils::TEXT_NO_BREAKS;
  1843   if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
  1844     textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
  1846   if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
  1847     textFlags |= gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR;
  1850   nsAutoTArray<int32_t,50> textBreakPoints;
  1851   TextRunUserData dummyData;
  1852   TextRunMappedFlow dummyMappedFlow;
  1854   TextRunUserData* userData;
  1855   TextRunUserData* userDataToDestroy;
  1856   // If the situation is particularly simple (and common) we don't need to
  1857   // allocate userData.
  1858   if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
  1859       mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
  1860     userData = &dummyData;
  1861     userDataToDestroy = nullptr;
  1862     dummyData.mMappedFlows = &dummyMappedFlow;
  1863   } else {
  1864     userData = static_cast<TextRunUserData*>
  1865       (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
  1866     userDataToDestroy = userData;
  1867     userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
  1869   userData->mMappedFlowCount = mMappedFlows.Length();
  1870   userData->mLastFlowIndex = 0;
  1872   uint32_t currentTransformedTextOffset = 0;
  1874   uint32_t nextBreakIndex = 0;
  1875   nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
  1876   bool enabledJustification = mLineContainer &&
  1877     (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
  1878      mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) &&
  1879     !mLineContainer->IsSVGText();
  1881   // for word-break style
  1882   switch (mLineContainer->StyleText()->mWordBreak) {
  1883     case NS_STYLE_WORDBREAK_BREAK_ALL:
  1884       mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_BreakAll);
  1885       break;
  1886     case NS_STYLE_WORDBREAK_KEEP_ALL:
  1887       mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_KeepAll);
  1888       break;
  1889     default:
  1890       mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_Normal);
  1891       break;
  1894   const nsStyleText* textStyle = nullptr;
  1895   const nsStyleFont* fontStyle = nullptr;
  1896   nsStyleContext* lastStyleContext = nullptr;
  1897   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
  1898     MappedFlow* mappedFlow = &mMappedFlows[i];
  1899     nsTextFrame* f = mappedFlow->mStartFrame;
  1901     lastStyleContext = f->StyleContext();
  1902     // Detect use of text-transform or font-variant anywhere in the run
  1903     textStyle = f->StyleText();
  1904     if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) {
  1905       anyTextTransformStyle = true;
  1907     textFlags |= GetSpacingFlags(LetterSpacing(f));
  1908     textFlags |= GetSpacingFlags(WordSpacing(f));
  1909     nsTextFrameUtils::CompressionMode compression =
  1910       CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
  1911     if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) {
  1912       textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
  1914     fontStyle = f->StyleFont();
  1915     if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) {
  1916       anySmallcapsStyle = true;
  1918     if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
  1919       anyMathMLStyling = true;
  1920     } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) {
  1921       textFlags |= nsTextFrameUtils::TEXT_IS_SINGLE_CHAR_MI;
  1922       anyMathMLStyling = true;
  1924     nsIFrame* parent = mLineContainer->GetParent();
  1925     if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
  1926       // All MathML tokens except <mtext> use 'math' script.
  1927       if (!(parent && parent->GetContent() &&
  1928           parent->GetContent()->Tag() == nsGkAtoms::mtext_)) {
  1929         textFlags |= gfxTextRunFactory::TEXT_USE_MATH_SCRIPT;
  1932     nsIFrame* child = mLineContainer;
  1933     uint8_t oldScriptLevel = 0;
  1934     while (parent && 
  1935            child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
  1936       // Reconstruct the script level ignoring any user overrides. It is
  1937       // calculated this way instead of using scriptlevel to ensure the 
  1938       // correct ssty font feature setting is used even if the user sets a
  1939       // different (especially negative) scriptlevel.
  1940       nsIMathMLFrame* mathFrame= do_QueryFrame(parent);
  1941       if (mathFrame) {
  1942         sstyScriptLevel += mathFrame->ScriptIncrement(child);
  1944       if (sstyScriptLevel < oldScriptLevel) {
  1945         // overflow
  1946         sstyScriptLevel = UINT8_MAX;
  1947         break;
  1949       child = parent;
  1950       parent = parent->GetParent();
  1951       oldScriptLevel = sstyScriptLevel;
  1953     if (sstyScriptLevel) {
  1954       anyMathMLStyling = true;
  1957     // Figure out what content is included in this flow.
  1958     nsIContent* content = f->GetContent();
  1959     const nsTextFragment* frag = content->GetText();
  1960     int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
  1961     int32_t contentEnd = mappedFlow->GetContentEnd();
  1962     int32_t contentLength = contentEnd - contentStart;
  1964     TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
  1965     newFlow->mStartFrame = mappedFlow->mStartFrame;
  1966     newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
  1967       mappedFlow->mStartFrame->GetContentOffset();
  1968     newFlow->mContentLength = contentLength;
  1970     while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
  1971       textBreakPoints.AppendElement(
  1972           nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
  1973       nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
  1976     uint32_t analysisFlags;
  1977     if (frag->Is2b()) {
  1978       NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
  1979       char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
  1980       char16_t* bufEnd = nsTextFrameUtils::TransformText(
  1981           frag->Get2b() + contentStart, contentLength, bufStart,
  1982           compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
  1983       aTextBuffer = bufEnd;
  1984       currentTransformedTextOffset = bufEnd - static_cast<const char16_t*>(textPtr);
  1985     } else {
  1986       if (mDoubleByteText) {
  1987         // Need to expand the text. First transform it into a temporary buffer,
  1988         // then expand.
  1989         AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
  1990         uint8_t* bufStart = tempBuf.AppendElements(contentLength);
  1991         if (!bufStart) {
  1992           DestroyUserData(userDataToDestroy);
  1993           return nullptr;
  1995         uint8_t* end = nsTextFrameUtils::TransformText(
  1996             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
  1997             bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
  1998         aTextBuffer = ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
  1999                                    tempBuf.Elements(), end - tempBuf.Elements());
  2000         currentTransformedTextOffset =
  2001           static_cast<char16_t*>(aTextBuffer) - static_cast<const char16_t*>(textPtr);
  2002       } else {
  2003         uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
  2004         uint8_t* end = nsTextFrameUtils::TransformText(
  2005             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
  2006             bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
  2007         aTextBuffer = end;
  2008         currentTransformedTextOffset = end - static_cast<const uint8_t*>(textPtr);
  2011     textFlags |= analysisFlags;
  2014   void* finalUserData;
  2015   if (userData == &dummyData) {
  2016     textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW;
  2017     userData = nullptr;
  2018     finalUserData = mMappedFlows[0].mStartFrame;
  2019   } else {
  2020     finalUserData = userData;
  2023   uint32_t transformedLength = currentTransformedTextOffset;
  2025   // Now build the textrun
  2026   nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
  2027   float fontInflation;
  2028   if (mWhichTextRun == nsTextFrame::eNotInflated) {
  2029     fontInflation = 1.0f;
  2030   } else {
  2031     fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
  2034   gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
  2035   if (!fontGroup) {
  2036     DestroyUserData(userDataToDestroy);
  2037     return nullptr;
  2040   if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
  2041     textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
  2043   if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
  2044     textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
  2046   if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) {
  2047     textFlags |= gfxTextRunFactory::TEXT_IS_RTL;
  2049   if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
  2050     textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE;
  2052   if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
  2053     textFlags |= gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR;
  2055   // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
  2056   // frame's style is used, so we use a mixture of the first frame and
  2057   // last frame's style
  2058   textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext,
  2059       fontStyle, textStyle, LetterSpacing(firstFrame, textStyle));
  2060   // XXX this is a bit of a hack. For performance reasons, if we're favouring
  2061   // performance over quality, don't try to get accurate glyph extents.
  2062   if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) {
  2063     textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
  2066   // Convert linebreak coordinates to transformed string offsets
  2067   NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
  2068                "Didn't find all the frames to break-before...");
  2069   gfxSkipCharsIterator iter(skipChars);
  2070   nsAutoTArray<uint32_t,50> textBreakPointsAfterTransform;
  2071   for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
  2072     nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, 
  2073             iter.ConvertOriginalToSkipped(textBreakPoints[i]));
  2075   if (mStartOfLine) {
  2076     nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
  2077                                             transformedLength);
  2080   // Setup factory chain
  2081   nsAutoPtr<nsTransformingTextRunFactory> transformingFactory;
  2082   if (anySmallcapsStyle) {
  2083     transformingFactory = new nsFontVariantTextRunFactory();
  2085   if (anyTextTransformStyle) {
  2086     transformingFactory =
  2087       new nsCaseTransformTextRunFactory(transformingFactory.forget());
  2089   if (anyMathMLStyling) {
  2090     transformingFactory =
  2091       new MathMLTextRunFactory(transformingFactory.forget(), sstyScriptLevel);
  2093   nsTArray<nsStyleContext*> styles;
  2094   if (transformingFactory) {
  2095     iter.SetOriginalOffset(0);
  2096     for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
  2097       MappedFlow* mappedFlow = &mMappedFlows[i];
  2098       nsTextFrame* f;
  2099       for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
  2100            f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
  2101         uint32_t offset = iter.GetSkippedOffset();
  2102         iter.AdvanceOriginal(f->GetContentLength());
  2103         uint32_t end = iter.GetSkippedOffset();
  2104         nsStyleContext* sc = f->StyleContext();
  2105         uint32_t j;
  2106         for (j = offset; j < end; ++j) {
  2107           styles.AppendElement(sc);
  2111     textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED;
  2112     NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
  2113                  "We didn't cover all the characters in the text run!");
  2116   gfxTextRun* textRun;
  2117   gfxTextRunFactory::Parameters params =
  2118       { mContext, finalUserData, &skipChars,
  2119         textBreakPointsAfterTransform.Elements(),
  2120         textBreakPointsAfterTransform.Length(),
  2121         int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
  2123   if (mDoubleByteText) {
  2124     const char16_t* text = static_cast<const char16_t*>(textPtr);
  2125     if (transformingFactory) {
  2126       textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
  2127                                                  fontGroup, textFlags, styles.Elements());
  2128       if (textRun) {
  2129         // ownership of the factory has passed to the textrun
  2130         transformingFactory.forget();
  2132     } else {
  2133       textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
  2135   } else {
  2136     const uint8_t* text = static_cast<const uint8_t*>(textPtr);
  2137     textFlags |= gfxFontGroup::TEXT_IS_8BIT;
  2138     if (transformingFactory) {
  2139       textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
  2140                                                  fontGroup, textFlags, styles.Elements());
  2141       if (textRun) {
  2142         // ownership of the factory has passed to the textrun
  2143         transformingFactory.forget();
  2145     } else {
  2146       textRun = MakeTextRun(text, transformedLength, fontGroup, &params, textFlags);
  2149   if (!textRun) {
  2150     DestroyUserData(userDataToDestroy);
  2151     return nullptr;
  2154   // We have to set these up after we've created the textrun, because
  2155   // the breaks may be stored in the textrun during this very call.
  2156   // This is a bit annoying because it requires another loop over the frames
  2157   // making up the textrun, but I don't see a way to avoid this.
  2158   uint32_t flags = 0;
  2159   if (mDoubleByteText) {
  2160     flags |= SBS_DOUBLE_BYTE;
  2162   if (mSkipIncompleteTextRuns) {
  2163     flags |= SBS_SUPPRESS_SINK;
  2165   SetupBreakSinksForTextRun(textRun, textPtr, flags);
  2167   if (mSkipIncompleteTextRuns) {
  2168     mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
  2169         transformedLength, mDoubleByteText);
  2170     // Arrange for this textrun to be deleted the next time the linebreaker
  2171     // is flushed out
  2172     mTextRunsToDelete.AppendElement(textRun);
  2173     // Since we're doing to destroy the user data now, avoid a dangling
  2174     // pointer. Strictly speaking we don't need to do this since it should
  2175     // not be used (since this textrun will not be used and will be
  2176     // itself deleted soon), but it's always better to not have dangling
  2177     // pointers around.
  2178     textRun->SetUserData(nullptr);
  2179     DestroyUserData(userDataToDestroy);
  2180     return nullptr;
  2183   // Actually wipe out the textruns associated with the mapped frames and associate
  2184   // those frames with this text run.
  2185   AssignTextRun(textRun, fontInflation);
  2186   return textRun;
  2189 // This is a cut-down version of BuildTextRunForFrames used to set up
  2190 // context for the line-breaker, when the textrun has already been created.
  2191 // So it does the same walk over the mMappedFlows, but doesn't actually
  2192 // build a new textrun.
  2193 bool
  2194 BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
  2196   AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
  2197   uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
  2198   if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
  2199     return false;
  2201   void *textPtr = buffer.AppendElements(bufferSize);
  2202   if (!textPtr) {
  2203     return false;
  2206   gfxSkipChars skipChars;
  2208   nsAutoTArray<int32_t,50> textBreakPoints;
  2209   TextRunUserData dummyData;
  2210   TextRunMappedFlow dummyMappedFlow;
  2212   TextRunUserData* userData;
  2213   TextRunUserData* userDataToDestroy;
  2214   // If the situation is particularly simple (and common) we don't need to
  2215   // allocate userData.
  2216   if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
  2217       mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
  2218     userData = &dummyData;
  2219     userDataToDestroy = nullptr;
  2220     dummyData.mMappedFlows = &dummyMappedFlow;
  2221   } else {
  2222     userData = static_cast<TextRunUserData*>
  2223       (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
  2224     userDataToDestroy = userData;
  2225     userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
  2227   userData->mMappedFlowCount = mMappedFlows.Length();
  2228   userData->mLastFlowIndex = 0;
  2230   uint32_t nextBreakIndex = 0;
  2231   nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
  2233   const nsStyleText* textStyle = nullptr;
  2234   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
  2235     MappedFlow* mappedFlow = &mMappedFlows[i];
  2236     nsTextFrame* f = mappedFlow->mStartFrame;
  2238     textStyle = f->StyleText();
  2239     nsTextFrameUtils::CompressionMode compression =
  2240       CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
  2242     // Figure out what content is included in this flow.
  2243     nsIContent* content = f->GetContent();
  2244     const nsTextFragment* frag = content->GetText();
  2245     int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
  2246     int32_t contentEnd = mappedFlow->GetContentEnd();
  2247     int32_t contentLength = contentEnd - contentStart;
  2249     TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
  2250     newFlow->mStartFrame = mappedFlow->mStartFrame;
  2251     newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
  2252       mappedFlow->mStartFrame->GetContentOffset();
  2253     newFlow->mContentLength = contentLength;
  2255     while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
  2256       textBreakPoints.AppendElement(
  2257           nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
  2258       nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
  2261     uint32_t analysisFlags;
  2262     if (frag->Is2b()) {
  2263       NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
  2264       char16_t* bufStart = static_cast<char16_t*>(textPtr);
  2265       char16_t* bufEnd = nsTextFrameUtils::TransformText(
  2266           frag->Get2b() + contentStart, contentLength, bufStart,
  2267           compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
  2268       textPtr = bufEnd;
  2269     } else {
  2270       if (mDoubleByteText) {
  2271         // Need to expand the text. First transform it into a temporary buffer,
  2272         // then expand.
  2273         AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
  2274         uint8_t* bufStart = tempBuf.AppendElements(contentLength);
  2275         if (!bufStart) {
  2276           DestroyUserData(userDataToDestroy);
  2277           return false;
  2279         uint8_t* end = nsTextFrameUtils::TransformText(
  2280             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
  2281             bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
  2282         textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
  2283                                tempBuf.Elements(), end - tempBuf.Elements());
  2284       } else {
  2285         uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
  2286         uint8_t* end = nsTextFrameUtils::TransformText(
  2287             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
  2288             bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
  2289         textPtr = end;
  2294   // We have to set these up after we've created the textrun, because
  2295   // the breaks may be stored in the textrun during this very call.
  2296   // This is a bit annoying because it requires another loop over the frames
  2297   // making up the textrun, but I don't see a way to avoid this.
  2298   uint32_t flags = 0;
  2299   if (mDoubleByteText) {
  2300     flags |= SBS_DOUBLE_BYTE;
  2302   if (mSkipIncompleteTextRuns) {
  2303     flags |= SBS_SUPPRESS_SINK;
  2305   SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), flags);
  2307   DestroyUserData(userDataToDestroy);
  2309   return true;
  2312 static bool
  2313 HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
  2314                                int32_t aContentEndOffset,
  2315                                const gfxSkipCharsIterator& aIterator)
  2317   if (!aIterator.IsOriginalCharSkipped())
  2318     return false;
  2320   gfxSkipCharsIterator iter = aIterator;
  2321   int32_t frameContentOffset = aFrame->GetContentOffset();
  2322   const nsTextFragment* frag = aFrame->GetContent()->GetText();
  2323   while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
  2324     if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
  2325       return true;
  2326     ++frameContentOffset;
  2327     iter.AdvanceOriginal(1);
  2329   return false;
  2332 void
  2333 BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
  2334                                                 const void* aTextPtr,
  2335                                                 uint32_t    aFlags)
  2337   // textruns have uniform language
  2338   const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont();
  2339   // We should only use a language for hyphenation if it was specified
  2340   // explicitly.
  2341   nsIAtom* hyphenationLanguage =
  2342     styleFont->mExplicitLanguage ? styleFont->mLanguage : nullptr;
  2343   // We keep this pointed at the skip-chars data for the current mappedFlow.
  2344   // This lets us cheaply check whether the flow has compressed initial
  2345   // whitespace...
  2346   gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
  2348   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
  2349     MappedFlow* mappedFlow = &mMappedFlows[i];
  2350     uint32_t offset = iter.GetSkippedOffset();
  2351     gfxSkipCharsIterator iterNext = iter;
  2352     iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
  2353             mappedFlow->mStartFrame->GetContentOffset());
  2355     nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
  2356       new BreakSink(aTextRun, mContext, offset,
  2357                     (aFlags & SBS_EXISTING_TEXTRUN) != 0));
  2358     if (!breakSink || !*breakSink)
  2359       return;
  2361     uint32_t length = iterNext.GetSkippedOffset() - offset;
  2362     uint32_t flags = 0;
  2363     nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
  2364     if (!initialBreakController) {
  2365       initialBreakController = mLineContainer;
  2367     if (!initialBreakController->StyleText()->
  2368                                  WhiteSpaceCanWrap(initialBreakController)) {
  2369       flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
  2371     nsTextFrame* startFrame = mappedFlow->mStartFrame;
  2372     const nsStyleText* textStyle = startFrame->StyleText();
  2373     if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
  2374       flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
  2376     if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
  2377       flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
  2379     if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
  2380       flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
  2382     if (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
  2383       flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
  2386     if (HasCompressedLeadingWhitespace(startFrame, textStyle,
  2387                                        mappedFlow->GetContentEnd(), iter)) {
  2388       mLineBreaker.AppendInvisibleWhitespace(flags);
  2391     if (length > 0) {
  2392       BreakSink* sink =
  2393         (aFlags & SBS_SUPPRESS_SINK) ? nullptr : (*breakSink).get();
  2394       if (aFlags & SBS_DOUBLE_BYTE) {
  2395         const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
  2396         mLineBreaker.AppendText(hyphenationLanguage, text + offset,
  2397                                 length, flags, sink);
  2398       } else {
  2399         const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
  2400         mLineBreaker.AppendText(hyphenationLanguage, text + offset,
  2401                                 length, flags, sink);
  2405     iter = iterNext;
  2409 // Find the flow corresponding to aContent in aUserData
  2410 static inline TextRunMappedFlow*
  2411 FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent)
  2413   // Find the flow that contains us
  2414   int32_t i = aUserData->mLastFlowIndex;
  2415   int32_t delta = 1;
  2416   int32_t sign = 1;
  2417   // Search starting at the current position and examine close-by
  2418   // positions first, moving further and further away as we go.
  2419   while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
  2420     TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
  2421     if (flow->mStartFrame->GetContent() == aContent) {
  2422       return flow;
  2425     i += delta;
  2426     sign = -sign;
  2427     delta = -delta + sign;
  2430   // We ran into an array edge.  Add |delta| to |i| once more to get
  2431   // back to the side where we still need to search, then step in
  2432   // the |sign| direction.
  2433   i += delta;
  2434   if (sign > 0) {
  2435     for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
  2436       TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
  2437       if (flow->mStartFrame->GetContent() == aContent) {
  2438         return flow;
  2441   } else {
  2442     for (; i >= 0; --i) {
  2443       TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
  2444       if (flow->mStartFrame->GetContent() == aContent) {
  2445         return flow;
  2450   return nullptr;
  2453 void
  2454 BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation)
  2456   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
  2457     MappedFlow* mappedFlow = &mMappedFlows[i];
  2458     nsTextFrame* startFrame = mappedFlow->mStartFrame;
  2459     nsTextFrame* endFrame = mappedFlow->mEndFrame;
  2460     nsTextFrame* f;
  2461     for (f = startFrame; f != endFrame;
  2462          f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
  2463 #ifdef DEBUG_roc
  2464       if (f->GetTextRun(mWhichTextRun)) {
  2465         gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
  2466         if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
  2467           if (mMappedFlows[0].mStartFrame != static_cast<nsTextFrame*>(textRun->GetUserData())) {
  2468             NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
  2470         } else {
  2471           TextRunUserData* userData =
  2472             static_cast<TextRunUserData*>(textRun->GetUserData());
  2474           if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
  2475               userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
  2476               mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) {
  2477             NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
  2481 #endif
  2483       gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
  2484       if (oldTextRun) {
  2485         nsTextFrame* firstFrame = nullptr;
  2486         uint32_t startOffset = 0;
  2487         if (oldTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
  2488           firstFrame = static_cast<nsTextFrame*>(oldTextRun->GetUserData());
  2490         else {
  2491           TextRunUserData* userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData());
  2492           firstFrame = userData->mMappedFlows[0].mStartFrame;
  2493           if (MOZ_UNLIKELY(f != firstFrame)) {
  2494             TextRunMappedFlow* flow = FindFlowForContent(userData, f->GetContent());
  2495             if (flow) {
  2496               startOffset = flow->mDOMOffsetToBeforeTransformOffset;
  2498             else {
  2499               NS_ERROR("Can't find flow containing frame 'f'");
  2504         // Optimization: if |f| is the first frame in the flow then there are no
  2505         // prev-continuations that use |oldTextRun|.
  2506         nsTextFrame* clearFrom = nullptr;
  2507         if (MOZ_UNLIKELY(f != firstFrame)) {
  2508           // If all the frames in the mapped flow starting at |f| (inclusive)
  2509           // are empty then we let the prev-continuations keep the old text run.
  2510           gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset());
  2511           uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
  2512           clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
  2514         f->ClearTextRun(clearFrom, mWhichTextRun);
  2516 #ifdef DEBUG
  2517         if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
  2518           // oldTextRun was destroyed - assert that we don't reference it.
  2519           for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
  2520             NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
  2521                          "destroyed text run is still in use");
  2524 #endif
  2526       f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
  2528     // Set this bit now; we can't set it any earlier because
  2529     // f->ClearTextRun() might clear it out.
  2530     nsFrameState whichTextRunState =
  2531       startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
  2532         ? TEXT_IN_TEXTRUN_USER_DATA
  2533         : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
  2534     startFrame->AddStateBits(whichTextRunState);
  2538 NS_QUERYFRAME_HEAD(nsTextFrame)
  2539   NS_QUERYFRAME_ENTRY(nsTextFrame)
  2540 NS_QUERYFRAME_TAIL_INHERITING(nsTextFrameBase)
  2542 gfxSkipCharsIterator
  2543 nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun,
  2544                            gfxContext* aReferenceContext,
  2545                            nsIFrame* aLineContainer,
  2546                            const nsLineList::iterator* aLine,
  2547                            uint32_t* aFlowEndInTextRun)
  2549   gfxTextRun *textRun = GetTextRun(aWhichTextRun);
  2550   if (textRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) {
  2551     if (textRun->GetExpirationState()->IsTracked()) {
  2552       gTextRuns->MarkUsed(textRun);
  2554   } else {
  2555     nsRefPtr<gfxContext> ctx = aReferenceContext;
  2556     if (!ctx) {
  2557       ctx = CreateReferenceThebesContext(this);
  2559     if (ctx) {
  2560       BuildTextRuns(ctx, this, aLineContainer, aLine, aWhichTextRun);
  2562     textRun = GetTextRun(aWhichTextRun);
  2563     if (!textRun) {
  2564       // A text run was not constructed for this frame. This is bad. The caller
  2565       // will check mTextRun.
  2566       static const gfxSkipChars emptySkipChars;
  2567       return gfxSkipCharsIterator(emptySkipChars, 0);
  2569     TabWidthStore* tabWidths =
  2570       static_cast<TabWidthStore*>(Properties().Get(TabWidthProperty()));
  2571     if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
  2572       Properties().Delete(TabWidthProperty());
  2576   if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
  2577     if (aFlowEndInTextRun) {
  2578       *aFlowEndInTextRun = textRun->GetLength();
  2580     return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
  2583   TextRunUserData* userData = static_cast<TextRunUserData*>(textRun->GetUserData());
  2584   TextRunMappedFlow* flow = FindFlowForContent(userData, mContent);
  2585   if (flow) {
  2586     // Since textruns can only contain one flow for a given content element,
  2587     // this must be our flow.
  2588     uint32_t flowIndex = flow - userData->mMappedFlows;
  2589     userData->mLastFlowIndex = flowIndex;
  2590     gfxSkipCharsIterator iter(textRun->GetSkipChars(),
  2591                               flow->mDOMOffsetToBeforeTransformOffset, mContentOffset);
  2592     if (aFlowEndInTextRun) {
  2593       if (flowIndex + 1 < userData->mMappedFlowCount) {
  2594         gfxSkipCharsIterator end(textRun->GetSkipChars());
  2595         *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
  2596               flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset);
  2597       } else {
  2598         *aFlowEndInTextRun = textRun->GetLength();
  2601     return iter;
  2604   NS_ERROR("Can't find flow containing this frame???");
  2605   static const gfxSkipChars emptySkipChars;
  2606   return gfxSkipCharsIterator(emptySkipChars, 0);
  2609 static uint32_t
  2610 GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
  2611                     uint32_t aStart, uint32_t aEnd,
  2612                     gfxSkipCharsIterator* aIterator)
  2614   aIterator->SetSkippedOffset(aEnd);
  2615   while (aIterator->GetSkippedOffset() > aStart) {
  2616     aIterator->AdvanceSkipped(-1);
  2617     if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
  2618       return aIterator->GetSkippedOffset() + 1;
  2620   return aStart;
  2623 nsTextFrame::TrimmedOffsets
  2624 nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
  2625                                bool aTrimAfter, bool aPostReflow)
  2627   NS_ASSERTION(mTextRun, "Need textrun here");
  2628   if (aPostReflow) {
  2629     // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
  2630     // to be set correctly.  If our parent wasn't reflowed due to the frame
  2631     // tree being too deep then the return value doesn't matter.
  2632     NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
  2633                  (GetParent()->GetStateBits() &
  2634                   NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
  2635                  "Can only call this on frames that have been reflowed");
  2636     NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
  2637                  "Can only call this on frames that are not being reflowed");
  2640   TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
  2641   const nsStyleText* textStyle = StyleText();
  2642   // Note that pre-line newlines should still allow us to trim spaces
  2643   // for display
  2644   if (textStyle->WhiteSpaceIsSignificant())
  2645     return offsets;
  2647   if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) {
  2648     int32_t whitespaceCount =
  2649       GetTrimmableWhitespaceCount(aFrag,
  2650                                   offsets.mStart, offsets.mLength, 1);
  2651     offsets.mStart += whitespaceCount;
  2652     offsets.mLength -= whitespaceCount;
  2655   if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) {
  2656     // This treats a trailing 'pre-line' newline as trimmable. That's fine,
  2657     // it's actually what we want since we want whitespace before it to
  2658     // be trimmed.
  2659     int32_t whitespaceCount =
  2660       GetTrimmableWhitespaceCount(aFrag,
  2661                                   offsets.GetEnd() - 1, offsets.mLength, -1);
  2662     offsets.mLength -= whitespaceCount;
  2664   return offsets;
  2667 /*
  2668  * Currently only Unicode characters below 0x10000 have their spacing modified
  2669  * by justification. If characters above 0x10000 turn out to need
  2670  * justification spacing, that will require extra work. Currently,
  2671  * this function must not include 0xd800 to 0xdbff because these characters
  2672  * are surrogates.
  2673  */
  2674 static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos,
  2675                                      bool aLangIsCJ)
  2677   char16_t ch = aFrag->CharAt(aPos);
  2678   if (ch == '\n' || ch == '\t' || ch == '\r')
  2679     return true;
  2680   if (ch == ' ' || ch == CH_NBSP) {
  2681     // Don't justify spaces that are combined with diacriticals
  2682     if (!aFrag->Is2b())
  2683       return true;
  2684     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
  2685         aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
  2687   if (ch < 0x2150u)
  2688     return false;
  2689   if (aLangIsCJ && (
  2690        (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators
  2691        (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics
  2692        (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
  2693        (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
  2694                                            // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
  2695                                            // Miscellaneous Symbols and Arrows
  2696        (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement,
  2697                                            // Ideographic Description Characters, CJK Symbols and Punctuation,
  2698                                            // Hiragana, Katakana, Bopomofo
  2699        (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
  2700                                            // Enclosed CJK Letters and Months, CJK Compatibility,
  2701                                            // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
  2702                                            // CJK Unified Ideographs, Yi Syllables, Yi Radicals
  2703        (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs
  2704        (0xff5eu <= ch && ch <= 0xff9fu)    // Halfwidth and Fullwidth Forms(a part)
  2705      ))
  2706     return true;
  2707   return false;
  2710 void
  2711 nsTextFrame::ClearMetrics(nsHTMLReflowMetrics& aMetrics)
  2713   aMetrics.Width() = 0;
  2714   aMetrics.Height() = 0;
  2715   aMetrics.SetTopAscent(0);
  2716   mAscent = 0;
  2719 static int32_t FindChar(const nsTextFragment* frag,
  2720                         int32_t aOffset, int32_t aLength, char16_t ch)
  2722   int32_t i = 0;
  2723   if (frag->Is2b()) {
  2724     const char16_t* str = frag->Get2b() + aOffset;
  2725     for (; i < aLength; ++i) {
  2726       if (*str == ch)
  2727         return i + aOffset;
  2728       ++str;
  2730   } else {
  2731     if (uint16_t(ch) <= 0xFF) {
  2732       const char* str = frag->Get1b() + aOffset;
  2733       const void* p = memchr(str, ch, aLength);
  2734       if (p)
  2735         return (static_cast<const char*>(p) - str) + aOffset;
  2738   return -1;
  2741 static bool IsChineseOrJapanese(nsIFrame* aFrame)
  2743   nsIAtom* language = aFrame->StyleFont()->mLanguage;
  2744   if (!language) {
  2745     return false;
  2747   const char16_t *lang = language->GetUTF16String();
  2748   return (!nsCRT::strncmp(lang, MOZ_UTF16("ja"), 2) ||
  2749           !nsCRT::strncmp(lang, MOZ_UTF16("zh"), 2)) &&
  2750          (language->GetLength() == 2 || lang[2] == '-');
  2753 #ifdef DEBUG
  2754 static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength,
  2755                          uint32_t aOffset, uint32_t aLength) {
  2756   if (aStart.GetSkippedOffset() > aOffset)
  2757     return false;
  2758   if (aContentLength == INT32_MAX)
  2759     return true;
  2760   gfxSkipCharsIterator iter(aStart);
  2761   iter.AdvanceOriginal(aContentLength);
  2762   return iter.GetSkippedOffset() >= aOffset + aLength;
  2764 #endif
  2766 class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider {
  2767 public:
  2768   /**
  2769    * Use this constructor for reflow, when we don't know what text is
  2770    * really mapped by the frame and we have a lot of other data around.
  2772    * @param aLength can be INT32_MAX to indicate we cover all the text
  2773    * associated with aFrame up to where its flow chain ends in the given
  2774    * textrun. If INT32_MAX is passed, justification and hyphen-related methods
  2775    * cannot be called, nor can GetOriginalLength().
  2776    */
  2777   PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
  2778                    const nsTextFragment* aFrag, nsTextFrame* aFrame,
  2779                    const gfxSkipCharsIterator& aStart, int32_t aLength,
  2780                    nsIFrame* aLineContainer,
  2781                    nscoord aOffsetFromBlockOriginForTabs,
  2782                    nsTextFrame::TextRunType aWhichTextRun)
  2783     : mTextRun(aTextRun), mFontGroup(nullptr),
  2784       mTextStyle(aTextStyle), mFrag(aFrag),
  2785       mLineContainer(aLineContainer),
  2786       mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
  2787       mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
  2788       mLength(aLength),
  2789       mWordSpacing(WordSpacing(aFrame, aTextStyle)),
  2790       mLetterSpacing(LetterSpacing(aFrame, aTextStyle)),
  2791       mJustificationSpacing(0),
  2792       mHyphenWidth(-1),
  2793       mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
  2794       mReflowing(true),
  2795       mWhichTextRun(aWhichTextRun)
  2797     NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
  2800   /**
  2801    * Use this constructor after the frame has been reflowed and we don't
  2802    * have other data around. Gets everything from the frame. EnsureTextRun
  2803    * *must* be called before this!!!
  2804    */
  2805   PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
  2806                    nsTextFrame::TextRunType aWhichTextRun)
  2807     : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr),
  2808       mTextStyle(aFrame->StyleText()),
  2809       mFrag(aFrame->GetContent()->GetText()),
  2810       mLineContainer(nullptr),
  2811       mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
  2812       mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
  2813       mLength(aFrame->GetContentLength()),
  2814       mWordSpacing(WordSpacing(aFrame)),
  2815       mLetterSpacing(LetterSpacing(aFrame)),
  2816       mJustificationSpacing(0),
  2817       mHyphenWidth(-1),
  2818       mOffsetFromBlockOriginForTabs(0),
  2819       mReflowing(false),
  2820       mWhichTextRun(aWhichTextRun)
  2822     NS_ASSERTION(mTextRun, "Textrun not initialized!");
  2825   // Call this after construction if you're not going to reflow the text
  2826   void InitializeForDisplay(bool aTrimAfter);
  2828   void InitializeForMeasure();
  2830   virtual void GetSpacing(uint32_t aStart, uint32_t aLength, Spacing* aSpacing);
  2831   virtual gfxFloat GetHyphenWidth();
  2832   virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
  2833                                     bool* aBreakBefore);
  2834   virtual int8_t GetHyphensOption() {
  2835     return mTextStyle->mHyphens;
  2838   virtual already_AddRefed<gfxContext> GetContext() {
  2839     return CreateReferenceThebesContext(GetFrame());
  2842   virtual uint32_t GetAppUnitsPerDevUnit() {
  2843     return mTextRun->GetAppUnitsPerDevUnit();
  2846   void GetSpacingInternal(uint32_t aStart, uint32_t aLength, Spacing* aSpacing,
  2847                           bool aIgnoreTabs);
  2849   /**
  2850    * Count the number of justifiable characters in the given DOM range
  2851    */
  2852   uint32_t ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength);
  2853   /**
  2854    * Find the start and end of the justifiable characters. Does not depend on the
  2855    * position of aStart or aEnd, although it's most efficient if they are near the
  2856    * start and end of the text frame.
  2857    */
  2858   void FindJustificationRange(gfxSkipCharsIterator* aStart,
  2859                               gfxSkipCharsIterator* aEnd);
  2861   const nsStyleText* StyleText() { return mTextStyle; }
  2862   nsTextFrame* GetFrame() { return mFrame; }
  2863   // This may not be equal to the frame offset/length in because we may have
  2864   // adjusted for whitespace trimming according to the state bits set in the frame
  2865   // (for the static provider)
  2866   const gfxSkipCharsIterator& GetStart() { return mStart; }
  2867   // May return INT32_MAX if that was given to the constructor
  2868   uint32_t GetOriginalLength() {
  2869     NS_ASSERTION(mLength != INT32_MAX, "Length not known");
  2870     return mLength;
  2872   const nsTextFragment* GetFragment() { return mFrag; }
  2874   gfxFontGroup* GetFontGroup() {
  2875     if (!mFontGroup)
  2876       InitFontGroupAndFontMetrics();
  2877     return mFontGroup;
  2880   nsFontMetrics* GetFontMetrics() {
  2881     if (!mFontMetrics)
  2882       InitFontGroupAndFontMetrics();
  2883     return mFontMetrics;
  2886   void CalcTabWidths(uint32_t aTransformedStart, uint32_t aTransformedLength);
  2888   const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
  2890 protected:
  2891   void SetupJustificationSpacing(bool aPostReflow);
  2893   void InitFontGroupAndFontMetrics() {
  2894     float inflation = (mWhichTextRun == nsTextFrame::eInflated)
  2895       ? mFrame->GetFontSizeInflation() : 1.0f;
  2896     mFontGroup = GetFontGroupForFrame(mFrame, inflation,
  2897                                       getter_AddRefs(mFontMetrics));
  2900   gfxTextRun*           mTextRun;
  2901   gfxFontGroup*         mFontGroup;
  2902   nsRefPtr<nsFontMetrics> mFontMetrics;
  2903   const nsStyleText*    mTextStyle;
  2904   const nsTextFragment* mFrag;
  2905   nsIFrame*             mLineContainer;
  2906   nsTextFrame*          mFrame;
  2907   gfxSkipCharsIterator  mStart;  // Offset in original and transformed string
  2908   gfxSkipCharsIterator  mTempIterator;
  2910   // Either null, or pointing to the frame's TabWidthProperty.
  2911   TabWidthStore*        mTabWidths;
  2912   // How far we've done tab-width calculation; this is ONLY valid when
  2913   // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
  2914   // It's a DOM offset relative to the current frame's offset.
  2915   uint32_t              mTabWidthsAnalyzedLimit;
  2917   int32_t               mLength; // DOM string length, may be INT32_MAX
  2918   gfxFloat              mWordSpacing;     // space for each whitespace char
  2919   gfxFloat              mLetterSpacing;   // space for each letter
  2920   gfxFloat              mJustificationSpacing;
  2921   gfxFloat              mHyphenWidth;
  2922   gfxFloat              mOffsetFromBlockOriginForTabs;
  2923   bool                  mReflowing;
  2924   nsTextFrame::TextRunType mWhichTextRun;
  2925 };
  2927 uint32_t
  2928 PropertyProvider::ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength)
  2930   // Scan non-skipped characters and count justifiable chars.
  2931   nsSkipCharsRunIterator
  2932     run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
  2933   run.SetOriginalOffset(aOffset);
  2934   uint32_t justifiableChars = 0;
  2935   bool isCJK = IsChineseOrJapanese(mFrame);
  2936   while (run.NextRun()) {
  2937     for (int32_t i = 0; i < run.GetRunLength(); ++i) {
  2938       justifiableChars +=
  2939         IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK);
  2942   return justifiableChars;
  2945 /**
  2946  * Finds the offset of the first character of the cluster containing aPos
  2947  */
  2948 static void FindClusterStart(gfxTextRun* aTextRun, int32_t aOriginalStart,
  2949                              gfxSkipCharsIterator* aPos)
  2951   while (aPos->GetOriginalOffset() > aOriginalStart) {
  2952     if (aPos->IsOriginalCharSkipped() ||
  2953         aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
  2954       break;
  2956     aPos->AdvanceOriginal(-1);
  2960 /**
  2961  * Finds the offset of the last character of the cluster containing aPos
  2962  */
  2963 static void FindClusterEnd(gfxTextRun* aTextRun, int32_t aOriginalEnd,
  2964                            gfxSkipCharsIterator* aPos)
  2966   NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd,
  2967                   "character outside string");
  2968   aPos->AdvanceOriginal(1);
  2969   while (aPos->GetOriginalOffset() < aOriginalEnd) {
  2970     if (aPos->IsOriginalCharSkipped() ||
  2971         aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
  2972       break;
  2974     aPos->AdvanceOriginal(1);
  2976   aPos->AdvanceOriginal(-1);
  2979 // aStart, aLength in transformed string offsets
  2980 void
  2981 PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength,
  2982                              Spacing* aSpacing)
  2984   GetSpacingInternal(aStart, aLength, aSpacing,
  2985                      (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0);
  2988 static bool
  2989 CanAddSpacingAfter(gfxTextRun* aTextRun, uint32_t aOffset)
  2991   if (aOffset + 1 >= aTextRun->GetLength())
  2992     return true;
  2993   return aTextRun->IsClusterStart(aOffset + 1) &&
  2994     aTextRun->IsLigatureGroupStart(aOffset + 1);
  2997 void
  2998 PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength,
  2999                                      Spacing* aSpacing, bool aIgnoreTabs)
  3001   NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
  3003   uint32_t index;
  3004   for (index = 0; index < aLength; ++index) {
  3005     aSpacing[index].mBefore = 0.0;
  3006     aSpacing[index].mAfter = 0.0;
  3009   // Find our offset into the original+transformed string
  3010   gfxSkipCharsIterator start(mStart);
  3011   start.SetSkippedOffset(aStart);
  3013   // First, compute the word and letter spacing
  3014   if (mWordSpacing || mLetterSpacing) {
  3015     // Iterate over non-skipped characters
  3016     nsSkipCharsRunIterator
  3017       run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
  3018     while (run.NextRun()) {
  3019       uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
  3020       gfxSkipCharsIterator iter = run.GetPos();
  3021       for (int32_t i = 0; i < run.GetRunLength(); ++i) {
  3022         if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
  3023           // End of a cluster, not in a ligature: put letter-spacing after it
  3024           aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
  3026         if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
  3027                                   mTextStyle)) {
  3028           // It kinda sucks, but space characters can be part of clusters,
  3029           // and even still be whitespace (I think!)
  3030           iter.SetSkippedOffset(run.GetSkippedOffset() + i);
  3031           FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
  3032                          &iter);
  3033           aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing;
  3039   // Ignore tab spacing rather than computing it, if the tab size is 0
  3040   if (!aIgnoreTabs)
  3041     aIgnoreTabs = mFrame->StyleText()->mTabSize == 0;
  3043   // Now add tab spacing, if there is any
  3044   if (!aIgnoreTabs) {
  3045     CalcTabWidths(aStart, aLength);
  3046     if (mTabWidths) {
  3047       mTabWidths->ApplySpacing(aSpacing,
  3048                                aStart - mStart.GetSkippedOffset(), aLength);
  3052   // Now add in justification spacing
  3053   if (mJustificationSpacing) {
  3054     gfxFloat halfJustificationSpace = mJustificationSpacing/2;
  3055     // Scan non-skipped characters and adjust justifiable chars, adding
  3056     // justification space on either side of the cluster
  3057     bool isCJK = IsChineseOrJapanese(mFrame);
  3058     gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart);
  3059     FindJustificationRange(&justificationStart, &justificationEnd);
  3061     nsSkipCharsRunIterator
  3062       run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
  3063     while (run.NextRun()) {
  3064       gfxSkipCharsIterator iter = run.GetPos();
  3065       int32_t runOriginalOffset = run.GetOriginalOffset();
  3066       for (int32_t i = 0; i < run.GetRunLength(); ++i) {
  3067         int32_t iterOriginalOffset = runOriginalOffset + i;
  3068         if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) {
  3069           iter.SetOriginalOffset(iterOriginalOffset);
  3070           FindClusterStart(mTextRun, runOriginalOffset, &iter);
  3071           uint32_t clusterFirstChar = iter.GetSkippedOffset();
  3072           FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter);
  3073           uint32_t clusterLastChar = iter.GetSkippedOffset();
  3074           // Only apply justification to characters before justificationEnd
  3075           if (clusterFirstChar >= justificationStart.GetSkippedOffset() &&
  3076               clusterLastChar < justificationEnd.GetSkippedOffset()) {
  3077             aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace;
  3078             aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace;
  3086 static gfxFloat
  3087 ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
  3089   // Get the number of spaces from CSS -moz-tab-size
  3090   const nsStyleText* textStyle = aFrame->StyleText();
  3092   // Round the space width when converting to appunits the same way
  3093   // textruns do
  3094   gfxFloat spaceWidthAppUnits =
  3095     NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup()).spaceWidth *
  3096               aTextRun->GetAppUnitsPerDevUnit());
  3097   return textStyle->mTabSize * spaceWidthAppUnits;
  3100 // aX and the result are in whole appunits.
  3101 static gfxFloat
  3102 AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
  3103                  gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth)
  3105   if (*aCachedTabWidth < 0) {
  3106     *aCachedTabWidth = ComputeTabWidthAppUnits(aFrame, aTextRun);
  3109   // Advance aX to the next multiple of *aCachedTabWidth. We must advance
  3110   // by at least 1 appunit.
  3111   // XXX should we make this 1 CSS pixel?
  3112   return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth);
  3115 void
  3116 PropertyProvider::CalcTabWidths(uint32_t aStart, uint32_t aLength)
  3118   if (!mTabWidths) {
  3119     if (mReflowing && !mLineContainer) {
  3120       // Intrinsic width computation does its own tab processing. We
  3121       // just don't do anything here.
  3122       return;
  3124     if (!mReflowing) {
  3125       mTabWidths = static_cast<TabWidthStore*>
  3126         (mFrame->Properties().Get(TabWidthProperty()));
  3127 #ifdef DEBUG
  3128       // If we're not reflowing, we should have already computed the
  3129       // tab widths; check that they're available as far as the last
  3130       // tab character present (if any)
  3131       for (uint32_t i = aStart + aLength; i > aStart; --i) {
  3132         if (mTextRun->CharIsTab(i - 1)) {
  3133           uint32_t startOffset = mStart.GetSkippedOffset();
  3134           NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
  3135                        "Precomputed tab widths are missing!");
  3136           break;
  3139 #endif
  3140       return;
  3144   uint32_t startOffset = mStart.GetSkippedOffset();
  3145   MOZ_ASSERT(aStart >= startOffset, "wrong start offset");
  3146   MOZ_ASSERT(aStart + aLength <= startOffset + mLength, "beyond the end");
  3147   uint32_t tabsEnd =
  3148     (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
  3149   if (tabsEnd < aStart + aLength) {
  3150     NS_ASSERTION(mReflowing,
  3151                  "We need precomputed tab widths, but don't have enough.");
  3153     gfxFloat tabWidth = -1;
  3154     for (uint32_t i = tabsEnd; i < aStart + aLength; ++i) {
  3155       Spacing spacing;
  3156       GetSpacingInternal(i, 1, &spacing, true);
  3157       mOffsetFromBlockOriginForTabs += spacing.mBefore;
  3159       if (!mTextRun->CharIsTab(i)) {
  3160         if (mTextRun->IsClusterStart(i)) {
  3161           uint32_t clusterEnd = i + 1;
  3162           while (clusterEnd < mTextRun->GetLength() &&
  3163                  !mTextRun->IsClusterStart(clusterEnd)) {
  3164             ++clusterEnd;
  3166           mOffsetFromBlockOriginForTabs +=
  3167             mTextRun->GetAdvanceWidth(i, clusterEnd - i, nullptr);
  3169       } else {
  3170         if (!mTabWidths) {
  3171           mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
  3172           mFrame->Properties().Set(TabWidthProperty(), mTabWidths);
  3174         double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
  3175                 mFrame, mTextRun, &tabWidth);
  3176         mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset, 
  3177                 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
  3178         mOffsetFromBlockOriginForTabs = nextTab;
  3181       mOffsetFromBlockOriginForTabs += spacing.mAfter;
  3184     if (mTabWidths) {
  3185       mTabWidths->mLimit = aStart + aLength - startOffset;
  3189   if (!mTabWidths) {
  3190     // Delete any stale property that may be left on the frame
  3191     mFrame->Properties().Delete(TabWidthProperty());
  3192     mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
  3193                                        aStart + aLength - startOffset);
  3197 gfxFloat
  3198 PropertyProvider::GetHyphenWidth()
  3200   if (mHyphenWidth < 0) {
  3201     mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
  3203   return mHyphenWidth + mLetterSpacing;
  3206 void
  3207 PropertyProvider::GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
  3208                                        bool* aBreakBefore)
  3210   NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
  3211   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
  3213   if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
  3214       mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE)
  3216     memset(aBreakBefore, false, aLength*sizeof(bool));
  3217     return;
  3220   // Iterate through the original-string character runs
  3221   nsSkipCharsRunIterator
  3222     run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
  3223   run.SetSkippedOffset(aStart);
  3224   // We need to visit skipped characters so that we can detect SHY
  3225   run.SetVisitSkipped();
  3227   int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
  3228   bool allowHyphenBreakBeforeNextChar =
  3229     prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
  3230     prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
  3231     mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
  3233   while (run.NextRun()) {
  3234     NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
  3235     if (run.IsSkipped()) {
  3236       // Check if there's a soft hyphen which would let us hyphenate before
  3237       // the next non-skipped character. Don't look at soft hyphens followed
  3238       // by other skipped characters, we won't use them.
  3239       allowHyphenBreakBeforeNextChar =
  3240         mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
  3241     } else {
  3242       int32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
  3243       memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool));
  3244       // Don't allow hyphen breaks at the start of the line
  3245       aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar &&
  3246           (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
  3247            run.GetSkippedOffset() > mStart.GetSkippedOffset());
  3248       allowHyphenBreakBeforeNextChar = false;
  3252   if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
  3253     for (uint32_t i = 0; i < aLength; ++i) {
  3254       if (mTextRun->CanHyphenateBefore(aStart + i)) {
  3255         aBreakBefore[i] = true;
  3261 void
  3262 PropertyProvider::InitializeForDisplay(bool aTrimAfter)
  3264   nsTextFrame::TrimmedOffsets trimmed =
  3265     mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
  3266   mStart.SetOriginalOffset(trimmed.mStart);
  3267   mLength = trimmed.mLength;
  3268   SetupJustificationSpacing(true);
  3271 void
  3272 PropertyProvider::InitializeForMeasure()
  3274   nsTextFrame::TrimmedOffsets trimmed =
  3275     mFrame->GetTrimmedOffsets(mFrag, true, false);
  3276   mStart.SetOriginalOffset(trimmed.mStart);
  3277   mLength = trimmed.mLength;
  3278   SetupJustificationSpacing(false);
  3282 static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart,
  3283                                    const gfxSkipCharsIterator& aEnd)
  3285   return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset();
  3288 void
  3289 PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart,
  3290                                          gfxSkipCharsIterator* aEnd)
  3292   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
  3293   NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null");
  3295   aStart->SetOriginalOffset(mStart.GetOriginalOffset());
  3296   aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength);
  3298   // Ignore first cluster at start of line for justification purposes
  3299   if (mFrame->GetStateBits() & TEXT_START_OF_LINE) {
  3300     while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) {
  3301       aStart->AdvanceOriginal(1);
  3302       if (!aStart->IsOriginalCharSkipped() &&
  3303           mTextRun->IsClusterStart(aStart->GetSkippedOffset()))
  3304         break;
  3308   // Ignore trailing cluster at end of line for justification purposes
  3309   if (mFrame->GetStateBits() & TEXT_END_OF_LINE) {
  3310     while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) {
  3311       aEnd->AdvanceOriginal(-1);
  3312       if (!aEnd->IsOriginalCharSkipped() &&
  3313           mTextRun->IsClusterStart(aEnd->GetSkippedOffset()))
  3314         break;
  3319 void
  3320 PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
  3322   NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
  3324   if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
  3325     return;
  3327   gfxSkipCharsIterator start(mStart), end(mStart);
  3328   // We can't just use our mLength here; when InitializeForDisplay is
  3329   // called with false for aTrimAfter, we still shouldn't be assigning
  3330   // justification space to any trailing whitespace.
  3331   nsTextFrame::TrimmedOffsets trimmed =
  3332     mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
  3333   end.AdvanceOriginal(trimmed.mLength);
  3334   gfxSkipCharsIterator realEnd(end);
  3335   FindJustificationRange(&start, &end);
  3337   int32_t justifiableCharacters =
  3338     ComputeJustifiableCharacters(start.GetOriginalOffset(),
  3339                                  end.GetOriginalOffset() - start.GetOriginalOffset());
  3340   if (justifiableCharacters == 0) {
  3341     // Nothing to do, nothing is justifiable and we shouldn't have any
  3342     // justification space assigned
  3343     return;
  3346   gfxFloat naturalWidth =
  3347     mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(),
  3348                               GetSkippedDistance(mStart, realEnd), this);
  3349   if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
  3350     naturalWidth += GetHyphenWidth();
  3352   gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth;
  3353   if (totalJustificationSpace <= 0) {
  3354     // No space available
  3355     return;
  3358   mJustificationSpacing = totalJustificationSpace/justifiableCharacters;
  3361 //----------------------------------------------------------------------
  3363 static nscolor
  3364 EnsureDifferentColors(nscolor colorA, nscolor colorB)
  3366   if (colorA == colorB) {
  3367     nscolor res;
  3368     res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
  3369                  NS_GET_G(colorA) ^ 0xff,
  3370                  NS_GET_B(colorA) ^ 0xff);
  3371     return res;
  3373   return colorA;
  3376 //-----------------------------------------------------------------------------
  3378 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
  3379   : mFrame(aFrame),
  3380     mPresContext(aFrame->PresContext()),
  3381     mInitCommonColors(false),
  3382     mInitSelectionColorsAndShadow(false),
  3383     mResolveColors(true),
  3384     mHasSelectionShadow(false)
  3386   for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
  3387     mSelectionStyle[i].mInit = false;
  3390 bool
  3391 nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor)
  3393   InitCommonColors();
  3395   // If the combination of selection background color and frame background color
  3396   // is sufficient contrast, don't exchange the selection colors.
  3397   int32_t backLuminosityDifference =
  3398             NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
  3399   if (backLuminosityDifference >= mSufficientContrast)
  3400     return false;
  3402   // Otherwise, we should use the higher-contrast color for the selection
  3403   // background color.
  3404   int32_t foreLuminosityDifference =
  3405             NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
  3406   if (backLuminosityDifference < foreLuminosityDifference) {
  3407     nscolor tmpColor = *aForeColor;
  3408     *aForeColor = *aBackColor;
  3409     *aBackColor = tmpColor;
  3410     return true;
  3412   return false;
  3415 nscolor
  3416 nsTextPaintStyle::GetTextColor()
  3418   if (mFrame->IsSVGText()) {
  3419     if (!mResolveColors)
  3420       return NS_SAME_AS_FOREGROUND_COLOR;
  3422     const nsStyleSVG* style = mFrame->StyleSVG();
  3423     switch (style->mFill.mType) {
  3424       case eStyleSVGPaintType_None:
  3425         return NS_RGBA(0, 0, 0, 0);
  3426       case eStyleSVGPaintType_Color:
  3427         return nsLayoutUtils::GetColor(mFrame, eCSSProperty_fill);
  3428       default:
  3429         NS_ERROR("cannot resolve SVG paint to nscolor");
  3430         return NS_RGBA(0, 0, 0, 255);
  3433   return nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
  3436 bool
  3437 nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
  3438                                      nscolor* aBackColor)
  3440   NS_ASSERTION(aForeColor, "aForeColor is null");
  3441   NS_ASSERTION(aBackColor, "aBackColor is null");
  3443   if (!InitSelectionColorsAndShadow())
  3444     return false;
  3446   *aForeColor = mSelectionTextColor;
  3447   *aBackColor = mSelectionBGColor;
  3448   return true;
  3451 void
  3452 nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
  3453                                      nscolor* aBackColor)
  3455   NS_ASSERTION(aForeColor, "aForeColor is null");
  3456   NS_ASSERTION(aBackColor, "aBackColor is null");
  3458   nscolor backColor =
  3459     LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
  3460   nscolor foreColor =
  3461     LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
  3462   EnsureSufficientContrast(&foreColor, &backColor);
  3463   *aForeColor = foreColor;
  3464   *aBackColor = backColor;
  3467 void
  3468 nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
  3470   NS_ASSERTION(aForeColor, "aForeColor is null");
  3472   nscolor textColor = GetTextColor();
  3473   textColor = NS_RGBA(NS_GET_R(textColor),
  3474                       NS_GET_G(textColor),
  3475                       NS_GET_B(textColor),
  3476                       (uint8_t)(255 * 0.5f));
  3477   // Don't use true alpha color for readability.
  3478   InitCommonColors();
  3479   *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
  3482 void
  3483 nsTextPaintStyle::GetIMESelectionColors(int32_t  aIndex,
  3484                                         nscolor* aForeColor,
  3485                                         nscolor* aBackColor)
  3487   NS_ASSERTION(aForeColor, "aForeColor is null");
  3488   NS_ASSERTION(aBackColor, "aBackColor is null");
  3489   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
  3491   nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
  3492   *aForeColor = selectionStyle->mTextColor;
  3493   *aBackColor = selectionStyle->mBGColor;
  3496 bool
  3497 nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t  aIndex,
  3498                                                 nscolor* aLineColor,
  3499                                                 float*   aRelativeSize,
  3500                                                 uint8_t* aStyle)
  3502   NS_ASSERTION(aLineColor, "aLineColor is null");
  3503   NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
  3504   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
  3506   nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
  3507   if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE ||
  3508       selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
  3509       selectionStyle->mUnderlineRelativeSize <= 0.0f)
  3510     return false;
  3512   *aLineColor = selectionStyle->mUnderlineColor;
  3513   *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
  3514   *aStyle = selectionStyle->mUnderlineStyle;
  3515   return true;
  3518 void
  3519 nsTextPaintStyle::InitCommonColors()
  3521   if (mInitCommonColors)
  3522     return;
  3524   nsIFrame* bgFrame =
  3525     nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
  3526   NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame.");
  3527   nscolor bgColor =
  3528     bgFrame->GetVisitedDependentColor(eCSSProperty_background_color);
  3530   nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
  3531   mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
  3533   if (bgFrame->IsThemed()) {
  3534     // Assume a native widget has sufficient contrast always
  3535     mSufficientContrast = 0;
  3536     mInitCommonColors = true;
  3537     return;
  3540   NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
  3541                "default background color is not opaque");
  3543   nscolor defaultWindowBackgroundColor =
  3544     LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground);
  3545   nscolor selectionTextColor =
  3546     LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
  3547   nscolor selectionBGColor =
  3548     LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
  3550   mSufficientContrast =
  3551     std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
  3552                   NS_LUMINOSITY_DIFFERENCE(selectionTextColor,
  3553                                            selectionBGColor)),
  3554                   NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
  3555                                            selectionBGColor));
  3557   mInitCommonColors = true;
  3560 static Element*
  3561 FindElementAncestorForMozSelection(nsIContent* aContent)
  3563   NS_ENSURE_TRUE(aContent, nullptr);
  3564   while (aContent && aContent->IsInNativeAnonymousSubtree()) {
  3565     aContent = aContent->GetBindingParent();
  3567   NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
  3568   while (aContent && !aContent->IsElement()) {
  3569     aContent = aContent->GetParent();
  3571   return aContent ? aContent->AsElement() : nullptr;
  3574 bool
  3575 nsTextPaintStyle::InitSelectionColorsAndShadow()
  3577   if (mInitSelectionColorsAndShadow)
  3578     return true;
  3580   int16_t selectionFlags;
  3581   int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
  3582   if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
  3583       selectionStatus < nsISelectionController::SELECTION_ON) {
  3584     // Not displaying the normal selection.
  3585     // We're not caching this fact, so every call to GetSelectionColors
  3586     // will come through here. We could avoid this, but it's not really worth it.
  3587     return false;
  3590   mInitSelectionColorsAndShadow = true;
  3592   nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame);
  3593   Element* selectionElement =
  3594     FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent());
  3596   if (selectionElement &&
  3597       selectionStatus == nsISelectionController::SELECTION_ON) {
  3598     nsRefPtr<nsStyleContext> sc = nullptr;
  3599     sc = mPresContext->StyleSet()->
  3600       ProbePseudoElementStyle(selectionElement,
  3601                               nsCSSPseudoElements::ePseudo_mozSelection,
  3602                               mFrame->StyleContext());
  3603     // Use -moz-selection pseudo class.
  3604     if (sc) {
  3605       mSelectionBGColor =
  3606         sc->GetVisitedDependentColor(eCSSProperty_background_color);
  3607       mSelectionTextColor = sc->GetVisitedDependentColor(eCSSProperty_color);
  3608       mHasSelectionShadow =
  3609         nsRuleNode::HasAuthorSpecifiedRules(sc,
  3610                                             NS_AUTHOR_SPECIFIED_TEXT_SHADOW,
  3611                                             true);
  3612       if (mHasSelectionShadow) {
  3613         mSelectionShadow = sc->StyleText()->mTextShadow;
  3615       return true;
  3619   nscolor selectionBGColor =
  3620     LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
  3622   if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
  3623     mSelectionBGColor =
  3624       LookAndFeel::GetColor(
  3625         LookAndFeel::eColorID_TextSelectBackgroundAttention);
  3626     mSelectionBGColor  = EnsureDifferentColors(mSelectionBGColor,
  3627                                                selectionBGColor);
  3628   } else if (selectionStatus != nsISelectionController::SELECTION_ON) {
  3629     mSelectionBGColor =
  3630       LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled);
  3631     mSelectionBGColor  = EnsureDifferentColors(mSelectionBGColor,
  3632                                                selectionBGColor);
  3633   } else {
  3634     mSelectionBGColor = selectionBGColor;
  3637   mSelectionTextColor =
  3638     LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
  3640   if (mResolveColors) {
  3641     // On MacOS X, we don't exchange text color and BG color.
  3642     if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
  3643       nsCSSProperty property = mFrame->IsSVGText() ? eCSSProperty_fill :
  3644                                                      eCSSProperty_color;
  3645       nscoord frameColor = mFrame->GetVisitedDependentColor(property);
  3646       mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor);
  3647     } else {
  3648       EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
  3650   } else {
  3651     if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
  3652       mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR;
  3655   return true;
  3658 nsTextPaintStyle::nsSelectionStyle*
  3659 nsTextPaintStyle::GetSelectionStyle(int32_t aIndex)
  3661   InitSelectionStyle(aIndex);
  3662   return &mSelectionStyle[aIndex];
  3665 struct StyleIDs {
  3666   LookAndFeel::ColorID mForeground, mBackground, mLine;
  3667   LookAndFeel::IntID mLineStyle;
  3668   LookAndFeel::FloatID mLineRelativeSize;
  3669 };
  3670 static StyleIDs SelectionStyleIDs[] = {
  3671   { LookAndFeel::eColorID_IMERawInputForeground,
  3672     LookAndFeel::eColorID_IMERawInputBackground,
  3673     LookAndFeel::eColorID_IMERawInputUnderline,
  3674     LookAndFeel::eIntID_IMERawInputUnderlineStyle,
  3675     LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
  3676   { LookAndFeel::eColorID_IMESelectedRawTextForeground,
  3677     LookAndFeel::eColorID_IMESelectedRawTextBackground,
  3678     LookAndFeel::eColorID_IMESelectedRawTextUnderline,
  3679     LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle,
  3680     LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
  3681   { LookAndFeel::eColorID_IMEConvertedTextForeground,
  3682     LookAndFeel::eColorID_IMEConvertedTextBackground,
  3683     LookAndFeel::eColorID_IMEConvertedTextUnderline,
  3684     LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle,
  3685     LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
  3686   { LookAndFeel::eColorID_IMESelectedConvertedTextForeground,
  3687     LookAndFeel::eColorID_IMESelectedConvertedTextBackground,
  3688     LookAndFeel::eColorID_IMESelectedConvertedTextUnderline,
  3689     LookAndFeel::eIntID_IMESelectedConvertedTextUnderline,
  3690     LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
  3691   { LookAndFeel::eColorID_LAST_COLOR,
  3692     LookAndFeel::eColorID_LAST_COLOR,
  3693     LookAndFeel::eColorID_SpellCheckerUnderline,
  3694     LookAndFeel::eIntID_SpellCheckerUnderlineStyle,
  3695     LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize }
  3696 };
  3698 void
  3699 nsTextPaintStyle::InitSelectionStyle(int32_t aIndex)
  3701   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
  3702   nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
  3703   if (selectionStyle->mInit)
  3704     return;
  3706   StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
  3708   nscolor foreColor, backColor;
  3709   if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) {
  3710     foreColor = NS_SAME_AS_FOREGROUND_COLOR;
  3711   } else {
  3712     foreColor = LookAndFeel::GetColor(styleIDs->mForeground);
  3714   if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) {
  3715     backColor = NS_TRANSPARENT;
  3716   } else {
  3717     backColor = LookAndFeel::GetColor(styleIDs->mBackground);
  3720   // Convert special color to actual color
  3721   NS_ASSERTION(foreColor != NS_TRANSPARENT,
  3722                "foreColor cannot be NS_TRANSPARENT");
  3723   NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
  3724                "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
  3725   NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
  3726                "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
  3728   if (mResolveColors) {
  3729     foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
  3731     if (NS_GET_A(backColor) > 0)
  3732       EnsureSufficientContrast(&foreColor, &backColor);
  3735   nscolor lineColor;
  3736   float relativeSize;
  3737   uint8_t lineStyle;
  3738   GetSelectionUnderline(mPresContext, aIndex,
  3739                         &lineColor, &relativeSize, &lineStyle);
  3741   if (mResolveColors)
  3742     lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
  3744   selectionStyle->mTextColor       = foreColor;
  3745   selectionStyle->mBGColor         = backColor;
  3746   selectionStyle->mUnderlineColor  = lineColor;
  3747   selectionStyle->mUnderlineStyle  = lineStyle;
  3748   selectionStyle->mUnderlineRelativeSize = relativeSize;
  3749   selectionStyle->mInit            = true;
  3752 /* static */ bool
  3753 nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext,
  3754                                         int32_t aIndex,
  3755                                         nscolor* aLineColor,
  3756                                         float* aRelativeSize,
  3757                                         uint8_t* aStyle)
  3759   NS_ASSERTION(aPresContext, "aPresContext is null");
  3760   NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
  3761   NS_ASSERTION(aStyle, "aStyle is null");
  3762   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
  3764   StyleIDs& styleID = SelectionStyleIDs[aIndex];
  3766   nscolor color = LookAndFeel::GetColor(styleID.mLine);
  3767   int32_t style = LookAndFeel::GetInt(styleID.mLineStyle);
  3768   if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
  3769     NS_ERROR("Invalid underline style value is specified");
  3770     style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
  3772   float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
  3774   NS_ASSERTION(size, "selection underline relative size must be larger than 0");
  3776   if (aLineColor) {
  3777     *aLineColor = color;
  3779   *aRelativeSize = size;
  3780   *aStyle = style;
  3782   return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
  3783          color != NS_TRANSPARENT &&
  3784          size > 0.0f;
  3787 bool
  3788 nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow)
  3790   if (!InitSelectionColorsAndShadow()) {
  3791     return false;
  3794   if (mHasSelectionShadow) {
  3795     *aShadow = mSelectionShadow;
  3796     return true;
  3799   return false;
  3802 inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor)
  3804   nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor),
  3805                               NS_GET_G(aForeColor),
  3806                               NS_GET_B(aForeColor),
  3807                               (uint8_t)(255 * 0.4f));
  3808   // Don't use true alpha color for readability.
  3809   return NS_ComposeColors(aBackColor, foreColor);
  3812 nscolor
  3813 nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
  3814                                        nscolor aDefaultForeColor,
  3815                                        nscolor aBackColor)
  3817   if (aColor == NS_SAME_AS_FOREGROUND_COLOR)
  3818     return aDefaultForeColor;
  3820   if (aColor != NS_40PERCENT_FOREGROUND_COLOR)
  3821     return aColor;
  3823   // Get actual background color
  3824   nscolor actualBGColor = aBackColor;
  3825   if (actualBGColor == NS_TRANSPARENT) {
  3826     InitCommonColors();
  3827     actualBGColor = mFrameBackgroundColor;
  3829   return Get40PercentColor(aDefaultForeColor, actualBGColor);
  3832 //-----------------------------------------------------------------------------
  3834 #ifdef ACCESSIBILITY
  3835 a11y::AccType
  3836 nsTextFrame::AccessibleType()
  3838   if (IsEmpty()) {
  3839     nsAutoString renderedWhitespace;
  3840     GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1);
  3841     if (renderedWhitespace.IsEmpty()) {
  3842       return a11y::eNoType;
  3846   return a11y::eTextLeafType;
  3848 #endif
  3851 //-----------------------------------------------------------------------------
  3852 void
  3853 nsTextFrame::Init(nsIContent*      aContent,
  3854                   nsIFrame*        aParent,
  3855                   nsIFrame*        aPrevInFlow)
  3857   NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
  3858   NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
  3859                   "Bogus content!");
  3861   // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
  3862   // might be invalid if the content was modified while there was no frame
  3863   aContent->DeleteProperty(nsGkAtoms::newline);
  3864   if (PresContext()->BidiEnabled()) {
  3865     aContent->DeleteProperty(nsGkAtoms::flowlength);
  3868   // Since our content has a frame now, this flag is no longer needed.
  3869   aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
  3871   // We're not a continuing frame.
  3872   // mContentOffset = 0; not necessary since we get zeroed out at init
  3873   nsFrame::Init(aContent, aParent, aPrevInFlow);
  3876 void
  3877 nsTextFrame::ClearFrameOffsetCache()
  3879   // See if we need to remove ourselves from the offset cache
  3880   if (GetStateBits() & TEXT_IN_OFFSET_CACHE) {
  3881     nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
  3882     if (primaryFrame) {
  3883       // The primary frame might be null here.  For example, nsLineBox::DeleteLineList
  3884       // just destroys the frames in order, which means that the primary frame is already
  3885       // dead if we're a continuing text frame, in which case, all of its properties are
  3886       // gone, and we don't need to worry about deleting this property here.
  3887       primaryFrame->Properties().Delete(OffsetToFrameProperty());
  3889     RemoveStateBits(TEXT_IN_OFFSET_CACHE);
  3893 void
  3894 nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
  3896   ClearFrameOffsetCache();
  3898   // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
  3899   // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
  3900   // type might be changing.  Not clear whether it's worth it.
  3901   ClearTextRuns();
  3902   if (mNextContinuation) {
  3903     mNextContinuation->SetPrevInFlow(nullptr);
  3905   // Let the base class destroy the frame
  3906   nsFrame::DestroyFrom(aDestructRoot);
  3909 class nsContinuingTextFrame : public nsTextFrame {
  3910 public:
  3911   NS_DECL_FRAMEARENA_HELPERS
  3913   friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
  3915   virtual void Init(nsIContent*      aContent,
  3916                     nsIFrame*        aParent,
  3917                     nsIFrame*        aPrevInFlow) MOZ_OVERRIDE;
  3919   virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
  3921   virtual nsIFrame* GetPrevContinuation() const MOZ_OVERRIDE {
  3922     return mPrevContinuation;
  3924   virtual void SetPrevContinuation(nsIFrame* aPrevContinuation) MOZ_OVERRIDE {
  3925     NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(),
  3926                   "setting a prev continuation with incorrect type!");
  3927     NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
  3928                   "creating a loop in continuation chain!");
  3929     mPrevContinuation = aPrevContinuation;
  3930     RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
  3932   virtual nsIFrame* GetPrevInFlowVirtual() const MOZ_OVERRIDE {
  3933     return GetPrevInFlow();
  3935   nsIFrame* GetPrevInFlow() const {
  3936     return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr;
  3938   virtual void SetPrevInFlow(nsIFrame* aPrevInFlow) MOZ_OVERRIDE {
  3939     NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(),
  3940                   "setting a prev in flow with incorrect type!");
  3941     NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
  3942                   "creating a loop in continuation chain!");
  3943     mPrevContinuation = aPrevInFlow;
  3944     AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
  3946   virtual nsIFrame* FirstInFlow() const MOZ_OVERRIDE;
  3947   virtual nsIFrame* FirstContinuation() const MOZ_OVERRIDE;
  3949   virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext,
  3950                                  InlineMinWidthData *aData) MOZ_OVERRIDE;
  3951   virtual void AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
  3952                                   InlinePrefWidthData *aData) MOZ_OVERRIDE;
  3954   virtual nsresult GetRenderedText(nsAString* aString = nullptr,
  3955                                    gfxSkipChars* aSkipChars = nullptr,
  3956                                    gfxSkipCharsIterator* aSkipIter = nullptr,
  3957                                    uint32_t aSkippedStartOffset = 0,
  3958                                    uint32_t aSkippedMaxLength = UINT32_MAX) MOZ_OVERRIDE
  3959   { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only
  3961 protected:
  3962   nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
  3963   nsIFrame* mPrevContinuation;
  3964 };
  3966 void
  3967 nsContinuingTextFrame::Init(nsIContent* aContent,
  3968                             nsIFrame*   aParent,
  3969                             nsIFrame*   aPrevInFlow)
  3971   NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
  3972   // NOTE: bypassing nsTextFrame::Init!!!
  3973   nsFrame::Init(aContent, aParent, aPrevInFlow);
  3975   nsTextFrame* nextContinuation =
  3976     static_cast<nsTextFrame*>(aPrevInFlow->GetNextContinuation());
  3977   // Hook the frame into the flow
  3978   SetPrevInFlow(aPrevInFlow);
  3979   aPrevInFlow->SetNextInFlow(this);
  3980   nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
  3981   mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
  3982   NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
  3983                "Creating ContinuingTextFrame, but there is no more content");
  3984   if (prev->StyleContext() != StyleContext()) {
  3985     // We're taking part of prev's text, and its style may be different
  3986     // so clear its textrun which may no longer be valid (and don't set ours)
  3987     prev->ClearTextRuns();
  3988   } else {
  3989     float inflation = prev->GetFontSizeInflation();
  3990     SetFontSizeInflation(inflation);
  3991     mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
  3992     if (inflation != 1.0f) {
  3993       gfxTextRun *uninflatedTextRun =
  3994         prev->GetTextRun(nsTextFrame::eNotInflated);
  3995       if (uninflatedTextRun) {
  3996         SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
  4000   if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
  4001     FramePropertyTable *propTable = PresContext()->PropertyTable();
  4002     // Get all the properties from the prev-in-flow first to take
  4003     // advantage of the propTable's cache and simplify the assertion below
  4004     void* embeddingLevel = propTable->Get(aPrevInFlow, EmbeddingLevelProperty());
  4005     void* baseLevel = propTable->Get(aPrevInFlow, BaseLevelProperty());
  4006     void* paragraphDepth = propTable->Get(aPrevInFlow, ParagraphDepthProperty());
  4007     propTable->Set(this, EmbeddingLevelProperty(), embeddingLevel);
  4008     propTable->Set(this, BaseLevelProperty(), baseLevel);
  4009     propTable->Set(this, ParagraphDepthProperty(), paragraphDepth);
  4011     if (nextContinuation) {
  4012       SetNextContinuation(nextContinuation);
  4013       nextContinuation->SetPrevContinuation(this);
  4014       // Adjust next-continuations' content offset as needed.
  4015       while (nextContinuation &&
  4016              nextContinuation->GetContentOffset() < mContentOffset) {
  4017         NS_ASSERTION(
  4018           embeddingLevel == propTable->Get(nextContinuation, EmbeddingLevelProperty()) &&
  4019           baseLevel == propTable->Get(nextContinuation, BaseLevelProperty()) &&
  4020           paragraphDepth == propTable->Get(nextContinuation, ParagraphDepthProperty()),
  4021           "stealing text from different type of BIDI continuation");
  4022         nextContinuation->mContentOffset = mContentOffset;
  4023         nextContinuation = static_cast<nsTextFrame*>(nextContinuation->GetNextContinuation());
  4026     mState |= NS_FRAME_IS_BIDI;
  4027   } // prev frame is bidi
  4030 void
  4031 nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
  4033   ClearFrameOffsetCache();
  4035   // The text associated with this frame will become associated with our
  4036   // prev-continuation. If that means the text has changed style, then
  4037   // we need to wipe out the text run for the text.
  4038   // Note that mPrevContinuation can be null if we're destroying the whole
  4039   // frame chain from the start to the end.
  4040   // If this frame is mentioned in the userData for a textrun (say
  4041   // because there's a direction change at the start of this frame), then
  4042   // we have to clear the textrun because we're going away and the
  4043   // textrun had better not keep a dangling reference to us.
  4044   if (IsInTextRunUserData() ||
  4045       (mPrevContinuation &&
  4046        mPrevContinuation->StyleContext() != StyleContext())) {
  4047     ClearTextRuns();
  4048     // Clear the previous continuation's text run also, so that it can rebuild
  4049     // the text run to include our text.
  4050     if (mPrevContinuation) {
  4051       nsTextFrame *prevContinuationText =
  4052         static_cast<nsTextFrame*>(mPrevContinuation);
  4053       prevContinuationText->ClearTextRuns();
  4056   nsSplittableFrame::RemoveFromFlow(this);
  4057   // Let the base class destroy the frame
  4058   nsFrame::DestroyFrom(aDestructRoot);
  4061 nsIFrame*
  4062 nsContinuingTextFrame::FirstInFlow() const
  4064   // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
  4065   nsIFrame *firstInFlow,
  4066            *previous = const_cast<nsIFrame*>
  4067                                  (static_cast<const nsIFrame*>(this));
  4068   do {
  4069     firstInFlow = previous;
  4070     previous = firstInFlow->GetPrevInFlow();
  4071   } while (previous);
  4072   MOZ_ASSERT(firstInFlow, "post-condition failed");
  4073   return firstInFlow;
  4076 nsIFrame*
  4077 nsContinuingTextFrame::FirstContinuation() const
  4079   // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
  4080   nsIFrame *firstContinuation,
  4081   *previous = const_cast<nsIFrame*>
  4082                         (static_cast<const nsIFrame*>(mPrevContinuation));
  4084   NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?");
  4086   do {
  4087     firstContinuation = previous;
  4088     previous = firstContinuation->GetPrevContinuation();
  4089   } while (previous);
  4090   MOZ_ASSERT(firstContinuation, "post-condition failed");
  4091   return firstContinuation;
  4094 // XXX Do we want to do all the work for the first-in-flow or do the
  4095 // work for each part?  (Be careful of first-letter / first-line, though,
  4096 // especially first-line!)  Doing all the work on the first-in-flow has
  4097 // the advantage of avoiding the potential for incremental reflow bugs,
  4098 // but depends on our maintining the frame tree in reasonable ways even
  4099 // for edge cases (block-within-inline splits, nextBidi, etc.)
  4101 // XXX We really need to make :first-letter happen during frame
  4102 // construction.
  4104 // Needed for text frames in XUL.
  4105 /* virtual */ nscoord
  4106 nsTextFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
  4108   return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext);
  4111 // Needed for text frames in XUL.
  4112 /* virtual */ nscoord
  4113 nsTextFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
  4115   return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext);
  4118 /* virtual */ void
  4119 nsContinuingTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
  4120                                          InlineMinWidthData *aData)
  4122   // Do nothing, since the first-in-flow accounts for everything.
  4123   return;
  4126 /* virtual */ void
  4127 nsContinuingTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
  4128                                           InlinePrefWidthData *aData)
  4130   // Do nothing, since the first-in-flow accounts for everything.
  4131   return;
  4134 static void 
  4135 DestroySelectionDetails(SelectionDetails* aDetails)
  4137   while (aDetails) {
  4138     SelectionDetails* next = aDetails->mNext;
  4139     delete aDetails;
  4140     aDetails = next;
  4144 //----------------------------------------------------------------------
  4146 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
  4147 static void
  4148 VerifyNotDirty(nsFrameState state)
  4150   bool isZero = state & NS_FRAME_FIRST_REFLOW;
  4151   bool isDirty = state & NS_FRAME_IS_DIRTY;
  4152   if (!isZero && isDirty)
  4153     NS_WARNING("internal offsets may be out-of-sync");
  4155 #define DEBUG_VERIFY_NOT_DIRTY(state) \
  4156 VerifyNotDirty(state)
  4157 #else
  4158 #define DEBUG_VERIFY_NOT_DIRTY(state)
  4159 #endif
  4161 nsIFrame*
  4162 NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  4164   return new (aPresShell) nsTextFrame(aContext);
  4167 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
  4169 nsIFrame*
  4170 NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
  4172   return new (aPresShell) nsContinuingTextFrame(aContext);
  4175 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
  4177 nsTextFrame::~nsTextFrame()
  4181 nsresult
  4182 nsTextFrame::GetCursor(const nsPoint& aPoint,
  4183                        nsIFrame::Cursor& aCursor)
  4185   FillCursorInformationFromStyle(StyleUserInterface(), aCursor);  
  4186   if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
  4187     aCursor.mCursor = NS_STYLE_CURSOR_TEXT;
  4188     // If this is editable, we should ignore tabindex value.
  4189     if (mContent->IsEditable()) {
  4190       return NS_OK;
  4193     // If tabindex >= 0, use default cursor to indicate it's not selectable
  4194     nsIFrame *ancestorFrame = this;
  4195     while ((ancestorFrame = ancestorFrame->GetParent()) != nullptr) {
  4196       nsIContent *ancestorContent = ancestorFrame->GetContent();
  4197       if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
  4198         nsAutoString tabIndexStr;
  4199         ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
  4200         if (!tabIndexStr.IsEmpty()) {
  4201           nsresult rv;
  4202           int32_t tabIndexVal = tabIndexStr.ToInteger(&rv);
  4203           if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) {
  4204             aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
  4205             break;
  4212   return NS_OK;
  4215 nsIFrame*
  4216 nsTextFrame::LastInFlow() const
  4218   nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
  4219   while (lastInFlow->GetNextInFlow())  {
  4220     lastInFlow = static_cast<nsTextFrame*>(lastInFlow->GetNextInFlow());
  4222   MOZ_ASSERT(lastInFlow, "post-condition failed");
  4223   return lastInFlow;
  4226 nsIFrame*
  4227 nsTextFrame::LastContinuation() const
  4229   nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
  4230   while (lastContinuation->mNextContinuation)  {
  4231     lastContinuation =
  4232       static_cast<nsTextFrame*>(lastContinuation->mNextContinuation);
  4234   MOZ_ASSERT(lastContinuation, "post-condition failed");
  4235   return lastContinuation;
  4238 void
  4239 nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey)
  4241   if (IsSVGText()) {
  4242     nsIFrame* svgTextFrame =
  4243       nsLayoutUtils::GetClosestFrameOfType(GetParent(),
  4244                                            nsGkAtoms::svgTextFrame);
  4245     svgTextFrame->InvalidateFrame();
  4246     return;
  4248   nsTextFrameBase::InvalidateFrame(aDisplayItemKey);
  4251 void
  4252 nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
  4254   if (IsSVGText()) {
  4255     nsIFrame* svgTextFrame =
  4256       nsLayoutUtils::GetClosestFrameOfType(GetParent(),
  4257                                            nsGkAtoms::svgTextFrame);
  4258     svgTextFrame->InvalidateFrame();
  4259     return;
  4261   nsTextFrameBase::InvalidateFrameWithRect(aRect, aDisplayItemKey);
  4264 gfxTextRun*
  4265 nsTextFrame::GetUninflatedTextRun()
  4267   return static_cast<gfxTextRun*>(
  4268            Properties().Get(UninflatedTextRunProperty()));
  4271 void
  4272 nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
  4273                         float aInflation)
  4275   NS_ASSERTION(aTextRun, "must have text run");
  4277   // Our inflated text run is always stored in mTextRun.  In the cases
  4278   // where our current inflation is not 1.0, however, we store two text
  4279   // runs, and the uninflated one goes in a frame property.  We never
  4280   // store a single text run in both.
  4281   if (aWhichTextRun == eInflated) {
  4282     if (HasFontSizeInflation() && aInflation == 1.0f) {
  4283       // FIXME: Probably shouldn't do this within each SetTextRun
  4284       // method, but it doesn't hurt.
  4285       ClearTextRun(nullptr, nsTextFrame::eNotInflated);
  4287     SetFontSizeInflation(aInflation);
  4288   } else {
  4289     NS_ABORT_IF_FALSE(aInflation == 1.0f, "unexpected inflation");
  4290     if (HasFontSizeInflation()) {
  4291       Properties().Set(UninflatedTextRunProperty(), aTextRun);
  4292       return;
  4294     // fall through to setting mTextRun
  4297   mTextRun = aTextRun;
  4299   // FIXME: Add assertions testing the relationship between
  4300   // GetFontSizeInflation() and whether we have an uninflated text run
  4301   // (but be aware that text runs can go away).
  4304 bool
  4305 nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
  4307   if (aTextRun == mTextRun) {
  4308     mTextRun = nullptr;
  4309     return true;
  4311   FrameProperties props = Properties();
  4312   if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
  4313       props.Get(UninflatedTextRunProperty()) == aTextRun) {
  4314     props.Delete(UninflatedTextRunProperty());
  4315     return true;
  4317   return false;
  4320 void
  4321 nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
  4322                           TextRunType aWhichTextRun)
  4324   gfxTextRun* textRun = GetTextRun(aWhichTextRun);
  4325   if (!textRun) {
  4326     return;
  4329   DebugOnly<bool> checkmTextrun = textRun == mTextRun;
  4330   UnhookTextRunFromFrames(textRun, aStartContinuation);
  4331   MOZ_ASSERT(checkmTextrun ? !mTextRun
  4332                            : !Properties().Get(UninflatedTextRunProperty()));
  4334   // see comments in BuildTextRunForFrames...
  4335 //  if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
  4336 //    NS_ERROR("Shouldn't reach here for now...");
  4337 //    // the textrun's text may be referencing a DOM node that has changed,
  4338 //    // so we'd better kill this textrun now.
  4339 //    if (textRun->GetExpirationState()->IsTracked()) {
  4340 //      gTextRuns->RemoveFromCache(textRun);
  4341 //    }
  4342 //    delete textRun;
  4343 //    return;
  4344 //  }
  4346   if (!textRun->GetUserData()) {
  4347     // Remove it now because it's not doing anything useful
  4348     gTextRuns->RemoveFromCache(textRun);
  4349     delete textRun;
  4353 void
  4354 nsTextFrame::DisconnectTextRuns()
  4356   MOZ_ASSERT(!IsInTextRunUserData(),
  4357              "Textrun mentions this frame in its user data so we can't just disconnect");
  4358   mTextRun = nullptr;
  4359   if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) {
  4360     Properties().Delete(UninflatedTextRunProperty());
  4364 nsresult
  4365 nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
  4367   mContent->DeleteProperty(nsGkAtoms::newline);
  4368   if (PresContext()->BidiEnabled()) {
  4369     mContent->DeleteProperty(nsGkAtoms::flowlength);
  4372   // Find the first frame whose text has changed. Frames that are entirely
  4373   // before the text change are completely unaffected.
  4374   nsTextFrame* next;
  4375   nsTextFrame* textFrame = this;
  4376   while (true) {
  4377     next = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
  4378     if (!next || next->GetContentOffset() > int32_t(aInfo->mChangeStart))
  4379       break;
  4380     textFrame = next;
  4383   int32_t endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength;
  4384   nsTextFrame* lastDirtiedFrame = nullptr;
  4386   nsIPresShell* shell = PresContext()->GetPresShell();
  4387   do {
  4388     // textFrame contained deleted text (or the insertion point,
  4389     // if this was a pure insertion).
  4390     textFrame->mState &= ~TEXT_WHITESPACE_FLAGS;
  4391     textFrame->ClearTextRuns();
  4392     if (!lastDirtiedFrame ||
  4393         lastDirtiedFrame->GetParent() != textFrame->GetParent()) {
  4394       // Ask the parent frame to reflow me.
  4395       shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange,
  4396                               NS_FRAME_IS_DIRTY);
  4397       lastDirtiedFrame = textFrame;
  4398     } else {
  4399       // if the parent is a block, we're cheating here because we should
  4400       // be marking our line dirty, but we're not. nsTextFrame::SetLength
  4401       // will do that when it gets called during reflow.
  4402       textFrame->AddStateBits(NS_FRAME_IS_DIRTY);
  4404     textFrame->InvalidateFrame();
  4406     // Below, frames that start after the deleted text will be adjusted so that
  4407     // their offsets move with the trailing unchanged text. If this change
  4408     // deletes more text than it inserts, those frame offsets will decrease.
  4409     // We need to maintain the invariant that mContentOffset is non-decreasing
  4410     // along the continuation chain. So we need to ensure that frames that
  4411     // started in the deleted text are all still starting before the
  4412     // unchanged text.
  4413     if (textFrame->mContentOffset > endOfChangedText) {
  4414       textFrame->mContentOffset = endOfChangedText;
  4417     textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
  4418   } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo->mChangeEnd));
  4420   // This is how much the length of the string changed by --- i.e.,
  4421   // how much the trailing unchanged text moved.
  4422   int32_t sizeChange =
  4423     aInfo->mChangeStart + aInfo->mReplaceLength - aInfo->mChangeEnd;
  4425   if (sizeChange) {
  4426     // Fix the offsets of the text frames that start in the trailing
  4427     // unchanged text.
  4428     while (textFrame) {
  4429       textFrame->mContentOffset += sizeChange;
  4430       // XXX we could rescue some text runs by adjusting their user data
  4431       // to reflect the change in DOM offsets
  4432       textFrame->ClearTextRuns();
  4433       textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
  4437   return NS_OK;
  4440 /* virtual */ void
  4441 nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
  4443   nsFrame::DidSetStyleContext(aOldStyleContext);
  4446 class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry
  4448 public:
  4449   nsDisplayTextGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
  4450     : nsDisplayItemGenericGeometry(aItem, aBuilder)
  4452     nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
  4453     f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations);
  4456   /**
  4457    * We store the computed text decorations here since they are
  4458    * computed using style data from parent frames. Any changes to these
  4459    * styles will only invalidate the parent frame and not this frame.
  4460    */
  4461   nsTextFrame::TextDecorations mDecorations;
  4462 };
  4464 class nsDisplayText : public nsCharClipDisplayItem {
  4465 public:
  4466   nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) :
  4467     nsCharClipDisplayItem(aBuilder, aFrame),
  4468     mDisableSubpixelAA(false) {
  4469     MOZ_COUNT_CTOR(nsDisplayText);
  4471 #ifdef NS_BUILD_REFCNT_LOGGING
  4472   virtual ~nsDisplayText() {
  4473     MOZ_COUNT_DTOR(nsDisplayText);
  4475 #endif
  4477   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
  4478                            bool* aSnap) MOZ_OVERRIDE {
  4479     *aSnap = false;
  4480     nsRect temp = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
  4481     // Bug 748228
  4482     temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
  4483     return temp;
  4485   virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
  4486                        HitTestState* aState,
  4487                        nsTArray<nsIFrame*> *aOutFrames) MOZ_OVERRIDE {
  4488     if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
  4489       aOutFrames->AppendElement(mFrame);
  4492   virtual void Paint(nsDisplayListBuilder* aBuilder,
  4493                      nsRenderingContext* aCtx) MOZ_OVERRIDE;
  4494   NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
  4496   virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE
  4498     bool snap;
  4499     return GetBounds(aBuilder, &snap);
  4502   virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE
  4504     return new nsDisplayTextGeometry(this, aBuilder);
  4507   virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
  4508                                          const nsDisplayItemGeometry* aGeometry,
  4509                                          nsRegion *aInvalidRegion) MOZ_OVERRIDE
  4511     const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry);
  4512     nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
  4514     nsTextFrame::TextDecorations decorations;
  4515     f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
  4517     bool snap;
  4518     nsRect newRect = geometry->mBounds;
  4519     nsRect oldRect = GetBounds(aBuilder, &snap);
  4520     if (decorations != geometry->mDecorations ||
  4521         !oldRect.IsEqualInterior(newRect) ||
  4522         !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
  4523       aInvalidRegion->Or(oldRect, newRect);
  4527   virtual void DisableComponentAlpha() MOZ_OVERRIDE {
  4528     mDisableSubpixelAA = true;
  4531   bool mDisableSubpixelAA;
  4532 };
  4534 void
  4535 nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
  4536                      nsRenderingContext* aCtx) {
  4537   PROFILER_LABEL("nsDisplayText", "Paint");
  4538   // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
  4539   // antialiased pixels beyond the measured text extents.
  4540   // This is temporary until we do this in the actual calculation of text extents.
  4541   nsRect extraVisible = mVisibleRect;
  4542   nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
  4543   extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel);
  4544   nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
  4546   gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
  4547                                                     mDisableSubpixelAA);
  4548   NS_ASSERTION(mLeftEdge >= 0, "illegal left edge");
  4549   NS_ASSERTION(mRightEdge >= 0, "illegal right edge");
  4550   f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this);
  4553 void
  4554 nsTextFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
  4555                               const nsRect&           aDirtyRect,
  4556                               const nsDisplayListSet& aLists)
  4558   if (!IsVisibleForPainting(aBuilder))
  4559     return;
  4561   DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
  4563   aLists.Content()->AppendNewToTop(
  4564     new (aBuilder) nsDisplayText(aBuilder, this));
  4567 static nsIFrame*
  4568 GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
  4570   *aIsBefore = false;
  4571   while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
  4572     if (aFrame->StyleContext()->GetPseudo() == nsCSSPseudoElements::before) {
  4573       *aIsBefore = true;
  4575     aFrame = aFrame->GetParent();
  4577   return aFrame;
  4580 SelectionDetails*
  4581 nsTextFrame::GetSelectionDetails()
  4583   const nsFrameSelection* frameSelection = GetConstFrameSelection();
  4584   if (frameSelection->GetTableCellSelection()) {
  4585     return nullptr;
  4587   if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
  4588     SelectionDetails* details =
  4589       frameSelection->LookUpSelection(mContent, GetContentOffset(),
  4590                                       GetContentLength(), false);
  4591     SelectionDetails* sd;
  4592     for (sd = details; sd; sd = sd->mNext) {
  4593       sd->mStart += mContentOffset;
  4594       sd->mEnd += mContentOffset;
  4596     return details;
  4599   // Check if the beginning or end of the element is selected, depending on
  4600   // whether we're :before content or :after content.
  4601   bool isBefore;
  4602   nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
  4603   if (!owner || !owner->GetContent())
  4604     return nullptr;
  4606   SelectionDetails* details =
  4607     frameSelection->LookUpSelection(owner->GetContent(),
  4608         isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false);
  4609   SelectionDetails* sd;
  4610   for (sd = details; sd; sd = sd->mNext) {
  4611     // The entire text is selected!
  4612     sd->mStart = GetContentOffset();
  4613     sd->mEnd = GetContentEnd();
  4615   return details;
  4618 static void
  4619 PaintSelectionBackground(gfxContext* aCtx, nsPresContext* aPresContext,
  4620                          nscolor aColor, const gfxRect& aDirtyRect,
  4621                          const gfxRect& aRect,
  4622                          nsTextFrame::DrawPathCallbacks* aCallbacks)
  4624   if (aCallbacks) {
  4625     aCallbacks->NotifyBeforeSelectionBackground(aColor);
  4628   gfxRect r = aRect.Intersect(aDirtyRect);
  4629   // For now, we need to put this in pixel coordinates
  4630   int32_t app = aPresContext->AppUnitsPerDevPixel();
  4631   aCtx->NewPath();
  4632   // pixel-snap
  4633   aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app,
  4634                           r.Width() / app, r.Height() / app), true);
  4636   if (aCallbacks) {
  4637     aCallbacks->NotifySelectionBackgroundPathEmitted();
  4638   } else {
  4639     aCtx->SetColor(gfxRGBA(aColor));
  4640     aCtx->Fill();
  4644 void
  4645 nsTextFrame::GetTextDecorations(
  4646                     nsPresContext* aPresContext,
  4647                     nsTextFrame::TextDecorationColorResolution aColorResolution,
  4648                     nsTextFrame::TextDecorations& aDecorations)
  4650   const nsCompatibility compatMode = aPresContext->CompatibilityMode();
  4652   bool useOverride = false;
  4653   nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
  4655   // frameTopOffset represents the offset to f's top from our baseline in our
  4656   // coordinate space
  4657   // baselineOffset represents the offset from our baseline to f's baseline or
  4658   // the nearest block's baseline, in our coordinate space, whichever is closest
  4659   // during the particular iteration
  4660   nscoord frameTopOffset = mAscent,
  4661           baselineOffset = 0;
  4663   bool nearestBlockFound = false;
  4665   for (nsIFrame* f = this, *fChild = nullptr;
  4666        f;
  4667        fChild = f,
  4668        f = nsLayoutUtils::GetParentOrPlaceholderFor(f))
  4670     nsStyleContext *const context = f->StyleContext();
  4671     if (!context->HasTextDecorationLines()) {
  4672       break;
  4675     const nsStyleTextReset *const styleText = context->StyleTextReset();
  4676     const uint8_t textDecorations = styleText->mTextDecorationLine;
  4678     if (!useOverride &&
  4679         (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) {
  4680       // This handles the <a href="blah.html"><font color="green">La 
  4681       // la la</font></a> case. The link underline should be green.
  4682       useOverride = true;
  4683       overrideColor =
  4684         nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
  4687     const bool firstBlock = !nearestBlockFound && nsLayoutUtils::GetAsBlock(f);
  4689     // Not updating positions once we hit a parent block is equivalent to
  4690     // the CSS 2.1 spec that blocks should propagate decorations down to their
  4691     // children (albeit the style should be preserved)
  4692     // However, if we're vertically aligned within a block, then we need to
  4693     // recover the right baseline from the line by querying the FrameProperty
  4694     // that should be set (see nsLineLayout::VerticalAlignLine).
  4695     if (firstBlock) {
  4696       // At this point, fChild can't be null since TextFrames can't be blocks
  4697       if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
  4698         // Since offset is the offset in the child's coordinate space, we have
  4699         // to undo the accumulation to bring the transform out of the block's
  4700         // coordinate space
  4701         baselineOffset =
  4702           frameTopOffset - fChild->GetNormalPosition().y
  4703           - NS_PTR_TO_INT32(
  4704               fChild->Properties().Get(nsIFrame::LineBaselineOffset()));
  4707     else if (!nearestBlockFound) {
  4708       baselineOffset = frameTopOffset - f->GetBaseline();
  4711     nearestBlockFound = nearestBlockFound || firstBlock;
  4712     frameTopOffset += f->GetNormalPosition().y;
  4714     const uint8_t style = styleText->GetDecorationStyle();
  4715     if (textDecorations) {
  4716       nscolor color;
  4717       if (useOverride) {
  4718         color = overrideColor;
  4719       } else if (IsSVGText()) {
  4720         // XXX We might want to do something with text-decoration-color when
  4721         //     painting SVG text, but it's not clear what we should do.  We
  4722         //     at least need SVG text decorations to paint with 'fill' if
  4723         //     text-decoration-color has its initial value currentColor.
  4724         //     We could choose to interpret currentColor as "currentFill"
  4725         //     for SVG text, and have e.g. text-decoration-color:red to
  4726         //     override the fill paint of the decoration.
  4727         color = aColorResolution == eResolvedColors ?
  4728                   nsLayoutUtils::GetColor(f, eCSSProperty_fill) :
  4729                   NS_SAME_AS_FOREGROUND_COLOR;
  4730       } else {
  4731         color = nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
  4734       if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
  4735         aDecorations.mUnderlines.AppendElement(
  4736           nsTextFrame::LineDecoration(f, baselineOffset, color, style));
  4738       if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
  4739         aDecorations.mOverlines.AppendElement(
  4740           nsTextFrame::LineDecoration(f, baselineOffset, color, style));
  4742       if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
  4743         aDecorations.mStrikes.AppendElement(
  4744           nsTextFrame::LineDecoration(f, baselineOffset, color, style));
  4748     // In all modes, if we're on an inline-block or inline-table (or
  4749     // inline-stack, inline-box, inline-grid), we're done.
  4750     uint8_t display = f->GetDisplay();
  4751     if (display != NS_STYLE_DISPLAY_INLINE &&
  4752         nsStyleDisplay::IsDisplayTypeInlineOutside(display)) {
  4753       break;
  4756     if (compatMode == eCompatibility_NavQuirks) {
  4757       // In quirks mode, if we're on an HTML table element, we're done.
  4758       if (f->GetContent()->IsHTML(nsGkAtoms::table)) {
  4759         break;
  4761     } else {
  4762       // In standards/almost-standards mode, if we're on an
  4763       // absolutely-positioned element or a floating element, we're done.
  4764       if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
  4765         break;
  4771 static float
  4772 GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
  4774   if (aFrame->IsSVGText()) {
  4775     const nsIFrame* container = aFrame;
  4776     while (container->GetType() != nsGkAtoms::svgTextFrame) {
  4777       container = container->GetParent();
  4779     NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
  4780     return
  4781       static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
  4783   return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
  4786 void
  4787 nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
  4788                                      const nsHTMLReflowState& aBlockReflowState,
  4789                                      PropertyProvider& aProvider,
  4790                                      nsRect* aVisualOverflowRect,
  4791                                      bool aIncludeTextDecorations)
  4793   // Text-shadow overflows
  4794   nsRect shadowRect =
  4795     nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
  4796   aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
  4798   if (IsFloatingFirstLetterChild()) {
  4799     // The underline/overline drawable area must be contained in the overflow
  4800     // rect when this is in floating first letter frame at *both* modes.
  4801     nsIFrame* firstLetterFrame = aBlockReflowState.frame;
  4802     uint8_t decorationStyle = firstLetterFrame->StyleContext()->
  4803                                 StyleTextReset()->GetDecorationStyle();
  4804     // If the style is none, let's include decoration line rect as solid style
  4805     // since changing the style from none to solid/dotted/dashed doesn't cause
  4806     // reflow.
  4807     if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  4808       decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
  4810     nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
  4811     nscoord underlineOffset, underlineSize;
  4812     fontMetrics->GetUnderline(underlineOffset, underlineSize);
  4813     nscoord maxAscent = fontMetrics->MaxAscent();
  4815     gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
  4816     gfxFloat gfxWidth = aVisualOverflowRect->width / appUnitsPerDevUnit;
  4817     gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit;
  4818     gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit;
  4819     gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit;
  4820     gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit;
  4821     nsRect underlineRect =
  4822       nsCSSRendering::GetTextDecorationRect(aPresContext,
  4823         gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset,
  4824         NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle);
  4825     nsRect overlineRect =
  4826       nsCSSRendering::GetTextDecorationRect(aPresContext,
  4827         gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent,
  4828         NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle);
  4830     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
  4831     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
  4833     // XXX If strikeoutSize is much thicker than the underlineSize, it may
  4834     //     cause overflowing from the overflow rect.  However, such case
  4835     //     isn't realistic, we don't need to compute it now.
  4837   if (aIncludeTextDecorations) {
  4838     // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
  4839     // style and position, they can be drawn at virtually any y-offset, so
  4840     // maxima and minima are required to reliably generate the rectangle for
  4841     // them
  4842     TextDecorations textDecs;
  4843     GetTextDecorations(aPresContext, eResolvedColors, textDecs);
  4844     if (textDecs.HasDecorationLines()) {
  4845       nscoord inflationMinFontSize =
  4846         nsLayoutUtils::InflationMinFontSizeFor(aBlockReflowState.frame);
  4848       const nscoord width = GetSize().width;
  4849       const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
  4850                      gfxWidth = width / appUnitsPerDevUnit,
  4851                      ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
  4852       nscoord top(nscoord_MAX), bottom(nscoord_MIN);
  4853       // Below we loop through all text decorations and compute the rectangle
  4854       // containing all of them, in this frame's coordinate space
  4855       for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) {
  4856         const LineDecoration& dec = textDecs.mUnderlines[i];
  4857         uint8_t decorationStyle = dec.mStyle;
  4858         // If the style is solid, let's include decoration line rect of solid
  4859         // style since changing the style from none to solid/dotted/dashed
  4860         // doesn't cause reflow.
  4861         if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  4862           decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
  4865         float inflation =
  4866           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
  4867         const gfxFont::Metrics metrics =
  4868           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
  4870         const nsRect decorationRect =
  4871           nsCSSRendering::GetTextDecorationRect(aPresContext,
  4872             gfxSize(gfxWidth, metrics.underlineSize),
  4873             ascent, metrics.underlineOffset,
  4874             NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle) +
  4875           nsPoint(0, -dec.mBaselineOffset);
  4877         top = std::min(decorationRect.y, top);
  4878         bottom = std::max(decorationRect.YMost(), bottom);
  4880       for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) {
  4881         const LineDecoration& dec = textDecs.mOverlines[i];
  4882         uint8_t decorationStyle = dec.mStyle;
  4883         // If the style is solid, let's include decoration line rect of solid
  4884         // style since changing the style from none to solid/dotted/dashed
  4885         // doesn't cause reflow.
  4886         if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  4887           decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
  4890         float inflation =
  4891           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
  4892         const gfxFont::Metrics metrics =
  4893           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
  4895         const nsRect decorationRect =
  4896           nsCSSRendering::GetTextDecorationRect(aPresContext,
  4897             gfxSize(gfxWidth, metrics.underlineSize),
  4898             ascent, metrics.maxAscent,
  4899             NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle) +
  4900           nsPoint(0, -dec.mBaselineOffset);
  4902         top = std::min(decorationRect.y, top);
  4903         bottom = std::max(decorationRect.YMost(), bottom);
  4905       for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) {
  4906         const LineDecoration& dec = textDecs.mStrikes[i];
  4907         uint8_t decorationStyle = dec.mStyle;
  4908         // If the style is solid, let's include decoration line rect of solid
  4909         // style since changing the style from none to solid/dotted/dashed
  4910         // doesn't cause reflow.
  4911         if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  4912           decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
  4915         float inflation =
  4916           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
  4917         const gfxFont::Metrics metrics =
  4918           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
  4920         const nsRect decorationRect =
  4921           nsCSSRendering::GetTextDecorationRect(aPresContext,
  4922             gfxSize(gfxWidth, metrics.strikeoutSize),
  4923             ascent, metrics.strikeoutOffset,
  4924             NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle) +
  4925           nsPoint(0, -dec.mBaselineOffset);
  4926         top = std::min(decorationRect.y, top);
  4927         bottom = std::max(decorationRect.YMost(), bottom);
  4930       aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
  4931                                      nsRect(0, top, width, bottom - top));
  4934   // When this frame is not selected, the text-decoration area must be in
  4935   // frame bounds.
  4936   if (!IsSelected() ||
  4937       !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
  4938     return;
  4939   AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
  4942 static gfxFloat
  4943 ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext,
  4944                                          nsTextFrame* aFrame,
  4945                                          const gfxFont::Metrics& aFontMetrics)
  4947   gfxFloat app = aPresContext->AppUnitsPerDevPixel();
  4948   nscoord lineHeightApp =
  4949     nsHTMLReflowState::CalcLineHeight(aFrame->GetContent(),
  4950                                       aFrame->StyleContext(), NS_AUTOHEIGHT,
  4951                                       aFrame->GetFontSizeInflation());
  4952   gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
  4953   if (lineHeight <= aFontMetrics.maxHeight) {
  4954     return aFontMetrics.maxDescent;
  4956   return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
  4960 // Make sure this stays in sync with DrawSelectionDecorations below
  4961 static const SelectionType SelectionTypesWithDecorations =
  4962   nsISelectionController::SELECTION_SPELLCHECK |
  4963   nsISelectionController::SELECTION_IME_RAWINPUT |
  4964   nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT |
  4965   nsISelectionController::SELECTION_IME_CONVERTEDTEXT |
  4966   nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT;
  4968 static gfxFloat
  4969 ComputeSelectionUnderlineHeight(nsPresContext* aPresContext,
  4970                                 const gfxFont::Metrics& aFontMetrics,
  4971                                 SelectionType aSelectionType)
  4973   switch (aSelectionType) {
  4974     case nsISelectionController::SELECTION_IME_RAWINPUT:
  4975     case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
  4976     case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
  4977     case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
  4978       return aFontMetrics.underlineSize;
  4979     case nsISelectionController::SELECTION_SPELLCHECK: {
  4980       // The thickness of the spellchecker underline shouldn't honor the font
  4981       // metrics.  It should be constant pixels value which is decided from the
  4982       // default font size.  Note that if the actual font size is smaller than
  4983       // the default font size, we should use the actual font size because the
  4984       // computed value from the default font size can be too thick for the
  4985       // current font size.
  4986       int32_t defaultFontSize =
  4987         aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size);
  4988       gfxFloat fontSize = std::min(gfxFloat(defaultFontSize),
  4989                                  aFontMetrics.emHeight);
  4990       fontSize = std::max(fontSize, 1.0);
  4991       return ceil(fontSize / 20);
  4993     default:
  4994       NS_WARNING("Requested underline style is not valid");
  4995       return aFontMetrics.underlineSize;
  4999 enum DecorationType {
  5000   eNormalDecoration,
  5001   eSelectionDecoration
  5002 };
  5004 static void
  5005 PaintDecorationLine(nsIFrame* aFrame,
  5006                     gfxContext* const aCtx,
  5007                     const gfxRect& aDirtyRect,
  5008                     nscolor aColor,
  5009                     const nscolor* aOverrideColor,
  5010                     const gfxPoint& aPt,
  5011                     gfxFloat aXInFrame,
  5012                     const gfxSize& aLineSize,
  5013                     gfxFloat aAscent,
  5014                     gfxFloat aOffset,
  5015                     uint8_t aDecoration,
  5016                     uint8_t aStyle,
  5017                     DecorationType aDecorationType,
  5018                     nsTextFrame::DrawPathCallbacks* aCallbacks,
  5019                     gfxFloat aDescentLimit = -1.0)
  5021   nscolor lineColor = aOverrideColor ? *aOverrideColor : aColor;
  5022   if (aCallbacks) {
  5023     if (aDecorationType == eNormalDecoration) {
  5024       aCallbacks->NotifyBeforeDecorationLine(lineColor);
  5025     } else {
  5026       aCallbacks->NotifyBeforeSelectionDecorationLine(lineColor);
  5028     nsCSSRendering::DecorationLineToPath(aFrame, aCtx, aDirtyRect, lineColor,
  5029       aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle,
  5030       aDescentLimit);
  5031     if (aDecorationType == eNormalDecoration) {
  5032       aCallbacks->NotifyDecorationLinePathEmitted();
  5033     } else {
  5034       aCallbacks->NotifySelectionDecorationLinePathEmitted();
  5036   } else {
  5037     nsCSSRendering::PaintDecorationLine(aFrame, aCtx, aDirtyRect, lineColor,
  5038       aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle,
  5039       aDescentLimit);
  5043 /**
  5044  * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
  5045  * drawing text decoration for selections.
  5046  */
  5047 static void DrawSelectionDecorations(gfxContext* aContext,
  5048     const gfxRect& aDirtyRect,
  5049     SelectionType aType,
  5050     nsTextFrame* aFrame,
  5051     nsTextPaintStyle& aTextPaintStyle,
  5052     const TextRangeStyle &aRangeStyle,
  5053     const gfxPoint& aPt, gfxFloat aXInFrame, gfxFloat aWidth,
  5054     gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics,
  5055     nsTextFrame::DrawPathCallbacks* aCallbacks)
  5057   gfxPoint pt(aPt);
  5058   gfxSize size(aWidth,
  5059                ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(),
  5060                                                aFontMetrics, aType));
  5061   gfxFloat descentLimit =
  5062     ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
  5063                                              aFrame, aFontMetrics);
  5065   float relativeSize;
  5066   uint8_t style;
  5067   nscolor color;
  5068   int32_t index =
  5069     nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType);
  5070   bool weDefineSelectionUnderline =
  5071     aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color,
  5072                                                   &relativeSize, &style);
  5074   switch (aType) {
  5075     case nsISelectionController::SELECTION_IME_RAWINPUT:
  5076     case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
  5077     case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
  5078     case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: {
  5079       // IME decoration lines should not be drawn on the both ends, i.e., we
  5080       // need to cut both edges of the decoration lines.  Because same style
  5081       // IME selections can adjoin, but the users need to be able to know
  5082       // where are the boundaries of the selections.
  5083       //
  5084       //  X: underline
  5085       //
  5086       //     IME selection #1        IME selection #2      IME selection #3
  5087       //  |                     |                      |                    
  5088       //  | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
  5089       //  +---------------------+----------------------+--------------------
  5090       //   ^                   ^ ^                    ^ ^
  5091       //  gap                  gap                    gap
  5092       pt.x += 1.0;
  5093       size.width -= 2.0;
  5094       if (aRangeStyle.IsDefined()) {
  5095         // If IME defines the style, that should override our definition.
  5096         if (aRangeStyle.IsLineStyleDefined()) {
  5097           if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
  5098             return;
  5100           style = aRangeStyle.mLineStyle;
  5101           relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
  5102         } else if (!weDefineSelectionUnderline) {
  5103           // There is no underline style definition.
  5104           return;
  5106         if (aRangeStyle.IsUnderlineColorDefined()) {
  5107           color = aRangeStyle.mUnderlineColor;
  5108         } else if (aRangeStyle.IsForegroundColorDefined()) {
  5109           color = aRangeStyle.mForegroundColor;
  5110         } else {
  5111           NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(),
  5112                        "Only the background color is defined");
  5113           color = aTextPaintStyle.GetTextColor();
  5115       } else if (!weDefineSelectionUnderline) {
  5116         // IME doesn't specify the selection style and we don't define selection
  5117         // underline.
  5118         return;
  5120       break;
  5122     case nsISelectionController::SELECTION_SPELLCHECK:
  5123       if (!weDefineSelectionUnderline)
  5124         return;
  5125       break;
  5126     default:
  5127       NS_WARNING("Requested selection decorations when there aren't any");
  5128       return;
  5130   size.height *= relativeSize;
  5131   PaintDecorationLine(aFrame, aContext, aDirtyRect, color, nullptr, pt,
  5132     pt.x - aPt.x + aXInFrame, size, aAscent, aFontMetrics.underlineOffset,
  5133     NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, style, eSelectionDecoration,
  5134     aCallbacks, descentLimit);
  5137 /**
  5138  * This function encapsulates all knowledge of how selections affect foreground
  5139  * and background colors.
  5140  * @return true if the selection affects colors, false otherwise
  5141  * @param aForeground the foreground color to use
  5142  * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
  5143  * background should be painted
  5144  */
  5145 static bool GetSelectionTextColors(SelectionType aType,
  5146                                      nsTextPaintStyle& aTextPaintStyle,
  5147                                      const TextRangeStyle &aRangeStyle,
  5148                                      nscolor* aForeground, nscolor* aBackground)
  5150   switch (aType) {
  5151     case nsISelectionController::SELECTION_NORMAL:
  5152       return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
  5153     case nsISelectionController::SELECTION_FIND:
  5154       aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
  5155       return true;
  5156     case nsISelectionController::SELECTION_URLSECONDARY:
  5157       aTextPaintStyle.GetURLSecondaryColor(aForeground);
  5158       *aBackground = NS_RGBA(0,0,0,0);
  5159       return true;
  5160     case nsISelectionController::SELECTION_IME_RAWINPUT:
  5161     case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
  5162     case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
  5163     case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
  5164       if (aRangeStyle.IsDefined()) {
  5165         *aForeground = aTextPaintStyle.GetTextColor();
  5166         *aBackground = NS_RGBA(0,0,0,0);
  5167         if (!aRangeStyle.IsForegroundColorDefined() &&
  5168             !aRangeStyle.IsBackgroundColorDefined()) {
  5169           return false;
  5171         if (aRangeStyle.IsForegroundColorDefined()) {
  5172           *aForeground = aRangeStyle.mForegroundColor;
  5174         if (aRangeStyle.IsBackgroundColorDefined()) {
  5175           *aBackground = aRangeStyle.mBackgroundColor;
  5177         return true;
  5179       aTextPaintStyle.GetIMESelectionColors(
  5180         nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType),
  5181         aForeground, aBackground);
  5182       return true;
  5183     default:
  5184       *aForeground = aTextPaintStyle.GetTextColor();
  5185       *aBackground = NS_RGBA(0,0,0,0);
  5186       return false;
  5190 /**
  5191  * This sets *aShadow to the appropriate shadow, if any, for the given
  5192  * type of selection. Returns true if *aShadow was set.
  5193  * If text-shadow was not specified, *aShadow is left untouched
  5194  * (NOT reset to null), and the function returns false.
  5195  */
  5196 static bool GetSelectionTextShadow(nsIFrame* aFrame,
  5197                                    SelectionType aType,
  5198                                    nsTextPaintStyle& aTextPaintStyle,
  5199                                    nsCSSShadowArray** aShadow)
  5201   switch (aType) {
  5202     case nsISelectionController::SELECTION_NORMAL:
  5203       return aTextPaintStyle.GetSelectionShadow(aShadow);
  5204     default:
  5205       return false;
  5209 /**
  5210  * This class lets us iterate over chunks of text in a uniform selection state,
  5211  * observing cluster boundaries, in content order, maintaining the current
  5212  * x-offset as we go, and telling whether the text chunk has a hyphen after
  5213  * it or not. The caller is responsible for actually computing the advance
  5214  * width of each chunk.
  5215  */
  5216 class SelectionIterator {
  5217 public:
  5218   /**
  5219    * aStart and aLength are in the original string. aSelectionDetails is
  5220    * according to the original string.
  5221    * @param aXOffset the offset from the origin of the frame to the start
  5222    * of the text (the left baseline origin for LTR, the right baseline origin
  5223    * for RTL)
  5224    */
  5225   SelectionIterator(SelectionDetails** aSelectionDetails,
  5226                     int32_t aStart, int32_t aLength,
  5227                     PropertyProvider& aProvider, gfxTextRun* aTextRun,
  5228                     gfxFloat aXOffset);
  5230   /**
  5231    * Returns the next segment of uniformly selected (or not) text.
  5232    * @param aXOffset the offset from the origin of the frame to the start
  5233    * of the text (the left baseline origin for LTR, the right baseline origin
  5234    * for RTL)
  5235    * @param aOffset the transformed string offset of the text for this segment
  5236    * @param aLength the transformed string length of the text for this segment
  5237    * @param aHyphenWidth if a hyphen is to be rendered after the text, the
  5238    * width of the hyphen, otherwise zero
  5239    * @param aType the selection type for this segment
  5240    * @param aStyle the selection style for this segment
  5241    * @return false if there are no more segments
  5242    */
  5243   bool GetNextSegment(gfxFloat* aXOffset, uint32_t* aOffset, uint32_t* aLength,
  5244                         gfxFloat* aHyphenWidth, SelectionType* aType,
  5245                         TextRangeStyle* aStyle);
  5246   void UpdateWithAdvance(gfxFloat aAdvance) {
  5247     mXOffset += aAdvance*mTextRun->GetDirection();
  5250 private:
  5251   SelectionDetails**      mSelectionDetails;
  5252   PropertyProvider&       mProvider;
  5253   gfxTextRun*             mTextRun;
  5254   gfxSkipCharsIterator    mIterator;
  5255   int32_t                 mOriginalStart;
  5256   int32_t                 mOriginalEnd;
  5257   gfxFloat                mXOffset;
  5258 };
  5260 SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
  5261     int32_t aStart, int32_t aLength, PropertyProvider& aProvider,
  5262     gfxTextRun* aTextRun, gfxFloat aXOffset)
  5263   : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
  5264     mTextRun(aTextRun), mIterator(aProvider.GetStart()),
  5265     mOriginalStart(aStart), mOriginalEnd(aStart + aLength),
  5266     mXOffset(aXOffset)
  5268   mIterator.SetOriginalOffset(aStart);
  5271 bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
  5272     uint32_t* aOffset, uint32_t* aLength, gfxFloat* aHyphenWidth,
  5273     SelectionType* aType, TextRangeStyle* aStyle)
  5275   if (mIterator.GetOriginalOffset() >= mOriginalEnd)
  5276     return false;
  5278   // save offset into transformed string now
  5279   uint32_t runOffset = mIterator.GetSkippedOffset();
  5281   int32_t index = mIterator.GetOriginalOffset() - mOriginalStart;
  5282   SelectionDetails* sdptr = mSelectionDetails[index];
  5283   SelectionType type =
  5284     sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE;
  5285   TextRangeStyle style;
  5286   if (sdptr) {
  5287     style = sdptr->mTextRangeStyle;
  5289   for (++index; mOriginalStart + index < mOriginalEnd; ++index) {
  5290     if (sdptr != mSelectionDetails[index])
  5291       break;
  5293   mIterator.SetOriginalOffset(index + mOriginalStart);
  5295   // Advance to the next cluster boundary
  5296   while (mIterator.GetOriginalOffset() < mOriginalEnd &&
  5297          !mIterator.IsOriginalCharSkipped() &&
  5298          !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
  5299     mIterator.AdvanceOriginal(1);
  5302   bool haveHyphenBreak =
  5303     (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
  5304   *aOffset = runOffset;
  5305   *aLength = mIterator.GetSkippedOffset() - runOffset;
  5306   *aXOffset = mXOffset;
  5307   *aHyphenWidth = 0;
  5308   if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) {
  5309     *aHyphenWidth = mProvider.GetHyphenWidth();
  5311   *aType = type;
  5312   *aStyle = style;
  5313   return true;
  5316 static void
  5317 AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun,
  5318                    gfxTextRun::Metrics* aMetrics,
  5319                    gfxFont::BoundingBoxType aBoundingBoxType,
  5320                    gfxContext* aContext)
  5322   // Fix up metrics to include hyphen
  5323   nsAutoPtr<gfxTextRun> hyphenTextRun(
  5324     GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame));
  5325   if (!hyphenTextRun.get())
  5326     return;
  5328   gfxTextRun::Metrics hyphenMetrics =
  5329     hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(),
  5330                                aBoundingBoxType, aContext, nullptr);
  5331   aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
  5334 void
  5335 nsTextFrame::PaintOneShadow(uint32_t aOffset, uint32_t aLength,
  5336                             nsCSSShadowItem* aShadowDetails,
  5337                             PropertyProvider* aProvider, const nsRect& aDirtyRect,
  5338                             const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
  5339                             gfxContext* aCtx, const nscolor& aForegroundColor,
  5340                             const nsCharClipDisplayItem::ClipEdges& aClipEdges,
  5341                             nscoord aLeftSideOffset, gfxRect& aBoundingBox)
  5343   PROFILER_LABEL("nsTextFrame", "PaintOneShadow");
  5344   gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
  5345   nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
  5347   // This rect is the box which is equivalent to where the shadow will be painted.
  5348   // The origin of aBoundingBox is the text baseline left, so we must translate it by
  5349   // that much in order to make the origin the top-left corner of the text bounding box.
  5350   gfxRect shadowGfxRect = aBoundingBox +
  5351     gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset;
  5352   nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
  5353                     NSToCoordRound(shadowGfxRect.Y()),
  5354                     NSToCoordRound(shadowGfxRect.Width()),
  5355                     NSToCoordRound(shadowGfxRect.Height()));
  5357   nsContextBoxBlur contextBoxBlur;
  5358   gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
  5359                                                   PresContext()->AppUnitsPerDevPixel(),
  5360                                                   aCtx, aDirtyRect, nullptr);
  5361   if (!shadowContext)
  5362     return;
  5364   nscolor shadowColor;
  5365   const nscolor* decorationOverrideColor;
  5366   if (aShadowDetails->mHasColor) {
  5367     shadowColor = aShadowDetails->mColor;
  5368     decorationOverrideColor = &shadowColor;
  5369   } else {
  5370     shadowColor = aForegroundColor;
  5371     decorationOverrideColor = nullptr;
  5374   aCtx->Save();
  5375   aCtx->NewPath();
  5376   aCtx->SetColor(gfxRGBA(shadowColor));
  5378   // Draw the text onto our alpha-only surface to capture the alpha values.
  5379   // Remember that the box blur context has a device offset on it, so we don't need to
  5380   // translate any coordinates to fit on the surface.
  5381   gfxFloat advanceWidth;
  5382   gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
  5383                     aDirtyRect.width, aDirtyRect.height);
  5384   DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset,
  5385            aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider,
  5386            nsTextPaintStyle(this),
  5387            aCtx == shadowContext ? shadowColor : NS_RGB(0, 0, 0), aClipEdges,
  5388            advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
  5389            decorationOverrideColor);
  5391   contextBoxBlur.DoPaint();
  5392   aCtx->Restore();
  5395 // Paints selection backgrounds and text in the correct colors. Also computes
  5396 // aAllTypes, the union of all selection types that are applying to this text.
  5397 bool
  5398 nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
  5399     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
  5400     const gfxRect& aDirtyRect,
  5401     PropertyProvider& aProvider,
  5402     uint32_t aContentOffset, uint32_t aContentLength,
  5403     nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
  5404     SelectionType* aAllTypes,
  5405     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
  5406     nsTextFrame::DrawPathCallbacks* aCallbacks)
  5408   // Figure out which selections control the colors to use for each character.
  5409   AutoFallibleTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
  5410   SelectionDetails** prevailingSelections =
  5411     prevailingSelectionsBuffer.AppendElements(aContentLength);
  5412   if (!prevailingSelections) {
  5413     return false;
  5416   SelectionType allTypes = 0;
  5417   for (uint32_t i = 0; i < aContentLength; ++i) {
  5418     prevailingSelections[i] = nullptr;
  5421   SelectionDetails *sdptr = aDetails;
  5422   bool anyBackgrounds = false;
  5423   while (sdptr) {
  5424     int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
  5425     int32_t end = std::min(int32_t(aContentLength),
  5426                          sdptr->mEnd - int32_t(aContentOffset));
  5427     SelectionType type = sdptr->mType;
  5428     if (start < end) {
  5429       allTypes |= type;
  5430       // Ignore selections that don't set colors
  5431       nscolor foreground, background;
  5432       if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle,
  5433                                  &foreground, &background)) {
  5434         if (NS_GET_A(background) > 0) {
  5435           anyBackgrounds = true;
  5437         for (int32_t i = start; i < end; ++i) {
  5438           // Favour normal selection over IME selections
  5439           if (!prevailingSelections[i] ||
  5440               type < prevailingSelections[i]->mType) {
  5441             prevailingSelections[i] = sdptr;
  5446     sdptr = sdptr->mNext;
  5448   *aAllTypes = allTypes;
  5450   if (!allTypes) {
  5451     // Nothing is selected in the given text range. XXX can this still occur?
  5452     return false;
  5455   const gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x;
  5456   gfxFloat xOffset, hyphenWidth;
  5457   uint32_t offset, length; // in transformed string
  5458   SelectionType type;
  5459   TextRangeStyle rangeStyle;
  5460   // Draw background colors
  5461   if (anyBackgrounds) {
  5462     SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
  5463                                aProvider, mTextRun, startXOffset);
  5464     while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
  5465                                    &type, &rangeStyle)) {
  5466       nscolor foreground, background;
  5467       GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
  5468                              &foreground, &background);
  5469       // Draw background color
  5470       gfxFloat advance = hyphenWidth +
  5471         mTextRun->GetAdvanceWidth(offset, length, &aProvider);
  5472       if (NS_GET_A(background) > 0) {
  5473         gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0);
  5474         PaintSelectionBackground(aCtx, aTextPaintStyle.PresContext(),
  5475                                  background, aDirtyRect,
  5476                                  gfxRect(aFramePt.x + x, aFramePt.y, advance,
  5477                                  GetSize().height), aCallbacks);
  5479       iterator.UpdateWithAdvance(advance);
  5483   // Draw text
  5484   const nsStyleText* textStyle = StyleText();
  5485   nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
  5486                    aDirtyRect.width, aDirtyRect.height);
  5487   SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
  5488                              aProvider, mTextRun, startXOffset);
  5489   while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
  5490                                  &type, &rangeStyle)) {
  5491     nscolor foreground, background;
  5492     GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
  5493                            &foreground, &background);
  5494     gfxPoint textBaselinePt(aFramePt.x + xOffset, aTextBaselinePt.y);
  5496     // Determine what shadow, if any, to draw - either from textStyle
  5497     // or from the ::-moz-selection pseudo-class if specified there
  5498     nsCSSShadowArray* shadow = textStyle->GetTextShadow();
  5499     GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow);
  5501     // Draw shadows, if any
  5502     if (shadow) {
  5503       gfxTextRun::Metrics shadowMetrics =
  5504         mTextRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS,
  5505                               nullptr, &aProvider);
  5506       if (GetStateBits() & TEXT_HYPHEN_BREAK) {
  5507         AddHyphenToMetrics(this, mTextRun, &shadowMetrics,
  5508                            gfxFont::LOOSE_INK_EXTENTS, aCtx);
  5510       for (uint32_t i = shadow->Length(); i > 0; --i) {
  5511         PaintOneShadow(offset, length,
  5512                        shadow->ShadowAt(i - 1), &aProvider,
  5513                        dirtyRect, aFramePt, textBaselinePt, aCtx,
  5514                        foreground, aClipEdges, 
  5515                        xOffset - (mTextRun->IsRightToLeft() ?
  5516                                   shadowMetrics.mBoundingBox.width : 0),
  5517                        shadowMetrics.mBoundingBox);
  5521     // Draw text segment
  5522     gfxFloat advance;
  5524     DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt,
  5525              offset, length, aProvider, aTextPaintStyle, foreground, aClipEdges,
  5526              advance, hyphenWidth > 0, nullptr, nullptr, aCallbacks);
  5527     if (hyphenWidth) {
  5528       advance += hyphenWidth;
  5530     iterator.UpdateWithAdvance(advance);
  5532   return true;
  5535 void
  5536 nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
  5537     const gfxPoint& aFramePt,
  5538     const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
  5539     PropertyProvider& aProvider,
  5540     uint32_t aContentOffset, uint32_t aContentLength,
  5541     nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
  5542     SelectionType aSelectionType,
  5543     nsTextFrame::DrawPathCallbacks* aCallbacks)
  5545   // Hide text decorations if we're currently hiding @font-face fallback text
  5546   if (aProvider.GetFontGroup()->ShouldSkipDrawing())
  5547     return;
  5549   // Figure out which characters will be decorated for this selection.
  5550   AutoFallibleTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
  5551   SelectionDetails** selectedChars =
  5552     selectedCharsBuffer.AppendElements(aContentLength);
  5553   if (!selectedChars) {
  5554     return;
  5556   for (uint32_t i = 0; i < aContentLength; ++i) {
  5557     selectedChars[i] = nullptr;
  5560   SelectionDetails *sdptr = aDetails;
  5561   while (sdptr) {
  5562     if (sdptr->mType == aSelectionType) {
  5563       int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
  5564       int32_t end = std::min(int32_t(aContentLength),
  5565                            sdptr->mEnd - int32_t(aContentOffset));
  5566       for (int32_t i = start; i < end; ++i) {
  5567         selectedChars[i] = sdptr;
  5570     sdptr = sdptr->mNext;
  5573   gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0);
  5574   if (!firstFont)
  5575     return; // OOM
  5576   gfxFont::Metrics decorationMetrics(firstFont->GetMetrics());
  5577   decorationMetrics.underlineOffset =
  5578     aProvider.GetFontGroup()->GetUnderlineOffset();
  5580   gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x;
  5581   SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
  5582                              aProvider, mTextRun, startXOffset);
  5583   gfxFloat xOffset, hyphenWidth;
  5584   uint32_t offset, length;
  5585   int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
  5586   // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
  5587   gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app);
  5588   gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
  5589                     aDirtyRect.width / app, aDirtyRect.height / app);
  5590   SelectionType type;
  5591   TextRangeStyle selectedStyle;
  5592   while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
  5593                                  &type, &selectedStyle)) {
  5594     gfxFloat advance = hyphenWidth +
  5595       mTextRun->GetAdvanceWidth(offset, length, &aProvider);
  5596     if (type == aSelectionType) {
  5597       pt.x = (aFramePt.x + xOffset -
  5598              (mTextRun->IsRightToLeft() ? advance : 0)) / app;
  5599       gfxFloat width = Abs(advance) / app;
  5600       gfxFloat xInFrame = pt.x - (aFramePt.x / app);
  5601       DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this,
  5602                                aTextPaintStyle, selectedStyle, pt, xInFrame,
  5603                                width, mAscent / app, decorationMetrics,
  5604                                aCallbacks);
  5606     iterator.UpdateWithAdvance(advance);
  5610 bool
  5611 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
  5612     const gfxPoint& aFramePt,
  5613     const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
  5614     PropertyProvider& aProvider,
  5615     uint32_t aContentOffset, uint32_t aContentLength,
  5616     nsTextPaintStyle& aTextPaintStyle,
  5617     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
  5618     gfxTextContextPaint* aContextPaint,
  5619     nsTextFrame::DrawPathCallbacks* aCallbacks)
  5621   NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
  5623   SelectionDetails* details = GetSelectionDetails();
  5624   if (!details) {
  5625     return false;
  5628   SelectionType allTypes;
  5629   if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
  5630                                     aProvider, aContentOffset, aContentLength,
  5631                                     aTextPaintStyle, details, &allTypes,
  5632                                     aClipEdges, aCallbacks)) {
  5633     DestroySelectionDetails(details);
  5634     return false;
  5636   // Iterate through just the selection types that paint decorations and
  5637   // paint decorations for any that actually occur in this frame. Paint
  5638   // higher-numbered selection types below lower-numered ones on the
  5639   // general principal that lower-numbered selections are higher priority.
  5640   allTypes &= SelectionTypesWithDecorations;
  5641   for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1;
  5642        i >= 1; --i) {
  5643     SelectionType type = 1 << (i - 1);
  5644     if (allTypes & type) {
  5645       // There is some selection of this type. Try to paint its decorations
  5646       // (there might not be any for this type but that's OK,
  5647       // PaintTextSelectionDecorations will exit early).
  5648       PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
  5649                                     aProvider, aContentOffset, aContentLength,
  5650                                     aTextPaintStyle, details, type,
  5651                                     aCallbacks);
  5655   DestroySelectionDetails(details);
  5656   return true;
  5659 nscolor
  5660 nsTextFrame::GetCaretColorAt(int32_t aOffset)
  5662   NS_PRECONDITION(aOffset >= 0, "aOffset must be positive");
  5664   nscolor result = nsFrame::GetCaretColorAt(aOffset);
  5665   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  5666   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
  5667   int32_t contentOffset = provider.GetStart().GetOriginalOffset();
  5668   int32_t contentLength = provider.GetOriginalLength();
  5669   NS_PRECONDITION(aOffset >= contentOffset &&
  5670                   aOffset <= contentOffset + contentLength,
  5671                   "aOffset must be in the frame's range");
  5672   int32_t offsetInFrame = aOffset - contentOffset;
  5673   if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
  5674     return result;
  5677   bool isSolidTextColor = true;
  5678   if (IsSVGText()) {
  5679     const nsStyleSVG* style = StyleSVG();
  5680     if (style->mFill.mType != eStyleSVGPaintType_None &&
  5681         style->mFill.mType != eStyleSVGPaintType_Color) {
  5682       isSolidTextColor = false;
  5686   nsTextPaintStyle textPaintStyle(this);
  5687   textPaintStyle.SetResolveColors(isSolidTextColor);
  5688   SelectionDetails* details = GetSelectionDetails();
  5689   SelectionDetails* sdptr = details;
  5690   SelectionType type = 0;
  5691   while (sdptr) {
  5692     int32_t start = std::max(0, sdptr->mStart - contentOffset);
  5693     int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
  5694     if (start <= offsetInFrame && offsetInFrame < end &&
  5695         (type == 0 || sdptr->mType < type)) {
  5696       nscolor foreground, background;
  5697       if (GetSelectionTextColors(sdptr->mType, textPaintStyle,
  5698                                  sdptr->mTextRangeStyle,
  5699                                  &foreground, &background)) {
  5700         if (!isSolidTextColor &&
  5701             NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
  5702           result = NS_RGBA(0, 0, 0, 255);
  5703         } else {
  5704           result = foreground;
  5706         type = sdptr->mType;
  5709     sdptr = sdptr->mNext;
  5712   DestroySelectionDetails(details);
  5713   return result;
  5716 static uint32_t
  5717 ComputeTransformedLength(PropertyProvider& aProvider)
  5719   gfxSkipCharsIterator iter(aProvider.GetStart());
  5720   uint32_t start = iter.GetSkippedOffset();
  5721   iter.AdvanceOriginal(aProvider.GetOriginalLength());
  5722   return iter.GetSkippedOffset() - start;
  5725 bool
  5726 nsTextFrame::MeasureCharClippedText(nscoord aLeftEdge, nscoord aRightEdge,
  5727                                     nscoord* aSnappedLeftEdge,
  5728                                     nscoord* aSnappedRightEdge)
  5730   // We need a *reference* rendering context (not one that might have a
  5731   // transform), so we don't have a rendering context argument.
  5732   // XXX get the block and line passed to us somehow! This is slow!
  5733   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  5734   if (!mTextRun)
  5735     return false;
  5737   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
  5738   // Trim trailing whitespace
  5739   provider.InitializeForDisplay(true);
  5741   uint32_t startOffset = provider.GetStart().GetSkippedOffset();
  5742   uint32_t maxLength = ComputeTransformedLength(provider);
  5743   return MeasureCharClippedText(provider, aLeftEdge, aRightEdge,
  5744                                 &startOffset, &maxLength,
  5745                                 aSnappedLeftEdge, aSnappedRightEdge);
  5748 static uint32_t GetClusterLength(gfxTextRun* aTextRun,
  5749                                  uint32_t    aStartOffset,
  5750                                  uint32_t    aMaxLength,
  5751                                  bool        aIsRTL)
  5753   uint32_t clusterLength = aIsRTL ? 0 : 1;
  5754   while (clusterLength < aMaxLength) {
  5755     if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
  5756       if (aIsRTL) {
  5757         ++clusterLength;
  5759       break;
  5761     ++clusterLength;
  5763   return clusterLength;
  5766 bool
  5767 nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider,
  5768                                     nscoord aLeftEdge, nscoord aRightEdge,
  5769                                     uint32_t* aStartOffset,
  5770                                     uint32_t* aMaxLength,
  5771                                     nscoord*  aSnappedLeftEdge,
  5772                                     nscoord*  aSnappedRightEdge)
  5774   *aSnappedLeftEdge = 0;
  5775   *aSnappedRightEdge = 0;
  5776   if (aLeftEdge <= 0 && aRightEdge <= 0) {
  5777     return true;
  5780   uint32_t offset = *aStartOffset;
  5781   uint32_t maxLength = *aMaxLength;
  5782   const nscoord frameWidth = GetSize().width;
  5783   const bool rtl = mTextRun->IsRightToLeft();
  5784   gfxFloat advanceWidth = 0;
  5785   const nscoord startEdge = rtl ? aRightEdge : aLeftEdge;
  5786   if (startEdge > 0) {
  5787     const gfxFloat maxAdvance = gfxFloat(startEdge);
  5788     while (maxLength > 0) {
  5789       uint32_t clusterLength =
  5790         GetClusterLength(mTextRun, offset, maxLength, rtl);
  5791       advanceWidth +=
  5792         mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
  5793       maxLength -= clusterLength;
  5794       offset += clusterLength;
  5795       if (advanceWidth >= maxAdvance) {
  5796         break;
  5799     nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge;
  5800     *snappedStartEdge = NSToCoordFloor(advanceWidth);
  5801     *aStartOffset = offset;
  5804   const nscoord endEdge = rtl ? aLeftEdge : aRightEdge;
  5805   if (endEdge > 0) {
  5806     const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge);
  5807     while (maxLength > 0) {
  5808       uint32_t clusterLength =
  5809         GetClusterLength(mTextRun, offset, maxLength, rtl);
  5810       gfxFloat nextAdvance = advanceWidth +
  5811         mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
  5812       if (nextAdvance > maxAdvance) {
  5813         break;
  5815       // This cluster fits, include it.
  5816       advanceWidth = nextAdvance;
  5817       maxLength -= clusterLength;
  5818       offset += clusterLength;
  5820     maxLength = offset - *aStartOffset;
  5821     nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge;
  5822     *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth);
  5824   *aMaxLength = maxLength;
  5825   return maxLength != 0;
  5828 void
  5829 nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
  5830                        const nsRect& aDirtyRect,
  5831                        const nsCharClipDisplayItem& aItem,
  5832                        gfxTextContextPaint* aContextPaint,
  5833                        nsTextFrame::DrawPathCallbacks* aCallbacks)
  5835   // Don't pass in aRenderingContext here, because we need a *reference*
  5836   // context and aRenderingContext might have some transform in it
  5837   // XXX get the block and line passed to us somehow! This is slow!
  5838   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  5839   if (!mTextRun)
  5840     return;
  5842   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
  5843   // Trim trailing whitespace
  5844   provider.InitializeForDisplay(true);
  5846   gfxContext* ctx = aRenderingContext->ThebesContext();
  5847   const bool rtl = mTextRun->IsRightToLeft();
  5848   const nscoord frameWidth = GetSize().width;
  5849   gfxPoint framePt(aPt.x, aPt.y);
  5850   gfxPoint textBaselinePt(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
  5851              nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
  5852   uint32_t startOffset = provider.GetStart().GetSkippedOffset();
  5853   uint32_t maxLength = ComputeTransformedLength(provider);
  5854   nscoord snappedLeftEdge, snappedRightEdge;
  5855   if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge,
  5856          &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
  5857     return;
  5859   textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
  5860   nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
  5861                                              snappedRightEdge);
  5862   nsTextPaintStyle textPaintStyle(this);
  5863   textPaintStyle.SetResolveColors(!aCallbacks);
  5865   gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
  5866                     aDirtyRect.width, aDirtyRect.height);
  5867   // Fork off to the (slower) paint-with-selection path if necessary.
  5868   if (IsSelected()) {
  5869     gfxSkipCharsIterator tmp(provider.GetStart());
  5870     int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset);
  5871     int32_t contentLength =
  5872       tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset;
  5873     if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect,
  5874                                provider, contentOffset, contentLength,
  5875                                textPaintStyle, clipEdges, aContextPaint,
  5876                                aCallbacks)) {
  5877       return;
  5881   nscolor foregroundColor = textPaintStyle.GetTextColor();
  5882   if (!aCallbacks) {
  5883     const nsStyleText* textStyle = StyleText();
  5884     if (textStyle->HasTextShadow()) {
  5885       // Text shadow happens with the last value being painted at the back,
  5886       // ie. it is painted first.
  5887       gfxTextRun::Metrics shadowMetrics = 
  5888         mTextRun->MeasureText(startOffset, maxLength, gfxFont::LOOSE_INK_EXTENTS,
  5889                               nullptr, &provider);
  5890       for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
  5891         PaintOneShadow(startOffset, maxLength,
  5892                        textStyle->mTextShadow->ShadowAt(i - 1), &provider,
  5893                        aDirtyRect, framePt, textBaselinePt, ctx,
  5894                        foregroundColor, clipEdges,
  5895                        snappedLeftEdge, shadowMetrics.mBoundingBox);
  5900   gfxFloat advanceWidth;
  5901   DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider,
  5902            textPaintStyle, foregroundColor, clipEdges, advanceWidth,
  5903            (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
  5904            nullptr, aContextPaint, aCallbacks);
  5907 static void
  5908 DrawTextRun(gfxTextRun* aTextRun,
  5909             gfxContext* const aCtx,
  5910             const gfxPoint& aTextBaselinePt,
  5911             uint32_t aOffset, uint32_t aLength,
  5912             PropertyProvider* aProvider,
  5913             nscolor aTextColor,
  5914             gfxFloat* aAdvanceWidth,
  5915             gfxTextContextPaint* aContextPaint,
  5916             nsTextFrame::DrawPathCallbacks* aCallbacks)
  5918   DrawMode drawMode = aCallbacks ? DrawMode::GLYPH_PATH :
  5919                                    DrawMode::GLYPH_FILL;
  5920   if (aCallbacks) {
  5921     aCallbacks->NotifyBeforeText(aTextColor);
  5922     aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
  5923                    aProvider, aAdvanceWidth, aContextPaint, aCallbacks);
  5924     aCallbacks->NotifyAfterText();
  5925   } else {
  5926     aCtx->SetColor(gfxRGBA(aTextColor));
  5927     aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
  5928                    aProvider, aAdvanceWidth, aContextPaint);
  5932 void
  5933 nsTextFrame::DrawTextRun(gfxContext* const aCtx,
  5934                          const gfxPoint& aTextBaselinePt,
  5935                          uint32_t aOffset, uint32_t aLength,
  5936                          PropertyProvider& aProvider,
  5937                          nscolor aTextColor,
  5938                          gfxFloat& aAdvanceWidth,
  5939                          bool aDrawSoftHyphen,
  5940                          gfxTextContextPaint* aContextPaint,
  5941                          nsTextFrame::DrawPathCallbacks* aCallbacks)
  5943   ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aOffset, aLength, &aProvider,
  5944                 aTextColor, &aAdvanceWidth, aContextPaint, aCallbacks);
  5946   if (aDrawSoftHyphen) {
  5947     // Don't use ctx as the context, because we need a reference context here,
  5948     // ctx may be transformed.
  5949     nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nullptr, this));
  5950     if (hyphenTextRun.get()) {
  5951       // For right-to-left text runs, the soft-hyphen is positioned at the left
  5952       // of the text, minus its own width
  5953       gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth -
  5954         (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nullptr) : 0);
  5955       ::DrawTextRun(hyphenTextRun.get(), aCtx,
  5956                     gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
  5957                     0, hyphenTextRun->GetLength(),
  5958                     nullptr, aTextColor, nullptr, aContextPaint, aCallbacks);
  5963 void
  5964 nsTextFrame::DrawTextRunAndDecorations(
  5965     gfxContext* const aCtx, const gfxRect& aDirtyRect,
  5966     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
  5967     uint32_t aOffset, uint32_t aLength,
  5968     PropertyProvider& aProvider,
  5969     const nsTextPaintStyle& aTextStyle,
  5970     nscolor aTextColor,
  5971     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
  5972     gfxFloat& aAdvanceWidth,
  5973     bool aDrawSoftHyphen,
  5974     const TextDecorations& aDecorations,
  5975     const nscolor* const aDecorationOverrideColor,
  5976     gfxTextContextPaint* aContextPaint,
  5977     nsTextFrame::DrawPathCallbacks* aCallbacks)
  5979     const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
  5981     // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
  5982     nscoord x = NSToCoordRound(aFramePt.x);
  5983     nscoord width = GetRect().width;
  5984     aClipEdges.Intersect(&x, &width);
  5986     gfxPoint decPt(x / app, 0);
  5987     gfxSize decSize(width / app, 0);
  5988     const gfxFloat ascent = gfxFloat(mAscent) / app;
  5989     const gfxFloat frameTop = aFramePt.y;
  5991     gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
  5992                       aDirtyRect.Width() / app, aDirtyRect.Height() / app);
  5994     nscoord inflationMinFontSize =
  5995       nsLayoutUtils::InflationMinFontSizeFor(this);
  5997     // Underlines
  5998     for (uint32_t i = aDecorations.mUnderlines.Length(); i-- > 0; ) {
  5999       const LineDecoration& dec = aDecorations.mUnderlines[i];
  6000       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  6001         continue;
  6004       float inflation =
  6005         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
  6006       const gfxFont::Metrics metrics =
  6007         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
  6009       decSize.height = metrics.underlineSize;
  6010       decPt.y = (frameTop - dec.mBaselineOffset) / app;
  6012       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
  6013         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
  6014         metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
  6015         dec.mStyle, eNormalDecoration, aCallbacks);
  6017     // Overlines
  6018     for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) {
  6019       const LineDecoration& dec = aDecorations.mOverlines[i];
  6020       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  6021         continue;
  6024       float inflation =
  6025         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
  6026       const gfxFont::Metrics metrics =
  6027         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
  6029       decSize.height = metrics.underlineSize;
  6030       decPt.y = (frameTop - dec.mBaselineOffset) / app;
  6032       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
  6033         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
  6034         metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
  6035         eNormalDecoration, aCallbacks);
  6038     // CSS 2.1 mandates that text be painted after over/underlines, and *then*
  6039     // line-throughs
  6040     DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
  6041                 aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
  6043     // Line-throughs
  6044     for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) {
  6045       const LineDecoration& dec = aDecorations.mStrikes[i];
  6046       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
  6047         continue;
  6050       float inflation =
  6051         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
  6052       const gfxFont::Metrics metrics =
  6053         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
  6055       decSize.height = metrics.strikeoutSize;
  6056       decPt.y = (frameTop - dec.mBaselineOffset) / app;
  6058       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
  6059         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
  6060         metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
  6061         dec.mStyle, eNormalDecoration, aCallbacks);
  6065 void
  6066 nsTextFrame::DrawText(
  6067     gfxContext* const aCtx, const gfxRect& aDirtyRect,
  6068     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
  6069     uint32_t aOffset, uint32_t aLength,
  6070     PropertyProvider& aProvider,
  6071     const nsTextPaintStyle& aTextStyle,
  6072     nscolor aTextColor,
  6073     const nsCharClipDisplayItem::ClipEdges& aClipEdges,
  6074     gfxFloat& aAdvanceWidth,
  6075     bool aDrawSoftHyphen,
  6076     const nscolor* const aDecorationOverrideColor,
  6077     gfxTextContextPaint* aContextPaint,
  6078     nsTextFrame::DrawPathCallbacks* aCallbacks)
  6080   TextDecorations decorations;
  6081   GetTextDecorations(aTextStyle.PresContext(),
  6082                      aCallbacks ? eUnresolvedColors : eResolvedColors,
  6083                      decorations);
  6085   // Hide text decorations if we're currently hiding @font-face fallback text
  6086   const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
  6087                                decorations.HasDecorationLines();
  6088   if (drawDecorations) {
  6089     DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength,
  6090                               aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth,
  6091                               aDrawSoftHyphen, decorations,
  6092                               aDecorationOverrideColor, aContextPaint, aCallbacks);
  6093   } else {
  6094     DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider,
  6095                 aTextColor, aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
  6099 int16_t
  6100 nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags)
  6102   // get the selection controller
  6103   nsCOMPtr<nsISelectionController> selectionController;
  6104   nsresult rv = GetSelectionController(PresContext(),
  6105                                        getter_AddRefs(selectionController));
  6106   if (NS_FAILED(rv) || !selectionController)
  6107     return nsISelectionController::SELECTION_OFF;
  6109   selectionController->GetSelectionFlags(aSelectionFlags);
  6111   int16_t selectionValue;
  6112   selectionController->GetDisplaySelection(&selectionValue);
  6114   return selectionValue;
  6117 bool
  6118 nsTextFrame::IsVisibleInSelection(nsISelection* aSelection)
  6120   // Check the quick way first
  6121   if (!GetContent()->IsSelectionDescendant())
  6122     return false;
  6124   SelectionDetails* details = GetSelectionDetails();
  6125   bool found = false;
  6127   // where are the selection points "really"
  6128   SelectionDetails *sdptr = details;
  6129   while (sdptr) {
  6130     if (sdptr->mEnd > GetContentOffset() &&
  6131         sdptr->mStart < GetContentEnd() &&
  6132         sdptr->mType == nsISelectionController::SELECTION_NORMAL) {
  6133       found = true;
  6134       break;
  6136     sdptr = sdptr->mNext;
  6138   DestroySelectionDetails(details);
  6140   return found;
  6143 /**
  6144  * Compute the longest prefix of text whose width is <= aWidth. Return
  6145  * the length of the prefix. Also returns the width of the prefix in aFitWidth.
  6146  */
  6147 static uint32_t
  6148 CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength,
  6149               gfxFloat aWidth, PropertyProvider* aProvider,
  6150               gfxFloat* aFitWidth)
  6152   uint32_t last = 0;
  6153   gfxFloat width = 0;
  6154   for (uint32_t i = 1; i <= aLength; ++i) {
  6155     if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
  6156       gfxFloat nextWidth = width +
  6157           aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
  6158       if (nextWidth > aWidth)
  6159         break;
  6160       last = i;
  6161       width = nextWidth;
  6164   *aFitWidth = width;
  6165   return last;
  6168 nsIFrame::ContentOffsets
  6169 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint)
  6171   return GetCharacterOffsetAtFramePointInternal(aPoint, true);
  6174 nsIFrame::ContentOffsets
  6175 nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint)
  6177   return GetCharacterOffsetAtFramePointInternal(aPoint, false);
  6180 nsIFrame::ContentOffsets
  6181 nsTextFrame::GetCharacterOffsetAtFramePointInternal(nsPoint aPoint,
  6182                                                     bool aForInsertionPoint)
  6184   ContentOffsets offsets;
  6186   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  6187   if (!mTextRun)
  6188     return offsets;
  6190   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
  6191   // Trim leading but not trailing whitespace if possible
  6192   provider.InitializeForDisplay(false);
  6193   gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x;
  6194   gfxFloat fitWidth;
  6195   uint32_t skippedLength = ComputeTransformedLength(provider);
  6197   uint32_t charsFit = CountCharsFit(mTextRun,
  6198       provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth);
  6200   int32_t selectedOffset;
  6201   if (charsFit < skippedLength) {
  6202     // charsFit characters fitted, but no more could fit. See if we're
  6203     // more than halfway through the cluster.. If we are, choose the next
  6204     // cluster.
  6205     gfxSkipCharsIterator extraCluster(provider.GetStart());
  6206     extraCluster.AdvanceSkipped(charsFit);
  6207     gfxSkipCharsIterator extraClusterLastChar(extraCluster);
  6208     FindClusterEnd(mTextRun,
  6209                    provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
  6210                    &extraClusterLastChar);
  6211     gfxFloat charWidth =
  6212         mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
  6213                                   GetSkippedDistance(extraCluster, extraClusterLastChar) + 1,
  6214                                   &provider);
  6215     selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2
  6216         ? extraCluster.GetOriginalOffset()
  6217         : extraClusterLastChar.GetOriginalOffset() + 1;
  6218   } else {
  6219     // All characters fitted, we're at (or beyond) the end of the text.
  6220     // XXX This could be some pathological situation where negative spacing
  6221     // caused characters to move backwards. We can't really handle that
  6222     // in the current frame system because frames can't have negative
  6223     // intrinsic widths.
  6224     selectedOffset =
  6225         provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
  6226     // If we're at the end of a preformatted line which has a terminating
  6227     // linefeed, we want to reduce the offset by one to make sure that the
  6228     // selection is placed before the linefeed character.
  6229     if (HasSignificantTerminalNewline()) {
  6230       --selectedOffset;
  6234   offsets.content = GetContent();
  6235   offsets.offset = offsets.secondaryOffset = selectedOffset;
  6236   offsets.associateWithNext = mContentOffset == offsets.offset;
  6237   return offsets;
  6240 bool
  6241 nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
  6242                                            nsRect& aRect)
  6244   if (aRect.IsEmpty())
  6245     return false;
  6247   nsRect givenRect = aRect;
  6249   nsRefPtr<nsFontMetrics> fm;
  6250   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
  6251                                         GetFontSizeInflation());
  6252   gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
  6253   gfxFont* firstFont = fontGroup->GetFontAt(0);
  6254   if (!firstFont)
  6255     return false; // OOM
  6256   const gfxFont::Metrics& metrics = firstFont->GetMetrics();
  6257   gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
  6258   gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
  6259   gfxFloat descentLimit =
  6260     ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics);
  6262   SelectionDetails *details = GetSelectionDetails();
  6263   for (SelectionDetails *sd = details; sd; sd = sd->mNext) {
  6264     if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations))
  6265       continue;
  6267     uint8_t style;
  6268     float relativeSize;
  6269     int32_t index =
  6270       nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType);
  6271     if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) {
  6272       if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr,
  6273                                                    &relativeSize, &style)) {
  6274         continue;
  6276     } else {
  6277       // IME selections
  6278       TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
  6279       if (rangeStyle.IsDefined()) {
  6280         if (!rangeStyle.IsLineStyleDefined() ||
  6281             rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
  6282           continue;
  6284         style = rangeStyle.mLineStyle;
  6285         relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
  6286       } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index,
  6287                                                           nullptr, &relativeSize,
  6288                                                           &style)) {
  6289         continue;
  6292     nsRect decorationArea;
  6293     gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width),
  6294                  ComputeSelectionUnderlineHeight(aPresContext,
  6295                                                  metrics, sd->mType));
  6296     relativeSize = std::max(relativeSize, 1.0f);
  6297     size.height *= relativeSize;
  6298     decorationArea =
  6299       nsCSSRendering::GetTextDecorationRect(aPresContext, size,
  6300         ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
  6301         style, descentLimit);
  6302     aRect.UnionRect(aRect, decorationArea);
  6304   DestroySelectionDetails(details);
  6306   return !aRect.IsEmpty() && !givenRect.Contains(aRect);
  6309 bool
  6310 nsTextFrame::IsFrameSelected() const
  6312   NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(),
  6313                "use the public IsSelected() instead");
  6314   return nsRange::IsNodeSelected(GetContent(), GetContentOffset(),
  6315                                  GetContentEnd());
  6318 void
  6319 nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected,
  6320                               SelectionType aType)
  6322   NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
  6323   DEBUG_VERIFY_NOT_DIRTY(mState);
  6325   // Selection is collapsed, which can't affect text frame rendering
  6326   if (aStart == aEnd)
  6327     return;
  6329   nsTextFrame* f = this;
  6330   while (f && f->GetContentEnd() <= int32_t(aStart)) {
  6331     f = static_cast<nsTextFrame*>(f->GetNextContinuation());
  6334   nsPresContext* presContext = PresContext();
  6335   while (f && f->GetContentOffset() < int32_t(aEnd)) {
  6336     // We may need to reflow to recompute the overflow area for
  6337     // spellchecking or IME underline if their underline is thicker than
  6338     // the normal decoration line.
  6339     if (aType & SelectionTypesWithDecorations) {
  6340       bool didHaveOverflowingSelection =
  6341         (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
  6342       nsRect r(nsPoint(0, 0), GetSize());
  6343       bool willHaveOverflowingSelection =
  6344         aSelected && f->CombineSelectionUnderlineRect(presContext, r);
  6345       if (didHaveOverflowingSelection || willHaveOverflowingSelection) {
  6346         presContext->PresShell()->FrameNeedsReflow(f,
  6347                                                    nsIPresShell::eStyleChange,
  6348                                                    NS_FRAME_IS_DIRTY);
  6351     // Selection might change anything. Invalidate the overflow area.
  6352     f->InvalidateFrame();
  6354     f = static_cast<nsTextFrame*>(f->GetNextContinuation());
  6358 nsresult
  6359 nsTextFrame::GetPointFromOffset(int32_t inOffset,
  6360                                 nsPoint* outPoint)
  6362   if (!outPoint)
  6363     return NS_ERROR_NULL_POINTER;
  6365   outPoint->x = 0;
  6366   outPoint->y = 0;
  6368   DEBUG_VERIFY_NOT_DIRTY(mState);
  6369   if (mState & NS_FRAME_IS_DIRTY)
  6370     return NS_ERROR_UNEXPECTED;
  6372   if (GetContentLength() <= 0) {
  6373     return NS_OK;
  6376   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  6377   if (!mTextRun)
  6378     return NS_ERROR_FAILURE;
  6380   PropertyProvider properties(this, iter, nsTextFrame::eInflated);
  6381   // Don't trim trailing whitespace, we want the caret to appear in the right
  6382   // place if it's positioned there
  6383   properties.InitializeForDisplay(false);  
  6385   if (inOffset < GetContentOffset()){
  6386     NS_WARNING("offset before this frame's content");
  6387     inOffset = GetContentOffset();
  6388   } else if (inOffset > GetContentEnd()) {
  6389     NS_WARNING("offset after this frame's content");
  6390     inOffset = GetContentEnd();
  6392   int32_t trimmedOffset = properties.GetStart().GetOriginalOffset();
  6393   int32_t trimmedEnd = trimmedOffset + properties.GetOriginalLength();
  6394   inOffset = std::max(inOffset, trimmedOffset);
  6395   inOffset = std::min(inOffset, trimmedEnd);
  6397   iter.SetOriginalOffset(inOffset);
  6399   if (inOffset < trimmedEnd &&
  6400       !iter.IsOriginalCharSkipped() &&
  6401       !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
  6402     NS_WARNING("GetPointFromOffset called for non-cluster boundary");
  6403     FindClusterStart(mTextRun, trimmedOffset, &iter);
  6406   gfxFloat advanceWidth =
  6407     mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(),
  6408                               GetSkippedDistance(properties.GetStart(), iter),
  6409                               &properties);
  6410   nscoord width = NSToCoordCeilClamped(advanceWidth);
  6412   if (mTextRun->IsRightToLeft()) {
  6413     outPoint->x = mRect.width - width;
  6414   } else {
  6415     outPoint->x = width;
  6417   outPoint->y = 0;
  6419   return NS_OK;
  6422 nsresult
  6423 nsTextFrame::GetChildFrameContainingOffset(int32_t   aContentOffset,
  6424                                            bool      aHint,
  6425                                            int32_t*  aOutOffset,
  6426                                            nsIFrame**aOutFrame)
  6428   DEBUG_VERIFY_NOT_DIRTY(mState);
  6429 #if 0 //XXXrbs disable due to bug 310227
  6430   if (mState & NS_FRAME_IS_DIRTY)
  6431     return NS_ERROR_UNEXPECTED;
  6432 #endif
  6434   NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
  6435   NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!");
  6436   nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
  6437   if (this != primaryFrame) {
  6438     // This call needs to happen on the primary frame
  6439     return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
  6440                                                        aOutOffset, aOutFrame);
  6443   nsTextFrame* f = this;
  6444   int32_t offset = mContentOffset;
  6446   // Try to look up the offset to frame property
  6447   nsTextFrame* cachedFrame = static_cast<nsTextFrame*>
  6448     (Properties().Get(OffsetToFrameProperty()));
  6450   if (cachedFrame) {
  6451     f = cachedFrame;
  6452     offset = f->GetContentOffset();
  6454     f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
  6457   if ((aContentOffset >= offset) &&
  6458       (aHint || aContentOffset != offset)) {
  6459     while (true) {
  6460       nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextContinuation());
  6461       if (!next || aContentOffset < next->GetContentOffset())
  6462         break;
  6463       if (aContentOffset == next->GetContentOffset()) {
  6464         if (aHint) {
  6465           f = next;
  6466           if (f->GetContentLength() == 0) {
  6467             continue; // use the last of the empty frames with this offset
  6470         break;
  6472       f = next;
  6474   } else {
  6475     while (true) {
  6476       nsTextFrame* prev = static_cast<nsTextFrame*>(f->GetPrevContinuation());
  6477       if (!prev || aContentOffset > f->GetContentOffset())
  6478         break;
  6479       if (aContentOffset == f->GetContentOffset()) {
  6480         if (!aHint) {
  6481           f = prev;
  6482           if (f->GetContentLength() == 0) {
  6483             continue; // use the first of the empty frames with this offset
  6486         break;
  6488       f = prev;
  6492   *aOutOffset = aContentOffset - f->GetContentOffset();
  6493   *aOutFrame = f;
  6495   // cache the frame we found
  6496   Properties().Set(OffsetToFrameProperty(), f);
  6497   f->AddStateBits(TEXT_IN_OFFSET_CACHE);
  6499   return NS_OK;
  6502 nsIFrame::FrameSearchResult
  6503 nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset)
  6505   NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range");
  6507   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  6508   if (!mTextRun)
  6509     return CONTINUE_EMPTY;
  6511   TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
  6512   // Check whether there are nonskipped characters in the trimmmed range
  6513   return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
  6514          iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE;
  6517 /**
  6518  * This class iterates through the clusters before or after the given
  6519  * aPosition (which is a content offset). You can test each cluster
  6520  * to see if it's whitespace (as far as selection/caret movement is concerned),
  6521  * or punctuation, or if there is a word break before the cluster. ("Before"
  6522  * is interpreted according to aDirection, so if aDirection is -1, "before"
  6523  * means actually *after* the cluster content.)
  6524  */
  6525 class MOZ_STACK_CLASS ClusterIterator {
  6526 public:
  6527   ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection,
  6528                   nsString& aContext);
  6530   bool NextCluster();
  6531   bool IsWhitespace();
  6532   bool IsPunctuation();
  6533   bool HaveWordBreakBefore() { return mHaveWordBreak; }
  6534   int32_t GetAfterOffset();
  6535   int32_t GetBeforeOffset();
  6537 private:
  6538   gfxSkipCharsIterator        mIterator;
  6539   const nsTextFragment*       mFrag;
  6540   nsTextFrame*                mTextFrame;
  6541   int32_t                     mDirection;
  6542   int32_t                     mCharIndex;
  6543   nsTextFrame::TrimmedOffsets mTrimmed;
  6544   nsTArray<bool>      mWordBreaks;
  6545   bool                        mHaveWordBreak;
  6546 };
  6548 static bool
  6549 IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
  6550                           bool aRespectClusters,
  6551                           gfxTextRun* aTextRun,
  6552                           nsIFrame* aFrame)
  6554   if (aIter.IsOriginalCharSkipped())
  6555     return false;
  6556   uint32_t index = aIter.GetSkippedOffset();
  6557   if (aRespectClusters && !aTextRun->IsClusterStart(index))
  6558     return false;
  6559   if (index > 0) {
  6560     // Check whether the proposed position is in between the two halves of a
  6561     // surrogate pair; if so, this is not a valid character boundary.
  6562     // (In the case where we are respecting clusters, we won't actually get
  6563     // this far because the low surrogate is also marked as non-clusterStart
  6564     // so we'll return FALSE above.)
  6565     if (aTextRun->CharIsLowSurrogate(index)) {
  6566       return false;
  6569   return true;
  6572 nsIFrame::FrameSearchResult
  6573 nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset,
  6574                                  bool aRespectClusters)
  6576   int32_t contentLength = GetContentLength();
  6577   NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
  6579   bool selectable;
  6580   uint8_t selectStyle;  
  6581   IsSelectable(&selectable, &selectStyle);
  6582   if (selectStyle == NS_STYLE_USER_SELECT_ALL)
  6583     return CONTINUE_UNSELECTABLE;
  6585   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  6586   if (!mTextRun)
  6587     return CONTINUE_EMPTY;
  6589   TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
  6591   // A negative offset means "end of frame".
  6592   int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
  6594   if (!aForward) {
  6595     // If at the beginning of the line, look at the previous continuation
  6596     for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
  6597          i >= trimmed.mStart; --i) {
  6598       iter.SetOriginalOffset(i);
  6599       if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
  6600         *aOffset = i - mContentOffset;
  6601         return FOUND;
  6604     *aOffset = 0;
  6605   } else {
  6606     // If we're at the end of a line, look at the next continuation
  6607     iter.SetOriginalOffset(startOffset);
  6608     if (startOffset <= trimmed.GetEnd() &&
  6609         !(startOffset < trimmed.GetEnd() &&
  6610           StyleText()->NewlineIsSignificant() &&
  6611           iter.GetSkippedOffset() < mTextRun->GetLength() &&
  6612           mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
  6613       for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
  6614         iter.SetOriginalOffset(i);
  6615         if (i == trimmed.GetEnd() ||
  6616             IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
  6617           *aOffset = i - mContentOffset;
  6618           return FOUND;
  6622     *aOffset = contentLength;
  6625   return CONTINUE;
  6628 bool
  6629 ClusterIterator::IsWhitespace()
  6631   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
  6632   return IsSelectionSpace(mFrag, mCharIndex);
  6635 bool
  6636 ClusterIterator::IsPunctuation()
  6638   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
  6639   nsIUGenCategory::nsUGenCategory c =
  6640     mozilla::unicode::GetGenCategory(mFrag->CharAt(mCharIndex));
  6641   return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol;
  6644 int32_t
  6645 ClusterIterator::GetBeforeOffset()
  6647   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
  6648   return mCharIndex + (mDirection > 0 ? 0 : 1);
  6651 int32_t
  6652 ClusterIterator::GetAfterOffset()
  6654   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
  6655   return mCharIndex + (mDirection > 0 ? 1 : 0);
  6658 bool
  6659 ClusterIterator::NextCluster()
  6661   if (!mDirection)
  6662     return false;
  6663   gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
  6665   mHaveWordBreak = false;
  6666   while (true) {
  6667     bool keepGoing = false;
  6668     if (mDirection > 0) {
  6669       if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd())
  6670         return false;
  6671       keepGoing = mIterator.IsOriginalCharSkipped() ||
  6672           mIterator.GetOriginalOffset() < mTrimmed.mStart ||
  6673           !textRun->IsClusterStart(mIterator.GetSkippedOffset());
  6674       mCharIndex = mIterator.GetOriginalOffset();
  6675       mIterator.AdvanceOriginal(1);
  6676     } else {
  6677       if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
  6678         return false;
  6679       mIterator.AdvanceOriginal(-1);
  6680       keepGoing = mIterator.IsOriginalCharSkipped() ||
  6681           mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
  6682           !textRun->IsClusterStart(mIterator.GetSkippedOffset());
  6683       mCharIndex = mIterator.GetOriginalOffset();
  6686     if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
  6687       mHaveWordBreak = true;
  6689     if (!keepGoing)
  6690       return true;
  6694 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
  6695                                  int32_t aDirection, nsString& aContext)
  6696   : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
  6698   mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
  6699   if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
  6700     mDirection = 0; // signal failure
  6701     return;
  6703   mIterator.SetOriginalOffset(aPosition);
  6705   mFrag = aTextFrame->GetContent()->GetText();
  6706   mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
  6708   int32_t textOffset = aTextFrame->GetContentOffset();
  6709   int32_t textLen = aTextFrame->GetContentLength();
  6710   if (!mWordBreaks.AppendElements(textLen + 1)) {
  6711     mDirection = 0; // signal failure
  6712     return;
  6714   memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool));
  6715   int32_t textStart;
  6716   if (aDirection > 0) {
  6717     if (aContext.IsEmpty()) {
  6718       // No previous context, so it must be the start of a line or text run
  6719       mWordBreaks[0] = true;
  6721     textStart = aContext.Length();
  6722     mFrag->AppendTo(aContext, textOffset, textLen);
  6723   } else {
  6724     if (aContext.IsEmpty()) {
  6725       // No following context, so it must be the end of a line or text run
  6726       mWordBreaks[textLen] = true;
  6728     textStart = 0;
  6729     nsAutoString str;
  6730     mFrag->AppendTo(str, textOffset, textLen);
  6731     aContext.Insert(str, 0);
  6733   nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
  6734   for (int32_t i = 0; i <= textLen; ++i) {
  6735     int32_t indexInText = i + textStart;
  6736     mWordBreaks[i] |=
  6737       wordBreaker->BreakInBetween(aContext.get(), indexInText,
  6738                                   aContext.get() + indexInText,
  6739                                   aContext.Length() - indexInText);
  6743 nsIFrame::FrameSearchResult
  6744 nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
  6745                             int32_t* aOffset, PeekWordState* aState)
  6747   int32_t contentLength = GetContentLength();
  6748   NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
  6750   bool selectable;
  6751   uint8_t selectStyle;
  6752   IsSelectable(&selectable, &selectStyle);
  6753   if (selectStyle == NS_STYLE_USER_SELECT_ALL)
  6754     return CONTINUE_UNSELECTABLE;
  6756   int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
  6757   ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
  6759   if (!cIter.NextCluster())
  6760     return CONTINUE_EMPTY;
  6762   do {
  6763     bool isPunctuation = cIter.IsPunctuation();
  6764     bool isWhitespace = cIter.IsWhitespace();
  6765     bool isWordBreakBefore = cIter.HaveWordBreakBefore();
  6766     if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
  6767       aState->SetSawBeforeType();
  6768       aState->Update(isPunctuation, isWhitespace);
  6769       continue;
  6771     // See if we can break before the current cluster
  6772     if (!aState->mAtStart) {
  6773       bool canBreak;
  6774       if (isPunctuation != aState->mLastCharWasPunctuation) {
  6775         canBreak = BreakWordBetweenPunctuation(aState, aForward,
  6776                      isPunctuation, isWhitespace, aIsKeyboardSelect);
  6777       } else if (!aState->mLastCharWasWhitespace &&
  6778                  !isWhitespace && !isPunctuation && isWordBreakBefore) {
  6779         // if both the previous and the current character are not white
  6780         // space but this can be word break before, we don't need to eat
  6781         // a white space in this case. This case happens in some languages
  6782         // that their words are not separated by white spaces. E.g.,
  6783         // Japanese and Chinese.
  6784         canBreak = true;
  6785       } else {
  6786         canBreak = isWordBreakBefore && aState->mSawBeforeType &&
  6787           (aWordSelectEatSpace != isWhitespace);
  6789       if (canBreak) {
  6790         *aOffset = cIter.GetBeforeOffset() - mContentOffset;
  6791         return FOUND;
  6794     aState->Update(isPunctuation, isWhitespace);
  6795   } while (cIter.NextCluster());
  6797   *aOffset = cIter.GetAfterOffset() - mContentOffset;
  6798   return CONTINUE;
  6801  // TODO this needs to be deCOMtaminated with the interface fixed in
  6802 // nsIFrame.h, but we won't do that until the old textframe is gone.
  6803 nsresult
  6804 nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex,
  6805     int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval)
  6807   if (!aRetval)
  6808     return NS_ERROR_NULL_POINTER;
  6810   // Text in the range is visible if there is at least one character in the range
  6811   // that is not skipped and is mapped by this frame (which is the primary frame)
  6812   // or one of its continuations.
  6813   for (nsTextFrame* f = this; f;
  6814        f = static_cast<nsTextFrame*>(GetNextContinuation())) {
  6815     int32_t dummyOffset = 0;
  6816     if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
  6817       *aRetval = true;
  6818       return NS_OK;
  6822   *aRetval = false;
  6823   return NS_OK;
  6826 nsresult
  6827 nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const
  6829   start = GetContentOffset();
  6830   end = GetContentEnd();
  6831   return NS_OK;
  6834 static int32_t
  6835 FindEndOfPunctuationRun(const nsTextFragment* aFrag,
  6836                         gfxTextRun* aTextRun,
  6837                         gfxSkipCharsIterator* aIter,
  6838                         int32_t aOffset,
  6839                         int32_t aStart,
  6840                         int32_t aEnd)
  6842   int32_t i;
  6844   for (i = aStart; i < aEnd - aOffset; ++i) {
  6845     if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) {
  6846       aIter->SetOriginalOffset(aOffset + i);
  6847       FindClusterEnd(aTextRun, aEnd, aIter);
  6848       i = aIter->GetOriginalOffset() - aOffset;
  6849     } else {
  6850       break;
  6853   return i;
  6856 /**
  6857  * Returns true if this text frame completes the first-letter, false
  6858  * if it does not contain a true "letter".
  6859  * If returns true, then it also updates aLength to cover just the first-letter
  6860  * text.
  6862  * XXX :first-letter should be handled during frame construction
  6863  * (and it has a good bit in common with nextBidi)
  6865  * @param aLength an in/out parameter: on entry contains the maximum length to
  6866  * return, on exit returns length of the first-letter fragment (which may
  6867  * include leading and trailing punctuation, for example)
  6868  */
  6869 static bool
  6870 FindFirstLetterRange(const nsTextFragment* aFrag,
  6871                      gfxTextRun* aTextRun,
  6872                      int32_t aOffset, const gfxSkipCharsIterator& aIter,
  6873                      int32_t* aLength)
  6875   int32_t i;
  6876   int32_t length = *aLength;
  6877   int32_t endOffset = aOffset + length;
  6878   gfxSkipCharsIterator iter(aIter);
  6880   // skip leading whitespace, then consume clusters that start with punctuation
  6881   i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, 
  6882                               GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1),
  6883                               endOffset);
  6884   if (i == length)
  6885     return false;
  6887   // If the next character is not a letter or number, there is no first-letter.
  6888   // Return true so that we don't go on looking, but set aLength to 0.
  6889   if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) {
  6890     *aLength = 0;
  6891     return true;
  6894   // consume another cluster (the actual first letter)
  6895   iter.SetOriginalOffset(aOffset + i);
  6896   FindClusterEnd(aTextRun, endOffset, &iter);
  6897   i = iter.GetOriginalOffset() - aOffset;
  6898   if (i + 1 == length)
  6899     return true;
  6901   // consume clusters that start with punctuation
  6902   i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset);
  6903   if (i < length)
  6904     *aLength = i;
  6905   return true;
  6908 static uint32_t
  6909 FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
  6910                                  nsIFrame::InlineIntrinsicWidthData* aData,
  6911                                  const nsStyleText* aTextStyle,
  6912                                  gfxSkipCharsIterator* aIterator,
  6913                                  uint32_t aFlowEndInTextRun)
  6915   if (aData->skipWhitespace) {
  6916     while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
  6917            IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
  6918       aIterator->AdvanceOriginal(1);
  6921   return aIterator->GetSkippedOffset();
  6924 union VoidPtrOrFloat {
  6925   VoidPtrOrFloat() : p(nullptr) {}
  6927   void *p;
  6928   float f;
  6929 };
  6931 float
  6932 nsTextFrame::GetFontSizeInflation() const
  6934   if (!HasFontSizeInflation()) {
  6935     return 1.0f;
  6937   VoidPtrOrFloat u;
  6938   u.p = Properties().Get(FontSizeInflationProperty());
  6939   return u.f;
  6942 void
  6943 nsTextFrame::SetFontSizeInflation(float aInflation)
  6945   if (aInflation == 1.0f) {
  6946     if (HasFontSizeInflation()) {
  6947       RemoveStateBits(TEXT_HAS_FONT_INFLATION);
  6948       Properties().Delete(FontSizeInflationProperty());
  6950     return;
  6953   AddStateBits(TEXT_HAS_FONT_INFLATION);
  6954   VoidPtrOrFloat u;
  6955   u.f = aInflation;
  6956   Properties().Set(FontSizeInflationProperty(), u.p);
  6959 /* virtual */ 
  6960 void nsTextFrame::MarkIntrinsicWidthsDirty()
  6962   ClearTextRuns();
  6963   nsFrame::MarkIntrinsicWidthsDirty();
  6966 // XXX this doesn't handle characters shaped by line endings. We need to
  6967 // temporarily override the "current line ending" settings.
  6968 void
  6969 nsTextFrame::AddInlineMinWidthForFlow(nsRenderingContext *aRenderingContext,
  6970                                       nsIFrame::InlineMinWidthData *aData,
  6971                                       TextRunType aTextRunType)
  6973   uint32_t flowEndInTextRun;
  6974   gfxContext* ctx = aRenderingContext->ThebesContext();
  6975   gfxSkipCharsIterator iter =
  6976     EnsureTextRun(aTextRunType, ctx, aData->lineContainer,
  6977                   aData->line, &flowEndInTextRun);
  6978   gfxTextRun *textRun = GetTextRun(aTextRunType);
  6979   if (!textRun)
  6980     return;
  6982   // Pass null for the line container. This will disable tab spacing, but that's
  6983   // OK since we can't really handle tabs for intrinsic sizing anyway.
  6984   const nsStyleText* textStyle = StyleText();
  6985   const nsTextFragment* frag = mContent->GetText();
  6987   // If we're hyphenating, the PropertyProvider needs the actual length;
  6988   // otherwise we can just pass INT32_MAX to mean "all the text"
  6989   int32_t len = INT32_MAX;
  6990   bool hyphenating = frag->GetLength() > 0 &&
  6991     (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO ||
  6992      (textStyle->mHyphens == NS_STYLE_HYPHENS_MANUAL &&
  6993       (textRun->GetFlags() & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
  6994   if (hyphenating) {
  6995     gfxSkipCharsIterator tmp(iter);
  6996     len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
  6997                  tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset();
  6999   PropertyProvider provider(textRun, textStyle, frag, this,
  7000                             iter, len, nullptr, 0, aTextRunType);
  7002   bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
  7003   bool preformatNewlines = textStyle->NewlineIsSignificant();
  7004   bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
  7005   gfxFloat tabWidth = -1;
  7006   uint32_t start =
  7007     FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
  7009   AutoFallibleTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer;
  7010   bool *hyphBreakBefore = nullptr;
  7011   if (hyphenating) {
  7012     hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start);
  7013     if (hyphBreakBefore) {
  7014       provider.GetHyphenationBreaks(start, flowEndInTextRun - start,
  7015                                     hyphBreakBefore);
  7019   for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
  7020     bool preformattedNewline = false;
  7021     bool preformattedTab = false;
  7022     if (i < flowEndInTextRun) {
  7023       // XXXldb Shouldn't we be including the newline as part of the
  7024       // segment that it ends rather than part of the segment that it
  7025       // starts?
  7026       preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
  7027       preformattedTab = preformatTabs && textRun->CharIsTab(i);
  7028       if (!textRun->CanBreakLineBefore(i) &&
  7029           !preformattedNewline &&
  7030           !preformattedTab &&
  7031           (!hyphBreakBefore || !hyphBreakBefore[i - start]))
  7033         // we can't break here (and it's not the end of the flow)
  7034         continue;
  7038     if (i > wordStart) {
  7039       nscoord width =
  7040         NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart, &provider));
  7041       aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
  7042       aData->atStartOfLine = false;
  7044       if (collapseWhitespace) {
  7045         uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
  7046         if (trimStart == start) {
  7047           // This is *all* trimmable whitespace, so whatever trailingWhitespace
  7048           // we saw previously is still trailing...
  7049           aData->trailingWhitespace += width;
  7050         } else {
  7051           // Some non-whitespace so the old trailingWhitespace is no longer trailing
  7052           aData->trailingWhitespace =
  7053             NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
  7055       } else {
  7056         aData->trailingWhitespace = 0;
  7060     if (preformattedTab) {
  7061       PropertyProvider::Spacing spacing;
  7062       provider.GetSpacing(i, 1, &spacing);
  7063       aData->currentLine += nscoord(spacing.mBefore);
  7064       gfxFloat afterTab =
  7065         AdvanceToNextTab(aData->currentLine, this,
  7066                          textRun, &tabWidth);
  7067       aData->currentLine = nscoord(afterTab + spacing.mAfter);
  7068       wordStart = i + 1;
  7069     } else if (i < flowEndInTextRun ||
  7070         (i == textRun->GetLength() &&
  7071          (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) {
  7072       if (preformattedNewline) {
  7073         aData->ForceBreak(aRenderingContext);
  7074       } else if (i < flowEndInTextRun && hyphBreakBefore &&
  7075                  hyphBreakBefore[i - start])
  7077         aData->OptionallyBreak(aRenderingContext, 
  7078                                NSToCoordRound(provider.GetHyphenWidth()));
  7079       } else {
  7080         aData->OptionallyBreak(aRenderingContext);
  7082       wordStart = i;
  7086   if (start < flowEndInTextRun) {
  7087     // Check if we have collapsible whitespace at the end
  7088     aData->skipWhitespace =
  7089       IsTrimmableSpace(provider.GetFragment(),
  7090                        iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
  7091                        textStyle);
  7095 bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
  7096   return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
  7099 // XXX Need to do something here to avoid incremental reflow bugs due to
  7100 // first-line and first-letter changing min-width
  7101 /* virtual */ void
  7102 nsTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
  7103                                nsIFrame::InlineMinWidthData *aData)
  7105   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
  7106   TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
  7108   if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
  7109     // FIXME: Ideally, if we already have a text run, we'd move it to be
  7110     // the uninflated text run.
  7111     ClearTextRun(nullptr, nsTextFrame::eInflated);
  7114   nsTextFrame* f;
  7115   gfxTextRun* lastTextRun = nullptr;
  7116   // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
  7117   // in the flow are handled right here.
  7118   for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
  7119     // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
  7120     // haven't set up textruns yet for f.  Except in OOM situations,
  7121     // lastTextRun will only be null for the first text frame.
  7122     if (f == this || f->GetTextRun(trtype) != lastTextRun) {
  7123       nsIFrame* lc;
  7124       if (aData->lineContainer &&
  7125           aData->lineContainer != (lc = FindLineContainer(f))) {
  7126         NS_ASSERTION(f != this, "wrong InlineMinWidthData container"
  7127                                 " for first continuation");
  7128         aData->line = nullptr;
  7129         aData->lineContainer = lc;
  7132       // This will process all the text frames that share the same textrun as f.
  7133       f->AddInlineMinWidthForFlow(aRenderingContext, aData, trtype);
  7134       lastTextRun = f->GetTextRun(trtype);
  7139 // XXX this doesn't handle characters shaped by line endings. We need to
  7140 // temporarily override the "current line ending" settings.
  7141 void
  7142 nsTextFrame::AddInlinePrefWidthForFlow(nsRenderingContext *aRenderingContext,
  7143                                        nsIFrame::InlinePrefWidthData *aData,
  7144                                        TextRunType aTextRunType)
  7146   uint32_t flowEndInTextRun;
  7147   gfxContext* ctx = aRenderingContext->ThebesContext();
  7148   gfxSkipCharsIterator iter =
  7149     EnsureTextRun(aTextRunType, ctx, aData->lineContainer,
  7150                   aData->line, &flowEndInTextRun);
  7151   gfxTextRun *textRun = GetTextRun(aTextRunType);
  7152   if (!textRun)
  7153     return;
  7155   // Pass null for the line container. This will disable tab spacing, but that's
  7156   // OK since we can't really handle tabs for intrinsic sizing anyway.
  7158   const nsStyleText* textStyle = StyleText();
  7159   const nsTextFragment* frag = mContent->GetText();
  7160   PropertyProvider provider(textRun, textStyle, frag, this,
  7161                             iter, INT32_MAX, nullptr, 0, aTextRunType);
  7163   bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
  7164   bool preformatNewlines = textStyle->NewlineIsSignificant();
  7165   bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
  7166   gfxFloat tabWidth = -1;
  7167   uint32_t start =
  7168     FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
  7170   // XXX Should we consider hyphenation here?
  7171   // If newlines and tabs aren't preformatted, nothing to do inside
  7172   // the loop so make i skip to the end
  7173   uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
  7174   for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
  7175     bool preformattedNewline = false;
  7176     bool preformattedTab = false;
  7177     if (i < flowEndInTextRun) {
  7178       // XXXldb Shouldn't we be including the newline as part of the
  7179       // segment that it ends rather than part of the segment that it
  7180       // starts?
  7181       NS_ASSERTION(preformatNewlines || textStyle->NewlineIsDiscarded(),
  7182                    "We can't be here unless newlines are hard breaks or are discarded");
  7183       preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
  7184       preformattedTab = preformatTabs && textRun->CharIsTab(i);
  7185       if (!preformattedNewline && !preformattedTab) {
  7186         // we needn't break here (and it's not the end of the flow)
  7187         continue;
  7191     if (i > lineStart) {
  7192       nscoord width =
  7193         NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart, &provider));
  7194       aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
  7196       if (collapseWhitespace) {
  7197         uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
  7198         if (trimStart == start) {
  7199           // This is *all* trimmable whitespace, so whatever trailingWhitespace
  7200           // we saw previously is still trailing...
  7201           aData->trailingWhitespace += width;
  7202         } else {
  7203           // Some non-whitespace so the old trailingWhitespace is no longer trailing
  7204           aData->trailingWhitespace =
  7205             NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
  7207       } else {
  7208         aData->trailingWhitespace = 0;
  7212     if (preformattedTab) {
  7213       PropertyProvider::Spacing spacing;
  7214       provider.GetSpacing(i, 1, &spacing);
  7215       aData->currentLine += nscoord(spacing.mBefore);
  7216       gfxFloat afterTab =
  7217         AdvanceToNextTab(aData->currentLine, this,
  7218                          textRun, &tabWidth);
  7219       aData->currentLine = nscoord(afterTab + spacing.mAfter);
  7220       lineStart = i + 1;
  7221     } else if (preformattedNewline) {
  7222       aData->ForceBreak(aRenderingContext);
  7223       lineStart = i;
  7227   // Check if we have collapsible whitespace at the end
  7228   if (start < flowEndInTextRun) {
  7229     aData->skipWhitespace =
  7230       IsTrimmableSpace(provider.GetFragment(),
  7231                        iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
  7232                        textStyle);
  7236 // XXX Need to do something here to avoid incremental reflow bugs due to
  7237 // first-line and first-letter changing pref-width
  7238 /* virtual */ void
  7239 nsTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
  7240                                 nsIFrame::InlinePrefWidthData *aData)
  7242   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
  7243   TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
  7245   if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
  7246     // FIXME: Ideally, if we already have a text run, we'd move it to be
  7247     // the uninflated text run.
  7248     ClearTextRun(nullptr, nsTextFrame::eInflated);
  7251   nsTextFrame* f;
  7252   gfxTextRun* lastTextRun = nullptr;
  7253   // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
  7254   // in the flow are handled right here.
  7255   for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
  7256     // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
  7257     // haven't set up textruns yet for f.  Except in OOM situations,
  7258     // lastTextRun will only be null for the first text frame.
  7259     if (f == this || f->GetTextRun(trtype) != lastTextRun) {
  7260       nsIFrame* lc;
  7261       if (aData->lineContainer &&
  7262           aData->lineContainer != (lc = FindLineContainer(f))) {
  7263         NS_ASSERTION(f != this, "wrong InlinePrefWidthData container"
  7264                                 " for first continuation");
  7265         aData->line = nullptr;
  7266         aData->lineContainer = lc;
  7269       // This will process all the text frames that share the same textrun as f.
  7270       f->AddInlinePrefWidthForFlow(aRenderingContext, aData, trtype);
  7271       lastTextRun = f->GetTextRun(trtype);
  7276 /* virtual */ nsSize
  7277 nsTextFrame::ComputeSize(nsRenderingContext *aRenderingContext,
  7278                          nsSize aCBSize, nscoord aAvailableWidth,
  7279                          nsSize aMargin, nsSize aBorder, nsSize aPadding,
  7280                          uint32_t aFlags)
  7282   // Inlines and text don't compute size before reflow.
  7283   return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
  7286 static nsRect
  7287 RoundOut(const gfxRect& aRect)
  7289   nsRect r;
  7290   r.x = NSToCoordFloor(aRect.X());
  7291   r.y = NSToCoordFloor(aRect.Y());
  7292   r.width = NSToCoordCeil(aRect.XMost()) - r.x;
  7293   r.height = NSToCoordCeil(aRect.YMost()) - r.y;
  7294   return r;
  7297 nsRect
  7298 nsTextFrame::ComputeTightBounds(gfxContext* aContext) const
  7300   if (StyleContext()->HasTextDecorationLines() ||
  7301       (GetStateBits() & TEXT_HYPHEN_BREAK)) {
  7302     // This is conservative, but OK.
  7303     return GetVisualOverflowRect();
  7306   gfxSkipCharsIterator iter =
  7307     const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
  7308   if (!mTextRun)
  7309     return nsRect(0, 0, 0, 0);
  7311   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
  7312                             nsTextFrame::eInflated);
  7313   // Trim trailing whitespace
  7314   provider.InitializeForDisplay(true);
  7316   gfxTextRun::Metrics metrics =
  7317         mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
  7318                               ComputeTransformedLength(provider),
  7319                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
  7320                               aContext, &provider);
  7321   // mAscent should be the same as metrics.mAscent, but it's what we use to
  7322   // paint so that's the one we'll use.
  7323   return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent);
  7326 /* virtual */ nsresult
  7327 nsTextFrame::GetPrefWidthTightBounds(nsRenderingContext* aContext,
  7328                                      nscoord* aX,
  7329                                      nscoord* aXMost)
  7331   gfxSkipCharsIterator iter =
  7332     const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
  7333   if (!mTextRun)
  7334     return NS_ERROR_FAILURE;
  7336   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
  7337                             nsTextFrame::eInflated);
  7338   provider.InitializeForMeasure();
  7340   gfxTextRun::Metrics metrics =
  7341         mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
  7342                               ComputeTransformedLength(provider),
  7343                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
  7344                               aContext->ThebesContext(), &provider);
  7345   // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
  7346   *aX = NSToCoordFloor(metrics.mBoundingBox.x);
  7347   *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
  7349   return NS_OK;
  7352 static bool
  7353 HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun,
  7354                     int32_t aStartOffset, const gfxSkipCharsIterator& aIter)
  7356   if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
  7357       aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
  7358     return true;
  7360   if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY))
  7361     return false;
  7362   gfxSkipCharsIterator iter = aIter;
  7363   while (iter.GetOriginalOffset() > aStartOffset) {
  7364     iter.AdvanceOriginal(-1);
  7365     if (!iter.IsOriginalCharSkipped())
  7366       break;
  7367     if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
  7368       return true;
  7370   return false;
  7373 /**
  7374  * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
  7375  * because their text has all been taken and reflowed by earlier frames.
  7376  */
  7377 static void
  7378 RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove)
  7380   NS_PRECONDITION(aFrame != aFirstToNotRemove, "This will go very badly");
  7381   // We have to be careful here, because some RemoveFrame implementations
  7382   // remove and destroy not only the passed-in frame but also all its following
  7383   // in-flows (and sometimes all its following continuations in general).  So
  7384   // we remove |f| and everything up to but not including firstToNotRemove from
  7385   // the flow first, to make sure that only the things we want destroyed are
  7386   // destroyed.
  7388   // This sadly duplicates some of the logic from
  7389   // nsSplittableFrame::RemoveFromFlow.  We can get away with not duplicating
  7390   // all of it, because we know that the prev-continuation links of
  7391   // firstToNotRemove and f are fluid, and non-null.
  7392   NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
  7393                aFirstToNotRemove->GetPrevInFlow() &&
  7394                aFirstToNotRemove->GetPrevInFlow() != nullptr,
  7395                "aFirstToNotRemove should have a fluid prev continuation");
  7396   NS_ASSERTION(aFrame->GetPrevContinuation() ==
  7397                aFrame->GetPrevInFlow() &&
  7398                aFrame->GetPrevInFlow() != nullptr,
  7399                "aFrame should have a fluid prev continuation");
  7401   nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
  7402   nsIFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
  7404   for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
  7405        f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
  7406     // f is going to be destroyed soon, after it is unlinked from the
  7407     // continuation chain. If its textrun is going to be destroyed we need to
  7408     // do it now, before we unlink the frames to remove from the flow,
  7409     // because DestroyFrom calls ClearTextRuns() and that will start at the
  7410     // first frame with the text run and walk the continuations.
  7411     if (f->IsInTextRunUserData()) {
  7412       f->ClearTextRuns();
  7413     } else {
  7414       f->DisconnectTextRuns();
  7418   prevContinuation->SetNextInFlow(aFirstToNotRemove);
  7419   aFirstToNotRemove->SetPrevInFlow(prevContinuation);
  7421   aFrame->SetPrevInFlow(nullptr);
  7422   lastRemoved->SetNextInFlow(nullptr);
  7424   nsIFrame* parent = aFrame->GetParent();
  7425   nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent);
  7426   if (parentBlock) {
  7427     // Manually call DoRemoveFrame so we can tell it that we're
  7428     // removing empty frames; this will keep it from blowing away
  7429     // text runs.
  7430     parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
  7431   } else {
  7432     // Just remove it normally; use kNoReflowPrincipalList to avoid posting
  7433     // new reflows.
  7434     parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
  7438 void
  7439 nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
  7440                        uint32_t aSetLengthFlags)
  7442   mContentLengthHint = aLength;
  7443   int32_t end = GetContentOffset() + aLength;
  7444   nsTextFrame* f = static_cast<nsTextFrame*>(GetNextInFlow());
  7445   if (!f)
  7446     return;
  7448   // If our end offset is moving, then even if frames are not being pushed or
  7449   // pulled, content is moving to or from the next line and the next line
  7450   // must be reflowed.
  7451   // If the next-continuation is dirty, then we should dirty the next line now
  7452   // because we may have skipped doing it if we dirtied it in
  7453   // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
  7454   // and ChildIsDirty to handle a range of frames would be worse.
  7455   if (aLineLayout &&
  7456       (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) {
  7457     aLineLayout->SetDirtyNextLine();
  7460   if (end < f->mContentOffset) {
  7461     // Our frame is shrinking. Give the text to our next in flow.
  7462     if (aLineLayout &&
  7463         HasSignificantTerminalNewline() &&
  7464         GetParent()->GetType() != nsGkAtoms::letterFrame &&
  7465         (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
  7466       // Whatever text we hand to our next-in-flow will end up in a frame all of
  7467       // its own, since it ends in a forced linebreak.  Might as well just put
  7468       // it in a separate frame now.  This is important to prevent text run
  7469       // churn; if we did not do that, then we'd likely end up rebuilding
  7470       // textruns for all our following continuations.
  7471       // We skip this optimization when the parent is a first-letter frame
  7472       // because it doesn't deal well with more than one child frame.
  7473       // We also skip this optimization if we were called during bidi
  7474       // resolution, so as not to create a new frame which doesn't appear in
  7475       // the bidi resolver's list of frames
  7476       nsPresContext* presContext = PresContext();
  7477       nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()->
  7478         CreateContinuingFrame(presContext, this, GetParent());
  7479       nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
  7480       nsFrameList temp(next, next);
  7481       GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp);
  7482       f = next;
  7485     f->mContentOffset = end;
  7486     if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
  7487       ClearTextRuns();
  7488       f->ClearTextRuns();
  7490     return;
  7492   // Our frame is growing. Take text from our in-flow(s).
  7493   // We can take text from frames in lines beyond just the next line.
  7494   // We don't dirty those lines. That's OK, because when we reflow
  7495   // our empty next-in-flow, it will take text from its next-in-flow and
  7496   // dirty that line.
  7498   // Note that in the process we may end up removing some frames from
  7499   // the flow if they end up empty.
  7500   nsTextFrame* framesToRemove = nullptr;
  7501   while (f && f->mContentOffset < end) {
  7502     f->mContentOffset = end;
  7503     if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
  7504       ClearTextRuns();
  7505       f->ClearTextRuns();
  7507     nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextInFlow());
  7508     // Note: the "f->GetNextSibling() == next" check below is to restrict
  7509     // this optimization to the case where they are on the same child list.
  7510     // Otherwise we might remove the only child of a nsFirstLetterFrame
  7511     // for example and it can't handle that.  See bug 597627 for details.
  7512     if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
  7513         (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
  7514       // |f| is now empty.  We may as well remove it, instead of copying all
  7515       // the text from |next| into it instead; the latter leads to use
  7516       // rebuilding textruns for all following continuations.
  7517       // We skip this optimization if we were called during bidi resolution,
  7518       // since the bidi resolver may try to handle the destroyed frame later
  7519       // and crash
  7520       if (!framesToRemove) {
  7521         // Remember that we have to remove this frame.
  7522         framesToRemove = f;
  7524     } else if (framesToRemove) {
  7525       RemoveEmptyInFlows(framesToRemove, f);
  7526       framesToRemove = nullptr;
  7528     f = next;
  7530   NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end),
  7531                    "How did we exit the loop if we null out framesToRemove if "
  7532                    "!next || next->mContentOffset > end ?");
  7533   if (framesToRemove) {
  7534     // We are guaranteed that we exited the loop with f not null, per the
  7535     // postcondition above
  7536     RemoveEmptyInFlows(framesToRemove, f);
  7539 #ifdef DEBUG
  7540   f = this;
  7541   int32_t iterations = 0;
  7542   while (f && iterations < 10) {
  7543     f->GetContentLength(); // Assert if negative length
  7544     f = static_cast<nsTextFrame*>(f->GetNextContinuation());
  7545     ++iterations;
  7547   f = this;
  7548   iterations = 0;
  7549   while (f && iterations < 10) {
  7550     f->GetContentLength(); // Assert if negative length
  7551     f = static_cast<nsTextFrame*>(f->GetPrevContinuation());
  7552     ++iterations;
  7554 #endif
  7557 bool
  7558 nsTextFrame::IsFloatingFirstLetterChild() const
  7560   nsIFrame* frame = GetParent();
  7561   return frame && frame->IsFloating() &&
  7562          frame->GetType() == nsGkAtoms::letterFrame;
  7565 struct NewlineProperty {
  7566   int32_t mStartOffset;
  7567   // The offset of the first \n after mStartOffset, or -1 if there is none
  7568   int32_t mNewlineOffset;
  7569 };
  7571 nsresult
  7572 nsTextFrame::Reflow(nsPresContext*           aPresContext,
  7573                     nsHTMLReflowMetrics&     aMetrics,
  7574                     const nsHTMLReflowState& aReflowState,
  7575                     nsReflowStatus&          aStatus)
  7577   DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
  7578   DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
  7580   // XXX If there's no line layout, we shouldn't even have created this
  7581   // frame. This may happen if, for example, this is text inside a table
  7582   // but not inside a cell. For now, just don't reflow.
  7583   if (!aReflowState.mLineLayout) {
  7584     ClearMetrics(aMetrics);
  7585     aStatus = NS_FRAME_COMPLETE;
  7586     return NS_OK;
  7589   ReflowText(*aReflowState.mLineLayout, aReflowState.AvailableWidth(),
  7590              aReflowState.rendContext, aMetrics, aStatus);
  7592   NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
  7593   return NS_OK;
  7596 #ifdef ACCESSIBILITY
  7597 /**
  7598  * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
  7599  */
  7600 class MOZ_STACK_CLASS ReflowTextA11yNotifier
  7602 public:
  7603   ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) :
  7604     mContent(aContent), mPresContext(aPresContext)
  7607   ~ReflowTextA11yNotifier()
  7609     nsAccessibilityService* accService = nsIPresShell::AccService();
  7610     if (accService) {
  7611       accService->UpdateText(mPresContext->PresShell(), mContent);
  7614 private:
  7615   ReflowTextA11yNotifier();
  7616   ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
  7617   ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&);
  7619   nsIContent* mContent;
  7620   nsPresContext* mPresContext;
  7621 };
  7622 #endif
  7624 void
  7625 nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
  7626                         nsRenderingContext* aRenderingContext,
  7627                         nsHTMLReflowMetrics& aMetrics,
  7628                         nsReflowStatus& aStatus)
  7630 #ifdef NOISY_REFLOW
  7631   ListTag(stdout);
  7632   printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
  7633 #endif
  7635   nsPresContext* presContext = PresContext();
  7637 #ifdef ACCESSIBILITY
  7638   // Schedule the update of accessible tree since rendered text might be changed.
  7639   ReflowTextA11yNotifier(presContext, mContent);
  7640 #endif
  7642   /////////////////////////////////////////////////////////////////////
  7643   // Set up flags and clear out state
  7644   /////////////////////////////////////////////////////////////////////
  7646   // Clear out the reflow state flags in mState. We also clear the whitespace
  7647   // flags because this can change whether the frame maps whitespace-only text
  7648   // or not.
  7649   RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
  7651   // Temporarily map all possible content while we construct our new textrun.
  7652   // so that when doing reflow our styles prevail over any part of the
  7653   // textrun we look at. Note that next-in-flows may be mapping the same
  7654   // content; gfxTextRun construction logic will ensure that we take priority.
  7655   int32_t maxContentLength = GetInFlowContentLength();
  7657   // We don't need to reflow if there is no content.
  7658   if (!maxContentLength) {
  7659     ClearMetrics(aMetrics);
  7660     aStatus = NS_FRAME_COMPLETE;
  7661     return;
  7664 #ifdef NOISY_BIDI
  7665     printf("Reflowed textframe\n");
  7666 #endif
  7668   const nsStyleText* textStyle = StyleText();
  7670   bool atStartOfLine = aLineLayout.LineAtStart();
  7671   if (atStartOfLine) {
  7672     AddStateBits(TEXT_START_OF_LINE);
  7675   uint32_t flowEndInTextRun;
  7676   nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
  7677   gfxContext* ctx = aRenderingContext->ThebesContext();
  7678   const nsTextFragment* frag = mContent->GetText();
  7680   // DOM offsets of the text range we need to measure, after trimming
  7681   // whitespace, restricting to first-letter, and restricting preformatted text
  7682   // to nearest newline
  7683   int32_t length = maxContentLength;
  7684   int32_t offset = GetContentOffset();
  7686   // Restrict preformatted text to the nearest newline
  7687   int32_t newLineOffset = -1; // this will be -1 or a content offset
  7688   int32_t contentNewLineOffset = -1;
  7689   // Pointer to the nsGkAtoms::newline set on this frame's element
  7690   NewlineProperty* cachedNewlineOffset = nullptr;
  7691   if (textStyle->NewlineIsSignificant()) {
  7692     cachedNewlineOffset =
  7693       static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline));
  7694     if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
  7695         (cachedNewlineOffset->mNewlineOffset == -1 ||
  7696          cachedNewlineOffset->mNewlineOffset >= offset)) {
  7697       contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
  7698     } else {
  7699       contentNewLineOffset = FindChar(frag, offset, 
  7700                                       mContent->TextLength() - offset, '\n');
  7702     if (contentNewLineOffset < offset + length) {
  7703       /*
  7704         The new line offset could be outside this frame if the frame has been
  7705         split by bidi resolution. In that case we won't use it in this reflow
  7706         (newLineOffset will remain -1), but we will still cache it in mContent
  7707       */
  7708       newLineOffset = contentNewLineOffset;
  7710     if (newLineOffset >= 0) {
  7711       length = newLineOffset + 1 - offset;
  7714   if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
  7715       (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
  7716     // Skip leading whitespace. Make sure we don't skip a 'pre-line'
  7717     // newline if there is one.
  7718     int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
  7719     int32_t whitespaceCount =
  7720       GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
  7721     if (whitespaceCount) {
  7722       offset += whitespaceCount;
  7723       length -= whitespaceCount;
  7724       // Make sure this frame maps the trimmable whitespace.
  7725       if (MOZ_UNLIKELY(offset > GetContentEnd())) {
  7726         SetLength(offset - GetContentOffset(), &aLineLayout,
  7727                   ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  7732   bool completedFirstLetter = false;
  7733   // Layout dependent styles are a problem because we need to reconstruct
  7734   // the gfxTextRun based on our layout.
  7735   if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
  7736     SetLength(maxContentLength, &aLineLayout,
  7737               ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  7739     if (aLineLayout.GetInFirstLetter()) {
  7740       // floating first-letter boundaries are significant in textrun
  7741       // construction, so clear the textrun out every time we hit a first-letter
  7742       // and have changed our length (which controls the first-letter boundary)
  7743       ClearTextRuns();
  7744       // Find the length of the first-letter. We need a textrun for this.
  7745       // REVIEW: maybe-bogus inflation should be ok (fixed below)
  7746       gfxSkipCharsIterator iter =
  7747         EnsureTextRun(nsTextFrame::eInflated, ctx,
  7748                       lineContainer, aLineLayout.GetLine(),
  7749                       &flowEndInTextRun);
  7751       if (mTextRun) {
  7752         int32_t firstLetterLength = length;
  7753         if (aLineLayout.GetFirstLetterStyleOK()) {
  7754           completedFirstLetter =
  7755             FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
  7756           if (newLineOffset >= 0) {
  7757             // Don't allow a preformatted newline to be part of a first-letter.
  7758             firstLetterLength = std::min(firstLetterLength, length - 1);
  7759             if (length == 1) {
  7760               // There is no text to be consumed by the first-letter before the
  7761               // preformatted newline. Note that the first letter is therefore
  7762               // complete (FindFirstLetterRange will have returned false).
  7763               completedFirstLetter = true;
  7766         } else {
  7767           // We're in a first-letter frame's first in flow, so if there
  7768           // was a first-letter, we'd be it. However, for one reason
  7769           // or another (e.g., preformatted line break before this text),
  7770           // we're not actually supposed to have first-letter style. So
  7771           // just make a zero-length first-letter.
  7772           firstLetterLength = 0;
  7773           completedFirstLetter = true;
  7775         length = firstLetterLength;
  7776         if (length) {
  7777           AddStateBits(TEXT_FIRST_LETTER);
  7779         // Change this frame's length to the first-letter length right now
  7780         // so that when we rebuild the textrun it will be built with the
  7781         // right first-letter boundary
  7782         SetLength(offset + length - GetContentOffset(), &aLineLayout,
  7783                   ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  7784         // Ensure that the textrun will be rebuilt
  7785         ClearTextRuns();
  7790   float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
  7792   if (!IsCurrentFontInflation(fontSizeInflation)) {
  7793     // FIXME: Ideally, if we already have a text run, we'd move it to be
  7794     // the uninflated text run.
  7795     ClearTextRun(nullptr, nsTextFrame::eInflated);
  7798   gfxSkipCharsIterator iter =
  7799     EnsureTextRun(nsTextFrame::eInflated, ctx,
  7800                   lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
  7802   NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
  7803                "EnsureTextRun should have set font size inflation");
  7805   if (mTextRun && iter.GetOriginalEnd() < offset + length) {
  7806     // The textrun does not map enough text for this frame. This can happen
  7807     // when the textrun was ended in the middle of a text node because a
  7808     // preformatted newline was encountered, and prev-in-flow frames have
  7809     // consumed all the text of the textrun. We need a new textrun.
  7810     ClearTextRuns();
  7811     iter = EnsureTextRun(nsTextFrame::eInflated, ctx,
  7812                          lineContainer, aLineLayout.GetLine(),
  7813                          &flowEndInTextRun);
  7816   if (!mTextRun) {
  7817     ClearMetrics(aMetrics);
  7818     aStatus = NS_FRAME_COMPLETE;
  7819     return;
  7822   NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length)
  7823                     <= mTextRun->GetLength(),
  7824                "Text run does not map enough text for our reflow");
  7826   /////////////////////////////////////////////////////////////////////
  7827   // See how much text should belong to this text frame, and measure it
  7828   /////////////////////////////////////////////////////////////////////
  7830   iter.SetOriginalOffset(offset);
  7831   nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ?
  7832     (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
  7833        lineContainer->GetUsedBorderAndPadding().left)
  7834     : -1;
  7835   PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
  7836       lineContainer, xOffsetForTabs, nsTextFrame::eInflated);
  7838   uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
  7840   // The metrics for the text go in here
  7841   gfxTextRun::Metrics textMetrics;
  7842   gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ?
  7843                                                gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS :
  7844                                                gfxFont::LOOSE_INK_EXTENTS;
  7845   NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
  7846                "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
  7848   int32_t limitLength = length;
  7849   int32_t forceBreak = aLineLayout.GetForcedBreakPosition(mContent);
  7850   bool forceBreakAfter = false;
  7851   if (forceBreak >= offset + length) {
  7852     forceBreakAfter = forceBreak == offset + length;
  7853     // The break is not within the text considered for this textframe.
  7854     forceBreak = -1;
  7856   if (forceBreak >= 0) {
  7857     limitLength = forceBreak - offset;
  7858     NS_ASSERTION(limitLength >= 0, "Weird break found!");
  7860   // This is the heart of text reflow right here! We don't know where
  7861   // to break, so we need to see how much text fits in the available width.
  7862   uint32_t transformedLength;
  7863   if (offset + limitLength >= int32_t(frag->GetLength())) {
  7864     NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
  7865                  "Content offset/length out of bounds");
  7866     NS_ASSERTION(flowEndInTextRun >= transformedOffset,
  7867                  "Negative flow length?");
  7868     transformedLength = flowEndInTextRun - transformedOffset;
  7869   } else {
  7870     // we're not looking at all the content, so we need to compute the
  7871     // length of the transformed substring we're looking at
  7872     gfxSkipCharsIterator iter(provider.GetStart());
  7873     iter.SetOriginalOffset(offset + limitLength);
  7874     transformedLength = iter.GetSkippedOffset() - transformedOffset;
  7876   uint32_t transformedLastBreak = 0;
  7877   bool usedHyphenation;
  7878   gfxFloat trimmedWidth = 0;
  7879   gfxFloat availWidth = aAvailableWidth;
  7880   bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
  7881                                    (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
  7882   int32_t unusedOffset;  
  7883   gfxBreakPriority breakPriority;
  7884   aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
  7885   uint32_t transformedCharsFit =
  7886     mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
  7887                                   (GetStateBits() & TEXT_START_OF_LINE) != 0,
  7888                                   availWidth,
  7889                                   &provider, !aLineLayout.LineIsBreakable(),
  7890                                   canTrimTrailingWhitespace ? &trimmedWidth : nullptr,
  7891                                   &textMetrics, boundingBoxType, ctx,
  7892                                   &usedHyphenation, &transformedLastBreak,
  7893                                   textStyle->WordCanWrap(this), &breakPriority);
  7894   if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
  7895     // If we're measuring a zero-length piece of text, update
  7896     // the height manually.
  7897     nsFontMetrics* fm = provider.GetFontMetrics();
  7898     if (fm) {
  7899       textMetrics.mAscent = gfxFloat(fm->MaxAscent());
  7900       textMetrics.mDescent = gfxFloat(fm->MaxDescent());
  7903   // The "end" iterator points to the first character after the string mapped
  7904   // by this frame. Basically, its original-string offset is offset+charsFit
  7905   // after we've computed charsFit.
  7906   gfxSkipCharsIterator end(provider.GetEndHint());
  7907   end.SetSkippedOffset(transformedOffset + transformedCharsFit);
  7908   int32_t charsFit = end.GetOriginalOffset() - offset;
  7909   if (offset + charsFit == newLineOffset) {
  7910     // We broke before a trailing preformatted '\n'. The newline should
  7911     // be assigned to this frame. Note that newLineOffset will be -1 if
  7912     // there was no preformatted newline, so we wouldn't get here in that
  7913     // case.
  7914     ++charsFit;
  7916   // That might have taken us beyond our assigned content range (because
  7917   // we might have advanced over some skipped chars that extend outside
  7918   // this frame), so get back in.
  7919   int32_t lastBreak = -1;
  7920   if (charsFit >= limitLength) {
  7921     charsFit = limitLength;
  7922     if (transformedLastBreak != UINT32_MAX) {
  7923       // lastBreak is needed.
  7924       // This may set lastBreak greater than 'length', but that's OK
  7925       lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak);
  7927     end.SetOriginalOffset(offset + charsFit);
  7928     // If we were forced to fit, and the break position is after a soft hyphen,
  7929     // note that this is a hyphenation break.
  7930     if ((forceBreak >= 0 || forceBreakAfter) &&
  7931         HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
  7932       usedHyphenation = true;
  7935   if (usedHyphenation) {
  7936     // Fix up metrics to include hyphen
  7937     AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx);
  7938     AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
  7941   gfxFloat trimmableWidth = 0;
  7942   bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
  7943   if (canTrimTrailingWhitespace) {
  7944     // Optimization: if we trimmed trailing whitespace, and we can be sure
  7945     // this frame will be at the end of the line, then leave it trimmed off.
  7946     // Otherwise we have to undo the trimming, in case we're not at the end of
  7947     // the line. (If we actually do end up at the end of the line, we'll have
  7948     // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
  7949     // having to re-do it.)
  7950     if (brokeText ||
  7951         (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
  7952       // We're definitely going to break so our trailing whitespace should
  7953       // definitely be trimmed. Record that we've already done it.
  7954       AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
  7955     } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
  7956       // We might not be at the end of the line. (Note that even if this frame
  7957       // ends in breakable whitespace, it might not be at the end of the line
  7958       // because it might be followed by breakable, but preformatted, whitespace.)
  7959       // Undo the trimming.
  7960       textMetrics.mAdvanceWidth += trimmedWidth;
  7961       trimmableWidth = trimmedWidth;
  7962       if (mTextRun->IsRightToLeft()) {
  7963         // Space comes before text, so the bounding box is moved to the
  7964         // right by trimmdWidth
  7965         textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
  7970   if (!brokeText && lastBreak >= 0) {
  7971     // Since everything fit and no break was forced,
  7972     // record the last break opportunity
  7973     NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aAvailableWidth,
  7974                  "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
  7975     aLineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, true, breakPriority);
  7978   int32_t contentLength = offset + charsFit - GetContentOffset();
  7980   /////////////////////////////////////////////////////////////////////
  7981   // Compute output metrics
  7982   /////////////////////////////////////////////////////////////////////
  7984   // first-letter frames should use the tight bounding box metrics for ascent/descent
  7985   // for good drop-cap effects
  7986   if (GetStateBits() & TEXT_FIRST_LETTER) {
  7987     textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
  7988     textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
  7991   // Setup metrics for caller
  7992   // Disallow negative widths
  7993   aMetrics.Width() = NSToCoordCeil(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
  7995   if (transformedCharsFit == 0 && !usedHyphenation) {
  7996     aMetrics.SetTopAscent(0);
  7997     aMetrics.Height() = 0;
  7998   } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
  7999     // Use actual text metrics for floating first letter frame.
  8000     aMetrics.SetTopAscent(NSToCoordCeil(textMetrics.mAscent));
  8001     aMetrics.Height() = aMetrics.TopAscent() + NSToCoordCeil(textMetrics.mDescent);
  8002   } else {
  8003     // Otherwise, ascent should contain the overline drawable area.
  8004     // And also descent should contain the underline drawable area.
  8005     // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
  8006     nsFontMetrics* fm = provider.GetFontMetrics();
  8007     nscoord fontAscent = fm->MaxAscent();
  8008     nscoord fontDescent = fm->MaxDescent();
  8009     aMetrics.SetTopAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
  8010     nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
  8011     aMetrics.Height() = aMetrics.TopAscent() + descent;
  8014   NS_ASSERTION(aMetrics.TopAscent() >= 0, "Negative ascent???");
  8015   NS_ASSERTION(aMetrics.Height() - aMetrics.TopAscent() >= 0, "Negative descent???");
  8017   mAscent = aMetrics.TopAscent();
  8019   // Handle text that runs outside its normal bounds.
  8020   nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
  8021   aMetrics.SetOverflowAreasToDesiredBounds();
  8022   aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
  8024   // When we have text decorations, we don't need to compute their overflow now
  8025   // because we're guaranteed to do it later
  8026   // (see nsLineLayout::RelativePositionFrames)
  8027   UnionAdditionalOverflow(presContext, *aLineLayout.LineContainerRS(),
  8028                           provider, &aMetrics.VisualOverflow(), false);
  8030   /////////////////////////////////////////////////////////////////////
  8031   // Clean up, update state
  8032   /////////////////////////////////////////////////////////////////////
  8034   // If all our characters are discarded or collapsed, then trimmable width
  8035   // from the last textframe should be preserved. Otherwise the trimmable width
  8036   // from this textframe overrides. (Currently in CSS trimmable width can be
  8037   // at most one space so there's no way for trimmable width from a previous
  8038   // frame to accumulate with trimmable width from this frame.)
  8039   if (transformedCharsFit > 0) {
  8040     aLineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
  8041     AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
  8043   if (charsFit > 0 && charsFit == length &&
  8044       textStyle->mHyphens != NS_STYLE_HYPHENS_NONE &&
  8045       HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
  8046     // Record a potential break after final soft hyphen
  8047     aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
  8048         textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth,
  8049                                            gfxBreakPriority::eNormalBreak);
  8051   bool breakAfter = forceBreakAfter;
  8052   // length == 0 means either the text is empty or it's all collapsed away
  8053   bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
  8054   if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
  8055       transformedOffset + transformedLength == mTextRun->GetLength() &&
  8056       (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) {
  8057     // We placed all the text in the textrun and we have a break opportunity at
  8058     // the end of the textrun. We need to record it because the following
  8059     // content may not care about nsLineBreaker.
  8061     // Note that because we didn't break, we can be sure that (thanks to the
  8062     // code up above) textMetrics.mAdvanceWidth includes the width of any
  8063     // trailing whitespace. So we need to subtract trimmableWidth here
  8064     // because if we did break at this point, that much width would be trimmed.
  8065     if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
  8066       breakAfter = true;
  8067     } else {
  8068       aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
  8069                                               true, gfxBreakPriority::eNormalBreak);
  8073   // Compute reflow status
  8074   aStatus = contentLength == maxContentLength
  8075     ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE;
  8077   if (charsFit == 0 && length > 0 && !usedHyphenation) {
  8078     // Couldn't place any text
  8079     aStatus = NS_INLINE_LINE_BREAK_BEFORE();
  8080   } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) {
  8081     // Ends in \n
  8082     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
  8083     aLineLayout.SetLineEndsInBR(true);
  8084   } else if (breakAfter) {
  8085     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
  8087   if (completedFirstLetter) {
  8088     aLineLayout.SetFirstLetterStyleOK(false);
  8089     aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE;
  8092   // Updated the cached NewlineProperty, or delete it.
  8093   if (contentLength < maxContentLength &&
  8094       textStyle->NewlineIsSignificant() &&
  8095       (contentNewLineOffset < 0 ||
  8096        mContentOffset + contentLength <= contentNewLineOffset)) {
  8097     if (!cachedNewlineOffset) {
  8098       cachedNewlineOffset = new NewlineProperty;
  8099       if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
  8100                                           nsINode::DeleteProperty<NewlineProperty>))) {
  8101         delete cachedNewlineOffset;
  8102         cachedNewlineOffset = nullptr;
  8105     if (cachedNewlineOffset) {
  8106       cachedNewlineOffset->mStartOffset = offset;
  8107       cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
  8109   } else if (cachedNewlineOffset) {
  8110     mContent->DeleteProperty(nsGkAtoms::newline);
  8113   // Compute space and letter counts for justification, if required
  8114   if (!textStyle->WhiteSpaceIsSignificant() &&
  8115       (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
  8116        lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) &&
  8117       !lineContainer->IsSVGText()) {
  8118     AddStateBits(TEXT_JUSTIFICATION_ENABLED);    // This will include a space for trailing whitespace, if any is present.
  8119     // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
  8120     int32_t numJustifiableCharacters =
  8121       provider.ComputeJustifiableCharacters(offset, charsFit);
  8123     NS_ASSERTION(numJustifiableCharacters <= charsFit,
  8124                  "Bad justifiable character count");
  8125     aLineLayout.SetTextJustificationWeights(numJustifiableCharacters,
  8126         charsFit - numJustifiableCharacters);
  8129   SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  8131   InvalidateFrame();
  8133 #ifdef NOISY_REFLOW
  8134   ListTag(stdout);
  8135   printf(": desiredSize=%d,%d(b=%d) status=%x\n",
  8136          aMetrics.Width(), aMetrics.Height(), aMetrics.TopAscent(),
  8137          aStatus);
  8138 #endif
  8141 /* virtual */ bool
  8142 nsTextFrame::CanContinueTextRun() const
  8144   // We can continue a text run through a text frame
  8145   return true;
  8148 nsTextFrame::TrimOutput
  8149 nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC)
  8151   TrimOutput result;
  8152   result.mChanged = false;
  8153   result.mLastCharIsJustifiable = false;
  8154   result.mDeltaWidth = 0;
  8156   AddStateBits(TEXT_END_OF_LINE);
  8158   int32_t contentLength = GetContentLength();
  8159   if (!contentLength)
  8160     return result;
  8162   gfxContext* ctx = aRC->ThebesContext();
  8163   gfxSkipCharsIterator start =
  8164     EnsureTextRun(nsTextFrame::eInflated, ctx);
  8165   NS_ENSURE_TRUE(mTextRun, result);
  8167   uint32_t trimmedStart = start.GetSkippedOffset();
  8169   const nsTextFragment* frag = mContent->GetText();
  8170   TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
  8171   gfxSkipCharsIterator trimmedEndIter = start;
  8172   const nsStyleText* textStyle = StyleText();
  8173   gfxFloat delta = 0;
  8174   uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
  8176   if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
  8177     // We pre-trimmed this frame, so the last character is justifiable
  8178     result.mLastCharIsJustifiable = true;
  8179   } else if (trimmed.GetEnd() < GetContentEnd()) {
  8180     gfxSkipCharsIterator end = trimmedEndIter;
  8181     uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
  8182     if (trimmedEnd < endOffset) {
  8183       // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
  8184       // OK to pass null for the line container.
  8185       PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
  8186                                 nullptr, 0, nsTextFrame::eInflated);
  8187       delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
  8188       // non-compressed whitespace being skipped at end of line -> justifiable
  8189       // XXX should we actually *count* justifiable characters that should be
  8190       // removed from the overall count? I think so...
  8191       result.mLastCharIsJustifiable = true;
  8192       result.mChanged = true;
  8196   if (!result.mLastCharIsJustifiable &&
  8197       (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
  8198     // Check if any character in the last cluster is justifiable
  8199     PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
  8200                               nullptr, 0, nsTextFrame::eInflated);
  8201     bool isCJK = IsChineseOrJapanese(this);
  8202     gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter);
  8203     provider.FindJustificationRange(&justificationStart, &justificationEnd);
  8205     for (int32_t i = justificationEnd.GetOriginalOffset();
  8206          i < trimmed.GetEnd(); ++i) {
  8207       if (IsJustifiableCharacter(frag, i, isCJK)) {
  8208         result.mLastCharIsJustifiable = true;
  8213   gfxFloat advanceDelta;
  8214   mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
  8215                           (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
  8216                           &advanceDelta, ctx);
  8217   if (advanceDelta != 0) {
  8218     result.mChanged = true;
  8221   // aDeltaWidth is *subtracted* from our width.
  8222   // If advanceDelta is positive then setting the line break made us longer,
  8223   // so aDeltaWidth could go negative.
  8224   result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
  8225   // If aDeltaWidth goes negative, that means this frame might not actually fit
  8226   // anymore!!! We need higher level line layout to recover somehow.
  8227   // If it's because the frame has a soft hyphen that is now being displayed,
  8228   // this should actually be OK, because our reflow recorded the break
  8229   // opportunity that allowed the soft hyphen to be used, and we wouldn't
  8230   // have recorded the opportunity unless the hyphen fit (or was the first
  8231   // opportunity on the line).
  8232   // Otherwise this can/ really only happen when we have glyphs with special
  8233   // shapes at the end of lines, I think. Breaking inside a kerning pair won't
  8234   // do it because that would mean we broke inside this textrun, and
  8235   // BreakAndMeasureText should make sure the resulting shaped substring fits.
  8236   // Maybe if we passed a maxTextLength? But that only happens at direction
  8237   // changes (so we wouldn't kern across the boundary) or for first-letter
  8238   // (which always fits because it starts the line!).
  8239   NS_WARN_IF_FALSE(result.mDeltaWidth >= 0,
  8240                    "Negative deltawidth, something odd is happening");
  8242 #ifdef NOISY_TRIM
  8243   ListTag(stdout);
  8244   printf(": trim => %d\n", result.mDeltaWidth);
  8245 #endif
  8246   return result;
  8249 nsOverflowAreas
  8250 nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState)
  8252   nsRect bounds(nsPoint(0, 0), GetSize());
  8253   nsOverflowAreas result(bounds, bounds);
  8255   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  8256   if (!mTextRun)
  8257     return result;
  8259   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
  8260   provider.InitializeForDisplay(true);
  8262   gfxTextRun::Metrics textMetrics =
  8263     mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
  8264                           ComputeTransformedLength(provider),
  8265                           gfxFont::LOOSE_INK_EXTENTS, nullptr,
  8266                           &provider);
  8267   nsRect &vis = result.VisualOverflow();
  8268   vis.UnionRect(vis, RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent));
  8269   UnionAdditionalOverflow(PresContext(), aBlockReflowState, provider,
  8270                           &vis, true);
  8271   return result;
  8273 static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
  8274                                uint32_t aSkippedOffset, char16_t aChar)
  8276   if (aChar == '\n') {
  8277     return aStyle->NewlineIsSignificant() || aStyle->NewlineIsDiscarded() ?
  8278              aChar : ' ';
  8280   switch (aStyle->mTextTransform) {
  8281   case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
  8282     aChar = ToLowerCase(aChar);
  8283     break;
  8284   case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
  8285     aChar = ToUpperCase(aChar);
  8286     break;
  8287   case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
  8288     if (aTextRun->CanBreakLineBefore(aSkippedOffset)) {
  8289       aChar = ToTitleCase(aChar);
  8291     break;
  8294   return aChar;
  8297 nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString,
  8298                                       gfxSkipChars* aSkipChars,
  8299                                       gfxSkipCharsIterator* aSkipIter,
  8300                                       uint32_t aSkippedStartOffset,
  8301                                       uint32_t aSkippedMaxLength)
  8303   // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
  8304   gfxSkipChars skipChars;
  8305   nsTextFrame* textFrame;
  8306   const nsTextFragment* textFrag = mContent->GetText();
  8307   uint32_t keptCharsLength = 0;
  8308   uint32_t validCharsLength = 0;
  8310   // Build skipChars and copy text, for each text frame in this continuation block
  8311   for (textFrame = this; textFrame;
  8312        textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
  8313     // For each text frame continuation in this block ...
  8315     if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
  8316       // We don't trust dirty frames, expecially when computing rendered text.
  8317       break;
  8320     // Ensure the text run and grab the gfxSkipCharsIterator for it
  8321     gfxSkipCharsIterator iter =
  8322       textFrame->EnsureTextRun(nsTextFrame::eInflated);
  8323     if (!textFrame->mTextRun)
  8324       return NS_ERROR_FAILURE;
  8326     // Skip to the start of the text run, past ignored chars at start of line
  8327     // XXX In the future we may decide to trim extra spaces before a hard line
  8328     // break, in which case we need to accurately detect those sitations and 
  8329     // call GetTrimmedOffsets() with true to trim whitespace at the line's end
  8330     TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false);
  8331     int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset;
  8332     if (startOfLineSkipChars > 0) {
  8333       skipChars.SkipChars(startOfLineSkipChars);
  8334       iter.SetOriginalOffset(trimmedContentOffsets.mStart);
  8337     // Keep and copy the appropriate chars withing the caller's requested range
  8338     const nsStyleText* textStyle = textFrame->StyleText();
  8339     while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() &&
  8340            keptCharsLength < aSkippedMaxLength) {
  8341       // For each original char from content text
  8342       if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) {
  8343         skipChars.SkipChar();
  8344       } else {
  8345         ++keptCharsLength;
  8346         skipChars.KeepChar();
  8347         if (aAppendToString) {
  8348           aAppendToString->Append(
  8349               TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(),
  8350                             textFrag->CharAt(iter.GetOriginalOffset())));
  8353       iter.AdvanceOriginal(1);
  8355     if (keptCharsLength >= aSkippedMaxLength) {
  8356       break; // Already past the end, don't build string or gfxSkipCharsIter anymore
  8360   if (aSkipChars) {
  8361     aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars
  8362     if (aSkipIter) {
  8363       // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
  8364       // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars.
  8365       *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength());
  8369   return NS_OK;
  8372 nsIAtom*
  8373 nsTextFrame::GetType() const
  8375   return nsGkAtoms::textFrame;
  8378 /* virtual */ bool
  8379 nsTextFrame::IsEmpty()
  8381   NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
  8382                !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
  8383                "Invalid state");
  8385   // XXXldb Should this check compatibility mode as well???
  8386   const nsStyleText* textStyle = StyleText();
  8387   if (textStyle->WhiteSpaceIsSignificant() &&
  8388       textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES) {
  8389     // XXX shouldn't we return true if the length is zero?
  8390     return false;
  8393   if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
  8394     return false;
  8397   if (mState & TEXT_IS_ONLY_WHITESPACE) {
  8398     return true;
  8401   bool isEmpty =
  8402     textStyle->mWhiteSpace == NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES ?
  8403       IsAllNewlines(mContent->GetText()) :
  8404       IsAllWhitespace(mContent->GetText(),
  8405                       textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE);
  8406   mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
  8407   return isEmpty;
  8410 #ifdef DEBUG_FRAME_DUMP
  8411 // Translate the mapped content into a string that's printable
  8412 void
  8413 nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const
  8415   // Get the frames text content
  8416   const nsTextFragment* frag = mContent->GetText();
  8417   if (!frag) {
  8418     return;
  8421   // Compute the total length of the text content.
  8422   *aTotalContentLength = frag->GetLength();
  8424   int32_t contentLength = GetContentLength();
  8425   // Set current fragment and current fragment offset
  8426   if (0 == contentLength) {
  8427     return;
  8429   int32_t fragOffset = GetContentOffset();
  8430   int32_t n = fragOffset + contentLength;
  8431   while (fragOffset < n) {
  8432     char16_t ch = frag->CharAt(fragOffset++);
  8433     if (ch == '\r') {
  8434       aBuf.AppendLiteral("\\r");
  8435     } else if (ch == '\n') {
  8436       aBuf.AppendLiteral("\\n");
  8437     } else if (ch == '\t') {
  8438       aBuf.AppendLiteral("\\t");
  8439     } else if ((ch < ' ') || (ch >= 127)) {
  8440       aBuf.Append(nsPrintfCString("\\u%04x", ch));
  8441     } else {
  8442       aBuf.Append(ch);
  8447 nsresult
  8448 nsTextFrame::GetFrameName(nsAString& aResult) const
  8450   MakeFrameName(NS_LITERAL_STRING("Text"), aResult);
  8451   int32_t totalContentLength;
  8452   nsAutoCString tmp;
  8453   ToCString(tmp, &totalContentLength);
  8454   tmp.SetLength(std::min(tmp.Length(), 50u));
  8455   aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\"");
  8456   return NS_OK;
  8459 void
  8460 nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
  8462   nsCString str;
  8463   ListGeneric(str, aPrefix, aFlags);
  8465   str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
  8467   // Output the first/last content offset and prev/next in flow info
  8468   bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
  8469   str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
  8470           isComplete ? 'T':'F');
  8472   if (IsSelected()) {
  8473     str += " SELECTED";
  8475   fprintf_stderr(out, "%s\n", str.get());
  8477 #endif
  8479 #ifdef DEBUG
  8480 nsFrameState
  8481 nsTextFrame::GetDebugStateBits() const
  8483   // mask out our emptystate flags; those are just caches
  8484   return nsFrame::GetDebugStateBits() &
  8485     ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS);
  8487 #endif
  8489 void
  8490 nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd)
  8492   AddStateBits(NS_FRAME_IS_BIDI);
  8493   mContent->DeleteProperty(nsGkAtoms::flowlength);
  8495   /*
  8496    * After Bidi resolution we may need to reassign text runs.
  8497    * This is called during bidi resolution from the block container, so we
  8498    * shouldn't be holding a local reference to a textrun anywhere.
  8499    */
  8500   ClearTextRuns();
  8502   nsTextFrame* prev = static_cast<nsTextFrame*>(GetPrevContinuation());
  8503   if (prev) {
  8504     // the bidi resolver can be very evil when columns/pages are involved. Don't
  8505     // let it violate our invariants.
  8506     int32_t prevOffset = prev->GetContentOffset();
  8507     aStart = std::max(aStart, prevOffset);
  8508     aEnd = std::max(aEnd, prevOffset);
  8509     prev->ClearTextRuns();
  8512   mContentOffset = aStart;
  8513   SetLength(aEnd - aStart, nullptr, 0);
  8515   /**
  8516    * After inserting text the caret Bidi level must be set to the level of the
  8517    * inserted text.This is difficult, because we cannot know what the level is
  8518    * until after the Bidi algorithm is applied to the whole paragraph.
  8520    * So we set the caret Bidi level to UNDEFINED here, and the caret code will
  8521    * set it correctly later
  8522    */
  8523   nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
  8524   if (frameSelection) {
  8525     frameSelection->UndefineCaretBidiLevel();
  8529 /**
  8530  * @return true if this text frame ends with a newline character.  It should return
  8531  * false if it is not a text frame.
  8532  */
  8533 bool
  8534 nsTextFrame::HasSignificantTerminalNewline() const
  8536   return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant();
  8539 bool
  8540 nsTextFrame::IsAtEndOfLine() const
  8542   return (GetStateBits() & TEXT_END_OF_LINE) != 0;
  8545 nscoord
  8546 nsTextFrame::GetBaseline() const
  8548   return mAscent;
  8551 bool
  8552 nsTextFrame::HasAnyNoncollapsedCharacters()
  8554   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  8555   int32_t offset = GetContentOffset(),
  8556           offsetEnd = GetContentEnd();
  8557   int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
  8558   int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
  8559   return skippedOffset != skippedOffsetEnd;

mercurial