mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	This is not part of the default word-segmentation rules in UAX#29 that are implemented by the ICU4X segmenter, but is suggested as an extension in a note following the rules, and corresponds with behavior seen in both Chrome and Safari (as well as in some editors/word processors). As such, I think it makes sense to do this. The included WPT fails without the patch, and passes with it. (It should also pass in both Safari and Chrome, afaict.) Differential Revision: https://phabricator.services.mozilla.com/D254847
		
			
				
	
	
		
			10999 lines
		
	
	
	
		
			421 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			10999 lines
		
	
	
	
		
			421 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | 
						||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | 
						||
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						||
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						||
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						||
 | 
						||
/* rendering object for textual content of elements */
 | 
						||
 | 
						||
#include "nsTextFrame.h"
 | 
						||
 | 
						||
#include "gfx2DGlue.h"
 | 
						||
 | 
						||
#include "gfxUtils.h"
 | 
						||
#include "mozilla/Attributes.h"
 | 
						||
#include "mozilla/CaretAssociationHint.h"
 | 
						||
#include "mozilla/ComputedStyle.h"
 | 
						||
#include "mozilla/DebugOnly.h"
 | 
						||
#include "mozilla/gfx/2D.h"
 | 
						||
#include "mozilla/Likely.h"
 | 
						||
#include "mozilla/MathAlgorithms.h"
 | 
						||
#include "mozilla/PresShell.h"
 | 
						||
#include "mozilla/StaticPrefs_layout.h"
 | 
						||
#include "mozilla/StaticPresData.h"
 | 
						||
#include "mozilla/SVGTextFrame.h"
 | 
						||
#include "mozilla/TextEditor.h"
 | 
						||
#include "mozilla/TextEvents.h"
 | 
						||
#include "mozilla/BinarySearch.h"
 | 
						||
#include "mozilla/IntegerRange.h"
 | 
						||
#include "mozilla/Unused.h"
 | 
						||
#include "mozilla/PodOperations.h"
 | 
						||
#include "mozilla/dom/PerformanceMainThread.h"
 | 
						||
 | 
						||
#include "nsCOMPtr.h"
 | 
						||
#include "nsBlockFrame.h"
 | 
						||
#include "nsFontMetrics.h"
 | 
						||
#include "nsSplittableFrame.h"
 | 
						||
#include "nsLineLayout.h"
 | 
						||
#include "nsString.h"
 | 
						||
#include "nsUnicharUtils.h"
 | 
						||
#include "nsPresContext.h"
 | 
						||
#include "nsIContent.h"
 | 
						||
#include "nsStyleConsts.h"
 | 
						||
#include "nsStyleStruct.h"
 | 
						||
#include "nsStyleStructInlines.h"
 | 
						||
#include "nsCoord.h"
 | 
						||
#include "gfxContext.h"
 | 
						||
#include "nsTArray.h"
 | 
						||
#include "nsCSSPseudoElements.h"
 | 
						||
#include "nsCSSFrameConstructor.h"
 | 
						||
#include "nsCompatibility.h"
 | 
						||
#include "nsCSSColorUtils.h"
 | 
						||
#include "nsLayoutUtils.h"
 | 
						||
#include "nsDisplayList.h"
 | 
						||
#include "nsIFrame.h"
 | 
						||
#include "nsIMathMLFrame.h"
 | 
						||
#include "nsFirstLetterFrame.h"
 | 
						||
#include "nsPlaceholderFrame.h"
 | 
						||
#include "nsTextFrameUtils.h"
 | 
						||
#include "nsTextPaintStyle.h"
 | 
						||
#include "nsTextRunTransformations.h"
 | 
						||
#include "MathMLTextRunFactory.h"
 | 
						||
#include "nsUnicodeProperties.h"
 | 
						||
#include "nsStyleUtil.h"
 | 
						||
#include "nsRubyFrame.h"
 | 
						||
#include "PresShellInlines.h"
 | 
						||
#include "TextDrawTarget.h"
 | 
						||
 | 
						||
#include "nsTextFragment.h"
 | 
						||
#include "nsGkAtoms.h"
 | 
						||
#include "nsFrameSelection.h"
 | 
						||
#include "nsRange.h"
 | 
						||
#include "nsCSSRendering.h"
 | 
						||
#include "nsContentUtils.h"
 | 
						||
#include "nsLineBreaker.h"
 | 
						||
#include "nsIFrameInlines.h"
 | 
						||
#include "mozilla/intl/Bidi.h"
 | 
						||
#include "mozilla/intl/Segmenter.h"
 | 
						||
#include "mozilla/intl/UnicodeProperties.h"
 | 
						||
#include "mozilla/ServoStyleSet.h"
 | 
						||
 | 
						||
#include <algorithm>
 | 
						||
#include <limits>
 | 
						||
#include <type_traits>
 | 
						||
#ifdef ACCESSIBILITY
 | 
						||
#  include "nsAccessibilityService.h"
 | 
						||
#endif
 | 
						||
 | 
						||
#include "nsPrintfCString.h"
 | 
						||
 | 
						||
#include "mozilla/gfx/DrawTargetRecording.h"
 | 
						||
 | 
						||
#include "mozilla/UniquePtr.h"
 | 
						||
#include "mozilla/dom/Element.h"
 | 
						||
#include "mozilla/LookAndFeel.h"
 | 
						||
#include "mozilla/ProfilerLabels.h"
 | 
						||
 | 
						||
#ifdef DEBUG
 | 
						||
#  undef NOISY_REFLOW
 | 
						||
#  undef NOISY_TRIM
 | 
						||
#else
 | 
						||
#  undef NOISY_REFLOW
 | 
						||
#  undef NOISY_TRIM
 | 
						||
#endif
 | 
						||
 | 
						||
#ifdef DrawText
 | 
						||
#  undef DrawText
 | 
						||
#endif
 | 
						||
 | 
						||
using namespace mozilla;
 | 
						||
using namespace mozilla::dom;
 | 
						||
using namespace mozilla::gfx;
 | 
						||
 | 
						||
typedef mozilla::layout::TextDrawTarget TextDrawTarget;
 | 
						||
 | 
						||
static bool NeedsToMaskPassword(nsTextFrame* aFrame) {
 | 
						||
  MOZ_ASSERT(aFrame);
 | 
						||
  MOZ_ASSERT(aFrame->GetContent());
 | 
						||
  if (!aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  nsIFrame* frame =
 | 
						||
      nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::TextInput);
 | 
						||
  MOZ_ASSERT(frame, "How do we have a masked text node without a text input?");
 | 
						||
  return !frame || !frame->GetContent()->AsElement()->State().HasState(
 | 
						||
                       ElementState::REVEALED);
 | 
						||
}
 | 
						||
 | 
						||
struct TabWidth {
 | 
						||
  TabWidth(uint32_t aOffset, uint32_t aWidth)
 | 
						||
      : mOffset(aOffset), mWidth(float(aWidth)) {}
 | 
						||
 | 
						||
  uint32_t mOffset;  // DOM offset relative to the current frame's offset.
 | 
						||
  float mWidth;      // extra space to be added at this position (in app units)
 | 
						||
};
 | 
						||
 | 
						||
struct nsTextFrame::TabWidthStore {
 | 
						||
  explicit TabWidthStore(int32_t aValidForContentOffset)
 | 
						||
      : mLimit(0), mValidForContentOffset(aValidForContentOffset) {}
 | 
						||
 | 
						||
  // Apply tab widths to the aSpacing array, which corresponds to characters
 | 
						||
  // beginning at aOffset and has length aLength. (Width records outside this
 | 
						||
  // range will be ignored.)
 | 
						||
  void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
 | 
						||
                    uint32_t aOffset, uint32_t aLength);
 | 
						||
 | 
						||
  // Offset up to which tabs have been measured; positions beyond this have not
 | 
						||
  // been calculated yet but may be appended if needed later.  It's a DOM
 | 
						||
  // offset relative to the current frame's offset.
 | 
						||
  uint32_t mLimit;
 | 
						||
 | 
						||
  // Need to recalc tab offsets if frame content offset differs from this.
 | 
						||
  int32_t mValidForContentOffset;
 | 
						||
 | 
						||
  // A TabWidth record for each tab character measured so far.
 | 
						||
  nsTArray<TabWidth> mWidths;
 | 
						||
};
 | 
						||
 | 
						||
namespace {
 | 
						||
 | 
						||
struct TabwidthAdaptor {
 | 
						||
  const nsTArray<TabWidth>& mWidths;
 | 
						||
  explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
 | 
						||
      : mWidths(aWidths) {}
 | 
						||
  uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
 | 
						||
};
 | 
						||
 | 
						||
}  // namespace
 | 
						||
 | 
						||
void nsTextFrame::TabWidthStore::ApplySpacing(
 | 
						||
    gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
 | 
						||
    uint32_t aLength) {
 | 
						||
  size_t i = 0;
 | 
						||
  const size_t len = mWidths.Length();
 | 
						||
 | 
						||
  // If aOffset is non-zero, do a binary search to find where to start
 | 
						||
  // processing the tab widths, in case the list is really long. (See bug
 | 
						||
  // 953247.)
 | 
						||
  // We need to start from the first entry where mOffset >= aOffset.
 | 
						||
  if (aOffset > 0) {
 | 
						||
    mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t limit = aOffset + aLength;
 | 
						||
  while (i < len) {
 | 
						||
    const TabWidth& tw = mWidths[i];
 | 
						||
    if (tw.mOffset >= limit) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
 | 
						||
    i++;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,
 | 
						||
                                    nsTextFrame::TabWidthStore)
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord)
 | 
						||
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty,
 | 
						||
                                      gfxTextRun::TrimmableWS)
 | 
						||
 | 
						||
struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams {
 | 
						||
  Point textBaselinePt;
 | 
						||
  PropertyProvider* provider = nullptr;
 | 
						||
  Range contentRange;
 | 
						||
  nsTextPaintStyle* textPaintStyle = nullptr;
 | 
						||
  Range glyphRange;
 | 
						||
  explicit PaintTextSelectionParams(const PaintTextParams& aParams)
 | 
						||
      : PaintTextParams(aParams) {}
 | 
						||
};
 | 
						||
 | 
						||
struct nsTextFrame::DrawTextRunParams {
 | 
						||
  gfxContext* context;
 | 
						||
  mozilla::gfx::PaletteCache& paletteCache;
 | 
						||
  PropertyProvider* provider = nullptr;
 | 
						||
  gfxFloat* advanceWidth = nullptr;
 | 
						||
  mozilla::SVGContextPaint* contextPaint = nullptr;
 | 
						||
  DrawPathCallbacks* callbacks = nullptr;
 | 
						||
  nscolor textColor = NS_RGBA(0, 0, 0, 0);
 | 
						||
  nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
 | 
						||
  nsAtom* fontPalette = nullptr;
 | 
						||
  float textStrokeWidth = 0.0f;
 | 
						||
  bool drawSoftHyphen = false;
 | 
						||
  bool hasTextShadow = false;
 | 
						||
  bool paintingShadows = false;
 | 
						||
  DrawTextRunParams(gfxContext* aContext,
 | 
						||
                    mozilla::gfx::PaletteCache& aPaletteCache)
 | 
						||
      : context(aContext), paletteCache(aPaletteCache) {}
 | 
						||
};
 | 
						||
 | 
						||
struct nsTextFrame::ClipEdges {
 | 
						||
  ClipEdges(const nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
 | 
						||
            nscoord aVisIStartEdge, nscoord aVisIEndEdge) {
 | 
						||
    nsRect r = aFrame->ScrollableOverflowRect() + aToReferenceFrame;
 | 
						||
    if (aFrame->GetWritingMode().IsVertical()) {
 | 
						||
      mVisIStart = aVisIStartEdge > 0 ? r.y + aVisIStartEdge : nscoord_MIN;
 | 
						||
      mVisIEnd = aVisIEndEdge > 0
 | 
						||
                     ? std::max(r.YMost() - aVisIEndEdge, mVisIStart)
 | 
						||
                     : nscoord_MAX;
 | 
						||
    } else {
 | 
						||
      mVisIStart = aVisIStartEdge > 0 ? r.x + aVisIStartEdge : nscoord_MIN;
 | 
						||
      mVisIEnd = aVisIEndEdge > 0
 | 
						||
                     ? std::max(r.XMost() - aVisIEndEdge, mVisIStart)
 | 
						||
                     : nscoord_MAX;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  void Intersect(nscoord* aVisIStart, nscoord* aVisISize) const {
 | 
						||
    nscoord end = *aVisIStart + *aVisISize;
 | 
						||
    *aVisIStart = std::max(*aVisIStart, mVisIStart);
 | 
						||
    *aVisISize = std::max(std::min(end, mVisIEnd) - *aVisIStart, 0);
 | 
						||
  }
 | 
						||
 | 
						||
  nscoord mVisIStart;
 | 
						||
  nscoord mVisIEnd;
 | 
						||
};
 | 
						||
 | 
						||
struct nsTextFrame::DrawTextParams : nsTextFrame::DrawTextRunParams {
 | 
						||
  Point framePt;
 | 
						||
  LayoutDeviceRect dirtyRect;
 | 
						||
  const nsTextPaintStyle* textStyle = nullptr;
 | 
						||
  const ClipEdges* clipEdges = nullptr;
 | 
						||
  const nscolor* decorationOverrideColor = nullptr;
 | 
						||
  Range glyphRange;
 | 
						||
  DrawTextParams(gfxContext* aContext,
 | 
						||
                 mozilla::gfx::PaletteCache& aPaletteCache)
 | 
						||
      : DrawTextRunParams(aContext, aPaletteCache) {}
 | 
						||
};
 | 
						||
 | 
						||
struct nsTextFrame::PaintShadowParams {
 | 
						||
  gfxTextRun::Range range;
 | 
						||
  LayoutDeviceRect dirtyRect;
 | 
						||
  Point framePt;
 | 
						||
  Point textBaselinePt;
 | 
						||
  gfxContext* context;
 | 
						||
  DrawPathCallbacks* callbacks = nullptr;
 | 
						||
  nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
 | 
						||
  const ClipEdges* clipEdges = nullptr;
 | 
						||
  PropertyProvider* provider = nullptr;
 | 
						||
  nscoord leftSideOffset = 0;
 | 
						||
  explicit PaintShadowParams(const PaintTextParams& aParams)
 | 
						||
      : dirtyRect(aParams.dirtyRect),
 | 
						||
        framePt(aParams.framePt),
 | 
						||
        context(aParams.context) {}
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * A glyph observer for the change of a font glyph in a text run.
 | 
						||
 *
 | 
						||
 * This is stored in {Simple, Complex}TextRunUserData.
 | 
						||
 */
 | 
						||
class GlyphObserver final : public gfxFont::GlyphChangeObserver {
 | 
						||
 public:
 | 
						||
  GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
 | 
						||
      : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
 | 
						||
    MOZ_ASSERT(aTextRun->GetUserData());
 | 
						||
  }
 | 
						||
  void NotifyGlyphsChanged() override;
 | 
						||
 | 
						||
 private:
 | 
						||
  gfxTextRun* mTextRun;
 | 
						||
};
 | 
						||
 | 
						||
static const nsFrameState TEXT_REFLOW_FLAGS =
 | 
						||
    TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE |
 | 
						||
    TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE |
 | 
						||
    TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS |
 | 
						||
    TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS;
 | 
						||
 | 
						||
static const nsFrameState TEXT_WHITESPACE_FLAGS =
 | 
						||
    TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE;
 | 
						||
 | 
						||
/*
 | 
						||
 * Some general notes
 | 
						||
 *
 | 
						||
 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
 | 
						||
 * transforms text to positioned glyphs. It can report the geometry of the
 | 
						||
 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
 | 
						||
 * spacing, language, and other information.
 | 
						||
 *
 | 
						||
 * A gfxTextRun can cover more than one DOM text node. This is necessary to
 | 
						||
 * get kerning, ligatures and shaping for text that spans multiple text nodes
 | 
						||
 * but is all the same font.
 | 
						||
 *
 | 
						||
 * The userdata for a gfxTextRun object can be:
 | 
						||
 *
 | 
						||
 *   - A nsTextFrame* in the case a text run maps to only one flow. In this
 | 
						||
 *   case, the textrun's user data pointer is a pointer to mStartFrame for that
 | 
						||
 *   flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
 | 
						||
 *   length of the text node.
 | 
						||
 *
 | 
						||
 *   - A SimpleTextRunUserData in the case a text run maps to one flow, but we
 | 
						||
 *   still have to keep a list of glyph observers.
 | 
						||
 *
 | 
						||
 *   - A ComplexTextRunUserData in the case a text run maps to multiple flows,
 | 
						||
 *   but we need to keep a list of glyph observers.
 | 
						||
 *
 | 
						||
 *   - A TextRunUserData in the case a text run maps multiple flows, but it
 | 
						||
 *   doesn't have any glyph observer for changes in SVG fonts.
 | 
						||
 *
 | 
						||
 * You can differentiate between the four different cases with the
 | 
						||
 * IsSimpleFlow and MightHaveGlyphChanges flags.
 | 
						||
 *
 | 
						||
 * We go to considerable effort to make sure things work even if in-flow
 | 
						||
 * siblings have different ComputedStyles (i.e., first-letter and first-line).
 | 
						||
 *
 | 
						||
 * Our convention is that unsigned integer character offsets are offsets into
 | 
						||
 * the transformed string. Signed integer character offsets are offsets into
 | 
						||
 * the DOM string.
 | 
						||
 *
 | 
						||
 * XXX currently we don't handle hyphenated breaks between text frames where the
 | 
						||
 * hyphen occurs at the end of the first text frame, e.g.
 | 
						||
 *   <b>Kit­</b>ty
 | 
						||
 */
 | 
						||
 | 
						||
/**
 | 
						||
 * This is our user data for the textrun, when textRun->GetFlags2() has
 | 
						||
 * IsSimpleFlow set, and also MightHaveGlyphChanges.
 | 
						||
 *
 | 
						||
 * This allows having an array of observers if there are fonts whose glyphs
 | 
						||
 * might change, but also avoid allocation in the simple case that there aren't.
 | 
						||
 */
 | 
						||
struct SimpleTextRunUserData {
 | 
						||
  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
 | 
						||
  nsTextFrame* mFrame;
 | 
						||
  explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * We use an array of these objects to record which text frames
 | 
						||
 * are associated with the textrun. mStartFrame is the start of a list of
 | 
						||
 * text frames. Some sequence of its continuations are covered by the textrun.
 | 
						||
 * A content textnode can have at most one TextRunMappedFlow associated with it
 | 
						||
 * for a given textrun.
 | 
						||
 *
 | 
						||
 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
 | 
						||
 * obtain the offset into the before-transformation text of the textrun. It can
 | 
						||
 * be positive (when a text node starts in the middle of a text run) or negative
 | 
						||
 * (when a text run starts in the middle of a text node). Of course it can also
 | 
						||
 * be zero.
 | 
						||
 */
 | 
						||
struct TextRunMappedFlow {
 | 
						||
  nsTextFrame* mStartFrame;
 | 
						||
  int32_t mDOMOffsetToBeforeTransformOffset;
 | 
						||
  // The text mapped starts at mStartFrame->GetContentOffset() and is this long
 | 
						||
  uint32_t mContentLength;
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * This is the type in the gfxTextRun's userdata field in the common case that
 | 
						||
 * the text run maps to multiple flows, but no fonts have been found with
 | 
						||
 * animatable glyphs.
 | 
						||
 *
 | 
						||
 * This way, we avoid allocating and constructing the extra nsTArray.
 | 
						||
 */
 | 
						||
struct TextRunUserData {
 | 
						||
#ifdef DEBUG
 | 
						||
  TextRunMappedFlow* mMappedFlows;
 | 
						||
#endif
 | 
						||
  uint32_t mMappedFlowCount;
 | 
						||
  uint32_t mLastFlowIndex;
 | 
						||
};
 | 
						||
 | 
						||
/**
 | 
						||
 * This is our user data for the textrun, when textRun->GetFlags2() does not
 | 
						||
 * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
 | 
						||
 */
 | 
						||
struct ComplexTextRunUserData : public TextRunUserData {
 | 
						||
  nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
 | 
						||
};
 | 
						||
 | 
						||
static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
 | 
						||
  TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
 | 
						||
      sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
 | 
						||
#ifdef DEBUG
 | 
						||
  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
 | 
						||
#endif
 | 
						||
  data->mMappedFlowCount = aMappedFlowCount;
 | 
						||
  data->mLastFlowIndex = 0;
 | 
						||
  return data;
 | 
						||
}
 | 
						||
 | 
						||
static void DestroyUserData(TextRunUserData* aUserData) {
 | 
						||
  if (aUserData) {
 | 
						||
    free(aUserData);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static ComplexTextRunUserData* CreateComplexUserData(
 | 
						||
    uint32_t aMappedFlowCount) {
 | 
						||
  ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
 | 
						||
      moz_xmalloc(sizeof(ComplexTextRunUserData) +
 | 
						||
                  aMappedFlowCount * sizeof(TextRunMappedFlow)));
 | 
						||
  new (data) ComplexTextRunUserData();
 | 
						||
#ifdef DEBUG
 | 
						||
  data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
 | 
						||
#endif
 | 
						||
  data->mMappedFlowCount = aMappedFlowCount;
 | 
						||
  data->mLastFlowIndex = 0;
 | 
						||
  return data;
 | 
						||
}
 | 
						||
 | 
						||
static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
 | 
						||
  if (aUserData) {
 | 
						||
    aUserData->~ComplexTextRunUserData();
 | 
						||
    free(aUserData);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
 | 
						||
  MOZ_ASSERT(aTextRun->GetUserData());
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    if (aTextRun->GetFlags2() &
 | 
						||
        nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
 | 
						||
      delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    if (aTextRun->GetFlags2() &
 | 
						||
        nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
 | 
						||
      DestroyComplexUserData(
 | 
						||
          static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
 | 
						||
    } else {
 | 
						||
      DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData()));
 | 
						||
    }
 | 
						||
  }
 | 
						||
  aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
 | 
						||
  aTextRun->SetUserData(nullptr);
 | 
						||
}
 | 
						||
 | 
						||
static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
 | 
						||
  MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
 | 
						||
  MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),
 | 
						||
             "The method should not be called for simple flows.");
 | 
						||
  TextRunMappedFlow* flows;
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
 | 
						||
    flows = reinterpret_cast<TextRunMappedFlow*>(
 | 
						||
        static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
 | 
						||
  } else {
 | 
						||
    flows = reinterpret_cast<TextRunMappedFlow*>(
 | 
						||
        static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
 | 
						||
  }
 | 
						||
  MOZ_ASSERT(
 | 
						||
      static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==
 | 
						||
          flows,
 | 
						||
      "GetMappedFlows should return the same pointer as mMappedFlows.");
 | 
						||
  return flows;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * These are utility functions just for helping with the complexity related with
 | 
						||
 * the text runs user data.
 | 
						||
 */
 | 
						||
static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
 | 
						||
  MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,
 | 
						||
             "Not so simple flow?");
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
 | 
						||
    return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  return static_cast<nsTextFrame*>(aTextRun->GetUserData());
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Remove |aTextRun| from the frame continuation chain starting at
 | 
						||
 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
 | 
						||
 * Unmark |aFrame| as a text run owner if it's the frame we start at.
 | 
						||
 * Return true if |aStartContinuation| is non-null and was found
 | 
						||
 * in the next-continuation chain of |aFrame|.
 | 
						||
 */
 | 
						||
static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
 | 
						||
                                      nsTextFrame* aStartContinuation,
 | 
						||
                                      nsFrameState aWhichTextRunState) {
 | 
						||
  MOZ_ASSERT(aFrame, "null frame");
 | 
						||
  MOZ_ASSERT(!aStartContinuation ||
 | 
						||
                 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
 | 
						||
                  aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==
 | 
						||
                      aTextRun) ||
 | 
						||
                 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
 | 
						||
                  aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==
 | 
						||
                      aTextRun),
 | 
						||
             "wrong aStartContinuation for this text run");
 | 
						||
 | 
						||
  if (!aStartContinuation || aStartContinuation == aFrame) {
 | 
						||
    aFrame->RemoveStateBits(aWhichTextRunState);
 | 
						||
  } else {
 | 
						||
    do {
 | 
						||
      NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
 | 
						||
      aFrame = aFrame->GetNextContinuation();
 | 
						||
    } while (aFrame && aFrame != aStartContinuation);
 | 
						||
  }
 | 
						||
  bool found = aStartContinuation == aFrame;
 | 
						||
  while (aFrame) {
 | 
						||
    NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
 | 
						||
    if (!aFrame->RemoveTextRun(aTextRun)) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    aFrame = aFrame->GetNextContinuation();
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
 | 
						||
  return found;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Kill all references to |aTextRun| starting at |aStartContinuation|.
 | 
						||
 * It could be referenced by any of its owners, and all their in-flows.
 | 
						||
 * If |aStartContinuation| is null then process all userdata frames
 | 
						||
 * and their continuations.
 | 
						||
 * @note the caller is expected to take care of possibly destroying the
 | 
						||
 * text run if all userdata frames were reset (userdata is deallocated
 | 
						||
 * by this function though). The caller can detect this has occured by
 | 
						||
 * checking |aTextRun->GetUserData() == nullptr|.
 | 
						||
 */
 | 
						||
static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
 | 
						||
                                    nsTextFrame* aStartContinuation) {
 | 
						||
  if (!aTextRun->GetUserData()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
 | 
						||
    nsFrameState whichTextRunState =
 | 
						||
        userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
 | 
						||
            ? TEXT_IN_TEXTRUN_USER_DATA
 | 
						||
            : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
 | 
						||
    DebugOnly<bool> found = ClearAllTextRunReferences(
 | 
						||
        userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
 | 
						||
    NS_ASSERTION(!aStartContinuation || found,
 | 
						||
                 "aStartContinuation wasn't found in simple flow text run");
 | 
						||
    if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
 | 
						||
      DestroyTextRunUserData(aTextRun);
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
 | 
						||
    TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
 | 
						||
    int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
 | 
						||
    for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
 | 
						||
      nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
 | 
						||
      nsFrameState whichTextRunState =
 | 
						||
          userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
 | 
						||
              ? TEXT_IN_TEXTRUN_USER_DATA
 | 
						||
              : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
 | 
						||
      bool found = ClearAllTextRunReferences(
 | 
						||
          userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
 | 
						||
      if (found) {
 | 
						||
        if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
 | 
						||
          destroyFromIndex = i + 1;
 | 
						||
        } else {
 | 
						||
          destroyFromIndex = i;
 | 
						||
        }
 | 
						||
        aStartContinuation = nullptr;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    NS_ASSERTION(destroyFromIndex >= 0,
 | 
						||
                 "aStartContinuation wasn't found in multi flow text run");
 | 
						||
    if (destroyFromIndex == 0) {
 | 
						||
      DestroyTextRunUserData(aTextRun);
 | 
						||
    } else {
 | 
						||
      userData->mMappedFlowCount = uint32_t(destroyFromIndex);
 | 
						||
      if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
 | 
						||
        userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
 | 
						||
  MOZ_ASSERT(aFrame);
 | 
						||
 | 
						||
  PresShell* presShell = aFrame->PresShell();
 | 
						||
  for (nsIFrame* f = aFrame; f;
 | 
						||
       f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
 | 
						||
    f->InvalidateFrame();
 | 
						||
 | 
						||
    // If this is a non-display text frame within SVG <text>, we need
 | 
						||
    // to reflow the SVGTextFrame. (This is similar to reflowing the
 | 
						||
    // SVGTextFrame in response to style changes, in
 | 
						||
    // SVGTextFrame::DidSetComputedStyle.)
 | 
						||
    if (f->IsInSVGTextSubtree() && f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
 | 
						||
      auto* svgTextFrame = static_cast<SVGTextFrame*>(
 | 
						||
          nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
 | 
						||
      svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::None);
 | 
						||
    } else {
 | 
						||
      // Theoretically we could just update overflow areas, perhaps using
 | 
						||
      // OverflowChangedTracker, but that would do a bunch of work eagerly that
 | 
						||
      // we should probably do lazily here since there could be a lot
 | 
						||
      // of text frames affected and we'd like to coalesce the work. So that's
 | 
						||
      // not easy to do well.
 | 
						||
      presShell->FrameNeedsReflow(f, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void GlyphObserver::NotifyGlyphsChanged() {
 | 
						||
  if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
 | 
						||
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
 | 
						||
  for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
 | 
						||
    InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
int32_t nsTextFrame::GetContentEnd() const {
 | 
						||
  nsTextFrame* next = GetNextContinuation();
 | 
						||
  // In case of allocation failure when setting/modifying the textfragment,
 | 
						||
  // it's possible our text might be missing. So we check the fragment length,
 | 
						||
  // in addition to the offset of the next continuation (if any).
 | 
						||
  int32_t fragLen = TextFragment()->GetLength();
 | 
						||
  return next ? std::min(fragLen, next->GetContentOffset()) : fragLen;
 | 
						||
}
 | 
						||
 | 
						||
struct FlowLengthProperty {
 | 
						||
  int32_t mStartOffset;
 | 
						||
  // The offset of the next fixed continuation after mStartOffset, or
 | 
						||
  // of the end of the text if there is none
 | 
						||
  int32_t mEndFlowOffset;
 | 
						||
};
 | 
						||
 | 
						||
int32_t nsTextFrame::GetInFlowContentLength() {
 | 
						||
  if (!HasAnyStateBits(NS_FRAME_IS_BIDI)) {
 | 
						||
    return mContent->TextLength() - mContentOffset;
 | 
						||
  }
 | 
						||
 | 
						||
  FlowLengthProperty* flowLength =
 | 
						||
      mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
 | 
						||
          ? static_cast<FlowLengthProperty*>(
 | 
						||
                mContent->GetProperty(nsGkAtoms::flowlength))
 | 
						||
          : nullptr;
 | 
						||
  MOZ_ASSERT(mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY) == !!flowLength,
 | 
						||
             "incorrect NS_HAS_FLOWLENGTH_PROPERTY flag");
 | 
						||
  /**
 | 
						||
   * This frame must start inside the cached flow. If the flow starts at
 | 
						||
   * mContentOffset but this frame is empty, logically it might be before the
 | 
						||
   * start of the cached flow.
 | 
						||
   */
 | 
						||
  if (flowLength &&
 | 
						||
      (flowLength->mStartOffset < mContentOffset ||
 | 
						||
       (flowLength->mStartOffset == mContentOffset &&
 | 
						||
        GetContentEnd() > mContentOffset)) &&
 | 
						||
      flowLength->mEndFlowOffset > mContentOffset) {
 | 
						||
#ifdef DEBUG
 | 
						||
    NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
 | 
						||
                 "frame crosses fixed continuation boundary");
 | 
						||
#endif
 | 
						||
    return flowLength->mEndFlowOffset - mContentOffset;
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
 | 
						||
  int32_t endFlow =
 | 
						||
      nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
 | 
						||
 | 
						||
  if (!flowLength) {
 | 
						||
    flowLength = new FlowLengthProperty;
 | 
						||
    if (NS_FAILED(mContent->SetProperty(
 | 
						||
            nsGkAtoms::flowlength, flowLength,
 | 
						||
            nsINode::DeleteProperty<FlowLengthProperty>))) {
 | 
						||
      delete flowLength;
 | 
						||
      flowLength = nullptr;
 | 
						||
    } else {
 | 
						||
      mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (flowLength) {
 | 
						||
    flowLength->mStartOffset = mContentOffset;
 | 
						||
    flowLength->mEndFlowOffset = endFlow;
 | 
						||
  }
 | 
						||
 | 
						||
  return endFlow - mContentOffset;
 | 
						||
}
 | 
						||
 | 
						||
// Smarter versions of dom::IsSpaceCharacter.
 | 
						||
// Unicode is really annoying; sometimes a space character isn't whitespace ---
 | 
						||
// when it combines with another character
 | 
						||
// So we have several versions of IsSpace for use in different contexts.
 | 
						||
 | 
						||
static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag,
 | 
						||
                                         uint32_t aPos) {
 | 
						||
  NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
 | 
						||
  if (!aFrag->Is2b()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
 | 
						||
      aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
 | 
						||
}
 | 
						||
 | 
						||
// Check whether aPos is a space for CSS 'word-spacing' purposes
 | 
						||
static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
 | 
						||
                                  const nsTextFrame* aFrame,
 | 
						||
                                  const nsStyleText* aStyleText) {
 | 
						||
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
 | 
						||
 | 
						||
  char16_t ch = aFrag->CharAt(aPos);
 | 
						||
  switch (ch) {
 | 
						||
    case ' ':
 | 
						||
    case CH_NBSP:
 | 
						||
      return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
 | 
						||
    case '\r':
 | 
						||
    case '\t':
 | 
						||
      return !aStyleText->WhiteSpaceIsSignificant();
 | 
						||
    case '\n':
 | 
						||
      return !aStyleText->NewlineIsSignificant(aFrame);
 | 
						||
    default:
 | 
						||
      return false;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
constexpr char16_t kOghamSpaceMark = 0x1680;
 | 
						||
 | 
						||
// Check whether the string aChars/aLength starts with space that's
 | 
						||
// trimmable according to CSS 'white-space:normal/nowrap'.
 | 
						||
static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
 | 
						||
  NS_ASSERTION(aLength > 0, "No text for IsSpace!");
 | 
						||
 | 
						||
  char16_t ch = *aChars;
 | 
						||
  if (ch == ' ' || ch == kOghamSpaceMark) {
 | 
						||
    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
 | 
						||
                                                           aLength - 1);
 | 
						||
  }
 | 
						||
  return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
 | 
						||
}
 | 
						||
 | 
						||
// Check whether the character aCh is trimmable according to CSS
 | 
						||
// 'white-space:normal/nowrap'
 | 
						||
static bool IsTrimmableSpace(char aCh) {
 | 
						||
  return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
 | 
						||
}
 | 
						||
 | 
						||
static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
 | 
						||
                             const nsStyleText* aStyleText,
 | 
						||
                             bool aAllowHangingWS = false) {
 | 
						||
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
 | 
						||
 | 
						||
  switch (aFrag->CharAt(aPos)) {
 | 
						||
    case ' ':
 | 
						||
    case kOghamSpaceMark:
 | 
						||
      return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
 | 
						||
             !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
 | 
						||
    case '\n':
 | 
						||
      return !aStyleText->NewlineIsSignificantStyle() &&
 | 
						||
             aStyleText->mWhiteSpaceCollapse !=
 | 
						||
                 StyleWhiteSpaceCollapse::PreserveSpaces;
 | 
						||
    case '\t':
 | 
						||
    case '\r':
 | 
						||
    case '\f':
 | 
						||
      return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
 | 
						||
    default:
 | 
						||
      return false;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static bool IsSelectionInlineWhitespace(const nsTextFragment* aFrag,
 | 
						||
                                        uint32_t aPos) {
 | 
						||
  NS_ASSERTION(aPos < aFrag->GetLength(),
 | 
						||
               "No text for IsSelectionInlineWhitespace!");
 | 
						||
  char16_t ch = aFrag->CharAt(aPos);
 | 
						||
  if (ch == ' ' || ch == CH_NBSP) {
 | 
						||
    return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
 | 
						||
  }
 | 
						||
  return ch == '\t' || ch == '\f';
 | 
						||
}
 | 
						||
 | 
						||
static bool IsSelectionNewline(const nsTextFragment* aFrag, uint32_t aPos) {
 | 
						||
  NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSelectionNewline!");
 | 
						||
  char16_t ch = aFrag->CharAt(aPos);
 | 
						||
  return ch == '\n' || ch == '\r';
 | 
						||
}
 | 
						||
 | 
						||
// Count the amount of trimmable whitespace (as per CSS
 | 
						||
// 'white-space:normal/nowrap') in a text fragment. The first
 | 
						||
// character is at offset aStartOffset; the maximum number of characters
 | 
						||
// to check is aLength. aDirection is -1 or 1 depending on whether we should
 | 
						||
// progress backwards or forwards.
 | 
						||
static uint32_t GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
 | 
						||
                                            int32_t aStartOffset,
 | 
						||
                                            int32_t aLength,
 | 
						||
                                            int32_t aDirection) {
 | 
						||
  if (!aLength) {
 | 
						||
    return 0;
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t count = 0;
 | 
						||
  if (aFrag->Is2b()) {
 | 
						||
    const char16_t* str = aFrag->Get2b() + aStartOffset;
 | 
						||
    int32_t fragLen = aFrag->GetLength() - aStartOffset;
 | 
						||
    for (; count < aLength; ++count) {
 | 
						||
      if (!IsTrimmableSpace(str, fragLen)) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      str += aDirection;
 | 
						||
      fragLen -= aDirection;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    const char* str = aFrag->Get1b() + aStartOffset;
 | 
						||
    for (; count < aLength; ++count) {
 | 
						||
      if (!IsTrimmableSpace(*str)) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      str += aDirection;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return count;
 | 
						||
}
 | 
						||
 | 
						||
static bool IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) {
 | 
						||
  if (aFrag->Is2b()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  int32_t len = aFrag->GetLength();
 | 
						||
  const char* str = aFrag->Get1b();
 | 
						||
  for (int32_t i = 0; i < len; ++i) {
 | 
						||
    char ch = str[i];
 | 
						||
    if (ch == ' ' || ch == '\t' || ch == '\r' ||
 | 
						||
        (ch == '\n' && aAllowNewline)) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
 | 
						||
  if (!(aTextRun->GetFlags2() &
 | 
						||
        nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
 | 
						||
        ->mGlyphObservers.Clear();
 | 
						||
  } else {
 | 
						||
    static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
 | 
						||
        ->mGlyphObservers.Clear();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
 | 
						||
  if (!aTextRun->GetUserData()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  ClearObserversFromTextRun(aTextRun);
 | 
						||
 | 
						||
  nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
 | 
						||
  uint32_t numGlyphRuns;
 | 
						||
  const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns);
 | 
						||
  for (uint32_t i = 0; i < numGlyphRuns; ++i) {
 | 
						||
    gfxFont* font = glyphRuns[i].mFont;
 | 
						||
    if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
 | 
						||
      fontsWithAnimatedGlyphs.AppendElement(font);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (fontsWithAnimatedGlyphs.IsEmpty()) {
 | 
						||
    // NB: Theoretically, we should clear the MightHaveGlyphChanges
 | 
						||
    // here. That would involve de-allocating the simple user data struct if
 | 
						||
    // present too, and resetting the pointer to the frame. In practice, I
 | 
						||
    // don't think worth doing that work here, given the flag's only purpose is
 | 
						||
    // to distinguish what kind of user data is there.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  nsTArray<UniquePtr<GlyphObserver>>* observers;
 | 
						||
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
 | 
						||
    // appropriate.
 | 
						||
    if (!(aTextRun->GetFlags2() &
 | 
						||
          nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
 | 
						||
      auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
 | 
						||
      aTextRun->SetUserData(new SimpleTextRunUserData(frame));
 | 
						||
    }
 | 
						||
 | 
						||
    auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
 | 
						||
    observers = &data->mGlyphObservers;
 | 
						||
  } else {
 | 
						||
    if (!(aTextRun->GetFlags2() &
 | 
						||
          nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
 | 
						||
      auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
 | 
						||
      TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
 | 
						||
      ComplexTextRunUserData* data =
 | 
						||
          CreateComplexUserData(oldData->mMappedFlowCount);
 | 
						||
      TextRunMappedFlow* dataMappedFlows =
 | 
						||
          reinterpret_cast<TextRunMappedFlow*>(data + 1);
 | 
						||
      data->mLastFlowIndex = oldData->mLastFlowIndex;
 | 
						||
      for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
 | 
						||
        dataMappedFlows[i] = oldMappedFlows[i];
 | 
						||
      }
 | 
						||
      DestroyUserData(oldData);
 | 
						||
      aTextRun->SetUserData(data);
 | 
						||
    }
 | 
						||
    auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
 | 
						||
    observers = &data->mGlyphObservers;
 | 
						||
  }
 | 
						||
 | 
						||
  aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
 | 
						||
 | 
						||
  observers->SetCapacity(observers->Length() +
 | 
						||
                         fontsWithAnimatedGlyphs.Length());
 | 
						||
  for (auto font : fontsWithAnimatedGlyphs) {
 | 
						||
    observers->AppendElement(MakeUnique<GlyphObserver>(font, aTextRun));
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * This class accumulates state as we scan a paragraph of text. It detects
 | 
						||
 * textrun boundaries (changes from text to non-text, hard
 | 
						||
 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
 | 
						||
 * It also detects linebreaker run boundaries (changes from text to non-text,
 | 
						||
 * and hard line breaks) and at each boundary runs the linebreaker to compute
 | 
						||
 * potential line breaks. It also records actual line breaks to store them in
 | 
						||
 * the textruns.
 | 
						||
 */
 | 
						||
class BuildTextRunsScanner {
 | 
						||
 public:
 | 
						||
  BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
 | 
						||
                       nsIFrame* aLineContainer,
 | 
						||
                       nsTextFrame::TextRunType aWhichTextRun,
 | 
						||
                       bool aDoLineBreaking)
 | 
						||
      : mDrawTarget(aDrawTarget),
 | 
						||
        mLineContainer(aLineContainer),
 | 
						||
        mCommonAncestorWithLastFrame(nullptr),
 | 
						||
        mMissingFonts(aPresContext->MissingFontRecorder()),
 | 
						||
        mBidiEnabled(aPresContext->BidiEnabled()),
 | 
						||
        mStartOfLine(true),
 | 
						||
        mSkipIncompleteTextRuns(false),
 | 
						||
        mCanStopOnThisLine(false),
 | 
						||
        mDoLineBreaking(aDoLineBreaking),
 | 
						||
        mWhichTextRun(aWhichTextRun),
 | 
						||
        mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
 | 
						||
        mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
 | 
						||
    ResetRunInfo();
 | 
						||
  }
 | 
						||
  ~BuildTextRunsScanner() {
 | 
						||
    NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
 | 
						||
    NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
 | 
						||
    NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
 | 
						||
  }
 | 
						||
 | 
						||
  void SetAtStartOfLine() {
 | 
						||
    mStartOfLine = true;
 | 
						||
    mCanStopOnThisLine = false;
 | 
						||
  }
 | 
						||
  void SetSkipIncompleteTextRuns(bool aSkip) {
 | 
						||
    mSkipIncompleteTextRuns = aSkip;
 | 
						||
  }
 | 
						||
  void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
 | 
						||
    mCommonAncestorWithLastFrame = aFrame;
 | 
						||
  }
 | 
						||
  bool CanStopOnThisLine() { return mCanStopOnThisLine; }
 | 
						||
  nsIFrame* GetCommonAncestorWithLastFrame() {
 | 
						||
    return mCommonAncestorWithLastFrame;
 | 
						||
  }
 | 
						||
  void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
 | 
						||
    if (mCommonAncestorWithLastFrame &&
 | 
						||
        mCommonAncestorWithLastFrame->GetParent() == aFrame) {
 | 
						||
      mCommonAncestorWithLastFrame = aFrame;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  void ScanFrame(nsIFrame* aFrame);
 | 
						||
  bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
 | 
						||
  void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
 | 
						||
  void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
 | 
						||
  void ResetRunInfo() {
 | 
						||
    mLastFrame = nullptr;
 | 
						||
    mMappedFlows.Clear();
 | 
						||
    mLineBreakBeforeFrames.Clear();
 | 
						||
    mMaxTextLength = 0;
 | 
						||
    mDoubleByteText = false;
 | 
						||
  }
 | 
						||
  void AccumulateRunInfo(nsTextFrame* aFrame);
 | 
						||
  /**
 | 
						||
   * @return null to indicate either textrun construction failed or
 | 
						||
   * we constructed just a partial textrun to set up linebreaker and other
 | 
						||
   * state for following textruns.
 | 
						||
   */
 | 
						||
  already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
 | 
						||
  bool SetupLineBreakerContext(gfxTextRun* aTextRun);
 | 
						||
  void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
 | 
						||
  nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
 | 
						||
  void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
 | 
						||
  void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
 | 
						||
  struct FindBoundaryState {
 | 
						||
    nsIFrame* mStopAtFrame;
 | 
						||
    nsTextFrame* mFirstTextFrame;
 | 
						||
    nsTextFrame* mLastTextFrame;
 | 
						||
    bool mSeenTextRunBoundaryOnLaterLine;
 | 
						||
    bool mSeenTextRunBoundaryOnThisLine;
 | 
						||
    bool mSeenSpaceForLineBreakingOnThisLine;
 | 
						||
    nsTArray<char16_t>& mBuffer;
 | 
						||
  };
 | 
						||
  enum FindBoundaryResult {
 | 
						||
    FB_CONTINUE,
 | 
						||
    FB_STOPPED_AT_STOP_FRAME,
 | 
						||
    FB_FOUND_VALID_TEXTRUN_BOUNDARY
 | 
						||
  };
 | 
						||
  FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
 | 
						||
                                    FindBoundaryState* aState);
 | 
						||
 | 
						||
  bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
 | 
						||
 | 
						||
  // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
 | 
						||
  // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
 | 
						||
  // continuations starting from mStartFrame are a sequence of in-flow frames).
 | 
						||
  struct MappedFlow {
 | 
						||
    nsTextFrame* mStartFrame;
 | 
						||
    nsTextFrame* mEndFrame;
 | 
						||
    // When we consider breaking between elements, the nearest common
 | 
						||
    // ancestor of the elements containing the characters is the one whose
 | 
						||
    // CSS 'white-space' property governs. So this records the nearest common
 | 
						||
    // ancestor of mStartFrame and the previous text frame, or null if there
 | 
						||
    // was no previous text frame on this line.
 | 
						||
    nsIFrame* mAncestorControllingInitialBreak;
 | 
						||
 | 
						||
    int32_t GetContentEnd() const {
 | 
						||
      int32_t fragLen = mStartFrame->TextFragment()->GetLength();
 | 
						||
      return mEndFrame ? std::min(fragLen, mEndFrame->GetContentOffset())
 | 
						||
                       : fragLen;
 | 
						||
    }
 | 
						||
  };
 | 
						||
 | 
						||
  class BreakSink final : public nsILineBreakSink {
 | 
						||
   public:
 | 
						||
    BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
 | 
						||
              uint32_t aOffsetIntoTextRun)
 | 
						||
        : mTextRun(aTextRun),
 | 
						||
          mDrawTarget(aDrawTarget),
 | 
						||
          mOffsetIntoTextRun(aOffsetIntoTextRun) {}
 | 
						||
 | 
						||
    void SetBreaks(uint32_t aOffset, uint32_t aLength,
 | 
						||
                   uint8_t* aBreakBefore) final {
 | 
						||
      gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
 | 
						||
                              aOffset + mOffsetIntoTextRun + aLength);
 | 
						||
      if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
 | 
						||
        // Be conservative and assume that some breaks have been set
 | 
						||
        mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    void SetCapitalization(uint32_t aOffset, uint32_t aLength,
 | 
						||
                           bool* aCapitalize) final {
 | 
						||
      MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,
 | 
						||
                 "Text run should be transformed!");
 | 
						||
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
 | 
						||
        nsTransformedTextRun* transformedTextRun =
 | 
						||
            static_cast<nsTransformedTextRun*>(mTextRun.get());
 | 
						||
        transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
 | 
						||
                                              aLength, aCapitalize);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    void Finish(gfxMissingFontRecorder* aMFR) {
 | 
						||
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
 | 
						||
        nsTransformedTextRun* transformedTextRun =
 | 
						||
            static_cast<nsTransformedTextRun*>(mTextRun.get());
 | 
						||
        transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
 | 
						||
      }
 | 
						||
      // The way nsTransformedTextRun is implemented, its glyph runs aren't
 | 
						||
      // available until after nsTransformedTextRun::FinishSettingProperties()
 | 
						||
      // is called. So that's why we defer checking for animated glyphs to here.
 | 
						||
      CreateObserversForAnimatedGlyphs(mTextRun);
 | 
						||
    }
 | 
						||
 | 
						||
    RefPtr<gfxTextRun> mTextRun;
 | 
						||
    DrawTarget* mDrawTarget;
 | 
						||
    uint32_t mOffsetIntoTextRun;
 | 
						||
  };
 | 
						||
 | 
						||
 private:
 | 
						||
  AutoTArray<MappedFlow, 10> mMappedFlows;
 | 
						||
  AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
 | 
						||
  AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
 | 
						||
  nsLineBreaker mLineBreaker;
 | 
						||
  RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
 | 
						||
  DrawTarget* mDrawTarget;
 | 
						||
  nsIFrame* mLineContainer;
 | 
						||
  nsTextFrame* mLastFrame;
 | 
						||
  // The common ancestor of the current frame and the previous leaf frame
 | 
						||
  // on the line, or null if there was no previous leaf frame.
 | 
						||
  nsIFrame* mCommonAncestorWithLastFrame;
 | 
						||
  gfxMissingFontRecorder* mMissingFonts;
 | 
						||
  // mMaxTextLength is an upper bound on the size of the text in all mapped
 | 
						||
  // frames The value UINT32_MAX represents overflow; text will be discarded
 | 
						||
  uint32_t mMaxTextLength;
 | 
						||
  bool mDoubleByteText;
 | 
						||
  bool mBidiEnabled;
 | 
						||
  bool mStartOfLine;
 | 
						||
  bool mSkipIncompleteTextRuns;
 | 
						||
  bool mCanStopOnThisLine;
 | 
						||
  bool mDoLineBreaking;
 | 
						||
  nsTextFrame::TextRunType mWhichTextRun;
 | 
						||
  uint8_t mNextRunContextInfo;
 | 
						||
  uint8_t mCurrentRunContextInfo;
 | 
						||
};
 | 
						||
 | 
						||
static const nsIFrame* FindLineContainer(const nsIFrame* aFrame) {
 | 
						||
  while (aFrame &&
 | 
						||
         (aFrame->IsLineParticipant() || aFrame->CanContinueTextRun())) {
 | 
						||
    aFrame = aFrame->GetParent();
 | 
						||
  }
 | 
						||
  return aFrame;
 | 
						||
}
 | 
						||
 | 
						||
static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
 | 
						||
  return const_cast<nsIFrame*>(
 | 
						||
      FindLineContainer(const_cast<const nsIFrame*>(aFrame)));
 | 
						||
}
 | 
						||
 | 
						||
static bool IsLineBreakingWhiteSpace(char16_t aChar) {
 | 
						||
  // 0x0A (\n) is not handled as white-space by the line breaker, since
 | 
						||
  // we break before it, if it isn't transformed to a normal space.
 | 
						||
  // (If we treat it as normal white-space then we'd only break after it.)
 | 
						||
  // However, it does induce a line break or is converted to a regular
 | 
						||
  // space, and either way it can be used to bound the region of text
 | 
						||
  // that needs to be analyzed for line breaking.
 | 
						||
  return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
 | 
						||
}
 | 
						||
 | 
						||
static bool TextContainsLineBreakerWhiteSpace(const void* aText,
 | 
						||
                                              uint32_t aLength,
 | 
						||
                                              bool aIsDoubleByte) {
 | 
						||
  if (aIsDoubleByte) {
 | 
						||
    const char16_t* chars = static_cast<const char16_t*>(aText);
 | 
						||
    for (uint32_t i = 0; i < aLength; ++i) {
 | 
						||
      if (IsLineBreakingWhiteSpace(chars[i])) {
 | 
						||
        return true;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return false;
 | 
						||
  } else {
 | 
						||
    const uint8_t* chars = static_cast<const uint8_t*>(aText);
 | 
						||
    for (uint32_t i = 0; i < aLength; ++i) {
 | 
						||
      if (IsLineBreakingWhiteSpace(chars[i])) {
 | 
						||
        return true;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
 | 
						||
    nsTextFrame* aFrame, const nsStyleText* aStyleText) {
 | 
						||
  switch (aStyleText->mWhiteSpaceCollapse) {
 | 
						||
    case StyleWhiteSpaceCollapse::Collapse:
 | 
						||
      return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
 | 
						||
    case StyleWhiteSpaceCollapse::PreserveBreaks:
 | 
						||
      return nsTextFrameUtils::COMPRESS_WHITESPACE;
 | 
						||
    case StyleWhiteSpaceCollapse::Preserve:
 | 
						||
    case StyleWhiteSpaceCollapse::PreserveSpaces:
 | 
						||
    case StyleWhiteSpaceCollapse::BreakSpaces:
 | 
						||
      if (!aStyleText->NewlineIsSignificant(aFrame)) {
 | 
						||
        // If newline is set to be preserved, but then suppressed,
 | 
						||
        // transform newline to space.
 | 
						||
        return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
 | 
						||
      }
 | 
						||
      return nsTextFrameUtils::COMPRESS_NONE;
 | 
						||
  }
 | 
						||
  MOZ_ASSERT_UNREACHABLE("Unknown white-space-collapse value");
 | 
						||
  return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
 | 
						||
}
 | 
						||
 | 
						||
struct FrameTextTraversal {
 | 
						||
  FrameTextTraversal()
 | 
						||
      : mFrameToScan(nullptr),
 | 
						||
        mOverflowFrameToScan(nullptr),
 | 
						||
        mScanSiblings(false),
 | 
						||
        mLineBreakerCanCrossFrameBoundary(false),
 | 
						||
        mTextRunCanCrossFrameBoundary(false) {}
 | 
						||
 | 
						||
  // These fields identify which frames should be recursively scanned
 | 
						||
  // The first normal frame to scan (or null, if no such frame should be
 | 
						||
  // scanned)
 | 
						||
  nsIFrame* mFrameToScan;
 | 
						||
  // The first overflow frame to scan (or null, if no such frame should be
 | 
						||
  // scanned)
 | 
						||
  nsIFrame* mOverflowFrameToScan;
 | 
						||
  // Whether to scan the siblings of
 | 
						||
  // mFrameToDescendInto/mOverflowFrameToDescendInto
 | 
						||
  bool mScanSiblings;
 | 
						||
 | 
						||
  // These identify the boundaries of the context required for
 | 
						||
  // line breaking or textrun construction
 | 
						||
  bool mLineBreakerCanCrossFrameBoundary;
 | 
						||
  bool mTextRunCanCrossFrameBoundary;
 | 
						||
 | 
						||
  nsIFrame* NextFrameToScan() {
 | 
						||
    nsIFrame* f;
 | 
						||
    if (mFrameToScan) {
 | 
						||
      f = mFrameToScan;
 | 
						||
      mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
 | 
						||
    } else if (mOverflowFrameToScan) {
 | 
						||
      f = mOverflowFrameToScan;
 | 
						||
      mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
 | 
						||
    } else {
 | 
						||
      f = nullptr;
 | 
						||
    }
 | 
						||
    return f;
 | 
						||
  }
 | 
						||
};
 | 
						||
 | 
						||
static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
 | 
						||
  FrameTextTraversal result;
 | 
						||
 | 
						||
  bool continuesTextRun = aFrame->CanContinueTextRun();
 | 
						||
  if (aFrame->IsPlaceholderFrame()) {
 | 
						||
    // placeholders are "invisible", so a text run should be able to span
 | 
						||
    // across one. But don't descend into the out-of-flow.
 | 
						||
    result.mLineBreakerCanCrossFrameBoundary = true;
 | 
						||
    if (continuesTextRun) {
 | 
						||
      // ... Except for first-letter floats, which are really in-flow
 | 
						||
      // from the point of view of capitalization etc, so we'd better
 | 
						||
      // descend into them. But we actually need to break the textrun for
 | 
						||
      // first-letter floats since things look bad if, say, we try to make a
 | 
						||
      // ligature across the float boundary.
 | 
						||
      result.mFrameToScan =
 | 
						||
          (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
 | 
						||
    } else {
 | 
						||
      result.mTextRunCanCrossFrameBoundary = true;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    if (continuesTextRun) {
 | 
						||
      result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
 | 
						||
      result.mOverflowFrameToScan =
 | 
						||
          aFrame->GetChildList(FrameChildListID::Overflow).FirstChild();
 | 
						||
      NS_WARNING_ASSERTION(
 | 
						||
          !result.mOverflowFrameToScan,
 | 
						||
          "Scanning overflow inline frames is something we should avoid");
 | 
						||
      result.mScanSiblings = true;
 | 
						||
      result.mTextRunCanCrossFrameBoundary = true;
 | 
						||
      result.mLineBreakerCanCrossFrameBoundary = true;
 | 
						||
    } else {
 | 
						||
      MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
 | 
						||
                 "Shouldn't call this method for ruby text container");
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
 | 
						||
    nsIFrame* aFrame, FindBoundaryState* aState) {
 | 
						||
  LayoutFrameType frameType = aFrame->Type();
 | 
						||
  if (frameType == LayoutFrameType::RubyTextContainer) {
 | 
						||
    // Don't stop a text run for ruby text container. We want ruby text
 | 
						||
    // containers to be skipped, but continue the text run across them.
 | 
						||
    return FB_CONTINUE;
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* textFrame = frameType == LayoutFrameType::Text
 | 
						||
                               ? static_cast<nsTextFrame*>(aFrame)
 | 
						||
                               : nullptr;
 | 
						||
  if (textFrame) {
 | 
						||
    if (aState->mLastTextFrame &&
 | 
						||
        textFrame != aState->mLastTextFrame->GetNextInFlow() &&
 | 
						||
        !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
 | 
						||
      aState->mSeenTextRunBoundaryOnThisLine = true;
 | 
						||
      if (aState->mSeenSpaceForLineBreakingOnThisLine) {
 | 
						||
        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    if (!aState->mFirstTextFrame) {
 | 
						||
      aState->mFirstTextFrame = textFrame;
 | 
						||
    }
 | 
						||
    aState->mLastTextFrame = textFrame;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aFrame == aState->mStopAtFrame) {
 | 
						||
    return FB_STOPPED_AT_STOP_FRAME;
 | 
						||
  }
 | 
						||
 | 
						||
  if (textFrame) {
 | 
						||
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
 | 
						||
      return FB_CONTINUE;
 | 
						||
    }
 | 
						||
    const nsTextFragment* frag = textFrame->TextFragment();
 | 
						||
    uint32_t start = textFrame->GetContentOffset();
 | 
						||
    uint32_t length = textFrame->GetContentLength();
 | 
						||
    const void* text;
 | 
						||
    const nsAtom* language = textFrame->StyleFont()->mLanguage;
 | 
						||
    if (frag->Is2b()) {
 | 
						||
      // It is possible that we may end up removing all whitespace in
 | 
						||
      // a piece of text because of The White Space Processing Rules,
 | 
						||
      // so we need to transform it before we can check existence of
 | 
						||
      // such whitespaces.
 | 
						||
      aState->mBuffer.EnsureLengthAtLeast(length);
 | 
						||
      nsTextFrameUtils::CompressionMode compression =
 | 
						||
          GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
 | 
						||
      uint8_t incomingFlags = 0;
 | 
						||
      gfxSkipChars skipChars;
 | 
						||
      nsTextFrameUtils::Flags analysisFlags;
 | 
						||
      char16_t* bufStart = aState->mBuffer.Elements();
 | 
						||
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
 | 
						||
          frag->Get2b() + start, length, bufStart, compression, &incomingFlags,
 | 
						||
          &skipChars, &analysisFlags, language);
 | 
						||
      text = bufStart;
 | 
						||
      length = bufEnd - bufStart;
 | 
						||
    } else {
 | 
						||
      // If the text only contains ASCII characters, it is currently
 | 
						||
      // impossible that TransformText would remove all whitespaces,
 | 
						||
      // and thus the check below should return the same result for
 | 
						||
      // transformed text and original text. So we don't need to try
 | 
						||
      // transforming it here.
 | 
						||
      text = static_cast<const void*>(frag->Get1b() + start);
 | 
						||
    }
 | 
						||
    if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
 | 
						||
      aState->mSeenSpaceForLineBreakingOnThisLine = true;
 | 
						||
      if (aState->mSeenTextRunBoundaryOnLaterLine) {
 | 
						||
        return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return FB_CONTINUE;
 | 
						||
  }
 | 
						||
 | 
						||
  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
 | 
						||
  if (!traversal.mTextRunCanCrossFrameBoundary) {
 | 
						||
    aState->mSeenTextRunBoundaryOnThisLine = true;
 | 
						||
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
 | 
						||
      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  for (nsIFrame* f = traversal.NextFrameToScan(); f;
 | 
						||
       f = traversal.NextFrameToScan()) {
 | 
						||
    FindBoundaryResult result = FindBoundaries(f, aState);
 | 
						||
    if (result != FB_CONTINUE) {
 | 
						||
      return result;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (!traversal.mTextRunCanCrossFrameBoundary) {
 | 
						||
    aState->mSeenTextRunBoundaryOnThisLine = true;
 | 
						||
    if (aState->mSeenSpaceForLineBreakingOnThisLine) {
 | 
						||
      return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return FB_CONTINUE;
 | 
						||
}
 | 
						||
 | 
						||
// build text runs for the 200 lines following aForFrame, and stop after that
 | 
						||
// when we get a chance.
 | 
						||
#define NUM_LINES_TO_BUILD_TEXT_RUNS 200
 | 
						||
 | 
						||
/**
 | 
						||
 * General routine for building text runs. This is hairy because of the need
 | 
						||
 * to build text runs that span content nodes.
 | 
						||
 *
 | 
						||
 * @param aContext The gfxContext we're using to construct this text run.
 | 
						||
 * @param aForFrame The nsTextFrame for which we're building this text run.
 | 
						||
 * @param aLineContainer the line container containing aForFrame; if null,
 | 
						||
 *        we'll walk the ancestors to find it.  It's required to be non-null
 | 
						||
 *        when aForFrameLine is non-null.
 | 
						||
 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
 | 
						||
 *        out the line (slowly)
 | 
						||
 * @param aWhichTextRun The type of text run we want to build. If font inflation
 | 
						||
 *        is enabled, this will be eInflated, otherwise it's eNotInflated.
 | 
						||
 */
 | 
						||
static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
 | 
						||
                          nsIFrame* aLineContainer,
 | 
						||
                          const nsLineList::iterator* aForFrameLine,
 | 
						||
                          nsTextFrame::TextRunType aWhichTextRun) {
 | 
						||
  MOZ_ASSERT(aForFrame, "for no frame?");
 | 
						||
  NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container");
 | 
						||
 | 
						||
  nsIFrame* lineContainerChild = aForFrame;
 | 
						||
  if (!aLineContainer) {
 | 
						||
    if (aForFrame->IsFloatingFirstLetterChild()) {
 | 
						||
      lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
 | 
						||
    }
 | 
						||
    aLineContainer = FindLineContainer(lineContainerChild);
 | 
						||
  } else {
 | 
						||
    NS_ASSERTION(
 | 
						||
        (aLineContainer == FindLineContainer(aForFrame) ||
 | 
						||
         (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),
 | 
						||
        "Wrong line container hint");
 | 
						||
  }
 | 
						||
 | 
						||
  if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
 | 
						||
    aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
 | 
						||
    if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
 | 
						||
      aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
 | 
						||
    aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
 | 
						||
  }
 | 
						||
 | 
						||
  nsPresContext* presContext = aLineContainer->PresContext();
 | 
						||
  bool doLineBreaking = !aForFrame->IsInSVGTextSubtree();
 | 
						||
  BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
 | 
						||
                               aWhichTextRun, doLineBreaking);
 | 
						||
 | 
						||
  nsBlockFrame* block = do_QueryFrame(aLineContainer);
 | 
						||
 | 
						||
  if (!block) {
 | 
						||
    nsIFrame* textRunContainer = aLineContainer;
 | 
						||
    if (aLineContainer->IsRubyTextContainerFrame()) {
 | 
						||
      textRunContainer = aForFrame;
 | 
						||
      while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
 | 
						||
        textRunContainer = textRunContainer->GetParent();
 | 
						||
      }
 | 
						||
      MOZ_ASSERT(textRunContainer &&
 | 
						||
                 textRunContainer->GetParent() == aLineContainer);
 | 
						||
    } else {
 | 
						||
      NS_ASSERTION(
 | 
						||
          !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
 | 
						||
          "Breakable non-block line containers other than "
 | 
						||
          "ruby text container is not supported");
 | 
						||
    }
 | 
						||
    // Just loop through all the children of the linecontainer ... it's really
 | 
						||
    // just one line
 | 
						||
    scanner.SetAtStartOfLine();
 | 
						||
    scanner.SetCommonAncestorWithLastFrame(nullptr);
 | 
						||
    for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
 | 
						||
      scanner.ScanFrame(child);
 | 
						||
    }
 | 
						||
    // Set mStartOfLine so FlushFrames knows its textrun ends a line
 | 
						||
    scanner.SetAtStartOfLine();
 | 
						||
    scanner.FlushFrames(true, false);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Find the line containing 'lineContainerChild'.
 | 
						||
 | 
						||
  bool isValid = true;
 | 
						||
  nsBlockInFlowLineIterator backIterator(block, &isValid);
 | 
						||
  if (aForFrameLine) {
 | 
						||
    backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
 | 
						||
  } else {
 | 
						||
    backIterator =
 | 
						||
        nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
 | 
						||
    NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
 | 
						||
    NS_ASSERTION(backIterator.GetContainer() == block,
 | 
						||
                 "Someone lied to us about the block");
 | 
						||
  }
 | 
						||
  nsBlockFrame::LineIterator startLine = backIterator.GetLine();
 | 
						||
 | 
						||
  // Find a line where we can start building text runs. We choose the last line
 | 
						||
  // where:
 | 
						||
  // -- there is a textrun boundary between the start of the line and the
 | 
						||
  // start of aForFrame
 | 
						||
  // -- there is a space between the start of the line and the textrun boundary
 | 
						||
  // (this is so we can be sure the line breaks will be set properly
 | 
						||
  // on the textruns we construct).
 | 
						||
  // The possibly-partial text runs up to and including the first space
 | 
						||
  // are not reconstructed. We construct partial text runs for that text ---
 | 
						||
  // for the sake of simplifying the code and feeding the linebreaker ---
 | 
						||
  // but we discard them instead of assigning them to frames.
 | 
						||
  // This is a little awkward because we traverse lines in the reverse direction
 | 
						||
  // but we traverse the frames in each line in the forward direction.
 | 
						||
  nsBlockInFlowLineIterator forwardIterator = backIterator;
 | 
						||
  nsIFrame* stopAtFrame = lineContainerChild;
 | 
						||
  nsTextFrame* nextLineFirstTextFrame = nullptr;
 | 
						||
  AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
 | 
						||
  bool seenTextRunBoundaryOnLaterLine = false;
 | 
						||
  bool mayBeginInTextRun = true;
 | 
						||
  while (true) {
 | 
						||
    forwardIterator = backIterator;
 | 
						||
    nsBlockFrame::LineIterator line = backIterator.GetLine();
 | 
						||
    if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
 | 
						||
      mayBeginInTextRun = false;
 | 
						||
      break;
 | 
						||
    }
 | 
						||
 | 
						||
    BuildTextRunsScanner::FindBoundaryState state = {
 | 
						||
        stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine),
 | 
						||
        false,       false,   buffer};
 | 
						||
    nsIFrame* child = line->mFirstChild;
 | 
						||
    bool foundBoundary = false;
 | 
						||
    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
 | 
						||
      BuildTextRunsScanner::FindBoundaryResult result =
 | 
						||
          scanner.FindBoundaries(child, &state);
 | 
						||
      if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
 | 
						||
        foundBoundary = true;
 | 
						||
        break;
 | 
						||
      } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      child = child->GetNextSibling();
 | 
						||
    }
 | 
						||
    if (foundBoundary) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
 | 
						||
        !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame,
 | 
						||
                                             nextLineFirstTextFrame)) {
 | 
						||
      // Found a usable textrun boundary at the end of the line
 | 
						||
      if (state.mSeenSpaceForLineBreakingOnThisLine) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      seenTextRunBoundaryOnLaterLine = true;
 | 
						||
    } else if (state.mSeenTextRunBoundaryOnThisLine) {
 | 
						||
      seenTextRunBoundaryOnLaterLine = true;
 | 
						||
    }
 | 
						||
    stopAtFrame = nullptr;
 | 
						||
    if (state.mFirstTextFrame) {
 | 
						||
      nextLineFirstTextFrame = state.mFirstTextFrame;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
 | 
						||
 | 
						||
  // Now iterate over all text frames starting from the current line.
 | 
						||
  // First-in-flow text frames will be accumulated into textRunFrames as we go.
 | 
						||
  // When a text run boundary is required we flush textRunFrames ((re)building
 | 
						||
  // their gfxTextRuns as necessary).
 | 
						||
  bool seenStartLine = false;
 | 
						||
  uint32_t linesAfterStartLine = 0;
 | 
						||
  do {
 | 
						||
    nsBlockFrame::LineIterator line = forwardIterator.GetLine();
 | 
						||
    if (line->IsBlock()) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    line->SetInvalidateTextRuns(false);
 | 
						||
    scanner.SetAtStartOfLine();
 | 
						||
    scanner.SetCommonAncestorWithLastFrame(nullptr);
 | 
						||
    nsIFrame* child = line->mFirstChild;
 | 
						||
    for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
 | 
						||
      scanner.ScanFrame(child);
 | 
						||
      child = child->GetNextSibling();
 | 
						||
    }
 | 
						||
    if (line.get() == startLine.get()) {
 | 
						||
      seenStartLine = true;
 | 
						||
    }
 | 
						||
    if (seenStartLine) {
 | 
						||
      ++linesAfterStartLine;
 | 
						||
      if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS &&
 | 
						||
          scanner.CanStopOnThisLine()) {
 | 
						||
        // Don't flush frames; we may be in the middle of a textrun
 | 
						||
        // that we can't end here. That's OK, we just won't build it.
 | 
						||
        // Note that we must already have finished the textrun for aForFrame,
 | 
						||
        // because we've seen the end of a textrun in a line after the line
 | 
						||
        // containing aForFrame.
 | 
						||
        scanner.FlushLineBreaks(nullptr);
 | 
						||
        // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
 | 
						||
        // silences assertions in the scanner destructor.
 | 
						||
        scanner.ResetRunInfo();
 | 
						||
        return;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  } while (forwardIterator.Next());
 | 
						||
 | 
						||
  // Set mStartOfLine so FlushFrames knows its textrun ends a line
 | 
						||
  scanner.SetAtStartOfLine();
 | 
						||
  scanner.FlushFrames(true, false);
 | 
						||
}
 | 
						||
 | 
						||
static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) {
 | 
						||
  while (aCount) {
 | 
						||
    *aDest = *aSrc;
 | 
						||
    ++aDest;
 | 
						||
    ++aSrc;
 | 
						||
    --aCount;
 | 
						||
  }
 | 
						||
  return aDest;
 | 
						||
}
 | 
						||
 | 
						||
bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
 | 
						||
    const gfxTextRun* aTextRun) {
 | 
						||
  if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    return mMappedFlows.Length() == 1 &&
 | 
						||
           mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
 | 
						||
           mMappedFlows[0].mEndFrame == nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
 | 
						||
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
 | 
						||
  if (userData->mMappedFlowCount != mMappedFlows.Length()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
 | 
						||
    if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
 | 
						||
        int32_t(userMappedFlows[i].mContentLength) !=
 | 
						||
            mMappedFlows[i].GetContentEnd() -
 | 
						||
                mMappedFlows[i].mStartFrame->GetContentOffset()) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * This gets called when we need to make a text run for the current list of
 | 
						||
 * frames.
 | 
						||
 */
 | 
						||
void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks,
 | 
						||
                                       bool aSuppressTrailingBreak) {
 | 
						||
  RefPtr<gfxTextRun> textRun;
 | 
						||
  if (!mMappedFlows.IsEmpty()) {
 | 
						||
    if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
 | 
						||
        !!(mCurrentFramesAllSameTextRun->GetFlags2() &
 | 
						||
           nsTextFrameUtils::Flags::IncomingWhitespace) ==
 | 
						||
            !!(mCurrentRunContextInfo &
 | 
						||
               nsTextFrameUtils::INCOMING_WHITESPACE) &&
 | 
						||
        !!(mCurrentFramesAllSameTextRun->GetFlags() &
 | 
						||
           gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
 | 
						||
            !!(mCurrentRunContextInfo &
 | 
						||
               nsTextFrameUtils::INCOMING_ARABICCHAR) &&
 | 
						||
        IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
 | 
						||
      // Optimization: We do not need to (re)build the textrun.
 | 
						||
      textRun = mCurrentFramesAllSameTextRun;
 | 
						||
 | 
						||
      if (mDoLineBreaking) {
 | 
						||
        // Feed this run's text into the linebreaker to provide context.
 | 
						||
        if (!SetupLineBreakerContext(textRun)) {
 | 
						||
          return;
 | 
						||
        }
 | 
						||
      }
 | 
						||
 | 
						||
      // Update mNextRunContextInfo appropriately
 | 
						||
      mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
 | 
						||
      if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) {
 | 
						||
        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
 | 
						||
      }
 | 
						||
      if (textRun->GetFlags() &
 | 
						||
          gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
 | 
						||
        mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
 | 
						||
      uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
 | 
						||
      if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
 | 
						||
          !buffer.AppendElements(bufferSize, fallible)) {
 | 
						||
        return;
 | 
						||
      }
 | 
						||
      textRun = BuildTextRunForFrames(buffer.Elements());
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (aFlushLineBreaks) {
 | 
						||
    FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
 | 
						||
    if (!mDoLineBreaking && textRun) {
 | 
						||
      CreateObserversForAnimatedGlyphs(textRun.get());
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  mCanStopOnThisLine = true;
 | 
						||
  ResetRunInfo();
 | 
						||
}
 | 
						||
 | 
						||
void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) {
 | 
						||
  // If the line-breaker is buffering a potentially-unfinished word,
 | 
						||
  // preserve the state of being in-word so that we don't spuriously
 | 
						||
  // capitalize the next letter.
 | 
						||
  bool inWord = mLineBreaker.InWord();
 | 
						||
  bool trailingLineBreak;
 | 
						||
  nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
 | 
						||
  mLineBreaker.SetWordContinuation(inWord);
 | 
						||
  // textRun may be null for various reasons, including because we constructed
 | 
						||
  // a partial textrun just to get the linebreaker and other state set up
 | 
						||
  // to build the next textrun.
 | 
						||
  if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
 | 
						||
    aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak);
 | 
						||
  }
 | 
						||
 | 
						||
  for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
 | 
						||
    // TODO cause frames associated with the textrun to be reflowed, if they
 | 
						||
    // aren't being reflowed already!
 | 
						||
    mBreakSinks[i]->Finish(mMissingFonts);
 | 
						||
  }
 | 
						||
  mBreakSinks.Clear();
 | 
						||
}
 | 
						||
 | 
						||
void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) {
 | 
						||
  if (mMaxTextLength != UINT32_MAX) {
 | 
						||
    NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),
 | 
						||
                 "integer overflow");
 | 
						||
    if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
 | 
						||
      mMaxTextLength = UINT32_MAX;
 | 
						||
    } else {
 | 
						||
      mMaxTextLength += aFrame->GetContentLength();
 | 
						||
    }
 | 
						||
  }
 | 
						||
  mDoubleByteText |= aFrame->TextFragment()->Is2b();
 | 
						||
  mLastFrame = aFrame;
 | 
						||
  mCommonAncestorWithLastFrame = aFrame->GetParent();
 | 
						||
 | 
						||
  MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
 | 
						||
  NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
 | 
						||
                   mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
 | 
						||
               "Overlapping or discontiguous frames => BAD");
 | 
						||
  mappedFlow->mEndFrame = aFrame->GetNextContinuation();
 | 
						||
  if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
 | 
						||
    mCurrentFramesAllSameTextRun = nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  if (mStartOfLine) {
 | 
						||
    mLineBreakBeforeFrames.AppendElement(aFrame);
 | 
						||
    mStartOfLine = false;
 | 
						||
  }
 | 
						||
 | 
						||
  // Default limits used by `hyphenate-limit-chars` for `auto` components, as
 | 
						||
  // suggested by the CSS Text spec.
 | 
						||
  // TODO: consider making these sensitive to the context, e.g. increasing the
 | 
						||
  // values for long line lengths to reduce the tendency to hyphenate too much.
 | 
						||
  const uint32_t kDefaultHyphenateTotalWordLength = 5;
 | 
						||
  const uint32_t kDefaultHyphenatePreBreakLength = 2;
 | 
						||
  const uint32_t kDefaultHyphenatePostBreakLength = 2;
 | 
						||
 | 
						||
  const auto& hyphenateLimitChars = aFrame->StyleText()->mHyphenateLimitChars;
 | 
						||
  uint32_t pre =
 | 
						||
      hyphenateLimitChars.pre_hyphen_length.IsAuto()
 | 
						||
          ? kDefaultHyphenatePreBreakLength
 | 
						||
          : std::max(0, hyphenateLimitChars.pre_hyphen_length.AsNumber());
 | 
						||
  uint32_t post =
 | 
						||
      hyphenateLimitChars.post_hyphen_length.IsAuto()
 | 
						||
          ? kDefaultHyphenatePostBreakLength
 | 
						||
          : std::max(0, hyphenateLimitChars.post_hyphen_length.AsNumber());
 | 
						||
  uint32_t total =
 | 
						||
      hyphenateLimitChars.total_word_length.IsAuto()
 | 
						||
          ? kDefaultHyphenateTotalWordLength
 | 
						||
          : std::max(0, hyphenateLimitChars.total_word_length.AsNumber());
 | 
						||
  total = std::max(total, pre + post);
 | 
						||
  mLineBreaker.SetHyphenateLimitChars(total, pre, post);
 | 
						||
}
 | 
						||
 | 
						||
static bool HasTerminalNewline(const nsTextFrame* aFrame) {
 | 
						||
  if (aFrame->GetContentLength() == 0) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  const nsTextFragment* frag = aFrame->TextFragment();
 | 
						||
  return frag->CharAt(AssertedCast<uint32_t>(aFrame->GetContentEnd()) - 1) ==
 | 
						||
         '\n';
 | 
						||
}
 | 
						||
 | 
						||
static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup,
 | 
						||
                                            bool aVerticalMetrics) {
 | 
						||
  if (!aFontGroup) {
 | 
						||
    return gfxFont::Metrics();
 | 
						||
  }
 | 
						||
  RefPtr<gfxFont> font = aFontGroup->GetFirstValidFont();
 | 
						||
  return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical
 | 
						||
                                           : nsFontMetrics::eHorizontal);
 | 
						||
}
 | 
						||
 | 
						||
static nscoord GetSpaceWidthAppUnits(const gfxTextRun* aTextRun) {
 | 
						||
  // Round the space width when converting to appunits the same way textruns
 | 
						||
  // do.
 | 
						||
  gfxFloat spaceWidthAppUnits =
 | 
						||
      NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
 | 
						||
                                   aTextRun->UseCenterBaseline())
 | 
						||
                   .spaceWidth *
 | 
						||
               aTextRun->GetAppUnitsPerDevUnit());
 | 
						||
 | 
						||
  return spaceWidthAppUnits;
 | 
						||
}
 | 
						||
 | 
						||
static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) {
 | 
						||
  gfxFloat chWidthAppUnits = NS_round(
 | 
						||
      GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical())
 | 
						||
          .ZeroOrAveCharWidth() *
 | 
						||
      aTextRun->GetAppUnitsPerDevUnit());
 | 
						||
  return 0.5 * chWidthAppUnits;
 | 
						||
}
 | 
						||
 | 
						||
static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) {
 | 
						||
  if (!aFrame->IsInSVGTextSubtree()) {
 | 
						||
    return 1.0f;
 | 
						||
  }
 | 
						||
  auto* container =
 | 
						||
      nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
 | 
						||
  MOZ_ASSERT(container);
 | 
						||
  return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
 | 
						||
}
 | 
						||
 | 
						||
static nscoord LetterSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) {
 | 
						||
  if (aFrame->IsInSVGTextSubtree()) {
 | 
						||
    // SVG text can have a scaling factor applied so that very small or very
 | 
						||
    // large font-sizes don't suffer from poor glyph placement due to app unit
 | 
						||
    // rounding. The used letter-spacing value must be scaled by the same
 | 
						||
    // factor. Unlike word-spacing (below), this applies to both lengths and
 | 
						||
    // percentages, as the percentage basis is 1em, not an already-scaled glyph
 | 
						||
    // dimension.
 | 
						||
    return GetSVGFontSizeScaleFactor(aFrame) *
 | 
						||
           aStyleText.mLetterSpacing.Resolve(
 | 
						||
               [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
 | 
						||
  }
 | 
						||
 | 
						||
  return aStyleText.mLetterSpacing.Resolve(
 | 
						||
      [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); });
 | 
						||
}
 | 
						||
 | 
						||
// This function converts non-coord values (e.g. percentages) to nscoord.
 | 
						||
static nscoord WordSpacing(nsIFrame* aFrame, const gfxTextRun* aTextRun,
 | 
						||
                           const nsStyleText& aStyleText) {
 | 
						||
  if (aFrame->IsInSVGTextSubtree()) {
 | 
						||
    // SVG text can have a scaling factor applied so that very small or very
 | 
						||
    // large font-sizes don't suffer from poor glyph placement due to app unit
 | 
						||
    // rounding. The used word-spacing value must be scaled by the same
 | 
						||
    // factor, although any percentage basis has already effectively been
 | 
						||
    // scaled, since it's the space glyph width, which is based on the already-
 | 
						||
    // scaled font-size.
 | 
						||
    auto spacing = aStyleText.mWordSpacing;
 | 
						||
    spacing.ScaleLengthsBy(GetSVGFontSizeScaleFactor(aFrame));
 | 
						||
    return spacing.Resolve([&] { return GetSpaceWidthAppUnits(aTextRun); });
 | 
						||
  }
 | 
						||
 | 
						||
  return aStyleText.mWordSpacing.Resolve(
 | 
						||
      [&] { return GetSpaceWidthAppUnits(aTextRun); });
 | 
						||
}
 | 
						||
 | 
						||
// Returns gfxTextRunFactory::TEXT_ENABLE_SPACING if non-standard
 | 
						||
// letter-spacing or word-spacing is present.
 | 
						||
static gfx::ShapedTextFlags GetSpacingFlags(
 | 
						||
    nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) {
 | 
						||
  const nsStyleText* styleText = aFrame->StyleText();
 | 
						||
  const auto& ls = styleText->mLetterSpacing;
 | 
						||
  const auto& ws = styleText->mWordSpacing;
 | 
						||
 | 
						||
  // It's possible to have a calc() value that computes to zero but for which
 | 
						||
  // IsDefinitelyZero() is false, in which case we'll return
 | 
						||
  // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
 | 
						||
  // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
 | 
						||
  bool nonStandardSpacing = !ls.IsDefinitelyZero() || !ws.IsDefinitelyZero();
 | 
						||
  return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
 | 
						||
                            : gfx::ShapedTextFlags();
 | 
						||
}
 | 
						||
 | 
						||
bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1,
 | 
						||
                                                       nsTextFrame* aFrame2) {
 | 
						||
  // We don't need to check font size inflation, since
 | 
						||
  // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
 | 
						||
  // ensures that text runs never cross block boundaries.  This means
 | 
						||
  // that the font size inflation on all text frames in the text run is
 | 
						||
  // already guaranteed to be the same as each other (and for the line
 | 
						||
  // container).
 | 
						||
  if (mBidiEnabled) {
 | 
						||
    FrameBidiData data1 = aFrame1->GetBidiData();
 | 
						||
    FrameBidiData data2 = aFrame2->GetBidiData();
 | 
						||
    if (data1.embeddingLevel != data2.embeddingLevel ||
 | 
						||
        data2.precedingControl != kBidiLevelNone) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  ComputedStyle* sc1 = aFrame1->Style();
 | 
						||
  ComputedStyle* sc2 = aFrame2->Style();
 | 
						||
 | 
						||
  // Any difference in writing-mode/directionality inhibits shaping across
 | 
						||
  // the boundary.
 | 
						||
  WritingMode wm(sc1);
 | 
						||
  if (wm != WritingMode(sc2)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  const nsStyleText* textStyle1 = sc1->StyleText();
 | 
						||
  // If the first frame ends in a preformatted newline, then we end the textrun
 | 
						||
  // here. This avoids creating giant textruns for an entire plain text file.
 | 
						||
  // Note that we create a single text frame for a preformatted text node,
 | 
						||
  // even if it has newlines in it, so typically we won't see trailing newlines
 | 
						||
  // until after reflow has broken up the frame into one (or more) frames per
 | 
						||
  // line. That's OK though.
 | 
						||
  if (textStyle1->NewlineIsSignificant(aFrame1) &&
 | 
						||
      HasTerminalNewline(aFrame1)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aFrame1->GetParent()->GetContent() !=
 | 
						||
      aFrame2->GetParent()->GetContent()) {
 | 
						||
    // Does aFrame, or any ancestor between it and aAncestor, have a property
 | 
						||
    // that should inhibit cross-element-boundary shaping on aSide?
 | 
						||
    auto PreventCrossBoundaryShaping = [](const nsIFrame* aFrame,
 | 
						||
                                          const nsIFrame* aAncestor,
 | 
						||
                                          Side aSide) {
 | 
						||
      while (aFrame != aAncestor) {
 | 
						||
        ComputedStyle* ctx = aFrame->Style();
 | 
						||
        const auto anchorResolutionParams =
 | 
						||
            AnchorPosResolutionParams::From(aFrame);
 | 
						||
        // According to https://drafts.csswg.org/css-text/#boundary-shaping:
 | 
						||
        //
 | 
						||
        // Text shaping must be broken at inline box boundaries when any of
 | 
						||
        // the following are true for any box whose boundary separates the
 | 
						||
        // two typographic character units:
 | 
						||
        //
 | 
						||
        // 1. Any of margin/border/padding separating the two typographic
 | 
						||
        //    character units in the inline axis is non-zero.
 | 
						||
        const auto margin = ctx->StyleMargin()->GetMargin(
 | 
						||
            aSide, anchorResolutionParams.mPosition);
 | 
						||
        if (!margin->ConvertsToLength() ||
 | 
						||
            margin->AsLengthPercentage().ToLength() != 0) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
        const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
 | 
						||
        if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
        if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
 | 
						||
        // 2. vertical-align is not baseline.
 | 
						||
        //
 | 
						||
        // FIXME: Should this use VerticalAlignEnum()?
 | 
						||
        const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign;
 | 
						||
        if (!verticalAlign.IsKeyword() ||
 | 
						||
            verticalAlign.AsKeyword() != StyleVerticalAlignKeyword::Baseline) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
 | 
						||
        // 3. The boundary is a bidi isolation boundary.
 | 
						||
        const auto unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
 | 
						||
        if (unicodeBidi == StyleUnicodeBidi::Isolate ||
 | 
						||
            unicodeBidi == StyleUnicodeBidi::IsolateOverride) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
 | 
						||
        aFrame = aFrame->GetParent();
 | 
						||
      }
 | 
						||
      return false;
 | 
						||
    };
 | 
						||
 | 
						||
    const nsIFrame* ancestor =
 | 
						||
        nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1,
 | 
						||
                                                                 aFrame2);
 | 
						||
 | 
						||
    if (!ancestor) {
 | 
						||
      // The two frames are within different blocks, e.g. due to block
 | 
						||
      // fragmentation. In theory we shouldn't prevent cross-frame shaping
 | 
						||
      // here, but it's an edge case where we should rarely decide to allow
 | 
						||
      // cross-frame shaping, so we don't try harder here.
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
 | 
						||
    // We inhibit cross-element-boundary shaping if we're in SVG content,
 | 
						||
    // as there are too many things SVG might be doing (like applying per-
 | 
						||
    // element positioning) that wouldn't make sense with shaping across
 | 
						||
    // the boundary.
 | 
						||
    if (ancestor->IsInSVGTextSubtree()) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
 | 
						||
    // Map inline-end and inline-start to physical sides for checking presence
 | 
						||
    // of non-zero margin/border/padding.
 | 
						||
    Side side1 = wm.PhysicalSide(LogicalSide::IEnd);
 | 
						||
    Side side2 = wm.PhysicalSide(LogicalSide::IStart);
 | 
						||
    // If the frames have an embedding level that is opposite to the writing
 | 
						||
    // mode, we need to swap which sides we're checking.
 | 
						||
    if (aFrame1->GetEmbeddingLevel().IsRTL() == wm.IsBidiLTR()) {
 | 
						||
      std::swap(side1, side2);
 | 
						||
    }
 | 
						||
 | 
						||
    if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) ||
 | 
						||
        PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (aFrame1->GetContent() == aFrame2->GetContent() &&
 | 
						||
      aFrame1->GetNextInFlow() != aFrame2) {
 | 
						||
    // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
 | 
						||
    // sometimes when the unicode-bidi property is used; the bidi resolver
 | 
						||
    // breaks text into different frames even though the text has the same
 | 
						||
    // direction. We can't allow these two frames to share the same textrun
 | 
						||
    // because that would violate our invariant that two flows in the same
 | 
						||
    // textrun have different content elements.
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  if (sc1 == sc2) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  const nsStyleText* textStyle2 = sc2->StyleText();
 | 
						||
  if (textStyle1->mTextTransform != textStyle2->mTextTransform ||
 | 
						||
      textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() ||
 | 
						||
      textStyle1->mLineBreak != textStyle2->mLineBreak) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  nsPresContext* pc = aFrame1->PresContext();
 | 
						||
  MOZ_ASSERT(pc == aFrame2->PresContext());
 | 
						||
 | 
						||
  const nsStyleFont* fontStyle1 = sc1->StyleFont();
 | 
						||
  const nsStyleFont* fontStyle2 = sc2->StyleFont();
 | 
						||
  nscoord letterSpacing1 = LetterSpacing(aFrame1, *textStyle1);
 | 
						||
  nscoord letterSpacing2 = LetterSpacing(aFrame2, *textStyle2);
 | 
						||
  return fontStyle1->mFont == fontStyle2->mFont &&
 | 
						||
         fontStyle1->mLanguage == fontStyle2->mLanguage &&
 | 
						||
         nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1,
 | 
						||
                                                letterSpacing1) ==
 | 
						||
             nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2,
 | 
						||
                                                    textStyle2, letterSpacing2);
 | 
						||
}
 | 
						||
 | 
						||
void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) {
 | 
						||
  LayoutFrameType frameType = aFrame->Type();
 | 
						||
  if (frameType == LayoutFrameType::RubyTextContainer) {
 | 
						||
    // Don't include any ruby text container into the text run.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // First check if we can extend the current mapped frame block. This is
 | 
						||
  // common.
 | 
						||
  if (mMappedFlows.Length() > 0) {
 | 
						||
    MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
 | 
						||
    if (mappedFlow->mEndFrame == aFrame &&
 | 
						||
        aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
 | 
						||
      NS_ASSERTION(frameType == LayoutFrameType::Text,
 | 
						||
                   "Flow-sibling of a text frame is not a text frame?");
 | 
						||
 | 
						||
      // Don't do this optimization if mLastFrame has a terminal newline...
 | 
						||
      // it's quite likely preformatted and we might want to end the textrun
 | 
						||
      // here. This is almost always true:
 | 
						||
      if (mLastFrame->Style() == aFrame->Style() &&
 | 
						||
          !HasTerminalNewline(mLastFrame)) {
 | 
						||
        AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
 | 
						||
        return;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Now see if we can add a new set of frames to the current textrun
 | 
						||
  if (frameType == LayoutFrameType::Text) {
 | 
						||
    nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
 | 
						||
 | 
						||
    if (mLastFrame) {
 | 
						||
      if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
 | 
						||
        FlushFrames(false, false);
 | 
						||
      } else {
 | 
						||
        if (mLastFrame->GetContent() == frame->GetContent()) {
 | 
						||
          AccumulateRunInfo(frame);
 | 
						||
          return;
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    MappedFlow* mappedFlow = mMappedFlows.AppendElement();
 | 
						||
    mappedFlow->mStartFrame = frame;
 | 
						||
    mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
 | 
						||
 | 
						||
    AccumulateRunInfo(frame);
 | 
						||
    if (mMappedFlows.Length() == 1) {
 | 
						||
      mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
 | 
						||
      mCurrentRunContextInfo = mNextRunContextInfo;
 | 
						||
    }
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (frameType == LayoutFrameType::Placeholder &&
 | 
						||
      aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS |
 | 
						||
                              PLACEHOLDER_FOR_FIXEDPOS)) {
 | 
						||
    // Somewhat hacky fix for bug 1418472:
 | 
						||
    // If this is a placeholder for an absolute-positioned frame, we need to
 | 
						||
    // flush the line-breaker to prevent the placeholder becoming separated
 | 
						||
    // from the immediately-following content.
 | 
						||
    // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos
 | 
						||
    // element occurs within a word where shaping should be in effect, but
 | 
						||
    // that's an edge case, unlikely to occur in real content. A more precise
 | 
						||
    // fix might require better separation of line-breaking from textrun setup,
 | 
						||
    // but that's a big invasive change (and potentially expensive for perf, as
 | 
						||
    // it might introduce an additional pass over all the frames).
 | 
						||
    FlushFrames(true, false);
 | 
						||
  }
 | 
						||
 | 
						||
  FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
 | 
						||
  bool isBR = frameType == LayoutFrameType::Br;
 | 
						||
  if (!traversal.mLineBreakerCanCrossFrameBoundary) {
 | 
						||
    // BR frames are special. We do not need or want to record a break
 | 
						||
    // opportunity before a BR frame.
 | 
						||
    FlushFrames(true, isBR);
 | 
						||
    mCommonAncestorWithLastFrame = aFrame;
 | 
						||
    mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
 | 
						||
    mStartOfLine = false;
 | 
						||
  } else if (!traversal.mTextRunCanCrossFrameBoundary) {
 | 
						||
    FlushFrames(false, false);
 | 
						||
  }
 | 
						||
 | 
						||
  for (nsIFrame* f = traversal.NextFrameToScan(); f;
 | 
						||
       f = traversal.NextFrameToScan()) {
 | 
						||
    ScanFrame(f);
 | 
						||
  }
 | 
						||
 | 
						||
  if (!traversal.mLineBreakerCanCrossFrameBoundary) {
 | 
						||
    // Really if we're a BR frame this is unnecessary since descendInto will be
 | 
						||
    // false. In fact this whole "if" statement should move into the
 | 
						||
    // descendInto.
 | 
						||
    FlushFrames(true, isBR);
 | 
						||
    mCommonAncestorWithLastFrame = aFrame;
 | 
						||
    mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
 | 
						||
  } else if (!traversal.mTextRunCanCrossFrameBoundary) {
 | 
						||
    FlushFrames(false, false);
 | 
						||
  }
 | 
						||
 | 
						||
  LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) {
 | 
						||
  uint32_t index = *aIndex;
 | 
						||
  if (index >= mLineBreakBeforeFrames.Length()) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
  *aIndex = index + 1;
 | 
						||
  return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
 | 
						||
}
 | 
						||
 | 
						||
static gfxFontGroup* GetFontGroupForFrame(
 | 
						||
    const nsIFrame* aFrame, float aFontSizeInflation,
 | 
						||
    nsFontMetrics** aOutFontMetrics = nullptr) {
 | 
						||
  RefPtr<nsFontMetrics> metrics =
 | 
						||
      nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
 | 
						||
  gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
 | 
						||
 | 
						||
  // Populate outparam before we return:
 | 
						||
  if (aOutFontMetrics) {
 | 
						||
    metrics.forget(aOutFontMetrics);
 | 
						||
  }
 | 
						||
  // XXX this is a bit bogus, we're releasing 'metrics' so the
 | 
						||
  // returned font-group might actually be torn down, although because
 | 
						||
  // of the way the device context caches font metrics, this seems to
 | 
						||
  // not actually happen. But we should fix this.
 | 
						||
  return fontGroup;
 | 
						||
}
 | 
						||
 | 
						||
nsFontMetrics* nsTextFrame::InflatedFontMetrics() const {
 | 
						||
  if (!mFontMetrics) {
 | 
						||
    float inflation = nsLayoutUtils::FontSizeInflationFor(this);
 | 
						||
    mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
 | 
						||
  }
 | 
						||
  return mFontMetrics;
 | 
						||
}
 | 
						||
 | 
						||
static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) {
 | 
						||
  gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
 | 
						||
  if (textRun) {
 | 
						||
    return textRun->GetFontGroup();
 | 
						||
  }
 | 
						||
  return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
 | 
						||
}
 | 
						||
 | 
						||
static already_AddRefed<DrawTarget> CreateReferenceDrawTarget(
 | 
						||
    const nsTextFrame* aTextFrame) {
 | 
						||
  UniquePtr<gfxContext> ctx =
 | 
						||
      aTextFrame->PresShell()->CreateReferenceRenderingContext();
 | 
						||
  RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
 | 
						||
  return dt.forget();
 | 
						||
}
 | 
						||
 | 
						||
static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame,
 | 
						||
                                                     DrawTarget* aDrawTarget) {
 | 
						||
  RefPtr<DrawTarget> dt = aDrawTarget;
 | 
						||
  if (!dt) {
 | 
						||
    dt = CreateReferenceDrawTarget(aTextFrame);
 | 
						||
    if (!dt) {
 | 
						||
      return nullptr;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  RefPtr<nsFontMetrics> fm =
 | 
						||
      nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame);
 | 
						||
  auto* fontGroup = fm->GetThebesFontGroup();
 | 
						||
  auto appPerDev = aTextFrame->PresContext()->AppUnitsPerDevPixel();
 | 
						||
  const auto& hyphenateChar = aTextFrame->StyleText()->mHyphenateCharacter;
 | 
						||
  gfx::ShapedTextFlags flags =
 | 
						||
      nsLayoutUtils::GetTextRunOrientFlagsForStyle(aTextFrame->Style());
 | 
						||
  // Make the directionality of the hyphen run (in case it is multi-char) match
 | 
						||
  // the text frame.
 | 
						||
  if (aTextFrame->GetWritingMode().IsBidiRTL()) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
 | 
						||
  }
 | 
						||
  if (hyphenateChar.IsAuto()) {
 | 
						||
    return fontGroup->MakeHyphenTextRun(dt, flags, appPerDev);
 | 
						||
  }
 | 
						||
  auto* missingFonts = aTextFrame->PresContext()->MissingFontRecorder();
 | 
						||
  const NS_ConvertUTF8toUTF16 hyphenStr(hyphenateChar.AsString().AsString());
 | 
						||
  return fontGroup->MakeTextRun(hyphenStr.BeginReading(), hyphenStr.Length(),
 | 
						||
                                dt, appPerDev, flags, nsTextFrameUtils::Flags(),
 | 
						||
                                missingFonts);
 | 
						||
}
 | 
						||
 | 
						||
already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames(
 | 
						||
    void* aTextBuffer) {
 | 
						||
  gfxSkipChars skipChars;
 | 
						||
 | 
						||
  const void* textPtr = aTextBuffer;
 | 
						||
  bool anyTextTransformStyle = false;
 | 
						||
  bool anyMathMLStyling = false;
 | 
						||
  bool anyTextEmphasis = false;
 | 
						||
  uint8_t sstyScriptLevel = 0;
 | 
						||
  uint32_t mathFlags = 0;
 | 
						||
  gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
 | 
						||
  nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks;
 | 
						||
 | 
						||
  if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
 | 
						||
    flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace;
 | 
						||
  }
 | 
						||
  if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
 | 
						||
  }
 | 
						||
 | 
						||
  AutoTArray<int32_t, 50> textBreakPoints;
 | 
						||
  TextRunUserData dummyData;
 | 
						||
  TextRunMappedFlow dummyMappedFlow;
 | 
						||
  TextRunMappedFlow* userMappedFlows;
 | 
						||
  TextRunUserData* userData;
 | 
						||
  TextRunUserData* userDataToDestroy;
 | 
						||
  // If the situation is particularly simple (and common) we don't need to
 | 
						||
  // allocate userData.
 | 
						||
  if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
 | 
						||
      mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
 | 
						||
    userData = &dummyData;
 | 
						||
    userMappedFlows = &dummyMappedFlow;
 | 
						||
    userDataToDestroy = nullptr;
 | 
						||
    dummyData.mMappedFlowCount = mMappedFlows.Length();
 | 
						||
    dummyData.mLastFlowIndex = 0;
 | 
						||
  } else {
 | 
						||
    userData = CreateUserData(mMappedFlows.Length());
 | 
						||
    userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
 | 
						||
    userDataToDestroy = userData;
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t currentTransformedTextOffset = 0;
 | 
						||
 | 
						||
  uint32_t nextBreakIndex = 0;
 | 
						||
  nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
 | 
						||
  bool isSVG = mLineContainer->IsInSVGTextSubtree();
 | 
						||
  bool enabledJustification =
 | 
						||
      (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
 | 
						||
       mLineContainer->StyleText()->mTextAlignLast ==
 | 
						||
           StyleTextAlignLast::Justify);
 | 
						||
 | 
						||
  const nsStyleText* textStyle = nullptr;
 | 
						||
  const nsStyleFont* fontStyle = nullptr;
 | 
						||
  ComputedStyle* lastComputedStyle = nullptr;
 | 
						||
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
 | 
						||
    MappedFlow* mappedFlow = &mMappedFlows[i];
 | 
						||
    nsTextFrame* f = mappedFlow->mStartFrame;
 | 
						||
 | 
						||
    lastComputedStyle = f->Style();
 | 
						||
    // Detect use of text-transform or font-variant anywhere in the run
 | 
						||
    textStyle = f->StyleText();
 | 
						||
    if (!textStyle->mTextTransform.IsNone() ||
 | 
						||
        textStyle->mWebkitTextSecurity != StyleTextSecurity::None ||
 | 
						||
        // text-combine-upright requires converting from full-width
 | 
						||
        // characters to non-full-width correspendent in some cases.
 | 
						||
        lastComputedStyle->IsTextCombined()) {
 | 
						||
      anyTextTransformStyle = true;
 | 
						||
    }
 | 
						||
    if (textStyle->HasEffectiveTextEmphasis()) {
 | 
						||
      anyTextEmphasis = true;
 | 
						||
    }
 | 
						||
    flags |= GetSpacingFlags(f);
 | 
						||
    nsTextFrameUtils::CompressionMode compression =
 | 
						||
        GetCSSWhitespaceToCompressionMode(f, textStyle);
 | 
						||
    if ((enabledJustification || f->ShouldSuppressLineBreak()) && !isSVG) {
 | 
						||
      flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
 | 
						||
    }
 | 
						||
    fontStyle = f->StyleFont();
 | 
						||
    nsIFrame* parent = mLineContainer->GetParent();
 | 
						||
    if (StyleMathVariant::None != fontStyle->mMathVariant) {
 | 
						||
      if (StyleMathVariant::Normal != fontStyle->mMathVariant) {
 | 
						||
        anyMathMLStyling = true;
 | 
						||
      }
 | 
						||
    } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
 | 
						||
      flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi;
 | 
						||
      anyMathMLStyling = true;
 | 
						||
    }
 | 
						||
    if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
 | 
						||
      // All MathML tokens except <mtext> use 'math' script.
 | 
						||
      if (!(parent && parent->GetContent() &&
 | 
						||
            parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext))) {
 | 
						||
        flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
 | 
						||
      }
 | 
						||
      nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
 | 
						||
      if (mathFrame) {
 | 
						||
        nsPresentationData presData;
 | 
						||
        mathFrame->GetPresentationData(presData);
 | 
						||
        if (NS_MATHML_IS_DTLS_SET(presData.flags)) {
 | 
						||
          mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
 | 
						||
          anyMathMLStyling = true;
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
    nsIFrame* child = mLineContainer;
 | 
						||
    uint8_t oldScriptLevel = 0;
 | 
						||
    while (parent &&
 | 
						||
           child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
 | 
						||
      // Reconstruct the script level ignoring any user overrides. It is
 | 
						||
      // calculated this way instead of using scriptlevel to ensure the
 | 
						||
      // correct ssty font feature setting is used even if the user sets a
 | 
						||
      // different (especially negative) scriptlevel.
 | 
						||
      nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
 | 
						||
      if (mathFrame) {
 | 
						||
        sstyScriptLevel += mathFrame->ScriptIncrement(child);
 | 
						||
      }
 | 
						||
      if (sstyScriptLevel < oldScriptLevel) {
 | 
						||
        // overflow
 | 
						||
        sstyScriptLevel = UINT8_MAX;
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      child = parent;
 | 
						||
      parent = parent->GetParent();
 | 
						||
      oldScriptLevel = sstyScriptLevel;
 | 
						||
    }
 | 
						||
    if (sstyScriptLevel) {
 | 
						||
      anyMathMLStyling = true;
 | 
						||
    }
 | 
						||
 | 
						||
    // Figure out what content is included in this flow.
 | 
						||
    nsIContent* content = f->GetContent();
 | 
						||
    const nsTextFragment* frag = f->TextFragment();
 | 
						||
    int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
 | 
						||
    int32_t contentEnd = mappedFlow->GetContentEnd();
 | 
						||
    int32_t contentLength = contentEnd - contentStart;
 | 
						||
    const nsAtom* language = f->StyleFont()->mLanguage;
 | 
						||
 | 
						||
    TextRunMappedFlow* newFlow = &userMappedFlows[i];
 | 
						||
    newFlow->mStartFrame = mappedFlow->mStartFrame;
 | 
						||
    newFlow->mDOMOffsetToBeforeTransformOffset =
 | 
						||
        skipChars.GetOriginalCharCount() -
 | 
						||
        mappedFlow->mStartFrame->GetContentOffset();
 | 
						||
    newFlow->mContentLength = contentLength;
 | 
						||
 | 
						||
    while (nextBreakBeforeFrame &&
 | 
						||
           nextBreakBeforeFrame->GetContent() == content) {
 | 
						||
      textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() +
 | 
						||
                                    newFlow->mDOMOffsetToBeforeTransformOffset);
 | 
						||
      nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
 | 
						||
    }
 | 
						||
 | 
						||
    nsTextFrameUtils::Flags analysisFlags;
 | 
						||
    if (frag->Is2b()) {
 | 
						||
      NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
 | 
						||
      char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
 | 
						||
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
 | 
						||
          frag->Get2b() + contentStart, contentLength, bufStart, compression,
 | 
						||
          &mNextRunContextInfo, &skipChars, &analysisFlags, language);
 | 
						||
      aTextBuffer = bufEnd;
 | 
						||
      currentTransformedTextOffset =
 | 
						||
          bufEnd - static_cast<const char16_t*>(textPtr);
 | 
						||
    } else {
 | 
						||
      if (mDoubleByteText) {
 | 
						||
        // Need to expand the text. First transform it into a temporary buffer,
 | 
						||
        // then expand.
 | 
						||
        AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
 | 
						||
        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
 | 
						||
        if (!bufStart) {
 | 
						||
          DestroyUserData(userDataToDestroy);
 | 
						||
          return nullptr;
 | 
						||
        }
 | 
						||
        uint8_t* end = nsTextFrameUtils::TransformText(
 | 
						||
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
 | 
						||
            contentLength, bufStart, compression, &mNextRunContextInfo,
 | 
						||
            &skipChars, &analysisFlags, language);
 | 
						||
        aTextBuffer =
 | 
						||
            ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
 | 
						||
                         tempBuf.Elements(), end - tempBuf.Elements());
 | 
						||
        currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) -
 | 
						||
                                       static_cast<const char16_t*>(textPtr);
 | 
						||
      } else {
 | 
						||
        uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
 | 
						||
        uint8_t* end = nsTextFrameUtils::TransformText(
 | 
						||
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
 | 
						||
            contentLength, bufStart, compression, &mNextRunContextInfo,
 | 
						||
            &skipChars, &analysisFlags, language);
 | 
						||
        aTextBuffer = end;
 | 
						||
        currentTransformedTextOffset =
 | 
						||
            end - static_cast<const uint8_t*>(textPtr);
 | 
						||
      }
 | 
						||
    }
 | 
						||
    flags2 |= analysisFlags;
 | 
						||
  }
 | 
						||
 | 
						||
  void* finalUserData;
 | 
						||
  if (userData == &dummyData) {
 | 
						||
    flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow;
 | 
						||
    userData = nullptr;
 | 
						||
    finalUserData = mMappedFlows[0].mStartFrame;
 | 
						||
  } else {
 | 
						||
    finalUserData = userData;
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t transformedLength = currentTransformedTextOffset;
 | 
						||
 | 
						||
  // Now build the textrun
 | 
						||
  nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
 | 
						||
  float fontInflation;
 | 
						||
  gfxFontGroup* fontGroup;
 | 
						||
  if (mWhichTextRun == nsTextFrame::eNotInflated) {
 | 
						||
    fontInflation = 1.0f;
 | 
						||
    fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
 | 
						||
  } else {
 | 
						||
    fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
 | 
						||
    fontGroup = GetInflatedFontGroupForFrame(firstFrame);
 | 
						||
  }
 | 
						||
  MOZ_ASSERT(fontGroup);
 | 
						||
 | 
						||
  if (flags2 & nsTextFrameUtils::Flags::HasTab) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
 | 
						||
  }
 | 
						||
  if (flags2 & nsTextFrameUtils::Flags::HasShy) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
 | 
						||
  }
 | 
						||
  if (mBidiEnabled && (firstFrame->GetEmbeddingLevel().IsRTL())) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
 | 
						||
  }
 | 
						||
  if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
 | 
						||
    flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace;
 | 
						||
  }
 | 
						||
  if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
 | 
						||
  }
 | 
						||
  // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
 | 
						||
  // frame's style is used, so we use a mixture of the first frame and
 | 
						||
  // last frame's style
 | 
						||
  flags |= nsLayoutUtils::GetTextRunFlagsForStyle(
 | 
						||
      lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle,
 | 
						||
      LetterSpacing(firstFrame, *textStyle));
 | 
						||
  // XXX this is a bit of a hack. For performance reasons, if we're favouring
 | 
						||
  // performance over quality, don't try to get accurate glyph extents.
 | 
						||
  if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
 | 
						||
  }
 | 
						||
 | 
						||
  // Convert linebreak coordinates to transformed string offsets
 | 
						||
  NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
 | 
						||
               "Didn't find all the frames to break-before...");
 | 
						||
  gfxSkipCharsIterator iter(skipChars);
 | 
						||
  AutoTArray<uint32_t, 50> textBreakPointsAfterTransform;
 | 
						||
  for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
 | 
						||
    nsTextFrameUtils::AppendLineBreakOffset(
 | 
						||
        &textBreakPointsAfterTransform,
 | 
						||
        iter.ConvertOriginalToSkipped(textBreakPoints[i]));
 | 
						||
  }
 | 
						||
  if (mStartOfLine) {
 | 
						||
    nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
 | 
						||
                                            transformedLength);
 | 
						||
  }
 | 
						||
 | 
						||
  // Setup factory chain
 | 
						||
  bool needsToMaskPassword = NeedsToMaskPassword(firstFrame);
 | 
						||
  UniquePtr<nsTransformingTextRunFactory> transformingFactory;
 | 
						||
  if (anyTextTransformStyle || needsToMaskPassword) {
 | 
						||
    char16_t maskChar =
 | 
						||
        needsToMaskPassword ? 0 : textStyle->TextSecurityMaskChar();
 | 
						||
    transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>(
 | 
						||
        std::move(transformingFactory), false, maskChar);
 | 
						||
  }
 | 
						||
  if (anyMathMLStyling) {
 | 
						||
    transformingFactory = MakeUnique<MathMLTextRunFactory>(
 | 
						||
        std::move(transformingFactory), mathFlags, sstyScriptLevel,
 | 
						||
        fontInflation);
 | 
						||
  }
 | 
						||
  nsTArray<RefPtr<nsTransformedCharStyle>> styles;
 | 
						||
  if (transformingFactory) {
 | 
						||
    uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX;
 | 
						||
    if (needsToMaskPassword) {
 | 
						||
      unmaskStart = unmaskEnd = UINT32_MAX;
 | 
						||
      const TextEditor* const passwordEditor =
 | 
						||
          nsContentUtils::GetExtantTextEditorFromAnonymousNode(
 | 
						||
              firstFrame->GetContent());
 | 
						||
      if (passwordEditor && !passwordEditor->IsAllMasked()) {
 | 
						||
        unmaskStart = passwordEditor->UnmaskedStart();
 | 
						||
        unmaskEnd = passwordEditor->UnmaskedEnd();
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    iter.SetOriginalOffset(0);
 | 
						||
    for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
 | 
						||
      MappedFlow* mappedFlow = &mMappedFlows[i];
 | 
						||
      nsTextFrame* f;
 | 
						||
      ComputedStyle* sc = nullptr;
 | 
						||
      RefPtr<nsTransformedCharStyle> defaultStyle;
 | 
						||
      RefPtr<nsTransformedCharStyle> unmaskStyle;
 | 
						||
      for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
 | 
						||
           f = f->GetNextContinuation()) {
 | 
						||
        uint32_t skippedOffset = iter.GetSkippedOffset();
 | 
						||
        // Text-combined frames have content-dependent transform, so we
 | 
						||
        // want to create new nsTransformedCharStyle for them anyway.
 | 
						||
        if (sc != f->Style() || sc->IsTextCombined()) {
 | 
						||
          sc = f->Style();
 | 
						||
          defaultStyle = new nsTransformedCharStyle(sc, f->PresContext());
 | 
						||
          if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
 | 
						||
            defaultStyle->mForceNonFullWidth = true;
 | 
						||
          }
 | 
						||
          if (needsToMaskPassword) {
 | 
						||
            defaultStyle->mMaskPassword = true;
 | 
						||
            if (unmaskStart != unmaskEnd) {
 | 
						||
              unmaskStyle = new nsTransformedCharStyle(sc, f->PresContext());
 | 
						||
              unmaskStyle->mForceNonFullWidth =
 | 
						||
                  defaultStyle->mForceNonFullWidth;
 | 
						||
            }
 | 
						||
          }
 | 
						||
        }
 | 
						||
        iter.AdvanceOriginal(f->GetContentLength());
 | 
						||
        uint32_t skippedEnd = iter.GetSkippedOffset();
 | 
						||
        if (unmaskStyle) {
 | 
						||
          uint32_t skippedUnmaskStart =
 | 
						||
              iter.ConvertOriginalToSkipped(unmaskStart);
 | 
						||
          uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd);
 | 
						||
          iter.SetSkippedOffset(skippedEnd);
 | 
						||
          for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart);
 | 
						||
               ++skippedOffset) {
 | 
						||
            styles.AppendElement(defaultStyle);
 | 
						||
          }
 | 
						||
          for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd);
 | 
						||
               ++skippedOffset) {
 | 
						||
            styles.AppendElement(unmaskStyle);
 | 
						||
          }
 | 
						||
          for (; skippedOffset < skippedEnd; ++skippedOffset) {
 | 
						||
            styles.AppendElement(defaultStyle);
 | 
						||
          }
 | 
						||
        } else {
 | 
						||
          for (; skippedOffset < skippedEnd; ++skippedOffset) {
 | 
						||
            styles.AppendElement(defaultStyle);
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
    flags2 |= nsTextFrameUtils::Flags::IsTransformed;
 | 
						||
    NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
 | 
						||
                 "We didn't cover all the characters in the text run!");
 | 
						||
  }
 | 
						||
 | 
						||
  RefPtr<gfxTextRun> textRun;
 | 
						||
  gfxTextRunFactory::Parameters params = {
 | 
						||
      mDrawTarget,
 | 
						||
      finalUserData,
 | 
						||
      &skipChars,
 | 
						||
      textBreakPointsAfterTransform.Elements(),
 | 
						||
      uint32_t(textBreakPointsAfterTransform.Length()),
 | 
						||
      int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
 | 
						||
 | 
						||
  if (mDoubleByteText) {
 | 
						||
    const char16_t* text = static_cast<const char16_t*>(textPtr);
 | 
						||
    if (transformingFactory) {
 | 
						||
      textRun = transformingFactory->MakeTextRun(
 | 
						||
          text, transformedLength, ¶ms, fontGroup, flags, flags2,
 | 
						||
          std::move(styles), true);
 | 
						||
    } else {
 | 
						||
      textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms, flags,
 | 
						||
                                       flags2, mMissingFonts);
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    const uint8_t* text = static_cast<const uint8_t*>(textPtr);
 | 
						||
    flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
 | 
						||
    if (transformingFactory) {
 | 
						||
      textRun = transformingFactory->MakeTextRun(
 | 
						||
          text, transformedLength, ¶ms, fontGroup, flags, flags2,
 | 
						||
          std::move(styles), true);
 | 
						||
    } else {
 | 
						||
      textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms, flags,
 | 
						||
                                       flags2, mMissingFonts);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (!textRun) {
 | 
						||
    DestroyUserData(userDataToDestroy);
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  // We have to set these up after we've created the textrun, because
 | 
						||
  // the breaks may be stored in the textrun during this very call.
 | 
						||
  // This is a bit annoying because it requires another loop over the frames
 | 
						||
  // making up the textrun, but I don't see a way to avoid this.
 | 
						||
  // We have to do this if line-breaking is required OR if a text-transform
 | 
						||
  // is in effect, because we depend on the line-breaker's scanner (via
 | 
						||
  // BreakSink::Finish) to finish building transformed textruns.
 | 
						||
  if (mDoLineBreaking || transformingFactory) {
 | 
						||
    SetupBreakSinksForTextRun(textRun.get(), textPtr);
 | 
						||
  }
 | 
						||
 | 
						||
  // Ownership of the factory has passed to the textrun
 | 
						||
  // TODO: bug 1285316: clean up ownership transfer from the factory to
 | 
						||
  // the textrun
 | 
						||
  Unused << transformingFactory.release();
 | 
						||
 | 
						||
  if (anyTextEmphasis) {
 | 
						||
    SetupTextEmphasisForTextRun(textRun.get(), textPtr);
 | 
						||
  }
 | 
						||
 | 
						||
  if (mSkipIncompleteTextRuns) {
 | 
						||
    mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(
 | 
						||
        textPtr, transformedLength, mDoubleByteText);
 | 
						||
    // Since we're doing to destroy the user data now, avoid a dangling
 | 
						||
    // pointer. Strictly speaking we don't need to do this since it should
 | 
						||
    // not be used (since this textrun will not be used and will be
 | 
						||
    // itself deleted soon), but it's always better to not have dangling
 | 
						||
    // pointers around.
 | 
						||
    textRun->SetUserData(nullptr);
 | 
						||
    DestroyUserData(userDataToDestroy);
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  // Actually wipe out the textruns associated with the mapped frames and
 | 
						||
  // associate those frames with this text run.
 | 
						||
  AssignTextRun(textRun.get(), fontInflation);
 | 
						||
  return textRun.forget();
 | 
						||
}
 | 
						||
 | 
						||
// This is a cut-down version of BuildTextRunForFrames used to set up
 | 
						||
// context for the line-breaker, when the textrun has already been created.
 | 
						||
// So it does the same walk over the mMappedFlows, but doesn't actually
 | 
						||
// build a new textrun.
 | 
						||
bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) {
 | 
						||
  AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
 | 
						||
  uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
 | 
						||
  if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  void* textPtr = buffer.AppendElements(bufferSize, fallible);
 | 
						||
  if (!textPtr) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipChars skipChars;
 | 
						||
  const nsAtom* language = mMappedFlows[0].mStartFrame->StyleFont()->mLanguage;
 | 
						||
 | 
						||
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
 | 
						||
    MappedFlow* mappedFlow = &mMappedFlows[i];
 | 
						||
    nsTextFrame* f = mappedFlow->mStartFrame;
 | 
						||
 | 
						||
    const nsStyleText* textStyle = f->StyleText();
 | 
						||
    nsTextFrameUtils::CompressionMode compression =
 | 
						||
        GetCSSWhitespaceToCompressionMode(f, textStyle);
 | 
						||
 | 
						||
    // Figure out what content is included in this flow.
 | 
						||
    const nsTextFragment* frag = f->TextFragment();
 | 
						||
    int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
 | 
						||
    int32_t contentEnd = mappedFlow->GetContentEnd();
 | 
						||
    int32_t contentLength = contentEnd - contentStart;
 | 
						||
 | 
						||
    nsTextFrameUtils::Flags analysisFlags;
 | 
						||
    if (frag->Is2b()) {
 | 
						||
      NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
 | 
						||
      char16_t* bufStart = static_cast<char16_t*>(textPtr);
 | 
						||
      char16_t* bufEnd = nsTextFrameUtils::TransformText(
 | 
						||
          frag->Get2b() + contentStart, contentLength, bufStart, compression,
 | 
						||
          &mNextRunContextInfo, &skipChars, &analysisFlags, language);
 | 
						||
      textPtr = bufEnd;
 | 
						||
    } else {
 | 
						||
      if (mDoubleByteText) {
 | 
						||
        // Need to expand the text. First transform it into a temporary buffer,
 | 
						||
        // then expand.
 | 
						||
        AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
 | 
						||
        uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
 | 
						||
        if (!bufStart) {
 | 
						||
          return false;
 | 
						||
        }
 | 
						||
        uint8_t* end = nsTextFrameUtils::TransformText(
 | 
						||
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
 | 
						||
            contentLength, bufStart, compression, &mNextRunContextInfo,
 | 
						||
            &skipChars, &analysisFlags, language);
 | 
						||
        textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
 | 
						||
                               tempBuf.Elements(), end - tempBuf.Elements());
 | 
						||
      } else {
 | 
						||
        uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
 | 
						||
        uint8_t* end = nsTextFrameUtils::TransformText(
 | 
						||
            reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
 | 
						||
            contentLength, bufStart, compression, &mNextRunContextInfo,
 | 
						||
            &skipChars, &analysisFlags, language);
 | 
						||
        textPtr = end;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // We have to set these up after we've created the textrun, because
 | 
						||
  // the breaks may be stored in the textrun during this very call.
 | 
						||
  // This is a bit annoying because it requires another loop over the frames
 | 
						||
  // making up the textrun, but I don't see a way to avoid this.
 | 
						||
  SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
 | 
						||
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
static bool HasCompressedLeadingWhitespace(
 | 
						||
    nsTextFrame* aFrame, const nsStyleText* aStyleText,
 | 
						||
    int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) {
 | 
						||
  if (!aIterator.IsOriginalCharSkipped()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = aIterator;
 | 
						||
  int32_t frameContentOffset = aFrame->GetContentOffset();
 | 
						||
  const nsTextFragment* frag = aFrame->TextFragment();
 | 
						||
  while (frameContentOffset < aContentEndOffset &&
 | 
						||
         iter.IsOriginalCharSkipped()) {
 | 
						||
    if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    ++frameContentOffset;
 | 
						||
    iter.AdvanceOriginal(1);
 | 
						||
  }
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
 | 
						||
                                                     const void* aTextPtr) {
 | 
						||
  using mozilla::intl::LineBreakRule;
 | 
						||
  using mozilla::intl::WordBreakRule;
 | 
						||
 | 
						||
  // textruns have uniform language
 | 
						||
  const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont();
 | 
						||
  // We should only use a language for hyphenation if it was specified
 | 
						||
  // explicitly.
 | 
						||
  nsAtom* hyphenationLanguage =
 | 
						||
      styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
 | 
						||
  // We keep this pointed at the skip-chars data for the current mappedFlow.
 | 
						||
  // This lets us cheaply check whether the flow has compressed initial
 | 
						||
  // whitespace...
 | 
						||
  gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
 | 
						||
 | 
						||
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
 | 
						||
    MappedFlow* mappedFlow = &mMappedFlows[i];
 | 
						||
    // The CSS word-break value may change within a word, so we reset it for
 | 
						||
    // each MappedFlow. The line-breaker will flush its text if the property
 | 
						||
    // actually changes.
 | 
						||
    const auto* styleText = mappedFlow->mStartFrame->StyleText();
 | 
						||
    auto wordBreak = styleText->EffectiveWordBreak();
 | 
						||
    switch (wordBreak) {
 | 
						||
      case StyleWordBreak::BreakAll:
 | 
						||
        mLineBreaker.SetWordBreak(WordBreakRule::BreakAll);
 | 
						||
        break;
 | 
						||
      case StyleWordBreak::KeepAll:
 | 
						||
        mLineBreaker.SetWordBreak(WordBreakRule::KeepAll);
 | 
						||
        break;
 | 
						||
      case StyleWordBreak::Normal:
 | 
						||
      default:
 | 
						||
        MOZ_ASSERT(wordBreak == StyleWordBreak::Normal);
 | 
						||
        mLineBreaker.SetWordBreak(WordBreakRule::Normal);
 | 
						||
        break;
 | 
						||
    }
 | 
						||
    switch (styleText->mLineBreak) {
 | 
						||
      case StyleLineBreak::Auto:
 | 
						||
        mLineBreaker.SetStrictness(LineBreakRule::Auto);
 | 
						||
        break;
 | 
						||
      case StyleLineBreak::Normal:
 | 
						||
        mLineBreaker.SetStrictness(LineBreakRule::Normal);
 | 
						||
        break;
 | 
						||
      case StyleLineBreak::Loose:
 | 
						||
        mLineBreaker.SetStrictness(LineBreakRule::Loose);
 | 
						||
        break;
 | 
						||
      case StyleLineBreak::Strict:
 | 
						||
        mLineBreaker.SetStrictness(LineBreakRule::Strict);
 | 
						||
        break;
 | 
						||
      case StyleLineBreak::Anywhere:
 | 
						||
        mLineBreaker.SetStrictness(LineBreakRule::Anywhere);
 | 
						||
        break;
 | 
						||
    }
 | 
						||
 | 
						||
    uint32_t offset = iter.GetSkippedOffset();
 | 
						||
    gfxSkipCharsIterator iterNext = iter;
 | 
						||
    iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
 | 
						||
                             mappedFlow->mStartFrame->GetContentOffset());
 | 
						||
 | 
						||
    UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
 | 
						||
        MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
 | 
						||
 | 
						||
    uint32_t length = iterNext.GetSkippedOffset() - offset;
 | 
						||
    uint32_t flags = 0;
 | 
						||
    nsIFrame* initialBreakController =
 | 
						||
        mappedFlow->mAncestorControllingInitialBreak;
 | 
						||
    if (!initialBreakController) {
 | 
						||
      initialBreakController = mLineContainer;
 | 
						||
    }
 | 
						||
    if (!initialBreakController->StyleText()->WhiteSpaceCanWrap(
 | 
						||
            initialBreakController)) {
 | 
						||
      flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
 | 
						||
    }
 | 
						||
    nsTextFrame* startFrame = mappedFlow->mStartFrame;
 | 
						||
    const nsStyleText* textStyle = startFrame->StyleText();
 | 
						||
    if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
 | 
						||
      flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
 | 
						||
    }
 | 
						||
    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) {
 | 
						||
      flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
 | 
						||
    }
 | 
						||
    if (textStyle->mTextTransform & StyleTextTransform::CAPITALIZE) {
 | 
						||
      flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
 | 
						||
    }
 | 
						||
    if (textStyle->mHyphens == StyleHyphens::Auto &&
 | 
						||
        textStyle->mLineBreak != StyleLineBreak::Anywhere) {
 | 
						||
      flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
 | 
						||
    }
 | 
						||
 | 
						||
    if (HasCompressedLeadingWhitespace(startFrame, textStyle,
 | 
						||
                                       mappedFlow->GetContentEnd(), iter)) {
 | 
						||
      mLineBreaker.AppendInvisibleWhitespace(flags);
 | 
						||
    }
 | 
						||
 | 
						||
    if (length > 0) {
 | 
						||
      BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
 | 
						||
      if (mDoubleByteText) {
 | 
						||
        const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
 | 
						||
        mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
 | 
						||
                                flags, sink);
 | 
						||
      } else {
 | 
						||
        const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
 | 
						||
        mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
 | 
						||
                                flags, sink);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    iter = iterNext;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static bool MayCharacterHaveEmphasisMark(uint32_t aCh) {
 | 
						||
  // Punctuation characters that *can* take emphasis marks (exceptions to the
 | 
						||
  // rule that characters with GeneralCategory=P* do not take emphasis), as per
 | 
						||
  // https://drafts.csswg.org/css-text-decor/#text-emphasis-style-property
 | 
						||
  // There are no non-BMP codepoints in the punctuation exceptions, so we can
 | 
						||
  // just use a 16-bit string to list & check them.
 | 
						||
  constexpr nsLiteralString kPunctuationAcceptsEmphasis =
 | 
						||
      u"\x0023"  // #  NUMBER SIGN
 | 
						||
      u"\x0025"  // %  PERCENT SIGN
 | 
						||
      u"\x0026"  // &  AMPERSAND
 | 
						||
      u"\x0040"  // @  COMMERCIAL AT
 | 
						||
      u"\x00A7"  // §  SECTION SIGN
 | 
						||
      u"\x00B6"  // ¶  PILCROW SIGN
 | 
						||
      u"\x0609"  // ؉  ARABIC-INDIC PER MILLE SIGN
 | 
						||
      u"\x060A"  // ؊  ARABIC-INDIC PER TEN THOUSAND SIGN
 | 
						||
      u"\x066A"  // ٪  ARABIC PERCENT SIGN
 | 
						||
      u"\x2030"  // ‰  PER MILLE SIGN
 | 
						||
      u"\x2031"  // ‱  PER TEN THOUSAND SIGN
 | 
						||
      u"\x204A"  // ⁊  TIRONIAN SIGN ET
 | 
						||
      u"\x204B"  // ⁋  REVERSED PILCROW SIGN
 | 
						||
      u"\x2053"  // ⁓  SWUNG DASH
 | 
						||
      u"\x303D"  // 〽️  PART ALTERNATION MARK
 | 
						||
      // Characters that are NFKD-equivalent to the above, extracted from
 | 
						||
      // UnicodeData.txt.
 | 
						||
      u"\xFE5F"      // SMALL NUMBER SIGN;Po;0;ET;<small> 0023;;;;N;;;;;
 | 
						||
      u"\xFE60"      // SMALL AMPERSAND;Po;0;ON;<small> 0026;;;;N;;;;;
 | 
						||
      u"\xFE6A"      // SMALL PERCENT SIGN;Po;0;ET;<small> 0025;;;;N;;;;;
 | 
						||
      u"\xFE6B"      // SMALL COMMERCIAL AT;Po;0;ON;<small> 0040;;;;N;;;;;
 | 
						||
      u"\xFF03"      // FULLWIDTH NUMBER SIGN;Po;0;ET;<wide> 0023;;;;N;;;;;
 | 
						||
      u"\xFF05"      // FULLWIDTH PERCENT SIGN;Po;0;ET;<wide> 0025;;;;N;;;;;
 | 
						||
      u"\xFF06"      // FULLWIDTH AMPERSAND;Po;0;ON;<wide> 0026;;;;N;;;;;
 | 
						||
      u"\xFF20"_ns;  // FULLWIDTH COMMERCIAL AT;Po;0;ON;<wide> 0040;;;;N;;;;;
 | 
						||
 | 
						||
  switch (unicode::GetGenCategory(aCh)) {
 | 
						||
    case nsUGenCategory::kSeparator:  // whitespace, line- & para-separators
 | 
						||
      return false;
 | 
						||
    case nsUGenCategory::kOther:  // control categories
 | 
						||
      return false;
 | 
						||
    case nsUGenCategory::kPunctuation:
 | 
						||
      return aCh <= 0xffff &&
 | 
						||
             kPunctuationAcceptsEmphasis.Contains(char16_t(aCh));
 | 
						||
    default:
 | 
						||
      return true;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
 | 
						||
                                                       const void* aTextPtr) {
 | 
						||
  if (!mDoubleByteText) {
 | 
						||
    auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
 | 
						||
    for (auto i : IntegerRange(aTextRun->GetLength())) {
 | 
						||
      if (!MayCharacterHaveEmphasisMark(text[i])) {
 | 
						||
        aTextRun->SetNoEmphasisMark(i);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    auto text = reinterpret_cast<const char16_t*>(aTextPtr);
 | 
						||
    auto length = aTextRun->GetLength();
 | 
						||
    for (size_t i = 0; i < length; ++i) {
 | 
						||
      if (i + 1 < length && NS_IS_SURROGATE_PAIR(text[i], text[i + 1])) {
 | 
						||
        uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]);
 | 
						||
        if (!MayCharacterHaveEmphasisMark(ch)) {
 | 
						||
          aTextRun->SetNoEmphasisMark(i);
 | 
						||
          aTextRun->SetNoEmphasisMark(i + 1);
 | 
						||
        }
 | 
						||
        ++i;
 | 
						||
      } else {
 | 
						||
        if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
 | 
						||
          aTextRun->SetNoEmphasisMark(i);
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// Find the flow corresponding to aContent in aUserData
 | 
						||
static inline TextRunMappedFlow* FindFlowForContent(
 | 
						||
    TextRunUserData* aUserData, nsIContent* aContent,
 | 
						||
    TextRunMappedFlow* userMappedFlows) {
 | 
						||
  // Find the flow that contains us
 | 
						||
  int32_t i = aUserData->mLastFlowIndex;
 | 
						||
  int32_t delta = 1;
 | 
						||
  int32_t sign = 1;
 | 
						||
  // Search starting at the current position and examine close-by
 | 
						||
  // positions first, moving further and further away as we go.
 | 
						||
  while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
 | 
						||
    TextRunMappedFlow* flow = &userMappedFlows[i];
 | 
						||
    if (flow->mStartFrame->GetContent() == aContent) {
 | 
						||
      return flow;
 | 
						||
    }
 | 
						||
 | 
						||
    i += delta;
 | 
						||
    sign = -sign;
 | 
						||
    delta = -delta + sign;
 | 
						||
  }
 | 
						||
 | 
						||
  // We ran into an array edge.  Add |delta| to |i| once more to get
 | 
						||
  // back to the side where we still need to search, then step in
 | 
						||
  // the |sign| direction.
 | 
						||
  i += delta;
 | 
						||
  if (sign > 0) {
 | 
						||
    for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
 | 
						||
      TextRunMappedFlow* flow = &userMappedFlows[i];
 | 
						||
      if (flow->mStartFrame->GetContent() == aContent) {
 | 
						||
        return flow;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    for (; i >= 0; --i) {
 | 
						||
      TextRunMappedFlow* flow = &userMappedFlows[i];
 | 
						||
      if (flow->mStartFrame->GetContent() == aContent) {
 | 
						||
        return flow;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return nullptr;
 | 
						||
}
 | 
						||
 | 
						||
void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun,
 | 
						||
                                         float aInflation) {
 | 
						||
  for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
 | 
						||
    MappedFlow* mappedFlow = &mMappedFlows[i];
 | 
						||
    nsTextFrame* startFrame = mappedFlow->mStartFrame;
 | 
						||
    nsTextFrame* endFrame = mappedFlow->mEndFrame;
 | 
						||
    nsTextFrame* f;
 | 
						||
    for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
 | 
						||
#ifdef DEBUG_roc
 | 
						||
      if (f->GetTextRun(mWhichTextRun)) {
 | 
						||
        gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
 | 
						||
        if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
          if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
 | 
						||
            NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
 | 
						||
          }
 | 
						||
        } else {
 | 
						||
          auto userData =
 | 
						||
              static_cast<TextRunUserData*>(aTextRun->GetUserData());
 | 
						||
          TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
 | 
						||
          if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
 | 
						||
              userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
 | 
						||
                  mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
 | 
						||
            NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
#endif
 | 
						||
 | 
						||
      gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
 | 
						||
      if (oldTextRun) {
 | 
						||
        nsTextFrame* firstFrame = nullptr;
 | 
						||
        uint32_t startOffset = 0;
 | 
						||
        if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
          firstFrame = GetFrameForSimpleFlow(oldTextRun);
 | 
						||
        } else {
 | 
						||
          auto userData =
 | 
						||
              static_cast<TextRunUserData*>(oldTextRun->GetUserData());
 | 
						||
          TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
 | 
						||
          firstFrame = userMappedFlows[0].mStartFrame;
 | 
						||
          if (MOZ_UNLIKELY(f != firstFrame)) {
 | 
						||
            TextRunMappedFlow* flow =
 | 
						||
                FindFlowForContent(userData, f->GetContent(), userMappedFlows);
 | 
						||
            if (flow) {
 | 
						||
              startOffset = flow->mDOMOffsetToBeforeTransformOffset;
 | 
						||
            } else {
 | 
						||
              NS_ERROR("Can't find flow containing frame 'f'");
 | 
						||
            }
 | 
						||
          }
 | 
						||
        }
 | 
						||
 | 
						||
        // Optimization: if |f| is the first frame in the flow then there are no
 | 
						||
        // prev-continuations that use |oldTextRun|.
 | 
						||
        nsTextFrame* clearFrom = nullptr;
 | 
						||
        if (MOZ_UNLIKELY(f != firstFrame)) {
 | 
						||
          // If all the frames in the mapped flow starting at |f| (inclusive)
 | 
						||
          // are empty then we let the prev-continuations keep the old text run.
 | 
						||
          gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset,
 | 
						||
                                    f->GetContentOffset());
 | 
						||
          uint32_t textRunOffset =
 | 
						||
              iter.ConvertOriginalToSkipped(f->GetContentOffset());
 | 
						||
          clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
 | 
						||
        }
 | 
						||
        f->ClearTextRun(clearFrom, mWhichTextRun);
 | 
						||
 | 
						||
#ifdef DEBUG
 | 
						||
        if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
 | 
						||
          // oldTextRun was destroyed - assert that we don't reference it.
 | 
						||
          for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
 | 
						||
            NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
 | 
						||
                         "destroyed text run is still in use");
 | 
						||
          }
 | 
						||
        }
 | 
						||
#endif
 | 
						||
      }
 | 
						||
      f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
 | 
						||
    }
 | 
						||
    // Set this bit now; we can't set it any earlier because
 | 
						||
    // f->ClearTextRun() might clear it out.
 | 
						||
    nsFrameState whichTextRunState =
 | 
						||
        startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
 | 
						||
            ? TEXT_IN_TEXTRUN_USER_DATA
 | 
						||
            : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
 | 
						||
    startFrame->AddStateBits(whichTextRunState);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
NS_QUERYFRAME_HEAD(nsTextFrame)
 | 
						||
  NS_QUERYFRAME_ENTRY(nsTextFrame)
 | 
						||
NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
 | 
						||
 | 
						||
gfxSkipCharsIterator nsTextFrame::EnsureTextRun(
 | 
						||
    TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget,
 | 
						||
    nsIFrame* aLineContainer, const nsLineList::iterator* aLine,
 | 
						||
    uint32_t* aFlowEndInTextRun) {
 | 
						||
  gfxTextRun* textRun = GetTextRun(aWhichTextRun);
 | 
						||
  if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
 | 
						||
    RefPtr<DrawTarget> refDT = aRefDrawTarget;
 | 
						||
    if (!refDT) {
 | 
						||
      refDT = CreateReferenceDrawTarget(this);
 | 
						||
    }
 | 
						||
    if (refDT) {
 | 
						||
      BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
 | 
						||
    }
 | 
						||
    textRun = GetTextRun(aWhichTextRun);
 | 
						||
    if (!textRun) {
 | 
						||
      // A text run was not constructed for this frame. This is bad. The caller
 | 
						||
      // will check mTextRun.
 | 
						||
      return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(),
 | 
						||
                                  0);
 | 
						||
    }
 | 
						||
    TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
 | 
						||
    if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
 | 
						||
      RemoveProperty(TabWidthProperty());
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
 | 
						||
    if (aFlowEndInTextRun) {
 | 
						||
      *aFlowEndInTextRun = textRun->GetLength();
 | 
						||
    }
 | 
						||
    return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
 | 
						||
  }
 | 
						||
 | 
						||
  auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
 | 
						||
  TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
 | 
						||
  TextRunMappedFlow* flow =
 | 
						||
      FindFlowForContent(userData, mContent, userMappedFlows);
 | 
						||
  if (flow) {
 | 
						||
    // Since textruns can only contain one flow for a given content element,
 | 
						||
    // this must be our flow.
 | 
						||
    uint32_t flowIndex = flow - userMappedFlows;
 | 
						||
    userData->mLastFlowIndex = flowIndex;
 | 
						||
    gfxSkipCharsIterator iter(textRun->GetSkipChars(),
 | 
						||
                              flow->mDOMOffsetToBeforeTransformOffset,
 | 
						||
                              mContentOffset);
 | 
						||
    if (aFlowEndInTextRun) {
 | 
						||
      if (flowIndex + 1 < userData->mMappedFlowCount) {
 | 
						||
        gfxSkipCharsIterator end(textRun->GetSkipChars());
 | 
						||
        *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
 | 
						||
            flow[1].mStartFrame->GetContentOffset() +
 | 
						||
            flow[1].mDOMOffsetToBeforeTransformOffset);
 | 
						||
      } else {
 | 
						||
        *aFlowEndInTextRun = textRun->GetLength();
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return iter;
 | 
						||
  }
 | 
						||
 | 
						||
  NS_ERROR("Can't find flow containing this frame???");
 | 
						||
  return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
 | 
						||
}
 | 
						||
 | 
						||
static uint32_t GetEndOfTrimmedText(const nsTextFragment* aFrag,
 | 
						||
                                    const nsStyleText* aStyleText,
 | 
						||
                                    uint32_t aStart, uint32_t aEnd,
 | 
						||
                                    gfxSkipCharsIterator* aIterator,
 | 
						||
                                    bool aAllowHangingWS = false) {
 | 
						||
  aIterator->SetSkippedOffset(aEnd);
 | 
						||
  while (aIterator->GetSkippedOffset() > aStart) {
 | 
						||
    aIterator->AdvanceSkipped(-1);
 | 
						||
    if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText,
 | 
						||
                          aAllowHangingWS)) {
 | 
						||
      return aIterator->GetSkippedOffset() + 1;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return aStart;
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets(
 | 
						||
    const nsTextFragment* aFrag, TrimmedOffsetFlags aFlags) const {
 | 
						||
  NS_ASSERTION(mTextRun, "Need textrun here");
 | 
						||
  if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) {
 | 
						||
    // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
 | 
						||
    // to be set correctly.  If our parent wasn't reflowed due to the frame
 | 
						||
    // tree being too deep then the return value doesn't matter.
 | 
						||
    NS_ASSERTION(
 | 
						||
        !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
 | 
						||
            GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
 | 
						||
        "Can only call this on frames that have been reflowed");
 | 
						||
    NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW),
 | 
						||
                 "Can only call this on frames that are not being reflowed");
 | 
						||
  }
 | 
						||
 | 
						||
  TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()};
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
  // Note that pre-line newlines should still allow us to trim spaces
 | 
						||
  // for display
 | 
						||
  if (textStyle->WhiteSpaceIsSignificant()) {
 | 
						||
    return offsets;
 | 
						||
  }
 | 
						||
 | 
						||
  if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) &&
 | 
						||
      ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
 | 
						||
       HasAnyStateBits(TEXT_START_OF_LINE))) {
 | 
						||
    int32_t whitespaceCount =
 | 
						||
        GetTrimmableWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
 | 
						||
    offsets.mStart += whitespaceCount;
 | 
						||
    offsets.mLength -= whitespaceCount;
 | 
						||
  }
 | 
						||
 | 
						||
  if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) &&
 | 
						||
      ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
 | 
						||
       HasAnyStateBits(TEXT_END_OF_LINE))) {
 | 
						||
    // This treats a trailing 'pre-line' newline as trimmable. That's fine,
 | 
						||
    // it's actually what we want since we want whitespace before it to
 | 
						||
    // be trimmed.
 | 
						||
    int32_t whitespaceCount = GetTrimmableWhitespaceCount(
 | 
						||
        aFrag, offsets.GetEnd() - 1, offsets.mLength, -1);
 | 
						||
    offsets.mLength -= whitespaceCount;
 | 
						||
  }
 | 
						||
  return offsets;
 | 
						||
}
 | 
						||
 | 
						||
static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
 | 
						||
                                   const nsTextFragment* aFrag, int32_t aPos,
 | 
						||
                                   bool aLangIsCJ) {
 | 
						||
  NS_ASSERTION(aPos >= 0, "negative position?!");
 | 
						||
 | 
						||
  StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
 | 
						||
  if (justifyStyle == StyleTextJustify::None) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  const char16_t ch = aFrag->CharAt(AssertedCast<uint32_t>(aPos));
 | 
						||
  if (ch == '\n' || ch == '\t' || ch == '\r') {
 | 
						||
    return !aTextStyle->WhiteSpaceIsSignificant();
 | 
						||
  }
 | 
						||
  if (ch == ' ' || ch == CH_NBSP) {
 | 
						||
    // Don't justify spaces that are combined with diacriticals
 | 
						||
    if (!aFrag->Is2b()) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
 | 
						||
        aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
 | 
						||
  }
 | 
						||
 | 
						||
  if (justifyStyle == StyleTextJustify::InterCharacter) {
 | 
						||
    return true;
 | 
						||
  } else if (justifyStyle == StyleTextJustify::InterWord) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  // text-justify: auto
 | 
						||
  if (ch < 0x2150u) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  if (aLangIsCJ) {
 | 
						||
    if (  // Number Forms, Arrows, Mathematical Operators
 | 
						||
        (0x2150u <= ch && ch <= 0x22ffu) ||
 | 
						||
        // Enclosed Alphanumerics
 | 
						||
        (0x2460u <= ch && ch <= 0x24ffu) ||
 | 
						||
        // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
 | 
						||
        (0x2580u <= ch && ch <= 0x27bfu) ||
 | 
						||
        // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
 | 
						||
        // Miscellaneous Mathematical Symbols-B,
 | 
						||
        // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows
 | 
						||
        (0x27f0u <= ch && ch <= 0x2bffu) ||
 | 
						||
        // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic
 | 
						||
        // Description Characters, CJK Symbols and Punctuation, Hiragana,
 | 
						||
        // Katakana, Bopomofo
 | 
						||
        (0x2e80u <= ch && ch <= 0x312fu) ||
 | 
						||
        // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
 | 
						||
        // Enclosed CJK Letters and Months, CJK Compatibility,
 | 
						||
        // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
 | 
						||
        // CJK Unified Ideographs, Yi Syllables, Yi Radicals
 | 
						||
        (0x3190u <= ch && ch <= 0xabffu) ||
 | 
						||
        // CJK Compatibility Ideographs
 | 
						||
        (0xf900u <= ch && ch <= 0xfaffu) ||
 | 
						||
        // Halfwidth and Fullwidth Forms (a part)
 | 
						||
        (0xff5eu <= ch && ch <= 0xff9fu)) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    if (NS_IS_HIGH_SURROGATE(ch)) {
 | 
						||
      if (char32_t u = aFrag->ScalarValueAt(AssertedCast<uint32_t>(aPos))) {
 | 
						||
        // CJK Unified Ideographs Extension B,
 | 
						||
        // CJK Unified Ideographs Extension C,
 | 
						||
        // CJK Unified Ideographs Extension D,
 | 
						||
        // CJK Compatibility Ideographs Supplement
 | 
						||
        if (0x20000u <= u && u <= 0x2ffffu) {
 | 
						||
          return true;
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) {
 | 
						||
  aMetrics.ClearSize();
 | 
						||
  aMetrics.SetBlockStartAscent(0);
 | 
						||
  mAscent = 0;
 | 
						||
 | 
						||
  AddStateBits(TEXT_NO_RENDERED_GLYPHS);
 | 
						||
}
 | 
						||
 | 
						||
static int32_t FindChar(const nsTextFragment* frag, int32_t aOffset,
 | 
						||
                        int32_t aLength, char16_t ch) {
 | 
						||
  int32_t i = 0;
 | 
						||
  if (frag->Is2b()) {
 | 
						||
    const char16_t* str = frag->Get2b() + aOffset;
 | 
						||
    for (; i < aLength; ++i) {
 | 
						||
      if (*str == ch) {
 | 
						||
        return i + aOffset;
 | 
						||
      }
 | 
						||
      ++str;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    if (uint16_t(ch) <= 0xFF) {
 | 
						||
      const char* str = frag->Get1b() + aOffset;
 | 
						||
      const void* p = memchr(str, ch, aLength);
 | 
						||
      if (p) {
 | 
						||
        return (static_cast<const char*>(p) - str) + aOffset;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return -1;
 | 
						||
}
 | 
						||
 | 
						||
static bool IsChineseOrJapanese(const nsTextFrame* aFrame) {
 | 
						||
  if (aFrame->ShouldSuppressLineBreak()) {
 | 
						||
    // Always treat ruby as CJ language so that those characters can
 | 
						||
    // be expanded properly even when surrounded by other language.
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  nsAtom* language = aFrame->StyleFont()->mLanguage;
 | 
						||
  if (!language) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
 | 
						||
         nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
 | 
						||
}
 | 
						||
 | 
						||
#ifdef DEBUG
 | 
						||
static bool IsInBounds(const gfxSkipCharsIterator& aStart,
 | 
						||
                       int32_t aContentLength, gfxTextRun::Range aRange) {
 | 
						||
  if (aStart.GetSkippedOffset() > aRange.start) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  if (aContentLength == INT32_MAX) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  gfxSkipCharsIterator iter(aStart);
 | 
						||
  iter.AdvanceOriginal(aContentLength);
 | 
						||
  return iter.GetSkippedOffset() >= aRange.end;
 | 
						||
}
 | 
						||
#endif
 | 
						||
 | 
						||
nsTextFrame::PropertyProvider::PropertyProvider(
 | 
						||
    gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
 | 
						||
    const nsTextFragment* aFrag, nsTextFrame* aFrame,
 | 
						||
    const gfxSkipCharsIterator& aStart, int32_t aLength,
 | 
						||
    nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs,
 | 
						||
    nsTextFrame::TextRunType aWhichTextRun, bool aAtStartOfLine)
 | 
						||
    : mTextRun(aTextRun),
 | 
						||
      mFontGroup(nullptr),
 | 
						||
      mTextStyle(aTextStyle),
 | 
						||
      mFrag(aFrag),
 | 
						||
      mLineContainer(aLineContainer),
 | 
						||
      mFrame(aFrame),
 | 
						||
      mStart(aStart),
 | 
						||
      mTempIterator(aStart),
 | 
						||
      mTabWidths(nullptr),
 | 
						||
      mTabWidthsAnalyzedLimit(0),
 | 
						||
      mLength(aLength),
 | 
						||
      mWordSpacing(WordSpacing(aFrame, mTextRun, *aTextStyle)),
 | 
						||
      mLetterSpacing(LetterSpacing(aFrame, *aTextStyle)),
 | 
						||
      mMinTabAdvance(-1.0),
 | 
						||
      mHyphenWidth(-1),
 | 
						||
      mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
 | 
						||
      mJustificationArrayStart(0),
 | 
						||
      mReflowing(true),
 | 
						||
      mWhichTextRun(aWhichTextRun) {
 | 
						||
  NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
 | 
						||
  if (aAtStartOfLine) {
 | 
						||
    mStartOfLineOffset = mStart.GetSkippedOffset();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame::PropertyProvider::PropertyProvider(
 | 
						||
    nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
 | 
						||
    nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics)
 | 
						||
    : mTextRun(aFrame->GetTextRun(aWhichTextRun)),
 | 
						||
      mFontGroup(nullptr),
 | 
						||
      mFontMetrics(aFontMetrics),
 | 
						||
      mTextStyle(aFrame->StyleText()),
 | 
						||
      mFrag(aFrame->TextFragment()),
 | 
						||
      mLineContainer(nullptr),
 | 
						||
      mFrame(aFrame),
 | 
						||
      mStart(aStart),
 | 
						||
      mTempIterator(aStart),
 | 
						||
      mTabWidths(nullptr),
 | 
						||
      mTabWidthsAnalyzedLimit(0),
 | 
						||
      mLength(aFrame->GetContentLength()),
 | 
						||
      mWordSpacing(WordSpacing(aFrame, mTextRun, *mTextStyle)),
 | 
						||
      mLetterSpacing(LetterSpacing(aFrame, *mTextStyle)),
 | 
						||
      mMinTabAdvance(-1.0),
 | 
						||
      mHyphenWidth(-1),
 | 
						||
      mOffsetFromBlockOriginForTabs(0),
 | 
						||
      mJustificationArrayStart(0),
 | 
						||
      mReflowing(false),
 | 
						||
      mWhichTextRun(aWhichTextRun) {
 | 
						||
  NS_ASSERTION(mTextRun, "Textrun not initialized!");
 | 
						||
}
 | 
						||
 | 
						||
gfx::ShapedTextFlags nsTextFrame::PropertyProvider::GetShapedTextFlags() const {
 | 
						||
  return nsLayoutUtils::GetTextRunOrientFlagsForStyle(mFrame->Style());
 | 
						||
}
 | 
						||
 | 
						||
already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget()
 | 
						||
    const {
 | 
						||
  return CreateReferenceDrawTarget(GetFrame());
 | 
						||
}
 | 
						||
 | 
						||
gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const {
 | 
						||
  if (mMinTabAdvance < 0.0) {
 | 
						||
    mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
 | 
						||
  }
 | 
						||
  return mMinTabAdvance;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Finds the offset of the first character of the cluster containing aPos
 | 
						||
 */
 | 
						||
static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart,
 | 
						||
                             gfxSkipCharsIterator* aPos) {
 | 
						||
  while (aPos->GetOriginalOffset() > aOriginalStart) {
 | 
						||
    if (aPos->IsOriginalCharSkipped() ||
 | 
						||
        aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    aPos->AdvanceOriginal(-1);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Finds the offset of the last character of the cluster containing aPos.
 | 
						||
 * If aAllowSplitLigature is false, we also check for a ligature-group
 | 
						||
 * start.
 | 
						||
 */
 | 
						||
static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd,
 | 
						||
                           gfxSkipCharsIterator* aPos,
 | 
						||
                           bool aAllowSplitLigature = true) {
 | 
						||
  MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,
 | 
						||
             "character outside string");
 | 
						||
 | 
						||
  aPos->AdvanceOriginal(1);
 | 
						||
  while (aPos->GetOriginalOffset() < aOriginalEnd) {
 | 
						||
    if (aPos->IsOriginalCharSkipped() ||
 | 
						||
        (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
 | 
						||
         (aAllowSplitLigature ||
 | 
						||
          aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    aPos->AdvanceOriginal(1);
 | 
						||
  }
 | 
						||
  aPos->AdvanceOriginal(-1);
 | 
						||
}
 | 
						||
 | 
						||
// Get the line number of aFrame in the lines referenced by aLineIter, if
 | 
						||
// known (returning -1 if we don't find it).
 | 
						||
static int32_t GetFrameLineNum(nsIFrame* aFrame, nsILineIterator* aLineIter) {
 | 
						||
  if (!aLineIter) {
 | 
						||
    return -1;
 | 
						||
  }
 | 
						||
  int32_t n = aLineIter->FindLineContaining(aFrame);
 | 
						||
  if (n >= 0) {
 | 
						||
    return n;
 | 
						||
  }
 | 
						||
  // If we didn't find the frame directly, but its parent is an inline,
 | 
						||
  // we want the line that the inline ancestor is on.
 | 
						||
  nsIFrame* ancestor = aFrame->GetParent();
 | 
						||
  while (ancestor && ancestor->IsInlineFrame()) {
 | 
						||
    n = aLineIter->FindLineContaining(ancestor);
 | 
						||
    if (n >= 0) {
 | 
						||
      return n;
 | 
						||
    }
 | 
						||
    ancestor = ancestor->GetParent();
 | 
						||
  }
 | 
						||
  return -1;
 | 
						||
}
 | 
						||
 | 
						||
// Get the position of the first preserved newline in aFrame, if any,
 | 
						||
// returning -1 if none.
 | 
						||
static int32_t FindFirstNewlinePosition(const nsTextFrame* aFrame) {
 | 
						||
  MOZ_ASSERT(aFrame->StyleText()->NewlineIsSignificantStyle(),
 | 
						||
             "how did the HasNewline flag get set?");
 | 
						||
  const auto* textFragment = aFrame->TextFragment();
 | 
						||
  for (auto i = aFrame->GetContentOffset(); i < aFrame->GetContentEnd(); ++i) {
 | 
						||
    if (textFragment->CharAt(i) == '\n') {
 | 
						||
      return i;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return -1;
 | 
						||
}
 | 
						||
 | 
						||
// Get the position of the last preserved tab in aFrame that is before the
 | 
						||
// preserved newline at aNewlinePos.
 | 
						||
// Passing -1 for aNewlinePos means there is no preserved newline, so we look
 | 
						||
// for the last preserved tab in the whole content.
 | 
						||
// Returns -1 if no such preserved tab is present.
 | 
						||
static int32_t FindLastTabPositionBeforeNewline(const nsTextFrame* aFrame,
 | 
						||
                                                int32_t aNewlinePos) {
 | 
						||
  // We only call this if white-space is not being collapsed.
 | 
						||
  MOZ_ASSERT(aFrame->StyleText()->WhiteSpaceIsSignificant(),
 | 
						||
             "how did the HasTab flag get set?");
 | 
						||
  const auto* textFragment = aFrame->TextFragment();
 | 
						||
  // If a non-negative newline position was given, we only need to search the
 | 
						||
  // text before that offset.
 | 
						||
  for (auto i = aNewlinePos < 0 ? aFrame->GetContentEnd() : aNewlinePos;
 | 
						||
       i > aFrame->GetContentOffset(); --i) {
 | 
						||
    if (textFragment->CharAt(i - 1) == '\t') {
 | 
						||
      return i;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return -1;
 | 
						||
}
 | 
						||
 | 
						||
// Look for preserved tab or newline in the given frame or its following
 | 
						||
// siblings on the same line, to determine whether justification should be
 | 
						||
// suppressed in order to avoid disrupting tab-stop positions.
 | 
						||
// Returns the first such preserved whitespace char, or 0 if none found.
 | 
						||
static char NextPreservedWhiteSpaceOnLine(nsIFrame* aSibling,
 | 
						||
                                          nsILineIterator* aLineIter,
 | 
						||
                                          int32_t aLineNum) {
 | 
						||
  while (aSibling) {
 | 
						||
    // If we find a <br>, treat it like a newline.
 | 
						||
    if (aSibling->IsBrFrame()) {
 | 
						||
      return '\n';
 | 
						||
    }
 | 
						||
    // If we've moved on to a later line, stop searching.
 | 
						||
    if (GetFrameLineNum(aSibling, aLineIter) > aLineNum) {
 | 
						||
      return 0;
 | 
						||
    }
 | 
						||
    // If we encounter an inline frame, recurse into it.
 | 
						||
    if (aSibling->IsInlineFrame()) {
 | 
						||
      auto* child = aSibling->PrincipalChildList().FirstChild();
 | 
						||
      char result = NextPreservedWhiteSpaceOnLine(child, aLineIter, aLineNum);
 | 
						||
      if (result) {
 | 
						||
        return result;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    // If we have a text frame, and whitespace is not collapsed, we need to
 | 
						||
    // check its contents.
 | 
						||
    if (aSibling->IsTextFrame()) {
 | 
						||
      const auto* textStyle = aSibling->StyleText();
 | 
						||
      if (textStyle->WhiteSpaceOrNewlineIsSignificant()) {
 | 
						||
        const auto* textFrame = static_cast<nsTextFrame*>(aSibling);
 | 
						||
        const auto* textFragment = textFrame->TextFragment();
 | 
						||
        for (auto i = textFrame->GetContentOffset();
 | 
						||
             i < textFrame->GetContentEnd(); ++i) {
 | 
						||
          const char16_t ch = textFragment->CharAt(i);
 | 
						||
          if (ch == '\n' && textStyle->NewlineIsSignificantStyle()) {
 | 
						||
            return '\n';
 | 
						||
          }
 | 
						||
          if (ch == '\t' && textStyle->WhiteSpaceIsSignificant()) {
 | 
						||
            return '\t';
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
    aSibling = aSibling->GetNextSibling();
 | 
						||
  }
 | 
						||
  return 0;
 | 
						||
}
 | 
						||
 | 
						||
static bool HasPreservedTabInFollowingSiblingOnLine(nsTextFrame* aFrame) {
 | 
						||
  bool foundTab = false;
 | 
						||
 | 
						||
  nsIFrame* lineContainer = FindLineContainer(aFrame);
 | 
						||
  nsILineIterator* iter = lineContainer->GetLineIterator();
 | 
						||
  int32_t line = GetFrameLineNum(aFrame, iter);
 | 
						||
  char ws = NextPreservedWhiteSpaceOnLine(aFrame->GetNextSibling(), iter, line);
 | 
						||
  if (ws == '\t') {
 | 
						||
    foundTab = true;
 | 
						||
  } else if (!ws) {
 | 
						||
    // Didn't find a preserved tab or newline in our siblings; if our parent
 | 
						||
    // (and its parent, etc) is an inline, we need to look at their following
 | 
						||
    // siblings, too, as long as they're on the same line.
 | 
						||
    const nsIFrame* maybeInline = aFrame->GetParent();
 | 
						||
    while (maybeInline && maybeInline->IsInlineFrame()) {
 | 
						||
      ws = NextPreservedWhiteSpaceOnLine(maybeInline->GetNextSibling(), iter,
 | 
						||
                                         line);
 | 
						||
      if (ws == '\t') {
 | 
						||
        foundTab = true;
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      if (ws == '\n') {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      maybeInline = maybeInline->GetParent();
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // We called lineContainer->GetLineIterator() above, but we mustn't
 | 
						||
  // allow a block frame to retain this iterator if we're currently in
 | 
						||
  // reflow, as it will become invalid as the line list is reflowed.
 | 
						||
  if (lineContainer->HasAnyStateBits(NS_FRAME_IN_REFLOW) &&
 | 
						||
      lineContainer->IsBlockFrameOrSubclass()) {
 | 
						||
    static_cast<nsBlockFrame*>(lineContainer)->ClearLineIterator();
 | 
						||
  }
 | 
						||
 | 
						||
  return foundTab;
 | 
						||
}
 | 
						||
 | 
						||
JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification(
 | 
						||
    Range aRange, nsTArray<JustificationAssignment>* aAssignments) {
 | 
						||
  JustificationInfo info;
 | 
						||
 | 
						||
  // Horizontal-in-vertical frame is orthogonal to the line, so it
 | 
						||
  // doesn't actually include any justification opportunity inside.
 | 
						||
  // The spec says such frame should be treated as a U+FFFC. Since we
 | 
						||
  // do not insert justification opportunities on the sides of that
 | 
						||
  // character, the sides of this frame are not justifiable either.
 | 
						||
  if (mFrame->Style()->IsTextCombined()) {
 | 
						||
    return info;
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t lastTab = -1;
 | 
						||
  if (StaticPrefs::layout_css_text_align_justify_only_after_last_tab()) {
 | 
						||
    // If there is a preserved tab on the line, we don't apply justification
 | 
						||
    // until we're past its position.
 | 
						||
    if (mTextStyle->WhiteSpaceIsSignificant()) {
 | 
						||
      // If there is a preserved newline within the text, we don't need to look
 | 
						||
      // beyond this frame, as following frames will not be on the same line.
 | 
						||
      int32_t newlinePos =
 | 
						||
          (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasNewline)
 | 
						||
              ? FindFirstNewlinePosition(mFrame)
 | 
						||
              : -1;
 | 
						||
      if (newlinePos < 0) {
 | 
						||
        // There's no preserved newline within this frame; if there's a tab
 | 
						||
        // in a later sibling frame on the same line, we won't apply any
 | 
						||
        // justification to this one.
 | 
						||
        if (HasPreservedTabInFollowingSiblingOnLine(mFrame)) {
 | 
						||
          return info;
 | 
						||
        }
 | 
						||
      }
 | 
						||
 | 
						||
      if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab) {
 | 
						||
        // Find last tab character in the content; we won't justify anything
 | 
						||
        // before that position, so that tab alignment remains correct.
 | 
						||
        lastTab = FindLastTabPositionBeforeNewline(mFrame, newlinePos);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  bool isCJ = IsChineseOrJapanese(mFrame);
 | 
						||
  nsSkipCharsRunIterator run(
 | 
						||
      mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
 | 
						||
  run.SetOriginalOffset(aRange.start);
 | 
						||
  mJustificationArrayStart = run.GetSkippedOffset();
 | 
						||
 | 
						||
  nsTArray<JustificationAssignment> assignments;
 | 
						||
  assignments.SetCapacity(aRange.Length());
 | 
						||
  while (run.NextRun()) {
 | 
						||
    uint32_t originalOffset = run.GetOriginalOffset();
 | 
						||
    uint32_t skippedOffset = run.GetSkippedOffset();
 | 
						||
    uint32_t length = run.GetRunLength();
 | 
						||
    assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
 | 
						||
 | 
						||
    gfxSkipCharsIterator iter = run.GetPos();
 | 
						||
    for (uint32_t i = 0; i < length; ++i) {
 | 
						||
      uint32_t offset = originalOffset + i;
 | 
						||
      if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ) ||
 | 
						||
          (lastTab >= 0 && offset <= uint32_t(lastTab))) {
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
 | 
						||
      iter.SetOriginalOffset(offset);
 | 
						||
 | 
						||
      FindClusterStart(mTextRun, originalOffset, &iter);
 | 
						||
      uint32_t firstCharOffset = iter.GetSkippedOffset();
 | 
						||
      uint32_t firstChar = firstCharOffset > mJustificationArrayStart
 | 
						||
                               ? firstCharOffset - mJustificationArrayStart
 | 
						||
                               : 0;
 | 
						||
      if (!firstChar) {
 | 
						||
        info.mIsStartJustifiable = true;
 | 
						||
      } else {
 | 
						||
        auto& assign = assignments[firstChar];
 | 
						||
        auto& prevAssign = assignments[firstChar - 1];
 | 
						||
        if (prevAssign.mGapsAtEnd) {
 | 
						||
          prevAssign.mGapsAtEnd = 1;
 | 
						||
          assign.mGapsAtStart = 1;
 | 
						||
        } else {
 | 
						||
          assign.mGapsAtStart = 2;
 | 
						||
          info.mInnerOpportunities++;
 | 
						||
        }
 | 
						||
      }
 | 
						||
 | 
						||
      FindClusterEnd(mTextRun, originalOffset + length, &iter);
 | 
						||
      uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
 | 
						||
      // Assign the two gaps temporary to the last char. If the next cluster is
 | 
						||
      // justifiable as well, one of the gaps will be removed by code above.
 | 
						||
      assignments[lastChar].mGapsAtEnd = 2;
 | 
						||
      info.mInnerOpportunities++;
 | 
						||
 | 
						||
      // Skip the whole cluster
 | 
						||
      i = iter.GetOriginalOffset() - originalOffset;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
 | 
						||
    // We counted the expansion opportunity after the last character,
 | 
						||
    // but it is not an inner opportunity.
 | 
						||
    MOZ_ASSERT(info.mInnerOpportunities > 0);
 | 
						||
    info.mInnerOpportunities--;
 | 
						||
    info.mIsEndJustifiable = true;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aAssignments) {
 | 
						||
    *aAssignments = std::move(assignments);
 | 
						||
  }
 | 
						||
  return info;
 | 
						||
}
 | 
						||
 | 
						||
// aStart, aLength in transformed string offsets
 | 
						||
void nsTextFrame::PropertyProvider::GetSpacing(Range aRange,
 | 
						||
                                               Spacing* aSpacing) const {
 | 
						||
  GetSpacingInternal(
 | 
						||
      aRange, aSpacing,
 | 
						||
      !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab));
 | 
						||
}
 | 
						||
 | 
						||
static bool CanAddSpacingBefore(const gfxTextRun* aTextRun, uint32_t aOffset,
 | 
						||
                                bool aNewlineIsSignificant) {
 | 
						||
  const auto* g = aTextRun->GetCharacterGlyphs();
 | 
						||
  MOZ_ASSERT(aOffset < aTextRun->GetLength());
 | 
						||
  if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  if (!aOffset) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  return g[aOffset].IsClusterStart() && g[aOffset].IsLigatureGroupStart() &&
 | 
						||
         !g[aOffset - 1].CharIsFormattingControl() && !g[aOffset].CharIsTab();
 | 
						||
}
 | 
						||
 | 
						||
static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset,
 | 
						||
                               bool aNewlineIsSignificant) {
 | 
						||
  const auto* g = aTextRun->GetCharacterGlyphs();
 | 
						||
  MOZ_ASSERT(aOffset < aTextRun->GetLength());
 | 
						||
  if (aNewlineIsSignificant && g[aOffset].CharIsNewline()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  if (aOffset + 1 >= aTextRun->GetLength()) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  return g[aOffset + 1].IsClusterStart() &&
 | 
						||
         g[aOffset + 1].IsLigatureGroupStart() &&
 | 
						||
         !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab();
 | 
						||
}
 | 
						||
 | 
						||
static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) {
 | 
						||
  const auto& tabSize = aFrame->StyleText()->mTabSize;
 | 
						||
  if (tabSize.IsLength()) {
 | 
						||
    nscoord w = tabSize.length._0.ToAppUnits();
 | 
						||
    MOZ_ASSERT(w >= 0);
 | 
						||
    return w;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(tabSize.IsNumber());
 | 
						||
  gfxFloat spaces = tabSize.number._0;
 | 
						||
  MOZ_ASSERT(spaces >= 0);
 | 
						||
 | 
						||
  const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay());
 | 
						||
  const auto* styleText = cb->StyleText();
 | 
						||
 | 
						||
  // Round the space width when converting to appunits the same way textruns do.
 | 
						||
  // We don't use GetFirstFontMetrics here because that may return a font that
 | 
						||
  // does not actually have the <space> character, yet is considered the "first
 | 
						||
  // available font" per CSS Fonts. Here, we want the font that would be used
 | 
						||
  // to render <space>, even if that means looking further down the font-family
 | 
						||
  // list.
 | 
						||
  RefPtr fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f);
 | 
						||
  bool vertical = cb->GetWritingMode().IsCentralBaseline();
 | 
						||
  RefPtr font = fm->GetThebesFontGroup()->GetFirstValidFont(' ');
 | 
						||
  auto metrics = font->GetMetrics(vertical ? nsFontMetrics::eVertical
 | 
						||
                                           : nsFontMetrics::eHorizontal);
 | 
						||
  nscoord spaceWidth = nscoord(
 | 
						||
      NS_round(metrics.spaceWidth * cb->PresContext()->AppUnitsPerDevPixel()));
 | 
						||
  return spaces *
 | 
						||
         (spaceWidth + styleText->mLetterSpacing.Resolve(fm->EmHeight()) +
 | 
						||
          styleText->mWordSpacing.Resolve(spaceWidth));
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange,
 | 
						||
                                                       Spacing* aSpacing,
 | 
						||
                                                       bool aIgnoreTabs) const {
 | 
						||
  MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
 | 
						||
 | 
						||
  uint32_t index;
 | 
						||
  for (index = 0; index < aRange.Length(); ++index) {
 | 
						||
    aSpacing[index].mBefore = 0.0;
 | 
						||
    aSpacing[index].mAfter = 0.0;
 | 
						||
  }
 | 
						||
 | 
						||
  if (mFrame->Style()->IsTextCombined()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Find our offset into the original+transformed string
 | 
						||
  gfxSkipCharsIterator start(mStart);
 | 
						||
  start.SetSkippedOffset(aRange.start);
 | 
						||
 | 
						||
  // First, compute the word and letter spacing
 | 
						||
  if (mWordSpacing || mLetterSpacing) {
 | 
						||
    // Iterate over non-skipped characters
 | 
						||
    nsSkipCharsRunIterator run(
 | 
						||
        start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
 | 
						||
    bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame);
 | 
						||
    // Which letter-spacing model are we using?
 | 
						||
    //   0 - Gecko legacy model, spacing added to trailing side of letter
 | 
						||
    //   1 - WebKit/Blink-compatible, spacing added to right-hand side
 | 
						||
    //   2 - Symmetrical spacing, half added to each side
 | 
						||
    gfxFloat before, after;
 | 
						||
    switch (StaticPrefs::layout_css_letter_spacing_model()) {
 | 
						||
      default:  // use Gecko legacy behavior if pref value is unknown
 | 
						||
      case 0:
 | 
						||
        before = 0.0;
 | 
						||
        after = mLetterSpacing;
 | 
						||
        break;
 | 
						||
      case 1:
 | 
						||
        if (mTextRun->IsRightToLeft()) {
 | 
						||
          before = mLetterSpacing;
 | 
						||
          after = 0.0;
 | 
						||
        } else {
 | 
						||
          before = 0.0;
 | 
						||
          after = mLetterSpacing;
 | 
						||
        }
 | 
						||
        break;
 | 
						||
      case 2:
 | 
						||
        before = mLetterSpacing / 2.0;
 | 
						||
        after = mLetterSpacing - before;
 | 
						||
        break;
 | 
						||
    }
 | 
						||
    bool atStart = mStartOfLineOffset == start.GetSkippedOffset() &&
 | 
						||
                   !mFrame->IsInSVGTextSubtree();
 | 
						||
    while (run.NextRun()) {
 | 
						||
      uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
 | 
						||
      gfxSkipCharsIterator iter = run.GetPos();
 | 
						||
      for (int32_t i = 0; i < run.GetRunLength(); ++i) {
 | 
						||
        if (!atStart && before != 0.0 &&
 | 
						||
            CanAddSpacingBefore(mTextRun, run.GetSkippedOffset() + i,
 | 
						||
                                newlineIsSignificant)) {
 | 
						||
          aSpacing[runOffsetInSubstring + i].mBefore += before;
 | 
						||
        }
 | 
						||
        if (after != 0.0 &&
 | 
						||
            CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i,
 | 
						||
                               newlineIsSignificant)) {
 | 
						||
          // End of a cluster, not in a ligature: put letter-spacing after it
 | 
						||
          aSpacing[runOffsetInSubstring + i].mAfter += after;
 | 
						||
        }
 | 
						||
        if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), mFrame,
 | 
						||
                                  mTextStyle)) {
 | 
						||
          // It kinda sucks, but space characters can be part of clusters,
 | 
						||
          // and even still be whitespace (I think!)
 | 
						||
          iter.SetSkippedOffset(run.GetSkippedOffset() + i);
 | 
						||
          FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
 | 
						||
                         &iter);
 | 
						||
          uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
 | 
						||
          aSpacing[runOffset].mAfter += mWordSpacing;
 | 
						||
        }
 | 
						||
        atStart = false;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Now add tab spacing, if there is any
 | 
						||
  if (!aIgnoreTabs) {
 | 
						||
    gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame);
 | 
						||
    if (tabWidth > 0) {
 | 
						||
      CalcTabWidths(aRange, tabWidth);
 | 
						||
      if (mTabWidths) {
 | 
						||
        mTabWidths->ApplySpacing(aSpacing,
 | 
						||
                                 aRange.start - mStart.GetSkippedOffset(),
 | 
						||
                                 aRange.Length());
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Now add in justification spacing
 | 
						||
  if (mJustificationSpacings.Length() > 0) {
 | 
						||
    // If there is any spaces trimmed at the end, aStart + aLength may
 | 
						||
    // be larger than the flags array. When that happens, we can simply
 | 
						||
    // ignore those spaces.
 | 
						||
    auto arrayEnd = mJustificationArrayStart +
 | 
						||
                    static_cast<uint32_t>(mJustificationSpacings.Length());
 | 
						||
    auto end = std::min(aRange.end, arrayEnd);
 | 
						||
    MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
 | 
						||
    for (auto i = aRange.start; i < end; i++) {
 | 
						||
      const auto& spacing =
 | 
						||
          mJustificationSpacings[i - mJustificationArrayStart];
 | 
						||
      uint32_t offset = i - aRange.start;
 | 
						||
      aSpacing[offset].mBefore += spacing.mBefore;
 | 
						||
      aSpacing[offset].mAfter += spacing.mAfter;
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// aX and the result are in whole appunits.
 | 
						||
static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth,
 | 
						||
                                 gfxFloat aMinAdvance) {
 | 
						||
  // Advance aX to the next multiple of aTabWidth. We must advance
 | 
						||
  // by at least aMinAdvance.
 | 
						||
  gfxFloat nextPos = aX + aMinAdvance;
 | 
						||
  return aTabWidth > 0.0 ? ceil(nextPos / aTabWidth) * aTabWidth : nextPos;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange,
 | 
						||
                                                  gfxFloat aTabWidth) const {
 | 
						||
  MOZ_ASSERT(aTabWidth > 0);
 | 
						||
 | 
						||
  if (!mTabWidths) {
 | 
						||
    if (mReflowing && !mLineContainer) {
 | 
						||
      // Intrinsic width computation does its own tab processing. We
 | 
						||
      // just don't do anything here.
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    if (!mReflowing) {
 | 
						||
      mTabWidths = mFrame->GetProperty(TabWidthProperty());
 | 
						||
#ifdef DEBUG
 | 
						||
      // If we're not reflowing, we should have already computed the
 | 
						||
      // tab widths; check that they're available as far as the last
 | 
						||
      // tab character present (if any)
 | 
						||
      for (uint32_t i = aRange.end; i > aRange.start; --i) {
 | 
						||
        if (mTextRun->CharIsTab(i - 1)) {
 | 
						||
          uint32_t startOffset = mStart.GetSkippedOffset();
 | 
						||
          NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
 | 
						||
                       "Precomputed tab widths are missing!");
 | 
						||
          break;
 | 
						||
        }
 | 
						||
      }
 | 
						||
#endif
 | 
						||
      return;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t startOffset = mStart.GetSkippedOffset();
 | 
						||
  MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
 | 
						||
  MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
 | 
						||
  uint32_t tabsEnd =
 | 
						||
      (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
 | 
						||
  if (tabsEnd < aRange.end) {
 | 
						||
    NS_ASSERTION(mReflowing,
 | 
						||
                 "We need precomputed tab widths, but don't have enough.");
 | 
						||
 | 
						||
    for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
 | 
						||
      Spacing spacing;
 | 
						||
      GetSpacingInternal(Range(i, i + 1), &spacing, true);
 | 
						||
      mOffsetFromBlockOriginForTabs += spacing.mBefore;
 | 
						||
 | 
						||
      if (!mTextRun->CharIsTab(i)) {
 | 
						||
        if (mTextRun->IsClusterStart(i)) {
 | 
						||
          uint32_t clusterEnd = i + 1;
 | 
						||
          while (clusterEnd < mTextRun->GetLength() &&
 | 
						||
                 !mTextRun->IsClusterStart(clusterEnd)) {
 | 
						||
            ++clusterEnd;
 | 
						||
          }
 | 
						||
          mOffsetFromBlockOriginForTabs +=
 | 
						||
              mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        if (!mTabWidths) {
 | 
						||
          mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
 | 
						||
          mFrame->SetProperty(TabWidthProperty(), mTabWidths);
 | 
						||
        }
 | 
						||
        double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
 | 
						||
                                          aTabWidth, MinTabAdvance());
 | 
						||
        mTabWidths->mWidths.AppendElement(
 | 
						||
            TabWidth(i - startOffset,
 | 
						||
                     NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
 | 
						||
        mOffsetFromBlockOriginForTabs = nextTab;
 | 
						||
      }
 | 
						||
 | 
						||
      mOffsetFromBlockOriginForTabs += spacing.mAfter;
 | 
						||
    }
 | 
						||
 | 
						||
    if (mTabWidths) {
 | 
						||
      mTabWidths->mLimit = aRange.end - startOffset;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (!mTabWidths) {
 | 
						||
    // Delete any stale property that may be left on the frame
 | 
						||
    mFrame->RemoveProperty(TabWidthProperty());
 | 
						||
    mTabWidthsAnalyzedLimit =
 | 
						||
        std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const {
 | 
						||
  if (mHyphenWidth < 0) {
 | 
						||
    const auto& hyphenateChar = mTextStyle->mHyphenateCharacter;
 | 
						||
    if (hyphenateChar.IsAuto()) {
 | 
						||
      mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
 | 
						||
    } else {
 | 
						||
      RefPtr<gfxTextRun> hyphRun = GetHyphenTextRun(mFrame, nullptr);
 | 
						||
      mHyphenWidth = hyphRun ? hyphRun->GetAdvanceWidth() : 0;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return mHyphenWidth + mLetterSpacing;
 | 
						||
}
 | 
						||
 | 
						||
static inline bool IS_HYPHEN(char16_t u) {
 | 
						||
  return u == char16_t('-') ||  // HYPHEN-MINUS
 | 
						||
         u == 0x058A ||         // ARMENIAN HYPHEN
 | 
						||
         u == 0x2010 ||         // HYPHEN
 | 
						||
         u == 0x2012 ||         // FIGURE DASH
 | 
						||
         u == 0x2013;           // EN DASH
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::GetHyphenationBreaks(
 | 
						||
    Range aRange, HyphenType* aBreakBefore) const {
 | 
						||
  MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
 | 
						||
  MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
 | 
						||
 | 
						||
  if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
 | 
						||
      mTextStyle->mHyphens == StyleHyphens::None) {
 | 
						||
    memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
 | 
						||
           aRange.Length() * sizeof(HyphenType));
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Iterate through the original-string character runs
 | 
						||
  nsSkipCharsRunIterator run(
 | 
						||
      mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
 | 
						||
  run.SetSkippedOffset(aRange.start);
 | 
						||
  // We need to visit skipped characters so that we can detect SHY
 | 
						||
  run.SetVisitSkipped();
 | 
						||
 | 
						||
  int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
 | 
						||
  bool allowHyphenBreakBeforeNextChar =
 | 
						||
      prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
 | 
						||
      prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
 | 
						||
      mFrag->CharAt(AssertedCast<uint32_t>(prevTrailingCharOffset)) == CH_SHY;
 | 
						||
 | 
						||
  while (run.NextRun()) {
 | 
						||
    NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
 | 
						||
    if (run.IsSkipped()) {
 | 
						||
      // Check if there's a soft hyphen which would let us hyphenate before
 | 
						||
      // the next non-skipped character. Don't look at soft hyphens followed
 | 
						||
      // by other skipped characters, we won't use them.
 | 
						||
      allowHyphenBreakBeforeNextChar =
 | 
						||
          mFrag->CharAt(AssertedCast<uint32_t>(
 | 
						||
              run.GetOriginalOffset() + run.GetRunLength() - 1)) == CH_SHY;
 | 
						||
    } else {
 | 
						||
      int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
 | 
						||
      memset(aBreakBefore + runOffsetInSubstring,
 | 
						||
             static_cast<uint8_t>(HyphenType::None),
 | 
						||
             run.GetRunLength() * sizeof(HyphenType));
 | 
						||
      // Don't allow hyphen breaks at the start of the line
 | 
						||
      aBreakBefore[runOffsetInSubstring] =
 | 
						||
          allowHyphenBreakBeforeNextChar &&
 | 
						||
                  (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) ||
 | 
						||
                   run.GetSkippedOffset() > mStart.GetSkippedOffset())
 | 
						||
              ? HyphenType::Soft
 | 
						||
              : HyphenType::None;
 | 
						||
      allowHyphenBreakBeforeNextChar = false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (mTextStyle->mHyphens == StyleHyphens::Auto) {
 | 
						||
    gfxSkipCharsIterator skipIter(mStart);
 | 
						||
    for (uint32_t i = 0; i < aRange.Length(); ++i) {
 | 
						||
      if (IS_HYPHEN(mFrag->CharAt(AssertedCast<uint32_t>(
 | 
						||
              skipIter.ConvertSkippedToOriginal(aRange.start + i))))) {
 | 
						||
        if (i < aRange.Length() - 1) {
 | 
						||
          aBreakBefore[i + 1] = HyphenType::Explicit;
 | 
						||
        }
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
 | 
						||
      if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
 | 
						||
          aBreakBefore[i] == HyphenType::None) {
 | 
						||
        aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) {
 | 
						||
  nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
 | 
						||
      mFrag, (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default
 | 
						||
                         : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter));
 | 
						||
  mStart.SetOriginalOffset(trimmed.mStart);
 | 
						||
  mLength = trimmed.mLength;
 | 
						||
  if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) {
 | 
						||
    mStartOfLineOffset = mStart.GetSkippedOffset();
 | 
						||
  }
 | 
						||
  SetupJustificationSpacing(true);
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::InitializeForMeasure() {
 | 
						||
  nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
 | 
						||
      mFrag, nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
 | 
						||
  mStart.SetOriginalOffset(trimmed.mStart);
 | 
						||
  mLength = trimmed.mLength;
 | 
						||
  if (mFrame->HasAnyStateBits(TEXT_START_OF_LINE)) {
 | 
						||
    mStartOfLineOffset = mStart.GetSkippedOffset();
 | 
						||
  }
 | 
						||
  SetupJustificationSpacing(false);
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
 | 
						||
    bool aPostReflow) {
 | 
						||
  MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
 | 
						||
 | 
						||
  if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator start(mStart), end(mStart);
 | 
						||
  // We can't just use our mLength here; when InitializeForDisplay is
 | 
						||
  // called with false for aTrimAfter, we still shouldn't be assigning
 | 
						||
  // justification space to any trailing whitespace.
 | 
						||
  nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
 | 
						||
      mFrag, (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
 | 
						||
                          : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
 | 
						||
  end.AdvanceOriginal(trimmed.mLength);
 | 
						||
  gfxSkipCharsIterator realEnd(end);
 | 
						||
 | 
						||
  Range range(uint32_t(start.GetOriginalOffset()),
 | 
						||
              uint32_t(end.GetOriginalOffset()));
 | 
						||
  nsTArray<JustificationAssignment> assignments;
 | 
						||
  JustificationInfo info = ComputeJustification(range, &assignments);
 | 
						||
 | 
						||
  auto assign = mFrame->GetJustificationAssignment();
 | 
						||
  auto totalGaps = JustificationUtils::CountGaps(info, assign);
 | 
						||
  if (!totalGaps || assignments.IsEmpty()) {
 | 
						||
    // Nothing to do, nothing is justifiable and we shouldn't have any
 | 
						||
    // justification space assigned
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Remember that textrun measurements are in the run's orientation,
 | 
						||
  // so its advance "width" is actually a height in vertical writing modes,
 | 
						||
  // corresponding to the inline-direction of the frame.
 | 
						||
  gfxFloat naturalWidth = mTextRun->GetAdvanceWidth(
 | 
						||
      Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this);
 | 
						||
  if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
 | 
						||
    naturalWidth += GetHyphenWidth();
 | 
						||
  }
 | 
						||
  nscoord totalSpacing = mFrame->ISize() - naturalWidth;
 | 
						||
  if (totalSpacing <= 0) {
 | 
						||
    // No space available
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  assignments[0].mGapsAtStart = assign.mGapsAtStart;
 | 
						||
  assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
 | 
						||
 | 
						||
  MOZ_ASSERT(mJustificationSpacings.IsEmpty());
 | 
						||
  JustificationApplicationState state(totalGaps, totalSpacing);
 | 
						||
  mJustificationSpacings.SetCapacity(assignments.Length());
 | 
						||
  for (const JustificationAssignment& assign : assignments) {
 | 
						||
    Spacing* spacing = mJustificationSpacings.AppendElement();
 | 
						||
    spacing->mBefore = state.Consume(assign.mGapsAtStart);
 | 
						||
    spacing->mAfter = state.Consume(assign.mGapsAtEnd);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
 | 
						||
  if (!mFontMetrics) {
 | 
						||
    if (mWhichTextRun == nsTextFrame::eInflated) {
 | 
						||
      mFontMetrics = mFrame->InflatedFontMetrics();
 | 
						||
    } else {
 | 
						||
      mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  mFontGroup = mFontMetrics->GetThebesFontGroup();
 | 
						||
}
 | 
						||
 | 
						||
#ifdef ACCESSIBILITY
 | 
						||
a11y::AccType nsTextFrame::AccessibleType() {
 | 
						||
  if (IsEmpty()) {
 | 
						||
    RenderedText text =
 | 
						||
        GetRenderedText(0, UINT32_MAX, TextOffsetType::OffsetsInContentText,
 | 
						||
                        TrailingWhitespace::DontTrim);
 | 
						||
    if (text.mString.IsEmpty()) {
 | 
						||
      return a11y::eNoType;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return a11y::eTextLeafType;
 | 
						||
}
 | 
						||
#endif
 | 
						||
 | 
						||
//-----------------------------------------------------------------------------
 | 
						||
void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
 | 
						||
                       nsIFrame* aPrevInFlow) {
 | 
						||
  NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
 | 
						||
  MOZ_ASSERT(aContent->IsText(), "Bogus content!");
 | 
						||
 | 
						||
  // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
 | 
						||
  // might be invalid if the content was modified while there was no frame
 | 
						||
  if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
 | 
						||
    aContent->RemoveProperty(nsGkAtoms::newline);
 | 
						||
    aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
 | 
						||
  }
 | 
						||
  if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
 | 
						||
    aContent->RemoveProperty(nsGkAtoms::flowlength);
 | 
						||
    aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
 | 
						||
  }
 | 
						||
 | 
						||
  // Since our content has a frame now, this flag is no longer needed.
 | 
						||
  aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
 | 
						||
 | 
						||
  // We're not a continuing frame.
 | 
						||
  // mContentOffset = 0; not necessary since we get zeroed out at init
 | 
						||
  nsIFrame::Init(aContent, aParent, aPrevInFlow);
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::ClearFrameOffsetCache() {
 | 
						||
  // See if we need to remove ourselves from the offset cache
 | 
						||
  if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) {
 | 
						||
    nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
 | 
						||
    if (primaryFrame) {
 | 
						||
      // The primary frame might be null here.  For example,
 | 
						||
      // nsLineBox::DeleteLineList just destroys the frames in order, which
 | 
						||
      // means that the primary frame is already dead if we're a continuing text
 | 
						||
      // frame, in which case, all of its properties are gone, and we don't need
 | 
						||
      // to worry about deleting this property here.
 | 
						||
      primaryFrame->RemoveProperty(OffsetToFrameProperty());
 | 
						||
    }
 | 
						||
    RemoveStateBits(TEXT_IN_OFFSET_CACHE);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::Destroy(DestroyContext& aContext) {
 | 
						||
  ClearFrameOffsetCache();
 | 
						||
 | 
						||
  // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
 | 
						||
  // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
 | 
						||
  // type might be changing.  Not clear whether it's worth it.
 | 
						||
  ClearTextRuns();
 | 
						||
  if (mNextContinuation) {
 | 
						||
    mNextContinuation->SetPrevInFlow(nullptr);
 | 
						||
  }
 | 
						||
  // Let the base class destroy the frame
 | 
						||
  nsIFrame::Destroy(aContext);
 | 
						||
}
 | 
						||
 | 
						||
nsTArray<nsTextFrame*>* nsTextFrame::GetContinuations() {
 | 
						||
  MOZ_ASSERT(NS_IsMainThread());
 | 
						||
  // Only for use on the primary frame, which has no prev-continuation.
 | 
						||
  MOZ_ASSERT(!GetPrevContinuation());
 | 
						||
  if (!mNextContinuation) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
  if (mPropertyFlags & PropertyFlags::Continuations) {
 | 
						||
    return GetProperty(ContinuationsProperty());
 | 
						||
  }
 | 
						||
  size_t count = 0;
 | 
						||
  for (nsIFrame* f = this; f; f = f->GetNextContinuation()) {
 | 
						||
    ++count;
 | 
						||
  }
 | 
						||
  auto* continuations = new nsTArray<nsTextFrame*>;
 | 
						||
  if (continuations->SetCapacity(count, fallible)) {
 | 
						||
    for (nsTextFrame* f = this; f;
 | 
						||
         f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
 | 
						||
      continuations->AppendElement(f);
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    delete continuations;
 | 
						||
    continuations = nullptr;
 | 
						||
  }
 | 
						||
  AddProperty(ContinuationsProperty(), continuations);
 | 
						||
  mPropertyFlags |= PropertyFlags::Continuations;
 | 
						||
  return continuations;
 | 
						||
}
 | 
						||
 | 
						||
class nsContinuingTextFrame final : public nsTextFrame {
 | 
						||
 public:
 | 
						||
  NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
 | 
						||
 | 
						||
  friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell,
 | 
						||
                                             ComputedStyle* aStyle);
 | 
						||
 | 
						||
  void Init(nsIContent* aContent, nsContainerFrame* aParent,
 | 
						||
            nsIFrame* aPrevInFlow) final;
 | 
						||
 | 
						||
  void Destroy(DestroyContext&) override;
 | 
						||
 | 
						||
  nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; }
 | 
						||
 | 
						||
  void SetPrevContinuation(nsIFrame* aPrevContinuation) final {
 | 
						||
    NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),
 | 
						||
                 "setting a prev continuation with incorrect type!");
 | 
						||
    NS_ASSERTION(
 | 
						||
        !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
 | 
						||
        "creating a loop in continuation chain!");
 | 
						||
    mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
 | 
						||
    RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
 | 
						||
    UpdateCachedContinuations();
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* GetPrevInFlow() const final {
 | 
						||
    return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation
 | 
						||
                                                           : nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  void SetPrevInFlow(nsIFrame* aPrevInFlow) final {
 | 
						||
    NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),
 | 
						||
                 "setting a prev in flow with incorrect type!");
 | 
						||
    NS_ASSERTION(
 | 
						||
        !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
 | 
						||
        "creating a loop in continuation chain!");
 | 
						||
    mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
 | 
						||
    AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
 | 
						||
    UpdateCachedContinuations();
 | 
						||
  }
 | 
						||
 | 
						||
  // Call this helper to update cache after mPrevContinuation is changed.
 | 
						||
  void UpdateCachedContinuations() {
 | 
						||
    nsTextFrame* prevFirst = mFirstContinuation;
 | 
						||
    if (mPrevContinuation) {
 | 
						||
      mFirstContinuation = mPrevContinuation->FirstContinuation();
 | 
						||
      if (mFirstContinuation) {
 | 
						||
        mFirstContinuation->ClearCachedContinuations();
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      mFirstContinuation = nullptr;
 | 
						||
    }
 | 
						||
    if (mFirstContinuation != prevFirst) {
 | 
						||
      if (prevFirst) {
 | 
						||
        prevFirst->ClearCachedContinuations();
 | 
						||
      }
 | 
						||
      auto* f = static_cast<nsContinuingTextFrame*>(mNextContinuation);
 | 
						||
      while (f) {
 | 
						||
        f->mFirstContinuation = mFirstContinuation;
 | 
						||
        f = static_cast<nsContinuingTextFrame*>(f->mNextContinuation);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  nsIFrame* FirstInFlow() const final;
 | 
						||
  nsTextFrame* FirstContinuation() const final {
 | 
						||
#if DEBUG
 | 
						||
    // If we have a prev-continuation pointer, then our first-continuation
 | 
						||
    // must be the same as that frame's.
 | 
						||
    if (mPrevContinuation) {
 | 
						||
      // If there's a prev-prev, then we can safely cast mPrevContinuation to
 | 
						||
      // an nsContinuingTextFrame and access its mFirstContinuation pointer
 | 
						||
      // directly, to avoid recursively calling FirstContinuation(), leading
 | 
						||
      // to exponentially-slow behavior in the assertion.
 | 
						||
      if (mPrevContinuation->GetPrevContinuation()) {
 | 
						||
        auto* prev = static_cast<nsContinuingTextFrame*>(mPrevContinuation);
 | 
						||
        MOZ_ASSERT(mFirstContinuation == prev->mFirstContinuation);
 | 
						||
      } else {
 | 
						||
        MOZ_ASSERT(mFirstContinuation ==
 | 
						||
                   mPrevContinuation->FirstContinuation());
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      MOZ_ASSERT(!mFirstContinuation);
 | 
						||
    }
 | 
						||
#endif
 | 
						||
    return mFirstContinuation;
 | 
						||
  };
 | 
						||
 | 
						||
  void AddInlineMinISize(const IntrinsicSizeInput& aInput,
 | 
						||
                         InlineMinISizeData* aData) final {
 | 
						||
    // Do nothing, since the first-in-flow accounts for everything.
 | 
						||
  }
 | 
						||
  void AddInlinePrefISize(const IntrinsicSizeInput& aInput,
 | 
						||
                          InlinePrefISizeData* aData) final {
 | 
						||
    // Do nothing, since the first-in-flow accounts for everything.
 | 
						||
  }
 | 
						||
 | 
						||
 protected:
 | 
						||
  explicit nsContinuingTextFrame(ComputedStyle* aStyle,
 | 
						||
                                 nsPresContext* aPresContext)
 | 
						||
      : nsTextFrame(aStyle, aPresContext, kClassID) {}
 | 
						||
 | 
						||
  nsTextFrame* mPrevContinuation = nullptr;
 | 
						||
  nsTextFrame* mFirstContinuation = nullptr;
 | 
						||
};
 | 
						||
 | 
						||
void nsContinuingTextFrame::Init(nsIContent* aContent,
 | 
						||
                                 nsContainerFrame* aParent,
 | 
						||
                                 nsIFrame* aPrevInFlow) {
 | 
						||
  NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
 | 
						||
 | 
						||
  // Hook the frame into the flow
 | 
						||
  nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
 | 
						||
  nsTextFrame* nextContinuation = prev->GetNextContinuation();
 | 
						||
  SetPrevInFlow(aPrevInFlow);
 | 
						||
  aPrevInFlow->SetNextInFlow(this);
 | 
						||
 | 
						||
  // NOTE: bypassing nsTextFrame::Init!!!
 | 
						||
  nsIFrame::Init(aContent, aParent, aPrevInFlow);
 | 
						||
 | 
						||
  mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
 | 
						||
  NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
 | 
						||
               "Creating ContinuingTextFrame, but there is no more content");
 | 
						||
  if (prev->Style() != Style()) {
 | 
						||
    // We're taking part of prev's text, and its style may be different
 | 
						||
    // so clear its textrun which may no longer be valid (and don't set ours)
 | 
						||
    prev->ClearTextRuns();
 | 
						||
  } else {
 | 
						||
    float inflation = prev->GetFontSizeInflation();
 | 
						||
    SetFontSizeInflation(inflation);
 | 
						||
    mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
 | 
						||
    if (inflation != 1.0f) {
 | 
						||
      gfxTextRun* uninflatedTextRun =
 | 
						||
          prev->GetTextRun(nsTextFrame::eNotInflated);
 | 
						||
      if (uninflatedTextRun) {
 | 
						||
        SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) {
 | 
						||
    FrameBidiData bidiData = aPrevInFlow->GetBidiData();
 | 
						||
    bidiData.precedingControl = kBidiLevelNone;
 | 
						||
    SetProperty(BidiDataProperty(), bidiData);
 | 
						||
 | 
						||
    if (nextContinuation) {
 | 
						||
      SetNextContinuation(nextContinuation);
 | 
						||
      nextContinuation->SetPrevContinuation(this);
 | 
						||
      // Adjust next-continuations' content offset as needed.
 | 
						||
      while (nextContinuation &&
 | 
						||
             nextContinuation->GetContentOffset() < mContentOffset) {
 | 
						||
#ifdef DEBUG
 | 
						||
        FrameBidiData nextBidiData = nextContinuation->GetBidiData();
 | 
						||
        NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&
 | 
						||
                         bidiData.baseLevel == nextBidiData.baseLevel,
 | 
						||
                     "stealing text from different type of BIDI continuation");
 | 
						||
        MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,
 | 
						||
                   "There shouldn't be any virtual bidi formatting character "
 | 
						||
                   "between continuations");
 | 
						||
#endif
 | 
						||
        nextContinuation->mContentOffset = mContentOffset;
 | 
						||
        nextContinuation = nextContinuation->GetNextContinuation();
 | 
						||
      }
 | 
						||
    }
 | 
						||
    AddStateBits(NS_FRAME_IS_BIDI);
 | 
						||
  }  // prev frame is bidi
 | 
						||
}
 | 
						||
 | 
						||
void nsContinuingTextFrame::Destroy(DestroyContext& aContext) {
 | 
						||
  ClearFrameOffsetCache();
 | 
						||
 | 
						||
  // The text associated with this frame will become associated with our
 | 
						||
  // prev-continuation. If that means the text has changed style, then
 | 
						||
  // we need to wipe out the text run for the text.
 | 
						||
  // Note that mPrevContinuation can be null if we're destroying the whole
 | 
						||
  // frame chain from the start to the end.
 | 
						||
  // If this frame is mentioned in the userData for a textrun (say
 | 
						||
  // because there's a direction change at the start of this frame), then
 | 
						||
  // we have to clear the textrun because we're going away and the
 | 
						||
  // textrun had better not keep a dangling reference to us.
 | 
						||
  if (IsInTextRunUserData() ||
 | 
						||
      (mPrevContinuation && mPrevContinuation->Style() != Style())) {
 | 
						||
    ClearTextRuns();
 | 
						||
    // Clear the previous continuation's text run also, so that it can rebuild
 | 
						||
    // the text run to include our text.
 | 
						||
    if (mPrevContinuation) {
 | 
						||
      mPrevContinuation->ClearTextRuns();
 | 
						||
    }
 | 
						||
  }
 | 
						||
  nsSplittableFrame::RemoveFromFlow(this);
 | 
						||
  // Let the base class destroy the frame
 | 
						||
  nsIFrame::Destroy(aContext);
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame* nsContinuingTextFrame::FirstInFlow() const {
 | 
						||
  // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
 | 
						||
  nsIFrame *firstInFlow,
 | 
						||
      *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this));
 | 
						||
  do {
 | 
						||
    firstInFlow = previous;
 | 
						||
    previous = firstInFlow->GetPrevInFlow();
 | 
						||
  } while (previous);
 | 
						||
  MOZ_ASSERT(firstInFlow, "post-condition failed");
 | 
						||
  return firstInFlow;
 | 
						||
}
 | 
						||
 | 
						||
// XXX Do we want to do all the work for the first-in-flow or do the
 | 
						||
// work for each part?  (Be careful of first-letter / first-line, though,
 | 
						||
// especially first-line!)  Doing all the work on the first-in-flow has
 | 
						||
// the advantage of avoiding the potential for incremental reflow bugs,
 | 
						||
// but depends on our maintining the frame tree in reasonable ways even
 | 
						||
// for edge cases (block-within-inline splits, nextBidi, etc.)
 | 
						||
 | 
						||
// XXX We really need to make :first-letter happen during frame
 | 
						||
// construction.
 | 
						||
 | 
						||
nscoord nsTextFrame::IntrinsicISize(const IntrinsicSizeInput& aInput,
 | 
						||
                                    IntrinsicISizeType aType) {
 | 
						||
  return IntrinsicISizeFromInline(aInput, aType);
 | 
						||
}
 | 
						||
 | 
						||
//----------------------------------------------------------------------
 | 
						||
 | 
						||
#if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
 | 
						||
static void VerifyNotDirty(nsFrameState state) {
 | 
						||
  bool isZero = state & NS_FRAME_FIRST_REFLOW;
 | 
						||
  bool isDirty = state & NS_FRAME_IS_DIRTY;
 | 
						||
  if (!isZero && isDirty) {
 | 
						||
    NS_WARNING("internal offsets may be out-of-sync");
 | 
						||
  }
 | 
						||
}
 | 
						||
#  define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state)
 | 
						||
#else
 | 
						||
#  define DEBUG_VERIFY_NOT_DIRTY(state)
 | 
						||
#endif
 | 
						||
 | 
						||
nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
 | 
						||
  return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext());
 | 
						||
}
 | 
						||
 | 
						||
NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
 | 
						||
 | 
						||
nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell,
 | 
						||
                                    ComputedStyle* aStyle) {
 | 
						||
  return new (aPresShell)
 | 
						||
      nsContinuingTextFrame(aStyle, aPresShell->GetPresContext());
 | 
						||
}
 | 
						||
 | 
						||
NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
 | 
						||
 | 
						||
nsTextFrame::~nsTextFrame() = default;
 | 
						||
 | 
						||
nsIFrame::Cursor nsTextFrame::GetCursor(const nsPoint& aPoint) {
 | 
						||
  StyleCursorKind kind = StyleUI()->Cursor().keyword;
 | 
						||
  if (kind == StyleCursorKind::Auto) {
 | 
						||
    if (!IsSelectable(nullptr)) {
 | 
						||
      kind = StyleCursorKind::Default;
 | 
						||
    } else {
 | 
						||
      kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
 | 
						||
                                           : StyleCursorKind::Text;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return Cursor{kind, AllowCustomCursorImage::Yes};
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame* nsTextFrame::LastInFlow() const {
 | 
						||
  nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
 | 
						||
  while (lastInFlow->GetNextInFlow()) {
 | 
						||
    lastInFlow = lastInFlow->GetNextInFlow();
 | 
						||
  }
 | 
						||
  MOZ_ASSERT(lastInFlow, "post-condition failed");
 | 
						||
  return lastInFlow;
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame* nsTextFrame::LastContinuation() const {
 | 
						||
  nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
 | 
						||
  while (lastContinuation->mNextContinuation) {
 | 
						||
    lastContinuation = lastContinuation->mNextContinuation;
 | 
						||
  }
 | 
						||
  MOZ_ASSERT(lastContinuation, "post-condition failed");
 | 
						||
  return lastContinuation;
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::ShouldSuppressLineBreak() const {
 | 
						||
  // If the parent frame of the text frame is ruby content box, it must
 | 
						||
  // suppress line break inside. This check is necessary, because when
 | 
						||
  // a whitespace is only contained by pseudo ruby frames, its style
 | 
						||
  // context won't have SuppressLineBreak bit set.
 | 
						||
  if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  return Style()->ShouldSuppressLineBreak();
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey,
 | 
						||
                                  bool aRebuildDisplayItems) {
 | 
						||
  InvalidateSelectionState();
 | 
						||
 | 
						||
  if (IsInSVGTextSubtree()) {
 | 
						||
    nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
 | 
						||
        GetParent(), LayoutFrameType::SVGText);
 | 
						||
    svgTextFrame->InvalidateFrame();
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect,
 | 
						||
                                          uint32_t aDisplayItemKey,
 | 
						||
                                          bool aRebuildDisplayItems) {
 | 
						||
  InvalidateSelectionState();
 | 
						||
 | 
						||
  if (IsInSVGTextSubtree()) {
 | 
						||
    nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
 | 
						||
        GetParent(), LayoutFrameType::SVGText);
 | 
						||
    svgTextFrame->InvalidateFrame();
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
 | 
						||
                                    aRebuildDisplayItems);
 | 
						||
}
 | 
						||
 | 
						||
gfxTextRun* nsTextFrame::GetUninflatedTextRun() const {
 | 
						||
  return GetProperty(UninflatedTextRunProperty());
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
 | 
						||
                             float aInflation) {
 | 
						||
  NS_ASSERTION(aTextRun, "must have text run");
 | 
						||
 | 
						||
  // Our inflated text run is always stored in mTextRun.  In the cases
 | 
						||
  // where our current inflation is not 1.0, however, we store two text
 | 
						||
  // runs, and the uninflated one goes in a frame property.  We never
 | 
						||
  // store a single text run in both.
 | 
						||
  if (aWhichTextRun == eInflated) {
 | 
						||
    if (HasFontSizeInflation() && aInflation == 1.0f) {
 | 
						||
      // FIXME: Probably shouldn't do this within each SetTextRun
 | 
						||
      // method, but it doesn't hurt.
 | 
						||
      ClearTextRun(nullptr, nsTextFrame::eNotInflated);
 | 
						||
    }
 | 
						||
    SetFontSizeInflation(aInflation);
 | 
						||
  } else {
 | 
						||
    MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation");
 | 
						||
    if (HasFontSizeInflation()) {
 | 
						||
      // Setting the property will not automatically increment the textrun's
 | 
						||
      // reference count, so we need to do it here.
 | 
						||
      aTextRun->AddRef();
 | 
						||
      SetProperty(UninflatedTextRunProperty(), aTextRun);
 | 
						||
      return;
 | 
						||
    }
 | 
						||
    // fall through to setting mTextRun
 | 
						||
  }
 | 
						||
 | 
						||
  mTextRun = aTextRun;
 | 
						||
 | 
						||
  // FIXME: Add assertions testing the relationship between
 | 
						||
  // GetFontSizeInflation() and whether we have an uninflated text run
 | 
						||
  // (but be aware that text runs can go away).
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) {
 | 
						||
  if (aTextRun == mTextRun) {
 | 
						||
    mTextRun = nullptr;
 | 
						||
    mFontMetrics = nullptr;
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) &&
 | 
						||
      GetProperty(UninflatedTextRunProperty()) == aTextRun) {
 | 
						||
    RemoveProperty(UninflatedTextRunProperty());
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
 | 
						||
                               TextRunType aWhichTextRun) {
 | 
						||
  RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
 | 
						||
  if (!textRun) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aWhichTextRun == nsTextFrame::eInflated) {
 | 
						||
    mFontMetrics = nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  DebugOnly<bool> checkmTextrun = textRun == mTextRun;
 | 
						||
  UnhookTextRunFromFrames(textRun, aStartContinuation);
 | 
						||
  MOZ_ASSERT(checkmTextrun ? !mTextRun
 | 
						||
                           : !GetProperty(UninflatedTextRunProperty()));
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::DisconnectTextRuns() {
 | 
						||
  MOZ_ASSERT(!IsInTextRunUserData(),
 | 
						||
             "Textrun mentions this frame in its user data so we can't just "
 | 
						||
             "disconnect");
 | 
						||
  mTextRun = nullptr;
 | 
						||
  if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) {
 | 
						||
    RemoveProperty(UninflatedTextRunProperty());
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) {
 | 
						||
  MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree());
 | 
						||
 | 
						||
  MarkIntrinsicISizesDirty();
 | 
						||
 | 
						||
  // This is to avoid making a new Reflow request in CharacterDataChanged:
 | 
						||
  for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
 | 
						||
    f->MarkSubtreeDirty();
 | 
						||
    f->mReflowRequestedForCharDataChange = true;
 | 
						||
  }
 | 
						||
 | 
						||
  // Pretend that all the text changed.
 | 
						||
  CharacterDataChangeInfo info;
 | 
						||
  info.mAppend = false;
 | 
						||
  info.mChangeStart = 0;
 | 
						||
  info.mChangeEnd = aOldLength;
 | 
						||
  info.mReplaceLength = GetContent()->TextLength();
 | 
						||
  CharacterDataChanged(info);
 | 
						||
}
 | 
						||
 | 
						||
nsresult nsTextFrame::CharacterDataChanged(
 | 
						||
    const CharacterDataChangeInfo& aInfo) {
 | 
						||
  if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
 | 
						||
    mContent->RemoveProperty(nsGkAtoms::newline);
 | 
						||
    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
 | 
						||
  }
 | 
						||
  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
 | 
						||
    mContent->RemoveProperty(nsGkAtoms::flowlength);
 | 
						||
    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
 | 
						||
  }
 | 
						||
 | 
						||
  // Find the first frame whose text has changed. Frames that are entirely
 | 
						||
  // before the text change are completely unaffected.
 | 
						||
  nsTextFrame* next;
 | 
						||
  nsTextFrame* textFrame = this;
 | 
						||
  while (true) {
 | 
						||
    next = textFrame->GetNextContinuation();
 | 
						||
    if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    textFrame = next;
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
 | 
						||
 | 
						||
  // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
 | 
						||
  // had already received an earlier FrameNeedsReflow call).
 | 
						||
  // (For subsequent frames with this same parent, we can just set their
 | 
						||
  // dirty bit without bothering to call FrameNeedsReflow again.)
 | 
						||
  nsIFrame* lastDirtiedFrameParent = nullptr;
 | 
						||
 | 
						||
  mozilla::PresShell* presShell = PresShell();
 | 
						||
  do {
 | 
						||
    // textFrame contained deleted text (or the insertion point,
 | 
						||
    // if this was a pure insertion).
 | 
						||
    textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
 | 
						||
    textFrame->ClearTextRuns();
 | 
						||
 | 
						||
    nsIFrame* parentOfTextFrame = textFrame->GetParent();
 | 
						||
    bool areAncestorsAwareOfReflowRequest = false;
 | 
						||
    if (lastDirtiedFrameParent == parentOfTextFrame) {
 | 
						||
      // An earlier iteration of this loop already called
 | 
						||
      // FrameNeedsReflow for a sibling of |textFrame|.
 | 
						||
      areAncestorsAwareOfReflowRequest = true;
 | 
						||
    } else {
 | 
						||
      lastDirtiedFrameParent = parentOfTextFrame;
 | 
						||
    }
 | 
						||
 | 
						||
    if (textFrame->mReflowRequestedForCharDataChange) {
 | 
						||
      // We already requested a reflow for this frame; nothing to do.
 | 
						||
      MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),
 | 
						||
                 "mReflowRequestedForCharDataChange should only be set "
 | 
						||
                 "on dirty frames");
 | 
						||
    } else {
 | 
						||
      // Make sure textFrame is queued up for a reflow.  Also set a flag so we
 | 
						||
      // don't waste time doing this again in repeated calls to this method.
 | 
						||
      textFrame->mReflowRequestedForCharDataChange = true;
 | 
						||
      if (!areAncestorsAwareOfReflowRequest) {
 | 
						||
        // Ask the parent frame to reflow me.
 | 
						||
        presShell->FrameNeedsReflow(
 | 
						||
            textFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
 | 
						||
            NS_FRAME_IS_DIRTY);
 | 
						||
      } else {
 | 
						||
        // We already called FrameNeedsReflow on behalf of an earlier sibling,
 | 
						||
        // so we can just mark this frame as dirty and don't need to bother
 | 
						||
        // telling its ancestors.
 | 
						||
        // Note: if the parent is a block, we're cheating here because we should
 | 
						||
        // be marking our line dirty, but we're not. nsTextFrame::SetLength will
 | 
						||
        // do that when it gets called during reflow.
 | 
						||
        textFrame->MarkSubtreeDirty();
 | 
						||
      }
 | 
						||
    }
 | 
						||
    textFrame->InvalidateFrame();
 | 
						||
 | 
						||
    // Below, frames that start after the deleted text will be adjusted so that
 | 
						||
    // their offsets move with the trailing unchanged text. If this change
 | 
						||
    // deletes more text than it inserts, those frame offsets will decrease.
 | 
						||
    // We need to maintain the invariant that mContentOffset is non-decreasing
 | 
						||
    // along the continuation chain. So we need to ensure that frames that
 | 
						||
    // started in the deleted text are all still starting before the
 | 
						||
    // unchanged text.
 | 
						||
    if (textFrame->mContentOffset > endOfChangedText) {
 | 
						||
      textFrame->mContentOffset = endOfChangedText;
 | 
						||
    }
 | 
						||
 | 
						||
    textFrame = textFrame->GetNextContinuation();
 | 
						||
  } while (textFrame &&
 | 
						||
           textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
 | 
						||
 | 
						||
  // This is how much the length of the string changed by --- i.e.,
 | 
						||
  // how much the trailing unchanged text moved.
 | 
						||
  int32_t sizeChange =
 | 
						||
      aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
 | 
						||
 | 
						||
  if (sizeChange) {
 | 
						||
    // Fix the offsets of the text frames that start in the trailing
 | 
						||
    // unchanged text.
 | 
						||
    while (textFrame) {
 | 
						||
      textFrame->mContentOffset += sizeChange;
 | 
						||
      // XXX we could rescue some text runs by adjusting their user data
 | 
						||
      // to reflect the change in DOM offsets
 | 
						||
      textFrame->ClearTextRuns();
 | 
						||
      textFrame = textFrame->GetNextContinuation();
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return NS_OK;
 | 
						||
}
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty, float)
 | 
						||
 | 
						||
float nsTextFrame::GetTextCombineScaleFactor(nsTextFrame* aFrame) {
 | 
						||
  float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
 | 
						||
  return factor ? factor : 1.0f;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
 | 
						||
                                   const nsDisplayListSet& aLists) {
 | 
						||
  if (!IsVisibleForPainting()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
 | 
						||
 | 
						||
  const nsStyleText* st = StyleText();
 | 
						||
  bool isTextTransparent =
 | 
						||
      NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 &&
 | 
						||
      NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0;
 | 
						||
  if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) ||
 | 
						||
       (isTextTransparent && !StyleText()->HasTextShadow())) &&
 | 
						||
      aBuilder->IsForPainting() && !IsInSVGTextSubtree()) {
 | 
						||
    if (!IsSelected()) {
 | 
						||
      TextDecorations textDecs;
 | 
						||
      GetTextDecorations(PresContext(), eResolvedColors, textDecs);
 | 
						||
      if (!textDecs.HasDecorationLines()) {
 | 
						||
        if (auto* currentPresContext = aBuilder->CurrentPresContext()) {
 | 
						||
          currentPresContext->SetBuiltInvisibleText();
 | 
						||
        }
 | 
						||
        return;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this);
 | 
						||
}
 | 
						||
 | 
						||
UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() {
 | 
						||
  const nsFrameSelection* frameSelection = GetConstFrameSelection();
 | 
						||
  if (frameSelection->IsInTableSelectionMode()) {
 | 
						||
    return nullptr;
 | 
						||
  }
 | 
						||
  UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection(
 | 
						||
      mContent, GetContentOffset(), GetContentLength(), false);
 | 
						||
  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
 | 
						||
    sd->mStart += mContentOffset;
 | 
						||
    sd->mEnd += mContentOffset;
 | 
						||
  }
 | 
						||
  return details;
 | 
						||
}
 | 
						||
 | 
						||
static void PaintSelectionBackground(
 | 
						||
    DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect,
 | 
						||
    const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) {
 | 
						||
  Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
 | 
						||
  MaybeSnapToDevicePixels(rect, aDrawTarget);
 | 
						||
 | 
						||
  if (aCallbacks) {
 | 
						||
    aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
 | 
						||
  } else {
 | 
						||
    ColorPattern color(ToDeviceColor(aColor));
 | 
						||
    aDrawTarget.FillRect(rect, color);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// Attempt to get the LineBaselineOffset property of aChildFrame
 | 
						||
// If not set, calculate this value for all child frames of aBlockFrame
 | 
						||
static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame,
 | 
						||
                                         nsBlockFrame* aBlockFrame) {
 | 
						||
  bool offsetFound;
 | 
						||
  nscoord offset =
 | 
						||
      aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound);
 | 
						||
 | 
						||
  if (!offsetFound) {
 | 
						||
    for (const auto& line : aBlockFrame->Lines()) {
 | 
						||
      if (line.IsInline()) {
 | 
						||
        int32_t n = line.GetChildCount();
 | 
						||
        nscoord lineBaseline = line.BStart() + line.GetLogicalAscent();
 | 
						||
        for (auto* lineFrame = line.mFirstChild; n > 0;
 | 
						||
             lineFrame = lineFrame->GetNextSibling(), --n) {
 | 
						||
          offset = lineBaseline - lineFrame->GetNormalPosition().y;
 | 
						||
          lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
    return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(),
 | 
						||
                                    &offsetFound);
 | 
						||
  } else {
 | 
						||
    return offset;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static bool IsUnderlineRight(const ComputedStyle& aStyle) {
 | 
						||
  // Check for 'left' or 'right' explicitly specified in the property;
 | 
						||
  // if neither is there, we use auto positioning based on lang.
 | 
						||
  const auto position = aStyle.StyleText()->mTextUnderlinePosition;
 | 
						||
  if (position.IsLeft()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  if (position.IsRight()) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  // If neither 'left' nor 'right' was specified, check the language.
 | 
						||
  nsAtom* langAtom = aStyle.StyleFont()->mLanguage;
 | 
						||
  if (!langAtom) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  return nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ja") ||
 | 
						||
         nsStyleUtil::MatchesLanguagePrefix(langAtom, u"ko") ||
 | 
						||
         nsStyleUtil::MatchesLanguagePrefix(langAtom, u"mn");
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::GetTextDecorations(
 | 
						||
    nsPresContext* aPresContext,
 | 
						||
    nsTextFrame::TextDecorationColorResolution aColorResolution,
 | 
						||
    nsTextFrame::TextDecorations& aDecorations) {
 | 
						||
  const nsCompatibility compatMode = aPresContext->CompatibilityMode();
 | 
						||
 | 
						||
  bool useOverride = false;
 | 
						||
  nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
 | 
						||
 | 
						||
  bool nearestBlockFound = false;
 | 
						||
  // Use writing mode of parent frame for orthogonal text frame to work.
 | 
						||
  // See comment in nsTextFrame::DrawTextRunAndDecorations.
 | 
						||
  WritingMode wm = GetParent()->GetWritingMode();
 | 
						||
  bool vertical = wm.IsVertical();
 | 
						||
 | 
						||
  nscoord ascent = GetLogicalBaseline(wm);
 | 
						||
  // physicalBlockStartOffset represents the offset from our baseline
 | 
						||
  // to f's physical block start, which is top in horizontal writing
 | 
						||
  // mode, and left in vertical writing modes, in our coordinate space.
 | 
						||
  // This physical block start is logical block start in most cases,
 | 
						||
  // but for vertical-rl, it is logical block end, and consequently in
 | 
						||
  // that case, it starts from the descent instead of ascent.
 | 
						||
  nscoord physicalBlockStartOffset =
 | 
						||
      wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
 | 
						||
  // baselineOffset represents the offset from our baseline to f's baseline or
 | 
						||
  // the nearest block's baseline, in our coordinate space, whichever is closest
 | 
						||
  // during the particular iteration
 | 
						||
  nscoord baselineOffset = 0;
 | 
						||
 | 
						||
  for (nsIFrame *f = this, *fChild = nullptr; f;
 | 
						||
       fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
 | 
						||
    ComputedStyle* const context = f->Style();
 | 
						||
    if (!context->HasTextDecorationLines()) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
 | 
						||
    if (context->GetPseudoType() == PseudoStyleType::marker &&
 | 
						||
        (context->StyleList()->mListStylePosition ==
 | 
						||
             StyleListStylePosition::Outside ||
 | 
						||
         !context->StyleDisplay()->IsInlineOutsideStyle())) {
 | 
						||
      // Outside ::marker pseudos, and inside markers that aren't inlines, don't
 | 
						||
      // have text decorations.
 | 
						||
      break;
 | 
						||
    }
 | 
						||
 | 
						||
    const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
 | 
						||
    StyleTextDecorationLine textDecorations =
 | 
						||
        styleTextReset->mTextDecorationLine;
 | 
						||
    bool ignoreSubproperties = false;
 | 
						||
 | 
						||
    auto lineStyle = styleTextReset->mTextDecorationStyle;
 | 
						||
    if (textDecorations == StyleTextDecorationLine::SPELLING_ERROR ||
 | 
						||
        textDecorations == StyleTextDecorationLine::GRAMMAR_ERROR) {
 | 
						||
      nscolor lineColor;
 | 
						||
      float relativeSize;
 | 
						||
      useOverride = nsTextPaintStyle::GetSelectionUnderline(
 | 
						||
          this, nsTextPaintStyle::SelectionStyleIndex::SpellChecker, &lineColor,
 | 
						||
          &relativeSize, &lineStyle);
 | 
						||
      if (useOverride) {
 | 
						||
        // We don't currently have a SelectionStyleIndex::GrammarChecker; for
 | 
						||
        // now just use SpellChecker and change its color to green.
 | 
						||
        overrideColor =
 | 
						||
            textDecorations == StyleTextDecorationLine::SPELLING_ERROR
 | 
						||
                ? lineColor
 | 
						||
                : NS_RGBA(0, 128, 0, 255);
 | 
						||
        textDecorations = StyleTextDecorationLine::UNDERLINE;
 | 
						||
        ignoreSubproperties = true;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    if (!useOverride &&
 | 
						||
        (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) {
 | 
						||
      // This handles the <a href="blah.html"><font color="green">La
 | 
						||
      // la la</font></a> case. The link underline should be green.
 | 
						||
      useOverride = true;
 | 
						||
      overrideColor = nsLayoutUtils::GetTextColor(
 | 
						||
          f, &nsStyleTextReset::mTextDecorationColor);
 | 
						||
    }
 | 
						||
 | 
						||
    nsBlockFrame* fBlock = do_QueryFrame(f);
 | 
						||
    const bool firstBlock = !nearestBlockFound && fBlock;
 | 
						||
 | 
						||
    // Not updating positions once we hit a parent block is equivalent to
 | 
						||
    // the CSS 2.1 spec that blocks should propagate decorations down to their
 | 
						||
    // children (albeit the style should be preserved)
 | 
						||
    // However, if we're vertically aligned within a block, then we need to
 | 
						||
    // recover the correct baseline from the line by querying the FrameProperty
 | 
						||
    // that should be set (see nsLineLayout::VerticalAlignLine).
 | 
						||
    if (firstBlock) {
 | 
						||
      // At this point, fChild can't be null since TextFrames can't be blocks
 | 
						||
      Maybe<StyleVerticalAlignKeyword> verticalAlign =
 | 
						||
          fChild->VerticalAlignEnum();
 | 
						||
      if (verticalAlign != Some(StyleVerticalAlignKeyword::Baseline)) {
 | 
						||
        // Since offset is the offset in the child's coordinate space, we have
 | 
						||
        // to undo the accumulation to bring the transform out of the block's
 | 
						||
        // coordinate space
 | 
						||
        const nscoord lineBaselineOffset =
 | 
						||
            LazyGetLineBaselineOffset(fChild, fBlock);
 | 
						||
 | 
						||
        baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
 | 
						||
                         (vertical ? fChild->GetNormalPosition().x
 | 
						||
                                   : fChild->GetNormalPosition().y);
 | 
						||
      }
 | 
						||
    } else if (!nearestBlockFound) {
 | 
						||
      // offset here is the offset from f's baseline to f's top/left
 | 
						||
      // boundary. It's descent for vertical-rl, and ascent otherwise.
 | 
						||
      nscoord offset = wm.IsVerticalRL()
 | 
						||
                           ? f->GetSize().width - f->GetLogicalBaseline(wm)
 | 
						||
                           : f->GetLogicalBaseline(wm);
 | 
						||
      baselineOffset = physicalBlockStartOffset - offset;
 | 
						||
    }
 | 
						||
 | 
						||
    nearestBlockFound = nearestBlockFound || firstBlock;
 | 
						||
    physicalBlockStartOffset +=
 | 
						||
        vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
 | 
						||
 | 
						||
    if (textDecorations) {
 | 
						||
      nscolor color;
 | 
						||
      if (useOverride) {
 | 
						||
        color = overrideColor;
 | 
						||
      } else if (IsInSVGTextSubtree()) {
 | 
						||
        // XXX We might want to do something with text-decoration-color when
 | 
						||
        //     painting SVG text, but it's not clear what we should do.  We
 | 
						||
        //     at least need SVG text decorations to paint with 'fill' if
 | 
						||
        //     text-decoration-color has its initial value currentColor.
 | 
						||
        //     We could choose to interpret currentColor as "currentFill"
 | 
						||
        //     for SVG text, and have e.g. text-decoration-color:red to
 | 
						||
        //     override the fill paint of the decoration.
 | 
						||
        color = aColorResolution == eResolvedColors
 | 
						||
                    ? nsLayoutUtils::GetTextColor(f, &nsStyleSVG::mFill)
 | 
						||
                    : NS_SAME_AS_FOREGROUND_COLOR;
 | 
						||
      } else {
 | 
						||
        color = nsLayoutUtils::GetTextColor(
 | 
						||
            f, &nsStyleTextReset::mTextDecorationColor);
 | 
						||
      }
 | 
						||
 | 
						||
      bool swapUnderlineAndOverline =
 | 
						||
          wm.IsCentralBaseline() && IsUnderlineRight(*context);
 | 
						||
      const auto kUnderline = swapUnderlineAndOverline
 | 
						||
                                  ? StyleTextDecorationLine::OVERLINE
 | 
						||
                                  : StyleTextDecorationLine::UNDERLINE;
 | 
						||
      const auto kOverline = swapUnderlineAndOverline
 | 
						||
                                 ? StyleTextDecorationLine::UNDERLINE
 | 
						||
                                 : StyleTextDecorationLine::OVERLINE;
 | 
						||
 | 
						||
      const nsStyleText* const styleText = context->StyleText();
 | 
						||
      const auto position = ignoreSubproperties
 | 
						||
                                ? StyleTextUnderlinePosition::AUTO
 | 
						||
                                : styleText->mTextUnderlinePosition;
 | 
						||
      const auto offset = ignoreSubproperties ? LengthPercentageOrAuto::Auto()
 | 
						||
                                              : styleText->mTextUnderlineOffset;
 | 
						||
      const auto thickness = ignoreSubproperties
 | 
						||
                                 ? StyleTextDecorationLength::Auto()
 | 
						||
                                 : styleTextReset->mTextDecorationThickness;
 | 
						||
 | 
						||
      if (textDecorations & kUnderline) {
 | 
						||
        aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
 | 
						||
            f, baselineOffset, position, offset, thickness, color, lineStyle,
 | 
						||
            !ignoreSubproperties));
 | 
						||
      }
 | 
						||
      if (textDecorations & kOverline) {
 | 
						||
        aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
 | 
						||
            f, baselineOffset, position, offset, thickness, color, lineStyle,
 | 
						||
            !ignoreSubproperties));
 | 
						||
      }
 | 
						||
      if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) {
 | 
						||
        aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
 | 
						||
            f, baselineOffset, position, offset, thickness, color, lineStyle,
 | 
						||
            !ignoreSubproperties));
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    // In all modes, if we're on an inline-block/table/grid/flex (or
 | 
						||
    // -moz-inline-box), we're done.
 | 
						||
    // If we're on a ruby frame other than ruby text container, we
 | 
						||
    // should continue.
 | 
						||
    mozilla::StyleDisplay display = f->GetDisplay();
 | 
						||
    if (!display.IsInlineFlow() &&
 | 
						||
        (!display.IsRuby() ||
 | 
						||
         display == mozilla::StyleDisplay::RubyTextContainer) &&
 | 
						||
        display.IsInlineOutside()) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
 | 
						||
    // In quirks mode, if we're on an HTML table element, we're done.
 | 
						||
    if (compatMode == eCompatibility_NavQuirks &&
 | 
						||
        f->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
 | 
						||
    // If we're on an absolutely-positioned element or a floating
 | 
						||
    // element, we're done.
 | 
						||
    if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
 | 
						||
    // If we're an outer <svg> element, which is classified as an atomic
 | 
						||
    // inline-level element, we're done.
 | 
						||
    if (f->IsSVGOuterSVGFrame()) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static float GetInflationForTextDecorations(nsIFrame* aFrame,
 | 
						||
                                            nscoord aInflationMinFontSize) {
 | 
						||
  if (aFrame->IsInSVGTextSubtree()) {
 | 
						||
    auto* container =
 | 
						||
        nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
 | 
						||
    MOZ_ASSERT(container);
 | 
						||
    return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
 | 
						||
  }
 | 
						||
  return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
 | 
						||
}
 | 
						||
 | 
						||
struct EmphasisMarkInfo {
 | 
						||
  RefPtr<gfxTextRun> textRun;
 | 
						||
  gfxFloat advance;
 | 
						||
  gfxFloat baselineOffset;
 | 
						||
};
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
 | 
						||
 | 
						||
static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle,
 | 
						||
                                           nsAString& aOut) {
 | 
						||
  MOZ_ASSERT(!aStyle.IsNone());
 | 
						||
  if (aStyle.IsString()) {
 | 
						||
    nsDependentCSubstring string = aStyle.AsString().AsString();
 | 
						||
    AppendUTF8toUTF16(string, aOut);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  const auto& keyword = aStyle.AsKeyword();
 | 
						||
  const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled;
 | 
						||
  switch (keyword.shape) {
 | 
						||
    case StyleTextEmphasisShapeKeyword::Dot:
 | 
						||
      return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6");
 | 
						||
    case StyleTextEmphasisShapeKeyword::Circle:
 | 
						||
      return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb");
 | 
						||
    case StyleTextEmphasisShapeKeyword::DoubleCircle:
 | 
						||
      return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce");
 | 
						||
    case StyleTextEmphasisShapeKeyword::Triangle:
 | 
						||
      return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3");
 | 
						||
    case StyleTextEmphasisShapeKeyword::Sesame:
 | 
						||
      return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46");
 | 
						||
    default:
 | 
						||
      MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape");
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks(
 | 
						||
    nsTextFrame* aFrame, gfxFontGroup* aFontGroup,
 | 
						||
    ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) {
 | 
						||
  nsAutoString string;
 | 
						||
  ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string);
 | 
						||
 | 
						||
  RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
 | 
						||
  auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
 | 
						||
  gfx::ShapedTextFlags flags =
 | 
						||
      nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
 | 
						||
  if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
 | 
						||
    // The emphasis marks should always be rendered upright per spec.
 | 
						||
    flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
 | 
						||
  }
 | 
						||
  return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt,
 | 
						||
                                           appUnitsPerDevUnit, flags,
 | 
						||
                                           nsTextFrameUtils::Flags(), nullptr);
 | 
						||
}
 | 
						||
 | 
						||
static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) {
 | 
						||
  nsRubyFrame* rubyFrame = nullptr;
 | 
						||
  for (nsIFrame* frame = aFrame->GetParent();
 | 
						||
       frame && frame->IsLineParticipant(); frame = frame->GetParent()) {
 | 
						||
    if (frame->IsRubyFrame()) {
 | 
						||
      rubyFrame = static_cast<nsRubyFrame*>(frame);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return rubyFrame;
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM,
 | 
						||
                                       PropertyProvider& aProvider) {
 | 
						||
  const nsStyleText* styleText = StyleText();
 | 
						||
  if (!styleText->HasEffectiveTextEmphasis()) {
 | 
						||
    RemoveProperty(EmphasisMarkProperty());
 | 
						||
    return nsRect();
 | 
						||
  }
 | 
						||
 | 
						||
  ComputedStyle* computedStyle = Style();
 | 
						||
  bool isTextCombined = computedStyle->IsTextCombined();
 | 
						||
  if (isTextCombined) {
 | 
						||
    computedStyle = GetParent()->Style();
 | 
						||
  }
 | 
						||
  RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
 | 
						||
      computedStyle, PresContext(), GetFontSizeInflation());
 | 
						||
  EmphasisMarkInfo* info = new EmphasisMarkInfo;
 | 
						||
  info->textRun = GenerateTextRunForEmphasisMarks(
 | 
						||
      this, fm->GetThebesFontGroup(), computedStyle, styleText);
 | 
						||
  info->advance = info->textRun->GetAdvanceWidth();
 | 
						||
 | 
						||
  // Calculate the baseline offset
 | 
						||
  LogicalSide side = styleText->TextEmphasisSide(aWM, StyleFont()->mLanguage);
 | 
						||
  LogicalSize frameSize = GetLogicalSize(aWM);
 | 
						||
  // The overflow rect is inflated in the inline direction by half
 | 
						||
  // advance of the emphasis mark on each side, so that even if a mark
 | 
						||
  // is drawn for a zero-width character, it won't be clipped.
 | 
						||
  LogicalRect overflowRect(aWM, -info->advance / 2,
 | 
						||
                           /* BStart to be computed below */ 0,
 | 
						||
                           frameSize.ISize(aWM) + info->advance,
 | 
						||
                           fm->MaxAscent() + fm->MaxDescent());
 | 
						||
  RefPtr<nsFontMetrics> baseFontMetrics =
 | 
						||
      isTextCombined
 | 
						||
          ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
 | 
						||
          : do_AddRef(aProvider.GetFontMetrics());
 | 
						||
  // When the writing mode is vertical-lr the line is inverted, and thus
 | 
						||
  // the ascent and descent are swapped.
 | 
						||
  nscoord absOffset = (side == LogicalSide::BStart) != aWM.IsLineInverted()
 | 
						||
                          ? baseFontMetrics->MaxAscent() + fm->MaxDescent()
 | 
						||
                          : baseFontMetrics->MaxDescent() + fm->MaxAscent();
 | 
						||
  RubyBlockLeadings leadings;
 | 
						||
  if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
 | 
						||
    leadings = ruby->GetBlockLeadings();
 | 
						||
  }
 | 
						||
  if (side == LogicalSide::BStart) {
 | 
						||
    info->baselineOffset = -absOffset - leadings.mStart;
 | 
						||
    overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
 | 
						||
  } else {
 | 
						||
    MOZ_ASSERT(side == LogicalSide::BEnd);
 | 
						||
    info->baselineOffset = absOffset + leadings.mEnd;
 | 
						||
    overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
 | 
						||
  }
 | 
						||
  // If text combined, fix the gap between the text frame and its parent.
 | 
						||
  if (isTextCombined) {
 | 
						||
    nscoord gap = (baseFontMetrics->MaxHeight() - frameSize.BSize(aWM)) / 2;
 | 
						||
    overflowRect.BStart(aWM) += gap * (side == LogicalSide::BStart ? -1 : 1);
 | 
						||
  }
 | 
						||
 | 
						||
  SetProperty(EmphasisMarkProperty(), info);
 | 
						||
  return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
 | 
						||
}
 | 
						||
 | 
						||
// helper function for implementing text-decoration-thickness
 | 
						||
// https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property
 | 
						||
// Returns the thickness in device pixels.
 | 
						||
static gfxFloat ComputeDecorationLineThickness(
 | 
						||
    const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue,
 | 
						||
    const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel,
 | 
						||
    const nsIFrame* aFrame) {
 | 
						||
  if (aThickness.IsAuto()) {
 | 
						||
    return aAutoValue;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aThickness.IsFromFont()) {
 | 
						||
    return aFontMetrics.underlineSize;
 | 
						||
  }
 | 
						||
  auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
 | 
						||
  return aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
 | 
						||
}
 | 
						||
 | 
						||
// Helper function for implementing text-underline-offset and -position
 | 
						||
// https://drafts.csswg.org/css-text-decor-4/#underline-offset
 | 
						||
// Returns the offset in device pixels.
 | 
						||
static gfxFloat ComputeDecorationLineOffset(
 | 
						||
    StyleTextDecorationLine aLineType,
 | 
						||
    const StyleTextUnderlinePosition& aPosition,
 | 
						||
    const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics,
 | 
						||
    const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame,
 | 
						||
    bool aIsCentralBaseline, bool aSwappedUnderline) {
 | 
						||
  // Em value to use if we need to resolve a percentage length.
 | 
						||
  auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
 | 
						||
  // If we're in vertical-upright typographic mode, we need to compute the
 | 
						||
  // offset of the decoration line from the default central baseline.
 | 
						||
  if (aIsCentralBaseline) {
 | 
						||
    // Line-through simply goes at the (central) baseline.
 | 
						||
    if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
 | 
						||
      return 0;
 | 
						||
    }
 | 
						||
 | 
						||
    // Compute "zero position" for the under- or overline.
 | 
						||
    gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight;
 | 
						||
 | 
						||
    // aOffset applies to underline only; for overline (or offset:auto) we use
 | 
						||
    // a somewhat arbitrary offset of half the font's (horziontal-mode) value
 | 
						||
    // for underline-offset, to get a little bit of separation between glyph
 | 
						||
    // edges and the line in typical cases.
 | 
						||
    // If we have swapped under-/overlines for text-underline-position:right,
 | 
						||
    // we need to take account of this to determine which decoration lines are
 | 
						||
    // "real" underlines which should respect the text-underline-* values.
 | 
						||
    bool isUnderline =
 | 
						||
        (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline;
 | 
						||
    gfxFloat offset =
 | 
						||
        isUnderline && !aOffset.IsAuto()
 | 
						||
            ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel
 | 
						||
            : aFontMetrics.underlineOffset * -0.5;
 | 
						||
 | 
						||
    // Direction of the decoration line's offset from the central baseline.
 | 
						||
    gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0;
 | 
						||
    return dir * (zeroPos + offset);
 | 
						||
  }
 | 
						||
 | 
						||
  // Compute line offset for horizontal typographic mode.
 | 
						||
  if (aLineType == StyleTextDecorationLine::UNDERLINE) {
 | 
						||
    if (aPosition.IsFromFont()) {
 | 
						||
      gfxFloat zeroPos = aFontMetrics.underlineOffset;
 | 
						||
      gfxFloat offset =
 | 
						||
          aOffset.IsAuto()
 | 
						||
              ? 0
 | 
						||
              : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
 | 
						||
      return zeroPos - offset;
 | 
						||
    }
 | 
						||
 | 
						||
    if (aPosition.IsUnder()) {
 | 
						||
      gfxFloat zeroPos = -aFontMetrics.maxDescent;
 | 
						||
      gfxFloat offset =
 | 
						||
          aOffset.IsAuto()
 | 
						||
              ? -0.5 * aFontMetrics.underlineOffset
 | 
						||
              : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
 | 
						||
      return zeroPos - offset;
 | 
						||
    }
 | 
						||
 | 
						||
    // text-underline-position must be 'auto', so zero position is the
 | 
						||
    // baseline and 'auto' offset will apply the font's underline-offset.
 | 
						||
    //
 | 
						||
    // If offset is `auto`, we clamp the offset (in horizontal typographic mode)
 | 
						||
    // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate
 | 
						||
    // skip-ink issues with fonts that leave the underlineOffset field as zero.
 | 
						||
    MOZ_ASSERT(aPosition.IsAuto());
 | 
						||
    return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset,
 | 
						||
                                       -aFontMetrics.emHeight / 16.0)
 | 
						||
                            : -aOffset.AsLengthPercentage().Resolve(em) /
 | 
						||
                                  aAppUnitsPerDevPixel;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aLineType == StyleTextDecorationLine::OVERLINE) {
 | 
						||
    return aFontMetrics.maxAscent;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
 | 
						||
    return aFontMetrics.strikeoutOffset;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT_UNREACHABLE("unknown decoration line type");
 | 
						||
  return 0;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
 | 
						||
                                          nsIFrame* aBlock,
 | 
						||
                                          PropertyProvider& aProvider,
 | 
						||
                                          nsRect* aInkOverflowRect,
 | 
						||
                                          bool aIncludeTextDecorations,
 | 
						||
                                          bool aIncludeShadows) {
 | 
						||
  const WritingMode wm = GetWritingMode();
 | 
						||
  bool verticalRun = mTextRun->IsVertical();
 | 
						||
  const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
 | 
						||
 | 
						||
  if (IsFloatingFirstLetterChild()) {
 | 
						||
    bool inverted = wm.IsLineInverted();
 | 
						||
    // The underline/overline drawable area must be contained in the overflow
 | 
						||
    // rect when this is in floating first letter frame at *both* modes.
 | 
						||
    // In this case, aBlock is the ::first-letter frame.
 | 
						||
    auto decorationStyle =
 | 
						||
        aBlock->Style()->StyleTextReset()->mTextDecorationStyle;
 | 
						||
    // If the style is none, let's include decoration line rect as solid style
 | 
						||
    // since changing the style from none to solid/dotted/dashed doesn't cause
 | 
						||
    // reflow.
 | 
						||
    if (decorationStyle == StyleTextDecorationStyle::None) {
 | 
						||
      decorationStyle = StyleTextDecorationStyle::Solid;
 | 
						||
    }
 | 
						||
    nsCSSRendering::DecorationRectParams params;
 | 
						||
 | 
						||
    bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
 | 
						||
    nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
 | 
						||
    RefPtr<gfxFont> font =
 | 
						||
        fontMetrics->GetThebesFontGroup()->GetFirstValidFont();
 | 
						||
    const gfxFont::Metrics& metrics =
 | 
						||
        font->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
 | 
						||
                                            : nsFontMetrics::eHorizontal);
 | 
						||
 | 
						||
    params.defaultLineThickness = metrics.underlineSize;
 | 
						||
    params.lineSize.height = ComputeDecorationLineThickness(
 | 
						||
        aBlock->Style()->StyleTextReset()->mTextDecorationThickness,
 | 
						||
        params.defaultLineThickness, metrics, appUnitsPerDevUnit, this);
 | 
						||
 | 
						||
    const auto* styleText = aBlock->StyleText();
 | 
						||
    bool swapUnderline =
 | 
						||
        wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style());
 | 
						||
    params.offset = ComputeDecorationLineOffset(
 | 
						||
        StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition,
 | 
						||
        styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this,
 | 
						||
        wm.IsCentralBaseline(), swapUnderline);
 | 
						||
 | 
						||
    nscoord maxAscent =
 | 
						||
        inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent();
 | 
						||
 | 
						||
    Float gfxWidth =
 | 
						||
        (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) /
 | 
						||
        appUnitsPerDevUnit;
 | 
						||
    params.lineSize.width = gfxWidth;
 | 
						||
    params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
 | 
						||
    params.style = decorationStyle;
 | 
						||
    params.vertical = verticalRun;
 | 
						||
    params.sidewaysLeft = mTextRun->IsSidewaysLeft();
 | 
						||
    params.decoration = StyleTextDecorationLine::UNDERLINE;
 | 
						||
    nsRect underlineRect =
 | 
						||
        nsCSSRendering::GetTextDecorationRect(aPresContext, params);
 | 
						||
 | 
						||
    // TODO(jfkthame):
 | 
						||
    // Should we actually be calling ComputeDecorationLineOffset again here?
 | 
						||
    params.offset = maxAscent / appUnitsPerDevUnit;
 | 
						||
    params.decoration = StyleTextDecorationLine::OVERLINE;
 | 
						||
    nsRect overlineRect =
 | 
						||
        nsCSSRendering::GetTextDecorationRect(aPresContext, params);
 | 
						||
 | 
						||
    aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect);
 | 
						||
    aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect);
 | 
						||
 | 
						||
    // XXX If strikeoutSize is much thicker than the underlineSize, it may
 | 
						||
    //     cause overflowing from the overflow rect.  However, such case
 | 
						||
    //     isn't realistic, we don't need to compute it now.
 | 
						||
  }
 | 
						||
  if (aIncludeTextDecorations) {
 | 
						||
    // Use writing mode of parent frame for orthogonal text frame to
 | 
						||
    // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
 | 
						||
    WritingMode parentWM = GetParent()->GetWritingMode();
 | 
						||
    bool verticalDec = parentWM.IsVertical();
 | 
						||
    bool useVerticalMetrics =
 | 
						||
        verticalDec != verticalRun
 | 
						||
            ? verticalDec
 | 
						||
            : verticalRun && mTextRun->UseCenterBaseline();
 | 
						||
 | 
						||
    // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
 | 
						||
    // style and position, they can be drawn at virtually any y-offset, so
 | 
						||
    // maxima and minima are required to reliably generate the rectangle for
 | 
						||
    // them
 | 
						||
    TextDecorations textDecs;
 | 
						||
    GetTextDecorations(aPresContext, eResolvedColors, textDecs);
 | 
						||
    if (textDecs.HasDecorationLines()) {
 | 
						||
      nscoord inflationMinFontSize =
 | 
						||
          nsLayoutUtils::InflationMinFontSizeFor(aBlock);
 | 
						||
 | 
						||
      const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
 | 
						||
      gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
 | 
						||
      gfxFloat ascent =
 | 
						||
          gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit;
 | 
						||
      nscoord frameBStart = 0;
 | 
						||
      if (parentWM.IsVerticalRL()) {
 | 
						||
        frameBStart = GetSize().width;
 | 
						||
        ascent = -ascent;
 | 
						||
      }
 | 
						||
 | 
						||
      nsCSSRendering::DecorationRectParams params;
 | 
						||
      params.lineSize = Size(gfxWidth, 0);
 | 
						||
      params.ascent = ascent;
 | 
						||
      params.vertical = verticalDec;
 | 
						||
      params.sidewaysLeft = mTextRun->IsSidewaysLeft();
 | 
						||
 | 
						||
      nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
 | 
						||
      typedef gfxFont::Metrics Metrics;
 | 
						||
      auto accumulateDecorationRect =
 | 
						||
          [&](const LineDecoration& dec, gfxFloat Metrics::* lineSize,
 | 
						||
              mozilla::StyleTextDecorationLine lineType) {
 | 
						||
            params.style = dec.mStyle;
 | 
						||
            // If the style is solid, let's include decoration line rect of
 | 
						||
            // solid style since changing the style from none to
 | 
						||
            // solid/dotted/dashed doesn't cause reflow.
 | 
						||
            if (params.style == StyleTextDecorationStyle::None) {
 | 
						||
              params.style = StyleTextDecorationStyle::Solid;
 | 
						||
            }
 | 
						||
 | 
						||
            float inflation = GetInflationForTextDecorations(
 | 
						||
                dec.mFrame, inflationMinFontSize);
 | 
						||
            const Metrics metrics =
 | 
						||
                GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
 | 
						||
                                    useVerticalMetrics);
 | 
						||
 | 
						||
            params.defaultLineThickness = metrics.*lineSize;
 | 
						||
            params.lineSize.height = ComputeDecorationLineThickness(
 | 
						||
                dec.mTextDecorationThickness, params.defaultLineThickness,
 | 
						||
                metrics, appUnitsPerDevUnit, this);
 | 
						||
 | 
						||
            bool swapUnderline =
 | 
						||
                parentWM.IsCentralBaseline() && IsUnderlineRight(*Style());
 | 
						||
            params.offset = ComputeDecorationLineOffset(
 | 
						||
                lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset,
 | 
						||
                metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(),
 | 
						||
                swapUnderline);
 | 
						||
 | 
						||
            const nsRect decorationRect =
 | 
						||
                nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
 | 
						||
                (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
 | 
						||
                             : nsPoint(0, -dec.mBaselineOffset));
 | 
						||
 | 
						||
            if (verticalDec) {
 | 
						||
              topOrLeft = std::min(decorationRect.x, topOrLeft);
 | 
						||
              bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
 | 
						||
            } else {
 | 
						||
              topOrLeft = std::min(decorationRect.y, topOrLeft);
 | 
						||
              bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
 | 
						||
            }
 | 
						||
          };
 | 
						||
 | 
						||
      // Below we loop through all text decorations and compute the rectangle
 | 
						||
      // containing all of them, in this frame's coordinate space
 | 
						||
      params.decoration = StyleTextDecorationLine::UNDERLINE;
 | 
						||
      for (const LineDecoration& dec : textDecs.mUnderlines) {
 | 
						||
        accumulateDecorationRect(dec, &Metrics::underlineSize,
 | 
						||
                                 params.decoration);
 | 
						||
      }
 | 
						||
      params.decoration = StyleTextDecorationLine::OVERLINE;
 | 
						||
      for (const LineDecoration& dec : textDecs.mOverlines) {
 | 
						||
        accumulateDecorationRect(dec, &Metrics::underlineSize,
 | 
						||
                                 params.decoration);
 | 
						||
      }
 | 
						||
      params.decoration = StyleTextDecorationLine::LINE_THROUGH;
 | 
						||
      for (const LineDecoration& dec : textDecs.mStrikes) {
 | 
						||
        accumulateDecorationRect(dec, &Metrics::strikeoutSize,
 | 
						||
                                 params.decoration);
 | 
						||
      }
 | 
						||
 | 
						||
      aInkOverflowRect->UnionRect(
 | 
						||
          *aInkOverflowRect,
 | 
						||
          verticalDec
 | 
						||
              ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
 | 
						||
              : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
 | 
						||
    }
 | 
						||
 | 
						||
    aInkOverflowRect->UnionRect(*aInkOverflowRect,
 | 
						||
                                UpdateTextEmphasis(parentWM, aProvider));
 | 
						||
  }
 | 
						||
 | 
						||
  // text-stroke overflows: add half of text-stroke-width on all sides
 | 
						||
  nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
 | 
						||
  if (textStrokeWidth > 0) {
 | 
						||
    // Inflate rect by stroke-width/2; we add an extra pixel to allow for
 | 
						||
    // antialiasing, rounding errors, etc.
 | 
						||
    nsRect strokeRect = *aInkOverflowRect;
 | 
						||
    strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
 | 
						||
    aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect);
 | 
						||
  }
 | 
						||
 | 
						||
  // Text-shadow overflows
 | 
						||
  if (aIncludeShadows) {
 | 
						||
    *aInkOverflowRect =
 | 
						||
        nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this);
 | 
						||
  }
 | 
						||
 | 
						||
  // When this frame is not selected, the text-decoration area must be in
 | 
						||
  // frame bounds.
 | 
						||
  if (!IsSelected() ||
 | 
						||
      !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
 | 
						||
}
 | 
						||
 | 
						||
nscoord nsTextFrame::ComputeLineHeight() const {
 | 
						||
  return ReflowInput::CalcLineHeight(*Style(), PresContext(), GetContent(),
 | 
						||
                                     NS_UNCONSTRAINEDSIZE,
 | 
						||
                                     GetFontSizeInflation());
 | 
						||
}
 | 
						||
 | 
						||
gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline(
 | 
						||
    nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) {
 | 
						||
  const gfxFloat lineHeight =
 | 
						||
      gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel();
 | 
						||
  if (lineHeight <= aFontMetrics.maxHeight) {
 | 
						||
    return aFontMetrics.maxDescent;
 | 
						||
  }
 | 
						||
  return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
 | 
						||
}
 | 
						||
 | 
						||
// Make sure this stays in sync with DrawSelectionDecorations below
 | 
						||
static constexpr SelectionTypeMask kSelectionTypesWithDecorations =
 | 
						||
    ToSelectionTypeMask(SelectionType::eSpellCheck) |
 | 
						||
    ToSelectionTypeMask(SelectionType::eURLStrikeout) |
 | 
						||
    ToSelectionTypeMask(SelectionType::eIMERawClause) |
 | 
						||
    ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
 | 
						||
    ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
 | 
						||
    ToSelectionTypeMask(SelectionType::eIMESelectedClause);
 | 
						||
 | 
						||
/* static */
 | 
						||
gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight(
 | 
						||
    nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
 | 
						||
    SelectionType aSelectionType) {
 | 
						||
  switch (aSelectionType) {
 | 
						||
    case SelectionType::eIMERawClause:
 | 
						||
    case SelectionType::eIMESelectedRawClause:
 | 
						||
    case SelectionType::eIMEConvertedClause:
 | 
						||
    case SelectionType::eIMESelectedClause:
 | 
						||
      return aFontMetrics.underlineSize;
 | 
						||
    case SelectionType::eSpellCheck: {
 | 
						||
      // The thickness of the spellchecker underline shouldn't honor the font
 | 
						||
      // metrics.  It should be constant pixels value which is decided from the
 | 
						||
      // default font size.  Note that if the actual font size is smaller than
 | 
						||
      // the default font size, we should use the actual font size because the
 | 
						||
      // computed value from the default font size can be too thick for the
 | 
						||
      // current font size.
 | 
						||
      Length defaultFontSize =
 | 
						||
          aPresContext->Document()
 | 
						||
              ->GetFontPrefsForLang(nullptr)
 | 
						||
              ->GetDefaultFont(StyleGenericFontFamily::None)
 | 
						||
              ->size;
 | 
						||
      int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels(
 | 
						||
          nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize)
 | 
						||
              .ToCSSPixels());
 | 
						||
      gfxFloat fontSize =
 | 
						||
          std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight);
 | 
						||
      fontSize = std::max(fontSize, 1.0);
 | 
						||
      return ceil(fontSize / 20);
 | 
						||
    }
 | 
						||
    default:
 | 
						||
      NS_WARNING("Requested underline style is not valid");
 | 
						||
      return aFontMetrics.underlineSize;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
enum class DecorationType { Normal, Selection };
 | 
						||
struct nsTextFrame::PaintDecorationLineParams
 | 
						||
    : nsCSSRendering::DecorationRectParams {
 | 
						||
  gfxContext* context = nullptr;
 | 
						||
  LayoutDeviceRect dirtyRect;
 | 
						||
  Point pt;
 | 
						||
  const nscolor* overrideColor = nullptr;
 | 
						||
  nscolor color = NS_RGBA(0, 0, 0, 0);
 | 
						||
  gfxFloat icoordInFrame = 0.0f;
 | 
						||
  gfxFloat baselineOffset = 0.0f;
 | 
						||
  DecorationType decorationType = DecorationType::Normal;
 | 
						||
  DrawPathCallbacks* callbacks = nullptr;
 | 
						||
  bool paintingShadows = false;
 | 
						||
  bool allowInkSkipping = true;
 | 
						||
};
 | 
						||
 | 
						||
void nsTextFrame::PaintDecorationLine(
 | 
						||
    const PaintDecorationLineParams& aParams) {
 | 
						||
  nsCSSRendering::PaintDecorationLineParams params;
 | 
						||
  static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
 | 
						||
  params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
 | 
						||
  params.pt = aParams.pt;
 | 
						||
  params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
 | 
						||
  params.icoordInFrame = Float(aParams.icoordInFrame);
 | 
						||
  params.baselineOffset = Float(aParams.baselineOffset);
 | 
						||
  params.allowInkSkipping = aParams.allowInkSkipping;
 | 
						||
  if (aParams.callbacks) {
 | 
						||
    Rect path = nsCSSRendering::DecorationLineToPath(params);
 | 
						||
    if (aParams.decorationType == DecorationType::Normal) {
 | 
						||
      aParams.callbacks->PaintDecorationLine(path, aParams.paintingShadows,
 | 
						||
                                             params.color);
 | 
						||
    } else {
 | 
						||
      aParams.callbacks->PaintSelectionDecorationLine(
 | 
						||
          path, aParams.paintingShadows, params.color);
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(),
 | 
						||
                                        params);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static StyleTextDecorationStyle ToStyleLineStyle(const TextRangeStyle& aStyle) {
 | 
						||
  switch (aStyle.mLineStyle) {
 | 
						||
    case TextRangeStyle::LineStyle::None:
 | 
						||
      return StyleTextDecorationStyle::None;
 | 
						||
    case TextRangeStyle::LineStyle::Solid:
 | 
						||
      return StyleTextDecorationStyle::Solid;
 | 
						||
    case TextRangeStyle::LineStyle::Dotted:
 | 
						||
      return StyleTextDecorationStyle::Dotted;
 | 
						||
    case TextRangeStyle::LineStyle::Dashed:
 | 
						||
      return StyleTextDecorationStyle::Dashed;
 | 
						||
    case TextRangeStyle::LineStyle::Double:
 | 
						||
      return StyleTextDecorationStyle::Double;
 | 
						||
    case TextRangeStyle::LineStyle::Wavy:
 | 
						||
      return StyleTextDecorationStyle::Wavy;
 | 
						||
  }
 | 
						||
  MOZ_ASSERT_UNREACHABLE("Invalid line style");
 | 
						||
  return StyleTextDecorationStyle::None;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
 | 
						||
 * about drawing text decoration for selections.
 | 
						||
 */
 | 
						||
void nsTextFrame::DrawSelectionDecorations(
 | 
						||
    gfxContext* aContext, const LayoutDeviceRect& aDirtyRect,
 | 
						||
    SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
 | 
						||
    const TextRangeStyle& aRangeStyle, const Point& aPt,
 | 
						||
    gfxFloat aICoordInFrame, gfxFloat aWidth, gfxFloat aAscent,
 | 
						||
    const gfxFont::Metrics& aFontMetrics, DrawPathCallbacks* aCallbacks,
 | 
						||
    bool aVertical, StyleTextDecorationLine aDecoration) {
 | 
						||
  PaintDecorationLineParams params;
 | 
						||
  params.context = aContext;
 | 
						||
  params.dirtyRect = aDirtyRect;
 | 
						||
  params.pt = aPt;
 | 
						||
  params.lineSize.width = aWidth;
 | 
						||
  params.ascent = aAscent;
 | 
						||
  params.decoration = aDecoration;
 | 
						||
  params.decorationType = DecorationType::Selection;
 | 
						||
  params.callbacks = aCallbacks;
 | 
						||
  params.vertical = aVertical;
 | 
						||
  params.sidewaysLeft = mTextRun->IsSidewaysLeft();
 | 
						||
  params.descentLimit = ComputeDescentLimitForSelectionUnderline(
 | 
						||
      aTextPaintStyle.PresContext(), aFontMetrics);
 | 
						||
 | 
						||
  float relativeSize;
 | 
						||
  const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
 | 
						||
  const gfxFloat appUnitsPerDevPixel =
 | 
						||
      aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
 | 
						||
 | 
						||
  const WritingMode wm = GetWritingMode();
 | 
						||
  switch (aSelectionType) {
 | 
						||
    case SelectionType::eIMERawClause:
 | 
						||
    case SelectionType::eIMESelectedRawClause:
 | 
						||
    case SelectionType::eIMEConvertedClause:
 | 
						||
    case SelectionType::eIMESelectedClause:
 | 
						||
    case SelectionType::eSpellCheck:
 | 
						||
    case SelectionType::eHighlight: {
 | 
						||
      auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
 | 
						||
          aSelectionType);
 | 
						||
      bool weDefineSelectionUnderline =
 | 
						||
          aTextPaintStyle.GetSelectionUnderlineForPaint(
 | 
						||
              index, ¶ms.color, &relativeSize, ¶ms.style);
 | 
						||
      params.defaultLineThickness = ComputeSelectionUnderlineHeight(
 | 
						||
          aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
 | 
						||
      params.lineSize.height = ComputeDecorationLineThickness(
 | 
						||
          decThickness, params.defaultLineThickness, aFontMetrics,
 | 
						||
          appUnitsPerDevPixel, this);
 | 
						||
 | 
						||
      bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
 | 
						||
      const auto* styleText = StyleText();
 | 
						||
      params.offset = ComputeDecorationLineOffset(
 | 
						||
          aDecoration, styleText->mTextUnderlinePosition,
 | 
						||
          styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel,
 | 
						||
          this, wm.IsCentralBaseline(), swapUnderline);
 | 
						||
 | 
						||
      bool isIMEType = aSelectionType != SelectionType::eSpellCheck &&
 | 
						||
                       aSelectionType != SelectionType::eHighlight;
 | 
						||
 | 
						||
      if (isIMEType) {
 | 
						||
        // IME decoration lines should not be drawn on the both ends, i.e., we
 | 
						||
        // need to cut both edges of the decoration lines.  Because same style
 | 
						||
        // IME selections can adjoin, but the users need to be able to know
 | 
						||
        // where are the boundaries of the selections.
 | 
						||
        //
 | 
						||
        //  X: underline
 | 
						||
        //
 | 
						||
        //     IME selection #1        IME selection #2      IME selection #3
 | 
						||
        //  |                     |                      |
 | 
						||
        //  | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
 | 
						||
        //  +---------------------+----------------------+--------------------
 | 
						||
        //   ^                   ^ ^                    ^ ^
 | 
						||
        //  gap                  gap                    gap
 | 
						||
        params.pt.x += 1.0;
 | 
						||
        params.lineSize.width -= 2.0;
 | 
						||
      }
 | 
						||
      if (isIMEType && aRangeStyle.IsDefined()) {
 | 
						||
        // If IME defines the style, that should override our definition.
 | 
						||
        if (aRangeStyle.IsLineStyleDefined()) {
 | 
						||
          if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
 | 
						||
            return;
 | 
						||
          }
 | 
						||
          params.style = ToStyleLineStyle(aRangeStyle);
 | 
						||
          relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
 | 
						||
        } else if (!weDefineSelectionUnderline) {
 | 
						||
          // There is no underline style definition.
 | 
						||
          return;
 | 
						||
        }
 | 
						||
        // If underline color is defined and that doesn't depend on the
 | 
						||
        // foreground color, we should use the color directly.
 | 
						||
        if (aRangeStyle.IsUnderlineColorDefined() &&
 | 
						||
            (!aRangeStyle.IsForegroundColorDefined() ||
 | 
						||
             aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
 | 
						||
          params.color = aRangeStyle.mUnderlineColor;
 | 
						||
        }
 | 
						||
        // If foreground color or background color is defined, the both colors
 | 
						||
        // are computed by GetSelectionTextColors().  Then, we should use its
 | 
						||
        // foreground color always.  The color should have sufficient contrast
 | 
						||
        // with the background color.
 | 
						||
        else if (aRangeStyle.IsForegroundColorDefined() ||
 | 
						||
                 aRangeStyle.IsBackgroundColorDefined()) {
 | 
						||
          nscolor bg;
 | 
						||
          GetSelectionTextColors(aSelectionType, nullptr, aTextPaintStyle,
 | 
						||
                                 aRangeStyle, ¶ms.color, &bg);
 | 
						||
        }
 | 
						||
        // Otherwise, use the foreground color of the frame.
 | 
						||
        else {
 | 
						||
          params.color = aTextPaintStyle.GetTextColor();
 | 
						||
        }
 | 
						||
      } else if (!weDefineSelectionUnderline) {
 | 
						||
        // IME doesn't specify the selection style and we don't define selection
 | 
						||
        // underline.
 | 
						||
        return;
 | 
						||
      }
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    case SelectionType::eURLStrikeout: {
 | 
						||
      nscoord inflationMinFontSize =
 | 
						||
          nsLayoutUtils::InflationMinFontSizeFor(this);
 | 
						||
      float inflation =
 | 
						||
          GetInflationForTextDecorations(this, inflationMinFontSize);
 | 
						||
      const gfxFont::Metrics metrics =
 | 
						||
          GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
 | 
						||
 | 
						||
      relativeSize = 2.0f;
 | 
						||
      aTextPaintStyle.GetURLSecondaryColor(¶ms.color);
 | 
						||
      params.style = StyleTextDecorationStyle::Solid;
 | 
						||
      params.defaultLineThickness = metrics.strikeoutSize;
 | 
						||
      params.lineSize.height = ComputeDecorationLineThickness(
 | 
						||
          decThickness, params.defaultLineThickness, metrics,
 | 
						||
          appUnitsPerDevPixel, this);
 | 
						||
      // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode!
 | 
						||
      params.offset = metrics.strikeoutOffset + 0.5;
 | 
						||
      params.decoration = StyleTextDecorationLine::LINE_THROUGH;
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    default:
 | 
						||
      NS_WARNING("Requested selection decorations when there aren't any");
 | 
						||
      return;
 | 
						||
  }
 | 
						||
  params.lineSize.height *= relativeSize;
 | 
						||
  params.defaultLineThickness *= relativeSize;
 | 
						||
  params.icoordInFrame =
 | 
						||
      (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame;
 | 
						||
  PaintDecorationLine(params);
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
 | 
						||
                                         nsAtom* aHighlightName,
 | 
						||
                                         nsTextPaintStyle& aTextPaintStyle,
 | 
						||
                                         const TextRangeStyle& aRangeStyle,
 | 
						||
                                         nscolor* aForeground,
 | 
						||
                                         nscolor* aBackground) {
 | 
						||
  switch (aSelectionType) {
 | 
						||
    case SelectionType::eNormal:
 | 
						||
      return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
 | 
						||
    case SelectionType::eFind:
 | 
						||
      aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
 | 
						||
      return true;
 | 
						||
    case SelectionType::eHighlight: {
 | 
						||
      // Intentionally not short-cutting here because the called methods have
 | 
						||
      // side-effects that affect outparams.
 | 
						||
      bool hasForeground = aTextPaintStyle.GetCustomHighlightTextColor(
 | 
						||
          aHighlightName, aForeground);
 | 
						||
      bool hasBackground = aTextPaintStyle.GetCustomHighlightBackgroundColor(
 | 
						||
          aHighlightName, aBackground);
 | 
						||
      return hasForeground || hasBackground;
 | 
						||
    }
 | 
						||
    case SelectionType::eTargetText: {
 | 
						||
      aTextPaintStyle.GetTargetTextColors(aForeground, aBackground);
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    case SelectionType::eURLSecondary:
 | 
						||
      aTextPaintStyle.GetURLSecondaryColor(aForeground);
 | 
						||
      *aBackground = NS_RGBA(0, 0, 0, 0);
 | 
						||
      return true;
 | 
						||
    case SelectionType::eIMERawClause:
 | 
						||
    case SelectionType::eIMESelectedRawClause:
 | 
						||
    case SelectionType::eIMEConvertedClause:
 | 
						||
    case SelectionType::eIMESelectedClause:
 | 
						||
      if (aRangeStyle.IsDefined()) {
 | 
						||
        if (!aRangeStyle.IsForegroundColorDefined() &&
 | 
						||
            !aRangeStyle.IsBackgroundColorDefined()) {
 | 
						||
          *aForeground = aTextPaintStyle.GetTextColor();
 | 
						||
          *aBackground = NS_RGBA(0, 0, 0, 0);
 | 
						||
          return false;
 | 
						||
        }
 | 
						||
        if (aRangeStyle.IsForegroundColorDefined()) {
 | 
						||
          *aForeground = aRangeStyle.mForegroundColor;
 | 
						||
          if (aRangeStyle.IsBackgroundColorDefined()) {
 | 
						||
            *aBackground = aRangeStyle.mBackgroundColor;
 | 
						||
          } else {
 | 
						||
            // If foreground color is defined but background color isn't
 | 
						||
            // defined, we can guess that IME must expect that the background
 | 
						||
            // color is system's default field background color.
 | 
						||
            *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor();
 | 
						||
          }
 | 
						||
        } else {  // aRangeStyle.IsBackgroundColorDefined() is true
 | 
						||
          *aBackground = aRangeStyle.mBackgroundColor;
 | 
						||
          // If background color is defined but foreground color isn't defined,
 | 
						||
          // we can assume that IME must expect that the foreground color is
 | 
						||
          // same as system's field text color.
 | 
						||
          *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor();
 | 
						||
        }
 | 
						||
        return true;
 | 
						||
      }
 | 
						||
      aTextPaintStyle.GetIMESelectionColors(
 | 
						||
          nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
 | 
						||
              aSelectionType),
 | 
						||
          aForeground, aBackground);
 | 
						||
      return true;
 | 
						||
    default:
 | 
						||
      *aForeground = aTextPaintStyle.GetTextColor();
 | 
						||
      *aBackground = NS_RGBA(0, 0, 0, 0);
 | 
						||
      return false;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * This sets *aShadows to the appropriate shadows, if any, for the given
 | 
						||
 * type of selection.
 | 
						||
 * If text-shadow was not specified, *aShadows is left untouched.
 | 
						||
 */
 | 
						||
void nsTextFrame::GetSelectionTextShadow(
 | 
						||
    SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
 | 
						||
    Span<const StyleSimpleShadow>* aShadows) {
 | 
						||
  if (aSelectionType != SelectionType::eNormal) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  aTextPaintStyle.GetSelectionShadow(aShadows);
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * This class lets us iterate over chunks of text recorded in an array of
 | 
						||
 * resolved selection ranges, observing cluster boundaries, in content order,
 | 
						||
 * maintaining the current x-offset as we go, and telling whether the text
 | 
						||
 * chunk has a hyphen after it or not.
 | 
						||
 * In addition to returning the selected chunks, the iterator is responsible
 | 
						||
 * to interpolate unselected chunks in any gaps between them.
 | 
						||
 * The caller is responsible for actually computing the advance width of each
 | 
						||
 * chunk.
 | 
						||
 */
 | 
						||
class MOZ_STACK_CLASS SelectionRangeIterator {
 | 
						||
  using PropertyProvider = nsTextFrame::PropertyProvider;
 | 
						||
  using CombinedSelectionRange = nsTextFrame::PriorityOrderedSelectionsForRange;
 | 
						||
 | 
						||
 public:
 | 
						||
  // aSelectionRanges and aRange are according to the original string.
 | 
						||
  SelectionRangeIterator(
 | 
						||
      const nsTArray<CombinedSelectionRange>& aSelectionRanges,
 | 
						||
      gfxTextRun::Range aRange, PropertyProvider& aProvider,
 | 
						||
      gfxTextRun* aTextRun, gfxFloat aXOffset);
 | 
						||
 | 
						||
  bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
 | 
						||
                      gfxFloat* aHyphenWidth,
 | 
						||
                      nsTArray<SelectionType>& aSelectionType,
 | 
						||
                      nsTArray<RefPtr<nsAtom>>& aHighlightName,
 | 
						||
                      nsTArray<TextRangeStyle>& aStyle);
 | 
						||
 | 
						||
  void UpdateWithAdvance(gfxFloat aAdvance) {
 | 
						||
    mXOffset += aAdvance * mTextRun->GetDirection();
 | 
						||
  }
 | 
						||
 | 
						||
 private:
 | 
						||
  const nsTArray<CombinedSelectionRange>& mSelectionRanges;
 | 
						||
  PropertyProvider& mProvider;
 | 
						||
  gfxTextRun* mTextRun;
 | 
						||
  gfxSkipCharsIterator mIterator;
 | 
						||
  gfxTextRun::Range mOriginalRange;
 | 
						||
  gfxFloat mXOffset;
 | 
						||
  uint32_t mIndex;
 | 
						||
};
 | 
						||
 | 
						||
SelectionRangeIterator::SelectionRangeIterator(
 | 
						||
    const nsTArray<nsTextFrame::PriorityOrderedSelectionsForRange>&
 | 
						||
        aSelectionRanges,
 | 
						||
    gfxTextRun::Range aRange, PropertyProvider& aProvider, gfxTextRun* aTextRun,
 | 
						||
    gfxFloat aXOffset)
 | 
						||
    : mSelectionRanges(aSelectionRanges),
 | 
						||
      mProvider(aProvider),
 | 
						||
      mTextRun(aTextRun),
 | 
						||
      mIterator(aProvider.GetStart()),
 | 
						||
      mOriginalRange(aRange),
 | 
						||
      mXOffset(aXOffset),
 | 
						||
      mIndex(0) {
 | 
						||
  mIterator.SetOriginalOffset(int32_t(aRange.start));
 | 
						||
}
 | 
						||
 | 
						||
bool SelectionRangeIterator::GetNextSegment(
 | 
						||
    gfxFloat* aXOffset, gfxTextRun::Range* aRange, gfxFloat* aHyphenWidth,
 | 
						||
    nsTArray<SelectionType>& aSelectionType,
 | 
						||
    nsTArray<RefPtr<nsAtom>>& aHighlightName,
 | 
						||
    nsTArray<TextRangeStyle>& aStyle) {
 | 
						||
  if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t runOffset = mIterator.GetSkippedOffset();
 | 
						||
  uint32_t segmentEnd = mOriginalRange.end;
 | 
						||
 | 
						||
  aSelectionType.Clear();
 | 
						||
  aHighlightName.Clear();
 | 
						||
  aStyle.Clear();
 | 
						||
 | 
						||
  if (mIndex == mSelectionRanges.Length() ||
 | 
						||
      mIterator.GetOriginalOffset() <
 | 
						||
          int32_t(mSelectionRanges[mIndex].mRange.start)) {
 | 
						||
    // There's an unselected segment before the next range (or at the end).
 | 
						||
    aSelectionType.AppendElement(SelectionType::eNone);
 | 
						||
    aHighlightName.AppendElement();
 | 
						||
    aStyle.AppendElement(TextRangeStyle());
 | 
						||
    if (mIndex < mSelectionRanges.Length()) {
 | 
						||
      segmentEnd = mSelectionRanges[mIndex].mRange.start;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    // Get the selection details for the next segment, and increment index.
 | 
						||
    for (const SelectionDetails* sdptr :
 | 
						||
         mSelectionRanges[mIndex].mSelectionRanges) {
 | 
						||
      aSelectionType.AppendElement(sdptr->mSelectionType);
 | 
						||
      aHighlightName.AppendElement(sdptr->mHighlightData.mHighlightName);
 | 
						||
      aStyle.AppendElement(sdptr->mTextRangeStyle);
 | 
						||
    }
 | 
						||
    segmentEnd = mSelectionRanges[mIndex].mRange.end;
 | 
						||
    ++mIndex;
 | 
						||
  }
 | 
						||
 | 
						||
  // Advance iterator to the end of the segment.
 | 
						||
  mIterator.SetOriginalOffset(int32_t(segmentEnd));
 | 
						||
 | 
						||
  // Further advance if necessary to a cluster boundary.
 | 
						||
  while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
 | 
						||
         !mIterator.IsOriginalCharSkipped() &&
 | 
						||
         !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
 | 
						||
    mIterator.AdvanceOriginal(1);
 | 
						||
  }
 | 
						||
 | 
						||
  aRange->start = runOffset;
 | 
						||
  aRange->end = mIterator.GetSkippedOffset();
 | 
						||
  *aXOffset = mXOffset;
 | 
						||
  *aHyphenWidth = 0;
 | 
						||
  if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
 | 
						||
      mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
 | 
						||
    *aHyphenWidth = mProvider.GetHyphenWidth();
 | 
						||
  }
 | 
						||
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft,
 | 
						||
                               gfxTextRun::Metrics* aMetrics,
 | 
						||
                               gfxFont::BoundingBoxType aBoundingBoxType,
 | 
						||
                               DrawTarget* aDrawTarget) {
 | 
						||
  // Fix up metrics to include hyphen
 | 
						||
  RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget);
 | 
						||
  if (!hyphenTextRun) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxTextRun::Metrics hyphenMetrics =
 | 
						||
      hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
 | 
						||
  if (aTextFrame->GetWritingMode().IsLineInverted()) {
 | 
						||
    hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
 | 
						||
  }
 | 
						||
  aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft);
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
 | 
						||
                                 const StyleSimpleShadow& aShadowDetails,
 | 
						||
                                 gfxRect& aBoundingBox, uint32_t aBlurFlags) {
 | 
						||
  AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
 | 
						||
 | 
						||
  nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(),
 | 
						||
                       aShadowDetails.vertical.ToAppUnits());
 | 
						||
  nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0);
 | 
						||
 | 
						||
  nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor);
 | 
						||
 | 
						||
  if (auto* textDrawer = aParams.context->GetTextDrawer()) {
 | 
						||
    wr::Shadow wrShadow;
 | 
						||
 | 
						||
    wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x),
 | 
						||
                       PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)};
 | 
						||
 | 
						||
    wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
 | 
						||
    wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
 | 
						||
 | 
						||
    bool inflate = true;
 | 
						||
    textDrawer->AppendShadow(wrShadow, inflate);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // This rect is the box which is equivalent to where the shadow will be
 | 
						||
  // painted. The origin of aBoundingBox is the text baseline left, so we must
 | 
						||
  // translate it by that much in order to make the origin the top-left corner
 | 
						||
  // of the text bounding box. Note that aLeftSideOffset is line-left, so
 | 
						||
  // actually means top offset in vertical writing modes.
 | 
						||
  gfxRect shadowGfxRect;
 | 
						||
  WritingMode wm = GetWritingMode();
 | 
						||
  if (wm.IsVertical()) {
 | 
						||
    shadowGfxRect = aBoundingBox;
 | 
						||
    if (wm.IsVerticalRL()) {
 | 
						||
      // for vertical-RL, reverse direction of x-coords of bounding box
 | 
						||
      shadowGfxRect.x = -shadowGfxRect.XMost();
 | 
						||
    }
 | 
						||
    shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
 | 
						||
                              aParams.framePt.y + aParams.leftSideOffset);
 | 
						||
  } else {
 | 
						||
    shadowGfxRect =
 | 
						||
        aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
 | 
						||
                                aParams.textBaselinePt.y);
 | 
						||
  }
 | 
						||
  Point shadowGfxOffset(shadowOffset.x, shadowOffset.y);
 | 
						||
  shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y);
 | 
						||
 | 
						||
  nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
 | 
						||
                    NSToCoordRound(shadowGfxRect.Y()),
 | 
						||
                    NSToCoordRound(shadowGfxRect.Width()),
 | 
						||
                    NSToCoordRound(shadowGfxRect.Height()));
 | 
						||
 | 
						||
  nsContextBoxBlur contextBoxBlur;
 | 
						||
  const auto A2D = PresContext()->AppUnitsPerDevPixel();
 | 
						||
  gfxContext* shadowContext =
 | 
						||
      contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context,
 | 
						||
                          LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D),
 | 
						||
                          nullptr, aBlurFlags);
 | 
						||
  if (!shadowContext) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  aParams.context->Save();
 | 
						||
  aParams.context->SetColor(sRGBColor::FromABGR(shadowColor));
 | 
						||
 | 
						||
  // Draw the text onto our alpha-only surface to capture the alpha values.
 | 
						||
  // Remember that the box blur context has a device offset on it, so we don't
 | 
						||
  // need to translate any coordinates to fit on the surface.
 | 
						||
  gfxFloat advanceWidth;
 | 
						||
  nsTextPaintStyle textPaintStyle(this);
 | 
						||
  DrawTextParams params(shadowContext, PresContext()->FontPaletteCache());
 | 
						||
  params.paintingShadows = true;
 | 
						||
  params.advanceWidth = &advanceWidth;
 | 
						||
  params.dirtyRect = aParams.dirtyRect;
 | 
						||
  params.framePt = aParams.framePt + shadowGfxOffset;
 | 
						||
  params.provider = aParams.provider;
 | 
						||
  params.textStyle = &textPaintStyle;
 | 
						||
  params.textColor =
 | 
						||
      aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
 | 
						||
  params.callbacks = aParams.callbacks;
 | 
						||
  params.clipEdges = aParams.clipEdges;
 | 
						||
  params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
 | 
						||
  // Multi-color shadow is not allowed, so we use the same color as the text
 | 
						||
  // color.
 | 
						||
  params.decorationOverrideColor = ¶ms.textColor;
 | 
						||
  params.fontPalette = StyleFont()->GetFontPaletteAtom();
 | 
						||
 | 
						||
  DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params);
 | 
						||
 | 
						||
  contextBoxBlur.DoPaint();
 | 
						||
  aParams.context->Restore();
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
SelectionTypeMask nsTextFrame::CreateSelectionRangeList(
 | 
						||
    const SelectionDetails* aDetails, SelectionType aSelectionType,
 | 
						||
    const PaintTextSelectionParams& aParams,
 | 
						||
    nsTArray<SelectionRange>& aSelectionRanges, bool* aAnyBackgrounds) {
 | 
						||
  SelectionTypeMask allTypes = 0;
 | 
						||
  bool anyBackgrounds = false;
 | 
						||
 | 
						||
  uint32_t priorityOfInsertionOrder = 0;
 | 
						||
  for (const SelectionDetails* sd = aDetails; sd; sd = sd->mNext.get()) {
 | 
						||
    MOZ_ASSERT(sd->mStart >= 0 && sd->mEnd >= 0);  // XXX make unsigned?
 | 
						||
    uint32_t start = std::max(aParams.contentRange.start, uint32_t(sd->mStart));
 | 
						||
    uint32_t end = std::min(aParams.contentRange.end, uint32_t(sd->mEnd));
 | 
						||
    if (start < end) {
 | 
						||
      // The PaintTextWithSelectionColors caller passes SelectionType::eNone,
 | 
						||
      // so we collect all selections that set colors, and prioritize them
 | 
						||
      // according to selection type (lower types take precedence).
 | 
						||
      if (aSelectionType == SelectionType::eNone) {
 | 
						||
        allTypes |= ToSelectionTypeMask(sd->mSelectionType);
 | 
						||
        // Ignore selections that don't set colors.
 | 
						||
        nscolor foreground(0), background(0);
 | 
						||
        if (GetSelectionTextColors(sd->mSelectionType,
 | 
						||
                                   sd->mHighlightData.mHighlightName,
 | 
						||
                                   *aParams.textPaintStyle, sd->mTextRangeStyle,
 | 
						||
                                   &foreground, &background)) {
 | 
						||
          if (NS_GET_A(background) > 0) {
 | 
						||
            anyBackgrounds = true;
 | 
						||
          }
 | 
						||
          aSelectionRanges.AppendElement(
 | 
						||
              SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
 | 
						||
        }
 | 
						||
      } else if (sd->mSelectionType == aSelectionType) {
 | 
						||
        // The PaintSelectionTextDecorations caller passes a specific type,
 | 
						||
        // so we include only ranges of that type, and keep them in order
 | 
						||
        // so that later ones take precedence over earlier.
 | 
						||
        aSelectionRanges.AppendElement(
 | 
						||
            SelectionRange{sd, {start, end}, priorityOfInsertionOrder++});
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (aAnyBackgrounds) {
 | 
						||
    *aAnyBackgrounds = anyBackgrounds;
 | 
						||
  }
 | 
						||
  return allTypes;
 | 
						||
}
 | 
						||
 | 
						||
/* static */
 | 
						||
void nsTextFrame::CombineSelectionRanges(
 | 
						||
    const nsTArray<SelectionRange>& aSelectionRanges,
 | 
						||
    nsTArray<PriorityOrderedSelectionsForRange>& aCombinedSelectionRanges) {
 | 
						||
  struct SelectionRangeEndCmp {
 | 
						||
    bool Equals(const SelectionRange* a, const SelectionRange* b) const {
 | 
						||
      return a->mRange.end == b->mRange.end;
 | 
						||
    }
 | 
						||
    bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
 | 
						||
      return a->mRange.end < b->mRange.end;
 | 
						||
    }
 | 
						||
  };
 | 
						||
 | 
						||
  struct SelectionRangePriorityCmp {
 | 
						||
    bool Equals(const SelectionRange* a, const SelectionRange* b) const {
 | 
						||
      const SelectionDetails* aDetails = a->mDetails;
 | 
						||
      const SelectionDetails* bDetails = b->mDetails;
 | 
						||
      if (aDetails->mSelectionType != bDetails->mSelectionType) {
 | 
						||
        return false;
 | 
						||
      }
 | 
						||
      if (aDetails->mSelectionType != SelectionType::eHighlight) {
 | 
						||
        return a->mPriority == b->mPriority;
 | 
						||
      }
 | 
						||
      if (aDetails->mHighlightData.mHighlight->Priority() !=
 | 
						||
          bDetails->mHighlightData.mHighlight->Priority()) {
 | 
						||
        return false;
 | 
						||
      }
 | 
						||
      return a->mPriority == b->mPriority;
 | 
						||
    }
 | 
						||
 | 
						||
    bool LessThan(const SelectionRange* a, const SelectionRange* b) const {
 | 
						||
      if (a->mDetails->mSelectionType != b->mDetails->mSelectionType) {
 | 
						||
        // Even though this looks counter-intuitive,
 | 
						||
        // this is intended, as values in `SelectionType` are inverted:
 | 
						||
        // a lower value indicates a higher priority.
 | 
						||
        return a->mDetails->mSelectionType > b->mDetails->mSelectionType;
 | 
						||
      }
 | 
						||
 | 
						||
      if (a->mDetails->mSelectionType != SelectionType::eHighlight) {
 | 
						||
        // for non-highlights, the selection which was added later
 | 
						||
        // has a higher priority.
 | 
						||
        return a->mPriority < b->mPriority;
 | 
						||
      }
 | 
						||
 | 
						||
      if (a->mDetails->mHighlightData.mHighlight->Priority() !=
 | 
						||
          b->mDetails->mHighlightData.mHighlight->Priority()) {
 | 
						||
        // For highlights, first compare the priorities set by the user.
 | 
						||
        return a->mDetails->mHighlightData.mHighlight->Priority() <
 | 
						||
               b->mDetails->mHighlightData.mHighlight->Priority();
 | 
						||
      }
 | 
						||
      // only if the user priorities are equal, let the highlight that was added
 | 
						||
      // later take precedence.
 | 
						||
      return a->mPriority < b->mPriority;
 | 
						||
    }
 | 
						||
  };
 | 
						||
 | 
						||
  uint32_t currentOffset = 0;
 | 
						||
  AutoTArray<const SelectionRange*, 1> activeSelectionsForCurrentSegment;
 | 
						||
  size_t rangeIndex = 0;
 | 
						||
 | 
						||
  // Divide the given selection ranges into segments which share the same
 | 
						||
  // set of selections.
 | 
						||
  // The following algorithm iterates `aSelectionRanges`, assuming
 | 
						||
  // that its elements are sorted by their start offset.
 | 
						||
  // Each time a new selection starts, it is pushed into an array of
 | 
						||
  // "currently present" selections, sorted by their *end* offset.
 | 
						||
  // For each iteration the next segment end offset is determined,
 | 
						||
  // which is either the start offset of the next selection or
 | 
						||
  // the next end offset of all "currently present" selections
 | 
						||
  // (which is always the first element of the array because of its order).
 | 
						||
  // Then, a `CombinedSelectionRange` can be constructed, which describes
 | 
						||
  // the text segment until its end offset (as determined above), and contains
 | 
						||
  // all elements of the "currently present" selection list, now sorted
 | 
						||
  // by their priority.
 | 
						||
  // If a range ends at the given offset, it is removed from the array.
 | 
						||
  while (rangeIndex < aSelectionRanges.Length() ||
 | 
						||
         !activeSelectionsForCurrentSegment.IsEmpty()) {
 | 
						||
    uint32_t currentSegmentEndOffset =
 | 
						||
        activeSelectionsForCurrentSegment.IsEmpty()
 | 
						||
            ? -1
 | 
						||
            : activeSelectionsForCurrentSegment[0]->mRange.end;
 | 
						||
    uint32_t nextRangeStartOffset =
 | 
						||
        rangeIndex < aSelectionRanges.Length()
 | 
						||
            ? aSelectionRanges[rangeIndex].mRange.start
 | 
						||
            : -1;
 | 
						||
    uint32_t nextOffset =
 | 
						||
        std::min(currentSegmentEndOffset, nextRangeStartOffset);
 | 
						||
    if (!activeSelectionsForCurrentSegment.IsEmpty() &&
 | 
						||
        currentOffset != nextOffset) {
 | 
						||
      auto activeSelectionRangesSortedByPriority =
 | 
						||
          activeSelectionsForCurrentSegment.Clone();
 | 
						||
      activeSelectionRangesSortedByPriority.Sort(SelectionRangePriorityCmp());
 | 
						||
 | 
						||
      AutoTArray<const SelectionDetails*, 1> selectionDetails;
 | 
						||
      selectionDetails.SetCapacity(
 | 
						||
          activeSelectionRangesSortedByPriority.Length());
 | 
						||
      // ensure that overlapping highlights which have the same name
 | 
						||
      // are only added once. If added each time, they would be painted
 | 
						||
      // several times (see wpt
 | 
						||
      // /css/css-highlight-api/painting/custom-highlight-painting-003.html)
 | 
						||
      // Comparing the highlight name with the previous one is
 | 
						||
      // sufficient here because selections are already sorted
 | 
						||
      // in a way that ensures that highlights of the same name are
 | 
						||
      // grouped together.
 | 
						||
      nsAtom* currentHighlightName = nullptr;
 | 
						||
      for (const auto* selectionRange : activeSelectionRangesSortedByPriority) {
 | 
						||
        if (selectionRange->mDetails->mSelectionType ==
 | 
						||
            SelectionType::eHighlight) {
 | 
						||
          if (selectionRange->mDetails->mHighlightData.mHighlightName ==
 | 
						||
              currentHighlightName) {
 | 
						||
            continue;
 | 
						||
          }
 | 
						||
          currentHighlightName =
 | 
						||
              selectionRange->mDetails->mHighlightData.mHighlightName;
 | 
						||
        }
 | 
						||
        selectionDetails.AppendElement(selectionRange->mDetails);
 | 
						||
      }
 | 
						||
      aCombinedSelectionRanges.AppendElement(PriorityOrderedSelectionsForRange{
 | 
						||
          std::move(selectionDetails), {currentOffset, nextOffset}});
 | 
						||
    }
 | 
						||
    currentOffset = nextOffset;
 | 
						||
 | 
						||
    if (nextRangeStartOffset < currentSegmentEndOffset) {
 | 
						||
      activeSelectionsForCurrentSegment.InsertElementSorted(
 | 
						||
          &aSelectionRanges[rangeIndex++], SelectionRangeEndCmp());
 | 
						||
    } else {
 | 
						||
      activeSelectionsForCurrentSegment.RemoveElementAt(0);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
SelectionTypeMask nsTextFrame::ResolveSelections(
 | 
						||
    const PaintTextSelectionParams& aParams, const SelectionDetails* aDetails,
 | 
						||
    nsTArray<PriorityOrderedSelectionsForRange>& aResult,
 | 
						||
    SelectionType aSelectionType, bool* aAnyBackgrounds) const {
 | 
						||
  AutoTArray<SelectionRange, 4> selectionRanges;
 | 
						||
 | 
						||
  SelectionTypeMask allTypes = CreateSelectionRangeList(
 | 
						||
      aDetails, aSelectionType, aParams, selectionRanges, aAnyBackgrounds);
 | 
						||
 | 
						||
  if (selectionRanges.IsEmpty()) {
 | 
						||
    return allTypes;
 | 
						||
  }
 | 
						||
 | 
						||
  struct SelectionRangeStartCmp {
 | 
						||
    bool Equals(const SelectionRange& a, const SelectionRange& b) const {
 | 
						||
      return a.mRange.start == b.mRange.start;
 | 
						||
    }
 | 
						||
    bool LessThan(const SelectionRange& a, const SelectionRange& b) const {
 | 
						||
      return a.mRange.start < b.mRange.start;
 | 
						||
    }
 | 
						||
  };
 | 
						||
  selectionRanges.Sort(SelectionRangeStartCmp());
 | 
						||
 | 
						||
  CombineSelectionRanges(selectionRanges, aResult);
 | 
						||
 | 
						||
  return allTypes;
 | 
						||
}
 | 
						||
 | 
						||
// Paints selection backgrounds and text in the correct colors. Also computes
 | 
						||
// aAllSelectionTypeMask, the union of all selection types that are applying to
 | 
						||
// this text.
 | 
						||
bool nsTextFrame::PaintTextWithSelectionColors(
 | 
						||
    const PaintTextSelectionParams& aParams,
 | 
						||
    const UniquePtr<SelectionDetails>& aDetails,
 | 
						||
    SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges) {
 | 
						||
  bool anyBackgrounds = false;
 | 
						||
  AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
 | 
						||
 | 
						||
  *aAllSelectionTypeMask =
 | 
						||
      ResolveSelections(aParams, aDetails.get(), selectionRanges,
 | 
						||
                        SelectionType::eNone, &anyBackgrounds);
 | 
						||
  bool vertical = mTextRun->IsVertical();
 | 
						||
  const gfxFloat startIOffset =
 | 
						||
      vertical ? aParams.textBaselinePt.y - aParams.framePt.y
 | 
						||
               : aParams.textBaselinePt.x - aParams.framePt.x;
 | 
						||
  gfxFloat iOffset, hyphenWidth;
 | 
						||
  Range range;  // in transformed string
 | 
						||
 | 
						||
  const gfxTextRun::Range& contentRange = aParams.contentRange;
 | 
						||
  auto* textDrawer = aParams.context->GetTextDrawer();
 | 
						||
 | 
						||
  if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
 | 
						||
    int32_t appUnitsPerDevPixel =
 | 
						||
        aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
 | 
						||
    SelectionRangeIterator iterator(selectionRanges, contentRange,
 | 
						||
                                    *aParams.provider, mTextRun, startIOffset);
 | 
						||
    AutoTArray<SelectionType, 1> selectionTypes;
 | 
						||
    AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
 | 
						||
    AutoTArray<TextRangeStyle, 1> rangeStyles;
 | 
						||
    while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
 | 
						||
                                   selectionTypes, highlightNames,
 | 
						||
                                   rangeStyles)) {
 | 
						||
      nscolor foreground(0), background(0);
 | 
						||
      gfxFloat advance =
 | 
						||
          hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
 | 
						||
      nsRect bgRect;
 | 
						||
      gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
 | 
						||
      if (vertical) {
 | 
						||
        bgRect = nsRect(nscoord(aParams.framePt.x),
 | 
						||
                        nscoord(aParams.framePt.y + offs), GetSize().width,
 | 
						||
                        nscoord(advance));
 | 
						||
      } else {
 | 
						||
        bgRect = nsRect(nscoord(aParams.framePt.x + offs),
 | 
						||
                        nscoord(aParams.framePt.y), nscoord(advance),
 | 
						||
                        GetSize().height);
 | 
						||
      }
 | 
						||
 | 
						||
      LayoutDeviceRect selectionRect =
 | 
						||
          LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
 | 
						||
      // The elements in `selectionTypes` are ordered ascending by their
 | 
						||
      // priority. To account for non-opaque overlapping selections, all
 | 
						||
      // selection backgrounds are painted.
 | 
						||
      for (size_t index = 0; index < selectionTypes.Length(); ++index) {
 | 
						||
        GetSelectionTextColors(selectionTypes[index], highlightNames[index],
 | 
						||
                               *aParams.textPaintStyle, rangeStyles[index],
 | 
						||
                               &foreground, &background);
 | 
						||
 | 
						||
        // Draw background color
 | 
						||
        if (NS_GET_A(background) > 0) {
 | 
						||
          if (textDrawer) {
 | 
						||
            textDrawer->AppendSelectionRect(selectionRect,
 | 
						||
                                            ToDeviceColor(background));
 | 
						||
          } else {
 | 
						||
            PaintSelectionBackground(*aParams.context->GetDrawTarget(),
 | 
						||
                                     background, aParams.dirtyRect,
 | 
						||
                                     selectionRect, aParams.callbacks);
 | 
						||
          }
 | 
						||
        }
 | 
						||
      }
 | 
						||
      iterator.UpdateWithAdvance(advance);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  gfxFloat advance;
 | 
						||
  DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
 | 
						||
  params.dirtyRect = aParams.dirtyRect;
 | 
						||
  params.framePt = aParams.framePt;
 | 
						||
  params.provider = aParams.provider;
 | 
						||
  params.textStyle = aParams.textPaintStyle;
 | 
						||
  params.clipEdges = &aClipEdges;
 | 
						||
  params.advanceWidth = &advance;
 | 
						||
  params.callbacks = aParams.callbacks;
 | 
						||
  params.glyphRange = aParams.glyphRange;
 | 
						||
  params.fontPalette = StyleFont()->GetFontPaletteAtom();
 | 
						||
  params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
 | 
						||
 | 
						||
  PaintShadowParams shadowParams(aParams);
 | 
						||
  shadowParams.provider = aParams.provider;
 | 
						||
  shadowParams.callbacks = aParams.callbacks;
 | 
						||
  shadowParams.clipEdges = &aClipEdges;
 | 
						||
 | 
						||
  // Draw text
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
  SelectionRangeIterator iterator(selectionRanges, contentRange,
 | 
						||
                                  *aParams.provider, mTextRun, startIOffset);
 | 
						||
  AutoTArray<SelectionType, 1> selectionTypes;
 | 
						||
  AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
 | 
						||
  AutoTArray<TextRangeStyle, 1> rangeStyles;
 | 
						||
  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, selectionTypes,
 | 
						||
                                 highlightNames, rangeStyles)) {
 | 
						||
    nscolor foreground(0), background(0);
 | 
						||
    if (aParams.IsGenerateTextMask()) {
 | 
						||
      foreground = NS_RGBA(0, 0, 0, 255);
 | 
						||
    } else {
 | 
						||
      nscolor tmpForeground(0);
 | 
						||
      bool colorHasBeenSet = false;
 | 
						||
      for (size_t index = 0; index < selectionTypes.Length(); ++index) {
 | 
						||
        if (selectionTypes[index] == SelectionType::eHighlight) {
 | 
						||
          if (aParams.textPaintStyle->GetCustomHighlightTextColor(
 | 
						||
                  highlightNames[index], &tmpForeground)) {
 | 
						||
            foreground = tmpForeground;
 | 
						||
            colorHasBeenSet = true;
 | 
						||
          }
 | 
						||
 | 
						||
        } else {
 | 
						||
          GetSelectionTextColors(selectionTypes[index], highlightNames[index],
 | 
						||
                                 *aParams.textPaintStyle, rangeStyles[index],
 | 
						||
                                 &foreground, &background);
 | 
						||
          colorHasBeenSet = true;
 | 
						||
        }
 | 
						||
      }
 | 
						||
      if (!colorHasBeenSet) {
 | 
						||
        foreground = tmpForeground;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    gfx::Point textBaselinePt =
 | 
						||
        vertical
 | 
						||
            ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset)
 | 
						||
            : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
 | 
						||
 | 
						||
    // Determine what shadow, if any, to draw - either from textStyle
 | 
						||
    // or from the ::-moz-selection pseudo-class if specified there
 | 
						||
    Span<const StyleSimpleShadow> shadows = textStyle->mTextShadow.AsSpan();
 | 
						||
    for (auto selectionType : selectionTypes) {
 | 
						||
      GetSelectionTextShadow(selectionType, *aParams.textPaintStyle, &shadows);
 | 
						||
    }
 | 
						||
    if (!shadows.IsEmpty()) {
 | 
						||
      nscoord startEdge = iOffset;
 | 
						||
      if (mTextRun->IsInlineReversed()) {
 | 
						||
        startEdge -=
 | 
						||
            hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
 | 
						||
      }
 | 
						||
      shadowParams.range = range;
 | 
						||
      shadowParams.textBaselinePt = textBaselinePt;
 | 
						||
      shadowParams.foregroundColor = foreground;
 | 
						||
      shadowParams.leftSideOffset = startEdge;
 | 
						||
      PaintShadows(shadows, shadowParams);
 | 
						||
    }
 | 
						||
 | 
						||
    // Draw text segment
 | 
						||
    params.textColor = foreground;
 | 
						||
    params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
 | 
						||
    params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
 | 
						||
    params.drawSoftHyphen = hyphenWidth > 0;
 | 
						||
    DrawText(range, textBaselinePt, params);
 | 
						||
    advance += hyphenWidth;
 | 
						||
    iterator.UpdateWithAdvance(advance);
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PaintTextSelectionDecorations(
 | 
						||
    const PaintTextSelectionParams& aParams,
 | 
						||
    const UniquePtr<SelectionDetails>& aDetails, SelectionType aSelectionType) {
 | 
						||
  // Hide text decorations if we're currently hiding @font-face fallback text
 | 
						||
  if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  AutoTArray<PriorityOrderedSelectionsForRange, 8> selectionRanges;
 | 
						||
  ResolveSelections(aParams, aDetails.get(), selectionRanges, aSelectionType);
 | 
						||
 | 
						||
  RefPtr<gfxFont> firstFont =
 | 
						||
      aParams.provider->GetFontGroup()->GetFirstValidFont();
 | 
						||
  bool verticalRun = mTextRun->IsVertical();
 | 
						||
  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
 | 
						||
  bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style());
 | 
						||
  const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE
 | 
						||
                                          : StyleTextDecorationLine::UNDERLINE;
 | 
						||
  gfxFont::Metrics decorationMetrics(
 | 
						||
      firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
 | 
						||
                                               : nsFontMetrics::eHorizontal));
 | 
						||
  decorationMetrics.underlineOffset =
 | 
						||
      aParams.provider->GetFontGroup()->GetUnderlineOffset();
 | 
						||
 | 
						||
  const gfxTextRun::Range& contentRange = aParams.contentRange;
 | 
						||
  gfxFloat startIOffset = verticalRun
 | 
						||
                              ? aParams.textBaselinePt.y - aParams.framePt.y
 | 
						||
                              : aParams.textBaselinePt.x - aParams.framePt.x;
 | 
						||
  SelectionRangeIterator iterator(selectionRanges, contentRange,
 | 
						||
                                  *aParams.provider, mTextRun, startIOffset);
 | 
						||
  gfxFloat iOffset, hyphenWidth;
 | 
						||
  Range range;
 | 
						||
  int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
 | 
						||
  // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
 | 
						||
  Point pt;
 | 
						||
  if (verticalRun) {
 | 
						||
    pt.x = (aParams.textBaselinePt.x - mAscent) / app;
 | 
						||
  } else {
 | 
						||
    pt.y = (aParams.textBaselinePt.y - mAscent) / app;
 | 
						||
  }
 | 
						||
  AutoTArray<SelectionType, 1> nextSelectionTypes;
 | 
						||
  AutoTArray<RefPtr<nsAtom>, 1> highlightNames;
 | 
						||
  AutoTArray<TextRangeStyle, 1> selectedStyles;
 | 
						||
 | 
						||
  while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
 | 
						||
                                 nextSelectionTypes, highlightNames,
 | 
						||
                                 selectedStyles)) {
 | 
						||
    gfxFloat advance =
 | 
						||
        hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
 | 
						||
    for (size_t index = 0; index < nextSelectionTypes.Length(); ++index) {
 | 
						||
      if (nextSelectionTypes[index] == aSelectionType) {
 | 
						||
        if (verticalRun) {
 | 
						||
          pt.y = (aParams.framePt.y + iOffset -
 | 
						||
                  (mTextRun->IsInlineReversed() ? advance : 0)) /
 | 
						||
                 app;
 | 
						||
        } else {
 | 
						||
          pt.x = (aParams.framePt.x + iOffset -
 | 
						||
                  (mTextRun->IsInlineReversed() ? advance : 0)) /
 | 
						||
                 app;
 | 
						||
        }
 | 
						||
        gfxFloat width = Abs(advance) / app;
 | 
						||
        gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
 | 
						||
        DrawSelectionDecorations(aParams.context, aParams.dirtyRect,
 | 
						||
                                 aSelectionType, *aParams.textPaintStyle,
 | 
						||
                                 selectedStyles[index], pt, xInFrame, width,
 | 
						||
                                 mAscent / app, decorationMetrics,
 | 
						||
                                 aParams.callbacks, verticalRun, kDecoration);
 | 
						||
      }
 | 
						||
    }
 | 
						||
    iterator.UpdateWithAdvance(advance);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::PaintTextWithSelection(
 | 
						||
    const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges) {
 | 
						||
  NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path");
 | 
						||
 | 
						||
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
 | 
						||
  if (!details) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  SelectionTypeMask allSelectionTypeMask;
 | 
						||
  if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask,
 | 
						||
                                    aClipEdges)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  // Iterate through just the selection rawSelectionTypes that paint decorations
 | 
						||
  // and paint decorations for any that actually occur in this frame. Paint
 | 
						||
  // higher-numbered selection rawSelectionTypes below lower-numered ones on the
 | 
						||
  // general principal that lower-numbered selections are higher priority.
 | 
						||
  allSelectionTypeMask &= kSelectionTypesWithDecorations;
 | 
						||
  MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,
 | 
						||
             "The following for loop assumes that the first item of "
 | 
						||
             "kPresentSelectionTypes is SelectionType::eNormal");
 | 
						||
  for (size_t i = std::size(kPresentSelectionTypes) - 1; i >= 1; --i) {
 | 
						||
    SelectionType selectionType = kPresentSelectionTypes[i];
 | 
						||
    if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
 | 
						||
      // There is some selection of this selectionType. Try to paint its
 | 
						||
      // decorations (there might not be any for this type but that's OK,
 | 
						||
      // PaintTextSelectionDecorations will exit early).
 | 
						||
      PaintTextSelectionDecorations(aParams, details, selectionType);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
 | 
						||
                                    const gfx::Point& aTextBaselinePt,
 | 
						||
                                    const gfx::Point& aFramePt, Range aRange,
 | 
						||
                                    const nscolor* aDecorationOverrideColor,
 | 
						||
                                    PropertyProvider* aProvider) {
 | 
						||
  const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
 | 
						||
  if (!info) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  bool isTextCombined = Style()->IsTextCombined();
 | 
						||
  if (isTextCombined && !aWM.IsVertical()) {
 | 
						||
    // XXX This only happens when the parent is display:contents with an
 | 
						||
    // orthogonal writing mode. This should be rare, and don't have use
 | 
						||
    // cases, so we don't care. It is non-trivial to implement a sane
 | 
						||
    // behavior for that case: if you treat the text as not combined,
 | 
						||
    // the marks would spread wider than the text (which is rendered as
 | 
						||
    // combined); if you try to draw a single mark, selecting part of
 | 
						||
    // the text could dynamically create multiple new marks.
 | 
						||
    NS_WARNING("Give up on combined text with horizontal wm");
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  nscolor color =
 | 
						||
      aDecorationOverrideColor
 | 
						||
          ? *aDecorationOverrideColor
 | 
						||
          : nsLayoutUtils::GetTextColor(this, &nsStyleText::mTextEmphasisColor);
 | 
						||
  aContext->SetColor(sRGBColor::FromABGR(color));
 | 
						||
  gfx::Point pt;
 | 
						||
  if (!isTextCombined) {
 | 
						||
    pt = aTextBaselinePt;
 | 
						||
  } else {
 | 
						||
    MOZ_ASSERT(aWM.IsVertical());
 | 
						||
    pt = aFramePt;
 | 
						||
    if (aWM.IsVerticalRL()) {
 | 
						||
      pt.x += GetSize().width - GetLogicalBaseline(aWM);
 | 
						||
    } else {
 | 
						||
      pt.x += GetLogicalBaseline(aWM);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (!aWM.IsVertical()) {
 | 
						||
    pt.y += info->baselineOffset;
 | 
						||
  } else {
 | 
						||
    if (aWM.IsVerticalRL()) {
 | 
						||
      pt.x -= info->baselineOffset;
 | 
						||
    } else {
 | 
						||
      pt.x += info->baselineOffset;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (!isTextCombined) {
 | 
						||
    mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance,
 | 
						||
                                pt, aRange, aProvider,
 | 
						||
                                PresContext()->FontPaletteCache());
 | 
						||
  } else {
 | 
						||
    pt.y += (GetSize().height - info->advance) / 2;
 | 
						||
    gfxTextRun::DrawParams params(aContext, PresContext()->FontPaletteCache());
 | 
						||
    info->textRun->Draw(Range(info->textRun.get()), pt, params);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) {
 | 
						||
  MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
 | 
						||
 | 
						||
  nscolor result = nsIFrame::GetCaretColorAt(aOffset);
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  int32_t contentOffset = provider.GetStart().GetOriginalOffset();
 | 
						||
  int32_t contentLength = provider.GetOriginalLength();
 | 
						||
  MOZ_ASSERT(
 | 
						||
      aOffset >= contentOffset && aOffset <= contentOffset + contentLength,
 | 
						||
      "aOffset must be in the frame's range");
 | 
						||
 | 
						||
  int32_t offsetInFrame = aOffset - contentOffset;
 | 
						||
  if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  bool isSolidTextColor = true;
 | 
						||
  if (IsInSVGTextSubtree()) {
 | 
						||
    const nsStyleSVG* style = StyleSVG();
 | 
						||
    if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) {
 | 
						||
      isSolidTextColor = false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextPaintStyle textPaintStyle(this);
 | 
						||
  textPaintStyle.SetResolveColors(isSolidTextColor);
 | 
						||
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
 | 
						||
  SelectionType selectionType = SelectionType::eNone;
 | 
						||
  for (SelectionDetails* sdptr = details.get(); sdptr;
 | 
						||
       sdptr = sdptr->mNext.get()) {
 | 
						||
    int32_t start = std::max(0, sdptr->mStart - contentOffset);
 | 
						||
    int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
 | 
						||
    if (start <= offsetInFrame && offsetInFrame < end &&
 | 
						||
        (selectionType == SelectionType::eNone ||
 | 
						||
         sdptr->mSelectionType < selectionType)) {
 | 
						||
      nscolor foreground, background;
 | 
						||
      if (GetSelectionTextColors(sdptr->mSelectionType,
 | 
						||
                                 sdptr->mHighlightData.mHighlightName,
 | 
						||
                                 textPaintStyle, sdptr->mTextRangeStyle,
 | 
						||
                                 &foreground, &background)) {
 | 
						||
        if (!isSolidTextColor && NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
 | 
						||
          result = NS_RGBA(0, 0, 0, 255);
 | 
						||
        } else {
 | 
						||
          result = foreground;
 | 
						||
        }
 | 
						||
        selectionType = sdptr->mSelectionType;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
static gfxTextRun::Range ComputeTransformedRange(
 | 
						||
    nsTextFrame::PropertyProvider& aProvider) {
 | 
						||
  gfxSkipCharsIterator iter(aProvider.GetStart());
 | 
						||
  uint32_t start = iter.GetSkippedOffset();
 | 
						||
  iter.AdvanceOriginal(aProvider.GetOriginalLength());
 | 
						||
  return gfxTextRun::Range(start, iter.GetSkippedOffset());
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
 | 
						||
                                         nscoord aVisIEndEdge,
 | 
						||
                                         nscoord* aSnappedStartEdge,
 | 
						||
                                         nscoord* aSnappedEndEdge) {
 | 
						||
  // We need a *reference* rendering context (not one that might have a
 | 
						||
  // transform), so we don't have a rendering context argument.
 | 
						||
  // XXX get the block and line passed to us somehow! This is slow!
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  // Trim trailing whitespace
 | 
						||
  provider.InitializeForDisplay(true);
 | 
						||
 | 
						||
  Range range = ComputeTransformedRange(provider);
 | 
						||
  uint32_t startOffset = range.start;
 | 
						||
  uint32_t maxLength = range.Length();
 | 
						||
  return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
 | 
						||
                                &startOffset, &maxLength, aSnappedStartEdge,
 | 
						||
                                aSnappedEndEdge);
 | 
						||
}
 | 
						||
 | 
						||
static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
 | 
						||
                                 uint32_t aStartOffset, uint32_t aMaxLength) {
 | 
						||
  uint32_t clusterLength = 0;
 | 
						||
  while (++clusterLength < aMaxLength) {
 | 
						||
    if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
 | 
						||
      return clusterLength;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return aMaxLength;
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::MeasureCharClippedText(
 | 
						||
    PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge,
 | 
						||
    uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge,
 | 
						||
    nscoord* aSnappedEndEdge) {
 | 
						||
  *aSnappedStartEdge = 0;
 | 
						||
  *aSnappedEndEdge = 0;
 | 
						||
  if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t offset = *aStartOffset;
 | 
						||
  uint32_t maxLength = *aMaxLength;
 | 
						||
  const nscoord frameISize = ISize();
 | 
						||
  const bool rtl = mTextRun->IsRightToLeft();
 | 
						||
  gfxFloat advanceWidth = 0;
 | 
						||
  const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
 | 
						||
  if (startEdge > 0) {
 | 
						||
    const gfxFloat maxAdvance = gfxFloat(startEdge);
 | 
						||
    while (maxLength > 0) {
 | 
						||
      uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
 | 
						||
      advanceWidth += mTextRun->GetAdvanceWidth(
 | 
						||
          Range(offset, offset + clusterLength), &aProvider);
 | 
						||
      maxLength -= clusterLength;
 | 
						||
      offset += clusterLength;
 | 
						||
      if (advanceWidth >= maxAdvance) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
 | 
						||
    *snappedStartEdge = NSToCoordFloor(advanceWidth);
 | 
						||
    *aStartOffset = offset;
 | 
						||
  }
 | 
						||
 | 
						||
  const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
 | 
						||
  if (endEdge > 0) {
 | 
						||
    const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
 | 
						||
    while (maxLength > 0) {
 | 
						||
      uint32_t clusterLength = GetClusterLength(mTextRun, offset, maxLength);
 | 
						||
      gfxFloat nextAdvance =
 | 
						||
          advanceWidth + mTextRun->GetAdvanceWidth(
 | 
						||
                             Range(offset, offset + clusterLength), &aProvider);
 | 
						||
      if (nextAdvance > maxAdvance) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      // This cluster fits, include it.
 | 
						||
      advanceWidth = nextAdvance;
 | 
						||
      maxLength -= clusterLength;
 | 
						||
      offset += clusterLength;
 | 
						||
    }
 | 
						||
    maxLength = offset - *aStartOffset;
 | 
						||
    nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
 | 
						||
    *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
 | 
						||
  }
 | 
						||
  *aMaxLength = maxLength;
 | 
						||
  return maxLength != 0;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows,
 | 
						||
                               const PaintShadowParams& aParams) {
 | 
						||
  if (aShadows.IsEmpty()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText(
 | 
						||
      aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider);
 | 
						||
  if (GetWritingMode().IsLineInverted()) {
 | 
						||
    std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
 | 
						||
    shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
 | 
						||
  }
 | 
						||
  if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
 | 
						||
    AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics,
 | 
						||
                       gfxFont::LOOSE_INK_EXTENTS,
 | 
						||
                       aParams.context->GetDrawTarget());
 | 
						||
  }
 | 
						||
  // Add bounds of text decorations
 | 
						||
  gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth,
 | 
						||
                         shadowMetrics.mAscent + shadowMetrics.mDescent);
 | 
						||
  shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
 | 
						||
                                       decorationRect);
 | 
						||
 | 
						||
  // If the textrun uses any color or SVG fonts, we need to force use of a mask
 | 
						||
  // for shadow rendering even if blur radius is zero.
 | 
						||
  // Force disable hardware acceleration for text shadows since it's usually
 | 
						||
  // more expensive than just doing it on the CPU.
 | 
						||
  uint32_t blurFlags = nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR;
 | 
						||
  uint32_t numGlyphRuns;
 | 
						||
  const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
 | 
						||
  while (numGlyphRuns-- > 0) {
 | 
						||
    if (run->mFont->AlwaysNeedsMaskForShadow()) {
 | 
						||
      blurFlags |= nsContextBoxBlur::FORCE_MASK;
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    run++;
 | 
						||
  }
 | 
						||
 | 
						||
  if (mTextRun->IsVertical()) {
 | 
						||
    std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
 | 
						||
    std::swap(shadowMetrics.mBoundingBox.width,
 | 
						||
              shadowMetrics.mBoundingBox.height);
 | 
						||
  }
 | 
						||
 | 
						||
  for (const auto& shadow : Reversed(aShadows)) {
 | 
						||
    PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::PaintText(const PaintTextParams& aParams,
 | 
						||
                            const nscoord aVisIStartEdge,
 | 
						||
                            const nscoord aVisIEndEdge,
 | 
						||
                            const nsPoint& aToReferenceFrame,
 | 
						||
                            const bool aIsSelected,
 | 
						||
                            float aOpacity /* = 1.0f */) {
 | 
						||
#ifdef DEBUG
 | 
						||
  if (IsInSVGTextSubtree()) {
 | 
						||
    auto* container =
 | 
						||
        nsLayoutUtils::GetClosestFrameOfType(this, LayoutFrameType::SVGText);
 | 
						||
    MOZ_ASSERT(container);
 | 
						||
    MOZ_ASSERT(!container->HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ||
 | 
						||
                   !aParams.IsPaintText(),
 | 
						||
               "Expecting IsPaintText to be false for a clipPath");
 | 
						||
  }
 | 
						||
#endif
 | 
						||
 | 
						||
  // Don't pass in the rendering context here, because we need a
 | 
						||
  // *reference* context and rendering context might have some transform
 | 
						||
  // in it
 | 
						||
  // XXX get the block and line passed to us somehow! This is slow!
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
 | 
						||
  // Trim trailing whitespace, unless we're painting a selection highlight,
 | 
						||
  // which should include trailing spaces if present (bug 1146754).
 | 
						||
  provider.InitializeForDisplay(!aIsSelected);
 | 
						||
 | 
						||
  const bool reversed = mTextRun->IsInlineReversed();
 | 
						||
  const bool verticalRun = mTextRun->IsVertical();
 | 
						||
  WritingMode wm = GetWritingMode();
 | 
						||
  const float frameWidth = GetSize().width;
 | 
						||
  const float frameHeight = GetSize().height;
 | 
						||
  gfx::Point textBaselinePt;
 | 
						||
  if (verticalRun) {
 | 
						||
    if (wm.IsVerticalLR()) {
 | 
						||
      textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX(
 | 
						||
          this, aParams.context, nscoord(aParams.framePt.x), mAscent);
 | 
						||
    } else {
 | 
						||
      textBaselinePt.x = nsLayoutUtils::GetMaybeSnappedBaselineX(
 | 
						||
          this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
 | 
						||
          -mAscent);
 | 
						||
    }
 | 
						||
    textBaselinePt.y = reversed ? aParams.framePt.y.value + frameHeight
 | 
						||
                                : aParams.framePt.y.value;
 | 
						||
  } else {
 | 
						||
    textBaselinePt =
 | 
						||
        gfx::Point(reversed ? aParams.framePt.x.value + frameWidth
 | 
						||
                            : aParams.framePt.x.value,
 | 
						||
                   nsLayoutUtils::GetMaybeSnappedBaselineY(
 | 
						||
                       this, aParams.context, aParams.framePt.y, mAscent));
 | 
						||
  }
 | 
						||
  Range range = ComputeTransformedRange(provider);
 | 
						||
  uint32_t startOffset = range.start;
 | 
						||
  uint32_t maxLength = range.Length();
 | 
						||
  nscoord snappedStartEdge, snappedEndEdge;
 | 
						||
  if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
 | 
						||
                              &startOffset, &maxLength, &snappedStartEdge,
 | 
						||
                              &snappedEndEdge)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  if (verticalRun) {
 | 
						||
    textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
 | 
						||
  } else {
 | 
						||
    textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
 | 
						||
  }
 | 
						||
  const ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge,
 | 
						||
                            snappedEndEdge);
 | 
						||
  nsTextPaintStyle textPaintStyle(this);
 | 
						||
  textPaintStyle.SetResolveColors(!aParams.callbacks);
 | 
						||
 | 
						||
  // Fork off to the (slower) paint-with-selection path if necessary.
 | 
						||
  if (aIsSelected) {
 | 
						||
    MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
 | 
						||
    gfxSkipCharsIterator tmp(provider.GetStart());
 | 
						||
    Range contentRange(
 | 
						||
        uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
 | 
						||
        uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
 | 
						||
    PaintTextSelectionParams params(aParams);
 | 
						||
    params.textBaselinePt = textBaselinePt;
 | 
						||
    params.provider = &provider;
 | 
						||
    params.contentRange = contentRange;
 | 
						||
    params.textPaintStyle = &textPaintStyle;
 | 
						||
    params.glyphRange = range;
 | 
						||
    if (PaintTextWithSelection(params, clipEdges)) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  nscolor foregroundColor = aParams.IsGenerateTextMask()
 | 
						||
                                ? NS_RGBA(0, 0, 0, 255)
 | 
						||
                                : textPaintStyle.GetTextColor();
 | 
						||
  if (aOpacity != 1.0f) {
 | 
						||
    gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor);
 | 
						||
    gfxColor.a *= aOpacity;
 | 
						||
    foregroundColor = gfxColor.ToABGR();
 | 
						||
  }
 | 
						||
 | 
						||
  nscolor textStrokeColor = aParams.IsGenerateTextMask()
 | 
						||
                                ? NS_RGBA(0, 0, 0, 255)
 | 
						||
                                : textPaintStyle.GetWebkitTextStrokeColor();
 | 
						||
  if (aOpacity != 1.0f) {
 | 
						||
    gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor);
 | 
						||
    gfxColor.a *= aOpacity;
 | 
						||
    textStrokeColor = gfxColor.ToABGR();
 | 
						||
  }
 | 
						||
 | 
						||
  range = Range(startOffset, startOffset + maxLength);
 | 
						||
  if (aParams.IsPaintText()) {
 | 
						||
    const nsStyleText* textStyle = StyleText();
 | 
						||
    PaintShadowParams shadowParams(aParams);
 | 
						||
    shadowParams.range = range;
 | 
						||
    shadowParams.textBaselinePt = textBaselinePt;
 | 
						||
    shadowParams.leftSideOffset = snappedStartEdge;
 | 
						||
    shadowParams.provider = &provider;
 | 
						||
    shadowParams.callbacks = aParams.callbacks;
 | 
						||
    shadowParams.foregroundColor = foregroundColor;
 | 
						||
    shadowParams.clipEdges = &clipEdges;
 | 
						||
    PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams);
 | 
						||
  }
 | 
						||
 | 
						||
  gfxFloat advanceWidth;
 | 
						||
  DrawTextParams params(aParams.context, PresContext()->FontPaletteCache());
 | 
						||
  params.dirtyRect = aParams.dirtyRect;
 | 
						||
  params.framePt = aParams.framePt;
 | 
						||
  params.provider = &provider;
 | 
						||
  params.advanceWidth = &advanceWidth;
 | 
						||
  params.textStyle = &textPaintStyle;
 | 
						||
  params.textColor = foregroundColor;
 | 
						||
  params.textStrokeColor = textStrokeColor;
 | 
						||
  params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
 | 
						||
  params.clipEdges = &clipEdges;
 | 
						||
  params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
 | 
						||
  params.contextPaint = aParams.contextPaint;
 | 
						||
  params.callbacks = aParams.callbacks;
 | 
						||
  params.glyphRange = range;
 | 
						||
  params.fontPalette = StyleFont()->GetFontPaletteAtom();
 | 
						||
  params.hasTextShadow = !StyleText()->mTextShadow.IsEmpty();
 | 
						||
 | 
						||
  DrawText(range, textBaselinePt, params);
 | 
						||
}
 | 
						||
 | 
						||
static void DrawTextRun(const gfxTextRun* aTextRun,
 | 
						||
                        const gfx::Point& aTextBaselinePt,
 | 
						||
                        gfxTextRun::Range aRange,
 | 
						||
                        const nsTextFrame::DrawTextRunParams& aParams,
 | 
						||
                        nsTextFrame* aFrame) {
 | 
						||
  gfxTextRun::DrawParams params(aParams.context, aParams.paletteCache);
 | 
						||
  params.provider = aParams.provider;
 | 
						||
  params.advanceWidth = aParams.advanceWidth;
 | 
						||
  params.contextPaint = aParams.contextPaint;
 | 
						||
  params.fontPalette = aParams.fontPalette;
 | 
						||
  params.callbacks = aParams.callbacks;
 | 
						||
  params.hasTextShadow = aParams.hasTextShadow;
 | 
						||
  if (aParams.callbacks) {
 | 
						||
    aParams.callbacks->NotifyBeforeText(aParams.paintingShadows,
 | 
						||
                                        aParams.textColor);
 | 
						||
    params.drawMode = DrawMode::GLYPH_PATH;
 | 
						||
    aTextRun->Draw(aRange, aTextBaselinePt, params);
 | 
						||
    aParams.callbacks->NotifyAfterText();
 | 
						||
  } else {
 | 
						||
    auto* textDrawer = aParams.context->GetTextDrawer();
 | 
						||
    if (NS_GET_A(aParams.textColor) != 0 || textDrawer ||
 | 
						||
        aParams.textStrokeWidth == 0.0f) {
 | 
						||
      aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor));
 | 
						||
    } else {
 | 
						||
      params.drawMode = DrawMode::GLYPH_STROKE;
 | 
						||
    }
 | 
						||
 | 
						||
    if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
 | 
						||
        aParams.textStrokeWidth != 0.0f) {
 | 
						||
      if (textDrawer) {
 | 
						||
        textDrawer->FoundUnsupportedFeature();
 | 
						||
        return;
 | 
						||
      }
 | 
						||
      params.drawMode |= DrawMode::GLYPH_STROKE;
 | 
						||
 | 
						||
      // Check the paint-order property; if we find stroke before fill,
 | 
						||
      // then change mode to GLYPH_STROKE_UNDERNEATH.
 | 
						||
      uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
 | 
						||
      while (paintOrder) {
 | 
						||
        auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
 | 
						||
        switch (component) {
 | 
						||
          case StylePaintOrder::Fill:
 | 
						||
            // Just break the loop, no need to check further
 | 
						||
            paintOrder = 0;
 | 
						||
            break;
 | 
						||
          case StylePaintOrder::Stroke:
 | 
						||
            params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
 | 
						||
            paintOrder = 0;
 | 
						||
            break;
 | 
						||
          default:
 | 
						||
            MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
 | 
						||
          case StylePaintOrder::Markers:
 | 
						||
          case StylePaintOrder::Normal:
 | 
						||
            break;
 | 
						||
        }
 | 
						||
        paintOrder >>= kPaintOrderShift;
 | 
						||
      }
 | 
						||
 | 
						||
      // Use ROUND joins as they are less likely to produce ugly artifacts
 | 
						||
      // when stroking glyphs with sharp angles (see bug 1546985).
 | 
						||
      StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND);
 | 
						||
      params.textStrokeColor = aParams.textStrokeColor;
 | 
						||
      params.strokeOpts = &strokeOpts;
 | 
						||
      aTextRun->Draw(aRange, aTextBaselinePt, params);
 | 
						||
    } else {
 | 
						||
      aTextRun->Draw(aRange, aTextBaselinePt, params);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
 | 
						||
                              const DrawTextRunParams& aParams) {
 | 
						||
  MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
 | 
						||
 | 
						||
  ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this);
 | 
						||
 | 
						||
  if (aParams.drawSoftHyphen) {
 | 
						||
    // Don't use ctx as the context, because we need a reference context here,
 | 
						||
    // ctx may be transformed.
 | 
						||
    DrawTextRunParams params = aParams;
 | 
						||
    params.provider = nullptr;
 | 
						||
    params.advanceWidth = nullptr;
 | 
						||
    RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr);
 | 
						||
    if (hyphenTextRun) {
 | 
						||
      gfx::Point p(aTextBaselinePt);
 | 
						||
      bool vertical = GetWritingMode().IsVertical();
 | 
						||
      // For right-to-left text runs, the soft-hyphen is positioned at the left
 | 
						||
      // of the text.
 | 
						||
      float shift = mTextRun->GetDirection() * (*aParams.advanceWidth);
 | 
						||
      if (vertical) {
 | 
						||
        p.y += shift;
 | 
						||
      } else {
 | 
						||
        p.x += shift;
 | 
						||
      }
 | 
						||
      ::DrawTextRun(hyphenTextRun.get(), p, Range(hyphenTextRun.get()), params,
 | 
						||
                    this);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::DrawTextRunAndDecorations(
 | 
						||
    Range aRange, const gfx::Point& aTextBaselinePt,
 | 
						||
    const DrawTextParams& aParams, const TextDecorations& aDecorations) {
 | 
						||
  const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
 | 
						||
  // Writing mode of parent frame is used because the text frame may
 | 
						||
  // be orthogonal to its parent when text-combine-upright is used or
 | 
						||
  // its parent has "display: contents", and in those cases, we want
 | 
						||
  // to draw the decoration lines according to parents' direction
 | 
						||
  // rather than ours.
 | 
						||
  const WritingMode wm = GetParent()->GetWritingMode();
 | 
						||
  bool verticalDec = wm.IsVertical();
 | 
						||
  bool verticalRun = mTextRun->IsVertical();
 | 
						||
  // If the text run and the decoration is orthogonal, we choose the
 | 
						||
  // metrics for decoration so that decoration line won't be broken.
 | 
						||
  bool useVerticalMetrics = verticalDec != verticalRun
 | 
						||
                                ? verticalDec
 | 
						||
                                : verticalRun && mTextRun->UseCenterBaseline();
 | 
						||
 | 
						||
  // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
 | 
						||
  nscoord x = NSToCoordRound(aParams.framePt.x);
 | 
						||
  nscoord y = NSToCoordRound(aParams.framePt.y);
 | 
						||
 | 
						||
  // 'measure' here is textrun-relative, so for a horizontal run it's the
 | 
						||
  // width, while for a vertical run it's the height of the decoration
 | 
						||
  const nsSize frameSize = GetSize();
 | 
						||
  nscoord measure = verticalDec ? frameSize.height : frameSize.width;
 | 
						||
 | 
						||
  if (verticalDec) {
 | 
						||
    aParams.clipEdges->Intersect(&y, &measure);
 | 
						||
  } else {
 | 
						||
    aParams.clipEdges->Intersect(&x, &measure);
 | 
						||
  }
 | 
						||
 | 
						||
  // decSize is a textrun-relative size, so its 'width' field is actually
 | 
						||
  // the run-relative measure, and 'height' will be the line thickness
 | 
						||
  gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
 | 
						||
  // The starting edge of the frame in block direction
 | 
						||
  gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
 | 
						||
 | 
						||
  // In vertical-rl mode, block coordinates are measured from the
 | 
						||
  // right, so we need to adjust here.
 | 
						||
  if (wm.IsVerticalRL()) {
 | 
						||
    frameBStart += frameSize.width;
 | 
						||
    ascent = -ascent;
 | 
						||
  }
 | 
						||
 | 
						||
  nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this);
 | 
						||
 | 
						||
  PaintDecorationLineParams params;
 | 
						||
  params.context = aParams.context;
 | 
						||
  params.dirtyRect = aParams.dirtyRect;
 | 
						||
  params.overrideColor = aParams.decorationOverrideColor;
 | 
						||
  params.callbacks = aParams.callbacks;
 | 
						||
  params.glyphRange = aParams.glyphRange;
 | 
						||
  params.provider = aParams.provider;
 | 
						||
  params.paintingShadows = aParams.paintingShadows;
 | 
						||
  // pt is the physical point where the decoration is to be drawn,
 | 
						||
  // relative to the frame; one of its coordinates will be updated below.
 | 
						||
  params.pt = Point(x / app, y / app);
 | 
						||
  Float& bCoord = verticalDec ? params.pt.x.value : params.pt.y.value;
 | 
						||
  params.lineSize = Size(measure / app, 0);
 | 
						||
  params.ascent = ascent;
 | 
						||
  params.vertical = verticalDec;
 | 
						||
  params.sidewaysLeft = mTextRun->IsSidewaysLeft();
 | 
						||
 | 
						||
  // The matrix of the context may have been altered for text-combine-
 | 
						||
  // upright. However, we want to draw decoration lines unscaled, thus
 | 
						||
  // we need to revert the scaling here.
 | 
						||
  gfxContextMatrixAutoSaveRestore scaledRestorer;
 | 
						||
  if (Style()->IsTextCombined()) {
 | 
						||
    float scaleFactor = GetTextCombineScaleFactor(this);
 | 
						||
    if (scaleFactor != 1.0f) {
 | 
						||
      scaledRestorer.SetContext(aParams.context);
 | 
						||
      gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
 | 
						||
      gfxPoint pt(x / app, y / app);
 | 
						||
      if (GetTextRun(nsTextFrame::eInflated)->IsRightToLeft()) {
 | 
						||
        pt.x += gfxFloat(frameSize.width) / app;
 | 
						||
      }
 | 
						||
      unscaled.PreTranslate(pt)
 | 
						||
          .PreScale(1.0f / scaleFactor, 1.0f)
 | 
						||
          .PreTranslate(-pt);
 | 
						||
      aParams.context->SetMatrixDouble(unscaled);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  typedef gfxFont::Metrics Metrics;
 | 
						||
  auto paintDecorationLine = [&](const LineDecoration& dec,
 | 
						||
                                 gfxFloat Metrics::* lineSize,
 | 
						||
                                 StyleTextDecorationLine lineType) {
 | 
						||
    if (dec.mStyle == StyleTextDecorationStyle::None) {
 | 
						||
      return;
 | 
						||
    }
 | 
						||
 | 
						||
    float inflation =
 | 
						||
        GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
 | 
						||
    const Metrics metrics = GetFirstFontMetrics(
 | 
						||
        GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics);
 | 
						||
 | 
						||
    bCoord = (frameBStart - dec.mBaselineOffset) / app;
 | 
						||
 | 
						||
    params.color = dec.mColor;
 | 
						||
    params.baselineOffset = dec.mBaselineOffset / app;
 | 
						||
    params.defaultLineThickness = metrics.*lineSize;
 | 
						||
    params.lineSize.height = ComputeDecorationLineThickness(
 | 
						||
        dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app,
 | 
						||
        dec.mFrame);
 | 
						||
 | 
						||
    bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
 | 
						||
    params.offset = ComputeDecorationLineOffset(
 | 
						||
        lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics,
 | 
						||
        app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline);
 | 
						||
 | 
						||
    params.style = dec.mStyle;
 | 
						||
    params.allowInkSkipping = dec.mAllowInkSkipping;
 | 
						||
    PaintDecorationLine(params);
 | 
						||
  };
 | 
						||
 | 
						||
  // We create a clip region in order to draw the decoration lines only in the
 | 
						||
  // range of the text. Restricting the draw area prevents the decoration lines
 | 
						||
  // to be drawn multiple times when a part of the text is selected.
 | 
						||
 | 
						||
  // We skip clipping for the following cases:
 | 
						||
  // - drawing the whole text
 | 
						||
  // - having different orientation of the text and the writing-mode, such as
 | 
						||
  //   "text-combine-upright" (Bug 1408825)
 | 
						||
  bool skipClipping =
 | 
						||
      aRange.Length() == mTextRun->GetLength() || verticalDec != verticalRun;
 | 
						||
 | 
						||
  gfxRect clipRect;
 | 
						||
  if (!skipClipping) {
 | 
						||
    // Get the inline-size according to the specified range.
 | 
						||
    gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
 | 
						||
    nsRect visualRect = InkOverflowRect();
 | 
						||
 | 
						||
    const bool isInlineReversed = mTextRun->IsInlineReversed();
 | 
						||
    if (verticalDec) {
 | 
						||
      clipRect.x = aParams.framePt.x + visualRect.x;
 | 
						||
      clipRect.y = isInlineReversed ? aTextBaselinePt.y.value - clipLength
 | 
						||
                                    : aTextBaselinePt.y.value;
 | 
						||
      clipRect.width = visualRect.width;
 | 
						||
      clipRect.height = clipLength;
 | 
						||
    } else {
 | 
						||
      clipRect.x = isInlineReversed ? aTextBaselinePt.x.value - clipLength
 | 
						||
                                    : aTextBaselinePt.x.value;
 | 
						||
      clipRect.y = aParams.framePt.y + visualRect.y;
 | 
						||
      clipRect.width = clipLength;
 | 
						||
      clipRect.height = visualRect.height;
 | 
						||
    }
 | 
						||
 | 
						||
    clipRect.Scale(1 / app);
 | 
						||
    clipRect.Round();
 | 
						||
    params.context->Clip(clipRect);
 | 
						||
  }
 | 
						||
 | 
						||
  // Underlines
 | 
						||
  params.decoration = StyleTextDecorationLine::UNDERLINE;
 | 
						||
  for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
 | 
						||
    paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
 | 
						||
  }
 | 
						||
 | 
						||
  // Overlines
 | 
						||
  params.decoration = StyleTextDecorationLine::OVERLINE;
 | 
						||
  for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
 | 
						||
    paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
 | 
						||
  }
 | 
						||
 | 
						||
  // Some glyphs and emphasis marks may extend outside the region, so we reset
 | 
						||
  // the clip region here. For an example, italic glyphs.
 | 
						||
  if (!skipClipping) {
 | 
						||
    params.context->PopClip();
 | 
						||
  }
 | 
						||
 | 
						||
  {
 | 
						||
    gfxContextMatrixAutoSaveRestore unscaledRestorer;
 | 
						||
    if (scaledRestorer.HasMatrix()) {
 | 
						||
      unscaledRestorer.SetContext(aParams.context);
 | 
						||
      aParams.context->SetMatrix(scaledRestorer.Matrix());
 | 
						||
    }
 | 
						||
 | 
						||
    // CSS 2.1 mandates that text be painted after over/underlines,
 | 
						||
    // and *then* line-throughs
 | 
						||
    DrawTextRun(aRange, aTextBaselinePt, aParams);
 | 
						||
  }
 | 
						||
 | 
						||
  // Emphasis marks
 | 
						||
  DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt,
 | 
						||
                    aRange, aParams.decorationOverrideColor, aParams.provider);
 | 
						||
 | 
						||
  // Re-apply the clip region when the line-through is being drawn.
 | 
						||
  if (!skipClipping) {
 | 
						||
    params.context->Clip(clipRect);
 | 
						||
  }
 | 
						||
 | 
						||
  // Line-throughs
 | 
						||
  params.decoration = StyleTextDecorationLine::LINE_THROUGH;
 | 
						||
  for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
 | 
						||
    paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration);
 | 
						||
  }
 | 
						||
 | 
						||
  if (!skipClipping) {
 | 
						||
    params.context->PopClip();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
 | 
						||
                           const DrawTextParams& aParams) {
 | 
						||
  TextDecorations decorations;
 | 
						||
  GetTextDecorations(aParams.textStyle->PresContext(),
 | 
						||
                     aParams.callbacks ? eUnresolvedColors : eResolvedColors,
 | 
						||
                     decorations);
 | 
						||
 | 
						||
  // Hide text decorations if we're currently hiding @font-face fallback text
 | 
						||
  const bool drawDecorations =
 | 
						||
      !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
 | 
						||
      (decorations.HasDecorationLines() ||
 | 
						||
       StyleText()->HasEffectiveTextEmphasis());
 | 
						||
  if (drawDecorations) {
 | 
						||
    DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations);
 | 
						||
  } else {
 | 
						||
    DrawTextRun(aRange, aTextBaselinePt, aParams);
 | 
						||
  }
 | 
						||
 | 
						||
  if (auto* textDrawer = aParams.context->GetTextDrawer()) {
 | 
						||
    textDrawer->TerminateShadows();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect)
 | 
						||
 | 
						||
nsRect nsTextFrame::WebRenderBounds() {
 | 
						||
  // WR text bounds is just our ink overflow rect but without shadows. So if we
 | 
						||
  // have no shadows, just use the layout bounds.
 | 
						||
  if (!StyleText()->HasTextShadow()) {
 | 
						||
    return InkOverflowRect();
 | 
						||
  }
 | 
						||
  nsRect* cachedBounds = GetProperty(WebRenderTextBounds());
 | 
						||
  if (!cachedBounds) {
 | 
						||
    OverflowAreas overflowAreas;
 | 
						||
    ComputeCustomOverflowInternal(overflowAreas, false);
 | 
						||
    cachedBounds = new nsRect(overflowAreas.InkOverflow());
 | 
						||
    SetProperty(WebRenderTextBounds(), cachedBounds);
 | 
						||
  }
 | 
						||
  return *cachedBounds;
 | 
						||
}
 | 
						||
 | 
						||
int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) {
 | 
						||
  // get the selection controller
 | 
						||
  nsCOMPtr<nsISelectionController> selectionController;
 | 
						||
  nsresult rv = GetSelectionController(PresContext(),
 | 
						||
                                       getter_AddRefs(selectionController));
 | 
						||
  if (NS_FAILED(rv) || !selectionController) {
 | 
						||
    return nsISelectionController::SELECTION_OFF;
 | 
						||
  }
 | 
						||
 | 
						||
  selectionController->GetSelectionFlags(aSelectionFlags);
 | 
						||
 | 
						||
  int16_t selectionValue;
 | 
						||
  selectionController->GetDisplaySelection(&selectionValue);
 | 
						||
 | 
						||
  return selectionValue;
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::IsEntirelyWhitespace() const {
 | 
						||
  const nsTextFragment& text = mContent->AsText()->TextFragment();
 | 
						||
  for (uint32_t index = 0; index < text.GetLength(); ++index) {
 | 
						||
    const char16_t ch = text.CharAt(index);
 | 
						||
    if (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' || ch == 0xa0) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Compute the longest prefix of text whose width is <= aWidth. Return
 | 
						||
 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
 | 
						||
 */
 | 
						||
static uint32_t CountCharsFit(const gfxTextRun* aTextRun,
 | 
						||
                              gfxTextRun::Range aRange, gfxFloat aWidth,
 | 
						||
                              nsTextFrame::PropertyProvider* aProvider,
 | 
						||
                              gfxFloat* aFitWidth) {
 | 
						||
  uint32_t last = 0;
 | 
						||
  gfxFloat width = 0;
 | 
						||
  for (uint32_t i = 1; i <= aRange.Length(); ++i) {
 | 
						||
    if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
 | 
						||
      gfxTextRun::Range range(aRange.start + last, aRange.start + i);
 | 
						||
      gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
 | 
						||
      if (nextWidth > aWidth) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      last = i;
 | 
						||
      width = nextWidth;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  *aFitWidth = width;
 | 
						||
  return last;
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint(
 | 
						||
    const nsPoint& aPoint) {
 | 
						||
  return GetCharacterOffsetAtFramePointInternal(aPoint, true);
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint(
 | 
						||
    const nsPoint& aPoint) {
 | 
						||
  return GetCharacterOffsetAtFramePointInternal(aPoint, false);
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal(
 | 
						||
    const nsPoint& aPoint, bool aForInsertionPoint) {
 | 
						||
  ContentOffsets offsets;
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return offsets;
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  // Trim leading but not trailing whitespace if possible
 | 
						||
  provider.InitializeForDisplay(false);
 | 
						||
  gfxFloat width =
 | 
						||
      mTextRun->IsVertical()
 | 
						||
          ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
 | 
						||
          : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
 | 
						||
  if (Style()->IsTextCombined()) {
 | 
						||
    width /= GetTextCombineScaleFactor(this);
 | 
						||
  }
 | 
						||
  gfxFloat fitWidth;
 | 
						||
  Range skippedRange = ComputeTransformedRange(provider);
 | 
						||
 | 
						||
  uint32_t charsFit =
 | 
						||
      CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth);
 | 
						||
 | 
						||
  int32_t selectedOffset;
 | 
						||
  if (charsFit < skippedRange.Length()) {
 | 
						||
    // charsFit characters fitted, but no more could fit. See if we're
 | 
						||
    // more than halfway through the cluster.. If we are, choose the next
 | 
						||
    // cluster.
 | 
						||
    gfxSkipCharsIterator extraCluster(provider.GetStart());
 | 
						||
    extraCluster.AdvanceSkipped(charsFit);
 | 
						||
 | 
						||
    bool allowSplitLigature = true;  // Allow selection of partial ligature...
 | 
						||
 | 
						||
    // ...but don't let selection/insertion-point split two Regional Indicator
 | 
						||
    // chars that are ligated in the textrun to form a single flag symbol.
 | 
						||
    uint32_t offs = extraCluster.GetOriginalOffset();
 | 
						||
    const nsTextFragment* frag = TextFragment();
 | 
						||
    if (frag->IsHighSurrogateFollowedByLowSurrogateAt(offs) &&
 | 
						||
        gfxFontUtils::IsRegionalIndicator(frag->ScalarValueAt(offs))) {
 | 
						||
      allowSplitLigature = false;
 | 
						||
      if (extraCluster.GetSkippedOffset() > 1 &&
 | 
						||
          !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
 | 
						||
        // CountCharsFit() left us in the middle of the flag; back up over the
 | 
						||
        // first character of the ligature, and adjust fitWidth accordingly.
 | 
						||
        extraCluster.AdvanceSkipped(-2);  // it's a surrogate pair: 2 code units
 | 
						||
        fitWidth -= mTextRun->GetAdvanceWidth(
 | 
						||
            Range(extraCluster.GetSkippedOffset(),
 | 
						||
                  extraCluster.GetSkippedOffset() + 2),
 | 
						||
            &provider);
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    gfxSkipCharsIterator extraClusterLastChar(extraCluster);
 | 
						||
    FindClusterEnd(
 | 
						||
        mTextRun,
 | 
						||
        provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
 | 
						||
        &extraClusterLastChar, allowSplitLigature);
 | 
						||
    PropertyProvider::Spacing spacing;
 | 
						||
    Range extraClusterRange(extraCluster.GetSkippedOffset(),
 | 
						||
                            extraClusterLastChar.GetSkippedOffset() + 1);
 | 
						||
    gfxFloat charWidth =
 | 
						||
        mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
 | 
						||
    charWidth -= spacing.mBefore + spacing.mAfter;
 | 
						||
    selectedOffset = !aForInsertionPoint ||
 | 
						||
                             width <= fitWidth + spacing.mBefore + charWidth / 2
 | 
						||
                         ? extraCluster.GetOriginalOffset()
 | 
						||
                         : extraClusterLastChar.GetOriginalOffset() + 1;
 | 
						||
  } else {
 | 
						||
    // All characters fitted, we're at (or beyond) the end of the text.
 | 
						||
    // XXX This could be some pathological situation where negative spacing
 | 
						||
    // caused characters to move backwards. We can't really handle that
 | 
						||
    // in the current frame system because frames can't have negative
 | 
						||
    // intrinsic widths.
 | 
						||
    selectedOffset =
 | 
						||
        provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
 | 
						||
    // If we're at the end of a preformatted line which has a terminating
 | 
						||
    // linefeed, we want to reduce the offset by one to make sure that the
 | 
						||
    // selection is placed before the linefeed character.
 | 
						||
    if (HasSignificantTerminalNewline()) {
 | 
						||
      --selectedOffset;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  offsets.content = GetContent();
 | 
						||
  offsets.offset = offsets.secondaryOffset = selectedOffset;
 | 
						||
  offsets.associate = mContentOffset == offsets.offset
 | 
						||
                          ? CaretAssociationHint::After
 | 
						||
                          : CaretAssociationHint::Before;
 | 
						||
  return offsets;
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
 | 
						||
                                                nsRect& aRect) {
 | 
						||
  if (aRect.IsEmpty()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  nsRect givenRect = aRect;
 | 
						||
 | 
						||
  gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this);
 | 
						||
  RefPtr<gfxFont> firstFont = fontGroup->GetFirstValidFont();
 | 
						||
  WritingMode wm = GetWritingMode();
 | 
						||
  bool verticalRun = wm.IsVertical();
 | 
						||
  bool useVerticalMetrics = verticalRun && !wm.IsSideways();
 | 
						||
  const gfxFont::Metrics& metrics =
 | 
						||
      firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
 | 
						||
                                               : nsFontMetrics::eHorizontal);
 | 
						||
 | 
						||
  nsCSSRendering::DecorationRectParams params;
 | 
						||
  params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
 | 
						||
 | 
						||
  params.offset = fontGroup->GetUnderlineOffset();
 | 
						||
 | 
						||
  TextDecorations textDecs;
 | 
						||
  GetTextDecorations(aPresContext, eResolvedColors, textDecs);
 | 
						||
 | 
						||
  params.descentLimit =
 | 
						||
      ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
 | 
						||
  params.vertical = verticalRun;
 | 
						||
 | 
						||
  if (verticalRun) {
 | 
						||
    EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
    params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
 | 
						||
  } else {
 | 
						||
    params.sidewaysLeft = false;
 | 
						||
  }
 | 
						||
 | 
						||
  UniquePtr<SelectionDetails> details = GetSelectionDetails();
 | 
						||
  for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
 | 
						||
    if (sd->mStart == sd->mEnd ||
 | 
						||
        sd->mSelectionType == SelectionType::eInvalid ||
 | 
						||
        !(ToSelectionTypeMask(sd->mSelectionType) &
 | 
						||
          kSelectionTypesWithDecorations) ||
 | 
						||
        // URL strikeout does not use underline.
 | 
						||
        sd->mSelectionType == SelectionType::eURLStrikeout) {
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
 | 
						||
    float relativeSize;
 | 
						||
    auto index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
 | 
						||
        sd->mSelectionType);
 | 
						||
    if (sd->mSelectionType == SelectionType::eSpellCheck) {
 | 
						||
      if (!nsTextPaintStyle::GetSelectionUnderline(
 | 
						||
              this, index, nullptr, &relativeSize, ¶ms.style)) {
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      // IME selections
 | 
						||
      TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
 | 
						||
      if (rangeStyle.IsDefined()) {
 | 
						||
        if (!rangeStyle.IsLineStyleDefined() ||
 | 
						||
            rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
 | 
						||
          continue;
 | 
						||
        }
 | 
						||
        params.style = ToStyleLineStyle(rangeStyle);
 | 
						||
        relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
 | 
						||
      } else if (!nsTextPaintStyle::GetSelectionUnderline(
 | 
						||
                     this, index, nullptr, &relativeSize, ¶ms.style)) {
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    nsRect decorationArea;
 | 
						||
 | 
						||
    const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
 | 
						||
    params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
 | 
						||
    params.defaultLineThickness = ComputeSelectionUnderlineHeight(
 | 
						||
        aPresContext, metrics, sd->mSelectionType);
 | 
						||
 | 
						||
    params.lineSize.height = ComputeDecorationLineThickness(
 | 
						||
        decThickness, params.defaultLineThickness, metrics,
 | 
						||
        aPresContext->AppUnitsPerDevPixel(), this);
 | 
						||
 | 
						||
    bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
 | 
						||
    const auto* styleText = StyleText();
 | 
						||
    params.offset = ComputeDecorationLineOffset(
 | 
						||
        textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE
 | 
						||
                                : StyleTextDecorationLine::OVERLINE,
 | 
						||
        styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset,
 | 
						||
        metrics, aPresContext->AppUnitsPerDevPixel(), this,
 | 
						||
        wm.IsCentralBaseline(), swapUnderline);
 | 
						||
 | 
						||
    relativeSize = std::max(relativeSize, 1.0f);
 | 
						||
    params.lineSize.height *= relativeSize;
 | 
						||
    params.defaultLineThickness *= relativeSize;
 | 
						||
    decorationArea =
 | 
						||
        nsCSSRendering::GetTextDecorationRect(aPresContext, params);
 | 
						||
    aRect.UnionRect(aRect, decorationArea);
 | 
						||
  }
 | 
						||
 | 
						||
  return !aRect.IsEmpty() && !givenRect.Contains(aRect);
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::IsFrameSelected() const {
 | 
						||
  NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
 | 
						||
               "use the public IsSelected() instead");
 | 
						||
  if (mIsSelected == nsTextFrame::SelectionState::Unknown) {
 | 
						||
    const bool isSelected =
 | 
						||
        GetContent()->IsSelected(GetContentOffset(), GetContentEnd(),
 | 
						||
                                 PresShell()->GetSelectionNodeCache());
 | 
						||
    mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected
 | 
						||
                             : nsTextFrame::SelectionState::NotSelected;
 | 
						||
  } else {
 | 
						||
#ifdef DEBUG
 | 
						||
    // Assert that the selection caching works.
 | 
						||
    const bool isReallySelected =
 | 
						||
        GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
 | 
						||
    MOZ_ASSERT((mIsSelected == nsTextFrame::SelectionState::Selected) ==
 | 
						||
                   isReallySelected,
 | 
						||
               "Should have called InvalidateSelectionState()");
 | 
						||
#endif
 | 
						||
  }
 | 
						||
 | 
						||
  return mIsSelected == nsTextFrame::SelectionState::Selected;
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame* nsTextFrame::FindContinuationForOffset(int32_t aOffset) {
 | 
						||
  // Use a continuations array to accelerate finding the first continuation
 | 
						||
  // of interest, if possible.
 | 
						||
  MOZ_ASSERT(!GetPrevContinuation(), "should be called on the primary frame");
 | 
						||
  auto* continuations = GetContinuations();
 | 
						||
  nsTextFrame* f = this;
 | 
						||
  if (continuations) {
 | 
						||
    size_t index;
 | 
						||
    if (BinarySearchIf(
 | 
						||
            *continuations, 0, continuations->Length(),
 | 
						||
            [=](nsTextFrame* aFrame) -> int {
 | 
						||
              return aOffset - aFrame->GetContentOffset();
 | 
						||
            },
 | 
						||
            &index)) {
 | 
						||
      f = (*continuations)[index];
 | 
						||
    } else {
 | 
						||
      f = (*continuations)[index ? index - 1 : 0];
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  while (f && f->GetContentEnd() <= aOffset) {
 | 
						||
    f = f->GetNextContinuation();
 | 
						||
  }
 | 
						||
 | 
						||
  return f;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd,
 | 
						||
                                        bool aSelected,
 | 
						||
                                        SelectionType aSelectionType) {
 | 
						||
  NS_ASSERTION(!GetPrevContinuation(),
 | 
						||
               "Should only be called for primary frame");
 | 
						||
  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
 | 
						||
 | 
						||
  InvalidateSelectionState();
 | 
						||
 | 
						||
  // Selection is collapsed, which can't affect text frame rendering
 | 
						||
  if (aStart == aEnd) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* f = FindContinuationForOffset(aStart);
 | 
						||
 | 
						||
  nsPresContext* presContext = PresContext();
 | 
						||
  while (f && f->GetContentOffset() < int32_t(aEnd)) {
 | 
						||
    // We may need to reflow to recompute the overflow area for
 | 
						||
    // spellchecking or IME underline if their underline is thicker than
 | 
						||
    // the normal decoration line.
 | 
						||
    if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) {
 | 
						||
      bool didHaveOverflowingSelection =
 | 
						||
          f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
 | 
						||
      nsRect r(nsPoint(0, 0), GetSize());
 | 
						||
      if (didHaveOverflowingSelection ||
 | 
						||
          (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
 | 
						||
        presContext->PresShell()->FrameNeedsReflow(
 | 
						||
            f, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY);
 | 
						||
      }
 | 
						||
    }
 | 
						||
    // Selection might change anything. Invalidate the overflow area.
 | 
						||
    f->InvalidateFrame();
 | 
						||
 | 
						||
    f = f->GetNextContinuation();
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
 | 
						||
                                           int32_t& aInOffset,
 | 
						||
                                           gfxSkipCharsIterator& aIter) {
 | 
						||
  if (aInOffset < GetContentOffset()) {
 | 
						||
    NS_WARNING("offset before this frame's content");
 | 
						||
    aInOffset = GetContentOffset();
 | 
						||
  } else if (aInOffset > GetContentEnd()) {
 | 
						||
    NS_WARNING("offset after this frame's content");
 | 
						||
    aInOffset = GetContentEnd();
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
 | 
						||
  int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
 | 
						||
  aInOffset = std::max(aInOffset, trimmedOffset);
 | 
						||
  aInOffset = std::min(aInOffset, trimmedEnd);
 | 
						||
 | 
						||
  aIter.SetOriginalOffset(aInOffset);
 | 
						||
 | 
						||
  if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() &&
 | 
						||
      !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
 | 
						||
    // Called for non-cluster boundary
 | 
						||
    FindClusterStart(mTextRun, trimmedOffset, &aIter);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
 | 
						||
                                          PropertyProvider& aProperties) {
 | 
						||
  Range range(aProperties.GetStart().GetSkippedOffset(),
 | 
						||
              aIter.GetSkippedOffset());
 | 
						||
  gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
 | 
						||
  nscoord iSize = NSToCoordCeilClamped(advance);
 | 
						||
  nsPoint point;
 | 
						||
 | 
						||
  if (mTextRun->IsVertical()) {
 | 
						||
    point.x = 0;
 | 
						||
    if (mTextRun->IsInlineReversed()) {
 | 
						||
      point.y = mRect.height - iSize;
 | 
						||
    } else {
 | 
						||
      point.y = iSize;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    point.y = 0;
 | 
						||
    if (Style()->IsTextCombined()) {
 | 
						||
      iSize *= GetTextCombineScaleFactor(this);
 | 
						||
    }
 | 
						||
    if (mTextRun->IsInlineReversed()) {
 | 
						||
      point.x = mRect.width - iSize;
 | 
						||
    } else {
 | 
						||
      point.x = iSize;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return point;
 | 
						||
}
 | 
						||
 | 
						||
nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
 | 
						||
  if (!outPoint) {
 | 
						||
    return NS_ERROR_NULL_POINTER;
 | 
						||
  }
 | 
						||
 | 
						||
  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
 | 
						||
  if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
 | 
						||
    return NS_ERROR_UNEXPECTED;
 | 
						||
  }
 | 
						||
 | 
						||
  if (GetContentLength() <= 0) {
 | 
						||
    outPoint->x = 0;
 | 
						||
    outPoint->y = 0;
 | 
						||
    return NS_OK;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return NS_ERROR_FAILURE;
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  // Don't trim trailing whitespace, we want the caret to appear in the right
 | 
						||
  // place if it's positioned there
 | 
						||
  properties.InitializeForDisplay(false);
 | 
						||
 | 
						||
  UpdateIteratorFromOffset(properties, inOffset, iter);
 | 
						||
 | 
						||
  *outPoint = GetPointFromIterator(iter, properties);
 | 
						||
 | 
						||
  return NS_OK;
 | 
						||
}
 | 
						||
 | 
						||
nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
 | 
						||
                                               int32_t aLength,
 | 
						||
                                               nsTArray<nsRect>& aRects) {
 | 
						||
  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
 | 
						||
  if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
 | 
						||
    return NS_ERROR_UNEXPECTED;
 | 
						||
  }
 | 
						||
 | 
						||
  if (GetContentLength() <= 0) {
 | 
						||
    return NS_OK;
 | 
						||
  }
 | 
						||
 | 
						||
  if (!mTextRun) {
 | 
						||
    return NS_ERROR_FAILURE;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  // Don't trim trailing whitespace, we want the caret to appear in the right
 | 
						||
  // place if it's positioned there
 | 
						||
  properties.InitializeForDisplay(false);
 | 
						||
 | 
						||
  // Initialize iter; this will call FindClusterStart if necessary to align
 | 
						||
  // iter to a cluster boundary.
 | 
						||
  UpdateIteratorFromOffset(properties, aInOffset, iter);
 | 
						||
  nsPoint point = GetPointFromIterator(iter, properties);
 | 
						||
 | 
						||
  const int32_t kContentEnd = GetContentEnd();
 | 
						||
  const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
 | 
						||
 | 
						||
  if (aInOffset >= kEndOffset) {
 | 
						||
    return NS_OK;
 | 
						||
  }
 | 
						||
 | 
						||
  if (!aRects.SetCapacity(aRects.Length() + kEndOffset - aInOffset,
 | 
						||
                          mozilla::fallible)) {
 | 
						||
    return NS_ERROR_OUT_OF_MEMORY;
 | 
						||
  }
 | 
						||
 | 
						||
  do {
 | 
						||
    // We'd like to assert here that |point| matches
 | 
						||
    // |GetPointFromIterator(iter, properties)|, which in principle should be
 | 
						||
    // true; however, testcases with vast dimensions can lead to coordinate
 | 
						||
    // overflow and disrupt the calculations. So we've dropped the assertion
 | 
						||
    // to avoid tripping the fuzzer unnecessarily.
 | 
						||
 | 
						||
    // Measure to the end of the cluster.
 | 
						||
    nscoord iSize = 0;
 | 
						||
    gfxSkipCharsIterator nextIter(iter);
 | 
						||
    if (aInOffset < kContentEnd) {
 | 
						||
      nextIter.AdvanceOriginal(1);
 | 
						||
      if (!nextIter.IsOriginalCharSkipped() &&
 | 
						||
          !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) &&
 | 
						||
          nextIter.GetOriginalOffset() < kContentEnd) {
 | 
						||
        FindClusterEnd(mTextRun, kContentEnd, &nextIter);
 | 
						||
      }
 | 
						||
 | 
						||
      gfxFloat advance = mTextRun->GetAdvanceWidth(
 | 
						||
          Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()),
 | 
						||
          &properties);
 | 
						||
      iSize = NSToCoordCeilClamped(advance);
 | 
						||
    }
 | 
						||
 | 
						||
    // Compute the cluster rect, depending on directionality, and update
 | 
						||
    // point to the origin we'll need for the next cluster.
 | 
						||
    nsRect rect;
 | 
						||
    rect.x = point.x;
 | 
						||
    rect.y = point.y;
 | 
						||
 | 
						||
    if (mTextRun->IsVertical()) {
 | 
						||
      rect.width = mRect.width;
 | 
						||
      rect.height = iSize;
 | 
						||
      if (mTextRun->IsInlineReversed()) {
 | 
						||
        // The iterator above returns a point with the origin at the
 | 
						||
        // bottom left instead of the top left. Move the origin to the top left
 | 
						||
        // by subtracting the character's height.
 | 
						||
        rect.y -= rect.height;
 | 
						||
        point.y -= iSize;
 | 
						||
      } else {
 | 
						||
        point.y += iSize;
 | 
						||
      }
 | 
						||
    } else {
 | 
						||
      if (Style()->IsTextCombined()) {
 | 
						||
        // The scale factor applies to the inline advance of the glyphs, so it
 | 
						||
        // affects both the rect width and the origin point for the next glyph.
 | 
						||
        iSize *= GetTextCombineScaleFactor(this);
 | 
						||
      }
 | 
						||
      rect.width = iSize;
 | 
						||
      rect.height = mRect.height;
 | 
						||
      if (mTextRun->IsInlineReversed()) {
 | 
						||
        // The iterator above returns a point with the origin at the
 | 
						||
        // top right instead of the top left. Move the origin to the top left by
 | 
						||
        // subtracting the character's width.
 | 
						||
        rect.x -= iSize;
 | 
						||
        point.x -= iSize;
 | 
						||
      } else {
 | 
						||
        point.x += iSize;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    // Set the rect for all characters in the cluster.
 | 
						||
    int32_t end = std::min(kEndOffset, nextIter.GetOriginalOffset());
 | 
						||
    while (aInOffset < end) {
 | 
						||
      aRects.AppendElement(rect);
 | 
						||
      aInOffset++;
 | 
						||
    }
 | 
						||
 | 
						||
    // Advance iter for the next cluster.
 | 
						||
    iter = nextIter;
 | 
						||
  } while (aInOffset < kEndOffset);
 | 
						||
 | 
						||
  return NS_OK;
 | 
						||
}
 | 
						||
 | 
						||
nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
 | 
						||
                                                    bool aHint,
 | 
						||
                                                    int32_t* aOutOffset,
 | 
						||
                                                    nsIFrame** aOutFrame) {
 | 
						||
  DEBUG_VERIFY_NOT_DIRTY(GetStateBits());
 | 
						||
#if 0  // XXXrbs disable due to bug 310227
 | 
						||
  if (HasAnyStateBits(NS_FRAME_IS_DIRTY))
 | 
						||
    return NS_ERROR_UNEXPECTED;
 | 
						||
#endif
 | 
						||
 | 
						||
  NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
 | 
						||
  NS_ASSERTION(aContentOffset >= 0,
 | 
						||
               "Negative content offset, existing code was very broken!");
 | 
						||
  nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
 | 
						||
  if (this != primaryFrame) {
 | 
						||
    // This call needs to happen on the primary frame
 | 
						||
    return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
 | 
						||
                                                       aOutOffset, aOutFrame);
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* f = this;
 | 
						||
  int32_t offset = mContentOffset;
 | 
						||
 | 
						||
  // Try to look up the offset to frame property
 | 
						||
  nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
 | 
						||
 | 
						||
  if (cachedFrame) {
 | 
						||
    f = cachedFrame;
 | 
						||
    offset = f->GetContentOffset();
 | 
						||
 | 
						||
    f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
 | 
						||
  }
 | 
						||
 | 
						||
  if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) {
 | 
						||
    while (true) {
 | 
						||
      nsTextFrame* next = f->GetNextContinuation();
 | 
						||
      if (!next || aContentOffset < next->GetContentOffset()) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      if (aContentOffset == next->GetContentOffset()) {
 | 
						||
        if (aHint) {
 | 
						||
          f = next;
 | 
						||
          if (f->GetContentLength() == 0) {
 | 
						||
            continue;  // use the last of the empty frames with this offset
 | 
						||
          }
 | 
						||
        }
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      f = next;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    while (true) {
 | 
						||
      nsTextFrame* prev = f->GetPrevContinuation();
 | 
						||
      if (!prev || aContentOffset > f->GetContentOffset()) {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      if (aContentOffset == f->GetContentOffset()) {
 | 
						||
        if (!aHint) {
 | 
						||
          f = prev;
 | 
						||
          if (f->GetContentLength() == 0) {
 | 
						||
            continue;  // use the first of the empty frames with this offset
 | 
						||
          }
 | 
						||
        }
 | 
						||
        break;
 | 
						||
      }
 | 
						||
      f = prev;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  *aOutOffset = aContentOffset - f->GetContentOffset();
 | 
						||
  *aOutFrame = f;
 | 
						||
 | 
						||
  // cache the frame we found
 | 
						||
  SetProperty(OffsetToFrameProperty(), f);
 | 
						||
  f->AddStateBits(TEXT_IN_OFFSET_CACHE);
 | 
						||
 | 
						||
  return NS_OK;
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
 | 
						||
                                                            int32_t* aOffset) {
 | 
						||
  NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),
 | 
						||
               "aOffset out of range");
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return CONTINUE_EMPTY;
 | 
						||
  }
 | 
						||
 | 
						||
  TrimmedOffsets trimmed = GetTrimmedOffsets(TextFragment());
 | 
						||
  // Check whether there are nonskipped characters in the trimmmed range
 | 
						||
  return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
 | 
						||
          iter.ConvertOriginalToSkipped(trimmed.mStart))
 | 
						||
             ? FOUND
 | 
						||
             : CONTINUE;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * This class iterates through the clusters before or after the given
 | 
						||
 * aPosition (which is a content offset). You can test each cluster
 | 
						||
 * to see if it's whitespace (as far as selection/caret movement is concerned),
 | 
						||
 * or punctuation, or if there is a word break before the cluster. ("Before"
 | 
						||
 * is interpreted according to aDirection, so if aDirection is -1, "before"
 | 
						||
 * means actually *after* the cluster content.)
 | 
						||
 */
 | 
						||
class MOZ_STACK_CLASS ClusterIterator {
 | 
						||
 public:
 | 
						||
  ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
 | 
						||
                  int32_t aDirection, nsString& aContext,
 | 
						||
                  bool aTrimSpaces = true);
 | 
						||
 | 
						||
  bool NextCluster();
 | 
						||
  bool IsInlineWhitespace() const;
 | 
						||
  bool IsNewline() const;
 | 
						||
  bool IsPunctuation() const;
 | 
						||
  intl::Script ScriptCode() const;
 | 
						||
  bool HaveWordBreakBefore() const { return mHaveWordBreak; }
 | 
						||
 | 
						||
  // Get the charIndex that corresponds to the "before" side of the current
 | 
						||
  // character, according to the direction of iteration: so for a forward
 | 
						||
  // iterator, this is simply mCharIndex, while for a reverse iterator it will
 | 
						||
  // be mCharIndex + <number of code units in the character>.
 | 
						||
  int32_t GetBeforeOffset() const {
 | 
						||
    MOZ_ASSERT(mCharIndex >= 0);
 | 
						||
    return mDirection < 0 ? GetAfterInternal() : mCharIndex;
 | 
						||
  }
 | 
						||
  // Get the charIndex that corresponds to the "before" side of the current
 | 
						||
  // character, according to the direction of iteration: the opposite side
 | 
						||
  // to what GetBeforeOffset returns.
 | 
						||
  int32_t GetAfterOffset() const {
 | 
						||
    MOZ_ASSERT(mCharIndex >= 0);
 | 
						||
    return mDirection > 0 ? GetAfterInternal() : mCharIndex;
 | 
						||
  }
 | 
						||
 | 
						||
 private:
 | 
						||
  // Helper for Get{After,Before}Offset; returns the charIndex after the
 | 
						||
  // current position in the text, accounting for surrogate pairs.
 | 
						||
  int32_t GetAfterInternal() const;
 | 
						||
 | 
						||
  gfxSkipCharsIterator mIterator;
 | 
						||
  // Usually, mFrag is pointer to `dom::CharacterData::mText`.  However, if
 | 
						||
  // we're in a password field, this points `mMaskedFrag`.
 | 
						||
  const nsTextFragment* mFrag;
 | 
						||
  // If we're in a password field, this is initialized with mask characters.
 | 
						||
  nsTextFragment mMaskedFrag;
 | 
						||
  nsTextFrame* mTextFrame;
 | 
						||
  int32_t mDirection;  // +1 or -1, or 0 to indicate failure
 | 
						||
  int32_t mCharIndex;
 | 
						||
  nsTextFrame::TrimmedOffsets mTrimmed;
 | 
						||
  nsTArray<bool> mWordBreaks;
 | 
						||
  bool mHaveWordBreak;
 | 
						||
};
 | 
						||
 | 
						||
static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
 | 
						||
                                      bool aRespectClusters,
 | 
						||
                                      const gfxTextRun* aTextRun,
 | 
						||
                                      nsTextFrame* aFrame) {
 | 
						||
  if (aIter.IsOriginalCharSkipped()) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  uint32_t index = aIter.GetSkippedOffset();
 | 
						||
  if (aRespectClusters && !aTextRun->IsClusterStart(index)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  if (index > 0) {
 | 
						||
    // Check whether the proposed position is in between the two halves of a
 | 
						||
    // surrogate pair, before a Variation Selector character, or within a
 | 
						||
    // ligated emoji sequence; if so, this is not a valid character boundary.
 | 
						||
    // (In the case where we are respecting clusters, we won't actually get
 | 
						||
    // this far because the low surrogate is also marked as non-clusterStart
 | 
						||
    // so we'll return FALSE above.)
 | 
						||
    const uint32_t offs = AssertedCast<uint32_t>(aIter.GetOriginalOffset());
 | 
						||
    const nsTextFragment* frag = aFrame->TextFragment();
 | 
						||
    const char16_t ch = frag->CharAt(offs);
 | 
						||
 | 
						||
    if (gfxFontUtils::IsVarSelector(ch) ||
 | 
						||
        frag->IsLowSurrogateFollowingHighSurrogateAt(offs) ||
 | 
						||
        (!aTextRun->IsLigatureGroupStart(index) &&
 | 
						||
         (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault ||
 | 
						||
          (unicode::GetEmojiPresentation(ch) == unicode::TextDefault &&
 | 
						||
           offs + 1 < frag->GetLength() &&
 | 
						||
           frag->CharAt(offs + 1) == gfxFontUtils::kUnicodeVS16)))) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
 | 
						||
    // If the proposed position is before a high surrogate, we need to decode
 | 
						||
    // the surrogate pair (if valid) and check the resulting character.
 | 
						||
    if (NS_IS_HIGH_SURROGATE(ch)) {
 | 
						||
      if (const char32_t ucs4 = frag->ScalarValueAt(offs)) {
 | 
						||
        // If the character is a (Plane-14) variation selector,
 | 
						||
        // or an emoji character that is ligated with the previous
 | 
						||
        // character (i.e. part of a Regional-Indicator flag pair,
 | 
						||
        // or an emoji-ZWJ sequence), this is not a valid boundary.
 | 
						||
        if (gfxFontUtils::IsVarSelector(ucs4) ||
 | 
						||
            (!aTextRun->IsLigatureGroupStart(index) &&
 | 
						||
             unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) {
 | 
						||
          return false;
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter(
 | 
						||
    bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
 | 
						||
  int32_t contentLength = GetContentLength();
 | 
						||
  NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
 | 
						||
 | 
						||
  if (!aOptions.mIgnoreUserStyleAll) {
 | 
						||
    StyleUserSelect selectStyle;
 | 
						||
    Unused << IsSelectable(&selectStyle);
 | 
						||
    if (selectStyle == StyleUserSelect::All) {
 | 
						||
      return CONTINUE_UNSELECTABLE;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return CONTINUE_EMPTY;
 | 
						||
  }
 | 
						||
 | 
						||
  TrimmedOffsets trimmed =
 | 
						||
      GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
 | 
						||
 | 
						||
  // A negative offset means "end of frame".
 | 
						||
  int32_t startOffset =
 | 
						||
      GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
 | 
						||
 | 
						||
  if (!aForward) {
 | 
						||
    // If at the beginning of the line, look at the previous continuation
 | 
						||
    for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
 | 
						||
         i >= trimmed.mStart; --i) {
 | 
						||
      iter.SetOriginalOffset(i);
 | 
						||
      if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
 | 
						||
                                    this)) {
 | 
						||
        *aOffset = i - mContentOffset;
 | 
						||
        return FOUND;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    *aOffset = 0;
 | 
						||
  } else {
 | 
						||
    // If we're at the end of a line, look at the next continuation
 | 
						||
    iter.SetOriginalOffset(startOffset);
 | 
						||
    if (startOffset <= trimmed.GetEnd() &&
 | 
						||
        !(startOffset < trimmed.GetEnd() &&
 | 
						||
          StyleText()->NewlineIsSignificant(this) &&
 | 
						||
          iter.GetSkippedOffset() < mTextRun->GetLength() &&
 | 
						||
          mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
 | 
						||
      for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
 | 
						||
        iter.SetOriginalOffset(i);
 | 
						||
        if (i == trimmed.GetEnd() ||
 | 
						||
            IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
 | 
						||
                                      this)) {
 | 
						||
          *aOffset = i - mContentOffset;
 | 
						||
          return FOUND;
 | 
						||
        }
 | 
						||
      }
 | 
						||
    }
 | 
						||
    *aOffset = contentLength;
 | 
						||
  }
 | 
						||
 | 
						||
  return CONTINUE;
 | 
						||
}
 | 
						||
 | 
						||
bool ClusterIterator::IsInlineWhitespace() const {
 | 
						||
  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
 | 
						||
  return IsSelectionInlineWhitespace(mFrag, mCharIndex);
 | 
						||
}
 | 
						||
 | 
						||
bool ClusterIterator::IsNewline() const {
 | 
						||
  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
 | 
						||
  return IsSelectionNewline(mFrag, mCharIndex);
 | 
						||
}
 | 
						||
 | 
						||
bool ClusterIterator::IsPunctuation() const {
 | 
						||
  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
 | 
						||
  const char16_t ch = mFrag->CharAt(AssertedCast<uint32_t>(mCharIndex));
 | 
						||
  return mozilla::IsPunctuationForWordSelect(ch);
 | 
						||
}
 | 
						||
 | 
						||
intl::Script ClusterIterator::ScriptCode() const {
 | 
						||
  NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
 | 
						||
  const char16_t ch = mFrag->CharAt(AssertedCast<uint32_t>(mCharIndex));
 | 
						||
  return intl::UnicodeProperties::GetScriptCode(ch);
 | 
						||
}
 | 
						||
 | 
						||
static inline bool IsKorean(intl::Script aScript) {
 | 
						||
  // We only need to check for HANGUL script code; there is a script code
 | 
						||
  // KOREAN but this is not assigned to any codepoints. (If that ever changes,
 | 
						||
  // we could check for both codes here.)
 | 
						||
  MOZ_ASSERT(aScript != intl::Script::KOREAN, "unexpected script code");
 | 
						||
  return aScript == intl::Script::HANGUL;
 | 
						||
}
 | 
						||
 | 
						||
int32_t ClusterIterator::GetAfterInternal() const {
 | 
						||
  if (mFrag->IsHighSurrogateFollowedByLowSurrogateAt(
 | 
						||
          AssertedCast<uint32_t>(mCharIndex))) {
 | 
						||
    return mCharIndex + 2;
 | 
						||
  }
 | 
						||
  return mCharIndex + 1;
 | 
						||
}
 | 
						||
 | 
						||
bool ClusterIterator::NextCluster() {
 | 
						||
  if (!mDirection) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
 | 
						||
 | 
						||
  mHaveWordBreak = false;
 | 
						||
  while (true) {
 | 
						||
    bool keepGoing = false;
 | 
						||
    if (mDirection > 0) {
 | 
						||
      if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) {
 | 
						||
        return false;
 | 
						||
      }
 | 
						||
      keepGoing = mIterator.IsOriginalCharSkipped() ||
 | 
						||
                  mIterator.GetOriginalOffset() < mTrimmed.mStart ||
 | 
						||
                  !textRun->IsClusterStart(mIterator.GetSkippedOffset());
 | 
						||
      mCharIndex = mIterator.GetOriginalOffset();
 | 
						||
      mIterator.AdvanceOriginal(1);
 | 
						||
    } else {
 | 
						||
      if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) {
 | 
						||
        // Trimming can skip backward word breakers, see bug 1667138
 | 
						||
        return mHaveWordBreak;
 | 
						||
      }
 | 
						||
      mIterator.AdvanceOriginal(-1);
 | 
						||
      keepGoing = mIterator.IsOriginalCharSkipped() ||
 | 
						||
                  mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
 | 
						||
                  !textRun->IsClusterStart(mIterator.GetSkippedOffset());
 | 
						||
      mCharIndex = mIterator.GetOriginalOffset();
 | 
						||
    }
 | 
						||
 | 
						||
    if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
 | 
						||
      mHaveWordBreak = true;
 | 
						||
    }
 | 
						||
    if (!keepGoing) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
 | 
						||
                                 int32_t aDirection, nsString& aContext,
 | 
						||
                                 bool aTrimSpaces)
 | 
						||
    : mIterator(aTextFrame->EnsureTextRun(nsTextFrame::eInflated)),
 | 
						||
      mTextFrame(aTextFrame),
 | 
						||
      mDirection(aDirection),
 | 
						||
      mCharIndex(-1),
 | 
						||
      mHaveWordBreak(false) {
 | 
						||
  gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!textRun) {
 | 
						||
    mDirection = 0;  // signal failure
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  mFrag = aTextFrame->TextFragment();
 | 
						||
 | 
						||
  const uint32_t textOffset =
 | 
						||
      AssertedCast<uint32_t>(aTextFrame->GetContentOffset());
 | 
						||
  const uint32_t textLen =
 | 
						||
      AssertedCast<uint32_t>(aTextFrame->GetContentLength());
 | 
						||
 | 
						||
  // If we're in a password field, some characters may be masked.  In such
 | 
						||
  // case, we need to treat each masked character as a mask character since
 | 
						||
  // we shouldn't expose word boundary which is hidden by the masking.
 | 
						||
  if (aTextFrame->GetContent() && mFrag->GetLength() > 0 &&
 | 
						||
      aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) &&
 | 
						||
      (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) {
 | 
						||
    const char16_t kPasswordMask = TextEditor::PasswordMask();
 | 
						||
    const nsTransformedTextRun* transformedTextRun =
 | 
						||
        static_cast<const nsTransformedTextRun*>(textRun);
 | 
						||
    // Use nsString and not nsAutoString so that we get a nsStringBuffer which
 | 
						||
    // can be just AddRefed in `mMaskedFrag`.
 | 
						||
    nsString maskedText;
 | 
						||
    maskedText.SetCapacity(mFrag->GetLength());
 | 
						||
    // Note that aTextFrame may not cover the whole of mFrag (in cases with
 | 
						||
    // bidi continuations), so we cannot rely on its textrun (and associated
 | 
						||
    // styles) being available for the entire fragment.
 | 
						||
    uint32_t i = 0;
 | 
						||
    // Just copy any text that precedes what aTextFrame covers.
 | 
						||
    while (i < textOffset) {
 | 
						||
      maskedText.Append(mFrag->CharAt(i++));
 | 
						||
    }
 | 
						||
    // For the range covered by aTextFrame, mask chars if appropriate.
 | 
						||
    while (i < textOffset + textLen) {
 | 
						||
      uint32_t skippedOffset = mIterator.ConvertOriginalToSkipped(i);
 | 
						||
      bool mask =
 | 
						||
          skippedOffset < transformedTextRun->GetLength()
 | 
						||
              ? transformedTextRun->mStyles[skippedOffset]->mMaskPassword
 | 
						||
              : false;
 | 
						||
      if (mFrag->IsHighSurrogateFollowedByLowSurrogateAt(i)) {
 | 
						||
        if (mask) {
 | 
						||
          maskedText.Append(kPasswordMask);
 | 
						||
          maskedText.Append(kPasswordMask);
 | 
						||
        } else {
 | 
						||
          maskedText.Append(mFrag->CharAt(i));
 | 
						||
          maskedText.Append(mFrag->CharAt(i + 1));
 | 
						||
        }
 | 
						||
        i += 2;
 | 
						||
      } else {
 | 
						||
        maskedText.Append(mask ? kPasswordMask : mFrag->CharAt(i));
 | 
						||
        ++i;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    // Copy any trailing text from the fragment.
 | 
						||
    while (i < mFrag->GetLength()) {
 | 
						||
      maskedText.Append(mFrag->CharAt(i++));
 | 
						||
    }
 | 
						||
    mMaskedFrag.SetTo(maskedText, mFrag->IsBidi(), true);
 | 
						||
    mFrag = &mMaskedFrag;
 | 
						||
  }
 | 
						||
 | 
						||
  mIterator.SetOriginalOffset(aPosition);
 | 
						||
  mTrimmed = aTextFrame->GetTrimmedOffsets(
 | 
						||
      mFrag, aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default
 | 
						||
                         : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter |
 | 
						||
                               nsTextFrame::TrimmedOffsetFlags::NoTrimBefore);
 | 
						||
 | 
						||
  // Allocate an extra element to record the word break at the end of the line
 | 
						||
  // or text run in mWordBreak[textLen].
 | 
						||
  mWordBreaks.AppendElements(textLen + 1);
 | 
						||
  PodZero(mWordBreaks.Elements(), textLen + 1);
 | 
						||
  uint32_t textStart;
 | 
						||
  if (aDirection > 0) {
 | 
						||
    if (aContext.IsEmpty()) {
 | 
						||
      // No previous context, so it must be the start of a line or text run
 | 
						||
      mWordBreaks[0] = true;
 | 
						||
    }
 | 
						||
    textStart = aContext.Length();
 | 
						||
    mFrag->AppendTo(aContext, textOffset, textLen);
 | 
						||
  } else {
 | 
						||
    if (aContext.IsEmpty()) {
 | 
						||
      // No following context, so it must be the end of a line or text run
 | 
						||
      mWordBreaks[textLen] = true;
 | 
						||
    }
 | 
						||
    textStart = 0;
 | 
						||
    nsAutoString str;
 | 
						||
    mFrag->AppendTo(str, textOffset, textLen);
 | 
						||
    aContext.Insert(str, 0);
 | 
						||
  }
 | 
						||
 | 
						||
  const uint32_t textEnd = textStart + textLen;
 | 
						||
  intl::WordBreakIteratorUtf16 wordBreakIter(aContext);
 | 
						||
  Maybe<uint32_t> nextBreak =
 | 
						||
      wordBreakIter.Seek(textStart > 0 ? textStart - 1 : textStart);
 | 
						||
  while (nextBreak && *nextBreak <= textEnd) {
 | 
						||
    mWordBreaks[*nextBreak - textStart] = true;
 | 
						||
    nextBreak = wordBreakIter.Next();
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(textEnd != aContext.Length() || mWordBreaks[textLen],
 | 
						||
             "There should be a word break at the end of a line or text run!");
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord(
 | 
						||
    bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
 | 
						||
    int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
 | 
						||
  int32_t contentLength = GetContentLength();
 | 
						||
  NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
 | 
						||
 | 
						||
  StyleUserSelect selectStyle;
 | 
						||
  Unused << IsSelectable(&selectStyle);
 | 
						||
  if (selectStyle == StyleUserSelect::All) {
 | 
						||
    return CONTINUE_UNSELECTABLE;
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t offset =
 | 
						||
      GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
 | 
						||
  ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext,
 | 
						||
                        aTrimSpaces);
 | 
						||
 | 
						||
  if (!cIter.NextCluster()) {
 | 
						||
    return CONTINUE_EMPTY;
 | 
						||
  }
 | 
						||
 | 
						||
  // Do we need to check for Korean characters?
 | 
						||
  bool is2b = TextFragment()->Is2b();
 | 
						||
  do {
 | 
						||
    bool isPunctuation = cIter.IsPunctuation();
 | 
						||
    bool isInlineWhitespace = cIter.IsInlineWhitespace();
 | 
						||
    bool isWhitespace = isInlineWhitespace || cIter.IsNewline();
 | 
						||
    bool isWordBreakBefore = cIter.HaveWordBreakBefore();
 | 
						||
    // If the text is one-byte, we don't actually care about script code as
 | 
						||
    // there cannot be any Korean in the frame.
 | 
						||
    intl::Script scriptCode = is2b ? cIter.ScriptCode() : intl::Script::COMMON;
 | 
						||
    if (!isWhitespace || isInlineWhitespace) {
 | 
						||
      aState->SetSawInlineCharacter();
 | 
						||
    }
 | 
						||
    if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
 | 
						||
      aState->SetSawBeforeType();
 | 
						||
      aState->Update(isPunctuation, isWhitespace, scriptCode);
 | 
						||
      continue;
 | 
						||
    }
 | 
						||
    // See if we can break before the current cluster
 | 
						||
    if (!aState->mAtStart) {
 | 
						||
      bool canBreak;
 | 
						||
      if (isPunctuation != aState->mLastCharWasPunctuation) {
 | 
						||
        canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation,
 | 
						||
                                               isWhitespace, aIsKeyboardSelect);
 | 
						||
      } else if (!aState->mLastCharWasWhitespace && !isWhitespace &&
 | 
						||
                 !isPunctuation && isWordBreakBefore) {
 | 
						||
        // if both the previous and the current character are not white
 | 
						||
        // space but this can be word break before, we don't need to eat
 | 
						||
        // a white space in this case. This case happens in some languages
 | 
						||
        // that their words are not separated by white spaces. E.g.,
 | 
						||
        // Japanese and Chinese.
 | 
						||
        canBreak = true;
 | 
						||
      } else {
 | 
						||
        canBreak = isWordBreakBefore && aState->mSawBeforeType &&
 | 
						||
                   (aWordSelectEatSpace != isWhitespace);
 | 
						||
      }
 | 
						||
      // Special-case for Korean: treat a boundary between Hangul & non-Hangul
 | 
						||
      // characters as a word boundary (see bug 1973393 and UAX#29).
 | 
						||
      if (!canBreak && is2b && aState->mLastScript != intl::Script::INVALID &&
 | 
						||
          IsKorean(aState->mLastScript) != IsKorean(scriptCode)) {
 | 
						||
        canBreak = true;
 | 
						||
      }
 | 
						||
      if (canBreak) {
 | 
						||
        *aOffset = cIter.GetBeforeOffset() - mContentOffset;
 | 
						||
        return FOUND;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    aState->Update(isPunctuation, isWhitespace, scriptCode);
 | 
						||
  } while (cIter.NextCluster());
 | 
						||
 | 
						||
  *aOffset = cIter.GetAfterOffset() - mContentOffset;
 | 
						||
  return CONTINUE;
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::HasVisibleText() {
 | 
						||
  // Text in the range is visible if there is at least one character in the
 | 
						||
  // range that is not skipped and is mapped by this frame (which is the primary
 | 
						||
  // frame) or one of its continuations.
 | 
						||
  for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
 | 
						||
    int32_t dummyOffset = 0;
 | 
						||
    if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const {
 | 
						||
  return std::make_pair(GetContentOffset(), GetContentEnd());
 | 
						||
}
 | 
						||
 | 
						||
static bool IsFirstLetterPrefixPunctuation(uint32_t aChar) {
 | 
						||
  switch (mozilla::unicode::GetGeneralCategory(aChar)) {
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION:    /* Pd */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION:    /* Ps */
 | 
						||
      return true;
 | 
						||
    default:
 | 
						||
      return false;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static bool IsFirstLetterSuffixPunctuation(uint32_t aChar) {
 | 
						||
  switch (mozilla::unicode::GetGeneralCategory(aChar)) {
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
 | 
						||
    case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
 | 
						||
      return true;
 | 
						||
    default:
 | 
						||
      return false;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static int32_t FindEndOfPrefixPunctuationRun(const nsTextFragment* aFrag,
 | 
						||
                                             const gfxTextRun* aTextRun,
 | 
						||
                                             gfxSkipCharsIterator* aIter,
 | 
						||
                                             int32_t aOffset, int32_t aStart,
 | 
						||
                                             int32_t aEnd) {
 | 
						||
  int32_t i;
 | 
						||
  for (i = aStart; i < aEnd - aOffset; ++i) {
 | 
						||
    if (IsFirstLetterPrefixPunctuation(
 | 
						||
            aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
 | 
						||
      aIter->SetOriginalOffset(aOffset + i);
 | 
						||
      FindClusterEnd(aTextRun, aEnd, aIter);
 | 
						||
      i = aIter->GetOriginalOffset() - aOffset;
 | 
						||
    } else {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return i;
 | 
						||
}
 | 
						||
 | 
						||
static int32_t FindEndOfSuffixPunctuationRun(const nsTextFragment* aFrag,
 | 
						||
                                             const gfxTextRun* aTextRun,
 | 
						||
                                             gfxSkipCharsIterator* aIter,
 | 
						||
                                             int32_t aOffset, int32_t aStart,
 | 
						||
                                             int32_t aEnd) {
 | 
						||
  int32_t i;
 | 
						||
  for (i = aStart; i < aEnd - aOffset; ++i) {
 | 
						||
    if (IsFirstLetterSuffixPunctuation(
 | 
						||
            aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i)))) {
 | 
						||
      aIter->SetOriginalOffset(aOffset + i);
 | 
						||
      FindClusterEnd(aTextRun, aEnd, aIter);
 | 
						||
      i = aIter->GetOriginalOffset() - aOffset;
 | 
						||
    } else {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return i;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Returns true if this text frame completes the first-letter, false
 | 
						||
 * if it does not contain a true "letter".
 | 
						||
 * If returns true, then it also updates aLength to cover just the first-letter
 | 
						||
 * text.
 | 
						||
 *
 | 
						||
 * XXX :first-letter should be handled during frame construction
 | 
						||
 * (and it has a good bit in common with nextBidi)
 | 
						||
 *
 | 
						||
 * @param aLength an in/out parameter: on entry contains the maximum length to
 | 
						||
 * return, on exit returns length of the first-letter fragment (which may
 | 
						||
 * include leading and trailing punctuation, for example)
 | 
						||
 */
 | 
						||
static bool FindFirstLetterRange(const nsTextFragment* aFrag,
 | 
						||
                                 const nsAtom* aLang,
 | 
						||
                                 const gfxTextRun* aTextRun, int32_t aOffset,
 | 
						||
                                 const gfxSkipCharsIterator& aIter,
 | 
						||
                                 int32_t* aLength) {
 | 
						||
  int32_t length = *aLength;
 | 
						||
  int32_t endOffset = aOffset + length;
 | 
						||
  gfxSkipCharsIterator iter(aIter);
 | 
						||
 | 
						||
  // Currently the only language-specific special case we handle here is the
 | 
						||
  // Dutch "IJ" digraph.
 | 
						||
  auto LangTagIsDutch = [](const nsAtom* aLang) -> bool {
 | 
						||
    if (!aLang) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
    if (aLang == nsGkAtoms::nl) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    // We don't need to fully parse as a Locale; just check the initial subtag.
 | 
						||
    nsDependentAtomString langStr(aLang);
 | 
						||
    int32_t index = langStr.FindChar('-');
 | 
						||
    if (index > 0) {
 | 
						||
      langStr.Truncate(index);
 | 
						||
      return langStr.EqualsLiteral("nl");
 | 
						||
    }
 | 
						||
    return false;
 | 
						||
  };
 | 
						||
 | 
						||
  // Skip any trimmable leading whitespace.
 | 
						||
  int32_t i = GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1);
 | 
						||
  while (true) {
 | 
						||
    // Scan past any leading punctuation. This leaves `j` at the first
 | 
						||
    // non-punctuation character.
 | 
						||
    int32_t j = FindEndOfPrefixPunctuationRun(aFrag, aTextRun, &iter, aOffset,
 | 
						||
                                              i, endOffset);
 | 
						||
    if (j == length) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
 | 
						||
    // Scan past any Unicode whitespace characters after punctuation.
 | 
						||
    while (j < length) {
 | 
						||
      char16_t ch = aFrag->CharAt(AssertedCast<uint32_t>(aOffset + j));
 | 
						||
      // The spec says to allow "characters that belong to the `Zs` Unicode
 | 
						||
      // general category _other than_ U+3000" here.
 | 
						||
      if (unicode::GetGeneralCategory(ch) ==
 | 
						||
              HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
 | 
						||
          ch != 0x3000) {
 | 
						||
        ++j;
 | 
						||
      } else {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
    }
 | 
						||
    if (j == length) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
    if (j == i) {
 | 
						||
      // If no whitespace was found, we've finished the first-letter prefix;
 | 
						||
      // if there was some, then go back to check for more punctuation.
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    i = j;
 | 
						||
  }
 | 
						||
 | 
						||
  // If the next character is not a letter, number or symbol, there is no
 | 
						||
  // first-letter.
 | 
						||
  // Return true so that we don't go on looking, but set aLength to 0.
 | 
						||
  const char32_t usv =
 | 
						||
      aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
 | 
						||
  if (!nsContentUtils::IsAlphanumericOrSymbol(usv)) {
 | 
						||
    *aLength = 0;
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  // Consume another cluster (the actual first letter):
 | 
						||
 | 
						||
  // For complex scripts such as Indic and SEAsian, where first-letter
 | 
						||
  // should extend to entire orthographic "syllable" clusters, we don't
 | 
						||
  // want to allow this to split a ligature.
 | 
						||
  bool allowSplitLigature;
 | 
						||
  bool usesIndicHalfForms = false;
 | 
						||
 | 
						||
  typedef intl::Script Script;
 | 
						||
  Script script = intl::UnicodeProperties::GetScriptCode(usv);
 | 
						||
  switch (script) {
 | 
						||
    default:
 | 
						||
      allowSplitLigature = true;
 | 
						||
      break;
 | 
						||
 | 
						||
    // Don't break regional-indicator ligatures.
 | 
						||
    case Script::COMMON:
 | 
						||
      allowSplitLigature = !gfxFontUtils::IsRegionalIndicator(usv);
 | 
						||
      break;
 | 
						||
 | 
						||
    // For now, lacking any definitive specification of when to apply this
 | 
						||
    // behavior, we'll base the decision on the HarfBuzz shaping engine
 | 
						||
    // used for each script: those that are handled by the Indic, Tibetan,
 | 
						||
    // Myanmar and SEAsian shapers will apply the "don't split ligatures"
 | 
						||
    // rule.
 | 
						||
 | 
						||
    // Indic
 | 
						||
    case Script::BENGALI:
 | 
						||
    case Script::DEVANAGARI:
 | 
						||
    case Script::GUJARATI:
 | 
						||
      usesIndicHalfForms = true;
 | 
						||
      [[fallthrough]];
 | 
						||
 | 
						||
    case Script::GURMUKHI:
 | 
						||
    case Script::KANNADA:
 | 
						||
    case Script::MALAYALAM:
 | 
						||
    case Script::ORIYA:
 | 
						||
    case Script::TAMIL:
 | 
						||
    case Script::TELUGU:
 | 
						||
    case Script::SINHALA:
 | 
						||
    case Script::BALINESE:
 | 
						||
    case Script::LEPCHA:
 | 
						||
    case Script::REJANG:
 | 
						||
    case Script::SUNDANESE:
 | 
						||
    case Script::JAVANESE:
 | 
						||
    case Script::KAITHI:
 | 
						||
    case Script::MEETEI_MAYEK:
 | 
						||
    case Script::CHAKMA:
 | 
						||
    case Script::SHARADA:
 | 
						||
    case Script::TAKRI:
 | 
						||
    case Script::KHMER:
 | 
						||
 | 
						||
    // Tibetan
 | 
						||
    case Script::TIBETAN:
 | 
						||
 | 
						||
    // Myanmar
 | 
						||
    case Script::MYANMAR:
 | 
						||
 | 
						||
    // Other SEAsian
 | 
						||
    case Script::BUGINESE:
 | 
						||
    case Script::NEW_TAI_LUE:
 | 
						||
    case Script::CHAM:
 | 
						||
    case Script::TAI_THAM:
 | 
						||
 | 
						||
      // What about Thai/Lao - any special handling needed?
 | 
						||
      // Should we special-case Arabic lam-alef?
 | 
						||
 | 
						||
      allowSplitLigature = false;
 | 
						||
      break;
 | 
						||
  }
 | 
						||
 | 
						||
  // NOTE that FindClusterEnd sets the iterator to the last character that is
 | 
						||
  // part of the cluster, NOT to the first character beyond it.
 | 
						||
  iter.SetOriginalOffset(aOffset + i);
 | 
						||
  FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
 | 
						||
 | 
						||
  // Index of the last character included in the first-letter cluster.
 | 
						||
  i = iter.GetOriginalOffset() - aOffset;
 | 
						||
 | 
						||
  // Heuristic for Indic scripts that like to form conjuncts:
 | 
						||
  // If we ended at a virama that is ligated with the preceding character
 | 
						||
  // (e.g. creating a half-form), then don't stop here; include the next
 | 
						||
  // cluster as well so that we don't break a conjunct.
 | 
						||
  //
 | 
						||
  // Unfortunately this cannot distinguish between a letter+virama that ligate
 | 
						||
  // to create a half-form (in which case we have a conjunct that should not
 | 
						||
  // be broken) and a letter+virama that ligate purely for presentational
 | 
						||
  // reasons to position the (visible) virama component (in which case breaking
 | 
						||
  // after the virama would be acceptable). So results may be imperfect,
 | 
						||
  // depending how the font has chosen to implement visible viramas.
 | 
						||
  if (usesIndicHalfForms) {
 | 
						||
    while (i + 1 < length &&
 | 
						||
           !aTextRun->IsLigatureGroupStart(iter.GetSkippedOffset())) {
 | 
						||
      char32_t c = aFrag->ScalarValueAt(AssertedCast<uint32_t>(aOffset + i));
 | 
						||
      if (intl::UnicodeProperties::GetCombiningClass(c) ==
 | 
						||
          HB_UNICODE_COMBINING_CLASS_VIRAMA) {
 | 
						||
        iter.AdvanceOriginal(1);
 | 
						||
        FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
 | 
						||
        i = iter.GetOriginalOffset() - aOffset;
 | 
						||
      } else {
 | 
						||
        break;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (i + 1 == length) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  // Check for Dutch "ij" digraph special case, but only if both letters have
 | 
						||
  // the same case.
 | 
						||
  if (script == Script::LATIN && LangTagIsDutch(aLang)) {
 | 
						||
    char16_t ch1 = aFrag->CharAt(AssertedCast<uint32_t>(aOffset + i));
 | 
						||
    char16_t ch2 = aFrag->CharAt(AssertedCast<uint32_t>(aOffset + i + 1));
 | 
						||
    if ((ch1 == 'i' && ch2 == 'j') || (ch1 == 'I' && ch2 == 'J')) {
 | 
						||
      iter.SetOriginalOffset(aOffset + i + 1);
 | 
						||
      FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
 | 
						||
      i = iter.GetOriginalOffset() - aOffset;
 | 
						||
      if (i + 1 == length) {
 | 
						||
        return true;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // When we reach here, `i` points to the last character of the first-letter
 | 
						||
  // cluster, NOT to the first character beyond it. Advance to the next char,
 | 
						||
  // ready to check for following whitespace/punctuation:
 | 
						||
  ++i;
 | 
						||
 | 
						||
  while (i < length) {
 | 
						||
    // Skip over whitespace, except for word separator characters, before the
 | 
						||
    // check for following punctuation. But remember the position before the
 | 
						||
    // whitespace, in case we need to reset.
 | 
						||
    const int32_t preWS = i;
 | 
						||
    while (i < length) {
 | 
						||
      char16_t ch = aFrag->CharAt(AssertedCast<uint32_t>(aOffset + i));
 | 
						||
      // The spec says the first-letter suffix includes "any intervening
 | 
						||
      // typographic space -- characters belonging to the Zs Unicode general
 | 
						||
      // category other than U+3000 IDEOGRAPHIC SPACE or a word separator",
 | 
						||
      // where "word separator" includes U+0020 and U+00A0.
 | 
						||
      if (ch == 0x0020 || ch == 0x00A0 || ch == 0x3000 ||
 | 
						||
          unicode::GetGeneralCategory(ch) !=
 | 
						||
              HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR) {
 | 
						||
        break;
 | 
						||
      } else {
 | 
						||
        ++i;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    // Consume clusters that start with punctuation.
 | 
						||
    const int32_t prePunct = i;
 | 
						||
    i = FindEndOfSuffixPunctuationRun(aFrag, aTextRun, &iter, aOffset, i,
 | 
						||
                                      endOffset);
 | 
						||
 | 
						||
    // If we didn't find punctuation here, then we also don't want to include
 | 
						||
    // any preceding whitespace, so reset our index.
 | 
						||
    if (i == prePunct) {
 | 
						||
      i = preWS;
 | 
						||
      break;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (i < length) {
 | 
						||
    *aLength = i;
 | 
						||
  }
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
static uint32_t FindStartAfterSkippingWhitespace(
 | 
						||
    nsTextFrame::PropertyProvider* aProvider,
 | 
						||
    nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle,
 | 
						||
    gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) {
 | 
						||
  if (aData->mSkipWhitespace) {
 | 
						||
    while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
 | 
						||
           IsTrimmableSpace(aProvider->GetFragment(),
 | 
						||
                            aIterator->GetOriginalOffset(), aTextStyle)) {
 | 
						||
      aIterator->AdvanceOriginal(1);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return aIterator->GetSkippedOffset();
 | 
						||
}
 | 
						||
 | 
						||
float nsTextFrame::GetFontSizeInflation() const {
 | 
						||
  if (!HasFontSizeInflation()) {
 | 
						||
    return 1.0f;
 | 
						||
  }
 | 
						||
  return GetProperty(FontSizeInflationProperty());
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SetFontSizeInflation(float aInflation) {
 | 
						||
  if (aInflation == 1.0f) {
 | 
						||
    if (HasFontSizeInflation()) {
 | 
						||
      RemoveStateBits(TEXT_HAS_FONT_INFLATION);
 | 
						||
      RemoveProperty(FontSizeInflationProperty());
 | 
						||
    }
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  AddStateBits(TEXT_HAS_FONT_INFLATION);
 | 
						||
  SetProperty(FontSizeInflationProperty(), aInflation);
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SetHangableISize(nscoord aISize) {
 | 
						||
  MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance");
 | 
						||
  if (aISize <= 0) {
 | 
						||
    ClearHangableISize();
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  SetProperty(HangableWhitespaceProperty(), aISize);
 | 
						||
  mPropertyFlags |= PropertyFlags::HangableWS;
 | 
						||
}
 | 
						||
 | 
						||
nscoord nsTextFrame::GetHangableISize() const {
 | 
						||
  MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) ==
 | 
						||
                 HasProperty(HangableWhitespaceProperty()),
 | 
						||
             "flag/property mismatch!");
 | 
						||
  return (mPropertyFlags & PropertyFlags::HangableWS)
 | 
						||
             ? GetProperty(HangableWhitespaceProperty())
 | 
						||
             : 0;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::ClearHangableISize() {
 | 
						||
  if (mPropertyFlags & PropertyFlags::HangableWS) {
 | 
						||
    RemoveProperty(HangableWhitespaceProperty());
 | 
						||
    mPropertyFlags &= ~PropertyFlags::HangableWS;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) {
 | 
						||
  MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size");
 | 
						||
  if (aTrimmableWS.mAdvance <= 0) {
 | 
						||
    ClearTrimmableWS();
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS);
 | 
						||
  mPropertyFlags |= PropertyFlags::TrimmableWS;
 | 
						||
}
 | 
						||
 | 
						||
gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const {
 | 
						||
  MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) ==
 | 
						||
                 HasProperty(TrimmableWhitespaceProperty()),
 | 
						||
             "flag/property mismatch!");
 | 
						||
  return (mPropertyFlags & PropertyFlags::TrimmableWS)
 | 
						||
             ? GetProperty(TrimmableWhitespaceProperty())
 | 
						||
             : gfxTextRun::TrimmableWS{};
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::ClearTrimmableWS() {
 | 
						||
  if (mPropertyFlags & PropertyFlags::TrimmableWS) {
 | 
						||
    RemoveProperty(TrimmableWhitespaceProperty());
 | 
						||
    mPropertyFlags &= ~PropertyFlags::TrimmableWS;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/* virtual */
 | 
						||
void nsTextFrame::MarkIntrinsicISizesDirty() {
 | 
						||
  ClearTextRuns();
 | 
						||
  nsIFrame::MarkIntrinsicISizesDirty();
 | 
						||
}
 | 
						||
 | 
						||
// XXX this doesn't handle characters shaped by line endings. We need to
 | 
						||
// temporarily override the "current line ending" settings.
 | 
						||
void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext,
 | 
						||
                                           InlineMinISizeData* aData,
 | 
						||
                                           TextRunType aTextRunType) {
 | 
						||
  uint32_t flowEndInTextRun;
 | 
						||
  gfxSkipCharsIterator iter =
 | 
						||
      EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
 | 
						||
                    aData->LineContainer(), aData->mLine, &flowEndInTextRun);
 | 
						||
  gfxTextRun* textRun = GetTextRun(aTextRunType);
 | 
						||
  if (!textRun) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Pass null for the line container. This will disable tab spacing, but that's
 | 
						||
  // OK since we can't really handle tabs for intrinsic sizing anyway.
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
 | 
						||
  // If we're hyphenating, the PropertyProvider needs the actual length;
 | 
						||
  // otherwise we can just pass INT32_MAX to mean "all the text"
 | 
						||
  int32_t len = INT32_MAX;
 | 
						||
  bool hyphenating = frag->GetLength() > 0 &&
 | 
						||
                     (textStyle->mHyphens == StyleHyphens::Auto ||
 | 
						||
                      (textStyle->mHyphens == StyleHyphens::Manual &&
 | 
						||
                       !!(textRun->GetFlags() &
 | 
						||
                          gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
 | 
						||
  if (hyphenating) {
 | 
						||
    gfxSkipCharsIterator tmp(iter);
 | 
						||
    len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
 | 
						||
                            tmp.ConvertSkippedToOriginal(flowEndInTextRun)) -
 | 
						||
          iter.GetOriginalOffset();
 | 
						||
  }
 | 
						||
  PropertyProvider provider(textRun, textStyle, frag, this, iter, len, nullptr,
 | 
						||
                            0, aTextRunType, aData->mAtStartOfLine);
 | 
						||
 | 
						||
  bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
 | 
						||
  bool preformatNewlines = textStyle->NewlineIsSignificant(this);
 | 
						||
  bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
 | 
						||
  bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
 | 
						||
  gfxFloat tabWidth = -1;
 | 
						||
  uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
 | 
						||
                                                    &iter, flowEndInTextRun);
 | 
						||
 | 
						||
  // text-combine-upright frame is constantly 1em on inline-axis.
 | 
						||
  if (Style()->IsTextCombined()) {
 | 
						||
    if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
 | 
						||
      aData->OptionallyBreak();
 | 
						||
    }
 | 
						||
    aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
 | 
						||
    aData->mTrailingWhitespace = 0;
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere &&
 | 
						||
      textStyle->WordCanWrap(this)) {
 | 
						||
    aData->OptionallyBreak();
 | 
						||
    aData->mCurrentLine +=
 | 
						||
        textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
 | 
						||
    aData->mTrailingWhitespace = 0;
 | 
						||
    aData->mAtStartOfLine = false;
 | 
						||
    aData->OptionallyBreak();
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
 | 
						||
  if (hyphenating) {
 | 
						||
    if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
 | 
						||
      provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
 | 
						||
                                    hyphBuffer.Elements());
 | 
						||
    } else {
 | 
						||
      hyphenating = false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
 | 
						||
    bool preformattedNewline = false;
 | 
						||
    bool preformattedTab = false;
 | 
						||
    if (i < flowEndInTextRun) {
 | 
						||
      // XXXldb Shouldn't we be including the newline as part of the
 | 
						||
      // segment that it ends rather than part of the segment that it
 | 
						||
      // starts?
 | 
						||
      preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
 | 
						||
      preformattedTab = preformatTabs && textRun->CharIsTab(i);
 | 
						||
      if (!textRun->CanBreakLineBefore(i) && !preformattedNewline &&
 | 
						||
          !preformattedTab &&
 | 
						||
          (!hyphenating ||
 | 
						||
           !gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start]))) {
 | 
						||
        // we can't break here (and it's not the end of the flow)
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    if (i > wordStart) {
 | 
						||
      nscoord width = NSToCoordCeilClamped(
 | 
						||
          textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
 | 
						||
      width = std::max(0, width);
 | 
						||
      aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
 | 
						||
      aData->mAtStartOfLine = false;
 | 
						||
 | 
						||
      if (collapseWhitespace || whitespaceCanHang) {
 | 
						||
        uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i,
 | 
						||
                                                 &iter, whitespaceCanHang);
 | 
						||
        if (trimStart == start) {
 | 
						||
          // This is *all* trimmable whitespace, so whatever trailingWhitespace
 | 
						||
          // we saw previously is still trailing...
 | 
						||
          aData->mTrailingWhitespace += width;
 | 
						||
        } else {
 | 
						||
          // Some non-whitespace so the old trailingWhitespace is no longer
 | 
						||
          // trailing
 | 
						||
          nscoord wsWidth = NSToCoordCeilClamped(
 | 
						||
              textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
 | 
						||
          aData->mTrailingWhitespace = std::max(0, wsWidth);
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        aData->mTrailingWhitespace = 0;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    if (preformattedTab) {
 | 
						||
      PropertyProvider::Spacing spacing;
 | 
						||
      provider.GetSpacing(Range(i, i + 1), &spacing);
 | 
						||
      aData->mCurrentLine += nscoord(spacing.mBefore);
 | 
						||
      if (tabWidth < 0) {
 | 
						||
        tabWidth = ComputeTabWidthAppUnits(this);
 | 
						||
      }
 | 
						||
      gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
 | 
						||
                                           provider.MinTabAdvance());
 | 
						||
      aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
 | 
						||
      wordStart = i + 1;
 | 
						||
    } else if (i < flowEndInTextRun ||
 | 
						||
               (i == textRun->GetLength() &&
 | 
						||
                (textRun->GetFlags2() &
 | 
						||
                 nsTextFrameUtils::Flags::HasTrailingBreak))) {
 | 
						||
      if (preformattedNewline) {
 | 
						||
        aData->ForceBreak();
 | 
						||
      } else if (i < flowEndInTextRun && hyphenating &&
 | 
						||
                 gfxTextRun::IsOptionalHyphenBreak(hyphBuffer[i - start])) {
 | 
						||
        aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
 | 
						||
      } else {
 | 
						||
        aData->OptionallyBreak();
 | 
						||
      }
 | 
						||
      if (aData->mSkipWhitespace) {
 | 
						||
        iter.SetSkippedOffset(i);
 | 
						||
        wordStart = FindStartAfterSkippingWhitespace(
 | 
						||
            &provider, aData, textStyle, &iter, flowEndInTextRun);
 | 
						||
      } else {
 | 
						||
        wordStart = i;
 | 
						||
      }
 | 
						||
      provider.SetStartOfLine(iter);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (start < flowEndInTextRun) {
 | 
						||
    // Check if we have collapsible whitespace at the end
 | 
						||
    aData->mSkipWhitespace = IsTrimmableSpace(
 | 
						||
        provider.GetFragment(),
 | 
						||
        iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
 | 
						||
  return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::MaybeSplitFramesForFirstLetter() {
 | 
						||
  if (!StaticPrefs::layout_css_intrinsic_size_first_letter_enabled()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (GetParent()->IsFloating() && GetContentLength() > 0) {
 | 
						||
    // We've already claimed our first-letter content, don't try again.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  if (GetPrevContinuation()) {
 | 
						||
    // This isn't the first part of the first-letter.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Find the length of the first-letter. We need a textrun for this; just bail
 | 
						||
  // out if we fail to create it.
 | 
						||
  // But in the floating first-letter case, the text is initially all in our
 | 
						||
  // next-in-flow, and the float itself is empty. So we need to look at that
 | 
						||
  // textrun instead of our own during FindFirstLetterRange.
 | 
						||
  nsTextFrame* f = GetParent()->IsFloating() ? GetNextInFlow() : this;
 | 
						||
  gfxSkipCharsIterator iter = f->EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  const gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated);
 | 
						||
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
  const int32_t length = GetInFlowContentLength();
 | 
						||
  const int32_t offset = GetContentOffset();
 | 
						||
  int32_t firstLetterLength = length;
 | 
						||
  NewlineProperty* cachedNewlineOffset = nullptr;
 | 
						||
  int32_t newLineOffset = -1;  // this will be -1 or a content offset
 | 
						||
  // This will just return -1 if newlines are not significant.
 | 
						||
  int32_t contentNewLineOffset =
 | 
						||
      GetContentNewLineOffset(offset, cachedNewlineOffset);
 | 
						||
  if (contentNewLineOffset < offset + length) {
 | 
						||
    // The new line offset could be outside this frame if the frame has been
 | 
						||
    // split by bidi resolution. In that case we won't use it in this reflow
 | 
						||
    // (newLineOffset will remain -1), but we will still cache it in mContent
 | 
						||
    newLineOffset = contentNewLineOffset;
 | 
						||
    if (newLineOffset >= 0) {
 | 
						||
      firstLetterLength = newLineOffset - offset;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (contentNewLineOffset >= 0 && contentNewLineOffset < offset) {
 | 
						||
    // We're in a first-letter frame's first in flow, so if there
 | 
						||
    // was a first-letter, we'd be it. However, for one reason
 | 
						||
    // or another (e.g., preformatted line break before this text),
 | 
						||
    // we're not actually supposed to have first-letter style. So
 | 
						||
    // just make a zero-length first-letter.
 | 
						||
    firstLetterLength = 0;
 | 
						||
  } else {
 | 
						||
    // We only pass a language code to FindFirstLetterRange if it was
 | 
						||
    // explicit in the content.
 | 
						||
    const nsStyleFont* styleFont = StyleFont();
 | 
						||
    const nsAtom* lang =
 | 
						||
        styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
 | 
						||
    FindFirstLetterRange(frag, lang, textRun, offset, iter, &firstLetterLength);
 | 
						||
    if (newLineOffset >= 0) {
 | 
						||
      // Don't allow a preformatted newline to be part of a first-letter.
 | 
						||
      firstLetterLength = std::min(firstLetterLength, length - 1);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (firstLetterLength) {
 | 
						||
    AddStateBits(TEXT_FIRST_LETTER);
 | 
						||
  }
 | 
						||
 | 
						||
  // Change this frame's length to the first-letter length right now
 | 
						||
  // so that when we rebuild the textrun it will be built with the
 | 
						||
  // right first-letter boundary.
 | 
						||
  SetFirstLetterLength(firstLetterLength);
 | 
						||
}
 | 
						||
 | 
						||
static bool IsUnreflowedLetterFrame(nsIFrame* aFrame) {
 | 
						||
  return aFrame->IsLetterFrame() &&
 | 
						||
         aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW);
 | 
						||
}
 | 
						||
 | 
						||
// XXX Need to do something here to avoid incremental reflow bugs due to
 | 
						||
// first-line changing min-width
 | 
						||
/* virtual */
 | 
						||
void nsTextFrame::AddInlineMinISize(const IntrinsicSizeInput& aInput,
 | 
						||
                                    InlineMinISizeData* aData) {
 | 
						||
  // Check if this textframe belongs to a first-letter frame that has not yet
 | 
						||
  // been reflowed; if so, we need to deal with splitting off a continuation
 | 
						||
  // before we can measure the advance correctly.
 | 
						||
  if (IsUnreflowedLetterFrame(GetParent())) {
 | 
						||
    MaybeSplitFramesForFirstLetter();
 | 
						||
  }
 | 
						||
 | 
						||
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
 | 
						||
  TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
 | 
						||
 | 
						||
  if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
 | 
						||
    // FIXME: Ideally, if we already have a text run, we'd move it to be
 | 
						||
    // the uninflated text run.
 | 
						||
    ClearTextRun(nullptr, nsTextFrame::eInflated);
 | 
						||
    mFontMetrics = nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* f;
 | 
						||
  const gfxTextRun* lastTextRun = nullptr;
 | 
						||
  // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
 | 
						||
  // in the flow are handled right here.
 | 
						||
  for (f = this; f; f = f->GetNextContinuation()) {
 | 
						||
    // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
 | 
						||
    // haven't set up textruns yet for f.  Except in OOM situations,
 | 
						||
    // lastTextRun will only be null for the first text frame.
 | 
						||
    if (f == this || f->GetTextRun(trtype) != lastTextRun) {
 | 
						||
      nsIFrame* lc;
 | 
						||
      if (aData->LineContainer() &&
 | 
						||
          aData->LineContainer() != (lc = f->FindLineContainer())) {
 | 
						||
        NS_ASSERTION(f != this,
 | 
						||
                     "wrong InlineMinISizeData container"
 | 
						||
                     " for first continuation");
 | 
						||
        aData->mLine = nullptr;
 | 
						||
        aData->SetLineContainer(lc);
 | 
						||
      }
 | 
						||
 | 
						||
      // This will process all the text frames that share the same textrun as f.
 | 
						||
      f->AddInlineMinISizeForFlow(aInput.mContext, aData, trtype);
 | 
						||
      lastTextRun = f->GetTextRun(trtype);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// XXX this doesn't handle characters shaped by line endings. We need to
 | 
						||
// temporarily override the "current line ending" settings.
 | 
						||
void nsTextFrame::AddInlinePrefISizeForFlow(gfxContext* aRenderingContext,
 | 
						||
                                            InlinePrefISizeData* aData,
 | 
						||
                                            TextRunType aTextRunType) {
 | 
						||
  if (IsUnreflowedLetterFrame(GetParent())) {
 | 
						||
    MaybeSplitFramesForFirstLetter();
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t flowEndInTextRun;
 | 
						||
  gfxSkipCharsIterator iter =
 | 
						||
      EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
 | 
						||
                    aData->LineContainer(), aData->mLine, &flowEndInTextRun);
 | 
						||
  gfxTextRun* textRun = GetTextRun(aTextRunType);
 | 
						||
  if (!textRun) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // Pass null for the line container. This will disable tab spacing, but that's
 | 
						||
  // OK since we can't really handle tabs for intrinsic sizing anyway.
 | 
						||
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
  PropertyProvider provider(textRun, textStyle, frag, this, iter, INT32_MAX,
 | 
						||
                            nullptr, 0, aTextRunType, aData->mLineIsEmpty);
 | 
						||
 | 
						||
  // text-combine-upright frame is constantly 1em on inline-axis.
 | 
						||
  if (Style()->IsTextCombined()) {
 | 
						||
    aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
 | 
						||
    aData->mTrailingWhitespace = 0;
 | 
						||
    aData->mLineIsEmpty = false;
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
 | 
						||
  bool preformatNewlines = textStyle->NewlineIsSignificant(this);
 | 
						||
  bool preformatTabs = textStyle->TabIsSignificant();
 | 
						||
  gfxFloat tabWidth = -1;
 | 
						||
  uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
 | 
						||
                                                    &iter, flowEndInTextRun);
 | 
						||
  if (aData->mLineIsEmpty) {
 | 
						||
    provider.SetStartOfLine(iter);
 | 
						||
  }
 | 
						||
 | 
						||
  // XXX Should we consider hyphenation here?
 | 
						||
  // If newlines and tabs aren't preformatted, nothing to do inside
 | 
						||
  // the loop so make i skip to the end
 | 
						||
  uint32_t loopStart =
 | 
						||
      (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
 | 
						||
  for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
 | 
						||
    bool preformattedNewline = false;
 | 
						||
    bool preformattedTab = false;
 | 
						||
    if (i < flowEndInTextRun) {
 | 
						||
      // XXXldb Shouldn't we be including the newline as part of the
 | 
						||
      // segment that it ends rather than part of the segment that it
 | 
						||
      // starts?
 | 
						||
      NS_ASSERTION(preformatNewlines || preformatTabs,
 | 
						||
                   "We can't be here unless newlines are "
 | 
						||
                   "hard breaks or there are tabs");
 | 
						||
      preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
 | 
						||
      preformattedTab = preformatTabs && textRun->CharIsTab(i);
 | 
						||
      if (!preformattedNewline && !preformattedTab) {
 | 
						||
        // we needn't break here (and it's not the end of the flow)
 | 
						||
        continue;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    if (i > lineStart) {
 | 
						||
      nscoord width = NSToCoordCeilClamped(
 | 
						||
          textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
 | 
						||
      width = std::max(0, width);
 | 
						||
      aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
 | 
						||
      aData->mLineIsEmpty = false;
 | 
						||
 | 
						||
      if (collapseWhitespace) {
 | 
						||
        uint32_t trimStart =
 | 
						||
            GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
 | 
						||
        if (trimStart == start) {
 | 
						||
          // This is *all* trimmable whitespace, so whatever trailingWhitespace
 | 
						||
          // we saw previously is still trailing...
 | 
						||
          aData->mTrailingWhitespace += width;
 | 
						||
        } else {
 | 
						||
          // Some non-whitespace so the old trailingWhitespace is no longer
 | 
						||
          // trailing
 | 
						||
          nscoord wsWidth = NSToCoordCeilClamped(
 | 
						||
              textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
 | 
						||
          aData->mTrailingWhitespace = std::max(0, wsWidth);
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        aData->mTrailingWhitespace = 0;
 | 
						||
      }
 | 
						||
    }
 | 
						||
 | 
						||
    if (preformattedTab) {
 | 
						||
      PropertyProvider::Spacing spacing;
 | 
						||
      provider.GetSpacing(Range(i, i + 1), &spacing);
 | 
						||
      aData->mCurrentLine += nscoord(spacing.mBefore);
 | 
						||
      if (tabWidth < 0) {
 | 
						||
        tabWidth = ComputeTabWidthAppUnits(this);
 | 
						||
      }
 | 
						||
      gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
 | 
						||
                                           provider.MinTabAdvance());
 | 
						||
      aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
 | 
						||
      aData->mLineIsEmpty = false;
 | 
						||
      lineStart = i + 1;
 | 
						||
    } else if (preformattedNewline) {
 | 
						||
      aData->ForceBreak();
 | 
						||
      lineStart = i;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Check if we have collapsible whitespace at the end
 | 
						||
  if (start < flowEndInTextRun) {
 | 
						||
    aData->mSkipWhitespace = IsTrimmableSpace(
 | 
						||
        provider.GetFragment(),
 | 
						||
        iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
// XXX Need to do something here to avoid incremental reflow bugs due to
 | 
						||
// first-line and first-letter changing pref-width
 | 
						||
/* virtual */
 | 
						||
void nsTextFrame::AddInlinePrefISize(const IntrinsicSizeInput& aInput,
 | 
						||
                                     InlinePrefISizeData* aData) {
 | 
						||
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
 | 
						||
  TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
 | 
						||
 | 
						||
  if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
 | 
						||
    // FIXME: Ideally, if we already have a text run, we'd move it to be
 | 
						||
    // the uninflated text run.
 | 
						||
    ClearTextRun(nullptr, nsTextFrame::eInflated);
 | 
						||
    mFontMetrics = nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  nsTextFrame* f;
 | 
						||
  const gfxTextRun* lastTextRun = nullptr;
 | 
						||
  // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
 | 
						||
  // in the flow are handled right here.
 | 
						||
  for (f = this; f; f = f->GetNextContinuation()) {
 | 
						||
    // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
 | 
						||
    // haven't set up textruns yet for f.  Except in OOM situations,
 | 
						||
    // lastTextRun will only be null for the first text frame.
 | 
						||
    if (f == this || f->GetTextRun(trtype) != lastTextRun) {
 | 
						||
      nsIFrame* lc;
 | 
						||
      if (aData->LineContainer() &&
 | 
						||
          aData->LineContainer() != (lc = f->FindLineContainer())) {
 | 
						||
        NS_ASSERTION(f != this,
 | 
						||
                     "wrong InlinePrefISizeData container"
 | 
						||
                     " for first continuation");
 | 
						||
        aData->mLine = nullptr;
 | 
						||
        aData->SetLineContainer(lc);
 | 
						||
      }
 | 
						||
 | 
						||
      // This will process all the text frames that share the same textrun as f.
 | 
						||
      f->AddInlinePrefISizeForFlow(aInput.mContext, aData, trtype);
 | 
						||
      lastTextRun = f->GetTextRun(trtype);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
/* virtual */
 | 
						||
nsIFrame::SizeComputationResult nsTextFrame::ComputeSize(
 | 
						||
    gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
 | 
						||
    nscoord aAvailableISize, const LogicalSize& aMargin,
 | 
						||
    const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
 | 
						||
    ComputeSizeFlags aFlags) {
 | 
						||
  // Inlines and text don't compute size before reflow.
 | 
						||
  return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
 | 
						||
          AspectRatioUsage::None};
 | 
						||
}
 | 
						||
 | 
						||
static nsRect RoundOut(const gfxRect& aRect) {
 | 
						||
  nsRect r;
 | 
						||
  r.x = NSToCoordFloor(aRect.X());
 | 
						||
  r.y = NSToCoordFloor(aRect.Y());
 | 
						||
  r.width = NSToCoordCeil(aRect.XMost()) - r.x;
 | 
						||
  r.height = NSToCoordCeil(aRect.YMost()) - r.y;
 | 
						||
  return r;
 | 
						||
}
 | 
						||
 | 
						||
nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
 | 
						||
  if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
 | 
						||
    // This is conservative, but OK.
 | 
						||
    return InkOverflowRect();
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter =
 | 
						||
      const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return nsRect();
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
 | 
						||
                            nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  // Trim trailing whitespace
 | 
						||
  provider.InitializeForDisplay(true);
 | 
						||
 | 
						||
  gfxTextRun::Metrics metrics = mTextRun->MeasureText(
 | 
						||
      ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
 | 
						||
      aDrawTarget, &provider);
 | 
						||
  if (GetWritingMode().IsLineInverted()) {
 | 
						||
    metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
 | 
						||
  }
 | 
						||
  // mAscent should be the same as metrics.mAscent, but it's what we use to
 | 
						||
  // paint so that's the one we'll use.
 | 
						||
  nsRect boundingBox = RoundOut(metrics.mBoundingBox);
 | 
						||
  boundingBox += nsPoint(0, mAscent);
 | 
						||
  if (mTextRun->IsVertical()) {
 | 
						||
    // Swap line-relative textMetrics dimensions to physical coordinates.
 | 
						||
    std::swap(boundingBox.x, boundingBox.y);
 | 
						||
    std::swap(boundingBox.width, boundingBox.height);
 | 
						||
  }
 | 
						||
  return boundingBox;
 | 
						||
}
 | 
						||
 | 
						||
/* virtual */
 | 
						||
nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
 | 
						||
                                              nscoord* aXMost) {
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return NS_ERROR_FAILURE;
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  provider.InitializeForMeasure();
 | 
						||
 | 
						||
  gfxTextRun::Metrics metrics = mTextRun->MeasureText(
 | 
						||
      ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
 | 
						||
      aContext->GetDrawTarget(), &provider);
 | 
						||
  // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
 | 
						||
  *aX = NSToCoordFloor(metrics.mBoundingBox.x);
 | 
						||
  *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
 | 
						||
 | 
						||
  return NS_OK;
 | 
						||
}
 | 
						||
 | 
						||
static bool HasSoftHyphenBefore(const nsTextFragment* aFrag,
 | 
						||
                                const gfxTextRun* aTextRun,
 | 
						||
                                int32_t aStartOffset,
 | 
						||
                                const gfxSkipCharsIterator& aIter) {
 | 
						||
  if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
 | 
						||
      aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
  if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  gfxSkipCharsIterator iter = aIter;
 | 
						||
  while (iter.GetOriginalOffset() > aStartOffset) {
 | 
						||
    iter.AdvanceOriginal(-1);
 | 
						||
    if (!iter.IsOriginalCharSkipped()) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
    if (aFrag->CharAt(AssertedCast<uint32_t>(iter.GetOriginalOffset())) ==
 | 
						||
        CH_SHY) {
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return false;
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
 | 
						||
 * because their text has all been taken and reflowed by earlier frames.
 | 
						||
 */
 | 
						||
static void RemoveEmptyInFlows(nsTextFrame* aFrame,
 | 
						||
                               nsTextFrame* aFirstToNotRemove) {
 | 
						||
  MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly");
 | 
						||
  // We have to be careful here, because some RemoveFrame implementations
 | 
						||
  // remove and destroy not only the passed-in frame but also all its following
 | 
						||
  // in-flows (and sometimes all its following continuations in general).  So
 | 
						||
  // we remove |f| and everything up to but not including firstToNotRemove from
 | 
						||
  // the flow first, to make sure that only the things we want destroyed are
 | 
						||
  // destroyed.
 | 
						||
 | 
						||
  // This sadly duplicates some of the logic from
 | 
						||
  // nsSplittableFrame::RemoveFromFlow.  We can get away with not duplicating
 | 
						||
  // all of it, because we know that the prev-continuation links of
 | 
						||
  // firstToNotRemove and f are fluid, and non-null.
 | 
						||
  NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
 | 
						||
                       aFirstToNotRemove->GetPrevInFlow() &&
 | 
						||
                   aFirstToNotRemove->GetPrevInFlow() != nullptr,
 | 
						||
               "aFirstToNotRemove should have a fluid prev continuation");
 | 
						||
  NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() &&
 | 
						||
                   aFrame->GetPrevInFlow() != nullptr,
 | 
						||
               "aFrame should have a fluid prev continuation");
 | 
						||
 | 
						||
  nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
 | 
						||
  nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
 | 
						||
 | 
						||
  for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
 | 
						||
       f = f->GetNextContinuation()) {
 | 
						||
    // f is going to be destroyed soon, after it is unlinked from the
 | 
						||
    // continuation chain. If its textrun is going to be destroyed we need to
 | 
						||
    // do it now, before we unlink the frames to remove from the flow,
 | 
						||
    // because Destroy calls ClearTextRuns() and that will start at the
 | 
						||
    // first frame with the text run and walk the continuations.
 | 
						||
    if (f->IsInTextRunUserData()) {
 | 
						||
      f->ClearTextRuns();
 | 
						||
    } else {
 | 
						||
      f->DisconnectTextRuns();
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  prevContinuation->SetNextInFlow(aFirstToNotRemove);
 | 
						||
  aFirstToNotRemove->SetPrevInFlow(prevContinuation);
 | 
						||
 | 
						||
  // **Note: it is important here that we clear the Next link from lastRemoved
 | 
						||
  // BEFORE clearing the Prev link from aFrame, because SetPrevInFlow() will
 | 
						||
  // follow the Next pointers, wiping out the cached mFirstContinuation field
 | 
						||
  // from each following frame in the list. We need this to stop when it
 | 
						||
  // reaches lastRemoved!
 | 
						||
  lastRemoved->SetNextInFlow(nullptr);
 | 
						||
  aFrame->SetPrevInFlow(nullptr);
 | 
						||
 | 
						||
  nsContainerFrame* parent = aFrame->GetParent();
 | 
						||
  nsIFrame::DestroyContext context(aFrame->PresShell());
 | 
						||
  nsBlockFrame* parentBlock = do_QueryFrame(parent);
 | 
						||
  if (parentBlock) {
 | 
						||
    // Manually call DoRemoveFrame so we can tell it that we're
 | 
						||
    // removing empty frames; this will keep it from blowing away
 | 
						||
    // text runs.
 | 
						||
    parentBlock->DoRemoveFrame(context, aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
 | 
						||
  } else {
 | 
						||
    // Just remove it normally; use FrameChildListID::NoReflowPrincipal to avoid
 | 
						||
    // posting new reflows.
 | 
						||
    parent->RemoveFrame(context, FrameChildListID::NoReflowPrincipal, aFrame);
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
 | 
						||
                            uint32_t aSetLengthFlags) {
 | 
						||
  mContentLengthHint = aLength;
 | 
						||
  int32_t end = GetContentOffset() + aLength;
 | 
						||
  nsTextFrame* f = GetNextInFlow();
 | 
						||
  if (!f) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  // If our end offset is moving, then even if frames are not being pushed or
 | 
						||
  // pulled, content is moving to or from the next line and the next line
 | 
						||
  // must be reflowed.
 | 
						||
  // If the next-continuation is dirty, then we should dirty the next line now
 | 
						||
  // because we may have skipped doing it if we dirtied it in
 | 
						||
  // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
 | 
						||
  // and ChildIsDirty to handle a range of frames would be worse.
 | 
						||
  if (aLineLayout &&
 | 
						||
      (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
 | 
						||
    aLineLayout->SetDirtyNextLine();
 | 
						||
  }
 | 
						||
 | 
						||
  if (end < f->mContentOffset) {
 | 
						||
    // Our frame is shrinking. Give the text to our next in flow.
 | 
						||
    if (aLineLayout && HasSignificantTerminalNewline() &&
 | 
						||
        !GetParent()->IsLetterFrame() &&
 | 
						||
        (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
 | 
						||
      // Whatever text we hand to our next-in-flow will end up in a frame all of
 | 
						||
      // its own, since it ends in a forced linebreak.  Might as well just put
 | 
						||
      // it in a separate frame now.  This is important to prevent text run
 | 
						||
      // churn; if we did not do that, then we'd likely end up rebuilding
 | 
						||
      // textruns for all our following continuations.
 | 
						||
      // We skip this optimization when the parent is a first-letter frame
 | 
						||
      // because it doesn't deal well with more than one child frame.
 | 
						||
      // We also skip this optimization if we were called during bidi
 | 
						||
      // resolution, so as not to create a new frame which doesn't appear in
 | 
						||
      // the bidi resolver's list of frames
 | 
						||
      nsIFrame* newFrame =
 | 
						||
          PresShell()->FrameConstructor()->CreateContinuingFrame(this,
 | 
						||
                                                                 GetParent());
 | 
						||
      nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
 | 
						||
      GetParent()->InsertFrames(FrameChildListID::NoReflowPrincipal, this,
 | 
						||
                                aLineLayout->GetLine(),
 | 
						||
                                nsFrameList(next, next));
 | 
						||
      f = next;
 | 
						||
    }
 | 
						||
 | 
						||
    f->mContentOffset = end;
 | 
						||
    if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
 | 
						||
      ClearTextRuns();
 | 
						||
      f->ClearTextRuns();
 | 
						||
    }
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  // Our frame is growing. Take text from our in-flow(s).
 | 
						||
  // We can take text from frames in lines beyond just the next line.
 | 
						||
  // We don't dirty those lines. That's OK, because when we reflow
 | 
						||
  // our empty next-in-flow, it will take text from its next-in-flow and
 | 
						||
  // dirty that line.
 | 
						||
 | 
						||
  // Note that in the process we may end up removing some frames from
 | 
						||
  // the flow if they end up empty.
 | 
						||
  nsTextFrame* framesToRemove = nullptr;
 | 
						||
  while (f && f->mContentOffset < end) {
 | 
						||
    f->mContentOffset = end;
 | 
						||
    if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
 | 
						||
      ClearTextRuns();
 | 
						||
      f->ClearTextRuns();
 | 
						||
    }
 | 
						||
    nsTextFrame* next = f->GetNextInFlow();
 | 
						||
    // Note: the "f->GetNextSibling() == next" check below is to restrict
 | 
						||
    // this optimization to the case where they are on the same child list.
 | 
						||
    // Otherwise we might remove the only child of a nsFirstLetterFrame
 | 
						||
    // for example and it can't handle that.  See bug 597627 for details.
 | 
						||
    if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
 | 
						||
        (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
 | 
						||
      // |f| is now empty.  We may as well remove it, instead of copying all
 | 
						||
      // the text from |next| into it instead; the latter leads to use
 | 
						||
      // rebuilding textruns for all following continuations.
 | 
						||
      // We skip this optimization if we were called during bidi resolution,
 | 
						||
      // since the bidi resolver may try to handle the destroyed frame later
 | 
						||
      // and crash
 | 
						||
      if (!framesToRemove) {
 | 
						||
        // Remember that we have to remove this frame.
 | 
						||
        framesToRemove = f;
 | 
						||
      }
 | 
						||
    } else if (framesToRemove) {
 | 
						||
      RemoveEmptyInFlows(framesToRemove, f);
 | 
						||
      framesToRemove = nullptr;
 | 
						||
    }
 | 
						||
    f = next;
 | 
						||
  }
 | 
						||
 | 
						||
  MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
 | 
						||
             "How did we exit the loop if we null out framesToRemove if "
 | 
						||
             "!next || next->mContentOffset > end ?");
 | 
						||
 | 
						||
  if (framesToRemove) {
 | 
						||
    // We are guaranteed that we exited the loop with f not null, per the
 | 
						||
    // postcondition above
 | 
						||
    RemoveEmptyInFlows(framesToRemove, f);
 | 
						||
  }
 | 
						||
 | 
						||
#ifdef DEBUG
 | 
						||
  f = this;
 | 
						||
  int32_t iterations = 0;
 | 
						||
  while (f && iterations < 10) {
 | 
						||
    f->GetContentLength();  // Assert if negative length
 | 
						||
    f = f->GetNextContinuation();
 | 
						||
    ++iterations;
 | 
						||
  }
 | 
						||
  f = this;
 | 
						||
  iterations = 0;
 | 
						||
  while (f && iterations < 10) {
 | 
						||
    f->GetContentLength();  // Assert if negative length
 | 
						||
    f = f->GetPrevContinuation();
 | 
						||
    ++iterations;
 | 
						||
  }
 | 
						||
#endif
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::SetFirstLetterLength(int32_t aLength) {
 | 
						||
  if (aLength == GetContentLength()) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  mContentLengthHint = aLength;
 | 
						||
  nsTextFrame* next = static_cast<nsTextFrame*>(GetNextInFlow());
 | 
						||
  if (!aLength && !next) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  if (aLength > GetContentLength()) {
 | 
						||
    // Stealing some text from our next-in-flow; this happens with floating
 | 
						||
    // first-letter, which is initially given a zero-length range, with all
 | 
						||
    // the text being in its continuation.
 | 
						||
    if (!next) {
 | 
						||
      MOZ_ASSERT_UNREACHABLE("Expected a next-in-flow; first-letter broken?");
 | 
						||
      return;
 | 
						||
    }
 | 
						||
  } else if (!next) {
 | 
						||
    // We need to create a continuation for the parent first-letter frame,
 | 
						||
    // and move any kids after this frame to the new one; if there are none,
 | 
						||
    // a new continuing text frame will be created there.
 | 
						||
    MOZ_ASSERT(GetParent()->IsLetterFrame());
 | 
						||
    auto* letterFrame = static_cast<nsFirstLetterFrame*>(GetParent());
 | 
						||
    next = letterFrame->CreateContinuationForFramesAfter(this);
 | 
						||
  }
 | 
						||
 | 
						||
  next->mContentOffset = GetContentOffset() + aLength;
 | 
						||
 | 
						||
  ClearTextRuns();
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::IsFloatingFirstLetterChild() const {
 | 
						||
  nsIFrame* frame = GetParent();
 | 
						||
  return frame && frame->IsFloating() && frame->IsLetterFrame();
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::IsInitialLetterChild() const {
 | 
						||
  nsIFrame* frame = GetParent();
 | 
						||
  return frame && frame->StyleTextReset()->mInitialLetter.size != 0.0f &&
 | 
						||
         frame->IsLetterFrame();
 | 
						||
}
 | 
						||
 | 
						||
struct nsTextFrame::NewlineProperty {
 | 
						||
  int32_t mStartOffset;
 | 
						||
  // The offset of the first \n after mStartOffset, or -1 if there is none
 | 
						||
  int32_t mNewlineOffset;
 | 
						||
};
 | 
						||
 | 
						||
int32_t nsTextFrame::GetContentNewLineOffset(
 | 
						||
    int32_t aOffset, NewlineProperty*& aCachedNewlineOffset) {
 | 
						||
  int32_t contentNewLineOffset = -1;  // this will be -1 or a content offset
 | 
						||
  if (StyleText()->NewlineIsSignificant(this)) {
 | 
						||
    // Pointer to the nsGkAtoms::newline set on this frame's element
 | 
						||
    aCachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
 | 
						||
                               ? static_cast<NewlineProperty*>(
 | 
						||
                                     mContent->GetProperty(nsGkAtoms::newline))
 | 
						||
                               : nullptr;
 | 
						||
    if (aCachedNewlineOffset && aCachedNewlineOffset->mStartOffset <= aOffset &&
 | 
						||
        (aCachedNewlineOffset->mNewlineOffset == -1 ||
 | 
						||
         aCachedNewlineOffset->mNewlineOffset >= aOffset)) {
 | 
						||
      contentNewLineOffset = aCachedNewlineOffset->mNewlineOffset;
 | 
						||
    } else {
 | 
						||
      contentNewLineOffset = FindChar(
 | 
						||
          TextFragment(), aOffset, GetContent()->TextLength() - aOffset, '\n');
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  return contentNewLineOffset;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
 | 
						||
                         const ReflowInput& aReflowInput,
 | 
						||
                         nsReflowStatus& aStatus) {
 | 
						||
  MarkInReflow();
 | 
						||
  DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
 | 
						||
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | 
						||
 | 
						||
  InvalidateSelectionState();
 | 
						||
 | 
						||
  // XXX If there's no line layout, we shouldn't even have created this
 | 
						||
  // frame. This may happen if, for example, this is text inside a table
 | 
						||
  // but not inside a cell. For now, just don't reflow.
 | 
						||
  if (!aReflowInput.mLineLayout) {
 | 
						||
    ClearMetrics(aMetrics);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
 | 
						||
             aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics,
 | 
						||
             aStatus);
 | 
						||
}
 | 
						||
 | 
						||
#ifdef ACCESSIBILITY
 | 
						||
/**
 | 
						||
 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
 | 
						||
 */
 | 
						||
class MOZ_STACK_CLASS ReflowTextA11yNotifier {
 | 
						||
 public:
 | 
						||
  ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent)
 | 
						||
      : mContent(aContent), mPresContext(aPresContext) {}
 | 
						||
  ~ReflowTextA11yNotifier() {
 | 
						||
    if (nsAccessibilityService* accService = GetAccService()) {
 | 
						||
      accService->UpdateText(mPresContext->PresShell(), mContent);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
 private:
 | 
						||
  ReflowTextA11yNotifier();
 | 
						||
  ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
 | 
						||
  ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&);
 | 
						||
 | 
						||
  nsIContent* mContent;
 | 
						||
  nsPresContext* mPresContext;
 | 
						||
};
 | 
						||
#endif
 | 
						||
 | 
						||
void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
 | 
						||
                             DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
 | 
						||
                             nsReflowStatus& aStatus) {
 | 
						||
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
 | 
						||
 | 
						||
#ifdef NOISY_REFLOW
 | 
						||
  ListTag(stdout);
 | 
						||
  printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
 | 
						||
#endif
 | 
						||
 | 
						||
  nsPresContext* presContext = PresContext();
 | 
						||
 | 
						||
#ifdef ACCESSIBILITY
 | 
						||
  // Schedule the update of accessible tree since rendered text might be
 | 
						||
  // changed.
 | 
						||
  if (StyleVisibility()->IsVisible()) {
 | 
						||
    ReflowTextA11yNotifier(presContext, mContent);
 | 
						||
  }
 | 
						||
#endif
 | 
						||
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
  // Set up flags and clear out state
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
 | 
						||
  // Clear out the reflow input flags in mState. We also clear the whitespace
 | 
						||
  // flags because this can change whether the frame maps whitespace-only text
 | 
						||
  // or not. We also clear the flag that tracks whether we had a pending
 | 
						||
  // reflow request from CharacterDataChanged (since we're reflowing now).
 | 
						||
  RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
 | 
						||
  mReflowRequestedForCharDataChange = false;
 | 
						||
  RemoveProperty(WebRenderTextBounds());
 | 
						||
 | 
						||
  // Discard cached continuations array that will be invalidated by the reflow.
 | 
						||
  if (nsTextFrame* first = FirstContinuation()) {
 | 
						||
    first->ClearCachedContinuations();
 | 
						||
  }
 | 
						||
 | 
						||
  // Temporarily map all possible content while we construct our new textrun.
 | 
						||
  // so that when doing reflow our styles prevail over any part of the
 | 
						||
  // textrun we look at. Note that next-in-flows may be mapping the same
 | 
						||
  // content; gfxTextRun construction logic will ensure that we take priority.
 | 
						||
  int32_t maxContentLength = GetInFlowContentLength();
 | 
						||
 | 
						||
  InvalidateSelectionState();
 | 
						||
 | 
						||
  // We don't need to reflow if there is no content.
 | 
						||
  if (!maxContentLength) {
 | 
						||
    ClearMetrics(aMetrics);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
#ifdef NOISY_BIDI
 | 
						||
  printf("Reflowed textframe\n");
 | 
						||
#endif
 | 
						||
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
 | 
						||
  bool atStartOfLine = aLineLayout.LineAtStart();
 | 
						||
  if (atStartOfLine) {
 | 
						||
    AddStateBits(TEXT_START_OF_LINE);
 | 
						||
  }
 | 
						||
 | 
						||
  uint32_t flowEndInTextRun;
 | 
						||
  nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
 | 
						||
  // DOM offsets of the text range we need to measure, after trimming
 | 
						||
  // whitespace, restricting to first-letter, and restricting preformatted text
 | 
						||
  // to nearest newline
 | 
						||
  int32_t length = maxContentLength;
 | 
						||
  int32_t offset = GetContentOffset();
 | 
						||
 | 
						||
  // Restrict preformatted text to the nearest newline
 | 
						||
  NewlineProperty* cachedNewlineOffset = nullptr;
 | 
						||
  int32_t newLineOffset = -1;  // this will be -1 or a content offset
 | 
						||
  // This will just return -1 if newlines are not significant.
 | 
						||
  int32_t contentNewLineOffset =
 | 
						||
      GetContentNewLineOffset(offset, cachedNewlineOffset);
 | 
						||
  if (contentNewLineOffset < offset + length) {
 | 
						||
    // The new line offset could be outside this frame if the frame has been
 | 
						||
    // split by bidi resolution. In that case we won't use it in this reflow
 | 
						||
    // (newLineOffset will remain -1), but we will still cache it in mContent
 | 
						||
    newLineOffset = contentNewLineOffset;
 | 
						||
  }
 | 
						||
  if (newLineOffset >= 0) {
 | 
						||
    length = newLineOffset + 1 - offset;
 | 
						||
  }
 | 
						||
 | 
						||
  if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
 | 
						||
      HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
 | 
						||
    // Skip leading whitespace. Make sure we don't skip a 'pre-line'
 | 
						||
    // newline if there is one.
 | 
						||
    int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
 | 
						||
    int32_t whitespaceCount =
 | 
						||
        GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
 | 
						||
    if (whitespaceCount) {
 | 
						||
      offset += whitespaceCount;
 | 
						||
      length -= whitespaceCount;
 | 
						||
      // Make sure this frame maps the trimmable whitespace.
 | 
						||
      if (MOZ_UNLIKELY(offset > GetContentEnd())) {
 | 
						||
        SetLength(offset - GetContentOffset(), &aLineLayout,
 | 
						||
                  ALLOW_FRAME_CREATION_AND_DESTRUCTION);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // If trimming whitespace left us with nothing to do, return early.
 | 
						||
  if (length == 0) {
 | 
						||
    ClearMetrics(aMetrics);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  bool completedFirstLetter = false;
 | 
						||
  // Layout dependent styles are a problem because we need to reconstruct
 | 
						||
  // the gfxTextRun based on our layout.
 | 
						||
  if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
 | 
						||
    SetLength(maxContentLength, &aLineLayout,
 | 
						||
              ALLOW_FRAME_CREATION_AND_DESTRUCTION);
 | 
						||
 | 
						||
    if (aLineLayout.GetInFirstLetter()) {
 | 
						||
      // floating first-letter boundaries are significant in textrun
 | 
						||
      // construction, so clear the textrun out every time we hit a first-letter
 | 
						||
      // and have changed our length (which controls the first-letter boundary)
 | 
						||
      ClearTextRuns();
 | 
						||
      // Find the length of the first-letter. We need a textrun for this.
 | 
						||
      // REVIEW: maybe-bogus inflation should be ok (fixed below)
 | 
						||
      gfxSkipCharsIterator iter =
 | 
						||
          EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
 | 
						||
                        aLineLayout.GetLine(), &flowEndInTextRun);
 | 
						||
 | 
						||
      if (mTextRun) {
 | 
						||
        int32_t firstLetterLength = length;
 | 
						||
        if (aLineLayout.GetFirstLetterStyleOK()) {
 | 
						||
          // We only pass a language code to FindFirstLetterRange if it was
 | 
						||
          // explicit in the content.
 | 
						||
          const nsStyleFont* styleFont = StyleFont();
 | 
						||
          const nsAtom* lang = styleFont->mExplicitLanguage
 | 
						||
                                   ? styleFont->mLanguage.get()
 | 
						||
                                   : nullptr;
 | 
						||
          completedFirstLetter = FindFirstLetterRange(
 | 
						||
              frag, lang, mTextRun, offset, iter, &firstLetterLength);
 | 
						||
          if (newLineOffset >= 0) {
 | 
						||
            // Don't allow a preformatted newline to be part of a first-letter.
 | 
						||
            firstLetterLength = std::min(firstLetterLength, length - 1);
 | 
						||
            if (length == 1) {
 | 
						||
              // There is no text to be consumed by the first-letter before the
 | 
						||
              // preformatted newline. Note that the first letter is therefore
 | 
						||
              // complete (FindFirstLetterRange will have returned false).
 | 
						||
              completedFirstLetter = true;
 | 
						||
            }
 | 
						||
          }
 | 
						||
        } else {
 | 
						||
          // We're in a first-letter frame's first in flow, so if there
 | 
						||
          // was a first-letter, we'd be it. However, for one reason
 | 
						||
          // or another (e.g., preformatted line break before this text),
 | 
						||
          // we're not actually supposed to have first-letter style. So
 | 
						||
          // just make a zero-length first-letter.
 | 
						||
          firstLetterLength = 0;
 | 
						||
          completedFirstLetter = true;
 | 
						||
        }
 | 
						||
        length = firstLetterLength;
 | 
						||
        if (length) {
 | 
						||
          AddStateBits(TEXT_FIRST_LETTER);
 | 
						||
        }
 | 
						||
        // Change this frame's length to the first-letter length right now
 | 
						||
        // so that when we rebuild the textrun it will be built with the
 | 
						||
        // right first-letter boundary
 | 
						||
        SetLength(offset + length - GetContentOffset(), &aLineLayout,
 | 
						||
                  ALLOW_FRAME_CREATION_AND_DESTRUCTION);
 | 
						||
        // Ensure that the textrun will be rebuilt
 | 
						||
        ClearTextRuns();
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
 | 
						||
 | 
						||
  if (!IsCurrentFontInflation(fontSizeInflation)) {
 | 
						||
    // FIXME: Ideally, if we already have a text run, we'd move it to be
 | 
						||
    // the uninflated text run.
 | 
						||
    ClearTextRun(nullptr, nsTextFrame::eInflated);
 | 
						||
    mFontMetrics = nullptr;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter =
 | 
						||
      EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
 | 
						||
                    aLineLayout.GetLine(), &flowEndInTextRun);
 | 
						||
 | 
						||
  NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
 | 
						||
               "EnsureTextRun should have set font size inflation");
 | 
						||
 | 
						||
  if (mTextRun && iter.GetOriginalEnd() < offset + length) {
 | 
						||
    // The textrun does not map enough text for this frame. This can happen
 | 
						||
    // when the textrun was ended in the middle of a text node because a
 | 
						||
    // preformatted newline was encountered, and prev-in-flow frames have
 | 
						||
    // consumed all the text of the textrun. We need a new textrun.
 | 
						||
    ClearTextRuns();
 | 
						||
    iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
 | 
						||
                         aLineLayout.GetLine(), &flowEndInTextRun);
 | 
						||
  }
 | 
						||
 | 
						||
  if (!mTextRun) {
 | 
						||
    ClearMetrics(aMetrics);
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(
 | 
						||
                   offset + length) <= mTextRun->GetLength(),
 | 
						||
               "Text run does not map enough text for our reflow");
 | 
						||
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
  // See how much text should belong to this text frame, and measure it
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
 | 
						||
  iter.SetOriginalOffset(offset);
 | 
						||
  nscoord xOffsetForTabs =
 | 
						||
      (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)
 | 
						||
          ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
 | 
						||
             lineContainer->GetUsedBorderAndPadding().left)
 | 
						||
          : -1;
 | 
						||
  PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
 | 
						||
                            lineContainer, xOffsetForTabs,
 | 
						||
                            nsTextFrame::eInflated,
 | 
						||
                            HasAnyStateBits(TEXT_START_OF_LINE));
 | 
						||
 | 
						||
  uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
 | 
						||
 | 
						||
  gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS;
 | 
						||
  if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) {
 | 
						||
    if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) {
 | 
						||
      if (firstLetter->UseTightBounds()) {
 | 
						||
        boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS;
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t limitLength = length;
 | 
						||
  int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
 | 
						||
  bool forceBreakAfter = false;
 | 
						||
  if (forceBreak >= length) {
 | 
						||
    forceBreakAfter = forceBreak == length;
 | 
						||
    // The break is not within the text considered for this textframe.
 | 
						||
    forceBreak = -1;
 | 
						||
  }
 | 
						||
  if (forceBreak >= 0) {
 | 
						||
    limitLength = forceBreak;
 | 
						||
  }
 | 
						||
  // This is the heart of text reflow right here! We don't know where
 | 
						||
  // to break, so we need to see how much text fits in the available width.
 | 
						||
  uint32_t transformedLength;
 | 
						||
  if (offset + limitLength >= int32_t(frag->GetLength())) {
 | 
						||
    NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
 | 
						||
                 "Content offset/length out of bounds");
 | 
						||
    NS_ASSERTION(flowEndInTextRun >= transformedOffset,
 | 
						||
                 "Negative flow length?");
 | 
						||
    transformedLength = flowEndInTextRun - transformedOffset;
 | 
						||
  } else {
 | 
						||
    // we're not looking at all the content, so we need to compute the
 | 
						||
    // length of the transformed substring we're looking at
 | 
						||
    gfxSkipCharsIterator iter(provider.GetStart());
 | 
						||
    iter.SetOriginalOffset(offset + limitLength);
 | 
						||
    transformedLength = iter.GetSkippedOffset() - transformedOffset;
 | 
						||
  }
 | 
						||
  gfxTextRun::Metrics textMetrics;
 | 
						||
  uint32_t transformedLastBreak = 0;
 | 
						||
  bool usedHyphenation = false;
 | 
						||
  gfxTextRun::TrimmableWS trimmableWS;
 | 
						||
  gfxFloat availWidth = aAvailableWidth;
 | 
						||
  if (Style()->IsTextCombined()) {
 | 
						||
    // If text-combine-upright is 'all', we would compress whatever long
 | 
						||
    // text into ~1em width, so there is no limited on the avail width.
 | 
						||
    availWidth = std::numeric_limits<gfxFloat>::infinity();
 | 
						||
  }
 | 
						||
  bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
 | 
						||
                                   HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML);
 | 
						||
  bool isBreakSpaces =
 | 
						||
      textStyle->mWhiteSpaceCollapse == StyleWhiteSpaceCollapse::BreakSpaces;
 | 
						||
  // allow whitespace to overflow the container
 | 
						||
  bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
 | 
						||
  gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
 | 
						||
  gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
 | 
						||
  bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
 | 
						||
  if (shouldSuppressLineBreak) {
 | 
						||
    suppressBreak = gfxTextRun::eSuppressAllBreaks;
 | 
						||
  } else if (!aLineLayout.LineIsBreakable()) {
 | 
						||
    suppressBreak = gfxTextRun::eSuppressInitialBreak;
 | 
						||
  }
 | 
						||
  uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText(
 | 
						||
      transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE),
 | 
						||
      availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget,
 | 
						||
      textStyle->WordCanWrap(this), textStyle->WhiteSpaceCanWrap(this),
 | 
						||
      isBreakSpaces,
 | 
						||
      // The following are output parameters:
 | 
						||
      canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr,
 | 
						||
      textMetrics, usedHyphenation, transformedLastBreak,
 | 
						||
      // In/out
 | 
						||
      breakPriority);
 | 
						||
  if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
 | 
						||
    // If we're measuring a zero-length piece of text, update
 | 
						||
    // the height manually.
 | 
						||
    nsFontMetrics* fm = provider.GetFontMetrics();
 | 
						||
    if (fm) {
 | 
						||
      textMetrics.mAscent = gfxFloat(fm->MaxAscent());
 | 
						||
      textMetrics.mDescent = gfxFloat(fm->MaxDescent());
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (GetWritingMode().IsLineInverted()) {
 | 
						||
    std::swap(textMetrics.mAscent, textMetrics.mDescent);
 | 
						||
    textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
 | 
						||
  }
 | 
						||
  // The "end" iterator points to the first character after the string mapped
 | 
						||
  // by this frame. Basically, its original-string offset is offset+charsFit
 | 
						||
  // after we've computed charsFit.
 | 
						||
  gfxSkipCharsIterator end(provider.GetEndHint());
 | 
						||
  end.SetSkippedOffset(transformedOffset + transformedCharsFit);
 | 
						||
  int32_t charsFit = end.GetOriginalOffset() - offset;
 | 
						||
  if (offset + charsFit == newLineOffset) {
 | 
						||
    // We broke before a trailing preformatted '\n'. The newline should
 | 
						||
    // be assigned to this frame. Note that newLineOffset will be -1 if
 | 
						||
    // there was no preformatted newline, so we wouldn't get here in that
 | 
						||
    // case.
 | 
						||
    ++charsFit;
 | 
						||
  }
 | 
						||
  // That might have taken us beyond our assigned content range (because
 | 
						||
  // we might have advanced over some skipped chars that extend outside
 | 
						||
  // this frame), so get back in.
 | 
						||
  int32_t lastBreak = -1;
 | 
						||
  if (charsFit >= limitLength) {
 | 
						||
    charsFit = limitLength;
 | 
						||
    if (transformedLastBreak != UINT32_MAX) {
 | 
						||
      // lastBreak is needed.
 | 
						||
      // This may set lastBreak greater than 'length', but that's OK
 | 
						||
      lastBreak = end.ConvertSkippedToOriginal(transformedOffset +
 | 
						||
                                               transformedLastBreak);
 | 
						||
    }
 | 
						||
    end.SetOriginalOffset(offset + charsFit);
 | 
						||
    // If we were forced to fit, and the break position is after a soft hyphen,
 | 
						||
    // note that this is a hyphenation break.
 | 
						||
    if ((forceBreak >= 0 || forceBreakAfter) &&
 | 
						||
        HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
 | 
						||
      usedHyphenation = true;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  if (usedHyphenation) {
 | 
						||
    // Fix up metrics to include hyphen
 | 
						||
    AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics,
 | 
						||
                       boundingBoxType, aDrawTarget);
 | 
						||
    AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
 | 
						||
  }
 | 
						||
  if (textMetrics.mBoundingBox.IsEmpty()) {
 | 
						||
    AddStateBits(TEXT_NO_RENDERED_GLYPHS);
 | 
						||
  }
 | 
						||
 | 
						||
  bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
 | 
						||
  if (trimmableWS.mAdvance > 0.0) {
 | 
						||
    if (canTrimTrailingWhitespace) {
 | 
						||
      // Optimization: if we we can be sure this frame will be at end of line,
 | 
						||
      // then trim the whitespace now.
 | 
						||
      if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
 | 
						||
        // We're definitely going to break so our trailing whitespace should
 | 
						||
        // definitely be trimmed. Record that we've already done it.
 | 
						||
        AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
 | 
						||
        textMetrics.mAdvanceWidth -= trimmableWS.mAdvance;
 | 
						||
        trimmableWS.mAdvance = 0.0;
 | 
						||
      }
 | 
						||
      ClearHangableISize();
 | 
						||
      ClearTrimmableWS();
 | 
						||
    } else if (whitespaceCanHang) {
 | 
						||
      // Figure out how much whitespace will hang if at end-of-line.
 | 
						||
      gfxFloat hang =
 | 
						||
          std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth),
 | 
						||
                   gfxFloat(trimmableWS.mAdvance));
 | 
						||
      SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang));
 | 
						||
      // nsLineLayout only needs the TrimmableWS property if justifying, so
 | 
						||
      // check whether this is relevant.
 | 
						||
      if (textStyle->mTextAlign == StyleTextAlign::Justify ||
 | 
						||
          textStyle->mTextAlignLast == StyleTextAlignLast::Justify) {
 | 
						||
        SetTrimmableWS(trimmableWS);
 | 
						||
      }
 | 
						||
      textMetrics.mAdvanceWidth -= hang;
 | 
						||
      trimmableWS.mAdvance = 0.0;
 | 
						||
    } else {
 | 
						||
      MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!");
 | 
						||
      ClearHangableISize();
 | 
						||
      ClearTrimmableWS();
 | 
						||
      trimmableWS.mAdvance = 0.0;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    // Remove any stale frame properties.
 | 
						||
    ClearHangableISize();
 | 
						||
    ClearTrimmableWS();
 | 
						||
  }
 | 
						||
 | 
						||
  if (!brokeText && lastBreak >= 0) {
 | 
						||
    // Since everything fit and no break was forced,
 | 
						||
    // record the last break opportunity
 | 
						||
    NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth,
 | 
						||
                 "If the text doesn't fit, and we have a break opportunity, "
 | 
						||
                 "why didn't MeasureText use it?");
 | 
						||
    MOZ_ASSERT(lastBreak >= offset, "Strange break position");
 | 
						||
    aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true,
 | 
						||
                                            breakPriority);
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t contentLength = offset + charsFit - GetContentOffset();
 | 
						||
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
  // Compute output metrics
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
 | 
						||
  // first-letter frames should use the tight bounding box metrics for
 | 
						||
  // ascent/descent for good drop-cap effects
 | 
						||
  if (HasAnyStateBits(TEXT_FIRST_LETTER)) {
 | 
						||
    textMetrics.mAscent =
 | 
						||
        std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
 | 
						||
    textMetrics.mDescent =
 | 
						||
        std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
 | 
						||
  }
 | 
						||
 | 
						||
  // Setup metrics for caller
 | 
						||
  // Disallow negative widths
 | 
						||
  WritingMode wm = GetWritingMode();
 | 
						||
  LogicalSize finalSize(wm);
 | 
						||
  finalSize.ISize(wm) =
 | 
						||
      NSToCoordCeilClamped(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
 | 
						||
 | 
						||
  nscoord fontBaseline;
 | 
						||
  // Note(dshin): Baseline should tecnhically be halfway through the em box for
 | 
						||
  // a central baseline. It is simply half of the text run block size so that it
 | 
						||
  // can be easily calculated in `GetNaturalBaselineBOffset`.
 | 
						||
  if (transformedCharsFit == 0 && !usedHyphenation) {
 | 
						||
    aMetrics.SetBlockStartAscent(0);
 | 
						||
    finalSize.BSize(wm) = 0;
 | 
						||
    fontBaseline = 0;
 | 
						||
  } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
 | 
						||
    fontBaseline = NSToCoordCeil(textMetrics.mAscent);
 | 
						||
    const auto size = fontBaseline + NSToCoordCeil(textMetrics.mDescent);
 | 
						||
    // Use actual text metrics for floating first letter frame.
 | 
						||
    aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
 | 
						||
                                                             : size / 2);
 | 
						||
    finalSize.BSize(wm) = size;
 | 
						||
  } else {
 | 
						||
    // Otherwise, ascent should contain the overline drawable area.
 | 
						||
    // And also descent should contain the underline drawable area.
 | 
						||
    // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
 | 
						||
    nsFontMetrics* fm = provider.GetFontMetrics();
 | 
						||
    nscoord fontAscent =
 | 
						||
        wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
 | 
						||
    nscoord fontDescent =
 | 
						||
        wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
 | 
						||
    fontBaseline = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
 | 
						||
    const auto size =
 | 
						||
        fontBaseline +
 | 
						||
        std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
 | 
						||
    aMetrics.SetBlockStartAscent(wm.IsAlphabeticalBaseline() ? fontBaseline
 | 
						||
                                                             : size / 2);
 | 
						||
    finalSize.BSize(wm) = size;
 | 
						||
  }
 | 
						||
  if (Style()->IsTextCombined()) {
 | 
						||
    nsFontMetrics* fm = provider.GetFontMetrics();
 | 
						||
    nscoord width = finalSize.ISize(wm);
 | 
						||
    nscoord em = fm->EmHeight();
 | 
						||
    // Compress the characters in horizontal axis if necessary.
 | 
						||
    if (width <= em) {
 | 
						||
      RemoveProperty(TextCombineScaleFactorProperty());
 | 
						||
    } else {
 | 
						||
      SetProperty(TextCombineScaleFactorProperty(),
 | 
						||
                  static_cast<float>(em) / static_cast<float>(width));
 | 
						||
      finalSize.ISize(wm) = em;
 | 
						||
    }
 | 
						||
    // Make the characters be in an 1em square.
 | 
						||
    if (finalSize.BSize(wm) != em) {
 | 
						||
      fontBaseline =
 | 
						||
          aMetrics.BlockStartAscent() + (em - finalSize.BSize(wm)) / 2;
 | 
						||
      aMetrics.SetBlockStartAscent(fontBaseline);
 | 
						||
      finalSize.BSize(wm) = em;
 | 
						||
    }
 | 
						||
  }
 | 
						||
  aMetrics.SetSize(wm, finalSize);
 | 
						||
 | 
						||
  NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???");
 | 
						||
  NS_ASSERTION(
 | 
						||
      (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode())
 | 
						||
                                 : aMetrics.BSize(aMetrics.GetWritingMode())) -
 | 
						||
              aMetrics.BlockStartAscent() >=
 | 
						||
          0,
 | 
						||
      "Negative descent???");
 | 
						||
 | 
						||
  mAscent = fontBaseline;
 | 
						||
 | 
						||
  // Handle text that runs outside its normal bounds.
 | 
						||
  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
 | 
						||
  if (mTextRun->IsVertical()) {
 | 
						||
    // Swap line-relative textMetrics dimensions to physical coordinates.
 | 
						||
    std::swap(boundingBox.x, boundingBox.y);
 | 
						||
    std::swap(boundingBox.width, boundingBox.height);
 | 
						||
    if (GetWritingMode().IsVerticalRL()) {
 | 
						||
      boundingBox.x = -boundingBox.XMost();
 | 
						||
      boundingBox.x += aMetrics.Width() - mAscent;
 | 
						||
    } else {
 | 
						||
      boundingBox.x += mAscent;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    boundingBox.y += mAscent;
 | 
						||
  }
 | 
						||
  aMetrics.SetOverflowAreasToDesiredBounds();
 | 
						||
  aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox);
 | 
						||
 | 
						||
  // When we have text decorations, we don't need to compute their overflow now
 | 
						||
  // because we're guaranteed to do it later
 | 
						||
  // (see nsLineLayout::RelativePositionFrames)
 | 
						||
  UnionAdditionalOverflow(presContext, aLineLayout.LineContainerFrame(),
 | 
						||
                          provider, &aMetrics.InkOverflow(), false, true);
 | 
						||
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
  // Clean up, update state
 | 
						||
  /////////////////////////////////////////////////////////////////////
 | 
						||
 | 
						||
  // If all our characters are discarded or collapsed, then trimmable width
 | 
						||
  // from the last textframe should be preserved. Otherwise the trimmable width
 | 
						||
  // from this textframe overrides. (Currently in CSS trimmable width can be
 | 
						||
  // at most one space so there's no way for trimmable width from a previous
 | 
						||
  // frame to accumulate with trimmable width from this frame.)
 | 
						||
  if (transformedCharsFit > 0) {
 | 
						||
    aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance));
 | 
						||
    AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
 | 
						||
  }
 | 
						||
  bool breakAfter = forceBreakAfter;
 | 
						||
  if (!shouldSuppressLineBreak) {
 | 
						||
    if (charsFit > 0 && charsFit == length &&
 | 
						||
        textStyle->mHyphens != StyleHyphens::None &&
 | 
						||
        HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
 | 
						||
      bool fits =
 | 
						||
          textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
 | 
						||
      // Record a potential break after final soft hyphen
 | 
						||
      aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
 | 
						||
                                              gfxBreakPriority::eNormalBreak);
 | 
						||
    }
 | 
						||
    // length == 0 means either the text is empty or it's all collapsed away
 | 
						||
    bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
 | 
						||
    if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
 | 
						||
        transformedOffset + transformedLength == mTextRun->GetLength() &&
 | 
						||
        (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) {
 | 
						||
      // We placed all the text in the textrun and we have a break opportunity
 | 
						||
      // at the end of the textrun. We need to record it because the following
 | 
						||
      // content may not care about nsLineBreaker.
 | 
						||
 | 
						||
      // Note that because we didn't break, we can be sure that (thanks to the
 | 
						||
      // code up above) textMetrics.mAdvanceWidth includes the width of any
 | 
						||
      // trailing whitespace. So we need to subtract trimmableWidth here
 | 
						||
      // because if we did break at this point, that much width would be
 | 
						||
      // trimmed.
 | 
						||
      if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) {
 | 
						||
        breakAfter = true;
 | 
						||
      } else {
 | 
						||
        aLineLayout.NotifyOptionalBreakPosition(this, length, true,
 | 
						||
                                                gfxBreakPriority::eNormalBreak);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  // Compute reflow status
 | 
						||
  if (contentLength != maxContentLength) {
 | 
						||
    aStatus.SetIncomplete();
 | 
						||
  }
 | 
						||
 | 
						||
  if (charsFit == 0 && length > 0 && !usedHyphenation) {
 | 
						||
    // Couldn't place any text
 | 
						||
    aStatus.SetInlineLineBreakBeforeAndReset();
 | 
						||
  } else if (contentLength > 0 &&
 | 
						||
             mContentOffset + contentLength - 1 == newLineOffset) {
 | 
						||
    // Ends in \n
 | 
						||
    aStatus.SetInlineLineBreakAfter();
 | 
						||
    aLineLayout.SetLineEndsInBR(true);
 | 
						||
  } else if (breakAfter) {
 | 
						||
    aStatus.SetInlineLineBreakAfter();
 | 
						||
  }
 | 
						||
  if (completedFirstLetter) {
 | 
						||
    aLineLayout.SetFirstLetterStyleOK(false);
 | 
						||
    aStatus.SetFirstLetterComplete();
 | 
						||
  }
 | 
						||
  if (brokeText && breakPriority == gfxBreakPriority::eWordWrapBreak) {
 | 
						||
    aLineLayout.SetUsedOverflowWrap();
 | 
						||
  }
 | 
						||
 | 
						||
  // Updated the cached NewlineProperty, or delete it.
 | 
						||
  if (contentLength < maxContentLength &&
 | 
						||
      textStyle->NewlineIsSignificant(this) &&
 | 
						||
      (contentNewLineOffset < 0 ||
 | 
						||
       mContentOffset + contentLength <= contentNewLineOffset)) {
 | 
						||
    if (!cachedNewlineOffset) {
 | 
						||
      cachedNewlineOffset = new NewlineProperty;
 | 
						||
      if (NS_FAILED(mContent->SetProperty(
 | 
						||
              nsGkAtoms::newline, cachedNewlineOffset,
 | 
						||
              nsINode::DeleteProperty<NewlineProperty>))) {
 | 
						||
        delete cachedNewlineOffset;
 | 
						||
        cachedNewlineOffset = nullptr;
 | 
						||
      }
 | 
						||
      mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
 | 
						||
    }
 | 
						||
    if (cachedNewlineOffset) {
 | 
						||
      cachedNewlineOffset->mStartOffset = offset;
 | 
						||
      cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
 | 
						||
    }
 | 
						||
  } else if (cachedNewlineOffset) {
 | 
						||
    mContent->RemoveProperty(nsGkAtoms::newline);
 | 
						||
    mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
 | 
						||
  }
 | 
						||
 | 
						||
  // Compute space and letter counts for justification, if required
 | 
						||
  if ((lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
 | 
						||
       lineContainer->StyleText()->mTextAlignLast ==
 | 
						||
           StyleTextAlignLast::Justify ||
 | 
						||
       shouldSuppressLineBreak) &&
 | 
						||
      !lineContainer->IsInSVGTextSubtree()) {
 | 
						||
    AddStateBits(TEXT_JUSTIFICATION_ENABLED);
 | 
						||
    Range range(uint32_t(offset), uint32_t(offset + charsFit));
 | 
						||
    aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
 | 
						||
  }
 | 
						||
 | 
						||
  SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
 | 
						||
 | 
						||
  InvalidateFrame();
 | 
						||
 | 
						||
#ifdef NOISY_REFLOW
 | 
						||
  ListTag(stdout);
 | 
						||
  printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(),
 | 
						||
         aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus);
 | 
						||
#endif
 | 
						||
}
 | 
						||
 | 
						||
/* virtual */
 | 
						||
bool nsTextFrame::CanContinueTextRun() const {
 | 
						||
  // We can continue a text run through a text frame
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace(
 | 
						||
    DrawTarget* aDrawTarget) {
 | 
						||
  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),
 | 
						||
             "frame should have been reflowed");
 | 
						||
 | 
						||
  TrimOutput result;
 | 
						||
  result.mChanged = false;
 | 
						||
  result.mDeltaWidth = 0;
 | 
						||
 | 
						||
  AddStateBits(TEXT_END_OF_LINE);
 | 
						||
 | 
						||
  if (!GetTextRun(nsTextFrame::eInflated)) {
 | 
						||
    // If reflow didn't create a textrun, there must have been no content once
 | 
						||
    // leading whitespace was trimmed, so nothing more to do here.
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t contentLength = GetContentLength();
 | 
						||
  if (!contentLength) {
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  gfxSkipCharsIterator start =
 | 
						||
      EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
 | 
						||
  NS_ENSURE_TRUE(mTextRun, result);
 | 
						||
 | 
						||
  uint32_t trimmedStart = start.GetSkippedOffset();
 | 
						||
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
  TrimmedOffsets trimmed = GetTrimmedOffsets(frag);
 | 
						||
  gfxSkipCharsIterator trimmedEndIter = start;
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
  gfxFloat delta = 0;
 | 
						||
  uint32_t trimmedEnd =
 | 
						||
      trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
 | 
						||
 | 
						||
  if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) &&
 | 
						||
      trimmed.GetEnd() < GetContentEnd()) {
 | 
						||
    gfxSkipCharsIterator end = trimmedEndIter;
 | 
						||
    uint32_t endOffset =
 | 
						||
        end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
 | 
						||
    if (trimmedEnd < endOffset) {
 | 
						||
      // We can't be dealing with tabs here ... they wouldn't be trimmed. So
 | 
						||
      // it's OK to pass null for the line container.
 | 
						||
      PropertyProvider provider(
 | 
						||
          mTextRun, textStyle, frag, this, start, contentLength, nullptr, 0,
 | 
						||
          nsTextFrame::eInflated, HasAnyStateBits(TEXT_START_OF_LINE));
 | 
						||
      delta =
 | 
						||
          mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
 | 
						||
      result.mChanged = true;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  gfxFloat advanceDelta;
 | 
						||
  mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
 | 
						||
                          HasAnyStateBits(TEXT_START_OF_LINE), true,
 | 
						||
                          &advanceDelta);
 | 
						||
  if (advanceDelta != 0) {
 | 
						||
    result.mChanged = true;
 | 
						||
  }
 | 
						||
 | 
						||
  // aDeltaWidth is *subtracted* from our width.
 | 
						||
  // If advanceDelta is positive then setting the line break made us longer,
 | 
						||
  // so aDeltaWidth could go negative.
 | 
						||
  result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
 | 
						||
  // If aDeltaWidth goes negative, that means this frame might not actually fit
 | 
						||
  // anymore!!! We need higher level line layout to recover somehow.
 | 
						||
  // If it's because the frame has a soft hyphen that is now being displayed,
 | 
						||
  // this should actually be OK, because our reflow recorded the break
 | 
						||
  // opportunity that allowed the soft hyphen to be used, and we wouldn't
 | 
						||
  // have recorded the opportunity unless the hyphen fit (or was the first
 | 
						||
  // opportunity on the line).
 | 
						||
  // Otherwise this can/ really only happen when we have glyphs with special
 | 
						||
  // shapes at the end of lines, I think. Breaking inside a kerning pair won't
 | 
						||
  // do it because that would mean we broke inside this textrun, and
 | 
						||
  // BreakAndMeasureText should make sure the resulting shaped substring fits.
 | 
						||
  // Maybe if we passed a maxTextLength? But that only happens at direction
 | 
						||
  // changes (so we wouldn't kern across the boundary) or for first-letter
 | 
						||
  // (which always fits because it starts the line!).
 | 
						||
  NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,
 | 
						||
                       "Negative deltawidth, something odd is happening");
 | 
						||
 | 
						||
#ifdef NOISY_TRIM
 | 
						||
  ListTag(stdout);
 | 
						||
  printf(": trim => %d\n", result.mDeltaWidth);
 | 
						||
#endif
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame,
 | 
						||
                                             bool aIncludeShadows) {
 | 
						||
  RemoveProperty(WebRenderTextBounds());
 | 
						||
 | 
						||
  nsRect bounds(nsPoint(0, 0), GetSize());
 | 
						||
  OverflowAreas result(bounds, bounds);
 | 
						||
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return result;
 | 
						||
  }
 | 
						||
 | 
						||
  PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
 | 
						||
  // Don't trim trailing space, in case we need to paint it as selected.
 | 
						||
  provider.InitializeForDisplay(false);
 | 
						||
 | 
						||
  gfxTextRun::Metrics textMetrics =
 | 
						||
      mTextRun->MeasureText(ComputeTransformedRange(provider),
 | 
						||
                            gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
 | 
						||
  if (GetWritingMode().IsLineInverted()) {
 | 
						||
    textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
 | 
						||
  }
 | 
						||
  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
 | 
						||
  boundingBox += nsPoint(0, mAscent);
 | 
						||
  if (mTextRun->IsVertical()) {
 | 
						||
    // Swap line-relative textMetrics dimensions to physical coordinates.
 | 
						||
    std::swap(boundingBox.x, boundingBox.y);
 | 
						||
    std::swap(boundingBox.width, boundingBox.height);
 | 
						||
  }
 | 
						||
  nsRect& vis = result.InkOverflow();
 | 
						||
  vis.UnionRect(vis, boundingBox);
 | 
						||
  UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true,
 | 
						||
                          aIncludeShadows);
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
 | 
						||
                           const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
 | 
						||
                           const nsTextFragment* aFrag, int32_t aFragOffset,
 | 
						||
                           int32_t aFragLen, nsAString& aOut) {
 | 
						||
  nsAutoString fragString;
 | 
						||
  char16_t* out;
 | 
						||
  bool needsToMaskPassword = NeedsToMaskPassword(aFrame);
 | 
						||
  if (aStyle->mTextTransform.IsNone() && !needsToMaskPassword &&
 | 
						||
      aStyle->mWebkitTextSecurity == StyleTextSecurity::None) {
 | 
						||
    // No text-transform, so we can copy directly to the output string.
 | 
						||
    aOut.SetLength(aOut.Length() + aFragLen);
 | 
						||
    out = aOut.EndWriting() - aFragLen;
 | 
						||
  } else {
 | 
						||
    // Use a temporary string as source for the transform.
 | 
						||
    fragString.SetLength(aFragLen);
 | 
						||
    out = fragString.BeginWriting();
 | 
						||
  }
 | 
						||
 | 
						||
  // Copy the text, with \n and \t replaced by <space> if appropriate.
 | 
						||
  MOZ_ASSERT(aFragOffset >= 0);
 | 
						||
  for (uint32_t i = 0; i < static_cast<uint32_t>(aFragLen); ++i) {
 | 
						||
    char16_t ch = aFrag->CharAt(static_cast<uint32_t>(aFragOffset) + i);
 | 
						||
    if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
 | 
						||
        (ch == '\t' && !aStyle->TabIsSignificant())) {
 | 
						||
      ch = ' ';
 | 
						||
    }
 | 
						||
    out[i] = ch;
 | 
						||
  }
 | 
						||
 | 
						||
  if (!aStyle->mTextTransform.IsNone() || needsToMaskPassword ||
 | 
						||
      aStyle->mWebkitTextSecurity != StyleTextSecurity::None) {
 | 
						||
    MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed);
 | 
						||
    if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
 | 
						||
      // Apply text-transform according to style in the transformed run.
 | 
						||
      char16_t maskChar =
 | 
						||
          needsToMaskPassword ? 0 : aStyle->TextSecurityMaskChar();
 | 
						||
      auto transformedTextRun =
 | 
						||
          static_cast<const nsTransformedTextRun*>(aTextRun);
 | 
						||
      nsAutoString convertedString;
 | 
						||
      AutoTArray<bool, 50> charsToMergeArray;
 | 
						||
      AutoTArray<bool, 50> deletedCharsArray;
 | 
						||
      nsCaseTransformTextRunFactory::TransformString(
 | 
						||
          fragString, convertedString, /* aGlobalTransform = */ Nothing(),
 | 
						||
          maskChar, /* aCaseTransformsOnly = */ true, nullptr,
 | 
						||
          charsToMergeArray, deletedCharsArray, transformedTextRun,
 | 
						||
          aSkippedOffset);
 | 
						||
      aOut.Append(convertedString);
 | 
						||
    } else {
 | 
						||
      // Should not happen (see assertion above), but as a fallback...
 | 
						||
      aOut.Append(fragString);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame,
 | 
						||
                                            nsBlockFrame* aLineContainer,
 | 
						||
                                            bool* aStartsAtHardBreak,
 | 
						||
                                            bool* aEndsAtHardBreak) {
 | 
						||
  bool foundValidLine;
 | 
						||
  nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
 | 
						||
  if (!foundValidLine) {
 | 
						||
    NS_ERROR("Invalid line!");
 | 
						||
    *aStartsAtHardBreak = *aEndsAtHardBreak = true;
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped();
 | 
						||
  if (iter.Prev()) {
 | 
						||
    *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped();
 | 
						||
  } else {
 | 
						||
    // Hit block boundary
 | 
						||
    *aStartsAtHardBreak = true;
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::AppendRenderedText(AppendRenderedTextState& aState,
 | 
						||
                                     RenderedText& aResult) {
 | 
						||
  if (HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
 | 
						||
    // We don't trust dirty frames, especially when computing rendered text.
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  // Ensure the text run and grab the gfxSkipCharsIterator for it
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  if (!mTextRun) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
  gfxSkipCharsIterator tmpIter = iter;
 | 
						||
 | 
						||
  // Check if the frame starts/ends at a hard line break, to determine
 | 
						||
  // whether whitespace should be trimmed.
 | 
						||
  bool startsAtHardBreak, endsAtHardBreak;
 | 
						||
  if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) {
 | 
						||
    startsAtHardBreak = endsAtHardBreak = false;
 | 
						||
  } else if (nsBlockFrame* thisLc = do_QueryFrame(FindLineContainer())) {
 | 
						||
    if (thisLc != aState.mLineContainer) {
 | 
						||
      // Setup line cursor when needed.
 | 
						||
      aState.mLineContainer = thisLc;
 | 
						||
      aState.mLineContainer->SetupLineCursorForQuery();
 | 
						||
    }
 | 
						||
    LineStartsOrEndsAtHardLineBreak(this, aState.mLineContainer,
 | 
						||
                                    &startsAtHardBreak, &endsAtHardBreak);
 | 
						||
  } else {
 | 
						||
    // Weird situation where we have a line layout without a block.
 | 
						||
    // No soft breaks occur in this situation.
 | 
						||
    startsAtHardBreak = endsAtHardBreak = true;
 | 
						||
  }
 | 
						||
 | 
						||
  // Whether we need to trim whitespaces after the text frame.
 | 
						||
  // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags
 | 
						||
  // in the cases where this should not occur.
 | 
						||
  TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default;
 | 
						||
  if (!IsAtEndOfLine() ||
 | 
						||
      aState.mTrimTrailingWhitespace != TrailingWhitespace::Trim ||
 | 
						||
      !endsAtHardBreak) {
 | 
						||
    trimFlags |= TrimmedOffsetFlags::NoTrimAfter;
 | 
						||
  }
 | 
						||
 | 
						||
  // Whether to trim whitespaces before the text frame.
 | 
						||
  if (!startsAtHardBreak) {
 | 
						||
    trimFlags |= TrimmedOffsetFlags::NoTrimBefore;
 | 
						||
  }
 | 
						||
 | 
						||
  TrimmedOffsets trimmedOffsets =
 | 
						||
      GetTrimmedOffsets(aState.mTextFrag, trimFlags);
 | 
						||
  bool trimmedSignificantNewline =
 | 
						||
      (trimmedOffsets.GetEnd() < GetContentEnd() ||
 | 
						||
       (aState.mTrimTrailingWhitespace == TrailingWhitespace::Trim &&
 | 
						||
        StyleText()->mWhiteSpaceCollapse ==
 | 
						||
            StyleWhiteSpaceCollapse::PreserveBreaks)) &&
 | 
						||
      HasSignificantTerminalNewline();
 | 
						||
  uint32_t skippedToRenderedStringOffset =
 | 
						||
      aState.mOffsetInRenderedString -
 | 
						||
      tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
 | 
						||
  uint32_t nextOffsetInRenderedString =
 | 
						||
      tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
 | 
						||
      (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
 | 
						||
 | 
						||
  if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) {
 | 
						||
    if (nextOffsetInRenderedString <= aState.mStartOffset) {
 | 
						||
      aState.mOffsetInRenderedString = nextOffsetInRenderedString;
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    if (!aState.mHaveOffsets) {
 | 
						||
      aResult.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal(
 | 
						||
          aState.mStartOffset - skippedToRenderedStringOffset);
 | 
						||
      aResult.mOffsetWithinNodeRenderedText = aState.mStartOffset;
 | 
						||
      aState.mHaveOffsets = true;
 | 
						||
    }
 | 
						||
    if (aState.mOffsetInRenderedString >= aState.mEndOffset) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  } else {
 | 
						||
    if (uint32_t(GetContentEnd()) <= aState.mStartOffset) {
 | 
						||
      aState.mOffsetInRenderedString = nextOffsetInRenderedString;
 | 
						||
      return true;
 | 
						||
    }
 | 
						||
    if (!aState.mHaveOffsets) {
 | 
						||
      aResult.mOffsetWithinNodeText = aState.mStartOffset;
 | 
						||
      // Skip trimmed space when computed the rendered text offset.
 | 
						||
      int32_t clamped =
 | 
						||
          std::max<int32_t>(aState.mStartOffset, trimmedOffsets.mStart);
 | 
						||
      aResult.mOffsetWithinNodeRenderedText =
 | 
						||
          tmpIter.ConvertOriginalToSkipped(clamped) +
 | 
						||
          skippedToRenderedStringOffset;
 | 
						||
      MOZ_ASSERT(aResult.mOffsetWithinNodeRenderedText >=
 | 
						||
                         aState.mOffsetInRenderedString &&
 | 
						||
                     aResult.mOffsetWithinNodeRenderedText <= INT32_MAX,
 | 
						||
                 "Bad offset within rendered text");
 | 
						||
      aState.mHaveOffsets = true;
 | 
						||
    }
 | 
						||
    if (uint32_t(mContentOffset) >= aState.mEndOffset) {
 | 
						||
      return false;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  int32_t startOffset;
 | 
						||
  int32_t endOffset;
 | 
						||
  if (aState.mOffsetType == TextOffsetType::OffsetsInRenderedText) {
 | 
						||
    startOffset = tmpIter.ConvertSkippedToOriginal(
 | 
						||
        aState.mStartOffset - skippedToRenderedStringOffset);
 | 
						||
    endOffset = tmpIter.ConvertSkippedToOriginal(aState.mEndOffset -
 | 
						||
                                                 skippedToRenderedStringOffset);
 | 
						||
  } else {
 | 
						||
    startOffset = aState.mStartOffset;
 | 
						||
    endOffset = std::min<uint32_t>(INT32_MAX, aState.mEndOffset);
 | 
						||
  }
 | 
						||
 | 
						||
  // If startOffset and/or endOffset are inside of trimmedOffsets' range,
 | 
						||
  // then clamp the edges of trimmedOffsets accordingly.
 | 
						||
  int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
 | 
						||
  trimmedOffsets.mStart =
 | 
						||
      std::max<uint32_t>(trimmedOffsets.mStart, startOffset);
 | 
						||
  trimmedOffsets.mLength =
 | 
						||
      std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) -
 | 
						||
      trimmedOffsets.mStart;
 | 
						||
 | 
						||
  if (trimmedOffsets.mLength > 0) {
 | 
						||
    const nsStyleText* textStyle = StyleText();
 | 
						||
    iter.SetOriginalOffset(trimmedOffsets.mStart);
 | 
						||
    while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
 | 
						||
      int32_t runLength;
 | 
						||
      bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
 | 
						||
      runLength = std::min(runLength,
 | 
						||
                           trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
 | 
						||
      if (isSkipped) {
 | 
						||
        MOZ_ASSERT(runLength >= 0);
 | 
						||
        for (uint32_t i = 0; i < static_cast<uint32_t>(runLength); ++i) {
 | 
						||
          const char16_t ch = aState.mTextFrag->CharAt(
 | 
						||
              AssertedCast<uint32_t>(iter.GetOriginalOffset() + i));
 | 
						||
          if (ch == CH_SHY) {
 | 
						||
            // We should preserve soft hyphens. They can't be transformed.
 | 
						||
            aResult.mString.Append(ch);
 | 
						||
          }
 | 
						||
        }
 | 
						||
      } else {
 | 
						||
        TransformChars(this, textStyle, mTextRun, iter.GetSkippedOffset(),
 | 
						||
                       aState.mTextFrag, iter.GetOriginalOffset(), runLength,
 | 
						||
                       aResult.mString);
 | 
						||
      }
 | 
						||
      iter.AdvanceOriginal(runLength);
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
 | 
						||
    // A significant newline was trimmed off (we must be
 | 
						||
    // white-space:pre-line). Put it back.
 | 
						||
    aResult.mString.Append('\n');
 | 
						||
  }
 | 
						||
  aState.mOffsetInRenderedString = nextOffsetInRenderedString;
 | 
						||
 | 
						||
  return true;
 | 
						||
}
 | 
						||
 | 
						||
nsIFrame::RenderedText nsTextFrame::GetRenderedText(
 | 
						||
    uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType,
 | 
						||
    TrailingWhitespace aTrimTrailingWhitespace) {
 | 
						||
  MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets");
 | 
						||
  MOZ_ASSERT(!GetPrevContinuation() ||
 | 
						||
                 (aOffsetType == TextOffsetType::OffsetsInContentText &&
 | 
						||
                  aStartOffset >= (uint32_t)GetContentOffset() &&
 | 
						||
                  aEndOffset <= (uint32_t)GetContentEnd()),
 | 
						||
             "Must be called on first-in-flow, or content offsets must be "
 | 
						||
             "given and be within this frame.");
 | 
						||
 | 
						||
  // The handling of offsets could be more efficient...
 | 
						||
  RenderedText result;
 | 
						||
  AppendRenderedTextState state{aStartOffset, aEndOffset, aOffsetType,
 | 
						||
                                aTrimTrailingWhitespace, TextFragment()};
 | 
						||
 | 
						||
  for (nsTextFrame* textFrame = this; textFrame;
 | 
						||
       textFrame = textFrame->GetNextContinuation()) {
 | 
						||
    if (!textFrame->AppendRenderedText(state, result)) {
 | 
						||
      break;
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  if (!state.mHaveOffsets) {
 | 
						||
    result.mOffsetWithinNodeText = state.mTextFrag->GetLength();
 | 
						||
    result.mOffsetWithinNodeRenderedText = state.mOffsetInRenderedString;
 | 
						||
  }
 | 
						||
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
/* virtual */
 | 
						||
bool nsTextFrame::IsEmpty() {
 | 
						||
  NS_ASSERTION(
 | 
						||
      !HasAllStateBits(TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE),
 | 
						||
      "Invalid state");
 | 
						||
 | 
						||
  // XXXldb Should this check compatibility mode as well???
 | 
						||
  const nsStyleText* textStyle = StyleText();
 | 
						||
  if (textStyle->WhiteSpaceIsSignificant()) {
 | 
						||
    // When WhiteSpaceIsSignificant styles are in effect, we only treat the
 | 
						||
    // frame as empty if its content really is entirely *empty* (not just
 | 
						||
    // whitespace).
 | 
						||
    return !GetContentLength();
 | 
						||
  }
 | 
						||
 | 
						||
  if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  if (HasAnyStateBits(TEXT_IS_ONLY_WHITESPACE)) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  bool isEmpty = IsAllWhitespace(TextFragment(),
 | 
						||
                                 textStyle->mWhiteSpaceCollapse !=
 | 
						||
                                     StyleWhiteSpaceCollapse::PreserveBreaks);
 | 
						||
  AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
 | 
						||
  return isEmpty;
 | 
						||
}
 | 
						||
 | 
						||
#ifdef DEBUG_FRAME_DUMP
 | 
						||
// Translate the mapped content into a string that's printable
 | 
						||
void nsTextFrame::ToCString(nsCString& aBuf) const {
 | 
						||
  // Get the frames text content
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
  if (!frag) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  const int32_t length = GetContentEnd() - mContentOffset;
 | 
						||
  if (length <= 0) {
 | 
						||
    // Negative lengths are possible during invalidation.
 | 
						||
    return;
 | 
						||
  }
 | 
						||
 | 
						||
  const uint32_t fragLength = AssertedCast<uint32_t>(GetContentEnd());
 | 
						||
  uint32_t fragOffset = AssertedCast<uint32_t>(GetContentOffset());
 | 
						||
 | 
						||
  while (fragOffset < fragLength) {
 | 
						||
    char16_t ch = frag->CharAt(fragOffset++);
 | 
						||
    if (ch == '\r') {
 | 
						||
      aBuf.AppendLiteral("\\r");
 | 
						||
    } else if (ch == '\n') {
 | 
						||
      aBuf.AppendLiteral("\\n");
 | 
						||
    } else if (ch == '\t') {
 | 
						||
      aBuf.AppendLiteral("\\t");
 | 
						||
    } else if ((ch < ' ') || (ch >= 127)) {
 | 
						||
      aBuf.Append(nsPrintfCString("\\u%04x", ch));
 | 
						||
    } else {
 | 
						||
      aBuf.Append(ch);
 | 
						||
    }
 | 
						||
  }
 | 
						||
}
 | 
						||
 | 
						||
nsresult nsTextFrame::GetFrameName(nsAString& aResult) const {
 | 
						||
  MakeFrameName(u"Text"_ns, aResult);
 | 
						||
  nsAutoCString tmp;
 | 
						||
  ToCString(tmp);
 | 
						||
  tmp.SetLength(std::min<size_t>(tmp.Length(), 50u));
 | 
						||
  aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns;
 | 
						||
  return NS_OK;
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
 | 
						||
  nsCString str;
 | 
						||
  ListGeneric(str, aPrefix, aFlags);
 | 
						||
 | 
						||
  if (!aFlags.contains(ListFlag::OnlyListDeterministicInfo)) {
 | 
						||
    str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
 | 
						||
  }
 | 
						||
 | 
						||
  // Output the first/last content offset and prev/next in flow info
 | 
						||
  bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
 | 
						||
  str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
 | 
						||
                         isComplete ? 'T' : 'F');
 | 
						||
 | 
						||
  if (IsSelected()) {
 | 
						||
    str += " SELECTED";
 | 
						||
  }
 | 
						||
  fprintf_stderr(out, "%s\n", str.get());
 | 
						||
}
 | 
						||
 | 
						||
void nsTextFrame::ListTextRuns(FILE* out,
 | 
						||
                               nsTHashSet<const void*>& aSeen) const {
 | 
						||
  if (!mTextRun || aSeen.Contains(mTextRun)) {
 | 
						||
    return;
 | 
						||
  }
 | 
						||
  aSeen.Insert(mTextRun);
 | 
						||
  mTextRun->Dump(out);
 | 
						||
}
 | 
						||
#endif
 | 
						||
 | 
						||
void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) {
 | 
						||
  AddStateBits(NS_FRAME_IS_BIDI);
 | 
						||
  if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
 | 
						||
    mContent->RemoveProperty(nsGkAtoms::flowlength);
 | 
						||
    mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
 | 
						||
  }
 | 
						||
 | 
						||
  /*
 | 
						||
   * After Bidi resolution we may need to reassign text runs.
 | 
						||
   * This is called during bidi resolution from the block container, so we
 | 
						||
   * shouldn't be holding a local reference to a textrun anywhere.
 | 
						||
   */
 | 
						||
  ClearTextRuns();
 | 
						||
 | 
						||
  nsTextFrame* prev = GetPrevContinuation();
 | 
						||
  if (prev) {
 | 
						||
    // the bidi resolver can be very evil when columns/pages are involved. Don't
 | 
						||
    // let it violate our invariants.
 | 
						||
    int32_t prevOffset = prev->GetContentOffset();
 | 
						||
    aStart = std::max(aStart, prevOffset);
 | 
						||
    aEnd = std::max(aEnd, prevOffset);
 | 
						||
    prev->ClearTextRuns();
 | 
						||
  }
 | 
						||
 | 
						||
  mContentOffset = aStart;
 | 
						||
  SetLength(aEnd - aStart, nullptr, 0);
 | 
						||
}
 | 
						||
 | 
						||
/**
 | 
						||
 * @return true if this text frame ends with a newline character.  It should
 | 
						||
 * return false if it is not a text frame.
 | 
						||
 */
 | 
						||
bool nsTextFrame::HasSignificantTerminalNewline() const {
 | 
						||
  return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::IsAtEndOfLine() const {
 | 
						||
  return HasAnyStateBits(TEXT_END_OF_LINE);
 | 
						||
}
 | 
						||
 | 
						||
Maybe<nscoord> nsTextFrame::GetNaturalBaselineBOffset(
 | 
						||
    WritingMode aWM, BaselineSharingGroup aBaselineGroup,
 | 
						||
    BaselineExportContext) const {
 | 
						||
  if (aBaselineGroup == BaselineSharingGroup::Last) {
 | 
						||
    return Nothing{};
 | 
						||
  }
 | 
						||
 | 
						||
  if (!aWM.IsOrthogonalTo(GetWritingMode())) {
 | 
						||
    if (aWM.IsCentralBaseline()) {
 | 
						||
      return Some(GetLogicalUsedBorderAndPadding(aWM).BStart(aWM) +
 | 
						||
                  ContentBSize(aWM) / 2);
 | 
						||
    }
 | 
						||
    return Some(mAscent);
 | 
						||
  }
 | 
						||
 | 
						||
  // When the text frame has a writing mode orthogonal to the desired
 | 
						||
  // writing mode, return a baseline coincides its parent frame.
 | 
						||
  nsIFrame* parent = GetParent();
 | 
						||
  nsPoint position = GetNormalPosition();
 | 
						||
  nscoord parentAscent = parent->GetLogicalBaseline(aWM);
 | 
						||
  if (aWM.IsVerticalRL()) {
 | 
						||
    nscoord parentDescent = parent->GetSize().width - parentAscent;
 | 
						||
    nscoord descent = parentDescent - position.x;
 | 
						||
    return Some(GetSize().width - descent);
 | 
						||
  }
 | 
						||
  return Some(parentAscent - (aWM.IsVertical() ? position.x : position.y));
 | 
						||
}
 | 
						||
 | 
						||
nscoord nsTextFrame::GetCaretBaseline() const {
 | 
						||
  if (mAscent == 0 && HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS)) {
 | 
						||
    nsBlockFrame* container = do_QueryFrame(FindLineContainer());
 | 
						||
    // TODO(emilio): Ideally we'd want to find out if only our line is empty,
 | 
						||
    // but that's non-trivial to do, and realistically empty inlines and text
 | 
						||
    // will get placed into a non-empty line unless all lines are empty, I
 | 
						||
    // believe...
 | 
						||
    if (container && container->LinesAreEmpty()) {
 | 
						||
      nscoord blockSize = container->ContentBSize(GetWritingMode());
 | 
						||
      return GetFontMetricsDerivedCaretBaseline(blockSize);
 | 
						||
    }
 | 
						||
  }
 | 
						||
  return nsIFrame::GetCaretBaseline();
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::HasAnyNoncollapsedCharacters() {
 | 
						||
  gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
 | 
						||
  int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd();
 | 
						||
  int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
 | 
						||
  int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
 | 
						||
  return skippedOffset != skippedOffsetEnd;
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
 | 
						||
  return ComputeCustomOverflowInternal(aOverflowAreas, true);
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas,
 | 
						||
                                                bool aIncludeShadows) {
 | 
						||
  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  nsIFrame* decorationsBlock;
 | 
						||
  if (IsFloatingFirstLetterChild()) {
 | 
						||
    decorationsBlock = GetParent();
 | 
						||
  } else {
 | 
						||
    nsIFrame* f = this;
 | 
						||
    for (;;) {
 | 
						||
      nsBlockFrame* fBlock = do_QueryFrame(f);
 | 
						||
      if (fBlock) {
 | 
						||
        decorationsBlock = fBlock;
 | 
						||
        break;
 | 
						||
      }
 | 
						||
 | 
						||
      f = f->GetParent();
 | 
						||
      if (!f) {
 | 
						||
        NS_ERROR("Couldn't find any block ancestor (for text decorations)");
 | 
						||
        return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
 | 
						||
      }
 | 
						||
    }
 | 
						||
  }
 | 
						||
 | 
						||
  aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows);
 | 
						||
  return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
 | 
						||
}
 | 
						||
 | 
						||
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)
 | 
						||
 | 
						||
void nsTextFrame::AssignJustificationGaps(
 | 
						||
    const mozilla::JustificationAssignment& aAssign) {
 | 
						||
  int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
 | 
						||
  static_assert(sizeof(aAssign) == 1,
 | 
						||
                "The encoding might be broken if JustificationAssignment "
 | 
						||
                "is larger than 1 byte");
 | 
						||
  SetProperty(JustificationAssignmentProperty(), encoded);
 | 
						||
}
 | 
						||
 | 
						||
mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment()
 | 
						||
    const {
 | 
						||
  int32_t encoded = GetProperty(JustificationAssignmentProperty());
 | 
						||
  mozilla::JustificationAssignment result;
 | 
						||
  result.mGapsAtStart = encoded >> 8;
 | 
						||
  result.mGapsAtEnd = encoded & 0xFF;
 | 
						||
  return result;
 | 
						||
}
 | 
						||
 | 
						||
uint32_t nsTextFrame::CountGraphemeClusters() const {
 | 
						||
  const nsTextFragment* frag = TextFragment();
 | 
						||
  MOZ_ASSERT(frag, "Text frame must have text fragment");
 | 
						||
  nsAutoString content;
 | 
						||
  frag->AppendTo(content, AssertedCast<uint32_t>(GetContentOffset()),
 | 
						||
                 AssertedCast<uint32_t>(GetContentLength()));
 | 
						||
  return unicode::CountGraphemeClusters(content);
 | 
						||
}
 | 
						||
 | 
						||
bool nsTextFrame::HasNonSuppressedText() const {
 | 
						||
  if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
 | 
						||
                      // If we haven't reflowed yet, or are currently doing so,
 | 
						||
                      // just return true because we can't be sure.
 | 
						||
                      NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) {
 | 
						||
    return true;
 | 
						||
  }
 | 
						||
 | 
						||
  if (!GetTextRun(nsTextFrame::eInflated)) {
 | 
						||
    return false;
 | 
						||
  }
 | 
						||
 | 
						||
  TrimmedOffsets offsets =
 | 
						||
      GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
 | 
						||
  return offsets.mLength != 0;
 | 
						||
}
 |