1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 /* rendering object for textual content of elements */
8 
9 #include "nsTextFrame.h"
10 
11 #include "gfx2DGlue.h"
12 
13 #include "gfxUtils.h"
14 #include "mozilla/Attributes.h"
15 #include "mozilla/ComputedStyle.h"
16 #include "mozilla/DebugOnly.h"
17 #include "mozilla/gfx/2D.h"
18 #include "mozilla/Likely.h"
19 #include "mozilla/MathAlgorithms.h"
20 #include "mozilla/PresShell.h"
21 #include "mozilla/StaticPrefs_layout.h"
22 #include "mozilla/StaticPresData.h"
23 #include "mozilla/SVGTextFrame.h"
24 #include "mozilla/SVGUtils.h"
25 #include "mozilla/TextEditor.h"
26 #include "mozilla/TextEvents.h"
27 #include "mozilla/BinarySearch.h"
28 #include "mozilla/IntegerRange.h"
29 #include "mozilla/Unused.h"
30 #include "mozilla/PodOperations.h"
31 
32 #include "nsCOMPtr.h"
33 #include "nsBlockFrame.h"
34 #include "nsFontMetrics.h"
35 #include "nsSplittableFrame.h"
36 #include "nsLineLayout.h"
37 #include "nsString.h"
38 #include "nsUnicharUtils.h"
39 #include "nsPresContext.h"
40 #include "nsIContent.h"
41 #include "nsStyleConsts.h"
42 #include "nsStyleStruct.h"
43 #include "nsStyleStructInlines.h"
44 #include "nsCoord.h"
45 #include "gfxContext.h"
46 #include "nsTArray.h"
47 #include "nsCSSPseudoElements.h"
48 #include "nsCSSFrameConstructor.h"
49 #include "nsCompatibility.h"
50 #include "nsCSSColorUtils.h"
51 #include "nsLayoutUtils.h"
52 #include "nsDisplayList.h"
53 #include "nsIFrame.h"
54 #include "nsIMathMLFrame.h"
55 #include "nsPlaceholderFrame.h"
56 #include "nsTextFrameUtils.h"
57 #include "nsTextRunTransformations.h"
58 #include "MathMLTextRunFactory.h"
59 #include "nsUnicodeProperties.h"
60 #include "nsStyleUtil.h"
61 #include "nsRubyFrame.h"
62 #include "TextDrawTarget.h"
63 
64 #include "nsTextFragment.h"
65 #include "nsGkAtoms.h"
66 #include "nsFrameSelection.h"
67 #include "nsRange.h"
68 #include "nsCSSRendering.h"
69 #include "nsContentUtils.h"
70 #include "nsLineBreaker.h"
71 #include "nsIFrameInlines.h"
72 #include "mozilla/intl/WordBreaker.h"
73 #include "mozilla/ServoStyleSet.h"
74 
75 #include <algorithm>
76 #include <limits>
77 #include <type_traits>
78 #ifdef ACCESSIBILITY
79 #  include "nsAccessibilityService.h"
80 #endif
81 
82 #include "nsPrintfCString.h"
83 
84 #include "mozilla/gfx/DrawTargetRecording.h"
85 
86 #include "mozilla/UniquePtr.h"
87 #include "mozilla/dom/Element.h"
88 #include "mozilla/LookAndFeel.h"
89 #include "mozilla/ProfilerLabels.h"
90 
91 #ifdef DEBUG
92 #  undef NOISY_REFLOW
93 #  undef NOISY_TRIM
94 #else
95 #  undef NOISY_REFLOW
96 #  undef NOISY_TRIM
97 #endif
98 
99 #ifdef DrawText
100 #  undef DrawText
101 #endif
102 
103 using namespace mozilla;
104 using namespace mozilla::dom;
105 using namespace mozilla::gfx;
106 
107 typedef mozilla::layout::TextDrawTarget TextDrawTarget;
108 
NeedsToMaskPassword(nsTextFrame * aFrame)109 static bool NeedsToMaskPassword(nsTextFrame* aFrame) {
110   MOZ_ASSERT(aFrame);
111   MOZ_ASSERT(aFrame->GetContent());
112   return aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED);
113 }
114 
115 struct TabWidth {
TabWidthTabWidth116   TabWidth(uint32_t aOffset, uint32_t aWidth)
117       : mOffset(aOffset), mWidth(float(aWidth)) {}
118 
119   uint32_t mOffset;  // DOM offset relative to the current frame's offset.
120   float mWidth;      // extra space to be added at this position (in app units)
121 };
122 
123 struct nsTextFrame::TabWidthStore {
TabWidthStorensTextFrame::TabWidthStore124   explicit TabWidthStore(int32_t aValidForContentOffset)
125       : mLimit(0), mValidForContentOffset(aValidForContentOffset) {}
126 
127   // Apply tab widths to the aSpacing array, which corresponds to characters
128   // beginning at aOffset and has length aLength. (Width records outside this
129   // range will be ignored.)
130   void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
131                     uint32_t aOffset, uint32_t aLength);
132 
133   // Offset up to which tabs have been measured; positions beyond this have not
134   // been calculated yet but may be appended if needed later.  It's a DOM
135   // offset relative to the current frame's offset.
136   uint32_t mLimit;
137 
138   // Need to recalc tab offsets if frame content offset differs from this.
139   int32_t mValidForContentOffset;
140 
141   // A TabWidth record for each tab character measured so far.
142   nsTArray<TabWidth> mWidths;
143 };
144 
145 namespace {
146 
147 struct TabwidthAdaptor {
148   const nsTArray<TabWidth>& mWidths;
TabwidthAdaptor__anon4d1b6f8f0111::TabwidthAdaptor149   explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
150       : mWidths(aWidths) {}
operator []__anon4d1b6f8f0111::TabwidthAdaptor151   uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
152 };
153 
154 }  // namespace
155 
ApplySpacing(gfxTextRun::PropertyProvider::Spacing * aSpacing,uint32_t aOffset,uint32_t aLength)156 void nsTextFrame::TabWidthStore::ApplySpacing(
157     gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
158     uint32_t aLength) {
159   size_t i = 0;
160   const size_t len = mWidths.Length();
161 
162   // If aOffset is non-zero, do a binary search to find where to start
163   // processing the tab widths, in case the list is really long. (See bug
164   // 953247.)
165   // We need to start from the first entry where mOffset >= aOffset.
166   if (aOffset > 0) {
167     mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
168   }
169 
170   uint32_t limit = aOffset + aLength;
171   while (i < len) {
172     const TabWidth& tw = mWidths[i];
173     if (tw.mOffset >= limit) {
174       break;
175     }
176     aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
177     i++;
178   }
179 }
180 
181 NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty,
182                                     nsTextFrame::TabWidthStore)
183 
184 NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)
185 
186 NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)
187 
188 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
189 
190 /**
191  * A glyph observer for the change of a font glyph in a text run.
192  *
193  * This is stored in {Simple, Complex}TextRunUserData.
194  */
195 class GlyphObserver final : public gfxFont::GlyphChangeObserver {
196  public:
GlyphObserver(gfxFont * aFont,gfxTextRun * aTextRun)197   GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
198       : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
199     MOZ_ASSERT(aTextRun->GetUserData());
200   }
201   void NotifyGlyphsChanged() override;
202 
203  private:
204   gfxTextRun* mTextRun;
205 };
206 
207 static const nsFrameState TEXT_REFLOW_FLAGS =
208     TEXT_FIRST_LETTER | TEXT_START_OF_LINE | TEXT_END_OF_LINE |
209     TEXT_HYPHEN_BREAK | TEXT_TRIMMED_TRAILING_WHITESPACE |
210     TEXT_JUSTIFICATION_ENABLED | TEXT_HAS_NONCOLLAPSED_CHARACTERS |
211     TEXT_SELECTION_UNDERLINE_OVERFLOWED | TEXT_NO_RENDERED_GLYPHS;
212 
213 static const nsFrameState TEXT_WHITESPACE_FLAGS =
214     TEXT_IS_ONLY_WHITESPACE | TEXT_ISNOT_ONLY_WHITESPACE;
215 
216 /*
217  * Some general notes
218  *
219  * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
220  * transforms text to positioned glyphs. It can report the geometry of the
221  * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
222  * spacing, language, and other information.
223  *
224  * A gfxTextRun can cover more than one DOM text node. This is necessary to
225  * get kerning, ligatures and shaping for text that spans multiple text nodes
226  * but is all the same font.
227  *
228  * The userdata for a gfxTextRun object can be:
229  *
230  *   - A nsTextFrame* in the case a text run maps to only one flow. In this
231  *   case, the textrun's user data pointer is a pointer to mStartFrame for that
232  *   flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
233  *   length of the text node.
234  *
235  *   - A SimpleTextRunUserData in the case a text run maps to one flow, but we
236  *   still have to keep a list of glyph observers.
237  *
238  *   - A ComplexTextRunUserData in the case a text run maps to multiple flows,
239  *   but we need to keep a list of glyph observers.
240  *
241  *   - A TextRunUserData in the case a text run maps multiple flows, but it
242  *   doesn't have any glyph observer for changes in SVG fonts.
243  *
244  * You can differentiate between the four different cases with the
245  * IsSimpleFlow and MightHaveGlyphChanges flags.
246  *
247  * We go to considerable effort to make sure things work even if in-flow
248  * siblings have different ComputedStyles (i.e., first-letter and first-line).
249  *
250  * Our convention is that unsigned integer character offsets are offsets into
251  * the transformed string. Signed integer character offsets are offsets into
252  * the DOM string.
253  *
254  * XXX currently we don't handle hyphenated breaks between text frames where the
255  * hyphen occurs at the end of the first text frame, e.g.
256  *   <b>Kit&shy;</b>ty
257  */
258 
259 /**
260  * This is our user data for the textrun, when textRun->GetFlags2() has
261  * IsSimpleFlow set, and also MightHaveGlyphChanges.
262  *
263  * This allows having an array of observers if there are fonts whose glyphs
264  * might change, but also avoid allocation in the simple case that there aren't.
265  */
266 struct SimpleTextRunUserData {
267   nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
268   nsTextFrame* mFrame;
SimpleTextRunUserDataSimpleTextRunUserData269   explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
270 };
271 
272 /**
273  * We use an array of these objects to record which text frames
274  * are associated with the textrun. mStartFrame is the start of a list of
275  * text frames. Some sequence of its continuations are covered by the textrun.
276  * A content textnode can have at most one TextRunMappedFlow associated with it
277  * for a given textrun.
278  *
279  * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
280  * obtain the offset into the before-transformation text of the textrun. It can
281  * be positive (when a text node starts in the middle of a text run) or negative
282  * (when a text run starts in the middle of a text node). Of course it can also
283  * be zero.
284  */
285 struct TextRunMappedFlow {
286   nsTextFrame* mStartFrame;
287   int32_t mDOMOffsetToBeforeTransformOffset;
288   // The text mapped starts at mStartFrame->GetContentOffset() and is this long
289   uint32_t mContentLength;
290 };
291 
292 /**
293  * This is the type in the gfxTextRun's userdata field in the common case that
294  * the text run maps to multiple flows, but no fonts have been found with
295  * animatable glyphs.
296  *
297  * This way, we avoid allocating and constructing the extra nsTArray.
298  */
299 struct TextRunUserData {
300 #ifdef DEBUG
301   TextRunMappedFlow* mMappedFlows;
302 #endif
303   uint32_t mMappedFlowCount;
304   uint32_t mLastFlowIndex;
305 };
306 
307 /**
308  * This is our user data for the textrun, when textRun->GetFlags2() does not
309  * have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
310  */
311 struct ComplexTextRunUserData : public TextRunUserData {
312   nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
313 };
314 
315 /**
316  * This helper object computes colors used for painting, and also IME
317  * underline information. The data is computed lazily and cached as necessary.
318  * These live for just the duration of one paint operation.
319  */
320 class nsTextPaintStyle {
321  public:
322   explicit nsTextPaintStyle(nsTextFrame* aFrame);
323 
SetResolveColors(bool aResolveColors)324   void SetResolveColors(bool aResolveColors) {
325     mResolveColors = aResolveColors;
326   }
327 
328   nscolor GetTextColor();
329 
330   // SVG text has its own painting process, so we should never get its stroke
331   // property from here.
GetWebkitTextStrokeColor()332   nscolor GetWebkitTextStrokeColor() {
333     if (SVGUtils::IsInSVGTextSubtree(mFrame)) {
334       return 0;
335     }
336     return mFrame->StyleText()->mWebkitTextStrokeColor.CalcColor(mFrame);
337   }
GetWebkitTextStrokeWidth()338   float GetWebkitTextStrokeWidth() {
339     if (SVGUtils::IsInSVGTextSubtree(mFrame)) {
340       return 0.0f;
341     }
342     nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
343     return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
344   }
345 
346   /**
347    * Compute the colors for normally-selected text. Returns false if
348    * the normal selection is not being displayed.
349    */
350   bool GetSelectionColors(nscolor* aForeColor, nscolor* aBackColor);
351   void GetHighlightColors(nscolor* aForeColor, nscolor* aBackColor);
352   void GetURLSecondaryColor(nscolor* aForeColor);
353   void GetIMESelectionColors(int32_t aIndex, nscolor* aForeColor,
354                              nscolor* aBackColor);
355   // if this returns false, we don't need to draw underline.
356   bool GetSelectionUnderlineForPaint(int32_t aIndex, nscolor* aLineColor,
357                                      float* aRelativeSize, uint8_t* aStyle);
358 
359   // if this returns false, we don't need to draw underline.
360   static bool GetSelectionUnderline(nsIFrame*, int32_t aIndex,
361                                     nscolor* aLineColor, float* aRelativeSize,
362                                     uint8_t* aStyle);
363 
364   // if this returns false, no text-shadow was specified for the selection
365   // and the *aShadow parameter was not modified.
366   bool GetSelectionShadow(Span<const StyleSimpleShadow>* aShadows);
367 
PresContext() const368   nsPresContext* PresContext() const { return mPresContext; }
369 
370   enum {
371     eIndexRawInput = 0,
372     eIndexSelRawText,
373     eIndexConvText,
374     eIndexSelConvText,
375     eIndexSpellChecker
376   };
377 
GetUnderlineStyleIndexForSelectionType(SelectionType aSelectionType)378   static int32_t GetUnderlineStyleIndexForSelectionType(
379       SelectionType aSelectionType) {
380     switch (aSelectionType) {
381       case SelectionType::eIMERawClause:
382         return eIndexRawInput;
383       case SelectionType::eIMESelectedRawClause:
384         return eIndexSelRawText;
385       case SelectionType::eIMEConvertedClause:
386         return eIndexConvText;
387       case SelectionType::eIMESelectedClause:
388         return eIndexSelConvText;
389       case SelectionType::eSpellCheck:
390         return eIndexSpellChecker;
391       default:
392         NS_WARNING("non-IME selection type");
393         return eIndexRawInput;
394     }
395   }
396 
397   nscolor GetSystemFieldForegroundColor();
398   nscolor GetSystemFieldBackgroundColor();
399 
400  protected:
401   nsTextFrame* mFrame;
402   nsPresContext* mPresContext;
403   bool mInitCommonColors;
404   bool mInitSelectionColorsAndShadow;
405   bool mResolveColors;
406 
407   // Selection data
408 
409   nscolor mSelectionTextColor;
410   nscolor mSelectionBGColor;
411   RefPtr<ComputedStyle> mSelectionPseudoStyle;
412 
413   // Common data
414 
415   int32_t mSufficientContrast;
416   nscolor mFrameBackgroundColor;
417   nscolor mSystemFieldForegroundColor;
418   nscolor mSystemFieldBackgroundColor;
419 
420   // selection colors and underline info, the colors are resolved colors if
421   // mResolveColors is true (which is the default), i.e., the foreground color
422   // and background color are swapped if it's needed. And also line color will
423   // be resolved from them.
424   struct nsSelectionStyle {
425     bool mInit;
426     nscolor mTextColor;
427     nscolor mBGColor;
428     nscolor mUnderlineColor;
429     uint8_t mUnderlineStyle;
430     float mUnderlineRelativeSize;
431   };
432   nsSelectionStyle mSelectionStyle[5];
433 
434   // Color initializations
435   void InitCommonColors();
436   bool InitSelectionColorsAndShadow();
437 
438   nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
439   void InitSelectionStyle(int32_t aIndex);
440 
441   // Ensures sufficient contrast between the frame background color and the
442   // selection background color, and swaps the selection text and background
443   // colors accordingly.
444   // Only used on platforms where mSelectionTextColor !=
445   // NS_SAME_AS_FOREGROUND_COLOR
446   bool EnsureSufficientContrast(nscolor* aForeColor, nscolor* aBackColor);
447 
448   nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
449                                nscolor aBackColor);
450 };
451 
CreateUserData(uint32_t aMappedFlowCount)452 static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
453   TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
454       sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
455 #ifdef DEBUG
456   data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
457 #endif
458   data->mMappedFlowCount = aMappedFlowCount;
459   data->mLastFlowIndex = 0;
460   return data;
461 }
462 
DestroyUserData(TextRunUserData * aUserData)463 static void DestroyUserData(TextRunUserData* aUserData) {
464   if (aUserData) {
465     free(aUserData);
466   }
467 }
468 
CreateComplexUserData(uint32_t aMappedFlowCount)469 static ComplexTextRunUserData* CreateComplexUserData(
470     uint32_t aMappedFlowCount) {
471   ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
472       moz_xmalloc(sizeof(ComplexTextRunUserData) +
473                   aMappedFlowCount * sizeof(TextRunMappedFlow)));
474   new (data) ComplexTextRunUserData();
475 #ifdef DEBUG
476   data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
477 #endif
478   data->mMappedFlowCount = aMappedFlowCount;
479   data->mLastFlowIndex = 0;
480   return data;
481 }
482 
DestroyComplexUserData(ComplexTextRunUserData * aUserData)483 static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
484   if (aUserData) {
485     aUserData->~ComplexTextRunUserData();
486     free(aUserData);
487   }
488 }
489 
DestroyTextRunUserData(gfxTextRun * aTextRun)490 static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
491   MOZ_ASSERT(aTextRun->GetUserData());
492   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
493     if (aTextRun->GetFlags2() &
494         nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
495       delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
496     }
497   } else {
498     if (aTextRun->GetFlags2() &
499         nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
500       DestroyComplexUserData(
501           static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
502     } else {
503       DestroyUserData(static_cast<TextRunUserData*>(aTextRun->GetUserData()));
504     }
505   }
506   aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
507   aTextRun->SetUserData(nullptr);
508 }
509 
GetMappedFlows(const gfxTextRun * aTextRun)510 static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
511   MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
512   MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),
513              "The method should not be called for simple flows.");
514   TextRunMappedFlow* flows;
515   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
516     flows = reinterpret_cast<TextRunMappedFlow*>(
517         static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
518   } else {
519     flows = reinterpret_cast<TextRunMappedFlow*>(
520         static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
521   }
522   MOZ_ASSERT(
523       static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==
524           flows,
525       "GetMappedFlows should return the same pointer as mMappedFlows.");
526   return flows;
527 }
528 
529 /**
530  * These are utility functions just for helping with the complexity related with
531  * the text runs user data.
532  */
GetFrameForSimpleFlow(const gfxTextRun * aTextRun)533 static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
534   MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,
535              "Not so simple flow?");
536   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
537     return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
538   }
539 
540   return static_cast<nsTextFrame*>(aTextRun->GetUserData());
541 }
542 
543 /**
544  * Remove |aTextRun| from the frame continuation chain starting at
545  * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
546  * Unmark |aFrame| as a text run owner if it's the frame we start at.
547  * Return true if |aStartContinuation| is non-null and was found
548  * in the next-continuation chain of |aFrame|.
549  */
ClearAllTextRunReferences(nsTextFrame * aFrame,gfxTextRun * aTextRun,nsTextFrame * aStartContinuation,nsFrameState aWhichTextRunState)550 static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
551                                       nsTextFrame* aStartContinuation,
552                                       nsFrameState aWhichTextRunState) {
553   MOZ_ASSERT(aFrame, "null frame");
554   MOZ_ASSERT(!aStartContinuation ||
555                  (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
556                   aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==
557                       aTextRun) ||
558                  (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
559                   aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==
560                       aTextRun),
561              "wrong aStartContinuation for this text run");
562 
563   if (!aStartContinuation || aStartContinuation == aFrame) {
564     aFrame->RemoveStateBits(aWhichTextRunState);
565   } else {
566     do {
567       NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
568       aFrame = aFrame->GetNextContinuation();
569     } while (aFrame && aFrame != aStartContinuation);
570   }
571   bool found = aStartContinuation == aFrame;
572   while (aFrame) {
573     NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
574     if (!aFrame->RemoveTextRun(aTextRun)) {
575       break;
576     }
577     aFrame = aFrame->GetNextContinuation();
578   }
579 
580   MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
581   return found;
582 }
583 
584 /**
585  * Kill all references to |aTextRun| starting at |aStartContinuation|.
586  * It could be referenced by any of its owners, and all their in-flows.
587  * If |aStartContinuation| is null then process all userdata frames
588  * and their continuations.
589  * @note the caller is expected to take care of possibly destroying the
590  * text run if all userdata frames were reset (userdata is deallocated
591  * by this function though). The caller can detect this has occured by
592  * checking |aTextRun->GetUserData() == nullptr|.
593  */
UnhookTextRunFromFrames(gfxTextRun * aTextRun,nsTextFrame * aStartContinuation)594 static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
595                                     nsTextFrame* aStartContinuation) {
596   if (!aTextRun->GetUserData()) {
597     return;
598   }
599 
600   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
601     nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
602     nsFrameState whichTextRunState =
603         userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
604             ? TEXT_IN_TEXTRUN_USER_DATA
605             : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
606     DebugOnly<bool> found = ClearAllTextRunReferences(
607         userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
608     NS_ASSERTION(!aStartContinuation || found,
609                  "aStartContinuation wasn't found in simple flow text run");
610     if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
611       DestroyTextRunUserData(aTextRun);
612     }
613   } else {
614     auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
615     TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
616     int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
617     for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
618       nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
619       nsFrameState whichTextRunState =
620           userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
621               ? TEXT_IN_TEXTRUN_USER_DATA
622               : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
623       bool found = ClearAllTextRunReferences(
624           userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
625       if (found) {
626         if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
627           destroyFromIndex = i + 1;
628         } else {
629           destroyFromIndex = i;
630         }
631         aStartContinuation = nullptr;
632       }
633     }
634     NS_ASSERTION(destroyFromIndex >= 0,
635                  "aStartContinuation wasn't found in multi flow text run");
636     if (destroyFromIndex == 0) {
637       DestroyTextRunUserData(aTextRun);
638     } else {
639       userData->mMappedFlowCount = uint32_t(destroyFromIndex);
640       if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
641         userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
642       }
643     }
644   }
645 }
646 
InvalidateFrameDueToGlyphsChanged(nsIFrame * aFrame)647 static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
648   MOZ_ASSERT(aFrame);
649 
650   PresShell* presShell = aFrame->PresShell();
651   for (nsIFrame* f = aFrame; f;
652        f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
653     f->InvalidateFrame();
654 
655     // If this is a non-display text frame within SVG <text>, we need
656     // to reflow the SVGTextFrame. (This is similar to reflowing the
657     // SVGTextFrame in response to style changes, in
658     // SVGTextFrame::DidSetComputedStyle.)
659     if (SVGUtils::IsInSVGTextSubtree(f) &&
660         f->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
661       auto svgTextFrame = static_cast<SVGTextFrame*>(
662           nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
663       svgTextFrame->ScheduleReflowSVGNonDisplayText(IntrinsicDirty::Resize);
664     } else {
665       // Theoretically we could just update overflow areas, perhaps using
666       // OverflowChangedTracker, but that would do a bunch of work eagerly that
667       // we should probably do lazily here since there could be a lot
668       // of text frames affected and we'd like to coalesce the work. So that's
669       // not easy to do well.
670       presShell->FrameNeedsReflow(f, IntrinsicDirty::Resize, NS_FRAME_IS_DIRTY);
671     }
672   }
673 }
674 
NotifyGlyphsChanged()675 void GlyphObserver::NotifyGlyphsChanged() {
676   if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
677     InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
678     return;
679   }
680 
681   auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
682   TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
683   for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
684     InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
685   }
686 }
687 
GetContentEnd() const688 int32_t nsTextFrame::GetContentEnd() const {
689   nsTextFrame* next = GetNextContinuation();
690   return next ? next->GetContentOffset() : TextFragment()->GetLength();
691 }
692 
693 struct FlowLengthProperty {
694   int32_t mStartOffset;
695   // The offset of the next fixed continuation after mStartOffset, or
696   // of the end of the text if there is none
697   int32_t mEndFlowOffset;
698 };
699 
GetInFlowContentLength()700 int32_t nsTextFrame::GetInFlowContentLength() {
701   if (!(mState & NS_FRAME_IS_BIDI)) {
702     return mContent->TextLength() - mContentOffset;
703   }
704 
705   FlowLengthProperty* flowLength =
706       mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
707           ? static_cast<FlowLengthProperty*>(
708                 mContent->GetProperty(nsGkAtoms::flowlength))
709           : nullptr;
710 
711   /**
712    * This frame must start inside the cached flow. If the flow starts at
713    * mContentOffset but this frame is empty, logically it might be before the
714    * start of the cached flow.
715    */
716   if (flowLength &&
717       (flowLength->mStartOffset < mContentOffset ||
718        (flowLength->mStartOffset == mContentOffset &&
719         GetContentEnd() > mContentOffset)) &&
720       flowLength->mEndFlowOffset > mContentOffset) {
721 #ifdef DEBUG
722     NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
723                  "frame crosses fixed continuation boundary");
724 #endif
725     return flowLength->mEndFlowOffset - mContentOffset;
726   }
727 
728   nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
729   int32_t endFlow =
730       nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
731 
732   if (!flowLength) {
733     flowLength = new FlowLengthProperty;
734     if (NS_FAILED(mContent->SetProperty(
735             nsGkAtoms::flowlength, flowLength,
736             nsINode::DeleteProperty<FlowLengthProperty>))) {
737       delete flowLength;
738       flowLength = nullptr;
739     }
740     mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
741   }
742   if (flowLength) {
743     flowLength->mStartOffset = mContentOffset;
744     flowLength->mEndFlowOffset = endFlow;
745   }
746 
747   return endFlow - mContentOffset;
748 }
749 
750 // Smarter versions of dom::IsSpaceCharacter.
751 // Unicode is really annoying; sometimes a space character isn't whitespace ---
752 // when it combines with another character
753 // So we have several versions of IsSpace for use in different contexts.
754 
IsSpaceCombiningSequenceTail(const nsTextFragment * aFrag,uint32_t aPos)755 static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag,
756                                          uint32_t aPos) {
757   NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
758   if (!aFrag->Is2b()) return false;
759   return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
760       aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
761 }
762 
763 // Check whether aPos is a space for CSS 'word-spacing' purposes
IsCSSWordSpacingSpace(const nsTextFragment * aFrag,uint32_t aPos,const nsTextFrame * aFrame,const nsStyleText * aStyleText)764 static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
765                                   const nsTextFrame* aFrame,
766                                   const nsStyleText* aStyleText) {
767   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
768 
769   char16_t ch = aFrag->CharAt(aPos);
770   switch (ch) {
771     case ' ':
772     case CH_NBSP:
773       return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
774     case '\r':
775     case '\t':
776       return !aStyleText->WhiteSpaceIsSignificant();
777     case '\n':
778       return !aStyleText->NewlineIsSignificant(aFrame);
779     default:
780       return false;
781   }
782 }
783 
784 constexpr char16_t kOghamSpaceMark = 0x1680;
785 
786 // Check whether the string aChars/aLength starts with space that's
787 // trimmable according to CSS 'white-space:normal/nowrap'.
IsTrimmableSpace(const char16_t * aChars,uint32_t aLength)788 static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
789   NS_ASSERTION(aLength > 0, "No text for IsSpace!");
790 
791   char16_t ch = *aChars;
792   if (ch == ' ' || ch == kOghamSpaceMark) {
793     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
794                                                            aLength - 1);
795   }
796   return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
797 }
798 
799 // Check whether the character aCh is trimmable according to CSS
800 // 'white-space:normal/nowrap'
IsTrimmableSpace(char aCh)801 static bool IsTrimmableSpace(char aCh) {
802   return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
803 }
804 
IsTrimmableSpace(const nsTextFragment * aFrag,uint32_t aPos,const nsStyleText * aStyleText,bool aAllowHangingWS=false)805 static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
806                              const nsStyleText* aStyleText,
807                              bool aAllowHangingWS = false) {
808   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
809 
810   switch (aFrag->CharAt(aPos)) {
811     case ' ':
812     case kOghamSpaceMark:
813       return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
814              !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
815     case '\n':
816       return !aStyleText->NewlineIsSignificantStyle() &&
817              aStyleText->mWhiteSpace != mozilla::StyleWhiteSpace::PreSpace;
818     case '\t':
819     case '\r':
820     case '\f':
821       return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
822     default:
823       return false;
824   }
825 }
826 
IsSelectionInlineWhitespace(const nsTextFragment * aFrag,uint32_t aPos)827 static bool IsSelectionInlineWhitespace(const nsTextFragment* aFrag,
828                                         uint32_t aPos) {
829   NS_ASSERTION(aPos < aFrag->GetLength(),
830                "No text for IsSelectionInlineWhitespace!");
831   char16_t ch = aFrag->CharAt(aPos);
832   if (ch == ' ' || ch == CH_NBSP)
833     return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
834   return ch == '\t' || ch == '\f';
835 }
836 
IsSelectionNewline(const nsTextFragment * aFrag,uint32_t aPos)837 static bool IsSelectionNewline(const nsTextFragment* aFrag, uint32_t aPos) {
838   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSelectionNewline!");
839   char16_t ch = aFrag->CharAt(aPos);
840   return ch == '\n' || ch == '\r';
841 }
842 
843 // Count the amount of trimmable whitespace (as per CSS
844 // 'white-space:normal/nowrap') in a text fragment. The first
845 // character is at offset aStartOffset; the maximum number of characters
846 // to check is aLength. aDirection is -1 or 1 depending on whether we should
847 // progress backwards or forwards.
GetTrimmableWhitespaceCount(const nsTextFragment * aFrag,int32_t aStartOffset,int32_t aLength,int32_t aDirection)848 static uint32_t GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
849                                             int32_t aStartOffset,
850                                             int32_t aLength,
851                                             int32_t aDirection) {
852   if (!aLength) {
853     return 0;
854   }
855 
856   int32_t count = 0;
857   if (aFrag->Is2b()) {
858     const char16_t* str = aFrag->Get2b() + aStartOffset;
859     int32_t fragLen = aFrag->GetLength() - aStartOffset;
860     for (; count < aLength; ++count) {
861       if (!IsTrimmableSpace(str, fragLen)) break;
862       str += aDirection;
863       fragLen -= aDirection;
864     }
865   } else {
866     const char* str = aFrag->Get1b() + aStartOffset;
867     for (; count < aLength; ++count) {
868       if (!IsTrimmableSpace(*str)) break;
869       str += aDirection;
870     }
871   }
872   return count;
873 }
874 
IsAllWhitespace(const nsTextFragment * aFrag,bool aAllowNewline)875 static bool IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) {
876   if (aFrag->Is2b()) return false;
877   int32_t len = aFrag->GetLength();
878   const char* str = aFrag->Get1b();
879   for (int32_t i = 0; i < len; ++i) {
880     char ch = str[i];
881     if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
882       continue;
883     return false;
884   }
885   return true;
886 }
887 
ClearObserversFromTextRun(gfxTextRun * aTextRun)888 static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
889   if (!(aTextRun->GetFlags2() &
890         nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
891     return;
892   }
893 
894   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
895     static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
896         ->mGlyphObservers.Clear();
897   } else {
898     static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
899         ->mGlyphObservers.Clear();
900   }
901 }
902 
CreateObserversForAnimatedGlyphs(gfxTextRun * aTextRun)903 static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
904   if (!aTextRun->GetUserData()) {
905     return;
906   }
907 
908   ClearObserversFromTextRun(aTextRun);
909 
910   nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
911   uint32_t numGlyphRuns;
912   const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns);
913   for (uint32_t i = 0; i < numGlyphRuns; ++i) {
914     gfxFont* font = glyphRuns[i].mFont;
915     if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
916       fontsWithAnimatedGlyphs.AppendElement(font);
917     }
918   }
919   if (fontsWithAnimatedGlyphs.IsEmpty()) {
920     // NB: Theoretically, we should clear the MightHaveGlyphChanges
921     // here. That would involve de-allocating the simple user data struct if
922     // present too, and resetting the pointer to the frame. In practice, I
923     // don't think worth doing that work here, given the flag's only purpose is
924     // to distinguish what kind of user data is there.
925     return;
926   }
927 
928   nsTArray<UniquePtr<GlyphObserver>>* observers;
929 
930   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
931     // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
932     // appropriate.
933     if (!(aTextRun->GetFlags2() &
934           nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
935       auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
936       aTextRun->SetUserData(new SimpleTextRunUserData(frame));
937     }
938 
939     auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
940     observers = &data->mGlyphObservers;
941   } else {
942     if (!(aTextRun->GetFlags2() &
943           nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
944       auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
945       TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
946       ComplexTextRunUserData* data =
947           CreateComplexUserData(oldData->mMappedFlowCount);
948       TextRunMappedFlow* dataMappedFlows =
949           reinterpret_cast<TextRunMappedFlow*>(data + 1);
950       data->mLastFlowIndex = oldData->mLastFlowIndex;
951       for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
952         dataMappedFlows[i] = oldMappedFlows[i];
953       }
954       DestroyUserData(oldData);
955       aTextRun->SetUserData(data);
956     }
957     auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
958     observers = &data->mGlyphObservers;
959   }
960 
961   aTextRun->SetFlagBits(nsTextFrameUtils::Flags::MightHaveGlyphChanges);
962 
963   for (auto font : fontsWithAnimatedGlyphs) {
964     observers->AppendElement(new GlyphObserver(font, aTextRun));
965   }
966 }
967 
968 /**
969  * This class accumulates state as we scan a paragraph of text. It detects
970  * textrun boundaries (changes from text to non-text, hard
971  * line breaks, and font changes) and builds a gfxTextRun at each boundary.
972  * It also detects linebreaker run boundaries (changes from text to non-text,
973  * and hard line breaks) and at each boundary runs the linebreaker to compute
974  * potential line breaks. It also records actual line breaks to store them in
975  * the textruns.
976  */
977 class BuildTextRunsScanner {
978  public:
BuildTextRunsScanner(nsPresContext * aPresContext,DrawTarget * aDrawTarget,nsIFrame * aLineContainer,nsTextFrame::TextRunType aWhichTextRun)979   BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
980                        nsIFrame* aLineContainer,
981                        nsTextFrame::TextRunType aWhichTextRun)
982       : mDrawTarget(aDrawTarget),
983         mLineContainer(aLineContainer),
984         mCommonAncestorWithLastFrame(nullptr),
985         mMissingFonts(aPresContext->MissingFontRecorder()),
986         mBidiEnabled(aPresContext->BidiEnabled()),
987         mStartOfLine(true),
988         mSkipIncompleteTextRuns(false),
989         mCanStopOnThisLine(false),
990         mWhichTextRun(aWhichTextRun),
991         mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
992         mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
993     ResetRunInfo();
994   }
~BuildTextRunsScanner()995   ~BuildTextRunsScanner() {
996     NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
997     NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
998     NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
999   }
1000 
SetAtStartOfLine()1001   void SetAtStartOfLine() {
1002     mStartOfLine = true;
1003     mCanStopOnThisLine = false;
1004   }
SetSkipIncompleteTextRuns(bool aSkip)1005   void SetSkipIncompleteTextRuns(bool aSkip) {
1006     mSkipIncompleteTextRuns = aSkip;
1007   }
SetCommonAncestorWithLastFrame(nsIFrame * aFrame)1008   void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
1009     mCommonAncestorWithLastFrame = aFrame;
1010   }
CanStopOnThisLine()1011   bool CanStopOnThisLine() { return mCanStopOnThisLine; }
GetCommonAncestorWithLastFrame()1012   nsIFrame* GetCommonAncestorWithLastFrame() {
1013     return mCommonAncestorWithLastFrame;
1014   }
LiftCommonAncestorWithLastFrameToParent(nsIFrame * aFrame)1015   void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
1016     if (mCommonAncestorWithLastFrame &&
1017         mCommonAncestorWithLastFrame->GetParent() == aFrame) {
1018       mCommonAncestorWithLastFrame = aFrame;
1019     }
1020   }
1021   void ScanFrame(nsIFrame* aFrame);
1022   bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
1023   void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
1024   void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
ResetRunInfo()1025   void ResetRunInfo() {
1026     mLastFrame = nullptr;
1027     mMappedFlows.Clear();
1028     mLineBreakBeforeFrames.Clear();
1029     mMaxTextLength = 0;
1030     mDoubleByteText = false;
1031   }
1032   void AccumulateRunInfo(nsTextFrame* aFrame);
1033   /**
1034    * @return null to indicate either textrun construction failed or
1035    * we constructed just a partial textrun to set up linebreaker and other
1036    * state for following textruns.
1037    */
1038   already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
1039   bool SetupLineBreakerContext(gfxTextRun* aTextRun);
1040   void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
1041   nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
1042   void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1043   void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1044   struct FindBoundaryState {
1045     nsIFrame* mStopAtFrame;
1046     nsTextFrame* mFirstTextFrame;
1047     nsTextFrame* mLastTextFrame;
1048     bool mSeenTextRunBoundaryOnLaterLine;
1049     bool mSeenTextRunBoundaryOnThisLine;
1050     bool mSeenSpaceForLineBreakingOnThisLine;
1051     nsTArray<char16_t>& mBuffer;
1052   };
1053   enum FindBoundaryResult {
1054     FB_CONTINUE,
1055     FB_STOPPED_AT_STOP_FRAME,
1056     FB_FOUND_VALID_TEXTRUN_BOUNDARY
1057   };
1058   FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
1059                                     FindBoundaryState* aState);
1060 
1061   bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
1062 
1063   // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
1064   // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
1065   // continuations starting from mStartFrame are a sequence of in-flow frames).
1066   struct MappedFlow {
1067     nsTextFrame* mStartFrame;
1068     nsTextFrame* mEndFrame;
1069     // When we consider breaking between elements, the nearest common
1070     // ancestor of the elements containing the characters is the one whose
1071     // CSS 'white-space' property governs. So this records the nearest common
1072     // ancestor of mStartFrame and the previous text frame, or null if there
1073     // was no previous text frame on this line.
1074     nsIFrame* mAncestorControllingInitialBreak;
1075 
GetContentEndBuildTextRunsScanner::MappedFlow1076     int32_t GetContentEnd() {
1077       return mEndFrame ? mEndFrame->GetContentOffset()
1078                        : mStartFrame->TextFragment()->GetLength();
1079     }
1080   };
1081 
1082   class BreakSink final : public nsILineBreakSink {
1083    public:
BreakSink(gfxTextRun * aTextRun,DrawTarget * aDrawTarget,uint32_t aOffsetIntoTextRun)1084     BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
1085               uint32_t aOffsetIntoTextRun)
1086         : mTextRun(aTextRun),
1087           mDrawTarget(aDrawTarget),
1088           mOffsetIntoTextRun(aOffsetIntoTextRun) {}
1089 
SetBreaks(uint32_t aOffset,uint32_t aLength,uint8_t * aBreakBefore)1090     void SetBreaks(uint32_t aOffset, uint32_t aLength,
1091                    uint8_t* aBreakBefore) final {
1092       gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
1093                               aOffset + mOffsetIntoTextRun + aLength);
1094       if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
1095         // Be conservative and assume that some breaks have been set
1096         mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::NoBreaks);
1097       }
1098     }
1099 
SetCapitalization(uint32_t aOffset,uint32_t aLength,bool * aCapitalize)1100     void SetCapitalization(uint32_t aOffset, uint32_t aLength,
1101                            bool* aCapitalize) final {
1102       MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,
1103                  "Text run should be transformed!");
1104       if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
1105         nsTransformedTextRun* transformedTextRun =
1106             static_cast<nsTransformedTextRun*>(mTextRun.get());
1107         transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
1108                                               aLength, aCapitalize);
1109       }
1110     }
1111 
Finish(gfxMissingFontRecorder * aMFR)1112     void Finish(gfxMissingFontRecorder* aMFR) {
1113       MOZ_ASSERT(
1114           !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::UnusedFlags),
1115           "Flag set that should never be set! (memory safety error?)");
1116       if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
1117         nsTransformedTextRun* transformedTextRun =
1118             static_cast<nsTransformedTextRun*>(mTextRun.get());
1119         transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
1120       }
1121       // The way nsTransformedTextRun is implemented, its glyph runs aren't
1122       // available until after nsTransformedTextRun::FinishSettingProperties()
1123       // is called. So that's why we defer checking for animated glyphs to here.
1124       CreateObserversForAnimatedGlyphs(mTextRun);
1125     }
1126 
1127     RefPtr<gfxTextRun> mTextRun;
1128     DrawTarget* mDrawTarget;
1129     uint32_t mOffsetIntoTextRun;
1130   };
1131 
1132  private:
1133   AutoTArray<MappedFlow, 10> mMappedFlows;
1134   AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
1135   AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
1136   nsLineBreaker mLineBreaker;
1137   RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
1138   DrawTarget* mDrawTarget;
1139   nsIFrame* mLineContainer;
1140   nsTextFrame* mLastFrame;
1141   // The common ancestor of the current frame and the previous leaf frame
1142   // on the line, or null if there was no previous leaf frame.
1143   nsIFrame* mCommonAncestorWithLastFrame;
1144   gfxMissingFontRecorder* mMissingFonts;
1145   // mMaxTextLength is an upper bound on the size of the text in all mapped
1146   // frames The value UINT32_MAX represents overflow; text will be discarded
1147   uint32_t mMaxTextLength;
1148   bool mDoubleByteText;
1149   bool mBidiEnabled;
1150   bool mStartOfLine;
1151   bool mSkipIncompleteTextRuns;
1152   bool mCanStopOnThisLine;
1153   nsTextFrame::TextRunType mWhichTextRun;
1154   uint8_t mNextRunContextInfo;
1155   uint8_t mCurrentRunContextInfo;
1156 };
1157 
FindLineContainer(nsIFrame * aFrame)1158 static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
1159   while (aFrame && (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
1160                     aFrame->CanContinueTextRun())) {
1161     aFrame = aFrame->GetParent();
1162   }
1163   return aFrame;
1164 }
1165 
IsLineBreakingWhiteSpace(char16_t aChar)1166 static bool IsLineBreakingWhiteSpace(char16_t aChar) {
1167   // 0x0A (\n) is not handled as white-space by the line breaker, since
1168   // we break before it, if it isn't transformed to a normal space.
1169   // (If we treat it as normal white-space then we'd only break after it.)
1170   // However, it does induce a line break or is converted to a regular
1171   // space, and either way it can be used to bound the region of text
1172   // that needs to be analyzed for line breaking.
1173   return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
1174 }
1175 
TextContainsLineBreakerWhiteSpace(const void * aText,uint32_t aLength,bool aIsDoubleByte)1176 static bool TextContainsLineBreakerWhiteSpace(const void* aText,
1177                                               uint32_t aLength,
1178                                               bool aIsDoubleByte) {
1179   if (aIsDoubleByte) {
1180     const char16_t* chars = static_cast<const char16_t*>(aText);
1181     for (uint32_t i = 0; i < aLength; ++i) {
1182       if (IsLineBreakingWhiteSpace(chars[i])) return true;
1183     }
1184     return false;
1185   } else {
1186     const uint8_t* chars = static_cast<const uint8_t*>(aText);
1187     for (uint32_t i = 0; i < aLength; ++i) {
1188       if (IsLineBreakingWhiteSpace(chars[i])) return true;
1189     }
1190     return false;
1191   }
1192 }
1193 
GetCSSWhitespaceToCompressionMode(nsTextFrame * aFrame,const nsStyleText * aStyleText)1194 static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
1195     nsTextFrame* aFrame, const nsStyleText* aStyleText) {
1196   switch (aStyleText->mWhiteSpace) {
1197     case StyleWhiteSpace::Normal:
1198     case StyleWhiteSpace::Nowrap:
1199       return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
1200     case StyleWhiteSpace::Pre:
1201     case StyleWhiteSpace::PreWrap:
1202     case StyleWhiteSpace::BreakSpaces:
1203       if (!aStyleText->NewlineIsSignificant(aFrame)) {
1204         // If newline is set to be preserved, but then suppressed,
1205         // transform newline to space.
1206         return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
1207       }
1208       return nsTextFrameUtils::COMPRESS_NONE;
1209     case StyleWhiteSpace::PreSpace:
1210       return nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
1211     case StyleWhiteSpace::PreLine:
1212       return nsTextFrameUtils::COMPRESS_WHITESPACE;
1213     default:
1214       MOZ_ASSERT_UNREACHABLE("Unknown white-space value");
1215       return nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE;
1216   }
1217 }
1218 
1219 struct FrameTextTraversal {
FrameTextTraversalFrameTextTraversal1220   FrameTextTraversal()
1221       : mFrameToScan(nullptr),
1222         mOverflowFrameToScan(nullptr),
1223         mScanSiblings(false),
1224         mLineBreakerCanCrossFrameBoundary(false),
1225         mTextRunCanCrossFrameBoundary(false) {}
1226 
1227   // These fields identify which frames should be recursively scanned
1228   // The first normal frame to scan (or null, if no such frame should be
1229   // scanned)
1230   nsIFrame* mFrameToScan;
1231   // The first overflow frame to scan (or null, if no such frame should be
1232   // scanned)
1233   nsIFrame* mOverflowFrameToScan;
1234   // Whether to scan the siblings of
1235   // mFrameToDescendInto/mOverflowFrameToDescendInto
1236   bool mScanSiblings;
1237 
1238   // These identify the boundaries of the context required for
1239   // line breaking or textrun construction
1240   bool mLineBreakerCanCrossFrameBoundary;
1241   bool mTextRunCanCrossFrameBoundary;
1242 
NextFrameToScanFrameTextTraversal1243   nsIFrame* NextFrameToScan() {
1244     nsIFrame* f;
1245     if (mFrameToScan) {
1246       f = mFrameToScan;
1247       mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1248     } else if (mOverflowFrameToScan) {
1249       f = mOverflowFrameToScan;
1250       mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1251     } else {
1252       f = nullptr;
1253     }
1254     return f;
1255   }
1256 };
1257 
CanTextCrossFrameBoundary(nsIFrame * aFrame)1258 static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
1259   FrameTextTraversal result;
1260 
1261   bool continuesTextRun = aFrame->CanContinueTextRun();
1262   if (aFrame->IsPlaceholderFrame()) {
1263     // placeholders are "invisible", so a text run should be able to span
1264     // across one. But don't descend into the out-of-flow.
1265     result.mLineBreakerCanCrossFrameBoundary = true;
1266     if (continuesTextRun) {
1267       // ... Except for first-letter floats, which are really in-flow
1268       // from the point of view of capitalization etc, so we'd better
1269       // descend into them. But we actually need to break the textrun for
1270       // first-letter floats since things look bad if, say, we try to make a
1271       // ligature across the float boundary.
1272       result.mFrameToScan =
1273           (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
1274     } else {
1275       result.mTextRunCanCrossFrameBoundary = true;
1276     }
1277   } else {
1278     if (continuesTextRun) {
1279       result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
1280       result.mOverflowFrameToScan =
1281           aFrame->GetChildList(nsIFrame::kOverflowList).FirstChild();
1282       NS_WARNING_ASSERTION(
1283           !result.mOverflowFrameToScan,
1284           "Scanning overflow inline frames is something we should avoid");
1285       result.mScanSiblings = true;
1286       result.mTextRunCanCrossFrameBoundary = true;
1287       result.mLineBreakerCanCrossFrameBoundary = true;
1288     } else {
1289       MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
1290                  "Shouldn't call this method for ruby text container");
1291     }
1292   }
1293   return result;
1294 }
1295 
FindBoundaries(nsIFrame * aFrame,FindBoundaryState * aState)1296 BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
1297     nsIFrame* aFrame, FindBoundaryState* aState) {
1298   LayoutFrameType frameType = aFrame->Type();
1299   if (frameType == LayoutFrameType::RubyTextContainer) {
1300     // Don't stop a text run for ruby text container. We want ruby text
1301     // containers to be skipped, but continue the text run across them.
1302     return FB_CONTINUE;
1303   }
1304 
1305   nsTextFrame* textFrame = frameType == LayoutFrameType::Text
1306                                ? static_cast<nsTextFrame*>(aFrame)
1307                                : nullptr;
1308   if (textFrame) {
1309     if (aState->mLastTextFrame &&
1310         textFrame != aState->mLastTextFrame->GetNextInFlow() &&
1311         !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
1312       aState->mSeenTextRunBoundaryOnThisLine = true;
1313       if (aState->mSeenSpaceForLineBreakingOnThisLine)
1314         return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1315     }
1316     if (!aState->mFirstTextFrame) {
1317       aState->mFirstTextFrame = textFrame;
1318     }
1319     aState->mLastTextFrame = textFrame;
1320   }
1321 
1322   if (aFrame == aState->mStopAtFrame) return FB_STOPPED_AT_STOP_FRAME;
1323 
1324   if (textFrame) {
1325     if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1326       return FB_CONTINUE;
1327     }
1328     const nsTextFragment* frag = textFrame->TextFragment();
1329     uint32_t start = textFrame->GetContentOffset();
1330     uint32_t length = textFrame->GetContentLength();
1331     const void* text;
1332     if (frag->Is2b()) {
1333       // It is possible that we may end up removing all whitespace in
1334       // a piece of text because of The White Space Processing Rules,
1335       // so we need to transform it before we can check existence of
1336       // such whitespaces.
1337       aState->mBuffer.EnsureLengthAtLeast(length);
1338       nsTextFrameUtils::CompressionMode compression =
1339           GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
1340       uint8_t incomingFlags = 0;
1341       gfxSkipChars skipChars;
1342       nsTextFrameUtils::Flags analysisFlags;
1343       char16_t* bufStart = aState->mBuffer.Elements();
1344       char16_t* bufEnd = nsTextFrameUtils::TransformText(
1345           frag->Get2b() + start, length, bufStart, compression, &incomingFlags,
1346           &skipChars, &analysisFlags);
1347       text = bufStart;
1348       length = bufEnd - bufStart;
1349     } else {
1350       // If the text only contains ASCII characters, it is currently
1351       // impossible that TransformText would remove all whitespaces,
1352       // and thus the check below should return the same result for
1353       // transformed text and original text. So we don't need to try
1354       // transforming it here.
1355       text = static_cast<const void*>(frag->Get1b() + start);
1356     }
1357     if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
1358       aState->mSeenSpaceForLineBreakingOnThisLine = true;
1359       if (aState->mSeenTextRunBoundaryOnLaterLine) {
1360         return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1361       }
1362     }
1363     return FB_CONTINUE;
1364   }
1365 
1366   FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
1367   if (!traversal.mTextRunCanCrossFrameBoundary) {
1368     aState->mSeenTextRunBoundaryOnThisLine = true;
1369     if (aState->mSeenSpaceForLineBreakingOnThisLine)
1370       return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1371   }
1372 
1373   for (nsIFrame* f = traversal.NextFrameToScan(); f;
1374        f = traversal.NextFrameToScan()) {
1375     FindBoundaryResult result = FindBoundaries(f, aState);
1376     if (result != FB_CONTINUE) return result;
1377   }
1378 
1379   if (!traversal.mTextRunCanCrossFrameBoundary) {
1380     aState->mSeenTextRunBoundaryOnThisLine = true;
1381     if (aState->mSeenSpaceForLineBreakingOnThisLine)
1382       return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1383   }
1384 
1385   return FB_CONTINUE;
1386 }
1387 
1388 // build text runs for the 200 lines following aForFrame, and stop after that
1389 // when we get a chance.
1390 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1391 
1392 /**
1393  * General routine for building text runs. This is hairy because of the need
1394  * to build text runs that span content nodes.
1395  *
1396  * @param aContext The gfxContext we're using to construct this text run.
1397  * @param aForFrame The nsTextFrame for which we're building this text run.
1398  * @param aLineContainer the line container containing aForFrame; if null,
1399  *        we'll walk the ancestors to find it.  It's required to be non-null
1400  *        when aForFrameLine is non-null.
1401  * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1402  *        out the line (slowly)
1403  * @param aWhichTextRun The type of text run we want to build. If font inflation
1404  *        is enabled, this will be eInflated, otherwise it's eNotInflated.
1405  */
BuildTextRuns(DrawTarget * aDrawTarget,nsTextFrame * aForFrame,nsIFrame * aLineContainer,const nsLineList::iterator * aForFrameLine,nsTextFrame::TextRunType aWhichTextRun)1406 static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
1407                           nsIFrame* aLineContainer,
1408                           const nsLineList::iterator* aForFrameLine,
1409                           nsTextFrame::TextRunType aWhichTextRun) {
1410   MOZ_ASSERT(aForFrame, "for no frame?");
1411   NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container");
1412 
1413   nsIFrame* lineContainerChild = aForFrame;
1414   if (!aLineContainer) {
1415     if (aForFrame->IsFloatingFirstLetterChild()) {
1416       lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
1417     }
1418     aLineContainer = FindLineContainer(lineContainerChild);
1419   } else {
1420     NS_ASSERTION(
1421         (aLineContainer == FindLineContainer(aForFrame) ||
1422          (aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),
1423         "Wrong line container hint");
1424   }
1425 
1426   if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
1427     aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
1428     if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
1429       aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
1430     }
1431   }
1432   if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
1433     aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
1434   }
1435 
1436   nsPresContext* presContext = aLineContainer->PresContext();
1437   BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
1438                                aWhichTextRun);
1439 
1440   nsBlockFrame* block = do_QueryFrame(aLineContainer);
1441 
1442   if (!block) {
1443     nsIFrame* textRunContainer = aLineContainer;
1444     if (aLineContainer->IsRubyTextContainerFrame()) {
1445       textRunContainer = aForFrame;
1446       while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
1447         textRunContainer = textRunContainer->GetParent();
1448       }
1449       MOZ_ASSERT(textRunContainer &&
1450                  textRunContainer->GetParent() == aLineContainer);
1451     } else {
1452       NS_ASSERTION(
1453           !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
1454           "Breakable non-block line containers other than "
1455           "ruby text container is not supported");
1456     }
1457     // Just loop through all the children of the linecontainer ... it's really
1458     // just one line
1459     scanner.SetAtStartOfLine();
1460     scanner.SetCommonAncestorWithLastFrame(nullptr);
1461     for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
1462       scanner.ScanFrame(child);
1463     }
1464     // Set mStartOfLine so FlushFrames knows its textrun ends a line
1465     scanner.SetAtStartOfLine();
1466     scanner.FlushFrames(true, false);
1467     return;
1468   }
1469 
1470   // Find the line containing 'lineContainerChild'.
1471 
1472   bool isValid = true;
1473   nsBlockInFlowLineIterator backIterator(block, &isValid);
1474   if (aForFrameLine) {
1475     backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
1476   } else {
1477     backIterator =
1478         nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
1479     NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
1480     NS_ASSERTION(backIterator.GetContainer() == block,
1481                  "Someone lied to us about the block");
1482   }
1483   nsBlockFrame::LineIterator startLine = backIterator.GetLine();
1484 
1485   // Find a line where we can start building text runs. We choose the last line
1486   // where:
1487   // -- there is a textrun boundary between the start of the line and the
1488   // start of aForFrame
1489   // -- there is a space between the start of the line and the textrun boundary
1490   // (this is so we can be sure the line breaks will be set properly
1491   // on the textruns we construct).
1492   // The possibly-partial text runs up to and including the first space
1493   // are not reconstructed. We construct partial text runs for that text ---
1494   // for the sake of simplifying the code and feeding the linebreaker ---
1495   // but we discard them instead of assigning them to frames.
1496   // This is a little awkward because we traverse lines in the reverse direction
1497   // but we traverse the frames in each line in the forward direction.
1498   nsBlockInFlowLineIterator forwardIterator = backIterator;
1499   nsIFrame* stopAtFrame = lineContainerChild;
1500   nsTextFrame* nextLineFirstTextFrame = nullptr;
1501   AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
1502   bool seenTextRunBoundaryOnLaterLine = false;
1503   bool mayBeginInTextRun = true;
1504   while (true) {
1505     forwardIterator = backIterator;
1506     nsBlockFrame::LineIterator line = backIterator.GetLine();
1507     if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
1508       mayBeginInTextRun = false;
1509       break;
1510     }
1511 
1512     BuildTextRunsScanner::FindBoundaryState state = {
1513         stopAtFrame, nullptr, nullptr, bool(seenTextRunBoundaryOnLaterLine),
1514         false,       false,   buffer};
1515     nsIFrame* child = line->mFirstChild;
1516     bool foundBoundary = false;
1517     for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1518       BuildTextRunsScanner::FindBoundaryResult result =
1519           scanner.FindBoundaries(child, &state);
1520       if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
1521         foundBoundary = true;
1522         break;
1523       } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
1524         break;
1525       }
1526       child = child->GetNextSibling();
1527     }
1528     if (foundBoundary) break;
1529     if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
1530         !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame,
1531                                              nextLineFirstTextFrame)) {
1532       // Found a usable textrun boundary at the end of the line
1533       if (state.mSeenSpaceForLineBreakingOnThisLine) break;
1534       seenTextRunBoundaryOnLaterLine = true;
1535     } else if (state.mSeenTextRunBoundaryOnThisLine) {
1536       seenTextRunBoundaryOnLaterLine = true;
1537     }
1538     stopAtFrame = nullptr;
1539     if (state.mFirstTextFrame) {
1540       nextLineFirstTextFrame = state.mFirstTextFrame;
1541     }
1542   }
1543   scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
1544 
1545   // Now iterate over all text frames starting from the current line.
1546   // First-in-flow text frames will be accumulated into textRunFrames as we go.
1547   // When a text run boundary is required we flush textRunFrames ((re)building
1548   // their gfxTextRuns as necessary).
1549   bool seenStartLine = false;
1550   uint32_t linesAfterStartLine = 0;
1551   do {
1552     nsBlockFrame::LineIterator line = forwardIterator.GetLine();
1553     if (line->IsBlock()) break;
1554     line->SetInvalidateTextRuns(false);
1555     scanner.SetAtStartOfLine();
1556     scanner.SetCommonAncestorWithLastFrame(nullptr);
1557     nsIFrame* child = line->mFirstChild;
1558     for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1559       scanner.ScanFrame(child);
1560       child = child->GetNextSibling();
1561     }
1562     if (line.get() == startLine.get()) {
1563       seenStartLine = true;
1564     }
1565     if (seenStartLine) {
1566       ++linesAfterStartLine;
1567       if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS &&
1568           scanner.CanStopOnThisLine()) {
1569         // Don't flush frames; we may be in the middle of a textrun
1570         // that we can't end here. That's OK, we just won't build it.
1571         // Note that we must already have finished the textrun for aForFrame,
1572         // because we've seen the end of a textrun in a line after the line
1573         // containing aForFrame.
1574         scanner.FlushLineBreaks(nullptr);
1575         // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1576         // silences assertions in the scanner destructor.
1577         scanner.ResetRunInfo();
1578         return;
1579       }
1580     }
1581   } while (forwardIterator.Next());
1582 
1583   // Set mStartOfLine so FlushFrames knows its textrun ends a line
1584   scanner.SetAtStartOfLine();
1585   scanner.FlushFrames(true, false);
1586 }
1587 
ExpandBuffer(char16_t * aDest,uint8_t * aSrc,uint32_t aCount)1588 static char16_t* ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) {
1589   while (aCount) {
1590     *aDest = *aSrc;
1591     ++aDest;
1592     ++aSrc;
1593     --aCount;
1594   }
1595   return aDest;
1596 }
1597 
IsTextRunValidForMappedFlows(const gfxTextRun * aTextRun)1598 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(
1599     const gfxTextRun* aTextRun) {
1600   if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
1601     return mMappedFlows.Length() == 1 &&
1602            mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
1603            mMappedFlows[0].mEndFrame == nullptr;
1604   }
1605 
1606   auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1607   TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
1608   if (userData->mMappedFlowCount != mMappedFlows.Length()) return false;
1609   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
1610     if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
1611         int32_t(userMappedFlows[i].mContentLength) !=
1612             mMappedFlows[i].GetContentEnd() -
1613                 mMappedFlows[i].mStartFrame->GetContentOffset())
1614       return false;
1615   }
1616   return true;
1617 }
1618 
1619 /**
1620  * This gets called when we need to make a text run for the current list of
1621  * frames.
1622  */
FlushFrames(bool aFlushLineBreaks,bool aSuppressTrailingBreak)1623 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks,
1624                                        bool aSuppressTrailingBreak) {
1625   RefPtr<gfxTextRun> textRun;
1626   if (!mMappedFlows.IsEmpty()) {
1627     if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
1628         !!(mCurrentFramesAllSameTextRun->GetFlags2() &
1629            nsTextFrameUtils::Flags::IncomingWhitespace) ==
1630             !!(mCurrentRunContextInfo &
1631                nsTextFrameUtils::INCOMING_WHITESPACE) &&
1632         !!(mCurrentFramesAllSameTextRun->GetFlags() &
1633            gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
1634             !!(mCurrentRunContextInfo &
1635                nsTextFrameUtils::INCOMING_ARABICCHAR) &&
1636         IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
1637       // Optimization: We do not need to (re)build the textrun.
1638       textRun = mCurrentFramesAllSameTextRun;
1639 
1640       // Feed this run's text into the linebreaker to provide context.
1641       if (!SetupLineBreakerContext(textRun)) {
1642         return;
1643       }
1644 
1645       // Update mNextRunContextInfo appropriately
1646       mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
1647       if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TrailingWhitespace) {
1648         mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
1649       }
1650       if (textRun->GetFlags() &
1651           gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
1652         mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
1653       }
1654     } else {
1655       AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
1656       uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
1657       if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
1658           !buffer.AppendElements(bufferSize, fallible)) {
1659         return;
1660       }
1661       textRun = BuildTextRunForFrames(buffer.Elements());
1662     }
1663   }
1664 
1665   if (aFlushLineBreaks) {
1666     FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
1667   }
1668 
1669   mCanStopOnThisLine = true;
1670   ResetRunInfo();
1671 }
1672 
FlushLineBreaks(gfxTextRun * aTrailingTextRun)1673 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) {
1674   // If the line-breaker is buffering a potentially-unfinished word,
1675   // preserve the state of being in-word so that we don't spuriously
1676   // capitalize the next letter.
1677   bool inWord = mLineBreaker.InWord();
1678   bool trailingLineBreak;
1679   nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
1680   mLineBreaker.SetWordContinuation(inWord);
1681   // textRun may be null for various reasons, including because we constructed
1682   // a partial textrun just to get the linebreaker and other state set up
1683   // to build the next textrun.
1684   if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
1685     aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::HasTrailingBreak);
1686   }
1687 
1688   for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
1689     // TODO cause frames associated with the textrun to be reflowed, if they
1690     // aren't being reflowed already!
1691     mBreakSinks[i]->Finish(mMissingFonts);
1692   }
1693   mBreakSinks.Clear();
1694 }
1695 
AccumulateRunInfo(nsTextFrame * aFrame)1696 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) {
1697   if (mMaxTextLength != UINT32_MAX) {
1698     NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(),
1699                  "integer overflow");
1700     if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
1701       mMaxTextLength = UINT32_MAX;
1702     } else {
1703       mMaxTextLength += aFrame->GetContentLength();
1704     }
1705   }
1706   mDoubleByteText |= aFrame->TextFragment()->Is2b();
1707   mLastFrame = aFrame;
1708   mCommonAncestorWithLastFrame = aFrame->GetParent();
1709 
1710   MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1711   NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
1712                    mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
1713                "Overlapping or discontiguous frames => BAD");
1714   mappedFlow->mEndFrame = aFrame->GetNextContinuation();
1715   if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
1716     mCurrentFramesAllSameTextRun = nullptr;
1717   }
1718 
1719   if (mStartOfLine) {
1720     mLineBreakBeforeFrames.AppendElement(aFrame);
1721     mStartOfLine = false;
1722   }
1723 }
1724 
HasTerminalNewline(const nsTextFrame * aFrame)1725 static bool HasTerminalNewline(const nsTextFrame* aFrame) {
1726   if (aFrame->GetContentLength() == 0) return false;
1727   const nsTextFragment* frag = aFrame->TextFragment();
1728   return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
1729 }
1730 
GetFirstFontMetrics(gfxFontGroup * aFontGroup,bool aVerticalMetrics)1731 static gfxFont::Metrics GetFirstFontMetrics(gfxFontGroup* aFontGroup,
1732                                             bool aVerticalMetrics) {
1733   if (!aFontGroup) return gfxFont::Metrics();
1734   gfxFont* font = aFontGroup->GetFirstValidFont();
1735   return font->GetMetrics(aVerticalMetrics ? nsFontMetrics::eVertical
1736                                            : nsFontMetrics::eHorizontal);
1737 }
1738 
GetSpaceWidthAppUnits(const gfxTextRun * aTextRun)1739 static nscoord GetSpaceWidthAppUnits(const gfxTextRun* aTextRun) {
1740   // Round the space width when converting to appunits the same way textruns
1741   // do.
1742   gfxFloat spaceWidthAppUnits =
1743       NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
1744                                    aTextRun->UseCenterBaseline())
1745                    .spaceWidth *
1746                aTextRun->GetAppUnitsPerDevUnit());
1747 
1748   return spaceWidthAppUnits;
1749 }
1750 
GetMinTabAdvanceAppUnits(const gfxTextRun * aTextRun)1751 static gfxFloat GetMinTabAdvanceAppUnits(const gfxTextRun* aTextRun) {
1752   gfxFloat chWidthAppUnits = NS_round(
1753       GetFirstFontMetrics(aTextRun->GetFontGroup(), aTextRun->IsVertical())
1754           .ZeroOrAveCharWidth() *
1755       aTextRun->GetAppUnitsPerDevUnit());
1756   return 0.5 * chWidthAppUnits;
1757 }
1758 
GetSVGFontSizeScaleFactor(nsIFrame * aFrame)1759 static float GetSVGFontSizeScaleFactor(nsIFrame* aFrame) {
1760   if (!SVGUtils::IsInSVGTextSubtree(aFrame)) {
1761     return 1.0f;
1762   }
1763   auto container =
1764       nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
1765   MOZ_ASSERT(container);
1766   return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
1767 }
1768 
LetterSpacing(nsIFrame * aFrame,const nsStyleText & aStyleText)1769 static nscoord LetterSpacing(nsIFrame* aFrame, const nsStyleText& aStyleText) {
1770   if (SVGUtils::IsInSVGTextSubtree(aFrame)) {
1771     // SVG text can have a scaling factor applied so that very small or very
1772     // large font-sizes don't suffer from poor glyph placement due to app unit
1773     // rounding. The used letter-spacing value must be scaled by the same
1774     // factor.
1775     Length spacing = aStyleText.mLetterSpacing;
1776     spacing.ScaleBy(GetSVGFontSizeScaleFactor(aFrame));
1777     return spacing.ToAppUnits();
1778   }
1779 
1780   return aStyleText.mLetterSpacing.ToAppUnits();
1781 }
1782 
1783 // This function converts non-coord values (e.g. percentages) to nscoord.
WordSpacing(nsIFrame * aFrame,const gfxTextRun * aTextRun,const nsStyleText & aStyleText)1784 static nscoord WordSpacing(nsIFrame* aFrame, const gfxTextRun* aTextRun,
1785                            const nsStyleText& aStyleText) {
1786   if (SVGUtils::IsInSVGTextSubtree(aFrame)) {
1787     // SVG text can have a scaling factor applied so that very small or very
1788     // large font-sizes don't suffer from poor glyph placement due to app unit
1789     // rounding. The used word-spacing value must be scaled by the same
1790     // factor, although any percentage basis has already effectively been
1791     // scaled, since it's the space glyph width, which is based on the already-
1792     // scaled font-size.
1793     auto spacing = aStyleText.mWordSpacing;
1794     spacing.ScaleLengthsBy(GetSVGFontSizeScaleFactor(aFrame));
1795     return spacing.Resolve([&] { return GetSpaceWidthAppUnits(aTextRun); });
1796   }
1797 
1798   return aStyleText.mWordSpacing.Resolve(
1799       [&] { return GetSpaceWidthAppUnits(aTextRun); });
1800 }
1801 
1802 // Returns gfxTextRunFactory::TEXT_ENABLE_SPACING if non-standard
1803 // letter-spacing or word-spacing is present.
GetSpacingFlags(nsIFrame * aFrame,const nsStyleText * aStyleText=nullptr)1804 static gfx::ShapedTextFlags GetSpacingFlags(
1805     nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) {
1806   const nsStyleText* styleText = aFrame->StyleText();
1807   const auto& ls = styleText->mLetterSpacing;
1808   const auto& ws = styleText->mWordSpacing;
1809 
1810   // It's possible to have a calc() value that computes to zero but for which
1811   // IsDefinitelyZero() is false, in which case we'll return
1812   // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
1813   // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
1814   bool nonStandardSpacing = !ls.IsZero() || !ws.IsDefinitelyZero();
1815   return nonStandardSpacing ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
1816                             : gfx::ShapedTextFlags();
1817 }
1818 
ContinueTextRunAcrossFrames(nsTextFrame * aFrame1,nsTextFrame * aFrame2)1819 bool BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1,
1820                                                        nsTextFrame* aFrame2) {
1821   // We don't need to check font size inflation, since
1822   // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
1823   // ensures that text runs never cross block boundaries.  This means
1824   // that the font size inflation on all text frames in the text run is
1825   // already guaranteed to be the same as each other (and for the line
1826   // container).
1827   if (mBidiEnabled) {
1828     FrameBidiData data1 = aFrame1->GetBidiData();
1829     FrameBidiData data2 = aFrame2->GetBidiData();
1830     if (data1.embeddingLevel != data2.embeddingLevel ||
1831         data2.precedingControl != kBidiLevelNone) {
1832       return false;
1833     }
1834   }
1835 
1836   ComputedStyle* sc1 = aFrame1->Style();
1837   ComputedStyle* sc2 = aFrame2->Style();
1838 
1839   // Any difference in writing-mode/directionality inhibits shaping across
1840   // the boundary.
1841   WritingMode wm(sc1);
1842   if (wm != WritingMode(sc2)) {
1843     return false;
1844   }
1845 
1846   const nsStyleText* textStyle1 = sc1->StyleText();
1847   // If the first frame ends in a preformatted newline, then we end the textrun
1848   // here. This avoids creating giant textruns for an entire plain text file.
1849   // Note that we create a single text frame for a preformatted text node,
1850   // even if it has newlines in it, so typically we won't see trailing newlines
1851   // until after reflow has broken up the frame into one (or more) frames per
1852   // line. That's OK though.
1853   if (textStyle1->NewlineIsSignificant(aFrame1) &&
1854       HasTerminalNewline(aFrame1)) {
1855     return false;
1856   }
1857 
1858   if (aFrame1->GetParent()->GetContent() !=
1859       aFrame2->GetParent()->GetContent()) {
1860     // Does aFrame, or any ancestor between it and aAncestor, have a property
1861     // that should inhibit cross-element-boundary shaping on aSide?
1862     auto PreventCrossBoundaryShaping = [](const nsIFrame* aFrame,
1863                                           const nsIFrame* aAncestor,
1864                                           Side aSide) {
1865       while (aFrame != aAncestor) {
1866         ComputedStyle* ctx = aFrame->Style();
1867         // According to https://drafts.csswg.org/css-text/#boundary-shaping:
1868         //
1869         // Text shaping must be broken at inline box boundaries when any of
1870         // the following are true for any box whose boundary separates the
1871         // two typographic character units:
1872         //
1873         // 1. Any of margin/border/padding separating the two typographic
1874         //    character units in the inline axis is non-zero.
1875         const auto& margin = ctx->StyleMargin()->mMargin.Get(aSide);
1876         if (!margin.ConvertsToLength() ||
1877             margin.AsLengthPercentage().ToLength() != 0) {
1878           return true;
1879         }
1880         const auto& padding = ctx->StylePadding()->mPadding.Get(aSide);
1881         if (!padding.ConvertsToLength() || padding.ToLength() != 0) {
1882           return true;
1883         }
1884         if (ctx->StyleBorder()->GetComputedBorderWidth(aSide) != 0) {
1885           return true;
1886         }
1887 
1888         // 2. vertical-align is not baseline.
1889         //
1890         // FIXME: Should this use VerticalAlignEnum()?
1891         const auto& verticalAlign = ctx->StyleDisplay()->mVerticalAlign;
1892         if (!verticalAlign.IsKeyword() ||
1893             verticalAlign.AsKeyword() != StyleVerticalAlignKeyword::Baseline) {
1894           return true;
1895         }
1896 
1897         // 3. The boundary is a bidi isolation boundary.
1898         const uint8_t unicodeBidi = ctx->StyleTextReset()->mUnicodeBidi;
1899         if (unicodeBidi == NS_STYLE_UNICODE_BIDI_ISOLATE ||
1900             unicodeBidi == NS_STYLE_UNICODE_BIDI_ISOLATE_OVERRIDE) {
1901           return true;
1902         }
1903 
1904         aFrame = aFrame->GetParent();
1905       }
1906       return false;
1907     };
1908 
1909     const nsIFrame* ancestor =
1910         nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(aFrame1,
1911                                                                  aFrame2);
1912 
1913     if (!ancestor) {
1914       // The two frames are within different blocks, e.g. due to block
1915       // fragmentation. In theory we shouldn't prevent cross-frame shaping
1916       // here, but it's an edge case where we should rarely decide to allow
1917       // cross-frame shaping, so we don't try harder here.
1918       return false;
1919     }
1920 
1921     // We inhibit cross-element-boundary shaping if we're in SVG content,
1922     // as there are too many things SVG might be doing (like applying per-
1923     // element positioning) that wouldn't make sense with shaping across
1924     // the boundary.
1925     if (SVGUtils::IsInSVGTextSubtree(ancestor)) {
1926       return false;
1927     }
1928 
1929     // Map inline-end and inline-start to physical sides for checking presence
1930     // of non-zero margin/border/padding.
1931     Side side1 = wm.PhysicalSide(eLogicalSideIEnd);
1932     Side side2 = wm.PhysicalSide(eLogicalSideIStart);
1933     // If the frames have an embedding level that is opposite to the writing
1934     // mode, we need to swap which sides we're checking.
1935     if (IS_LEVEL_RTL(aFrame1->GetEmbeddingLevel()) == wm.IsBidiLTR()) {
1936       std::swap(side1, side2);
1937     }
1938 
1939     if (PreventCrossBoundaryShaping(aFrame1, ancestor, side1) ||
1940         PreventCrossBoundaryShaping(aFrame2, ancestor, side2)) {
1941       return false;
1942     }
1943   }
1944 
1945   if (aFrame1->GetContent() == aFrame2->GetContent() &&
1946       aFrame1->GetNextInFlow() != aFrame2) {
1947     // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1948     // sometimes when the unicode-bidi property is used; the bidi resolver
1949     // breaks text into different frames even though the text has the same
1950     // direction. We can't allow these two frames to share the same textrun
1951     // because that would violate our invariant that two flows in the same
1952     // textrun have different content elements.
1953     return false;
1954   }
1955 
1956   if (sc1 == sc2) {
1957     return true;
1958   }
1959 
1960   const nsStyleText* textStyle2 = sc2->StyleText();
1961   if (textStyle1->mTextTransform != textStyle2->mTextTransform ||
1962       textStyle1->EffectiveWordBreak() != textStyle2->EffectiveWordBreak() ||
1963       textStyle1->mLineBreak != textStyle2->mLineBreak) {
1964     return false;
1965   }
1966 
1967   nsPresContext* pc = aFrame1->PresContext();
1968   MOZ_ASSERT(pc == aFrame2->PresContext());
1969 
1970   const nsStyleFont* fontStyle1 = sc1->StyleFont();
1971   const nsStyleFont* fontStyle2 = sc2->StyleFont();
1972   nscoord letterSpacing1 = LetterSpacing(aFrame1, *textStyle1);
1973   nscoord letterSpacing2 = LetterSpacing(aFrame2, *textStyle2);
1974   return fontStyle1->mFont == fontStyle2->mFont &&
1975          fontStyle1->mLanguage == fontStyle2->mLanguage &&
1976          nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1,
1977                                                 letterSpacing1) ==
1978              nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2,
1979                                                     textStyle2, letterSpacing2);
1980 }
1981 
ScanFrame(nsIFrame * aFrame)1982 void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) {
1983   LayoutFrameType frameType = aFrame->Type();
1984   if (frameType == LayoutFrameType::RubyTextContainer) {
1985     // Don't include any ruby text container into the text run.
1986     return;
1987   }
1988 
1989   // First check if we can extend the current mapped frame block. This is
1990   // common.
1991   if (mMappedFlows.Length() > 0) {
1992     MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1993     if (mappedFlow->mEndFrame == aFrame &&
1994         aFrame->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) {
1995       NS_ASSERTION(frameType == LayoutFrameType::Text,
1996                    "Flow-sibling of a text frame is not a text frame?");
1997 
1998       // Don't do this optimization if mLastFrame has a terminal newline...
1999       // it's quite likely preformatted and we might want to end the textrun
2000       // here. This is almost always true:
2001       if (mLastFrame->Style() == aFrame->Style() &&
2002           !HasTerminalNewline(mLastFrame)) {
2003         AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
2004         return;
2005       }
2006     }
2007   }
2008 
2009   // Now see if we can add a new set of frames to the current textrun
2010   if (frameType == LayoutFrameType::Text) {
2011     nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
2012 
2013     if (mLastFrame) {
2014       if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
2015         FlushFrames(false, false);
2016       } else {
2017         if (mLastFrame->GetContent() == frame->GetContent()) {
2018           AccumulateRunInfo(frame);
2019           return;
2020         }
2021       }
2022     }
2023 
2024     MappedFlow* mappedFlow = mMappedFlows.AppendElement();
2025     if (!mappedFlow) return;
2026 
2027     mappedFlow->mStartFrame = frame;
2028     mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
2029 
2030     AccumulateRunInfo(frame);
2031     if (mMappedFlows.Length() == 1) {
2032       mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
2033       mCurrentRunContextInfo = mNextRunContextInfo;
2034     }
2035     return;
2036   }
2037 
2038   if (frameType == LayoutFrameType::Placeholder &&
2039       aFrame->HasAnyStateBits(PLACEHOLDER_FOR_ABSPOS |
2040                               PLACEHOLDER_FOR_FIXEDPOS)) {
2041     // Somewhat hacky fix for bug 1418472:
2042     // If this is a placeholder for an absolute-positioned frame, we need to
2043     // flush the line-breaker to prevent the placeholder becoming separated
2044     // from the immediately-following content.
2045     // XXX This will interrupt text shaping (ligatures, etc) if an abs-pos
2046     // element occurs within a word where shaping should be in effect, but
2047     // that's an edge case, unlikely to occur in real content. A more precise
2048     // fix might require better separation of line-breaking from textrun setup,
2049     // but that's a big invasive change (and potentially expensive for perf, as
2050     // it might introduce an additional pass over all the frames).
2051     FlushFrames(true, false);
2052   }
2053 
2054   FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
2055   bool isBR = frameType == LayoutFrameType::Br;
2056   if (!traversal.mLineBreakerCanCrossFrameBoundary) {
2057     // BR frames are special. We do not need or want to record a break
2058     // opportunity before a BR frame.
2059     FlushFrames(true, isBR);
2060     mCommonAncestorWithLastFrame = aFrame;
2061     mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
2062     mStartOfLine = false;
2063   } else if (!traversal.mTextRunCanCrossFrameBoundary) {
2064     FlushFrames(false, false);
2065   }
2066 
2067   for (nsIFrame* f = traversal.NextFrameToScan(); f;
2068        f = traversal.NextFrameToScan()) {
2069     ScanFrame(f);
2070   }
2071 
2072   if (!traversal.mLineBreakerCanCrossFrameBoundary) {
2073     // Really if we're a BR frame this is unnecessary since descendInto will be
2074     // false. In fact this whole "if" statement should move into the
2075     // descendInto.
2076     FlushFrames(true, isBR);
2077     mCommonAncestorWithLastFrame = aFrame;
2078     mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
2079   } else if (!traversal.mTextRunCanCrossFrameBoundary) {
2080     FlushFrames(false, false);
2081   }
2082 
2083   LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
2084 }
2085 
GetNextBreakBeforeFrame(uint32_t * aIndex)2086 nsTextFrame* BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) {
2087   uint32_t index = *aIndex;
2088   if (index >= mLineBreakBeforeFrames.Length()) return nullptr;
2089   *aIndex = index + 1;
2090   return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
2091 }
2092 
GetFontGroupForFrame(const nsIFrame * aFrame,float aFontSizeInflation,nsFontMetrics ** aOutFontMetrics=nullptr)2093 static gfxFontGroup* GetFontGroupForFrame(
2094     const nsIFrame* aFrame, float aFontSizeInflation,
2095     nsFontMetrics** aOutFontMetrics = nullptr) {
2096   RefPtr<nsFontMetrics> metrics =
2097       nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
2098   gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
2099 
2100   // Populate outparam before we return:
2101   if (aOutFontMetrics) {
2102     metrics.forget(aOutFontMetrics);
2103   }
2104   // XXX this is a bit bogus, we're releasing 'metrics' so the
2105   // returned font-group might actually be torn down, although because
2106   // of the way the device context caches font metrics, this seems to
2107   // not actually happen. But we should fix this.
2108   return fontGroup;
2109 }
2110 
GetInflatedFontGroupForFrame(nsTextFrame * aFrame)2111 static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) {
2112   gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
2113   if (textRun) {
2114     return textRun->GetFontGroup();
2115   }
2116   if (!aFrame->InflatedFontMetrics()) {
2117     float inflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
2118     RefPtr<nsFontMetrics> metrics =
2119         nsLayoutUtils::GetFontMetricsForFrame(aFrame, inflation);
2120     aFrame->SetInflatedFontMetrics(metrics);
2121   }
2122   return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
2123 }
2124 
CreateReferenceDrawTarget(const nsTextFrame * aTextFrame)2125 static already_AddRefed<DrawTarget> CreateReferenceDrawTarget(
2126     const nsTextFrame* aTextFrame) {
2127   RefPtr<gfxContext> ctx =
2128       aTextFrame->PresShell()->CreateReferenceRenderingContext();
2129   RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
2130   return dt.forget();
2131 }
2132 
GetHyphenTextRun(nsTextFrame * aTextFrame,DrawTarget * aDrawTarget)2133 static already_AddRefed<gfxTextRun> GetHyphenTextRun(nsTextFrame* aTextFrame,
2134                                                      DrawTarget* aDrawTarget) {
2135   RefPtr<DrawTarget> dt = aDrawTarget;
2136   if (!dt) {
2137     dt = CreateReferenceDrawTarget(aTextFrame);
2138     if (!dt) {
2139       return nullptr;
2140     }
2141   }
2142 
2143   RefPtr<nsFontMetrics> fm =
2144       nsLayoutUtils::GetInflatedFontMetricsForFrame(aTextFrame);
2145   return fm->GetThebesFontGroup()->MakeHyphenTextRun(
2146       dt, aTextFrame->PresContext()->AppUnitsPerDevPixel());
2147 }
2148 
BuildTextRunForFrames(void * aTextBuffer)2149 already_AddRefed<gfxTextRun> BuildTextRunsScanner::BuildTextRunForFrames(
2150     void* aTextBuffer) {
2151   gfxSkipChars skipChars;
2152 
2153   const void* textPtr = aTextBuffer;
2154   bool anyTextTransformStyle = false;
2155   bool anyMathMLStyling = false;
2156   bool anyTextEmphasis = false;
2157   uint8_t sstyScriptLevel = 0;
2158   uint32_t mathFlags = 0;
2159   gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
2160   nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::NoBreaks;
2161 
2162   if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2163     flags2 |= nsTextFrameUtils::Flags::IncomingWhitespace;
2164   }
2165   if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2166     flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
2167   }
2168 
2169   AutoTArray<int32_t, 50> textBreakPoints;
2170   TextRunUserData dummyData;
2171   TextRunMappedFlow dummyMappedFlow;
2172   TextRunMappedFlow* userMappedFlows;
2173   TextRunUserData* userData;
2174   TextRunUserData* userDataToDestroy;
2175   // If the situation is particularly simple (and common) we don't need to
2176   // allocate userData.
2177   if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2178       mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2179     userData = &dummyData;
2180     userMappedFlows = &dummyMappedFlow;
2181     userDataToDestroy = nullptr;
2182     dummyData.mMappedFlowCount = mMappedFlows.Length();
2183     dummyData.mLastFlowIndex = 0;
2184   } else {
2185     userData = CreateUserData(mMappedFlows.Length());
2186     userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2187     userDataToDestroy = userData;
2188   }
2189 
2190   uint32_t currentTransformedTextOffset = 0;
2191 
2192   uint32_t nextBreakIndex = 0;
2193   nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2194   bool isSVG = SVGUtils::IsInSVGTextSubtree(mLineContainer);
2195   bool enabledJustification =
2196       (mLineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
2197        mLineContainer->StyleText()->mTextAlignLast ==
2198            StyleTextAlignLast::Justify);
2199 
2200   const nsStyleText* textStyle = nullptr;
2201   const nsStyleFont* fontStyle = nullptr;
2202   ComputedStyle* lastComputedStyle = nullptr;
2203   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2204     MappedFlow* mappedFlow = &mMappedFlows[i];
2205     nsTextFrame* f = mappedFlow->mStartFrame;
2206 
2207     lastComputedStyle = f->Style();
2208     // Detect use of text-transform or font-variant anywhere in the run
2209     textStyle = f->StyleText();
2210     if (!textStyle->mTextTransform.IsNone() ||
2211         // text-combine-upright requires converting from full-width
2212         // characters to non-full-width correspendent in some cases.
2213         lastComputedStyle->IsTextCombined()) {
2214       anyTextTransformStyle = true;
2215     }
2216     if (textStyle->HasEffectiveTextEmphasis()) {
2217       anyTextEmphasis = true;
2218     }
2219     flags |= GetSpacingFlags(f);
2220     nsTextFrameUtils::CompressionMode compression =
2221         GetCSSWhitespaceToCompressionMode(f, textStyle);
2222     if ((enabledJustification || f->ShouldSuppressLineBreak()) &&
2223         !textStyle->WhiteSpaceIsSignificant() && !isSVG) {
2224       flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2225     }
2226     fontStyle = f->StyleFont();
2227     nsIFrame* parent = mLineContainer->GetParent();
2228     if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
2229       if (NS_MATHML_MATHVARIANT_NORMAL != fontStyle->mMathVariant) {
2230         anyMathMLStyling = true;
2231       }
2232     } else if (mLineContainer->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
2233       flags2 |= nsTextFrameUtils::Flags::IsSingleCharMi;
2234       anyMathMLStyling = true;
2235       // Test for fontstyle attribute as StyleFont() may not be accurate
2236       // To be consistent in terms of ignoring CSS style changes, fontweight
2237       // gets checked too.
2238       if (parent) {
2239         nsIContent* content = parent->GetContent();
2240         if (content && content->IsElement()) {
2241           if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
2242                                                 nsGkAtoms::fontstyle_,
2243                                                 u"normal"_ns, eCaseMatters)) {
2244             mathFlags |= MathMLTextRunFactory::MATH_FONT_STYLING_NORMAL;
2245           }
2246           if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
2247                                                 nsGkAtoms::fontweight_,
2248                                                 u"bold"_ns, eCaseMatters)) {
2249             mathFlags |= MathMLTextRunFactory::MATH_FONT_WEIGHT_BOLD;
2250           }
2251         }
2252       }
2253     }
2254     if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
2255       // All MathML tokens except <mtext> use 'math' script.
2256       if (!(parent && parent->GetContent() &&
2257             parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext_))) {
2258         flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
2259       }
2260       nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
2261       if (mathFrame) {
2262         nsPresentationData presData;
2263         mathFrame->GetPresentationData(presData);
2264         if (NS_MATHML_IS_DTLS_SET(presData.flags)) {
2265           mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
2266           anyMathMLStyling = true;
2267         }
2268       }
2269     }
2270     nsIFrame* child = mLineContainer;
2271     uint8_t oldScriptLevel = 0;
2272     while (parent &&
2273            child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
2274       // Reconstruct the script level ignoring any user overrides. It is
2275       // calculated this way instead of using scriptlevel to ensure the
2276       // correct ssty font feature setting is used even if the user sets a
2277       // different (especially negative) scriptlevel.
2278       nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
2279       if (mathFrame) {
2280         sstyScriptLevel += mathFrame->ScriptIncrement(child);
2281       }
2282       if (sstyScriptLevel < oldScriptLevel) {
2283         // overflow
2284         sstyScriptLevel = UINT8_MAX;
2285         break;
2286       }
2287       child = parent;
2288       parent = parent->GetParent();
2289       oldScriptLevel = sstyScriptLevel;
2290     }
2291     if (sstyScriptLevel) {
2292       anyMathMLStyling = true;
2293     }
2294 
2295     // Figure out what content is included in this flow.
2296     nsIContent* content = f->GetContent();
2297     const nsTextFragment* frag = f->TextFragment();
2298     int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2299     int32_t contentEnd = mappedFlow->GetContentEnd();
2300     int32_t contentLength = contentEnd - contentStart;
2301 
2302     TextRunMappedFlow* newFlow = &userMappedFlows[i];
2303     newFlow->mStartFrame = mappedFlow->mStartFrame;
2304     newFlow->mDOMOffsetToBeforeTransformOffset =
2305         skipChars.GetOriginalCharCount() -
2306         mappedFlow->mStartFrame->GetContentOffset();
2307     newFlow->mContentLength = contentLength;
2308 
2309     while (nextBreakBeforeFrame &&
2310            nextBreakBeforeFrame->GetContent() == content) {
2311       textBreakPoints.AppendElement(nextBreakBeforeFrame->GetContentOffset() +
2312                                     newFlow->mDOMOffsetToBeforeTransformOffset);
2313       nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2314     }
2315 
2316     nsTextFrameUtils::Flags analysisFlags;
2317     if (frag->Is2b()) {
2318       NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2319       char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
2320       char16_t* bufEnd = nsTextFrameUtils::TransformText(
2321           frag->Get2b() + contentStart, contentLength, bufStart, compression,
2322           &mNextRunContextInfo, &skipChars, &analysisFlags);
2323       aTextBuffer = bufEnd;
2324       currentTransformedTextOffset =
2325           bufEnd - static_cast<const char16_t*>(textPtr);
2326     } else {
2327       if (mDoubleByteText) {
2328         // Need to expand the text. First transform it into a temporary buffer,
2329         // then expand.
2330         AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
2331         uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2332         if (!bufStart) {
2333           DestroyUserData(userDataToDestroy);
2334           return nullptr;
2335         }
2336         uint8_t* end = nsTextFrameUtils::TransformText(
2337             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
2338             contentLength, bufStart, compression, &mNextRunContextInfo,
2339             &skipChars, &analysisFlags);
2340         aTextBuffer =
2341             ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
2342                          tempBuf.Elements(), end - tempBuf.Elements());
2343         currentTransformedTextOffset = static_cast<char16_t*>(aTextBuffer) -
2344                                        static_cast<const char16_t*>(textPtr);
2345       } else {
2346         uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
2347         uint8_t* end = nsTextFrameUtils::TransformText(
2348             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
2349             contentLength, bufStart, compression, &mNextRunContextInfo,
2350             &skipChars, &analysisFlags);
2351         aTextBuffer = end;
2352         currentTransformedTextOffset =
2353             end - static_cast<const uint8_t*>(textPtr);
2354       }
2355     }
2356     flags2 |= analysisFlags;
2357   }
2358 
2359   void* finalUserData;
2360   if (userData == &dummyData) {
2361     flags2 |= nsTextFrameUtils::Flags::IsSimpleFlow;
2362     userData = nullptr;
2363     finalUserData = mMappedFlows[0].mStartFrame;
2364   } else {
2365     finalUserData = userData;
2366   }
2367 
2368   uint32_t transformedLength = currentTransformedTextOffset;
2369 
2370   // Now build the textrun
2371   nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
2372   float fontInflation;
2373   gfxFontGroup* fontGroup;
2374   if (mWhichTextRun == nsTextFrame::eNotInflated) {
2375     fontInflation = 1.0f;
2376     fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
2377   } else {
2378     fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
2379     fontGroup = GetInflatedFontGroupForFrame(firstFrame);
2380   }
2381 
2382   if (fontGroup) {
2383     // Refresh fontgroup if necessary, before trying to build textruns.
2384     fontGroup->CheckForUpdatedPlatformList();
2385   } else {
2386     DestroyUserData(userDataToDestroy);
2387     return nullptr;
2388   }
2389 
2390   if (flags2 & nsTextFrameUtils::Flags::HasTab) {
2391     flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2392   }
2393   if (flags2 & nsTextFrameUtils::Flags::HasShy) {
2394     flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
2395   }
2396   if (mBidiEnabled && (IS_LEVEL_RTL(firstFrame->GetEmbeddingLevel()))) {
2397     flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
2398   }
2399   if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2400     flags2 |= nsTextFrameUtils::Flags::TrailingWhitespace;
2401   }
2402   if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2403     flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
2404   }
2405   // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
2406   // frame's style is used, so we use a mixture of the first frame and
2407   // last frame's style
2408   flags |= nsLayoutUtils::GetTextRunFlagsForStyle(
2409       lastComputedStyle, firstFrame->PresContext(), fontStyle, textStyle,
2410       LetterSpacing(firstFrame, *textStyle));
2411   // XXX this is a bit of a hack. For performance reasons, if we're favouring
2412   // performance over quality, don't try to get accurate glyph extents.
2413   if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
2414     flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
2415   }
2416 
2417   // Convert linebreak coordinates to transformed string offsets
2418   NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
2419                "Didn't find all the frames to break-before...");
2420   gfxSkipCharsIterator iter(skipChars);
2421   AutoTArray<uint32_t, 50> textBreakPointsAfterTransform;
2422   for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
2423     nsTextFrameUtils::AppendLineBreakOffset(
2424         &textBreakPointsAfterTransform,
2425         iter.ConvertOriginalToSkipped(textBreakPoints[i]));
2426   }
2427   if (mStartOfLine) {
2428     nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2429                                             transformedLength);
2430   }
2431 
2432   // Setup factory chain
2433   bool needsToMaskPassword = NeedsToMaskPassword(firstFrame);
2434   UniquePtr<nsTransformingTextRunFactory> transformingFactory;
2435   if (anyTextTransformStyle || needsToMaskPassword) {
2436     transformingFactory = MakeUnique<nsCaseTransformTextRunFactory>(
2437         std::move(transformingFactory));
2438   }
2439   if (anyMathMLStyling) {
2440     transformingFactory = MakeUnique<MathMLTextRunFactory>(
2441         std::move(transformingFactory), mathFlags, sstyScriptLevel,
2442         fontInflation);
2443   }
2444   nsTArray<RefPtr<nsTransformedCharStyle>> styles;
2445   if (transformingFactory) {
2446     uint32_t unmaskStart = 0, unmaskEnd = UINT32_MAX;
2447     if (needsToMaskPassword) {
2448       unmaskStart = unmaskEnd = UINT32_MAX;
2449       TextEditor* passwordEditor =
2450           nsContentUtils::GetTextEditorFromAnonymousNodeWithoutCreation(
2451               firstFrame->GetContent());
2452       if (passwordEditor && !passwordEditor->IsAllMasked()) {
2453         unmaskStart = passwordEditor->UnmaskedStart();
2454         unmaskEnd = passwordEditor->UnmaskedEnd();
2455       }
2456     }
2457 
2458     iter.SetOriginalOffset(0);
2459     for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2460       MappedFlow* mappedFlow = &mMappedFlows[i];
2461       nsTextFrame* f;
2462       ComputedStyle* sc = nullptr;
2463       RefPtr<nsTransformedCharStyle> defaultStyle;
2464       RefPtr<nsTransformedCharStyle> unmaskStyle;
2465       for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
2466            f = f->GetNextContinuation()) {
2467         uint32_t skippedOffset = iter.GetSkippedOffset();
2468         // Text-combined frames have content-dependent transform, so we
2469         // want to create new nsTransformedCharStyle for them anyway.
2470         if (sc != f->Style() || sc->IsTextCombined()) {
2471           sc = f->Style();
2472           defaultStyle = new nsTransformedCharStyle(sc, f->PresContext());
2473           if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
2474             defaultStyle->mForceNonFullWidth = true;
2475           }
2476           if (needsToMaskPassword) {
2477             defaultStyle->mMaskPassword = true;
2478             if (unmaskStart != unmaskEnd) {
2479               unmaskStyle = new nsTransformedCharStyle(sc, f->PresContext());
2480               unmaskStyle->mForceNonFullWidth =
2481                   defaultStyle->mForceNonFullWidth;
2482             }
2483           }
2484         }
2485         iter.AdvanceOriginal(f->GetContentLength());
2486         uint32_t skippedEnd = iter.GetSkippedOffset();
2487         if (unmaskStyle) {
2488           uint32_t skippedUnmaskStart =
2489               iter.ConvertOriginalToSkipped(unmaskStart);
2490           uint32_t skippedUnmaskEnd = iter.ConvertOriginalToSkipped(unmaskEnd);
2491           iter.SetSkippedOffset(skippedEnd);
2492           for (; skippedOffset < std::min(skippedEnd, skippedUnmaskStart);
2493                ++skippedOffset) {
2494             styles.AppendElement(defaultStyle);
2495           }
2496           for (; skippedOffset < std::min(skippedEnd, skippedUnmaskEnd);
2497                ++skippedOffset) {
2498             styles.AppendElement(unmaskStyle);
2499           }
2500           for (; skippedOffset < skippedEnd; ++skippedOffset) {
2501             styles.AppendElement(defaultStyle);
2502           }
2503         } else {
2504           for (; skippedOffset < skippedEnd; ++skippedOffset) {
2505             styles.AppendElement(defaultStyle);
2506           }
2507         }
2508       }
2509     }
2510     flags2 |= nsTextFrameUtils::Flags::IsTransformed;
2511     NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
2512                  "We didn't cover all the characters in the text run!");
2513   }
2514 
2515   RefPtr<gfxTextRun> textRun;
2516   gfxTextRunFactory::Parameters params = {
2517       mDrawTarget,
2518       finalUserData,
2519       &skipChars,
2520       textBreakPointsAfterTransform.Elements(),
2521       uint32_t(textBreakPointsAfterTransform.Length()),
2522       int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
2523 
2524   if (mDoubleByteText) {
2525     const char16_t* text = static_cast<const char16_t*>(textPtr);
2526     if (transformingFactory) {
2527       textRun = transformingFactory->MakeTextRun(
2528           text, transformedLength, &params, fontGroup, flags, flags2,
2529           std::move(styles), true);
2530       if (textRun) {
2531         // ownership of the factory has passed to the textrun
2532         // TODO: bug 1285316: clean up ownership transfer from the factory to
2533         // the textrun
2534         Unused << transformingFactory.release();
2535       }
2536     } else {
2537       textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
2538                                        flags2, mMissingFonts);
2539     }
2540   } else {
2541     const uint8_t* text = static_cast<const uint8_t*>(textPtr);
2542     flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
2543     if (transformingFactory) {
2544       textRun = transformingFactory->MakeTextRun(
2545           text, transformedLength, &params, fontGroup, flags, flags2,
2546           std::move(styles), true);
2547       if (textRun) {
2548         // ownership of the factory has passed to the textrun
2549         // TODO: bug 1285316: clean up ownership transfer from the factory to
2550         // the textrun
2551         Unused << transformingFactory.release();
2552       }
2553     } else {
2554       textRun = fontGroup->MakeTextRun(text, transformedLength, &params, flags,
2555                                        flags2, mMissingFonts);
2556     }
2557   }
2558   if (!textRun) {
2559     DestroyUserData(userDataToDestroy);
2560     return nullptr;
2561   }
2562 
2563   // We have to set these up after we've created the textrun, because
2564   // the breaks may be stored in the textrun during this very call.
2565   // This is a bit annoying because it requires another loop over the frames
2566   // making up the textrun, but I don't see a way to avoid this.
2567   SetupBreakSinksForTextRun(textRun.get(), textPtr);
2568 
2569   if (anyTextEmphasis) {
2570     SetupTextEmphasisForTextRun(textRun.get(), textPtr);
2571   }
2572 
2573   if (mSkipIncompleteTextRuns) {
2574     mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(
2575         textPtr, transformedLength, mDoubleByteText);
2576     // Since we're doing to destroy the user data now, avoid a dangling
2577     // pointer. Strictly speaking we don't need to do this since it should
2578     // not be used (since this textrun will not be used and will be
2579     // itself deleted soon), but it's always better to not have dangling
2580     // pointers around.
2581     textRun->SetUserData(nullptr);
2582     DestroyUserData(userDataToDestroy);
2583     return nullptr;
2584   }
2585 
2586   // Actually wipe out the textruns associated with the mapped frames and
2587   // associate those frames with this text run.
2588   AssignTextRun(textRun.get(), fontInflation);
2589   return textRun.forget();
2590 }
2591 
2592 // This is a cut-down version of BuildTextRunForFrames used to set up
2593 // context for the line-breaker, when the textrun has already been created.
2594 // So it does the same walk over the mMappedFlows, but doesn't actually
2595 // build a new textrun.
SetupLineBreakerContext(gfxTextRun * aTextRun)2596 bool BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun* aTextRun) {
2597   AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> buffer;
2598   uint32_t bufferSize = mMaxTextLength * (mDoubleByteText ? 2 : 1);
2599   if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
2600     return false;
2601   }
2602   void* textPtr = buffer.AppendElements(bufferSize, fallible);
2603   if (!textPtr) {
2604     return false;
2605   }
2606 
2607   gfxSkipChars skipChars;
2608 
2609   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2610     MappedFlow* mappedFlow = &mMappedFlows[i];
2611     nsTextFrame* f = mappedFlow->mStartFrame;
2612 
2613     const nsStyleText* textStyle = f->StyleText();
2614     nsTextFrameUtils::CompressionMode compression =
2615         GetCSSWhitespaceToCompressionMode(f, textStyle);
2616 
2617     // Figure out what content is included in this flow.
2618     const nsTextFragment* frag = f->TextFragment();
2619     int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2620     int32_t contentEnd = mappedFlow->GetContentEnd();
2621     int32_t contentLength = contentEnd - contentStart;
2622 
2623     nsTextFrameUtils::Flags analysisFlags;
2624     if (frag->Is2b()) {
2625       NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2626       char16_t* bufStart = static_cast<char16_t*>(textPtr);
2627       char16_t* bufEnd = nsTextFrameUtils::TransformText(
2628           frag->Get2b() + contentStart, contentLength, bufStart, compression,
2629           &mNextRunContextInfo, &skipChars, &analysisFlags);
2630       textPtr = bufEnd;
2631     } else {
2632       if (mDoubleByteText) {
2633         // Need to expand the text. First transform it into a temporary buffer,
2634         // then expand.
2635         AutoTArray<uint8_t, BIG_TEXT_NODE_SIZE> tempBuf;
2636         uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2637         if (!bufStart) {
2638           return false;
2639         }
2640         uint8_t* end = nsTextFrameUtils::TransformText(
2641             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
2642             contentLength, bufStart, compression, &mNextRunContextInfo,
2643             &skipChars, &analysisFlags);
2644         textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
2645                                tempBuf.Elements(), end - tempBuf.Elements());
2646       } else {
2647         uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
2648         uint8_t* end = nsTextFrameUtils::TransformText(
2649             reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart,
2650             contentLength, bufStart, compression, &mNextRunContextInfo,
2651             &skipChars, &analysisFlags);
2652         textPtr = end;
2653       }
2654     }
2655   }
2656 
2657   // We have to set these up after we've created the textrun, because
2658   // the breaks may be stored in the textrun during this very call.
2659   // This is a bit annoying because it requires another loop over the frames
2660   // making up the textrun, but I don't see a way to avoid this.
2661   SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
2662 
2663   return true;
2664 }
2665 
HasCompressedLeadingWhitespace(nsTextFrame * aFrame,const nsStyleText * aStyleText,int32_t aContentEndOffset,const gfxSkipCharsIterator & aIterator)2666 static bool HasCompressedLeadingWhitespace(
2667     nsTextFrame* aFrame, const nsStyleText* aStyleText,
2668     int32_t aContentEndOffset, const gfxSkipCharsIterator& aIterator) {
2669   if (!aIterator.IsOriginalCharSkipped()) return false;
2670 
2671   gfxSkipCharsIterator iter = aIterator;
2672   int32_t frameContentOffset = aFrame->GetContentOffset();
2673   const nsTextFragment* frag = aFrame->TextFragment();
2674   while (frameContentOffset < aContentEndOffset &&
2675          iter.IsOriginalCharSkipped()) {
2676     if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) return true;
2677     ++frameContentOffset;
2678     iter.AdvanceOriginal(1);
2679   }
2680   return false;
2681 }
2682 
SetupBreakSinksForTextRun(gfxTextRun * aTextRun,const void * aTextPtr)2683 void BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
2684                                                      const void* aTextPtr) {
2685   using mozilla::intl::LineBreaker;
2686 
2687   // textruns have uniform language
2688   const nsStyleFont* styleFont = mMappedFlows[0].mStartFrame->StyleFont();
2689   // We should only use a language for hyphenation if it was specified
2690   // explicitly.
2691   nsAtom* hyphenationLanguage =
2692       styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
2693   // We keep this pointed at the skip-chars data for the current mappedFlow.
2694   // This lets us cheaply check whether the flow has compressed initial
2695   // whitespace...
2696   gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
2697 
2698   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2699     MappedFlow* mappedFlow = &mMappedFlows[i];
2700     // The CSS word-break value may change within a word, so we reset it for
2701     // each MappedFlow. The line-breaker will flush its text if the property
2702     // actually changes.
2703     const auto* styleText = mappedFlow->mStartFrame->StyleText();
2704     auto wordBreak = styleText->EffectiveWordBreak();
2705     switch (wordBreak) {
2706       case StyleWordBreak::BreakAll:
2707         mLineBreaker.SetWordBreak(LineBreaker::WordBreak::BreakAll);
2708         break;
2709       case StyleWordBreak::KeepAll:
2710         mLineBreaker.SetWordBreak(LineBreaker::WordBreak::KeepAll);
2711         break;
2712       case StyleWordBreak::Normal:
2713       default:
2714         MOZ_ASSERT(wordBreak == StyleWordBreak::Normal);
2715         mLineBreaker.SetWordBreak(LineBreaker::WordBreak::Normal);
2716         break;
2717     }
2718     switch (styleText->mLineBreak) {
2719       case StyleLineBreak::Auto:
2720         mLineBreaker.SetStrictness(LineBreaker::Strictness::Auto);
2721         break;
2722       case StyleLineBreak::Normal:
2723         mLineBreaker.SetStrictness(LineBreaker::Strictness::Normal);
2724         break;
2725       case StyleLineBreak::Loose:
2726         mLineBreaker.SetStrictness(LineBreaker::Strictness::Loose);
2727         break;
2728       case StyleLineBreak::Strict:
2729         mLineBreaker.SetStrictness(LineBreaker::Strictness::Strict);
2730         break;
2731       case StyleLineBreak::Anywhere:
2732         mLineBreaker.SetStrictness(LineBreaker::Strictness::Anywhere);
2733         break;
2734     }
2735 
2736     uint32_t offset = iter.GetSkippedOffset();
2737     gfxSkipCharsIterator iterNext = iter;
2738     iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
2739                              mappedFlow->mStartFrame->GetContentOffset());
2740 
2741     UniquePtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
2742         MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
2743     if (!breakSink || !*breakSink) return;
2744 
2745     uint32_t length = iterNext.GetSkippedOffset() - offset;
2746     uint32_t flags = 0;
2747     nsIFrame* initialBreakController =
2748         mappedFlow->mAncestorControllingInitialBreak;
2749     if (!initialBreakController) {
2750       initialBreakController = mLineContainer;
2751     }
2752     if (!initialBreakController->StyleText()->WhiteSpaceCanWrap(
2753             initialBreakController)) {
2754       flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
2755     }
2756     nsTextFrame* startFrame = mappedFlow->mStartFrame;
2757     const nsStyleText* textStyle = startFrame->StyleText();
2758     if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
2759       flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
2760     }
2761     if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::NoBreaks) {
2762       flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
2763     }
2764     if (textStyle->mTextTransform.case_ == StyleTextTransformCase::Capitalize) {
2765       flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
2766     }
2767     if (textStyle->mHyphens == StyleHyphens::Auto &&
2768         textStyle->mLineBreak != StyleLineBreak::Anywhere) {
2769       flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
2770     }
2771 
2772     if (HasCompressedLeadingWhitespace(startFrame, textStyle,
2773                                        mappedFlow->GetContentEnd(), iter)) {
2774       mLineBreaker.AppendInvisibleWhitespace(flags);
2775     }
2776 
2777     if (length > 0) {
2778       BreakSink* sink = mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
2779       if (mDoubleByteText) {
2780         const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
2781         mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
2782                                 flags, sink);
2783       } else {
2784         const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
2785         mLineBreaker.AppendText(hyphenationLanguage, text + offset, length,
2786                                 flags, sink);
2787       }
2788     }
2789 
2790     iter = iterNext;
2791   }
2792 }
2793 
MayCharacterHaveEmphasisMark(uint32_t aCh)2794 static bool MayCharacterHaveEmphasisMark(uint32_t aCh) {
2795   auto category = unicode::GetGeneralCategory(aCh);
2796   // Comparing an unsigned variable against zero is a compile error,
2797   // so we use static assert here to ensure we really don't need to
2798   // compare it with the given constant.
2799   static_assert(std::is_unsigned_v<decltype(category)> &&
2800                     HB_UNICODE_GENERAL_CATEGORY_CONTROL == 0,
2801                 "if this constant is not zero, or category is signed, "
2802                 "we need to explicitly do the comparison below");
2803   return !(category <= HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED ||
2804            (category >= HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR &&
2805             category <= HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR));
2806 }
2807 
MayCharacterHaveEmphasisMark(uint8_t aCh)2808 static bool MayCharacterHaveEmphasisMark(uint8_t aCh) {
2809   // 0x00~0x1f and 0x7f~0x9f are in category Cc
2810   // 0x20 and 0xa0 are in category Zs
2811   bool result = !(aCh <= 0x20 || (aCh >= 0x7f && aCh <= 0xa0));
2812   MOZ_ASSERT(result == MayCharacterHaveEmphasisMark(uint32_t(aCh)),
2813              "result for uint8_t should match result for uint32_t");
2814   return result;
2815 }
2816 
SetupTextEmphasisForTextRun(gfxTextRun * aTextRun,const void * aTextPtr)2817 void BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
2818                                                        const void* aTextPtr) {
2819   if (!mDoubleByteText) {
2820     auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
2821     for (auto i : IntegerRange(aTextRun->GetLength())) {
2822       if (!MayCharacterHaveEmphasisMark(text[i])) {
2823         aTextRun->SetNoEmphasisMark(i);
2824       }
2825     }
2826   } else {
2827     auto text = reinterpret_cast<const char16_t*>(aTextPtr);
2828     auto length = aTextRun->GetLength();
2829     for (size_t i = 0; i < length; ++i) {
2830       if (i + 1 < length && NS_IS_SURROGATE_PAIR(text[i], text[i + 1])) {
2831         uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]);
2832         if (!MayCharacterHaveEmphasisMark(ch)) {
2833           aTextRun->SetNoEmphasisMark(i);
2834           aTextRun->SetNoEmphasisMark(i + 1);
2835         }
2836         ++i;
2837       } else {
2838         if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
2839           aTextRun->SetNoEmphasisMark(i);
2840         }
2841       }
2842     }
2843   }
2844 }
2845 
2846 // Find the flow corresponding to aContent in aUserData
FindFlowForContent(TextRunUserData * aUserData,nsIContent * aContent,TextRunMappedFlow * userMappedFlows)2847 static inline TextRunMappedFlow* FindFlowForContent(
2848     TextRunUserData* aUserData, nsIContent* aContent,
2849     TextRunMappedFlow* userMappedFlows) {
2850   // Find the flow that contains us
2851   int32_t i = aUserData->mLastFlowIndex;
2852   int32_t delta = 1;
2853   int32_t sign = 1;
2854   // Search starting at the current position and examine close-by
2855   // positions first, moving further and further away as we go.
2856   while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
2857     TextRunMappedFlow* flow = &userMappedFlows[i];
2858     if (flow->mStartFrame->GetContent() == aContent) {
2859       return flow;
2860     }
2861 
2862     i += delta;
2863     sign = -sign;
2864     delta = -delta + sign;
2865   }
2866 
2867   // We ran into an array edge.  Add |delta| to |i| once more to get
2868   // back to the side where we still need to search, then step in
2869   // the |sign| direction.
2870   i += delta;
2871   if (sign > 0) {
2872     for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
2873       TextRunMappedFlow* flow = &userMappedFlows[i];
2874       if (flow->mStartFrame->GetContent() == aContent) {
2875         return flow;
2876       }
2877     }
2878   } else {
2879     for (; i >= 0; --i) {
2880       TextRunMappedFlow* flow = &userMappedFlows[i];
2881       if (flow->mStartFrame->GetContent() == aContent) {
2882         return flow;
2883       }
2884     }
2885   }
2886 
2887   return nullptr;
2888 }
2889 
AssignTextRun(gfxTextRun * aTextRun,float aInflation)2890 void BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun,
2891                                          float aInflation) {
2892   for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2893     MappedFlow* mappedFlow = &mMappedFlows[i];
2894     nsTextFrame* startFrame = mappedFlow->mStartFrame;
2895     nsTextFrame* endFrame = mappedFlow->mEndFrame;
2896     nsTextFrame* f;
2897     for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
2898 #ifdef DEBUG_roc
2899       if (f->GetTextRun(mWhichTextRun)) {
2900         gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
2901         if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
2902           if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
2903             NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2904           }
2905         } else {
2906           auto userData =
2907               static_cast<TextRunUserData*>(aTextRun->GetUserData());
2908           TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
2909           if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
2910               userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
2911                   mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
2912             NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2913           }
2914         }
2915       }
2916 #endif
2917 
2918       gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
2919       if (oldTextRun) {
2920         nsTextFrame* firstFrame = nullptr;
2921         uint32_t startOffset = 0;
2922         if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
2923           firstFrame = GetFrameForSimpleFlow(oldTextRun);
2924         } else {
2925           auto userData =
2926               static_cast<TextRunUserData*>(oldTextRun->GetUserData());
2927           TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
2928           firstFrame = userMappedFlows[0].mStartFrame;
2929           if (MOZ_UNLIKELY(f != firstFrame)) {
2930             TextRunMappedFlow* flow =
2931                 FindFlowForContent(userData, f->GetContent(), userMappedFlows);
2932             if (flow) {
2933               startOffset = flow->mDOMOffsetToBeforeTransformOffset;
2934             } else {
2935               NS_ERROR("Can't find flow containing frame 'f'");
2936             }
2937           }
2938         }
2939 
2940         // Optimization: if |f| is the first frame in the flow then there are no
2941         // prev-continuations that use |oldTextRun|.
2942         nsTextFrame* clearFrom = nullptr;
2943         if (MOZ_UNLIKELY(f != firstFrame)) {
2944           // If all the frames in the mapped flow starting at |f| (inclusive)
2945           // are empty then we let the prev-continuations keep the old text run.
2946           gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset,
2947                                     f->GetContentOffset());
2948           uint32_t textRunOffset =
2949               iter.ConvertOriginalToSkipped(f->GetContentOffset());
2950           clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
2951         }
2952         f->ClearTextRun(clearFrom, mWhichTextRun);
2953 
2954 #ifdef DEBUG
2955         if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
2956           // oldTextRun was destroyed - assert that we don't reference it.
2957           for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
2958             NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
2959                          "destroyed text run is still in use");
2960           }
2961         }
2962 #endif
2963       }
2964       f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
2965     }
2966     // Set this bit now; we can't set it any earlier because
2967     // f->ClearTextRun() might clear it out.
2968     nsFrameState whichTextRunState =
2969         startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
2970             ? TEXT_IN_TEXTRUN_USER_DATA
2971             : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
2972     startFrame->AddStateBits(whichTextRunState);
2973   }
2974 }
2975 
2976 NS_QUERYFRAME_HEAD(nsTextFrame)
NS_QUERYFRAME_ENTRY(nsTextFrame)2977   NS_QUERYFRAME_ENTRY(nsTextFrame)
2978 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
2979 
2980 gfxSkipCharsIterator nsTextFrame::EnsureTextRun(
2981     TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget,
2982     nsIFrame* aLineContainer, const nsLineList::iterator* aLine,
2983     uint32_t* aFlowEndInTextRun) {
2984   gfxTextRun* textRun = GetTextRun(aWhichTextRun);
2985   if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
2986     RefPtr<DrawTarget> refDT = aRefDrawTarget;
2987     if (!refDT) {
2988       refDT = CreateReferenceDrawTarget(this);
2989     }
2990     if (refDT) {
2991       BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
2992     }
2993     textRun = GetTextRun(aWhichTextRun);
2994     if (!textRun) {
2995       // A text run was not constructed for this frame. This is bad. The caller
2996       // will check mTextRun.
2997       return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(),
2998                                   0);
2999     }
3000     TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
3001     if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
3002       RemoveProperty(TabWidthProperty());
3003     }
3004   }
3005 
3006   if (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
3007     if (aFlowEndInTextRun) {
3008       *aFlowEndInTextRun = textRun->GetLength();
3009     }
3010     return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
3011   }
3012 
3013   auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
3014   TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
3015   TextRunMappedFlow* flow =
3016       FindFlowForContent(userData, mContent, userMappedFlows);
3017   if (flow) {
3018     // Since textruns can only contain one flow for a given content element,
3019     // this must be our flow.
3020     uint32_t flowIndex = flow - userMappedFlows;
3021     userData->mLastFlowIndex = flowIndex;
3022     gfxSkipCharsIterator iter(textRun->GetSkipChars(),
3023                               flow->mDOMOffsetToBeforeTransformOffset,
3024                               mContentOffset);
3025     if (aFlowEndInTextRun) {
3026       if (flowIndex + 1 < userData->mMappedFlowCount) {
3027         gfxSkipCharsIterator end(textRun->GetSkipChars());
3028         *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
3029             flow[1].mStartFrame->GetContentOffset() +
3030             flow[1].mDOMOffsetToBeforeTransformOffset);
3031       } else {
3032         *aFlowEndInTextRun = textRun->GetLength();
3033       }
3034     }
3035     return iter;
3036   }
3037 
3038   NS_ERROR("Can't find flow containing this frame???");
3039   return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
3040 }
3041 
GetEndOfTrimmedText(const nsTextFragment * aFrag,const nsStyleText * aStyleText,uint32_t aStart,uint32_t aEnd,gfxSkipCharsIterator * aIterator,bool aAllowHangingWS=false)3042 static uint32_t GetEndOfTrimmedText(const nsTextFragment* aFrag,
3043                                     const nsStyleText* aStyleText,
3044                                     uint32_t aStart, uint32_t aEnd,
3045                                     gfxSkipCharsIterator* aIterator,
3046                                     bool aAllowHangingWS = false) {
3047   aIterator->SetSkippedOffset(aEnd);
3048   while (aIterator->GetSkippedOffset() > aStart) {
3049     aIterator->AdvanceSkipped(-1);
3050     if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText,
3051                           aAllowHangingWS))
3052       return aIterator->GetSkippedOffset() + 1;
3053   }
3054   return aStart;
3055 }
3056 
GetTrimmedOffsets(const nsTextFragment * aFrag,TrimmedOffsetFlags aFlags) const3057 nsTextFrame::TrimmedOffsets nsTextFrame::GetTrimmedOffsets(
3058     const nsTextFragment* aFrag, TrimmedOffsetFlags aFlags) const {
3059   NS_ASSERTION(mTextRun, "Need textrun here");
3060   if (!(aFlags & TrimmedOffsetFlags::NotPostReflow)) {
3061     // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
3062     // to be set correctly.  If our parent wasn't reflowed due to the frame
3063     // tree being too deep then the return value doesn't matter.
3064     NS_ASSERTION(
3065         !HasAnyStateBits(NS_FRAME_FIRST_REFLOW) ||
3066             GetParent()->HasAnyStateBits(NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
3067         "Can only call this on frames that have been reflowed");
3068     NS_ASSERTION(!HasAnyStateBits(NS_FRAME_IN_REFLOW),
3069                  "Can only call this on frames that are not being reflowed");
3070   }
3071 
3072   TrimmedOffsets offsets = {GetContentOffset(), GetContentLength()};
3073   const nsStyleText* textStyle = StyleText();
3074   // Note that pre-line newlines should still allow us to trim spaces
3075   // for display
3076   if (textStyle->WhiteSpaceIsSignificant()) return offsets;
3077 
3078   if (!(aFlags & TrimmedOffsetFlags::NoTrimBefore) &&
3079       ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
3080        HasAnyStateBits(TEXT_START_OF_LINE))) {
3081     int32_t whitespaceCount =
3082         GetTrimmableWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
3083     offsets.mStart += whitespaceCount;
3084     offsets.mLength -= whitespaceCount;
3085   }
3086 
3087   if (!(aFlags & TrimmedOffsetFlags::NoTrimAfter) &&
3088       ((aFlags & TrimmedOffsetFlags::NotPostReflow) ||
3089        HasAnyStateBits(TEXT_END_OF_LINE))) {
3090     // This treats a trailing 'pre-line' newline as trimmable. That's fine,
3091     // it's actually what we want since we want whitespace before it to
3092     // be trimmed.
3093     int32_t whitespaceCount = GetTrimmableWhitespaceCount(
3094         aFrag, offsets.GetEnd() - 1, offsets.mLength, -1);
3095     offsets.mLength -= whitespaceCount;
3096   }
3097   return offsets;
3098 }
3099 
IsJustifiableCharacter(const nsStyleText * aTextStyle,const nsTextFragment * aFrag,int32_t aPos,bool aLangIsCJ)3100 static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
3101                                    const nsTextFragment* aFrag, int32_t aPos,
3102                                    bool aLangIsCJ) {
3103   NS_ASSERTION(aPos >= 0, "negative position?!");
3104 
3105   StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
3106   if (justifyStyle == StyleTextJustify::None) {
3107     return false;
3108   }
3109 
3110   char16_t ch = aFrag->CharAt(aPos);
3111   if (ch == '\n' || ch == '\t' || ch == '\r') {
3112     return true;
3113   }
3114   if (ch == ' ' || ch == CH_NBSP) {
3115     // Don't justify spaces that are combined with diacriticals
3116     if (!aFrag->Is2b()) {
3117       return true;
3118     }
3119     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
3120         aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
3121   }
3122 
3123   if (justifyStyle == StyleTextJustify::InterCharacter) {
3124     return true;
3125   } else if (justifyStyle == StyleTextJustify::InterWord) {
3126     return false;
3127   }
3128 
3129   // text-justify: auto
3130   if (ch < 0x2150u) {
3131     return false;
3132   }
3133   if (aLangIsCJ) {
3134     if (  // Number Forms, Arrows, Mathematical Operators
3135         (0x2150u <= ch && ch <= 0x22ffu) ||
3136         // Enclosed Alphanumerics
3137         (0x2460u <= ch && ch <= 0x24ffu) ||
3138         // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
3139         (0x2580u <= ch && ch <= 0x27bfu) ||
3140         // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
3141         // Miscellaneous Mathematical Symbols-B,
3142         // Supplemental Mathematical Operators, Miscellaneous Symbols and Arrows
3143         (0x27f0u <= ch && ch <= 0x2bffu) ||
3144         // CJK Radicals Supplement, CJK Radicals Supplement, Ideographic
3145         // Description Characters, CJK Symbols and Punctuation, Hiragana,
3146         // Katakana, Bopomofo
3147         (0x2e80u <= ch && ch <= 0x312fu) ||
3148         // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
3149         // Enclosed CJK Letters and Months, CJK Compatibility,
3150         // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
3151         // CJK Unified Ideographs, Yi Syllables, Yi Radicals
3152         (0x3190u <= ch && ch <= 0xabffu) ||
3153         // CJK Compatibility Ideographs
3154         (0xf900u <= ch && ch <= 0xfaffu) ||
3155         // Halfwidth and Fullwidth Forms (a part)
3156         (0xff5eu <= ch && ch <= 0xff9fu)) {
3157       return true;
3158     }
3159     if (NS_IS_HIGH_SURROGATE(ch)) {
3160       if (char32_t u = aFrag->ScalarValueAt(aPos)) {
3161         // CJK Unified Ideographs Extension B,
3162         // CJK Unified Ideographs Extension C,
3163         // CJK Unified Ideographs Extension D,
3164         // CJK Compatibility Ideographs Supplement
3165         if (0x20000u <= u && u <= 0x2ffffu) {
3166           return true;
3167         }
3168       }
3169     }
3170   }
3171   return false;
3172 }
3173 
ClearMetrics(ReflowOutput & aMetrics)3174 void nsTextFrame::ClearMetrics(ReflowOutput& aMetrics) {
3175   aMetrics.ClearSize();
3176   aMetrics.SetBlockStartAscent(0);
3177   mAscent = 0;
3178 
3179   AddStateBits(TEXT_NO_RENDERED_GLYPHS);
3180 }
3181 
FindChar(const nsTextFragment * frag,int32_t aOffset,int32_t aLength,char16_t ch)3182 static int32_t FindChar(const nsTextFragment* frag, int32_t aOffset,
3183                         int32_t aLength, char16_t ch) {
3184   int32_t i = 0;
3185   if (frag->Is2b()) {
3186     const char16_t* str = frag->Get2b() + aOffset;
3187     for (; i < aLength; ++i) {
3188       if (*str == ch) return i + aOffset;
3189       ++str;
3190     }
3191   } else {
3192     if (uint16_t(ch) <= 0xFF) {
3193       const char* str = frag->Get1b() + aOffset;
3194       const void* p = memchr(str, ch, aLength);
3195       if (p) return (static_cast<const char*>(p) - str) + aOffset;
3196     }
3197   }
3198   return -1;
3199 }
3200 
IsChineseOrJapanese(const nsTextFrame * aFrame)3201 static bool IsChineseOrJapanese(const nsTextFrame* aFrame) {
3202   if (aFrame->ShouldSuppressLineBreak()) {
3203     // Always treat ruby as CJ language so that those characters can
3204     // be expanded properly even when surrounded by other language.
3205     return true;
3206   }
3207 
3208   nsAtom* language = aFrame->StyleFont()->mLanguage;
3209   if (!language) {
3210     return false;
3211   }
3212   return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
3213          nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
3214 }
3215 
3216 #ifdef DEBUG
IsInBounds(const gfxSkipCharsIterator & aStart,int32_t aContentLength,gfxTextRun::Range aRange)3217 static bool IsInBounds(const gfxSkipCharsIterator& aStart,
3218                        int32_t aContentLength, gfxTextRun::Range aRange) {
3219   if (aStart.GetSkippedOffset() > aRange.start) return false;
3220   if (aContentLength == INT32_MAX) return true;
3221   gfxSkipCharsIterator iter(aStart);
3222   iter.AdvanceOriginal(aContentLength);
3223   return iter.GetSkippedOffset() >= aRange.end;
3224 }
3225 #endif
3226 
PropertyProvider(gfxTextRun * aTextRun,const nsStyleText * aTextStyle,const nsTextFragment * aFrag,nsTextFrame * aFrame,const gfxSkipCharsIterator & aStart,int32_t aLength,nsIFrame * aLineContainer,nscoord aOffsetFromBlockOriginForTabs,nsTextFrame::TextRunType aWhichTextRun)3227 nsTextFrame::PropertyProvider::PropertyProvider(
3228     gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
3229     const nsTextFragment* aFrag, nsTextFrame* aFrame,
3230     const gfxSkipCharsIterator& aStart, int32_t aLength,
3231     nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs,
3232     nsTextFrame::TextRunType aWhichTextRun)
3233     : mTextRun(aTextRun),
3234       mFontGroup(nullptr),
3235       mTextStyle(aTextStyle),
3236       mFrag(aFrag),
3237       mLineContainer(aLineContainer),
3238       mFrame(aFrame),
3239       mStart(aStart),
3240       mTempIterator(aStart),
3241       mTabWidths(nullptr),
3242       mTabWidthsAnalyzedLimit(0),
3243       mLength(aLength),
3244       mWordSpacing(WordSpacing(aFrame, mTextRun, *aTextStyle)),
3245       mLetterSpacing(LetterSpacing(aFrame, *aTextStyle)),
3246       mMinTabAdvance(-1.0),
3247       mHyphenWidth(-1),
3248       mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
3249       mJustificationArrayStart(0),
3250       mReflowing(true),
3251       mWhichTextRun(aWhichTextRun) {
3252   NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
3253 }
3254 
PropertyProvider(nsTextFrame * aFrame,const gfxSkipCharsIterator & aStart,nsTextFrame::TextRunType aWhichTextRun,nsFontMetrics * aFontMetrics)3255 nsTextFrame::PropertyProvider::PropertyProvider(
3256     nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
3257     nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics)
3258     : mTextRun(aFrame->GetTextRun(aWhichTextRun)),
3259       mFontGroup(nullptr),
3260       mFontMetrics(aFontMetrics),
3261       mTextStyle(aFrame->StyleText()),
3262       mFrag(aFrame->TextFragment()),
3263       mLineContainer(nullptr),
3264       mFrame(aFrame),
3265       mStart(aStart),
3266       mTempIterator(aStart),
3267       mTabWidths(nullptr),
3268       mTabWidthsAnalyzedLimit(0),
3269       mLength(aFrame->GetContentLength()),
3270       mWordSpacing(WordSpacing(aFrame, mTextRun, *mTextStyle)),
3271       mLetterSpacing(LetterSpacing(aFrame, *mTextStyle)),
3272       mMinTabAdvance(-1.0),
3273       mHyphenWidth(-1),
3274       mOffsetFromBlockOriginForTabs(0),
3275       mJustificationArrayStart(0),
3276       mReflowing(false),
3277       mWhichTextRun(aWhichTextRun) {
3278   NS_ASSERTION(mTextRun, "Textrun not initialized!");
3279 }
3280 
GetDrawTarget() const3281 already_AddRefed<DrawTarget> nsTextFrame::PropertyProvider::GetDrawTarget()
3282     const {
3283   return CreateReferenceDrawTarget(GetFrame());
3284 }
3285 
MinTabAdvance() const3286 gfxFloat nsTextFrame::PropertyProvider::MinTabAdvance() const {
3287   if (mMinTabAdvance < 0.0) {
3288     mMinTabAdvance = GetMinTabAdvanceAppUnits(mTextRun);
3289   }
3290   return mMinTabAdvance;
3291 }
3292 
3293 /**
3294  * Finds the offset of the first character of the cluster containing aPos
3295  */
FindClusterStart(const gfxTextRun * aTextRun,int32_t aOriginalStart,gfxSkipCharsIterator * aPos)3296 static void FindClusterStart(const gfxTextRun* aTextRun, int32_t aOriginalStart,
3297                              gfxSkipCharsIterator* aPos) {
3298   while (aPos->GetOriginalOffset() > aOriginalStart) {
3299     if (aPos->IsOriginalCharSkipped() ||
3300         aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
3301       break;
3302     }
3303     aPos->AdvanceOriginal(-1);
3304   }
3305 }
3306 
3307 /**
3308  * Finds the offset of the last character of the cluster containing aPos.
3309  * If aAllowSplitLigature is false, we also check for a ligature-group
3310  * start.
3311  */
FindClusterEnd(const gfxTextRun * aTextRun,int32_t aOriginalEnd,gfxSkipCharsIterator * aPos,bool aAllowSplitLigature=true)3312 static void FindClusterEnd(const gfxTextRun* aTextRun, int32_t aOriginalEnd,
3313                            gfxSkipCharsIterator* aPos,
3314                            bool aAllowSplitLigature = true) {
3315   MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,
3316              "character outside string");
3317 
3318   aPos->AdvanceOriginal(1);
3319   while (aPos->GetOriginalOffset() < aOriginalEnd) {
3320     if (aPos->IsOriginalCharSkipped() ||
3321         (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
3322          (aAllowSplitLigature ||
3323           aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
3324       break;
3325     }
3326     aPos->AdvanceOriginal(1);
3327   }
3328   aPos->AdvanceOriginal(-1);
3329 }
3330 
ComputeJustification(Range aRange,nsTArray<JustificationAssignment> * aAssignments)3331 JustificationInfo nsTextFrame::PropertyProvider::ComputeJustification(
3332     Range aRange, nsTArray<JustificationAssignment>* aAssignments) {
3333   JustificationInfo info;
3334 
3335   // Horizontal-in-vertical frame is orthogonal to the line, so it
3336   // doesn't actually include any justification opportunity inside.
3337   // The spec says such frame should be treated as a U+FFFC. Since we
3338   // do not insert justification opportunities on the sides of that
3339   // character, the sides of this frame are not justifiable either.
3340   if (mFrame->Style()->IsTextCombined()) {
3341     return info;
3342   }
3343 
3344   bool isCJ = IsChineseOrJapanese(mFrame);
3345   nsSkipCharsRunIterator run(
3346       mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
3347   run.SetOriginalOffset(aRange.start);
3348   mJustificationArrayStart = run.GetSkippedOffset();
3349 
3350   nsTArray<JustificationAssignment> assignments;
3351   assignments.SetCapacity(aRange.Length());
3352   while (run.NextRun()) {
3353     uint32_t originalOffset = run.GetOriginalOffset();
3354     uint32_t skippedOffset = run.GetSkippedOffset();
3355     uint32_t length = run.GetRunLength();
3356     assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
3357 
3358     gfxSkipCharsIterator iter = run.GetPos();
3359     for (uint32_t i = 0; i < length; ++i) {
3360       uint32_t offset = originalOffset + i;
3361       if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ)) {
3362         continue;
3363       }
3364 
3365       iter.SetOriginalOffset(offset);
3366 
3367       FindClusterStart(mTextRun, originalOffset, &iter);
3368       uint32_t firstCharOffset = iter.GetSkippedOffset();
3369       uint32_t firstChar = firstCharOffset > mJustificationArrayStart
3370                                ? firstCharOffset - mJustificationArrayStart
3371                                : 0;
3372       if (!firstChar) {
3373         info.mIsStartJustifiable = true;
3374       } else {
3375         auto& assign = assignments[firstChar];
3376         auto& prevAssign = assignments[firstChar - 1];
3377         if (prevAssign.mGapsAtEnd) {
3378           prevAssign.mGapsAtEnd = 1;
3379           assign.mGapsAtStart = 1;
3380         } else {
3381           assign.mGapsAtStart = 2;
3382           info.mInnerOpportunities++;
3383         }
3384       }
3385 
3386       FindClusterEnd(mTextRun, originalOffset + length, &iter);
3387       uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
3388       // Assign the two gaps temporary to the last char. If the next cluster is
3389       // justifiable as well, one of the gaps will be removed by code above.
3390       assignments[lastChar].mGapsAtEnd = 2;
3391       info.mInnerOpportunities++;
3392 
3393       // Skip the whole cluster
3394       i = iter.GetOriginalOffset() - originalOffset;
3395     }
3396   }
3397 
3398   if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
3399     // We counted the expansion opportunity after the last character,
3400     // but it is not an inner opportunity.
3401     MOZ_ASSERT(info.mInnerOpportunities > 0);
3402     info.mInnerOpportunities--;
3403     info.mIsEndJustifiable = true;
3404   }
3405 
3406   if (aAssignments) {
3407     *aAssignments = std::move(assignments);
3408   }
3409   return info;
3410 }
3411 
3412 // aStart, aLength in transformed string offsets
GetSpacing(Range aRange,Spacing * aSpacing) const3413 void nsTextFrame::PropertyProvider::GetSpacing(Range aRange,
3414                                                Spacing* aSpacing) const {
3415   GetSpacingInternal(
3416       aRange, aSpacing,
3417       !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab));
3418 }
3419 
CanAddSpacingAfter(const gfxTextRun * aTextRun,uint32_t aOffset,bool aNewlineIsSignificant)3420 static bool CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset,
3421                                bool aNewlineIsSignificant) {
3422   if (aOffset + 1 >= aTextRun->GetLength()) {
3423     return true;
3424   }
3425   const auto* g = aTextRun->GetCharacterGlyphs();
3426   return g[aOffset + 1].IsClusterStart() &&
3427          g[aOffset + 1].IsLigatureGroupStart() &&
3428          !g[aOffset].CharIsFormattingControl() && !g[aOffset].CharIsTab() &&
3429          !(aNewlineIsSignificant && g[aOffset].CharIsNewline());
3430 }
3431 
ComputeTabWidthAppUnits(const nsIFrame * aFrame)3432 static gfxFloat ComputeTabWidthAppUnits(const nsIFrame* aFrame) {
3433   const auto& tabSize = aFrame->StyleText()->mTabSize;
3434   if (tabSize.IsLength()) {
3435     nscoord w = tabSize.length._0.ToAppUnits();
3436     MOZ_ASSERT(w >= 0);
3437     return w;
3438   }
3439 
3440   MOZ_ASSERT(tabSize.IsNumber());
3441   gfxFloat spaces = tabSize.number._0;
3442   MOZ_ASSERT(spaces >= 0);
3443 
3444   const nsIFrame* cb = aFrame->GetContainingBlock(0, aFrame->StyleDisplay());
3445   const auto* styleText = cb->StyleText();
3446 
3447   // Round the space width when converting to appunits the same way textruns do.
3448   RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(cb, 1.0f);
3449   bool vertical = cb->GetWritingMode().IsCentralBaseline();
3450   nscoord spaceWidth = nscoord(NS_round(
3451       GetFirstFontMetrics(fm->GetThebesFontGroup(), vertical).spaceWidth *
3452       cb->PresContext()->AppUnitsPerDevPixel()));
3453   return spaces * (spaceWidth + styleText->mLetterSpacing.ToAppUnits() +
3454                    styleText->mWordSpacing.Resolve(spaceWidth));
3455 }
3456 
GetSpacingInternal(Range aRange,Spacing * aSpacing,bool aIgnoreTabs) const3457 void nsTextFrame::PropertyProvider::GetSpacingInternal(Range aRange,
3458                                                        Spacing* aSpacing,
3459                                                        bool aIgnoreTabs) const {
3460   MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
3461 
3462   uint32_t index;
3463   for (index = 0; index < aRange.Length(); ++index) {
3464     aSpacing[index].mBefore = 0.0;
3465     aSpacing[index].mAfter = 0.0;
3466   }
3467 
3468   if (mFrame->Style()->IsTextCombined()) {
3469     return;
3470   }
3471 
3472   // Find our offset into the original+transformed string
3473   gfxSkipCharsIterator start(mStart);
3474   start.SetSkippedOffset(aRange.start);
3475 
3476   // First, compute the word and letter spacing
3477   if (mWordSpacing || mLetterSpacing) {
3478     // Iterate over non-skipped characters
3479     nsSkipCharsRunIterator run(
3480         start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
3481     bool newlineIsSignificant = mTextStyle->NewlineIsSignificant(mFrame);
3482     while (run.NextRun()) {
3483       uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
3484       gfxSkipCharsIterator iter = run.GetPos();
3485       for (int32_t i = 0; i < run.GetRunLength(); ++i) {
3486         if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i,
3487                                newlineIsSignificant)) {
3488           // End of a cluster, not in a ligature: put letter-spacing after it
3489           aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
3490         }
3491         if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), mFrame,
3492                                   mTextStyle)) {
3493           // It kinda sucks, but space characters can be part of clusters,
3494           // and even still be whitespace (I think!)
3495           iter.SetSkippedOffset(run.GetSkippedOffset() + i);
3496           FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
3497                          &iter);
3498           uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
3499           aSpacing[runOffset].mAfter += mWordSpacing;
3500         }
3501       }
3502     }
3503   }
3504 
3505   // Now add tab spacing, if there is any
3506   if (!aIgnoreTabs) {
3507     gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame);
3508     if (tabWidth > 0) {
3509       CalcTabWidths(aRange, tabWidth);
3510       if (mTabWidths) {
3511         mTabWidths->ApplySpacing(aSpacing,
3512                                  aRange.start - mStart.GetSkippedOffset(),
3513                                  aRange.Length());
3514       }
3515     }
3516   }
3517 
3518   // Now add in justification spacing
3519   if (mJustificationSpacings.Length() > 0) {
3520     // If there is any spaces trimmed at the end, aStart + aLength may
3521     // be larger than the flags array. When that happens, we can simply
3522     // ignore those spaces.
3523     auto arrayEnd = mJustificationArrayStart +
3524                     static_cast<uint32_t>(mJustificationSpacings.Length());
3525     auto end = std::min(aRange.end, arrayEnd);
3526     MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
3527     for (auto i = aRange.start; i < end; i++) {
3528       const auto& spacing =
3529           mJustificationSpacings[i - mJustificationArrayStart];
3530       uint32_t offset = i - aRange.start;
3531       aSpacing[offset].mBefore += spacing.mBefore;
3532       aSpacing[offset].mAfter += spacing.mAfter;
3533     }
3534   }
3535 }
3536 
3537 // aX and the result are in whole appunits.
AdvanceToNextTab(gfxFloat aX,gfxFloat aTabWidth,gfxFloat aMinAdvance)3538 static gfxFloat AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth,
3539                                  gfxFloat aMinAdvance) {
3540   // Advance aX to the next multiple of aTabWidth. We must advance
3541   // by at least aMinAdvance.
3542   return ceil((aX + aMinAdvance) / aTabWidth) * aTabWidth;
3543 }
3544 
CalcTabWidths(Range aRange,gfxFloat aTabWidth) const3545 void nsTextFrame::PropertyProvider::CalcTabWidths(Range aRange,
3546                                                   gfxFloat aTabWidth) const {
3547   MOZ_ASSERT(aTabWidth > 0);
3548 
3549   if (!mTabWidths) {
3550     if (mReflowing && !mLineContainer) {
3551       // Intrinsic width computation does its own tab processing. We
3552       // just don't do anything here.
3553       return;
3554     }
3555     if (!mReflowing) {
3556       mTabWidths = mFrame->GetProperty(TabWidthProperty());
3557 #ifdef DEBUG
3558       // If we're not reflowing, we should have already computed the
3559       // tab widths; check that they're available as far as the last
3560       // tab character present (if any)
3561       for (uint32_t i = aRange.end; i > aRange.start; --i) {
3562         if (mTextRun->CharIsTab(i - 1)) {
3563           uint32_t startOffset = mStart.GetSkippedOffset();
3564           NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
3565                        "Precomputed tab widths are missing!");
3566           break;
3567         }
3568       }
3569 #endif
3570       return;
3571     }
3572   }
3573 
3574   uint32_t startOffset = mStart.GetSkippedOffset();
3575   MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
3576   MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
3577   uint32_t tabsEnd =
3578       (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
3579   if (tabsEnd < aRange.end) {
3580     NS_ASSERTION(mReflowing,
3581                  "We need precomputed tab widths, but don't have enough.");
3582 
3583     for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
3584       Spacing spacing;
3585       GetSpacingInternal(Range(i, i + 1), &spacing, true);
3586       mOffsetFromBlockOriginForTabs += spacing.mBefore;
3587 
3588       if (!mTextRun->CharIsTab(i)) {
3589         if (mTextRun->IsClusterStart(i)) {
3590           uint32_t clusterEnd = i + 1;
3591           while (clusterEnd < mTextRun->GetLength() &&
3592                  !mTextRun->IsClusterStart(clusterEnd)) {
3593             ++clusterEnd;
3594           }
3595           mOffsetFromBlockOriginForTabs +=
3596               mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
3597         }
3598       } else {
3599         if (!mTabWidths) {
3600           mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
3601           mFrame->SetProperty(TabWidthProperty(), mTabWidths);
3602         }
3603         double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
3604                                           aTabWidth, MinTabAdvance());
3605         mTabWidths->mWidths.AppendElement(
3606             TabWidth(i - startOffset,
3607                      NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
3608         mOffsetFromBlockOriginForTabs = nextTab;
3609       }
3610 
3611       mOffsetFromBlockOriginForTabs += spacing.mAfter;
3612     }
3613 
3614     if (mTabWidths) {
3615       mTabWidths->mLimit = aRange.end - startOffset;
3616     }
3617   }
3618 
3619   if (!mTabWidths) {
3620     // Delete any stale property that may be left on the frame
3621     mFrame->RemoveProperty(TabWidthProperty());
3622     mTabWidthsAnalyzedLimit =
3623         std::max(mTabWidthsAnalyzedLimit, aRange.end - startOffset);
3624   }
3625 }
3626 
GetHyphenWidth() const3627 gfxFloat nsTextFrame::PropertyProvider::GetHyphenWidth() const {
3628   if (mHyphenWidth < 0) {
3629     mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
3630   }
3631   return mHyphenWidth + mLetterSpacing;
3632 }
3633 
IS_HYPHEN(char16_t u)3634 static inline bool IS_HYPHEN(char16_t u) {
3635   return u == char16_t('-') ||  // HYPHEN-MINUS
3636          u == 0x058A ||         // ARMENIAN HYPHEN
3637          u == 0x2010 ||         // HYPHEN
3638          u == 0x2012 ||         // FIGURE DASH
3639          u == 0x2013;           // EN DASH
3640 }
3641 
GetHyphenationBreaks(Range aRange,HyphenType * aBreakBefore) const3642 void nsTextFrame::PropertyProvider::GetHyphenationBreaks(
3643     Range aRange, HyphenType* aBreakBefore) const {
3644   MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
3645   MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
3646 
3647   if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
3648       mTextStyle->mHyphens == StyleHyphens::None) {
3649     memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
3650            aRange.Length() * sizeof(HyphenType));
3651     return;
3652   }
3653 
3654   // Iterate through the original-string character runs
3655   nsSkipCharsRunIterator run(
3656       mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
3657   run.SetSkippedOffset(aRange.start);
3658   // We need to visit skipped characters so that we can detect SHY
3659   run.SetVisitSkipped();
3660 
3661   int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
3662   bool allowHyphenBreakBeforeNextChar =
3663       prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
3664       prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
3665       mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
3666 
3667   while (run.NextRun()) {
3668     NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
3669     if (run.IsSkipped()) {
3670       // Check if there's a soft hyphen which would let us hyphenate before
3671       // the next non-skipped character. Don't look at soft hyphens followed
3672       // by other skipped characters, we won't use them.
3673       allowHyphenBreakBeforeNextChar =
3674           mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) ==
3675           CH_SHY;
3676     } else {
3677       int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
3678       memset(aBreakBefore + runOffsetInSubstring,
3679              static_cast<uint8_t>(HyphenType::None),
3680              run.GetRunLength() * sizeof(HyphenType));
3681       // Don't allow hyphen breaks at the start of the line
3682       aBreakBefore[runOffsetInSubstring] =
3683           allowHyphenBreakBeforeNextChar &&
3684                   (!mFrame->HasAnyStateBits(TEXT_START_OF_LINE) ||
3685                    run.GetSkippedOffset() > mStart.GetSkippedOffset())
3686               ? HyphenType::Soft
3687               : HyphenType::None;
3688       allowHyphenBreakBeforeNextChar = false;
3689     }
3690   }
3691 
3692   if (mTextStyle->mHyphens == StyleHyphens::Auto) {
3693     gfxSkipCharsIterator skipIter(mStart);
3694     for (uint32_t i = 0; i < aRange.Length(); ++i) {
3695       if (IS_HYPHEN(mFrag->CharAt(
3696               skipIter.ConvertSkippedToOriginal(aRange.start + i)))) {
3697         if (i < aRange.Length() - 1) {
3698           aBreakBefore[i + 1] = HyphenType::Explicit;
3699         }
3700         continue;
3701       }
3702 
3703       if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
3704           aBreakBefore[i] == HyphenType::None) {
3705         aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
3706       }
3707     }
3708   }
3709 }
3710 
InitializeForDisplay(bool aTrimAfter)3711 void nsTextFrame::PropertyProvider::InitializeForDisplay(bool aTrimAfter) {
3712   nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
3713       mFrag, (aTrimAfter ? nsTextFrame::TrimmedOffsetFlags::Default
3714                          : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter));
3715   mStart.SetOriginalOffset(trimmed.mStart);
3716   mLength = trimmed.mLength;
3717   SetupJustificationSpacing(true);
3718 }
3719 
InitializeForMeasure()3720 void nsTextFrame::PropertyProvider::InitializeForMeasure() {
3721   nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
3722       mFrag, nsTextFrame::TrimmedOffsetFlags::NotPostReflow);
3723   mStart.SetOriginalOffset(trimmed.mStart);
3724   mLength = trimmed.mLength;
3725   SetupJustificationSpacing(false);
3726 }
3727 
SetupJustificationSpacing(bool aPostReflow)3728 void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
3729     bool aPostReflow) {
3730   MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
3731 
3732   if (!mFrame->HasAnyStateBits(TEXT_JUSTIFICATION_ENABLED)) {
3733     return;
3734   }
3735 
3736   gfxSkipCharsIterator start(mStart), end(mStart);
3737   // We can't just use our mLength here; when InitializeForDisplay is
3738   // called with false for aTrimAfter, we still shouldn't be assigning
3739   // justification space to any trailing whitespace.
3740   nsTextFrame::TrimmedOffsets trimmed = mFrame->GetTrimmedOffsets(
3741       mFrag, (aPostReflow ? nsTextFrame::TrimmedOffsetFlags::Default
3742                           : nsTextFrame::TrimmedOffsetFlags::NotPostReflow));
3743   end.AdvanceOriginal(trimmed.mLength);
3744   gfxSkipCharsIterator realEnd(end);
3745 
3746   Range range(uint32_t(start.GetOriginalOffset()),
3747               uint32_t(end.GetOriginalOffset()));
3748   nsTArray<JustificationAssignment> assignments;
3749   JustificationInfo info = ComputeJustification(range, &assignments);
3750 
3751   auto assign = mFrame->GetJustificationAssignment();
3752   auto totalGaps = JustificationUtils::CountGaps(info, assign);
3753   if (!totalGaps || assignments.IsEmpty()) {
3754     // Nothing to do, nothing is justifiable and we shouldn't have any
3755     // justification space assigned
3756     return;
3757   }
3758 
3759   // Remember that textrun measurements are in the run's orientation,
3760   // so its advance "width" is actually a height in vertical writing modes,
3761   // corresponding to the inline-direction of the frame.
3762   gfxFloat naturalWidth = mTextRun->GetAdvanceWidth(
3763       Range(mStart.GetSkippedOffset(), realEnd.GetSkippedOffset()), this);
3764   if (mFrame->HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
3765     naturalWidth += GetHyphenWidth();
3766   }
3767   nscoord totalSpacing = mFrame->ISize() - naturalWidth;
3768   if (totalSpacing <= 0) {
3769     // No space available
3770     return;
3771   }
3772 
3773   assignments[0].mGapsAtStart = assign.mGapsAtStart;
3774   assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
3775 
3776   MOZ_ASSERT(mJustificationSpacings.IsEmpty());
3777   JustificationApplicationState state(totalGaps, totalSpacing);
3778   mJustificationSpacings.SetCapacity(assignments.Length());
3779   for (const JustificationAssignment& assign : assignments) {
3780     Spacing* spacing = mJustificationSpacings.AppendElement();
3781     spacing->mBefore = state.Consume(assign.mGapsAtStart);
3782     spacing->mAfter = state.Consume(assign.mGapsAtEnd);
3783   }
3784 }
3785 
InitFontGroupAndFontMetrics() const3786 void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
3787   if (!mFontMetrics) {
3788     if (mWhichTextRun == nsTextFrame::eInflated) {
3789       if (!mFrame->InflatedFontMetrics()) {
3790         float inflation = mFrame->GetFontSizeInflation();
3791         mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, inflation);
3792         mFrame->SetInflatedFontMetrics(mFontMetrics);
3793       } else {
3794         mFontMetrics = mFrame->InflatedFontMetrics();
3795       }
3796     } else {
3797       mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
3798     }
3799   }
3800   mFontGroup = mFontMetrics->GetThebesFontGroup();
3801 }
3802 
3803 //----------------------------------------------------------------------
3804 
EnsureDifferentColors(nscolor colorA,nscolor colorB)3805 static nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB) {
3806   if (colorA == colorB) {
3807     nscolor res;
3808     res = NS_RGB(NS_GET_R(colorA) ^ 0xff, NS_GET_G(colorA) ^ 0xff,
3809                  NS_GET_B(colorA) ^ 0xff);
3810     return res;
3811   }
3812   return colorA;
3813 }
3814 
3815 //-----------------------------------------------------------------------------
3816 
nsTextPaintStyle(nsTextFrame * aFrame)3817 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
3818     : mFrame(aFrame),
3819       mPresContext(aFrame->PresContext()),
3820       mInitCommonColors(false),
3821       mInitSelectionColorsAndShadow(false),
3822       mResolveColors(true),
3823       mSelectionTextColor(NS_RGBA(0, 0, 0, 0)),
3824       mSelectionBGColor(NS_RGBA(0, 0, 0, 0)),
3825       mSufficientContrast(0),
3826       mFrameBackgroundColor(NS_RGBA(0, 0, 0, 0)),
3827       mSystemFieldForegroundColor(NS_RGBA(0, 0, 0, 0)),
3828       mSystemFieldBackgroundColor(NS_RGBA(0, 0, 0, 0)) {
3829   for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
3830     mSelectionStyle[i].mInit = false;
3831 }
3832 
EnsureSufficientContrast(nscolor * aForeColor,nscolor * aBackColor)3833 bool nsTextPaintStyle::EnsureSufficientContrast(nscolor* aForeColor,
3834                                                 nscolor* aBackColor) {
3835   InitCommonColors();
3836 
3837   // If the combination of selection background color and frame background color
3838   // has sufficient contrast, don't exchange the selection colors.
3839   //
3840   // Note we use a different threshold here: mSufficientContrast is for contrast
3841   // between text and background colors, but since we're diffing two
3842   // backgrounds, we don't need that much contrast.  We match the heuristic from
3843   // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG and use 20% of mSufficientContrast.
3844   const int32_t minLuminosityDifferenceForBackground = mSufficientContrast / 5;
3845   const int32_t backLuminosityDifference =
3846       NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
3847   if (backLuminosityDifference >= minLuminosityDifferenceForBackground) {
3848     return false;
3849   }
3850 
3851   // Otherwise, we should use the higher-contrast color for the selection
3852   // background color.
3853   int32_t foreLuminosityDifference =
3854       NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
3855   if (backLuminosityDifference < foreLuminosityDifference) {
3856     nscolor tmpColor = *aForeColor;
3857     *aForeColor = *aBackColor;
3858     *aBackColor = tmpColor;
3859     return true;
3860   }
3861   return false;
3862 }
3863 
GetTextColor()3864 nscolor nsTextPaintStyle::GetTextColor() {
3865   if (SVGUtils::IsInSVGTextSubtree(mFrame)) {
3866     if (!mResolveColors) {
3867       return NS_SAME_AS_FOREGROUND_COLOR;
3868     }
3869 
3870     const nsStyleSVG* style = mFrame->StyleSVG();
3871     switch (style->mFill.kind.tag) {
3872       case StyleSVGPaintKind::Tag::None:
3873         return NS_RGBA(0, 0, 0, 0);
3874       case StyleSVGPaintKind::Tag::Color:
3875         return nsLayoutUtils::GetColor(mFrame, &nsStyleSVG::mFill);
3876       default:
3877         NS_ERROR("cannot resolve SVG paint to nscolor");
3878         return NS_RGBA(0, 0, 0, 255);
3879     }
3880   }
3881 
3882   return nsLayoutUtils::GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
3883 }
3884 
GetSelectionColors(nscolor * aForeColor,nscolor * aBackColor)3885 bool nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
3886                                           nscolor* aBackColor) {
3887   NS_ASSERTION(aForeColor, "aForeColor is null");
3888   NS_ASSERTION(aBackColor, "aBackColor is null");
3889 
3890   if (!InitSelectionColorsAndShadow()) return false;
3891 
3892   *aForeColor = mSelectionTextColor;
3893   *aBackColor = mSelectionBGColor;
3894   return true;
3895 }
3896 
GetHighlightColors(nscolor * aForeColor,nscolor * aBackColor)3897 void nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
3898                                           nscolor* aBackColor) {
3899   NS_ASSERTION(aForeColor, "aForeColor is null");
3900   NS_ASSERTION(aBackColor, "aBackColor is null");
3901 
3902   const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
3903   const Selection* selection =
3904       frameSelection->GetSelection(SelectionType::eFind);
3905   const SelectionCustomColors* customColors = nullptr;
3906   if (selection) {
3907     customColors = selection->GetCustomColors();
3908   }
3909 
3910   if (!customColors) {
3911     nscolor backColor = LookAndFeel::Color(
3912         LookAndFeel::ColorID::TextHighlightBackground, mFrame);
3913     nscolor foreColor = LookAndFeel::Color(
3914         LookAndFeel::ColorID::TextHighlightForeground, mFrame);
3915     EnsureSufficientContrast(&foreColor, &backColor);
3916     *aForeColor = foreColor;
3917     *aBackColor = backColor;
3918     return;
3919   }
3920 
3921   if (customColors->mForegroundColor && customColors->mBackgroundColor) {
3922     nscolor foreColor = *customColors->mForegroundColor;
3923     nscolor backColor = *customColors->mBackgroundColor;
3924 
3925     if (EnsureSufficientContrast(&foreColor, &backColor) &&
3926         customColors->mAltForegroundColor &&
3927         customColors->mAltBackgroundColor) {
3928       foreColor = *customColors->mAltForegroundColor;
3929       backColor = *customColors->mAltBackgroundColor;
3930     }
3931 
3932     *aForeColor = foreColor;
3933     *aBackColor = backColor;
3934     return;
3935   }
3936 
3937   InitCommonColors();
3938 
3939   if (customColors->mBackgroundColor) {
3940     // !mForegroundColor means "currentColor"; the current color of the text.
3941     nscolor foreColor = GetTextColor();
3942     nscolor backColor = *customColors->mBackgroundColor;
3943 
3944     int32_t luminosityDifference =
3945         NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
3946 
3947     if (mSufficientContrast > luminosityDifference &&
3948         customColors->mAltBackgroundColor) {
3949       int32_t altLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(
3950           foreColor, *customColors->mAltBackgroundColor);
3951 
3952       if (luminosityDifference < altLuminosityDifference) {
3953         backColor = *customColors->mAltBackgroundColor;
3954       }
3955     }
3956 
3957     *aForeColor = foreColor;
3958     *aBackColor = backColor;
3959     return;
3960   }
3961 
3962   if (customColors->mForegroundColor) {
3963     nscolor foreColor = *customColors->mForegroundColor;
3964     // !mBackgroundColor means "transparent"; the current color of the
3965     // background.
3966 
3967     int32_t luminosityDifference =
3968         NS_LUMINOSITY_DIFFERENCE(foreColor, mFrameBackgroundColor);
3969 
3970     if (mSufficientContrast > luminosityDifference &&
3971         customColors->mAltForegroundColor) {
3972       int32_t altLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(
3973           *customColors->mForegroundColor, mFrameBackgroundColor);
3974 
3975       if (luminosityDifference < altLuminosityDifference) {
3976         foreColor = *customColors->mAltForegroundColor;
3977       }
3978     }
3979 
3980     *aForeColor = foreColor;
3981     *aBackColor = NS_TRANSPARENT;
3982     return;
3983   }
3984 
3985   // There are neither mForegroundColor nor mBackgroundColor.
3986   *aForeColor = GetTextColor();
3987   *aBackColor = NS_TRANSPARENT;
3988 }
3989 
GetURLSecondaryColor(nscolor * aForeColor)3990 void nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor) {
3991   NS_ASSERTION(aForeColor, "aForeColor is null");
3992 
3993   nscolor textColor = GetTextColor();
3994   textColor = NS_RGBA(NS_GET_R(textColor), NS_GET_G(textColor),
3995                       NS_GET_B(textColor), (uint8_t)(255 * 0.5f));
3996   // Don't use true alpha color for readability.
3997   InitCommonColors();
3998   *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
3999 }
4000 
GetIMESelectionColors(int32_t aIndex,nscolor * aForeColor,nscolor * aBackColor)4001 void nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex,
4002                                              nscolor* aForeColor,
4003                                              nscolor* aBackColor) {
4004   NS_ASSERTION(aForeColor, "aForeColor is null");
4005   NS_ASSERTION(aBackColor, "aBackColor is null");
4006   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
4007 
4008   nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
4009   *aForeColor = selectionStyle->mTextColor;
4010   *aBackColor = selectionStyle->mBGColor;
4011 }
4012 
GetSelectionUnderlineForPaint(int32_t aIndex,nscolor * aLineColor,float * aRelativeSize,uint8_t * aStyle)4013 bool nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex,
4014                                                      nscolor* aLineColor,
4015                                                      float* aRelativeSize,
4016                                                      uint8_t* aStyle) {
4017   NS_ASSERTION(aLineColor, "aLineColor is null");
4018   NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
4019   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
4020 
4021   nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
4022   if (selectionStyle->mUnderlineStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE ||
4023       selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
4024       selectionStyle->mUnderlineRelativeSize <= 0.0f)
4025     return false;
4026 
4027   *aLineColor = selectionStyle->mUnderlineColor;
4028   *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
4029   *aStyle = selectionStyle->mUnderlineStyle;
4030   return true;
4031 }
4032 
InitCommonColors()4033 void nsTextPaintStyle::InitCommonColors() {
4034   if (mInitCommonColors) {
4035     return;
4036   }
4037 
4038   auto bgFrame = nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
4039   nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
4040   nscolor bgColor = bgFrame.mFrame ? bgFrame.mFrame->GetVisitedDependentColor(
4041                                          &nsStyleBackground::mBackgroundColor)
4042                                    : defaultBgColor;
4043 
4044   mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
4045 
4046   mSystemFieldForegroundColor =
4047       LookAndFeel::Color(LookAndFeel::ColorID::Fieldtext, mFrame);
4048   mSystemFieldBackgroundColor =
4049       LookAndFeel::Color(LookAndFeel::ColorID::Field, mFrame);
4050 
4051   if (bgFrame.mIsThemed) {
4052     // Assume a native widget has sufficient contrast always
4053     mSufficientContrast = 0;
4054     mInitCommonColors = true;
4055     return;
4056   }
4057 
4058   NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
4059                "default background color is not opaque");
4060 
4061   nscolor defaultWindowBackgroundColor =
4062       LookAndFeel::Color(LookAndFeel::ColorID::WindowBackground, mFrame);
4063   nscolor selectionTextColor =
4064       LookAndFeel::Color(LookAndFeel::ColorID::TextSelectForeground, mFrame);
4065   nscolor selectionBGColor =
4066       LookAndFeel::Color(LookAndFeel::ColorID::TextSelectBackground, mFrame);
4067 
4068   mSufficientContrast = std::min(
4069       std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
4070                NS_LUMINOSITY_DIFFERENCE(selectionTextColor, selectionBGColor)),
4071       NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, selectionBGColor));
4072 
4073   mInitCommonColors = true;
4074 }
4075 
GetSystemFieldForegroundColor()4076 nscolor nsTextPaintStyle::GetSystemFieldForegroundColor() {
4077   InitCommonColors();
4078   return mSystemFieldForegroundColor;
4079 }
4080 
GetSystemFieldBackgroundColor()4081 nscolor nsTextPaintStyle::GetSystemFieldBackgroundColor() {
4082   InitCommonColors();
4083   return mSystemFieldBackgroundColor;
4084 }
4085 
InitSelectionColorsAndShadow()4086 bool nsTextPaintStyle::InitSelectionColorsAndShadow() {
4087   if (mInitSelectionColorsAndShadow) return true;
4088 
4089   int16_t selectionFlags;
4090   const int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
4091   if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
4092       selectionStatus < nsISelectionController::SELECTION_ON) {
4093     // Not displaying the normal selection.
4094     // We're not caching this fact, so every call to GetSelectionColors
4095     // will come through here. We could avoid this, but it's not really worth
4096     // it.
4097     return false;
4098   }
4099 
4100   mInitSelectionColorsAndShadow = true;
4101 
4102   // Use ::selection pseudo class if applicable.
4103   if (RefPtr<ComputedStyle> style =
4104           mFrame->ComputeSelectionStyle(selectionStatus)) {
4105     mSelectionBGColor =
4106         style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
4107     mSelectionTextColor =
4108         style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4109     mSelectionPseudoStyle = std::move(style);
4110     return true;
4111   }
4112 
4113   nscolor selectionBGColor =
4114       LookAndFeel::Color(LookAndFeel::ColorID::TextSelectBackground, mFrame);
4115 
4116   switch (selectionStatus) {
4117     case nsISelectionController::SELECTION_ATTENTION: {
4118       mSelectionBGColor = LookAndFeel::Color(
4119           LookAndFeel::ColorID::TextSelectBackgroundAttention, mFrame);
4120       mSelectionBGColor =
4121           EnsureDifferentColors(mSelectionBGColor, selectionBGColor);
4122       break;
4123     }
4124     case nsISelectionController::SELECTION_ON: {
4125       mSelectionBGColor = selectionBGColor;
4126       break;
4127     }
4128     default: {
4129       mSelectionBGColor = LookAndFeel::Color(
4130           LookAndFeel::ColorID::TextSelectBackgroundDisabled, mFrame);
4131       mSelectionBGColor =
4132           EnsureDifferentColors(mSelectionBGColor, selectionBGColor);
4133       break;
4134     }
4135   }
4136 
4137   mSelectionTextColor =
4138       LookAndFeel::Color(LookAndFeel::ColorID::TextSelectForeground, mFrame);
4139 
4140   if (mResolveColors) {
4141     // On MacOS X, only the background color gets set,
4142     // the text color remains intact.
4143     if (mSelectionTextColor == NS_SAME_AS_FOREGROUND_COLOR) {
4144       nscolor frameColor =
4145           SVGUtils::IsInSVGTextSubtree(mFrame)
4146               ? mFrame->GetVisitedDependentColor(&nsStyleSVG::mFill)
4147               : mFrame->GetVisitedDependentColor(
4148                     &nsStyleText::mWebkitTextFillColor);
4149       mSelectionTextColor =
4150           EnsureDifferentColors(frameColor, mSelectionBGColor);
4151     } else {
4152       EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
4153     }
4154   }
4155   return true;
4156 }
4157 
GetSelectionStyle(int32_t aIndex)4158 nsTextPaintStyle::nsSelectionStyle* nsTextPaintStyle::GetSelectionStyle(
4159     int32_t aIndex) {
4160   InitSelectionStyle(aIndex);
4161   return &mSelectionStyle[aIndex];
4162 }
4163 
4164 struct StyleIDs {
4165   LookAndFeel::ColorID mForeground, mBackground, mLine;
4166   LookAndFeel::IntID mLineStyle;
4167   LookAndFeel::FloatID mLineRelativeSize;
4168 };
4169 static StyleIDs SelectionStyleIDs[] = {
4170     {LookAndFeel::ColorID::IMERawInputForeground,
4171      LookAndFeel::ColorID::IMERawInputBackground,
4172      LookAndFeel::ColorID::IMERawInputUnderline,
4173      LookAndFeel::IntID::IMERawInputUnderlineStyle,
4174      LookAndFeel::FloatID::IMEUnderlineRelativeSize},
4175     {LookAndFeel::ColorID::IMESelectedRawTextForeground,
4176      LookAndFeel::ColorID::IMESelectedRawTextBackground,
4177      LookAndFeel::ColorID::IMESelectedRawTextUnderline,
4178      LookAndFeel::IntID::IMESelectedRawTextUnderlineStyle,
4179      LookAndFeel::FloatID::IMEUnderlineRelativeSize},
4180     {LookAndFeel::ColorID::IMEConvertedTextForeground,
4181      LookAndFeel::ColorID::IMEConvertedTextBackground,
4182      LookAndFeel::ColorID::IMEConvertedTextUnderline,
4183      LookAndFeel::IntID::IMEConvertedTextUnderlineStyle,
4184      LookAndFeel::FloatID::IMEUnderlineRelativeSize},
4185     {LookAndFeel::ColorID::IMESelectedConvertedTextForeground,
4186      LookAndFeel::ColorID::IMESelectedConvertedTextBackground,
4187      LookAndFeel::ColorID::IMESelectedConvertedTextUnderline,
4188      LookAndFeel::IntID::IMESelectedConvertedTextUnderline,
4189      LookAndFeel::FloatID::IMEUnderlineRelativeSize},
4190     {LookAndFeel::ColorID::End, LookAndFeel::ColorID::End,
4191      LookAndFeel::ColorID::SpellCheckerUnderline,
4192      LookAndFeel::IntID::SpellCheckerUnderlineStyle,
4193      LookAndFeel::FloatID::SpellCheckerUnderlineRelativeSize}};
4194 
InitSelectionStyle(int32_t aIndex)4195 void nsTextPaintStyle::InitSelectionStyle(int32_t aIndex) {
4196   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
4197   nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
4198   if (selectionStyle->mInit) return;
4199 
4200   StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
4201 
4202   nscolor foreColor, backColor;
4203   if (styleIDs->mForeground == LookAndFeel::ColorID::End) {
4204     foreColor = NS_SAME_AS_FOREGROUND_COLOR;
4205   } else {
4206     foreColor = LookAndFeel::Color(styleIDs->mForeground, mFrame);
4207   }
4208   if (styleIDs->mBackground == LookAndFeel::ColorID::End) {
4209     backColor = NS_TRANSPARENT;
4210   } else {
4211     backColor = LookAndFeel::Color(styleIDs->mBackground, mFrame);
4212   }
4213 
4214   // Convert special color to actual color
4215   NS_ASSERTION(foreColor != NS_TRANSPARENT,
4216                "foreColor cannot be NS_TRANSPARENT");
4217   NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
4218                "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
4219   NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
4220                "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
4221 
4222   if (mResolveColors) {
4223     foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
4224 
4225     if (NS_GET_A(backColor) > 0)
4226       EnsureSufficientContrast(&foreColor, &backColor);
4227   }
4228 
4229   nscolor lineColor;
4230   float relativeSize;
4231   uint8_t lineStyle;
4232   GetSelectionUnderline(mFrame, aIndex, &lineColor, &relativeSize, &lineStyle);
4233 
4234   if (mResolveColors)
4235     lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
4236 
4237   selectionStyle->mTextColor = foreColor;
4238   selectionStyle->mBGColor = backColor;
4239   selectionStyle->mUnderlineColor = lineColor;
4240   selectionStyle->mUnderlineStyle = lineStyle;
4241   selectionStyle->mUnderlineRelativeSize = relativeSize;
4242   selectionStyle->mInit = true;
4243 }
4244 
4245 /* static */
GetSelectionUnderline(nsIFrame * aFrame,int32_t aIndex,nscolor * aLineColor,float * aRelativeSize,uint8_t * aStyle)4246 bool nsTextPaintStyle::GetSelectionUnderline(nsIFrame* aFrame, int32_t aIndex,
4247                                              nscolor* aLineColor,
4248                                              float* aRelativeSize,
4249                                              uint8_t* aStyle) {
4250   NS_ASSERTION(aFrame, "aFrame is null");
4251   NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
4252   NS_ASSERTION(aStyle, "aStyle is null");
4253   NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
4254 
4255   StyleIDs& styleID = SelectionStyleIDs[aIndex];
4256 
4257   nscolor color = LookAndFeel::Color(styleID.mLine, aFrame);
4258   int32_t style = LookAndFeel::GetInt(styleID.mLineStyle);
4259   if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
4260     NS_ERROR("Invalid underline style value is specified");
4261     style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
4262   }
4263   float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
4264 
4265   NS_ASSERTION(size, "selection underline relative size must be larger than 0");
4266 
4267   if (aLineColor) {
4268     *aLineColor = color;
4269   }
4270   *aRelativeSize = size;
4271   *aStyle = style;
4272 
4273   return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
4274          color != NS_TRANSPARENT && size > 0.0f;
4275 }
4276 
GetSelectionShadow(Span<const StyleSimpleShadow> * aShadows)4277 bool nsTextPaintStyle::GetSelectionShadow(
4278     Span<const StyleSimpleShadow>* aShadows) {
4279   if (!InitSelectionColorsAndShadow()) {
4280     return false;
4281   }
4282 
4283   if (mSelectionPseudoStyle) {
4284     *aShadows = mSelectionPseudoStyle->StyleText()->mTextShadow.AsSpan();
4285     return true;
4286   }
4287 
4288   return false;
4289 }
4290 
Get40PercentColor(nscolor aForeColor,nscolor aBackColor)4291 inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) {
4292   nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), NS_GET_G(aForeColor),
4293                               NS_GET_B(aForeColor), (uint8_t)(255 * 0.4f));
4294   // Don't use true alpha color for readability.
4295   return NS_ComposeColors(aBackColor, foreColor);
4296 }
4297 
GetResolvedForeColor(nscolor aColor,nscolor aDefaultForeColor,nscolor aBackColor)4298 nscolor nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
4299                                                nscolor aDefaultForeColor,
4300                                                nscolor aBackColor) {
4301   if (aColor == NS_SAME_AS_FOREGROUND_COLOR) return aDefaultForeColor;
4302 
4303   if (aColor != NS_40PERCENT_FOREGROUND_COLOR) return aColor;
4304 
4305   // Get actual background color
4306   nscolor actualBGColor = aBackColor;
4307   if (actualBGColor == NS_TRANSPARENT) {
4308     InitCommonColors();
4309     actualBGColor = mFrameBackgroundColor;
4310   }
4311   return Get40PercentColor(aDefaultForeColor, actualBGColor);
4312 }
4313 
4314 //-----------------------------------------------------------------------------
4315 
4316 #ifdef ACCESSIBILITY
AccessibleType()4317 a11y::AccType nsTextFrame::AccessibleType() {
4318   if (IsEmpty()) {
4319     RenderedText text =
4320         GetRenderedText(0, UINT32_MAX, TextOffsetType::OffsetsInContentText,
4321                         TrailingWhitespace::DontTrim);
4322     if (text.mString.IsEmpty()) {
4323       return a11y::eNoType;
4324     }
4325   }
4326 
4327   return a11y::eTextLeafType;
4328 }
4329 #endif
4330 
4331 //-----------------------------------------------------------------------------
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)4332 void nsTextFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
4333                        nsIFrame* aPrevInFlow) {
4334   NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
4335   MOZ_ASSERT(aContent->IsText(), "Bogus content!");
4336 
4337   // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
4338   // might be invalid if the content was modified while there was no frame
4339   if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4340     aContent->RemoveProperty(nsGkAtoms::newline);
4341     aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4342   }
4343   if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4344     aContent->RemoveProperty(nsGkAtoms::flowlength);
4345     aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4346   }
4347 
4348   // Since our content has a frame now, this flag is no longer needed.
4349   aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
4350 
4351   // We're not a continuing frame.
4352   // mContentOffset = 0; not necessary since we get zeroed out at init
4353   nsIFrame::Init(aContent, aParent, aPrevInFlow);
4354 }
4355 
ClearFrameOffsetCache()4356 void nsTextFrame::ClearFrameOffsetCache() {
4357   // See if we need to remove ourselves from the offset cache
4358   if (HasAnyStateBits(TEXT_IN_OFFSET_CACHE)) {
4359     nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
4360     if (primaryFrame) {
4361       // The primary frame might be null here.  For example,
4362       // nsLineBox::DeleteLineList just destroys the frames in order, which
4363       // means that the primary frame is already dead if we're a continuing text
4364       // frame, in which case, all of its properties are gone, and we don't need
4365       // to worry about deleting this property here.
4366       primaryFrame->RemoveProperty(OffsetToFrameProperty());
4367     }
4368     RemoveStateBits(TEXT_IN_OFFSET_CACHE);
4369   }
4370 }
4371 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)4372 void nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot,
4373                               PostDestroyData& aPostDestroyData) {
4374   ClearFrameOffsetCache();
4375 
4376   // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
4377   // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
4378   // type might be changing.  Not clear whether it's worth it.
4379   ClearTextRuns();
4380   if (mNextContinuation) {
4381     mNextContinuation->SetPrevInFlow(nullptr);
4382   }
4383   // Let the base class destroy the frame
4384   nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
4385 }
4386 
4387 class nsContinuingTextFrame final : public nsTextFrame {
4388  public:
4389   NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4390 
4391   friend nsIFrame* NS_NewContinuingTextFrame(mozilla::PresShell* aPresShell,
4392                                              ComputedStyle* aStyle);
4393 
4394   void Init(nsIContent* aContent, nsContainerFrame* aParent,
4395             nsIFrame* aPrevInFlow) final;
4396 
4397   void DestroyFrom(nsIFrame* aDestructRoot,
4398                    PostDestroyData& aPostDestroyData) final;
4399 
GetPrevContinuation() const4400   nsTextFrame* GetPrevContinuation() const final { return mPrevContinuation; }
SetPrevContinuation(nsIFrame * aPrevContinuation)4401   void SetPrevContinuation(nsIFrame* aPrevContinuation) final {
4402     NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),
4403                  "setting a prev continuation with incorrect type!");
4404     NS_ASSERTION(
4405         !nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
4406         "creating a loop in continuation chain!");
4407     mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
4408     RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4409   }
GetPrevInFlow() const4410   nsTextFrame* GetPrevInFlow() const final {
4411     return HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation
4412                                                            : nullptr;
4413   }
SetPrevInFlow(nsIFrame * aPrevInFlow)4414   void SetPrevInFlow(nsIFrame* aPrevInFlow) final {
4415     NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),
4416                  "setting a prev in flow with incorrect type!");
4417     NS_ASSERTION(
4418         !nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
4419         "creating a loop in continuation chain!");
4420     mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
4421     AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4422   }
4423   nsIFrame* FirstInFlow() const final;
4424   nsIFrame* FirstContinuation() const final;
4425 
4426   void AddInlineMinISize(gfxContext* aRenderingContext,
4427                          InlineMinISizeData* aData) final;
4428   void AddInlinePrefISize(gfxContext* aRenderingContext,
4429                           InlinePrefISizeData* aData) final;
4430 
4431  protected:
nsContinuingTextFrame(ComputedStyle * aStyle,nsPresContext * aPresContext)4432   explicit nsContinuingTextFrame(ComputedStyle* aStyle,
4433                                  nsPresContext* aPresContext)
4434       : nsTextFrame(aStyle, aPresContext, kClassID) {}
4435 
4436   nsTextFrame* mPrevContinuation;
4437 };
4438 
Init(nsIContent * aContent,nsContainerFrame * aParent,nsIFrame * aPrevInFlow)4439 void nsContinuingTextFrame::Init(nsIContent* aContent,
4440                                  nsContainerFrame* aParent,
4441                                  nsIFrame* aPrevInFlow) {
4442   NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
4443 
4444   // Hook the frame into the flow
4445   nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
4446   nsTextFrame* nextContinuation = prev->GetNextContinuation();
4447   SetPrevInFlow(aPrevInFlow);
4448   aPrevInFlow->SetNextInFlow(this);
4449 
4450   // NOTE: bypassing nsTextFrame::Init!!!
4451   nsIFrame::Init(aContent, aParent, aPrevInFlow);
4452 
4453   mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
4454   NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
4455                "Creating ContinuingTextFrame, but there is no more content");
4456   if (prev->Style() != Style()) {
4457     // We're taking part of prev's text, and its style may be different
4458     // so clear its textrun which may no longer be valid (and don't set ours)
4459     prev->ClearTextRuns();
4460   } else {
4461     float inflation = prev->GetFontSizeInflation();
4462     SetFontSizeInflation(inflation);
4463     mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
4464     if (inflation != 1.0f) {
4465       gfxTextRun* uninflatedTextRun =
4466           prev->GetTextRun(nsTextFrame::eNotInflated);
4467       if (uninflatedTextRun) {
4468         SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
4469       }
4470     }
4471   }
4472   if (aPrevInFlow->HasAnyStateBits(NS_FRAME_IS_BIDI)) {
4473     FrameBidiData bidiData = aPrevInFlow->GetBidiData();
4474     bidiData.precedingControl = kBidiLevelNone;
4475     SetProperty(BidiDataProperty(), bidiData);
4476 
4477     if (nextContinuation) {
4478       SetNextContinuation(nextContinuation);
4479       nextContinuation->SetPrevContinuation(this);
4480       // Adjust next-continuations' content offset as needed.
4481       while (nextContinuation &&
4482              nextContinuation->GetContentOffset() < mContentOffset) {
4483 #ifdef DEBUG
4484         FrameBidiData nextBidiData = nextContinuation->GetBidiData();
4485         NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&
4486                          bidiData.baseLevel == nextBidiData.baseLevel,
4487                      "stealing text from different type of BIDI continuation");
4488         MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,
4489                    "There shouldn't be any virtual bidi formatting character "
4490                    "between continuations");
4491 #endif
4492         nextContinuation->mContentOffset = mContentOffset;
4493         nextContinuation = nextContinuation->GetNextContinuation();
4494       }
4495     }
4496     AddStateBits(NS_FRAME_IS_BIDI);
4497   }  // prev frame is bidi
4498 }
4499 
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)4500 void nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot,
4501                                         PostDestroyData& aPostDestroyData) {
4502   ClearFrameOffsetCache();
4503 
4504   // The text associated with this frame will become associated with our
4505   // prev-continuation. If that means the text has changed style, then
4506   // we need to wipe out the text run for the text.
4507   // Note that mPrevContinuation can be null if we're destroying the whole
4508   // frame chain from the start to the end.
4509   // If this frame is mentioned in the userData for a textrun (say
4510   // because there's a direction change at the start of this frame), then
4511   // we have to clear the textrun because we're going away and the
4512   // textrun had better not keep a dangling reference to us.
4513   if (IsInTextRunUserData() ||
4514       (mPrevContinuation && mPrevContinuation->Style() != Style())) {
4515     ClearTextRuns();
4516     // Clear the previous continuation's text run also, so that it can rebuild
4517     // the text run to include our text.
4518     if (mPrevContinuation) {
4519       mPrevContinuation->ClearTextRuns();
4520     }
4521   }
4522   nsSplittableFrame::RemoveFromFlow(this);
4523   // Let the base class destroy the frame
4524   nsIFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
4525 }
4526 
FirstInFlow() const4527 nsIFrame* nsContinuingTextFrame::FirstInFlow() const {
4528   // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4529   nsIFrame *firstInFlow,
4530       *previous = const_cast<nsIFrame*>(static_cast<const nsIFrame*>(this));
4531   do {
4532     firstInFlow = previous;
4533     previous = firstInFlow->GetPrevInFlow();
4534   } while (previous);
4535   MOZ_ASSERT(firstInFlow, "post-condition failed");
4536   return firstInFlow;
4537 }
4538 
FirstContinuation() const4539 nsIFrame* nsContinuingTextFrame::FirstContinuation() const {
4540   // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4541   nsIFrame *firstContinuation,
4542       *previous = const_cast<nsIFrame*>(
4543           static_cast<const nsIFrame*>(mPrevContinuation));
4544 
4545   NS_ASSERTION(previous,
4546                "How can an nsContinuingTextFrame be the first continuation?");
4547 
4548   do {
4549     firstContinuation = previous;
4550     previous = firstContinuation->GetPrevContinuation();
4551   } while (previous);
4552   MOZ_ASSERT(firstContinuation, "post-condition failed");
4553   return firstContinuation;
4554 }
4555 
4556 // XXX Do we want to do all the work for the first-in-flow or do the
4557 // work for each part?  (Be careful of first-letter / first-line, though,
4558 // especially first-line!)  Doing all the work on the first-in-flow has
4559 // the advantage of avoiding the potential for incremental reflow bugs,
4560 // but depends on our maintining the frame tree in reasonable ways even
4561 // for edge cases (block-within-inline splits, nextBidi, etc.)
4562 
4563 // XXX We really need to make :first-letter happen during frame
4564 // construction.
4565 
4566 // Needed for text frames in XUL.
4567 /* virtual */
GetMinISize(gfxContext * aRenderingContext)4568 nscoord nsTextFrame::GetMinISize(gfxContext* aRenderingContext) {
4569   return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
4570 }
4571 
4572 // Needed for text frames in XUL.
4573 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)4574 nscoord nsTextFrame::GetPrefISize(gfxContext* aRenderingContext) {
4575   return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
4576 }
4577 
4578 /* virtual */
AddInlineMinISize(gfxContext * aRenderingContext,InlineMinISizeData * aData)4579 void nsContinuingTextFrame::AddInlineMinISize(gfxContext* aRenderingContext,
4580                                               InlineMinISizeData* aData) {
4581   // Do nothing, since the first-in-flow accounts for everything.
4582 }
4583 
4584 /* virtual */
AddInlinePrefISize(gfxContext * aRenderingContext,InlinePrefISizeData * aData)4585 void nsContinuingTextFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
4586                                                InlinePrefISizeData* aData) {
4587   // Do nothing, since the first-in-flow accounts for everything.
4588 }
4589 
4590 //----------------------------------------------------------------------
4591 
4592 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
VerifyNotDirty(nsFrameState state)4593 static void VerifyNotDirty(nsFrameState state) {
4594   bool isZero = state & NS_FRAME_FIRST_REFLOW;
4595   bool isDirty = state & NS_FRAME_IS_DIRTY;
4596   if (!isZero && isDirty) NS_WARNING("internal offsets may be out-of-sync");
4597 }
4598 #  define DEBUG_VERIFY_NOT_DIRTY(state) VerifyNotDirty(state)
4599 #else
4600 #  define DEBUG_VERIFY_NOT_DIRTY(state)
4601 #endif
4602 
NS_NewTextFrame(PresShell * aPresShell,ComputedStyle * aStyle)4603 nsIFrame* NS_NewTextFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
4604   return new (aPresShell) nsTextFrame(aStyle, aPresShell->GetPresContext());
4605 }
4606 
NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)4607 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
4608 
4609 nsIFrame* NS_NewContinuingTextFrame(PresShell* aPresShell,
4610                                     ComputedStyle* aStyle) {
4611   return new (aPresShell)
4612       nsContinuingTextFrame(aStyle, aPresShell->GetPresContext());
4613 }
4614 
4615 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4616 
4617 nsTextFrame::~nsTextFrame() = default;
4618 
GetCursor(const nsPoint & aPoint)4619 Maybe<nsIFrame::Cursor> nsTextFrame::GetCursor(const nsPoint& aPoint) {
4620   StyleCursorKind kind = StyleUI()->mCursor.keyword;
4621   if (kind == StyleCursorKind::Auto) {
4622     if (!IsSelectable(nullptr)) {
4623       kind = StyleCursorKind::Default;
4624     } else {
4625       kind = GetWritingMode().IsVertical() ? StyleCursorKind::VerticalText
4626                                            : StyleCursorKind::Text;
4627     }
4628   }
4629   return Some(Cursor{kind, AllowCustomCursorImage::Yes});
4630 }
4631 
LastInFlow() const4632 nsTextFrame* nsTextFrame::LastInFlow() const {
4633   nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4634   while (lastInFlow->GetNextInFlow()) {
4635     lastInFlow = lastInFlow->GetNextInFlow();
4636   }
4637   MOZ_ASSERT(lastInFlow, "post-condition failed");
4638   return lastInFlow;
4639 }
4640 
LastContinuation() const4641 nsTextFrame* nsTextFrame::LastContinuation() const {
4642   nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
4643   while (lastContinuation->mNextContinuation) {
4644     lastContinuation = lastContinuation->mNextContinuation;
4645   }
4646   MOZ_ASSERT(lastContinuation, "post-condition failed");
4647   return lastContinuation;
4648 }
4649 
InvalidateFrame(uint32_t aDisplayItemKey,bool aRebuildDisplayItems)4650 void nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey,
4651                                   bool aRebuildDisplayItems) {
4652   InvalidateSelectionState();
4653 
4654   if (SVGUtils::IsInSVGTextSubtree(this)) {
4655     nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
4656         GetParent(), LayoutFrameType::SVGText);
4657     svgTextFrame->InvalidateFrame();
4658     return;
4659   }
4660   nsIFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
4661 }
4662 
InvalidateFrameWithRect(const nsRect & aRect,uint32_t aDisplayItemKey,bool aRebuildDisplayItems)4663 void nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect,
4664                                           uint32_t aDisplayItemKey,
4665                                           bool aRebuildDisplayItems) {
4666   InvalidateSelectionState();
4667 
4668   if (SVGUtils::IsInSVGTextSubtree(this)) {
4669     nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
4670         GetParent(), LayoutFrameType::SVGText);
4671     svgTextFrame->InvalidateFrame();
4672     return;
4673   }
4674   nsIFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey,
4675                                     aRebuildDisplayItems);
4676 }
4677 
GetUninflatedTextRun() const4678 gfxTextRun* nsTextFrame::GetUninflatedTextRun() const {
4679   return GetProperty(UninflatedTextRunProperty());
4680 }
4681 
SetTextRun(gfxTextRun * aTextRun,TextRunType aWhichTextRun,float aInflation)4682 void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
4683                              float aInflation) {
4684   NS_ASSERTION(aTextRun, "must have text run");
4685 
4686   // Our inflated text run is always stored in mTextRun.  In the cases
4687   // where our current inflation is not 1.0, however, we store two text
4688   // runs, and the uninflated one goes in a frame property.  We never
4689   // store a single text run in both.
4690   if (aWhichTextRun == eInflated) {
4691     if (HasFontSizeInflation() && aInflation == 1.0f) {
4692       // FIXME: Probably shouldn't do this within each SetTextRun
4693       // method, but it doesn't hurt.
4694       ClearTextRun(nullptr, nsTextFrame::eNotInflated);
4695     }
4696     SetFontSizeInflation(aInflation);
4697   } else {
4698     MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation");
4699     if (HasFontSizeInflation()) {
4700       // Setting the property will not automatically increment the textrun's
4701       // reference count, so we need to do it here.
4702       aTextRun->AddRef();
4703       SetProperty(UninflatedTextRunProperty(), aTextRun);
4704       return;
4705     }
4706     // fall through to setting mTextRun
4707   }
4708 
4709   mTextRun = aTextRun;
4710 
4711   // FIXME: Add assertions testing the relationship between
4712   // GetFontSizeInflation() and whether we have an uninflated text run
4713   // (but be aware that text runs can go away).
4714 }
4715 
RemoveTextRun(gfxTextRun * aTextRun)4716 bool nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) {
4717   if (aTextRun == mTextRun) {
4718     mTextRun = nullptr;
4719     mFontMetrics = nullptr;
4720     return true;
4721   }
4722   if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION) &&
4723       GetProperty(UninflatedTextRunProperty()) == aTextRun) {
4724     RemoveProperty(UninflatedTextRunProperty());
4725     return true;
4726   }
4727   return false;
4728 }
4729 
ClearTextRun(nsTextFrame * aStartContinuation,TextRunType aWhichTextRun)4730 void nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
4731                                TextRunType aWhichTextRun) {
4732   RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
4733   if (!textRun) {
4734     return;
4735   }
4736 
4737   if (aWhichTextRun == nsTextFrame::eInflated) {
4738     mFontMetrics = nullptr;
4739   }
4740 
4741   DebugOnly<bool> checkmTextrun = textRun == mTextRun;
4742   UnhookTextRunFromFrames(textRun, aStartContinuation);
4743   MOZ_ASSERT(checkmTextrun ? !mTextRun
4744                            : !GetProperty(UninflatedTextRunProperty()));
4745 }
4746 
DisconnectTextRuns()4747 void nsTextFrame::DisconnectTextRuns() {
4748   MOZ_ASSERT(!IsInTextRunUserData(),
4749              "Textrun mentions this frame in its user data so we can't just "
4750              "disconnect");
4751   mTextRun = nullptr;
4752   if (HasAnyStateBits(TEXT_HAS_FONT_INFLATION)) {
4753     RemoveProperty(UninflatedTextRunProperty());
4754   }
4755 }
4756 
NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength)4757 void nsTextFrame::NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength) {
4758   MOZ_ASSERT(mContent->IsInNativeAnonymousSubtree());
4759 
4760   MarkIntrinsicISizesDirty();
4761 
4762   // This is to avoid making a new Reflow request in CharacterDataChanged:
4763   for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
4764     f->MarkSubtreeDirty();
4765     f->mReflowRequestedForCharDataChange = true;
4766   }
4767 
4768   // Pretend that all the text changed.
4769   CharacterDataChangeInfo info;
4770   info.mAppend = false;
4771   info.mChangeStart = 0;
4772   info.mChangeEnd = aOldLength;
4773   info.mReplaceLength = GetContent()->TextLength();
4774   CharacterDataChanged(info);
4775 }
4776 
CharacterDataChanged(const CharacterDataChangeInfo & aInfo)4777 nsresult nsTextFrame::CharacterDataChanged(
4778     const CharacterDataChangeInfo& aInfo) {
4779   if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4780     mContent->RemoveProperty(nsGkAtoms::newline);
4781     mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4782   }
4783   if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4784     mContent->RemoveProperty(nsGkAtoms::flowlength);
4785     mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4786   }
4787 
4788   // Find the first frame whose text has changed. Frames that are entirely
4789   // before the text change are completely unaffected.
4790   nsTextFrame* next;
4791   nsTextFrame* textFrame = this;
4792   while (true) {
4793     next = textFrame->GetNextContinuation();
4794     if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart)) break;
4795     textFrame = next;
4796   }
4797 
4798   int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
4799 
4800   // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
4801   // had already received an earlier FrameNeedsReflow call).
4802   // (For subsequent frames with this same parent, we can just set their
4803   // dirty bit without bothering to call FrameNeedsReflow again.)
4804   nsIFrame* lastDirtiedFrameParent = nullptr;
4805 
4806   mozilla::PresShell* presShell = PresContext()->GetPresShell();
4807   do {
4808     // textFrame contained deleted text (or the insertion point,
4809     // if this was a pure insertion).
4810     textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
4811     textFrame->ClearTextRuns();
4812 
4813     nsIFrame* parentOfTextFrame = textFrame->GetParent();
4814     bool areAncestorsAwareOfReflowRequest = false;
4815     if (lastDirtiedFrameParent == parentOfTextFrame) {
4816       // An earlier iteration of this loop already called
4817       // FrameNeedsReflow for a sibling of |textFrame|.
4818       areAncestorsAwareOfReflowRequest = true;
4819     } else {
4820       lastDirtiedFrameParent = parentOfTextFrame;
4821     }
4822 
4823     if (textFrame->mReflowRequestedForCharDataChange) {
4824       // We already requested a reflow for this frame; nothing to do.
4825       MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),
4826                  "mReflowRequestedForCharDataChange should only be set "
4827                  "on dirty frames");
4828     } else {
4829       // Make sure textFrame is queued up for a reflow.  Also set a flag so we
4830       // don't waste time doing this again in repeated calls to this method.
4831       textFrame->mReflowRequestedForCharDataChange = true;
4832       if (!areAncestorsAwareOfReflowRequest) {
4833         // Ask the parent frame to reflow me.
4834         presShell->FrameNeedsReflow(textFrame, IntrinsicDirty::StyleChange,
4835                                     NS_FRAME_IS_DIRTY);
4836       } else {
4837         // We already called FrameNeedsReflow on behalf of an earlier sibling,
4838         // so we can just mark this frame as dirty and don't need to bother
4839         // telling its ancestors.
4840         // Note: if the parent is a block, we're cheating here because we should
4841         // be marking our line dirty, but we're not. nsTextFrame::SetLength will
4842         // do that when it gets called during reflow.
4843         textFrame->MarkSubtreeDirty();
4844       }
4845     }
4846     textFrame->InvalidateFrame();
4847 
4848     // Below, frames that start after the deleted text will be adjusted so that
4849     // their offsets move with the trailing unchanged text. If this change
4850     // deletes more text than it inserts, those frame offsets will decrease.
4851     // We need to maintain the invariant that mContentOffset is non-decreasing
4852     // along the continuation chain. So we need to ensure that frames that
4853     // started in the deleted text are all still starting before the
4854     // unchanged text.
4855     if (textFrame->mContentOffset > endOfChangedText) {
4856       textFrame->mContentOffset = endOfChangedText;
4857     }
4858 
4859     textFrame = textFrame->GetNextContinuation();
4860   } while (textFrame &&
4861            textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
4862 
4863   // This is how much the length of the string changed by --- i.e.,
4864   // how much the trailing unchanged text moved.
4865   int32_t sizeChange =
4866       aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
4867 
4868   if (sizeChange) {
4869     // Fix the offsets of the text frames that start in the trailing
4870     // unchanged text.
4871     while (textFrame) {
4872       textFrame->mContentOffset += sizeChange;
4873       // XXX we could rescue some text runs by adjusting their user data
4874       // to reflect the change in DOM offsets
4875       textFrame->ClearTextRuns();
4876       textFrame = textFrame->GetNextContinuation();
4877     }
4878   }
4879 
4880   return NS_OK;
4881 }
4882 
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty,float)4883 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty, float)
4884 
4885 float nsTextFrame::GetTextCombineScaleFactor(nsTextFrame* aFrame) {
4886   float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
4887   return factor ? factor : 1.0f;
4888 }
4889 
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)4890 void nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
4891                                    const nsDisplayListSet& aLists) {
4892   if (!IsVisibleForPainting()) return;
4893 
4894   DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
4895 
4896   const nsStyleText* st = StyleText();
4897   bool isTextTransparent =
4898       NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 &&
4899       NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0;
4900   if ((HasAnyStateBits(TEXT_NO_RENDERED_GLYPHS) ||
4901        (isTextTransparent && !StyleText()->HasTextShadow())) &&
4902       aBuilder->IsForPainting() && !SVGUtils::IsInSVGTextSubtree(this)) {
4903     if (!IsSelected()) {
4904       TextDecorations textDecs;
4905       GetTextDecorations(PresContext(), eResolvedColors, textDecs);
4906       if (!textDecs.HasDecorationLines()) {
4907         if (auto* currentPresContext = aBuilder->CurrentPresContext()) {
4908           currentPresContext->SetBuiltInvisibleText();
4909         }
4910         return;
4911       }
4912     }
4913   }
4914 
4915   aLists.Content()->AppendNewToTop<nsDisplayText>(aBuilder, this);
4916 }
4917 
GetSelectionDetails()4918 UniquePtr<SelectionDetails> nsTextFrame::GetSelectionDetails() {
4919   const nsFrameSelection* frameSelection = GetConstFrameSelection();
4920   if (frameSelection->IsInTableSelectionMode()) {
4921     return nullptr;
4922   }
4923   UniquePtr<SelectionDetails> details = frameSelection->LookUpSelection(
4924       mContent, GetContentOffset(), GetContentLength(), false);
4925   for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
4926     sd->mStart += mContentOffset;
4927     sd->mEnd += mContentOffset;
4928   }
4929   return details;
4930 }
4931 
PaintSelectionBackground(DrawTarget & aDrawTarget,nscolor aColor,const LayoutDeviceRect & aDirtyRect,const LayoutDeviceRect & aRect,nsTextFrame::DrawPathCallbacks * aCallbacks)4932 static void PaintSelectionBackground(
4933     DrawTarget& aDrawTarget, nscolor aColor, const LayoutDeviceRect& aDirtyRect,
4934     const LayoutDeviceRect& aRect, nsTextFrame::DrawPathCallbacks* aCallbacks) {
4935   Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
4936   MaybeSnapToDevicePixels(rect, aDrawTarget);
4937 
4938   if (aCallbacks) {
4939     aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
4940   } else {
4941     ColorPattern color(ToDeviceColor(aColor));
4942     aDrawTarget.FillRect(rect, color);
4943   }
4944 }
4945 
4946 // Attempt to get the LineBaselineOffset property of aChildFrame
4947 // If not set, calculate this value for all child frames of aBlockFrame
LazyGetLineBaselineOffset(nsIFrame * aChildFrame,nsBlockFrame * aBlockFrame)4948 static nscoord LazyGetLineBaselineOffset(nsIFrame* aChildFrame,
4949                                          nsBlockFrame* aBlockFrame) {
4950   bool offsetFound;
4951   nscoord offset =
4952       aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(), &offsetFound);
4953 
4954   if (!offsetFound) {
4955     for (const auto& line : aBlockFrame->Lines()) {
4956       if (line.IsInline()) {
4957         int32_t n = line.GetChildCount();
4958         nscoord lineBaseline = line.BStart() + line.GetLogicalAscent();
4959         for (auto* lineFrame = line.mFirstChild; n > 0;
4960              lineFrame = lineFrame->GetNextSibling(), --n) {
4961           offset = lineBaseline - lineFrame->GetNormalPosition().y;
4962           lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
4963         }
4964       }
4965     }
4966     return aChildFrame->GetProperty(nsIFrame::LineBaselineOffset(),
4967                                     &offsetFound);
4968   } else {
4969     return offset;
4970   }
4971 }
4972 
IsUnderlineRight(const ComputedStyle & aStyle)4973 static bool IsUnderlineRight(const ComputedStyle& aStyle) {
4974   // Check for 'left' or 'right' explicitly specified in the property;
4975   // if neither is there, we use auto positioning based on lang.
4976   const auto position = aStyle.StyleText()->mTextUnderlinePosition;
4977   if (position.IsLeft()) {
4978     return false;
4979   }
4980   if (position.IsRight()) {
4981     return true;
4982   }
4983   // If neither 'left' nor 'right' was specified, check the language.
4984   nsAtom* langAtom = aStyle.StyleFont()->mLanguage;
4985   if (!langAtom) {
4986     return false;
4987   }
4988   nsDependentAtomString langStr(langAtom);
4989   return (StringBeginsWith(langStr, u"ja"_ns) ||
4990           StringBeginsWith(langStr, u"ko"_ns)) &&
4991          (langStr.Length() == 2 || langStr[2] == '-');
4992 }
4993 
GetTextDecorations(nsPresContext * aPresContext,nsTextFrame::TextDecorationColorResolution aColorResolution,nsTextFrame::TextDecorations & aDecorations)4994 void nsTextFrame::GetTextDecorations(
4995     nsPresContext* aPresContext,
4996     nsTextFrame::TextDecorationColorResolution aColorResolution,
4997     nsTextFrame::TextDecorations& aDecorations) {
4998   const nsCompatibility compatMode = aPresContext->CompatibilityMode();
4999 
5000   bool useOverride = false;
5001   nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
5002 
5003   bool nearestBlockFound = false;
5004   // Use writing mode of parent frame for orthogonal text frame to work.
5005   // See comment in nsTextFrame::DrawTextRunAndDecorations.
5006   WritingMode wm = GetParent()->GetWritingMode();
5007   bool vertical = wm.IsVertical();
5008 
5009   nscoord ascent = GetLogicalBaseline(wm);
5010   // physicalBlockStartOffset represents the offset from our baseline
5011   // to f's physical block start, which is top in horizontal writing
5012   // mode, and left in vertical writing modes, in our coordinate space.
5013   // This physical block start is logical block start in most cases,
5014   // but for vertical-rl, it is logical block end, and consequently in
5015   // that case, it starts from the descent instead of ascent.
5016   nscoord physicalBlockStartOffset =
5017       wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
5018   // baselineOffset represents the offset from our baseline to f's baseline or
5019   // the nearest block's baseline, in our coordinate space, whichever is closest
5020   // during the particular iteration
5021   nscoord baselineOffset = 0;
5022 
5023   for (nsIFrame *f = this, *fChild = nullptr; f;
5024        fChild = f, f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) {
5025     ComputedStyle* const context = f->Style();
5026     if (!context->HasTextDecorationLines()) {
5027       break;
5028     }
5029 
5030     if (context->GetPseudoType() == PseudoStyleType::marker &&
5031         (context->StyleList()->mListStylePosition ==
5032              NS_STYLE_LIST_STYLE_POSITION_OUTSIDE ||
5033          !context->StyleDisplay()->IsInlineOutsideStyle())) {
5034       // Outside ::marker pseudos, and inside markers that aren't inlines, don't
5035       // have text decorations.
5036       break;
5037     }
5038 
5039     const nsStyleTextReset* const styleTextReset = context->StyleTextReset();
5040     const StyleTextDecorationLine textDecorations =
5041         styleTextReset->mTextDecorationLine;
5042 
5043     if (!useOverride &&
5044         (StyleTextDecorationLine::COLOR_OVERRIDE & textDecorations)) {
5045       // This handles the <a href="blah.html"><font color="green">La
5046       // la la</font></a> case. The link underline should be green.
5047       useOverride = true;
5048       overrideColor =
5049           nsLayoutUtils::GetColor(f, &nsStyleTextReset::mTextDecorationColor);
5050     }
5051 
5052     nsBlockFrame* fBlock = do_QueryFrame(f);
5053     const bool firstBlock = !nearestBlockFound && fBlock;
5054 
5055     // Not updating positions once we hit a parent block is equivalent to
5056     // the CSS 2.1 spec that blocks should propagate decorations down to their
5057     // children (albeit the style should be preserved)
5058     // However, if we're vertically aligned within a block, then we need to
5059     // recover the correct baseline from the line by querying the FrameProperty
5060     // that should be set (see nsLineLayout::VerticalAlignLine).
5061     if (firstBlock) {
5062       // At this point, fChild can't be null since TextFrames can't be blocks
5063       Maybe<StyleVerticalAlignKeyword> verticalAlign =
5064           fChild->VerticalAlignEnum();
5065       if (verticalAlign != Some(StyleVerticalAlignKeyword::Baseline)) {
5066         // Since offset is the offset in the child's coordinate space, we have
5067         // to undo the accumulation to bring the transform out of the block's
5068         // coordinate space
5069         const nscoord lineBaselineOffset =
5070             LazyGetLineBaselineOffset(fChild, fBlock);
5071 
5072         baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
5073                          (vertical ? fChild->GetNormalPosition().x
5074                                    : fChild->GetNormalPosition().y);
5075       }
5076     } else if (!nearestBlockFound) {
5077       // offset here is the offset from f's baseline to f's top/left
5078       // boundary. It's descent for vertical-rl, and ascent otherwise.
5079       nscoord offset = wm.IsVerticalRL()
5080                            ? f->GetSize().width - f->GetLogicalBaseline(wm)
5081                            : f->GetLogicalBaseline(wm);
5082       baselineOffset = physicalBlockStartOffset - offset;
5083     }
5084 
5085     nearestBlockFound = nearestBlockFound || firstBlock;
5086     physicalBlockStartOffset +=
5087         vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
5088 
5089     const uint8_t style = styleTextReset->mTextDecorationStyle;
5090     if (textDecorations) {
5091       nscolor color;
5092       if (useOverride) {
5093         color = overrideColor;
5094       } else if (SVGUtils::IsInSVGTextSubtree(this)) {
5095         // XXX We might want to do something with text-decoration-color when
5096         //     painting SVG text, but it's not clear what we should do.  We
5097         //     at least need SVG text decorations to paint with 'fill' if
5098         //     text-decoration-color has its initial value currentColor.
5099         //     We could choose to interpret currentColor as "currentFill"
5100         //     for SVG text, and have e.g. text-decoration-color:red to
5101         //     override the fill paint of the decoration.
5102         color = aColorResolution == eResolvedColors
5103                     ? nsLayoutUtils::GetColor(f, &nsStyleSVG::mFill)
5104                     : NS_SAME_AS_FOREGROUND_COLOR;
5105       } else {
5106         color =
5107             nsLayoutUtils::GetColor(f, &nsStyleTextReset::mTextDecorationColor);
5108       }
5109 
5110       bool swapUnderlineAndOverline =
5111           wm.IsCentralBaseline() && IsUnderlineRight(*context);
5112       const auto kUnderline = swapUnderlineAndOverline
5113                                   ? StyleTextDecorationLine::OVERLINE
5114                                   : StyleTextDecorationLine::UNDERLINE;
5115       const auto kOverline = swapUnderlineAndOverline
5116                                  ? StyleTextDecorationLine::UNDERLINE
5117                                  : StyleTextDecorationLine::OVERLINE;
5118 
5119       const nsStyleText* const styleText = context->StyleText();
5120       if (textDecorations & kUnderline) {
5121         aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
5122             f, baselineOffset, styleText->mTextUnderlinePosition,
5123             styleText->mTextUnderlineOffset,
5124             styleTextReset->mTextDecorationThickness, color, style));
5125       }
5126       if (textDecorations & kOverline) {
5127         aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
5128             f, baselineOffset, styleText->mTextUnderlinePosition,
5129             styleText->mTextUnderlineOffset,
5130             styleTextReset->mTextDecorationThickness, color, style));
5131       }
5132       if (textDecorations & StyleTextDecorationLine::LINE_THROUGH) {
5133         aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
5134             f, baselineOffset, styleText->mTextUnderlinePosition,
5135             styleText->mTextUnderlineOffset,
5136             styleTextReset->mTextDecorationThickness, color, style));
5137       }
5138     }
5139 
5140     // In all modes, if we're on an inline-block/table/grid/flex (or
5141     // -moz-inline-box), we're done.
5142     // If we're on a ruby frame other than ruby text container, we
5143     // should continue.
5144     mozilla::StyleDisplay display = f->GetDisplay();
5145     if (!nsStyleDisplay::IsInlineFlow(display) &&
5146         (!nsStyleDisplay::IsRubyDisplayType(display) ||
5147          display == mozilla::StyleDisplay::RubyTextContainer) &&
5148         nsStyleDisplay::IsDisplayTypeInlineOutside(display)) {
5149       break;
5150     }
5151 
5152     // In quirks mode, if we're on an HTML table element, we're done.
5153     if (compatMode == eCompatibility_NavQuirks &&
5154         f->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
5155       break;
5156     }
5157 
5158     // If we're on an absolutely-positioned element or a floating
5159     // element, we're done.
5160     if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
5161       break;
5162     }
5163 
5164     // If we're an outer <svg> element, which is classified as an atomic
5165     // inline-level element, we're done.
5166     if (f->IsSVGOuterSVGFrame()) {
5167       break;
5168     }
5169   }
5170 }
5171 
GetInflationForTextDecorations(nsIFrame * aFrame,nscoord aInflationMinFontSize)5172 static float GetInflationForTextDecorations(nsIFrame* aFrame,
5173                                             nscoord aInflationMinFontSize) {
5174   if (SVGUtils::IsInSVGTextSubtree(aFrame)) {
5175     auto container =
5176         nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
5177     MOZ_ASSERT(container);
5178     return static_cast<SVGTextFrame*>(container)->GetFontSizeScaleFactor();
5179   }
5180   return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
5181 }
5182 
5183 struct EmphasisMarkInfo {
5184   RefPtr<gfxTextRun> textRun;
5185   gfxFloat advance;
5186   gfxFloat baselineOffset;
5187 };
5188 
NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty,EmphasisMarkInfo)5189 NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
5190 
5191 static void ComputeTextEmphasisStyleString(const StyleTextEmphasisStyle& aStyle,
5192                                            nsAString& aOut) {
5193   MOZ_ASSERT(!aStyle.IsNone());
5194   if (aStyle.IsString()) {
5195     nsDependentCSubstring string = aStyle.AsString().AsString();
5196     AppendUTF8toUTF16(string, aOut);
5197     return;
5198   }
5199   const auto& keyword = aStyle.AsKeyword();
5200   const bool fill = keyword.fill == StyleTextEmphasisFillMode::Filled;
5201   switch (keyword.shape) {
5202     case StyleTextEmphasisShapeKeyword::Dot:
5203       return aOut.AppendLiteral(fill ? u"\u2022" : u"\u25e6");
5204     case StyleTextEmphasisShapeKeyword::Circle:
5205       return aOut.AppendLiteral(fill ? u"\u25cf" : u"\u25cb");
5206     case StyleTextEmphasisShapeKeyword::DoubleCircle:
5207       return aOut.AppendLiteral(fill ? u"\u25c9" : u"\u25ce");
5208     case StyleTextEmphasisShapeKeyword::Triangle:
5209       return aOut.AppendLiteral(fill ? u"\u25b2" : u"\u25b3");
5210     case StyleTextEmphasisShapeKeyword::Sesame:
5211       return aOut.AppendLiteral(fill ? u"\ufe45" : u"\ufe46");
5212     default:
5213       MOZ_ASSERT_UNREACHABLE("Unknown emphasis style shape");
5214   }
5215 }
5216 
GenerateTextRunForEmphasisMarks(nsTextFrame * aFrame,gfxFontGroup * aFontGroup,ComputedStyle * aComputedStyle,const nsStyleText * aStyleText)5217 static already_AddRefed<gfxTextRun> GenerateTextRunForEmphasisMarks(
5218     nsTextFrame* aFrame, gfxFontGroup* aFontGroup,
5219     ComputedStyle* aComputedStyle, const nsStyleText* aStyleText) {
5220   nsAutoString string;
5221   ComputeTextEmphasisStyleString(aStyleText->mTextEmphasisStyle, string);
5222 
5223   RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
5224   auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5225   gfx::ShapedTextFlags flags =
5226       nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
5227   if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
5228     // The emphasis marks should always be rendered upright per spec.
5229     flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
5230   }
5231   return aFontGroup->MakeTextRun<char16_t>(string.get(), string.Length(), dt,
5232                                            appUnitsPerDevUnit, flags,
5233                                            nsTextFrameUtils::Flags(), nullptr);
5234 }
5235 
FindFurthestInlineRubyAncestor(nsTextFrame * aFrame)5236 static nsRubyFrame* FindFurthestInlineRubyAncestor(nsTextFrame* aFrame) {
5237   nsRubyFrame* rubyFrame = nullptr;
5238   for (nsIFrame* frame = aFrame->GetParent();
5239        frame && frame->IsFrameOfType(nsIFrame::eLineParticipant);
5240        frame = frame->GetParent()) {
5241     if (frame->IsRubyFrame()) {
5242       rubyFrame = static_cast<nsRubyFrame*>(frame);
5243     }
5244   }
5245   return rubyFrame;
5246 }
5247 
UpdateTextEmphasis(WritingMode aWM,PropertyProvider & aProvider)5248 nsRect nsTextFrame::UpdateTextEmphasis(WritingMode aWM,
5249                                        PropertyProvider& aProvider) {
5250   const nsStyleText* styleText = StyleText();
5251   if (!styleText->HasEffectiveTextEmphasis()) {
5252     RemoveProperty(EmphasisMarkProperty());
5253     return nsRect();
5254   }
5255 
5256   ComputedStyle* computedStyle = Style();
5257   bool isTextCombined = computedStyle->IsTextCombined();
5258   if (isTextCombined) {
5259     computedStyle = GetParent()->Style();
5260   }
5261   RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
5262       computedStyle, PresContext(), GetFontSizeInflation());
5263   EmphasisMarkInfo* info = new EmphasisMarkInfo;
5264   info->textRun = GenerateTextRunForEmphasisMarks(
5265       this, fm->GetThebesFontGroup(), computedStyle, styleText);
5266   info->advance = info->textRun->GetAdvanceWidth();
5267 
5268   // Calculate the baseline offset
5269   LogicalSide side = styleText->TextEmphasisSide(aWM);
5270   LogicalSize frameSize = GetLogicalSize(aWM);
5271   // The overflow rect is inflated in the inline direction by half
5272   // advance of the emphasis mark on each side, so that even if a mark
5273   // is drawn for a zero-width character, it won't be clipped.
5274   LogicalRect overflowRect(aWM, -info->advance / 2,
5275                            /* BStart to be computed below */ 0,
5276                            frameSize.ISize(aWM) + info->advance,
5277                            fm->MaxAscent() + fm->MaxDescent());
5278   RefPtr<nsFontMetrics> baseFontMetrics =
5279       isTextCombined
5280           ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
5281           : do_AddRef(aProvider.GetFontMetrics());
5282   // When the writing mode is vertical-lr the line is inverted, and thus
5283   // the ascent and descent are swapped.
5284   nscoord absOffset = (side == eLogicalSideBStart) != aWM.IsLineInverted()
5285                           ? baseFontMetrics->MaxAscent() + fm->MaxDescent()
5286                           : baseFontMetrics->MaxDescent() + fm->MaxAscent();
5287   RubyBlockLeadings leadings;
5288   if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
5289     leadings = ruby->GetBlockLeadings();
5290   }
5291   if (side == eLogicalSideBStart) {
5292     info->baselineOffset = -absOffset - leadings.mStart;
5293     overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
5294   } else {
5295     MOZ_ASSERT(side == eLogicalSideBEnd);
5296     info->baselineOffset = absOffset + leadings.mEnd;
5297     overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
5298   }
5299   // If text combined, fix the gap between the text frame and its parent.
5300   if (isTextCombined) {
5301     nscoord gap = (baseFontMetrics->MaxHeight() - frameSize.BSize(aWM)) / 2;
5302     overflowRect.BStart(aWM) += gap * (side == eLogicalSideBStart ? -1 : 1);
5303   }
5304 
5305   SetProperty(EmphasisMarkProperty(), info);
5306   return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
5307 }
5308 
5309 // helper function for implementing text-decoration-thickness
5310 // https://drafts.csswg.org/css-text-decor-4/#text-decoration-width-property
5311 // Returns the thickness in device pixels.
ComputeDecorationLineThickness(const StyleTextDecorationLength & aThickness,const gfxFloat aAutoValue,const gfxFont::Metrics & aFontMetrics,const gfxFloat aAppUnitsPerDevPixel,const nsIFrame * aFrame)5312 static gfxFloat ComputeDecorationLineThickness(
5313     const StyleTextDecorationLength& aThickness, const gfxFloat aAutoValue,
5314     const gfxFont::Metrics& aFontMetrics, const gfxFloat aAppUnitsPerDevPixel,
5315     const nsIFrame* aFrame) {
5316   if (aThickness.IsAuto()) {
5317     return aAutoValue;
5318   }
5319 
5320   if (aThickness.IsFromFont()) {
5321     return aFontMetrics.underlineSize;
5322   }
5323   auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
5324   return aThickness.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
5325 }
5326 
5327 // Helper function for implementing text-underline-offset and -position
5328 // https://drafts.csswg.org/css-text-decor-4/#underline-offset
5329 // Returns the offset in device pixels.
ComputeDecorationLineOffset(StyleTextDecorationLine aLineType,const StyleTextUnderlinePosition & aPosition,const LengthPercentageOrAuto & aOffset,const gfxFont::Metrics & aFontMetrics,const gfxFloat aAppUnitsPerDevPixel,const nsIFrame * aFrame,bool aIsCentralBaseline,bool aSwappedUnderline)5330 static gfxFloat ComputeDecorationLineOffset(
5331     StyleTextDecorationLine aLineType,
5332     const StyleTextUnderlinePosition& aPosition,
5333     const LengthPercentageOrAuto& aOffset, const gfxFont::Metrics& aFontMetrics,
5334     const gfxFloat aAppUnitsPerDevPixel, const nsIFrame* aFrame,
5335     bool aIsCentralBaseline, bool aSwappedUnderline) {
5336   // Em value to use if we need to resolve a percentage length.
5337   auto em = [&] { return aFrame->StyleFont()->mSize.ToAppUnits(); };
5338   // If we're in vertical-upright typographic mode, we need to compute the
5339   // offset of the decoration line from the default central baseline.
5340   if (aIsCentralBaseline) {
5341     // Line-through simply goes at the (central) baseline.
5342     if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
5343       return 0;
5344     }
5345 
5346     // Compute "zero position" for the under- or overline.
5347     gfxFloat zeroPos = 0.5 * aFontMetrics.emHeight;
5348 
5349     // aOffset applies to underline only; for overline (or offset:auto) we use
5350     // a somewhat arbitrary offset of half the font's (horziontal-mode) value
5351     // for underline-offset, to get a little bit of separation between glyph
5352     // edges and the line in typical cases.
5353     // If we have swapped under-/overlines for text-underline-position:right,
5354     // we need to take account of this to determine which decoration lines are
5355     // "real" underlines which should respect the text-underline-* values.
5356     bool isUnderline =
5357         (aLineType == StyleTextDecorationLine::UNDERLINE) != aSwappedUnderline;
5358     gfxFloat offset =
5359         isUnderline && !aOffset.IsAuto()
5360             ? aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel
5361             : aFontMetrics.underlineOffset * -0.5;
5362 
5363     // Direction of the decoration line's offset from the central baseline.
5364     gfxFloat dir = aLineType == StyleTextDecorationLine::OVERLINE ? 1.0 : -1.0;
5365     return dir * (zeroPos + offset);
5366   }
5367 
5368   // Compute line offset for horizontal typographic mode.
5369   if (aLineType == StyleTextDecorationLine::UNDERLINE) {
5370     if (aPosition.IsFromFont()) {
5371       gfxFloat zeroPos = aFontMetrics.underlineOffset;
5372       gfxFloat offset =
5373           aOffset.IsAuto()
5374               ? 0
5375               : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
5376       return zeroPos - offset;
5377     }
5378 
5379     if (aPosition.IsUnder()) {
5380       gfxFloat zeroPos = -aFontMetrics.maxDescent;
5381       gfxFloat offset =
5382           aOffset.IsAuto()
5383               ? -0.5 * aFontMetrics.underlineOffset
5384               : aOffset.AsLengthPercentage().Resolve(em) / aAppUnitsPerDevPixel;
5385       return zeroPos - offset;
5386     }
5387 
5388     // text-underline-position must be 'auto', so zero position is the
5389     // baseline and 'auto' offset will apply the font's underline-offset.
5390     //
5391     // If offset is `auto`, we clamp the offset (in horizontal typographic mode)
5392     // to a minimum of 1/16 em (equivalent to 1px at font-size 16px) to mitigate
5393     // skip-ink issues with fonts that leave the underlineOffset field as zero.
5394     MOZ_ASSERT(aPosition.IsAuto());
5395     return aOffset.IsAuto() ? std::min(aFontMetrics.underlineOffset,
5396                                        -aFontMetrics.emHeight / 16.0)
5397                             : -aOffset.AsLengthPercentage().Resolve(em) /
5398                                   aAppUnitsPerDevPixel;
5399   }
5400 
5401   if (aLineType == StyleTextDecorationLine::OVERLINE) {
5402     return aFontMetrics.maxAscent;
5403   }
5404 
5405   if (aLineType == StyleTextDecorationLine::LINE_THROUGH) {
5406     return aFontMetrics.strikeoutOffset;
5407   }
5408 
5409   MOZ_ASSERT_UNREACHABLE("unknown decoration line type");
5410   return 0;
5411 }
5412 
UnionAdditionalOverflow(nsPresContext * aPresContext,nsIFrame * aBlock,PropertyProvider & aProvider,nsRect * aInkOverflowRect,bool aIncludeTextDecorations,bool aIncludeShadows)5413 void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
5414                                           nsIFrame* aBlock,
5415                                           PropertyProvider& aProvider,
5416                                           nsRect* aInkOverflowRect,
5417                                           bool aIncludeTextDecorations,
5418                                           bool aIncludeShadows) {
5419   const WritingMode wm = GetWritingMode();
5420   bool verticalRun = mTextRun->IsVertical();
5421   const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
5422 
5423   if (IsFloatingFirstLetterChild()) {
5424     bool inverted = wm.IsLineInverted();
5425     // The underline/overline drawable area must be contained in the overflow
5426     // rect when this is in floating first letter frame at *both* modes.
5427     // In this case, aBlock is the ::first-letter frame.
5428     uint8_t decorationStyle =
5429         aBlock->Style()->StyleTextReset()->mTextDecorationStyle;
5430     // If the style is none, let's include decoration line rect as solid style
5431     // since changing the style from none to solid/dotted/dashed doesn't cause
5432     // reflow.
5433     if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5434       decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5435     }
5436     nsCSSRendering::DecorationRectParams params;
5437 
5438     bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
5439     nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
5440     const gfxFont::Metrics& metrics =
5441         fontMetrics->GetThebesFontGroup()->GetFirstValidFont()->GetMetrics(
5442             useVerticalMetrics ? nsFontMetrics::eVertical
5443                                : nsFontMetrics::eHorizontal);
5444 
5445     params.defaultLineThickness = metrics.underlineSize;
5446     params.lineSize.height = ComputeDecorationLineThickness(
5447         aBlock->Style()->StyleTextReset()->mTextDecorationThickness,
5448         params.defaultLineThickness, metrics, appUnitsPerDevUnit, this);
5449 
5450     const auto* styleText = aBlock->StyleText();
5451     bool swapUnderline =
5452         wm.IsCentralBaseline() && IsUnderlineRight(*aBlock->Style());
5453     params.offset = ComputeDecorationLineOffset(
5454         StyleTextDecorationLine::UNDERLINE, styleText->mTextUnderlinePosition,
5455         styleText->mTextUnderlineOffset, metrics, appUnitsPerDevUnit, this,
5456         wm.IsCentralBaseline(), swapUnderline);
5457 
5458     nscoord maxAscent =
5459         inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent();
5460 
5461     Float gfxWidth =
5462         (verticalRun ? aInkOverflowRect->height : aInkOverflowRect->width) /
5463         appUnitsPerDevUnit;
5464     params.lineSize.width = gfxWidth;
5465     params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
5466     params.style = decorationStyle;
5467     params.vertical = verticalRun;
5468     params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5469     params.decoration = StyleTextDecorationLine::UNDERLINE;
5470     nsRect underlineRect =
5471         nsCSSRendering::GetTextDecorationRect(aPresContext, params);
5472 
5473     // TODO(jfkthame):
5474     // Should we actually be calling ComputeDecorationLineOffset again here?
5475     params.offset = maxAscent / appUnitsPerDevUnit;
5476     params.decoration = StyleTextDecorationLine::OVERLINE;
5477     nsRect overlineRect =
5478         nsCSSRendering::GetTextDecorationRect(aPresContext, params);
5479 
5480     aInkOverflowRect->UnionRect(*aInkOverflowRect, underlineRect);
5481     aInkOverflowRect->UnionRect(*aInkOverflowRect, overlineRect);
5482 
5483     // XXX If strikeoutSize is much thicker than the underlineSize, it may
5484     //     cause overflowing from the overflow rect.  However, such case
5485     //     isn't realistic, we don't need to compute it now.
5486   }
5487   if (aIncludeTextDecorations) {
5488     // Use writing mode of parent frame for orthogonal text frame to
5489     // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
5490     WritingMode parentWM = GetParent()->GetWritingMode();
5491     bool verticalDec = parentWM.IsVertical();
5492     bool useVerticalMetrics =
5493         verticalDec != verticalRun
5494             ? verticalDec
5495             : verticalRun && mTextRun->UseCenterBaseline();
5496 
5497     // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
5498     // style and position, they can be drawn at virtually any y-offset, so
5499     // maxima and minima are required to reliably generate the rectangle for
5500     // them
5501     TextDecorations textDecs;
5502     GetTextDecorations(aPresContext, eResolvedColors, textDecs);
5503     if (textDecs.HasDecorationLines()) {
5504       nscoord inflationMinFontSize =
5505           nsLayoutUtils::InflationMinFontSizeFor(aBlock);
5506 
5507       const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
5508       gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
5509       gfxFloat ascent =
5510           gfxFloat(GetLogicalBaseline(parentWM)) / appUnitsPerDevUnit;
5511       nscoord frameBStart = 0;
5512       if (parentWM.IsVerticalRL()) {
5513         frameBStart = GetSize().width;
5514         ascent = -ascent;
5515       }
5516 
5517       nsCSSRendering::DecorationRectParams params;
5518       params.lineSize = Size(gfxWidth, 0);
5519       params.ascent = ascent;
5520       params.vertical = verticalDec;
5521       params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5522 
5523       nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
5524       typedef gfxFont::Metrics Metrics;
5525       auto accumulateDecorationRect =
5526           [&](const LineDecoration& dec, gfxFloat Metrics::*lineSize,
5527               mozilla::StyleTextDecorationLine lineType) {
5528             params.style = dec.mStyle;
5529             // If the style is solid, let's include decoration line rect of
5530             // solid style since changing the style from none to
5531             // solid/dotted/dashed doesn't cause reflow.
5532             if (params.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5533               params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5534             }
5535 
5536             float inflation = GetInflationForTextDecorations(
5537                 dec.mFrame, inflationMinFontSize);
5538             const Metrics metrics =
5539                 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
5540                                     useVerticalMetrics);
5541 
5542             params.defaultLineThickness = metrics.*lineSize;
5543             params.lineSize.height = ComputeDecorationLineThickness(
5544                 dec.mTextDecorationThickness, params.defaultLineThickness,
5545                 metrics, appUnitsPerDevUnit, this);
5546 
5547             bool swapUnderline =
5548                 parentWM.IsCentralBaseline() && IsUnderlineRight(*Style());
5549             params.offset = ComputeDecorationLineOffset(
5550                 lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset,
5551                 metrics, appUnitsPerDevUnit, this, parentWM.IsCentralBaseline(),
5552                 swapUnderline);
5553 
5554             const nsRect decorationRect =
5555                 nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
5556                 (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
5557                              : nsPoint(0, -dec.mBaselineOffset));
5558 
5559             if (verticalDec) {
5560               topOrLeft = std::min(decorationRect.x, topOrLeft);
5561               bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
5562             } else {
5563               topOrLeft = std::min(decorationRect.y, topOrLeft);
5564               bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
5565             }
5566           };
5567 
5568       // Below we loop through all text decorations and compute the rectangle
5569       // containing all of them, in this frame's coordinate space
5570       params.decoration = StyleTextDecorationLine::UNDERLINE;
5571       for (const LineDecoration& dec : textDecs.mUnderlines) {
5572         accumulateDecorationRect(dec, &Metrics::underlineSize,
5573                                  params.decoration);
5574       }
5575       params.decoration = StyleTextDecorationLine::OVERLINE;
5576       for (const LineDecoration& dec : textDecs.mOverlines) {
5577         accumulateDecorationRect(dec, &Metrics::underlineSize,
5578                                  params.decoration);
5579       }
5580       params.decoration = StyleTextDecorationLine::LINE_THROUGH;
5581       for (const LineDecoration& dec : textDecs.mStrikes) {
5582         accumulateDecorationRect(dec, &Metrics::strikeoutSize,
5583                                  params.decoration);
5584       }
5585 
5586       aInkOverflowRect->UnionRect(
5587           *aInkOverflowRect,
5588           verticalDec
5589               ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
5590               : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
5591     }
5592 
5593     aInkOverflowRect->UnionRect(*aInkOverflowRect,
5594                                 UpdateTextEmphasis(parentWM, aProvider));
5595   }
5596 
5597   // text-stroke overflows: add half of text-stroke-width on all sides
5598   nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
5599   if (textStrokeWidth > 0) {
5600     // Inflate rect by stroke-width/2; we add an extra pixel to allow for
5601     // antialiasing, rounding errors, etc.
5602     nsRect strokeRect = *aInkOverflowRect;
5603     strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
5604     aInkOverflowRect->UnionRect(*aInkOverflowRect, strokeRect);
5605   }
5606 
5607   // Text-shadow overflows
5608   if (aIncludeShadows) {
5609     nsRect shadowRect =
5610         nsLayoutUtils::GetTextShadowRectsUnion(*aInkOverflowRect, this);
5611     aInkOverflowRect->UnionRect(*aInkOverflowRect, shadowRect);
5612   }
5613 
5614   // When this frame is not selected, the text-decoration area must be in
5615   // frame bounds.
5616   if (!IsSelected() ||
5617       !CombineSelectionUnderlineRect(aPresContext, *aInkOverflowRect))
5618     return;
5619   AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
5620 }
5621 
ComputeDescentLimitForSelectionUnderline(nsPresContext * aPresContext,const gfxFont::Metrics & aFontMetrics)5622 gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline(
5623     nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) {
5624   gfxFloat app = aPresContext->AppUnitsPerDevPixel();
5625   nscoord lineHeightApp =
5626       ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
5627                                   NS_UNCONSTRAINEDSIZE, GetFontSizeInflation());
5628   gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
5629   if (lineHeight <= aFontMetrics.maxHeight) {
5630     return aFontMetrics.maxDescent;
5631   }
5632   return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
5633 }
5634 
5635 // Make sure this stays in sync with DrawSelectionDecorations below
5636 static const SelectionTypeMask kSelectionTypesWithDecorations =
5637     ToSelectionTypeMask(SelectionType::eSpellCheck) |
5638     ToSelectionTypeMask(SelectionType::eURLStrikeout) |
5639     ToSelectionTypeMask(SelectionType::eIMERawClause) |
5640     ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
5641     ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
5642     ToSelectionTypeMask(SelectionType::eIMESelectedClause);
5643 
5644 /* static */
ComputeSelectionUnderlineHeight(nsPresContext * aPresContext,const gfxFont::Metrics & aFontMetrics,SelectionType aSelectionType)5645 gfxFloat nsTextFrame::ComputeSelectionUnderlineHeight(
5646     nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics,
5647     SelectionType aSelectionType) {
5648   switch (aSelectionType) {
5649     case SelectionType::eIMERawClause:
5650     case SelectionType::eIMESelectedRawClause:
5651     case SelectionType::eIMEConvertedClause:
5652     case SelectionType::eIMESelectedClause:
5653       return aFontMetrics.underlineSize;
5654     case SelectionType::eSpellCheck: {
5655       // The thickness of the spellchecker underline shouldn't honor the font
5656       // metrics.  It should be constant pixels value which is decided from the
5657       // default font size.  Note that if the actual font size is smaller than
5658       // the default font size, we should use the actual font size because the
5659       // computed value from the default font size can be too thick for the
5660       // current font size.
5661       Length defaultFontSize =
5662           aPresContext->Document()
5663               ->GetFontPrefsForLang(nullptr)
5664               ->GetDefaultFont(StyleGenericFontFamily::None)
5665               ->size;
5666       int32_t zoomedFontSize = aPresContext->CSSPixelsToDevPixels(
5667           nsStyleFont::ZoomText(*aPresContext->Document(), defaultFontSize)
5668               .ToCSSPixels());
5669       gfxFloat fontSize =
5670           std::min(gfxFloat(zoomedFontSize), aFontMetrics.emHeight);
5671       fontSize = std::max(fontSize, 1.0);
5672       return ceil(fontSize / 20);
5673     }
5674     default:
5675       NS_WARNING("Requested underline style is not valid");
5676       return aFontMetrics.underlineSize;
5677   }
5678 }
5679 
5680 enum class DecorationType { Normal, Selection };
5681 struct nsTextFrame::PaintDecorationLineParams
5682     : nsCSSRendering::DecorationRectParams {
5683   gfxContext* context = nullptr;
5684   LayoutDeviceRect dirtyRect;
5685   Point pt;
5686   const nscolor* overrideColor = nullptr;
5687   nscolor color = NS_RGBA(0, 0, 0, 0);
5688   gfxFloat icoordInFrame = 0.0f;
5689   gfxFloat baselineOffset = 0.0f;
5690   DecorationType decorationType = DecorationType::Normal;
5691   DrawPathCallbacks* callbacks = nullptr;
5692 };
5693 
PaintDecorationLine(const PaintDecorationLineParams & aParams)5694 void nsTextFrame::PaintDecorationLine(
5695     const PaintDecorationLineParams& aParams) {
5696   nsCSSRendering::PaintDecorationLineParams params;
5697   static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
5698   params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
5699   params.pt = aParams.pt;
5700   params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
5701   params.icoordInFrame = Float(aParams.icoordInFrame);
5702   params.baselineOffset = Float(aParams.baselineOffset);
5703   if (aParams.callbacks) {
5704     Rect path = nsCSSRendering::DecorationLineToPath(params);
5705     if (aParams.decorationType == DecorationType::Normal) {
5706       aParams.callbacks->PaintDecorationLine(path, params.color);
5707     } else {
5708       aParams.callbacks->PaintSelectionDecorationLine(path, params.color);
5709     }
5710   } else {
5711     nsCSSRendering::PaintDecorationLine(this, *aParams.context->GetDrawTarget(),
5712                                         params);
5713   }
5714 }
5715 
ToStyleLineStyle(const TextRangeStyle & aStyle)5716 static uint8_t ToStyleLineStyle(const TextRangeStyle& aStyle) {
5717   switch (aStyle.mLineStyle) {
5718     case TextRangeStyle::LineStyle::None:
5719       return NS_STYLE_TEXT_DECORATION_STYLE_NONE;
5720     case TextRangeStyle::LineStyle::Solid:
5721       return NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5722     case TextRangeStyle::LineStyle::Dotted:
5723       return NS_STYLE_TEXT_DECORATION_STYLE_DOTTED;
5724     case TextRangeStyle::LineStyle::Dashed:
5725       return NS_STYLE_TEXT_DECORATION_STYLE_DASHED;
5726     case TextRangeStyle::LineStyle::Double:
5727       return NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE;
5728     case TextRangeStyle::LineStyle::Wavy:
5729       return NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
5730   }
5731   MOZ_ASSERT_UNREACHABLE("Invalid line style");
5732   return NS_STYLE_TEXT_DECORATION_STYLE_NONE;
5733 }
5734 
5735 /**
5736  * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
5737  * about drawing text decoration for selections.
5738  */
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)5739 void nsTextFrame::DrawSelectionDecorations(
5740     gfxContext* aContext, const LayoutDeviceRect& aDirtyRect,
5741     SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle,
5742     const TextRangeStyle& aRangeStyle, const Point& aPt,
5743     gfxFloat aICoordInFrame, gfxFloat aWidth, gfxFloat aAscent,
5744     const gfxFont::Metrics& aFontMetrics, DrawPathCallbacks* aCallbacks,
5745     bool aVertical, StyleTextDecorationLine aDecoration) {
5746   PaintDecorationLineParams params;
5747   params.context = aContext;
5748   params.dirtyRect = aDirtyRect;
5749   params.pt = aPt;
5750   params.lineSize.width = aWidth;
5751   params.ascent = aAscent;
5752   params.decoration = aDecoration;
5753   params.decorationType = DecorationType::Selection;
5754   params.callbacks = aCallbacks;
5755   params.vertical = aVertical;
5756   params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5757   params.descentLimit = ComputeDescentLimitForSelectionUnderline(
5758       aTextPaintStyle.PresContext(), aFontMetrics);
5759 
5760   float relativeSize;
5761   const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
5762   const gfxFloat appUnitsPerDevPixel =
5763       aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
5764 
5765   const WritingMode wm = GetWritingMode();
5766   switch (aSelectionType) {
5767     case SelectionType::eIMERawClause:
5768     case SelectionType::eIMESelectedRawClause:
5769     case SelectionType::eIMEConvertedClause:
5770     case SelectionType::eIMESelectedClause:
5771     case SelectionType::eSpellCheck: {
5772       int32_t index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
5773           aSelectionType);
5774       bool weDefineSelectionUnderline =
5775           aTextPaintStyle.GetSelectionUnderlineForPaint(
5776               index, &params.color, &relativeSize, &params.style);
5777       params.defaultLineThickness = ComputeSelectionUnderlineHeight(
5778           aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
5779       params.lineSize.height = ComputeDecorationLineThickness(
5780           decThickness, params.defaultLineThickness, aFontMetrics,
5781           appUnitsPerDevPixel, this);
5782 
5783       bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
5784       const auto* styleText = StyleText();
5785       params.offset = ComputeDecorationLineOffset(
5786           aDecoration, styleText->mTextUnderlinePosition,
5787           styleText->mTextUnderlineOffset, aFontMetrics, appUnitsPerDevPixel,
5788           this, wm.IsCentralBaseline(), swapUnderline);
5789 
5790       bool isIMEType = aSelectionType != SelectionType::eSpellCheck;
5791 
5792       if (isIMEType) {
5793         // IME decoration lines should not be drawn on the both ends, i.e., we
5794         // need to cut both edges of the decoration lines.  Because same style
5795         // IME selections can adjoin, but the users need to be able to know
5796         // where are the boundaries of the selections.
5797         //
5798         //  X: underline
5799         //
5800         //     IME selection #1        IME selection #2      IME selection #3
5801         //  |                     |                      |
5802         //  | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
5803         //  +---------------------+----------------------+--------------------
5804         //   ^                   ^ ^                    ^ ^
5805         //  gap                  gap                    gap
5806         params.pt.x += 1.0;
5807         params.lineSize.width -= 2.0;
5808       }
5809       if (isIMEType && aRangeStyle.IsDefined()) {
5810         // If IME defines the style, that should override our definition.
5811         if (aRangeStyle.IsLineStyleDefined()) {
5812           if (aRangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
5813             return;
5814           }
5815           params.style = ToStyleLineStyle(aRangeStyle);
5816           relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
5817         } else if (!weDefineSelectionUnderline) {
5818           // There is no underline style definition.
5819           return;
5820         }
5821         // If underline color is defined and that doesn't depend on the
5822         // foreground color, we should use the color directly.
5823         if (aRangeStyle.IsUnderlineColorDefined() &&
5824             (!aRangeStyle.IsForegroundColorDefined() ||
5825              aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
5826           params.color = aRangeStyle.mUnderlineColor;
5827         }
5828         // If foreground color or background color is defined, the both colors
5829         // are computed by GetSelectionTextColors().  Then, we should use its
5830         // foreground color always.  The color should have sufficient contrast
5831         // with the background color.
5832         else if (aRangeStyle.IsForegroundColorDefined() ||
5833                  aRangeStyle.IsBackgroundColorDefined()) {
5834           nscolor bg;
5835           GetSelectionTextColors(aSelectionType, aTextPaintStyle, aRangeStyle,
5836                                  &params.color, &bg);
5837         }
5838         // Otherwise, use the foreground color of the frame.
5839         else {
5840           params.color = aTextPaintStyle.GetTextColor();
5841         }
5842       } else if (!weDefineSelectionUnderline) {
5843         // IME doesn't specify the selection style and we don't define selection
5844         // underline.
5845         return;
5846       }
5847       break;
5848     }
5849     case SelectionType::eURLStrikeout: {
5850       nscoord inflationMinFontSize =
5851           nsLayoutUtils::InflationMinFontSizeFor(this);
5852       float inflation =
5853           GetInflationForTextDecorations(this, inflationMinFontSize);
5854       const gfxFont::Metrics metrics =
5855           GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
5856 
5857       relativeSize = 2.0f;
5858       aTextPaintStyle.GetURLSecondaryColor(&params.color);
5859       params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5860       params.defaultLineThickness = metrics.strikeoutSize;
5861       params.lineSize.height = ComputeDecorationLineThickness(
5862           decThickness, params.defaultLineThickness, metrics,
5863           appUnitsPerDevPixel, this);
5864       // TODO(jfkthame): ComputeDecorationLineOffset? check vertical mode!
5865       params.offset = metrics.strikeoutOffset + 0.5;
5866       params.decoration = StyleTextDecorationLine::LINE_THROUGH;
5867       break;
5868     }
5869     default:
5870       NS_WARNING("Requested selection decorations when there aren't any");
5871       return;
5872   }
5873   params.lineSize.height *= relativeSize;
5874   params.defaultLineThickness *= relativeSize;
5875   params.icoordInFrame =
5876       (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame;
5877   PaintDecorationLine(params);
5878 }
5879 
5880 /* static */
GetSelectionTextColors(SelectionType aSelectionType,nsTextPaintStyle & aTextPaintStyle,const TextRangeStyle & aRangeStyle,nscolor * aForeground,nscolor * aBackground)5881 bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
5882                                          nsTextPaintStyle& aTextPaintStyle,
5883                                          const TextRangeStyle& aRangeStyle,
5884                                          nscolor* aForeground,
5885                                          nscolor* aBackground) {
5886   switch (aSelectionType) {
5887     case SelectionType::eNormal:
5888       return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
5889     case SelectionType::eFind:
5890       aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
5891       return true;
5892     case SelectionType::eURLSecondary:
5893       aTextPaintStyle.GetURLSecondaryColor(aForeground);
5894       *aBackground = NS_RGBA(0, 0, 0, 0);
5895       return true;
5896     case SelectionType::eIMERawClause:
5897     case SelectionType::eIMESelectedRawClause:
5898     case SelectionType::eIMEConvertedClause:
5899     case SelectionType::eIMESelectedClause:
5900       if (aRangeStyle.IsDefined()) {
5901         if (!aRangeStyle.IsForegroundColorDefined() &&
5902             !aRangeStyle.IsBackgroundColorDefined()) {
5903           *aForeground = aTextPaintStyle.GetTextColor();
5904           *aBackground = NS_RGBA(0, 0, 0, 0);
5905           return false;
5906         }
5907         if (aRangeStyle.IsForegroundColorDefined()) {
5908           *aForeground = aRangeStyle.mForegroundColor;
5909           if (aRangeStyle.IsBackgroundColorDefined()) {
5910             *aBackground = aRangeStyle.mBackgroundColor;
5911           } else {
5912             // If foreground color is defined but background color isn't
5913             // defined, we can guess that IME must expect that the background
5914             // color is system's default field background color.
5915             *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor();
5916           }
5917         } else {  // aRangeStyle.IsBackgroundColorDefined() is true
5918           *aBackground = aRangeStyle.mBackgroundColor;
5919           // If background color is defined but foreground color isn't defined,
5920           // we can assume that IME must expect that the foreground color is
5921           // same as system's field text color.
5922           *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor();
5923         }
5924         return true;
5925       }
5926       aTextPaintStyle.GetIMESelectionColors(
5927           nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
5928               aSelectionType),
5929           aForeground, aBackground);
5930       return true;
5931     default:
5932       *aForeground = aTextPaintStyle.GetTextColor();
5933       *aBackground = NS_RGBA(0, 0, 0, 0);
5934       return false;
5935   }
5936 }
5937 
5938 /**
5939  * This sets *aShadows to the appropriate shadows, if any, for the given
5940  * type of selection.
5941  * If text-shadow was not specified, *aShadows is left untouched.
5942  */
GetSelectionTextShadow(nsIFrame * aFrame,SelectionType aSelectionType,nsTextPaintStyle & aTextPaintStyle,Span<const StyleSimpleShadow> * aShadows)5943 static void GetSelectionTextShadow(nsIFrame* aFrame,
5944                                    SelectionType aSelectionType,
5945                                    nsTextPaintStyle& aTextPaintStyle,
5946                                    Span<const StyleSimpleShadow>* aShadows) {
5947   if (aSelectionType != SelectionType::eNormal) {
5948     return;
5949   }
5950   aTextPaintStyle.GetSelectionShadow(aShadows);
5951 }
5952 
5953 /**
5954  * This class lets us iterate over chunks of text in a uniform selection state,
5955  * observing cluster boundaries, in content order, maintaining the current
5956  * x-offset as we go, and telling whether the text chunk has a hyphen after
5957  * it or not. The caller is responsible for actually computing the advance
5958  * width of each chunk.
5959  */
5960 class SelectionIterator {
5961   typedef nsTextFrame::PropertyProvider PropertyProvider;
5962 
5963  public:
5964   /**
5965    * aStart and aLength are in the original string. aSelectionDetails is
5966    * according to the original string.
5967    * @param aXOffset the offset from the origin of the frame to the start
5968    * of the text (the left baseline origin for LTR, the right baseline origin
5969    * for RTL)
5970    */
5971   SelectionIterator(SelectionDetails** aSelectionDetails,
5972                     gfxTextRun::Range aRange, PropertyProvider& aProvider,
5973                     gfxTextRun* aTextRun, gfxFloat aXOffset);
5974 
5975   /**
5976    * Returns the next segment of uniformly selected (or not) text.
5977    * @param aXOffset the offset from the origin of the frame to the start
5978    * of the text (the left baseline origin for LTR, the right baseline origin
5979    * for RTL)
5980    * @param aRange the transformed string range of the text for this segment
5981    * @param aHyphenWidth if a hyphen is to be rendered after the text, the
5982    * width of the hyphen, otherwise zero
5983    * @param aSelectionType the selection type for this segment
5984    * @param aStyle the selection style for this segment
5985    * @return false if there are no more segments
5986    */
5987   bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
5988                       gfxFloat* aHyphenWidth, SelectionType* aSelectionType,
5989                       TextRangeStyle* aStyle);
UpdateWithAdvance(gfxFloat aAdvance)5990   void UpdateWithAdvance(gfxFloat aAdvance) {
5991     mXOffset += aAdvance * mTextRun->GetDirection();
5992   }
5993 
5994  private:
5995   SelectionDetails** mSelectionDetails;
5996   PropertyProvider& mProvider;
5997   RefPtr<gfxTextRun> mTextRun;
5998   gfxSkipCharsIterator mIterator;
5999   gfxTextRun::Range mOriginalRange;
6000   gfxFloat mXOffset;
6001 };
6002 
SelectionIterator(SelectionDetails ** aSelectionDetails,gfxTextRun::Range aRange,PropertyProvider & aProvider,gfxTextRun * aTextRun,gfxFloat aXOffset)6003 SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
6004                                      gfxTextRun::Range aRange,
6005                                      PropertyProvider& aProvider,
6006                                      gfxTextRun* aTextRun, gfxFloat aXOffset)
6007     : mSelectionDetails(aSelectionDetails),
6008       mProvider(aProvider),
6009       mTextRun(aTextRun),
6010       mIterator(aProvider.GetStart()),
6011       mOriginalRange(aRange),
6012       mXOffset(aXOffset) {
6013   mIterator.SetOriginalOffset(aRange.start);
6014 }
6015 
GetNextSegment(gfxFloat * aXOffset,gfxTextRun::Range * aRange,gfxFloat * aHyphenWidth,SelectionType * aSelectionType,TextRangeStyle * aStyle)6016 bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
6017                                        gfxTextRun::Range* aRange,
6018                                        gfxFloat* aHyphenWidth,
6019                                        SelectionType* aSelectionType,
6020                                        TextRangeStyle* aStyle) {
6021   if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end))
6022     return false;
6023 
6024   // save offset into transformed string now
6025   uint32_t runOffset = mIterator.GetSkippedOffset();
6026 
6027   uint32_t index = mIterator.GetOriginalOffset() - mOriginalRange.start;
6028   SelectionDetails* sdptr = mSelectionDetails[index];
6029   SelectionType selectionType =
6030       sdptr ? sdptr->mSelectionType : SelectionType::eNone;
6031   TextRangeStyle style;
6032   if (sdptr) {
6033     style = sdptr->mTextRangeStyle;
6034   }
6035   for (++index; index < mOriginalRange.Length(); ++index) {
6036     if (sdptr != mSelectionDetails[index]) break;
6037   }
6038   mIterator.SetOriginalOffset(index + mOriginalRange.start);
6039 
6040   // Advance to the next cluster boundary
6041   while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
6042          !mIterator.IsOriginalCharSkipped() &&
6043          !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
6044     mIterator.AdvanceOriginal(1);
6045   }
6046 
6047   bool haveHyphenBreak =
6048       mProvider.GetFrame()->HasAnyStateBits(TEXT_HYPHEN_BREAK);
6049   aRange->start = runOffset;
6050   aRange->end = mIterator.GetSkippedOffset();
6051   *aXOffset = mXOffset;
6052   *aHyphenWidth = 0;
6053   if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
6054       haveHyphenBreak) {
6055     *aHyphenWidth = mProvider.GetHyphenWidth();
6056   }
6057   *aSelectionType = selectionType;
6058   *aStyle = style;
6059   return true;
6060 }
6061 
AddHyphenToMetrics(nsTextFrame * aTextFrame,bool aIsRightToLeft,gfxTextRun::Metrics * aMetrics,gfxFont::BoundingBoxType aBoundingBoxType,DrawTarget * aDrawTarget)6062 static void AddHyphenToMetrics(nsTextFrame* aTextFrame, bool aIsRightToLeft,
6063                                gfxTextRun::Metrics* aMetrics,
6064                                gfxFont::BoundingBoxType aBoundingBoxType,
6065                                DrawTarget* aDrawTarget) {
6066   // Fix up metrics to include hyphen
6067   RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(aTextFrame, aDrawTarget);
6068   if (!hyphenTextRun) {
6069     return;
6070   }
6071 
6072   gfxTextRun::Metrics hyphenMetrics =
6073       hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
6074   if (aTextFrame->GetWritingMode().IsLineInverted()) {
6075     hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
6076   }
6077   aMetrics->CombineWith(hyphenMetrics, aIsRightToLeft);
6078 }
6079 
PaintOneShadow(const PaintShadowParams & aParams,const StyleSimpleShadow & aShadowDetails,gfxRect & aBoundingBox,uint32_t aBlurFlags)6080 void nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
6081                                  const StyleSimpleShadow& aShadowDetails,
6082                                  gfxRect& aBoundingBox, uint32_t aBlurFlags) {
6083   AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
6084 
6085   nsPoint shadowOffset(aShadowDetails.horizontal.ToAppUnits(),
6086                        aShadowDetails.vertical.ToAppUnits());
6087   nscoord blurRadius = std::max(aShadowDetails.blur.ToAppUnits(), 0);
6088 
6089   nscolor shadowColor = aShadowDetails.color.CalcColor(aParams.foregroundColor);
6090 
6091   if (auto* textDrawer = aParams.context->GetTextDrawer()) {
6092     wr::Shadow wrShadow;
6093 
6094     wrShadow.offset = {PresContext()->AppUnitsToFloatDevPixels(shadowOffset.x),
6095                        PresContext()->AppUnitsToFloatDevPixels(shadowOffset.y)};
6096 
6097     wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
6098     wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
6099 
6100     bool inflate = true;
6101     textDrawer->AppendShadow(wrShadow, inflate);
6102     return;
6103   }
6104 
6105   // This rect is the box which is equivalent to where the shadow will be
6106   // painted. The origin of aBoundingBox is the text baseline left, so we must
6107   // translate it by that much in order to make the origin the top-left corner
6108   // of the text bounding box. Note that aLeftSideOffset is line-left, so
6109   // actually means top offset in vertical writing modes.
6110   gfxRect shadowGfxRect;
6111   WritingMode wm = GetWritingMode();
6112   if (wm.IsVertical()) {
6113     shadowGfxRect = aBoundingBox;
6114     if (wm.IsVerticalRL()) {
6115       // for vertical-RL, reverse direction of x-coords of bounding box
6116       shadowGfxRect.x = -shadowGfxRect.XMost();
6117     }
6118     shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
6119                               aParams.framePt.y + aParams.leftSideOffset);
6120   } else {
6121     shadowGfxRect =
6122         aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
6123                                 aParams.textBaselinePt.y);
6124   }
6125   Point shadowGfxOffset(shadowOffset.x, shadowOffset.y);
6126   shadowGfxRect += gfxPoint(shadowGfxOffset.x, shadowOffset.y);
6127 
6128   nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
6129                     NSToCoordRound(shadowGfxRect.Y()),
6130                     NSToCoordRound(shadowGfxRect.Width()),
6131                     NSToCoordRound(shadowGfxRect.Height()));
6132 
6133   nsContextBoxBlur contextBoxBlur;
6134   const auto A2D = PresContext()->AppUnitsPerDevPixel();
6135   gfxContext* shadowContext =
6136       contextBoxBlur.Init(shadowRect, 0, blurRadius, A2D, aParams.context,
6137                           LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D),
6138                           nullptr, aBlurFlags);
6139   if (!shadowContext) return;
6140 
6141   aParams.context->Save();
6142   aParams.context->SetColor(sRGBColor::FromABGR(shadowColor));
6143 
6144   // Draw the text onto our alpha-only surface to capture the alpha values.
6145   // Remember that the box blur context has a device offset on it, so we don't
6146   // need to translate any coordinates to fit on the surface.
6147   gfxFloat advanceWidth;
6148   nsTextPaintStyle textPaintStyle(this);
6149   DrawTextParams params(shadowContext);
6150   params.advanceWidth = &advanceWidth;
6151   params.dirtyRect = aParams.dirtyRect;
6152   params.framePt = aParams.framePt + shadowGfxOffset;
6153   params.provider = aParams.provider;
6154   params.textStyle = &textPaintStyle;
6155   params.textColor =
6156       aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
6157   params.clipEdges = aParams.clipEdges;
6158   params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
6159   // Multi-color shadow is not allowed, so we use the same color of the text
6160   // color.
6161   params.decorationOverrideColor = &params.textColor;
6162   DrawText(aParams.range, aParams.textBaselinePt + shadowGfxOffset, params);
6163 
6164   contextBoxBlur.DoPaint();
6165   aParams.context->Restore();
6166 }
6167 
6168 // Paints selection backgrounds and text in the correct colors. Also computes
6169 // aAllTypes, the union of all selection types that are applying to this text.
PaintTextWithSelectionColors(const PaintTextSelectionParams & aParams,const UniquePtr<SelectionDetails> & aDetails,SelectionTypeMask * aAllSelectionTypeMask,const nsDisplayText::ClipEdges & aClipEdges)6170 bool nsTextFrame::PaintTextWithSelectionColors(
6171     const PaintTextSelectionParams& aParams,
6172     const UniquePtr<SelectionDetails>& aDetails,
6173     SelectionTypeMask* aAllSelectionTypeMask,
6174     const nsDisplayText::ClipEdges& aClipEdges) {
6175   const gfxTextRun::Range& contentRange = aParams.contentRange;
6176 
6177   // Figure out which selections control the colors to use for each character.
6178   // Note: prevailingSelectionsBuffer is keeping extra raw pointers to
6179   // uniquely-owned resources, but it's safe because it's temporary and the
6180   // resources are owned by the caller. Therefore, they'll outlive this object.
6181   AutoTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
6182   SelectionDetails** prevailingSelections =
6183       prevailingSelectionsBuffer.AppendElements(contentRange.Length(),
6184                                                 fallible);
6185   if (!prevailingSelections) {
6186     return false;
6187   }
6188 
6189   SelectionTypeMask allSelectionTypeMask = 0;
6190   for (uint32_t i = 0; i < contentRange.Length(); ++i) {
6191     prevailingSelections[i] = nullptr;
6192   }
6193 
6194   bool anyBackgrounds = false;
6195   for (SelectionDetails* sdptr = aDetails.get(); sdptr;
6196        sdptr = sdptr->mNext.get()) {
6197     int32_t start = std::max(0, sdptr->mStart - int32_t(contentRange.start));
6198     int32_t end = std::min(int32_t(contentRange.Length()),
6199                            sdptr->mEnd - int32_t(contentRange.start));
6200     SelectionType selectionType = sdptr->mSelectionType;
6201     if (start < end) {
6202       allSelectionTypeMask |= ToSelectionTypeMask(selectionType);
6203       // Ignore selections that don't set colors
6204       nscolor foreground, background;
6205       if (GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6206                                  sdptr->mTextRangeStyle, &foreground,
6207                                  &background)) {
6208         if (NS_GET_A(background) > 0) {
6209           anyBackgrounds = true;
6210         }
6211         for (int32_t i = start; i < end; ++i) {
6212           // Favour normal selection over IME selections
6213           if (!prevailingSelections[i] ||
6214               selectionType < prevailingSelections[i]->mSelectionType) {
6215             prevailingSelections[i] = sdptr;
6216           }
6217         }
6218       }
6219     }
6220   }
6221   *aAllSelectionTypeMask = allSelectionTypeMask;
6222 
6223   if (!allSelectionTypeMask) {
6224     // Nothing is selected in the given text range. XXX can this still occur?
6225     return false;
6226   }
6227 
6228   bool vertical = mTextRun->IsVertical();
6229   const gfxFloat startIOffset =
6230       vertical ? aParams.textBaselinePt.y - aParams.framePt.y
6231                : aParams.textBaselinePt.x - aParams.framePt.x;
6232   gfxFloat iOffset, hyphenWidth;
6233   Range range;  // in transformed string
6234   TextRangeStyle rangeStyle;
6235   // Draw background colors
6236 
6237   auto* textDrawer = aParams.context->GetTextDrawer();
6238 
6239   if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
6240     int32_t appUnitsPerDevPixel =
6241         aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
6242     SelectionIterator iterator(prevailingSelections, contentRange,
6243                                *aParams.provider, mTextRun, startIOffset);
6244     SelectionType selectionType;
6245     while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6246                                    &selectionType, &rangeStyle)) {
6247       nscolor foreground, background;
6248       GetSelectionTextColors(selectionType, *aParams.textPaintStyle, rangeStyle,
6249                              &foreground, &background);
6250       // Draw background color
6251       gfxFloat advance =
6252           hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
6253       if (NS_GET_A(background) > 0) {
6254         nsRect bgRect;
6255         gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
6256         if (vertical) {
6257           bgRect = nsRect(aParams.framePt.x, aParams.framePt.y + offs,
6258                           GetSize().width, advance);
6259         } else {
6260           bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y, advance,
6261                           GetSize().height);
6262         }
6263 
6264         LayoutDeviceRect selectionRect =
6265             LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
6266 
6267         if (textDrawer) {
6268           textDrawer->AppendSelectionRect(selectionRect,
6269                                           ToDeviceColor(background));
6270         } else {
6271           PaintSelectionBackground(*aParams.context->GetDrawTarget(),
6272                                    background, aParams.dirtyRect, selectionRect,
6273                                    aParams.callbacks);
6274         }
6275       }
6276       iterator.UpdateWithAdvance(advance);
6277     }
6278   }
6279 
6280   gfxFloat advance;
6281   DrawTextParams params(aParams.context);
6282   params.dirtyRect = aParams.dirtyRect;
6283   params.framePt = aParams.framePt;
6284   params.provider = aParams.provider;
6285   params.textStyle = aParams.textPaintStyle;
6286   params.clipEdges = &aClipEdges;
6287   params.advanceWidth = &advance;
6288   params.callbacks = aParams.callbacks;
6289   params.glyphRange = aParams.glyphRange;
6290 
6291   PaintShadowParams shadowParams(aParams);
6292   shadowParams.provider = aParams.provider;
6293   shadowParams.clipEdges = &aClipEdges;
6294 
6295   // Draw text
6296   const nsStyleText* textStyle = StyleText();
6297   SelectionIterator iterator(prevailingSelections, contentRange,
6298                              *aParams.provider, mTextRun, startIOffset);
6299   SelectionType selectionType;
6300   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth, &selectionType,
6301                                  &rangeStyle)) {
6302     nscolor foreground, background;
6303     if (aParams.IsGenerateTextMask()) {
6304       foreground = NS_RGBA(0, 0, 0, 255);
6305     } else {
6306       GetSelectionTextColors(selectionType, *aParams.textPaintStyle, rangeStyle,
6307                              &foreground, &background);
6308     }
6309 
6310     gfx::Point textBaselinePt =
6311         vertical
6312             ? gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset)
6313             : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
6314 
6315     // Determine what shadow, if any, to draw - either from textStyle
6316     // or from the ::-moz-selection pseudo-class if specified there
6317     Span<const StyleSimpleShadow> shadows = textStyle->mTextShadow.AsSpan();
6318     GetSelectionTextShadow(this, selectionType, *aParams.textPaintStyle,
6319                            &shadows);
6320     if (!shadows.IsEmpty()) {
6321       nscoord startEdge = iOffset;
6322       if (mTextRun->IsInlineReversed()) {
6323         startEdge -=
6324             hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
6325       }
6326       shadowParams.range = range;
6327       shadowParams.textBaselinePt = textBaselinePt;
6328       shadowParams.foregroundColor = foreground;
6329       shadowParams.leftSideOffset = startEdge;
6330       PaintShadows(shadows, shadowParams);
6331     }
6332 
6333     // Draw text segment
6334     params.textColor = foreground;
6335     params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
6336     params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
6337     params.drawSoftHyphen = hyphenWidth > 0;
6338     DrawText(range, textBaselinePt, params);
6339     advance += hyphenWidth;
6340     iterator.UpdateWithAdvance(advance);
6341   }
6342   return true;
6343 }
6344 
PaintTextSelectionDecorations(const PaintTextSelectionParams & aParams,const UniquePtr<SelectionDetails> & aDetails,SelectionType aSelectionType)6345 void nsTextFrame::PaintTextSelectionDecorations(
6346     const PaintTextSelectionParams& aParams,
6347     const UniquePtr<SelectionDetails>& aDetails, SelectionType aSelectionType) {
6348   // Hide text decorations if we're currently hiding @font-face fallback text
6349   if (aParams.provider->GetFontGroup()->ShouldSkipDrawing()) return;
6350 
6351   // Figure out which characters will be decorated for this selection.
6352   // Note: selectedCharsBuffer is keeping extra raw pointers to
6353   // uniquely-owned resources, but it's safe because it's temporary and the
6354   // resources are owned by the caller. Therefore, they'll outlive this object.
6355   const gfxTextRun::Range& contentRange = aParams.contentRange;
6356   AutoTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
6357   SelectionDetails** selectedChars =
6358       selectedCharsBuffer.AppendElements(contentRange.Length(), fallible);
6359   if (!selectedChars) {
6360     return;
6361   }
6362   for (uint32_t i = 0; i < contentRange.Length(); ++i) {
6363     selectedChars[i] = nullptr;
6364   }
6365 
6366   for (SelectionDetails* sdptr = aDetails.get(); sdptr;
6367        sdptr = sdptr->mNext.get()) {
6368     if (sdptr->mSelectionType == aSelectionType) {
6369       int32_t start = std::max(0, sdptr->mStart - int32_t(contentRange.start));
6370       int32_t end = std::min(int32_t(contentRange.Length()),
6371                              sdptr->mEnd - int32_t(contentRange.start));
6372       for (int32_t i = start; i < end; ++i) {
6373         selectedChars[i] = sdptr;
6374       }
6375     }
6376   }
6377 
6378   gfxFont* firstFont = aParams.provider->GetFontGroup()->GetFirstValidFont();
6379   bool verticalRun = mTextRun->IsVertical();
6380   bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
6381   bool rightUnderline = useVerticalMetrics && IsUnderlineRight(*Style());
6382   const auto kDecoration = rightUnderline ? StyleTextDecorationLine::OVERLINE
6383                                           : StyleTextDecorationLine::UNDERLINE;
6384   gfxFont::Metrics decorationMetrics(
6385       firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
6386                                                : nsFontMetrics::eHorizontal));
6387   decorationMetrics.underlineOffset =
6388       aParams.provider->GetFontGroup()->GetUnderlineOffset();
6389 
6390   gfxFloat startIOffset = verticalRun
6391                               ? aParams.textBaselinePt.y - aParams.framePt.y
6392                               : aParams.textBaselinePt.x - aParams.framePt.x;
6393   SelectionIterator iterator(selectedChars, contentRange, *aParams.provider,
6394                              mTextRun, startIOffset);
6395   gfxFloat iOffset, hyphenWidth;
6396   Range range;
6397   int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
6398   // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
6399   Point pt;
6400   if (verticalRun) {
6401     pt.x = (aParams.textBaselinePt.x - mAscent) / app;
6402   } else {
6403     pt.y = (aParams.textBaselinePt.y - mAscent) / app;
6404   }
6405   SelectionType nextSelectionType;
6406   TextRangeStyle selectedStyle;
6407 
6408   while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6409                                  &nextSelectionType, &selectedStyle)) {
6410     gfxFloat advance =
6411         hyphenWidth + mTextRun->GetAdvanceWidth(range, aParams.provider);
6412     if (nextSelectionType == aSelectionType) {
6413       if (verticalRun) {
6414         pt.y = (aParams.framePt.y + iOffset -
6415                 (mTextRun->IsInlineReversed() ? advance : 0)) /
6416                app;
6417       } else {
6418         pt.x = (aParams.framePt.x + iOffset -
6419                 (mTextRun->IsInlineReversed() ? advance : 0)) /
6420                app;
6421       }
6422       gfxFloat width = Abs(advance) / app;
6423       gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
6424       DrawSelectionDecorations(aParams.context, aParams.dirtyRect,
6425                                aSelectionType, *aParams.textPaintStyle,
6426                                selectedStyle, pt, xInFrame, width,
6427                                mAscent / app, decorationMetrics,
6428                                aParams.callbacks, verticalRun, kDecoration);
6429     }
6430     iterator.UpdateWithAdvance(advance);
6431   }
6432 }
6433 
PaintTextWithSelection(const PaintTextSelectionParams & aParams,const nsDisplayText::ClipEdges & aClipEdges)6434 bool nsTextFrame::PaintTextWithSelection(
6435     const PaintTextSelectionParams& aParams,
6436     const nsDisplayText::ClipEdges& aClipEdges) {
6437   NS_ASSERTION(GetContent()->IsMaybeSelected(), "wrong paint path");
6438 
6439   UniquePtr<SelectionDetails> details = GetSelectionDetails();
6440   if (!details) {
6441     return false;
6442   }
6443 
6444   SelectionTypeMask allSelectionTypeMask;
6445   if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask,
6446                                     aClipEdges)) {
6447     return false;
6448   }
6449   // Iterate through just the selection rawSelectionTypes that paint decorations
6450   // and paint decorations for any that actually occur in this frame. Paint
6451   // higher-numbered selection rawSelectionTypes below lower-numered ones on the
6452   // general principal that lower-numbered selections are higher priority.
6453   allSelectionTypeMask &= kSelectionTypesWithDecorations;
6454   MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,
6455              "The following for loop assumes that the first item of "
6456              "kPresentSelectionTypes is SelectionType::eNormal");
6457   for (size_t i = ArrayLength(kPresentSelectionTypes) - 1; i >= 1; --i) {
6458     SelectionType selectionType = kPresentSelectionTypes[i];
6459     if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
6460       // There is some selection of this selectionType. Try to paint its
6461       // decorations (there might not be any for this type but that's OK,
6462       // PaintTextSelectionDecorations will exit early).
6463       PaintTextSelectionDecorations(aParams, details, selectionType);
6464     }
6465   }
6466 
6467   return true;
6468 }
6469 
DrawEmphasisMarks(gfxContext * aContext,WritingMode aWM,const gfx::Point & aTextBaselinePt,const gfx::Point & aFramePt,Range aRange,const nscolor * aDecorationOverrideColor,PropertyProvider * aProvider)6470 void nsTextFrame::DrawEmphasisMarks(gfxContext* aContext, WritingMode aWM,
6471                                     const gfx::Point& aTextBaselinePt,
6472                                     const gfx::Point& aFramePt, Range aRange,
6473                                     const nscolor* aDecorationOverrideColor,
6474                                     PropertyProvider* aProvider) {
6475   const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
6476   if (!info) {
6477     return;
6478   }
6479 
6480   bool isTextCombined = Style()->IsTextCombined();
6481   if (isTextCombined && !aWM.IsVertical()) {
6482     // XXX This only happens when the parent is display:contents with an
6483     // orthogonal writing mode. This should be rare, and don't have use
6484     // cases, so we don't care. It is non-trivial to implement a sane
6485     // behavior for that case: if you treat the text as not combined,
6486     // the marks would spread wider than the text (which is rendered as
6487     // combined); if you try to draw a single mark, selecting part of
6488     // the text could dynamically create multiple new marks.
6489     NS_WARNING("Give up on combined text with horizontal wm");
6490     return;
6491   }
6492   nscolor color =
6493       aDecorationOverrideColor
6494           ? *aDecorationOverrideColor
6495           : nsLayoutUtils::GetColor(this, &nsStyleText::mTextEmphasisColor);
6496   aContext->SetColor(sRGBColor::FromABGR(color));
6497   gfx::Point pt;
6498   if (!isTextCombined) {
6499     pt = aTextBaselinePt;
6500   } else {
6501     MOZ_ASSERT(aWM.IsVertical());
6502     pt = aFramePt;
6503     if (aWM.IsVerticalRL()) {
6504       pt.x += GetSize().width - GetLogicalBaseline(aWM);
6505     } else {
6506       pt.x += GetLogicalBaseline(aWM);
6507     }
6508   }
6509   if (!aWM.IsVertical()) {
6510     pt.y += info->baselineOffset;
6511   } else {
6512     if (aWM.IsVerticalRL()) {
6513       pt.x -= info->baselineOffset;
6514     } else {
6515       pt.x += info->baselineOffset;
6516     }
6517   }
6518   if (!isTextCombined) {
6519     mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(), info->advance,
6520                                 pt, aRange, aProvider);
6521   } else {
6522     pt.y += (GetSize().height - info->advance) / 2;
6523     gfxTextRun::DrawParams params(aContext);
6524     info->textRun->Draw(Range(info->textRun.get()), pt, params);
6525   }
6526 }
6527 
GetCaretColorAt(int32_t aOffset)6528 nscolor nsTextFrame::GetCaretColorAt(int32_t aOffset) {
6529   MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
6530 
6531   nscolor result = nsIFrame::GetCaretColorAt(aOffset);
6532   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6533   PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
6534   int32_t contentOffset = provider.GetStart().GetOriginalOffset();
6535   int32_t contentLength = provider.GetOriginalLength();
6536   MOZ_ASSERT(
6537       aOffset >= contentOffset && aOffset <= contentOffset + contentLength,
6538       "aOffset must be in the frame's range");
6539 
6540   int32_t offsetInFrame = aOffset - contentOffset;
6541   if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
6542     return result;
6543   }
6544 
6545   bool isSolidTextColor = true;
6546   if (SVGUtils::IsInSVGTextSubtree(this)) {
6547     const nsStyleSVG* style = StyleSVG();
6548     if (!style->mFill.kind.IsNone() && !style->mFill.kind.IsColor()) {
6549       isSolidTextColor = false;
6550     }
6551   }
6552 
6553   nsTextPaintStyle textPaintStyle(this);
6554   textPaintStyle.SetResolveColors(isSolidTextColor);
6555   UniquePtr<SelectionDetails> details = GetSelectionDetails();
6556   SelectionType selectionType = SelectionType::eNone;
6557   for (SelectionDetails* sdptr = details.get(); sdptr;
6558        sdptr = sdptr->mNext.get()) {
6559     int32_t start = std::max(0, sdptr->mStart - contentOffset);
6560     int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
6561     if (start <= offsetInFrame && offsetInFrame < end &&
6562         (selectionType == SelectionType::eNone ||
6563          sdptr->mSelectionType < selectionType)) {
6564       nscolor foreground, background;
6565       if (GetSelectionTextColors(sdptr->mSelectionType, textPaintStyle,
6566                                  sdptr->mTextRangeStyle, &foreground,
6567                                  &background)) {
6568         if (!isSolidTextColor && NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
6569           result = NS_RGBA(0, 0, 0, 255);
6570         } else {
6571           result = foreground;
6572         }
6573         selectionType = sdptr->mSelectionType;
6574       }
6575     }
6576   }
6577 
6578   return result;
6579 }
6580 
ComputeTransformedRange(nsTextFrame::PropertyProvider & aProvider)6581 static gfxTextRun::Range ComputeTransformedRange(
6582     nsTextFrame::PropertyProvider& aProvider) {
6583   gfxSkipCharsIterator iter(aProvider.GetStart());
6584   uint32_t start = iter.GetSkippedOffset();
6585   iter.AdvanceOriginal(aProvider.GetOriginalLength());
6586   return gfxTextRun::Range(start, iter.GetSkippedOffset());
6587 }
6588 
MeasureCharClippedText(nscoord aVisIStartEdge,nscoord aVisIEndEdge,nscoord * aSnappedStartEdge,nscoord * aSnappedEndEdge)6589 bool nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
6590                                          nscoord aVisIEndEdge,
6591                                          nscoord* aSnappedStartEdge,
6592                                          nscoord* aSnappedEndEdge) {
6593   // We need a *reference* rendering context (not one that might have a
6594   // transform), so we don't have a rendering context argument.
6595   // XXX get the block and line passed to us somehow! This is slow!
6596   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6597   if (!mTextRun) return false;
6598 
6599   PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
6600   // Trim trailing whitespace
6601   provider.InitializeForDisplay(true);
6602 
6603   Range range = ComputeTransformedRange(provider);
6604   uint32_t startOffset = range.start;
6605   uint32_t maxLength = range.Length();
6606   return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
6607                                 &startOffset, &maxLength, aSnappedStartEdge,
6608                                 aSnappedEndEdge);
6609 }
6610 
GetClusterLength(const gfxTextRun * aTextRun,uint32_t aStartOffset,uint32_t aMaxLength,bool aIsRTL)6611 static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
6612                                  uint32_t aStartOffset, uint32_t aMaxLength,
6613                                  bool aIsRTL) {
6614   uint32_t clusterLength = aIsRTL ? 0 : 1;
6615   while (clusterLength < aMaxLength) {
6616     if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
6617       if (aIsRTL) {
6618         ++clusterLength;
6619       }
6620       break;
6621     }
6622     ++clusterLength;
6623   }
6624   return clusterLength;
6625 }
6626 
MeasureCharClippedText(PropertyProvider & aProvider,nscoord aVisIStartEdge,nscoord aVisIEndEdge,uint32_t * aStartOffset,uint32_t * aMaxLength,nscoord * aSnappedStartEdge,nscoord * aSnappedEndEdge)6627 bool nsTextFrame::MeasureCharClippedText(
6628     PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge,
6629     uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge,
6630     nscoord* aSnappedEndEdge) {
6631   *aSnappedStartEdge = 0;
6632   *aSnappedEndEdge = 0;
6633   if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
6634     return true;
6635   }
6636 
6637   uint32_t offset = *aStartOffset;
6638   uint32_t maxLength = *aMaxLength;
6639   const nscoord frameISize = ISize();
6640   const bool rtl = mTextRun->IsRightToLeft();
6641   gfxFloat advanceWidth = 0;
6642   const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
6643   if (startEdge > 0) {
6644     const gfxFloat maxAdvance = gfxFloat(startEdge);
6645     while (maxLength > 0) {
6646       uint32_t clusterLength =
6647           GetClusterLength(mTextRun, offset, maxLength, rtl);
6648       advanceWidth += mTextRun->GetAdvanceWidth(
6649           Range(offset, offset + clusterLength), &aProvider);
6650       maxLength -= clusterLength;
6651       offset += clusterLength;
6652       if (advanceWidth >= maxAdvance) {
6653         break;
6654       }
6655     }
6656     nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
6657     *snappedStartEdge = NSToCoordFloor(advanceWidth);
6658     *aStartOffset = offset;
6659   }
6660 
6661   const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
6662   if (endEdge > 0) {
6663     const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
6664     while (maxLength > 0) {
6665       uint32_t clusterLength =
6666           GetClusterLength(mTextRun, offset, maxLength, rtl);
6667       gfxFloat nextAdvance =
6668           advanceWidth + mTextRun->GetAdvanceWidth(
6669                              Range(offset, offset + clusterLength), &aProvider);
6670       if (nextAdvance > maxAdvance) {
6671         break;
6672       }
6673       // This cluster fits, include it.
6674       advanceWidth = nextAdvance;
6675       maxLength -= clusterLength;
6676       offset += clusterLength;
6677     }
6678     maxLength = offset - *aStartOffset;
6679     nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
6680     *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
6681   }
6682   *aMaxLength = maxLength;
6683   return maxLength != 0;
6684 }
6685 
PaintShadows(Span<const StyleSimpleShadow> aShadows,const PaintShadowParams & aParams)6686 void nsTextFrame::PaintShadows(Span<const StyleSimpleShadow> aShadows,
6687                                const PaintShadowParams& aParams) {
6688   if (aShadows.IsEmpty()) {
6689     return;
6690   }
6691 
6692   gfxTextRun::Metrics shadowMetrics = mTextRun->MeasureText(
6693       aParams.range, gfxFont::LOOSE_INK_EXTENTS, nullptr, aParams.provider);
6694   if (GetWritingMode().IsLineInverted()) {
6695     std::swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
6696     shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
6697   }
6698   if (HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
6699     AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &shadowMetrics,
6700                        gfxFont::LOOSE_INK_EXTENTS,
6701                        aParams.context->GetDrawTarget());
6702   }
6703   // Add bounds of text decorations
6704   gfxRect decorationRect(0, -shadowMetrics.mAscent, shadowMetrics.mAdvanceWidth,
6705                          shadowMetrics.mAscent + shadowMetrics.mDescent);
6706   shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
6707                                        decorationRect);
6708 
6709   // If the textrun uses any color or SVG fonts, we need to force use of a mask
6710   // for shadow rendering even if blur radius is zero.
6711   // Force disable hardware acceleration for text shadows since it's usually
6712   // more expensive than just doing it on the CPU.
6713   uint32_t blurFlags = nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR;
6714   uint32_t numGlyphRuns;
6715   const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
6716   while (numGlyphRuns-- > 0) {
6717     if (run->mFont->AlwaysNeedsMaskForShadow()) {
6718       blurFlags |= nsContextBoxBlur::FORCE_MASK;
6719       break;
6720     }
6721     run++;
6722   }
6723 
6724   if (mTextRun->IsVertical()) {
6725     std::swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
6726     std::swap(shadowMetrics.mBoundingBox.width,
6727               shadowMetrics.mBoundingBox.height);
6728   }
6729 
6730   for (const auto& shadow : Reversed(aShadows)) {
6731     PaintOneShadow(aParams, shadow, shadowMetrics.mBoundingBox, blurFlags);
6732   }
6733 }
6734 
PaintText(const PaintTextParams & aParams,const nscoord aVisIStartEdge,const nscoord aVisIEndEdge,const nsPoint & aToReferenceFrame,const bool aIsSelected,float aOpacity)6735 void nsTextFrame::PaintText(const PaintTextParams& aParams,
6736                             const nscoord aVisIStartEdge,
6737                             const nscoord aVisIEndEdge,
6738                             const nsPoint& aToReferenceFrame,
6739                             const bool aIsSelected,
6740                             float aOpacity /* = 1.0f */) {
6741   // Don't pass in the rendering context here, because we need a
6742   // *reference* context and rendering context might have some transform
6743   // in it
6744   // XXX get the block and line passed to us somehow! This is slow!
6745   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6746   if (!mTextRun) return;
6747 
6748   PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
6749 
6750   // Trim trailing whitespace, unless we're painting a selection highlight,
6751   // which should include trailing spaces if present (bug 1146754).
6752   provider.InitializeForDisplay(!aIsSelected);
6753 
6754   const bool reversed = mTextRun->IsInlineReversed();
6755   const bool verticalRun = mTextRun->IsVertical();
6756   WritingMode wm = GetWritingMode();
6757   const float frameWidth = GetSize().width;
6758   const float frameHeight = GetSize().height;
6759   gfx::Point textBaselinePt;
6760   if (verticalRun) {
6761     if (wm.IsVerticalLR()) {
6762       textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
6763           this, aParams.context, nscoord(aParams.framePt.x), mAscent);
6764     } else {
6765       textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
6766           this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
6767           -mAscent);
6768     }
6769     textBaselinePt.y =
6770         reversed ? aParams.framePt.y + frameHeight : aParams.framePt.y;
6771   } else {
6772     textBaselinePt = gfx::Point(
6773         reversed ? aParams.framePt.x + frameWidth : aParams.framePt.x,
6774         nsLayoutUtils::GetSnappedBaselineY(this, aParams.context,
6775                                            aParams.framePt.y, mAscent));
6776   }
6777   Range range = ComputeTransformedRange(provider);
6778   uint32_t startOffset = range.start;
6779   uint32_t maxLength = range.Length();
6780   nscoord snappedStartEdge, snappedEndEdge;
6781   if (!MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
6782                               &startOffset, &maxLength, &snappedStartEdge,
6783                               &snappedEndEdge)) {
6784     return;
6785   }
6786   if (verticalRun) {
6787     textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
6788   } else {
6789     textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
6790   }
6791   nsDisplayText::ClipEdges clipEdges(this, aToReferenceFrame, snappedStartEdge,
6792                                      snappedEndEdge);
6793   nsTextPaintStyle textPaintStyle(this);
6794   textPaintStyle.SetResolveColors(!aParams.callbacks);
6795 
6796   // Fork off to the (slower) paint-with-selection path if necessary.
6797   if (aIsSelected) {
6798     MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
6799     gfxSkipCharsIterator tmp(provider.GetStart());
6800     Range contentRange(
6801         uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
6802         uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
6803     PaintTextSelectionParams params(aParams);
6804     params.textBaselinePt = textBaselinePt;
6805     params.provider = &provider;
6806     params.contentRange = contentRange;
6807     params.textPaintStyle = &textPaintStyle;
6808     params.glyphRange = range;
6809     if (PaintTextWithSelection(params, clipEdges)) {
6810       return;
6811     }
6812   }
6813 
6814   nscolor foregroundColor = aParams.IsGenerateTextMask()
6815                                 ? NS_RGBA(0, 0, 0, 255)
6816                                 : textPaintStyle.GetTextColor();
6817   if (aOpacity != 1.0f) {
6818     gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(foregroundColor);
6819     gfxColor.a *= aOpacity;
6820     foregroundColor = gfxColor.ToABGR();
6821   }
6822 
6823   nscolor textStrokeColor = aParams.IsGenerateTextMask()
6824                                 ? NS_RGBA(0, 0, 0, 255)
6825                                 : textPaintStyle.GetWebkitTextStrokeColor();
6826   if (aOpacity != 1.0f) {
6827     gfx::sRGBColor gfxColor = gfx::sRGBColor::FromABGR(textStrokeColor);
6828     gfxColor.a *= aOpacity;
6829     textStrokeColor = gfxColor.ToABGR();
6830   }
6831 
6832   range = Range(startOffset, startOffset + maxLength);
6833   if (!aParams.callbacks && aParams.IsPaintText()) {
6834     const nsStyleText* textStyle = StyleText();
6835     PaintShadowParams shadowParams(aParams);
6836     shadowParams.range = range;
6837     shadowParams.textBaselinePt = textBaselinePt;
6838     shadowParams.leftSideOffset = snappedStartEdge;
6839     shadowParams.provider = &provider;
6840     shadowParams.foregroundColor = foregroundColor;
6841     shadowParams.clipEdges = &clipEdges;
6842     PaintShadows(textStyle->mTextShadow.AsSpan(), shadowParams);
6843   }
6844 
6845   gfxFloat advanceWidth;
6846   DrawTextParams params(aParams.context);
6847   params.dirtyRect = aParams.dirtyRect;
6848   params.framePt = aParams.framePt;
6849   params.provider = &provider;
6850   params.advanceWidth = &advanceWidth;
6851   params.textStyle = &textPaintStyle;
6852   params.textColor = foregroundColor;
6853   params.textStrokeColor = textStrokeColor;
6854   params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
6855   params.clipEdges = &clipEdges;
6856   params.drawSoftHyphen = HasAnyStateBits(TEXT_HYPHEN_BREAK);
6857   params.contextPaint = aParams.contextPaint;
6858   params.callbacks = aParams.callbacks;
6859   params.glyphRange = range;
6860   DrawText(range, textBaselinePt, params);
6861 }
6862 
DrawTextRun(const gfxTextRun * aTextRun,const gfx::Point & aTextBaselinePt,gfxTextRun::Range aRange,const nsTextFrame::DrawTextRunParams & aParams,nsTextFrame * aFrame)6863 static void DrawTextRun(const gfxTextRun* aTextRun,
6864                         const gfx::Point& aTextBaselinePt,
6865                         gfxTextRun::Range aRange,
6866                         const nsTextFrame::DrawTextRunParams& aParams,
6867                         nsTextFrame* aFrame) {
6868   gfxTextRun::DrawParams params(aParams.context);
6869   params.provider = aParams.provider;
6870   params.advanceWidth = aParams.advanceWidth;
6871   params.contextPaint = aParams.contextPaint;
6872   params.callbacks = aParams.callbacks;
6873   if (aParams.callbacks) {
6874     aParams.callbacks->NotifyBeforeText(aParams.textColor);
6875     params.drawMode = DrawMode::GLYPH_PATH;
6876     aTextRun->Draw(aRange, aTextBaselinePt, params);
6877     aParams.callbacks->NotifyAfterText();
6878   } else {
6879     auto* textDrawer = aParams.context->GetTextDrawer();
6880     if (NS_GET_A(aParams.textColor) != 0 || textDrawer ||
6881         aParams.textStrokeWidth == 0.0f) {
6882       aParams.context->SetColor(sRGBColor::FromABGR(aParams.textColor));
6883     } else {
6884       params.drawMode = DrawMode::GLYPH_STROKE;
6885     }
6886 
6887     if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
6888         aParams.textStrokeWidth != 0.0f) {
6889       if (textDrawer) {
6890         textDrawer->FoundUnsupportedFeature();
6891         return;
6892       }
6893       params.drawMode |= DrawMode::GLYPH_STROKE;
6894 
6895       // Check the paint-order property; if we find stroke before fill,
6896       // then change mode to GLYPH_STROKE_UNDERNEATH.
6897       uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
6898       while (paintOrder) {
6899         auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
6900         switch (component) {
6901           case StylePaintOrder::Fill:
6902             // Just break the loop, no need to check further
6903             paintOrder = 0;
6904             break;
6905           case StylePaintOrder::Stroke:
6906             params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
6907             paintOrder = 0;
6908             break;
6909           default:
6910             MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
6911           case StylePaintOrder::Markers:
6912           case StylePaintOrder::Normal:
6913             break;
6914         }
6915         paintOrder >>= kPaintOrderShift;
6916       }
6917 
6918       // Use ROUND joins as they are less likely to produce ugly artifacts
6919       // when stroking glyphs with sharp angles (see bug 1546985).
6920       StrokeOptions strokeOpts(aParams.textStrokeWidth, JoinStyle::ROUND);
6921       params.textStrokeColor = aParams.textStrokeColor;
6922       params.strokeOpts = &strokeOpts;
6923       aTextRun->Draw(aRange, aTextBaselinePt, params);
6924     } else {
6925       aTextRun->Draw(aRange, aTextBaselinePt, params);
6926     }
6927   }
6928 }
6929 
DrawTextRun(Range aRange,const gfx::Point & aTextBaselinePt,const DrawTextRunParams & aParams)6930 void nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
6931                               const DrawTextRunParams& aParams) {
6932   MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
6933 
6934   ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this);
6935 
6936   if (aParams.drawSoftHyphen) {
6937     // Don't use ctx as the context, because we need a reference context here,
6938     // ctx may be transformed.
6939     RefPtr<gfxTextRun> hyphenTextRun = GetHyphenTextRun(this, nullptr);
6940     if (hyphenTextRun) {
6941       // For right-to-left text runs, the soft-hyphen is positioned at the left
6942       // of the text, minus its own width
6943       float hyphenBaselineX =
6944           aTextBaselinePt.x +
6945           mTextRun->GetDirection() * (*aParams.advanceWidth) -
6946           (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth() : 0);
6947       DrawTextRunParams params = aParams;
6948       params.provider = nullptr;
6949       params.advanceWidth = nullptr;
6950       ::DrawTextRun(hyphenTextRun.get(),
6951                     gfx::Point(hyphenBaselineX, aTextBaselinePt.y),
6952                     Range(hyphenTextRun.get()), params, this);
6953     }
6954   }
6955 }
6956 
DrawTextRunAndDecorations(Range aRange,const gfx::Point & aTextBaselinePt,const DrawTextParams & aParams,const TextDecorations & aDecorations)6957 void nsTextFrame::DrawTextRunAndDecorations(
6958     Range aRange, const gfx::Point& aTextBaselinePt,
6959     const DrawTextParams& aParams, const TextDecorations& aDecorations) {
6960   const gfxFloat app = aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
6961   // Writing mode of parent frame is used because the text frame may
6962   // be orthogonal to its parent when text-combine-upright is used or
6963   // its parent has "display: contents", and in those cases, we want
6964   // to draw the decoration lines according to parents' direction
6965   // rather than ours.
6966   const WritingMode wm = GetParent()->GetWritingMode();
6967   bool verticalDec = wm.IsVertical();
6968   bool verticalRun = mTextRun->IsVertical();
6969   // If the text run and the decoration is orthogonal, we choose the
6970   // metrics for decoration so that decoration line won't be broken.
6971   bool useVerticalMetrics = verticalDec != verticalRun
6972                                 ? verticalDec
6973                                 : verticalRun && mTextRun->UseCenterBaseline();
6974 
6975   // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
6976   nscoord x = NSToCoordRound(aParams.framePt.x);
6977   nscoord y = NSToCoordRound(aParams.framePt.y);
6978 
6979   // 'measure' here is textrun-relative, so for a horizontal run it's the
6980   // width, while for a vertical run it's the height of the decoration
6981   const nsSize frameSize = GetSize();
6982   nscoord measure = verticalDec ? frameSize.height : frameSize.width;
6983 
6984   if (verticalDec) {
6985     aParams.clipEdges->Intersect(&y, &measure);
6986   } else {
6987     aParams.clipEdges->Intersect(&x, &measure);
6988   }
6989 
6990   // decSize is a textrun-relative size, so its 'width' field is actually
6991   // the run-relative measure, and 'height' will be the line thickness
6992   gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
6993   // The starting edge of the frame in block direction
6994   gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
6995 
6996   // In vertical-rl mode, block coordinates are measured from the
6997   // right, so we need to adjust here.
6998   if (wm.IsVerticalRL()) {
6999     frameBStart += frameSize.width;
7000     ascent = -ascent;
7001   }
7002 
7003   nscoord inflationMinFontSize = nsLayoutUtils::InflationMinFontSizeFor(this);
7004 
7005   PaintDecorationLineParams params;
7006   params.context = aParams.context;
7007   params.dirtyRect = aParams.dirtyRect;
7008   params.overrideColor = aParams.decorationOverrideColor;
7009   params.callbacks = aParams.callbacks;
7010   params.glyphRange = aParams.glyphRange;
7011   params.provider = aParams.provider;
7012   // pt is the physical point where the decoration is to be drawn,
7013   // relative to the frame; one of its coordinates will be updated below.
7014   params.pt = Point(x / app, y / app);
7015   Float& bCoord = verticalDec ? params.pt.x : params.pt.y;
7016   params.lineSize = Size(measure / app, 0);
7017   params.ascent = ascent;
7018   params.vertical = verticalDec;
7019   params.sidewaysLeft = mTextRun->IsSidewaysLeft();
7020 
7021   // The matrix of the context may have been altered for text-combine-
7022   // upright. However, we want to draw decoration lines unscaled, thus
7023   // we need to revert the scaling here.
7024   gfxContextMatrixAutoSaveRestore scaledRestorer;
7025   if (Style()->IsTextCombined()) {
7026     float scaleFactor = GetTextCombineScaleFactor(this);
7027     if (scaleFactor != 1.0f) {
7028       scaledRestorer.SetContext(aParams.context);
7029       gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
7030       gfxPoint pt(x / app, y / app);
7031       unscaled.PreTranslate(pt)
7032           .PreScale(1.0f / scaleFactor, 1.0f)
7033           .PreTranslate(-pt);
7034       aParams.context->SetMatrixDouble(unscaled);
7035     }
7036   }
7037 
7038   typedef gfxFont::Metrics Metrics;
7039   auto paintDecorationLine = [&](const LineDecoration& dec,
7040                                  gfxFloat Metrics::*lineSize,
7041                                  StyleTextDecorationLine lineType) {
7042     if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
7043       return;
7044     }
7045 
7046     float inflation =
7047         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
7048     const Metrics metrics = GetFirstFontMetrics(
7049         GetFontGroupForFrame(dec.mFrame, inflation), useVerticalMetrics);
7050 
7051     bCoord = (frameBStart - dec.mBaselineOffset) / app;
7052 
7053     params.color = dec.mColor;
7054     params.baselineOffset = dec.mBaselineOffset / app;
7055     params.defaultLineThickness = metrics.*lineSize;
7056     params.lineSize.height = ComputeDecorationLineThickness(
7057         dec.mTextDecorationThickness, params.defaultLineThickness, metrics, app,
7058         dec.mFrame);
7059 
7060     bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
7061     params.offset = ComputeDecorationLineOffset(
7062         lineType, dec.mTextUnderlinePosition, dec.mTextUnderlineOffset, metrics,
7063         app, dec.mFrame, wm.IsCentralBaseline(), swapUnderline);
7064 
7065     params.style = dec.mStyle;
7066     PaintDecorationLine(params);
7067   };
7068 
7069   // We create a clip region in order to draw the decoration lines only in the
7070   // range of the text. Restricting the draw area prevents the decoration lines
7071   // to be drawn multiple times when a part of the text is selected.
7072 
7073   // We skip clipping for the following cases:
7074   // - drawing the whole text
7075   // - having different orientation of the text and the writing-mode, such as
7076   //   "text-combine-upright" (Bug 1408825)
7077   bool skipClipping =
7078       aRange.Length() == mTextRun->GetLength() || verticalDec != verticalRun;
7079 
7080   gfxRect clipRect;
7081   if (!skipClipping) {
7082     // Get the inline-size according to the specified range.
7083     gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
7084     nsRect visualRect = InkOverflowRect();
7085 
7086     const bool isInlineReversed = mTextRun->IsInlineReversed();
7087     if (verticalDec) {
7088       clipRect.x = aParams.framePt.x + visualRect.x;
7089       clipRect.y =
7090           isInlineReversed ? aTextBaselinePt.y - clipLength : aTextBaselinePt.y;
7091       clipRect.width = visualRect.width;
7092       clipRect.height = clipLength;
7093     } else {
7094       clipRect.x =
7095           isInlineReversed ? aTextBaselinePt.x - clipLength : aTextBaselinePt.x;
7096       clipRect.y = aParams.framePt.y + visualRect.y;
7097       clipRect.width = clipLength;
7098       clipRect.height = visualRect.height;
7099     }
7100 
7101     clipRect.Scale(1 / app);
7102     clipRect.Round();
7103     params.context->Clip(clipRect);
7104   }
7105 
7106   // Underlines
7107   params.decoration = StyleTextDecorationLine::UNDERLINE;
7108   for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
7109     paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
7110   }
7111 
7112   // Overlines
7113   params.decoration = StyleTextDecorationLine::OVERLINE;
7114   for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
7115     paintDecorationLine(dec, &Metrics::underlineSize, params.decoration);
7116   }
7117 
7118   // Some glyphs and emphasis marks may extend outside the region, so we reset
7119   // the clip region here. For an example, italic glyphs.
7120   if (!skipClipping) {
7121     params.context->PopClip();
7122   }
7123 
7124   {
7125     gfxContextMatrixAutoSaveRestore unscaledRestorer;
7126     if (scaledRestorer.HasMatrix()) {
7127       unscaledRestorer.SetContext(aParams.context);
7128       aParams.context->SetMatrix(scaledRestorer.Matrix());
7129     }
7130 
7131     // CSS 2.1 mandates that text be painted after over/underlines,
7132     // and *then* line-throughs
7133     DrawTextRun(aRange, aTextBaselinePt, aParams);
7134   }
7135 
7136   // Emphasis marks
7137   DrawEmphasisMarks(aParams.context, wm, aTextBaselinePt, aParams.framePt,
7138                     aRange, aParams.decorationOverrideColor, aParams.provider);
7139 
7140   // Re-apply the clip region when the line-through is being drawn.
7141   if (!skipClipping) {
7142     params.context->Clip(clipRect);
7143   }
7144 
7145   // Line-throughs
7146   params.decoration = StyleTextDecorationLine::LINE_THROUGH;
7147   for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
7148     paintDecorationLine(dec, &Metrics::strikeoutSize, params.decoration);
7149   }
7150 
7151   if (!skipClipping) {
7152     params.context->PopClip();
7153   }
7154 }
7155 
DrawText(Range aRange,const gfx::Point & aTextBaselinePt,const DrawTextParams & aParams)7156 void nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
7157                            const DrawTextParams& aParams) {
7158   TextDecorations decorations;
7159   GetTextDecorations(aParams.textStyle->PresContext(),
7160                      aParams.callbacks ? eUnresolvedColors : eResolvedColors,
7161                      decorations);
7162 
7163   // Hide text decorations if we're currently hiding @font-face fallback text
7164   const bool drawDecorations =
7165       !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
7166       (decorations.HasDecorationLines() ||
7167        StyleText()->HasEffectiveTextEmphasis());
7168   if (drawDecorations) {
7169     DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations);
7170   } else {
7171     DrawTextRun(aRange, aTextBaselinePt, aParams);
7172   }
7173 
7174   if (auto* textDrawer = aParams.context->GetTextDrawer()) {
7175     textDrawer->TerminateShadows();
7176   }
7177 }
7178 
NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds,nsRect)7179 NS_DECLARE_FRAME_PROPERTY_DELETABLE(WebRenderTextBounds, nsRect)
7180 
7181 nsRect nsTextFrame::WebRenderBounds() {
7182   nsRect* cachedBounds = GetProperty(WebRenderTextBounds());
7183   if (!cachedBounds) {
7184     OverflowAreas overflowAreas;
7185     ComputeCustomOverflowInternal(overflowAreas, false);
7186     cachedBounds = new nsRect();
7187     *cachedBounds = overflowAreas.InkOverflow();
7188     SetProperty(WebRenderTextBounds(), cachedBounds);
7189   }
7190   return *cachedBounds;
7191 }
7192 
GetSelectionStatus(int16_t * aSelectionFlags)7193 int16_t nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) {
7194   // get the selection controller
7195   nsCOMPtr<nsISelectionController> selectionController;
7196   nsresult rv = GetSelectionController(PresContext(),
7197                                        getter_AddRefs(selectionController));
7198   if (NS_FAILED(rv) || !selectionController)
7199     return nsISelectionController::SELECTION_OFF;
7200 
7201   selectionController->GetSelectionFlags(aSelectionFlags);
7202 
7203   int16_t selectionValue;
7204   selectionController->GetDisplaySelection(&selectionValue);
7205 
7206   return selectionValue;
7207 }
7208 
7209 /**
7210  * Compute the longest prefix of text whose width is <= aWidth. Return
7211  * the length of the prefix. Also returns the width of the prefix in aFitWidth.
7212  */
CountCharsFit(const gfxTextRun * aTextRun,gfxTextRun::Range aRange,gfxFloat aWidth,nsTextFrame::PropertyProvider * aProvider,gfxFloat * aFitWidth)7213 static uint32_t CountCharsFit(const gfxTextRun* aTextRun,
7214                               gfxTextRun::Range aRange, gfxFloat aWidth,
7215                               nsTextFrame::PropertyProvider* aProvider,
7216                               gfxFloat* aFitWidth) {
7217   uint32_t last = 0;
7218   gfxFloat width = 0;
7219   for (uint32_t i = 1; i <= aRange.Length(); ++i) {
7220     if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
7221       gfxTextRun::Range range(aRange.start + last, aRange.start + i);
7222       gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
7223       if (nextWidth > aWidth) break;
7224       last = i;
7225       width = nextWidth;
7226     }
7227   }
7228   *aFitWidth = width;
7229   return last;
7230 }
7231 
CalcContentOffsetsFromFramePoint(const nsPoint & aPoint)7232 nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint(
7233     const nsPoint& aPoint) {
7234   return GetCharacterOffsetAtFramePointInternal(aPoint, true);
7235 }
7236 
GetCharacterOffsetAtFramePoint(const nsPoint & aPoint)7237 nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePoint(
7238     const nsPoint& aPoint) {
7239   return GetCharacterOffsetAtFramePointInternal(aPoint, false);
7240 }
7241 
GetCharacterOffsetAtFramePointInternal(const nsPoint & aPoint,bool aForInsertionPoint)7242 nsIFrame::ContentOffsets nsTextFrame::GetCharacterOffsetAtFramePointInternal(
7243     const nsPoint& aPoint, bool aForInsertionPoint) {
7244   ContentOffsets offsets;
7245 
7246   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7247   if (!mTextRun) return offsets;
7248 
7249   PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
7250   // Trim leading but not trailing whitespace if possible
7251   provider.InitializeForDisplay(false);
7252   gfxFloat width =
7253       mTextRun->IsVertical()
7254           ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
7255           : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
7256   if (Style()->IsTextCombined()) {
7257     width /= GetTextCombineScaleFactor(this);
7258   }
7259   gfxFloat fitWidth;
7260   Range skippedRange = ComputeTransformedRange(provider);
7261 
7262   uint32_t charsFit =
7263       CountCharsFit(mTextRun, skippedRange, width, &provider, &fitWidth);
7264 
7265   int32_t selectedOffset;
7266   if (charsFit < skippedRange.Length()) {
7267     // charsFit characters fitted, but no more could fit. See if we're
7268     // more than halfway through the cluster.. If we are, choose the next
7269     // cluster.
7270     gfxSkipCharsIterator extraCluster(provider.GetStart());
7271     extraCluster.AdvanceSkipped(charsFit);
7272 
7273     bool allowSplitLigature = true;  // Allow selection of partial ligature...
7274 
7275     // ...but don't let selection/insertion-point split two Regional Indicator
7276     // chars that are ligated in the textrun to form a single flag symbol.
7277     uint32_t offs = extraCluster.GetOriginalOffset();
7278     const nsTextFragment* frag = TextFragment();
7279     if (frag->IsHighSurrogateFollowedByLowSurrogateAt(offs) &&
7280         gfxFontUtils::IsRegionalIndicator(frag->ScalarValueAt(offs))) {
7281       allowSplitLigature = false;
7282       if (extraCluster.GetSkippedOffset() > 1 &&
7283           !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
7284         // CountCharsFit() left us in the middle of the flag; back up over the
7285         // first character of the ligature, and adjust fitWidth accordingly.
7286         extraCluster.AdvanceSkipped(-2);  // it's a surrogate pair: 2 code units
7287         fitWidth -= mTextRun->GetAdvanceWidth(
7288             Range(extraCluster.GetSkippedOffset(),
7289                   extraCluster.GetSkippedOffset() + 2),
7290             &provider);
7291       }
7292     }
7293 
7294     gfxSkipCharsIterator extraClusterLastChar(extraCluster);
7295     FindClusterEnd(
7296         mTextRun,
7297         provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
7298         &extraClusterLastChar, allowSplitLigature);
7299     PropertyProvider::Spacing spacing;
7300     Range extraClusterRange(extraCluster.GetSkippedOffset(),
7301                             extraClusterLastChar.GetSkippedOffset() + 1);
7302     gfxFloat charWidth =
7303         mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
7304     charWidth -= spacing.mBefore + spacing.mAfter;
7305     selectedOffset = !aForInsertionPoint ||
7306                              width <= fitWidth + spacing.mBefore + charWidth / 2
7307                          ? extraCluster.GetOriginalOffset()
7308                          : extraClusterLastChar.GetOriginalOffset() + 1;
7309   } else {
7310     // All characters fitted, we're at (or beyond) the end of the text.
7311     // XXX This could be some pathological situation where negative spacing
7312     // caused characters to move backwards. We can't really handle that
7313     // in the current frame system because frames can't have negative
7314     // intrinsic widths.
7315     selectedOffset =
7316         provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
7317     // If we're at the end of a preformatted line which has a terminating
7318     // linefeed, we want to reduce the offset by one to make sure that the
7319     // selection is placed before the linefeed character.
7320     if (HasSignificantTerminalNewline()) {
7321       --selectedOffset;
7322     }
7323   }
7324 
7325   offsets.content = GetContent();
7326   offsets.offset = offsets.secondaryOffset = selectedOffset;
7327   offsets.associate = mContentOffset == offsets.offset ? CARET_ASSOCIATE_AFTER
7328                                                        : CARET_ASSOCIATE_BEFORE;
7329   return offsets;
7330 }
7331 
CombineSelectionUnderlineRect(nsPresContext * aPresContext,nsRect & aRect)7332 bool nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
7333                                                 nsRect& aRect) {
7334   if (aRect.IsEmpty()) return false;
7335 
7336   nsRect givenRect = aRect;
7337 
7338   gfxFontGroup* fontGroup = GetInflatedFontGroupForFrame(this);
7339   gfxFont* firstFont = fontGroup->GetFirstValidFont();
7340   WritingMode wm = GetWritingMode();
7341   bool verticalRun = wm.IsVertical();
7342   bool useVerticalMetrics = verticalRun && !wm.IsSideways();
7343   const gfxFont::Metrics& metrics =
7344       firstFont->GetMetrics(useVerticalMetrics ? nsFontMetrics::eVertical
7345                                                : nsFontMetrics::eHorizontal);
7346 
7347   nsCSSRendering::DecorationRectParams params;
7348   params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
7349 
7350   params.offset = fontGroup->GetUnderlineOffset();
7351 
7352   TextDecorations textDecs;
7353   GetTextDecorations(aPresContext, eResolvedColors, textDecs);
7354 
7355   params.descentLimit =
7356       ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
7357   params.vertical = verticalRun;
7358 
7359   EnsureTextRun(nsTextFrame::eInflated);
7360   params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
7361 
7362   UniquePtr<SelectionDetails> details = GetSelectionDetails();
7363   for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
7364     if (sd->mStart == sd->mEnd ||
7365         sd->mSelectionType == SelectionType::eInvalid ||
7366         !(ToSelectionTypeMask(sd->mSelectionType) &
7367           kSelectionTypesWithDecorations) ||
7368         // URL strikeout does not use underline.
7369         sd->mSelectionType == SelectionType::eURLStrikeout) {
7370       continue;
7371     }
7372 
7373     float relativeSize;
7374     int32_t index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
7375         sd->mSelectionType);
7376     if (sd->mSelectionType == SelectionType::eSpellCheck) {
7377       if (!nsTextPaintStyle::GetSelectionUnderline(
7378               this, index, nullptr, &relativeSize, &params.style)) {
7379         continue;
7380       }
7381     } else {
7382       // IME selections
7383       TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
7384       if (rangeStyle.IsDefined()) {
7385         if (!rangeStyle.IsLineStyleDefined() ||
7386             rangeStyle.mLineStyle == TextRangeStyle::LineStyle::None) {
7387           continue;
7388         }
7389         params.style = ToStyleLineStyle(rangeStyle);
7390         relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
7391       } else if (!nsTextPaintStyle::GetSelectionUnderline(
7392                      this, index, nullptr, &relativeSize, &params.style)) {
7393         continue;
7394       }
7395     }
7396     nsRect decorationArea;
7397 
7398     const auto& decThickness = StyleTextReset()->mTextDecorationThickness;
7399     params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
7400     params.defaultLineThickness = ComputeSelectionUnderlineHeight(
7401         aPresContext, metrics, sd->mSelectionType);
7402 
7403     params.lineSize.height = ComputeDecorationLineThickness(
7404         decThickness, params.defaultLineThickness, metrics,
7405         aPresContext->AppUnitsPerDevPixel(), this);
7406 
7407     bool swapUnderline = wm.IsCentralBaseline() && IsUnderlineRight(*Style());
7408     const auto* styleText = StyleText();
7409     params.offset = ComputeDecorationLineOffset(
7410         textDecs.HasUnderline() ? StyleTextDecorationLine::UNDERLINE
7411                                 : StyleTextDecorationLine::OVERLINE,
7412         styleText->mTextUnderlinePosition, styleText->mTextUnderlineOffset,
7413         metrics, aPresContext->AppUnitsPerDevPixel(), this,
7414         wm.IsCentralBaseline(), swapUnderline);
7415 
7416     relativeSize = std::max(relativeSize, 1.0f);
7417     params.lineSize.height *= relativeSize;
7418     params.defaultLineThickness *= relativeSize;
7419     decorationArea =
7420         nsCSSRendering::GetTextDecorationRect(aPresContext, params);
7421     aRect.UnionRect(aRect, decorationArea);
7422   }
7423 
7424   return !aRect.IsEmpty() && !givenRect.Contains(aRect);
7425 }
7426 
IsFrameSelected() const7427 bool nsTextFrame::IsFrameSelected() const {
7428   NS_ASSERTION(!GetContent() || GetContent()->IsMaybeSelected(),
7429                "use the public IsSelected() instead");
7430   if (mIsSelected == nsTextFrame::SelectionState::Unknown) {
7431     const bool isSelected =
7432         GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
7433     mIsSelected = isSelected ? nsTextFrame::SelectionState::Selected
7434                              : nsTextFrame::SelectionState::NotSelected;
7435   } else {
7436 #ifdef DEBUG
7437     // Assert that the selection caching works.
7438     const bool isReallySelected =
7439         GetContent()->IsSelected(GetContentOffset(), GetContentEnd());
7440     NS_ASSERTION((mIsSelected == nsTextFrame::SelectionState::Selected) ==
7441                      isReallySelected,
7442                  "Should have called InvalidateSelectionState()");
7443 #endif
7444   }
7445 
7446   return mIsSelected == nsTextFrame::SelectionState::Selected;
7447 }
7448 
SelectionStateChanged(uint32_t aStart,uint32_t aEnd,bool aSelected,SelectionType aSelectionType)7449 void nsTextFrame::SelectionStateChanged(uint32_t aStart, uint32_t aEnd,
7450                                         bool aSelected,
7451                                         SelectionType aSelectionType) {
7452   NS_ASSERTION(!GetPrevContinuation(),
7453                "Should only be called for primary frame");
7454   DEBUG_VERIFY_NOT_DIRTY(mState);
7455 
7456   InvalidateSelectionState();
7457 
7458   // Selection is collapsed, which can't affect text frame rendering
7459   if (aStart == aEnd) return;
7460 
7461   nsTextFrame* f = this;
7462   while (f && f->GetContentEnd() <= int32_t(aStart)) {
7463     f = f->GetNextContinuation();
7464   }
7465 
7466   nsPresContext* presContext = PresContext();
7467   while (f && f->GetContentOffset() < int32_t(aEnd)) {
7468     // We may need to reflow to recompute the overflow area for
7469     // spellchecking or IME underline if their underline is thicker than
7470     // the normal decoration line.
7471     if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) {
7472       bool didHaveOverflowingSelection =
7473           f->HasAnyStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
7474       nsRect r(nsPoint(0, 0), GetSize());
7475       if (didHaveOverflowingSelection ||
7476           (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
7477         presContext->PresShell()->FrameNeedsReflow(
7478             f, IntrinsicDirty::StyleChange, NS_FRAME_IS_DIRTY);
7479       }
7480     }
7481     // Selection might change anything. Invalidate the overflow area.
7482     f->InvalidateFrame();
7483 
7484     f = f->GetNextContinuation();
7485   }
7486 }
7487 
UpdateIteratorFromOffset(const PropertyProvider & aProperties,int32_t & aInOffset,gfxSkipCharsIterator & aIter)7488 void nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
7489                                            int32_t& aInOffset,
7490                                            gfxSkipCharsIterator& aIter) {
7491   if (aInOffset < GetContentOffset()) {
7492     NS_WARNING("offset before this frame's content");
7493     aInOffset = GetContentOffset();
7494   } else if (aInOffset > GetContentEnd()) {
7495     NS_WARNING("offset after this frame's content");
7496     aInOffset = GetContentEnd();
7497   }
7498 
7499   int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
7500   int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
7501   aInOffset = std::max(aInOffset, trimmedOffset);
7502   aInOffset = std::min(aInOffset, trimmedEnd);
7503 
7504   aIter.SetOriginalOffset(aInOffset);
7505 
7506   if (aInOffset < trimmedEnd && !aIter.IsOriginalCharSkipped() &&
7507       !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
7508     // Called for non-cluster boundary
7509     FindClusterStart(mTextRun, trimmedOffset, &aIter);
7510   }
7511 }
7512 
GetPointFromIterator(const gfxSkipCharsIterator & aIter,PropertyProvider & aProperties)7513 nsPoint nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
7514                                           PropertyProvider& aProperties) {
7515   Range range(aProperties.GetStart().GetSkippedOffset(),
7516               aIter.GetSkippedOffset());
7517   gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
7518   nscoord iSize = NSToCoordCeilClamped(advance);
7519   nsPoint point;
7520 
7521   if (mTextRun->IsVertical()) {
7522     point.x = 0;
7523     if (mTextRun->IsInlineReversed()) {
7524       point.y = mRect.height - iSize;
7525     } else {
7526       point.y = iSize;
7527     }
7528   } else {
7529     point.y = 0;
7530     if (mTextRun->IsInlineReversed()) {
7531       point.x = mRect.width - iSize;
7532     } else {
7533       point.x = iSize;
7534     }
7535     if (Style()->IsTextCombined()) {
7536       point.x *= GetTextCombineScaleFactor(this);
7537     }
7538   }
7539   return point;
7540 }
7541 
GetPointFromOffset(int32_t inOffset,nsPoint * outPoint)7542 nsresult nsTextFrame::GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) {
7543   if (!outPoint) return NS_ERROR_NULL_POINTER;
7544 
7545   DEBUG_VERIFY_NOT_DIRTY(mState);
7546   if (mState & NS_FRAME_IS_DIRTY) return NS_ERROR_UNEXPECTED;
7547 
7548   if (GetContentLength() <= 0) {
7549     outPoint->x = 0;
7550     outPoint->y = 0;
7551     return NS_OK;
7552   }
7553 
7554   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7555   if (!mTextRun) return NS_ERROR_FAILURE;
7556 
7557   PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
7558   // Don't trim trailing whitespace, we want the caret to appear in the right
7559   // place if it's positioned there
7560   properties.InitializeForDisplay(false);
7561 
7562   UpdateIteratorFromOffset(properties, inOffset, iter);
7563 
7564   *outPoint = GetPointFromIterator(iter, properties);
7565 
7566   return NS_OK;
7567 }
7568 
GetCharacterRectsInRange(int32_t aInOffset,int32_t aLength,nsTArray<nsRect> & aRects)7569 nsresult nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
7570                                                int32_t aLength,
7571                                                nsTArray<nsRect>& aRects) {
7572   DEBUG_VERIFY_NOT_DIRTY(mState);
7573   if (mState & NS_FRAME_IS_DIRTY) {
7574     return NS_ERROR_UNEXPECTED;
7575   }
7576 
7577   if (GetContentLength() <= 0) {
7578     return NS_OK;
7579   }
7580 
7581   if (!mTextRun) {
7582     return NS_ERROR_FAILURE;
7583   }
7584 
7585   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7586   PropertyProvider properties(this, iter, nsTextFrame::eInflated, mFontMetrics);
7587   // Don't trim trailing whitespace, we want the caret to appear in the right
7588   // place if it's positioned there
7589   properties.InitializeForDisplay(false);
7590 
7591   UpdateIteratorFromOffset(properties, aInOffset, iter);
7592 
7593   const int32_t kContentEnd = GetContentEnd();
7594   const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
7595   while (aInOffset < kEndOffset) {
7596     if (!iter.IsOriginalCharSkipped() &&
7597         !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
7598       FindClusterStart(mTextRun,
7599                        properties.GetStart().GetOriginalOffset() +
7600                            properties.GetOriginalLength(),
7601                        &iter);
7602     }
7603 
7604     nsPoint point = GetPointFromIterator(iter, properties);
7605     nsRect rect;
7606     rect.x = point.x;
7607     rect.y = point.y;
7608 
7609     nscoord iSize = 0;
7610     if (aInOffset < kContentEnd) {
7611       gfxSkipCharsIterator nextIter(iter);
7612       nextIter.AdvanceOriginal(1);
7613       if (!nextIter.IsOriginalCharSkipped() &&
7614           !mTextRun->IsClusterStart(nextIter.GetSkippedOffset()) &&
7615           nextIter.GetOriginalOffset() < kContentEnd) {
7616         FindClusterEnd(mTextRun, kContentEnd, &nextIter);
7617       }
7618 
7619       gfxFloat advance = mTextRun->GetAdvanceWidth(
7620           Range(iter.GetSkippedOffset(), nextIter.GetSkippedOffset()),
7621           &properties);
7622       iSize = NSToCoordCeilClamped(advance);
7623     }
7624 
7625     if (mTextRun->IsVertical()) {
7626       rect.width = mRect.width;
7627       rect.height = iSize;
7628     } else {
7629       rect.width = iSize;
7630       rect.height = mRect.height;
7631 
7632       if (Style()->IsTextCombined()) {
7633         rect.width *= GetTextCombineScaleFactor(this);
7634       }
7635     }
7636     aRects.AppendElement(rect);
7637     aInOffset++;
7638     // Don't advance iter if we've reached the end
7639     if (aInOffset < kEndOffset) {
7640       iter.AdvanceOriginal(1);
7641     }
7642   }
7643 
7644   return NS_OK;
7645 }
7646 
GetChildFrameContainingOffset(int32_t aContentOffset,bool aHint,int32_t * aOutOffset,nsIFrame ** aOutFrame)7647 nsresult nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
7648                                                     bool aHint,
7649                                                     int32_t* aOutOffset,
7650                                                     nsIFrame** aOutFrame) {
7651   DEBUG_VERIFY_NOT_DIRTY(mState);
7652 #if 0  // XXXrbs disable due to bug 310227
7653   if (mState & NS_FRAME_IS_DIRTY)
7654     return NS_ERROR_UNEXPECTED;
7655 #endif
7656 
7657   NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
7658   NS_ASSERTION(aContentOffset >= 0,
7659                "Negative content offset, existing code was very broken!");
7660   nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
7661   if (this != primaryFrame) {
7662     // This call needs to happen on the primary frame
7663     return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
7664                                                        aOutOffset, aOutFrame);
7665   }
7666 
7667   nsTextFrame* f = this;
7668   int32_t offset = mContentOffset;
7669 
7670   // Try to look up the offset to frame property
7671   nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
7672 
7673   if (cachedFrame) {
7674     f = cachedFrame;
7675     offset = f->GetContentOffset();
7676 
7677     f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
7678   }
7679 
7680   if ((aContentOffset >= offset) && (aHint || aContentOffset != offset)) {
7681     while (true) {
7682       nsTextFrame* next = f->GetNextContinuation();
7683       if (!next || aContentOffset < next->GetContentOffset()) break;
7684       if (aContentOffset == next->GetContentOffset()) {
7685         if (aHint) {
7686           f = next;
7687           if (f->GetContentLength() == 0) {
7688             continue;  // use the last of the empty frames with this offset
7689           }
7690         }
7691         break;
7692       }
7693       f = next;
7694     }
7695   } else {
7696     while (true) {
7697       nsTextFrame* prev = f->GetPrevContinuation();
7698       if (!prev || aContentOffset > f->GetContentOffset()) break;
7699       if (aContentOffset == f->GetContentOffset()) {
7700         if (!aHint) {
7701           f = prev;
7702           if (f->GetContentLength() == 0) {
7703             continue;  // use the first of the empty frames with this offset
7704           }
7705         }
7706         break;
7707       }
7708       f = prev;
7709     }
7710   }
7711 
7712   *aOutOffset = aContentOffset - f->GetContentOffset();
7713   *aOutFrame = f;
7714 
7715   // cache the frame we found
7716   SetProperty(OffsetToFrameProperty(), f);
7717   f->AddStateBits(TEXT_IN_OFFSET_CACHE);
7718 
7719   return NS_OK;
7720 }
7721 
PeekOffsetNoAmount(bool aForward,int32_t * aOffset)7722 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetNoAmount(bool aForward,
7723                                                             int32_t* aOffset) {
7724   NS_ASSERTION(aOffset && *aOffset <= GetContentLength(),
7725                "aOffset out of range");
7726 
7727   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7728   if (!mTextRun) return CONTINUE_EMPTY;
7729 
7730   TrimmedOffsets trimmed = GetTrimmedOffsets(TextFragment());
7731   // Check whether there are nonskipped characters in the trimmmed range
7732   return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
7733           iter.ConvertOriginalToSkipped(trimmed.mStart))
7734              ? FOUND
7735              : CONTINUE;
7736 }
7737 
7738 /**
7739  * This class iterates through the clusters before or after the given
7740  * aPosition (which is a content offset). You can test each cluster
7741  * to see if it's whitespace (as far as selection/caret movement is concerned),
7742  * or punctuation, or if there is a word break before the cluster. ("Before"
7743  * is interpreted according to aDirection, so if aDirection is -1, "before"
7744  * means actually *after* the cluster content.)
7745  */
7746 class MOZ_STACK_CLASS ClusterIterator {
7747  public:
7748   ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
7749                   int32_t aDirection, nsString& aContext,
7750                   bool aTrimSpaces = true);
7751 
7752   bool NextCluster();
7753   bool IsInlineWhitespace() const;
7754   bool IsNewline() const;
7755   bool IsPunctuation() const;
HaveWordBreakBefore() const7756   bool HaveWordBreakBefore() const { return mHaveWordBreak; }
7757 
7758   // Get the charIndex that corresponds to the "before" side of the current
7759   // character, according to the direction of iteration: so for a forward
7760   // iterator, this is simply mCharIndex, while for a reverse iterator it will
7761   // be mCharIndex + <number of code units in the character>.
GetBeforeOffset() const7762   int32_t GetBeforeOffset() const {
7763     MOZ_ASSERT(mCharIndex >= 0);
7764     return mDirection < 0 ? GetAfterInternal() : mCharIndex;
7765   }
7766   // Get the charIndex that corresponds to the "before" side of the current
7767   // character, according to the direction of iteration: the opposite side
7768   // to what GetBeforeOffset returns.
GetAfterOffset() const7769   int32_t GetAfterOffset() const {
7770     MOZ_ASSERT(mCharIndex >= 0);
7771     return mDirection > 0 ? GetAfterInternal() : mCharIndex;
7772   }
7773 
7774  private:
7775   // Helper for Get{After,Before}Offset; returns the charIndex after the
7776   // current position in the text, accounting for surrogate pairs.
7777   int32_t GetAfterInternal() const;
7778 
7779   gfxSkipCharsIterator mIterator;
7780   // Usually, mFrag is pointer to `dom::CharacterData::mText`.  However, if
7781   // we're in a password field, this points `mMaskedFrag`.
7782   const nsTextFragment* mFrag;
7783   // If we're in a password field, this is initialized with mask characters.
7784   nsTextFragment mMaskedFrag;
7785   nsTextFrame* mTextFrame;
7786   int32_t mDirection;  // +1 or -1, or 0 to indicate failure
7787   int32_t mCharIndex;
7788   nsTextFrame::TrimmedOffsets mTrimmed;
7789   nsTArray<bool> mWordBreaks;
7790   bool mHaveWordBreak;
7791 };
7792 
IsAcceptableCaretPosition(const gfxSkipCharsIterator & aIter,bool aRespectClusters,const gfxTextRun * aTextRun,nsTextFrame * aFrame)7793 static bool IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
7794                                       bool aRespectClusters,
7795                                       const gfxTextRun* aTextRun,
7796                                       nsTextFrame* aFrame) {
7797   if (aIter.IsOriginalCharSkipped()) return false;
7798   uint32_t index = aIter.GetSkippedOffset();
7799   if (aRespectClusters && !aTextRun->IsClusterStart(index)) return false;
7800   if (index > 0) {
7801     // Check whether the proposed position is in between the two halves of a
7802     // surrogate pair, before a Variation Selector character, or within a
7803     // ligated emoji sequence; if so, this is not a valid character boundary.
7804     // (In the case where we are respecting clusters, we won't actually get
7805     // this far because the low surrogate is also marked as non-clusterStart
7806     // so we'll return FALSE above.)
7807     uint32_t offs = aIter.GetOriginalOffset();
7808     const nsTextFragment* frag = aFrame->TextFragment();
7809     uint32_t ch = frag->CharAt(offs);
7810 
7811     if (gfxFontUtils::IsVarSelector(ch) ||
7812         frag->IsLowSurrogateFollowingHighSurrogateAt(offs) ||
7813         (!aTextRun->IsLigatureGroupStart(index) &&
7814          (unicode::GetEmojiPresentation(ch) == unicode::EmojiDefault ||
7815           (unicode::GetEmojiPresentation(ch) == unicode::TextDefault &&
7816            offs + 1 < frag->GetLength() &&
7817            frag->CharAt(offs + 1) == gfxFontUtils::kUnicodeVS16)))) {
7818       return false;
7819     }
7820 
7821     // If the proposed position is before a high surrogate, we need to decode
7822     // the surrogate pair (if valid) and check the resulting character.
7823     if (NS_IS_HIGH_SURROGATE(ch)) {
7824       if (char32_t ucs4 = frag->ScalarValueAt(offs)) {
7825         // If the character is a (Plane-14) variation selector,
7826         // or an emoji character that is ligated with the previous
7827         // character (i.e. part of a Regional-Indicator flag pair,
7828         // or an emoji-ZWJ sequence), this is not a valid boundary.
7829         if (gfxFontUtils::IsVarSelector(ucs4) ||
7830             (!aTextRun->IsLigatureGroupStart(index) &&
7831              unicode::GetEmojiPresentation(ucs4) == unicode::EmojiDefault)) {
7832           return false;
7833         }
7834       }
7835     }
7836   }
7837   return true;
7838 }
7839 
PeekOffsetCharacter(bool aForward,int32_t * aOffset,PeekOffsetCharacterOptions aOptions)7840 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetCharacter(
7841     bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions) {
7842   int32_t contentLength = GetContentLength();
7843   NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
7844 
7845   if (!aOptions.mIgnoreUserStyleAll) {
7846     StyleUserSelect selectStyle;
7847     Unused << IsSelectable(&selectStyle);
7848     if (selectStyle == StyleUserSelect::All) {
7849       return CONTINUE_UNSELECTABLE;
7850     }
7851   }
7852 
7853   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7854   if (!mTextRun) return CONTINUE_EMPTY;
7855 
7856   TrimmedOffsets trimmed =
7857       GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
7858 
7859   // A negative offset means "end of frame".
7860   int32_t startOffset =
7861       GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
7862 
7863   if (!aForward) {
7864     // If at the beginning of the line, look at the previous continuation
7865     for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
7866          i >= trimmed.mStart; --i) {
7867       iter.SetOriginalOffset(i);
7868       if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
7869                                     this)) {
7870         *aOffset = i - mContentOffset;
7871         return FOUND;
7872       }
7873     }
7874     *aOffset = 0;
7875   } else {
7876     // If we're at the end of a line, look at the next continuation
7877     iter.SetOriginalOffset(startOffset);
7878     if (startOffset <= trimmed.GetEnd() &&
7879         !(startOffset < trimmed.GetEnd() &&
7880           StyleText()->NewlineIsSignificant(this) &&
7881           iter.GetSkippedOffset() < mTextRun->GetLength() &&
7882           mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
7883       for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
7884         iter.SetOriginalOffset(i);
7885         if (i == trimmed.GetEnd() ||
7886             IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
7887                                       this)) {
7888           *aOffset = i - mContentOffset;
7889           return FOUND;
7890         }
7891       }
7892     }
7893     *aOffset = contentLength;
7894   }
7895 
7896   return CONTINUE;
7897 }
7898 
IsInlineWhitespace() const7899 bool ClusterIterator::IsInlineWhitespace() const {
7900   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
7901   return IsSelectionInlineWhitespace(mFrag, mCharIndex);
7902 }
7903 
IsNewline() const7904 bool ClusterIterator::IsNewline() const {
7905   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
7906   return IsSelectionNewline(mFrag, mCharIndex);
7907 }
7908 
IsPunctuation() const7909 bool ClusterIterator::IsPunctuation() const {
7910   NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
7911   // Return true for all Punctuation categories (Unicode general category P?),
7912   // and also for Symbol categories (S?) except for Modifier Symbol, which is
7913   // kept together with any adjacent letter/number. (Bug 1066756)
7914   uint32_t ch = mFrag->CharAt(mCharIndex);
7915   uint8_t cat = unicode::GetGeneralCategory(ch);
7916   switch (cat) {
7917     case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
7918       if (ch == '_' && !StaticPrefs::layout_word_select_stop_at_underscore()) {
7919         return false;
7920       }
7921       [[fallthrough]];
7922     case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION:    /* Pd */
7923     case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
7924     case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
7925     case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
7926     case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
7927     case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION:    /* Ps */
7928     case HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL:     /* Sc */
7929     // Deliberately omitted:
7930     // case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL:     /* Sk */
7931     case HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL:  /* Sm */
7932     case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: /* So */
7933       return true;
7934     default:
7935       return false;
7936   }
7937 }
7938 
GetAfterInternal() const7939 int32_t ClusterIterator::GetAfterInternal() const {
7940   if (mFrag->IsHighSurrogateFollowedByLowSurrogateAt(mCharIndex)) {
7941     return mCharIndex + 2;
7942   }
7943   return mCharIndex + 1;
7944 }
7945 
NextCluster()7946 bool ClusterIterator::NextCluster() {
7947   if (!mDirection) return false;
7948   const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
7949 
7950   mHaveWordBreak = false;
7951   while (true) {
7952     bool keepGoing = false;
7953     if (mDirection > 0) {
7954       if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) return false;
7955       keepGoing = mIterator.IsOriginalCharSkipped() ||
7956                   mIterator.GetOriginalOffset() < mTrimmed.mStart ||
7957                   !textRun->IsClusterStart(mIterator.GetSkippedOffset());
7958       mCharIndex = mIterator.GetOriginalOffset();
7959       mIterator.AdvanceOriginal(1);
7960     } else {
7961       if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) {
7962         // Trimming can skip backward word breakers, see bug 1667138
7963         return mHaveWordBreak;
7964       }
7965       mIterator.AdvanceOriginal(-1);
7966       keepGoing = mIterator.IsOriginalCharSkipped() ||
7967                   mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
7968                   !textRun->IsClusterStart(mIterator.GetSkippedOffset());
7969       mCharIndex = mIterator.GetOriginalOffset();
7970     }
7971 
7972     if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
7973       mHaveWordBreak = true;
7974     }
7975     if (!keepGoing) return true;
7976   }
7977 }
7978 
ClusterIterator(nsTextFrame * aTextFrame,int32_t aPosition,int32_t aDirection,nsString & aContext,bool aTrimSpaces)7979 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
7980                                  int32_t aDirection, nsString& aContext,
7981                                  bool aTrimSpaces)
7982     : mTextFrame(aTextFrame),
7983       mDirection(aDirection),
7984       mCharIndex(-1),
7985       mHaveWordBreak(false) {
7986   mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
7987   gfxTextRun* textRun = aTextFrame->GetTextRun(nsTextFrame::eInflated);
7988   if (!textRun) {
7989     mDirection = 0;  // signal failure
7990     return;
7991   }
7992 
7993   mFrag = aTextFrame->TextFragment();
7994   // If we're in a password field, some characters may be masked.  In such
7995   // case, we need to treat each masked character is a mask character since
7996   // we shouldn't expose word boundary which is hidden by the masking.
7997   if (aTextFrame->GetContent() && mFrag->GetLength() > 0 &&
7998       aTextFrame->GetContent()->HasFlag(NS_MAYBE_MASKED) &&
7999       (textRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed)) {
8000     const char16_t kPasswordMask = TextEditor::PasswordMask();
8001     const nsTransformedTextRun* transformedTextRun =
8002         static_cast<const nsTransformedTextRun*>(textRun);
8003     // Use nsString and not nsAutoString so that we get a nsStringBuffer which
8004     // can be just AddRefed in `mMaskedFrag`.
8005     nsString maskedText;
8006     maskedText.SetCapacity(mFrag->GetLength());
8007     for (uint32_t i = 0; i < mFrag->GetLength(); ++i) {
8008       mIterator.SetOriginalOffset(i);
8009       uint32_t skippedOffset = mIterator.GetSkippedOffset();
8010       if (mFrag->IsHighSurrogateFollowedByLowSurrogateAt(i)) {
8011         if (transformedTextRun->mStyles[skippedOffset]->mMaskPassword) {
8012           maskedText.Append(kPasswordMask);
8013           maskedText.Append(kPasswordMask);
8014         } else {
8015           maskedText.Append(mFrag->CharAt(i));
8016           maskedText.Append(mFrag->CharAt(i + 1));
8017         }
8018         ++i;
8019       } else {
8020         maskedText.Append(
8021             transformedTextRun->mStyles[skippedOffset]->mMaskPassword
8022                 ? kPasswordMask
8023                 : mFrag->CharAt(i));
8024       }
8025     }
8026     mMaskedFrag.SetTo(maskedText, mFrag->IsBidi(), true);
8027     mFrag = &mMaskedFrag;
8028   }
8029 
8030   mIterator.SetOriginalOffset(aPosition);
8031   mTrimmed = aTextFrame->GetTrimmedOffsets(
8032       mFrag, aTrimSpaces ? nsTextFrame::TrimmedOffsetFlags::Default
8033                          : nsTextFrame::TrimmedOffsetFlags::NoTrimAfter |
8034                                nsTextFrame::TrimmedOffsetFlags::NoTrimBefore);
8035 
8036   int32_t textOffset = aTextFrame->GetContentOffset();
8037   int32_t textLen = aTextFrame->GetContentLength();
8038   // XXX(Bug 1631371) Check if this should use a fallible operation as it
8039   // pretended earlier.
8040   mWordBreaks.AppendElements(textLen + 1);
8041   memset(mWordBreaks.Elements(), false, (textLen + 1) * sizeof(bool));
8042   int32_t textStart;
8043   if (aDirection > 0) {
8044     if (aContext.IsEmpty()) {
8045       // No previous context, so it must be the start of a line or text run
8046       mWordBreaks[0] = true;
8047     }
8048     textStart = aContext.Length();
8049     mFrag->AppendTo(aContext, textOffset, textLen);
8050   } else {
8051     if (aContext.IsEmpty()) {
8052       // No following context, so it must be the end of a line or text run
8053       mWordBreaks[textLen] = true;
8054     }
8055     textStart = 0;
8056     nsAutoString str;
8057     mFrag->AppendTo(str, textOffset, textLen);
8058     aContext.Insert(str, 0);
8059   }
8060   mozilla::intl::WordBreaker* wordBreaker = nsContentUtils::WordBreaker();
8061   int32_t nextWord = textStart > 0 ? textStart - 1 : textStart;
8062   while (true) {
8063     nextWord =
8064         wordBreaker->NextWord(aContext.get(), aContext.Length(), nextWord);
8065     if (NS_WORDBREAKER_NEED_MORE_TEXT == nextWord ||
8066         nextWord > textStart + textLen) {
8067       break;
8068     }
8069     mWordBreaks[nextWord - textStart] = true;
8070   }
8071 }
8072 
PeekOffsetWord(bool aForward,bool aWordSelectEatSpace,bool aIsKeyboardSelect,int32_t * aOffset,PeekWordState * aState,bool aTrimSpaces)8073 nsIFrame::FrameSearchResult nsTextFrame::PeekOffsetWord(
8074     bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
8075     int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) {
8076   int32_t contentLength = GetContentLength();
8077   NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
8078 
8079   StyleUserSelect selectStyle;
8080   Unused << IsSelectable(&selectStyle);
8081   if (selectStyle == StyleUserSelect::All) return CONTINUE_UNSELECTABLE;
8082 
8083   int32_t offset =
8084       GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
8085   ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext,
8086                         aTrimSpaces);
8087 
8088   if (!cIter.NextCluster()) return CONTINUE_EMPTY;
8089 
8090   do {
8091     bool isPunctuation = cIter.IsPunctuation();
8092     bool isInlineWhitespace = cIter.IsInlineWhitespace();
8093     bool isWhitespace = isInlineWhitespace || cIter.IsNewline();
8094     bool isWordBreakBefore = cIter.HaveWordBreakBefore();
8095     if (!isWhitespace || isInlineWhitespace) {
8096       aState->SetSawInlineCharacter();
8097     }
8098     if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
8099       aState->SetSawBeforeType();
8100       aState->Update(isPunctuation, isWhitespace);
8101       continue;
8102     }
8103     // See if we can break before the current cluster
8104     if (!aState->mAtStart) {
8105       bool canBreak;
8106       if (isPunctuation != aState->mLastCharWasPunctuation) {
8107         canBreak = BreakWordBetweenPunctuation(aState, aForward, isPunctuation,
8108                                                isWhitespace, aIsKeyboardSelect);
8109       } else if (!aState->mLastCharWasWhitespace && !isWhitespace &&
8110                  !isPunctuation && isWordBreakBefore) {
8111         // if both the previous and the current character are not white
8112         // space but this can be word break before, we don't need to eat
8113         // a white space in this case. This case happens in some languages
8114         // that their words are not separated by white spaces. E.g.,
8115         // Japanese and Chinese.
8116         canBreak = true;
8117       } else {
8118         canBreak = isWordBreakBefore && aState->mSawBeforeType &&
8119                    (aWordSelectEatSpace != isWhitespace);
8120       }
8121       if (canBreak) {
8122         *aOffset = cIter.GetBeforeOffset() - mContentOffset;
8123         return FOUND;
8124       }
8125     }
8126     aState->Update(isPunctuation, isWhitespace);
8127   } while (cIter.NextCluster());
8128 
8129   *aOffset = cIter.GetAfterOffset() - mContentOffset;
8130   return CONTINUE;
8131 }
8132 
8133 // TODO this needs to be deCOMtaminated with the interface fixed in
8134 // nsIFrame.h, but we won't do that until the old textframe is gone.
CheckVisibility(nsPresContext * aContext,int32_t aStartIndex,int32_t aEndIndex,bool aRecurse,bool * aFinished,bool * aRetval)8135 nsresult nsTextFrame::CheckVisibility(nsPresContext* aContext,
8136                                       int32_t aStartIndex, int32_t aEndIndex,
8137                                       bool aRecurse, bool* aFinished,
8138                                       bool* aRetval) {
8139   if (!aRetval) return NS_ERROR_NULL_POINTER;
8140 
8141   // Text in the range is visible if there is at least one character in the
8142   // range that is not skipped and is mapped by this frame (which is the primary
8143   // frame) or one of its continuations.
8144   for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
8145     int32_t dummyOffset = 0;
8146     if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
8147       *aRetval = true;
8148       return NS_OK;
8149     }
8150   }
8151 
8152   *aRetval = false;
8153   return NS_OK;
8154 }
8155 
GetOffsets() const8156 std::pair<int32_t, int32_t> nsTextFrame::GetOffsets() const {
8157   return std::make_pair(GetContentOffset(), GetContentEnd());
8158 }
8159 
FindEndOfPunctuationRun(const nsTextFragment * aFrag,const gfxTextRun * aTextRun,gfxSkipCharsIterator * aIter,int32_t aOffset,int32_t aStart,int32_t aEnd)8160 static int32_t FindEndOfPunctuationRun(const nsTextFragment* aFrag,
8161                                        const gfxTextRun* aTextRun,
8162                                        gfxSkipCharsIterator* aIter,
8163                                        int32_t aOffset, int32_t aStart,
8164                                        int32_t aEnd) {
8165   int32_t i;
8166 
8167   for (i = aStart; i < aEnd - aOffset; ++i) {
8168     if (nsContentUtils::IsFirstLetterPunctuation(
8169             aFrag->ScalarValueAt(aOffset + i))) {
8170       aIter->SetOriginalOffset(aOffset + i);
8171       FindClusterEnd(aTextRun, aEnd, aIter);
8172       i = aIter->GetOriginalOffset() - aOffset;
8173     } else {
8174       break;
8175     }
8176   }
8177   return i;
8178 }
8179 
8180 /**
8181  * Returns true if this text frame completes the first-letter, false
8182  * if it does not contain a true "letter".
8183  * If returns true, then it also updates aLength to cover just the first-letter
8184  * text.
8185  *
8186  * XXX :first-letter should be handled during frame construction
8187  * (and it has a good bit in common with nextBidi)
8188  *
8189  * @param aLength an in/out parameter: on entry contains the maximum length to
8190  * return, on exit returns length of the first-letter fragment (which may
8191  * include leading and trailing punctuation, for example)
8192  */
FindFirstLetterRange(const nsTextFragment * aFrag,const nsAtom * aLang,const gfxTextRun * aTextRun,int32_t aOffset,const gfxSkipCharsIterator & aIter,int32_t * aLength)8193 static bool FindFirstLetterRange(const nsTextFragment* aFrag,
8194                                  const nsAtom* aLang,
8195                                  const gfxTextRun* aTextRun, int32_t aOffset,
8196                                  const gfxSkipCharsIterator& aIter,
8197                                  int32_t* aLength) {
8198   int32_t i;
8199   int32_t length = *aLength;
8200   int32_t endOffset = aOffset + length;
8201   gfxSkipCharsIterator iter(aIter);
8202 
8203   // Currently the only language-specific special case we handle here is the
8204   // Dutch "IJ" digraph.
8205   auto LangTagIsDutch = [](const nsAtom* aLang) -> bool {
8206     if (!aLang) {
8207       return false;
8208     }
8209     if (aLang == nsGkAtoms::nl) {
8210       return true;
8211     }
8212     // We don't need to fully parse as a Locale; just check the initial subtag.
8213     nsDependentAtomString langStr(aLang);
8214     int32_t index = langStr.FindChar('-');
8215     if (index > 0) {
8216       langStr.Truncate(index);
8217       return langStr.EqualsLiteral("nl");
8218     }
8219     return false;
8220   };
8221 
8222   // skip leading whitespace, then consume clusters that start with punctuation
8223   i = FindEndOfPunctuationRun(
8224       aFrag, aTextRun, &iter, aOffset,
8225       GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1), endOffset);
8226   if (i == length) {
8227     return false;
8228   }
8229 
8230   // If the next character is not a letter, number or symbol, there is no
8231   // first-letter.
8232   // Return true so that we don't go on looking, but set aLength to 0.
8233   if (!nsContentUtils::IsAlphanumericOrSymbolAt(aFrag, aOffset + i)) {
8234     *aLength = 0;
8235     return true;
8236   }
8237 
8238   // consume another cluster (the actual first letter)
8239 
8240   // For complex scripts such as Indic and SEAsian, where first-letter
8241   // should extend to entire orthographic "syllable" clusters, we don't
8242   // want to allow this to split a ligature.
8243   bool allowSplitLigature;
8244 
8245   typedef unicode::Script Script;
8246   Script script = unicode::GetScriptCode(aFrag->CharAt(aOffset + i));
8247   switch (script) {
8248     default:
8249       allowSplitLigature = true;
8250       break;
8251 
8252     // For now, lacking any definitive specification of when to apply this
8253     // behavior, we'll base the decision on the HarfBuzz shaping engine
8254     // used for each script: those that are handled by the Indic, Tibetan,
8255     // Myanmar and SEAsian shapers will apply the "don't split ligatures"
8256     // rule.
8257 
8258     // Indic
8259     case Script::BENGALI:
8260     case Script::DEVANAGARI:
8261     case Script::GUJARATI:
8262     case Script::GURMUKHI:
8263     case Script::KANNADA:
8264     case Script::MALAYALAM:
8265     case Script::ORIYA:
8266     case Script::TAMIL:
8267     case Script::TELUGU:
8268     case Script::SINHALA:
8269     case Script::BALINESE:
8270     case Script::LEPCHA:
8271     case Script::REJANG:
8272     case Script::SUNDANESE:
8273     case Script::JAVANESE:
8274     case Script::KAITHI:
8275     case Script::MEETEI_MAYEK:
8276     case Script::CHAKMA:
8277     case Script::SHARADA:
8278     case Script::TAKRI:
8279     case Script::KHMER:
8280 
8281     // Tibetan
8282     case Script::TIBETAN:
8283 
8284     // Myanmar
8285     case Script::MYANMAR:
8286 
8287     // Other SEAsian
8288     case Script::BUGINESE:
8289     case Script::NEW_TAI_LUE:
8290     case Script::CHAM:
8291     case Script::TAI_THAM:
8292 
8293       // What about Thai/Lao - any special handling needed?
8294       // Should we special-case Arabic lam-alef?
8295 
8296       allowSplitLigature = false;
8297       break;
8298   }
8299 
8300   iter.SetOriginalOffset(aOffset + i);
8301   FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
8302 
8303   i = iter.GetOriginalOffset() - aOffset;
8304   if (i + 1 == length) {
8305     return true;
8306   }
8307 
8308   // Check for Dutch "ij" digraph special case.
8309   if (script == Script::LATIN && LangTagIsDutch(aLang)) {
8310     if (ToLowerCase(aFrag->CharAt(aOffset + i)) == 'i' &&
8311         ToLowerCase(aFrag->CharAt(aOffset + i + 1)) == 'j') {
8312       iter.SetOriginalOffset(aOffset + i + 1);
8313       FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
8314       i = iter.GetOriginalOffset() - aOffset;
8315       if (i + 1 == length) {
8316         return true;
8317       }
8318     }
8319   }
8320 
8321   // consume clusters that start with punctuation
8322   i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1,
8323                               endOffset);
8324   if (i < length) {
8325     *aLength = i;
8326   }
8327   return true;
8328 }
8329 
FindStartAfterSkippingWhitespace(nsTextFrame::PropertyProvider * aProvider,nsIFrame::InlineIntrinsicISizeData * aData,const nsStyleText * aTextStyle,gfxSkipCharsIterator * aIterator,uint32_t aFlowEndInTextRun)8330 static uint32_t FindStartAfterSkippingWhitespace(
8331     nsTextFrame::PropertyProvider* aProvider,
8332     nsIFrame::InlineIntrinsicISizeData* aData, const nsStyleText* aTextStyle,
8333     gfxSkipCharsIterator* aIterator, uint32_t aFlowEndInTextRun) {
8334   if (aData->mSkipWhitespace) {
8335     while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
8336            IsTrimmableSpace(aProvider->GetFragment(),
8337                             aIterator->GetOriginalOffset(), aTextStyle)) {
8338       aIterator->AdvanceOriginal(1);
8339     }
8340   }
8341   return aIterator->GetSkippedOffset();
8342 }
8343 
GetFontSizeInflation() const8344 float nsTextFrame::GetFontSizeInflation() const {
8345   if (!HasFontSizeInflation()) {
8346     return 1.0f;
8347   }
8348   return GetProperty(FontSizeInflationProperty());
8349 }
8350 
SetFontSizeInflation(float aInflation)8351 void nsTextFrame::SetFontSizeInflation(float aInflation) {
8352   if (aInflation == 1.0f) {
8353     if (HasFontSizeInflation()) {
8354       RemoveStateBits(TEXT_HAS_FONT_INFLATION);
8355       RemoveProperty(FontSizeInflationProperty());
8356     }
8357     return;
8358   }
8359 
8360   AddStateBits(TEXT_HAS_FONT_INFLATION);
8361   SetProperty(FontSizeInflationProperty(), aInflation);
8362 }
8363 
8364 /* virtual */
MarkIntrinsicISizesDirty()8365 void nsTextFrame::MarkIntrinsicISizesDirty() {
8366   ClearTextRuns();
8367   nsIFrame::MarkIntrinsicISizesDirty();
8368 }
8369 
8370 // XXX this doesn't handle characters shaped by line endings. We need to
8371 // temporarily override the "current line ending" settings.
AddInlineMinISizeForFlow(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData,TextRunType aTextRunType)8372 void nsTextFrame::AddInlineMinISizeForFlow(gfxContext* aRenderingContext,
8373                                            nsIFrame::InlineMinISizeData* aData,
8374                                            TextRunType aTextRunType) {
8375   uint32_t flowEndInTextRun;
8376   gfxSkipCharsIterator iter =
8377       EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
8378                     aData->LineContainer(), aData->mLine, &flowEndInTextRun);
8379   gfxTextRun* textRun = GetTextRun(aTextRunType);
8380   if (!textRun) return;
8381 
8382   // Pass null for the line container. This will disable tab spacing, but that's
8383   // OK since we can't really handle tabs for intrinsic sizing anyway.
8384   const nsStyleText* textStyle = StyleText();
8385   const nsTextFragment* frag = TextFragment();
8386 
8387   // If we're hyphenating, the PropertyProvider needs the actual length;
8388   // otherwise we can just pass INT32_MAX to mean "all the text"
8389   int32_t len = INT32_MAX;
8390   bool hyphenating = frag->GetLength() > 0 &&
8391                      (textStyle->mHyphens == StyleHyphens::Auto ||
8392                       (textStyle->mHyphens == StyleHyphens::Manual &&
8393                        !!(textRun->GetFlags() &
8394                           gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
8395   if (hyphenating) {
8396     gfxSkipCharsIterator tmp(iter);
8397     len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
8398                             tmp.ConvertSkippedToOriginal(flowEndInTextRun)) -
8399           iter.GetOriginalOffset();
8400   }
8401   PropertyProvider provider(textRun, textStyle, frag, this, iter, len, nullptr,
8402                             0, aTextRunType);
8403 
8404   bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
8405   bool preformatNewlines = textStyle->NewlineIsSignificant(this);
8406   bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
8407   bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
8408   gfxFloat tabWidth = -1;
8409   uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
8410                                                     &iter, flowEndInTextRun);
8411 
8412   // text-combine-upright frame is constantly 1em on inline-axis.
8413   if (Style()->IsTextCombined()) {
8414     if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
8415       aData->OptionallyBreak();
8416     }
8417     aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
8418     aData->mTrailingWhitespace = 0;
8419     return;
8420   }
8421 
8422   if (textStyle->EffectiveOverflowWrap() == StyleOverflowWrap::Anywhere &&
8423       textStyle->WordCanWrap(this)) {
8424     aData->OptionallyBreak();
8425     aData->mCurrentLine +=
8426         textRun->GetMinAdvanceWidth(Range(start, flowEndInTextRun));
8427     aData->mTrailingWhitespace = 0;
8428     aData->mAtStartOfLine = false;
8429     aData->OptionallyBreak();
8430     return;
8431   }
8432 
8433   AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
8434   if (hyphenating) {
8435     if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
8436       provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
8437                                     hyphBuffer.Elements());
8438     } else {
8439       hyphenating = false;
8440     }
8441   }
8442 
8443   for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
8444     bool preformattedNewline = false;
8445     bool preformattedTab = false;
8446     if (i < flowEndInTextRun) {
8447       // XXXldb Shouldn't we be including the newline as part of the
8448       // segment that it ends rather than part of the segment that it
8449       // starts?
8450       preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
8451       preformattedTab = preformatTabs && textRun->CharIsTab(i);
8452       if (!textRun->CanBreakLineBefore(i) && !preformattedNewline &&
8453           !preformattedTab &&
8454           (!hyphenating ||
8455            hyphBuffer[i - start] == gfxTextRun::HyphenType::None)) {
8456         // we can't break here (and it's not the end of the flow)
8457         continue;
8458       }
8459     }
8460 
8461     if (i > wordStart) {
8462       nscoord width = NSToCoordCeilClamped(
8463           textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
8464       width = std::max(0, width);
8465       aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
8466       aData->mAtStartOfLine = false;
8467 
8468       if (collapseWhitespace || whitespaceCanHang) {
8469         uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i,
8470                                                  &iter, whitespaceCanHang);
8471         if (trimStart == start) {
8472           // This is *all* trimmable whitespace, so whatever trailingWhitespace
8473           // we saw previously is still trailing...
8474           aData->mTrailingWhitespace += width;
8475         } else {
8476           // Some non-whitespace so the old trailingWhitespace is no longer
8477           // trailing
8478           nscoord wsWidth = NSToCoordCeilClamped(
8479               textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
8480           aData->mTrailingWhitespace = std::max(0, wsWidth);
8481         }
8482       } else {
8483         aData->mTrailingWhitespace = 0;
8484       }
8485     }
8486 
8487     if (preformattedTab) {
8488       PropertyProvider::Spacing spacing;
8489       provider.GetSpacing(Range(i, i + 1), &spacing);
8490       aData->mCurrentLine += nscoord(spacing.mBefore);
8491       if (tabWidth < 0) {
8492         tabWidth = ComputeTabWidthAppUnits(this);
8493       }
8494       gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
8495                                            provider.MinTabAdvance());
8496       aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
8497       wordStart = i + 1;
8498     } else if (i < flowEndInTextRun ||
8499                (i == textRun->GetLength() &&
8500                 (textRun->GetFlags2() &
8501                  nsTextFrameUtils::Flags::HasTrailingBreak))) {
8502       if (preformattedNewline) {
8503         aData->ForceBreak();
8504       } else if (i < flowEndInTextRun && hyphenating &&
8505                  hyphBuffer[i - start] != gfxTextRun::HyphenType::None) {
8506         aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
8507       } else {
8508         aData->OptionallyBreak();
8509       }
8510       if (aData->mSkipWhitespace) {
8511         iter.SetSkippedOffset(i);
8512         wordStart = FindStartAfterSkippingWhitespace(
8513             &provider, aData, textStyle, &iter, flowEndInTextRun);
8514       } else {
8515         wordStart = i;
8516       }
8517     }
8518   }
8519 
8520   if (start < flowEndInTextRun) {
8521     // Check if we have collapsible whitespace at the end
8522     aData->mSkipWhitespace = IsTrimmableSpace(
8523         provider.GetFragment(),
8524         iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
8525   }
8526 }
8527 
IsCurrentFontInflation(float aInflation) const8528 bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
8529   return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
8530 }
8531 
8532 // XXX Need to do something here to avoid incremental reflow bugs due to
8533 // first-line and first-letter changing min-width
8534 /* virtual */
AddInlineMinISize(gfxContext * aRenderingContext,nsIFrame::InlineMinISizeData * aData)8535 void nsTextFrame::AddInlineMinISize(gfxContext* aRenderingContext,
8536                                     nsIFrame::InlineMinISizeData* aData) {
8537   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
8538   TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
8539 
8540   if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
8541     // FIXME: Ideally, if we already have a text run, we'd move it to be
8542     // the uninflated text run.
8543     ClearTextRun(nullptr, nsTextFrame::eInflated);
8544     mFontMetrics = nullptr;
8545   }
8546 
8547   nsTextFrame* f;
8548   const gfxTextRun* lastTextRun = nullptr;
8549   // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8550   // in the flow are handled right here.
8551   for (f = this; f; f = f->GetNextContinuation()) {
8552     // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8553     // haven't set up textruns yet for f.  Except in OOM situations,
8554     // lastTextRun will only be null for the first text frame.
8555     if (f == this || f->GetTextRun(trtype) != lastTextRun) {
8556       nsIFrame* lc;
8557       if (aData->LineContainer() &&
8558           aData->LineContainer() != (lc = FindLineContainer(f))) {
8559         NS_ASSERTION(f != this,
8560                      "wrong InlineMinISizeData container"
8561                      " for first continuation");
8562         aData->mLine = nullptr;
8563         aData->SetLineContainer(lc);
8564       }
8565 
8566       // This will process all the text frames that share the same textrun as f.
8567       f->AddInlineMinISizeForFlow(aRenderingContext, aData, trtype);
8568       lastTextRun = f->GetTextRun(trtype);
8569     }
8570   }
8571 }
8572 
8573 // XXX this doesn't handle characters shaped by line endings. We need to
8574 // temporarily override the "current line ending" settings.
AddInlinePrefISizeForFlow(gfxContext * aRenderingContext,nsIFrame::InlinePrefISizeData * aData,TextRunType aTextRunType)8575 void nsTextFrame::AddInlinePrefISizeForFlow(
8576     gfxContext* aRenderingContext, nsIFrame::InlinePrefISizeData* aData,
8577     TextRunType aTextRunType) {
8578   uint32_t flowEndInTextRun;
8579   gfxSkipCharsIterator iter =
8580       EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
8581                     aData->LineContainer(), aData->mLine, &flowEndInTextRun);
8582   gfxTextRun* textRun = GetTextRun(aTextRunType);
8583   if (!textRun) return;
8584 
8585   // Pass null for the line container. This will disable tab spacing, but that's
8586   // OK since we can't really handle tabs for intrinsic sizing anyway.
8587 
8588   const nsStyleText* textStyle = StyleText();
8589   const nsTextFragment* frag = TextFragment();
8590   PropertyProvider provider(textRun, textStyle, frag, this, iter, INT32_MAX,
8591                             nullptr, 0, aTextRunType);
8592 
8593   // text-combine-upright frame is constantly 1em on inline-axis.
8594   if (Style()->IsTextCombined()) {
8595     aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
8596     aData->mTrailingWhitespace = 0;
8597     aData->mLineIsEmpty = false;
8598     return;
8599   }
8600 
8601   bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
8602   bool preformatNewlines = textStyle->NewlineIsSignificant(this);
8603   bool preformatTabs = textStyle->TabIsSignificant();
8604   gfxFloat tabWidth = -1;
8605   uint32_t start = FindStartAfterSkippingWhitespace(&provider, aData, textStyle,
8606                                                     &iter, flowEndInTextRun);
8607 
8608   // XXX Should we consider hyphenation here?
8609   // If newlines and tabs aren't preformatted, nothing to do inside
8610   // the loop so make i skip to the end
8611   uint32_t loopStart =
8612       (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
8613   for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
8614     bool preformattedNewline = false;
8615     bool preformattedTab = false;
8616     if (i < flowEndInTextRun) {
8617       // XXXldb Shouldn't we be including the newline as part of the
8618       // segment that it ends rather than part of the segment that it
8619       // starts?
8620       NS_ASSERTION(preformatNewlines || preformatTabs,
8621                    "We can't be here unless newlines are "
8622                    "hard breaks or there are tabs");
8623       preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
8624       preformattedTab = preformatTabs && textRun->CharIsTab(i);
8625       if (!preformattedNewline && !preformattedTab) {
8626         // we needn't break here (and it's not the end of the flow)
8627         continue;
8628       }
8629     }
8630 
8631     if (i > lineStart) {
8632       nscoord width = NSToCoordCeilClamped(
8633           textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
8634       width = std::max(0, width);
8635       aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
8636       aData->mLineIsEmpty = false;
8637 
8638       if (collapseWhitespace) {
8639         uint32_t trimStart =
8640             GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
8641         if (trimStart == start) {
8642           // This is *all* trimmable whitespace, so whatever trailingWhitespace
8643           // we saw previously is still trailing...
8644           aData->mTrailingWhitespace += width;
8645         } else {
8646           // Some non-whitespace so the old trailingWhitespace is no longer
8647           // trailing
8648           nscoord wsWidth = NSToCoordCeilClamped(
8649               textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
8650           aData->mTrailingWhitespace = std::max(0, wsWidth);
8651         }
8652       } else {
8653         aData->mTrailingWhitespace = 0;
8654       }
8655     }
8656 
8657     if (preformattedTab) {
8658       PropertyProvider::Spacing spacing;
8659       provider.GetSpacing(Range(i, i + 1), &spacing);
8660       aData->mCurrentLine += nscoord(spacing.mBefore);
8661       if (tabWidth < 0) {
8662         tabWidth = ComputeTabWidthAppUnits(this);
8663       }
8664       gfxFloat afterTab = AdvanceToNextTab(aData->mCurrentLine, tabWidth,
8665                                            provider.MinTabAdvance());
8666       aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
8667       aData->mLineIsEmpty = false;
8668       lineStart = i + 1;
8669     } else if (preformattedNewline) {
8670       aData->ForceBreak();
8671       lineStart = i;
8672     }
8673   }
8674 
8675   // Check if we have collapsible whitespace at the end
8676   if (start < flowEndInTextRun) {
8677     aData->mSkipWhitespace = IsTrimmableSpace(
8678         provider.GetFragment(),
8679         iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), textStyle);
8680   }
8681 }
8682 
8683 // XXX Need to do something here to avoid incremental reflow bugs due to
8684 // first-line and first-letter changing pref-width
8685 /* virtual */
AddInlinePrefISize(gfxContext * aRenderingContext,nsIFrame::InlinePrefISizeData * aData)8686 void nsTextFrame::AddInlinePrefISize(gfxContext* aRenderingContext,
8687                                      nsIFrame::InlinePrefISizeData* aData) {
8688   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
8689   TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
8690 
8691   if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
8692     // FIXME: Ideally, if we already have a text run, we'd move it to be
8693     // the uninflated text run.
8694     ClearTextRun(nullptr, nsTextFrame::eInflated);
8695     mFontMetrics = nullptr;
8696   }
8697 
8698   nsTextFrame* f;
8699   const gfxTextRun* lastTextRun = nullptr;
8700   // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8701   // in the flow are handled right here.
8702   for (f = this; f; f = f->GetNextContinuation()) {
8703     // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8704     // haven't set up textruns yet for f.  Except in OOM situations,
8705     // lastTextRun will only be null for the first text frame.
8706     if (f == this || f->GetTextRun(trtype) != lastTextRun) {
8707       nsIFrame* lc;
8708       if (aData->LineContainer() &&
8709           aData->LineContainer() != (lc = FindLineContainer(f))) {
8710         NS_ASSERTION(f != this,
8711                      "wrong InlinePrefISizeData container"
8712                      " for first continuation");
8713         aData->mLine = nullptr;
8714         aData->SetLineContainer(lc);
8715       }
8716 
8717       // This will process all the text frames that share the same textrun as f.
8718       f->AddInlinePrefISizeForFlow(aRenderingContext, aData, trtype);
8719       lastTextRun = f->GetTextRun(trtype);
8720     }
8721   }
8722 }
8723 
8724 /* virtual */
ComputeSize(gfxContext * aRenderingContext,WritingMode aWM,const LogicalSize & aCBSize,nscoord aAvailableISize,const LogicalSize & aMargin,const LogicalSize & aBorderPadding,const StyleSizeOverrides & aSizeOverrides,ComputeSizeFlags aFlags)8725 nsIFrame::SizeComputationResult nsTextFrame::ComputeSize(
8726     gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
8727     nscoord aAvailableISize, const LogicalSize& aMargin,
8728     const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
8729     ComputeSizeFlags aFlags) {
8730   // Inlines and text don't compute size before reflow.
8731   return {LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
8732           AspectRatioUsage::None};
8733 }
8734 
RoundOut(const gfxRect & aRect)8735 static nsRect RoundOut(const gfxRect& aRect) {
8736   nsRect r;
8737   r.x = NSToCoordFloor(aRect.X());
8738   r.y = NSToCoordFloor(aRect.Y());
8739   r.width = NSToCoordCeil(aRect.XMost()) - r.x;
8740   r.height = NSToCoordCeil(aRect.YMost()) - r.y;
8741   return r;
8742 }
8743 
ComputeTightBounds(DrawTarget * aDrawTarget) const8744 nsRect nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const {
8745   if (Style()->HasTextDecorationLines() || HasAnyStateBits(TEXT_HYPHEN_BREAK)) {
8746     // This is conservative, but OK.
8747     return InkOverflowRect();
8748   }
8749 
8750   gfxSkipCharsIterator iter =
8751       const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
8752   if (!mTextRun) return nsRect(0, 0, 0, 0);
8753 
8754   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
8755                             nsTextFrame::eInflated, mFontMetrics);
8756   // Trim trailing whitespace
8757   provider.InitializeForDisplay(true);
8758 
8759   gfxTextRun::Metrics metrics = mTextRun->MeasureText(
8760       ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
8761       aDrawTarget, &provider);
8762   if (GetWritingMode().IsLineInverted()) {
8763     metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
8764   }
8765   // mAscent should be the same as metrics.mAscent, but it's what we use to
8766   // paint so that's the one we'll use.
8767   nsRect boundingBox = RoundOut(metrics.mBoundingBox);
8768   boundingBox += nsPoint(0, mAscent);
8769   if (mTextRun->IsVertical()) {
8770     // Swap line-relative textMetrics dimensions to physical coordinates.
8771     std::swap(boundingBox.x, boundingBox.y);
8772     std::swap(boundingBox.width, boundingBox.height);
8773   }
8774   return boundingBox;
8775 }
8776 
8777 /* virtual */
GetPrefWidthTightBounds(gfxContext * aContext,nscoord * aX,nscoord * aXMost)8778 nsresult nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX,
8779                                               nscoord* aXMost) {
8780   gfxSkipCharsIterator iter =
8781       const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
8782   if (!mTextRun) return NS_ERROR_FAILURE;
8783 
8784   PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
8785                             nsTextFrame::eInflated, mFontMetrics);
8786   provider.InitializeForMeasure();
8787 
8788   gfxTextRun::Metrics metrics = mTextRun->MeasureText(
8789       ComputeTransformedRange(provider), gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
8790       aContext->GetDrawTarget(), &provider);
8791   // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
8792   *aX = NSToCoordFloor(metrics.mBoundingBox.x);
8793   *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
8794 
8795   return NS_OK;
8796 }
8797 
HasSoftHyphenBefore(const nsTextFragment * aFrag,const gfxTextRun * aTextRun,int32_t aStartOffset,const gfxSkipCharsIterator & aIter)8798 static bool HasSoftHyphenBefore(const nsTextFragment* aFrag,
8799                                 const gfxTextRun* aTextRun,
8800                                 int32_t aStartOffset,
8801                                 const gfxSkipCharsIterator& aIter) {
8802   if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
8803       aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
8804     return true;
8805   }
8806   if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasShy)) return false;
8807   gfxSkipCharsIterator iter = aIter;
8808   while (iter.GetOriginalOffset() > aStartOffset) {
8809     iter.AdvanceOriginal(-1);
8810     if (!iter.IsOriginalCharSkipped()) break;
8811     if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY) return true;
8812   }
8813   return false;
8814 }
8815 
8816 /**
8817  * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
8818  * because their text has all been taken and reflowed by earlier frames.
8819  */
RemoveEmptyInFlows(nsTextFrame * aFrame,nsTextFrame * aFirstToNotRemove)8820 static void RemoveEmptyInFlows(nsTextFrame* aFrame,
8821                                nsTextFrame* aFirstToNotRemove) {
8822   MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly");
8823   // We have to be careful here, because some RemoveFrame implementations
8824   // remove and destroy not only the passed-in frame but also all its following
8825   // in-flows (and sometimes all its following continuations in general).  So
8826   // we remove |f| and everything up to but not including firstToNotRemove from
8827   // the flow first, to make sure that only the things we want destroyed are
8828   // destroyed.
8829 
8830   // This sadly duplicates some of the logic from
8831   // nsSplittableFrame::RemoveFromFlow.  We can get away with not duplicating
8832   // all of it, because we know that the prev-continuation links of
8833   // firstToNotRemove and f are fluid, and non-null.
8834   NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
8835                        aFirstToNotRemove->GetPrevInFlow() &&
8836                    aFirstToNotRemove->GetPrevInFlow() != nullptr,
8837                "aFirstToNotRemove should have a fluid prev continuation");
8838   NS_ASSERTION(aFrame->GetPrevContinuation() == aFrame->GetPrevInFlow() &&
8839                    aFrame->GetPrevInFlow() != nullptr,
8840                "aFrame should have a fluid prev continuation");
8841 
8842   nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
8843   nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
8844 
8845   for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
8846        f = f->GetNextContinuation()) {
8847     // f is going to be destroyed soon, after it is unlinked from the
8848     // continuation chain. If its textrun is going to be destroyed we need to
8849     // do it now, before we unlink the frames to remove from the flow,
8850     // because DestroyFrom calls ClearTextRuns() and that will start at the
8851     // first frame with the text run and walk the continuations.
8852     if (f->IsInTextRunUserData()) {
8853       f->ClearTextRuns();
8854     } else {
8855       f->DisconnectTextRuns();
8856     }
8857   }
8858 
8859   prevContinuation->SetNextInFlow(aFirstToNotRemove);
8860   aFirstToNotRemove->SetPrevInFlow(prevContinuation);
8861 
8862   aFrame->SetPrevInFlow(nullptr);
8863   lastRemoved->SetNextInFlow(nullptr);
8864 
8865   nsContainerFrame* parent = aFrame->GetParent();
8866   nsBlockFrame* parentBlock = do_QueryFrame(parent);
8867   if (parentBlock) {
8868     // Manually call DoRemoveFrame so we can tell it that we're
8869     // removing empty frames; this will keep it from blowing away
8870     // text runs.
8871     parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
8872   } else {
8873     // Just remove it normally; use kNoReflowPrincipalList to avoid posting
8874     // new reflows.
8875     parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
8876   }
8877 }
8878 
SetLength(int32_t aLength,nsLineLayout * aLineLayout,uint32_t aSetLengthFlags)8879 void nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
8880                             uint32_t aSetLengthFlags) {
8881   mContentLengthHint = aLength;
8882   int32_t end = GetContentOffset() + aLength;
8883   nsTextFrame* f = GetNextInFlow();
8884   if (!f) return;
8885 
8886   // If our end offset is moving, then even if frames are not being pushed or
8887   // pulled, content is moving to or from the next line and the next line
8888   // must be reflowed.
8889   // If the next-continuation is dirty, then we should dirty the next line now
8890   // because we may have skipped doing it if we dirtied it in
8891   // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
8892   // and ChildIsDirty to handle a range of frames would be worse.
8893   if (aLineLayout &&
8894       (end != f->mContentOffset || f->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
8895     aLineLayout->SetDirtyNextLine();
8896   }
8897 
8898   if (end < f->mContentOffset) {
8899     // Our frame is shrinking. Give the text to our next in flow.
8900     if (aLineLayout && HasSignificantTerminalNewline() &&
8901         !GetParent()->IsLetterFrame() &&
8902         (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
8903       // Whatever text we hand to our next-in-flow will end up in a frame all of
8904       // its own, since it ends in a forced linebreak.  Might as well just put
8905       // it in a separate frame now.  This is important to prevent text run
8906       // churn; if we did not do that, then we'd likely end up rebuilding
8907       // textruns for all our following continuations.
8908       // We skip this optimization when the parent is a first-letter frame
8909       // because it doesn't deal well with more than one child frame.
8910       // We also skip this optimization if we were called during bidi
8911       // resolution, so as not to create a new frame which doesn't appear in
8912       // the bidi resolver's list of frames
8913       nsIFrame* newFrame =
8914           PresShell()->FrameConstructor()->CreateContinuingFrame(this,
8915                                                                  GetParent());
8916       nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
8917       nsFrameList temp(next, next);
8918       GetParent()->InsertFrames(kNoReflowPrincipalList, this,
8919                                 aLineLayout->GetLine(), temp);
8920       f = next;
8921     }
8922 
8923     f->mContentOffset = end;
8924     if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
8925       ClearTextRuns();
8926       f->ClearTextRuns();
8927     }
8928     return;
8929   }
8930   // Our frame is growing. Take text from our in-flow(s).
8931   // We can take text from frames in lines beyond just the next line.
8932   // We don't dirty those lines. That's OK, because when we reflow
8933   // our empty next-in-flow, it will take text from its next-in-flow and
8934   // dirty that line.
8935 
8936   // Note that in the process we may end up removing some frames from
8937   // the flow if they end up empty.
8938   nsTextFrame* framesToRemove = nullptr;
8939   while (f && f->mContentOffset < end) {
8940     f->mContentOffset = end;
8941     if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
8942       ClearTextRuns();
8943       f->ClearTextRuns();
8944     }
8945     nsTextFrame* next = f->GetNextInFlow();
8946     // Note: the "f->GetNextSibling() == next" check below is to restrict
8947     // this optimization to the case where they are on the same child list.
8948     // Otherwise we might remove the only child of a nsFirstLetterFrame
8949     // for example and it can't handle that.  See bug 597627 for details.
8950     if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
8951         (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
8952       // |f| is now empty.  We may as well remove it, instead of copying all
8953       // the text from |next| into it instead; the latter leads to use
8954       // rebuilding textruns for all following continuations.
8955       // We skip this optimization if we were called during bidi resolution,
8956       // since the bidi resolver may try to handle the destroyed frame later
8957       // and crash
8958       if (!framesToRemove) {
8959         // Remember that we have to remove this frame.
8960         framesToRemove = f;
8961       }
8962     } else if (framesToRemove) {
8963       RemoveEmptyInFlows(framesToRemove, f);
8964       framesToRemove = nullptr;
8965     }
8966     f = next;
8967   }
8968 
8969   MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
8970              "How did we exit the loop if we null out framesToRemove if "
8971              "!next || next->mContentOffset > end ?");
8972 
8973   if (framesToRemove) {
8974     // We are guaranteed that we exited the loop with f not null, per the
8975     // postcondition above
8976     RemoveEmptyInFlows(framesToRemove, f);
8977   }
8978 
8979 #ifdef DEBUG
8980   f = this;
8981   int32_t iterations = 0;
8982   while (f && iterations < 10) {
8983     f->GetContentLength();  // Assert if negative length
8984     f = f->GetNextContinuation();
8985     ++iterations;
8986   }
8987   f = this;
8988   iterations = 0;
8989   while (f && iterations < 10) {
8990     f->GetContentLength();  // Assert if negative length
8991     f = f->GetPrevContinuation();
8992     ++iterations;
8993   }
8994 #endif
8995 }
8996 
IsFloatingFirstLetterChild() const8997 bool nsTextFrame::IsFloatingFirstLetterChild() const {
8998   nsIFrame* frame = GetParent();
8999   return frame && frame->IsFloating() && frame->IsLetterFrame();
9000 }
9001 
IsInitialLetterChild() const9002 bool nsTextFrame::IsInitialLetterChild() const {
9003   nsIFrame* frame = GetParent();
9004   return frame && frame->StyleTextReset()->mInitialLetterSize != 0.0f &&
9005          frame->IsLetterFrame();
9006 }
9007 
9008 struct NewlineProperty {
9009   int32_t mStartOffset;
9010   // The offset of the first \n after mStartOffset, or -1 if there is none
9011   int32_t mNewlineOffset;
9012 };
9013 
Reflow(nsPresContext * aPresContext,ReflowOutput & aMetrics,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)9014 void nsTextFrame::Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics,
9015                          const ReflowInput& aReflowInput,
9016                          nsReflowStatus& aStatus) {
9017   MarkInReflow();
9018   DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
9019   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
9020   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
9021 
9022   InvalidateSelectionState();
9023 
9024   // XXX If there's no line layout, we shouldn't even have created this
9025   // frame. This may happen if, for example, this is text inside a table
9026   // but not inside a cell. For now, just don't reflow.
9027   if (!aReflowInput.mLineLayout) {
9028     ClearMetrics(aMetrics);
9029     return;
9030   }
9031 
9032   ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
9033              aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics,
9034              aStatus);
9035 
9036   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
9037 }
9038 
9039 #ifdef ACCESSIBILITY
9040 /**
9041  * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
9042  */
9043 class MOZ_STACK_CLASS ReflowTextA11yNotifier {
9044  public:
ReflowTextA11yNotifier(nsPresContext * aPresContext,nsIContent * aContent)9045   ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent)
9046       : mContent(aContent), mPresContext(aPresContext) {}
~ReflowTextA11yNotifier()9047   ~ReflowTextA11yNotifier() {
9048     if (nsAccessibilityService* accService =
9049             PresShell::GetAccessibilityService()) {
9050       accService->UpdateText(mPresContext->PresShell(), mContent);
9051     }
9052   }
9053 
9054  private:
9055   ReflowTextA11yNotifier();
9056   ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
9057   ReflowTextA11yNotifier& operator=(const ReflowTextA11yNotifier&);
9058 
9059   nsIContent* mContent;
9060   nsPresContext* mPresContext;
9061 };
9062 #endif
9063 
ReflowText(nsLineLayout & aLineLayout,nscoord aAvailableWidth,DrawTarget * aDrawTarget,ReflowOutput & aMetrics,nsReflowStatus & aStatus)9064 void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
9065                              DrawTarget* aDrawTarget, ReflowOutput& aMetrics,
9066                              nsReflowStatus& aStatus) {
9067   MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
9068 
9069 #ifdef NOISY_REFLOW
9070   ListTag(stdout);
9071   printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
9072 #endif
9073 
9074   nsPresContext* presContext = PresContext();
9075 
9076 #ifdef ACCESSIBILITY
9077   // Schedule the update of accessible tree since rendered text might be
9078   // changed.
9079   if (StyleVisibility()->IsVisible()) {
9080     ReflowTextA11yNotifier(presContext, mContent);
9081   }
9082 #endif
9083 
9084   /////////////////////////////////////////////////////////////////////
9085   // Set up flags and clear out state
9086   /////////////////////////////////////////////////////////////////////
9087 
9088   // Clear out the reflow input flags in mState. We also clear the whitespace
9089   // flags because this can change whether the frame maps whitespace-only text
9090   // or not. We also clear the flag that tracks whether we had a pending
9091   // reflow request from CharacterDataChanged (since we're reflowing now).
9092   RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
9093   mReflowRequestedForCharDataChange = false;
9094   RemoveProperty(WebRenderTextBounds());
9095   // Temporarily map all possible content while we construct our new textrun.
9096   // so that when doing reflow our styles prevail over any part of the
9097   // textrun we look at. Note that next-in-flows may be mapping the same
9098   // content; gfxTextRun construction logic will ensure that we take priority.
9099   int32_t maxContentLength = GetInFlowContentLength();
9100 
9101   InvalidateSelectionState();
9102 
9103   // We don't need to reflow if there is no content.
9104   if (!maxContentLength) {
9105     ClearMetrics(aMetrics);
9106     return;
9107   }
9108 
9109 #ifdef NOISY_BIDI
9110   printf("Reflowed textframe\n");
9111 #endif
9112 
9113   const nsStyleText* textStyle = StyleText();
9114 
9115   bool atStartOfLine = aLineLayout.LineAtStart();
9116   if (atStartOfLine) {
9117     AddStateBits(TEXT_START_OF_LINE);
9118   }
9119 
9120   uint32_t flowEndInTextRun;
9121   nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
9122   const nsTextFragment* frag = TextFragment();
9123 
9124   // DOM offsets of the text range we need to measure, after trimming
9125   // whitespace, restricting to first-letter, and restricting preformatted text
9126   // to nearest newline
9127   int32_t length = maxContentLength;
9128   int32_t offset = GetContentOffset();
9129 
9130   // Restrict preformatted text to the nearest newline
9131   int32_t newLineOffset = -1;  // this will be -1 or a content offset
9132   int32_t contentNewLineOffset = -1;
9133   // Pointer to the nsGkAtoms::newline set on this frame's element
9134   NewlineProperty* cachedNewlineOffset = nullptr;
9135   if (textStyle->NewlineIsSignificant(this)) {
9136     cachedNewlineOffset = mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
9137                               ? static_cast<NewlineProperty*>(
9138                                     mContent->GetProperty(nsGkAtoms::newline))
9139                               : nullptr;
9140     if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
9141         (cachedNewlineOffset->mNewlineOffset == -1 ||
9142          cachedNewlineOffset->mNewlineOffset >= offset)) {
9143       contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
9144     } else {
9145       contentNewLineOffset =
9146           FindChar(frag, offset, GetContent()->TextLength() - offset, '\n');
9147     }
9148     if (contentNewLineOffset < offset + length) {
9149       /*
9150         The new line offset could be outside this frame if the frame has been
9151         split by bidi resolution. In that case we won't use it in this reflow
9152         (newLineOffset will remain -1), but we will still cache it in mContent
9153       */
9154       newLineOffset = contentNewLineOffset;
9155     }
9156     if (newLineOffset >= 0) {
9157       length = newLineOffset + 1 - offset;
9158     }
9159   }
9160   if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
9161       HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
9162     // Skip leading whitespace. Make sure we don't skip a 'pre-line'
9163     // newline if there is one.
9164     int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
9165     int32_t whitespaceCount =
9166         GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
9167     if (whitespaceCount) {
9168       offset += whitespaceCount;
9169       length -= whitespaceCount;
9170       // Make sure this frame maps the trimmable whitespace.
9171       if (MOZ_UNLIKELY(offset > GetContentEnd())) {
9172         SetLength(offset - GetContentOffset(), &aLineLayout,
9173                   ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9174       }
9175     }
9176   }
9177 
9178   // If trimming whitespace left us with nothing to do, return early.
9179   if (length == 0) {
9180     ClearMetrics(aMetrics);
9181     return;
9182   }
9183 
9184   bool completedFirstLetter = false;
9185   // Layout dependent styles are a problem because we need to reconstruct
9186   // the gfxTextRun based on our layout.
9187   if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
9188     SetLength(maxContentLength, &aLineLayout,
9189               ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9190 
9191     if (aLineLayout.GetInFirstLetter()) {
9192       // floating first-letter boundaries are significant in textrun
9193       // construction, so clear the textrun out every time we hit a first-letter
9194       // and have changed our length (which controls the first-letter boundary)
9195       ClearTextRuns();
9196       // Find the length of the first-letter. We need a textrun for this.
9197       // REVIEW: maybe-bogus inflation should be ok (fixed below)
9198       gfxSkipCharsIterator iter =
9199           EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
9200                         aLineLayout.GetLine(), &flowEndInTextRun);
9201 
9202       if (mTextRun) {
9203         int32_t firstLetterLength = length;
9204         if (aLineLayout.GetFirstLetterStyleOK()) {
9205           // We only pass a language code to FindFirstLetterRange if it was
9206           // explicit in the content.
9207           const nsStyleFont* styleFont = StyleFont();
9208           const nsAtom* lang = styleFont->mExplicitLanguage
9209                                    ? styleFont->mLanguage.get()
9210                                    : nullptr;
9211           completedFirstLetter = FindFirstLetterRange(
9212               frag, lang, mTextRun, offset, iter, &firstLetterLength);
9213           if (newLineOffset >= 0) {
9214             // Don't allow a preformatted newline to be part of a first-letter.
9215             firstLetterLength = std::min(firstLetterLength, length - 1);
9216             if (length == 1) {
9217               // There is no text to be consumed by the first-letter before the
9218               // preformatted newline. Note that the first letter is therefore
9219               // complete (FindFirstLetterRange will have returned false).
9220               completedFirstLetter = true;
9221             }
9222           }
9223         } else {
9224           // We're in a first-letter frame's first in flow, so if there
9225           // was a first-letter, we'd be it. However, for one reason
9226           // or another (e.g., preformatted line break before this text),
9227           // we're not actually supposed to have first-letter style. So
9228           // just make a zero-length first-letter.
9229           firstLetterLength = 0;
9230           completedFirstLetter = true;
9231         }
9232         length = firstLetterLength;
9233         if (length) {
9234           AddStateBits(TEXT_FIRST_LETTER);
9235         }
9236         // Change this frame's length to the first-letter length right now
9237         // so that when we rebuild the textrun it will be built with the
9238         // right first-letter boundary
9239         SetLength(offset + length - GetContentOffset(), &aLineLayout,
9240                   ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9241         // Ensure that the textrun will be rebuilt
9242         ClearTextRuns();
9243       }
9244     }
9245   }
9246 
9247   float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
9248 
9249   if (!IsCurrentFontInflation(fontSizeInflation)) {
9250     // FIXME: Ideally, if we already have a text run, we'd move it to be
9251     // the uninflated text run.
9252     ClearTextRun(nullptr, nsTextFrame::eInflated);
9253     mFontMetrics = nullptr;
9254   }
9255 
9256   gfxSkipCharsIterator iter =
9257       EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
9258                     aLineLayout.GetLine(), &flowEndInTextRun);
9259 
9260   NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
9261                "EnsureTextRun should have set font size inflation");
9262 
9263   if (mTextRun && iter.GetOriginalEnd() < offset + length) {
9264     // The textrun does not map enough text for this frame. This can happen
9265     // when the textrun was ended in the middle of a text node because a
9266     // preformatted newline was encountered, and prev-in-flow frames have
9267     // consumed all the text of the textrun. We need a new textrun.
9268     ClearTextRuns();
9269     iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget, lineContainer,
9270                          aLineLayout.GetLine(), &flowEndInTextRun);
9271   }
9272 
9273   if (!mTextRun) {
9274     ClearMetrics(aMetrics);
9275     return;
9276   }
9277 
9278   NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(
9279                    offset + length) <= mTextRun->GetLength(),
9280                "Text run does not map enough text for our reflow");
9281 
9282   /////////////////////////////////////////////////////////////////////
9283   // See how much text should belong to this text frame, and measure it
9284   /////////////////////////////////////////////////////////////////////
9285 
9286   iter.SetOriginalOffset(offset);
9287   nscoord xOffsetForTabs =
9288       (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTab)
9289           ? (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
9290              lineContainer->GetUsedBorderAndPadding().left)
9291           : -1;
9292   PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
9293                             lineContainer, xOffsetForTabs,
9294                             nsTextFrame::eInflated);
9295 
9296   uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
9297 
9298   // The metrics for the text go in here
9299   gfxTextRun::Metrics textMetrics;
9300   gfxFont::BoundingBoxType boundingBoxType =
9301       IsFloatingFirstLetterChild() || IsInitialLetterChild()
9302           ? gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
9303           : gfxFont::LOOSE_INK_EXTENTS;
9304 
9305   int32_t limitLength = length;
9306   int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
9307   bool forceBreakAfter = false;
9308   if (forceBreak >= length) {
9309     forceBreakAfter = forceBreak == length;
9310     // The break is not within the text considered for this textframe.
9311     forceBreak = -1;
9312   }
9313   if (forceBreak >= 0) {
9314     limitLength = forceBreak;
9315   }
9316   // This is the heart of text reflow right here! We don't know where
9317   // to break, so we need to see how much text fits in the available width.
9318   uint32_t transformedLength;
9319   if (offset + limitLength >= int32_t(frag->GetLength())) {
9320     NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
9321                  "Content offset/length out of bounds");
9322     NS_ASSERTION(flowEndInTextRun >= transformedOffset,
9323                  "Negative flow length?");
9324     transformedLength = flowEndInTextRun - transformedOffset;
9325   } else {
9326     // we're not looking at all the content, so we need to compute the
9327     // length of the transformed substring we're looking at
9328     gfxSkipCharsIterator iter(provider.GetStart());
9329     iter.SetOriginalOffset(offset + limitLength);
9330     transformedLength = iter.GetSkippedOffset() - transformedOffset;
9331   }
9332   uint32_t transformedLastBreak = 0;
9333   bool usedHyphenation;
9334   gfxFloat trimmedWidth = 0;
9335   gfxFloat availWidth = aAvailableWidth;
9336   if (Style()->IsTextCombined()) {
9337     // If text-combine-upright is 'all', we would compress whatever long
9338     // text into ~1em width, so there is no limited on the avail width.
9339     availWidth = std::numeric_limits<gfxFloat>::infinity();
9340   }
9341   bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
9342                                    HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML);
9343 
9344   bool isBreakSpaces = textStyle->mWhiteSpace == StyleWhiteSpace::BreakSpaces;
9345   // allow whitespace to overflow the container
9346   bool whitespaceCanHang = textStyle->WhiteSpaceCanHangOrVisuallyCollapse();
9347   gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
9348   gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
9349   bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
9350   if (shouldSuppressLineBreak) {
9351     suppressBreak = gfxTextRun::eSuppressAllBreaks;
9352   } else if (!aLineLayout.LineIsBreakable()) {
9353     suppressBreak = gfxTextRun::eSuppressInitialBreak;
9354   }
9355   uint32_t transformedCharsFit = mTextRun->BreakAndMeasureText(
9356       transformedOffset, transformedLength, HasAnyStateBits(TEXT_START_OF_LINE),
9357       availWidth, &provider, suppressBreak,
9358       canTrimTrailingWhitespace ? &trimmedWidth : nullptr, whitespaceCanHang,
9359       &textMetrics, boundingBoxType, aDrawTarget, &usedHyphenation,
9360       &transformedLastBreak, textStyle->WordCanWrap(this), isBreakSpaces,
9361       &breakPriority);
9362   if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
9363     // If we're measuring a zero-length piece of text, update
9364     // the height manually.
9365     nsFontMetrics* fm = provider.GetFontMetrics();
9366     if (fm) {
9367       textMetrics.mAscent = gfxFloat(fm->MaxAscent());
9368       textMetrics.mDescent = gfxFloat(fm->MaxDescent());
9369     }
9370   }
9371   if (GetWritingMode().IsLineInverted()) {
9372     std::swap(textMetrics.mAscent, textMetrics.mDescent);
9373     textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
9374   }
9375   // The "end" iterator points to the first character after the string mapped
9376   // by this frame. Basically, its original-string offset is offset+charsFit
9377   // after we've computed charsFit.
9378   gfxSkipCharsIterator end(provider.GetEndHint());
9379   end.SetSkippedOffset(transformedOffset + transformedCharsFit);
9380   int32_t charsFit = end.GetOriginalOffset() - offset;
9381   if (offset + charsFit == newLineOffset) {
9382     // We broke before a trailing preformatted '\n'. The newline should
9383     // be assigned to this frame. Note that newLineOffset will be -1 if
9384     // there was no preformatted newline, so we wouldn't get here in that
9385     // case.
9386     ++charsFit;
9387   }
9388   // That might have taken us beyond our assigned content range (because
9389   // we might have advanced over some skipped chars that extend outside
9390   // this frame), so get back in.
9391   int32_t lastBreak = -1;
9392   if (charsFit >= limitLength) {
9393     charsFit = limitLength;
9394     if (transformedLastBreak != UINT32_MAX) {
9395       // lastBreak is needed.
9396       // This may set lastBreak greater than 'length', but that's OK
9397       lastBreak = end.ConvertSkippedToOriginal(transformedOffset +
9398                                                transformedLastBreak);
9399     }
9400     end.SetOriginalOffset(offset + charsFit);
9401     // If we were forced to fit, and the break position is after a soft hyphen,
9402     // note that this is a hyphenation break.
9403     if ((forceBreak >= 0 || forceBreakAfter) &&
9404         HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
9405       usedHyphenation = true;
9406     }
9407   }
9408   if (usedHyphenation) {
9409     // Fix up metrics to include hyphen
9410     AddHyphenToMetrics(this, mTextRun->IsRightToLeft(), &textMetrics,
9411                        boundingBoxType, aDrawTarget);
9412     AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
9413   }
9414   if (textMetrics.mBoundingBox.IsEmpty()) {
9415     AddStateBits(TEXT_NO_RENDERED_GLYPHS);
9416   }
9417 
9418   gfxFloat trimmableWidth = 0;
9419   bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
9420   if (canTrimTrailingWhitespace) {
9421     // Optimization: if we trimmed trailing whitespace, and we can be sure
9422     // this frame will be at the end of the line, then leave it trimmed off.
9423     // Otherwise we have to undo the trimming, in case we're not at the end of
9424     // the line. (If we actually do end up at the end of the line, we'll have
9425     // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
9426     // having to re-do it.)
9427     if (brokeText || HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
9428       // We're definitely going to break so our trailing whitespace should
9429       // definitely be trimmed. Record that we've already done it.
9430       AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
9431     } else if (!HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
9432       // We might not be at the end of the line. (Note that even if this frame
9433       // ends in breakable whitespace, it might not be at the end of the line
9434       // because it might be followed by breakable, but preformatted,
9435       // whitespace.) Undo the trimming.
9436       textMetrics.mAdvanceWidth += trimmedWidth;
9437       trimmableWidth = trimmedWidth;
9438       if (mTextRun->IsRightToLeft()) {
9439         // Space comes before text, so the bounding box is moved to the
9440         // right by trimmdWidth
9441         textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
9442       }
9443     }
9444   }
9445 
9446   if (!brokeText && lastBreak >= 0) {
9447     // Since everything fit and no break was forced,
9448     // record the last break opportunity
9449     NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= availWidth,
9450                  "If the text doesn't fit, and we have a break opportunity, "
9451                  "why didn't MeasureText use it?");
9452     MOZ_ASSERT(lastBreak >= offset, "Strange break position");
9453     aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset, true,
9454                                             breakPriority);
9455   }
9456 
9457   int32_t contentLength = offset + charsFit - GetContentOffset();
9458 
9459   /////////////////////////////////////////////////////////////////////
9460   // Compute output metrics
9461   /////////////////////////////////////////////////////////////////////
9462 
9463   // first-letter frames should use the tight bounding box metrics for
9464   // ascent/descent for good drop-cap effects
9465   if (HasAnyStateBits(TEXT_FIRST_LETTER)) {
9466     textMetrics.mAscent =
9467         std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
9468     textMetrics.mDescent =
9469         std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
9470   }
9471 
9472   // Setup metrics for caller
9473   // Disallow negative widths
9474   WritingMode wm = GetWritingMode();
9475   LogicalSize finalSize(wm);
9476   finalSize.ISize(wm) =
9477       NSToCoordCeil(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth));
9478 
9479   if (transformedCharsFit == 0 && !usedHyphenation) {
9480     aMetrics.SetBlockStartAscent(0);
9481     finalSize.BSize(wm) = 0;
9482   } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
9483     // Use actual text metrics for floating first letter frame.
9484     aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
9485     finalSize.BSize(wm) =
9486         aMetrics.BlockStartAscent() + NSToCoordCeil(textMetrics.mDescent);
9487   } else {
9488     // Otherwise, ascent should contain the overline drawable area.
9489     // And also descent should contain the underline drawable area.
9490     // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
9491     nsFontMetrics* fm = provider.GetFontMetrics();
9492     nscoord fontAscent =
9493         wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
9494     nscoord fontDescent =
9495         wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
9496     aMetrics.SetBlockStartAscent(
9497         std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
9498     nscoord descent =
9499         std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
9500     finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
9501   }
9502   if (Style()->IsTextCombined()) {
9503     nsFontMetrics* fm = provider.GetFontMetrics();
9504     gfxFloat width = finalSize.ISize(wm);
9505     gfxFloat em = fm->EmHeight();
9506     // Compress the characters in horizontal axis if necessary.
9507     if (width <= em) {
9508       RemoveProperty(TextCombineScaleFactorProperty());
9509     } else {
9510       SetProperty(TextCombineScaleFactorProperty(), em / width);
9511       finalSize.ISize(wm) = em;
9512     }
9513     // Make the characters be in an 1em square.
9514     if (finalSize.BSize(wm) != em) {
9515       aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
9516                                    (em - finalSize.BSize(wm)) / 2);
9517       finalSize.BSize(wm) = em;
9518     }
9519   }
9520   aMetrics.SetSize(wm, finalSize);
9521 
9522   NS_ASSERTION(aMetrics.BlockStartAscent() >= 0, "Negative ascent???");
9523   NS_ASSERTION(
9524       (Style()->IsTextCombined() ? aMetrics.ISize(aMetrics.GetWritingMode())
9525                                  : aMetrics.BSize(aMetrics.GetWritingMode())) -
9526               aMetrics.BlockStartAscent() >=
9527           0,
9528       "Negative descent???");
9529 
9530   mAscent = aMetrics.BlockStartAscent();
9531 
9532   // Handle text that runs outside its normal bounds.
9533   nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
9534   if (mTextRun->IsVertical()) {
9535     // Swap line-relative textMetrics dimensions to physical coordinates.
9536     std::swap(boundingBox.x, boundingBox.y);
9537     std::swap(boundingBox.width, boundingBox.height);
9538     if (GetWritingMode().IsVerticalRL()) {
9539       boundingBox.x = -boundingBox.XMost();
9540       boundingBox.x += aMetrics.Width() - mAscent;
9541     } else {
9542       boundingBox.x += mAscent;
9543     }
9544   } else {
9545     boundingBox.y += mAscent;
9546   }
9547   aMetrics.SetOverflowAreasToDesiredBounds();
9548   aMetrics.InkOverflow().UnionRect(aMetrics.InkOverflow(), boundingBox);
9549 
9550   // When we have text decorations, we don't need to compute their overflow now
9551   // because we're guaranteed to do it later
9552   // (see nsLineLayout::RelativePositionFrames)
9553   UnionAdditionalOverflow(presContext, aLineLayout.LineContainerRI()->mFrame,
9554                           provider, &aMetrics.InkOverflow(), false, true);
9555 
9556   /////////////////////////////////////////////////////////////////////
9557   // Clean up, update state
9558   /////////////////////////////////////////////////////////////////////
9559 
9560   // If all our characters are discarded or collapsed, then trimmable width
9561   // from the last textframe should be preserved. Otherwise the trimmable width
9562   // from this textframe overrides. (Currently in CSS trimmable width can be
9563   // at most one space so there's no way for trimmable width from a previous
9564   // frame to accumulate with trimmable width from this frame.)
9565   if (transformedCharsFit > 0) {
9566     aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWidth));
9567     AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
9568   }
9569   bool breakAfter = forceBreakAfter;
9570   if (!shouldSuppressLineBreak) {
9571     if (charsFit > 0 && charsFit == length &&
9572         textStyle->mHyphens != StyleHyphens::None &&
9573         HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
9574       bool fits =
9575           textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
9576       // Record a potential break after final soft hyphen
9577       aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
9578                                               gfxBreakPriority::eNormalBreak);
9579     }
9580     // length == 0 means either the text is empty or it's all collapsed away
9581     bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
9582     if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
9583         transformedOffset + transformedLength == mTextRun->GetLength() &&
9584         (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::HasTrailingBreak)) {
9585       // We placed all the text in the textrun and we have a break opportunity
9586       // at the end of the textrun. We need to record it because the following
9587       // content may not care about nsLineBreaker.
9588 
9589       // Note that because we didn't break, we can be sure that (thanks to the
9590       // code up above) textMetrics.mAdvanceWidth includes the width of any
9591       // trailing whitespace. So we need to subtract trimmableWidth here
9592       // because if we did break at this point, that much width would be
9593       // trimmed.
9594       if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
9595         breakAfter = true;
9596       } else {
9597         aLineLayout.NotifyOptionalBreakPosition(this, length, true,
9598                                                 gfxBreakPriority::eNormalBreak);
9599       }
9600     }
9601   }
9602 
9603   // Compute reflow status
9604   if (contentLength != maxContentLength) {
9605     aStatus.SetIncomplete();
9606   }
9607 
9608   if (charsFit == 0 && length > 0 && !usedHyphenation) {
9609     // Couldn't place any text
9610     aStatus.SetInlineLineBreakBeforeAndReset();
9611   } else if (contentLength > 0 &&
9612              mContentOffset + contentLength - 1 == newLineOffset) {
9613     // Ends in \n
9614     aStatus.SetInlineLineBreakAfter();
9615     aLineLayout.SetLineEndsInBR(true);
9616   } else if (breakAfter) {
9617     aStatus.SetInlineLineBreakAfter();
9618   }
9619   if (completedFirstLetter) {
9620     aLineLayout.SetFirstLetterStyleOK(false);
9621     aStatus.SetFirstLetterComplete();
9622   }
9623 
9624   // Updated the cached NewlineProperty, or delete it.
9625   if (contentLength < maxContentLength &&
9626       textStyle->NewlineIsSignificant(this) &&
9627       (contentNewLineOffset < 0 ||
9628        mContentOffset + contentLength <= contentNewLineOffset)) {
9629     if (!cachedNewlineOffset) {
9630       cachedNewlineOffset = new NewlineProperty;
9631       if (NS_FAILED(mContent->SetProperty(
9632               nsGkAtoms::newline, cachedNewlineOffset,
9633               nsINode::DeleteProperty<NewlineProperty>))) {
9634         delete cachedNewlineOffset;
9635         cachedNewlineOffset = nullptr;
9636       }
9637       mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
9638     }
9639     if (cachedNewlineOffset) {
9640       cachedNewlineOffset->mStartOffset = offset;
9641       cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
9642     }
9643   } else if (cachedNewlineOffset) {
9644     mContent->RemoveProperty(nsGkAtoms::newline);
9645     mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
9646   }
9647 
9648   // Compute space and letter counts for justification, if required
9649   if (!textStyle->WhiteSpaceIsSignificant() &&
9650       (lineContainer->StyleText()->mTextAlign == StyleTextAlign::Justify ||
9651        lineContainer->StyleText()->mTextAlignLast ==
9652            StyleTextAlignLast::Justify ||
9653        shouldSuppressLineBreak) &&
9654       !SVGUtils::IsInSVGTextSubtree(lineContainer)) {
9655     AddStateBits(TEXT_JUSTIFICATION_ENABLED);
9656     Range range(uint32_t(offset), uint32_t(offset + charsFit));
9657     aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
9658   }
9659 
9660   SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9661 
9662   InvalidateFrame();
9663 
9664 #ifdef NOISY_REFLOW
9665   ListTag(stdout);
9666   printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.Width(),
9667          aMetrics.Height(), aMetrics.BlockStartAscent(), aStatus);
9668 #endif
9669 }
9670 
9671 /* virtual */
CanContinueTextRun() const9672 bool nsTextFrame::CanContinueTextRun() const {
9673   // We can continue a text run through a text frame
9674   return true;
9675 }
9676 
TrimTrailingWhiteSpace(DrawTarget * aDrawTarget)9677 nsTextFrame::TrimOutput nsTextFrame::TrimTrailingWhiteSpace(
9678     DrawTarget* aDrawTarget) {
9679   MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),
9680              "frame should have been reflowed");
9681 
9682   TrimOutput result;
9683   result.mChanged = false;
9684   result.mDeltaWidth = 0;
9685 
9686   AddStateBits(TEXT_END_OF_LINE);
9687 
9688   if (!GetTextRun(nsTextFrame::eInflated)) {
9689     // If reflow didn't create a textrun, there must have been no content once
9690     // leading whitespace was trimmed, so nothing more to do here.
9691     return result;
9692   }
9693 
9694   int32_t contentLength = GetContentLength();
9695   if (!contentLength) return result;
9696 
9697   gfxSkipCharsIterator start =
9698       EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
9699   NS_ENSURE_TRUE(mTextRun, result);
9700 
9701   uint32_t trimmedStart = start.GetSkippedOffset();
9702 
9703   const nsTextFragment* frag = TextFragment();
9704   TrimmedOffsets trimmed = GetTrimmedOffsets(frag);
9705   gfxSkipCharsIterator trimmedEndIter = start;
9706   const nsStyleText* textStyle = StyleText();
9707   gfxFloat delta = 0;
9708   uint32_t trimmedEnd =
9709       trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
9710 
9711   if (!HasAnyStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE) &&
9712       trimmed.GetEnd() < GetContentEnd()) {
9713     gfxSkipCharsIterator end = trimmedEndIter;
9714     uint32_t endOffset =
9715         end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
9716     if (trimmedEnd < endOffset) {
9717       // We can't be dealing with tabs here ... they wouldn't be trimmed. So
9718       // it's OK to pass null for the line container.
9719       PropertyProvider provider(mTextRun, textStyle, frag, this, start,
9720                                 contentLength, nullptr, 0,
9721                                 nsTextFrame::eInflated);
9722       delta =
9723           mTextRun->GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
9724       result.mChanged = true;
9725     }
9726   }
9727 
9728   gfxFloat advanceDelta;
9729   mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
9730                           HasAnyStateBits(TEXT_START_OF_LINE), true,
9731                           &advanceDelta);
9732   if (advanceDelta != 0) {
9733     result.mChanged = true;
9734   }
9735 
9736   // aDeltaWidth is *subtracted* from our width.
9737   // If advanceDelta is positive then setting the line break made us longer,
9738   // so aDeltaWidth could go negative.
9739   result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
9740   // If aDeltaWidth goes negative, that means this frame might not actually fit
9741   // anymore!!! We need higher level line layout to recover somehow.
9742   // If it's because the frame has a soft hyphen that is now being displayed,
9743   // this should actually be OK, because our reflow recorded the break
9744   // opportunity that allowed the soft hyphen to be used, and we wouldn't
9745   // have recorded the opportunity unless the hyphen fit (or was the first
9746   // opportunity on the line).
9747   // Otherwise this can/ really only happen when we have glyphs with special
9748   // shapes at the end of lines, I think. Breaking inside a kerning pair won't
9749   // do it because that would mean we broke inside this textrun, and
9750   // BreakAndMeasureText should make sure the resulting shaped substring fits.
9751   // Maybe if we passed a maxTextLength? But that only happens at direction
9752   // changes (so we wouldn't kern across the boundary) or for first-letter
9753   // (which always fits because it starts the line!).
9754   NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,
9755                        "Negative deltawidth, something odd is happening");
9756 
9757 #ifdef NOISY_TRIM
9758   ListTag(stdout);
9759   printf(": trim => %d\n", result.mDeltaWidth);
9760 #endif
9761   return result;
9762 }
9763 
RecomputeOverflow(nsIFrame * aBlockFrame,bool aIncludeShadows)9764 OverflowAreas nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame,
9765                                              bool aIncludeShadows) {
9766   RemoveProperty(WebRenderTextBounds());
9767 
9768   nsRect bounds(nsPoint(0, 0), GetSize());
9769   OverflowAreas result(bounds, bounds);
9770 
9771   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
9772   if (!mTextRun) return result;
9773 
9774   PropertyProvider provider(this, iter, nsTextFrame::eInflated, mFontMetrics);
9775   // Don't trim trailing space, in case we need to paint it as selected.
9776   provider.InitializeForDisplay(false);
9777 
9778   gfxTextRun::Metrics textMetrics =
9779       mTextRun->MeasureText(ComputeTransformedRange(provider),
9780                             gfxFont::LOOSE_INK_EXTENTS, nullptr, &provider);
9781   if (GetWritingMode().IsLineInverted()) {
9782     textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
9783   }
9784   nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
9785   boundingBox += nsPoint(0, mAscent);
9786   if (mTextRun->IsVertical()) {
9787     // Swap line-relative textMetrics dimensions to physical coordinates.
9788     std::swap(boundingBox.x, boundingBox.y);
9789     std::swap(boundingBox.width, boundingBox.height);
9790   }
9791   nsRect& vis = result.InkOverflow();
9792   vis.UnionRect(vis, boundingBox);
9793   UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true,
9794                           aIncludeShadows);
9795   return result;
9796 }
9797 
TransformChars(nsTextFrame * aFrame,const nsStyleText * aStyle,const gfxTextRun * aTextRun,uint32_t aSkippedOffset,const nsTextFragment * aFrag,int32_t aFragOffset,int32_t aFragLen,nsAString & aOut)9798 static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
9799                            const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
9800                            const nsTextFragment* aFrag, int32_t aFragOffset,
9801                            int32_t aFragLen, nsAString& aOut) {
9802   nsAutoString fragString;
9803   char16_t* out;
9804   if (aStyle->mTextTransform.IsNone() && !NeedsToMaskPassword(aFrame)) {
9805     // No text-transform, so we can copy directly to the output string.
9806     aOut.SetLength(aOut.Length() + aFragLen);
9807     out = aOut.EndWriting() - aFragLen;
9808   } else {
9809     // Use a temporary string as source for the transform.
9810     fragString.SetLength(aFragLen);
9811     out = fragString.BeginWriting();
9812   }
9813 
9814   // Copy the text, with \n and \t replaced by <space> if appropriate.
9815   for (int32_t i = 0; i < aFragLen; ++i) {
9816     char16_t ch = aFrag->CharAt(aFragOffset + i);
9817     if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
9818         (ch == '\t' && !aStyle->TabIsSignificant())) {
9819       ch = ' ';
9820     }
9821     out[i] = ch;
9822   }
9823 
9824   if (!aStyle->mTextTransform.IsNone() || NeedsToMaskPassword(aFrame)) {
9825     MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed);
9826     if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
9827       // Apply text-transform according to style in the transformed run.
9828       auto transformedTextRun =
9829           static_cast<const nsTransformedTextRun*>(aTextRun);
9830       nsAutoString convertedString;
9831       AutoTArray<bool, 50> charsToMergeArray;
9832       AutoTArray<bool, 50> deletedCharsArray;
9833       nsCaseTransformTextRunFactory::TransformString(
9834           fragString, convertedString, /* aAllUppercase = */ false,
9835           /* aCaseTransformsOnly = */ true, nullptr, charsToMergeArray,
9836           deletedCharsArray, transformedTextRun, aSkippedOffset);
9837       aOut.Append(convertedString);
9838     } else {
9839       // Should not happen (see assertion above), but as a fallback...
9840       aOut.Append(fragString);
9841     }
9842   }
9843 }
9844 
LineStartsOrEndsAtHardLineBreak(nsTextFrame * aFrame,nsBlockFrame * aLineContainer,bool * aStartsAtHardBreak,bool * aEndsAtHardBreak)9845 static void LineStartsOrEndsAtHardLineBreak(nsTextFrame* aFrame,
9846                                             nsBlockFrame* aLineContainer,
9847                                             bool* aStartsAtHardBreak,
9848                                             bool* aEndsAtHardBreak) {
9849   bool foundValidLine;
9850   nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
9851   if (!foundValidLine) {
9852     NS_ERROR("Invalid line!");
9853     *aStartsAtHardBreak = *aEndsAtHardBreak = true;
9854     return;
9855   }
9856 
9857   *aEndsAtHardBreak = !iter.GetLine()->IsLineWrapped();
9858   if (iter.Prev()) {
9859     *aStartsAtHardBreak = !iter.GetLine()->IsLineWrapped();
9860   } else {
9861     // Hit block boundary
9862     *aStartsAtHardBreak = true;
9863   }
9864 }
9865 
GetRenderedText(uint32_t aStartOffset,uint32_t aEndOffset,TextOffsetType aOffsetType,TrailingWhitespace aTrimTrailingWhitespace)9866 nsIFrame::RenderedText nsTextFrame::GetRenderedText(
9867     uint32_t aStartOffset, uint32_t aEndOffset, TextOffsetType aOffsetType,
9868     TrailingWhitespace aTrimTrailingWhitespace) {
9869   MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets");
9870   MOZ_ASSERT(!GetPrevContinuation() ||
9871                  (aOffsetType == TextOffsetType::OffsetsInContentText &&
9872                   aStartOffset >= (uint32_t)GetContentOffset() &&
9873                   aEndOffset <= (uint32_t)GetContentEnd()),
9874              "Must be called on first-in-flow, or content offsets must be "
9875              "given and be within this frame.");
9876 
9877   // The handling of offsets could be more efficient...
9878   RenderedText result;
9879   nsBlockFrame* lineContainer = nullptr;
9880   nsTextFrame* textFrame;
9881   const nsTextFragment* textFrag = TextFragment();
9882   uint32_t offsetInRenderedString = 0;
9883   bool haveOffsets = false;
9884 
9885   Maybe<nsBlockFrame::AutoLineCursorSetup> autoLineCursor;
9886   for (textFrame = this; textFrame;
9887        textFrame = textFrame->GetNextContinuation()) {
9888     if (textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY)) {
9889       // We don't trust dirty frames, especially when computing rendered text.
9890       break;
9891     }
9892 
9893     // Ensure the text run and grab the gfxSkipCharsIterator for it
9894     gfxSkipCharsIterator iter =
9895         textFrame->EnsureTextRun(nsTextFrame::eInflated);
9896     if (!textFrame->mTextRun) {
9897       break;
9898     }
9899     gfxSkipCharsIterator tmpIter = iter;
9900 
9901     // Check if the frame starts/ends at a hard line break, to determine
9902     // whether whitespace should be trimmed.
9903     bool startsAtHardBreak, endsAtHardBreak;
9904     if (!HasAnyStateBits(TEXT_START_OF_LINE | TEXT_END_OF_LINE)) {
9905       startsAtHardBreak = endsAtHardBreak = false;
9906     } else if (nsBlockFrame* thisLc =
9907                    do_QueryFrame(FindLineContainer(textFrame))) {
9908       if (thisLc != lineContainer) {
9909         // Setup line cursor when needed.
9910         lineContainer = thisLc;
9911         autoLineCursor.reset();
9912         autoLineCursor.emplace(lineContainer);
9913       }
9914       LineStartsOrEndsAtHardLineBreak(textFrame, lineContainer,
9915                                       &startsAtHardBreak, &endsAtHardBreak);
9916     } else {
9917       // Weird situation where we have a line layout without a block.
9918       // No soft breaks occur in this situation.
9919       startsAtHardBreak = endsAtHardBreak = true;
9920     }
9921 
9922     // Whether we need to trim whitespaces after the text frame.
9923     // TrimmedOffsetFlags::Default will allow trimming; we set NoTrim* flags
9924     // in the cases where this should not occur.
9925     TrimmedOffsetFlags trimFlags = TrimmedOffsetFlags::Default;
9926     if (!textFrame->IsAtEndOfLine() ||
9927         aTrimTrailingWhitespace != TrailingWhitespace::Trim ||
9928         !endsAtHardBreak) {
9929       trimFlags |= TrimmedOffsetFlags::NoTrimAfter;
9930     }
9931 
9932     // Whether to trim whitespaces before the text frame.
9933     if (!startsAtHardBreak) {
9934       trimFlags |= TrimmedOffsetFlags::NoTrimBefore;
9935     }
9936 
9937     TrimmedOffsets trimmedOffsets =
9938         textFrame->GetTrimmedOffsets(textFrag, trimFlags);
9939     bool trimmedSignificantNewline =
9940         trimmedOffsets.GetEnd() < GetContentEnd() &&
9941         HasSignificantTerminalNewline();
9942     uint32_t skippedToRenderedStringOffset =
9943         offsetInRenderedString -
9944         tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
9945     uint32_t nextOffsetInRenderedString =
9946         tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
9947         (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
9948 
9949     if (aOffsetType == TextOffsetType::OffsetsInRenderedText) {
9950       if (nextOffsetInRenderedString <= aStartOffset) {
9951         offsetInRenderedString = nextOffsetInRenderedString;
9952         continue;
9953       }
9954       if (!haveOffsets) {
9955         result.mOffsetWithinNodeText = tmpIter.ConvertSkippedToOriginal(
9956             aStartOffset - skippedToRenderedStringOffset);
9957         result.mOffsetWithinNodeRenderedText = aStartOffset;
9958         haveOffsets = true;
9959       }
9960       if (offsetInRenderedString >= aEndOffset) {
9961         break;
9962       }
9963     } else {
9964       if (uint32_t(textFrame->GetContentEnd()) <= aStartOffset) {
9965         offsetInRenderedString = nextOffsetInRenderedString;
9966         continue;
9967       }
9968       if (!haveOffsets) {
9969         result.mOffsetWithinNodeText = aStartOffset;
9970         // Skip trimmed space when computed the rendered text offset.
9971         int32_t clamped =
9972             std::max<int32_t>(aStartOffset, trimmedOffsets.mStart);
9973         result.mOffsetWithinNodeRenderedText =
9974             tmpIter.ConvertOriginalToSkipped(clamped) +
9975             skippedToRenderedStringOffset;
9976         MOZ_ASSERT(
9977             result.mOffsetWithinNodeRenderedText >= offsetInRenderedString &&
9978                 result.mOffsetWithinNodeRenderedText <= INT32_MAX,
9979             "Bad offset within rendered text");
9980         haveOffsets = true;
9981       }
9982       if (uint32_t(textFrame->mContentOffset) >= aEndOffset) {
9983         break;
9984       }
9985     }
9986 
9987     int32_t startOffset;
9988     int32_t endOffset;
9989     if (aOffsetType == TextOffsetType::OffsetsInRenderedText) {
9990       startOffset = tmpIter.ConvertSkippedToOriginal(
9991           aStartOffset - skippedToRenderedStringOffset);
9992       endOffset = tmpIter.ConvertSkippedToOriginal(
9993           aEndOffset - skippedToRenderedStringOffset);
9994     } else {
9995       startOffset = aStartOffset;
9996       endOffset = std::min<uint32_t>(INT32_MAX, aEndOffset);
9997     }
9998 
9999     // If startOffset and/or endOffset are inside of trimmedOffsets' range,
10000     // then clamp the edges of trimmedOffsets accordingly.
10001     int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
10002     trimmedOffsets.mStart =
10003         std::max<uint32_t>(trimmedOffsets.mStart, startOffset);
10004     trimmedOffsets.mLength =
10005         std::min<uint32_t>(origTrimmedOffsetsEnd, endOffset) -
10006         trimmedOffsets.mStart;
10007     if (trimmedOffsets.mLength <= 0) {
10008       offsetInRenderedString = nextOffsetInRenderedString;
10009       continue;
10010     }
10011 
10012     const nsStyleText* textStyle = textFrame->StyleText();
10013     iter.SetOriginalOffset(trimmedOffsets.mStart);
10014     while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
10015       int32_t runLength;
10016       bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
10017       runLength = std::min(runLength,
10018                            trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
10019       if (isSkipped) {
10020         for (int32_t i = 0; i < runLength; ++i) {
10021           char16_t ch = textFrag->CharAt(iter.GetOriginalOffset() + i);
10022           if (ch == CH_SHY) {
10023             // We should preserve soft hyphens. They can't be transformed.
10024             result.mString.Append(ch);
10025           }
10026         }
10027       } else {
10028         TransformChars(textFrame, textStyle, textFrame->mTextRun,
10029                        iter.GetSkippedOffset(), textFrag,
10030                        iter.GetOriginalOffset(), runLength, result.mString);
10031       }
10032       iter.AdvanceOriginal(runLength);
10033     }
10034 
10035     if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
10036       // A significant newline was trimmed off (we must be
10037       // white-space:pre-line). Put it back.
10038       result.mString.Append('\n');
10039     }
10040     offsetInRenderedString = nextOffsetInRenderedString;
10041   }
10042 
10043   if (!haveOffsets) {
10044     result.mOffsetWithinNodeText = textFrag->GetLength();
10045     result.mOffsetWithinNodeRenderedText = offsetInRenderedString;
10046   }
10047   return result;
10048 }
10049 
10050 /* virtual */
IsEmpty()10051 bool nsTextFrame::IsEmpty() {
10052   NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
10053                    !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
10054                "Invalid state");
10055 
10056   // XXXldb Should this check compatibility mode as well???
10057   const nsStyleText* textStyle = StyleText();
10058   if (textStyle->WhiteSpaceIsSignificant()) {
10059     // XXX shouldn't we return true if the length is zero?
10060     return false;
10061   }
10062 
10063   if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
10064     return false;
10065   }
10066 
10067   if (mState & TEXT_IS_ONLY_WHITESPACE) {
10068     return true;
10069   }
10070 
10071   bool isEmpty =
10072       IsAllWhitespace(TextFragment(), textStyle->mWhiteSpace !=
10073                                           mozilla::StyleWhiteSpace::PreLine);
10074   AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
10075   return isEmpty;
10076 }
10077 
10078 #ifdef DEBUG_FRAME_DUMP
10079 // Translate the mapped content into a string that's printable
ToCString(nsCString & aBuf,int32_t * aTotalContentLength) const10080 void nsTextFrame::ToCString(nsCString& aBuf,
10081                             int32_t* aTotalContentLength) const {
10082   // Get the frames text content
10083   const nsTextFragment* frag = TextFragment();
10084   if (!frag) {
10085     return;
10086   }
10087 
10088   // Compute the total length of the text content.
10089   *aTotalContentLength = frag->GetLength();
10090 
10091   int32_t contentLength = GetContentLength();
10092   // Set current fragment and current fragment offset
10093   if (0 == contentLength) {
10094     return;
10095   }
10096   int32_t fragOffset = GetContentOffset();
10097   int32_t n = fragOffset + contentLength;
10098   while (fragOffset < n) {
10099     char16_t ch = frag->CharAt(fragOffset++);
10100     if (ch == '\r') {
10101       aBuf.AppendLiteral("\\r");
10102     } else if (ch == '\n') {
10103       aBuf.AppendLiteral("\\n");
10104     } else if (ch == '\t') {
10105       aBuf.AppendLiteral("\\t");
10106     } else if ((ch < ' ') || (ch >= 127)) {
10107       aBuf.Append(nsPrintfCString("\\u%04x", ch));
10108     } else {
10109       aBuf.Append(ch);
10110     }
10111   }
10112 }
10113 
GetFrameName(nsAString & aResult) const10114 nsresult nsTextFrame::GetFrameName(nsAString& aResult) const {
10115   MakeFrameName(u"Text"_ns, aResult);
10116   int32_t totalContentLength;
10117   nsAutoCString tmp;
10118   ToCString(tmp, &totalContentLength);
10119   tmp.SetLength(std::min<size_t>(tmp.Length(), 50u));
10120   aResult += u"\""_ns + NS_ConvertASCIItoUTF16(tmp) + u"\""_ns;
10121   return NS_OK;
10122 }
10123 
List(FILE * out,const char * aPrefix,ListFlags aFlags) const10124 void nsTextFrame::List(FILE* out, const char* aPrefix, ListFlags aFlags) const {
10125   nsCString str;
10126   ListGeneric(str, aPrefix, aFlags);
10127 
10128   str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
10129 
10130   // Output the first/last content offset and prev/next in flow info
10131   bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
10132   str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
10133                          isComplete ? 'T' : 'F');
10134 
10135   if (IsSelected()) {
10136     str += " SELECTED";
10137   }
10138   fprintf_stderr(out, "%s\n", str.get());
10139 }
10140 
ListTextRuns(FILE * out,nsTHashSet<const void * > & aSeen) const10141 void nsTextFrame::ListTextRuns(FILE* out,
10142                                nsTHashSet<const void*>& aSeen) const {
10143   if (!mTextRun || aSeen.Contains(mTextRun)) {
10144     return;
10145   }
10146   aSeen.Insert(mTextRun);
10147   mTextRun->Dump(out);
10148 }
10149 #endif
10150 
AdjustOffsetsForBidi(int32_t aStart,int32_t aEnd)10151 void nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) {
10152   AddStateBits(NS_FRAME_IS_BIDI);
10153   if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
10154     mContent->RemoveProperty(nsGkAtoms::flowlength);
10155     mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
10156   }
10157 
10158   /*
10159    * After Bidi resolution we may need to reassign text runs.
10160    * This is called during bidi resolution from the block container, so we
10161    * shouldn't be holding a local reference to a textrun anywhere.
10162    */
10163   ClearTextRuns();
10164 
10165   nsTextFrame* prev = GetPrevContinuation();
10166   if (prev) {
10167     // the bidi resolver can be very evil when columns/pages are involved. Don't
10168     // let it violate our invariants.
10169     int32_t prevOffset = prev->GetContentOffset();
10170     aStart = std::max(aStart, prevOffset);
10171     aEnd = std::max(aEnd, prevOffset);
10172     prev->ClearTextRuns();
10173   }
10174 
10175   mContentOffset = aStart;
10176   SetLength(aEnd - aStart, nullptr, 0);
10177 }
10178 
10179 /**
10180  * @return true if this text frame ends with a newline character.  It should
10181  * return false if it is not a text frame.
10182  */
HasSignificantTerminalNewline() const10183 bool nsTextFrame::HasSignificantTerminalNewline() const {
10184   return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
10185 }
10186 
IsAtEndOfLine() const10187 bool nsTextFrame::IsAtEndOfLine() const {
10188   return HasAnyStateBits(TEXT_END_OF_LINE);
10189 }
10190 
GetLogicalBaseline(WritingMode aWM) const10191 nscoord nsTextFrame::GetLogicalBaseline(WritingMode aWM) const {
10192   if (!aWM.IsOrthogonalTo(GetWritingMode())) {
10193     return mAscent;
10194   }
10195 
10196   // When the text frame has a writing mode orthogonal to the desired
10197   // writing mode, return a baseline coincides its parent frame.
10198   nsIFrame* parent = GetParent();
10199   nsPoint position = GetNormalPosition();
10200   nscoord parentAscent = parent->GetLogicalBaseline(aWM);
10201   if (aWM.IsVerticalRL()) {
10202     nscoord parentDescent = parent->GetSize().width - parentAscent;
10203     nscoord descent = parentDescent - position.x;
10204     return GetSize().width - descent;
10205   }
10206   return parentAscent - (aWM.IsVertical() ? position.x : position.y);
10207 }
10208 
HasAnyNoncollapsedCharacters()10209 bool nsTextFrame::HasAnyNoncollapsedCharacters() {
10210   gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
10211   int32_t offset = GetContentOffset(), offsetEnd = GetContentEnd();
10212   int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
10213   int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
10214   return skippedOffset != skippedOffsetEnd;
10215 }
10216 
ComputeCustomOverflow(OverflowAreas & aOverflowAreas)10217 bool nsTextFrame::ComputeCustomOverflow(OverflowAreas& aOverflowAreas) {
10218   return ComputeCustomOverflowInternal(aOverflowAreas, true);
10219 }
10220 
ComputeCustomOverflowInternal(OverflowAreas & aOverflowAreas,bool aIncludeShadows)10221 bool nsTextFrame::ComputeCustomOverflowInternal(OverflowAreas& aOverflowAreas,
10222                                                 bool aIncludeShadows) {
10223   if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
10224     return true;
10225   }
10226 
10227   nsIFrame* decorationsBlock;
10228   if (IsFloatingFirstLetterChild()) {
10229     decorationsBlock = GetParent();
10230   } else {
10231     nsIFrame* f = this;
10232     for (;;) {
10233       nsBlockFrame* fBlock = do_QueryFrame(f);
10234       if (fBlock) {
10235         decorationsBlock = fBlock;
10236         break;
10237       }
10238 
10239       f = f->GetParent();
10240       if (!f) {
10241         NS_ERROR("Couldn't find any block ancestor (for text decorations)");
10242         return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
10243       }
10244     }
10245   }
10246 
10247   aOverflowAreas = RecomputeOverflow(decorationsBlock, aIncludeShadows);
10248   return nsIFrame::ComputeCustomOverflow(aOverflowAreas);
10249 }
10250 
NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty,int32_t)10251 NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)
10252 
10253 void nsTextFrame::AssignJustificationGaps(
10254     const mozilla::JustificationAssignment& aAssign) {
10255   int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
10256   static_assert(sizeof(aAssign) == 1,
10257                 "The encoding might be broken if JustificationAssignment "
10258                 "is larger than 1 byte");
10259   SetProperty(JustificationAssignmentProperty(), encoded);
10260 }
10261 
GetJustificationAssignment() const10262 mozilla::JustificationAssignment nsTextFrame::GetJustificationAssignment()
10263     const {
10264   int32_t encoded = GetProperty(JustificationAssignmentProperty());
10265   mozilla::JustificationAssignment result;
10266   result.mGapsAtStart = encoded >> 8;
10267   result.mGapsAtEnd = encoded & 0xFF;
10268   return result;
10269 }
10270 
CountGraphemeClusters() const10271 uint32_t nsTextFrame::CountGraphemeClusters() const {
10272   const nsTextFragment* frag = TextFragment();
10273   MOZ_ASSERT(frag, "Text frame must have text fragment");
10274   nsAutoString content;
10275   frag->AppendTo(content, GetContentOffset(), GetContentLength());
10276   return unicode::CountGraphemeClusters(content.Data(), content.Length());
10277 }
10278 
HasNonSuppressedText() const10279 bool nsTextFrame::HasNonSuppressedText() const {
10280   if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
10281                       // If we haven't reflowed yet, or are currently doing so,
10282                       // just return true because we can't be sure.
10283                       NS_FRAME_FIRST_REFLOW | NS_FRAME_IN_REFLOW)) {
10284     return true;
10285   }
10286 
10287   if (!GetTextRun(nsTextFrame::eInflated)) {
10288     return false;
10289   }
10290 
10291   TrimmedOffsets offsets =
10292       GetTrimmedOffsets(TextFragment(), TrimmedOffsetFlags::NoTrimAfter);
10293   return offsets.mLength != 0;
10294 }
10295