tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsTextFrame.cpp (468601B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 /* rendering object for textual content of elements */
      8 
      9 #include "nsTextFrame.h"
     10 
     11 #include <algorithm>
     12 #include <limits>
     13 
     14 #include "MathMLTextRunFactory.h"
     15 #include "PresShellInlines.h"
     16 #include "TextDrawTarget.h"
     17 #include "gfx2DGlue.h"
     18 #include "gfxContext.h"
     19 #include "gfxUtils.h"
     20 #include "mozilla/Attributes.h"
     21 #include "mozilla/BinarySearch.h"
     22 #include "mozilla/CaretAssociationHint.h"
     23 #include "mozilla/ComputedStyle.h"
     24 #include "mozilla/DebugOnly.h"
     25 #include "mozilla/IntegerRange.h"
     26 #include "mozilla/Likely.h"
     27 #include "mozilla/MathAlgorithms.h"
     28 #include "mozilla/PodOperations.h"
     29 #include "mozilla/PresShell.h"
     30 #include "mozilla/SVGTextFrame.h"
     31 #include "mozilla/ServoStyleSet.h"
     32 #include "mozilla/StaticPrefs_layout.h"
     33 #include "mozilla/StaticPresData.h"
     34 #include "mozilla/TextEditor.h"
     35 #include "mozilla/TextEvents.h"
     36 #include "mozilla/TextUtils.h"
     37 #include "mozilla/dom/CharacterDataBuffer.h"
     38 #include "mozilla/dom/PerformanceMainThread.h"
     39 #include "mozilla/gfx/2D.h"
     40 #include "mozilla/intl/Bidi.h"
     41 #include "mozilla/intl/Segmenter.h"
     42 #include "mozilla/intl/UnicodeProperties.h"
     43 #include "mozilla/widget/ThemeDrawing.h"
     44 #include "nsBlockFrame.h"
     45 #include "nsCOMPtr.h"
     46 #include "nsCSSColorUtils.h"
     47 #include "nsCSSFrameConstructor.h"
     48 #include "nsCSSPseudoElements.h"
     49 #include "nsCSSRendering.h"
     50 #include "nsCompatibility.h"
     51 #include "nsContentUtils.h"
     52 #include "nsCoord.h"
     53 #include "nsDisplayList.h"
     54 #include "nsFirstLetterFrame.h"
     55 #include "nsFontMetrics.h"
     56 #include "nsFrameSelection.h"
     57 #include "nsGkAtoms.h"
     58 #include "nsIContent.h"
     59 #include "nsIFrame.h"
     60 #include "nsIFrameInlines.h"
     61 #include "nsIMathMLFrame.h"
     62 #include "nsLayoutUtils.h"
     63 #include "nsLineBreaker.h"
     64 #include "nsLineLayout.h"
     65 #include "nsPlaceholderFrame.h"
     66 #include "nsPresContext.h"
     67 #include "nsRange.h"
     68 #include "nsRubyFrame.h"
     69 #include "nsSplittableFrame.h"
     70 #include "nsString.h"
     71 #include "nsStyleConsts.h"
     72 #include "nsStyleStruct.h"
     73 #include "nsStyleStructInlines.h"
     74 #include "nsStyleUtil.h"
     75 #include "nsTArray.h"
     76 #include "nsTextFrameUtils.h"
     77 #include "nsTextPaintStyle.h"
     78 #include "nsTextRunTransformations.h"
     79 #include "nsUnicharUtils.h"
     80 #include "nsUnicodeProperties.h"
     81 #ifdef ACCESSIBILITY
     82 #  include "nsAccessibilityService.h"
     83 #endif
     84 
     85 #include "mozilla/LookAndFeel.h"
     86 #include "mozilla/ProfilerLabels.h"
     87 #include "mozilla/UniquePtr.h"
     88 #include "mozilla/dom/Element.h"
     89 #include "mozilla/gfx/DrawTargetRecording.h"
     90 #include "nsPrintfCString.h"
     91 
     92 #ifdef DEBUG
     93 #  undef NOISY_REFLOW
     94 #  undef NOISY_TRIM
     95 #else
     96 #  undef NOISY_REFLOW
     97 #  undef NOISY_TRIM
     98 #endif
     99 
    100 #ifdef DrawText
    101 #  undef DrawText
    102 #endif
    103 
    104 using namespace mozilla;
    105 using namespace mozilla::dom;
    106 using namespace mozilla::gfx;
    107 
    108 static bool NeedsToMaskPassword(const nsTextFrame* aFrame) {
    109  MOZ_ASSERT(aFrame);
    110  MOZ_ASSERT(aFrame->GetContent());
    111  if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) {
    112    return false;
    113  }
    114  const nsIFrame* frame =
    115      nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput);
    116  MOZ_ASSERT(frame, "How do we have a masked text node without a text input?");
    117  return !frame || !frame->GetContent()->AsElement()->State().HasState(
    118                       ElementState::REVEALED);
    119 }
    120 
    121 namespace mozilla {
    122 
    123 bool TextAutospace::ShouldSuppressLetterNumeralSpacing(const nsIFrame* aFrame) {
    124  const auto wm = aFrame->GetWritingMode();
    125  if (wm.IsVertical() && !wm.IsVerticalSideways() &&
    126      aFrame->StyleVisibility()->mTextOrientation ==
    127          StyleTextOrientation::Upright) {
    128    // The characters are in vertical writing mode with forced upright glyph
    129    // orientation.
    130    return true;
    131  }
    132  if (aFrame->Style()->IsTextCombined()) {
    133    // The characters have combined forced upright glyph orientation.
    134    return true;
    135  }
    136  if (aFrame->StyleText()->mTextTransform & StyleTextTransform::FULL_WIDTH) {
    137    // The characters are transformed to full-width, so non-ideographic
    138    // letters/numerals look like ideograph letter/numerals.
    139    return true;
    140  }
    141  return false;
    142 }
    143 
    144 bool TextAutospace::Enabled(const StyleTextAutospace& aStyleTextAutospace,
    145                            const nsTextFrame* aFrame) {
    146  if (aStyleTextAutospace == StyleTextAutospace::NO_AUTOSPACE) {
    147    return false;
    148  }
    149 
    150  if (aStyleTextAutospace == StyleTextAutospace::AUTO) {
    151    // 'text-autospace: auto' is UA-defined. Currently, WebKit parses `auto` but
    152    // does not add spacing; Blink does not parse 'auto' (treated as invalid).
    153    // To align with other engines, we treat 'auto' the same as a no-op.
    154    return false;
    155  }
    156 
    157  if (ShouldSuppressLetterNumeralSpacing(aFrame)) {
    158    // If we suppress the spacing for aFrame, ideograph-alpha or
    159    // ideograph-numeric boundaries cannot occur.
    160    return false;
    161  }
    162 
    163  if (NeedsToMaskPassword(aFrame)) {
    164    // Don't allow autospacing in masked password fields, as it could reveal
    165    // hints about the types of characters present.
    166    return false;
    167  }
    168 
    169  return true;
    170 }
    171 
    172 TextAutospace::TextAutospace(const StyleTextAutospace& aStyleTextAutospace,
    173                             nscoord aInterScriptSpacing)
    174    : mBoundarySet(InitBoundarySet(aStyleTextAutospace)),
    175      mInterScriptSpacing(aInterScriptSpacing) {}
    176 
    177 bool TextAutospace::ShouldApplySpacing(CharClass aPrevClass,
    178                                       CharClass aCurrClass) const {
    179  const EnumSet<CharClass> classes{aPrevClass, aCurrClass};
    180  if (mBoundarySet.contains(Boundary::IdeographAlpha)) {
    181    constexpr EnumSet<CharClass> kIdeographAlphaMask{
    182        CharClass::Ideograph, CharClass::NonIdeographicLetter};
    183    if (classes == kIdeographAlphaMask) {
    184      return true;
    185    }
    186  }
    187 
    188  if (mBoundarySet.contains(Boundary::IdeographNumeric)) {
    189    constexpr EnumSet<CharClass> kIdeographNumericMask{
    190        CharClass::Ideograph, CharClass::NonIdeographicNumeral};
    191    if (classes == kIdeographNumericMask) {
    192      return true;
    193    }
    194  }
    195 
    196  return false;
    197 }
    198 
    199 bool TextAutospace::IsIdeograph(char32_t aChar) {
    200  // All characters in the range of U+3041 to U+30FF, except those that belong
    201  // to Unicode Punctuation [P*] general category.
    202  if (0x3041 <= aChar && aChar <= 0x30FF) {
    203    return !intl::UnicodeProperties::IsPunctuation(aChar);
    204  }
    205 
    206  // CJK Strokes (U+31C0 to U+31EF) and Katakana Phonetic Extensions (U+31F0 to
    207  // U+31FF).
    208  if (0x31C0 <= aChar && aChar <= 0x31FF) {
    209    return true;
    210  }
    211 
    212  // All characters that have the Han script property.
    213  if (intl::UnicodeProperties::GetScriptCode(aChar) == intl::Script::HAN) {
    214    return true;
    215  }
    216 
    217  return false;
    218 }
    219 
    220 TextAutospace::CharClass TextAutospace::GetCharClass(char32_t aChar) {
    221  if (IsAsciiAlpha(aChar)) {
    222    return CharClass::NonIdeographicLetter;
    223  }
    224 
    225  if (IsAsciiDigit(aChar)) {
    226    return CharClass::NonIdeographicNumeral;
    227  }
    228 
    229  if (IsIdeograph(aChar)) {
    230    return CharClass::Ideograph;
    231  }
    232 
    233  // From now on, aChar is *not* an ideograph.
    234  if (intl::UnicodeProperties::IsCombiningMark(aChar)) {
    235    // Do not treat combining mark as letter.
    236    return CharClass::CombiningMark;
    237  }
    238 
    239  if (intl::UnicodeProperties::IsLetter(aChar) &&
    240      !intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) {
    241    return CharClass::NonIdeographicLetter;
    242  }
    243 
    244  if (intl::UnicodeProperties::CharType(aChar) ==
    245      intl::GeneralCategory::Decimal_Number) {
    246    if (!intl::UnicodeProperties::IsEastAsianFullWidth(aChar)) {
    247      return CharClass::NonIdeographicNumeral;
    248    }
    249  }
    250 
    251  return CharClass::Other;
    252 }
    253 
    254 TextAutospace::BoundarySet TextAutospace::InitBoundarySet(
    255    const StyleTextAutospace& aStyleTextAutospace) const {
    256  if (aStyleTextAutospace == StyleTextAutospace::NORMAL) {
    257    return {Boundary::IdeographAlpha, Boundary::IdeographNumeric};
    258  }
    259 
    260  if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_ALPHA) {
    261    return {Boundary::IdeographAlpha};
    262  }
    263 
    264  if (aStyleTextAutospace == StyleTextAutospace::IDEOGRAPH_NUMERIC) {
    265    return {Boundary::IdeographNumeric};
    266  }
    267 
    268  return {};
    269 }
    270 
    271 }  // namespace mozilla
    272 
    273 struct TabWidth {
    274  TabWidth(uint32_t aOffset, uint32_t aWidth)
    275      : mOffset(aOffset), mWidth(float(aWidth)) {}
    276 
    277  uint32_t mOffset;  // DOM offset relative to the current frame's offset.
    278  float mWidth;      // extra space to be added at this position (in app units)
    279 };
    280 
    281 struct nsTextFrame::TabWidthStore {
    282  explicit TabWidthStore(int32_t aValidForContentOffset)
    283      : mLimit(0), mValidForContentOffset(aValidForContentOffset) {}
    284 
    285  // Apply tab widths to the aSpacing array, which corresponds to characters
    286  // beginning at aOffset and has length aLength. (Width records outside this
    287  // range will be ignored.)
    288  void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
    289                    uint32_t aOffset, uint32_t aLength);
    290 
    291  // Offset up to which tabs have been measured; positions beyond this have not
    292  // been calculated yet but may be appended if needed later.  It's a DOM
    293  // offset relative to the current frame's offset.
    294  uint32_t mLimit;
    295 
    296  // Need to recalc tab offsets if frame content offset differs from this.
    297  int32_t mValidForContentOffset;
    298 
    299  // A TabWidth record for each tab character measured so far.
    300  nsTArray<TabWidth> mWidths;
    301 };
    302 
    303 namespace {
    304 
    305 struct TabwidthAdaptor {
    306  const nsTArray<TabWidth>& mWidths;
    307  explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
    308      : mWidths(aWidths) {}
    309  uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
    310 };
    311 
    312 }  // namespace
    313 
    314 void nsTextFrame::TabWidthStore::ApplySpacing(
    315    gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
    316    uint32_t aLength) {
    317  size_t i = 0;
    318  const size_t len = mWidths.Length();
    319 
    320  // If aOffset is non-zero, do a binary search to find where to start
    321  // processing the tab widths, in case the list is really long. (See bug
    322  // 953247.)
    323  // We need to start from the first entry where mOffset >= aOffset.
    324  if (aOffset > 0) {
    325    mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
    326  }
    327 
    328  uint32_t limit = aOffset + aLength;
    329  while (i < len) {
    330    const TabWidth& tw = mWidths[i];
    331    if (tw.mOffset >= limit) {
    332      break;
    333    }
    334    aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
    335    i++;
    336  }
    337 }
    338 
    339 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,
    340                                    nsTextFrame::TabWidthStore)
    341 
    342 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)
    343 
    344 NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)
    345 
    346 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
    347 
    348 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord)
    349 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty,
    350                                      gfxTextRun::TrimmableWS)
    351 
    352 struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams {
    353  Point textBaselinePt;
    354  PropertyProvider* provider = nullptr;
    355  Range contentRange;
    356  nsTextPaintStyle* textPaintStyle = nullptr;
    357  Range glyphRange;
    358  explicit PaintTextSelectionParams(const PaintTextParams& aParams)
    359      : PaintTextParams(aParams) {}
    360 };
    361 
    362 struct nsTextFrame::DrawTextRunParams {
    363  gfxContext* context;
    364  mozilla::gfx::PaletteCache& paletteCache;
    365  PropertyProvider* provider = nullptr;
    366  gfxFloat* advanceWidth = nullptr;
    367  mozilla::SVGContextPaint* contextPaint = nullptr;
    368  DrawPathCallbacks* callbacks = nullptr;
    369  nscolor textColor = NS_RGBA(0, 0, 0, 0);
    370  nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
    371  nsAtom* fontPalette = nullptr;
    372  float textStrokeWidth = 0.0f;
    373  bool drawSoftHyphen = false;
    374  bool hasTextShadow = false;
    375  bool paintingShadows = false;
    376  DrawTextRunParams(gfxContext* aContext,
    377                    mozilla::gfx::PaletteCache& aPaletteCache)
    378      : context(aContext), paletteCache(aPaletteCache) {}
    379 };
    380 
    381 struct nsTextFrame::ClipEdges {
    382  ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
    383            nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
    384    nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame;
    385    if (aFrame->GetWritingMode().IsVertical()) {
    386      mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
    387      mVisIEnd = aVisIEndEdge > 0
    388                     ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
    389                     : nscoord_MAX;
    390    } else {
    391      mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
    392      mVisIEnd = aVisIEndEdge > 0
    393                     ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
    394                     : nscoord_MAX;
    395    }
    396  }
    397 
    398  void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
    399    nscoord end = *aVisIStart + *aVisISize;
    400    *aVisIStart = std::max(*aVisIStart, mVisIStart);
    401    *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
    402  }
    403 
    404  nscoord mVisIStart;
    405  nscoord mVisIEnd;
    406 };
    407 
    408 struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams {
    409  Point framePt;
    410  LayoutDeviceRect dirtyRect;
    411  const nsTextPaintStyle* textStyle = nullptr;
    412  const ClipEdges* clipEdges = nullptr;
    413  const nscolor* decorationOverrideColor = nullptr;
    414  Range glyphRange;
    415  DrawTextParams(gfxContext* aContext,
    416                 mozilla::gfx::PaletteCache& aPaletteCache)
    417      : DrawTextRunParams(aContext, aPaletteCache) {}
    418 };
    419 
    420 struct nsTextFrame::PaintShadowParams {
    421  gfxTextRun::Range range;
    422  LayoutDeviceRect dirtyRect;
    423  Point framePt;
    424  Point textBaselinePt;
    425  gfxContext* context;
    426  DrawPathCallbacks* callbacks = nullptr;
    427  nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
    428  const ClipEdges* clipEdges = nullptr;
    429  PropertyProvider* provider = nullptr;
    430  nscoord leftSideOffset = 0;
    431  explicit PaintShadowParams(const PaintTextParams& aParams)
    432      : dirtyRect(aParams.dirtyRect),
    433        framePt(aParams.framePt),
    434        context(aParams.context) {}
    435 };
    436 
    437 /**
    438 * A glyph observer for the change of a font glyph in a text run.
    439 *
    440 * This is stored in {Simple, Complex}TextRunUserData.
    441 */
    442 class GlyphObserver final : public gfxFont::GlyphChangeObserver {
    443 public:
    444  GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
    445      : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
    446    MOZ_ASSERT(aTextRun->GetUserData());
    447  }
    448  void NotifyGlyphsChanged() override;
    449 
    450 private:
    451  gfxTextRun* mTextRun;
    452 };
    453 
    454 static const nsFrameState TEXT_REFLOW_FLAGS =
    455    TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE |
    456    TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE |
    457    TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS |
    458    TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS;
    459 
    460 static const nsFrameState TEXT_WHITESPACE_FLAGS =
    461    TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE;
    462 
    463 /*
    464 * Some general notes
    465 *
    466 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
    467 * transforms text to positioned glyphs. It can report the geometry of the
    468 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
    469 * spacing, language, and other information.
    470 *
    471 * A gfxTextRun can cover more than one DOM text node. This is necessary to
    472 * get kerning, ligatures and shaping for text that spans multiple text nodes
    473 * but is all the same font.
    474 *
    475 * The userdata for a gfxTextRun object can be:
    476 *
    477 *   - A nsTextFrame* in the case a text run maps to only one flow. In this
    478 *   case, the textrun's user data pointer is a pointer to mStartFrame for that
    479 *   flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
    480 *   length of the text node.
    481 *
    482 *   - A SimpleTextRunUserData in the case a text run maps to one flow, but we
    483 *   still have to keep a list of glyph observers.
    484 *
    485 *   - A ComplexTextRunUserData in the case a text run maps to multiple flows,
    486 *   but we need to keep a list of glyph observers.
    487 *
    488 *   - A TextRunUserData in the case a text run maps multiple flows, but it
    489 *   doesn't have any glyph observer for changes in SVG fonts.
    490 *
    491 * You can differentiate between the four different cases with the
    492 * IsSimpleFlow and MightHaveGlyphChanges flags.
    493 *
    494 * We go to considerable effort to make sure things work even if in-flow
    495 * siblings have different ComputedStyles (i.e., first-letter and first-line).
    496 *
    497 * Our convention is that unsigned integer character offsets are offsets into
    498 * the transformed string. Signed integer character offsets are offsets into
    499 * the DOM string.
    500 *
    501 * XXX currently we don't handle hyphenated breaks between text frames where the
    502 * hyphen occurs at the end of the first text frame, e.g.
    503 *   <b>Kit&shy;</b>ty
    504 */
    505 
    506 /**
    507 * This is our user data for the textrun, when textRun->GetFlags2() has
    508 * IsSimpleFlow set, and also MightHaveGlyphChanges.
    509 *
    510 * This allows having an array of observers if there are fonts whose glyphs
    511 * might change, but also avoid allocation in the simple case that there aren't.
    512 */
    513 struct SimpleTextRunUserData {
    514  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
    515  nsTextFrame* mFrame;
    516  explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
    517 };
    518 
    519 /**
    520 * We use an array of these objects to record which text frames
    521 * are associated with the textrun. mStartFrame is the start of a list of
    522 * text frames. Some sequence of its continuations are covered by the textrun.
    523 * A content textnode can have at most one TextRunMappedFlow associated with it
    524 * for a given textrun.
    525 *
    526 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
    527 * obtain the offset into the before-transformation text of the textrun. It can
    528 * be positive (when a text node starts in the middle of a text run) or negative
    529 * (when a text run starts in the middle of a text node). Of course it can also
    530 * be zero.
    531 */
    532 struct TextRunMappedFlow {
    533  nsTextFrame* mStartFrame;
    534  int32_t mDOMOffsetToBeforeTransformOffset;
    535  // The text mapped starts at mStartFrame->GetContentOffset() and is this long
    536  uint32_t mContentLength;
    537 };
    538 
    539 /**
    540 * This is the type in the gfxTextRun's userdata field in the common case that
    541 * the text run maps to multiple flows, but no fonts have been found with
    542 * animatable glyphs.
    543 *
    544 * This way, we avoid allocating and constructing the extra nsTArray.
    545 */
    546 struct TextRunUserData {
    547 #ifdef DEBUG
    548  TextRunMappedFlow* mMappedFlows;
    549 #endif
    550  uint32_t mMappedFlowCount;
    551  uint32_t mLastFlowIndex;
    552 };
    553 
    554 /**
    555 * This is our user data for the textrun, when textRun->GetFlags2() does not
    556 * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
    557 */
    558 struct ComplexTextRunUserData : public TextRunUserData {
    559  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
    560 };
    561 
    562 static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
    563  TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
    564      sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
    565 #ifdef DEBUG
    566  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
    567 #endif
    568  data->mMappedFlowCount = aMappedFlowCount;
    569  data->mLastFlowIndex = 0;
    570  return data;
    571 }
    572 
    573 static void DestroyUserData(TextRunUserData* aUserData) {
    574  if (aUserData) {
    575    free(aUserData);
    576  }
    577 }
    578 
    579 static ComplexTextRunUserData* CreateComplexUserData(
    580    uint32_t aMappedFlowCount) {
    581  ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
    582      moz_xmalloc(sizeof(ComplexTextRunUserData) +
    583                  aMappedFlowCount * sizeof(TextRunMappedFlow)));
    584  new (data) ComplexTextRunUserData();
    585 #ifdef DEBUG
    586  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
    587 #endif
    588  data->mMappedFlowCount = aMappedFlowCount;
    589  data->mLastFlowIndex = 0;
    590  return data;
    591 }
    592 
    593 static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
    594  if (aUserData) {
    595    aUserData->~ComplexTextRunUserData();
    596    free(aUserData);
    597  }
    598 }
    599 
    600 static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
    601  MOZ_ASSERT(aTextRun->GetUserData());
    602  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    603    if (aTextRun->GetFlags2() &
    604        nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
    605      delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
    606    }
    607  } else {
    608    if (aTextRun->GetFlags2() &
    609        nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
    610      DestroyComplexUserData(
    611          static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
    612    } else {
    613      DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData()));
    614    }
    615  }
    616  aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
    617  aTextRun->SetUserData(nullptr);
    618 }
    619 
    620 static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
    621  MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
    622  MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),
    623             "The method should not be called for simple flows.");
    624  TextRunMappedFlow* flows;
    625  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
    626    flows = reinterpret_cast<TextRunMappedFlow*>(
    627        static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
    628  } else {
    629    flows = reinterpret_cast<TextRunMappedFlow*>(
    630        static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
    631  }
    632  MOZ_ASSERT(
    633      static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==
    634          flows,
    635      "GetMappedFlows should return the same pointer as mMappedFlows.");
    636  return flows;
    637 }
    638 
    639 /**
    640 * These are utility functions just for helping with the complexity related with
    641 * the text runs user data.
    642 */
    643 static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
    644  MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,
    645             "Not so simple flow?");
    646  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
    647    return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
    648  }
    649 
    650  return static_cast<nsTextFrame*>(aTextRun->GetUserData());
    651 }
    652 
    653 /**
    654 * Remove |aTextRun| from the frame continuation chain starting at
    655 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
    656 * Unmark |aFrame| as a text run owner if it's the frame we start at.
    657 * Return true if |aStartContinuation| is non-null and was found
    658 * in the next-continuation chain of |aFrame|.
    659 */
    660 static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
    661                                      nsTextFrame* aStartContinuation,
    662                                      nsFrameState aWhichTextRunState) {
    663  MOZ_ASSERT(aFrame, "null frame");
    664  MOZ_ASSERT(!aStartContinuation ||
    665                 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
    666                  aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==
    667                      aTextRun) ||
    668                 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
    669                  aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==
    670                      aTextRun),
    671             "wrong aStartContinuation for this text run");
    672 
    673  if (!aStartContinuation || aStartContinuation == aFrame) {
    674    aFrame->RemoveStateBits(aWhichTextRunState);
    675  } else {
    676    do {
    677      NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
    678      aFrame = aFrame->GetNextContinuation();
    679    } while (aFrame && aFrame != aStartContinuation);
    680  }
    681  bool found = aStartContinuation == aFrame;
    682  while (aFrame) {
    683    NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
    684    if (!aFrame->RemoveTextRun(aTextRun)) {
    685      break;
    686    }
    687    aFrame = aFrame->GetNextContinuation();
    688  }
    689 
    690  MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
    691  return found;
    692 }
    693 
    694 /**
    695 * Kill all references to |aTextRun| starting at |aStartContinuation|.
    696 * It could be referenced by any of its owners, and all their in-flows.
    697 * If |aStartContinuation| is null then process all userdata frames
    698 * and their continuations.
    699 * @note the caller is expected to take care of possibly destroying the
    700 * text run if all userdata frames were reset (userdata is deallocated
    701 * by this function though). The caller can detect this has occured by
    702 * checking |aTextRun->GetUserData() == nullptr|.
    703 */
    704 static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
    705                                    nsTextFrame* aStartContinuation) {
    706  if (!aTextRun->GetUserData()) {
    707    return;
    708  }
    709 
    710  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    711    nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
    712    nsFrameState whichTextRunState =
    713        userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
    714            ? TEXT_IN_TEXTRUN_USER_DATA
    715            : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
    716    DebugOnly<bool> found = ClearAllTextRunReferences(
    717        userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
    718    NS_ASSERTION(!aStartContinuation || found,
    719                 "aStartContinuation wasn't found in simple flow text run");
    720    if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
    721      DestroyTextRunUserData(aTextRun);
    722    }
    723  } else {
    724    auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
    725    TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
    726    int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
    727    for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
    728      nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
    729      nsFrameState whichTextRunState =
    730          userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
    731              ? TEXT_IN_TEXTRUN_USER_DATA
    732              : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
    733      bool found = ClearAllTextRunReferences(
    734          userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
    735      if (found) {
    736        if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
    737          destroyFromIndex = i + 1;
    738        } else {
    739          destroyFromIndex = i;
    740        }
    741        aStartContinuation = nullptr;
    742      }
    743    }
    744    NS_ASSERTION(destroyFromIndex >= 0,
    745                 "aStartContinuation wasn't found in multi flow text run");
    746    if (destroyFromIndex == 0) {
    747      DestroyTextRunUserData(aTextRun);
    748    } else {
    749      userData->mMappedFlowCount = uint32_t(destroyFromIndex);
    750      if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
    751        userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
    752      }
    753    }
    754  }
    755 }
    756 
    757 static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
    758  MOZ_ASSERT(aFrame);
    759 
    760  PresShell* presShell = aFrame->PresShell();
    761  for (nsIFrame* f = aFrame; f;
    762       f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
    763    f->InvalidateFrame();
    764 
    765    // If this is a non-display text frame within SVG <text>, we need
    766    // to reflow the SVGTextFrame. (This is similar to reflowing the
    767    // SVGTextFrame in response to style changes, in
    768    // SVGTextFrame::DidSetComputedStyle.)
    769    if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
    770      auto* svgTextFrame = static_cast<SVGTextFrame*>(
    771          nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
    772      svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None);
    773    } else {
    774      // Theoretically we could just update overflow areas, perhaps using
    775      // OverflowChangedTracker, but that would do a bunch of work eagerly that
    776      // we should probably do lazily here since there could be a lot
    777      // of text frames affected and we'd like to coalesce the work. So that's
    778      // not easy to do well.
    779      presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
    780    }
    781  }
    782 }
    783 
    784 void GlyphObserver::NotifyGlyphsChanged() {
    785  if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
    786    InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
    787    return;
    788  }
    789 
    790  auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
    791  TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
    792  for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
    793    InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
    794  }
    795 }
    796 
    797 int32_t nsTextFrame::GetContentEnd() const {
    798  nsTextFrame* next = GetNextContinuation();
    799  // In case of allocation failure when setting/modifying the text buffer,
    800  // it's possible our text might be missing. So we check the buffer length,
    801  // in addition to the offset of the next continuation (if any).
    802  int32_t bufferLen = CharacterDataBuffer().GetLength();
    803  return next ? std::min(bufferLen, next->GetContentOffset()) : bufferLen;
    804 }
    805 
    806 struct FlowLengthProperty {
    807  int32_t mStartOffset;
    808  // The offset of the next fixed continuation after mStartOffset, or
    809  // of the end of the text if there is none
    810  int32_t mEndFlowOffset;
    811 };
    812 
    813 int32_t nsTextFrame::GetInFlowContentLength() {
    814  if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) {
    815    return mContent->TextLength() - mContentOffset;
    816  }
    817 
    818  FlowLengthProperty* flowLength =
    819      mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
    820          ? static_cast<FlowLengthProperty*>(
    821                mContent->GetProperty(nsGkAtoms::flowlength))
    822          : nullptr;
    823  MOZ_ASSERT(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength,
    824             "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag");
    825  /**
    826   * This frame must start inside the cached flow. If the flow starts at
    827   * mContentOffset but this frame is empty, logically it might be before the
    828   * start of the cached flow.
    829   */
    830  if (flowLength &&
    831      (flowLength->mStartOffset < mContentOffset ||
    832       (flowLength->mStartOffset == mContentOffset &&
    833        GetContentEnd() > mContentOffset)) &&
    834      flowLength->mEndFlowOffset > mContentOffset) {
    835 #ifdef DEBUG
    836    NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
    837                 "frame crosses fixed continuation boundary");
    838 #endif
    839    return flowLength->mEndFlowOffset - mContentOffset;
    840  }
    841 
    842  nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
    843  int32_t endFlow =
    844      nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
    845 
    846  if (!flowLength) {
    847    flowLength = new FlowLengthProperty;
    848    if (NS_FAILED(mContent->SetProperty(
    849            nsGkAtoms::flowlength, flowLength,
    850            nsINode::DeleteProperty<FlowLengthProperty>))) {
    851      delete flowLength;
    852      flowLength = nullptr;
    853    } else {
    854      mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
    855    }
    856  }
    857  if (flowLength) {
    858    flowLength->mStartOffset = mContentOffset;
    859    flowLength->mEndFlowOffset = endFlow;
    860  }
    861 
    862  return endFlow - mContentOffset;
    863 }
    864 
    865 // Smarter versions of dom::IsSpaceCharacter.
    866 // Unicode is really annoying; sometimes a space character isn't whitespace ---
    867 // when it combines with another character
    868 // So we have several versions of IsSpace for use in different contexts.
    869 
    870 static bool IsSpaceCombiningSequenceTail(const CharacterDataBuffer& aBuffer,
    871                                         uint32_t aPos) {
    872  NS_ASSERTION(aPos <= aBuffer.GetLength(), "Bad offset");
    873  if (!aBuffer.Is2b()) {
    874    return false;
    875  }
    876  return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
    877      aBuffer.Get2b() + aPos, aBuffer.GetLength() - aPos);
    878 }
    879 
    880 // Check whether aPos is a space for CSS 'word-spacing' purposes
    881 static bool IsCSSWordSpacingSpace(const CharacterDataBuffer& aBuffer,
    882                                  uint32_t aPos, const nsTextFrame* aFrame,
    883                                  const nsStyleText* aStyleText) {
    884  NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!");
    885 
    886  char16_t ch = aBuffer.CharAt(aPos);
    887  switch (ch) {
    888    case ' ':
    889    case CH_NBSP:
    890      return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1);
    891    case '\r':
    892    case '\t':
    893      return !aStyleText->WhiteSpaceIsSignificant();
    894    case '\n':
    895      return !aStyleText->NewlineIsSignificant(aFrame);
    896    default:
    897      return false;
    898  }
    899 }
    900 
    901 constexpr char16_t kOghamSpaceMark = 0x1680;
    902 
    903 // Check whether the string aChars/aLength starts with space that's
    904 // trimmable according to CSS 'white-space:normal/nowrap'.
    905 static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
    906  NS_ASSERTION(aLength > 0, "No text for IsSpace!");
    907 
    908  char16_t ch = *aChars;
    909  if (ch == ' ' || ch == kOghamSpaceMark) {
    910    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
    911                                                           aLength - 1);
    912  }
    913  return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
    914 }
    915 
    916 // Check whether the character aCh is trimmable according to CSS
    917 // 'white-space:normal/nowrap'
    918 static bool IsTrimmableSpace(char aCh) {
    919  return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
    920 }
    921 
    922 static bool IsTrimmableSpace(const CharacterDataBuffer& aBuffer, uint32_t aPos,
    923                             const nsStyleText* aStyleText,
    924                             bool aAllowHangingWS = false) {
    925  NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSpace!");
    926 
    927  switch (aBuffer.CharAt(aPos)) {
    928    case ' ':
    929    case kOghamSpaceMark:
    930      return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
    931             !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1);
    932    case '\n':
    933      return !aStyleText->NewlineIsSignificantStyle() &&
    934             aStyleText->mWhiteSpaceCollapse !=
    935                 StyleWhiteSpaceCollapse::PreserveSpaces;
    936    case '\t':
    937    case '\r':
    938    case '\f':
    939      return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
    940    default:
    941      return false;
    942  }
    943 }
    944 
    945 static bool IsSelectionInlineWhitespace(const CharacterDataBuffer& aBuffer,
    946                                        uint32_t aPos) {
    947  NS_ASSERTION(aPos < aBuffer.GetLength(),
    948               "No text for IsSelectionInlineWhitespace!");
    949  char16_t ch = aBuffer.CharAt(aPos);
    950  if (ch == ' ' || ch == CH_NBSP) {
    951    return !IsSpaceCombiningSequenceTail(aBuffer, aPos + 1);
    952  }
    953  return ch == '\t' || ch == '\f';
    954 }
    955 
    956 static bool IsSelectionNewline(const CharacterDataBuffer& aBuffer,
    957                               uint32_t aPos) {
    958  NS_ASSERTION(aPos < aBuffer.GetLength(), "No text for IsSelectionNewline!");
    959  char16_t ch = aBuffer.CharAt(aPos);
    960  return ch == '\n' || ch == '\r';
    961 }
    962 
    963 // Count the amount of trimmable whitespace (as per CSS
    964 // 'white-space:normal/nowrap') in a character buffer. The first
    965 // character is at offset aStartOffset; the maximum number of characters
    966 // to check is aLength. aDirection is -1 or 1 depending on whether we should
    967 // progress backwards or forwards.
    968 static uint32_t GetTrimmableWhitespaceCount(const CharacterDataBuffer& aBuffer,
    969                                            int32_t aStartOffset,
    970                                            int32_t aLength,
    971                                            int32_t aDirection) {
    972  if (!aLength) {
    973    return 0;
    974  }
    975 
    976  int32_t count = 0;
    977  if (aBuffer.Is2b()) {
    978    const char16_t* str = aBuffer.Get2b() + aStartOffset;
    979    int32_t bufferLen = aBuffer.GetLength() - aStartOffset;
    980    for (; count < aLength; ++count) {
    981      if (!IsTrimmableSpace(str, bufferLen)) {
    982        break;
    983      }
    984      str += aDirection;
    985      bufferLen -= aDirection;
    986    }
    987  } else {
    988    const char* str = aBuffer.Get1b() + aStartOffset;
    989    for (; count < aLength; ++count) {
    990      if (!IsTrimmableSpace(*str)) {
    991        break;
    992      }
    993      str += aDirection;
    994    }
    995  }
    996  return count;
    997 }
    998 
    999 static bool IsAllWhitespace(const CharacterDataBuffer& aBuffer,
   1000                            bool aAllowNewline) {
   1001  if (aBuffer.Is2b()) {
   1002    return false;
   1003  }
   1004  int32_t len = aBuffer.GetLength();
   1005  const char* str = aBuffer.Get1b();
   1006  for (int32_t i = 0; i < len; ++i) {
   1007    char ch = str[i];
   1008    if (ch == ' ' || ch == '\t' || ch == '\r' ||
   1009        (ch == '\n' && aAllowNewline)) {
   1010      continue;
   1011    }
   1012    return false;
   1013  }
   1014  return true;
   1015 }
   1016 
   1017 static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
   1018  if (!(aTextRun->GetFlags2() &
   1019        nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
   1020    return;
   1021  }
   1022 
   1023  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   1024    static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
   1025        ->mGlyphObservers.Clear();
   1026  } else {
   1027    static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
   1028        ->mGlyphObservers.Clear();
   1029  }
   1030 }
   1031 
   1032 static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
   1033  if (!aTextRun->GetUserData()) {
   1034    return;
   1035  }
   1036 
   1037  ClearObserversFromTextRun(aTextRun);
   1038 
   1039  nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
   1040  uint32_t numGlyphRuns;
   1041  const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns);
   1042  for (uint32_t i = 0; i < numGlyphRuns; ++i) {
   1043    gfxFont* font = glyphRuns[i].mFont;
   1044    if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
   1045      fontsWithAnimatedGlyphs.AppendElement(font);
   1046    }
   1047  }
   1048  if (fontsWithAnimatedGlyphs.IsEmpty()) {
   1049    // NB: Theoretically, we should clear the MightHaveGlyphChanges
   1050    // here. That would involve de-allocating the simple user data struct if
   1051    // present too, and resetting the pointer to the frame. In practice, I
   1052    // don't think worth doing that work here, given the flag's only purpose is
   1053    // to distinguish what kind of user data is there.
   1054    return;
   1055  }
   1056 
   1057  nsTArray<UniquePtr<GlyphObserver>>* observers;
   1058 
   1059  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   1060    // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
   1061    // appropriate.
   1062    if (!(aTextRun->GetFlags2() &
   1063          nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
   1064      auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
   1065      aTextRun->SetUserData(new SimpleTextRunUserData(frame));
   1066    }
   1067 
   1068    auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
   1069    observers = &data->mGlyphObservers;
   1070  } else {
   1071    if (!(aTextRun->GetFlags2() &
   1072          nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
   1073      auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
   1074      TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
   1075      ComplexTextRunUserData* data =
   1076          CreateComplexUserData(oldData->mMappedFlowCount);
   1077      TextRunMappedFlow* dataMappedFlows =
   1078          reinterpret_cast<TextRunMappedFlow*>(data + 1);
   1079      data->mLastFlowIndex = oldData->mLastFlowIndex;
   1080      for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
   1081        dataMappedFlows[i] = oldMappedFlows[i];
   1082      }
   1083      DestroyUserData(oldData);
   1084      aTextRun->SetUserData(data);
   1085    }
   1086    auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
   1087    observers = &data->mGlyphObservers;
   1088  }
   1089 
   1090  aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
   1091 
   1092  observers->SetCapacity(observers->Length() +
   1093                         fontsWithAnimatedGlyphs.Length());
   1094  for (auto font : fontsWithAnimatedGlyphs) {
   1095    observers->AppendElement(MakeUnique<GlyphObserver>(font, aTextRun));
   1096  }
   1097 }
   1098 
   1099 /**
   1100 * This class accumulates state as we scan a paragraph of text. It detects
   1101 * textrun boundaries (changes from text to non-text, hard
   1102 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
   1103 * It also detects linebreaker run boundaries (changes from text to non-text,
   1104 * and hard line breaks) and at each boundary runs the linebreaker to compute
   1105 * potential line breaks. It also records actual line breaks to store them in
   1106 * the textruns.
   1107 */
   1108 class BuildTextRunsScanner {
   1109 public:
   1110  BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
   1111                       nsIFrame* aLineContainer,
   1112                       nsTextFrame::TextRunType aWhichTextRun,
   1113                       bool aDoLineBreaking)
   1114      : mDrawTarget(aDrawTarget),
   1115        mLineContainer(aLineContainer),
   1116        mCommonAncestorWithLastFrame(nullptr),
   1117        mMissingFonts(aPresContext->MissingFontRecorder()),
   1118        mBidiEnabled(aPresContext->BidiEnabled()),
   1119        mStartOfLine(true),
   1120        mSkipIncompleteTextRuns(false),
   1121        mCanStopOnThisLine(false),
   1122        mDoLineBreaking(aDoLineBreaking),
   1123        mWhichTextRun(aWhichTextRun),
   1124        mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
   1125        mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
   1126    ResetRunInfo();
   1127  }
   1128  ~BuildTextRunsScanner() {
   1129    NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
   1130    NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
   1131    NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
   1132  }
   1133 
   1134  void SetAtStartOfLine() {
   1135    mStartOfLine = true;
   1136    mCanStopOnThisLine = false;
   1137  }
   1138  void SetSkipIncompleteTextRuns(bool aSkip) {
   1139    mSkipIncompleteTextRuns = aSkip;
   1140  }
   1141  void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
   1142    mCommonAncestorWithLastFrame = aFrame;
   1143  }
   1144  bool CanStopOnThisLine() { return mCanStopOnThisLine; }
   1145  nsIFrame* GetCommonAncestorWithLastFrame() {
   1146    return mCommonAncestorWithLastFrame;
   1147  }
   1148  void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
   1149    if (mCommonAncestorWithLastFrame &&
   1150        mCommonAncestorWithLastFrame->GetParent() == aFrame) {
   1151      mCommonAncestorWithLastFrame = aFrame;
   1152    }
   1153  }
   1154  void ScanFrame(nsIFrame* aFrame);
   1155  bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
   1156  void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
   1157  void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
   1158  void ResetRunInfo() {
   1159    mLastFrame = nullptr;
   1160    mMappedFlows.Clear();
   1161    mLineBreakBeforeFrames.Clear();
   1162    mMaxTextLength = 0;
   1163    mDoubleByteText = false;
   1164  }
   1165  void AccumulateRunInfo(nsTextFrame* aFrame);
   1166  /**
   1167   * @return null to indicate either textrun construction failed or
   1168   * we constructed just a partial textrun to set up linebreaker and other
   1169   * state for following textruns.
   1170   */
   1171  already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
   1172  bool SetupLineBreakerContext(gfxTextRun* aTextRun);
   1173  void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
   1174  nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
   1175  void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
   1176  void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
   1177  struct FindBoundaryState {
   1178    nsIFrame* mStopAtFrame;
   1179    nsTextFrame* mFirstTextFrame;
   1180    nsTextFrame* mLastTextFrame;
   1181    bool mSeenTextRunBoundaryOnLaterLine;
   1182    bool mSeenTextRunBoundaryOnThisLine;
   1183    bool mSeenSpaceForLineBreakingOnThisLine;
   1184    nsTArray<char16_t>& mBuffer;
   1185  };
   1186  enum FindBoundaryResult {
   1187    FB_CONTINUE,
   1188    FB_STOPPED_AT_STOP_FRAME,
   1189    FB_FOUND_VALID_TEXTRUN_BOUNDARY
   1190  };
   1191  FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
   1192                                    FindBoundaryState* aState);
   1193 
   1194  bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
   1195 
   1196  // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
   1197  // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
   1198  // continuations starting from mStartFrame are a sequence of in-flow frames).
   1199  struct MappedFlow {
   1200    nsTextFrame* mStartFrame;
   1201    nsTextFrame* mEndFrame;
   1202    // When we consider breaking between elements, the nearest common
   1203    // ancestor of the elements containing the characters is the one whose
   1204    // CSS 'white-space' property governs. So this records the nearest common
   1205    // ancestor of mStartFrame and the previous text frame, or null if there
   1206    // was no previous text frame on this line.
   1207    nsIFrame* mAncestorControllingInitialBreak;
   1208 
   1209    int32_t GetContentEnd() const {
   1210      int32_t bufferLen = mStartFrame->CharacterDataBuffer().GetLength();
   1211      return mEndFrame ? std::min(bufferLen, mEndFrame->GetContentOffset())
   1212                       : bufferLen;
   1213    }
   1214  };
   1215 
   1216  class BreakSink final : public nsILineBreakSink {
   1217   public:
   1218    BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
   1219              uint32_t aOffsetIntoTextRun)
   1220        : mTextRun(aTextRun),
   1221          mDrawTarget(aDrawTarget),
   1222          mOffsetIntoTextRun(aOffsetIntoTextRun) {}
   1223 
   1224    void SetBreaks(uint32_t aOffset, uint32_t aLength,
   1225                   uint8_t* aBreakBefore) final {
   1226      gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
   1227                              aOffset + mOffsetIntoTextRun + aLength);
   1228      if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
   1229        // Be conservative and assume that some breaks have been set
   1230        mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks);
   1231      }
   1232    }
   1233 
   1234    void SetCapitalization(uint32_t aOffset, uint32_t aLength,
   1235                           bool* aCapitalize) final {
   1236      MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,
   1237                 "Text run should be transformed!");
   1238      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
   1239        nsTransformedTextRun* transformedTextRun =
   1240            static_cast<nsTransformedTextRun*>(mTextRun.get());
   1241        transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
   1242                                              aLength, aCapitalize);
   1243      }
   1244    }
   1245 
   1246    void Finish(gfxMissingFontRecorder* aMFR) {
   1247      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
   1248        nsTransformedTextRun* transformedTextRun =
   1249            static_cast<nsTransformedTextRun*>(mTextRun.get());
   1250        transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
   1251      }
   1252      // The way nsTransformedTextRun is implemented, its glyph runs aren't
   1253      // available until after nsTransformedTextRun::FinishSettingProperties()
   1254      // is called. So that's why we defer checking for animated glyphs to here.
   1255      CreateObserversForAnimatedGlyphs(mTextRun);
   1256    }
   1257 
   1258    RefPtr<gfxTextRun> mTextRun;
   1259    DrawTarget* mDrawTarget;
   1260    uint32_t mOffsetIntoTextRun;
   1261  };
   1262 
   1263 private:
   1264  AutoTArray<MappedFlow, 10> mMappedFlows;
   1265  AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
   1266  AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
   1267  nsLineBreaker mLineBreaker;
   1268  RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
   1269  DrawTarget* mDrawTarget;
   1270  nsIFrame* mLineContainer;
   1271  nsTextFrame* mLastFrame;
   1272  // The common ancestor of the current frame and the previous leaf frame
   1273  // on the line, or null if there was no previous leaf frame.
   1274  nsIFrame* mCommonAncestorWithLastFrame;
   1275  gfxMissingFontRecorder* mMissingFonts;
   1276  // mMaxTextLength is an upper bound on the size of the text in all mapped
   1277  // frames The value UINT32_MAX represents overflow; text will be discarded
   1278  uint32_t mMaxTextLength;
   1279  bool mDoubleByteText;
   1280  bool mBidiEnabled;
   1281  bool mStartOfLine;
   1282  bool mSkipIncompleteTextRuns;
   1283  bool mCanStopOnThisLine;
   1284  bool mDoLineBreaking;
   1285  nsTextFrame::TextRunType mWhichTextRun;
   1286  uint8_t mNextRunContextInfo;
   1287  uint8_t mCurrentRunContextInfo;
   1288 };
   1289 
   1290 static const nsIFrame* FindLineContainer(const nsIFrame* aFrame) {
   1291  while (aFrame &&
   1292         (aFrame->IsLineParticipant() || aFrame->CanContinueTextRun())) {
   1293    aFrame = aFrame->GetParent();
   1294  }
   1295  return aFrame;
   1296 }
   1297 
   1298 static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
   1299  return const_cast<nsIFrame*>(
   1300      FindLineContainer(const_cast<const nsIFrame*>(aFrame)));
   1301 }
   1302 
   1303 static bool IsLineBreakingWhiteSpace(char16_t aChar) {
   1304  // 0x0A (\n) is not handled as white-space by the line breaker, since
   1305  // we break before it, if it isn't transformed to a normal space.
   1306  // (If we treat it as normal white-space then we'd only break after it.)
   1307  // However, it does induce a line break or is converted to a regular
   1308  // space, and either way it can be used to bound the region of text
   1309  // that needs to be analyzed for line breaking.
   1310  return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
   1311 }
   1312 
   1313 static bool TextContainsLineBreakerWhiteSpace(const void* aText,
   1314                                              uint32_t aLength,
   1315                                              bool aIsDoubleByte) {
   1316  if (aIsDoubleByte) {
   1317    const char16_t* chars = static_cast<const char16_t*>(aText);
   1318    for (uint32_t i = 0; i < aLength; ++i) {
   1319      if (IsLineBreakingWhiteSpace(chars[i])) {
   1320        return true;
   1321      }
   1322    }
   1323    return false;
   1324  } else {
   1325    const uint8_t* chars = static_cast<const uint8_t*>(aText);
   1326    for (uint32_t i = 0; i < aLength; ++i) {
   1327      if (IsLineBreakingWhiteSpace(chars[i])) {
   1328        return true;
   1329      }
   1330    }
   1331    return false;
   1332  }
   1333 }
   1334 
   1335 static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
   1336    nsTextFrame* aFrame, const nsStyleText* aStyleText) {
   1337  switch (aStyleText->mWhiteSpaceCollapse) {
   1338    case StyleWhiteSpaceCollapse::Collapse:
   1339      return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
   1340    case StyleWhiteSpaceCollapse::PreserveBreaks:
   1341      return nsTextFrameUtils::COMPRESS_WHITESPACE;
   1342    case StyleWhiteSpaceCollapse::Preserve:
   1343    case StyleWhiteSpaceCollapse::PreserveSpaces:
   1344    case StyleWhiteSpaceCollapse::BreakSpaces:
   1345      if (!aStyleText->NewlineIsSignificant(aFrame)) {
   1346        // If newline is set to be preserved, but then suppressed,
   1347        // transform newline to space.
   1348        return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
   1349      }
   1350      return nsTextFrameUtils::COMPRESS_NONE;
   1351  }
   1352  MOZ_ASSERT_UNREACHABLE("Unknown white-space-collapse value");
   1353  return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
   1354 }
   1355 
   1356 struct FrameTextTraversal {
   1357  FrameTextTraversal()
   1358      : mFrameToScan(nullptr),
   1359        mOverflowFrameToScan(nullptr),
   1360        mScanSiblings(false),
   1361        mLineBreakerCanCrossFrameBoundary(false),
   1362        mTextRunCanCrossFrameBoundary(false) {}
   1363 
   1364  // These fields identify which frames should be recursively scanned
   1365  // The first normal frame to scan (or null, if no such frame should be
   1366  // scanned)
   1367  nsIFrame* mFrameToScan;
   1368  // The first overflow frame to scan (or null, if no such frame should be
   1369  // scanned)
   1370  nsIFrame* mOverflowFrameToScan;
   1371  // Whether to scan the siblings of
   1372  // mFrameToDescendInto/mOverflowFrameToDescendInto
   1373  bool mScanSiblings;
   1374 
   1375  // These identify the boundaries of the context required for
   1376  // line breaking or textrun construction
   1377  bool mLineBreakerCanCrossFrameBoundary;
   1378  bool mTextRunCanCrossFrameBoundary;
   1379 
   1380  nsIFrame* NextFrameToScan() {
   1381    nsIFrame* f;
   1382    if (mFrameToScan) {
   1383      f = mFrameToScan;
   1384      mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
   1385    } else if (mOverflowFrameToScan) {
   1386      f = mOverflowFrameToScan;
   1387      mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
   1388    } else {
   1389      f = nullptr;
   1390    }
   1391    return f;
   1392  }
   1393 };
   1394 
   1395 static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
   1396  FrameTextTraversal result;
   1397 
   1398  bool continuesTextRun = aFrame->CanContinueTextRun();
   1399  if (aFrame->IsPlaceholderFrame()) {
   1400    // placeholders are "invisible", so a text run should be able to span
   1401    // across one. But don't descend into the out-of-flow.
   1402    result.mLineBreakerCanCrossFrameBoundary = true;
   1403    if (continuesTextRun) {
   1404      // ... Except for first-letter floats, which are really in-flow
   1405      // from the point of view of capitalization etc, so we'd better
   1406      // descend into them. But we actually need to break the textrun for
   1407      // first-letter floats since things look bad if, say, we try to make a
   1408      // ligature across the float boundary.
   1409      result.mFrameToScan =
   1410          (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
   1411    } else {
   1412      result.mTextRunCanCrossFrameBoundary = true;
   1413    }
   1414  } else {
   1415    if (continuesTextRun) {
   1416      result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
   1417      result.mOverflowFrameToScan =
   1418          aFrame->GetChildList(FrameChildListID::Overflow).FirstChild();
   1419      NS_WARNING_ASSERTION(
   1420          !result.mOverflowFrameToScan,
   1421          "Scanning overflow inline frames is something we should avoid");
   1422      result.mScanSiblings = true;
   1423      result.mTextRunCanCrossFrameBoundary = true;
   1424      result.mLineBreakerCanCrossFrameBoundary = true;
   1425    } else {
   1426      MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
   1427                 "Shouldn't call this method for ruby text container");
   1428    }
   1429  }
   1430  return result;
   1431 }
   1432 
   1433 BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
   1434    nsIFrame* aFrame, FindBoundaryState* aState) {
   1435  LayoutFrameType frameType = aFrame->Type();
   1436  if (frameType == LayoutFrameType::RubyTextContainer) {
   1437    // Don't stop a text run for ruby text container. We want ruby text
   1438    // containers to be skipped, but continue the text run across them.
   1439    return FB_CONTINUE;
   1440  }
   1441 
   1442  nsTextFrame* textFrame = frameType == LayoutFrameType::Text
   1443                               ? static_cast<nsTextFrame*>(aFrame)
   1444                               : nullptr;
   1445  if (textFrame) {
   1446    if (aState->mLastTextFrame &&
   1447        textFrame != aState->mLastTextFrame->GetNextInFlow() &&
   1448        !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
   1449      aState->mSeenTextRunBoundaryOnThisLine = true;
   1450      if (aState->mSeenSpaceForLineBreakingOnThisLine) {
   1451        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
   1452      }
   1453    }
   1454    if (!aState->mFirstTextFrame) {
   1455      aState->mFirstTextFrame = textFrame;
   1456    }
   1457    aState->mLastTextFrame = textFrame;
   1458  }
   1459 
   1460  if (aFrame == aState->mStopAtFrame) {
   1461    return FB_STOPPED_AT_STOP_FRAME;
   1462  }
   1463 
   1464  if (textFrame) {
   1465    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
   1466      return FB_CONTINUE;
   1467    }
   1468    const CharacterDataBuffer& characterDataBuffer =
   1469        textFrame->CharacterDataBuffer();
   1470    uint32_t start = textFrame->GetContentOffset();
   1471    uint32_t length = textFrame->GetContentLength();
   1472    const void* text;
   1473    const nsAtom* language = textFrame->StyleFont()->mLanguage;
   1474    if (characterDataBuffer.Is2b()) {
   1475      // It is possible that we may end up removing all whitespace in
   1476      // a piece of text because of The White Space Processing Rules,
   1477      // so we need to transform it before we can check existence of
   1478      // such whitespaces.
   1479      aState->mBuffer.EnsureLengthAtLeast(length);
   1480      nsTextFrameUtils::CompressionMode compression =
   1481          GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
   1482      uint8_t incomingFlags = 0;
   1483      gfxSkipChars skipChars;
   1484      nsTextFrameUtils::Flags analysisFlags;
   1485      char16_t* bufStart = aState->mBuffer.Elements();
   1486      char16_t* bufEnd = nsTextFrameUtils::TransformText(
   1487          characterDataBuffer.Get2b() + start, length, bufStart, compression,
   1488          &incomingFlags, &skipChars, &analysisFlags, language);
   1489      text = bufStart;
   1490      length = bufEnd - bufStart;
   1491    } else {
   1492      // If the text only contains ASCII characters, it is currently
   1493      // impossible that TransformText would remove all whitespaces,
   1494      // and thus the check below should return the same result for
   1495      // transformed text and original text. So we don't need to try
   1496      // transforming it here.
   1497      text = static_cast<const void*>(characterDataBuffer.Get1b() + start);
   1498    }
   1499    if (TextContainsLineBreakerWhiteSpace(text, length,
   1500                                          characterDataBuffer.Is2b())) {
   1501      aState->mSeenSpaceForLineBreakingOnThisLine = true;
   1502      if (aState->mSeenTextRunBoundaryOnLaterLine) {
   1503        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
   1504      }
   1505    }
   1506    return FB_CONTINUE;
   1507  }
   1508 
   1509  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
   1510  if (!traversal.mTextRunCanCrossFrameBoundary) {
   1511    aState->mSeenTextRunBoundaryOnThisLine = true;
   1512    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
   1513      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
   1514    }
   1515  }
   1516 
   1517  for (nsIFrame* f = traversal.NextFrameToScan(); f;
   1518       f = traversal.NextFrameToScan()) {
   1519    FindBoundaryResult result = FindBoundaries(f, aState);
   1520    if (result != FB_CONTINUE) {
   1521      return result;
   1522    }
   1523  }
   1524 
   1525  if (!traversal.mTextRunCanCrossFrameBoundary) {
   1526    aState->mSeenTextRunBoundaryOnThisLine = true;
   1527    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
   1528      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
   1529    }
   1530  }
   1531 
   1532  return FB_CONTINUE;
   1533 }
   1534 
   1535 // build text runs for the 200 lines following aForFrame, and stop after that
   1536 // when we get a chance.
   1537 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
   1538 
   1539 /**
   1540 * General routine for building text runs. This is hairy because of the need
   1541 * to build text runs that span content nodes.
   1542 *
   1543 * @param aContext The gfxContext we're using to construct this text run.
   1544 * @param aForFrame The nsTextFrame for which we're building this text run.
   1545 * @param aLineContainer the line container containing aForFrame; if null,
   1546 *        we'll walk the ancestors to find it.  It's required to be non-null
   1547 *        when aForFrameLine is non-null.
   1548 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
   1549 *        out the line (slowly)
   1550 * @param aWhichTextRun The type of text run we want to build. If font inflation
   1551 *        is enabled, this will be eInflated, otherwise it's eNotInflated.
   1552 */
   1553 static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
   1554                          nsIFrame* aLineContainer,
   1555                          const nsLineList::iterator* aForFrameLine,
   1556                          nsTextFrame::TextRunType aWhichTextRun) {
   1557  MOZ_ASSERT(aForFrame, "for no frame?");
   1558  NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container");
   1559 
   1560  nsIFrame* lineContainerChild = aForFrame;
   1561  if (!aLineContainer) {
   1562    if (aForFrame->IsFloatingFirstLetterChild()) {
   1563      lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
   1564    }
   1565    aLineContainer = FindLineContainer(lineContainerChild);
   1566  } else {
   1567    NS_ASSERTION(
   1568        (aLineContainer == FindLineContainer(aForFrame) ||
   1569         (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),
   1570        "Wrong line container hint");
   1571  }
   1572 
   1573  if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
   1574    aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
   1575    if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
   1576      aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
   1577    }
   1578  }
   1579  if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
   1580    aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
   1581  }
   1582 
   1583  nsPresContext* presContext = aLineContainer->PresContext();
   1584  bool doLineBreaking = !aForFrame->IsInSVGTextSubtree();
   1585  BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
   1586                               aWhichTextRun, doLineBreaking);
   1587 
   1588  nsBlockFrame* block = do_QueryFrame(aLineContainer);
   1589 
   1590  if (!block) {
   1591    nsIFrame* textRunContainer = aLineContainer;
   1592    if (aLineContainer->IsRubyTextContainerFrame()) {
   1593      textRunContainer = aForFrame;
   1594      while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
   1595        textRunContainer = textRunContainer->GetParent();
   1596      }
   1597      MOZ_ASSERT(textRunContainer &&
   1598                 textRunContainer->GetParent() == aLineContainer);
   1599    } else {
   1600      NS_ASSERTION(
   1601          !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
   1602          "Breakable non-block line containers other than "
   1603          "ruby text container is not supported");
   1604    }
   1605    // Just loop through all the children of the linecontainer ... it's really
   1606    // just one line
   1607    scanner.SetAtStartOfLine();
   1608    scanner.SetCommonAncestorWithLastFrame(nullptr);
   1609    for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
   1610      scanner.ScanFrame(child);
   1611    }
   1612    // Set mStartOfLine so FlushFrames knows its textrun ends a line
   1613    scanner.SetAtStartOfLine();
   1614    scanner.FlushFrames(true, false);
   1615    return;
   1616  }
   1617 
   1618  // Find the line containing 'lineContainerChild'.
   1619 
   1620  bool isValid = true;
   1621  nsBlockInFlowLineIterator backIterator(block, &isValid);
   1622  if (aForFrameLine) {
   1623    backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
   1624  } else {
   1625    backIterator =
   1626        nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
   1627    NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
   1628    NS_ASSERTION(backIterator.GetContainer() == block,
   1629                 "Someone lied to us about the block");
   1630  }
   1631  nsBlockFrame::LineIterator startLine = backIterator.GetLine();
   1632 
   1633  // Find a line where we can start building text runs. We choose the last line
   1634  // where:
   1635  // -- there is a textrun boundary between the start of the line and the
   1636  // start of aForFrame
   1637  // -- there is a space between the start of the line and the textrun boundary
   1638  // (this is so we can be sure the line breaks will be set properly
   1639  // on the textruns we construct).
   1640  // The possibly-partial text runs up to and including the first space
   1641  // are not reconstructed. We construct partial text runs for that text ---
   1642  // for the sake of simplifying the code and feeding the linebreaker ---
   1643  // but we discard them instead of assigning them to frames.
   1644  // This is a little awkward because we traverse lines in the reverse direction
   1645  // but we traverse the frames in each line in the forward direction.
   1646  nsBlockInFlowLineIterator forwardIterator = backIterator;
   1647  nsIFrame* stopAtFrame = lineContainerChild;
   1648  nsTextFrame* nextLineFirstTextFrame = nullptr;
   1649  AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
   1650  bool seenTextRunBoundaryOnLaterLine = false;
   1651  bool mayBeginInTextRun = true;
   1652  while (true) {
   1653    forwardIterator = backIterator;
   1654    nsBlockFrame::LineIterator line = backIterator.GetLine();
   1655    if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
   1656      mayBeginInTextRun = false;
   1657      break;
   1658    }
   1659 
   1660    BuildTextRunsScanner::FindBoundaryState state = {
   1661        stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine),
   1662        false,       false,   buffer};
   1663    nsIFrame* child = line->mFirstChild;
   1664    bool foundBoundary = false;
   1665    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
   1666      BuildTextRunsScanner::FindBoundaryResult result =
   1667          scanner.FindBoundaries(child, &state);
   1668      if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
   1669        foundBoundary = true;
   1670        break;
   1671      } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
   1672        break;
   1673      }
   1674      child = child->GetNextSibling();
   1675    }
   1676    if (foundBoundary) {
   1677      break;
   1678    }
   1679    if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
   1680        !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame,
   1681                                             nextLineFirstTextFrame)) {
   1682      // Found a usable textrun boundary at the end of the line
   1683      if (state.mSeenSpaceForLineBreakingOnThisLine) {
   1684        break;
   1685      }
   1686      seenTextRunBoundaryOnLaterLine = true;
   1687    } else if (state.mSeenTextRunBoundaryOnThisLine) {
   1688      seenTextRunBoundaryOnLaterLine = true;
   1689    }
   1690    stopAtFrame = nullptr;
   1691    if (state.mFirstTextFrame) {
   1692      nextLineFirstTextFrame = state.mFirstTextFrame;
   1693    }
   1694  }
   1695  scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
   1696 
   1697  // Now iterate over all text frames starting from the current line.
   1698  // First-in-flow text frames will be accumulated into textRunFrames as we go.
   1699  // When a text run boundary is required we flush textRunFrames ((re)building
   1700  // their gfxTextRuns as necessary).
   1701  bool seenStartLine = false;
   1702  uint32_t linesAfterStartLine = 0;
   1703  do {
   1704    nsBlockFrame::LineIterator line = forwardIterator.GetLine();
   1705    if (line->IsBlock()) {
   1706      break;
   1707    }
   1708    line->SetInvalidateTextRuns(false);
   1709    scanner.SetAtStartOfLine();
   1710    scanner.SetCommonAncestorWithLastFrame(nullptr);
   1711    nsIFrame* child = line->mFirstChild;
   1712    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
   1713      scanner.ScanFrame(child);
   1714      child = child->GetNextSibling();
   1715    }
   1716    if (line.get() == startLine.get()) {
   1717      seenStartLine = true;
   1718    }
   1719    if (seenStartLine) {
   1720      ++linesAfterStartLine;
   1721      if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS &&
   1722          scanner.CanStopOnThisLine()) {
   1723        // Don't flush frames; we may be in the middle of a textrun
   1724        // that we can't end here. That's OK, we just won't build it.
   1725        // Note that we must already have finished the textrun for aForFrame,
   1726        // because we've seen the end of a textrun in a line after the line
   1727        // containing aForFrame.
   1728        scanner.FlushLineBreaks(nullptr);
   1729        // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
   1730        // silences assertions in the scanner destructor.
   1731        scanner.ResetRunInfo();
   1732        return;
   1733      }
   1734    }
   1735  } while (forwardIterator.Next());
   1736 
   1737  // Set mStartOfLine so FlushFrames knows its textrun ends a line
   1738  scanner.SetAtStartOfLine();
   1739  scanner.FlushFrames(true, false);
   1740 }
   1741 
   1742 static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) {
   1743  while (aCount) {
   1744    *aDest = *aSrc;
   1745    ++aDest;
   1746    ++aSrc;
   1747    --aCount;
   1748  }
   1749  return aDest;
   1750 }
   1751 
   1752 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
   1753    const gfxTextRun* aTextRun) {
   1754  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   1755    return mMappedFlows.Length() == 1 &&
   1756           mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
   1757           mMappedFlows[0].mEndFrame == nullptr;
   1758  }
   1759 
   1760  auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
   1761  TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
   1762  if (userData->mMappedFlowCount != mMappedFlows.Length()) {
   1763    return false;
   1764  }
   1765  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
   1766    if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
   1767        int32_t(userMappedFlows[i].mContentLength) !=
   1768            mMappedFlows[i].GetContentEnd() -
   1769                mMappedFlows[i].mStartFrame->GetContentOffset()) {
   1770      return false;
   1771    }
   1772  }
   1773  return true;
   1774 }
   1775 
   1776 /**
   1777 * This gets called when we need to make a text run for the current list of
   1778 * frames.
   1779 */
   1780 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks,
   1781                                       bool aSuppressTrailingBreak) {
   1782  RefPtr<gfxTextRun> textRun;
   1783  if (!mMappedFlows.IsEmpty()) {
   1784    if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
   1785        !!(mCurrentFramesAllSameTextRun->GetFlags2() &
   1786           nsTextFrameUtils::Flags::IncomingWhitespace) ==
   1787            !!(mCurrentRunContextInfo &
   1788               nsTextFrameUtils::INCOMING_WHITESPACE) &&
   1789        !!(mCurrentFramesAllSameTextRun->GetFlags() &
   1790           gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
   1791            !!(mCurrentRunContextInfo &
   1792               nsTextFrameUtils::INCOMING_ARABICCHAR) &&
   1793        IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
   1794      // Optimization: We do not need to (re)build the textrun.
   1795      textRun = mCurrentFramesAllSameTextRun;
   1796 
   1797      if (mDoLineBreaking) {
   1798        // Feed this run's text into the linebreaker to provide context.
   1799        if (!SetupLineBreakerContext(textRun)) {
   1800          return;
   1801        }
   1802      }
   1803 
   1804      // Update mNextRunContextInfo appropriately
   1805      mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
   1806      if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) {
   1807        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
   1808      }
   1809      if (textRun->GetFlags() &
   1810          gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
   1811        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
   1812      }
   1813    } else {
   1814      AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
   1815      uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
   1816      if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
   1817          !buffer.AppendElements(bufferSize, fallible)) {
   1818        return;
   1819      }
   1820      textRun = BuildTextRunForFrames(buffer.Elements());
   1821    }
   1822  }
   1823 
   1824  if (aFlushLineBreaks) {
   1825    FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
   1826    if (!mDoLineBreaking && textRun) {
   1827      CreateObserversForAnimatedGlyphs(textRun.get());
   1828    }
   1829  }
   1830 
   1831  mCanStopOnThisLine = true;
   1832  ResetRunInfo();
   1833 }
   1834 
   1835 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) {
   1836  // If the line-breaker is buffering a potentially-unfinished word,
   1837  // preserve the state of being in-word so that we don't spuriously
   1838  // capitalize the next letter.
   1839  bool inWord = mLineBreaker.InWord();
   1840  bool trailingLineBreak;
   1841  nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
   1842  mLineBreaker.SetWordContinuation(inWord);
   1843  // textRun may be null for various reasons, including because we constructed
   1844  // a partial textrun just to get the linebreaker and other state set up
   1845  // to build the next textrun.
   1846  if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
   1847    aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak);
   1848  }
   1849 
   1850  for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
   1851    // TODO cause frames associated with the textrun to be reflowed, if they
   1852    // aren't being reflowed already!
   1853    mBreakSinks[i]->Finish(mMissingFonts);
   1854  }
   1855  mBreakSinks.Clear();
   1856 }
   1857 
   1858 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) {
   1859  if (mMaxTextLength != UINT32_MAX) {
   1860    NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),
   1861                 "integer overflow");
   1862    if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
   1863      mMaxTextLength = UINT32_MAX;
   1864    } else {
   1865      mMaxTextLength += aFrame->GetContentLength();
   1866    }
   1867  }
   1868  mDoubleByteText |= aFrame->CharacterDataBuffer().Is2b();
   1869  mLastFrame = aFrame;
   1870  mCommonAncestorWithLastFrame = aFrame->GetParent();
   1871 
   1872  MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
   1873  NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
   1874                   mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
   1875               "Overlapping or discontiguous frames => BAD");
   1876  mappedFlow->mEndFrame = aFrame->GetNextContinuation();
   1877  if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
   1878    mCurrentFramesAllSameTextRun = nullptr;
   1879  }
   1880 
   1881  if (mStartOfLine) {
   1882    mLineBreakBeforeFrames.AppendElement(aFrame);
   1883    mStartOfLine = false;
   1884  }
   1885 
   1886  // Default limits used by `hyphenate-limit-chars` for `auto` components, as
   1887  // suggested by the CSS Text spec.
   1888  // TODO: consider making these sensitive to the context, e.g. increasing the
   1889  // values for long line lengths to reduce the tendency to hyphenate too much.
   1890  const uint32_t kDefaultHyphenateTotalWordLength = 5;
   1891  const uint32_t kDefaultHyphenatePreBreakLength = 2;
   1892  const uint32_t kDefaultHyphenatePostBreakLength = 2;
   1893 
   1894  const auto& hyphenateLimitChars = aFrame->StyleText()->mHyphenateLimitChars;
   1895  uint32_t pre =
   1896      hyphenateLimitChars.pre_hyphen_length.IsAuto()
   1897          ? kDefaultHyphenatePreBreakLength
   1898          : std::max(0, hyphenateLimitChars.pre_hyphen_length.AsNumber());
   1899  uint32_t post =
   1900      hyphenateLimitChars.post_hyphen_length.IsAuto()
   1901          ? kDefaultHyphenatePostBreakLength
   1902          : std::max(0, hyphenateLimitChars.post_hyphen_length.AsNumber());
   1903  uint32_t total =
   1904      hyphenateLimitChars.total_word_length.IsAuto()
   1905          ? kDefaultHyphenateTotalWordLength
   1906          : std::max(0, hyphenateLimitChars.total_word_length.AsNumber());
   1907  total = std::max(total, pre + post);
   1908  mLineBreaker.SetHyphenateLimitChars(total, pre, post);
   1909 }
   1910 
   1911 static bool HasTerminalNewline(const nsTextFrame* aFrame) {
   1912  if (aFrame->GetContentLength() == 0) {
   1913    return false;
   1914  }
   1915  const CharacterDataBuffer& characterDataBuffer =
   1916      aFrame->CharacterDataBuffer();
   1917  return characterDataBuffer.CharAt(
   1918             AssertedCast<uint32_t>(aFrame->GetContentEnd()) - 1) == '\n';
   1919 }
   1920 
   1921 static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup,
   1922                                            bool aVerticalMetrics) {
   1923  if (!aFontGroup) {
   1924    return gfxFont::Metrics();
   1925  }
   1926  RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
   1927  return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical
   1928                                           : nsFontMetrics::eHorizontal);
   1929 }
   1930 
   1931 static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) {
   1932  gfxFloat chWidthAppUnits = NS_round(
   1933      GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical())
   1934          .ZeroOrAveCharWidth() *
   1935      aTextRun->GetAppUnitsPerDevUnit());
   1936  return 0.5 * chWidthAppUnits;
   1937 }
   1938 
   1939 static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) {
   1940  if (!aFrame->IsInSVGTextSubtree()) {
   1941    return 1.0f;
   1942  }
   1943  auto* container =
   1944      nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
   1945  MOZ_ASSERT(container);
   1946  return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
   1947 }
   1948 
   1949 static nscoord LetterSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) {
   1950  if (aFrame->IsInSVGTextSubtree()) {
   1951    // SVG text can have a scaling factor applied so that very small or very
   1952    // large font-sizes don't suffer from poor glyph placement due to app unit
   1953    // rounding. The used letter-spacing value must be scaled by the same
   1954    // factor.
   1955    return GetSVGFontSizeScaleFactor(aFrame) *
   1956           aStyleText.mLetterSpacing.Resolve(
   1957               [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
   1958  }
   1959 
   1960  return aStyleText.mLetterSpacing.Resolve(
   1961      [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
   1962 }
   1963 
   1964 // This function converts non-coord values (e.g. percentages) to nscoord.
   1965 static nscoord WordSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) {
   1966  if (aFrame->IsInSVGTextSubtree()) {
   1967    // SVG text can have a scaling factor applied so that very small or very
   1968    // large font-sizes don't suffer from poor glyph placement due to app unit
   1969    // rounding. The used word-spacing value must be scaled by the same
   1970    // factor.
   1971    return GetSVGFontSizeScaleFactor(aFrame) *
   1972           aStyleText.mWordSpacing.Resolve(
   1973               [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
   1974  }
   1975 
   1976  return aStyleText.mWordSpacing.Resolve(
   1977      [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
   1978 }
   1979 
   1980 gfx::ShapedTextFlags nsTextFrame::GetSpacingFlags() const {
   1981  const nsStyleText* styleText = StyleText();
   1982  const auto& ls = styleText->mLetterSpacing;
   1983  const auto& ws = styleText->mWordSpacing;
   1984 
   1985  // It's possible to have a calc() value that computes to zero but for which
   1986  // IsDefinitelyZero() is false, in which case we'll return
   1987  // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
   1988  // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
   1989  bool nonStandardSpacing =
   1990      !ls.IsDefinitelyZero() || !ws.IsDefinitelyZero() ||
   1991      TextAutospace::Enabled(styleText->EffectiveTextAutospace(), this);
   1992  return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
   1993                            : gfx::ShapedTextFlags();
   1994 }
   1995 
   1996 bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1,
   1997                                                       nsTextFrame* aFrame2) {
   1998  // We don't need to check font size inflation, since
   1999  // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
   2000  // ensures that text runs never cross block boundaries.  This means
   2001  // that the font size inflation on all text frames in the text run is
   2002  // already guaranteed to be the same as each other (and for the line
   2003  // container).
   2004  if (mBidiEnabled) {
   2005    FrameBidiData data1 = aFrame1->GetBidiData();
   2006    FrameBidiData data2 = aFrame2->GetBidiData();
   2007    if (data1.embeddingLevel != data2.embeddingLevel ||
   2008        data2.precedingControl != kBidiLevelNone) {
   2009      return false;
   2010    }
   2011  }
   2012 
   2013  ComputedStyle* sc1 = aFrame1->Style();
   2014  ComputedStyle* sc2 = aFrame2->Style();
   2015 
   2016  // Any difference in writing-mode/directionality inhibits shaping across
   2017  // the boundary.
   2018  WritingMode wm(sc1);
   2019  if (wm != WritingMode(sc2)) {
   2020    return false;
   2021  }
   2022 
   2023  const nsStyleText* textStyle1 = sc1->StyleText();
   2024  // If the first frame ends in a preformatted newline, then we end the textrun
   2025  // here. This avoids creating giant textruns for an entire plain text file.
   2026  // Note that we create a single text frame for a preformatted text node,
   2027  // even if it has newlines in it, so typically we won't see trailing newlines
   2028  // until after reflow has broken up the frame into one (or more) frames per
   2029  // line. That's OK though.
   2030  if (textStyle1->NewlineIsSignificant(aFrame1) &&
   2031      HasTerminalNewline(aFrame1)) {
   2032    return false;
   2033  }
   2034 
   2035  if (aFrame1->GetParent()->GetContent() !=
   2036      aFrame2->GetParent()->GetContent()) {
   2037    // Does aFrame, or any ancestor between it and aAncestor, have a property
   2038    // that should inhibit cross-element-boundary shaping on aSide?
   2039    auto PreventCrossBoundaryShaping = [](const nsIFrame* aFrame,
   2040                                          const nsIFrame* aAncestor,
   2041                                          Side aSide) {
   2042      while (aFrame != aAncestor) {
   2043        ComputedStyle* ctx = aFrame->Style();
   2044        const auto anchorResolutionParams =
   2045            AnchorPosResolutionParams::From(aFrame);
   2046        // According to https://drafts.csswg.org/css-text/#boundary-shaping:
   2047        //
   2048        // Text shaping must be broken at inline box boundaries when any of
   2049        // the following are true for any box whose boundary separates the
   2050        // two typographic character units:
   2051        //
   2052        // 1. Any of margin/border/padding separating the two typographic
   2053        //    character units in the inline axis is non-zero.
   2054        const auto margin =
   2055            ctx->StyleMargin()->GetMargin(aSide, anchorResolutionParams);
   2056        if (!margin->ConvertsToLength() ||
   2057            margin->AsLengthPercentage().ToLength() != 0) {
   2058          return true;
   2059        }
   2060        const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
   2061        if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
   2062          return true;
   2063        }
   2064        if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
   2065          return true;
   2066        }
   2067 
   2068        // 2. vertical-align is not baseline.
   2069        //
   2070        // FIXME: Should this use VerticalAlignEnum()?
   2071        const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign;
   2072        if (!verticalAlign.IsKeyword() ||
   2073            verticalAlign.AsKeyword() != StyleVerticalAlignKeyword::Baseline) {
   2074          return true;
   2075        }
   2076 
   2077        // 3. The boundary is a bidi isolation boundary.
   2078        const auto unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
   2079        if (unicodeBidi == StyleUnicodeBidi::Isolate ||
   2080            unicodeBidi == StyleUnicodeBidi::IsolateOverride) {
   2081          return true;
   2082        }
   2083 
   2084        aFrame = aFrame->GetParent();
   2085      }
   2086      return false;
   2087    };
   2088 
   2089    const nsIFrame* ancestor =
   2090        nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1,
   2091                                                                 aFrame2);
   2092 
   2093    if (!ancestor) {
   2094      // The two frames are within different blocks, e.g. due to block
   2095      // fragmentation. In theory we shouldn't prevent cross-frame shaping
   2096      // here, but it's an edge case where we should rarely decide to allow
   2097      // cross-frame shaping, so we don't try harder here.
   2098      return false;
   2099    }
   2100 
   2101    // We inhibit cross-element-boundary shaping if we're in SVG content,
   2102    // as there are too many things SVG might be doing (like applying per-
   2103    // element positioning) that wouldn't make sense with shaping across
   2104    // the boundary.
   2105    if (ancestor->IsInSVGTextSubtree()) {
   2106      return false;
   2107    }
   2108 
   2109    // Map inline-end and inline-start to physical sides for checking presence
   2110    // of non-zero margin/border/padding.
   2111    Side side1 = wm.PhysicalSide(LogicalSide::IEnd);
   2112    Side side2 = wm.PhysicalSide(LogicalSide::IStart);
   2113    // If the frames have an embedding level that is opposite to the writing
   2114    // mode, we need to swap which sides we're checking.
   2115    if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) {
   2116      std::swap(side1, side2);
   2117    }
   2118 
   2119    if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) ||
   2120        PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) {
   2121      return false;
   2122    }
   2123  }
   2124 
   2125  if (aFrame1->GetContent() == aFrame2->GetContent() &&
   2126      aFrame1->GetNextInFlow() != aFrame2) {
   2127    // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
   2128    // sometimes when the unicode-bidi property is used; the bidi resolver
   2129    // breaks text into different frames even though the text has the same
   2130    // direction. We can't allow these two frames to share the same textrun
   2131    // because that would violate our invariant that two flows in the same
   2132    // textrun have different content elements.
   2133    return false;
   2134  }
   2135 
   2136  if (sc1 == sc2) {
   2137    return true;
   2138  }
   2139 
   2140  const nsStyleText* textStyle2 = sc2->StyleText();
   2141  if (textStyle1->mTextTransform != textStyle2->mTextTransform ||
   2142      textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() ||
   2143      textStyle1->mLineBreak != textStyle2->mLineBreak) {
   2144    return false;
   2145  }
   2146 
   2147  nsPresContext* pc = aFrame1->PresContext();
   2148  MOZ_ASSERT(pc == aFrame2->PresContext());
   2149 
   2150  const nsStyleFont* fontStyle1 = sc1->StyleFont();
   2151  const nsStyleFont* fontStyle2 = sc2->StyleFont();
   2152  nscoord letterSpacing1 = LetterSpacing(aFrame1, *textStyle1);
   2153  nscoord letterSpacing2 = LetterSpacing(aFrame2, *textStyle2);
   2154  return fontStyle1->mFont == fontStyle2->mFont &&
   2155         fontStyle1->mLanguage == fontStyle2->mLanguage &&
   2156         nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1,
   2157                                                letterSpacing1) ==
   2158             nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2,
   2159                                                    textStyle2, letterSpacing2);
   2160 }
   2161 
   2162 void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) {
   2163  LayoutFrameType frameType = aFrame->Type();
   2164  if (frameType == LayoutFrameType::RubyTextContainer) {
   2165    // Don't include any ruby text container into the text run.
   2166    return;
   2167  }
   2168 
   2169  // First check if we can extend the current mapped frame block. This is
   2170  // common.
   2171  if (mMappedFlows.Length() > 0) {
   2172    MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
   2173    if (mappedFlow->mEndFrame == aFrame &&
   2174        aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
   2175      NS_ASSERTION(frameType == LayoutFrameType::Text,
   2176                   "Flow-sibling of a text frame is not a text frame?");
   2177 
   2178      // Don't do this optimization if mLastFrame has a terminal newline...
   2179      // it's quite likely preformatted and we might want to end the textrun
   2180      // here. This is almost always true:
   2181      if (mLastFrame->Style() == aFrame->Style() &&
   2182          !HasTerminalNewline(mLastFrame)) {
   2183        AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
   2184        return;
   2185      }
   2186    }
   2187  }
   2188 
   2189  // Now see if we can add a new set of frames to the current textrun
   2190  if (frameType == LayoutFrameType::Text) {
   2191    nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
   2192 
   2193    if (mLastFrame) {
   2194      if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
   2195        FlushFrames(false, false);
   2196      } else {
   2197        if (mLastFrame->GetContent() == frame->GetContent()) {
   2198          AccumulateRunInfo(frame);
   2199          return;
   2200        }
   2201      }
   2202    }
   2203 
   2204    MappedFlow* mappedFlow = mMappedFlows.AppendElement();
   2205    mappedFlow->mStartFrame = frame;
   2206    mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
   2207 
   2208    AccumulateRunInfo(frame);
   2209    if (mMappedFlows.Length() == 1) {
   2210      mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
   2211      mCurrentRunContextInfo = mNextRunContextInfo;
   2212    }
   2213    return;
   2214  }
   2215 
   2216  if (frameType == LayoutFrameType::Placeholder &&
   2217      aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS |
   2218                              PLACEHOLDER_FOR_FIXEDPOS)) {
   2219    // Somewhat hacky fix for bug 1418472:
   2220    // If this is a placeholder for an absolute-positioned frame, we need to
   2221    // flush the line-breaker to prevent the placeholder becoming separated
   2222    // from the immediately-following content.
   2223    // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos
   2224    // element occurs within a word where shaping should be in effect, but
   2225    // that's an edge case, unlikely to occur in real content. A more precise
   2226    // fix might require better separation of line-breaking from textrun setup,
   2227    // but that's a big invasive change (and potentially expensive for perf, as
   2228    // it might introduce an additional pass over all the frames).
   2229    FlushFrames(true, false);
   2230  }
   2231 
   2232  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
   2233  bool isBR = frameType == LayoutFrameType::Br;
   2234  if (!traversal.mLineBreakerCanCrossFrameBoundary) {
   2235    // BR frames are special. We do not need or want to record a break
   2236    // opportunity before a BR frame.
   2237    FlushFrames(true, isBR);
   2238    mCommonAncestorWithLastFrame = aFrame;
   2239    mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
   2240    mStartOfLine = false;
   2241  } else if (!traversal.mTextRunCanCrossFrameBoundary) {
   2242    FlushFrames(false, false);
   2243  }
   2244 
   2245  for (nsIFrame* f = traversal.NextFrameToScan(); f;
   2246       f = traversal.NextFrameToScan()) {
   2247    ScanFrame(f);
   2248  }
   2249 
   2250  if (!traversal.mLineBreakerCanCrossFrameBoundary) {
   2251    // Really if we're a BR frame this is unnecessary since descendInto will be
   2252    // false. In fact this whole "if" statement should move into the
   2253    // descendInto.
   2254    FlushFrames(true, isBR);
   2255    mCommonAncestorWithLastFrame = aFrame;
   2256    mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
   2257  } else if (!traversal.mTextRunCanCrossFrameBoundary) {
   2258    FlushFrames(false, false);
   2259  }
   2260 
   2261  LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
   2262 }
   2263 
   2264 nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) {
   2265  uint32_t index = *aIndex;
   2266  if (index >= mLineBreakBeforeFrames.Length()) {
   2267    return nullptr;
   2268  }
   2269  *aIndex = index + 1;
   2270  return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
   2271 }
   2272 
   2273 static gfxFontGroup* GetFontGroupForFrame(
   2274    const nsIFrame* aFrame, float aFontSizeInflation,
   2275    nsFontMetrics** aOutFontMetrics = nullptr) {
   2276  RefPtr<nsFontMetrics> metrics =
   2277      nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
   2278  gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
   2279 
   2280  // Populate outparam before we return:
   2281  if (aOutFontMetrics) {
   2282    metrics.forget(aOutFontMetrics);
   2283  }
   2284  // XXX this is a bit bogus, we're releasing 'metrics' so the
   2285  // returned font-group might actually be torn down, although because
   2286  // of the way the device context caches font metrics, this seems to
   2287  // not actually happen. But we should fix this.
   2288  return fontGroup;
   2289 }
   2290 
   2291 nsFontMetrics* nsTextFrame::InflatedFontMetrics() const {
   2292  if (!mFontMetrics) {
   2293    float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   2294    mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
   2295  }
   2296  return mFontMetrics;
   2297 }
   2298 
   2299 static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) {
   2300  gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
   2301  if (textRun) {
   2302    return textRun->GetFontGroup();
   2303  }
   2304  return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
   2305 }
   2306 
   2307 static already_AddRefed<DrawTarget> CreateReferenceDrawTarget(
   2308    const nsTextFrame* aTextFrame) {
   2309  UniquePtr<gfxContext> ctx =
   2310      aTextFrame->PresShell()->CreateReferenceRenderingContext();
   2311  RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
   2312  return dt.forget();
   2313 }
   2314 
   2315 static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame,
   2316                                                     DrawTarget* aDrawTarget) {
   2317  RefPtr<DrawTarget> dt = aDrawTarget;
   2318  if (!dt) {
   2319    dt = CreateReferenceDrawTarget(aTextFrame);
   2320    if (!dt) {
   2321      return nullptr;
   2322    }
   2323  }
   2324 
   2325  RefPtr<nsFontMetrics> fm =
   2326      nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame);
   2327  auto* fontGroup = fm->GetThebesFontGroup();
   2328  auto appPerDev = aTextFrame->PresContext()->AppUnitsPerDevPixel();
   2329  const auto& hyphenateChar = aTextFrame->StyleText()->mHyphenateCharacter;
   2330  gfx::ShapedTextFlags flags =
   2331      nsLayoutUtils::GetTextRunOrientFlagsForStyle(aTextFrame->Style());
   2332  // Make the directionality of the hyphen run (in case it is multi-char) match
   2333  // the text frame.
   2334  if (aTextFrame->GetWritingMode().IsBidiRTL()) {
   2335    flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
   2336  }
   2337  if (hyphenateChar.IsAuto()) {
   2338    return fontGroup->MakeHyphenTextRun(dt, flags, appPerDev);
   2339  }
   2340  auto* missingFonts = aTextFrame->PresContext()->MissingFontRecorder();
   2341  const NS_ConvertUTF8toUTF16 hyphenStr(hyphenateChar.AsString().AsString());
   2342  return fontGroup->MakeTextRun(hyphenStr.BeginReading(), hyphenStr.Length(),
   2343                                dt, appPerDev, flags, nsTextFrameUtils::Flags(),
   2344                                missingFonts);
   2345 }
   2346 
   2347 already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames(
   2348    void* aTextBuffer) {
   2349  gfxSkipChars skipChars;
   2350 
   2351  const void* textPtr = aTextBuffer;
   2352  bool anyTextTransformStyle = false;
   2353  bool anyMathMLStyling = false;
   2354  bool anyTextEmphasis = false;
   2355  uint8_t sstyScriptLevel = 0;
   2356  uint32_t mathFlags = 0;
   2357  gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
   2358  nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks;
   2359 
   2360  if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
   2361    flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace;
   2362  }
   2363  if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
   2364    flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
   2365  }
   2366 
   2367  AutoTArray<int32_t, 50> textBreakPoints;
   2368  TextRunUserData dummyData;
   2369  TextRunMappedFlow dummyMappedFlow;
   2370  TextRunMappedFlow* userMappedFlows;
   2371  TextRunUserData* userData;
   2372  TextRunUserData* userDataToDestroy;
   2373  // If the situation is particularly simple (and common) we don't need to
   2374  // allocate userData.
   2375  if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
   2376      mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
   2377    userData = &dummyData;
   2378    userMappedFlows = &dummyMappedFlow;
   2379    userDataToDestroy = nullptr;
   2380    dummyData.mMappedFlowCount = mMappedFlows.Length();
   2381    dummyData.mLastFlowIndex = 0;
   2382  } else {
   2383    userData = CreateUserData(mMappedFlows.Length());
   2384    userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
   2385    userDataToDestroy = userData;
   2386  }
   2387 
   2388  uint32_t currentTransformedTextOffset = 0;
   2389 
   2390  uint32_t nextBreakIndex = 0;
   2391  nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
   2392  bool isSVG = mLineContainer->IsInSVGTextSubtree();
   2393  bool enabledJustification =
   2394      (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
   2395       mLineContainer->StyleText()->mTextAlignLast ==
   2396           StyleTextAlignLast::Justify);
   2397 
   2398  const nsStyleText* textStyle = nullptr;
   2399  const nsStyleFont* fontStyle = nullptr;
   2400  ComputedStyle* lastComputedStyle = nullptr;
   2401  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
   2402    MappedFlow* mappedFlow = &mMappedFlows[i];
   2403    nsTextFrame* f = mappedFlow->mStartFrame;
   2404 
   2405    lastComputedStyle = f->Style();
   2406    // Detect use of text-transform or font-variant anywhere in the run
   2407    textStyle = f->StyleText();
   2408    if (!textStyle->mTextTransform.IsNone() ||
   2409        textStyle->mWebkitTextSecurity != StyleTextSecurity::None ||
   2410        // text-combine-upright requires converting from full-width
   2411        // characters to non-full-width correspendent in some cases.
   2412        lastComputedStyle->IsTextCombined()) {
   2413      anyTextTransformStyle = true;
   2414    }
   2415    if (textStyle->HasEffectiveTextEmphasis()) {
   2416      anyTextEmphasis = true;
   2417    }
   2418    flags |= f->GetSpacingFlags();
   2419    nsTextFrameUtils::CompressionMode compression =
   2420        GetCSSWhitespaceToCompressionMode(f, textStyle);
   2421    if ((enabledJustification || f->ShouldSuppressLineBreak()) && !isSVG) {
   2422      flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
   2423    }
   2424    fontStyle = f->StyleFont();
   2425    nsIFrame* parent = mLineContainer->GetParent();
   2426    if (StyleMathVariant::None != fontStyle->mMathVariant) {
   2427      if (StyleMathVariant::Normal != fontStyle->mMathVariant) {
   2428        anyMathMLStyling = true;
   2429      }
   2430    } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
   2431      flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi;
   2432      anyMathMLStyling = true;
   2433    }
   2434    if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
   2435      // All MathML tokens except <mtext> use 'math' script.
   2436      if (!(parent && parent->GetContent() &&
   2437            parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext))) {
   2438        flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
   2439      }
   2440      nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
   2441      if (mathFrame) {
   2442        nsPresentationData presData;
   2443        mathFrame->GetPresentationData(presData);
   2444        if (presData.flags.contains(MathMLPresentationFlag::Dtls)) {
   2445          mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
   2446          anyMathMLStyling = true;
   2447        }
   2448      }
   2449    }
   2450    nsIFrame* child = mLineContainer;
   2451    uint8_t oldScriptLevel = 0;
   2452    while (parent &&
   2453           child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
   2454      // Reconstruct the script level ignoring any user overrides. It is
   2455      // calculated this way instead of using scriptlevel to ensure the
   2456      // correct ssty font feature setting is used even if the user sets a
   2457      // different (especially negative) scriptlevel.
   2458      nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
   2459      if (mathFrame) {
   2460        sstyScriptLevel += mathFrame->ScriptIncrement(child);
   2461      }
   2462      if (sstyScriptLevel < oldScriptLevel) {
   2463        // overflow
   2464        sstyScriptLevel = UINT8_MAX;
   2465        break;
   2466      }
   2467      child = parent;
   2468      parent = parent->GetParent();
   2469      oldScriptLevel = sstyScriptLevel;
   2470    }
   2471    if (sstyScriptLevel) {
   2472      anyMathMLStyling = true;
   2473    }
   2474 
   2475    // Figure out what content is included in this flow.
   2476    nsIContent* content = f->GetContent();
   2477    const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer();
   2478    int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
   2479    int32_t contentEnd = mappedFlow->GetContentEnd();
   2480    int32_t contentLength = contentEnd - contentStart;
   2481    const nsAtom* language = f->StyleFont()->mLanguage;
   2482 
   2483    TextRunMappedFlow* newFlow = &userMappedFlows[i];
   2484    newFlow->mStartFrame = mappedFlow->mStartFrame;
   2485    newFlow->mDOMOffsetToBeforeTransformOffset =
   2486        skipChars.GetOriginalCharCount() -
   2487        mappedFlow->mStartFrame->GetContentOffset();
   2488    newFlow->mContentLength = contentLength;
   2489 
   2490    while (nextBreakBeforeFrame &&
   2491           nextBreakBeforeFrame->GetContent() == content) {
   2492      textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() +
   2493                                    newFlow->mDOMOffsetToBeforeTransformOffset);
   2494      nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
   2495    }
   2496 
   2497    nsTextFrameUtils::Flags analysisFlags;
   2498    if (characterDataBuffer.Is2b()) {
   2499      NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
   2500      char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
   2501      char16_t* bufEnd = nsTextFrameUtils::TransformText(
   2502          characterDataBuffer.Get2b() + contentStart, contentLength, bufStart,
   2503          compression, &mNextRunContextInfo, &skipChars, &analysisFlags,
   2504          language);
   2505      aTextBuffer = bufEnd;
   2506      currentTransformedTextOffset =
   2507          bufEnd - static_cast<const char16_t*>(textPtr);
   2508    } else {
   2509      if (mDoubleByteText) {
   2510        // Need to expand the text. First transform it into a temporary buffer,
   2511        // then expand.
   2512        AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
   2513        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
   2514        if (!bufStart) {
   2515          DestroyUserData(userDataToDestroy);
   2516          return nullptr;
   2517        }
   2518        uint8_t* end = nsTextFrameUtils::TransformText(
   2519            reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
   2520                contentStart,
   2521            contentLength, bufStart, compression, &mNextRunContextInfo,
   2522            &skipChars, &analysisFlags, language);
   2523        aTextBuffer =
   2524            ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
   2525                         tempBuf.Elements(), end - tempBuf.Elements());
   2526        currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) -
   2527                                       static_cast<const char16_t*>(textPtr);
   2528      } else {
   2529        uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
   2530        uint8_t* end = nsTextFrameUtils::TransformText(
   2531            reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
   2532                contentStart,
   2533            contentLength, bufStart, compression, &mNextRunContextInfo,
   2534            &skipChars, &analysisFlags, language);
   2535        aTextBuffer = end;
   2536        currentTransformedTextOffset =
   2537            end - static_cast<const uint8_t*>(textPtr);
   2538      }
   2539    }
   2540    flags2 |= analysisFlags;
   2541  }
   2542 
   2543  void* finalUserData;
   2544  if (userData == &dummyData) {
   2545    flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow;
   2546    userData = nullptr;
   2547    finalUserData = mMappedFlows[0].mStartFrame;
   2548  } else {
   2549    finalUserData = userData;
   2550  }
   2551 
   2552  uint32_t transformedLength = currentTransformedTextOffset;
   2553 
   2554  // Now build the textrun
   2555  nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
   2556  float fontInflation;
   2557  gfxFontGroup* fontGroup;
   2558  if (mWhichTextRun == nsTextFrame::eNotInflated) {
   2559    fontInflation = 1.0f;
   2560    fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
   2561  } else {
   2562    fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
   2563    fontGroup = GetInflatedFontGroupForFrame(firstFrame);
   2564  }
   2565  MOZ_ASSERT(fontGroup);
   2566 
   2567  if (flags2 & nsTextFrameUtils::Flags::HasTab) {
   2568    flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
   2569  }
   2570  if (flags2 & nsTextFrameUtils::Flags::HasShy) {
   2571    flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
   2572  }
   2573  if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) {
   2574    flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
   2575  }
   2576  if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
   2577    flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace;
   2578  }
   2579  if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
   2580    flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
   2581  }
   2582  // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
   2583  // frame's style is used, so we use a mixture of the first frame and
   2584  // last frame's style
   2585  flags |= nsLayoutUtils::GetTextRunFlagsForStyle(
   2586      lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle,
   2587      LetterSpacing(firstFrame, *textStyle));
   2588  // XXX this is a bit of a hack. For performance reasons, if we're favouring
   2589  // performance over quality, don't try to get accurate glyph extents.
   2590  if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
   2591    flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
   2592  }
   2593 
   2594  // Convert linebreak coordinates to transformed string offsets
   2595  NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
   2596               "Didn't find all the frames to break-before...");
   2597  gfxSkipCharsIterator iter(skipChars);
   2598  AutoTArray<uint32_t, 50> textBreakPointsAfterTransform;
   2599  for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
   2600    nsTextFrameUtils::AppendLineBreakOffset(
   2601        &textBreakPointsAfterTransform,
   2602        iter.ConvertOriginalToSkipped(textBreakPoints[i]));
   2603  }
   2604  if (mStartOfLine) {
   2605    nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
   2606                                            transformedLength);
   2607  }
   2608 
   2609  // Setup factory chain
   2610  bool needsToMaskPassword = NeedsToMaskPassword(firstFrame);
   2611  UniquePtr<nsTransformingTextRunFactory> transformingFactory;
   2612  if (anyTextTransformStyle || needsToMaskPassword) {
   2613    char16_t maskChar =
   2614        needsToMaskPassword ? 0 : textStyle->TextSecurityMaskChar();
   2615    transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>(
   2616        std::move(transformingFactory), false, maskChar);
   2617  }
   2618  if (anyMathMLStyling) {
   2619    transformingFactory = MakeUnique<MathMLTextRunFactory>(
   2620        std::move(transformingFactory), mathFlags, sstyScriptLevel,
   2621        fontInflation);
   2622  }
   2623  nsTArray<RefPtr<nsTransformedCharStyle>> styles;
   2624  if (transformingFactory) {
   2625    uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX;
   2626    if (needsToMaskPassword) {
   2627      unmaskStart = unmaskEnd = UINT32_MAX;
   2628      const TextEditor* const passwordEditor =
   2629          nsContentUtils::GetExtantTextEditorFromAnonymousNode(
   2630              firstFrame->GetContent());
   2631      if (passwordEditor && !passwordEditor->IsAllMasked()) {
   2632        unmaskStart = passwordEditor->UnmaskedStart();
   2633        unmaskEnd = passwordEditor->UnmaskedEnd();
   2634      }
   2635    }
   2636 
   2637    iter.SetOriginalOffset(0);
   2638    for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
   2639      MappedFlow* mappedFlow = &mMappedFlows[i];
   2640      nsTextFrame* f;
   2641      ComputedStyle* sc = nullptr;
   2642      RefPtr<nsTransformedCharStyle> defaultStyle;
   2643      RefPtr<nsTransformedCharStyle> unmaskStyle;
   2644      for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
   2645           f = f->GetNextContinuation()) {
   2646        uint32_t skippedOffset = iter.GetSkippedOffset();
   2647        // Text-combined frames have content-dependent transform, so we
   2648        // want to create new nsTransformedCharStyle for them anyway.
   2649        if (sc != f->Style() || sc->IsTextCombined()) {
   2650          sc = f->Style();
   2651          defaultStyle = new nsTransformedCharStyle(sc, f->PresContext());
   2652          if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
   2653            defaultStyle->mForceNonFullWidth = true;
   2654          }
   2655          if (needsToMaskPassword) {
   2656            defaultStyle->mMaskPassword = true;
   2657            if (unmaskStart != unmaskEnd) {
   2658              unmaskStyle = new nsTransformedCharStyle(sc, f->PresContext());
   2659              unmaskStyle->mForceNonFullWidth =
   2660                  defaultStyle->mForceNonFullWidth;
   2661            }
   2662          }
   2663        }
   2664        iter.AdvanceOriginal(f->GetContentLength());
   2665        uint32_t skippedEnd = iter.GetSkippedOffset();
   2666        if (unmaskStyle) {
   2667          uint32_t skippedUnmaskStart =
   2668              iter.ConvertOriginalToSkipped(unmaskStart);
   2669          uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd);
   2670          iter.SetSkippedOffset(skippedEnd);
   2671          for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart);
   2672               ++skippedOffset) {
   2673            styles.AppendElement(defaultStyle);
   2674          }
   2675          for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd);
   2676               ++skippedOffset) {
   2677            styles.AppendElement(unmaskStyle);
   2678          }
   2679          for (; skippedOffset < skippedEnd; ++skippedOffset) {
   2680            styles.AppendElement(defaultStyle);
   2681          }
   2682        } else {
   2683          for (; skippedOffset < skippedEnd; ++skippedOffset) {
   2684            styles.AppendElement(defaultStyle);
   2685          }
   2686        }
   2687      }
   2688    }
   2689    flags2 |= nsTextFrameUtils::Flags::IsTransformed;
   2690    NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
   2691                 "We didn't cover all the characters in the text run!");
   2692  }
   2693 
   2694  RefPtr<gfxTextRun> textRun;
   2695  gfxTextRunFactory::Parameters params = {
   2696      mDrawTarget,
   2697      finalUserData,
   2698      &skipChars,
   2699      textBreakPointsAfterTransform.Elements(),
   2700      uint32_t(textBreakPointsAfterTransform.Length()),
   2701      int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
   2702 
   2703  if (mDoubleByteText) {
   2704    const char16_t* text = static_cast<const char16_t*>(textPtr);
   2705    if (transformingFactory) {
   2706      textRun = transformingFactory->MakeTextRun(
   2707          text, transformedLength, &params, fontGroup, flags, flags2,
   2708          std::move(styles), true);
   2709    } else {
   2710      textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
   2711                                       flags2, mMissingFonts);
   2712    }
   2713  } else {
   2714    const uint8_t* text = static_cast<const uint8_t*>(textPtr);
   2715    flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
   2716    if (transformingFactory) {
   2717      textRun = transformingFactory->MakeTextRun(
   2718          text, transformedLength, &params, fontGroup, flags, flags2,
   2719          std::move(styles), true);
   2720    } else {
   2721      textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
   2722                                       flags2, mMissingFonts);
   2723    }
   2724  }
   2725  if (!textRun) {
   2726    DestroyUserData(userDataToDestroy);
   2727    return nullptr;
   2728  }
   2729 
   2730  // We have to set these up after we've created the textrun, because
   2731  // the breaks may be stored in the textrun during this very call.
   2732  // This is a bit annoying because it requires another loop over the frames
   2733  // making up the textrun, but I don't see a way to avoid this.
   2734  // We have to do this if line-breaking is required OR if a text-transform
   2735  // is in effect, because we depend on the line-breaker's scanner (via
   2736  // BreakSink::Finish) to finish building transformed textruns.
   2737  if (mDoLineBreaking || transformingFactory) {
   2738    SetupBreakSinksForTextRun(textRun.get(), textPtr);
   2739  }
   2740 
   2741  // Ownership of the factory has passed to the textrun
   2742  // TODO: bug 1285316: clean up ownership transfer from the factory to
   2743  // the textrun
   2744  (void)transformingFactory.release();
   2745 
   2746  if (anyTextEmphasis) {
   2747    SetupTextEmphasisForTextRun(textRun.get(), textPtr);
   2748  }
   2749 
   2750  if (mSkipIncompleteTextRuns) {
   2751    mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(
   2752        textPtr, transformedLength, mDoubleByteText);
   2753    // Since we're doing to destroy the user data now, avoid a dangling
   2754    // pointer. Strictly speaking we don't need to do this since it should
   2755    // not be used (since this textrun will not be used and will be
   2756    // itself deleted soon), but it's always better to not have dangling
   2757    // pointers around.
   2758    textRun->SetUserData(nullptr);
   2759    DestroyUserData(userDataToDestroy);
   2760    return nullptr;
   2761  }
   2762 
   2763  // Actually wipe out the textruns associated with the mapped frames and
   2764  // associate those frames with this text run.
   2765  AssignTextRun(textRun.get(), fontInflation);
   2766  return textRun.forget();
   2767 }
   2768 
   2769 // This is a cut-down version of BuildTextRunForFrames used to set up
   2770 // context for the line-breaker, when the textrun has already been created.
   2771 // So it does the same walk over the mMappedFlows, but doesn't actually
   2772 // build a new textrun.
   2773 bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) {
   2774  AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
   2775  uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
   2776  if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
   2777    return false;
   2778  }
   2779  void* textPtr = buffer.AppendElements(bufferSize, fallible);
   2780  if (!textPtr) {
   2781    return false;
   2782  }
   2783 
   2784  gfxSkipChars skipChars;
   2785  const nsAtom* language = mMappedFlows[0].mStartFrame->StyleFont()->mLanguage;
   2786 
   2787  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
   2788    MappedFlow* mappedFlow = &mMappedFlows[i];
   2789    nsTextFrame* f = mappedFlow->mStartFrame;
   2790 
   2791    const nsStyleText* textStyle = f->StyleText();
   2792    nsTextFrameUtils::CompressionMode compression =
   2793        GetCSSWhitespaceToCompressionMode(f, textStyle);
   2794 
   2795    // Figure out what content is included in this flow.
   2796    const CharacterDataBuffer& characterDataBuffer = f->CharacterDataBuffer();
   2797    int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
   2798    int32_t contentEnd = mappedFlow->GetContentEnd();
   2799    int32_t contentLength = contentEnd - contentStart;
   2800 
   2801    nsTextFrameUtils::Flags analysisFlags;
   2802    if (characterDataBuffer.Is2b()) {
   2803      NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
   2804      char16_t* bufStart = static_cast<char16_t*>(textPtr);
   2805      char16_t* bufEnd = nsTextFrameUtils::TransformText(
   2806          characterDataBuffer.Get2b() + contentStart, contentLength, bufStart,
   2807          compression, &mNextRunContextInfo, &skipChars, &analysisFlags,
   2808          language);
   2809      textPtr = bufEnd;
   2810    } else {
   2811      if (mDoubleByteText) {
   2812        // Need to expand the text. First transform it into a temporary buffer,
   2813        // then expand.
   2814        AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
   2815        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
   2816        if (!bufStart) {
   2817          return false;
   2818        }
   2819        uint8_t* end = nsTextFrameUtils::TransformText(
   2820            reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
   2821                contentStart,
   2822            contentLength, bufStart, compression, &mNextRunContextInfo,
   2823            &skipChars, &analysisFlags, language);
   2824        textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
   2825                               tempBuf.Elements(), end - tempBuf.Elements());
   2826      } else {
   2827        uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
   2828        uint8_t* end = nsTextFrameUtils::TransformText(
   2829            reinterpret_cast<const uint8_t*>(characterDataBuffer.Get1b()) +
   2830                contentStart,
   2831            contentLength, bufStart, compression, &mNextRunContextInfo,
   2832            &skipChars, &analysisFlags, language);
   2833        textPtr = end;
   2834      }
   2835    }
   2836  }
   2837 
   2838  // We have to set these up after we've created the textrun, because
   2839  // the breaks may be stored in the textrun during this very call.
   2840  // This is a bit annoying because it requires another loop over the frames
   2841  // making up the textrun, but I don't see a way to avoid this.
   2842  SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
   2843 
   2844  return true;
   2845 }
   2846 
   2847 static bool HasCompressedLeadingWhitespace(
   2848    nsTextFrame* aFrame, const nsStyleText* aStyleText,
   2849    int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) {
   2850  if (!aIterator.IsOriginalCharSkipped()) {
   2851    return false;
   2852  }
   2853 
   2854  gfxSkipCharsIterator iter = aIterator;
   2855  int32_t frameContentOffset = aFrame->GetContentOffset();
   2856  const CharacterDataBuffer& characterDataBuffer =
   2857      aFrame->CharacterDataBuffer();
   2858  while (frameContentOffset < aContentEndOffset &&
   2859         iter.IsOriginalCharSkipped()) {
   2860    if (IsTrimmableSpace(characterDataBuffer, frameContentOffset, aStyleText)) {
   2861      return true;
   2862    }
   2863    ++frameContentOffset;
   2864    iter.AdvanceOriginal(1);
   2865  }
   2866  return false;
   2867 }
   2868 
   2869 void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
   2870                                                     const void* aTextPtr) {
   2871  using mozilla::intl::LineBreakRule;
   2872  using mozilla::intl::WordBreakRule;
   2873 
   2874  // textruns have uniform language
   2875  const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont();
   2876  // We should only use a language for hyphenation if it was specified
   2877  // explicitly.
   2878  nsAtom* hyphenationLanguage =
   2879      styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
   2880  // We keep this pointed at the skip-chars data for the current mappedFlow.
   2881  // This lets us cheaply check whether the flow has compressed initial
   2882  // whitespace...
   2883  gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
   2884 
   2885  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
   2886    MappedFlow* mappedFlow = &mMappedFlows[i];
   2887    // The CSS word-break value may change within a word, so we reset it for
   2888    // each MappedFlow. The line-breaker will flush its text if the property
   2889    // actually changes.
   2890    const auto* styleText = mappedFlow->mStartFrame->StyleText();
   2891    auto wordBreak = styleText->EffectiveWordBreak();
   2892    switch (wordBreak) {
   2893      case StyleWordBreak::BreakAll:
   2894        mLineBreaker.SetWordBreak(WordBreakRule::BreakAll);
   2895        break;
   2896      case StyleWordBreak::KeepAll:
   2897        mLineBreaker.SetWordBreak(WordBreakRule::KeepAll);
   2898        break;
   2899      case StyleWordBreak::Normal:
   2900      default:
   2901        MOZ_ASSERT(wordBreak == StyleWordBreak::Normal);
   2902        mLineBreaker.SetWordBreak(WordBreakRule::Normal);
   2903        break;
   2904    }
   2905    switch (styleText->mLineBreak) {
   2906      case StyleLineBreak::Auto:
   2907        mLineBreaker.SetStrictness(LineBreakRule::Auto);
   2908        break;
   2909      case StyleLineBreak::Normal:
   2910        mLineBreaker.SetStrictness(LineBreakRule::Normal);
   2911        break;
   2912      case StyleLineBreak::Loose:
   2913        mLineBreaker.SetStrictness(LineBreakRule::Loose);
   2914        break;
   2915      case StyleLineBreak::Strict:
   2916        mLineBreaker.SetStrictness(LineBreakRule::Strict);
   2917        break;
   2918      case StyleLineBreak::Anywhere:
   2919        mLineBreaker.SetStrictness(LineBreakRule::Anywhere);
   2920        break;
   2921    }
   2922 
   2923    uint32_t offset = iter.GetSkippedOffset();
   2924    gfxSkipCharsIterator iterNext = iter;
   2925    iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
   2926                             mappedFlow->mStartFrame->GetContentOffset());
   2927 
   2928    UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
   2929        MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
   2930 
   2931    uint32_t length = iterNext.GetSkippedOffset() - offset;
   2932    uint32_t flags = 0;
   2933    nsIFrame* initialBreakController =
   2934        mappedFlow->mAncestorControllingInitialBreak;
   2935    if (!initialBreakController) {
   2936      initialBreakController = mLineContainer;
   2937    }
   2938    if (!initialBreakController->StyleText()->WhiteSpaceCanWrap(
   2939            initialBreakController)) {
   2940      flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
   2941    }
   2942    nsTextFrame* startFrame = mappedFlow->mStartFrame;
   2943    const nsStyleText* textStyle = startFrame->StyleText();
   2944    if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
   2945      flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
   2946    }
   2947    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) {
   2948      flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
   2949    }
   2950    if (textStyle->mTextTransform & StyleTextTransform::CAPITALIZE) {
   2951      flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
   2952    }
   2953    if (textStyle->mHyphens == StyleHyphens::Auto &&
   2954        textStyle->mLineBreak != StyleLineBreak::Anywhere) {
   2955      flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
   2956    }
   2957 
   2958    if (HasCompressedLeadingWhitespace(startFrame, textStyle,
   2959                                       mappedFlow->GetContentEnd(), iter)) {
   2960      mLineBreaker.AppendInvisibleWhitespace(flags);
   2961    }
   2962 
   2963    if (length > 0) {
   2964      BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
   2965      if (mDoubleByteText) {
   2966        const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
   2967        mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
   2968                                flags, sink);
   2969      } else {
   2970        const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
   2971        mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
   2972                                flags, sink);
   2973      }
   2974    }
   2975 
   2976    iter = iterNext;
   2977  }
   2978 }
   2979 
   2980 static bool MayCharacterHaveEmphasisMark(uint32_t aCh) {
   2981  // Punctuation characters that *can* take emphasis marks (exceptions to the
   2982  // rule that characters with GeneralCategory=P* do not take emphasis), as per
   2983  // https://drafts.csswg.org/css-text-decor/#text-emphasis-style-property
   2984  // There are no non-BMP codepoints in the punctuation exceptions, so we can
   2985  // just use a 16-bit string to list & check them.
   2986  constexpr nsLiteralString kPunctuationAcceptsEmphasis =
   2987      u"\x0023"  // #  NUMBER SIGN
   2988      u"\x0025"  // %  PERCENT SIGN
   2989      u"\x0026"  // &  AMPERSAND
   2990      u"\x0040"  // @  COMMERCIAL AT
   2991      u"\x00A7"  // §  SECTION SIGN
   2992      u"\x00B6"  // ¶  PILCROW SIGN
   2993      u"\x0609"  // ؉  ARABIC-INDIC PER MILLE SIGN
   2994      u"\x060A"  // ؊  ARABIC-INDIC PER TEN THOUSAND SIGN
   2995      u"\x066A"  // ٪  ARABIC PERCENT SIGN
   2996      u"\x2030"  // ‰  PER MILLE SIGN
   2997      u"\x2031"  // ‱  PER TEN THOUSAND SIGN
   2998      u"\x204A"  // ⁊  TIRONIAN SIGN ET
   2999      u"\x204B"  // ⁋  REVERSED PILCROW SIGN
   3000      u"\x2053"  // ⁓  SWUNG DASH
   3001      u"\x303D"  // 〽️  PART ALTERNATION MARK
   3002      // Characters that are NFKD-equivalent to the above, extracted from
   3003      // UnicodeData.txt.
   3004      u"\xFE5F"      // SMALL NUMBER SIGN;Po;0;ET;<small> 0023;;;;N;;;;;
   3005      u"\xFE60"      // SMALL AMPERSAND;Po;0;ON;<small> 0026;;;;N;;;;;
   3006      u"\xFE6A"      // SMALL PERCENT SIGN;Po;0;ET;<small> 0025;;;;N;;;;;
   3007      u"\xFE6B"      // SMALL COMMERCIAL AT;Po;0;ON;<small> 0040;;;;N;;;;;
   3008      u"\xFF03"      // FULLWIDTH NUMBER SIGN;Po;0;ET;<wide> 0023;;;;N;;;;;
   3009      u"\xFF05"      // FULLWIDTH PERCENT SIGN;Po;0;ET;<wide> 0025;;;;N;;;;;
   3010      u"\xFF06"      // FULLWIDTH AMPERSAND;Po;0;ON;<wide> 0026;;;;N;;;;;
   3011      u"\xFF20"_ns;  // FULLWIDTH COMMERCIAL AT;Po;0;ON;<wide> 0040;;;;N;;;;;
   3012 
   3013  switch (unicode::GetGenCategory(aCh)) {
   3014    case nsUGenCategory::kSeparator:  // whitespace, line- & para-separators
   3015      return false;
   3016    case nsUGenCategory::kOther:  // control categories
   3017      return false;
   3018    case nsUGenCategory::kPunctuation:
   3019      return aCh <= 0xffff &&
   3020             kPunctuationAcceptsEmphasis.Contains(char16_t(aCh));
   3021    default:
   3022      return true;
   3023  }
   3024 }
   3025 
   3026 void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
   3027                                                       const void* aTextPtr) {
   3028  if (!mDoubleByteText) {
   3029    auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
   3030    for (auto i : IntegerRange(aTextRun->GetLength())) {
   3031      if (!MayCharacterHaveEmphasisMark(text[i])) {
   3032        aTextRun->SetNoEmphasisMark(i);
   3033      }
   3034    }
   3035  } else {
   3036    auto text = reinterpret_cast<const char16_t*>(aTextPtr);
   3037    auto length = aTextRun->GetLength();
   3038    for (size_t i = 0; i < length; ++i) {
   3039      if (i + 1 < length && NS_IS_SURROGATE_PAIR(text[i], text[i + 1])) {
   3040        uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]);
   3041        if (!MayCharacterHaveEmphasisMark(ch)) {
   3042          aTextRun->SetNoEmphasisMark(i);
   3043          aTextRun->SetNoEmphasisMark(i + 1);
   3044        }
   3045        ++i;
   3046      } else {
   3047        if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
   3048          aTextRun->SetNoEmphasisMark(i);
   3049        }
   3050      }
   3051    }
   3052  }
   3053 }
   3054 
   3055 // Find the flow corresponding to aContent in aUserData
   3056 static inline TextRunMappedFlow* FindFlowForContent(
   3057    TextRunUserData* aUserData, nsIContent* aContent,
   3058    TextRunMappedFlow* userMappedFlows) {
   3059  // Find the flow that contains us
   3060  int32_t i = aUserData->mLastFlowIndex;
   3061  int32_t delta = 1;
   3062  int32_t sign = 1;
   3063  // Search starting at the current position and examine close-by
   3064  // positions first, moving further and further away as we go.
   3065  while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
   3066    TextRunMappedFlow* flow = &userMappedFlows[i];
   3067    if (flow->mStartFrame->GetContent() == aContent) {
   3068      return flow;
   3069    }
   3070 
   3071    i += delta;
   3072    sign = -sign;
   3073    delta = -delta + sign;
   3074  }
   3075 
   3076  // We ran into an array edge.  Add |delta| to |i| once more to get
   3077  // back to the side where we still need to search, then step in
   3078  // the |sign| direction.
   3079  i += delta;
   3080  if (sign > 0) {
   3081    for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
   3082      TextRunMappedFlow* flow = &userMappedFlows[i];
   3083      if (flow->mStartFrame->GetContent() == aContent) {
   3084        return flow;
   3085      }
   3086    }
   3087  } else {
   3088    for (; i >= 0; --i) {
   3089      TextRunMappedFlow* flow = &userMappedFlows[i];
   3090      if (flow->mStartFrame->GetContent() == aContent) {
   3091        return flow;
   3092      }
   3093    }
   3094  }
   3095 
   3096  return nullptr;
   3097 }
   3098 
   3099 void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun,
   3100                                         float aInflation) {
   3101  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
   3102    MappedFlow* mappedFlow = &mMappedFlows[i];
   3103    nsTextFrame* startFrame = mappedFlow->mStartFrame;
   3104    nsTextFrame* endFrame = mappedFlow->mEndFrame;
   3105    nsTextFrame* f;
   3106    for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
   3107 #ifdef DEBUG_roc
   3108      if (f->GetTextRun(mWhichTextRun)) {
   3109        gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
   3110        if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   3111          if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
   3112            NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
   3113          }
   3114        } else {
   3115          auto userData =
   3116              static_cast<TextRunUserData*>(aTextRun->GetUserData());
   3117          TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
   3118          if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
   3119              userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
   3120                  mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
   3121            NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
   3122          }
   3123        }
   3124      }
   3125 #endif
   3126 
   3127      gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
   3128      if (oldTextRun) {
   3129        nsTextFrame* firstFrame = nullptr;
   3130        uint32_t startOffset = 0;
   3131        if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   3132          firstFrame = GetFrameForSimpleFlow(oldTextRun);
   3133        } else {
   3134          auto userData =
   3135              static_cast<TextRunUserData*>(oldTextRun->GetUserData());
   3136          TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
   3137          firstFrame = userMappedFlows[0].mStartFrame;
   3138          if (MOZ_UNLIKELY(f != firstFrame)) {
   3139            TextRunMappedFlow* flow =
   3140                FindFlowForContent(userData, f->GetContent(), userMappedFlows);
   3141            if (flow) {
   3142              startOffset = flow->mDOMOffsetToBeforeTransformOffset;
   3143            } else {
   3144              NS_ERROR("Can't find flow containing frame 'f'");
   3145            }
   3146          }
   3147        }
   3148 
   3149        // Optimization: if |f| is the first frame in the flow then there are no
   3150        // prev-continuations that use |oldTextRun|.
   3151        nsTextFrame* clearFrom = nullptr;
   3152        if (MOZ_UNLIKELY(f != firstFrame)) {
   3153          // If all the frames in the mapped flow starting at |f| (inclusive)
   3154          // are empty then we let the prev-continuations keep the old text run.
   3155          gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset,
   3156                                    f->GetContentOffset());
   3157          uint32_t textRunOffset =
   3158              iter.ConvertOriginalToSkipped(f->GetContentOffset());
   3159          clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
   3160        }
   3161        f->ClearTextRun(clearFrom, mWhichTextRun);
   3162 
   3163 #ifdef DEBUG
   3164        if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
   3165          // oldTextRun was destroyed - assert that we don't reference it.
   3166          for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
   3167            NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
   3168                         "destroyed text run is still in use");
   3169          }
   3170        }
   3171 #endif
   3172      }
   3173      f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
   3174    }
   3175    // Set this bit now; we can't set it any earlier because
   3176    // f->ClearTextRun() might clear it out.
   3177    nsFrameState whichTextRunState =
   3178        startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
   3179            ? TEXT_IN_TEXTRUN_USER_DATA
   3180            : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
   3181    startFrame->AddStateBits(whichTextRunState);
   3182  }
   3183 }
   3184 
   3185 NS_QUERYFRAME_HEAD(nsTextFrame)
   3186  NS_QUERYFRAME_ENTRY(nsTextFrame)
   3187 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
   3188 
   3189 gfxSkipCharsIterator nsTextFrame::EnsureTextRun(
   3190    TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget,
   3191    nsIFrame* aLineContainer, const nsLineList::iterator* aLine,
   3192    uint32_t* aFlowEndInTextRun) {
   3193  gfxTextRun* textRun = GetTextRun(aWhichTextRun);
   3194  if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
   3195    RefPtr<DrawTarget> refDT = aRefDrawTarget;
   3196    if (!refDT) {
   3197      refDT = CreateReferenceDrawTarget(this);
   3198    }
   3199    if (refDT) {
   3200      BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
   3201    }
   3202    textRun = GetTextRun(aWhichTextRun);
   3203    if (!textRun) {
   3204      // A text run was not constructed for this frame. This is bad. The caller
   3205      // will check mTextRun.
   3206      return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(),
   3207                                  0);
   3208    }
   3209    TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
   3210    if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
   3211      RemoveProperty(TabWidthProperty());
   3212    }
   3213  }
   3214 
   3215  if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   3216    if (aFlowEndInTextRun) {
   3217      *aFlowEndInTextRun = textRun->GetLength();
   3218    }
   3219    return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
   3220  }
   3221 
   3222  auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
   3223  TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
   3224  TextRunMappedFlow* flow =
   3225      FindFlowForContent(userData, mContent, userMappedFlows);
   3226  if (flow) {
   3227    // Since textruns can only contain one flow for a given content element,
   3228    // this must be our flow.
   3229    uint32_t flowIndex = flow - userMappedFlows;
   3230    userData->mLastFlowIndex = flowIndex;
   3231    gfxSkipCharsIterator iter(textRun->GetSkipChars(),
   3232                              flow->mDOMOffsetToBeforeTransformOffset,
   3233                              mContentOffset);
   3234    if (aFlowEndInTextRun) {
   3235      if (flowIndex + 1 < userData->mMappedFlowCount) {
   3236        gfxSkipCharsIterator end(textRun->GetSkipChars());
   3237        *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
   3238            flow[1].mStartFrame->GetContentOffset() +
   3239            flow[1].mDOMOffsetToBeforeTransformOffset);
   3240      } else {
   3241        *aFlowEndInTextRun = textRun->GetLength();
   3242      }
   3243    }
   3244    return iter;
   3245  }
   3246 
   3247  NS_ERROR("Can't find flow containing this frame???");
   3248  return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
   3249 }
   3250 
   3251 static uint32_t GetEndOfTrimmedText(const CharacterDataBuffer& aBuffer,
   3252                                    const nsStyleText* aStyleText,
   3253                                    uint32_t aStart, uint32_t aEnd,
   3254                                    gfxSkipCharsIterator* aIterator,
   3255                                    bool aAllowHangingWS = false) {
   3256  aIterator->SetSkippedOffset(aEnd);
   3257  while (aIterator->GetSkippedOffset() > aStart) {
   3258    aIterator->AdvanceSkipped(-1);
   3259    if (!IsTrimmableSpace(aBuffer, aIterator->GetOriginalOffset(), aStyleText,
   3260                          aAllowHangingWS)) {
   3261      return aIterator->GetSkippedOffset() + 1;
   3262    }
   3263  }
   3264  return aStart;
   3265 }
   3266 
   3267 nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets(
   3268    const class CharacterDataBuffer& aBuffer, TrimmedOffsetFlags aFlags) const {
   3269  NS_ASSERTION(mTextRun, "Need textrun here");
   3270  if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) {
   3271    // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
   3272    // to be set correctly.  If our parent wasn't reflowed due to the frame
   3273    // tree being too deep then the return value doesn't matter.
   3274    NS_ASSERTION(
   3275        !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
   3276            GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
   3277        "Can only call this on frames that have been reflowed");
   3278    NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW),
   3279                 "Can only call this on frames that are not being reflowed");
   3280  }
   3281 
   3282  TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()};
   3283  const nsStyleText* textStyle = StyleText();
   3284  // Note that pre-line newlines should still allow us to trim spaces
   3285  // for display
   3286  if (textStyle->WhiteSpaceIsSignificant()) {
   3287    return offsets;
   3288  }
   3289 
   3290  if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) &&
   3291      ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
   3292       HasAnyStateBits(TEXT_START_OF_LINE))) {
   3293    int32_t whitespaceCount = GetTrimmableWhitespaceCount(
   3294        aBuffer, offsets.mStart, offsets.mLength, 1);
   3295    offsets.mStart += whitespaceCount;
   3296    offsets.mLength -= whitespaceCount;
   3297  }
   3298 
   3299  if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) &&
   3300      ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
   3301       HasAnyStateBits(TEXT_END_OF_LINE))) {
   3302    // This treats a trailing 'pre-line' newline as trimmable. That's fine,
   3303    // it's actually what we want since we want whitespace before it to
   3304    // be trimmed.
   3305    int32_t whitespaceCount = GetTrimmableWhitespaceCount(
   3306        aBuffer, offsets.GetEnd() - 1, offsets.mLength, -1);
   3307    offsets.mLength -= whitespaceCount;
   3308  }
   3309  return offsets;
   3310 }
   3311 
   3312 static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
   3313                                   const CharacterDataBuffer& aBuffer,
   3314                                   int32_t aPos, bool aLangIsCJ) {
   3315  NS_ASSERTION(aPos >= 0, "negative position?!");
   3316 
   3317  StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
   3318  if (justifyStyle == StyleTextJustify::None) {
   3319    return false;
   3320  }
   3321 
   3322  const char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aPos));
   3323  if (ch == '\n' || ch == '\t' || ch == '\r') {
   3324    return !aTextStyle->WhiteSpaceIsSignificant();
   3325  }
   3326  if (ch == ' ' || ch == CH_NBSP) {
   3327    // Don't justify spaces that are combined with diacriticals
   3328    if (!aBuffer.Is2b()) {
   3329      return true;
   3330    }
   3331    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
   3332        aBuffer.Get2b() + aPos + 1, aBuffer.GetLength() - (aPos + 1));
   3333  }
   3334 
   3335  if (justifyStyle == StyleTextJustify::InterCharacter) {
   3336    char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos));
   3337    if (intl::UnicodeProperties::IsCursiveScript(u)) {
   3338      return false;
   3339    }
   3340    return true;
   3341  } else if (justifyStyle == StyleTextJustify::InterWord) {
   3342    return false;
   3343  }
   3344 
   3345  // text-justify: auto
   3346  if (ch < 0x2150u) {
   3347    return false;
   3348  }
   3349  if (aLangIsCJ) {
   3350    if (  // Number Forms, Arrows, Mathematical Operators
   3351        (0x2150u <= ch && ch <= 0x22ffu) ||
   3352        // Enclosed Alphanumerics
   3353        (0x2460u <= ch && ch <= 0x24ffu) ||
   3354        // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
   3355        (0x2580u <= ch && ch <= 0x27bfu) ||
   3356        // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
   3357        // Miscellaneous Mathematical Symbols-B,
   3358        // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows
   3359        (0x27f0u <= ch && ch <= 0x2bffu) ||
   3360        // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic
   3361        // Description Characters, CJK Symbols and Punctuation, Hiragana,
   3362        // Katakana, Bopomofo
   3363        (0x2e80u <= ch && ch <= 0x312fu) ||
   3364        // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
   3365        // Enclosed CJK Letters and Months, CJK Compatibility,
   3366        // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
   3367        // CJK Unified Ideographs, Yi Syllables, Yi Radicals
   3368        (0x3190u <= ch && ch <= 0xabffu) ||
   3369        // CJK Compatibility Ideographs
   3370        (0xf900u <= ch && ch <= 0xfaffu) ||
   3371        // Halfwidth and Fullwidth Forms (a part)
   3372        (0xff5eu <= ch && ch <= 0xff9fu)) {
   3373      return true;
   3374    }
   3375    if (NS_IS_HIGH_SURROGATE(ch)) {
   3376      if (char32_t u = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aPos))) {
   3377        // CJK Unified Ideographs Extension B,
   3378        // CJK Unified Ideographs Extension C,
   3379        // CJK Unified Ideographs Extension D,
   3380        // CJK Compatibility Ideographs Supplement
   3381        if (0x20000u <= u && u <= 0x2ffffu) {
   3382          return true;
   3383        }
   3384      }
   3385    }
   3386  }
   3387  return false;
   3388 }
   3389 
   3390 void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) {
   3391  aMetrics.ClearSize();
   3392  aMetrics.SetBlockStartAscent(0);
   3393  mAscent = 0;
   3394 
   3395  AddStateBits(TEXT_NO_RENDERED_GLYPHS);
   3396 }
   3397 
   3398 static int32_t FindChar(const CharacterDataBuffer& characterDataBuffer,
   3399                        int32_t aOffset, int32_t aLength, char16_t ch) {
   3400  int32_t i = 0;
   3401  if (characterDataBuffer.Is2b()) {
   3402    const char16_t* str = characterDataBuffer.Get2b() + aOffset;
   3403    for (; i < aLength; ++i) {
   3404      if (*str == ch) {
   3405        return i + aOffset;
   3406      }
   3407      ++str;
   3408    }
   3409  } else {
   3410    if (uint16_t(ch) <= 0xFF) {
   3411      const char* str = characterDataBuffer.Get1b() + aOffset;
   3412      const void* p = memchr(str, ch, aLength);
   3413      if (p) {
   3414        return (static_cast<const char*>(p) - str) + aOffset;
   3415      }
   3416    }
   3417  }
   3418  return -1;
   3419 }
   3420 
   3421 static bool IsChineseOrJapanese(const nsTextFrame* aFrame) {
   3422  if (aFrame->ShouldSuppressLineBreak()) {
   3423    // Always treat ruby as CJ language so that those characters can
   3424    // be expanded properly even when surrounded by other language.
   3425    return true;
   3426  }
   3427 
   3428  nsAtom* language = aFrame->StyleFont()->mLanguage;
   3429  if (!language) {
   3430    return false;
   3431  }
   3432  return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
   3433         nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
   3434 }
   3435 
   3436 #ifdef DEBUG
   3437 static bool IsInBounds(const gfxSkipCharsIterator& aStart,
   3438                       int32_t aContentLength, gfxTextRun::Range aRange) {
   3439  if (aStart.GetSkippedOffset() > aRange.start) {
   3440    return false;
   3441  }
   3442  if (aContentLength == INT32_MAX) {
   3443    return true;
   3444  }
   3445  gfxSkipCharsIterator iter(aStart);
   3446  iter.AdvanceOriginal(aContentLength);
   3447  return iter.GetSkippedOffset() >= aRange.end;
   3448 }
   3449 #endif
   3450 
   3451 nsTextFrame::PropertyProvider::PropertyProvider(
   3452    gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
   3453    const class CharacterDataBuffer& aBuffer, nsTextFrame* aFrame,
   3454    const gfxSkipCharsIterator& aStart, int32_t aLength,
   3455    nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs,
   3456    nsTextFrame::TextRunType aWhichTextRun, bool aAtStartOfLine)
   3457    : mTextRun(aTextRun),
   3458      mFontGroup(nullptr),
   3459      mTextStyle(aTextStyle),
   3460      mCharacterDataBuffer(aBuffer),
   3461      mLineContainer(aLineContainer),
   3462      mFrame(aFrame),
   3463      mStart(aStart),
   3464      mTempIterator(aStart),
   3465      mTabWidths(nullptr),
   3466      mTabWidthsAnalyzedLimit(0),
   3467      mLength(aLength),
   3468      mWordSpacing(WordSpacing(aFrame, *aTextStyle)),
   3469      mLetterSpacing(LetterSpacing(aFrame, *aTextStyle)),
   3470      mMinTabAdvance(-1.0),
   3471      mHyphenWidth(-1),
   3472      mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
   3473      mJustificationArrayStart(0),
   3474      mReflowing(true),
   3475      mWhichTextRun(aWhichTextRun) {
   3476  NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
   3477  if (aAtStartOfLine) {
   3478    mStartOfLineOffset = mStart.GetSkippedOffset();
   3479  }
   3480  InitTextAutospace();
   3481 }
   3482 
   3483 nsTextFrame::PropertyProvider::PropertyProvider(
   3484    nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
   3485    nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics)
   3486    : mTextRun(aFrame->GetTextRun(aWhichTextRun)),
   3487      mFontGroup(nullptr),
   3488      mFontMetrics(aFontMetrics),
   3489      mTextStyle(aFrame->StyleText()),
   3490      mCharacterDataBuffer(aFrame->CharacterDataBuffer()),
   3491      mLineContainer(nullptr),
   3492      mFrame(aFrame),
   3493      mStart(aStart),
   3494      mTempIterator(aStart),
   3495      mTabWidths(nullptr),
   3496      mTabWidthsAnalyzedLimit(0),
   3497      mLength(aFrame->GetContentLength()),
   3498      mWordSpacing(WordSpacing(aFrame, *mTextStyle)),
   3499      mLetterSpacing(LetterSpacing(aFrame, *mTextStyle)),
   3500      mMinTabAdvance(-1.0),
   3501      mHyphenWidth(-1),
   3502      mOffsetFromBlockOriginForTabs(0),
   3503      mJustificationArrayStart(0),
   3504      mReflowing(false),
   3505      mWhichTextRun(aWhichTextRun) {
   3506  NS_ASSERTION(mTextRun, "Textrun not initialized!");
   3507  InitTextAutospace();
   3508 }
   3509 
   3510 gfx::ShapedTextFlags nsTextFrame::PropertyProvider::GetShapedTextFlags() const {
   3511  return nsLayoutUtils::GetTextRunOrientFlagsForStyle(mFrame->Style());
   3512 }
   3513 
   3514 already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget()
   3515    const {
   3516  return CreateReferenceDrawTarget(GetFrame());
   3517 }
   3518 
   3519 gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const {
   3520  if (mMinTabAdvance < 0.0) {
   3521    mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
   3522  }
   3523  return mMinTabAdvance;
   3524 }
   3525 
   3526 /**
   3527 * Finds the offset of the first character of the cluster containing aPos
   3528 */
   3529 static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart,
   3530                             gfxSkipCharsIterator* aPos) {
   3531  while (aPos->GetOriginalOffset() > aOriginalStart) {
   3532    if (aPos->IsOriginalCharSkipped() ||
   3533        aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
   3534      break;
   3535    }
   3536    aPos->AdvanceOriginal(-1);
   3537  }
   3538 }
   3539 
   3540 /**
   3541 * Finds the offset of the last character of the cluster containing aPos.
   3542 * If aAllowSplitLigature is false, we also check for a ligature-group
   3543 * start.
   3544 */
   3545 static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd,
   3546                           gfxSkipCharsIterator* aPos,
   3547                           bool aAllowSplitLigature = true) {
   3548  MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,
   3549             "character outside string");
   3550 
   3551  aPos->AdvanceOriginal(1);
   3552  while (aPos->GetOriginalOffset() < aOriginalEnd) {
   3553    if (aPos->IsOriginalCharSkipped() ||
   3554        (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
   3555         (aAllowSplitLigature ||
   3556          aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
   3557      break;
   3558    }
   3559    aPos->AdvanceOriginal(1);
   3560  }
   3561  aPos->AdvanceOriginal(-1);
   3562 }
   3563 
   3564 // Get the line number of aFrame in the lines referenced by aLineIter, if
   3565 // known (returning -1 if we don't find it).
   3566 static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) {
   3567  if (!aLineIter) {
   3568    return -1;
   3569  }
   3570  // If we don't find the frame directly, but its parent is an inline or other
   3571  // "line participant" (e.g. nsFirstLineFrame), we want the line that the
   3572  // inline ancestor is on.
   3573  do {
   3574    int32_t n = aLineIter->FindLineContaining(aFrame);
   3575    if (n >= 0) {
   3576      return n;
   3577    }
   3578    aFrame = aFrame->GetParent();
   3579  } while (aFrame && aFrame->IsLineParticipant());
   3580  return -1;
   3581 }
   3582 
   3583 // Get the position of the first preserved newline in aFrame, if any,
   3584 // returning -1 if none.
   3585 static int32_t FindFirstNewlinePosition(const nsTextFrame* aFrame) {
   3586  MOZ_ASSERT(aFrame->StyleText()->NewlineIsSignificantStyle(),
   3587             "how did the HasNewline flag get set?");
   3588  const auto& characterDataBuffer = aFrame->CharacterDataBuffer();
   3589  for (auto i = aFrame->GetContentOffset(); i < aFrame->GetContentEnd(); ++i) {
   3590    if (characterDataBuffer.CharAt(i) == '\n') {
   3591      return i;
   3592    }
   3593  }
   3594  return -1;
   3595 }
   3596 
   3597 // Get the position of the last preserved tab in aFrame that is before the
   3598 // preserved newline at aNewlinePos.
   3599 // Passing -1 for aNewlinePos means there is no preserved newline, so we look
   3600 // for the last preserved tab in the whole content.
   3601 // Returns -1 if no such preserved tab is present.
   3602 static int32_t FindLastTabPositionBeforeNewline(const nsTextFrame* aFrame,
   3603                                                int32_t aNewlinePos) {
   3604  // We only call this if white-space is not being collapsed.
   3605  MOZ_ASSERT(aFrame->StyleText()->WhiteSpaceIsSignificant(),
   3606             "how did the HasTab flag get set?");
   3607  const auto& characterDataBuffer = aFrame->CharacterDataBuffer();
   3608  // If a non-negative newline position was given, we only need to search the
   3609  // text before that offset.
   3610  for (auto i = aNewlinePos < 0 ? aFrame->GetContentEnd() : aNewlinePos;
   3611       i > aFrame->GetContentOffset(); --i) {
   3612    if (characterDataBuffer.CharAt(i - 1) == '\t') {
   3613      return i;
   3614    }
   3615  }
   3616  return -1;
   3617 }
   3618 
   3619 // Look for preserved tab or newline in the given frame or its following
   3620 // siblings on the same line, to determine whether justification should be
   3621 // suppressed in order to avoid disrupting tab-stop positions.
   3622 // Returns the first such preserved whitespace char, or 0 if none found.
   3623 static char NextPreservedWhiteSpaceOnLine(nsIFrame* aSibling,
   3624                                          nsILineIterator* aLineIter,
   3625                                          int32_t aLineNum) {
   3626  while (aSibling) {
   3627    // If we find a <br>, treat it like a newline.
   3628    if (aSibling->IsBrFrame()) {
   3629      return '\n';
   3630    }
   3631    // If we've moved on to a later line, stop searching.
   3632    if (GetFrameLineNum(aSibling, aLineIter) > aLineNum) {
   3633      return 0;
   3634    }
   3635    // If we encounter an inline frame, recurse into it.
   3636    if (aSibling->IsInlineFrame()) {
   3637      auto* child = aSibling->PrincipalChildList().FirstChild();
   3638      char result = NextPreservedWhiteSpaceOnLine(child, aLineIter, aLineNum);
   3639      if (result) {
   3640        return result;
   3641      }
   3642    }
   3643    // If we have a text frame, and whitespace is not collapsed, we need to
   3644    // check its contents.
   3645    if (aSibling->IsTextFrame()) {
   3646      const auto* textStyle = aSibling->StyleText();
   3647      if (textStyle->WhiteSpaceOrNewlineIsSignificant()) {
   3648        const auto* textFrame = static_cast<nsTextFrame*>(aSibling);
   3649        const auto& characterDataBuffer = textFrame->CharacterDataBuffer();
   3650        for (auto i = textFrame->GetContentOffset();
   3651             i < textFrame->GetContentEnd(); ++i) {
   3652          const char16_t ch = characterDataBuffer.CharAt(i);
   3653          if (ch == '\n' && textStyle->NewlineIsSignificantStyle()) {
   3654            return '\n';
   3655          }
   3656          if (ch == '\t' && textStyle->WhiteSpaceIsSignificant()) {
   3657            return '\t';
   3658          }
   3659        }
   3660      }
   3661    }
   3662    aSibling = aSibling->GetNextSibling();
   3663  }
   3664  return 0;
   3665 }
   3666 
   3667 static bool HasPreservedTabInFollowingSiblingOnLine(nsTextFrame* aFrame) {
   3668  bool foundTab = false;
   3669 
   3670  nsIFrame* lineContainer = FindLineContainer(aFrame);
   3671  nsILineIterator* iter = lineContainer->GetLineIterator();
   3672  int32_t line = GetFrameLineNum(aFrame, iter);
   3673  char ws = NextPreservedWhiteSpaceOnLine(aFrame->GetNextSibling(), iter, line);
   3674  if (ws == '\t') {
   3675    foundTab = true;
   3676  } else if (!ws) {
   3677    // Didn't find a preserved tab or newline in our siblings; if our parent
   3678    // (and its parent, etc) is an inline, we need to look at their following
   3679    // siblings, too, as long as they're on the same line.
   3680    const nsIFrame* maybeInline = aFrame->GetParent();
   3681    while (maybeInline && maybeInline->IsInlineFrame()) {
   3682      ws = NextPreservedWhiteSpaceOnLine(maybeInline->GetNextSibling(), iter,
   3683                                         line);
   3684      if (ws == '\t') {
   3685        foundTab = true;
   3686        break;
   3687      }
   3688      if (ws == '\n') {
   3689        break;
   3690      }
   3691      maybeInline = maybeInline->GetParent();
   3692    }
   3693  }
   3694 
   3695  // We called lineContainer->GetLineIterator() above, but we mustn't
   3696  // allow a block frame to retain this iterator if we're currently in
   3697  // reflow, as it will become invalid as the line list is reflowed.
   3698  if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) &&
   3699      lineContainer->IsBlockFrameOrSubclass()) {
   3700    static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator();
   3701  }
   3702 
   3703  return foundTab;
   3704 }
   3705 
   3706 JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification(
   3707    Range aRange, nsTArray<JustificationAssignment>* aAssignments) {
   3708  JustificationInfo info;
   3709 
   3710  // Horizontal-in-vertical frame is orthogonal to the line, so it
   3711  // doesn't actually include any justification opportunity inside.
   3712  // The spec says such frame should be treated as a U+FFFC. Since we
   3713  // do not insert justification opportunities on the sides of that
   3714  // character, the sides of this frame are not justifiable either.
   3715  if (mFrame->Style()->IsTextCombined()) {
   3716    return info;
   3717  }
   3718 
   3719  int32_t lastTab = -1;
   3720  if (StaticPrefs::layout_css_text_align_justify_only_after_last_tab()) {
   3721    // If there is a preserved tab on the line, we don't apply justification
   3722    // until we're past its position.
   3723    if (mTextStyle->WhiteSpaceIsSignificant()) {
   3724      // If there is a preserved newline within the text, we don't need to look
   3725      // beyond this frame, as following frames will not be on the same line.
   3726      int32_t newlinePos =
   3727          (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasNewline)
   3728              ? FindFirstNewlinePosition(mFrame)
   3729              : -1;
   3730      if (newlinePos < 0) {
   3731        // There's no preserved newline within this frame; if there's a tab
   3732        // in a later sibling frame on the same line, we won't apply any
   3733        // justification to this one.
   3734        if (HasPreservedTabInFollowingSiblingOnLine(mFrame)) {
   3735          return info;
   3736        }
   3737      }
   3738 
   3739      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) {
   3740        // Find last tab character in the content; we won't justify anything
   3741        // before that position, so that tab alignment remains correct.
   3742        lastTab = FindLastTabPositionBeforeNewline(mFrame, newlinePos);
   3743      }
   3744    }
   3745  }
   3746 
   3747  bool isCJ = IsChineseOrJapanese(mFrame);
   3748  nsSkipCharsRunIterator run(
   3749      mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
   3750  run.SetOriginalOffset(aRange.start);
   3751  mJustificationArrayStart = run.GetSkippedOffset();
   3752 
   3753  nsTArray<JustificationAssignment> assignments;
   3754  assignments.SetCapacity(aRange.Length());
   3755  while (run.NextRun()) {
   3756    uint32_t originalOffset = run.GetOriginalOffset();
   3757    uint32_t skippedOffset = run.GetSkippedOffset();
   3758    uint32_t length = run.GetRunLength();
   3759    assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
   3760 
   3761    gfxSkipCharsIterator iter = run.GetPos();
   3762    for (uint32_t i = 0; i < length; ++i) {
   3763      uint32_t offset = originalOffset + i;
   3764      if (!IsJustifiableCharacter(mTextStyle, mCharacterDataBuffer, offset,
   3765                                  isCJ) ||
   3766          (lastTab >= 0 && offset <= uint32_t(lastTab))) {
   3767        continue;
   3768      }
   3769 
   3770      iter.SetOriginalOffset(offset);
   3771 
   3772      FindClusterStart(mTextRun, originalOffset, &iter);
   3773      uint32_t firstCharOffset = iter.GetSkippedOffset();
   3774      uint32_t firstChar = firstCharOffset > mJustificationArrayStart
   3775                               ? firstCharOffset - mJustificationArrayStart
   3776                               : 0;
   3777      if (!firstChar) {
   3778        info.mIsStartJustifiable = true;
   3779      } else {
   3780        auto& assign = assignments[firstChar];
   3781        auto& prevAssign = assignments[firstChar - 1];
   3782        if (prevAssign.mGapsAtEnd) {
   3783          prevAssign.mGapsAtEnd = 1;
   3784          assign.mGapsAtStart = 1;
   3785        } else {
   3786          assign.mGapsAtStart = 2;
   3787          info.mInnerOpportunities++;
   3788        }
   3789      }
   3790 
   3791      FindClusterEnd(mTextRun, originalOffset + length, &iter);
   3792      uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
   3793      // Assign the two gaps temporary to the last char. If the next cluster is
   3794      // justifiable as well, one of the gaps will be removed by code above.
   3795      assignments[lastChar].mGapsAtEnd = 2;
   3796      info.mInnerOpportunities++;
   3797 
   3798      // Skip the whole cluster
   3799      i = iter.GetOriginalOffset() - originalOffset;
   3800    }
   3801  }
   3802 
   3803  if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
   3804    // We counted the expansion opportunity after the last character,
   3805    // but it is not an inner opportunity.
   3806    MOZ_ASSERT(info.mInnerOpportunities > 0);
   3807    info.mInnerOpportunities--;
   3808    info.mIsEndJustifiable = true;
   3809  }
   3810 
   3811  if (aAssignments) {
   3812    *aAssignments = std::move(assignments);
   3813  }
   3814  return info;
   3815 }
   3816 
   3817 // aStart, aLength in transformed string offsets.
   3818 // Returns false if no non-standard spacing was required.
   3819 bool nsTextFrame::PropertyProvider::GetSpacing(Range aRange,
   3820                                               Spacing* aSpacing) const {
   3821  return GetSpacingInternal(
   3822      aRange, aSpacing,
   3823      !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab));
   3824 }
   3825 
   3826 static bool CanAddSpacingBefore(const gfxTextRun* aTextRun, uint32_t aOffset,
   3827                                bool aNewlineIsSignificant) {
   3828  const auto* g = aTextRun->GetCharacterGlyphs();
   3829  MOZ_ASSERT(aOffset < aTextRun->GetLength());
   3830  if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
   3831    return false;
   3832  }
   3833  if (!aOffset) {
   3834    return true;
   3835  }
   3836  return g[aOffset].IsClusterStart() && g[aOffset].IsLigatureGroupStart() &&
   3837         !g[aOffset - 1].CharIsFormattingControl() && !g[aOffset].CharIsTab();
   3838 }
   3839 
   3840 static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset,
   3841                               bool aNewlineIsSignificant) {
   3842  const auto* g = aTextRun->GetCharacterGlyphs();
   3843  MOZ_ASSERT(aOffset < aTextRun->GetLength());
   3844  if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
   3845    return false;
   3846  }
   3847  if (aOffset + 1 >= aTextRun->GetLength()) {
   3848    return true;
   3849  }
   3850  return g[aOffset + 1].IsClusterStart() &&
   3851         g[aOffset + 1].IsLigatureGroupStart() &&
   3852         !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab();
   3853 }
   3854 
   3855 static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) {
   3856  const auto& tabSize = aFrame->StyleText()->mTabSize;
   3857  if (tabSize.IsLength()) {
   3858    nscoord w = tabSize.length._0.ToAppUnits();
   3859    MOZ_ASSERT(w >= 0);
   3860    return w;
   3861  }
   3862 
   3863  MOZ_ASSERT(tabSize.IsNumber());
   3864  gfxFloat spaces = tabSize.number._0;
   3865  MOZ_ASSERT(spaces >= 0);
   3866 
   3867  const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay());
   3868  const auto* styleText = cb->StyleText();
   3869 
   3870  // Round the space width when converting to appunits the same way textruns do.
   3871  // We don't use GetFirstFontMetrics here because that may return a font that
   3872  // does not actually have the <space> character, yet is considered the "first
   3873  // available font" per CSS Fonts. Here, we want the font that would be used
   3874  // to render <space>, even if that means looking further down the font-family
   3875  // list.
   3876  RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f);
   3877  bool vertical = cb->GetWritingMode().IsCentralBaseline();
   3878  RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(' ');
   3879  auto metrics = font->GetMetrics(vertical ? nsFontMetrics::eVertical
   3880                                           : nsFontMetrics::eHorizontal);
   3881  nscoord spaceWidth = NSToCoordRound(metrics.spaceWidth *
   3882                                      cb->PresContext()->AppUnitsPerDevPixel());
   3883  return spaces *
   3884         (spaceWidth + styleText->mLetterSpacing.Resolve(fm->EmHeight()) +
   3885          styleText->mWordSpacing.Resolve(spaceWidth));
   3886 }
   3887 
   3888 // Walk backward from aIter to prior cluster starts (within the same textframe's
   3889 // content) and return the first non-mark autospace class.
   3890 //
   3891 // @param aContentOffsetAtFrameStart the original content offset at the start of
   3892 // the textframe.
   3893 static Maybe<TextAutospace::CharClass> LastNonMarkCharClass(
   3894    gfxSkipCharsIterator& aIter, int32_t aContentOffsetAtFrameStart,
   3895    const gfxTextRun* aTextRun, const CharacterDataBuffer& aBuffer) {
   3896  while (aIter.GetOriginalOffset() > aContentOffsetAtFrameStart) {
   3897    aIter.AdvanceOriginal(-1);
   3898    FindClusterStart(aTextRun, aContentOffsetAtFrameStart, &aIter);
   3899    const char32_t ch = aBuffer.ScalarValueAt(aIter.GetOriginalOffset());
   3900    auto cls = TextAutospace::GetCharClass(ch);
   3901    if (cls != TextAutospace::CharClass::CombiningMark) {
   3902      return Some(cls);
   3903    }
   3904  }
   3905  return Nothing();
   3906 }
   3907 
   3908 // Walk backward through the frame's content and return the first non-mark
   3909 // autospace class. (Unlike the function above, this is usable when the frame
   3910 // does not currently have a textrun.)
   3911 static Maybe<TextAutospace::CharClass> LastNonMarkCharClass(
   3912    const nsTextFrame* aFrame) {
   3913  using CharClass = TextAutospace::CharClass;
   3914  bool trimSpace = aFrame->HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
   3915  const auto& buffer = aFrame->CharacterDataBuffer();
   3916  const uint32_t startOffset = aFrame->GetContentOffset();
   3917  uint32_t i = aFrame->GetContentEnd();
   3918  while (i > startOffset) {
   3919    // Get trailing character, decoding surrogate pair if necessary.
   3920    char32_t ch = buffer.CharAt(--i);
   3921    if (NS_IS_LOW_SURROGATE(ch) && i > startOffset) {
   3922      // Get potential high surrogate, and decode.
   3923      char32 hi = buffer.CharAt(i - 1);
   3924      if (NS_IS_HIGH_SURROGATE(hi)) {
   3925        ch = SURROGATE_TO_UCS4(hi, ch);
   3926        --i;
   3927      }
   3928    }
   3929    // Skip over trailing whitespace if the frame was trimmed.
   3930    if (trimSpace) {
   3931      if (IsTrimmableSpace(ch)) {
   3932        continue;
   3933      }
   3934      trimSpace = false;
   3935    }
   3936    // If it has a non-CombiningMark class, return it.
   3937    auto cls = TextAutospace::GetCharClass(ch);
   3938    if (cls != CharClass::CombiningMark) {
   3939      return Some(cls);
   3940    }
   3941  }
   3942  // No (non-mark, non-trimmed) characters were found.
   3943  return Nothing();
   3944 }
   3945 
   3946 // Return the first non-mark autospace class from the end of content in aFrame.
   3947 static Maybe<TextAutospace::CharClass> LastNonMarkCharClassInFrame(
   3948    nsTextFrame* aFrame) {
   3949  using CharClass = TextAutospace::CharClass;
   3950  if (!aFrame->GetContentLength()) {
   3951    return Nothing();
   3952  }
   3953  Maybe<CharClass> prevClass;
   3954  if (aFrame->GetTextRun(nsTextFrame::eInflated)) {
   3955    // If the frame has a textrun, we can use that to find the last cluster
   3956    // start character and return its class.
   3957    gfxSkipCharsIterator iter = aFrame->EnsureTextRun(nsTextFrame::eInflated);
   3958    iter.SetOriginalOffset(aFrame->GetContentEnd());
   3959    prevClass = LastNonMarkCharClass(iter, aFrame->GetContentOffset(),
   3960                                     aFrame->GetTextRun(nsTextFrame::eInflated),
   3961                                     aFrame->CharacterDataBuffer());
   3962  } else {
   3963    // We can't call EnsureTextRun if it would build new textruns, because that
   3964    // could destroy the glyph runs that we're currently iterating over. So
   3965    // instead we fall back to inspecting the content directly. This means we
   3966    // ignore CSS whitespace-collapsing, which could mean some of the content
   3967    // should be skipped, but in practice none of the characters that are
   3968    // relevant for autospace classes would be affected.
   3969    prevClass = LastNonMarkCharClass(aFrame);
   3970  }
   3971  if (prevClass) {
   3972    return prevClass;
   3973  }
   3974  if (aFrame->GetPrevInFlow()) {
   3975    // If aFrame has a prev-in-flow, it is after a line-break, so autospace does
   3976    // not apply here; just return Other.
   3977    return Some(CharClass::Other);
   3978  }
   3979  return Nothing();
   3980 }
   3981 
   3982 // Look for the autospace class of the content preceding the given aFrame
   3983 // in the mapped flows of the current textrun.
   3984 static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromMappedFlows(
   3985    const nsTextFrame* aFrame, const gfxTextRun* aTextRun) {
   3986  using CharClass = TextAutospace::CharClass;
   3987 
   3988  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
   3989    return Nothing();
   3990  }
   3991 
   3992  auto data = static_cast<TextRunUserData*>(aTextRun->GetUserData());
   3993  if (!data) {
   3994    return Nothing();
   3995  }
   3996  TextRunMappedFlow* mappedFlows = GetMappedFlows(aTextRun);
   3997 
   3998  // Search for aFrame in the mapped flows.
   3999  uint32_t i = 0;
   4000  for (; i < data->mMappedFlowCount; ++i) {
   4001    if (mappedFlows[i].mStartFrame == aFrame) {
   4002      break;
   4003    }
   4004  }
   4005  MOZ_ASSERT(mappedFlows[i].mStartFrame == aFrame,
   4006             "aFrame not found in mapped flows!");
   4007 
   4008  while (i > 0) {
   4009    nsTextFrame* f = mappedFlows[--i].mStartFrame->LastInFlow();
   4010    if (Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f)) {
   4011      return prevClass;
   4012    }
   4013  }
   4014  return Nothing();
   4015 }
   4016 
   4017 // Look for the autospace class of content preceding the given frame.
   4018 static Maybe<TextAutospace::CharClass> GetPrecedingCharClassFromFrameTree(
   4019    nsIFrame* aFrame) {
   4020  using CharClass = TextAutospace::CharClass;
   4021  while (!aFrame->GetPrevSibling() && aFrame->GetParent()->IsInlineFrame()) {
   4022    // If this is the first child of an inline container, we want to ascend to
   4023    // the parent and look at what precedes it.
   4024    aFrame = aFrame->GetParent();
   4025  }
   4026  aFrame = aFrame->GetPrevSibling();
   4027  while (aFrame) {
   4028    if (aFrame->IsPlaceholderFrame()) {
   4029      // Skip over out-of-flow placeholders.
   4030      aFrame = aFrame->GetPrevSibling();
   4031      continue;
   4032    }
   4033    if (aFrame->IsInlineFrame()) {
   4034      // Descend into inline containers and go backwards through their content.
   4035      aFrame = aFrame->PrincipalChildList().LastChild();
   4036      continue;
   4037    }
   4038    if (nsTextFrame* f = do_QueryFrame(aFrame)) {
   4039      // Look for the class of the last character in the textframe.
   4040      Maybe<CharClass> prevClass = LastNonMarkCharClassInFrame(f);
   4041      if (prevClass) {
   4042        if ((*prevClass == CharClass::NonIdeographicLetter ||
   4043             *prevClass == CharClass::NonIdeographicNumeral) &&
   4044            TextAutospace::ShouldSuppressLetterNumeralSpacing(f)) {
   4045          return Some(CharClass::Other);
   4046        }
   4047        return prevClass;
   4048      }
   4049      aFrame = aFrame->GetPrevSibling();
   4050      continue;
   4051    }
   4052    return Nothing();
   4053  }
   4054  return Nothing();
   4055 }
   4056 
   4057 static bool HasCJKGlyphRun(const gfxTextRun* aTextRun) {
   4058  uint32_t numGlyphRuns;
   4059  const gfxTextRun::GlyphRun* run = aTextRun->GetGlyphRuns(&numGlyphRuns);
   4060  while (numGlyphRuns-- > 0) {
   4061    if (run->mIsCJK) {
   4062      return true;
   4063    }
   4064    run++;
   4065  }
   4066  return false;
   4067 }
   4068 
   4069 bool nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange,
   4070                                                       Spacing* aSpacing,
   4071                                                       bool aIgnoreTabs) const {
   4072  MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
   4073 
   4074  std::memset(aSpacing, 0, aRange.Length() * sizeof(*aSpacing));
   4075 
   4076  if (mFrame->Style()->IsTextCombined()) {
   4077    return false;
   4078  }
   4079 
   4080  // Track whether any non-standard spacing is actually present in this range.
   4081  // If letter-spacing is non-zero this will always be true, but for the
   4082  /// word-spacing and text-autospace cases it will depend on the actual text.
   4083  bool spacingPresent = mLetterSpacing;
   4084 
   4085  // First, compute the word spacing, letter spacing, and text-autospace
   4086  // spacing.
   4087  if (mWordSpacing || mLetterSpacing || mTextAutospace) {
   4088    bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame);
   4089    // Which letter-spacing model are we using?
   4090    //   0 - Gecko legacy model, spacing added to trailing side of letter
   4091    //   1 - WebKit/Blink-compatible, spacing added to right-hand side
   4092    //   2 - Symmetrical spacing, half added to each side
   4093    nscoord before, after;
   4094    switch (StaticPrefs::layout_css_letter_spacing_model()) {
   4095      default:  // use Gecko legacy behavior if pref value is unknown
   4096      case 0:
   4097        before = 0;
   4098        after = mLetterSpacing;
   4099        break;
   4100      case 1:
   4101        if (mTextRun->IsRightToLeft()) {
   4102          before = mLetterSpacing;
   4103          after = 0;
   4104        } else {
   4105          before = 0;
   4106          after = mLetterSpacing;
   4107        }
   4108        break;
   4109      case 2:
   4110        before = NSToCoordRound(mLetterSpacing * 0.5);
   4111        after = mLetterSpacing - before;
   4112        break;
   4113    }
   4114 
   4115    // Find our offset into the original+transformed string
   4116    gfxSkipCharsIterator start(mStart);
   4117    start.SetSkippedOffset(aRange.start);
   4118    bool atStart = mStartOfLineOffset == start.GetSkippedOffset() &&
   4119                   !mFrame->IsInSVGTextSubtree();
   4120 
   4121    using CharClass = TextAutospace::CharClass;
   4122    // The non-mark class of a previous character at a cluster start (if any).
   4123    Maybe<CharClass> prevClass;
   4124 
   4125    // Initialization of prevClass at start-of-frame may be a bit expensive,
   4126    // and we don't always need that initial value, so we encapsulate it in a
   4127    // helper to be called on-demand.
   4128    auto findPrecedingClass = [&]() -> CharClass {
   4129      // Get the class of the character immediately before the current aRange.
   4130      Maybe<CharClass> prevClass;
   4131      if (aRange.start > 0) {
   4132        gfxSkipCharsIterator iter = start;
   4133        prevClass = LastNonMarkCharClass(iter, mFrame->GetContentOffset(),
   4134                                         mTextRun, mCharacterDataBuffer);
   4135      }
   4136      // If no class was found, we need to look at the preceding content (if
   4137      // any) to see what it ended with.
   4138      if (!prevClass) {
   4139        // If we have a prev-in-flow, we're after a line-break, so autospace
   4140        // does not apply here; just set prevClass to Other.
   4141        if (mFrame->GetPrevInFlow()) {
   4142          prevClass = Some(CharClass::Other);
   4143        } else {
   4144          // If the textrun is mapping multiple content flows, we may be able
   4145          // to find preceding content from there (without having to walk the
   4146          // potentially more complex frame tree).
   4147          prevClass = GetPrecedingCharClassFromMappedFlows(mFrame, mTextRun);
   4148          // If we couldn't get it from an earlier flow covered by the textrun,
   4149          // we'll have to delve into the frame tree to see what preceded this.
   4150          if (!prevClass) {
   4151            prevClass = GetPrecedingCharClassFromFrameTree(mFrame);
   4152          }
   4153        }
   4154      }
   4155      // If no valid class was found, return `Other`, which never participates
   4156      // in autospacing rules.
   4157      return prevClass.valueOr(CharClass::Other);
   4158    };
   4159 
   4160    // If text-autospace is enabled, we may be able to skip some processing if
   4161    // there are no CJK glyphs in the textrun, so check for their presence.
   4162    bool textIncludesCJK = mTextAutospace && mCharacterDataBuffer.Is2b() &&
   4163                           HasCJKGlyphRun(mTextRun);
   4164 
   4165    // Iterate over non-skipped characters
   4166    nsSkipCharsRunIterator run(
   4167        start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
   4168    while (run.NextRun()) {
   4169      uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
   4170      gfxSkipCharsIterator iter = run.GetPos();
   4171      for (int32_t i = 0; i < run.GetRunLength(); ++i) {
   4172        auto currScalar = [&]() -> char32_t {
   4173          iter.SetSkippedOffset(run.GetSkippedOffset() + i);
   4174          return mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset());
   4175        };
   4176        if (!atStart && before != 0 &&
   4177            CanAddSpacingBefore(mTextRun, run.GetSkippedOffset() + i,
   4178                                newlineIsSignificant) &&
   4179            !intl::UnicodeProperties::IsCursiveScript(currScalar())) {
   4180          aSpacing[runOffsetInSubstring + i].mBefore += before;
   4181        }
   4182        if (after != 0 &&
   4183            CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i,
   4184                               newlineIsSignificant)) {
   4185          // End of a cluster, not in a ligature: put letter-spacing after it,
   4186          // unless the base char of the cluster belonged to a cursive script.
   4187          iter.SetSkippedOffset(run.GetSkippedOffset() + i);
   4188          FindClusterStart(mTextRun, run.GetOriginalOffset(), &iter);
   4189          char32_t baseChar =
   4190              mCharacterDataBuffer.ScalarValueAt(iter.GetOriginalOffset());
   4191          if (!intl::UnicodeProperties::IsCursiveScript(baseChar)) {
   4192            aSpacing[runOffsetInSubstring + i].mAfter += after;
   4193          }
   4194        }
   4195        if (mWordSpacing && IsCSSWordSpacingSpace(mCharacterDataBuffer,
   4196                                                  i + run.GetOriginalOffset(),
   4197                                                  mFrame, mTextStyle)) {
   4198          // It kinda sucks, but space characters can be part of clusters,
   4199          // and even still be whitespace (I think!)
   4200          iter.SetSkippedOffset(run.GetSkippedOffset() + i);
   4201          FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
   4202                         &iter);
   4203          uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
   4204          aSpacing[runOffset].mAfter += mWordSpacing;
   4205          spacingPresent = true;
   4206        }
   4207        // Add text-autospace spacing only at cluster starts. Always check the
   4208        // character classes if the textrun includes CJK; otherwise, check only
   4209        // at the frame start (as preceding content might be an ideograph
   4210        // requiring autospacing).
   4211        if (mTextAutospace &&
   4212            (textIncludesCJK ||
   4213             run.GetOriginalOffset() + i == mFrame->GetContentOffset()) &&
   4214            mTextRun->IsClusterStart(run.GetSkippedOffset() + i)) {
   4215          const auto currClass = TextAutospace::GetCharClass(currScalar());
   4216 
   4217          // It is rare for the current class to be a combining mark, as
   4218          // combining marks are not cluster starts. We still check in case a
   4219          // stray mark appears at the start of a frame.
   4220          if (currClass != CharClass::CombiningMark) {
   4221            // We don't need to do anything if at start of line, or if the
   4222            // current class is `Other`, which never participates in spacing.
   4223            if (!atStart && currClass != CharClass::Other &&
   4224                mTextAutospace->ShouldApplySpacing(
   4225                    prevClass.valueOrFrom(findPrecedingClass), currClass)) {
   4226              aSpacing[runOffsetInSubstring + i].mBefore +=
   4227                  mTextAutospace->InterScriptSpacing();
   4228              spacingPresent = true;
   4229            }
   4230            // Even if we didn't actually need to check spacing rules here, we
   4231            // record the new prevClass. (Incidentally, this ensure that we'll
   4232            // only call the findPrecedingClass() helper once.)
   4233            prevClass = Some(currClass);
   4234          }
   4235        }
   4236        atStart = false;
   4237      }
   4238    }
   4239  }
   4240 
   4241  // Now add tab spacing, if there is any
   4242  if (!aIgnoreTabs) {
   4243    gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame);
   4244    if (tabWidth > 0) {
   4245      CalcTabWidths(aRange, tabWidth);
   4246      if (mTabWidths) {
   4247        mTabWidths->ApplySpacing(aSpacing,
   4248                                 aRange.start - mStart.GetSkippedOffset(),
   4249                                 aRange.Length());
   4250        spacingPresent = true;
   4251      }
   4252    }
   4253  }
   4254 
   4255  // Now add in justification spacing
   4256  if (mJustificationSpacings.Length() > 0) {
   4257    // If there is any spaces trimmed at the end, aStart + aLength may
   4258    // be larger than the flags array. When that happens, we can simply
   4259    // ignore those spaces.
   4260    auto arrayEnd = mJustificationArrayStart +
   4261                    static_cast<uint32_t>(mJustificationSpacings.Length());
   4262    auto end = std::min(aRange.end, arrayEnd);
   4263    MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
   4264    for (auto i = aRange.start; i < end; i++) {
   4265      const auto& spacing =
   4266          mJustificationSpacings[i - mJustificationArrayStart];
   4267      uint32_t offset = i - aRange.start;
   4268      aSpacing[offset].mBefore += spacing.mBefore;
   4269      aSpacing[offset].mAfter += spacing.mAfter;
   4270    }
   4271    spacingPresent = true;
   4272  }
   4273 
   4274  return spacingPresent;
   4275 }
   4276 
   4277 // aX and the result are in whole appunits.
   4278 static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth,
   4279                                 gfxFloat aMinAdvance) {
   4280  // Advance aX to the next multiple of aTabWidth. We must advance
   4281  // by at least aMinAdvance.
   4282  gfxFloat nextPos = aX + aMinAdvance;
   4283  return aTabWidth > 0.0 ? ceil(nextPos / aTabWidth) * aTabWidth : nextPos;
   4284 }
   4285 
   4286 void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange,
   4287                                                  gfxFloat aTabWidth) const {
   4288  MOZ_ASSERT(aTabWidth > 0);
   4289 
   4290  if (!mTabWidths) {
   4291    if (mReflowing && !mLineContainer) {
   4292      // Intrinsic width computation does its own tab processing. We
   4293      // just don't do anything here.
   4294      return;
   4295    }
   4296    if (!mReflowing) {
   4297      mTabWidths = mFrame->GetProperty(TabWidthProperty());
   4298 #ifdef DEBUG
   4299      // If we're not reflowing, we should have already computed the
   4300      // tab widths; check that they're available as far as the last
   4301      // tab character present (if any)
   4302      for (uint32_t i = aRange.end; i > aRange.start; --i) {
   4303        if (mTextRun->CharIsTab(i - 1)) {
   4304          uint32_t startOffset = mStart.GetSkippedOffset();
   4305          NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
   4306                       "Precomputed tab widths are missing!");
   4307          break;
   4308        }
   4309      }
   4310 #endif
   4311      return;
   4312    }
   4313  }
   4314 
   4315  uint32_t startOffset = mStart.GetSkippedOffset();
   4316  MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
   4317  MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
   4318  uint32_t tabsEnd =
   4319      (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
   4320  if (tabsEnd < aRange.end) {
   4321    NS_ASSERTION(mReflowing,
   4322                 "We need precomputed tab widths, but don't have enough.");
   4323 
   4324    for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
   4325      Spacing spacing;
   4326      GetSpacingInternal(Range(i, i + 1), &spacing, true);
   4327      mOffsetFromBlockOriginForTabs += spacing.mBefore;
   4328 
   4329      if (!mTextRun->CharIsTab(i)) {
   4330        if (mTextRun->IsClusterStart(i)) {
   4331          uint32_t clusterEnd = i + 1;
   4332          while (clusterEnd < mTextRun->GetLength() &&
   4333                 !mTextRun->IsClusterStart(clusterEnd)) {
   4334            ++clusterEnd;
   4335          }
   4336          mOffsetFromBlockOriginForTabs +=
   4337              mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
   4338        }
   4339      } else {
   4340        if (!mTabWidths) {
   4341          mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
   4342          mFrame->SetProperty(TabWidthProperty(), mTabWidths);
   4343        }
   4344        double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
   4345                                          aTabWidth, MinTabAdvance());
   4346        mTabWidths->mWidths.AppendElement(
   4347            TabWidth(i - startOffset,
   4348                     NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
   4349        mOffsetFromBlockOriginForTabs = nextTab;
   4350      }
   4351 
   4352      mOffsetFromBlockOriginForTabs += spacing.mAfter;
   4353    }
   4354 
   4355    if (mTabWidths) {
   4356      mTabWidths->mLimit = aRange.end - startOffset;
   4357    }
   4358  }
   4359 
   4360  if (!mTabWidths) {
   4361    // Delete any stale property that may be left on the frame
   4362    mFrame->RemoveProperty(TabWidthProperty());
   4363    mTabWidthsAnalyzedLimit =
   4364        std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset);
   4365  }
   4366 }
   4367 
   4368 gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const {
   4369  if (mHyphenWidth < 0) {
   4370    const auto& hyphenateChar = mTextStyle->mHyphenateCharacter;
   4371    if (hyphenateChar.IsAuto()) {
   4372      mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
   4373    } else {
   4374      RefPtr<gfxTextRun> hyphRun = GetHyphenTextRun(mFrame, nullptr);
   4375      mHyphenWidth = hyphRun ? hyphRun->GetAdvanceWidth() : 0;
   4376    }
   4377  }
   4378  return mHyphenWidth + mLetterSpacing;
   4379 }
   4380 
   4381 static inline bool IS_HYPHEN(char16_t u) {
   4382  return u == char16_t('-') ||  // HYPHEN-MINUS
   4383         u == 0x058A ||         // ARMENIAN HYPHEN
   4384         u == 0x2010 ||         // HYPHEN
   4385         u == 0x2012 ||         // FIGURE DASH
   4386         u == 0x2013;           // EN DASH
   4387 }
   4388 
   4389 void nsTextFrame::PropertyProvider::GetHyphenationBreaks(
   4390    Range aRange, HyphenType* aBreakBefore) const {
   4391  MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
   4392  MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
   4393 
   4394  if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
   4395      mTextStyle->mHyphens == StyleHyphens::None) {
   4396    memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
   4397           aRange.Length() * sizeof(HyphenType));
   4398    return;
   4399  }
   4400 
   4401  // Iterate through the original-string character runs
   4402  nsSkipCharsRunIterator run(
   4403      mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
   4404  run.SetSkippedOffset(aRange.start);
   4405  // We need to visit skipped characters so that we can detect SHY
   4406  run.SetVisitSkipped();
   4407 
   4408  int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
   4409  bool allowHyphenBreakBeforeNextChar =
   4410      prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
   4411      prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
   4412      mCharacterDataBuffer.CharAt(
   4413          AssertedCast<uint32_t>(prevTrailingCharOffset)) == CH_SHY;
   4414 
   4415  while (run.NextRun()) {
   4416    NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
   4417    if (run.IsSkipped()) {
   4418      // Check if there's a soft hyphen which would let us hyphenate before
   4419      // the next non-skipped character. Don't look at soft hyphens followed
   4420      // by other skipped characters, we won't use them.
   4421      allowHyphenBreakBeforeNextChar =
   4422          mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>(
   4423              run.GetOriginalOffset() + run.GetRunLength() - 1)) == CH_SHY;
   4424    } else {
   4425      int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
   4426      memset(aBreakBefore + runOffsetInSubstring,
   4427             static_cast<uint8_t>(HyphenType::None),
   4428             run.GetRunLength() * sizeof(HyphenType));
   4429      // Don't allow hyphen breaks at the start of the line
   4430      aBreakBefore[runOffsetInSubstring] =
   4431          allowHyphenBreakBeforeNextChar &&
   4432                  (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) ||
   4433                   run.GetSkippedOffset() > mStart.GetSkippedOffset())
   4434              ? HyphenType::Soft
   4435              : HyphenType::None;
   4436      allowHyphenBreakBeforeNextChar = false;
   4437    }
   4438  }
   4439 
   4440  if (mTextStyle->mHyphens == StyleHyphens::Auto) {
   4441    gfxSkipCharsIterator skipIter(mStart);
   4442    for (uint32_t i = 0; i < aRange.Length(); ++i) {
   4443      if (IS_HYPHEN(mCharacterDataBuffer.CharAt(AssertedCast<uint32_t>(
   4444              skipIter.ConvertSkippedToOriginal(aRange.start + i))))) {
   4445        if (i < aRange.Length() - 1) {
   4446          aBreakBefore[i + 1] = HyphenType::Explicit;
   4447        }
   4448        continue;
   4449      }
   4450 
   4451      if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
   4452          aBreakBefore[i] == HyphenType::None) {
   4453        aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
   4454      }
   4455    }
   4456  }
   4457 }
   4458 
   4459 void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) {
   4460  nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
   4461      mCharacterDataBuffer,
   4462      (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default
   4463                  : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter));
   4464  mStart.SetOriginalOffset(trimmed.mStart);
   4465  mLength = trimmed.mLength;
   4466  if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) {
   4467    mStartOfLineOffset = mStart.GetSkippedOffset();
   4468  }
   4469  SetupJustificationSpacing(true);
   4470 }
   4471 
   4472 void nsTextFrame::PropertyProvider::InitializeForMeasure() {
   4473  nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
   4474      mCharacterDataBuffer, nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
   4475  mStart.SetOriginalOffset(trimmed.mStart);
   4476  mLength = trimmed.mLength;
   4477  if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) {
   4478    mStartOfLineOffset = mStart.GetSkippedOffset();
   4479  }
   4480  SetupJustificationSpacing(false);
   4481 }
   4482 
   4483 void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
   4484    bool aPostReflow) {
   4485  MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
   4486 
   4487  if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) {
   4488    return;
   4489  }
   4490 
   4491  gfxSkipCharsIterator start(mStart), end(mStart);
   4492  // We can't just use our mLength here; when InitializeForDisplay is
   4493  // called with false for aTrimAfter, we still shouldn't be assigning
   4494  // justification space to any trailing whitespace.
   4495  nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
   4496      mCharacterDataBuffer,
   4497      (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
   4498                   : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
   4499  end.AdvanceOriginal(trimmed.mLength);
   4500  gfxSkipCharsIterator realEnd(end);
   4501 
   4502  Range range(uint32_t(start.GetOriginalOffset()),
   4503              uint32_t(end.GetOriginalOffset()));
   4504  nsTArray<JustificationAssignment> assignments;
   4505  JustificationInfo info = ComputeJustification(range, &assignments);
   4506 
   4507  auto assign = mFrame->GetJustificationAssignment();
   4508  auto totalGaps = JustificationUtils::CountGaps(info, assign);
   4509  if (!totalGaps || assignments.IsEmpty()) {
   4510    // Nothing to do, nothing is justifiable and we shouldn't have any
   4511    // justification space assigned
   4512    return;
   4513  }
   4514 
   4515  // Remember that textrun measurements are in the run's orientation,
   4516  // so its advance "width" is actually a height in vertical writing modes,
   4517  // corresponding to the inline-direction of the frame.
   4518  gfxFloat naturalWidth = mTextRun->GetAdvanceWidth(
   4519      Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this);
   4520  if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
   4521    naturalWidth += GetHyphenWidth();
   4522  }
   4523  nscoord totalSpacing = mFrame->ISize() - naturalWidth;
   4524  if (totalSpacing <= 0) {
   4525    // No space available
   4526    return;
   4527  }
   4528 
   4529  assignments[0].mGapsAtStart = assign.mGapsAtStart;
   4530  assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
   4531 
   4532  MOZ_ASSERT(mJustificationSpacings.IsEmpty());
   4533  JustificationApplicationState state(totalGaps, totalSpacing);
   4534  mJustificationSpacings.SetCapacity(assignments.Length());
   4535  for (const JustificationAssignment& assign : assignments) {
   4536    Spacing* spacing = mJustificationSpacings.AppendElement();
   4537    spacing->mBefore = state.Consume(assign.mGapsAtStart);
   4538    spacing->mAfter = state.Consume(assign.mGapsAtEnd);
   4539  }
   4540 }
   4541 
   4542 void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
   4543  if (!mFontMetrics) {
   4544    if (mWhichTextRun == nsTextFrame::eInflated) {
   4545      mFontMetrics = mFrame->InflatedFontMetrics();
   4546    } else {
   4547      mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
   4548    }
   4549  }
   4550  mFontGroup = mFontMetrics->GetThebesFontGroup();
   4551 }
   4552 
   4553 void nsTextFrame::PropertyProvider::InitTextAutospace() {
   4554  const auto styleTextAutospace = mTextStyle->EffectiveTextAutospace();
   4555  if (TextAutospace::Enabled(styleTextAutospace, mFrame)) {
   4556    mTextAutospace.emplace(styleTextAutospace,
   4557                           GetFontMetrics()->InterScriptSpacingWidth());
   4558  }
   4559 }
   4560 
   4561 #ifdef ACCESSIBILITY
   4562 a11y::AccType nsTextFrame::AccessibleType() {
   4563  if (IsEmpty()) {
   4564    RenderedText text =
   4565        GetRenderedText(0, UINT32_MAX, TextOffsetType::OffsetsInContentText,
   4566                        TrailingWhitespace::DontTrim);
   4567    if (text.mString.IsEmpty()) {
   4568      return a11y::eNoType;
   4569    }
   4570  }
   4571 
   4572  return a11y::eTextLeafType;
   4573 }
   4574 #endif
   4575 
   4576 //-----------------------------------------------------------------------------
   4577 void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
   4578                       nsIFrame* aPrevInFlow) {
   4579  NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
   4580  MOZ_ASSERT(aContent->IsText(), "Bogus content!");
   4581 
   4582  // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
   4583  // might be invalid if the content was modified while there was no frame
   4584  if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
   4585    aContent->RemoveProperty(nsGkAtoms::newline);
   4586    aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
   4587  }
   4588  if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
   4589    aContent->RemoveProperty(nsGkAtoms::flowlength);
   4590    aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
   4591  }
   4592 
   4593  // Since our content has a frame now, this flag is no longer needed.
   4594  aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
   4595 
   4596  // We're not a continuing frame.
   4597  // mContentOffset = 0; not necessary since we get zeroed out at init
   4598  nsIFrame::Init(aContent, aParent, aPrevInFlow);
   4599 }
   4600 
   4601 void nsTextFrame::ClearFrameOffsetCache() {
   4602  // See if we need to remove ourselves from the offset cache
   4603  if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) {
   4604    nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
   4605    if (primaryFrame) {
   4606      // The primary frame might be null here.  For example,
   4607      // nsLineBox::DeleteLineList just destroys the frames in order, which
   4608      // means that the primary frame is already dead if we're a continuing text
   4609      // frame, in which case, all of its properties are gone, and we don't need
   4610      // to worry about deleting this property here.
   4611      primaryFrame->RemoveProperty(OffsetToFrameProperty());
   4612    }
   4613    RemoveStateBits(TEXT_IN_OFFSET_CACHE);
   4614  }
   4615 }
   4616 
   4617 void nsTextFrame::Destroy(DestroyContext& aContext) {
   4618  ClearFrameOffsetCache();
   4619 
   4620  // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
   4621  // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
   4622  // type might be changing.  Not clear whether it's worth it.
   4623  ClearTextRuns();
   4624  if (mNextContinuation) {
   4625    mNextContinuation->SetPrevInFlow(nullptr);
   4626  }
   4627  // Let the base class destroy the frame
   4628  nsIFrame::Destroy(aContext);
   4629 }
   4630 
   4631 nsTArray<nsTextFrame*>* nsTextFrame::GetContinuations() {
   4632  MOZ_ASSERT(NS_IsMainThread());
   4633  // Only for use on the primary frame, which has no prev-continuation.
   4634  MOZ_ASSERT(!GetPrevContinuation());
   4635  if (!mNextContinuation) {
   4636    return nullptr;
   4637  }
   4638  if (mPropertyFlags & PropertyFlags::Continuations) {
   4639    return GetProperty(ContinuationsProperty());
   4640  }
   4641  size_t count = 0;
   4642  for (nsIFrame* f = this; f; f = f->GetNextContinuation()) {
   4643    ++count;
   4644  }
   4645  auto* continuations = new nsTArray<nsTextFrame*>;
   4646  if (continuations->SetCapacity(count, fallible)) {
   4647    for (nsTextFrame* f = this; f;
   4648         f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
   4649      continuations->AppendElement(f);
   4650    }
   4651  } else {
   4652    delete continuations;
   4653    continuations = nullptr;
   4654  }
   4655  AddProperty(ContinuationsProperty(), continuations);
   4656  mPropertyFlags |= PropertyFlags::Continuations;
   4657  return continuations;
   4658 }
   4659 
   4660 class nsContinuingTextFrame final : public nsTextFrame {
   4661 public:
   4662  NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
   4663 
   4664  friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell,
   4665                                             ComputedStyle* aStyle);
   4666 
   4667  void Init(nsIContent* aContent, nsContainerFrame* aParent,
   4668            nsIFrame* aPrevInFlow) final;
   4669 
   4670  void Destroy(DestroyContext&) override;
   4671 
   4672  nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; }
   4673 
   4674  void SetPrevContinuation(nsIFrame* aPrevContinuation) final {
   4675    NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),
   4676                 "setting a prev continuation with incorrect type!");
   4677    NS_ASSERTION(
   4678        !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
   4679        "creating a loop in continuation chain!");
   4680    mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
   4681    RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
   4682    UpdateCachedContinuations();
   4683  }
   4684 
   4685  nsTextFrame* GetPrevInFlow() const final {
   4686    return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation
   4687                                                           : nullptr;
   4688  }
   4689 
   4690  void SetPrevInFlow(nsIFrame* aPrevInFlow) final {
   4691    NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),
   4692                 "setting a prev in flow with incorrect type!");
   4693    NS_ASSERTION(
   4694        !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
   4695        "creating a loop in continuation chain!");
   4696    mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
   4697    AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
   4698    UpdateCachedContinuations();
   4699  }
   4700 
   4701  // Call this helper to update cache after mPrevContinuation is changed.
   4702  void UpdateCachedContinuations() {
   4703    nsTextFrame* prevFirst = mFirstContinuation;
   4704    if (mPrevContinuation) {
   4705      mFirstContinuation = mPrevContinuation->FirstContinuation();
   4706      if (mFirstContinuation) {
   4707        mFirstContinuation->ClearCachedContinuations();
   4708      }
   4709    } else {
   4710      mFirstContinuation = nullptr;
   4711    }
   4712    if (mFirstContinuation != prevFirst) {
   4713      if (prevFirst) {
   4714        prevFirst->ClearCachedContinuations();
   4715      }
   4716      auto* f = static_cast<nsContinuingTextFrame*>(mNextContinuation);
   4717      while (f) {
   4718        f->mFirstContinuation = mFirstContinuation;
   4719        f = static_cast<nsContinuingTextFrame*>(f->mNextContinuation);
   4720      }
   4721    }
   4722  }
   4723 
   4724  nsIFrame* FirstInFlow() const final;
   4725  nsTextFrame* FirstContinuation() const final {
   4726 #if DEBUG
   4727    // If we have a prev-continuation pointer, then our first-continuation
   4728    // must be the same as that frame's.
   4729    if (mPrevContinuation) {
   4730      // If there's a prev-prev, then we can safely cast mPrevContinuation to
   4731      // an nsContinuingTextFrame and access its mFirstContinuation pointer
   4732      // directly, to avoid recursively calling FirstContinuation(), leading
   4733      // to exponentially-slow behavior in the assertion.
   4734      if (mPrevContinuation->GetPrevContinuation()) {
   4735        auto* prev = static_cast<nsContinuingTextFrame*>(mPrevContinuation);
   4736        MOZ_ASSERT(mFirstContinuation == prev->mFirstContinuation);
   4737      } else {
   4738        MOZ_ASSERT(mFirstContinuation ==
   4739                   mPrevContinuation->FirstContinuation());
   4740      }
   4741    } else {
   4742      MOZ_ASSERT(!mFirstContinuation);
   4743    }
   4744 #endif
   4745    return mFirstContinuation;
   4746  };
   4747 
   4748  void AddInlineMinISize(const IntrinsicSizeInput& aInput,
   4749                         InlineMinISizeData* aData) final {
   4750    // Do nothing, since the first-in-flow accounts for everything.
   4751  }
   4752  void AddInlinePrefISize(const IntrinsicSizeInput& aInput,
   4753                          InlinePrefISizeData* aData) final {
   4754    // Do nothing, since the first-in-flow accounts for everything.
   4755  }
   4756 
   4757 protected:
   4758  explicit nsContinuingTextFrame(ComputedStyle* aStyle,
   4759                                 nsPresContext* aPresContext)
   4760      : nsTextFrame(aStyle, aPresContext, kClassID) {}
   4761 
   4762  nsTextFrame* mPrevContinuation = nullptr;
   4763  nsTextFrame* mFirstContinuation = nullptr;
   4764 };
   4765 
   4766 void nsContinuingTextFrame::Init(nsIContent* aContent,
   4767                                 nsContainerFrame* aParent,
   4768                                 nsIFrame* aPrevInFlow) {
   4769  NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
   4770 
   4771  // Hook the frame into the flow
   4772  nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
   4773  nsTextFrame* nextContinuation = prev->GetNextContinuation();
   4774  SetPrevInFlow(aPrevInFlow);
   4775  aPrevInFlow->SetNextInFlow(this);
   4776 
   4777  // NOTE: bypassing nsTextFrame::Init!!!
   4778  nsIFrame::Init(aContent, aParent, aPrevInFlow);
   4779 
   4780  mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
   4781  NS_ASSERTION(
   4782      mContentOffset < int32_t(aContent->GetCharacterDataBuffer()->GetLength()),
   4783      "Creating ContinuingTextFrame, but there is no more content");
   4784  if (prev->Style() != Style()) {
   4785    // We're taking part of prev's text, and its style may be different
   4786    // so clear its textrun which may no longer be valid (and don't set ours)
   4787    prev->ClearTextRuns();
   4788  } else {
   4789    float inflation = prev->GetFontSizeInflation();
   4790    SetFontSizeInflation(inflation);
   4791    mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
   4792    if (inflation != 1.0f) {
   4793      gfxTextRun* uninflatedTextRun =
   4794          prev->GetTextRun(nsTextFrame::eNotInflated);
   4795      if (uninflatedTextRun) {
   4796        SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
   4797      }
   4798    }
   4799  }
   4800  if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) {
   4801    FrameBidiData bidiData = aPrevInFlow->GetBidiData();
   4802    bidiData.precedingControl = kBidiLevelNone;
   4803    SetProperty(BidiDataProperty(), bidiData);
   4804 
   4805    if (nextContinuation) {
   4806      SetNextContinuation(nextContinuation);
   4807      nextContinuation->SetPrevContinuation(this);
   4808      // Adjust next-continuations' content offset as needed.
   4809      while (nextContinuation &&
   4810             nextContinuation->GetContentOffset() < mContentOffset) {
   4811 #ifdef DEBUG
   4812        FrameBidiData nextBidiData = nextContinuation->GetBidiData();
   4813        NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&
   4814                         bidiData.baseLevel == nextBidiData.baseLevel,
   4815                     "stealing text from different type of BIDI continuation");
   4816        MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,
   4817                   "There shouldn't be any virtual bidi formatting character "
   4818                   "between continuations");
   4819 #endif
   4820        nextContinuation->mContentOffset = mContentOffset;
   4821        nextContinuation = nextContinuation->GetNextContinuation();
   4822      }
   4823    }
   4824    AddStateBits(NS_FRAME_IS_BIDI);
   4825  }  // prev frame is bidi
   4826 }
   4827 
   4828 void nsContinuingTextFrame::Destroy(DestroyContext& aContext) {
   4829  ClearFrameOffsetCache();
   4830 
   4831  // The text associated with this frame will become associated with our
   4832  // prev-continuation. If that means the text has changed style, then
   4833  // we need to wipe out the text run for the text.
   4834  // Note that mPrevContinuation can be null if we're destroying the whole
   4835  // frame chain from the start to the end.
   4836  // If this frame is mentioned in the userData for a textrun (say
   4837  // because there's a direction change at the start of this frame), then
   4838  // we have to clear the textrun because we're going away and the
   4839  // textrun had better not keep a dangling reference to us.
   4840  if (IsInTextRunUserData() ||
   4841      (mPrevContinuation && mPrevContinuation->Style() != Style())) {
   4842    ClearTextRuns();
   4843    // Clear the previous continuation's text run also, so that it can rebuild
   4844    // the text run to include our text.
   4845    if (mPrevContinuation) {
   4846      mPrevContinuation->ClearTextRuns();
   4847    }
   4848  }
   4849  nsSplittableFrame::RemoveFromFlow(this);
   4850  // Let the base class destroy the frame
   4851  nsIFrame::Destroy(aContext);
   4852 }
   4853 
   4854 nsIFrame* nsContinuingTextFrame::FirstInFlow() const {
   4855  // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
   4856  nsIFrame *firstInFlow,
   4857      *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this));
   4858  do {
   4859    firstInFlow = previous;
   4860    previous = firstInFlow->GetPrevInFlow();
   4861  } while (previous);
   4862  MOZ_ASSERT(firstInFlow, "post-condition failed");
   4863  return firstInFlow;
   4864 }
   4865 
   4866 // XXX Do we want to do all the work for the first-in-flow or do the
   4867 // work for each part?  (Be careful of first-letter / first-line, though,
   4868 // especially first-line!)  Doing all the work on the first-in-flow has
   4869 // the advantage of avoiding the potential for incremental reflow bugs,
   4870 // but depends on our maintining the frame tree in reasonable ways even
   4871 // for edge cases (block-within-inline splits, nextBidi, etc.)
   4872 
   4873 // XXX We really need to make :first-letter happen during frame
   4874 // construction.
   4875 
   4876 nscoord nsTextFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
   4877                                    IntrinsicISizeType aType) {
   4878  return IntrinsicISizeFromInline(aInput, aType);
   4879 }
   4880 
   4881 //----------------------------------------------------------------------
   4882 
   4883 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
   4884 static void VerifyNotDirty(nsFrameState state) {
   4885  bool isZero = state & NS_FRAME_FIRST_REFLOW;
   4886  bool isDirty = state & NS_FRAME_IS_DIRTY;
   4887  if (!isZero && isDirty) {
   4888    NS_WARNING("internal offsets may be out-of-sync");
   4889  }
   4890 }
   4891 #  define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state)
   4892 #else
   4893 #  define DEBUG_VERIFY_NOT_DIRTY(state)
   4894 #endif
   4895 
   4896 nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
   4897  return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext());
   4898 }
   4899 
   4900 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
   4901 
   4902 nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell,
   4903                                    ComputedStyle* aStyle) {
   4904  return new (aPresShell)
   4905      nsContinuingTextFrame(aStyle, aPresShell->GetPresContext());
   4906 }
   4907 
   4908 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
   4909 
   4910 nsTextFrame::~nsTextFrame() = default;
   4911 
   4912 nsIFrame::Cursor nsTextFrame::GetCursor(const nsPoint& aPoint) {
   4913  StyleCursorKind kind = StyleUI()->Cursor().keyword;
   4914  if (kind == StyleCursorKind::Auto) {
   4915    if (!IsSelectable()) {
   4916      kind = StyleCursorKind::Default;
   4917    } else {
   4918      kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
   4919                                           : StyleCursorKind::Text;
   4920    }
   4921  }
   4922  return Cursor{kind, AllowCustomCursorImage::Yes};
   4923 }
   4924 
   4925 nsTextFrame* nsTextFrame::LastInFlow() const {
   4926  nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
   4927  while (lastInFlow->GetNextInFlow()) {
   4928    lastInFlow = lastInFlow->GetNextInFlow();
   4929  }
   4930  MOZ_ASSERT(lastInFlow, "post-condition failed");
   4931  return lastInFlow;
   4932 }
   4933 
   4934 nsTextFrame* nsTextFrame::LastContinuation() const {
   4935  nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
   4936  while (lastContinuation->mNextContinuation) {
   4937    lastContinuation = lastContinuation->mNextContinuation;
   4938  }
   4939  MOZ_ASSERT(lastContinuation, "post-condition failed");
   4940  return lastContinuation;
   4941 }
   4942 
   4943 bool nsTextFrame::ShouldSuppressLineBreak() const {
   4944  // If the parent frame of the text frame is ruby content box, it must
   4945  // suppress line break inside. This check is necessary, because when
   4946  // a whitespace is only contained by pseudo ruby frames, its style
   4947  // context won't have SuppressLineBreak bit set.
   4948  if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) {
   4949    return true;
   4950  }
   4951  return Style()->ShouldSuppressLineBreak();
   4952 }
   4953 
   4954 void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey,
   4955                                  bool aRebuildDisplayItems) {
   4956  InvalidateSelectionState();
   4957 
   4958  if (IsInSVGTextSubtree()) {
   4959    nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
   4960        GetParent(), LayoutFrameType::SVGText);
   4961    svgTextFrame->InvalidateFrame();
   4962    return;
   4963  }
   4964  nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
   4965 }
   4966 
   4967 void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect,
   4968                                          uint32_t aDisplayItemKey,
   4969                                          bool aRebuildDisplayItems) {
   4970  InvalidateSelectionState();
   4971 
   4972  if (IsInSVGTextSubtree()) {
   4973    nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
   4974        GetParent(), LayoutFrameType::SVGText);
   4975    svgTextFrame->InvalidateFrame();
   4976    return;
   4977  }
   4978  nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
   4979                                    aRebuildDisplayItems);
   4980 }
   4981 
   4982 gfxTextRun* nsTextFrame::GetUninflatedTextRun() const {
   4983  return GetProperty(UninflatedTextRunProperty());
   4984 }
   4985 
   4986 void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
   4987                             float aInflation) {
   4988  NS_ASSERTION(aTextRun, "must have text run");
   4989 
   4990  // Our inflated text run is always stored in mTextRun.  In the cases
   4991  // where our current inflation is not 1.0, however, we store two text
   4992  // runs, and the uninflated one goes in a frame property.  We never
   4993  // store a single text run in both.
   4994  if (aWhichTextRun == eInflated) {
   4995    if (HasFontSizeInflation() && aInflation == 1.0f) {
   4996      // FIXME: Probably shouldn't do this within each SetTextRun
   4997      // method, but it doesn't hurt.
   4998      ClearTextRun(nullptr, nsTextFrame::eNotInflated);
   4999    }
   5000    SetFontSizeInflation(aInflation);
   5001  } else {
   5002    MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation");
   5003    if (HasFontSizeInflation()) {
   5004      // Setting the property will not automatically increment the textrun's
   5005      // reference count, so we need to do it here.
   5006      aTextRun->AddRef();
   5007      SetProperty(UninflatedTextRunProperty(), aTextRun);
   5008      return;
   5009    }
   5010    // fall through to setting mTextRun
   5011  }
   5012 
   5013  mTextRun = aTextRun;
   5014 
   5015  // FIXME: Add assertions testing the relationship between
   5016  // GetFontSizeInflation() and whether we have an uninflated text run
   5017  // (but be aware that text runs can go away).
   5018 }
   5019 
   5020 bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) {
   5021  if (aTextRun == mTextRun) {
   5022    mTextRun = nullptr;
   5023    mFontMetrics = nullptr;
   5024    return true;
   5025  }
   5026  if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) &&
   5027      GetProperty(UninflatedTextRunProperty()) == aTextRun) {
   5028    RemoveProperty(UninflatedTextRunProperty());
   5029    return true;
   5030  }
   5031  return false;
   5032 }
   5033 
   5034 void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
   5035                               TextRunType aWhichTextRun) {
   5036  RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
   5037  if (!textRun) {
   5038    return;
   5039  }
   5040 
   5041  if (aWhichTextRun == nsTextFrame::eInflated) {
   5042    mFontMetrics = nullptr;
   5043  }
   5044 
   5045  DebugOnly<bool> checkmTextrun = textRun == mTextRun;
   5046  UnhookTextRunFromFrames(textRun, aStartContinuation);
   5047  MOZ_ASSERT(checkmTextrun ? !mTextRun
   5048                           : !GetProperty(UninflatedTextRunProperty()));
   5049 }
   5050 
   5051 void nsTextFrame::DisconnectTextRuns() {
   5052  MOZ_ASSERT(!IsInTextRunUserData(),
   5053             "Textrun mentions this frame in its user data so we can't just "
   5054             "disconnect");
   5055  mTextRun = nullptr;
   5056  if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) {
   5057    RemoveProperty(UninflatedTextRunProperty());
   5058  }
   5059 }
   5060 
   5061 void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) {
   5062  MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree());
   5063 
   5064  MarkIntrinsicISizesDirty();
   5065 
   5066  // This is to avoid making a new Reflow request in CharacterDataChanged:
   5067  for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
   5068    f->MarkSubtreeDirty();
   5069    f->mReflowRequestedForCharDataChange = true;
   5070  }
   5071 
   5072  // Pretend that all the text changed.
   5073  CharacterDataChangeInfo info;
   5074  info.mAppend = false;
   5075  info.mChangeStart = 0;
   5076  info.mChangeEnd = aOldLength;
   5077  info.mReplaceLength = GetContent()->TextLength();
   5078  CharacterDataChanged(info);
   5079 }
   5080 
   5081 nsresult nsTextFrame::CharacterDataChanged(
   5082    const CharacterDataChangeInfo& aInfo) {
   5083  if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
   5084    mContent->RemoveProperty(nsGkAtoms::newline);
   5085    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
   5086  }
   5087  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
   5088    mContent->RemoveProperty(nsGkAtoms::flowlength);
   5089    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
   5090  }
   5091 
   5092  // Find the first frame whose text has changed. Frames that are entirely
   5093  // before the text change are completely unaffected.
   5094  nsTextFrame* next;
   5095  nsTextFrame* textFrame = this;
   5096  while (true) {
   5097    next = textFrame->GetNextContinuation();
   5098    if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) {
   5099      break;
   5100    }
   5101    textFrame = next;
   5102  }
   5103 
   5104  int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
   5105 
   5106  // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
   5107  // had already received an earlier FrameNeedsReflow call).
   5108  // (For subsequent frames with this same parent, we can just set their
   5109  // dirty bit without bothering to call FrameNeedsReflow again.)
   5110  nsIFrame* lastDirtiedFrameParent = nullptr;
   5111 
   5112  mozilla::PresShell* presShell = PresShell();
   5113  do {
   5114    // textFrame contained deleted text (or the insertion point,
   5115    // if this was a pure insertion).
   5116    textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
   5117    textFrame->ClearTextRuns();
   5118 
   5119    nsIFrame* parentOfTextFrame = textFrame->GetParent();
   5120    bool areAncestorsAwareOfReflowRequest = false;
   5121    if (lastDirtiedFrameParent == parentOfTextFrame) {
   5122      // An earlier iteration of this loop already called
   5123      // FrameNeedsReflow for a sibling of |textFrame|.
   5124      areAncestorsAwareOfReflowRequest = true;
   5125    } else {
   5126      lastDirtiedFrameParent = parentOfTextFrame;
   5127    }
   5128 
   5129    if (textFrame->mReflowRequestedForCharDataChange) {
   5130      // We already requested a reflow for this frame; nothing to do.
   5131      MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),
   5132                 "mReflowRequestedForCharDataChange should only be set "
   5133                 "on dirty frames");
   5134    } else {
   5135      // Make sure textFrame is queued up for a reflow.  Also set a flag so we
   5136      // don't waste time doing this again in repeated calls to this method.
   5137      textFrame->mReflowRequestedForCharDataChange = true;
   5138      if (!areAncestorsAwareOfReflowRequest) {
   5139        // Ask the parent frame to reflow me.
   5140        presShell->FrameNeedsReflow(
   5141            textFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
   5142            NS_FRAME_IS_DIRTY);
   5143      } else {
   5144        // We already called FrameNeedsReflow on behalf of an earlier sibling,
   5145        // so we can just mark this frame as dirty and don't need to bother
   5146        // telling its ancestors.
   5147        // Note: if the parent is a block, we're cheating here because we should
   5148        // be marking our line dirty, but we're not. nsTextFrame::SetLength will
   5149        // do that when it gets called during reflow.
   5150        textFrame->MarkSubtreeDirty();
   5151      }
   5152    }
   5153    textFrame->InvalidateFrame();
   5154 
   5155    // Below, frames that start after the deleted text will be adjusted so that
   5156    // their offsets move with the trailing unchanged text. If this change
   5157    // deletes more text than it inserts, those frame offsets will decrease.
   5158    // We need to maintain the invariant that mContentOffset is non-decreasing
   5159    // along the continuation chain. So we need to ensure that frames that
   5160    // started in the deleted text are all still starting before the
   5161    // unchanged text.
   5162    if (textFrame->mContentOffset > endOfChangedText) {
   5163      textFrame->mContentOffset = endOfChangedText;
   5164    }
   5165 
   5166    textFrame = textFrame->GetNextContinuation();
   5167  } while (textFrame &&
   5168           textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
   5169 
   5170  // This is how much the length of the string changed by --- i.e.,
   5171  // how much the trailing unchanged text moved.
   5172  int32_t sizeChange =
   5173      aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
   5174 
   5175  if (sizeChange) {
   5176    // Fix the offsets of the text frames that start in the trailing
   5177    // unchanged text.
   5178    while (textFrame) {
   5179      textFrame->mContentOffset += sizeChange;
   5180      // XXX we could rescue some text runs by adjusting their user data
   5181      // to reflect the change in DOM offsets
   5182      textFrame->ClearTextRuns();
   5183      textFrame = textFrame->GetNextContinuation();
   5184    }
   5185  }
   5186 
   5187  return NS_OK;
   5188 }
   5189 
   5190 struct TextCombineData {
   5191  // Measured advance of the text before any text-combine scaling is applied.
   5192  nscoord mNaturalWidth = 0;
   5193  // Inline offset to place this text within the 1-em block of the upright-
   5194  // combined cell.
   5195  nscoord mOffset = 0;
   5196  // Inline scaling factor to apply (always <= 1.0, as the text may be
   5197  // compressed but is never expanded to fit the 1-em cell).
   5198  float mScale = 1.0f;
   5199 };
   5200 
   5201 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TextCombineDataProperty, TextCombineData)
   5202 
   5203 float nsTextFrame::GetTextCombineScale() const {
   5204  const auto* data = GetProperty(TextCombineDataProperty());
   5205  return data ? data->mScale : 1.0f;
   5206 }
   5207 
   5208 std::pair<nscoord, float> nsTextFrame::GetTextCombineOffsetAndScale() const {
   5209  const auto* data = GetProperty(TextCombineDataProperty());
   5210  return data ? std::pair(data->mOffset, data->mScale) : std::pair(0, 1.0f);
   5211 }
   5212 
   5213 void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
   5214                                   const nsDisplayListSet& aLists) {
   5215  if (!IsVisibleForPainting()) {
   5216    return;
   5217  }
   5218 
   5219  DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
   5220 
   5221  const nsStyleText* st = StyleText();
   5222  bool isTextTransparent =
   5223      NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 &&
   5224      NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0;
   5225  if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) ||
   5226       (isTextTransparent && !StyleText()->HasTextShadow())) &&
   5227      aBuilder->IsForPainting() && !IsInSVGTextSubtree()) {
   5228    if (!IsSelected()) {
   5229      TextDecorations textDecs;
   5230      GetTextDecorations(PresContext(), eResolvedColors, textDecs);
   5231      if (!textDecs.HasDecorationLines()) {
   5232        if (auto* currentPresContext = aBuilder->CurrentPresContext()) {
   5233          currentPresContext->SetBuiltInvisibleText();
   5234        }
   5235        return;
   5236      }
   5237    }
   5238  }
   5239 
   5240  aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this);
   5241 }
   5242 
   5243 UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() {
   5244  const nsFrameSelection* frameSelection = GetConstFrameSelection();
   5245  if (frameSelection->IsInTableSelectionMode()) {
   5246    return nullptr;
   5247  }
   5248  UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection(
   5249      mContent, GetContentOffset(), GetContentLength(),
   5250      // We don't want to paint text as selected if this is not selectable.
   5251      // Note if this is editable, this is always treated as selectable, i.e.,
   5252      // if `user-select` is specified to `none` so that we never stop painting
   5253      // selections when there is IME composition which may need normal
   5254      // selection as a part of it.
   5255      ShouldPaintNormalSelection()
   5256          ? nsFrameSelection::IgnoreNormalSelection::No
   5257          : nsFrameSelection::IgnoreNormalSelection::Yes);
   5258  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
   5259    sd->mStart += mContentOffset;
   5260    sd->mEnd += mContentOffset;
   5261  }
   5262  return details;
   5263 }
   5264 
   5265 static void PaintSelectionBackground(
   5266    DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect,
   5267    const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) {
   5268  Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
   5269  MaybeSnapToDevicePixels(rect, aDrawTarget);
   5270 
   5271  if (aCallbacks) {
   5272    aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
   5273  } else {
   5274    ColorPattern color(ToDeviceColor(aColor));
   5275    aDrawTarget.FillRect(rect, color);
   5276  }
   5277 }
   5278 
   5279 // Attempt to get the LineBaselineOffset property of aChildFrame
   5280 // If not set, calculate this value for all child frames of aBlockFrame
   5281 static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame,
   5282                                         nsBlockFrame* aBlockFrame) {
   5283  bool offsetFound;
   5284  nscoord offset =
   5285      aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound);
   5286 
   5287  if (!offsetFound) {
   5288    for (const auto& line : aBlockFrame->Lines()) {
   5289      if (line.IsInline()) {
   5290        int32_t n = line.GetChildCount();
   5291        nscoord lineBaseline = line.BStart() + line.GetLogicalAscent();
   5292        for (auto* lineFrame = line.mFirstChild; n > 0;
   5293             lineFrame = lineFrame->GetNextSibling(), --n) {
   5294          offset = lineBaseline - lineFrame->GetNormalPosition().y;
   5295          lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
   5296        }
   5297      }
   5298    }
   5299    return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(),
   5300                                    &offsetFound);
   5301  } else {
   5302    return offset;
   5303  }
   5304 }
   5305 
   5306 static bool IsUnderlineRight(const ComputedStyle& aStyle) {
   5307  // Check for 'left' or 'right' explicitly specified in the property;
   5308  // if neither is there, we use auto positioning based on lang.
   5309  const auto position = aStyle.StyleText()->mTextUnderlinePosition;
   5310  if (position.IsLeft()) {
   5311    return false;
   5312  }
   5313  if (position.IsRight()) {
   5314    return true;
   5315  }
   5316  // If neither 'left' nor 'right' was specified, check the language.
   5317  nsAtom* langAtom = aStyle.StyleFont()->mLanguage;
   5318  if (!langAtom) {
   5319    return false;
   5320  }
   5321  return nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ja") ||
   5322         nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ko") ||
   5323         nsStyleUtil::MatchesLanguagePrefix(langAtom, u"mn");
   5324 }
   5325 
   5326 static bool FrameStopsLineDecorationPropagation(nsIFrame* aFrame,
   5327                                                nsCompatibility aCompatMode) {
   5328  // In all modes, if we're on an inline-block/table/grid/flex, we're done.
   5329  // If we're on a ruby frame other than ruby text container, we
   5330  // should continue.
   5331  mozilla::StyleDisplay display = aFrame->GetDisplay();
   5332  if (!display.IsInlineFlow() &&
   5333      (!display.IsRuby() ||
   5334       display == mozilla::StyleDisplay::RubyTextContainer) &&
   5335      display.IsInlineOutside()) {
   5336    return true;
   5337  }
   5338  // In quirks mode, if we're on an HTML table element, we're done.
   5339  if (aCompatMode == eCompatibility_NavQuirks &&
   5340      aFrame->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
   5341    return true;
   5342  }
   5343  // If we're on an absolutely-positioned element or a floating
   5344  // element, we're done.
   5345  if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
   5346    return true;
   5347  }
   5348  // If we're an outer <svg> element, which is classified as an atomic
   5349  // inline-level element, we're done.
   5350  if (aFrame->IsSVGOuterSVGFrame()) {
   5351    return true;
   5352  }
   5353  return false;
   5354 }
   5355 
   5356 void nsTextFrame::GetTextDecorations(
   5357    nsPresContext* aPresContext,
   5358    nsTextFrame::TextDecorationColorResolution aColorResolution,
   5359    nsTextFrame::TextDecorations& aDecorations) {
   5360  const nsCompatibility compatMode = aPresContext->CompatibilityMode();
   5361 
   5362  bool useOverride = false;
   5363  nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
   5364 
   5365  bool nearestBlockFound = false;
   5366  // Use writing mode of parent frame for orthogonal text frame to work.
   5367  // See comment in nsTextFrame::DrawTextRunAndDecorations.
   5368  WritingMode wm = GetParent()->GetWritingMode();
   5369  bool vertical = wm.IsVertical();
   5370 
   5371  nscoord ascent = GetLogicalBaseline(wm);
   5372  // physicalBlockStartOffset represents the offset from our baseline
   5373  // to f's physical block start, which is top in horizontal writing
   5374  // mode, and left in vertical writing modes, in our coordinate space.
   5375  // This physical block start is logical block start in most cases,
   5376  // but for vertical-rl, it is logical block end, and consequently in
   5377  // that case, it starts from the descent instead of ascent.
   5378  nscoord physicalBlockStartOffset =
   5379      wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
   5380  // baselineOffset represents the offset from our baseline to f's baseline or
   5381  // the nearest block's baseline, in our coordinate space, whichever is closest
   5382  // during the particular iteration
   5383  nscoord baselineOffset = 0;
   5384 
   5385  for (nsIFrame *f = this, *fChild = nullptr; f;
   5386       fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
   5387    ComputedStyle* const context = f->Style();
   5388    if (!context->HasTextDecorationLines()) {
   5389      break;
   5390    }
   5391 
   5392    if (context->GetPseudoType() == PseudoStyleType::marker &&
   5393        (context->StyleList()->mListStylePosition ==
   5394             StyleListStylePosition::Outside ||
   5395         !context->StyleDisplay()->IsInlineOutsideStyle())) {
   5396      // Outside ::marker pseudos, and inside markers that aren't inlines, don't
   5397      // have text decorations.
   5398      break;
   5399    }
   5400 
   5401    const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
   5402    StyleTextDecorationLine textDecorations =
   5403        styleTextReset->mTextDecorationLine;
   5404    bool ignoreSubproperties = false;
   5405 
   5406    auto lineStyle = styleTextReset->mTextDecorationStyle;
   5407    if (textDecorations == StyleTextDecorationLine::SPELLING_ERROR ||
   5408        textDecorations == StyleTextDecorationLine::GRAMMAR_ERROR) {
   5409      nscolor lineColor;
   5410      float relativeSize;
   5411      useOverride = nsTextPaintStyle::GetSelectionUnderline(
   5412          this, nsTextPaintStyle::SelectionStyleIndex::SpellChecker, &lineColor,
   5413          &relativeSize, &lineStyle);
   5414      if (useOverride) {
   5415        // We don't currently have a SelectionStyleIndex::GrammarChecker; for
   5416        // now just use SpellChecker and change its color to green.
   5417        overrideColor =
   5418            textDecorations == StyleTextDecorationLine::SPELLING_ERROR
   5419                ? lineColor
   5420                : NS_RGBA(0, 128, 0, 255);
   5421        textDecorations = StyleTextDecorationLine::UNDERLINE;
   5422        ignoreSubproperties = true;
   5423      }
   5424    }
   5425 
   5426    if (!useOverride &&
   5427        (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) {
   5428      // This handles the <a href="blah.html"><font color="green">La
   5429      // la la</font></a> case. The link underline should be green.
   5430      useOverride = true;
   5431      overrideColor = nsLayoutUtils::GetTextColor(
   5432          f, &nsStyleTextReset::mTextDecorationColor);
   5433    }
   5434 
   5435    nsBlockFrame* fBlock = do_QueryFrame(f);
   5436    const bool firstBlock = !nearestBlockFound && fBlock;
   5437 
   5438    // Not updating positions once we hit a parent block is equivalent to
   5439    // the CSS 2.1 spec that blocks should propagate decorations down to their
   5440    // children (albeit the style should be preserved)
   5441    // However, if we're vertically aligned within a block, then we need to
   5442    // recover the correct baseline from the line by querying the FrameProperty
   5443    // that should be set (see nsLineLayout::VerticalAlignLine).
   5444    if (firstBlock) {
   5445      // At this point, fChild can't be null since TextFrames can't be blocks
   5446      Maybe<StyleVerticalAlignKeyword> verticalAlign =
   5447          fChild->VerticalAlignEnum();
   5448      if (verticalAlign != Some(StyleVerticalAlignKeyword::Baseline)) {
   5449        // Since offset is the offset in the child's coordinate space, we have
   5450        // to undo the accumulation to bring the transform out of the block's
   5451        // coordinate space
   5452        const nscoord lineBaselineOffset =
   5453            LazyGetLineBaselineOffset(fChild, fBlock);
   5454 
   5455        baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
   5456                         (vertical ? fChild->GetNormalPosition().x
   5457                                   : fChild->GetNormalPosition().y);
   5458      }
   5459    } else if (!nearestBlockFound) {
   5460      // offset here is the offset from f's baseline to f's top/left
   5461      // boundary. It's descent for vertical-rl, and ascent otherwise.
   5462      nscoord offset = wm.IsVerticalRL()
   5463                           ? f->GetSize().width - f->GetLogicalBaseline(wm)
   5464                           : f->GetLogicalBaseline(wm);
   5465      baselineOffset = physicalBlockStartOffset - offset;
   5466    }
   5467 
   5468    nearestBlockFound = nearestBlockFound || firstBlock;
   5469    physicalBlockStartOffset +=
   5470        vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
   5471 
   5472    if (textDecorations) {
   5473      nscolor color;
   5474      if (useOverride) {
   5475        color = overrideColor;
   5476      } else if (IsInSVGTextSubtree()) {
   5477        // XXX We might want to do something with text-decoration-color when
   5478        //     painting SVG text, but it's not clear what we should do.  We
   5479        //     at least need SVG text decorations to paint with 'fill' if
   5480        //     text-decoration-color has its initial value currentColor.
   5481        //     We could choose to interpret currentColor as "currentFill"
   5482        //     for SVG text, and have e.g. text-decoration-color:red to
   5483        //     override the fill paint of the decoration.
   5484        color = aColorResolution == eResolvedColors
   5485                    ? nsLayoutUtils::GetTextColor(f, &nsStyleSVG::mFill)
   5486                    : NS_SAME_AS_FOREGROUND_COLOR;
   5487      } else {
   5488        color = nsLayoutUtils::GetTextColor(
   5489            f, &nsStyleTextReset::mTextDecorationColor);
   5490      }
   5491 
   5492      bool swapUnderlineAndOverline =
   5493          wm.IsCentralBaseline() && IsUnderlineRight(*context);
   5494      const auto kUnderline = swapUnderlineAndOverline
   5495                                  ? StyleTextDecorationLine::OVERLINE
   5496                                  : StyleTextDecorationLine::UNDERLINE;
   5497      const auto kOverline = swapUnderlineAndOverline
   5498                                 ? StyleTextDecorationLine::UNDERLINE
   5499                                 : StyleTextDecorationLine::OVERLINE;
   5500 
   5501      const nsStyleText* const styleText = context->StyleText();
   5502      const auto position = ignoreSubproperties
   5503                                ? StyleTextUnderlinePosition::AUTO
   5504                                : styleText->mTextUnderlinePosition;
   5505      const auto offset = ignoreSubproperties ? LengthPercentageOrAuto::Auto()
   5506                                              : styleText->mTextUnderlineOffset;
   5507      const auto thickness = ignoreSubproperties
   5508                                 ? StyleTextDecorationLength::Auto()
   5509                                 : styleTextReset->mTextDecorationThickness;
   5510 
   5511      if (textDecorations & kUnderline) {
   5512        aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
   5513            f, baselineOffset, position, offset, thickness, color, lineStyle,
   5514            !ignoreSubproperties));
   5515      }
   5516      if (textDecorations & kOverline) {
   5517        aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
   5518            f, baselineOffset, position, offset, thickness, color, lineStyle,
   5519            !ignoreSubproperties));
   5520      }
   5521      if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) {
   5522        aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
   5523            f, baselineOffset, position, offset, thickness, color, lineStyle,
   5524            !ignoreSubproperties));
   5525      }
   5526    }
   5527    if (FrameStopsLineDecorationPropagation(f, compatMode)) {
   5528      break;
   5529    }
   5530  }
   5531 }
   5532 
   5533 static float GetInflationForTextDecorations(nsIFrame* aFrame,
   5534                                            nscoord aInflationMinFontSize) {
   5535  if (aFrame->IsInSVGTextSubtree()) {
   5536    auto* container =
   5537        nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
   5538    MOZ_ASSERT(container);
   5539    return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
   5540  }
   5541  return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
   5542 }
   5543 
   5544 struct EmphasisMarkInfo {
   5545  RefPtr<gfxTextRun> textRun;
   5546  gfxFloat advance;
   5547  gfxFloat baselineOffset;
   5548 };
   5549 
   5550 NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
   5551 
   5552 static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle,
   5553                                           nsAString& aOut) {
   5554  MOZ_ASSERT(!aStyle.IsNone());
   5555  if (aStyle.IsString()) {
   5556    nsDependentCSubstring string = aStyle.AsString().AsString();
   5557    AppendUTF8toUTF16(string, aOut);
   5558    return;
   5559  }
   5560  const auto& keyword = aStyle.AsKeyword();
   5561  const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled;
   5562  switch (keyword.shape) {
   5563    case StyleTextEmphasisShapeKeyword::Dot:
   5564      return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6");
   5565    case StyleTextEmphasisShapeKeyword::Circle:
   5566      return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb");
   5567    case StyleTextEmphasisShapeKeyword::DoubleCircle:
   5568      return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce");
   5569    case StyleTextEmphasisShapeKeyword::Triangle:
   5570      return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3");
   5571    case StyleTextEmphasisShapeKeyword::Sesame:
   5572      return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46");
   5573    default:
   5574      MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape");
   5575  }
   5576 }
   5577 
   5578 static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks(
   5579    nsTextFrame* aFrame, gfxFontGroup* aFontGroup,
   5580    ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) {
   5581  nsAutoString string;
   5582  ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string);
   5583 
   5584  RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
   5585  auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
   5586  gfx::ShapedTextFlags flags =
   5587      nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
   5588  if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
   5589    // The emphasis marks should always be rendered upright per spec.
   5590    flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
   5591  }
   5592  return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt,
   5593                                           appUnitsPerDevUnit, flags,
   5594                                           nsTextFrameUtils::Flags(), nullptr);
   5595 }
   5596 
   5597 static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) {
   5598  nsRubyFrame* rubyFrame = nullptr;
   5599  for (nsIFrame* frame = aFrame->GetParent();
   5600       frame && frame->IsLineParticipant(); frame = frame->GetParent()) {
   5601    if (frame->IsRubyFrame()) {
   5602      rubyFrame = static_cast<nsRubyFrame*>(frame);
   5603    }
   5604  }
   5605  return rubyFrame;
   5606 }
   5607 
   5608 nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM,
   5609                                       PropertyProvider& aProvider) {
   5610  const nsStyleText* styleText = StyleText();
   5611  if (!styleText->HasEffectiveTextEmphasis()) {
   5612    RemoveProperty(EmphasisMarkProperty());
   5613    return nsRect();
   5614  }
   5615 
   5616  ComputedStyle* computedStyle = Style();
   5617  bool isTextCombined = computedStyle->IsTextCombined();
   5618  if (isTextCombined) {
   5619    computedStyle = GetParent()->Style();
   5620  }
   5621  RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
   5622      computedStyle, PresContext(), GetFontSizeInflation());
   5623  EmphasisMarkInfo* info = new EmphasisMarkInfo;
   5624  info->textRun = GenerateTextRunForEmphasisMarks(
   5625      this, fm->GetThebesFontGroup(), computedStyle, styleText);
   5626  info->advance = info->textRun->GetAdvanceWidth();
   5627 
   5628  bool normalizeRubyMetrics = PresContext()->NormalizeRubyMetrics();
   5629  float rubyMetricsFactor =
   5630      normalizeRubyMetrics ? PresContext()->RubyPositioningFactor() : 0.0f;
   5631 
   5632  // Calculate the baseline offset
   5633  LogicalSide side = styleText->TextEmphasisSide(aWM, StyleFont()->mLanguage);
   5634  LogicalSize frameSize = GetLogicalSize(aWM);
   5635  // The overflow rect is inflated in the inline direction by half
   5636  // advance of the emphasis mark on each side, so that even if a mark
   5637  // is drawn for a zero-width character, it won't be clipped.
   5638  LogicalRect overflowRect(
   5639      aWM, -info->advance / 2, /* BStart to be computed below */ 0,
   5640      frameSize.ISize(aWM) + info->advance,
   5641      normalizeRubyMetrics
   5642          ? rubyMetricsFactor * (fm->TrimmedAscent() + fm->TrimmedDescent())
   5643          : fm->MaxAscent() + fm->MaxDescent());
   5644  RefPtr<nsFontMetrics> baseFontMetrics =
   5645      isTextCombined
   5646          ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
   5647          : do_AddRef(aProvider.GetFontMetrics());
   5648  // When the writing mode is vertical-lr the line is inverted, and thus
   5649  // the ascent and descent are swapped.
   5650  bool startSideOrInvertedLine =
   5651      (side == LogicalSide::BStart) != aWM.IsLineInverted();
   5652  nscoord absOffset;
   5653  if (normalizeRubyMetrics) {
   5654    absOffset = startSideOrInvertedLine
   5655                    ? baseFontMetrics->TrimmedAscent() + fm->TrimmedDescent()
   5656                    : baseFontMetrics->TrimmedDescent() + fm->TrimmedAscent();
   5657    absOffset *= rubyMetricsFactor;
   5658  } else {
   5659    absOffset = startSideOrInvertedLine
   5660                    ? baseFontMetrics->MaxAscent() + fm->MaxDescent()
   5661                    : baseFontMetrics->MaxDescent() + fm->MaxAscent();
   5662  }
   5663  RubyBlockLeadings leadings;
   5664  if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
   5665    leadings = ruby->GetBlockLeadings();
   5666    if (normalizeRubyMetrics) {
   5667      // Adjust absOffset to account for any ruby annotations that effectively
   5668      // added to the trimmed height of the base text.
   5669      auto [ascent, descent] = ruby->RubyMetrics(rubyMetricsFactor);
   5670      absOffset = std::max(absOffset, side == LogicalSide::BStart
   5671                                          ? ascent + fm->TrimmedDescent()
   5672                                          : descent + fm->TrimmedAscent());
   5673    }
   5674  }
   5675  if (side == LogicalSide::BStart) {
   5676    info->baselineOffset =
   5677        normalizeRubyMetrics ? -absOffset : -absOffset - leadings.mStart;
   5678    overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
   5679  } else {
   5680    MOZ_ASSERT(side == LogicalSide::BEnd);
   5681    info->baselineOffset =
   5682        normalizeRubyMetrics ? absOffset : absOffset + leadings.mEnd;
   5683    overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
   5684  }
   5685  // If text combined, fix the gap between the text frame and its parent.
   5686  if (isTextCombined) {
   5687    nscoord height =
   5688        normalizeRubyMetrics
   5689            ? rubyMetricsFactor * (baseFontMetrics->TrimmedAscent() +
   5690                                   baseFontMetrics->TrimmedDescent())
   5691            : baseFontMetrics->MaxHeight();
   5692    nscoord gap = (height - frameSize.BSize(aWM)) / 2;
   5693    overflowRect.BStart(aWM) += gap * (side == LogicalSide::BStart ? -1 : 1);
   5694  }
   5695 
   5696  SetProperty(EmphasisMarkProperty(), info);
   5697  return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
   5698 }
   5699 
   5700 // helper function for implementing text-decoration-thickness
   5701 // https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property
   5702 // Returns the thickness in device pixels.
   5703 static gfxFloat ComputeDecorationLineThickness(
   5704    const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue,
   5705    const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel,
   5706    const nsIFrame* aFrame) {
   5707  if (aThickness.IsAuto()) {
   5708    return widget::ThemeDrawing::SnapBorderWidth(aAutoValue);
   5709  }
   5710  if (aThickness.IsFromFont()) {
   5711    return widget::ThemeDrawing::SnapBorderWidth(aFontMetrics.underlineSize);
   5712  }
   5713  auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
   5714  return widget::ThemeDrawing::SnapBorderWidth(
   5715      aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel);
   5716 }
   5717 
   5718 // Helper function for implementing text-underline-offset and -position
   5719 // https://drafts.csswg.org/css-text-decor-4/#underline-offset
   5720 // Returns the offset in device pixels.
   5721 static gfxFloat ComputeDecorationLineOffset(
   5722    StyleTextDecorationLine aLineType,
   5723    const StyleTextUnderlinePosition& aPosition,
   5724    const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics,
   5725    const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame,
   5726    bool aIsCentralBaseline, bool aSwappedUnderline) {
   5727  // Em value to use if we need to resolve a percentage length.
   5728  auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
   5729  // If we're in vertical-upright typographic mode, we need to compute the
   5730  // offset of the decoration line from the default central baseline.
   5731  if (aIsCentralBaseline) {
   5732    // Line-through simply goes at the (central) baseline.
   5733    if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
   5734      return 0;
   5735    }
   5736 
   5737    // Compute "zero position" for the under- or overline.
   5738    gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight;
   5739 
   5740    // aOffset applies to underline only; for overline (or offset:auto) we use
   5741    // a somewhat arbitrary offset of half the font's (horziontal-mode) value
   5742    // for underline-offset, to get a little bit of separation between glyph
   5743    // edges and the line in typical cases.
   5744    // If we have swapped under-/overlines for text-underline-position:right,
   5745    // we need to take account of this to determine which decoration lines are
   5746    // "real" underlines which should respect the text-underline-* values.
   5747    bool isUnderline =
   5748        (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline;
   5749    gfxFloat offset =
   5750        isUnderline && !aOffset.IsAuto()
   5751            ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel
   5752            : aFontMetrics.underlineOffset * -0.5;
   5753 
   5754    // Direction of the decoration line's offset from the central baseline.
   5755    gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0;
   5756    return dir * (zeroPos + offset);
   5757  }
   5758 
   5759  // Compute line offset for horizontal typographic mode.
   5760  if (aLineType == StyleTextDecorationLine::UNDERLINE) {
   5761    if (aPosition.IsFromFont()) {
   5762      gfxFloat zeroPos = aFontMetrics.underlineOffset;
   5763      gfxFloat offset =
   5764          aOffset.IsAuto()
   5765              ? 0
   5766              : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
   5767      return zeroPos - offset;
   5768    }
   5769 
   5770    if (aPosition.IsUnder()) {
   5771      gfxFloat zeroPos = -aFontMetrics.maxDescent;
   5772      gfxFloat offset =
   5773          aOffset.IsAuto()
   5774              ? -0.5 * aFontMetrics.underlineOffset
   5775              : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
   5776      return zeroPos - offset;
   5777    }
   5778 
   5779    // text-underline-position must be 'auto', so zero position is the
   5780    // baseline and 'auto' offset will apply the font's underline-offset.
   5781    //
   5782    // If offset is `auto`, we clamp the offset (in horizontal typographic mode)
   5783    // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate
   5784    // skip-ink issues with fonts that leave the underlineOffset field as zero.
   5785    MOZ_ASSERT(aPosition.IsAuto());
   5786    return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset,
   5787                                       -aFontMetrics.emHeight / 16.0)
   5788                            : -aOffset.AsLengthPercentage().Resolve(em) /
   5789                                  aAppUnitsPerDevPixel;
   5790  }
   5791 
   5792  if (aLineType == StyleTextDecorationLine::OVERLINE) {
   5793    return aFontMetrics.maxAscent;
   5794  }
   5795 
   5796  if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
   5797    return aFontMetrics.strikeoutOffset;
   5798  }
   5799 
   5800  MOZ_ASSERT_UNREACHABLE("unknown decoration line type");
   5801  return 0;
   5802 }
   5803 
   5804 // Helper to determine decoration inset.
   5805 // Returns false if the inset would cut off the decoration entirely.
   5806 // If aOnlyExtend is true, this will only consider cases with negative inset
   5807 // (i.e. the line will actually be extended beyond the normal length).
   5808 static bool ComputeDecorationInset(
   5809    nsTextFrame* aFrame, const nsPresContext* aPresCtx,
   5810    const nsIFrame* aDecFrame, const gfxFont::Metrics& aMetrics,
   5811    nsCSSRendering::DecorationRectParams& aParams, bool aOnlyExtend = false) {
   5812  const WritingMode wm = aDecFrame->GetWritingMode();
   5813  bool verticalDec = wm.IsVertical();
   5814 
   5815  aParams.insetLeft = 0.0;
   5816  aParams.insetRight = 0.0;
   5817 
   5818  // Find the decoration-line inset values for this frame.
   5819  const StyleTextDecorationInset& cssInset =
   5820      aDecFrame->StyleTextReset()->mTextDecorationInset;
   5821  nscoord insetLeft, insetRight;
   5822  if (cssInset.IsAuto()) {
   5823    // Use an inset factor of 1/12.5, so we get 2px of inset (resulting in 4px
   5824    // gap between adjacent lines) at font-size 25px.
   5825    constexpr gfxFloat kAutoInsetFactor = 1.0 / 12.5;
   5826    // Use the EM size multiplied by kAutoInsetFactor, with a minimum of one
   5827    // CSS pixel to ensure that at least some separation occurs.
   5828    const nscoord autoDecorationInset =
   5829        std::max(aPresCtx->DevPixelsToAppUnits(
   5830                     NS_round(aMetrics.emHeight * kAutoInsetFactor)),
   5831                 nsPresContext::CSSPixelsToAppUnits(1));
   5832    insetLeft = autoDecorationInset;
   5833    insetRight = autoDecorationInset;
   5834  } else {
   5835    MOZ_ASSERT(cssInset.IsLength(), "Impossible text-decoration-inset");
   5836    const auto& length = cssInset.AsLength();
   5837    if (length.start.IsZero() && length.end.IsZero()) {
   5838      // We can avoid doing the geometric calculations below, potentially
   5839      // walking up and back down the frame tree, and walking continuations.
   5840      return true;
   5841    }
   5842    insetLeft = length.start.ToAppUnits();
   5843    insetRight = length.end.ToAppUnits();
   5844  }
   5845 
   5846  // If we only care about extended lines (for UnionAdditionalOverflow),
   5847  // we can bail out if neither inset value is negative.
   5848  if (aOnlyExtend && insetLeft >= 0 && insetRight >= 0) {
   5849    return true;
   5850  }
   5851 
   5852  if (wm.IsInlineReversed()) {
   5853    std::swap(insetLeft, insetRight);
   5854  }
   5855 
   5856  // The rect of the decorating box (if an inline) or of the current line (if
   5857  // the decoration is propagated from a block ancestor). We will need to
   5858  // compare this with the rect of the current frame, which may be only a
   5859  // sub-range of the entire decorated range.
   5860  nsRect decRect;
   5861 
   5862  // The container of the decoration, or the fragment of it on this line.
   5863  const nsIFrame* decContainer;
   5864 
   5865  // If the decorating frame is an inline frame, we can use it as the
   5866  // reference frame for measurements.
   5867  // If the decorating frame is not inline, then we will need to consider
   5868  // text indentation and calculate geometry using line boxes.
   5869  if (aDecFrame->IsInlineFrame()) {
   5870    decRect = aDecFrame->GetContentRectRelativeToSelf();
   5871    decContainer = aDecFrame;
   5872  } else {
   5873    nsIFrame* const lineContainer = FindLineContainer(aFrame);
   5874    // If the frame specifies text-combined, then it might have an orthogonal
   5875    // writing mode to the line container.
   5876    MOZ_ASSERT(
   5877        !wm.IsOrthogonalTo(lineContainer->GetWritingMode()) ||
   5878            aFrame->Style()->IsTextCombined(),
   5879        "Decorating frame and line container must have writing modes in the "
   5880        "same axis");
   5881    if (nsILineIterator* const iter = lineContainer->GetLineIterator()) {
   5882      const int32_t lineNum = GetFrameLineNum(aFrame, iter);
   5883      const nsILineIterator::LineInfo lineInfo =
   5884          iter->GetLine(lineNum).unwrap();
   5885      decRect = lineInfo.mLineBounds;
   5886 
   5887      // Account for text-indent, which will push text frames into the line box.
   5888      const StyleTextIndent& textIndent = aFrame->StyleText()->mTextIndent;
   5889      if (!textIndent.length.IsDefinitelyZero()) {
   5890        bool isFirstLineOrAfterHardBreak = true;
   5891        if (lineNum > 0 && !textIndent.each_line) {
   5892          isFirstLineOrAfterHardBreak = false;
   5893        } else if (nsBlockFrame* prevBlock =
   5894                       do_QueryFrame(lineContainer->GetPrevInFlow())) {
   5895          if (!(textIndent.each_line &&
   5896                (prevBlock->Lines().empty() ||
   5897                 !prevBlock->LinesEnd().prev()->IsLineWrapped()))) {
   5898            isFirstLineOrAfterHardBreak = false;
   5899          }
   5900        }
   5901        if (isFirstLineOrAfterHardBreak != textIndent.hanging) {
   5902          // Determine which side to shrink.
   5903          const Side side = wm.PhysicalSide(LogicalSide::IStart);
   5904          // Calculate the text indent, and shrink the line box by this amount
   5905          // to account for the indent size at the start of the line.
   5906          const nscoord basis = lineContainer->GetLogicalSize(wm).ISize(wm);
   5907          nsMargin indentMargin;
   5908          indentMargin.Side(side) = textIndent.length.Resolve(basis);
   5909          decRect.Deflate(indentMargin);
   5910        }
   5911      }
   5912 
   5913      // We can't allow a block frame to retain a line iterator if we're
   5914      // currently in reflow, as it will become invalid as the line list is
   5915      // reflowed.
   5916      if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) &&
   5917          lineContainer->IsBlockFrameOrSubclass()) {
   5918        static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator();
   5919      }
   5920    } else {
   5921      // Not a block or similar container with multiple lines; just use the
   5922      // content rect directly.
   5923      decRect = lineContainer->GetContentRectRelativeToSelf();
   5924    }
   5925 
   5926    decContainer = lineContainer;
   5927  }
   5928 
   5929  // The rect of the current frame, mapped to the same coordinate space as
   5930  // decRect so that we can compare their edges.
   5931  const nsRect frameRect =
   5932      aFrame->GetRectRelativeToSelf() + aFrame->GetOffsetTo(decContainer);
   5933 
   5934  // The nominal size of the decoration (prior to insets being applied) is
   5935  // reduced by any margin, border, and padding present on frames intervening
   5936  // between aFrame and decContainer.
   5937  for (const nsIFrame* parent = aFrame->GetParent(); parent != decContainer;
   5938       parent = parent->GetParent()) {
   5939    decRect.Deflate(parent->GetUsedMargin());
   5940    decRect.Deflate(parent->GetUsedBorderAndPadding());
   5941  }
   5942 
   5943  // Find the margin of the of this frame inside its container.
   5944  nscoord marginLeft, marginRight, frameSize;
   5945  const nsMargin difference = decRect - frameRect;
   5946  if (verticalDec) {
   5947    marginLeft = difference.top;
   5948    marginRight = difference.bottom;
   5949    frameSize = frameRect.height;
   5950  } else {
   5951    marginLeft = difference.left;
   5952    marginRight = difference.right;
   5953    frameSize = frameRect.width;
   5954  }
   5955 
   5956  const bool cloneDecBreak = aDecFrame->StyleBorder()->mBoxDecorationBreak ==
   5957                             StyleBoxDecorationBreak::Clone;
   5958  // TODO alaskanemily: This will not correctly account for the case that the
   5959  // continuations are bidi continuations.
   5960  bool applyLeft = cloneDecBreak || (!aFrame->GetPrevContinuation() &&
   5961                                     !aDecFrame->GetPrevContinuation());
   5962  bool applyRight = cloneDecBreak || (!aFrame->GetNextContinuation() &&
   5963                                      !aDecFrame->GetNextContinuation());
   5964  if (wm.IsInlineReversed()) {
   5965    std::swap(applyLeft, applyRight);
   5966  }
   5967  if (applyLeft) {
   5968    insetLeft -= marginLeft;
   5969  } else {
   5970    insetLeft = 0;
   5971  }
   5972  if (applyRight) {
   5973    insetRight -= marginRight;
   5974  } else {
   5975    insetRight = 0;
   5976  }
   5977 
   5978  if (insetLeft + insetRight >= frameSize) {
   5979    // This frame does not contain the decoration at all.
   5980    return false;
   5981  }
   5982  // TODO alaskanemily: We currently determine if we should have a negative
   5983  // inset value by checking if we are at the edge of frame from which the
   5984  // decloration comes from.
   5985  //
   5986  // This is not absolutely correct, there could in theory be a zero-width
   5987  // frame before/after this frame, and we will draw the decoration extension
   5988  // twice (causing a visible inaccuracy for semi-transparent decorations).
   5989  //
   5990  // I am unsure if it's possible that the first/last frame might be inset
   5991  // for some reason, as well, in which case we will not draw the outset
   5992  // decorations.
   5993  if (insetLeft > 0 || marginLeft == 0) {
   5994    aParams.insetLeft = aPresCtx->AppUnitsToFloatDevPixels(insetLeft);
   5995  }
   5996  if (insetRight > 0 || marginRight == 0) {
   5997    aParams.insetRight = aPresCtx->AppUnitsToFloatDevPixels(insetRight);
   5998  }
   5999  return true;
   6000 }
   6001 
   6002 void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
   6003                                          nsIFrame* aBlock,
   6004                                          PropertyProvider& aProvider,
   6005                                          nsRect* aInkOverflowRect,
   6006                                          bool aIncludeTextDecorations,
   6007                                          bool aIncludeShadows) {
   6008  const WritingMode wm = GetWritingMode();
   6009  bool verticalRun = mTextRun->IsVertical();
   6010  const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
   6011 
   6012  if (IsFloatingFirstLetterChild()) {
   6013    bool inverted = wm.IsLineInverted();
   6014    // The underline/overline drawable area must be contained in the overflow
   6015    // rect when this is in floating first letter frame at *both* modes.
   6016    // In this case, aBlock is the ::first-letter frame.
   6017    auto decorationStyle =
   6018        aBlock->Style()->StyleTextReset()->mTextDecorationStyle;
   6019    // If the style is none, let's include decoration line rect as solid style
   6020    // since changing the style from none to solid/dotted/dashed doesn't cause
   6021    // reflow.
   6022    if (decorationStyle == StyleTextDecorationStyle::None) {
   6023      decorationStyle = StyleTextDecorationStyle::Solid;
   6024    }
   6025    nsCSSRendering::DecorationRectParams params;
   6026    bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
   6027    nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
   6028    RefPtr<gfxFont> font =
   6029        fontMetrics->GetThebesFontGroup()->GetFirstValidFont();
   6030    const gfxFont::Metrics& metrics =
   6031        font->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
   6032                                            : nsFontMetrics::eHorizontal);
   6033 
   6034    params.defaultLineThickness = metrics.underlineSize;
   6035    params.lineSize.height = ComputeDecorationLineThickness(
   6036        aBlock->Style()->StyleTextReset()->mTextDecorationThickness,
   6037        params.defaultLineThickness, metrics, appUnitsPerDevUnit, this);
   6038 
   6039    const auto* styleText = aBlock->StyleText();
   6040    bool swapUnderline =
   6041        wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style());
   6042    params.offset = ComputeDecorationLineOffset(
   6043        StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition,
   6044        styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this,
   6045        wm.IsCentralBaseline(), swapUnderline);
   6046 
   6047    nscoord maxAscent =
   6048        inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent();
   6049 
   6050    Float gfxWidth =
   6051        (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) /
   6052        appUnitsPerDevUnit;
   6053    params.lineSize.width = gfxWidth;
   6054    params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
   6055    params.style = decorationStyle;
   6056    params.vertical = verticalRun;
   6057    params.sidewaysLeft = mTextRun->IsSidewaysLeft();
   6058    params.decoration = StyleTextDecorationLine::UNDERLINE;
   6059    nsRect underlineRect =
   6060        nsCSSRendering::GetTextDecorationRect(aPresContext, params);
   6061 
   6062    // TODO(jfkthame):
   6063    // Should we actually be calling ComputeDecorationLineOffset again here?
   6064    params.offset = maxAscent / appUnitsPerDevUnit;
   6065    params.decoration = StyleTextDecorationLine::OVERLINE;
   6066    nsRect overlineRect =
   6067        nsCSSRendering::GetTextDecorationRect(aPresContext, params);
   6068 
   6069    aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect);
   6070    aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect);
   6071 
   6072    // XXX If strikeoutSize is much thicker than the underlineSize, it may
   6073    //     cause overflowing from the overflow rect.  However, such case
   6074    //     isn't realistic, we don't need to compute it now.
   6075  }
   6076  if (aIncludeTextDecorations) {
   6077    // Use writing mode of parent frame for orthogonal text frame to
   6078    // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
   6079    WritingMode parentWM = GetParent()->GetWritingMode();
   6080    bool verticalDec = parentWM.IsVertical();
   6081    bool useVerticalMetrics =
   6082        verticalDec != verticalRun
   6083            ? verticalDec
   6084            : verticalRun && mTextRun->UseCenterBaseline();
   6085 
   6086    // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
   6087    // style and position, they can be drawn at virtually any y-offset, so
   6088    // maxima and minima are required to reliably generate the rectangle for
   6089    // them
   6090    TextDecorations textDecs;
   6091    GetTextDecorations(aPresContext, eResolvedColors, textDecs);
   6092    if (textDecs.HasDecorationLines()) {
   6093      nscoord inflationMinFontSize =
   6094          nsLayoutUtils::InflationMinFontSizeFor(aBlock);
   6095 
   6096      const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
   6097      gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
   6098      gfxFloat ascent =
   6099          gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit;
   6100      nscoord frameBStart = 0;
   6101      if (parentWM.IsVerticalRL()) {
   6102        frameBStart = GetSize().width;
   6103        ascent = -ascent;
   6104      }
   6105 
   6106      nsCSSRendering::DecorationRectParams params;
   6107      params.lineSize = Size(gfxWidth, 0);
   6108      params.ascent = ascent;
   6109      params.vertical = verticalDec;
   6110      params.sidewaysLeft = mTextRun->IsSidewaysLeft();
   6111 
   6112      typedef gfxFont::Metrics Metrics;
   6113      auto accumulateDecorationRect =
   6114          [&](const LineDecoration& dec, gfxFloat Metrics::* lineSize,
   6115              mozilla::StyleTextDecorationLine lineType) {
   6116            params.style = dec.mStyle;
   6117            // If the style is solid, let's include decoration line rect of
   6118            // solid style since changing the style from none to
   6119            // solid/dotted/dashed doesn't cause reflow.
   6120            if (params.style == StyleTextDecorationStyle::None) {
   6121              params.style = StyleTextDecorationStyle::Solid;
   6122            }
   6123 
   6124            float inflation = GetInflationForTextDecorations(
   6125                dec.mFrame, inflationMinFontSize);
   6126            const Metrics metrics =
   6127                GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
   6128                                    useVerticalMetrics);
   6129 
   6130            params.defaultLineThickness = metrics.*lineSize;
   6131            params.lineSize.height = ComputeDecorationLineThickness(
   6132                dec.mTextDecorationThickness, params.defaultLineThickness,
   6133                metrics, appUnitsPerDevUnit, this);
   6134 
   6135            bool swapUnderline =
   6136                parentWM.IsCentralBaseline() && IsUnderlineRight(*Style());
   6137            params.offset = ComputeDecorationLineOffset(
   6138                lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset,
   6139                metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(),
   6140                swapUnderline);
   6141 
   6142            if (!ComputeDecorationInset(this, aPresContext, dec.mFrame, metrics,
   6143                                        params, /* aOnlyExtend = */ true)) {
   6144              return;
   6145            }
   6146 
   6147            nsRect decorationRect =
   6148                nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
   6149                (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
   6150                             : nsPoint(0, -dec.mBaselineOffset));
   6151 
   6152            aInkOverflowRect->UnionRect(*aInkOverflowRect, decorationRect);
   6153          };
   6154 
   6155      // Below we loop through all text decorations and compute the rectangle
   6156      // containing all of them, in this frame's coordinate space
   6157      params.decoration = StyleTextDecorationLine::UNDERLINE;
   6158      for (const LineDecoration& dec : textDecs.mUnderlines) {
   6159        accumulateDecorationRect(dec, &Metrics::underlineSize,
   6160                                 params.decoration);
   6161      }
   6162      params.decoration = StyleTextDecorationLine::OVERLINE;
   6163      for (const LineDecoration& dec : textDecs.mOverlines) {
   6164        accumulateDecorationRect(dec, &Metrics::underlineSize,
   6165                                 params.decoration);
   6166      }
   6167      params.decoration = StyleTextDecorationLine::LINE_THROUGH;
   6168      for (const LineDecoration& dec : textDecs.mStrikes) {
   6169        accumulateDecorationRect(dec, &Metrics::strikeoutSize,
   6170                                 params.decoration);
   6171      }
   6172    }
   6173 
   6174    aInkOverflowRect->UnionRect(*aInkOverflowRect,
   6175                                UpdateTextEmphasis(parentWM, aProvider));
   6176  }
   6177 
   6178  // text-stroke overflows: add half of text-stroke-width on all sides
   6179  nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
   6180  if (textStrokeWidth > 0) {
   6181    // Inflate rect by stroke-width/2; we add an extra pixel to allow for
   6182    // antialiasing, rounding errors, etc.
   6183    nsRect strokeRect = *aInkOverflowRect;
   6184    strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
   6185    aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect);
   6186  }
   6187 
   6188  // Text-shadow overflows
   6189  if (aIncludeShadows) {
   6190    *aInkOverflowRect =
   6191        nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this);
   6192  }
   6193 
   6194  // When this frame is not selected, the text-decoration area must be in
   6195  // frame bounds.
   6196  if (!IsSelected() ||
   6197      !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect)) {
   6198    return;
   6199  }
   6200  AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
   6201 }
   6202 
   6203 nscoord nsTextFrame::ComputeLineHeight() const {
   6204  return ReflowInput::CalcLineHeight(*Style(), PresContext(), GetContent(),
   6205                                     NS_UNCONSTRAINEDSIZE,
   6206                                     GetFontSizeInflation());
   6207 }
   6208 
   6209 gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline(
   6210    nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) {
   6211  const gfxFloat lineHeight =
   6212      gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel();
   6213  if (lineHeight <= aFontMetrics.maxHeight) {
   6214    return aFontMetrics.maxDescent;
   6215  }
   6216  return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
   6217 }
   6218 
   6219 // Make sure this stays in sync with DrawSelectionDecorations below
   6220 static constexpr SelectionTypeMask kSelectionTypesWithDecorations =
   6221    ToSelectionTypeMask(SelectionType::eNormal) |
   6222    ToSelectionTypeMask(SelectionType::eTargetText) |
   6223    ToSelectionTypeMask(SelectionType::eHighlight) |
   6224    ToSelectionTypeMask(SelectionType::eSpellCheck) |
   6225    ToSelectionTypeMask(SelectionType::eURLStrikeout) |
   6226    ToSelectionTypeMask(SelectionType::eIMERawClause) |
   6227    ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
   6228    ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
   6229    ToSelectionTypeMask(SelectionType::eIMESelectedClause);
   6230 
   6231 /* static */
   6232 gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight(
   6233    nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
   6234    SelectionType aSelectionType) {
   6235  switch (aSelectionType) {
   6236    case SelectionType::eNormal:
   6237    case SelectionType::eTargetText:
   6238    case SelectionType::eHighlight:
   6239    case SelectionType::eIMERawClause:
   6240    case SelectionType::eIMESelectedRawClause:
   6241    case SelectionType::eIMEConvertedClause:
   6242    case SelectionType::eIMESelectedClause:
   6243      return aFontMetrics.underlineSize;
   6244    case SelectionType::eSpellCheck: {
   6245      // The thickness of the spellchecker underline shouldn't honor the font
   6246      // metrics.  It should be constant pixels value which is decided from the
   6247      // default font size.  Note that if the actual font size is smaller than
   6248      // the default font size, we should use the actual font size because the
   6249      // computed value from the default font size can be too thick for the
   6250      // current font size.
   6251      Length defaultFontSize =
   6252          aPresContext->Document()
   6253              ->GetFontPrefsForLang(nullptr)
   6254              ->GetDefaultFont(StyleGenericFontFamily::None)
   6255              ->size;
   6256      int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels(
   6257          nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize)
   6258              .ToCSSPixels());
   6259      gfxFloat fontSize =
   6260          std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight);
   6261      fontSize = std::max(fontSize, 1.0);
   6262      return ceil(fontSize / 20);
   6263    }
   6264    default:
   6265      NS_WARNING("Requested underline style is not valid");
   6266      return aFontMetrics.underlineSize;
   6267  }
   6268 }
   6269 
   6270 enum class DecorationType { Normal, Selection };
   6271 struct nsTextFrame::PaintDecorationLineParams
   6272    : nsCSSRendering::DecorationRectParams {
   6273  gfxContext* context = nullptr;
   6274  LayoutDeviceRect dirtyRect;
   6275  Point pt;
   6276  const nscolor* overrideColor = nullptr;
   6277  nscolor color = NS_RGBA(0, 0, 0, 0);
   6278  gfxFloat icoordInFrame = 0.0f;
   6279  gfxFloat baselineOffset = 0.0f;
   6280  DecorationType decorationType = DecorationType::Normal;
   6281  DrawPathCallbacks* callbacks = nullptr;
   6282  bool paintingShadows = false;
   6283  bool allowInkSkipping = true;
   6284  StyleTextDecorationSkipInk skipInk = StyleTextDecorationSkipInk::None;
   6285 };
   6286 
   6287 void nsTextFrame::PaintDecorationLine(
   6288    const PaintDecorationLineParams& aParams) {
   6289  nsCSSRendering::PaintDecorationLineParams params;
   6290  static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
   6291  params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
   6292  params.pt = aParams.pt;
   6293  params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
   6294  params.icoordInFrame = Float(aParams.icoordInFrame);
   6295  params.baselineOffset = Float(aParams.baselineOffset);
   6296  // Disable ink-skipping for frames with text-combine-upright.
   6297  params.allowInkSkipping =
   6298      aParams.allowInkSkipping && !Style()->IsTextCombined();
   6299  params.skipInk = aParams.skipInk;
   6300  if (aParams.callbacks) {
   6301    Rect path = nsCSSRendering::DecorationLineToPath(params);
   6302    if (aParams.decorationType == DecorationType::Normal) {
   6303      aParams.callbacks->PaintDecorationLine(path, aParams.paintingShadows,
   6304                                             params.color);
   6305    } else {
   6306      aParams.callbacks->PaintSelectionDecorationLine(
   6307          path, aParams.paintingShadows, params.color);
   6308    }
   6309  } else {
   6310    nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(),
   6311                                        params);
   6312  }
   6313 }
   6314 
   6315 static StyleTextDecorationStyle ToStyleLineStyle(const TextRangeStyle& aStyle) {
   6316  switch (aStyle.mLineStyle) {
   6317    case TextRangeStyle::LineStyle::None:
   6318      return StyleTextDecorationStyle::None;
   6319    case TextRangeStyle::LineStyle::Solid:
   6320      return StyleTextDecorationStyle::Solid;
   6321    case TextRangeStyle::LineStyle::Dotted:
   6322      return StyleTextDecorationStyle::Dotted;
   6323    case TextRangeStyle::LineStyle::Dashed:
   6324      return StyleTextDecorationStyle::Dashed;
   6325    case TextRangeStyle::LineStyle::Double:
   6326      return StyleTextDecorationStyle::Double;
   6327    case TextRangeStyle::LineStyle::Wavy:
   6328      return StyleTextDecorationStyle::Wavy;
   6329  }
   6330  MOZ_ASSERT_UNREACHABLE("Invalid line style");
   6331  return StyleTextDecorationStyle::None;
   6332 }
   6333 
   6334 /**
   6335 * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
   6336 * about drawing text decoration for selections.
   6337 */
   6338 void nsTextFrame::DrawSelectionDecorations(
   6339    gfxContext* aContext, const LayoutDeviceRect& aDirtyRect,
   6340    SelectionType aSelectionType, nsAtom* aHighlightName,
   6341    nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle,
   6342    const Point& aPt, gfxFloat aICoordInFrame, gfxFloat aWidth,
   6343    gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics,
   6344    DrawPathCallbacks* aCallbacks, bool aVertical,
   6345    StyleTextDecorationLine aDecoration, const Range& aGlyphRange,
   6346    PropertyProvider* aProvider) {
   6347  PaintDecorationLineParams params;
   6348  params.context = aContext;
   6349  params.dirtyRect = aDirtyRect;
   6350  params.pt = aPt;
   6351  params.lineSize.width = aWidth;
   6352  params.ascent = aAscent;
   6353  params.decoration = aDecoration;
   6354  params.decorationType = DecorationType::Selection;
   6355  params.callbacks = aCallbacks;
   6356  params.vertical = aVertical;
   6357  params.sidewaysLeft = mTextRun->IsSidewaysLeft();
   6358  params.descentLimit = ComputeDescentLimitForSelectionUnderline(
   6359      aTextPaintStyle.PresContext(), aFontMetrics);
   6360  params.glyphRange = aGlyphRange;
   6361  params.provider = aProvider;
   6362 
   6363  float relativeSize = 1.f;
   6364  const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
   6365  const gfxFloat appUnitsPerDevPixel =
   6366      aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
   6367 
   6368  const WritingMode wm = GetWritingMode();
   6369  switch (aSelectionType) {
   6370    case SelectionType::eNormal:
   6371    case SelectionType::eHighlight:
   6372    case SelectionType::eTargetText: {
   6373      RefPtr computedStyleFromPseudo =
   6374          aTextPaintStyle.GetComputedStyleForSelectionPseudo(aSelectionType,
   6375                                                             aHighlightName);
   6376      const bool hasTextDecorations =
   6377          computedStyleFromPseudo
   6378              ? computedStyleFromPseudo->HasTextDecorationLines()
   6379              : false;
   6380      if (!hasTextDecorations) {
   6381        return;
   6382      }
   6383      params.style =
   6384          computedStyleFromPseudo->StyleTextReset()->mTextDecorationStyle;
   6385      params.color = computedStyleFromPseudo->StyleTextReset()
   6386                         ->mTextDecorationColor.CalcColor(this);
   6387      params.decoration =
   6388          computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine;
   6389      params.descentLimit = -1.f;
   6390      params.defaultLineThickness = ComputeSelectionUnderlineHeight(
   6391          aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
   6392      params.lineSize.height = ComputeDecorationLineThickness(
   6393          computedStyleFromPseudo->StyleTextReset()->mTextDecorationThickness,
   6394          params.defaultLineThickness, aFontMetrics, appUnitsPerDevPixel, this);
   6395 
   6396      const bool swapUnderline =
   6397          wm.IsCentralBaseline() && IsUnderlineRight(*Style());
   6398      params.icoordInFrame = aICoordInFrame;
   6399      auto paintForLine = [&](StyleTextDecorationLine decoration) {
   6400        if (!(computedStyleFromPseudo->StyleTextReset()->mTextDecorationLine &
   6401              decoration)) {
   6402          return;
   6403        }
   6404 
   6405        params.allowInkSkipping = true;
   6406        params.skipInk =
   6407            computedStyleFromPseudo->StyleText()->mTextDecorationSkipInk;
   6408        params.decoration = decoration;
   6409        params.offset = ComputeDecorationLineOffset(
   6410            params.decoration,
   6411            computedStyleFromPseudo->StyleText()->mTextUnderlinePosition,
   6412            computedStyleFromPseudo->StyleText()->mTextUnderlineOffset,
   6413            aFontMetrics, appUnitsPerDevPixel, this, wm.IsCentralBaseline(),
   6414            swapUnderline);
   6415 
   6416        PaintDecorationLine(params);
   6417      };
   6418      paintForLine(StyleTextDecorationLine::UNDERLINE);
   6419      paintForLine(StyleTextDecorationLine::OVERLINE);
   6420      paintForLine(StyleTextDecorationLine::LINE_THROUGH);
   6421      return;
   6422    }
   6423    case SelectionType::eIMERawClause:
   6424    case SelectionType::eIMESelectedRawClause:
   6425    case SelectionType::eIMEConvertedClause:
   6426    case SelectionType::eIMESelectedClause:
   6427    case SelectionType::eSpellCheck: {
   6428      auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
   6429          aSelectionType);
   6430      bool weDefineSelectionUnderline =
   6431          aTextPaintStyle.GetSelectionUnderlineForPaint(
   6432              index, &params.color, &relativeSize, &params.style);
   6433      params.defaultLineThickness = ComputeSelectionUnderlineHeight(
   6434          aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
   6435      params.lineSize.height = ComputeDecorationLineThickness(
   6436          decThickness, params.defaultLineThickness, aFontMetrics,
   6437          appUnitsPerDevPixel, this);
   6438 
   6439      bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
   6440      const auto* styleText = StyleText();
   6441      params.offset = ComputeDecorationLineOffset(
   6442          aDecoration, styleText->mTextUnderlinePosition,
   6443          styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel,
   6444          this, wm.IsCentralBaseline(), swapUnderline);
   6445 
   6446      bool isIMEType = aSelectionType != SelectionType::eSpellCheck;
   6447 
   6448      if (isIMEType) {
   6449        // IME decoration lines should not be drawn on the both ends, i.e., we
   6450        // need to cut both edges of the decoration lines.  Because same style
   6451        // IME selections can adjoin, but the users need to be able to know
   6452        // where are the boundaries of the selections.
   6453        //
   6454        //  X: underline
   6455        //
   6456        //     IME selection #1        IME selection #2      IME selection #3
   6457        //  |                     |                      |
   6458        //  | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
   6459        //  +---------------------+----------------------+--------------------
   6460        //   ^                   ^ ^                    ^ ^
   6461        //  gap                  gap                    gap
   6462        params.pt.x += 1.0;
   6463        params.lineSize.width -= 2.0;
   6464      }
   6465      if (isIMEType && aRangeStyle.IsDefined()) {
   6466        // If IME defines the style, that should override our definition.
   6467        if (aRangeStyle.IsLineStyleDefined()) {
   6468          if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
   6469            return;
   6470          }
   6471          params.style = ToStyleLineStyle(aRangeStyle);
   6472          relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
   6473        } else if (!weDefineSelectionUnderline) {
   6474          // There is no underline style definition.
   6475          return;
   6476        }
   6477        // If underline color is defined and that doesn't depend on the
   6478        // foreground color, we should use the color directly.
   6479        if (aRangeStyle.IsUnderlineColorDefined() &&
   6480            (!aRangeStyle.IsForegroundColorDefined() ||
   6481             aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
   6482          params.color = aRangeStyle.mUnderlineColor;
   6483        }
   6484        // If foreground color or background color is defined, the both colors
   6485        // are computed by GetSelectionTextColors().  Then, we should use its
   6486        // foreground color always.  The color should have sufficient contrast
   6487        // with the background color.
   6488        else if (aRangeStyle.IsForegroundColorDefined() ||
   6489                 aRangeStyle.IsBackgroundColorDefined()) {
   6490          nscolor bg;
   6491          GetSelectionTextColors(aSelectionType, nullptr, aTextPaintStyle,
   6492                                 aRangeStyle, &params.color, &bg);
   6493        }
   6494        // Otherwise, use the foreground color of the frame.
   6495        else {
   6496          params.color = aTextPaintStyle.GetTextColor();
   6497        }
   6498      } else if (!weDefineSelectionUnderline) {
   6499        // IME doesn't specify the selection style and we don't define selection
   6500        // underline.
   6501        return;
   6502      }
   6503      break;
   6504    }
   6505    case SelectionType::eURLStrikeout: {
   6506      nscoord inflationMinFontSize =
   6507          nsLayoutUtils::InflationMinFontSizeFor(this);
   6508      float inflation =
   6509          GetInflationForTextDecorations(this, inflationMinFontSize);
   6510      const gfxFont::Metrics metrics =
   6511          GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
   6512 
   6513      relativeSize = 2.0f;
   6514      aTextPaintStyle.GetURLSecondaryColor(&params.color);
   6515      params.style = StyleTextDecorationStyle::Solid;
   6516      params.defaultLineThickness = metrics.strikeoutSize;
   6517      params.lineSize.height = ComputeDecorationLineThickness(
   6518          decThickness, params.defaultLineThickness, metrics,
   6519          appUnitsPerDevPixel, this);
   6520      // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode!
   6521      params.offset = metrics.strikeoutOffset + 0.5;
   6522      params.decoration = StyleTextDecorationLine::LINE_THROUGH;
   6523      break;
   6524    }
   6525    default:
   6526      NS_WARNING("Requested selection decorations when there aren't any");
   6527      return;
   6528  }
   6529  params.lineSize.height *= relativeSize;
   6530  params.defaultLineThickness *= relativeSize;
   6531  params.icoordInFrame =
   6532      (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame;
   6533  PaintDecorationLine(params);
   6534 }
   6535 
   6536 /* static */
   6537 bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
   6538                                         nsAtom* aHighlightName,
   6539                                         nsTextPaintStyle& aTextPaintStyle,
   6540                                         const TextRangeStyle& aRangeStyle,
   6541                                         nscolor* aForeground,
   6542                                         nscolor* aBackground) {
   6543  switch (aSelectionType) {
   6544    case SelectionType::eNormal:
   6545      return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
   6546    case SelectionType::eFind:
   6547      aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
   6548      return true;
   6549    case SelectionType::eHighlight: {
   6550      // Intentionally not short-cutting here because the called methods have
   6551      // side-effects that affect outparams.
   6552      bool hasForeground = aTextPaintStyle.GetCustomHighlightTextColor(
   6553          aHighlightName, aForeground);
   6554      bool hasBackground = aTextPaintStyle.GetCustomHighlightBackgroundColor(
   6555          aHighlightName, aBackground);
   6556      return hasForeground || hasBackground;
   6557    }
   6558    case SelectionType::eTargetText: {
   6559      aTextPaintStyle.GetTargetTextColors(aForeground, aBackground);
   6560      return true;
   6561    }
   6562    case SelectionType::eURLSecondary:
   6563      aTextPaintStyle.GetURLSecondaryColor(aForeground);
   6564      *aBackground = NS_RGBA(0, 0, 0, 0);
   6565      return true;
   6566    case SelectionType::eIMERawClause:
   6567    case SelectionType::eIMESelectedRawClause:
   6568    case SelectionType::eIMEConvertedClause:
   6569    case SelectionType::eIMESelectedClause:
   6570      if (aRangeStyle.IsDefined()) {
   6571        if (!aRangeStyle.IsForegroundColorDefined() &&
   6572            !aRangeStyle.IsBackgroundColorDefined()) {
   6573          *aForeground = aTextPaintStyle.GetTextColor();
   6574          *aBackground = NS_RGBA(0, 0, 0, 0);
   6575          return false;
   6576        }
   6577        if (aRangeStyle.IsForegroundColorDefined()) {
   6578          *aForeground = aRangeStyle.mForegroundColor;
   6579          if (aRangeStyle.IsBackgroundColorDefined()) {
   6580            *aBackground = aRangeStyle.mBackgroundColor;
   6581          } else {
   6582            // If foreground color is defined but background color isn't
   6583            // defined, we can guess that IME must expect that the background
   6584            // color is system's default field background color.
   6585            *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor();
   6586          }
   6587        } else {  // aRangeStyle.IsBackgroundColorDefined() is true
   6588          *aBackground = aRangeStyle.mBackgroundColor;
   6589          // If background color is defined but foreground color isn't defined,
   6590          // we can assume that IME must expect that the foreground color is
   6591          // same as system's field text color.
   6592          *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor();
   6593        }
   6594        return true;
   6595      }
   6596      aTextPaintStyle.GetIMESelectionColors(
   6597          nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
   6598              aSelectionType),
   6599          aForeground, aBackground);
   6600      return true;
   6601    default:
   6602      *aForeground = aTextPaintStyle.GetTextColor();
   6603      *aBackground = NS_RGBA(0, 0, 0, 0);
   6604      return false;
   6605  }
   6606 }
   6607 
   6608 /**
   6609 * This sets *aShadows to the appropriate shadows, if any, for the given
   6610 * type of selection.
   6611 * If text-shadow was not specified, *aShadows is left untouched.
   6612 */
   6613 void nsTextFrame::GetSelectionTextShadow(
   6614    SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
   6615    Span<const StyleSimpleShadow>* aShadows) {
   6616  if (aSelectionType != SelectionType::eNormal) {
   6617    return;
   6618  }
   6619  aTextPaintStyle.GetSelectionShadow(aShadows);
   6620 }
   6621 
   6622 /**
   6623 * This class lets us iterate over chunks of text recorded in an array of
   6624 * resolved selection ranges, observing cluster boundaries, in content order,
   6625 * maintaining the current x-offset as we go, and telling whether the text
   6626 * chunk has a hyphen after it or not.
   6627 * In addition to returning the selected chunks, the iterator is responsible
   6628 * to interpolate unselected chunks in any gaps between them.
   6629 * The caller is responsible for actually computing the advance width of each
   6630 * chunk.
   6631 */
   6632 class MOZ_STACK_CLASS SelectionRangeIterator {
   6633  using PropertyProvider = nsTextFrame::PropertyProvider;
   6634  using CombinedSelectionRange = nsTextFrame::PriorityOrderedSelectionsForRange;
   6635 
   6636 public:
   6637  // aSelectionRanges and aRange are according to the original string.
   6638  SelectionRangeIterator(
   6639      const nsTArray<CombinedSelectionRange>& aSelectionRanges,
   6640      gfxTextRun::Range aRange, PropertyProvider& aProvider,
   6641      gfxTextRun* aTextRun, gfxFloat aXOffset);
   6642 
   6643  bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
   6644                      gfxFloat* aHyphenWidth,
   6645                      nsTArray<SelectionType>& aSelectionType,
   6646                      nsTArray<RefPtr<nsAtom>>& aHighlightName,
   6647                      nsTArray<TextRangeStyle>& aStyle);
   6648 
   6649  void UpdateWithAdvance(gfxFloat aAdvance) {
   6650    mXOffset += aAdvance * mTextRun->GetDirection();
   6651  }
   6652 
   6653 private:
   6654  const nsTArray<CombinedSelectionRange>& mSelectionRanges;
   6655  PropertyProvider& mProvider;
   6656  gfxTextRun* mTextRun;
   6657  gfxSkipCharsIterator mIterator;
   6658  gfxTextRun::Range mOriginalRange;
   6659  gfxFloat mXOffset;
   6660  uint32_t mIndex;
   6661 };
   6662 
   6663 SelectionRangeIterator::SelectionRangeIterator(
   6664    const nsTArray<nsTextFrame::PriorityOrderedSelectionsForRange>&
   6665        aSelectionRanges,
   6666    gfxTextRun::Range aRange, PropertyProvider& aProvider, gfxTextRun* aTextRun,
   6667    gfxFloat aXOffset)
   6668    : mSelectionRanges(aSelectionRanges),
   6669      mProvider(aProvider),
   6670      mTextRun(aTextRun),
   6671      mIterator(aProvider.GetStart()),
   6672      mOriginalRange(aRange),
   6673      mXOffset(aXOffset),
   6674      mIndex(0) {
   6675  mIterator.SetOriginalOffset(int32_t(aRange.start));
   6676 }
   6677 
   6678 bool SelectionRangeIterator::GetNextSegment(
   6679    gfxFloat* aXOffset, gfxTextRun::Range* aRange, gfxFloat* aHyphenWidth,
   6680    nsTArray<SelectionType>& aSelectionType,
   6681    nsTArray<RefPtr<nsAtom>>& aHighlightName,
   6682    nsTArray<TextRangeStyle>& aStyle) {
   6683  if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end)) {
   6684    return false;
   6685  }
   6686 
   6687  uint32_t runOffset = mIterator.GetSkippedOffset();
   6688  uint32_t segmentEnd = mOriginalRange.end;
   6689 
   6690  aSelectionType.Clear();
   6691  aHighlightName.Clear();
   6692  aStyle.Clear();
   6693 
   6694  if (mIndex == mSelectionRanges.Length() ||
   6695      mIterator.GetOriginalOffset() <
   6696          int32_t(mSelectionRanges[mIndex].mRange.start)) {
   6697    // There's an unselected segment before the next range (or at the end).
   6698    aSelectionType.AppendElement(SelectionType::eNone);
   6699    aHighlightName.AppendElement();
   6700    aStyle.AppendElement(TextRangeStyle());
   6701    if (mIndex < mSelectionRanges.Length()) {
   6702      segmentEnd = mSelectionRanges[mIndex].mRange.start;
   6703    }
   6704  } else {
   6705    // Get the selection details for the next segment, and increment index.
   6706    for (const SelectionDetails* sdptr :
   6707         mSelectionRanges[mIndex].mSelectionRanges) {
   6708      aSelectionType.AppendElement(sdptr->mSelectionType);
   6709      aHighlightName.AppendElement(sdptr->mHighlightData.mHighlightName);
   6710      aStyle.AppendElement(sdptr->mTextRangeStyle);
   6711    }
   6712    segmentEnd = mSelectionRanges[mIndex].mRange.end;
   6713    ++mIndex;
   6714  }
   6715 
   6716  // Advance iterator to the end of the segment.
   6717  mIterator.SetOriginalOffset(int32_t(segmentEnd));
   6718 
   6719  // Further advance if necessary to a cluster boundary.
   6720  while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
   6721         !mIterator.IsOriginalCharSkipped() &&
   6722         !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
   6723    mIterator.AdvanceOriginal(1);
   6724  }
   6725 
   6726  aRange->start = runOffset;
   6727  aRange->end = mIterator.GetSkippedOffset();
   6728  *aXOffset = mXOffset;
   6729  *aHyphenWidth = 0;
   6730  if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
   6731      mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
   6732    *aHyphenWidth = mProvider.GetHyphenWidth();
   6733  }
   6734 
   6735  return true;
   6736 }
   6737 
   6738 static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft,
   6739                               gfxTextRun::Metrics* aMetrics,
   6740                               gfxFont::BoundingBoxType aBoundingBoxType,
   6741                               DrawTarget* aDrawTarget) {
   6742  // Fix up metrics to include hyphen
   6743  RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget);
   6744  if (!hyphenTextRun) {
   6745    return;
   6746  }
   6747 
   6748  gfxTextRun::Metrics hyphenMetrics =
   6749      hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
   6750  if (aTextFrame->GetWritingMode().IsLineInverted()) {
   6751    hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
   6752  }
   6753  aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft);
   6754 }
   6755 
   6756 void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
   6757                                 const StyleSimpleShadow& aShadowDetails,
   6758                                 gfxRect& aBoundingBox, uint32_t aBlurFlags) {
   6759  AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
   6760 
   6761  nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(),
   6762                       aShadowDetails.vertical.ToAppUnits());
   6763  nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0);
   6764 
   6765  nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor);
   6766 
   6767  if (auto* textDrawer = aParams.context->GetTextDrawer()) {
   6768    wr::Shadow wrShadow;
   6769 
   6770    wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x),
   6771                       PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)};
   6772 
   6773    wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
   6774    wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
   6775 
   6776    bool inflate = true;
   6777    textDrawer->AppendShadow(wrShadow, inflate);
   6778    return;
   6779  }
   6780 
   6781  // This rect is the box which is equivalent to where the shadow will be
   6782  // painted. The origin of aBoundingBox is the text baseline left, so we must
   6783  // translate it by that much in order to make the origin the top-left corner
   6784  // of the text bounding box. Note that aLeftSideOffset is line-left, so
   6785  // actually means top offset in vertical writing modes.
   6786  gfxRect shadowGfxRect;
   6787  WritingMode wm = GetWritingMode();
   6788  if (wm.IsVertical()) {
   6789    shadowGfxRect = aBoundingBox;
   6790    if (wm.IsVerticalRL()) {
   6791      // for vertical-RL, reverse direction of x-coords of bounding box
   6792      shadowGfxRect.x = -shadowGfxRect.XMost();
   6793    }
   6794    shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
   6795                              aParams.framePt.y + aParams.leftSideOffset);
   6796  } else {
   6797    shadowGfxRect =
   6798        aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
   6799                                aParams.textBaselinePt.y);
   6800  }
   6801  Point shadowGfxOffset(shadowOffset.x, shadowOffset.y);
   6802  shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y);
   6803 
   6804  nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
   6805                    NSToCoordRound(shadowGfxRect.Y()),
   6806                    NSToCoordRound(shadowGfxRect.Width()),
   6807                    NSToCoordRound(shadowGfxRect.Height()));
   6808 
   6809  nsContextBoxBlur contextBoxBlur;
   6810  const auto A2D = PresContext()->AppUnitsPerDevPixel();
   6811  gfxContext* shadowContext =
   6812      contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context,
   6813                          LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D),
   6814                          nullptr, aBlurFlags);
   6815  if (!shadowContext) {
   6816    return;
   6817  }
   6818 
   6819  aParams.context->Save();
   6820  aParams.context->SetColor(sRGBColor::FromABGR(shadowColor));
   6821 
   6822  // Draw the text onto our alpha-only surface to capture the alpha values.
   6823  // Remember that the box blur context has a device offset on it, so we don't
   6824  // need to translate any coordinates to fit on the surface.
   6825  gfxFloat advanceWidth;
   6826  nsTextPaintStyle textPaintStyle(this);
   6827  DrawTextParams params(shadowContext, PresContext()->FontPaletteCache());
   6828  params.paintingShadows = true;
   6829  params.advanceWidth = &advanceWidth;
   6830  params.dirtyRect = aParams.dirtyRect;
   6831  params.framePt = aParams.framePt + shadowGfxOffset;
   6832  params.provider = aParams.provider;
   6833  params.textStyle = &textPaintStyle;
   6834  params.textColor =
   6835      aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
   6836  params.callbacks = aParams.callbacks;
   6837  params.clipEdges = aParams.clipEdges;
   6838  params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
   6839  // Multi-color shadow is not allowed, so we use the same color as the text
   6840  // color.
   6841  params.decorationOverrideColor = &params.textColor;
   6842  params.fontPalette = StyleFont()->GetFontPaletteAtom();
   6843 
   6844  DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params);
   6845 
   6846  contextBoxBlur.DoPaint();
   6847  aParams.context->Restore();
   6848 }
   6849 
   6850 /* static */
   6851 SelectionTypeMask nsTextFrame::CreateSelectionRangeList(
   6852    const SelectionDetails* aDetails, SelectionType aSelectionType,
   6853    const PaintTextSelectionParams& aParams,
   6854    nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds) {
   6855  SelectionTypeMask allTypes = 0;
   6856  bool anyBackgrounds = false;
   6857 
   6858  uint32_t priorityOfInsertionOrder = 0;
   6859  for (const SelectionDetails* sd = aDetails; sd; sd = sd->mNext.get()) {
   6860    MOZ_ASSERT(sd->mStart >= 0 && sd->mEnd >= 0);  // XXX make unsigned?
   6861    uint32_t start = std::max(aParams.contentRange.start, uint32_t(sd->mStart));
   6862    uint32_t end = std::min(aParams.contentRange.end, uint32_t(sd->mEnd));
   6863    if (start < end) {
   6864      // The PaintTextWithSelectionColors caller passes SelectionType::eNone,
   6865      // so we collect all selections that set colors, and prioritize them
   6866      // according to selection type (lower types take precedence).
   6867      if (aSelectionType == SelectionType::eNone) {
   6868        allTypes |= ToSelectionTypeMask(sd->mSelectionType);
   6869        // Ignore selections that don't set colors.
   6870        nscolor foreground(0), background(0);
   6871        if (GetSelectionTextColors(sd->mSelectionType,
   6872                                   sd->mHighlightData.mHighlightName,
   6873                                   *aParams.textPaintStyle, sd->mTextRangeStyle,
   6874                                   &foreground, &background)) {
   6875          if (NS_GET_A(background) > 0) {
   6876            anyBackgrounds = true;
   6877          }
   6878          aSelectionRanges.AppendElement(
   6879              SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
   6880        }
   6881      } else if (sd->mSelectionType == aSelectionType) {
   6882        // The PaintSelectionTextDecorations caller passes a specific type,
   6883        // so we include only ranges of that type, and keep them in order
   6884        // so that later ones take precedence over earlier.
   6885        aSelectionRanges.AppendElement(
   6886            SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
   6887      }
   6888    }
   6889  }
   6890  if (aAnyBackgrounds) {
   6891    *aAnyBackgrounds = anyBackgrounds;
   6892  }
   6893  return allTypes;
   6894 }
   6895 
   6896 /* static */
   6897 void nsTextFrame::CombineSelectionRanges(
   6898    const nsTArray<SelectionRange>& aSelectionRanges,
   6899    nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges) {
   6900  struct SelectionRangeEndCmp {
   6901    bool Equals(const SelectionRange* a, const SelectionRange* b) const {
   6902      return a->mRange.end == b->mRange.end;
   6903    }
   6904    bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
   6905      return a->mRange.end < b->mRange.end;
   6906    }
   6907  };
   6908 
   6909  struct SelectionRangePriorityCmp {
   6910    bool Equals(const SelectionRange* a, const SelectionRange* b) const {
   6911      const SelectionDetails* aDetails = a->mDetails;
   6912      const SelectionDetails* bDetails = b->mDetails;
   6913      if (aDetails->mSelectionType != bDetails->mSelectionType) {
   6914        return false;
   6915      }
   6916      if (aDetails->mSelectionType != SelectionType::eHighlight) {
   6917        return a->mPriority == b->mPriority;
   6918      }
   6919      if (aDetails->mHighlightData.mHighlight->Priority() !=
   6920          bDetails->mHighlightData.mHighlight->Priority()) {
   6921        return false;
   6922      }
   6923      return a->mPriority == b->mPriority;
   6924    }
   6925 
   6926    bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
   6927      if (a->mDetails->mSelectionType != b->mDetails->mSelectionType) {
   6928        // Even though this looks counter-intuitive,
   6929        // this is intended, as values in `SelectionType` are inverted:
   6930        // a lower value indicates a higher priority.
   6931        return a->mDetails->mSelectionType > b->mDetails->mSelectionType;
   6932      }
   6933 
   6934      if (a->mDetails->mSelectionType != SelectionType::eHighlight) {
   6935        // for non-highlights, the selection which was added later
   6936        // has a higher priority.
   6937        return a->mPriority < b->mPriority;
   6938      }
   6939 
   6940      if (a->mDetails->mHighlightData.mHighlight->Priority() !=
   6941          b->mDetails->mHighlightData.mHighlight->Priority()) {
   6942        // For highlights, first compare the priorities set by the user.
   6943        return a->mDetails->mHighlightData.mHighlight->Priority() <
   6944               b->mDetails->mHighlightData.mHighlight->Priority();
   6945      }
   6946      // only if the user priorities are equal, let the highlight that was added
   6947      // later take precedence.
   6948      return a->mPriority < b->mPriority;
   6949    }
   6950  };
   6951 
   6952  uint32_t currentOffset = 0;
   6953  AutoTArray<const SelectionRange*, 1> activeSelectionsForCurrentSegment;
   6954  size_t rangeIndex = 0;
   6955 
   6956  // Divide the given selection ranges into segments which share the same
   6957  // set of selections.
   6958  // The following algorithm iterates `aSelectionRanges`, assuming
   6959  // that its elements are sorted by their start offset.
   6960  // Each time a new selection starts, it is pushed into an array of
   6961  // "currently present" selections, sorted by their *end* offset.
   6962  // For each iteration the next segment end offset is determined,
   6963  // which is either the start offset of the next selection or
   6964  // the next end offset of all "currently present" selections
   6965  // (which is always the first element of the array because of its order).
   6966  // Then, a `CombinedSelectionRange` can be constructed, which describes
   6967  // the text segment until its end offset (as determined above), and contains
   6968  // all elements of the "currently present" selection list, now sorted
   6969  // by their priority.
   6970  // If a range ends at the given offset, it is removed from the array.
   6971  while (rangeIndex < aSelectionRanges.Length() ||
   6972         !activeSelectionsForCurrentSegment.IsEmpty()) {
   6973    uint32_t currentSegmentEndOffset =
   6974        activeSelectionsForCurrentSegment.IsEmpty()
   6975            ? -1
   6976            : activeSelectionsForCurrentSegment[0]->mRange.end;
   6977    uint32_t nextRangeStartOffset =
   6978        rangeIndex < aSelectionRanges.Length()
   6979            ? aSelectionRanges[rangeIndex].mRange.start
   6980            : -1;
   6981    uint32_t nextOffset =
   6982        std::min(currentSegmentEndOffset, nextRangeStartOffset);
   6983    if (!activeSelectionsForCurrentSegment.IsEmpty() &&
   6984        currentOffset != nextOffset) {
   6985      auto activeSelectionRangesSortedByPriority =
   6986          activeSelectionsForCurrentSegment.Clone();
   6987      activeSelectionRangesSortedByPriority.Sort(SelectionRangePriorityCmp());
   6988 
   6989      AutoTArray<const SelectionDetails*, 1> selectionDetails;
   6990      selectionDetails.SetCapacity(
   6991          activeSelectionRangesSortedByPriority.Length());
   6992      // ensure that overlapping highlights which have the same name
   6993      // are only added once. If added each time, they would be painted
   6994      // several times (see wpt
   6995      // /css/css-highlight-api/painting/custom-highlight-painting-003.html)
   6996      // Comparing the highlight name with the previous one is
   6997      // sufficient here because selections are already sorted
   6998      // in a way that ensures that highlights of the same name are
   6999      // grouped together.
   7000      nsAtom* currentHighlightName = nullptr;
   7001      for (const auto* selectionRange : activeSelectionRangesSortedByPriority) {
   7002        if (selectionRange->mDetails->mSelectionType ==
   7003            SelectionType::eHighlight) {
   7004          if (selectionRange->mDetails->mHighlightData.mHighlightName ==
   7005              currentHighlightName) {
   7006            continue;
   7007          }
   7008          currentHighlightName =
   7009              selectionRange->mDetails->mHighlightData.mHighlightName;
   7010        }
   7011        selectionDetails.AppendElement(selectionRange->mDetails);
   7012      }
   7013      aCombinedSelectionRanges.AppendElement(PriorityOrderedSelectionsForRange{
   7014          std::move(selectionDetails), {currentOffset, nextOffset}});
   7015    }
   7016    currentOffset = nextOffset;
   7017 
   7018    if (nextRangeStartOffset < currentSegmentEndOffset) {
   7019      activeSelectionsForCurrentSegment.InsertElementSorted(
   7020          &aSelectionRanges[rangeIndex++], SelectionRangeEndCmp());
   7021    } else {
   7022      activeSelectionsForCurrentSegment.RemoveElementAt(0);
   7023    }
   7024  }
   7025 }
   7026 
   7027 SelectionTypeMask nsTextFrame::ResolveSelections(
   7028    const PaintTextSelectionParams& aParams, const SelectionDetails* aDetails,
   7029    nsTArray<PriorityOrderedSelectionsForRange>& aResult,
   7030    SelectionType aSelectionType, bool* aAnyBackgrounds) const {
   7031  AutoTArray<SelectionRange, 4> selectionRanges;
   7032 
   7033  SelectionTypeMask allTypes = CreateSelectionRangeList(
   7034      aDetails, aSelectionType, aParams, selectionRanges, aAnyBackgrounds);
   7035 
   7036  if (selectionRanges.IsEmpty()) {
   7037    return allTypes;
   7038  }
   7039 
   7040  struct SelectionRangeStartCmp {
   7041    bool Equals(const SelectionRange& a, const SelectionRange& b) const {
   7042      return a.mRange.start == b.mRange.start;
   7043    }
   7044    bool LessThan(const SelectionRange& a, const SelectionRange& b) const {
   7045      return a.mRange.start < b.mRange.start;
   7046    }
   7047  };
   7048  selectionRanges.Sort(SelectionRangeStartCmp());
   7049 
   7050  CombineSelectionRanges(selectionRanges, aResult);
   7051 
   7052  return allTypes;
   7053 }
   7054 
   7055 // Paints selection backgrounds and text in the correct colors. Also computes
   7056 // aAllSelectionTypeMask, the union of all selection types that are applying to
   7057 // this text.
   7058 bool nsTextFrame::PaintTextWithSelectionColors(
   7059    const PaintTextSelectionParams& aParams,
   7060    const UniquePtr<SelectionDetails>& aDetails,
   7061    SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges) {
   7062  bool anyBackgrounds = false;
   7063  AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
   7064 
   7065  *aAllSelectionTypeMask =
   7066      ResolveSelections(aParams, aDetails.get(), selectionRanges,
   7067                        SelectionType::eNone, &anyBackgrounds);
   7068  bool vertical = mTextRun->IsVertical();
   7069  const gfxFloat startIOffset =
   7070      vertical ? aParams.textBaselinePt.y - aParams.framePt.y
   7071               : aParams.textBaselinePt.x - aParams.framePt.x;
   7072  gfxFloat iOffset, hyphenWidth;
   7073  Range range;  // in transformed string
   7074 
   7075  const gfxTextRun::Range& contentRange = aParams.contentRange;
   7076  auto* textDrawer = aParams.context->GetTextDrawer();
   7077 
   7078  if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
   7079    int32_t appUnitsPerDevPixel =
   7080        aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
   7081    SelectionRangeIterator iterator(selectionRanges, contentRange,
   7082                                    *aParams.provider, mTextRun, startIOffset);
   7083    AutoTArray<SelectionType, 1> selectionTypes;
   7084    AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
   7085    AutoTArray<TextRangeStyle, 1> rangeStyles;
   7086    while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
   7087                                   selectionTypes, highlightNames,
   7088                                   rangeStyles)) {
   7089      nscolor foreground(0), background(0);
   7090      gfxFloat advance =
   7091          hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
   7092      nsRect bgRect;
   7093      gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
   7094      if (vertical) {
   7095        bgRect = nsRect(nscoord(aParams.framePt.x),
   7096                        nscoord(aParams.framePt.y + offs), GetSize().width,
   7097                        nscoord(advance));
   7098      } else {
   7099        // For text-combine-upright, we may have collapsed the frame height,
   7100        // so we instead use 1em to ensure the selection is visible.
   7101        nscoord height = Style()->IsTextCombined()
   7102                             ? aParams.provider->GetFontMetrics()->EmHeight()
   7103                             : GetSize().height;
   7104        bgRect = nsRect(nscoord(aParams.framePt.x + offs),
   7105                        nscoord(aParams.framePt.y), nscoord(advance), height);
   7106      }
   7107 
   7108      LayoutDeviceRect selectionRect =
   7109          LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
   7110      // The elements in `selectionTypes` are ordered ascending by their
   7111      // priority. To account for non-opaque overlapping selections, all
   7112      // selection backgrounds are painted.
   7113      for (size_t index = 0; index < selectionTypes.Length(); ++index) {
   7114        GetSelectionTextColors(selectionTypes[index], highlightNames[index],
   7115                               *aParams.textPaintStyle, rangeStyles[index],
   7116                               &foreground, &background);
   7117 
   7118        // Draw background color
   7119        if (NS_GET_A(background) > 0) {
   7120          if (textDrawer) {
   7121            textDrawer->AppendSelectionRect(selectionRect,
   7122                                            ToDeviceColor(background));
   7123          } else {
   7124            PaintSelectionBackground(*aParams.context->GetDrawTarget(),
   7125                                     background, aParams.dirtyRect,
   7126                                     selectionRect, aParams.callbacks);
   7127          }
   7128        }
   7129      }
   7130      iterator.UpdateWithAdvance(advance);
   7131    }
   7132  }
   7133 
   7134  gfxFloat advance;
   7135  DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
   7136  params.dirtyRect = aParams.dirtyRect;
   7137  params.framePt = aParams.framePt;
   7138  params.provider = aParams.provider;
   7139  params.textStyle = aParams.textPaintStyle;
   7140  params.clipEdges = &aClipEdges;
   7141  params.advanceWidth = &advance;
   7142  params.callbacks = aParams.callbacks;
   7143  params.glyphRange = aParams.glyphRange;
   7144  params.fontPalette = StyleFont()->GetFontPaletteAtom();
   7145  params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
   7146 
   7147  PaintShadowParams shadowParams(aParams);
   7148  shadowParams.provider = aParams.provider;
   7149  shadowParams.callbacks = aParams.callbacks;
   7150  shadowParams.clipEdges = &aClipEdges;
   7151 
   7152  // Draw text
   7153  const nsStyleText* textStyle = StyleText();
   7154  SelectionRangeIterator iterator(selectionRanges, contentRange,
   7155                                  *aParams.provider, mTextRun, startIOffset);
   7156  AutoTArray<SelectionType, 1> selectionTypes;
   7157  AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
   7158  AutoTArray<TextRangeStyle, 1> rangeStyles;
   7159  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, selectionTypes,
   7160                                 highlightNames, rangeStyles)) {
   7161    nscolor foreground(0), background(0);
   7162    if (aParams.IsGenerateTextMask()) {
   7163      foreground = NS_RGBA(0, 0, 0, 255);
   7164    } else {
   7165      nscolor tmpForeground(0);
   7166      bool colorHasBeenSet = false;
   7167      for (size_t index = 0; index < selectionTypes.Length(); ++index) {
   7168        if (selectionTypes[index] == SelectionType::eHighlight) {
   7169          if (aParams.textPaintStyle->GetCustomHighlightTextColor(
   7170                  highlightNames[index], &tmpForeground)) {
   7171            foreground = tmpForeground;
   7172            colorHasBeenSet = true;
   7173          }
   7174 
   7175        } else {
   7176          GetSelectionTextColors(selectionTypes[index], highlightNames[index],
   7177                                 *aParams.textPaintStyle, rangeStyles[index],
   7178                                 &foreground, &background);
   7179          colorHasBeenSet = true;
   7180        }
   7181      }
   7182      if (!colorHasBeenSet) {
   7183        foreground = tmpForeground;
   7184      }
   7185    }
   7186 
   7187    gfx::Point textBaselinePt =
   7188        vertical
   7189            ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset)
   7190            : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
   7191 
   7192    // Determine what shadow, if any, to draw - either from textStyle
   7193    // or from the ::-moz-selection pseudo-class if specified there
   7194    Span<const StyleSimpleShadow> shadows = textStyle->mTextShadow.AsSpan();
   7195    for (auto selectionType : selectionTypes) {
   7196      GetSelectionTextShadow(selectionType, *aParams.textPaintStyle, &shadows);
   7197    }
   7198    if (!shadows.IsEmpty()) {
   7199      nscoord startEdge = iOffset;
   7200      if (mTextRun->IsInlineReversed()) {
   7201        startEdge -=
   7202            hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
   7203      }
   7204      shadowParams.range = range;
   7205      shadowParams.textBaselinePt = textBaselinePt;
   7206      shadowParams.foregroundColor = foreground;
   7207      shadowParams.leftSideOffset = startEdge;
   7208      PaintShadows(shadows, shadowParams);
   7209    }
   7210 
   7211    // Draw text segment
   7212    params.textColor = foreground;
   7213    params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
   7214    params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
   7215    params.drawSoftHyphen = hyphenWidth > 0;
   7216    DrawText(range, textBaselinePt, params);
   7217    advance += hyphenWidth;
   7218    iterator.UpdateWithAdvance(advance);
   7219  }
   7220  return true;
   7221 }
   7222 
   7223 void nsTextFrame::PaintTextSelectionDecorations(
   7224    const PaintTextSelectionParams& aParams,
   7225    const UniquePtr<SelectionDetails>& aDetails, SelectionType aSelectionType) {
   7226  // Hide text decorations if we're currently hiding @font-face fallback text
   7227  if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) {
   7228    return;
   7229  }
   7230 
   7231  AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
   7232  ResolveSelections(aParams, aDetails.get(), selectionRanges, aSelectionType);
   7233 
   7234  RefPtr<gfxFont> firstFont =
   7235      aParams.provider->GetFontGroup()->GetFirstValidFont();
   7236  bool verticalRun = mTextRun->IsVertical();
   7237  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
   7238  bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style());
   7239  const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE
   7240                                          : StyleTextDecorationLine::UNDERLINE;
   7241  gfxFont::Metrics decorationMetrics(
   7242      firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
   7243                                               : nsFontMetrics::eHorizontal));
   7244  decorationMetrics.underlineOffset =
   7245      aParams.provider->GetFontGroup()->GetUnderlineOffset();
   7246 
   7247  const gfxTextRun::Range& contentRange = aParams.contentRange;
   7248  gfxFloat startIOffset = verticalRun
   7249                              ? aParams.textBaselinePt.y - aParams.framePt.y
   7250                              : aParams.textBaselinePt.x - aParams.framePt.x;
   7251  SelectionRangeIterator iterator(selectionRanges, contentRange,
   7252                                  *aParams.provider, mTextRun, startIOffset);
   7253  gfxFloat iOffset, hyphenWidth;
   7254  Range range;
   7255  int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
   7256  // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
   7257  Point pt;
   7258  if (verticalRun) {
   7259    pt.x = (aParams.textBaselinePt.x - mAscent) / app;
   7260  } else {
   7261    pt.y = (aParams.textBaselinePt.y - mAscent) / app;
   7262  }
   7263  AutoTArray<SelectionType, 1> nextSelectionTypes;
   7264  AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
   7265  AutoTArray<TextRangeStyle, 1> selectedStyles;
   7266 
   7267  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
   7268                                 nextSelectionTypes, highlightNames,
   7269                                 selectedStyles)) {
   7270    gfxFloat advance =
   7271        hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
   7272    for (size_t index = 0; index < nextSelectionTypes.Length(); ++index) {
   7273      if (nextSelectionTypes[index] == aSelectionType) {
   7274        if (verticalRun) {
   7275          pt.y = (aParams.framePt.y + iOffset -
   7276                  (mTextRun->IsInlineReversed() ? advance : 0)) /
   7277                 app;
   7278        } else {
   7279          pt.x = (aParams.framePt.x + iOffset -
   7280                  (mTextRun->IsInlineReversed() ? advance : 0)) /
   7281                 app;
   7282        }
   7283        gfxFloat width = Abs(advance) / app;
   7284        gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
   7285        DrawSelectionDecorations(
   7286            aParams.context, aParams.dirtyRect, aSelectionType,
   7287            highlightNames[index], *aParams.textPaintStyle,
   7288            selectedStyles[index], pt, xInFrame, width, mAscent / app,
   7289            decorationMetrics, aParams.callbacks, verticalRun, kDecoration,
   7290            aParams.glyphRange, aParams.provider);
   7291      }
   7292    }
   7293    iterator.UpdateWithAdvance(advance);
   7294  }
   7295 }
   7296 
   7297 bool nsTextFrame::PaintTextWithSelection(
   7298    const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges) {
   7299  NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path");
   7300 
   7301  UniquePtr<SelectionDetails> details = GetSelectionDetails();
   7302  if (!details) {
   7303    return false;
   7304  }
   7305 
   7306  SelectionTypeMask allSelectionTypeMask;
   7307  if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask,
   7308                                    aClipEdges)) {
   7309    return false;
   7310  }
   7311  // Iterate through just the selection rawSelectionTypes that paint decorations
   7312  // and paint decorations for any that actually occur in this frame. Paint
   7313  // higher-numbered selection rawSelectionTypes below lower-numered ones on the
   7314  // general principal that lower-numbered selections are higher priority.
   7315  allSelectionTypeMask &= kSelectionTypesWithDecorations;
   7316  MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,
   7317             "The following for loop assumes that the first item of "
   7318             "kPresentSelectionTypes is SelectionType::eNormal");
   7319 
   7320  Span presentTypes(kPresentSelectionTypes);
   7321  for (SelectionType selectionType : Reversed(presentTypes)) {
   7322    if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
   7323      // There is some selection of this selectionType. Try to paint its
   7324      // decorations (there might not be any for this type but that's OK,
   7325      // PaintTextSelectionDecorations will exit early).
   7326      PaintTextSelectionDecorations(aParams, details, selectionType);
   7327    }
   7328  }
   7329 
   7330  return true;
   7331 }
   7332 
   7333 void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
   7334                                    const gfx::Point& aTextBaselinePt,
   7335                                    const gfx::Point& aFramePt, Range aRange,
   7336                                    const nscolor* aDecorationOverrideColor,
   7337                                    PropertyProvider* aProvider) {
   7338  const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
   7339  if (!info) {
   7340    return;
   7341  }
   7342 
   7343  bool isTextCombined = Style()->IsTextCombined();
   7344  if (isTextCombined && !aWM.IsVertical()) {
   7345    // XXX This only happens when the parent is display:contents with an
   7346    // orthogonal writing mode. This should be rare, and don't have use
   7347    // cases, so we don't care. It is non-trivial to implement a sane
   7348    // behavior for that case: if you treat the text as not combined,
   7349    // the marks would spread wider than the text (which is rendered as
   7350    // combined); if you try to draw a single mark, selecting part of
   7351    // the text could dynamically create multiple new marks.
   7352    NS_WARNING("Give up on combined text with horizontal wm");
   7353    return;
   7354  }
   7355  nscolor color =
   7356      aDecorationOverrideColor
   7357          ? *aDecorationOverrideColor
   7358          : nsLayoutUtils::GetTextColor(this, &nsStyleText::mTextEmphasisColor);
   7359  aContext->SetColor(sRGBColor::FromABGR(color));
   7360  gfx::Point pt;
   7361  if (!isTextCombined) {
   7362    pt = aTextBaselinePt;
   7363  } else {
   7364    MOZ_ASSERT(aWM.IsVertical());
   7365    pt = aFramePt;
   7366    if (aWM.IsVerticalRL()) {
   7367      pt.x += GetSize().width - GetLogicalBaseline(aWM);
   7368    } else {
   7369      pt.x += GetLogicalBaseline(aWM);
   7370    }
   7371  }
   7372  if (!aWM.IsVertical()) {
   7373    pt.y += info->baselineOffset;
   7374  } else {
   7375    if (aWM.IsVerticalRL()) {
   7376      pt.x -= info->baselineOffset;
   7377    } else {
   7378      pt.x += info->baselineOffset;
   7379    }
   7380  }
   7381  if (!isTextCombined) {
   7382    mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance,
   7383                                pt, aRange, aProvider,
   7384                                PresContext()->FontPaletteCache());
   7385  } else {
   7386    pt.y += (GetSize().height - info->advance) / 2;
   7387    gfxTextRun::DrawParams params(aContext, PresContext()->FontPaletteCache());
   7388    info->textRun->Draw(Range(info->textRun.get()), pt, params);
   7389  }
   7390 }
   7391 
   7392 nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) {
   7393  MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
   7394 
   7395  nscolor result = nsIFrame::GetCaretColorAt(aOffset);
   7396  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   7397  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   7398  int32_t contentOffset = provider.GetStart().GetOriginalOffset();
   7399  int32_t contentLength = provider.GetOriginalLength();
   7400  MOZ_ASSERT(
   7401      aOffset >= contentOffset && aOffset <= contentOffset + contentLength,
   7402      "aOffset must be in the frame's range");
   7403 
   7404  int32_t offsetInFrame = aOffset - contentOffset;
   7405  if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
   7406    return result;
   7407  }
   7408 
   7409  bool isSolidTextColor = true;
   7410  if (IsInSVGTextSubtree()) {
   7411    const nsStyleSVG* style = StyleSVG();
   7412    if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) {
   7413      isSolidTextColor = false;
   7414    }
   7415  }
   7416 
   7417  nsTextPaintStyle textPaintStyle(this);
   7418  textPaintStyle.SetResolveColors(isSolidTextColor);
   7419  UniquePtr<SelectionDetails> details = GetSelectionDetails();
   7420  SelectionType selectionType = SelectionType::eNone;
   7421  for (SelectionDetails* sdptr = details.get(); sdptr;
   7422       sdptr = sdptr->mNext.get()) {
   7423    int32_t start = std::max(0, sdptr->mStart - contentOffset);
   7424    int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
   7425    if (start <= offsetInFrame && offsetInFrame < end &&
   7426        (selectionType == SelectionType::eNone ||
   7427         sdptr->mSelectionType < selectionType)) {
   7428      nscolor foreground, background;
   7429      if (GetSelectionTextColors(sdptr->mSelectionType,
   7430                                 sdptr->mHighlightData.mHighlightName,
   7431                                 textPaintStyle, sdptr->mTextRangeStyle,
   7432                                 &foreground, &background)) {
   7433        if (!isSolidTextColor && NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
   7434          result = NS_RGBA(0, 0, 0, 255);
   7435        } else {
   7436          result = foreground;
   7437        }
   7438        selectionType = sdptr->mSelectionType;
   7439      }
   7440    }
   7441  }
   7442 
   7443  return result;
   7444 }
   7445 
   7446 static gfxTextRun::Range ComputeTransformedRange(
   7447    nsTextFrame::PropertyProvider& aProvider) {
   7448  gfxSkipCharsIterator iter(aProvider.GetStart());
   7449  uint32_t start = iter.GetSkippedOffset();
   7450  iter.AdvanceOriginal(aProvider.GetOriginalLength());
   7451  return gfxTextRun::Range(start, iter.GetSkippedOffset());
   7452 }
   7453 
   7454 bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
   7455                                         nscoord aVisIEndEdge,
   7456                                         nscoord* aSnappedStartEdge,
   7457                                         nscoord* aSnappedEndEdge) {
   7458  // We need a *reference* rendering context (not one that might have a
   7459  // transform), so we don't have a rendering context argument.
   7460  // XXX get the block and line passed to us somehow! This is slow!
   7461  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   7462  if (!mTextRun) {
   7463    return false;
   7464  }
   7465 
   7466  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   7467  // Trim trailing whitespace
   7468  provider.InitializeForDisplay(true);
   7469 
   7470  Range range = ComputeTransformedRange(provider);
   7471  uint32_t startOffset = range.start;
   7472  uint32_t maxLength = range.Length();
   7473  return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
   7474                                &startOffset, &maxLength, aSnappedStartEdge,
   7475                                aSnappedEndEdge);
   7476 }
   7477 
   7478 static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
   7479                                 uint32_t aStartOffset, uint32_t aMaxLength) {
   7480  uint32_t clusterLength = 0;
   7481  while (++clusterLength < aMaxLength) {
   7482    if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
   7483      return clusterLength;
   7484    }
   7485  }
   7486  return aMaxLength;
   7487 }
   7488 
   7489 bool nsTextFrame::MeasureCharClippedText(
   7490    PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge,
   7491    uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge,
   7492    nscoord* aSnappedEndEdge) {
   7493  *aSnappedStartEdge = 0;
   7494  *aSnappedEndEdge = 0;
   7495  if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
   7496    return true;
   7497  }
   7498 
   7499  uint32_t offset = *aStartOffset;
   7500  uint32_t maxLength = *aMaxLength;
   7501  const nscoord frameISize = ISize();
   7502  const bool rtl = mTextRun->IsRightToLeft();
   7503  gfxFloat advanceWidth = 0;
   7504  const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
   7505  if (startEdge > 0) {
   7506    const gfxFloat maxAdvance = gfxFloat(startEdge);
   7507    while (maxLength > 0) {
   7508      uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
   7509      advanceWidth += mTextRun->GetAdvanceWidth(
   7510          Range(offset, offset + clusterLength), &aProvider);
   7511      maxLength -= clusterLength;
   7512      offset += clusterLength;
   7513      if (advanceWidth >= maxAdvance) {
   7514        break;
   7515      }
   7516    }
   7517    nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
   7518    *snappedStartEdge = NSToCoordFloor(advanceWidth);
   7519    *aStartOffset = offset;
   7520  }
   7521 
   7522  const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
   7523  if (endEdge > 0) {
   7524    const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
   7525    while (maxLength > 0) {
   7526      uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
   7527      gfxFloat nextAdvance =
   7528          advanceWidth + mTextRun->GetAdvanceWidth(
   7529                             Range(offset, offset + clusterLength), &aProvider);
   7530      if (nextAdvance > maxAdvance) {
   7531        break;
   7532      }
   7533      // This cluster fits, include it.
   7534      advanceWidth = nextAdvance;
   7535      maxLength -= clusterLength;
   7536      offset += clusterLength;
   7537    }
   7538    maxLength = offset - *aStartOffset;
   7539    nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
   7540    *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
   7541  }
   7542  *aMaxLength = maxLength;
   7543  return maxLength != 0;
   7544 }
   7545 
   7546 void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows,
   7547                               const PaintShadowParams& aParams) {
   7548  if (aShadows.IsEmpty()) {
   7549    return;
   7550  }
   7551 
   7552  gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText(
   7553      aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider);
   7554  if (GetWritingMode().IsLineInverted()) {
   7555    std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
   7556    shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
   7557  }
   7558  if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
   7559    AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics,
   7560                       gfxFont::LOOSE_INK_EXTENTS,
   7561                       aParams.context->GetDrawTarget());
   7562  }
   7563  // Add bounds of text decorations
   7564  gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth,
   7565                         shadowMetrics.mAscent + shadowMetrics.mDescent);
   7566  shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
   7567                                       decorationRect);
   7568 
   7569  // If the textrun uses any color or SVG fonts, we need to force use of a mask
   7570  // for shadow rendering even if blur radius is zero.
   7571  uint32_t blurFlags = 0;
   7572  uint32_t numGlyphRuns;
   7573  const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
   7574  while (numGlyphRuns-- > 0) {
   7575    if (run->mFont->AlwaysNeedsMaskForShadow()) {
   7576      blurFlags |= nsContextBoxBlur::FORCE_MASK;
   7577      break;
   7578    }
   7579    run++;
   7580  }
   7581 
   7582  if (mTextRun->IsVertical()) {
   7583    std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
   7584    std::swap(shadowMetrics.mBoundingBox.width,
   7585              shadowMetrics.mBoundingBox.height);
   7586  }
   7587 
   7588  for (const auto& shadow : Reversed(aShadows)) {
   7589    PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags);
   7590  }
   7591 }
   7592 
   7593 void nsTextFrame::PaintText(const PaintTextParams& aParams,
   7594                            const nscoord aVisIStartEdge,
   7595                            const nscoord aVisIEndEdge,
   7596                            const nsPoint& aToReferenceFrame,
   7597                            const bool aIsSelected,
   7598                            float aOpacity /* = 1.0f */) {
   7599 #ifdef DEBUG
   7600  if (IsInSVGTextSubtree()) {
   7601    auto* container =
   7602        nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText);
   7603    MOZ_ASSERT(container);
   7604    MOZ_ASSERT(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ||
   7605                   !aParams.IsPaintText(),
   7606               "Expecting IsPaintText to be false for a clipPath");
   7607  }
   7608 #endif
   7609 
   7610  // Don't pass in the rendering context here, because we need a
   7611  // *reference* context and rendering context might have some transform
   7612  // in it
   7613  // XXX get the block and line passed to us somehow! This is slow!
   7614  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   7615  if (!mTextRun) {
   7616    return;
   7617  }
   7618 
   7619  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   7620 
   7621  // Trim trailing whitespace, unless we're painting a selection highlight,
   7622  // which should include trailing spaces if present (bug 1146754).
   7623  provider.InitializeForDisplay(!aIsSelected);
   7624 
   7625  const bool reversed = mTextRun->IsInlineReversed();
   7626  const bool verticalRun = mTextRun->IsVertical();
   7627  WritingMode wm = GetWritingMode();
   7628  const float frameWidth = GetSize().width;
   7629  const float frameHeight = GetSize().height;
   7630  gfx::Point textBaselinePt;
   7631  if (verticalRun) {
   7632    if (wm.IsVerticalLR()) {
   7633      textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX(
   7634          this, aParams.context, nscoord(aParams.framePt.x), mAscent);
   7635    } else {
   7636      textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX(
   7637          this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
   7638          -mAscent);
   7639    }
   7640    textBaselinePt.y = reversed ? aParams.framePt.y.value + frameHeight
   7641                                : aParams.framePt.y.value;
   7642  } else {
   7643    textBaselinePt =
   7644        gfx::Point(reversed ? aParams.framePt.x.value + frameWidth
   7645                            : aParams.framePt.x.value,
   7646                   nsLayoutUtils::GetMaybeSnappedBaselineY(
   7647                       this, aParams.context, aParams.framePt.y, mAscent));
   7648  }
   7649  Range range = ComputeTransformedRange(provider);
   7650  uint32_t startOffset = range.start;
   7651  uint32_t maxLength = range.Length();
   7652  nscoord snappedStartEdge, snappedEndEdge;
   7653  if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
   7654                              &startOffset, &maxLength, &snappedStartEdge,
   7655                              &snappedEndEdge)) {
   7656    return;
   7657  }
   7658  if (verticalRun) {
   7659    textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
   7660  } else {
   7661    textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
   7662  }
   7663  const ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge,
   7664                            snappedEndEdge);
   7665  nsTextPaintStyle textPaintStyle(this);
   7666  textPaintStyle.SetResolveColors(!aParams.callbacks);
   7667 
   7668  // Fork off to the (slower) paint-with-selection path if necessary.
   7669  if (aIsSelected) {
   7670    MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
   7671    gfxSkipCharsIterator tmp(provider.GetStart());
   7672    Range contentRange(
   7673        uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
   7674        uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
   7675    PaintTextSelectionParams params(aParams);
   7676    params.textBaselinePt = textBaselinePt;
   7677    params.provider = &provider;
   7678    params.contentRange = contentRange;
   7679    params.textPaintStyle = &textPaintStyle;
   7680    params.glyphRange = range;
   7681    if (PaintTextWithSelection(params, clipEdges)) {
   7682      return;
   7683    }
   7684  }
   7685 
   7686  nscolor foregroundColor = aParams.IsGenerateTextMask()
   7687                                ? NS_RGBA(0, 0, 0, 255)
   7688                                : textPaintStyle.GetTextColor();
   7689  if (aOpacity != 1.0f) {
   7690    gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor);
   7691    gfxColor.a *= aOpacity;
   7692    foregroundColor = gfxColor.ToABGR();
   7693  }
   7694 
   7695  nscolor textStrokeColor = aParams.IsGenerateTextMask()
   7696                                ? NS_RGBA(0, 0, 0, 255)
   7697                                : textPaintStyle.GetWebkitTextStrokeColor();
   7698  if (aOpacity != 1.0f) {
   7699    gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor);
   7700    gfxColor.a *= aOpacity;
   7701    textStrokeColor = gfxColor.ToABGR();
   7702  }
   7703 
   7704  range = Range(startOffset, startOffset + maxLength);
   7705  if (aParams.IsPaintText()) {
   7706    const nsStyleText* textStyle = StyleText();
   7707    PaintShadowParams shadowParams(aParams);
   7708    shadowParams.range = range;
   7709    shadowParams.textBaselinePt = textBaselinePt;
   7710    shadowParams.leftSideOffset = snappedStartEdge;
   7711    shadowParams.provider = &provider;
   7712    shadowParams.callbacks = aParams.callbacks;
   7713    shadowParams.foregroundColor = foregroundColor;
   7714    shadowParams.clipEdges = &clipEdges;
   7715    PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams);
   7716  }
   7717 
   7718  gfxFloat advanceWidth;
   7719  DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
   7720  params.dirtyRect = aParams.dirtyRect;
   7721  params.framePt = aParams.framePt;
   7722  params.provider = &provider;
   7723  params.advanceWidth = &advanceWidth;
   7724  params.textStyle = &textPaintStyle;
   7725  params.textColor = foregroundColor;
   7726  params.textStrokeColor = textStrokeColor;
   7727  params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
   7728  params.clipEdges = &clipEdges;
   7729  params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
   7730  params.contextPaint = aParams.contextPaint;
   7731  params.callbacks = aParams.callbacks;
   7732  params.glyphRange = range;
   7733  params.fontPalette = StyleFont()->GetFontPaletteAtom();
   7734  params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
   7735 
   7736  DrawText(range, textBaselinePt, params);
   7737 }
   7738 
   7739 static void DrawTextRun(const gfxTextRun* aTextRun,
   7740                        const gfx::Point& aTextBaselinePt,
   7741                        gfxTextRun::Range aRange,
   7742                        const nsTextFrame::DrawTextRunParams& aParams,
   7743                        nsTextFrame* aFrame) {
   7744  gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
   7745  params.provider = aParams.provider;
   7746  params.advanceWidth = aParams.advanceWidth;
   7747  params.contextPaint = aParams.contextPaint;
   7748  params.fontPalette = aParams.fontPalette;
   7749  params.callbacks = aParams.callbacks;
   7750  params.hasTextShadow = aParams.hasTextShadow;
   7751  if (aParams.callbacks) {
   7752    aParams.callbacks->NotifyBeforeText(aParams.paintingShadows,
   7753                                        aParams.textColor);
   7754    params.drawMode = DrawMode::GLYPH_PATH;
   7755    aTextRun->Draw(aRange, aTextBaselinePt, params);
   7756    aParams.callbacks->NotifyAfterText();
   7757  } else {
   7758    auto* textDrawer = aParams.context->GetTextDrawer();
   7759    if (NS_GET_A(aParams.textColor) != 0 || textDrawer ||
   7760        aParams.textStrokeWidth == 0.0f) {
   7761      aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor));
   7762    } else {
   7763      params.drawMode = DrawMode::GLYPH_STROKE;
   7764    }
   7765 
   7766    if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
   7767        aParams.textStrokeWidth != 0.0f) {
   7768      if (textDrawer) {
   7769        textDrawer->FoundUnsupportedFeature();
   7770        return;
   7771      }
   7772      params.drawMode |= DrawMode::GLYPH_STROKE;
   7773 
   7774      // Check the paint-order property; if we find stroke before fill,
   7775      // then change mode to GLYPH_STROKE_UNDERNEATH.
   7776      uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
   7777      while (paintOrder) {
   7778        auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
   7779        switch (component) {
   7780          case StylePaintOrder::Fill:
   7781            // Just break the loop, no need to check further
   7782            paintOrder = 0;
   7783            break;
   7784          case StylePaintOrder::Stroke:
   7785            params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
   7786            paintOrder = 0;
   7787            break;
   7788          default:
   7789            MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
   7790          case StylePaintOrder::Markers:
   7791          case StylePaintOrder::Normal:
   7792            break;
   7793        }
   7794        paintOrder >>= kPaintOrderShift;
   7795      }
   7796 
   7797      // Use ROUND joins as they are less likely to produce ugly artifacts
   7798      // when stroking glyphs with sharp angles (see bug 1546985).
   7799      StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND);
   7800      params.textStrokeColor = aParams.textStrokeColor;
   7801      params.strokeOpts = &strokeOpts;
   7802      aTextRun->Draw(aRange, aTextBaselinePt, params);
   7803    } else {
   7804      aTextRun->Draw(aRange, aTextBaselinePt, params);
   7805    }
   7806  }
   7807 }
   7808 
   7809 void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
   7810                              const DrawTextRunParams& aParams) {
   7811  MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
   7812 
   7813  ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this);
   7814 
   7815  if (aParams.drawSoftHyphen) {
   7816    // Don't use ctx as the context, because we need a reference context here,
   7817    // ctx may be transformed.
   7818    DrawTextRunParams params = aParams;
   7819    params.provider = nullptr;
   7820    params.advanceWidth = nullptr;
   7821    RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr);
   7822    if (hyphenTextRun) {
   7823      gfx::Point p(aTextBaselinePt);
   7824      bool vertical = GetWritingMode().IsVertical();
   7825      // For right-to-left text runs, the soft-hyphen is positioned at the left
   7826      // of the text.
   7827      float shift = mTextRun->GetDirection() * (*aParams.advanceWidth);
   7828      if (vertical) {
   7829        p.y += shift;
   7830      } else {
   7831        p.x += shift;
   7832      }
   7833      ::DrawTextRun(hyphenTextRun.get(), p, Range(hyphenTextRun.get()), params,
   7834                    this);
   7835    }
   7836  }
   7837 }
   7838 
   7839 void nsTextFrame::DrawTextRunAndDecorations(
   7840    Range aRange, const gfx::Point& aTextBaselinePt,
   7841    const DrawTextParams& aParams, const TextDecorations& aDecorations) {
   7842  const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
   7843  // Writing mode of parent frame is used because the text frame may
   7844  // be orthogonal to its parent when text-combine-upright is used or
   7845  // its parent has "display: contents", and in those cases, we want
   7846  // to draw the decoration lines according to parents' direction
   7847  // rather than ours.
   7848  const WritingMode wm = GetParent()->GetWritingMode();
   7849  bool verticalDec = wm.IsVertical();
   7850  bool verticalRun = mTextRun->IsVertical();
   7851  // If the text run and the decoration is orthogonal, we choose the
   7852  // metrics for decoration so that decoration line won't be broken.
   7853  bool useVerticalMetrics = verticalDec != verticalRun
   7854                                ? verticalDec
   7855                                : verticalRun && mTextRun->UseCenterBaseline();
   7856 
   7857  // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
   7858  nscoord x = NSToCoordRound(aParams.framePt.x);
   7859  nscoord y = NSToCoordRound(aParams.framePt.y);
   7860 
   7861  // 'measure' here is textrun-relative, so for a horizontal run it's the
   7862  // width, while for a vertical run it's the height of the decoration
   7863  const nsSize frameSize = GetSize();
   7864  nscoord measure = verticalDec ? frameSize.height : frameSize.width;
   7865 
   7866  if (verticalDec) {
   7867    aParams.clipEdges->Intersect(&y, &measure);
   7868  } else {
   7869    aParams.clipEdges->Intersect(&x, &measure);
   7870  }
   7871 
   7872  // decSize is a textrun-relative size, so its 'width' field is actually
   7873  // the run-relative measure, and 'height' will be the line thickness
   7874  gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
   7875  // The starting edge of the frame in block direction
   7876  gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
   7877 
   7878  // In vertical-rl mode, block coordinates are measured from the
   7879  // right, so we need to adjust here.
   7880  if (wm.IsVerticalRL()) {
   7881    frameBStart += frameSize.width;
   7882    ascent = -ascent;
   7883  }
   7884 
   7885  nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this);
   7886 
   7887  PaintDecorationLineParams params;
   7888  params.context = aParams.context;
   7889  params.dirtyRect = aParams.dirtyRect;
   7890  params.overrideColor = aParams.decorationOverrideColor;
   7891  params.callbacks = aParams.callbacks;
   7892  params.glyphRange = aParams.glyphRange;
   7893  params.provider = aParams.provider;
   7894  params.paintingShadows = aParams.paintingShadows;
   7895  // pt is the physical point where the decoration is to be drawn,
   7896  // relative to the frame; one of its coordinates will be updated below.
   7897  params.pt = Point(x / app, y / app);
   7898  Float& bCoord = verticalDec ? params.pt.x.value : params.pt.y.value;
   7899  params.lineSize = Size(measure / app, 0);
   7900  params.ascent = ascent;
   7901  params.vertical = verticalDec;
   7902  params.sidewaysLeft = mTextRun->IsSidewaysLeft();
   7903 
   7904  // The matrix of the context may have been altered for text-combine-
   7905  // upright. However, we want to draw decoration lines unscaled, thus
   7906  // we need to revert the scaling here.
   7907  gfxContextMatrixAutoSaveRestore scaledRestorer;
   7908  if (Style()->IsTextCombined()) {
   7909    float scaleFactor = GetTextCombineScale();
   7910    if (scaleFactor != 1.0f) {
   7911      scaledRestorer.SetContext(aParams.context);
   7912      gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
   7913      gfxPoint pt(x / app, y / app);
   7914      if (GetTextRun(nsTextFrame::eInflated)->IsRightToLeft()) {
   7915        pt.x += gfxFloat(frameSize.width) / app;
   7916      }
   7917      unscaled.PreTranslate(pt)
   7918          .PreScale(1.0f / scaleFactor, 1.0f)
   7919          .PreTranslate(-pt);
   7920      aParams.context->SetMatrixDouble(unscaled);
   7921    }
   7922  }
   7923 
   7924  // We create a clip region in order to draw the decoration lines only in the
   7925  // range of the text. Restricting the draw area prevents the decoration lines
   7926  // to be drawn multiple times when a part of the text is selected.
   7927  Maybe<gfxRect> clipRect;
   7928 
   7929  // We skip clipping for the following cases:
   7930  // - drawing the whole text
   7931  // - having different orientation of the text and the writing-mode, such as
   7932  //   "text-combine-upright" (Bug 1408825)
   7933  if (aRange.Length() != mTextRun->GetLength() && verticalDec == verticalRun) {
   7934    // Get the inline-size according to the specified range.
   7935    gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
   7936    nsRect visualRect = InkOverflowRect();
   7937 
   7938    const bool isInlineReversed = mTextRun->IsInlineReversed();
   7939    gfxFloat x, y, w, h;
   7940    if (verticalDec) {
   7941      x = aParams.framePt.x + visualRect.x;
   7942      y = isInlineReversed ? aTextBaselinePt.y.value - clipLength
   7943                           : aTextBaselinePt.y.value;
   7944      w = visualRect.width;
   7945      h = clipLength;
   7946    } else {
   7947      x = isInlineReversed ? aTextBaselinePt.x.value - clipLength
   7948                           : aTextBaselinePt.x.value;
   7949      y = aParams.framePt.y + visualRect.y;
   7950      w = clipLength;
   7951      h = visualRect.height;
   7952    }
   7953    clipRect.emplace(x, y, w, h);
   7954    clipRect->Scale(1 / app);
   7955    clipRect->Round();
   7956  }
   7957 
   7958  typedef gfxFont::Metrics Metrics;
   7959  auto paintDecorationLine = [&](const LineDecoration& dec,
   7960                                 gfxFloat Metrics::* lineSize,
   7961                                 StyleTextDecorationLine lineType) {
   7962    if (dec.mStyle == StyleTextDecorationStyle::None) {
   7963      return;
   7964    }
   7965 
   7966    float inflation =
   7967        GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
   7968    const Metrics metrics = GetFirstFontMetrics(
   7969        GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics);
   7970    if (!ComputeDecorationInset(this, aParams.textStyle->PresContext(),
   7971                                dec.mFrame, metrics, params)) {
   7972      return;
   7973    }
   7974    bCoord = (frameBStart - dec.mBaselineOffset) / app;
   7975 
   7976    params.color = dec.mColor;
   7977    params.baselineOffset = dec.mBaselineOffset / app;
   7978    params.defaultLineThickness = metrics.*lineSize;
   7979    params.lineSize.height = ComputeDecorationLineThickness(
   7980        dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app,
   7981        dec.mFrame);
   7982 
   7983    bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
   7984    params.offset = ComputeDecorationLineOffset(
   7985        lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics,
   7986        app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline);
   7987 
   7988    params.style = dec.mStyle;
   7989    params.allowInkSkipping = dec.mAllowInkSkipping;
   7990    params.skipInk = StyleText()->mTextDecorationSkipInk;
   7991    gfxClipAutoSaveRestore clipRestore(params.context);
   7992    // If we have a negative inset value, then the decoration will extend
   7993    // outside the edges of the text.
   7994    // TODO alaskanemily: Ideally we would adjust the clipping rect, but as
   7995    // an initial pass we just disable clipping in this case.
   7996    if (clipRect && !params.HasNegativeInset()) {
   7997      clipRestore.Clip(*clipRect);
   7998    }
   7999    PaintDecorationLine(params);
   8000  };
   8001 
   8002  // Underlines
   8003  params.decoration = StyleTextDecorationLine::UNDERLINE;
   8004  for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
   8005    paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
   8006  }
   8007 
   8008  // Overlines
   8009  params.decoration = StyleTextDecorationLine::OVERLINE;
   8010  for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
   8011    paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
   8012  }
   8013 
   8014  // Some glyphs and emphasis marks may extend outside the region, so we do
   8015  // not set the clip region here to the clip rect. For an example, italic
   8016  // glyphs.
   8017 
   8018  {
   8019    gfxContextMatrixAutoSaveRestore unscaledRestorer;
   8020    if (scaledRestorer.HasMatrix()) {
   8021      unscaledRestorer.SetContext(aParams.context);
   8022      aParams.context->SetMatrix(scaledRestorer.Matrix());
   8023    }
   8024 
   8025    // CSS 2.1 mandates that text be painted after over/underlines,
   8026    // and *then* line-throughs
   8027    DrawTextRun(aRange, aTextBaselinePt, aParams);
   8028  }
   8029 
   8030  // Emphasis marks
   8031  DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt,
   8032                    aRange, aParams.decorationOverrideColor, aParams.provider);
   8033 
   8034  // Line-throughs
   8035  params.decoration = StyleTextDecorationLine::LINE_THROUGH;
   8036  for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
   8037    paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration);
   8038  }
   8039 }
   8040 
   8041 void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
   8042                           const DrawTextParams& aParams) {
   8043  TextDecorations decorations;
   8044  GetTextDecorations(aParams.textStyle->PresContext(),
   8045                     aParams.callbacks ? eUnresolvedColors : eResolvedColors,
   8046                     decorations);
   8047 
   8048  // Hide text decorations if we're currently hiding @font-face fallback text
   8049  const bool drawDecorations =
   8050      !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
   8051      (decorations.HasDecorationLines() ||
   8052       StyleText()->HasEffectiveTextEmphasis());
   8053  if (drawDecorations) {
   8054    DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations);
   8055  } else {
   8056    DrawTextRun(aRange, aTextBaselinePt, aParams);
   8057  }
   8058 
   8059  if (auto* textDrawer = aParams.context->GetTextDrawer()) {
   8060    textDrawer->TerminateShadows();
   8061  }
   8062 }
   8063 
   8064 NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect)
   8065 
   8066 nsRect nsTextFrame::WebRenderBounds() {
   8067  // WR text bounds is just our ink overflow rect but without shadows. So if we
   8068  // have no shadows, just use the layout bounds.
   8069  if (!StyleText()->HasTextShadow()) {
   8070    return InkOverflowRect();
   8071  }
   8072  nsRect* cachedBounds = GetProperty(WebRenderTextBounds());
   8073  if (!cachedBounds) {
   8074    OverflowAreas overflowAreas;
   8075    ComputeCustomOverflowInternal(overflowAreas, false);
   8076    cachedBounds = new nsRect(overflowAreas.InkOverflow());
   8077    SetProperty(WebRenderTextBounds(), cachedBounds);
   8078  }
   8079  return *cachedBounds;
   8080 }
   8081 
   8082 int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) {
   8083  nsISelectionController* const selCon = GetSelectionController();
   8084  if (MOZ_UNLIKELY(!selCon)) {
   8085    return nsISelectionController::SELECTION_OFF;
   8086  }
   8087 
   8088  selCon->GetSelectionFlags(aSelectionFlags);
   8089 
   8090  int16_t selectionValue = nsISelectionController::SELECTION_OFF;
   8091  selCon->GetDisplaySelection(&selectionValue);
   8092  return selectionValue;
   8093 }
   8094 
   8095 bool nsTextFrame::IsEntirelyWhitespace() const {
   8096  const auto& characterDataBuffer = mContent->AsText()->DataBuffer();
   8097  for (uint32_t index = 0; index < characterDataBuffer.GetLength(); ++index) {
   8098    const char16_t ch = characterDataBuffer.CharAt(index);
   8099    if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0xa0) {
   8100      continue;
   8101    }
   8102    return false;
   8103  }
   8104  return true;
   8105 }
   8106 
   8107 /**
   8108 * Compute the longest prefix of text whose width is <= aWidth. Return
   8109 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
   8110 */
   8111 static uint32_t CountCharsFit(const gfxTextRun* aTextRun,
   8112                              gfxTextRun::Range aRange, gfxFloat aWidth,
   8113                              nsTextFrame::PropertyProvider* aProvider,
   8114                              gfxFloat* aFitWidth) {
   8115  uint32_t last = 0;
   8116  gfxFloat width = 0;
   8117  for (uint32_t i = 1; i <= aRange.Length(); ++i) {
   8118    if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
   8119      gfxTextRun::Range range(aRange.start + last, aRange.start + i);
   8120      gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
   8121      if (nextWidth > aWidth) {
   8122        break;
   8123      }
   8124      last = i;
   8125      width = nextWidth;
   8126    }
   8127  }
   8128  *aFitWidth = width;
   8129  return last;
   8130 }
   8131 
   8132 nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint(
   8133    const nsPoint& aPoint) {
   8134  return GetCharacterOffsetAtFramePointInternal(aPoint, true);
   8135 }
   8136 
   8137 nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint(
   8138    const nsPoint& aPoint) {
   8139  return GetCharacterOffsetAtFramePointInternal(aPoint, false);
   8140 }
   8141 
   8142 nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal(
   8143    const nsPoint& aPoint, bool aForInsertionPoint) {
   8144  ContentOffsets offsets;
   8145 
   8146  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   8147  if (!mTextRun) {
   8148    return offsets;
   8149  }
   8150 
   8151  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
   8152  // Trim leading but not trailing whitespace if possible
   8153  provider.InitializeForDisplay(false);
   8154  gfxFloat width =
   8155      mTextRun->IsVertical()
   8156          ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
   8157          : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
   8158  if (Style()->IsTextCombined()) {
   8159    width /= GetTextCombineScale();
   8160  }
   8161  gfxFloat fitWidth;
   8162  Range skippedRange = ComputeTransformedRange(provider);
   8163 
   8164  uint32_t charsFit =
   8165      CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth);
   8166 
   8167  int32_t selectedOffset;
   8168  if (charsFit < skippedRange.Length()) {
   8169    // charsFit characters fitted, but no more could fit. See if we're
   8170    // more than halfway through the cluster.. If we are, choose the next
   8171    // cluster.
   8172    gfxSkipCharsIterator extraCluster(provider.GetStart());
   8173    extraCluster.AdvanceSkipped(charsFit);
   8174 
   8175    bool allowSplitLigature = true;  // Allow selection of partial ligature...
   8176 
   8177    // ...but don't let selection/insertion-point split two Regional Indicator
   8178    // chars that are ligated in the textrun to form a single flag symbol.
   8179    uint32_t offs = extraCluster.GetOriginalOffset();
   8180    const auto& characterDataBuffer = CharacterDataBuffer();
   8181    if (characterDataBuffer.IsHighSurrogateFollowedByLowSurrogateAt(offs) &&
   8182        gfxFontUtils::IsRegionalIndicator(
   8183            characterDataBuffer.ScalarValueAt(offs))) {
   8184      allowSplitLigature = false;
   8185      if (extraCluster.GetSkippedOffset() > 1 &&
   8186          !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
   8187        // CountCharsFit() left us in the middle of the flag; back up over the
   8188        // first character of the ligature, and adjust fitWidth accordingly.
   8189        extraCluster.AdvanceSkipped(-2);  // it's a surrogate pair: 2 code units
   8190        fitWidth -= mTextRun->GetAdvanceWidth(
   8191            Range(extraCluster.GetSkippedOffset(),
   8192                  extraCluster.GetSkippedOffset() + 2),
   8193            &provider);
   8194      }
   8195    }
   8196 
   8197    gfxSkipCharsIterator extraClusterLastChar(extraCluster);
   8198    FindClusterEnd(
   8199        mTextRun,
   8200        provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
   8201        &extraClusterLastChar, allowSplitLigature);
   8202    PropertyProvider::Spacing spacing;
   8203    Range extraClusterRange(extraCluster.GetSkippedOffset(),
   8204                            extraClusterLastChar.GetSkippedOffset() + 1);
   8205    gfxFloat charWidth =
   8206        mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
   8207    charWidth -= spacing.mBefore + spacing.mAfter;
   8208    selectedOffset = !aForInsertionPoint ||
   8209                             width <= fitWidth + spacing.mBefore + charWidth / 2
   8210                         ? extraCluster.GetOriginalOffset()
   8211                         : extraClusterLastChar.GetOriginalOffset() + 1;
   8212  } else {
   8213    // All characters fitted, we're at (or beyond) the end of the text.
   8214    // XXX This could be some pathological situation where negative spacing
   8215    // caused characters to move backwards. We can't really handle that
   8216    // in the current frame system because frames can't have negative
   8217    // intrinsic widths.
   8218    selectedOffset =
   8219        provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
   8220    // If we're at the end of a preformatted line which has a terminating
   8221    // linefeed, we want to reduce the offset by one to make sure that the
   8222    // selection is placed before the linefeed character.
   8223    if (HasSignificantTerminalNewline()) {
   8224      --selectedOffset;
   8225    }
   8226  }
   8227 
   8228  offsets.content = GetContent();
   8229  offsets.offset = offsets.secondaryOffset = selectedOffset;
   8230  offsets.associate = mContentOffset == offsets.offset
   8231                          ? CaretAssociationHint::After
   8232                          : CaretAssociationHint::Before;
   8233  return offsets;
   8234 }
   8235 
   8236 bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
   8237                                                nsRect& aRect) {
   8238  if (aRect.IsEmpty()) {
   8239    return false;
   8240  }
   8241 
   8242  nsRect givenRect = aRect;
   8243 
   8244  gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this);
   8245  RefPtr<gfxFont> firstFont = fontGroup->GetFirstValidFont();
   8246  WritingMode wm = GetWritingMode();
   8247  bool verticalRun = wm.IsVertical();
   8248  bool useVerticalMetrics = verticalRun && !wm.IsSideways();
   8249  const gfxFont::Metrics& metrics =
   8250      firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
   8251                                               : nsFontMetrics::eHorizontal);
   8252 
   8253  nsCSSRendering::DecorationRectParams params;
   8254  params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
   8255 
   8256  params.offset = fontGroup->GetUnderlineOffset();
   8257 
   8258  TextDecorations textDecs;
   8259  GetTextDecorations(aPresContext, eResolvedColors, textDecs);
   8260 
   8261  params.descentLimit =
   8262      ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
   8263  params.vertical = verticalRun;
   8264 
   8265  if (verticalRun) {
   8266    EnsureTextRun(nsTextFrame::eInflated);
   8267    params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
   8268  } else {
   8269    params.sidewaysLeft = false;
   8270  }
   8271 
   8272  UniquePtr<SelectionDetails> details = GetSelectionDetails();
   8273  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
   8274    if (sd->mStart == sd->mEnd ||
   8275        sd->mSelectionType == SelectionType::eInvalid ||
   8276        !(ToSelectionTypeMask(sd->mSelectionType) &
   8277          kSelectionTypesWithDecorations) ||
   8278        // URL strikeout does not use underline.
   8279        sd->mSelectionType == SelectionType::eURLStrikeout) {
   8280      continue;
   8281    }
   8282    float relativeSize = 1.f;
   8283    RefPtr<ComputedStyle> style = Style();
   8284 
   8285    if (sd->mSelectionType == SelectionType::eNormal ||
   8286        sd->mSelectionType == SelectionType::eTargetText ||
   8287        sd->mSelectionType == SelectionType::eHighlight) {
   8288      style = [&]() {
   8289        if (sd->mSelectionType == SelectionType::eHighlight) {
   8290          return ComputeHighlightSelectionStyle(
   8291              sd->mHighlightData.mHighlightName);
   8292        }
   8293        if (sd->mSelectionType == SelectionType::eTargetText) {
   8294          return ComputeTargetTextStyle();
   8295        }
   8296        int16_t unusedFlags = 0;
   8297        const int16_t selectionStatus = GetSelectionStatus(&unusedFlags);
   8298        return ComputeSelectionStyle(selectionStatus);
   8299      }();
   8300      if (!style || !style->HasTextDecorationLines()) {
   8301        continue;
   8302      }
   8303      params.style = style->StyleTextReset()->mTextDecorationStyle;
   8304    } else {
   8305      auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
   8306          sd->mSelectionType);
   8307      if (sd->mSelectionType == SelectionType::eSpellCheck) {
   8308        if (!nsTextPaintStyle::GetSelectionUnderline(
   8309                this, index, nullptr, &relativeSize, &params.style)) {
   8310          continue;
   8311        }
   8312      } else {
   8313        // IME selections
   8314        TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
   8315        if (rangeStyle.IsDefined()) {
   8316          if (!rangeStyle.IsLineStyleDefined() ||
   8317              rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
   8318            continue;
   8319          }
   8320          params.style = ToStyleLineStyle(rangeStyle);
   8321          relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
   8322        } else if (!nsTextPaintStyle::GetSelectionUnderline(
   8323                       this, index, nullptr, &relativeSize, &params.style)) {
   8324          continue;
   8325        }
   8326      }
   8327    }
   8328    nsRect decorationArea;
   8329 
   8330    const auto& decThickness =
   8331        style->StyleTextReset()->mTextDecorationThickness;
   8332    params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
   8333    params.defaultLineThickness = ComputeSelectionUnderlineHeight(
   8334        aPresContext, metrics, sd->mSelectionType);
   8335 
   8336    params.lineSize.height = ComputeDecorationLineThickness(
   8337        decThickness, params.defaultLineThickness, metrics,
   8338        aPresContext->AppUnitsPerDevPixel(), this);
   8339 
   8340    bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*style);
   8341    const auto* styleText = style->StyleText();
   8342    params.offset = ComputeDecorationLineOffset(
   8343        textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE
   8344                                : StyleTextDecorationLine::OVERLINE,
   8345        styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset,
   8346        metrics, aPresContext->AppUnitsPerDevPixel(), this,
   8347        wm.IsCentralBaseline(), swapUnderline);
   8348 
   8349    relativeSize = std::max(relativeSize, 1.0f);
   8350    params.lineSize.height *= relativeSize;
   8351    params.defaultLineThickness *= relativeSize;
   8352    decorationArea =
   8353        nsCSSRendering::GetTextDecorationRect(aPresContext, params);
   8354    aRect.UnionRect(aRect, decorationArea);
   8355  }
   8356 
   8357  return !aRect.IsEmpty() && !givenRect.Contains(aRect);
   8358 }
   8359 
   8360 bool nsTextFrame::IsFrameSelected() const {
   8361  NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
   8362               "use the public IsSelected() instead");
   8363  if (mIsSelected == nsTextFrame::SelectionState::Unknown) {
   8364    const bool isSelected =
   8365        GetContent()->IsSelected(GetContentOffset(), GetContentEnd(),
   8366                                 PresShell()->GetSelectionNodeCache());
   8367    mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected
   8368                             : nsTextFrame::SelectionState::NotSelected;
   8369  } else {
   8370 #ifdef DEBUG
   8371    // Assert that the selection caching works.
   8372    const bool isReallySelected =
   8373        GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
   8374    MOZ_ASSERT((mIsSelected == nsTextFrame::SelectionState::Selected) ==
   8375                   isReallySelected,
   8376               "Should have called InvalidateSelectionState()");
   8377 #endif
   8378  }
   8379 
   8380  return mIsSelected == nsTextFrame::SelectionState::Selected;
   8381 }
   8382 
   8383 nsTextFrame* nsTextFrame::FindContinuationForOffset(int32_t aOffset) {
   8384  // Use a continuations array to accelerate finding the first continuation
   8385  // of interest, if possible.
   8386  MOZ_ASSERT(!GetPrevContinuation(), "should be called on the primary frame");
   8387  auto* continuations = GetContinuations();
   8388  nsTextFrame* f = this;
   8389  if (continuations) {
   8390    size_t index;
   8391    if (BinarySearchIf(
   8392            *continuations, 0, continuations->Length(),
   8393            [=](nsTextFrame* aFrame) -> int {
   8394              return aOffset - aFrame->GetContentOffset();
   8395            },
   8396            &index)) {
   8397      f = (*continuations)[index];
   8398    } else {
   8399      f = (*continuations)[index ? index - 1 : 0];
   8400    }
   8401  }
   8402 
   8403  while (f && f->GetContentEnd() <= aOffset) {
   8404    f = f->GetNextContinuation();
   8405  }
   8406 
   8407  return f;
   8408 }
   8409 
   8410 void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd,
   8411                                        bool aSelected,
   8412                                        SelectionType aSelectionType) {
   8413  NS_ASSERTION(!GetPrevContinuation(),
   8414               "Should only be called for primary frame");
   8415  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
   8416 
   8417  InvalidateSelectionState();
   8418 
   8419  // Selection is collapsed, which can't affect text frame rendering
   8420  if (aStart == aEnd) {
   8421    return;
   8422  }
   8423 
   8424  nsTextFrame* f = FindContinuationForOffset(aStart);
   8425 
   8426  nsPresContext* presContext = PresContext();
   8427  while (f && f->GetContentOffset() < int32_t(aEnd)) {
   8428    // We may need to reflow to recompute the overflow area for
   8429    // spellchecking or IME underline if their underline is thicker than
   8430    // the normal decoration line.
   8431    if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) {
   8432      bool didHaveOverflowingSelection =
   8433          f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
   8434      nsRect r(nsPoint(0, 0), GetSize());
   8435      if (didHaveOverflowingSelection ||
   8436          (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
   8437        presContext->PresShell()->FrameNeedsReflow(
   8438            f, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
   8439      }
   8440    }
   8441    // Selection might change anything. Invalidate the overflow area.
   8442    f->InvalidateFrame();
   8443 
   8444    f = f->GetNextContinuation();
   8445  }
   8446 }
   8447 
   8448 void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
   8449                                           int32_t& aInOffset,
   8450                                           gfxSkipCharsIterator& aIter) {
   8451  if (aInOffset < GetContentOffset()) {
   8452    NS_WARNING("offset before this frame's content");
   8453    aInOffset = GetContentOffset();
   8454  } else if (aInOffset > GetContentEnd()) {
   8455    NS_WARNING("offset after this frame's content");
   8456    aInOffset = GetContentEnd();
   8457  }
   8458 
   8459  int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
   8460  int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
   8461  aInOffset = std::max(aInOffset, trimmedOffset);
   8462  aInOffset = std::min(aInOffset, trimmedEnd);
   8463 
   8464  aIter.SetOriginalOffset(aInOffset);
   8465 
   8466  if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() &&
   8467      !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
   8468    // Called for non-cluster boundary
   8469    FindClusterStart(mTextRun, trimmedOffset, &aIter);
   8470  }
   8471 }
   8472 
   8473 nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
   8474                                          PropertyProvider& aProperties) {
   8475  Range range(aProperties.GetStart().GetSkippedOffset(),
   8476              aIter.GetSkippedOffset());
   8477  gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
   8478  nscoord iSize = NSToCoordCeilClamped(advance);
   8479  nsPoint point;
   8480 
   8481  if (mTextRun->IsVertical()) {
   8482    point.x = 0;
   8483    if (mTextRun->IsInlineReversed()) {
   8484      point.y = mRect.height - iSize;
   8485    } else {
   8486      point.y = iSize;
   8487    }
   8488  } else {
   8489    point.y = 0;
   8490    if (Style()->IsTextCombined()) {
   8491      iSize *= GetTextCombineScale();
   8492    }
   8493    if (mTextRun->IsInlineReversed()) {
   8494      point.x = mRect.width - iSize;
   8495    } else {
   8496      point.x = iSize;
   8497    }
   8498  }
   8499  return point;
   8500 }
   8501 
   8502 nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
   8503  if (!outPoint) {
   8504    return NS_ERROR_NULL_POINTER;
   8505  }
   8506 
   8507  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
   8508  if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
   8509    return NS_ERROR_UNEXPECTED;
   8510  }
   8511 
   8512  if (GetContentLength() <= 0) {
   8513    outPoint->x = 0;
   8514    outPoint->y = 0;
   8515    return NS_OK;
   8516  }
   8517 
   8518  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   8519  if (!mTextRun) {
   8520    return NS_ERROR_FAILURE;
   8521  }
   8522 
   8523  PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
   8524  // Don't trim trailing whitespace, we want the caret to appear in the right
   8525  // place if it's positioned there
   8526  properties.InitializeForDisplay(false);
   8527 
   8528  UpdateIteratorFromOffset(properties, inOffset, iter);
   8529 
   8530  *outPoint = GetPointFromIterator(iter, properties);
   8531 
   8532  return NS_OK;
   8533 }
   8534 
   8535 nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
   8536                                               int32_t aLength,
   8537                                               nsTArray<nsRect>& aRects) {
   8538  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
   8539  if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
   8540    return NS_ERROR_UNEXPECTED;
   8541  }
   8542 
   8543  if (GetContentLength() <= 0) {
   8544    return NS_OK;
   8545  }
   8546 
   8547  if (!mTextRun) {
   8548    return NS_ERROR_FAILURE;
   8549  }
   8550 
   8551  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   8552  PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
   8553  // Don't trim trailing whitespace, we want the caret to appear in the right
   8554  // place if it's positioned there
   8555  properties.InitializeForDisplay(false);
   8556 
   8557  // Initialize iter; this will call FindClusterStart if necessary to align
   8558  // iter to a cluster boundary.
   8559  UpdateIteratorFromOffset(properties, aInOffset, iter);
   8560  nsPoint point = GetPointFromIterator(iter, properties);
   8561 
   8562  const int32_t kContentEnd = GetContentEnd();
   8563  const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
   8564 
   8565  if (aInOffset >= kEndOffset) {
   8566    return NS_OK;
   8567  }
   8568 
   8569  if (!aRects.SetCapacity(aRects.Length() + kEndOffset - aInOffset,
   8570                          mozilla::fallible)) {
   8571    return NS_ERROR_OUT_OF_MEMORY;
   8572  }
   8573 
   8574  do {
   8575    // We'd like to assert here that |point| matches
   8576    // |GetPointFromIterator(iter, properties)|, which in principle should be
   8577    // true; however, testcases with vast dimensions can lead to coordinate
   8578    // overflow and disrupt the calculations. So we've dropped the assertion
   8579    // to avoid tripping the fuzzer unnecessarily.
   8580 
   8581    // Measure to the end of the cluster.
   8582    nscoord iSize = 0;
   8583    gfxSkipCharsIterator nextIter(iter);
   8584    if (aInOffset < kContentEnd) {
   8585      nextIter.AdvanceOriginal(1);
   8586      if (!nextIter.IsOriginalCharSkipped() &&
   8587          !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) &&
   8588          nextIter.GetOriginalOffset() < kContentEnd) {
   8589        FindClusterEnd(mTextRun, kContentEnd, &nextIter);
   8590      }
   8591 
   8592      gfxFloat advance = mTextRun->GetAdvanceWidth(
   8593          Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()),
   8594          &properties);
   8595      iSize = NSToCoordCeilClamped(advance);
   8596    }
   8597 
   8598    // Compute the cluster rect, depending on directionality, and update
   8599    // point to the origin we'll need for the next cluster.
   8600    nsRect rect;
   8601    rect.x = point.x;
   8602    rect.y = point.y;
   8603 
   8604    if (mTextRun->IsVertical()) {
   8605      rect.width = mRect.width;
   8606      rect.height = iSize;
   8607      if (mTextRun->IsInlineReversed()) {
   8608        // The iterator above returns a point with the origin at the
   8609        // bottom left instead of the top left. Move the origin to the top left
   8610        // by subtracting the character's height.
   8611        rect.y -= rect.height;
   8612        point.y -= iSize;
   8613      } else {
   8614        point.y += iSize;
   8615      }
   8616    } else {
   8617      if (Style()->IsTextCombined()) {
   8618        // The scale factor applies to the inline advance of the glyphs, so it
   8619        // affects both the rect width and the origin point for the next glyph.
   8620        iSize *= GetTextCombineScale();
   8621      }
   8622      rect.width = iSize;
   8623      rect.height = mRect.height;
   8624      if (mTextRun->IsInlineReversed()) {
   8625        // The iterator above returns a point with the origin at the
   8626        // top right instead of the top left. Move the origin to the top left by
   8627        // subtracting the character's width.
   8628        rect.x -= iSize;
   8629        point.x -= iSize;
   8630      } else {
   8631        point.x += iSize;
   8632      }
   8633    }
   8634 
   8635    // Set the rect for all characters in the cluster.
   8636    int32_t end = std::min(kEndOffset, nextIter.GetOriginalOffset());
   8637    while (aInOffset < end) {
   8638      aRects.AppendElement(rect);
   8639      aInOffset++;
   8640    }
   8641 
   8642    // Advance iter for the next cluster.
   8643    iter = nextIter;
   8644  } while (aInOffset < kEndOffset);
   8645 
   8646  return NS_OK;
   8647 }
   8648 
   8649 nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
   8650                                                    bool aHint,
   8651                                                    int32_t* aOutOffset,
   8652                                                    nsIFrame** aOutFrame) {
   8653  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
   8654 #if 0  // XXXrbs disable due to bug 310227
   8655  if (HasAnyStateBits(NS_FRAME_IS_DIRTY))
   8656    return NS_ERROR_UNEXPECTED;
   8657 #endif
   8658 
   8659  NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
   8660  NS_ASSERTION(aContentOffset >= 0,
   8661               "Negative content offset, existing code was very broken!");
   8662  nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
   8663  if (this != primaryFrame) {
   8664    // This call needs to happen on the primary frame
   8665    return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
   8666                                                       aOutOffset, aOutFrame);
   8667  }
   8668 
   8669  nsTextFrame* f = this;
   8670  int32_t offset = mContentOffset;
   8671 
   8672  // Try to look up the offset to frame property
   8673  nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
   8674 
   8675  if (cachedFrame) {
   8676    f = cachedFrame;
   8677    offset = f->GetContentOffset();
   8678 
   8679    f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
   8680  }
   8681 
   8682  if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) {
   8683    while (true) {
   8684      nsTextFrame* next = f->GetNextContinuation();
   8685      if (!next || aContentOffset < next->GetContentOffset()) {
   8686        break;
   8687      }
   8688      if (aContentOffset == next->GetContentOffset()) {
   8689        if (aHint) {
   8690          f = next;
   8691          if (f->GetContentLength() == 0) {
   8692            continue;  // use the last of the empty frames with this offset
   8693          }
   8694        }
   8695        break;
   8696      }
   8697      f = next;
   8698    }
   8699  } else {
   8700    while (true) {
   8701      nsTextFrame* prev = f->GetPrevContinuation();
   8702      if (!prev || aContentOffset > f->GetContentOffset()) {
   8703        break;
   8704      }
   8705      if (aContentOffset == f->GetContentOffset()) {
   8706        if (!aHint) {
   8707          f = prev;
   8708          if (f->GetContentLength() == 0) {
   8709            continue;  // use the first of the empty frames with this offset
   8710          }
   8711        }
   8712        break;
   8713      }
   8714      f = prev;
   8715    }
   8716  }
   8717 
   8718  *aOutOffset = aContentOffset - f->GetContentOffset();
   8719  *aOutFrame = f;
   8720 
   8721  // cache the frame we found
   8722  SetProperty(OffsetToFrameProperty(), f);
   8723  f->AddStateBits(TEXT_IN_OFFSET_CACHE);
   8724 
   8725  return NS_OK;
   8726 }
   8727 
   8728 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
   8729                                                            int32_t* aOffset) {
   8730  NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),
   8731               "aOffset out of range");
   8732 
   8733  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   8734  if (!mTextRun) {
   8735    return CONTINUE_EMPTY;
   8736  }
   8737 
   8738  TrimmedOffsets trimmed = GetTrimmedOffsets(CharacterDataBuffer());
   8739  // Check whether there are nonskipped characters in the trimmmed range
   8740  return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
   8741          iter.ConvertOriginalToSkipped(trimmed.mStart))
   8742             ? FOUND
   8743             : CONTINUE;
   8744 }
   8745 
   8746 /**
   8747 * This class iterates through the clusters before or after the given
   8748 * aPosition (which is a content offset). You can test each cluster
   8749 * to see if it's whitespace (as far as selection/caret movement is concerned),
   8750 * or punctuation, or if there is a word break before the cluster. ("Before"
   8751 * is interpreted according to aDirection, so if aDirection is -1, "before"
   8752 * means actually *after* the cluster content.)
   8753 */
   8754 class MOZ_STACK_CLASS ClusterIterator {
   8755 public:
   8756  ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
   8757                  int32_t aDirection, nsString& aContext,
   8758                  bool aTrimSpaces = true);
   8759 
   8760  bool NextCluster();
   8761  bool IsInlineWhitespace() const;
   8762  bool IsNewline() const;
   8763  bool IsPunctuation() const;
   8764  intl::Script ScriptCode() const;
   8765  bool HaveWordBreakBefore() const { return mHaveWordBreak; }
   8766 
   8767  // Get the charIndex that corresponds to the "before" side of the current
   8768  // character, according to the direction of iteration: so for a forward
   8769  // iterator, this is simply mCharIndex, while for a reverse iterator it will
   8770  // be mCharIndex + <number of code units in the character>.
   8771  int32_t GetBeforeOffset() const {
   8772    MOZ_ASSERT(mCharIndex >= 0);
   8773    return mDirection < 0 ? GetAfterInternal() : mCharIndex;
   8774  }
   8775  // Get the charIndex that corresponds to the "before" side of the current
   8776  // character, according to the direction of iteration: the opposite side
   8777  // to what GetBeforeOffset returns.
   8778  int32_t GetAfterOffset() const {
   8779    MOZ_ASSERT(mCharIndex >= 0);
   8780    return mDirection > 0 ? GetAfterInternal() : mCharIndex;
   8781  }
   8782 
   8783 private:
   8784  // Helper for Get{After,Before}Offset; returns the charIndex after the
   8785  // current position in the text, accounting for surrogate pairs.
   8786  int32_t GetAfterInternal() const;
   8787 
   8788  gfxSkipCharsIterator mIterator;
   8789  // Usually, mCharacterDataBuffer is pointer to `dom::CharacterData::mText`.
   8790  // However, if we're in a password field, this points to `mMaskedBuffer`.
   8791  const CharacterDataBuffer* mCharacterDataBuffer;
   8792  // If we're in a password field, this is initialized with mask characters.
   8793  CharacterDataBuffer mMaskedBuffer;
   8794  nsTextFrame* mTextFrame;
   8795  int32_t mDirection;  // +1 or -1, or 0 to indicate failure
   8796  int32_t mCharIndex;
   8797  nsTextFrame::TrimmedOffsets mTrimmed;
   8798  nsTArray<bool> mWordBreaks;
   8799  bool mHaveWordBreak;
   8800 };
   8801 
   8802 static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
   8803                                      bool aRespectClusters,
   8804                                      const gfxTextRun* aTextRun,
   8805                                      nsTextFrame* aFrame) {
   8806  if (aIter.IsOriginalCharSkipped()) {
   8807    return false;
   8808  }
   8809  uint32_t index = aIter.GetSkippedOffset();
   8810  if (aRespectClusters && !aTextRun->IsClusterStart(index)) {
   8811    return false;
   8812  }
   8813  if (index > 0) {
   8814    // Check whether the proposed position is in between the two halves of a
   8815    // surrogate pair, before a Variation Selector character, or within a
   8816    // ligated emoji sequence; if so, this is not a valid character boundary.
   8817    // (In the case where we are respecting clusters, we won't actually get
   8818    // this far because the low surrogate is also marked as non-clusterStart
   8819    // so we'll return FALSE above.)
   8820    const uint32_t offs = AssertedCast<uint32_t>(aIter.GetOriginalOffset());
   8821    const CharacterDataBuffer& characterDataBuffer =
   8822        aFrame->CharacterDataBuffer();
   8823    const char16_t ch = characterDataBuffer.CharAt(offs);
   8824 
   8825    if (gfxFontUtils::IsVarSelector(ch) ||
   8826        characterDataBuffer.IsLowSurrogateFollowingHighSurrogateAt(offs) ||
   8827        (!aTextRun->IsLigatureGroupStart(index) &&
   8828         (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault ||
   8829          (unicode::GetEmojiPresentation(ch) == unicode::TextDefault &&
   8830           offs + 1 < characterDataBuffer.GetLength() &&
   8831           characterDataBuffer.CharAt(offs + 1) ==
   8832               gfxFontUtils::kUnicodeVS16)))) {
   8833      return false;
   8834    }
   8835 
   8836    // If the proposed position is before a high surrogate, we need to decode
   8837    // the surrogate pair (if valid) and check the resulting character.
   8838    if (NS_IS_HIGH_SURROGATE(ch)) {
   8839      if (const char32_t ucs4 = characterDataBuffer.ScalarValueAt(offs)) {
   8840        // If the character is a (Plane-14) variation selector,
   8841        // or an emoji character that is ligated with the previous
   8842        // character (i.e. part of a Regional-Indicator flag pair,
   8843        // or an emoji-ZWJ sequence), this is not a valid boundary.
   8844        if (gfxFontUtils::IsVarSelector(ucs4) ||
   8845            (!aTextRun->IsLigatureGroupStart(index) &&
   8846             unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) {
   8847          return false;
   8848        }
   8849      }
   8850    }
   8851  }
   8852  return true;
   8853 }
   8854 
   8855 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter(
   8856    bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
   8857  const int32_t contentLengthInFrame = GetContentLength();
   8858  NS_ASSERTION(aOffset && *aOffset <= contentLengthInFrame,
   8859               "aOffset out of range");
   8860 
   8861  if (!aOptions.mIgnoreUserStyleAll) {
   8862    StyleUserSelect selectStyle;
   8863    (void)IsSelectable(&selectStyle);
   8864    if (selectStyle == StyleUserSelect::All) {
   8865      return CONTINUE_UNSELECTABLE;
   8866    }
   8867  }
   8868 
   8869  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
   8870  if (!mTextRun) {
   8871    return CONTINUE_EMPTY;
   8872  }
   8873  const TrimmedOffsets trimmed =
   8874      GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter);
   8875 
   8876  // A negative offset means "end of frame".
   8877  const int32_t offset =
   8878      GetContentOffset() + (*aOffset < 0 ? contentLengthInFrame : *aOffset);
   8879 
   8880  if (!aForward) {
   8881    const int32_t endOffset = [&]() -> int32_t {
   8882      const int32_t minEndOffset = std::min(trimmed.GetEnd(), offset);
   8883      if (minEndOffset <= trimmed.mStart ||
   8884          minEndOffset + 1 >= trimmed.GetEnd()) {
   8885        return minEndOffset;
   8886      }
   8887      // If the end offset points a character in this frame and it's skipped
   8888      // character, we want to scan previous character of the prceding first
   8889      // non-skipped character.
   8890      for (const int32_t i :
   8891           Reversed(IntegerRange(trimmed.mStart, minEndOffset + 1))) {
   8892        iter.SetOriginalOffset(i);
   8893        if (!iter.IsOriginalCharSkipped()) {
   8894          return i;
   8895        }
   8896      }
   8897      return trimmed.mStart;
   8898    }();
   8899    // If we're at the start of a line, look at the next continuation
   8900    if (endOffset <= trimmed.mStart) {
   8901      *aOffset = 0;
   8902      return CONTINUE;
   8903    }
   8904    // If at the beginning of the line, look at the previous continuation
   8905    for (const int32_t i : Reversed(IntegerRange(trimmed.mStart, endOffset))) {
   8906      iter.SetOriginalOffset(i);
   8907      // If we entered into a skipped char range again, we should skip all
   8908      // of them.  However, we cannot know the number of preceding skipped
   8909      // chars.  Therefore, we cannot skip all of them once.
   8910      if (iter.IsOriginalCharSkipped()) {
   8911        continue;
   8912      }
   8913      if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
   8914                                    this)) {
   8915        *aOffset = i - mContentOffset;
   8916        return FOUND;
   8917      }
   8918    }
   8919    *aOffset = 0;
   8920    return CONTINUE;
   8921  }
   8922 
   8923  // If we're at the end of a line, look at the next continuation
   8924  if (offset + 1 > trimmed.GetEnd()) {
   8925    *aOffset = contentLengthInFrame;
   8926    return CONTINUE;
   8927  }
   8928 
   8929  iter.SetOriginalOffset(offset);
   8930 
   8931  // If we're at a preformatted linefeed, look at the next continutation
   8932  if (offset < trimmed.GetEnd() && StyleText()->NewlineIsSignificant(this) &&
   8933      iter.GetSkippedOffset() < mTextRun->GetLength() &&
   8934      mTextRun->CharIsNewline(iter.GetSkippedOffset())) {
   8935    *aOffset = contentLengthInFrame;
   8936    return CONTINUE;
   8937  }
   8938 
   8939  const int32_t scanStartOffset = [&]() -> int32_t {
   8940    // If current char is skipped, scan starting from the following
   8941    // non-skipped char.
   8942    int32_t skippedLength = 0;
   8943    if (iter.IsOriginalCharSkipped(&skippedLength)) {
   8944      const int32_t skippedLengthInFrame =
   8945          std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset());
   8946      return iter.GetOriginalOffset() + skippedLengthInFrame + 1;
   8947    }
   8948    return iter.GetOriginalOffset() + 1;
   8949  }();
   8950 
   8951  for (int32_t i = scanStartOffset; i < trimmed.GetEnd(); i++) {
   8952    iter.SetOriginalOffset(i);
   8953    // If we entered into a skipped char range again, we should skip all
   8954    // of them.
   8955    int32_t skippedLength = 0;
   8956    if (iter.IsOriginalCharSkipped(&skippedLength)) {
   8957      const int32_t skippedLengthInFrame =
   8958          std::min(skippedLength, trimmed.GetEnd() - iter.GetOriginalOffset());
   8959      if (skippedLengthInFrame) {
   8960        i += skippedLengthInFrame - 1;
   8961      }
   8962      continue;
   8963    }
   8964    if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
   8965                                  this)) {
   8966      *aOffset = i - mContentOffset;
   8967      return FOUND;
   8968    }
   8969  }
   8970 
   8971  *aOffset = trimmed.GetEnd() - mContentOffset;
   8972  return FOUND;
   8973 }
   8974 
   8975 bool ClusterIterator::IsInlineWhitespace() const {
   8976  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
   8977  return IsSelectionInlineWhitespace(*mCharacterDataBuffer, mCharIndex);
   8978 }
   8979 
   8980 bool ClusterIterator::IsNewline() const {
   8981  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
   8982  return IsSelectionNewline(*mCharacterDataBuffer, mCharIndex);
   8983 }
   8984 
   8985 bool ClusterIterator::IsPunctuation() const {
   8986  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
   8987  const char16_t ch =
   8988      mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex));
   8989  return mozilla::IsPunctuationForWordSelect(ch);
   8990 }
   8991 
   8992 intl::Script ClusterIterator::ScriptCode() const {
   8993  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
   8994  const char16_t ch =
   8995      mCharacterDataBuffer->CharAt(AssertedCast<uint32_t>(mCharIndex));
   8996  return intl::UnicodeProperties::GetScriptCode(ch);
   8997 }
   8998 
   8999 static inline bool IsKorean(intl::Script aScript) {
   9000  // We only need to check for HANGUL script code; there is a script code
   9001  // KOREAN but this is not assigned to any codepoints. (If that ever changes,
   9002  // we could check for both codes here.)
   9003  MOZ_ASSERT(aScript != intl::Script::KOREAN, "unexpected script code");
   9004  return aScript == intl::Script::HANGUL;
   9005 }
   9006 
   9007 int32_t ClusterIterator::GetAfterInternal() const {
   9008  if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt(
   9009          AssertedCast<uint32_t>(mCharIndex))) {
   9010    return mCharIndex + 2;
   9011  }
   9012  return mCharIndex + 1;
   9013 }
   9014 
   9015 bool ClusterIterator::NextCluster() {
   9016  if (!mDirection) {
   9017    return false;
   9018  }
   9019  const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
   9020 
   9021  mHaveWordBreak = false;
   9022  while (true) {
   9023    bool keepGoing = false;
   9024    if (mDirection > 0) {
   9025      if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) {
   9026        return false;
   9027      }
   9028      keepGoing = mIterator.IsOriginalCharSkipped() ||
   9029                  mIterator.GetOriginalOffset() < mTrimmed.mStart ||
   9030                  !textRun->IsClusterStart(mIterator.GetSkippedOffset());
   9031      mCharIndex = mIterator.GetOriginalOffset();
   9032      mIterator.AdvanceOriginal(1);
   9033    } else {
   9034      if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) {
   9035        // Trimming can skip backward word breakers, see bug 1667138
   9036        return mHaveWordBreak;
   9037      }
   9038      mIterator.AdvanceOriginal(-1);
   9039      keepGoing = mIterator.IsOriginalCharSkipped() ||
   9040                  mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
   9041                  !textRun->IsClusterStart(mIterator.GetSkippedOffset());
   9042      mCharIndex = mIterator.GetOriginalOffset();
   9043    }
   9044 
   9045    if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
   9046      mHaveWordBreak = true;
   9047    }
   9048    if (!keepGoing) {
   9049      return true;
   9050    }
   9051  }
   9052 }
   9053 
   9054 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
   9055                                 int32_t aDirection, nsString& aContext,
   9056                                 bool aTrimSpaces)
   9057    : mIterator(aTextFrame->EnsureTextRun(nsTextFrame::eInflated)),
   9058      mTextFrame(aTextFrame),
   9059      mDirection(aDirection),
   9060      mCharIndex(-1),
   9061      mHaveWordBreak(false) {
   9062  gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated);
   9063  if (!textRun) {
   9064    mDirection = 0;  // signal failure
   9065    return;
   9066  }
   9067 
   9068  mCharacterDataBuffer = &aTextFrame->CharacterDataBuffer();
   9069 
   9070  const uint32_t textOffset =
   9071      AssertedCast<uint32_t>(aTextFrame->GetContentOffset());
   9072  const uint32_t textLen =
   9073      AssertedCast<uint32_t>(aTextFrame->GetContentLength());
   9074 
   9075  // If we're in a password field, some characters may be masked.  In such
   9076  // case, we need to treat each masked character as a mask character since
   9077  // we shouldn't expose word boundary which is hidden by the masking.
   9078  if (aTextFrame->GetContent() && mCharacterDataBuffer->GetLength() > 0 &&
   9079      aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) &&
   9080      (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) {
   9081    const char16_t kPasswordMask = TextEditor::PasswordMask();
   9082    const nsTransformedTextRun* transformedTextRun =
   9083        static_cast<const nsTransformedTextRun*>(textRun);
   9084    // Use nsString and not nsAutoString so that we get a nsStringBuffer which
   9085    // can be just AddRefed in `mMaskedFrag`.
   9086    nsString maskedText;
   9087    maskedText.SetCapacity(mCharacterDataBuffer->GetLength());
   9088    // Note that aTextFrame may not cover the whole of mFrag (in cases with
   9089    // bidi continuations), so we cannot rely on its textrun (and associated
   9090    // styles) being available for the entire fragment.
   9091    uint32_t i = 0;
   9092    // Just copy any text that precedes what aTextFrame covers.
   9093    while (i < textOffset) {
   9094      maskedText.Append(mCharacterDataBuffer->CharAt(i++));
   9095    }
   9096    // For the range covered by aTextFrame, mask chars if appropriate.
   9097    while (i < textOffset + textLen) {
   9098      uint32_t skippedOffset = mIterator.ConvertOriginalToSkipped(i);
   9099      bool mask =
   9100          skippedOffset < transformedTextRun->GetLength()
   9101              ? transformedTextRun->mStyles[skippedOffset]->mMaskPassword
   9102              : false;
   9103      if (mCharacterDataBuffer->IsHighSurrogateFollowedByLowSurrogateAt(i)) {
   9104        if (mask) {
   9105          maskedText.Append(kPasswordMask);
   9106          maskedText.Append(kPasswordMask);
   9107        } else {
   9108          maskedText.Append(mCharacterDataBuffer->CharAt(i));
   9109          maskedText.Append(mCharacterDataBuffer->CharAt(i + 1));
   9110        }
   9111        i += 2;
   9112      } else {
   9113        maskedText.Append(mask ? kPasswordMask
   9114                               : mCharacterDataBuffer->CharAt(i));
   9115        ++i;
   9116      }
   9117    }
   9118    // Copy any trailing text from the fragment.
   9119    while (i < mCharacterDataBuffer->GetLength()) {
   9120      maskedText.Append(mCharacterDataBuffer->CharAt(i++));
   9121    }
   9122    mMaskedBuffer.SetTo(maskedText, mCharacterDataBuffer->IsBidi(), true);
   9123    mCharacterDataBuffer = &mMaskedBuffer;
   9124  }
   9125 
   9126  mIterator.SetOriginalOffset(aPosition);
   9127  mTrimmed = aTextFrame->GetTrimmedOffsets(
   9128      *mCharacterDataBuffer,
   9129      aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default
   9130                  : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter |
   9131                        nsTextFrame::TrimmedOffsetFlags::NoTrimBefore);
   9132 
   9133  // Allocate an extra element to record the word break at the end of the line
   9134  // or text run in mWordBreak[textLen].
   9135  mWordBreaks.AppendElements(textLen + 1);
   9136  PodZero(mWordBreaks.Elements(), textLen + 1);
   9137  uint32_t textStart;
   9138  if (aDirection > 0) {
   9139    if (aContext.IsEmpty()) {
   9140      // No previous context, so it must be the start of a line or text run
   9141      mWordBreaks[0] = true;
   9142    }
   9143    textStart = aContext.Length();
   9144    mCharacterDataBuffer->AppendTo(aContext, textOffset, textLen);
   9145  } else {
   9146    if (aContext.IsEmpty()) {
   9147      // No following context, so it must be the end of a line or text run
   9148      mWordBreaks[textLen] = true;
   9149    }
   9150    textStart = 0;
   9151    nsAutoString str;
   9152    mCharacterDataBuffer->AppendTo(str, textOffset, textLen);
   9153    aContext.Insert(str, 0);
   9154  }
   9155 
   9156  const uint32_t textEnd = textStart + textLen;
   9157  intl::WordBreakIteratorUtf16 wordBreakIter(aContext);
   9158  Maybe<uint32_t> nextBreak =
   9159      wordBreakIter.Seek(textStart > 0 ? textStart - 1 : textStart);
   9160  while (nextBreak && *nextBreak <= textEnd) {
   9161    mWordBreaks[*nextBreak - textStart] = true;
   9162    nextBreak = wordBreakIter.Next();
   9163  }
   9164 
   9165  MOZ_ASSERT(textEnd != aContext.Length() || mWordBreaks[textLen],
   9166             "There should be a word break at the end of a line or text run!");
   9167 }
   9168 
   9169 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord(
   9170    bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
   9171    int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
   9172  int32_t contentLength = GetContentLength();
   9173  NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
   9174 
   9175  StyleUserSelect selectStyle;
   9176  (void)IsSelectable(&selectStyle);
   9177  if (selectStyle == StyleUserSelect::All) {
   9178    return CONTINUE_UNSELECTABLE;
   9179  }
   9180 
   9181  int32_t offset =
   9182      GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
   9183  ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext,
   9184                        aTrimSpaces);
   9185 
   9186  if (!cIter.NextCluster()) {
   9187    return CONTINUE_EMPTY;
   9188  }
   9189 
   9190  // Do we need to check for Korean characters?
   9191  bool is2b = CharacterDataBuffer().Is2b();
   9192  do {
   9193    bool isPunctuation = cIter.IsPunctuation();
   9194    bool isInlineWhitespace = cIter.IsInlineWhitespace();
   9195    bool isWhitespace = isInlineWhitespace || cIter.IsNewline();
   9196    bool isWordBreakBefore = cIter.HaveWordBreakBefore();
   9197    // If the text is one-byte, we don't actually care about script code as
   9198    // there cannot be any Korean in the frame.
   9199    intl::Script scriptCode = is2b ? cIter.ScriptCode() : intl::Script::COMMON;
   9200    if (!isWhitespace || isInlineWhitespace) {
   9201      aState->SetSawInlineCharacter();
   9202    }
   9203    if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
   9204      aState->SetSawBeforeType();
   9205      aState->Update(isPunctuation, isWhitespace, scriptCode);
   9206      continue;
   9207    }
   9208    // See if we can break before the current cluster
   9209    if (!aState->mAtStart) {
   9210      bool canBreak;
   9211      if (isPunctuation != aState->mLastCharWasPunctuation) {
   9212        canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation,
   9213                                               isWhitespace, aIsKeyboardSelect);
   9214      } else if (!aState->mLastCharWasWhitespace && !isWhitespace &&
   9215                 !isPunctuation && isWordBreakBefore) {
   9216        // if both the previous and the current character are not white
   9217        // space but this can be word break before, we don't need to eat
   9218        // a white space in this case. This case happens in some languages
   9219        // that their words are not separated by white spaces. E.g.,
   9220        // Japanese and Chinese.
   9221        canBreak = true;
   9222      } else {
   9223        canBreak = isWordBreakBefore && aState->mSawBeforeType &&
   9224                   (aWordSelectEatSpace != isWhitespace);
   9225      }
   9226      // Special-case for Korean: treat a boundary between Hangul & non-Hangul
   9227      // characters as a word boundary (see bug 1973393 and UAX#29).
   9228      if (!canBreak && is2b && aState->mLastScript != intl::Script::INVALID &&
   9229          IsKorean(aState->mLastScript) != IsKorean(scriptCode)) {
   9230        canBreak = true;
   9231      }
   9232      if (canBreak) {
   9233        *aOffset = cIter.GetBeforeOffset() - mContentOffset;
   9234        return FOUND;
   9235      }
   9236    }
   9237    aState->Update(isPunctuation, isWhitespace, scriptCode);
   9238  } while (cIter.NextCluster());
   9239 
   9240  *aOffset = cIter.GetAfterOffset() - mContentOffset;
   9241  return CONTINUE;
   9242 }
   9243 
   9244 bool nsTextFrame::HasVisibleText() {
   9245  // Text in the range is visible if there is at least one character in the
   9246  // range that is not skipped and is mapped by this frame (which is the primary
   9247  // frame) or one of its continuations.
   9248  for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
   9249    int32_t dummyOffset = 0;
   9250    if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
   9251      return true;
   9252    }
   9253  }
   9254  return false;
   9255 }
   9256 
   9257 std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const {
   9258  return std::make_pair(GetContentOffset(), GetContentEnd());
   9259 }
   9260 
   9261 static bool IsFirstLetterPrefixPunctuation(uint32_t aChar) {
   9262  switch (mozilla::unicode::GetGeneralCategory(aChar)) {
   9263    case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
   9264    case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION:    /* Pd */
   9265    case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
   9266    case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
   9267    case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
   9268    case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
   9269    case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION:    /* Ps */
   9270      return true;
   9271    default:
   9272      return false;
   9273  }
   9274 }
   9275 
   9276 static bool IsFirstLetterSuffixPunctuation(uint32_t aChar) {
   9277  switch (mozilla::unicode::GetGeneralCategory(aChar)) {
   9278    case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
   9279    case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
   9280    case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
   9281    case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
   9282    case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
   9283      return true;
   9284    default:
   9285      return false;
   9286  }
   9287 }
   9288 
   9289 static int32_t FindEndOfPrefixPunctuationRun(const CharacterDataBuffer& aBuffer,
   9290                                             const gfxTextRun* aTextRun,
   9291                                             gfxSkipCharsIterator* aIter,
   9292                                             int32_t aOffset, int32_t aStart,
   9293                                             int32_t aEnd) {
   9294  int32_t i;
   9295  for (i = aStart; i < aEnd - aOffset; ++i) {
   9296    if (IsFirstLetterPrefixPunctuation(
   9297            aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
   9298      aIter->SetOriginalOffset(aOffset + i);
   9299      FindClusterEnd(aTextRun, aEnd, aIter);
   9300      i = aIter->GetOriginalOffset() - aOffset;
   9301    } else {
   9302      break;
   9303    }
   9304  }
   9305  return i;
   9306 }
   9307 
   9308 static int32_t FindEndOfSuffixPunctuationRun(const CharacterDataBuffer& aBuffer,
   9309                                             const gfxTextRun* aTextRun,
   9310                                             gfxSkipCharsIterator* aIter,
   9311                                             int32_t aOffset, int32_t aStart,
   9312                                             int32_t aEnd) {
   9313  int32_t i;
   9314  for (i = aStart; i < aEnd - aOffset; ++i) {
   9315    if (IsFirstLetterSuffixPunctuation(
   9316            aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
   9317      aIter->SetOriginalOffset(aOffset + i);
   9318      FindClusterEnd(aTextRun, aEnd, aIter);
   9319      i = aIter->GetOriginalOffset() - aOffset;
   9320    } else {
   9321      break;
   9322    }
   9323  }
   9324  return i;
   9325 }
   9326 
   9327 /**
   9328 * Returns true if this text frame completes the first-letter, false
   9329 * if it does not contain a true "letter".
   9330 * If returns true, then it also updates aLength to cover just the first-letter
   9331 * text.
   9332 *
   9333 * XXX :first-letter should be handled during frame construction
   9334 * (and it has a good bit in common with nextBidi)
   9335 *
   9336 * @param aLength an in/out parameter: on entry contains the maximum length to
   9337 * return, on exit returns length of the first-letter fragment (which may
   9338 * include leading and trailing punctuation, for example)
   9339 */
   9340 static bool FindFirstLetterRange(const CharacterDataBuffer& aBuffer,
   9341                                 const nsAtom* aLang,
   9342                                 const gfxTextRun* aTextRun, int32_t aOffset,
   9343                                 const gfxSkipCharsIterator& aIter,
   9344                                 int32_t* aLength) {
   9345  int32_t length = *aLength;
   9346  int32_t endOffset = aOffset + length;
   9347  gfxSkipCharsIterator iter(aIter);
   9348 
   9349  // Currently the only language-specific special case we handle here is the
   9350  // Dutch "IJ" digraph.
   9351  auto LangTagIsDutch = [](const nsAtom* aLang) -> bool {
   9352    if (!aLang) {
   9353      return false;
   9354    }
   9355    if (aLang == nsGkAtoms::nl) {
   9356      return true;
   9357    }
   9358    // We don't need to fully parse as a Locale; just check the initial subtag.
   9359    nsDependentAtomString langStr(aLang);
   9360    int32_t index = langStr.FindChar('-');
   9361    if (index > 0) {
   9362      langStr.Truncate(index);
   9363      return langStr.EqualsLiteral("nl");
   9364    }
   9365    return false;
   9366  };
   9367 
   9368  // Skip any trimmable leading whitespace.
   9369  int32_t i = GetTrimmableWhitespaceCount(aBuffer, aOffset, length, 1);
   9370  while (true) {
   9371    // Scan past any leading punctuation. This leaves `j` at the first
   9372    // non-punctuation character.
   9373    int32_t j = FindEndOfPrefixPunctuationRun(aBuffer, aTextRun, &iter, aOffset,
   9374                                              i, endOffset);
   9375    if (j == length) {
   9376      return false;
   9377    }
   9378 
   9379    // Scan past any Unicode whitespace characters after punctuation.
   9380    while (j < length) {
   9381      char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + j));
   9382      // The spec says to allow "characters that belong to the `Zs` Unicode
   9383      // general category _other than_ U+3000" here.
   9384      if (unicode::GetGeneralCategory(ch) ==
   9385              HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
   9386          ch != 0x3000) {
   9387        ++j;
   9388      } else {
   9389        break;
   9390      }
   9391    }
   9392    if (j == length) {
   9393      return false;
   9394    }
   9395    if (j == i) {
   9396      // If no whitespace was found, we've finished the first-letter prefix;
   9397      // if there was some, then go back to check for more punctuation.
   9398      break;
   9399    }
   9400    i = j;
   9401  }
   9402 
   9403  // If the next character is not a letter, number or symbol, there is no
   9404  // first-letter.
   9405  // Return true so that we don't go on looking, but set aLength to 0.
   9406  const char32_t usv =
   9407      aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
   9408  if (!nsContentUtils::IsAlphanumericOrSymbol(usv)) {
   9409    *aLength = 0;
   9410    return true;
   9411  }
   9412 
   9413  // Consume another cluster (the actual first letter):
   9414 
   9415  // For complex scripts such as Indic and SEAsian, where first-letter
   9416  // should extend to entire orthographic "syllable" clusters, we don't
   9417  // want to allow this to split a ligature.
   9418  bool allowSplitLigature;
   9419  bool usesIndicHalfForms = false;
   9420 
   9421  typedef intl::Script Script;
   9422  Script script = intl::UnicodeProperties::GetScriptCode(usv);
   9423  switch (script) {
   9424    default:
   9425      allowSplitLigature = true;
   9426      break;
   9427 
   9428    // Don't break regional-indicator ligatures.
   9429    case Script::COMMON:
   9430      allowSplitLigature = !gfxFontUtils::IsRegionalIndicator(usv);
   9431      break;
   9432 
   9433    // For now, lacking any definitive specification of when to apply this
   9434    // behavior, we'll base the decision on the HarfBuzz shaping engine
   9435    // used for each script: those that are handled by the Indic, Tibetan,
   9436    // Myanmar and SEAsian shapers will apply the "don't split ligatures"
   9437    // rule.
   9438 
   9439    // Indic
   9440    case Script::BENGALI:
   9441    case Script::DEVANAGARI:
   9442    case Script::GUJARATI:
   9443      usesIndicHalfForms = true;
   9444      [[fallthrough]];
   9445 
   9446    case Script::GURMUKHI:
   9447    case Script::KANNADA:
   9448    case Script::MALAYALAM:
   9449    case Script::ORIYA:
   9450    case Script::TAMIL:
   9451    case Script::TELUGU:
   9452    case Script::SINHALA:
   9453    case Script::BALINESE:
   9454    case Script::LEPCHA:
   9455    case Script::REJANG:
   9456    case Script::SUNDANESE:
   9457    case Script::JAVANESE:
   9458    case Script::KAITHI:
   9459    case Script::MEETEI_MAYEK:
   9460    case Script::CHAKMA:
   9461    case Script::SHARADA:
   9462    case Script::TAKRI:
   9463    case Script::KHMER:
   9464 
   9465    // Tibetan
   9466    case Script::TIBETAN:
   9467 
   9468    // Myanmar
   9469    case Script::MYANMAR:
   9470 
   9471    // Other SEAsian
   9472    case Script::BUGINESE:
   9473    case Script::NEW_TAI_LUE:
   9474    case Script::CHAM:
   9475    case Script::TAI_THAM:
   9476 
   9477      // What about Thai/Lao - any special handling needed?
   9478      // Should we special-case Arabic lam-alef?
   9479 
   9480      allowSplitLigature = false;
   9481      break;
   9482  }
   9483 
   9484  // NOTE that FindClusterEnd sets the iterator to the last character that is
   9485  // part of the cluster, NOT to the first character beyond it.
   9486  iter.SetOriginalOffset(aOffset + i);
   9487  FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
   9488 
   9489  // Index of the last character included in the first-letter cluster.
   9490  i = iter.GetOriginalOffset() - aOffset;
   9491 
   9492  // Heuristic for Indic scripts that like to form conjuncts:
   9493  // If we ended at a virama that is ligated with the preceding character
   9494  // (e.g. creating a half-form), then don't stop here; include the next
   9495  // cluster as well so that we don't break a conjunct.
   9496  //
   9497  // Unfortunately this cannot distinguish between a letter+virama that ligate
   9498  // to create a half-form (in which case we have a conjunct that should not
   9499  // be broken) and a letter+virama that ligate purely for presentational
   9500  // reasons to position the (visible) virama component (in which case breaking
   9501  // after the virama would be acceptable). So results may be imperfect,
   9502  // depending how the font has chosen to implement visible viramas.
   9503  if (usesIndicHalfForms) {
   9504    while (i + 1 < length &&
   9505           !aTextRun->IsLigatureGroupStart(iter.GetSkippedOffset())) {
   9506      char32_t c = aBuffer.ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
   9507      if (intl::UnicodeProperties::GetCombiningClass(c) ==
   9508          HB_UNICODE_COMBINING_CLASS_VIRAMA) {
   9509        iter.AdvanceOriginal(1);
   9510        FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
   9511        i = iter.GetOriginalOffset() - aOffset;
   9512      } else {
   9513        break;
   9514      }
   9515    }
   9516  }
   9517 
   9518  if (i + 1 == length) {
   9519    return true;
   9520  }
   9521 
   9522  // Check for Dutch "ij" digraph special case, but only if both letters have
   9523  // the same case.
   9524  if (script == Script::LATIN && LangTagIsDutch(aLang)) {
   9525    char16_t ch1 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i));
   9526    char16_t ch2 = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i + 1));
   9527    if ((ch1 == 'i' && ch2 == 'j') || (ch1 == 'I' && ch2 == 'J')) {
   9528      iter.SetOriginalOffset(aOffset + i + 1);
   9529      FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
   9530      i = iter.GetOriginalOffset() - aOffset;
   9531      if (i + 1 == length) {
   9532        return true;
   9533      }
   9534    }
   9535  }
   9536 
   9537  // When we reach here, `i` points to the last character of the first-letter
   9538  // cluster, NOT to the first character beyond it. Advance to the next char,
   9539  // ready to check for following whitespace/punctuation:
   9540  ++i;
   9541 
   9542  while (i < length) {
   9543    // Skip over whitespace, except for word separator characters, before the
   9544    // check for following punctuation. But remember the position before the
   9545    // whitespace, in case we need to reset.
   9546    const int32_t preWS = i;
   9547    while (i < length) {
   9548      char16_t ch = aBuffer.CharAt(AssertedCast<uint32_t>(aOffset + i));
   9549      // The spec says the first-letter suffix includes "any intervening
   9550      // typographic space -- characters belonging to the Zs Unicode general
   9551      // category other than U+3000 IDEOGRAPHIC SPACE or a word separator",
   9552      // where "word separator" includes U+0020 and U+00A0.
   9553      if (ch == 0x0020 || ch == 0x00A0 || ch == 0x3000 ||
   9554          unicode::GetGeneralCategory(ch) !=
   9555              HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR) {
   9556        break;
   9557      } else {
   9558        ++i;
   9559      }
   9560    }
   9561 
   9562    // Consume clusters that start with punctuation.
   9563    const int32_t prePunct = i;
   9564    i = FindEndOfSuffixPunctuationRun(aBuffer, aTextRun, &iter, aOffset, i,
   9565                                      endOffset);
   9566 
   9567    // If we didn't find punctuation here, then we also don't want to include
   9568    // any preceding whitespace, so reset our index.
   9569    if (i == prePunct) {
   9570      i = preWS;
   9571      break;
   9572    }
   9573  }
   9574 
   9575  if (i < length) {
   9576    *aLength = i;
   9577  }
   9578  return true;
   9579 }
   9580 
   9581 static uint32_t FindStartAfterSkippingWhitespace(
   9582    nsTextFrame::PropertyProvider* aProvider,
   9583    nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle,
   9584    gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) {
   9585  if (aData->mSkipWhitespace) {
   9586    while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
   9587           IsTrimmableSpace(aProvider->GetCharacterDataBuffer(),
   9588                            aIterator->GetOriginalOffset(), aTextStyle)) {
   9589      aIterator->AdvanceOriginal(1);
   9590    }
   9591  }
   9592  return aIterator->GetSkippedOffset();
   9593 }
   9594 
   9595 float nsTextFrame::GetFontSizeInflation() const {
   9596  if (!HasFontSizeInflation()) {
   9597    return 1.0f;
   9598  }
   9599  return GetProperty(FontSizeInflationProperty());
   9600 }
   9601 
   9602 void nsTextFrame::SetFontSizeInflation(float aInflation) {
   9603  if (aInflation == 1.0f) {
   9604    if (HasFontSizeInflation()) {
   9605      RemoveStateBits(TEXT_HAS_FONT_INFLATION);
   9606      RemoveProperty(FontSizeInflationProperty());
   9607    }
   9608    return;
   9609  }
   9610 
   9611  AddStateBits(TEXT_HAS_FONT_INFLATION);
   9612  SetProperty(FontSizeInflationProperty(), aInflation);
   9613 }
   9614 
   9615 void nsTextFrame::SetHangableISize(nscoord aISize) {
   9616  MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance");
   9617  if (aISize <= 0) {
   9618    ClearHangableISize();
   9619    return;
   9620  }
   9621  SetProperty(HangableWhitespaceProperty(), aISize);
   9622  mPropertyFlags |= PropertyFlags::HangableWS;
   9623 }
   9624 
   9625 nscoord nsTextFrame::GetHangableISize() const {
   9626  MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) ==
   9627                 HasProperty(HangableWhitespaceProperty()),
   9628             "flag/property mismatch!");
   9629  return (mPropertyFlags & PropertyFlags::HangableWS)
   9630             ? GetProperty(HangableWhitespaceProperty())
   9631             : 0;
   9632 }
   9633 
   9634 void nsTextFrame::ClearHangableISize() {
   9635  if (mPropertyFlags & PropertyFlags::HangableWS) {
   9636    RemoveProperty(HangableWhitespaceProperty());
   9637    mPropertyFlags &= ~PropertyFlags::HangableWS;
   9638  }
   9639 }
   9640 
   9641 void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) {
   9642  MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size");
   9643  if (aTrimmableWS.mAdvance <= 0) {
   9644    ClearTrimmableWS();
   9645    return;
   9646  }
   9647  SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS);
   9648  mPropertyFlags |= PropertyFlags::TrimmableWS;
   9649 }
   9650 
   9651 gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const {
   9652  MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==
   9653                 HasProperty(TrimmableWhitespaceProperty()),
   9654             "flag/property mismatch!");
   9655  return (mPropertyFlags & PropertyFlags::TrimmableWS)
   9656             ? GetProperty(TrimmableWhitespaceProperty())
   9657             : gfxTextRun::TrimmableWS{};
   9658 }
   9659 
   9660 void nsTextFrame::ClearTrimmableWS() {
   9661  if (mPropertyFlags & PropertyFlags::TrimmableWS) {
   9662    RemoveProperty(TrimmableWhitespaceProperty());
   9663    mPropertyFlags &= ~PropertyFlags::TrimmableWS;
   9664  }
   9665 }
   9666 
   9667 /* virtual */
   9668 void nsTextFrame::MarkIntrinsicISizesDirty() {
   9669  ClearTextRuns();
   9670  nsIFrame::MarkIntrinsicISizesDirty();
   9671 }
   9672 
   9673 // XXX this doesn't handle characters shaped by line endings. We need to
   9674 // temporarily override the "current line ending" settings.
   9675 void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext,
   9676                                           InlineMinISizeData* aData,
   9677                                           TextRunType aTextRunType) {
   9678  uint32_t flowEndInTextRun;
   9679  gfxSkipCharsIterator iter =
   9680      EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
   9681                    aData->LineContainer(), aData->mLine, &flowEndInTextRun);
   9682  gfxTextRun* textRun = GetTextRun(aTextRunType);
   9683  if (!textRun) {
   9684    return;
   9685  }
   9686 
   9687  // Pass null for the line container. This will disable tab spacing, but that's
   9688  // OK since we can't really handle tabs for intrinsic sizing anyway.
   9689  const nsStyleText* textStyle = StyleText();
   9690  const auto& characterDataBuffer = CharacterDataBuffer();
   9691 
   9692  // If we're hyphenating, the PropertyProvider needs the actual length;
   9693  // otherwise we can just pass INT32_MAX to mean "all the text"
   9694  int32_t len = INT32_MAX;
   9695  bool hyphenating = characterDataBuffer.GetLength() > 0 &&
   9696                     (textStyle->mHyphens == StyleHyphens::Auto ||
   9697                      (textStyle->mHyphens == StyleHyphens::Manual &&
   9698                       !!(textRun->GetFlags() &
   9699                          gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
   9700  if (hyphenating) {
   9701    gfxSkipCharsIterator tmp(iter);
   9702    len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
   9703                            tmp.ConvertSkippedToOriginal(flowEndInTextRun)) -
   9704          iter.GetOriginalOffset();
   9705  }
   9706  PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter,
   9707                            len, nullptr, 0, aTextRunType,
   9708                            aData->mAtStartOfLine);
   9709 
   9710  bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
   9711  bool preformatNewlines = textStyle->NewlineIsSignificant(this);
   9712  bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
   9713  bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
   9714  gfxFloat tabWidth = -1;
   9715  uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
   9716                                                    &iter, flowEndInTextRun);
   9717 
   9718  // text-combine-upright frame is constantly 1em on inline-axis; but if it has
   9719  // a following sibling with the same style, it contributes nothing.
   9720  if (Style()->IsTextCombined()) {
   9721    if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
   9722      aData->OptionallyBreak();
   9723    }
   9724    if (!GetNextSibling() || GetNextSibling()->Style() != Style()) {
   9725      aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
   9726    }
   9727    aData->mTrailingWhitespace = 0;
   9728    return;
   9729  }
   9730 
   9731  if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere &&
   9732      textStyle->WordCanWrap(this)) {
   9733    aData->OptionallyBreak();
   9734    aData->mCurrentLine +=
   9735        textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
   9736    aData->mTrailingWhitespace = 0;
   9737    aData->mAtStartOfLine = false;
   9738    aData->OptionallyBreak();
   9739    return;
   9740  }
   9741 
   9742  AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
   9743  if (hyphenating) {
   9744    if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
   9745      provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
   9746                                    hyphBuffer.Elements());
   9747    } else {
   9748      hyphenating = false;
   9749    }
   9750  }
   9751 
   9752  for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
   9753    bool preformattedNewline = false;
   9754    bool preformattedTab = false;
   9755    if (i < flowEndInTextRun) {
   9756      // XXXldb Shouldn't we be including the newline as part of the
   9757      // segment that it ends rather than part of the segment that it
   9758      // starts?
   9759      preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
   9760      preformattedTab = preformatTabs && textRun->CharIsTab(i);
   9761      if (!textRun->CanBreakLineBefore(i) && !preformattedNewline &&
   9762          !preformattedTab &&
   9763          (!hyphenating ||
   9764           !gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start]))) {
   9765        // we can't break here (and it's not the end of the flow)
   9766        continue;
   9767      }
   9768    }
   9769 
   9770    if (i > wordStart) {
   9771      nscoord width = NSToCoordCeilClamped(
   9772          textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
   9773      width = std::max(0, width);
   9774      aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
   9775      aData->mAtStartOfLine = false;
   9776 
   9777      if (collapseWhitespace || whitespaceCanHang) {
   9778        uint32_t trimStart =
   9779            GetEndOfTrimmedText(characterDataBuffer, textStyle, wordStart, i,
   9780                                &iter, whitespaceCanHang);
   9781        if (trimStart == start) {
   9782          // This is *all* trimmable whitespace, so whatever trailingWhitespace
   9783          // we saw previously is still trailing...
   9784          aData->mTrailingWhitespace += width;
   9785        } else {
   9786          // Some non-whitespace so the old trailingWhitespace is no longer
   9787          // trailing
   9788          nscoord wsWidth = NSToCoordCeilClamped(
   9789              textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
   9790          aData->mTrailingWhitespace = std::max(0, wsWidth);
   9791        }
   9792      } else {
   9793        aData->mTrailingWhitespace = 0;
   9794      }
   9795    }
   9796 
   9797    if (preformattedTab) {
   9798      PropertyProvider::Spacing spacing;
   9799      provider.GetSpacing(Range(i, i + 1), &spacing);
   9800      aData->mCurrentLine += nscoord(spacing.mBefore);
   9801      if (tabWidth < 0) {
   9802        tabWidth = ComputeTabWidthAppUnits(this);
   9803      }
   9804      gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
   9805                                           provider.MinTabAdvance());
   9806      aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
   9807      wordStart = i + 1;
   9808    } else if (i < flowEndInTextRun ||
   9809               (i == textRun->GetLength() &&
   9810                (textRun->GetFlags2() &
   9811                 nsTextFrameUtils::Flags::HasTrailingBreak))) {
   9812      if (preformattedNewline) {
   9813        aData->ForceBreak();
   9814      } else if (i < flowEndInTextRun && hyphenating &&
   9815                 gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start])) {
   9816        aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
   9817      } else {
   9818        aData->OptionallyBreak();
   9819      }
   9820      if (aData->mSkipWhitespace) {
   9821        iter.SetSkippedOffset(i);
   9822        wordStart = FindStartAfterSkippingWhitespace(
   9823            &provider, aData, textStyle, &iter, flowEndInTextRun);
   9824      } else {
   9825        wordStart = i;
   9826      }
   9827      provider.SetStartOfLine(iter);
   9828    }
   9829  }
   9830 
   9831  if (start < flowEndInTextRun) {
   9832    // Check if we have collapsible whitespace at the end
   9833    aData->mSkipWhitespace = IsTrimmableSpace(
   9834        provider.GetCharacterDataBuffer(),
   9835        iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
   9836  }
   9837 }
   9838 
   9839 bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
   9840  return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
   9841 }
   9842 
   9843 void nsTextFrame::MaybeSplitFramesForFirstLetter() {
   9844  if (!StaticPrefs::layout_css_intrinsic_size_first_letter_enabled()) {
   9845    return;
   9846  }
   9847 
   9848  if (GetParent()->IsFloating() && GetContentLength() > 0) {
   9849    // We've already claimed our first-letter content, don't try again.
   9850    return;
   9851  }
   9852  if (GetPrevContinuation()) {
   9853    // This isn't the first part of the first-letter.
   9854    return;
   9855  }
   9856 
   9857  // Find the length of the first-letter. We need a textrun for this; just bail
   9858  // out if we fail to create it.
   9859  // But in the floating first-letter case, the text is initially all in our
   9860  // next-in-flow, and the float itself is empty. So we need to look at that
   9861  // textrun instead of our own during FindFirstLetterRange.
   9862  nsTextFrame* f = GetParent()->IsFloating() ? GetNextInFlow() : this;
   9863  gfxSkipCharsIterator iter = f->EnsureTextRun(nsTextFrame::eInflated);
   9864  const gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated);
   9865 
   9866  const auto& characterDataBuffer = CharacterDataBuffer();
   9867  const int32_t length = GetInFlowContentLength();
   9868  const int32_t offset = GetContentOffset();
   9869  int32_t firstLetterLength = length;
   9870  NewlineProperty* cachedNewlineOffset = nullptr;
   9871  int32_t newLineOffset = -1;  // this will be -1 or a content offset
   9872  // This will just return -1 if newlines are not significant.
   9873  int32_t contentNewLineOffset =
   9874      GetContentNewLineOffset(offset, cachedNewlineOffset);
   9875  if (contentNewLineOffset < offset + length) {
   9876    // The new line offset could be outside this frame if the frame has been
   9877    // split by bidi resolution. In that case we won't use it in this reflow
   9878    // (newLineOffset will remain -1), but we will still cache it in mContent
   9879    newLineOffset = contentNewLineOffset;
   9880    if (newLineOffset >= 0) {
   9881      firstLetterLength = newLineOffset - offset;
   9882    }
   9883  }
   9884 
   9885  if (contentNewLineOffset >= 0 && contentNewLineOffset < offset) {
   9886    // We're in a first-letter frame's first in flow, so if there
   9887    // was a first-letter, we'd be it. However, for one reason
   9888    // or another (e.g., preformatted line break before this text),
   9889    // we're not actually supposed to have first-letter style. So
   9890    // just make a zero-length first-letter.
   9891    firstLetterLength = 0;
   9892  } else {
   9893    // We only pass a language code to FindFirstLetterRange if it was
   9894    // explicit in the content.
   9895    const nsStyleFont* styleFont = StyleFont();
   9896    const nsAtom* lang =
   9897        styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
   9898    FindFirstLetterRange(characterDataBuffer, lang, textRun, offset, iter,
   9899                         &firstLetterLength);
   9900    if (newLineOffset >= 0) {
   9901      // Don't allow a preformatted newline to be part of a first-letter.
   9902      firstLetterLength = std::min(firstLetterLength, length - 1);
   9903    }
   9904  }
   9905  if (firstLetterLength) {
   9906    AddStateBits(TEXT_FIRST_LETTER);
   9907  }
   9908 
   9909  // Change this frame's length to the first-letter length right now
   9910  // so that when we rebuild the textrun it will be built with the
   9911  // right first-letter boundary.
   9912  SetFirstLetterLength(firstLetterLength);
   9913 }
   9914 
   9915 static bool IsUnreflowedLetterFrame(nsIFrame* aFrame) {
   9916  return aFrame->IsLetterFrame() &&
   9917         aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
   9918 }
   9919 
   9920 // XXX Need to do something here to avoid incremental reflow bugs due to
   9921 // first-line changing min-width
   9922 /* virtual */
   9923 void nsTextFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput,
   9924                                    InlineMinISizeData* aData) {
   9925  // Check if this textframe belongs to a first-letter frame that has not yet
   9926  // been reflowed; if so, we need to deal with splitting off a continuation
   9927  // before we can measure the advance correctly.
   9928  if (IsUnreflowedLetterFrame(GetParent())) {
   9929    MaybeSplitFramesForFirstLetter();
   9930  }
   9931 
   9932  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   9933  TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
   9934 
   9935  if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
   9936    // FIXME: Ideally, if we already have a text run, we'd move it to be
   9937    // the uninflated text run.
   9938    ClearTextRun(nullptr, nsTextFrame::eInflated);
   9939    mFontMetrics = nullptr;
   9940  }
   9941 
   9942  nsTextFrame* f;
   9943  const gfxTextRun* lastTextRun = nullptr;
   9944  // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
   9945  // in the flow are handled right here.
   9946  for (f = this; f; f = f->GetNextContinuation()) {
   9947    // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
   9948    // haven't set up textruns yet for f.  Except in OOM situations,
   9949    // lastTextRun will only be null for the first text frame.
   9950    if (f == this || f->GetTextRun(trtype) != lastTextRun) {
   9951      nsIFrame* lc;
   9952      if (aData->LineContainer() &&
   9953          aData->LineContainer() != (lc = f->FindLineContainer())) {
   9954        NS_ASSERTION(f != this,
   9955                     "wrong InlineMinISizeData container"
   9956                     " for first continuation");
   9957        aData->mLine = nullptr;
   9958        aData->SetLineContainer(lc);
   9959      }
   9960 
   9961      // This will process all the text frames that share the same textrun as f.
   9962      f->AddInlineMinISizeForFlow(aInput.mContext, aData, trtype);
   9963      lastTextRun = f->GetTextRun(trtype);
   9964    }
   9965  }
   9966 }
   9967 
   9968 // XXX this doesn't handle characters shaped by line endings. We need to
   9969 // temporarily override the "current line ending" settings.
   9970 void nsTextFrame::AddInlinePrefISizeForFlow(gfxContext* aRenderingContext,
   9971                                            InlinePrefISizeData* aData,
   9972                                            TextRunType aTextRunType) {
   9973  if (IsUnreflowedLetterFrame(GetParent())) {
   9974    MaybeSplitFramesForFirstLetter();
   9975  }
   9976 
   9977  uint32_t flowEndInTextRun;
   9978  gfxSkipCharsIterator iter =
   9979      EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
   9980                    aData->LineContainer(), aData->mLine, &flowEndInTextRun);
   9981  gfxTextRun* textRun = GetTextRun(aTextRunType);
   9982  if (!textRun) {
   9983    return;
   9984  }
   9985 
   9986  // Pass null for the line container. This will disable tab spacing, but that's
   9987  // OK since we can't really handle tabs for intrinsic sizing anyway.
   9988 
   9989  const nsStyleText* textStyle = StyleText();
   9990  const auto& characterDataBuffer = CharacterDataBuffer();
   9991  PropertyProvider provider(textRun, textStyle, characterDataBuffer, this, iter,
   9992                            INT32_MAX, nullptr, 0, aTextRunType,
   9993                            aData->mLineIsEmpty);
   9994 
   9995  // text-combine-upright frame is constantly 1em on inline-axis, or zero if
   9996  // there is a following sibling with the same style.
   9997  if (Style()->IsTextCombined()) {
   9998    if (!GetNextSibling() || GetNextSibling()->Style() != Style()) {
   9999      aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
  10000    }
  10001    aData->mTrailingWhitespace = 0;
  10002    aData->mLineIsEmpty = false;
  10003    return;
  10004  }
  10005 
  10006  bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
  10007  bool preformatNewlines = textStyle->NewlineIsSignificant(this);
  10008  bool preformatTabs = textStyle->TabIsSignificant();
  10009  gfxFloat tabWidth = -1;
  10010  uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
  10011                                                    &iter, flowEndInTextRun);
  10012  if (aData->mLineIsEmpty) {
  10013    provider.SetStartOfLine(iter);
  10014  }
  10015 
  10016  // XXX Should we consider hyphenation here?
  10017  // If newlines and tabs aren't preformatted, nothing to do inside
  10018  // the loop so make i skip to the end
  10019  uint32_t loopStart =
  10020      (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
  10021  for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
  10022    bool preformattedNewline = false;
  10023    bool preformattedTab = false;
  10024    if (i < flowEndInTextRun) {
  10025      // XXXldb Shouldn't we be including the newline as part of the
  10026      // segment that it ends rather than part of the segment that it
  10027      // starts?
  10028      NS_ASSERTION(preformatNewlines || preformatTabs,
  10029                   "We can't be here unless newlines are "
  10030                   "hard breaks or there are tabs");
  10031      preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
  10032      preformattedTab = preformatTabs && textRun->CharIsTab(i);
  10033      if (!preformattedNewline && !preformattedTab) {
  10034        // we needn't break here (and it's not the end of the flow)
  10035        continue;
  10036      }
  10037    }
  10038 
  10039    if (i > lineStart) {
  10040      nscoord width = NSToCoordCeilClamped(
  10041          textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
  10042      width = std::max(0, width);
  10043      aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
  10044      aData->mLineIsEmpty = false;
  10045 
  10046      if (collapseWhitespace) {
  10047        uint32_t trimStart = GetEndOfTrimmedText(characterDataBuffer, textStyle,
  10048                                                 lineStart, i, &iter);
  10049        if (trimStart == start) {
  10050          // This is *all* trimmable whitespace, so whatever trailingWhitespace
  10051          // we saw previously is still trailing...
  10052          aData->mTrailingWhitespace += width;
  10053        } else {
  10054          // Some non-whitespace so the old trailingWhitespace is no longer
  10055          // trailing
  10056          nscoord wsWidth = NSToCoordCeilClamped(
  10057              textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
  10058          aData->mTrailingWhitespace = std::max(0, wsWidth);
  10059        }
  10060      } else {
  10061        aData->mTrailingWhitespace = 0;
  10062      }
  10063    }
  10064 
  10065    if (preformattedTab) {
  10066      PropertyProvider::Spacing spacing;
  10067      provider.GetSpacing(Range(i, i + 1), &spacing);
  10068      aData->mCurrentLine += nscoord(spacing.mBefore);
  10069      if (tabWidth < 0) {
  10070        tabWidth = ComputeTabWidthAppUnits(this);
  10071      }
  10072      gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
  10073                                           provider.MinTabAdvance());
  10074      aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
  10075      aData->mLineIsEmpty = false;
  10076      lineStart = i + 1;
  10077    } else if (preformattedNewline) {
  10078      aData->ForceBreak();
  10079      lineStart = i;
  10080    }
  10081  }
  10082 
  10083  // Check if we have collapsible whitespace at the end
  10084  if (start < flowEndInTextRun) {
  10085    aData->mSkipWhitespace = IsTrimmableSpace(
  10086        provider.GetCharacterDataBuffer(),
  10087        iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
  10088  }
  10089 }
  10090 
  10091 // XXX Need to do something here to avoid incremental reflow bugs due to
  10092 // first-line and first-letter changing pref-width
  10093 /* virtual */
  10094 void nsTextFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput,
  10095                                     InlinePrefISizeData* aData) {
  10096  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
  10097  TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
  10098 
  10099  if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
  10100    // FIXME: Ideally, if we already have a text run, we'd move it to be
  10101    // the uninflated text run.
  10102    ClearTextRun(nullptr, nsTextFrame::eInflated);
  10103    mFontMetrics = nullptr;
  10104  }
  10105 
  10106  nsTextFrame* f;
  10107  const gfxTextRun* lastTextRun = nullptr;
  10108  // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
  10109  // in the flow are handled right here.
  10110  for (f = this; f; f = f->GetNextContinuation()) {
  10111    // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
  10112    // haven't set up textruns yet for f.  Except in OOM situations,
  10113    // lastTextRun will only be null for the first text frame.
  10114    if (f == this || f->GetTextRun(trtype) != lastTextRun) {
  10115      nsIFrame* lc;
  10116      if (aData->LineContainer() &&
  10117          aData->LineContainer() != (lc = f->FindLineContainer())) {
  10118        NS_ASSERTION(f != this,
  10119                     "wrong InlinePrefISizeData container"
  10120                     " for first continuation");
  10121        aData->mLine = nullptr;
  10122        aData->SetLineContainer(lc);
  10123      }
  10124 
  10125      // This will process all the text frames that share the same textrun as f.
  10126      f->AddInlinePrefISizeForFlow(aInput.mContext, aData, trtype);
  10127      lastTextRun = f->GetTextRun(trtype);
  10128    }
  10129  }
  10130 }
  10131 
  10132 /* virtual */
  10133 nsIFrame::SizeComputationResult nsTextFrame::ComputeSize(
  10134    const SizeComputationInput& aSizingInput, WritingMode aWM,
  10135    const LogicalSize& aCBSize, nscoord aAvailableISize,
  10136    const LogicalSize& aMargin, const LogicalSize& aBorderPadding,
  10137    const StyleSizeOverrides& aSizeOverrides, ComputeSizeFlags aFlags) {
  10138  // Inlines and text don't compute size before reflow.
  10139  return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
  10140          AspectRatioUsage::None};
  10141 }
  10142 
  10143 static nsRect RoundOut(const gfxRect& aRect) {
  10144  nsRect r;
  10145  r.x = NSToCoordFloor(aRect.X());
  10146  r.y = NSToCoordFloor(aRect.Y());
  10147  r.width = NSToCoordCeil(aRect.XMost()) - r.x;
  10148  r.height = NSToCoordCeil(aRect.YMost()) - r.y;
  10149  return r;
  10150 }
  10151 
  10152 nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
  10153  if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
  10154    // This is conservative, but OK.
  10155    return InkOverflowRect();
  10156  }
  10157 
  10158  gfxSkipCharsIterator iter =
  10159      const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
  10160  if (!mTextRun) {
  10161    return nsRect();
  10162  }
  10163 
  10164  PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
  10165                            nsTextFrame::eInflated, mFontMetrics);
  10166  // Trim trailing whitespace
  10167  provider.InitializeForDisplay(true);
  10168 
  10169  gfxTextRun::Metrics metrics = mTextRun->MeasureText(
  10170      ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
  10171      aDrawTarget, &provider);
  10172  if (GetWritingMode().IsLineInverted()) {
  10173    metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
  10174  }
  10175  // mAscent should be the same as metrics.mAscent, but it's what we use to
  10176  // paint so that's the one we'll use.
  10177  nsRect boundingBox = RoundOut(metrics.mBoundingBox);
  10178  boundingBox += nsPoint(0, mAscent);
  10179  if (mTextRun->IsVertical()) {
  10180    // Swap line-relative textMetrics dimensions to physical coordinates.
  10181    std::swap(boundingBox.x, boundingBox.y);
  10182    std::swap(boundingBox.width, boundingBox.height);
  10183  }
  10184  return boundingBox;
  10185 }
  10186 
  10187 /* virtual */
  10188 nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
  10189                                              nscoord* aXMost) {
  10190  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  10191  if (!mTextRun) {
  10192    return NS_ERROR_FAILURE;
  10193  }
  10194 
  10195  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
  10196  provider.InitializeForMeasure();
  10197 
  10198  gfxTextRun::Metrics metrics = mTextRun->MeasureText(
  10199      ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
  10200      aContext->GetDrawTarget(), &provider);
  10201  // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
  10202  *aX = NSToCoordFloor(metrics.mBoundingBox.x);
  10203  *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
  10204 
  10205  return NS_OK;
  10206 }
  10207 
  10208 static bool HasSoftHyphenBefore(const CharacterDataBuffer& aBuffer,
  10209                                const gfxTextRun* aTextRun,
  10210                                int32_t aStartOffset,
  10211                                const gfxSkipCharsIterator& aIter) {
  10212  if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
  10213      aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
  10214    return true;
  10215  }
  10216  if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) {
  10217    return false;
  10218  }
  10219  gfxSkipCharsIterator iter = aIter;
  10220  while (iter.GetOriginalOffset() > aStartOffset) {
  10221    iter.AdvanceOriginal(-1);
  10222    if (!iter.IsOriginalCharSkipped()) {
  10223      break;
  10224    }
  10225    if (aBuffer.CharAt(AssertedCast<uint32_t>(iter.GetOriginalOffset())) ==
  10226        CH_SHY) {
  10227      return true;
  10228    }
  10229  }
  10230  return false;
  10231 }
  10232 
  10233 /**
  10234 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
  10235 * because their text has all been taken and reflowed by earlier frames.
  10236 */
  10237 static void RemoveEmptyInFlows(nsTextFrame* aFrame,
  10238                               nsTextFrame* aFirstToNotRemove) {
  10239  MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly");
  10240  // We have to be careful here, because some RemoveFrame implementations
  10241  // remove and destroy not only the passed-in frame but also all its following
  10242  // in-flows (and sometimes all its following continuations in general).  So
  10243  // we remove |f| and everything up to but not including firstToNotRemove from
  10244  // the flow first, to make sure that only the things we want destroyed are
  10245  // destroyed.
  10246 
  10247  // This sadly duplicates some of the logic from
  10248  // nsSplittableFrame::RemoveFromFlow.  We can get away with not duplicating
  10249  // all of it, because we know that the prev-continuation links of
  10250  // firstToNotRemove and f are fluid, and non-null.
  10251  NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
  10252                       aFirstToNotRemove->GetPrevInFlow() &&
  10253                   aFirstToNotRemove->GetPrevInFlow() != nullptr,
  10254               "aFirstToNotRemove should have a fluid prev continuation");
  10255  NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() &&
  10256                   aFrame->GetPrevInFlow() != nullptr,
  10257               "aFrame should have a fluid prev continuation");
  10258 
  10259  nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
  10260  nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
  10261 
  10262  for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
  10263       f = f->GetNextContinuation()) {
  10264    // f is going to be destroyed soon, after it is unlinked from the
  10265    // continuation chain. If its textrun is going to be destroyed we need to
  10266    // do it now, before we unlink the frames to remove from the flow,
  10267    // because Destroy calls ClearTextRuns() and that will start at the
  10268    // first frame with the text run and walk the continuations.
  10269    if (f->IsInTextRunUserData()) {
  10270      f->ClearTextRuns();
  10271    } else {
  10272      f->DisconnectTextRuns();
  10273    }
  10274  }
  10275 
  10276  prevContinuation->SetNextInFlow(aFirstToNotRemove);
  10277  aFirstToNotRemove->SetPrevInFlow(prevContinuation);
  10278 
  10279  // **Note: it is important here that we clear the Next link from lastRemoved
  10280  // BEFORE clearing the Prev link from aFrame, because SetPrevInFlow() will
  10281  // follow the Next pointers, wiping out the cached mFirstContinuation field
  10282  // from each following frame in the list. We need this to stop when it
  10283  // reaches lastRemoved!
  10284  lastRemoved->SetNextInFlow(nullptr);
  10285  aFrame->SetPrevInFlow(nullptr);
  10286 
  10287  nsContainerFrame* parent = aFrame->GetParent();
  10288  nsIFrame::DestroyContext context(aFrame->PresShell());
  10289  nsBlockFrame* parentBlock = do_QueryFrame(parent);
  10290  if (parentBlock) {
  10291    // Manually call DoRemoveFrame so we can tell it that we're
  10292    // removing empty frames; this will keep it from blowing away
  10293    // text runs.
  10294    parentBlock->DoRemoveFrame(context, aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
  10295  } else {
  10296    // Just remove it normally; use FrameChildListID::NoReflowPrincipal to avoid
  10297    // posting new reflows.
  10298    parent->RemoveFrame(context, FrameChildListID::NoReflowPrincipal, aFrame);
  10299  }
  10300 }
  10301 
  10302 void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
  10303                            uint32_t aSetLengthFlags) {
  10304  mContentLengthHint = aLength;
  10305  int32_t end = GetContentOffset() + aLength;
  10306  nsTextFrame* f = GetNextInFlow();
  10307  if (!f) {
  10308    return;
  10309  }
  10310 
  10311  // If our end offset is moving, then even if frames are not being pushed or
  10312  // pulled, content is moving to or from the next line and the next line
  10313  // must be reflowed.
  10314  // If the next-continuation is dirty, then we should dirty the next line now
  10315  // because we may have skipped doing it if we dirtied it in
  10316  // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
  10317  // and ChildIsDirty to handle a range of frames would be worse.
  10318  if (aLineLayout &&
  10319      (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
  10320    aLineLayout->SetDirtyNextLine();
  10321  }
  10322 
  10323  if (end < f->mContentOffset) {
  10324    // Our frame is shrinking. Give the text to our next in flow.
  10325    if (aLineLayout && HasSignificantTerminalNewline() &&
  10326        !GetParent()->IsLetterFrame() &&
  10327        (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
  10328      // Whatever text we hand to our next-in-flow will end up in a frame all of
  10329      // its own, since it ends in a forced linebreak.  Might as well just put
  10330      // it in a separate frame now.  This is important to prevent text run
  10331      // churn; if we did not do that, then we'd likely end up rebuilding
  10332      // textruns for all our following continuations.
  10333      // We skip this optimization when the parent is a first-letter frame
  10334      // because it doesn't deal well with more than one child frame.
  10335      // We also skip this optimization if we were called during bidi
  10336      // resolution, so as not to create a new frame which doesn't appear in
  10337      // the bidi resolver's list of frames
  10338      nsIFrame* newFrame =
  10339          PresShell()->FrameConstructor()->CreateContinuingFrame(this,
  10340                                                                 GetParent());
  10341      nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
  10342      GetParent()->InsertFrames(FrameChildListID::NoReflowPrincipal, this,
  10343                                aLineLayout->GetLine(),
  10344                                nsFrameList(next, next));
  10345      f = next;
  10346    }
  10347 
  10348    f->mContentOffset = end;
  10349    if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
  10350      ClearTextRuns();
  10351      f->ClearTextRuns();
  10352    }
  10353    return;
  10354  }
  10355  // Our frame is growing. Take text from our in-flow(s).
  10356  // We can take text from frames in lines beyond just the next line.
  10357  // We don't dirty those lines. That's OK, because when we reflow
  10358  // our empty next-in-flow, it will take text from its next-in-flow and
  10359  // dirty that line.
  10360 
  10361  // Note that in the process we may end up removing some frames from
  10362  // the flow if they end up empty.
  10363  nsTextFrame* framesToRemove = nullptr;
  10364  while (f && f->mContentOffset < end) {
  10365    f->mContentOffset = end;
  10366    if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
  10367      ClearTextRuns();
  10368      f->ClearTextRuns();
  10369    }
  10370    nsTextFrame* next = f->GetNextInFlow();
  10371    // Note: the "f->GetNextSibling() == next" check below is to restrict
  10372    // this optimization to the case where they are on the same child list.
  10373    // Otherwise we might remove the only child of a nsFirstLetterFrame
  10374    // for example and it can't handle that.  See bug 597627 for details.
  10375    if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
  10376        (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
  10377      // |f| is now empty.  We may as well remove it, instead of copying all
  10378      // the text from |next| into it instead; the latter leads to use
  10379      // rebuilding textruns for all following continuations.
  10380      // We skip this optimization if we were called during bidi resolution,
  10381      // since the bidi resolver may try to handle the destroyed frame later
  10382      // and crash
  10383      if (!framesToRemove) {
  10384        // Remember that we have to remove this frame.
  10385        framesToRemove = f;
  10386      }
  10387    } else if (framesToRemove) {
  10388      RemoveEmptyInFlows(framesToRemove, f);
  10389      framesToRemove = nullptr;
  10390    }
  10391    f = next;
  10392  }
  10393 
  10394  MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
  10395             "How did we exit the loop if we null out framesToRemove if "
  10396             "!next || next->mContentOffset > end ?");
  10397 
  10398  if (framesToRemove) {
  10399    // We are guaranteed that we exited the loop with f not null, per the
  10400    // postcondition above
  10401    RemoveEmptyInFlows(framesToRemove, f);
  10402  }
  10403 
  10404 #ifdef DEBUG
  10405  f = this;
  10406  int32_t iterations = 0;
  10407  while (f && iterations < 10) {
  10408    f->GetContentLength();  // Assert if negative length
  10409    f = f->GetNextContinuation();
  10410    ++iterations;
  10411  }
  10412  f = this;
  10413  iterations = 0;
  10414  while (f && iterations < 10) {
  10415    f->GetContentLength();  // Assert if negative length
  10416    f = f->GetPrevContinuation();
  10417    ++iterations;
  10418  }
  10419 #endif
  10420 }
  10421 
  10422 void nsTextFrame::SetFirstLetterLength(int32_t aLength) {
  10423  if (aLength == GetContentLength()) {
  10424    return;
  10425  }
  10426 
  10427  mContentLengthHint = aLength;
  10428  nsTextFrame* next = static_cast<nsTextFrame*>(GetNextInFlow());
  10429  if (!aLength && !next) {
  10430    return;
  10431  }
  10432 
  10433  if (aLength > GetContentLength()) {
  10434    // Stealing some text from our next-in-flow; this happens with floating
  10435    // first-letter, which is initially given a zero-length range, with all
  10436    // the text being in its continuation.
  10437    if (!next) {
  10438      MOZ_ASSERT_UNREACHABLE("Expected a next-in-flow; first-letter broken?");
  10439      return;
  10440    }
  10441  } else if (!next) {
  10442    // We need to create a continuation for the parent first-letter frame,
  10443    // and move any kids after this frame to the new one; if there are none,
  10444    // a new continuing text frame will be created there.
  10445    MOZ_ASSERT(GetParent()->IsLetterFrame());
  10446    auto* letterFrame = static_cast<nsFirstLetterFrame*>(GetParent());
  10447    next = letterFrame->CreateContinuationForFramesAfter(this);
  10448  }
  10449 
  10450  next->mContentOffset = GetContentOffset() + aLength;
  10451 
  10452  ClearTextRuns();
  10453 }
  10454 
  10455 bool nsTextFrame::IsFloatingFirstLetterChild() const {
  10456  nsIFrame* frame = GetParent();
  10457  return frame && frame->IsFloating() && frame->IsLetterFrame();
  10458 }
  10459 
  10460 bool nsTextFrame::IsInitialLetterChild() const {
  10461  nsIFrame* frame = GetParent();
  10462  return frame && frame->StyleTextReset()->mInitialLetter.size != 0.0f &&
  10463         frame->IsLetterFrame();
  10464 }
  10465 
  10466 struct nsTextFrame::NewlineProperty {
  10467  int32_t mStartOffset;
  10468  // The offset of the first \n after mStartOffset, or -1 if there is none
  10469  int32_t mNewlineOffset;
  10470 };
  10471 
  10472 int32_t nsTextFrame::GetContentNewLineOffset(
  10473    int32_t aOffset, NewlineProperty*& aCachedNewlineOffset) {
  10474  int32_t contentNewLineOffset = -1;  // this will be -1 or a content offset
  10475  if (StyleText()->NewlineIsSignificant(this)) {
  10476    // Pointer to the nsGkAtoms::newline set on this frame's element
  10477    aCachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
  10478                               ? static_cast<NewlineProperty*>(
  10479                                     mContent->GetProperty(nsGkAtoms::newline))
  10480                               : nullptr;
  10481    if (aCachedNewlineOffset && aCachedNewlineOffset->mStartOffset <= aOffset &&
  10482        (aCachedNewlineOffset->mNewlineOffset == -1 ||
  10483         aCachedNewlineOffset->mNewlineOffset >= aOffset)) {
  10484      contentNewLineOffset = aCachedNewlineOffset->mNewlineOffset;
  10485    } else {
  10486      contentNewLineOffset =
  10487          FindChar(CharacterDataBuffer(), aOffset,
  10488                   GetContent()->TextLength() - aOffset, '\n');
  10489    }
  10490  }
  10491 
  10492  return contentNewLineOffset;
  10493 }
  10494 
  10495 void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
  10496                         const ReflowInput& aReflowInput,
  10497                         nsReflowStatus& aStatus) {
  10498  MarkInReflow();
  10499  DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
  10500  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  10501 
  10502  InvalidateSelectionState();
  10503 
  10504  // XXX If there's no line layout, we shouldn't even have created this
  10505  // frame. This may happen if, for example, this is text inside a table
  10506  // but not inside a cell. For now, just don't reflow.
  10507  if (!aReflowInput.mLineLayout) {
  10508    ClearMetrics(aMetrics);
  10509    return;
  10510  }
  10511 
  10512  ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
  10513             aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics,
  10514             aStatus);
  10515 }
  10516 
  10517 #ifdef ACCESSIBILITY
  10518 /**
  10519 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
  10520 */
  10521 class MOZ_STACK_CLASS ReflowTextA11yNotifier {
  10522 public:
  10523  ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent)
  10524      : mContent(aContent), mPresContext(aPresContext) {}
  10525  ~ReflowTextA11yNotifier() {
  10526    if (nsAccessibilityService* accService = GetAccService()) {
  10527      accService->UpdateText(mPresContext->PresShell(), mContent);
  10528    }
  10529  }
  10530 
  10531 private:
  10532  ReflowTextA11yNotifier();
  10533  ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
  10534  ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&);
  10535 
  10536  nsIContent* mContent;
  10537  nsPresContext* mPresContext;
  10538 };
  10539 #endif
  10540 
  10541 void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
  10542                             DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
  10543                             nsReflowStatus& aStatus) {
  10544  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
  10545 
  10546 #ifdef NOISY_REFLOW
  10547  ListTag(stdout);
  10548  printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
  10549 #endif
  10550 
  10551  nsPresContext* presContext = PresContext();
  10552 
  10553 #ifdef ACCESSIBILITY
  10554  // Schedule the update of accessible tree since rendered text might be
  10555  // changed.
  10556  if (StyleVisibility()->IsVisible()) {
  10557    ReflowTextA11yNotifier(presContext, mContent);
  10558  }
  10559 #endif
  10560 
  10561  /////////////////////////////////////////////////////////////////////
  10562  // Set up flags and clear out state
  10563  /////////////////////////////////////////////////////////////////////
  10564 
  10565  // Clear out the reflow input flags in mState. We also clear the whitespace
  10566  // flags because this can change whether the frame maps whitespace-only text
  10567  // or not. We also clear the flag that tracks whether we had a pending
  10568  // reflow request from CharacterDataChanged (since we're reflowing now).
  10569  RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
  10570  mReflowRequestedForCharDataChange = false;
  10571  RemoveProperty(WebRenderTextBounds());
  10572 
  10573  // Discard cached continuations array that will be invalidated by the reflow.
  10574  if (nsTextFrame* first = FirstContinuation()) {
  10575    first->ClearCachedContinuations();
  10576  }
  10577 
  10578  // Temporarily map all possible content while we construct our new textrun.
  10579  // so that when doing reflow our styles prevail over any part of the
  10580  // textrun we look at. Note that next-in-flows may be mapping the same
  10581  // content; gfxTextRun construction logic will ensure that we take priority.
  10582  int32_t maxContentLength = GetInFlowContentLength();
  10583 
  10584  InvalidateSelectionState();
  10585 
  10586  // We don't need to reflow if there is no content.
  10587  if (!maxContentLength) {
  10588    ClearMetrics(aMetrics);
  10589    return;
  10590  }
  10591 
  10592 #ifdef NOISY_BIDI
  10593  printf("Reflowed textframe\n");
  10594 #endif
  10595 
  10596  const nsStyleText* textStyle = StyleText();
  10597 
  10598  bool atStartOfLine = aLineLayout.LineAtStart();
  10599  if (atStartOfLine) {
  10600    AddStateBits(TEXT_START_OF_LINE);
  10601  }
  10602 
  10603  uint32_t flowEndInTextRun;
  10604  nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
  10605  const auto& characterDataBuffer = CharacterDataBuffer();
  10606 
  10607  // DOM offsets of the text range we need to measure, after trimming
  10608  // whitespace, restricting to first-letter, and restricting preformatted text
  10609  // to nearest newline
  10610  int32_t length = maxContentLength;
  10611  int32_t offset = GetContentOffset();
  10612 
  10613  // Restrict preformatted text to the nearest newline
  10614  NewlineProperty* cachedNewlineOffset = nullptr;
  10615  int32_t newLineOffset = -1;  // this will be -1 or a content offset
  10616  // This will just return -1 if newlines are not significant.
  10617  int32_t contentNewLineOffset =
  10618      GetContentNewLineOffset(offset, cachedNewlineOffset);
  10619  if (contentNewLineOffset < offset + length) {
  10620    // The new line offset could be outside this frame if the frame has been
  10621    // split by bidi resolution. In that case we won't use it in this reflow
  10622    // (newLineOffset will remain -1), but we will still cache it in mContent
  10623    newLineOffset = contentNewLineOffset;
  10624  }
  10625  if (newLineOffset >= 0) {
  10626    length = newLineOffset + 1 - offset;
  10627  }
  10628 
  10629  if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
  10630      HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
  10631    // Skip leading whitespace. Make sure we don't skip a 'pre-line'
  10632    // newline if there is one.
  10633    int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
  10634    int32_t whitespaceCount =
  10635        GetTrimmableWhitespaceCount(characterDataBuffer, offset, skipLength, 1);
  10636    if (whitespaceCount) {
  10637      offset += whitespaceCount;
  10638      length -= whitespaceCount;
  10639      // Make sure this frame maps the trimmable whitespace.
  10640      if (MOZ_UNLIKELY(offset > GetContentEnd())) {
  10641        SetLength(offset - GetContentOffset(), &aLineLayout,
  10642                  ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  10643      }
  10644    }
  10645  }
  10646 
  10647  // If trimming whitespace left us with nothing to do, return early.
  10648  if (length == 0) {
  10649    ClearMetrics(aMetrics);
  10650    return;
  10651  }
  10652 
  10653  bool completedFirstLetter = false;
  10654  // Layout dependent styles are a problem because we need to reconstruct
  10655  // the gfxTextRun based on our layout.
  10656  if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
  10657    SetLength(maxContentLength, &aLineLayout,
  10658              ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  10659 
  10660    if (aLineLayout.GetInFirstLetter()) {
  10661      // floating first-letter boundaries are significant in textrun
  10662      // construction, so clear the textrun out every time we hit a first-letter
  10663      // and have changed our length (which controls the first-letter boundary)
  10664      ClearTextRuns();
  10665      // Find the length of the first-letter. We need a textrun for this.
  10666      // REVIEW: maybe-bogus inflation should be ok (fixed below)
  10667      gfxSkipCharsIterator iter =
  10668          EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
  10669                        aLineLayout.GetLine(), &flowEndInTextRun);
  10670 
  10671      if (mTextRun) {
  10672        int32_t firstLetterLength = length;
  10673        if (aLineLayout.GetFirstLetterStyleOK()) {
  10674          // We only pass a language code to FindFirstLetterRange if it was
  10675          // explicit in the content.
  10676          const nsStyleFont* styleFont = StyleFont();
  10677          const nsAtom* lang = styleFont->mExplicitLanguage
  10678                                   ? styleFont->mLanguage.get()
  10679                                   : nullptr;
  10680          completedFirstLetter =
  10681              FindFirstLetterRange(characterDataBuffer, lang, mTextRun, offset,
  10682                                   iter, &firstLetterLength);
  10683          if (newLineOffset >= 0) {
  10684            // Don't allow a preformatted newline to be part of a first-letter.
  10685            firstLetterLength = std::min(firstLetterLength, length - 1);
  10686            if (length == 1) {
  10687              // There is no text to be consumed by the first-letter before the
  10688              // preformatted newline. Note that the first letter is therefore
  10689              // complete (FindFirstLetterRange will have returned false).
  10690              completedFirstLetter = true;
  10691            }
  10692          }
  10693        } else {
  10694          // We're in a first-letter frame's first in flow, so if there
  10695          // was a first-letter, we'd be it. However, for one reason
  10696          // or another (e.g., preformatted line break before this text),
  10697          // we're not actually supposed to have first-letter style. So
  10698          // just make a zero-length first-letter.
  10699          firstLetterLength = 0;
  10700          completedFirstLetter = true;
  10701        }
  10702        length = firstLetterLength;
  10703        if (length) {
  10704          AddStateBits(TEXT_FIRST_LETTER);
  10705        }
  10706        // Change this frame's length to the first-letter length right now
  10707        // so that when we rebuild the textrun it will be built with the
  10708        // right first-letter boundary
  10709        SetLength(offset + length - GetContentOffset(), &aLineLayout,
  10710                  ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  10711        // Ensure that the textrun will be rebuilt
  10712        ClearTextRuns();
  10713      }
  10714    }
  10715  }
  10716 
  10717  float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
  10718 
  10719  if (!IsCurrentFontInflation(fontSizeInflation)) {
  10720    // FIXME: Ideally, if we already have a text run, we'd move it to be
  10721    // the uninflated text run.
  10722    ClearTextRun(nullptr, nsTextFrame::eInflated);
  10723    mFontMetrics = nullptr;
  10724  }
  10725 
  10726  gfxSkipCharsIterator iter =
  10727      EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
  10728                    aLineLayout.GetLine(), &flowEndInTextRun);
  10729 
  10730  NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
  10731               "EnsureTextRun should have set font size inflation");
  10732 
  10733  if (mTextRun && iter.GetOriginalEnd() < offset + length) {
  10734    // The textrun does not map enough text for this frame. This can happen
  10735    // when the textrun was ended in the middle of a text node because a
  10736    // preformatted newline was encountered, and prev-in-flow frames have
  10737    // consumed all the text of the textrun. We need a new textrun.
  10738    ClearTextRuns();
  10739    iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
  10740                         aLineLayout.GetLine(), &flowEndInTextRun);
  10741  }
  10742 
  10743  if (!mTextRun) {
  10744    ClearMetrics(aMetrics);
  10745    return;
  10746  }
  10747 
  10748  NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(
  10749                   offset + length) <= mTextRun->GetLength(),
  10750               "Text run does not map enough text for our reflow");
  10751 
  10752  /////////////////////////////////////////////////////////////////////
  10753  // See how much text should belong to this text frame, and measure it
  10754  /////////////////////////////////////////////////////////////////////
  10755 
  10756  iter.SetOriginalOffset(offset);
  10757  nscoord xOffsetForTabs =
  10758      (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)
  10759          ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
  10760             lineContainer->GetUsedBorderAndPadding().left)
  10761          : -1;
  10762  PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this,
  10763                            iter, length, lineContainer, xOffsetForTabs,
  10764                            nsTextFrame::eInflated,
  10765                            HasAnyStateBits(TEXT_START_OF_LINE));
  10766 
  10767  uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
  10768 
  10769  gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS;
  10770  if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) {
  10771    if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) {
  10772      if (firstLetter->UseTightBounds()) {
  10773        boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS;
  10774      }
  10775    }
  10776  }
  10777 
  10778  int32_t limitLength = length;
  10779  int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
  10780  bool forceBreakAfter = false;
  10781  if (forceBreak >= length) {
  10782    forceBreakAfter = forceBreak == length;
  10783    // The break is not within the text considered for this textframe.
  10784    forceBreak = -1;
  10785  }
  10786  if (forceBreak >= 0) {
  10787    limitLength = forceBreak;
  10788  }
  10789  // This is the heart of text reflow right here! We don't know where
  10790  // to break, so we need to see how much text fits in the available width.
  10791  uint32_t transformedLength;
  10792  if (offset + limitLength >= int32_t(characterDataBuffer.GetLength())) {
  10793    NS_ASSERTION(
  10794        offset + limitLength == int32_t(characterDataBuffer.GetLength()),
  10795        "Content offset/length out of bounds");
  10796    NS_ASSERTION(flowEndInTextRun >= transformedOffset,
  10797                 "Negative flow length?");
  10798    transformedLength = flowEndInTextRun - transformedOffset;
  10799  } else {
  10800    // we're not looking at all the content, so we need to compute the
  10801    // length of the transformed substring we're looking at
  10802    gfxSkipCharsIterator iter(provider.GetStart());
  10803    iter.SetOriginalOffset(offset + limitLength);
  10804    transformedLength = iter.GetSkippedOffset() - transformedOffset;
  10805  }
  10806  gfxTextRun::Metrics textMetrics;
  10807  uint32_t transformedLastBreak = 0;
  10808  bool usedHyphenation = false;
  10809  gfxTextRun::TrimmableWS trimmableWS;
  10810  gfxFloat availWidth = aAvailableWidth;
  10811  if (Style()->IsTextCombined()) {
  10812    // If text-combine-upright is 'all', we would compress whatever long
  10813    // text into ~1em width, so there is no limited on the avail width.
  10814    availWidth = std::numeric_limits<gfxFloat>::infinity();
  10815  }
  10816  bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
  10817                                   HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML);
  10818  bool isBreakSpaces =
  10819      textStyle->mWhiteSpaceCollapse == StyleWhiteSpaceCollapse::BreakSpaces;
  10820  // allow whitespace to overflow the container
  10821  bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
  10822  gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
  10823  gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
  10824  bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
  10825  if (shouldSuppressLineBreak) {
  10826    suppressBreak = gfxTextRun::eSuppressAllBreaks;
  10827  } else if (!aLineLayout.LineIsBreakable()) {
  10828    suppressBreak = gfxTextRun::eSuppressInitialBreak;
  10829  }
  10830  uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText(
  10831      transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE),
  10832      availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget,
  10833      textStyle->WordCanWrap(this), textStyle->WhiteSpaceCanWrap(this),
  10834      isBreakSpaces,
  10835      // The following are output parameters:
  10836      canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr,
  10837      textMetrics, usedHyphenation, transformedLastBreak,
  10838      // In/out
  10839      breakPriority);
  10840  if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
  10841    // If we're measuring a zero-length piece of text, update
  10842    // the height manually.
  10843    nsFontMetrics* fm = provider.GetFontMetrics();
  10844    if (fm) {
  10845      textMetrics.mAscent = gfxFloat(fm->MaxAscent());
  10846      textMetrics.mDescent = gfxFloat(fm->MaxDescent());
  10847    }
  10848  }
  10849  if (GetWritingMode().IsLineInverted()) {
  10850    std::swap(textMetrics.mAscent, textMetrics.mDescent);
  10851    textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
  10852  }
  10853  // The "end" iterator points to the first character after the string mapped
  10854  // by this frame. Basically, its original-string offset is offset+charsFit
  10855  // after we've computed charsFit.
  10856  gfxSkipCharsIterator end(provider.GetEndHint());
  10857  end.SetSkippedOffset(transformedOffset + transformedCharsFit);
  10858  int32_t charsFit = end.GetOriginalOffset() - offset;
  10859  if (offset + charsFit == newLineOffset) {
  10860    // We broke before a trailing preformatted '\n'. The newline should
  10861    // be assigned to this frame. Note that newLineOffset will be -1 if
  10862    // there was no preformatted newline, so we wouldn't get here in that
  10863    // case.
  10864    ++charsFit;
  10865  }
  10866  // That might have taken us beyond our assigned content range (because
  10867  // we might have advanced over some skipped chars that extend outside
  10868  // this frame), so get back in.
  10869  int32_t lastBreak = -1;
  10870  if (charsFit >= limitLength) {
  10871    charsFit = limitLength;
  10872    if (transformedLastBreak != UINT32_MAX) {
  10873      // lastBreak is needed.
  10874      // This may set lastBreak greater than 'length', but that's OK
  10875      lastBreak = end.ConvertSkippedToOriginal(transformedOffset +
  10876                                               transformedLastBreak);
  10877    }
  10878    end.SetOriginalOffset(offset + charsFit);
  10879    // If we were forced to fit, and the break position is after a soft hyphen,
  10880    // note that this is a hyphenation break.
  10881    if ((forceBreak >= 0 || forceBreakAfter) &&
  10882        HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) {
  10883      usedHyphenation = true;
  10884    }
  10885  }
  10886  if (usedHyphenation) {
  10887    // Fix up metrics to include hyphen
  10888    AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics,
  10889                       boundingBoxType, aDrawTarget);
  10890    AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
  10891  }
  10892  if (textMetrics.mBoundingBox.IsEmpty()) {
  10893    AddStateBits(TEXT_NO_RENDERED_GLYPHS);
  10894  }
  10895 
  10896  bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
  10897  if (trimmableWS.mAdvance > 0.0) {
  10898    if (canTrimTrailingWhitespace) {
  10899      // Optimization: if we we can be sure this frame will be at end of line,
  10900      // then trim the whitespace now.
  10901      if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
  10902        // We're definitely going to break so our trailing whitespace should
  10903        // definitely be trimmed. Record that we've already done it.
  10904        AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
  10905        textMetrics.mAdvanceWidth -= trimmableWS.mAdvance;
  10906        trimmableWS.mAdvance = 0.0;
  10907      }
  10908      ClearHangableISize();
  10909      ClearTrimmableWS();
  10910    } else if (whitespaceCanHang) {
  10911      // Figure out how much whitespace will hang if at end-of-line.
  10912      gfxFloat hang =
  10913          std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth),
  10914                   gfxFloat(trimmableWS.mAdvance));
  10915      SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang));
  10916      // nsLineLayout only needs the TrimmableWS property if justifying, so
  10917      // check whether this is relevant.
  10918      if (textStyle->mTextAlign == StyleTextAlign::Justify ||
  10919          textStyle->mTextAlignLast == StyleTextAlignLast::Justify) {
  10920        SetTrimmableWS(trimmableWS);
  10921      }
  10922      textMetrics.mAdvanceWidth -= hang;
  10923      trimmableWS.mAdvance = 0.0;
  10924    } else {
  10925      MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!");
  10926      ClearHangableISize();
  10927      ClearTrimmableWS();
  10928      trimmableWS.mAdvance = 0.0;
  10929    }
  10930  } else {
  10931    // Remove any stale frame properties.
  10932    ClearHangableISize();
  10933    ClearTrimmableWS();
  10934  }
  10935 
  10936  if (!brokeText && lastBreak >= 0) {
  10937    // Since everything fit and no break was forced,
  10938    // record the last break opportunity
  10939    NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth,
  10940                 "If the text doesn't fit, and we have a break opportunity, "
  10941                 "why didn't MeasureText use it?");
  10942    MOZ_ASSERT(lastBreak >= offset, "Strange break position");
  10943    aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true,
  10944                                            breakPriority);
  10945  }
  10946 
  10947  int32_t contentLength = offset + charsFit - GetContentOffset();
  10948 
  10949  /////////////////////////////////////////////////////////////////////
  10950  // Compute output metrics
  10951  /////////////////////////////////////////////////////////////////////
  10952 
  10953  // first-letter frames should use the tight bounding box metrics for
  10954  // ascent/descent for good drop-cap effects
  10955  if (HasAnyStateBits(TEXT_FIRST_LETTER)) {
  10956    textMetrics.mAscent =
  10957        std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
  10958    textMetrics.mDescent =
  10959        std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
  10960  }
  10961 
  10962  // Setup metrics for caller
  10963  // Disallow negative widths
  10964  WritingMode wm = GetWritingMode();
  10965  LogicalSize finalSize(wm);
  10966  finalSize.ISize(wm) =
  10967      NSToCoordCeilClamped(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
  10968 
  10969  nscoord fontBaseline;
  10970  // Note(dshin): Baseline should tecnhically be halfway through the em box for
  10971  // a central baseline. It is simply half of the text run block size so that it
  10972  // can be easily calculated in `GetNaturalBaselineBOffset`.
  10973  if (transformedCharsFit == 0 && !usedHyphenation) {
  10974    aMetrics.SetBlockStartAscent(0);
  10975    finalSize.BSize(wm) = 0;
  10976    fontBaseline = 0;
  10977  } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
  10978    fontBaseline = NSToCoordCeil(textMetrics.mAscent);
  10979    const auto size = fontBaseline + NSToCoordCeil(textMetrics.mDescent);
  10980    // Use actual text metrics for floating first letter frame.
  10981    aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
  10982                                                             : size / 2);
  10983    finalSize.BSize(wm) = size;
  10984  } else {
  10985    // Otherwise, ascent should contain the overline drawable area.
  10986    // And also descent should contain the underline drawable area.
  10987    // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
  10988    nsFontMetrics* fm = provider.GetFontMetrics();
  10989    nscoord fontAscent =
  10990        wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
  10991    nscoord fontDescent =
  10992        wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
  10993    fontBaseline = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
  10994    const auto size =
  10995        fontBaseline +
  10996        std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
  10997    aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
  10998                                                             : size / 2);
  10999    finalSize.BSize(wm) = size;
  11000  }
  11001  if (Style()->IsTextCombined()) {
  11002    nsFontMetrics* fm = provider.GetFontMetrics();
  11003    nscoord width = finalSize.ISize(wm);
  11004    nscoord em = fm->EmHeight();
  11005    // Compress the characters in horizontal axis if necessary.
  11006    auto* data = GetProperty(TextCombineDataProperty());
  11007    if (!data) {
  11008      data = new TextCombineData;
  11009      SetProperty(TextCombineDataProperty(), data);
  11010    }
  11011    data->mNaturalWidth = width;
  11012    finalSize.ISize(wm) = em;
  11013    // If we're going to have to adjust the block-size to make it 1em, make an
  11014    // appropriate adjustment to the font baseline (determined by the ascent
  11015    // returned in aMetrics)
  11016    if (finalSize.BSize(wm) != em) {
  11017      fontBaseline =
  11018          aMetrics.BlockStartAscent() + (em - finalSize.BSize(wm)) / 2;
  11019      aMetrics.SetBlockStartAscent(fontBaseline);
  11020    }
  11021    // If we have a next-in-flow with the same style, remove our block-size
  11022    // so that they end up beside each other; only the last in the series
  11023    // gets to keep its block-axis size.
  11024    if (GetNextSibling() && GetNextSibling()->Style() == Style()) {
  11025      finalSize.BSize(wm) = 0;
  11026    } else {
  11027      // Make the characters be in an 1em square.
  11028      finalSize.BSize(wm) = em;
  11029    }
  11030    // If there are earlier sibling frames with the same style, and we have
  11031    // reached the end of the run of same-style siblings, recompute the scale
  11032    // as necessary to make them all fit. (This is a bit inefficient, but is
  11033    // such a rare case that we don't much care.)
  11034    nsIFrame* f = GetPrevSibling();
  11035    if (f && f->Style() == Style() &&
  11036        (!GetNextSibling() || GetNextSibling()->Style() != Style())) {
  11037      // Collect the total "natural" width of the text.
  11038      while (f->Style() == Style()) {
  11039        if (auto* fData = f->GetProperty(TextCombineDataProperty())) {
  11040          width += fData->mNaturalWidth;
  11041        }
  11042        if (!f->GetPrevSibling()) {
  11043          break;
  11044        }
  11045        f = f->GetPrevSibling();
  11046      }
  11047    } else {
  11048      // Not at the end of a run of frames; we're just going to handle `this`.
  11049      f = this;
  11050    }
  11051    // Figure out scaling to apply to this frame (or to the run of same-style
  11052    // sibling frames), and the starting offset within the em square.
  11053    float scale;
  11054    nscoord offset;
  11055    if (width > em) {
  11056      scale = static_cast<float>(em) / width;
  11057      offset = 0;
  11058    } else {
  11059      scale = 1.0f;
  11060      offset = (em - width) / 2;
  11061    }
  11062    while (true) {
  11063      if (auto* fData = f->GetProperty(TextCombineDataProperty())) {
  11064        fData->mScale = scale;
  11065        fData->mOffset = offset;
  11066        offset += fData->mNaturalWidth * scale;
  11067      }
  11068      if (f == this) {
  11069        break;
  11070      }
  11071      f = f->GetNextSibling();
  11072    }
  11073  }
  11074  aMetrics.SetSize(wm, finalSize);
  11075 
  11076  NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???");
  11077  // The effective "descent" here will be negative in the case of a frame that
  11078  // is participating in text-combine-upright, but is not the last frame within
  11079  // the combined upright cell, because we zero out its block-size (see above).
  11080  // So this creates an exception to the non-negative assertion here.
  11081  DebugOnly<nscoord> descent =
  11082      (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode())
  11083                                 : aMetrics.BSize(aMetrics.GetWritingMode())) -
  11084      aMetrics.BlockStartAscent();
  11085  NS_ASSERTION(descent >= 0 || (Style()->IsTextCombined() && GetNextSibling() &&
  11086                                GetNextSibling()->Style() == Style()),
  11087               "Unexpected negative descent???");
  11088 
  11089  mAscent = fontBaseline;
  11090 
  11091  // Handle text that runs outside its normal bounds.
  11092  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
  11093  if (mTextRun->IsVertical()) {
  11094    // Swap line-relative textMetrics dimensions to physical coordinates.
  11095    std::swap(boundingBox.x, boundingBox.y);
  11096    std::swap(boundingBox.width, boundingBox.height);
  11097    if (GetWritingMode().IsVerticalRL()) {
  11098      boundingBox.x = -boundingBox.XMost();
  11099      boundingBox.x += aMetrics.Width() - mAscent;
  11100    } else {
  11101      boundingBox.x += mAscent;
  11102    }
  11103  } else {
  11104    boundingBox.y += mAscent;
  11105  }
  11106  aMetrics.SetOverflowAreasToDesiredBounds();
  11107  aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox);
  11108 
  11109  // When we have text decorations, we don't need to compute their overflow now
  11110  // because we're guaranteed to do it later
  11111  // (see nsLineLayout::RelativePositionFrames)
  11112  UnionAdditionalOverflow(presContext, aLineLayout.LineContainerFrame(),
  11113                          provider, &aMetrics.InkOverflow(), false, true);
  11114 
  11115  /////////////////////////////////////////////////////////////////////
  11116  // Clean up, update state
  11117  /////////////////////////////////////////////////////////////////////
  11118 
  11119  // If all our characters are discarded or collapsed, then trimmable width
  11120  // from the last textframe should be preserved. Otherwise the trimmable width
  11121  // from this textframe overrides. (Currently in CSS trimmable width can be
  11122  // at most one space so there's no way for trimmable width from a previous
  11123  // frame to accumulate with trimmable width from this frame.)
  11124  if (transformedCharsFit > 0) {
  11125    aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance));
  11126    AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
  11127  }
  11128  bool breakAfter = forceBreakAfter;
  11129  if (!shouldSuppressLineBreak) {
  11130    if (charsFit > 0 && charsFit == length &&
  11131        textStyle->mHyphens != StyleHyphens::None &&
  11132        HasSoftHyphenBefore(characterDataBuffer, mTextRun, offset, end)) {
  11133      bool fits =
  11134          textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
  11135      // Record a potential break after final soft hyphen
  11136      aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
  11137                                              gfxBreakPriority::eNormalBreak);
  11138    }
  11139    // length == 0 means either the text is empty or it's all collapsed away
  11140    bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
  11141    if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
  11142        transformedOffset + transformedLength == mTextRun->GetLength() &&
  11143        (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) {
  11144      // We placed all the text in the textrun and we have a break opportunity
  11145      // at the end of the textrun. We need to record it because the following
  11146      // content may not care about nsLineBreaker.
  11147 
  11148      // Note that because we didn't break, we can be sure that (thanks to the
  11149      // code up above) textMetrics.mAdvanceWidth includes the width of any
  11150      // trailing whitespace. So we need to subtract trimmableWidth here
  11151      // because if we did break at this point, that much width would be
  11152      // trimmed.
  11153      if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) {
  11154        breakAfter = true;
  11155      } else {
  11156        aLineLayout.NotifyOptionalBreakPosition(this, length, true,
  11157                                                gfxBreakPriority::eNormalBreak);
  11158      }
  11159    }
  11160  }
  11161 
  11162  // Compute reflow status
  11163  if (contentLength != maxContentLength) {
  11164    aStatus.SetIncomplete();
  11165  }
  11166 
  11167  if (charsFit == 0 && length > 0 && !usedHyphenation) {
  11168    // Couldn't place any text
  11169    aStatus.SetInlineLineBreakBeforeAndReset();
  11170  } else if (contentLength > 0 &&
  11171             mContentOffset + contentLength - 1 == newLineOffset) {
  11172    // Ends in \n
  11173    aStatus.SetInlineLineBreakAfter();
  11174    aLineLayout.SetLineEndsInBR(true);
  11175  } else if (breakAfter) {
  11176    aStatus.SetInlineLineBreakAfter();
  11177  }
  11178  if (completedFirstLetter) {
  11179    aLineLayout.SetFirstLetterStyleOK(false);
  11180    aStatus.SetFirstLetterComplete();
  11181  }
  11182  if (brokeText && breakPriority == gfxBreakPriority::eWordWrapBreak) {
  11183    aLineLayout.SetUsedOverflowWrap();
  11184  }
  11185 
  11186  // Updated the cached NewlineProperty, or delete it.
  11187  if (contentLength < maxContentLength &&
  11188      textStyle->NewlineIsSignificant(this) &&
  11189      (contentNewLineOffset < 0 ||
  11190       mContentOffset + contentLength <= contentNewLineOffset)) {
  11191    if (!cachedNewlineOffset) {
  11192      cachedNewlineOffset = new NewlineProperty;
  11193      if (NS_FAILED(mContent->SetProperty(
  11194              nsGkAtoms::newline, cachedNewlineOffset,
  11195              nsINode::DeleteProperty<NewlineProperty>))) {
  11196        delete cachedNewlineOffset;
  11197        cachedNewlineOffset = nullptr;
  11198      }
  11199      mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
  11200    }
  11201    if (cachedNewlineOffset) {
  11202      cachedNewlineOffset->mStartOffset = offset;
  11203      cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
  11204    }
  11205  } else if (cachedNewlineOffset) {
  11206    mContent->RemoveProperty(nsGkAtoms::newline);
  11207    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
  11208  }
  11209 
  11210  // Compute space and letter counts for justification, if required
  11211  if ((lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
  11212       lineContainer->StyleText()->mTextAlignLast ==
  11213           StyleTextAlignLast::Justify ||
  11214       shouldSuppressLineBreak) &&
  11215      !lineContainer->IsInSVGTextSubtree()) {
  11216    AddStateBits(TEXT_JUSTIFICATION_ENABLED);
  11217    Range range(uint32_t(offset), uint32_t(offset + charsFit));
  11218    aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
  11219  }
  11220 
  11221  SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
  11222 
  11223  InvalidateFrame();
  11224 
  11225 #ifdef NOISY_REFLOW
  11226  ListTag(stdout);
  11227  printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(),
  11228         aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus);
  11229 #endif
  11230 }
  11231 
  11232 /* virtual */
  11233 bool nsTextFrame::CanContinueTextRun() const {
  11234  // We can continue a text run through a text frame
  11235  return true;
  11236 }
  11237 
  11238 nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace(
  11239    DrawTarget* aDrawTarget) {
  11240  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),
  11241             "frame should have been reflowed");
  11242 
  11243  TrimOutput result;
  11244  result.mChanged = false;
  11245  result.mDeltaWidth = 0;
  11246 
  11247  AddStateBits(TEXT_END_OF_LINE);
  11248 
  11249  if (!GetTextRun(nsTextFrame::eInflated)) {
  11250    // If reflow didn't create a textrun, there must have been no content once
  11251    // leading whitespace was trimmed, so nothing more to do here.
  11252    return result;
  11253  }
  11254 
  11255  int32_t contentLength = GetContentLength();
  11256  if (!contentLength) {
  11257    return result;
  11258  }
  11259 
  11260  gfxSkipCharsIterator start =
  11261      EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
  11262  NS_ENSURE_TRUE(mTextRun, result);
  11263 
  11264  uint32_t trimmedStart = start.GetSkippedOffset();
  11265 
  11266  const auto& characterDataBuffer = CharacterDataBuffer();
  11267  TrimmedOffsets trimmed = GetTrimmedOffsets(characterDataBuffer);
  11268  gfxSkipCharsIterator trimmedEndIter = start;
  11269  const nsStyleText* textStyle = StyleText();
  11270  gfxFloat delta = 0;
  11271  uint32_t trimmedEnd =
  11272      trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
  11273 
  11274  if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) &&
  11275      trimmed.GetEnd() < GetContentEnd()) {
  11276    gfxSkipCharsIterator end = trimmedEndIter;
  11277    uint32_t endOffset =
  11278        end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
  11279    if (trimmedEnd < endOffset) {
  11280      // We can't be dealing with tabs here ... they wouldn't be trimmed. So
  11281      // it's OK to pass null for the line container.
  11282      PropertyProvider provider(mTextRun, textStyle, characterDataBuffer, this,
  11283                                start, contentLength, nullptr, 0,
  11284                                nsTextFrame::eInflated,
  11285                                HasAnyStateBits(TEXT_START_OF_LINE));
  11286      delta =
  11287          mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
  11288      result.mChanged = true;
  11289    }
  11290  }
  11291 
  11292  gfxFloat advanceDelta;
  11293  mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
  11294                          HasAnyStateBits(TEXT_START_OF_LINE), true,
  11295                          &advanceDelta);
  11296  if (advanceDelta != 0) {
  11297    result.mChanged = true;
  11298  }
  11299 
  11300  // aDeltaWidth is *subtracted* from our width.
  11301  // If advanceDelta is positive then setting the line break made us longer,
  11302  // so aDeltaWidth could go negative.
  11303  result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
  11304  // If aDeltaWidth goes negative, that means this frame might not actually fit
  11305  // anymore!!! We need higher level line layout to recover somehow.
  11306  // If it's because the frame has a soft hyphen that is now being displayed,
  11307  // this should actually be OK, because our reflow recorded the break
  11308  // opportunity that allowed the soft hyphen to be used, and we wouldn't
  11309  // have recorded the opportunity unless the hyphen fit (or was the first
  11310  // opportunity on the line).
  11311  // Otherwise this can/ really only happen when we have glyphs with special
  11312  // shapes at the end of lines, I think. Breaking inside a kerning pair won't
  11313  // do it because that would mean we broke inside this textrun, and
  11314  // BreakAndMeasureText should make sure the resulting shaped substring fits.
  11315  // Maybe if we passed a maxTextLength? But that only happens at direction
  11316  // changes (so we wouldn't kern across the boundary) or for first-letter
  11317  // (which always fits because it starts the line!).
  11318  NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,
  11319                       "Negative deltawidth, something odd is happening");
  11320 
  11321 #ifdef NOISY_TRIM
  11322  ListTag(stdout);
  11323  printf(": trim => %d\n", result.mDeltaWidth);
  11324 #endif
  11325  return result;
  11326 }
  11327 
  11328 OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame,
  11329                                             bool aIncludeShadows) {
  11330  RemoveProperty(WebRenderTextBounds());
  11331 
  11332  nsRect bounds(nsPoint(0, 0), GetSize());
  11333  OverflowAreas result(bounds, bounds);
  11334 
  11335  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  11336  if (!mTextRun) {
  11337    return result;
  11338  }
  11339 
  11340  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
  11341  // Don't trim trailing space, in case we need to paint it as selected.
  11342  provider.InitializeForDisplay(false);
  11343 
  11344  gfxTextRun::Metrics textMetrics =
  11345      mTextRun->MeasureText(ComputeTransformedRange(provider),
  11346                            gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
  11347  if (GetWritingMode().IsLineInverted()) {
  11348    textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
  11349  }
  11350  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
  11351  boundingBox += nsPoint(0, mAscent);
  11352  if (mTextRun->IsVertical()) {
  11353    // Swap line-relative textMetrics dimensions to physical coordinates.
  11354    std::swap(boundingBox.x, boundingBox.y);
  11355    std::swap(boundingBox.width, boundingBox.height);
  11356  }
  11357  nsRect& vis = result.InkOverflow();
  11358  vis.UnionRect(vis, boundingBox);
  11359  UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true,
  11360                          aIncludeShadows);
  11361  return result;
  11362 }
  11363 
  11364 static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
  11365                           const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
  11366                           const CharacterDataBuffer& aBuffer,
  11367                           int32_t aBufferOffset, int32_t aBufferLen,
  11368                           nsAString& aOut) {
  11369  nsAutoString fragString;
  11370  char16_t* out;
  11371  bool needsToMaskPassword = NeedsToMaskPassword(aFrame);
  11372  if (aStyle->mTextTransform.IsNone() && !needsToMaskPassword &&
  11373      aStyle->mWebkitTextSecurity == StyleTextSecurity::None) {
  11374    // No text-transform, so we can copy directly to the output string.
  11375    aOut.SetLength(aOut.Length() + aBufferLen);
  11376    out = aOut.EndWriting() - aBufferLen;
  11377  } else {
  11378    // Use a temporary string as source for the transform.
  11379    fragString.SetLength(aBufferLen);
  11380    out = fragString.BeginWriting();
  11381  }
  11382 
  11383  // Copy the text, with \n and \t replaced by <space> if appropriate.
  11384  MOZ_ASSERT(aBufferOffset >= 0);
  11385  for (uint32_t i = 0; i < static_cast<uint32_t>(aBufferLen); ++i) {
  11386    char16_t ch = aBuffer.CharAt(static_cast<uint32_t>(aBufferOffset) + i);
  11387    if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
  11388        (ch == '\t' && !aStyle->TabIsSignificant())) {
  11389      ch = ' ';
  11390    }
  11391    out[i] = ch;
  11392  }
  11393 
  11394  if (!aStyle->mTextTransform.IsNone() || needsToMaskPassword ||
  11395      aStyle->mWebkitTextSecurity != StyleTextSecurity::None) {
  11396    MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed);
  11397    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
  11398      // Apply text-transform according to style in the transformed run.
  11399      char16_t maskChar =
  11400          needsToMaskPassword ? 0 : aStyle->TextSecurityMaskChar();
  11401      auto transformedTextRun =
  11402          static_cast<const nsTransformedTextRun*>(aTextRun);
  11403      nsAutoString convertedString;
  11404      AutoTArray<bool, 50> charsToMergeArray;
  11405      AutoTArray<bool, 50> deletedCharsArray;
  11406      nsCaseTransformTextRunFactory::TransformString(
  11407          fragString, convertedString, /* aGlobalTransform = */ Nothing(),
  11408          maskChar, /* aCaseTransformsOnly = */ true, nullptr,
  11409          charsToMergeArray, deletedCharsArray, transformedTextRun,
  11410          aSkippedOffset);
  11411      aOut.Append(convertedString);
  11412    } else {
  11413      // Should not happen (see assertion above), but as a fallback...
  11414      aOut.Append(fragString);
  11415    }
  11416  }
  11417 }
  11418 
  11419 static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame,
  11420                                            nsBlockFrame* aLineContainer,
  11421                                            bool* aStartsAtHardBreak,
  11422                                            bool* aEndsAtHardBreak) {
  11423  bool foundValidLine;
  11424  nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
  11425  if (!foundValidLine) {
  11426    NS_ERROR("Invalid line!");
  11427    *aStartsAtHardBreak = *aEndsAtHardBreak = true;
  11428    return;
  11429  }
  11430 
  11431  *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped();
  11432  if (iter.Prev()) {
  11433    *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped();
  11434  } else {
  11435    // Hit block boundary
  11436    *aStartsAtHardBreak = true;
  11437  }
  11438 }
  11439 
  11440 bool nsTextFrame::AppendRenderedText(AppendRenderedTextState& aState,
  11441                                     RenderedText& aResult) {
  11442  if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
  11443    // We don't trust dirty frames, especially when computing rendered text.
  11444    return false;
  11445  }
  11446 
  11447  // Ensure the text run and grab the gfxSkipCharsIterator for it
  11448  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  11449  if (!mTextRun) {
  11450    return false;
  11451  }
  11452  gfxSkipCharsIterator tmpIter = iter;
  11453 
  11454  // Check if the frame starts/ends at a hard line break, to determine
  11455  // whether whitespace should be trimmed.
  11456  bool startsAtHardBreak, endsAtHardBreak;
  11457  if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) {
  11458    startsAtHardBreak = endsAtHardBreak = false;
  11459  } else if (nsBlockFrame* thisLc = do_QueryFrame(FindLineContainer())) {
  11460    if (thisLc != aState.mLineContainer) {
  11461      // Setup line cursor when needed.
  11462      aState.mLineContainer = thisLc;
  11463      aState.mLineContainer->SetupLineCursorForQuery();
  11464    }
  11465    LineStartsOrEndsAtHardLineBreak(this, aState.mLineContainer,
  11466                                    &startsAtHardBreak, &endsAtHardBreak);
  11467  } else {
  11468    // Weird situation where we have a line layout without a block.
  11469    // No soft breaks occur in this situation.
  11470    startsAtHardBreak = endsAtHardBreak = true;
  11471  }
  11472 
  11473  // Whether we need to trim whitespaces after the text frame.
  11474  // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags
  11475  // in the cases where this should not occur.
  11476  TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default;
  11477  if (!IsAtEndOfLine() ||
  11478      aState.mTrimTrailingWhitespace != TrailingWhitespace::Trim ||
  11479      !endsAtHardBreak) {
  11480    trimFlags |= TrimmedOffsetFlags::NoTrimAfter;
  11481  }
  11482 
  11483  // Whether to trim whitespaces before the text frame.
  11484  if (!startsAtHardBreak) {
  11485    trimFlags |= TrimmedOffsetFlags::NoTrimBefore;
  11486  }
  11487 
  11488  TrimmedOffsets trimmedOffsets =
  11489      GetTrimmedOffsets(aState.mCharacterDataBuffer, trimFlags);
  11490  bool trimmedSignificantNewline =
  11491      (trimmedOffsets.GetEnd() < GetContentEnd() ||
  11492       (aState.mTrimTrailingWhitespace == TrailingWhitespace::Trim &&
  11493        StyleText()->mWhiteSpaceCollapse ==
  11494            StyleWhiteSpaceCollapse::PreserveBreaks)) &&
  11495      HasSignificantTerminalNewline();
  11496  uint32_t skippedToRenderedStringOffset =
  11497      aState.mOffsetInRenderedString -
  11498      tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
  11499  uint32_t nextOffsetInRenderedString =
  11500      tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
  11501      (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
  11502 
  11503  if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) {
  11504    if (nextOffsetInRenderedString <= aState.mStartOffset) {
  11505      aState.mOffsetInRenderedString = nextOffsetInRenderedString;
  11506      return true;
  11507    }
  11508    if (!aState.mHaveOffsets) {
  11509      aResult.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal(
  11510          aState.mStartOffset - skippedToRenderedStringOffset);
  11511      aResult.mOffsetWithinNodeRenderedText = aState.mStartOffset;
  11512      aState.mHaveOffsets = true;
  11513    }
  11514    if (aState.mOffsetInRenderedString >= aState.mEndOffset) {
  11515      return false;
  11516    }
  11517  } else {
  11518    if (uint32_t(GetContentEnd()) <= aState.mStartOffset) {
  11519      aState.mOffsetInRenderedString = nextOffsetInRenderedString;
  11520      return true;
  11521    }
  11522    if (!aState.mHaveOffsets) {
  11523      aResult.mOffsetWithinNodeText = aState.mStartOffset;
  11524      // Skip trimmed space when computed the rendered text offset.
  11525      int32_t clamped =
  11526          std::max<int32_t>(aState.mStartOffset, trimmedOffsets.mStart);
  11527      aResult.mOffsetWithinNodeRenderedText =
  11528          tmpIter.ConvertOriginalToSkipped(clamped) +
  11529          skippedToRenderedStringOffset;
  11530      MOZ_ASSERT(aResult.mOffsetWithinNodeRenderedText >=
  11531                         aState.mOffsetInRenderedString &&
  11532                     aResult.mOffsetWithinNodeRenderedText <= INT32_MAX,
  11533                 "Bad offset within rendered text");
  11534      aState.mHaveOffsets = true;
  11535    }
  11536    if (uint32_t(mContentOffset) >= aState.mEndOffset) {
  11537      return false;
  11538    }
  11539  }
  11540 
  11541  int32_t startOffset;
  11542  int32_t endOffset;
  11543  if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) {
  11544    startOffset = tmpIter.ConvertSkippedToOriginal(
  11545        aState.mStartOffset - skippedToRenderedStringOffset);
  11546    endOffset = tmpIter.ConvertSkippedToOriginal(aState.mEndOffset -
  11547                                                 skippedToRenderedStringOffset);
  11548  } else {
  11549    startOffset = aState.mStartOffset;
  11550    endOffset = std::min<uint32_t>(INT32_MAX, aState.mEndOffset);
  11551  }
  11552 
  11553  // If startOffset and/or endOffset are inside of trimmedOffsets' range,
  11554  // then clamp the edges of trimmedOffsets accordingly.
  11555  int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
  11556  trimmedOffsets.mStart =
  11557      std::max<uint32_t>(trimmedOffsets.mStart, startOffset);
  11558  trimmedOffsets.mLength =
  11559      std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) -
  11560      trimmedOffsets.mStart;
  11561 
  11562  if (trimmedOffsets.mLength > 0) {
  11563    const nsStyleText* textStyle = StyleText();
  11564    iter.SetOriginalOffset(trimmedOffsets.mStart);
  11565    while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
  11566      int32_t runLength;
  11567      bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
  11568      runLength = std::min(runLength,
  11569                           trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
  11570      if (isSkipped) {
  11571        MOZ_ASSERT(runLength >= 0);
  11572        for (uint32_t i = 0; i < static_cast<uint32_t>(runLength); ++i) {
  11573          const char16_t ch = aState.mCharacterDataBuffer.CharAt(
  11574              AssertedCast<uint32_t>(iter.GetOriginalOffset() + i));
  11575          if (ch == CH_SHY) {
  11576            // We should preserve soft hyphens. They can't be transformed.
  11577            aResult.mString.Append(ch);
  11578          }
  11579        }
  11580      } else {
  11581        TransformChars(this, textStyle, mTextRun, iter.GetSkippedOffset(),
  11582                       aState.mCharacterDataBuffer, iter.GetOriginalOffset(),
  11583                       runLength, aResult.mString);
  11584      }
  11585      iter.AdvanceOriginal(runLength);
  11586    }
  11587  }
  11588 
  11589  if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
  11590    // A significant newline was trimmed off (we must be
  11591    // white-space:pre-line). Put it back.
  11592    aResult.mString.Append('\n');
  11593  }
  11594  aState.mOffsetInRenderedString = nextOffsetInRenderedString;
  11595 
  11596  return true;
  11597 }
  11598 
  11599 nsIFrame::RenderedText nsTextFrame::GetRenderedText(
  11600    uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType,
  11601    TrailingWhitespace aTrimTrailingWhitespace) {
  11602  MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets");
  11603  MOZ_ASSERT(!GetPrevContinuation() ||
  11604                 (aOffsetType == TextOffsetType::OffsetsInContentText &&
  11605                  aStartOffset >= (uint32_t)GetContentOffset() &&
  11606                  aEndOffset <= (uint32_t)GetContentEnd()),
  11607             "Must be called on first-in-flow, or content offsets must be "
  11608             "given and be within this frame.");
  11609 
  11610  // The handling of offsets could be more efficient...
  11611  RenderedText result;
  11612  AppendRenderedTextState state{aStartOffset, aEndOffset, aOffsetType,
  11613                                aTrimTrailingWhitespace, CharacterDataBuffer()};
  11614 
  11615  for (nsTextFrame* textFrame = this; textFrame;
  11616       textFrame = textFrame->GetNextContinuation()) {
  11617    if (!textFrame->AppendRenderedText(state, result)) {
  11618      break;
  11619    }
  11620  }
  11621 
  11622  if (!state.mHaveOffsets) {
  11623    result.mOffsetWithinNodeText = state.mCharacterDataBuffer.GetLength();
  11624    result.mOffsetWithinNodeRenderedText = state.mOffsetInRenderedString;
  11625  }
  11626 
  11627  return result;
  11628 }
  11629 
  11630 /* virtual */
  11631 bool nsTextFrame::IsEmpty() {
  11632  NS_ASSERTION(
  11633      !HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE),
  11634      "Invalid state");
  11635 
  11636  // XXXldb Should this check compatibility mode as well???
  11637  const nsStyleText* textStyle = StyleText();
  11638  if (textStyle->WhiteSpaceIsSignificant()) {
  11639    // When WhiteSpaceIsSignificant styles are in effect, we only treat the
  11640    // frame as empty if its content really is entirely *empty* (not just
  11641    // whitespace).
  11642    return !GetContentLength();
  11643  }
  11644 
  11645  if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE)) {
  11646    return false;
  11647  }
  11648 
  11649  if (HasAnyStateBits(TEXT_IS_ONLY_WHITESPACE)) {
  11650    return true;
  11651  }
  11652 
  11653  bool isEmpty = IsAllWhitespace(CharacterDataBuffer(),
  11654                                 textStyle->mWhiteSpaceCollapse !=
  11655                                     StyleWhiteSpaceCollapse::PreserveBreaks);
  11656  AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
  11657  return isEmpty;
  11658 }
  11659 
  11660 #ifdef DEBUG_FRAME_DUMP
  11661 // Translate the mapped content into a string that's printable
  11662 void nsTextFrame::ToCString(nsCString& aBuf) const {
  11663  // Get the frame's text content
  11664  const auto& characterDataBuffer = CharacterDataBuffer();
  11665 
  11666  const int32_t length = GetContentEnd() - mContentOffset;
  11667  if (length <= 0) {
  11668    // Negative lengths are possible during invalidation.
  11669    return;
  11670  }
  11671 
  11672  const uint32_t bufferLength = AssertedCast<uint32_t>(GetContentEnd());
  11673  uint32_t bufferOffset = AssertedCast<uint32_t>(GetContentOffset());
  11674 
  11675  while (bufferOffset < bufferLength) {
  11676    char16_t ch = characterDataBuffer.CharAt(bufferOffset++);
  11677    if (ch == '\r') {
  11678      aBuf.AppendLiteral("\\r");
  11679    } else if (ch == '\n') {
  11680      aBuf.AppendLiteral("\\n");
  11681    } else if (ch == '\t') {
  11682      aBuf.AppendLiteral("\\t");
  11683    } else if ((ch < ' ') || (ch >= 127)) {
  11684      aBuf.Append(nsPrintfCString("\\u%04x", ch));
  11685    } else {
  11686      aBuf.Append(ch);
  11687    }
  11688  }
  11689 }
  11690 
  11691 nsresult nsTextFrame::GetFrameName(nsAString& aResult) const {
  11692  MakeFrameName(u"Text"_ns, aResult);
  11693  nsAutoCString tmp;
  11694  ToCString(tmp);
  11695  tmp.SetLength(std::min<size_t>(tmp.Length(), 50u));
  11696  aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns;
  11697  return NS_OK;
  11698 }
  11699 
  11700 void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
  11701  nsCString str;
  11702  ListGeneric(str, aPrefix, aFlags);
  11703 
  11704  if (!aFlags.contains(ListFlag::OnlyListDeterministicInfo)) {
  11705    str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
  11706  }
  11707 
  11708  // Output the first/last content offset and prev/next in flow info
  11709  bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
  11710  str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
  11711                         isComplete ? 'T' : 'F');
  11712 
  11713  if (IsSelected()) {
  11714    str += " SELECTED";
  11715  }
  11716  fprintf_stderr(out, "%s\n", str.get());
  11717 }
  11718 
  11719 void nsTextFrame::ListTextRuns(FILE* out,
  11720                               nsTHashSet<const void*>& aSeen) const {
  11721  if (!mTextRun || aSeen.Contains(mTextRun)) {
  11722    return;
  11723  }
  11724  aSeen.Insert(mTextRun);
  11725  mTextRun->Dump(out);
  11726 }
  11727 #endif
  11728 
  11729 void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) {
  11730  AddStateBits(NS_FRAME_IS_BIDI);
  11731  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
  11732    mContent->RemoveProperty(nsGkAtoms::flowlength);
  11733    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
  11734  }
  11735 
  11736  /*
  11737   * After Bidi resolution we may need to reassign text runs.
  11738   * This is called during bidi resolution from the block container, so we
  11739   * shouldn't be holding a local reference to a textrun anywhere.
  11740   */
  11741  ClearTextRuns();
  11742 
  11743  nsTextFrame* prev = GetPrevContinuation();
  11744  if (prev) {
  11745    // the bidi resolver can be very evil when columns/pages are involved. Don't
  11746    // let it violate our invariants.
  11747    int32_t prevOffset = prev->GetContentOffset();
  11748    aStart = std::max(aStart, prevOffset);
  11749    aEnd = std::max(aEnd, prevOffset);
  11750    prev->ClearTextRuns();
  11751  }
  11752 
  11753  mContentOffset = aStart;
  11754  SetLength(aEnd - aStart, nullptr, 0);
  11755 }
  11756 
  11757 /**
  11758 * @return true if this text frame ends with a newline character.  It should
  11759 * return false if it is not a text frame.
  11760 */
  11761 bool nsTextFrame::HasSignificantTerminalNewline() const {
  11762  return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
  11763 }
  11764 
  11765 bool nsTextFrame::IsAtEndOfLine() const {
  11766  return HasAnyStateBits(TEXT_END_OF_LINE);
  11767 }
  11768 
  11769 Maybe<nscoord> nsTextFrame::GetNaturalBaselineBOffset(
  11770    WritingMode aWM, BaselineSharingGroup aBaselineGroup,
  11771    BaselineExportContext) const {
  11772  if (aBaselineGroup == BaselineSharingGroup::Last) {
  11773    return Nothing{};
  11774  }
  11775 
  11776  if (!aWM.IsOrthogonalTo(GetWritingMode())) {
  11777    if (aWM.IsCentralBaseline()) {
  11778      return Some(GetLogicalUsedBorderAndPadding(aWM).BStart(aWM) +
  11779                  ContentBSize(aWM) / 2);
  11780    }
  11781    return Some(mAscent);
  11782  }
  11783 
  11784  // When the text frame has a writing mode orthogonal to the desired
  11785  // writing mode, return a baseline coincides its parent frame.
  11786  nsIFrame* parent = GetParent();
  11787  nsPoint position = GetNormalPosition();
  11788  nscoord parentAscent = parent->GetLogicalBaseline(aWM);
  11789  if (aWM.IsVerticalRL()) {
  11790    nscoord parentDescent = parent->GetSize().width - parentAscent;
  11791    nscoord descent = parentDescent - position.x;
  11792    return Some(GetSize().width - descent);
  11793  }
  11794  return Some(parentAscent - (aWM.IsVertical() ? position.x : position.y));
  11795 }
  11796 
  11797 nscoord nsTextFrame::GetCaretBaseline() const {
  11798  if (mAscent == 0 && HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS)) {
  11799    nsBlockFrame* container = do_QueryFrame(FindLineContainer());
  11800    // TODO(emilio): Ideally we'd want to find out if only our line is empty,
  11801    // but that's non-trivial to do, and realistically empty inlines and text
  11802    // will get placed into a non-empty line unless all lines are empty, I
  11803    // believe...
  11804    if (container && container->LinesAreEmpty()) {
  11805      nscoord blockSize = container->ContentBSize(GetWritingMode());
  11806      return GetFontMetricsDerivedCaretBaseline(blockSize);
  11807    }
  11808  }
  11809  return nsIFrame::GetCaretBaseline();
  11810 }
  11811 
  11812 bool nsTextFrame::HasAnyNoncollapsedCharacters() {
  11813  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
  11814  int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd();
  11815  int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
  11816  int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
  11817  return skippedOffset != skippedOffsetEnd;
  11818 }
  11819 
  11820 bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
  11821  return ComputeCustomOverflowInternal(aOverflowAreas, true);
  11822 }
  11823 
  11824 bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas,
  11825                                                bool aIncludeShadows) {
  11826  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
  11827    return true;
  11828  }
  11829 
  11830  nsIFrame* decorationsBlock;
  11831  if (IsFloatingFirstLetterChild()) {
  11832    decorationsBlock = GetParent();
  11833  } else {
  11834    nsIFrame* f = this;
  11835    for (;;) {
  11836      nsBlockFrame* fBlock = do_QueryFrame(f);
  11837      if (fBlock) {
  11838        decorationsBlock = fBlock;
  11839        break;
  11840      }
  11841 
  11842      f = f->GetParent();
  11843      if (!f) {
  11844        NS_ERROR("Couldn't find any block ancestor (for text decorations)");
  11845        return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
  11846      }
  11847    }
  11848  }
  11849 
  11850  aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows);
  11851  return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
  11852 }
  11853 
  11854 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)
  11855 
  11856 void nsTextFrame::AssignJustificationGaps(
  11857    const mozilla::JustificationAssignment& aAssign) {
  11858  int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
  11859  static_assert(sizeof(aAssign) == 1,
  11860                "The encoding might be broken if JustificationAssignment "
  11861                "is larger than 1 byte");
  11862  SetProperty(JustificationAssignmentProperty(), encoded);
  11863 }
  11864 
  11865 mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment()
  11866    const {
  11867  int32_t encoded = GetProperty(JustificationAssignmentProperty());
  11868  mozilla::JustificationAssignment result;
  11869  result.mGapsAtStart = encoded >> 8;
  11870  result.mGapsAtEnd = encoded & 0xFF;
  11871  return result;
  11872 }
  11873 
  11874 uint32_t nsTextFrame::CountGraphemeClusters() const {
  11875  const auto& characterDataBuffer = CharacterDataBuffer();
  11876  nsAutoString content;
  11877  characterDataBuffer.AppendTo(content,
  11878                               AssertedCast<uint32_t>(GetContentOffset()),
  11879                               AssertedCast<uint32_t>(GetContentLength()));
  11880  return unicode::CountGraphemeClusters(content);
  11881 }
  11882 
  11883 bool nsTextFrame::HasNonSuppressedText() const {
  11884  if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
  11885                      // If we haven't reflowed yet, or are currently doing so,
  11886                      // just return true because we can't be sure.
  11887                      NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) {
  11888    return true;
  11889  }
  11890 
  11891  if (!GetTextRun(nsTextFrame::eInflated)) {
  11892    return false;
  11893  }
  11894 
  11895  TrimmedOffsets offsets =
  11896      GetTrimmedOffsets(CharacterDataBuffer(), TrimmedOffsetFlags::NoTrimAfter);
  11897  return offsets.mLength != 0;
  11898 }