Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at */
/* rendering object for textual content of elements */
#include "nsTextFrame.h"
#include "gfx2DGlue.h"
#include "gfxUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/Likely.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPresData.h"
#include "mozilla/SVGTextFrame.h"
#include "mozilla/SVGUtils.h"
#include "mozilla/TextEditor.h"
#include "mozilla/TextEvents.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/Unused.h"
#include "mozilla/PodOperations.h"
#include "nsCOMPtr.h"
#include "nsBlockFrame.h"
#include "nsFontMetrics.h"
#include "nsSplittableFrame.h"
#include "nsLineLayout.h"
#include "nsString.h"
#include "nsUnicharUtils.h"
#include "nsPresContext.h"
#include "nsIContent.h"
#include "nsStyleConsts.h"
#include "nsStyleStruct.h"
#include "nsStyleStructInlines.h"
#include "nsCoord.h"
#include "gfxContext.h"
#include "nsTArray.h"
#include "nsCSSPseudoElements.h"
#include "nsCSSFrameConstructor.h"
#include "nsCompatibility.h"
#include "nsCSSColorUtils.h"
#include "nsLayoutUtils.h"
#include "nsDisplayList.h"
#include "nsIFrame.h"
#include "nsIMathMLFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsTextFrameUtils.h"
#include "nsTextRunTransformations.h"
#include "MathMLTextRunFactory.h"
#include "nsUnicodeProperties.h"
#include "nsStyleUtil.h"
#include "nsRubyFrame.h"
#include "TextDrawTarget.h"
#include "nsTextFragment.h"
#include "nsGkAtoms.h"
#include "nsFrameSelection.h"
#include "nsRange.h"
#include "nsCSSRendering.h"
#include "nsContentUtils.h"
#include "nsLineBreaker.h"
#include "nsIFrameInlines.h"
#include "mozilla/intl/WordBreaker.h"
#include "mozilla/ServoStyleSet.h"
#include <algorithm>
#include <limits>
#include <type_traits>
# include "nsAccessibilityService.h"
#include "nsPrintfCString.h"
#include "mozilla/gfx/DrawTargetRecording.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/Element.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/ProfilerLabels.h"
#ifdef DEBUG
# undef NOISY_TRIM
# undef NOISY_TRIM
#ifdef DrawText
# undef DrawText
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
typedef mozilla::layout::TextDrawTarget TextDrawTarget;
static bool NeedsToMaskPassword(nsTextFrame* aFrame) {
return aFrame->GetContent()->HasFlag(NS_MAYBE_MASKED);
struct TabWidth {
TabWidth(uint32_t aOffset, uint32_t aWidth)
: mOffset(aOffset), mWidth(float(aWidth)) {}
uint32_t mOffset; // DOM offset relative to the current frame's offset.
float mWidth; // extra space to be added at this position (in app units)
struct nsTextFrame::TabWidthStore {
explicit TabWidthStore(int32_t aValidForContentOffset)
: mLimit(0), mValidForContentOffset(aValidForContentOffset) {}
// Apply tab widths to the aSpacing array, which corresponds to characters
// beginning at aOffset and has length aLength. (Width records outside this
// range will be ignored.)
void ApplySpacing(gfxTextRun::PropertyProvider::Spacing* aSpacing,
uint32_t aOffset, uint32_t aLength);
// Offset up to which tabs have been measured; positions beyond this have not
// been calculated yet but may be appended if needed later. It's a DOM
// offset relative to the current frame's offset.
uint32_t mLimit;
// Need to recalc tab offsets if frame content offset differs from this.
int32_t mValidForContentOffset;
// A TabWidth record for each tab character measured so far.
nsTArray<TabWidth> mWidths;
namespace {
struct TabwidthAdaptor {
const nsTArray<TabWidth>& mWidths;
explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
: mWidths(aWidths) {}
uint32_t operator[](size_t aIdx) const { return mWidths[aIdx].mOffset; }
} // namespace
void nsTextFrame::TabWidthStore::ApplySpacing(
gfxTextRun::PropertyProvider::Spacing* aSpacing, uint32_t aOffset,
uint32_t aLength) {
size_t i = 0;
const size_t len = mWidths.Length();
// If aOffset is non-zero, do a binary search to find where to start
// processing the tab widths, in case the list is really long. (See bug
// 953247.)
// We need to start from the first entry where mOffset >= aOffset.
if (aOffset > 0) {
mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
uint32_t limit = aOffset + aLength;
while (i < len) {
const TabWidth& tw = mWidths[i];
if (tw.mOffset >= limit) {
aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
* A glyph observer for the change of a font glyph in a text run.
* This is stored in {Simple, Complex}TextRunUserData.
class GlyphObserver final : public gfxFont::GlyphChangeObserver {
GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
: gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
void NotifyGlyphsChanged() override;
gfxTextRun* mTextRun;
static const nsFrameState TEXT_REFLOW_FLAGS =
static const nsFrameState TEXT_WHITESPACE_FLAGS =
* Some general notes
* Text frames delegate work to gfxTextRun objects. The gfxTextRun object
* transforms text to positioned glyphs. It can report the geometry of the
* glyphs and paint them. Text frames configure gfxTextRuns by providing text,
* spacing, language, and other information.
* A gfxTextRun can cover more than one DOM text node. This is necessary to
* get kerning, ligatures and shaping for text that spans multiple text nodes
* but is all the same font.
* The userdata for a gfxTextRun object can be:
* - A nsTextFrame* in the case a text run maps to only one flow. In this
* case, the textrun's user data pointer is a pointer to mStartFrame for that
* flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
* length of the text node.
* - A SimpleTextRunUserData in the case a text run maps to one flow, but we
* still have to keep a list of glyph observers.
* - A ComplexTextRunUserData in the case a text run maps to multiple flows,
* but we need to keep a list of glyph observers.
* - A TextRunUserData in the case a text run maps multiple flows, but it
* doesn't have any glyph observer for changes in SVG fonts.
* You can differentiate between the four different cases with the
* IsSimpleFlow and MightHaveGlyphChanges flags.
* We go to considerable effort to make sure things work even if in-flow
* siblings have different ComputedStyles (i.e., first-letter and first-line).
* Our convention is that unsigned integer character offsets are offsets into
* the transformed string. Signed integer character offsets are offsets into
* the DOM string.
* XXX currently we don't handle hyphenated breaks between text frames where the
* hyphen occurs at the end of the first text frame, e.g.
* <b>Kit&shy;</b>ty
* This is our user data for the textrun, when textRun->GetFlags2() has
* IsSimpleFlow set, and also MightHaveGlyphChanges.
* This allows having an array of observers if there are fonts whose glyphs
* might change, but also avoid allocation in the simple case that there aren't.
struct SimpleTextRunUserData {
nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
nsTextFrame* mFrame;
explicit SimpleTextRunUserData(nsTextFrame* aFrame) : mFrame(aFrame) {}
* We use an array of these objects to record which text frames
* are associated with the textrun. mStartFrame is the start of a list of
* text frames. Some sequence of its continuations are covered by the textrun.
* A content textnode can have at most one TextRunMappedFlow associated with it
* for a given textrun.
* mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to
* obtain the offset into the before-transformation text of the textrun. It can
* be positive (when a text node starts in the middle of a text run) or negative
* (when a text run starts in the middle of a text node). Of course it can also
* be zero.
struct TextRunMappedFlow {
nsTextFrame* mStartFrame;
int32_t mDOMOffsetToBeforeTransformOffset;
// The text mapped starts at mStartFrame->GetContentOffset() and is this long
uint32_t mContentLength;
* This is the type in the gfxTextRun's userdata field in the common case that
* the text run maps to multiple flows, but no fonts have been found with
* animatable glyphs.
* This way, we avoid allocating and constructing the extra nsTArray.
struct TextRunUserData {
#ifdef DEBUG
TextRunMappedFlow* mMappedFlows;
uint32_t mMappedFlowCount;
uint32_t mLastFlowIndex;
* This is our user data for the textrun, when textRun->GetFlags2() does not
* have IsSimpleFlow set and has the MightHaveGlyphChanges flag.
struct ComplexTextRunUserData : public TextRunUserData {
nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
* This helper object computes colors used for painting, and also IME
* underline information. The data is computed lazily and cached as necessary.
* These live for just the duration of one paint operation.
class nsTextPaintStyle {
explicit nsTextPaintStyle(nsTextFrame* aFrame);
void SetResolveColors(bool aResolveColors) {
mResolveColors = aResolveColors;
nscolor GetTextColor();
// SVG text has its own painting process, so we should never get its stroke
// property from here.
nscolor GetWebkitTextStrokeColor() {
if (SVGUtils::IsInSVGTextSubtree(mFrame)) {
return 0;
return mFrame->StyleText()->mWebkitTextStrokeColor.CalcColor(mFrame);
float GetWebkitTextStrokeWidth() {
if (SVGUtils::IsInSVGTextSubtree(mFrame)) {
return 0.0f;
nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
* Compute the colors for normally-selected text. Returns false if
* the normal selection is not being displayed.
bool GetSelectionColors(nscolor* aForeColor, nscolor* aBackColor);
void GetHighlightColors(nscolor* aForeColor, nscolor* aBackColor);
void GetURLSecondaryColor(nscolor* aForeColor);
void GetIMESelectionColors(int32_t aIndex, nscolor* aForeColor,
nscolor* aBackColor);
// if this returns false, we don't need to draw underline.
bool GetSelectionUnderlineForPaint(int32_t aIndex, nscolor* aLineColor,
float* aRelativeSize, uint8_t* aStyle);
// if this returns false, we don't need to draw underline.
static bool GetSelectionUnderline(nsIFrame*, int32_t aIndex,
nscolor* aLineColor, float* aRelativeSize,
uint8_t* aStyle);
// if this returns false, no text-shadow was specified for the selection
// and the *aShadow parameter was not modified.
bool GetSelectionShadow(Span<const StyleSimpleShadow>* aShadows);
nsPresContext* PresContext() const { return mPresContext; }
enum {
eIndexRawInput = 0,
static int32_t GetUnderlineStyleIndexForSelectionType(
SelectionType aSelectionType) {
switch (aSelectionType) {
case SelectionType::eIMERawClause:
return eIndexRawInput;
case SelectionType::eIMESelectedRawClause:
return eIndexSelRawText;
case SelectionType::eIMEConvertedClause:
return eIndexConvText;
case SelectionType::eIMESelectedClause:
return eIndexSelConvText;
case SelectionType::eSpellCheck:
return eIndexSpellChecker;
NS_WARNING("non-IME selection type");
return eIndexRawInput;
nscolor GetSystemFieldForegroundColor();
nscolor GetSystemFieldBackgroundColor();
nsTextFrame* mFrame;
nsPresContext* mPresContext;
bool mInitCommonColors;
bool mInitSelectionColorsAndShadow;
bool mResolveColors;
// Selection data
nscolor mSelectionTextColor;
nscolor mSelectionBGColor;
RefPtr<ComputedStyle> mSelectionPseudoStyle;
// Common data
int32_t mSufficientContrast;
nscolor mFrameBackgroundColor;
nscolor mSystemFieldForegroundColor;
nscolor mSystemFieldBackgroundColor;
// selection colors and underline info, the colors are resolved colors if
// mResolveColors is true (which is the default), i.e., the foreground color
// and background color are swapped if it's needed. And also line color will
// be resolved from them.
struct nsSelectionStyle {
bool mInit;
nscolor mTextColor;
nscolor mBGColor;
nscolor mUnderlineColor;
uint8_t mUnderlineStyle;
float mUnderlineRelativeSize;
nsSelectionStyle mSelectionStyle[5];
// Color initializations
void InitCommonColors();
bool InitSelectionColorsAndShadow();
nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
void InitSelectionStyle(int32_t aIndex);
// Ensures sufficient contrast between the frame background color and the
// selection background color, and swaps the selection text and background
// colors accordingly.
// Only used on platforms where mSelectionTextColor != NS_DONT_CHANGE_COLOR
bool EnsureSufficientContrast(nscolor* aForeColor, nscolor* aBackColor);
nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
nscolor aBackColor);
static TextRunUserData* CreateUserData(uint32_t aMappedFlowCount) {
TextRunUserData* data = static_cast<TextRunUserData*>(moz_xmalloc(
sizeof(TextRunUserData) + aMappedFlowCount * sizeof(TextRunMappedFlow)));
#ifdef DEBUG
data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
data->mMappedFlowCount = aMappedFlowCount;
data->mLastFlowIndex = 0;
return data;
static void DestroyUserData(TextRunUserData* aUserData) {
if (aUserData) {
static ComplexTextRunUserData* CreateComplexUserData(
uint32_t aMappedFlowCount) {
ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>(
moz_xmalloc(sizeof(ComplexTextRunUserData) +
aMappedFlowCount * sizeof(TextRunMappedFlow)));
new (data) ComplexTextRunUserData();
#ifdef DEBUG
data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
data->mMappedFlowCount = aMappedFlowCount;
data->mLastFlowIndex = 0;
return data;
static void DestroyComplexUserData(ComplexTextRunUserData* aUserData) {
if (aUserData) {
static void DestroyTextRunUserData(gfxTextRun* aTextRun) {
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
if (aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
} else {
if (aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
} else {
static TextRunMappedFlow* GetMappedFlows(const gfxTextRun* aTextRun) {
MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow),
"The method should not be called for simple flows.");
TextRunMappedFlow* flows;
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
flows = reinterpret_cast<TextRunMappedFlow*>(
static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
} else {
flows = reinterpret_cast<TextRunMappedFlow*>(
static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
static_cast<TextRunUserData*>(aTextRun->GetUserData())->mMappedFlows ==
"GetMappedFlows should return the same pointer as mMappedFlows.");
return flows;
* These are utility functions just for helping with the complexity related with
* the text runs user data.
static nsTextFrame* GetFrameForSimpleFlow(const gfxTextRun* aTextRun) {
MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow,
"Not so simple flow?");
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::MightHaveGlyphChanges) {
return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
return static_cast<nsTextFrame*>(aTextRun->GetUserData());
* Remove |aTextRun| from the frame continuation chain starting at
* |aStartContinuation| if non-null, otherwise starting at |aFrame|.
* Unmark |aFrame| as a text run owner if it's the frame we start at.
* Return true if |aStartContinuation| is non-null and was found
* in the next-continuation chain of |aFrame|.
static bool ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
nsTextFrame* aStartContinuation,
nsFrameState aWhichTextRunState) {
MOZ_ASSERT(aFrame, "null frame");
MOZ_ASSERT(!aStartContinuation ||
(!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
aStartContinuation->GetTextRun(nsTextFrame::eInflated) ==
aTextRun) ||
(!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ==
"wrong aStartContinuation for this text run");
if (!aStartContinuation || aStartContinuation == aFrame) {
} else {
do {
NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
aFrame = aFrame->GetNextContinuation();
} while (aFrame && aFrame != aStartContinuation);
bool found = aStartContinuation == aFrame;
while (aFrame) {
NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
if (!aFrame->RemoveTextRun(aTextRun)) {
aFrame = aFrame->GetNextContinuation();
MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
return found;
* Kill all references to |aTextRun| starting at |aStartContinuation|.
* It could be referenced by any of its owners, and all their in-flows.
* If |aStartContinuation| is null then process all userdata frames
* and their continuations.
* @note the caller is expected to take care of possibly destroying the
* text run if all userdata frames were reset (userdata is deallocated
* by this function though). The caller can detect this has occured by
* checking |aTextRun->GetUserData() == nullptr|.
static void UnhookTextRunFromFrames(gfxTextRun* aTextRun,
nsTextFrame* aStartContinuation) {
if (!aTextRun->GetUserData()) {
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
nsFrameState whichTextRunState =
userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
DebugOnly<bool> found = ClearAllTextRunReferences(
userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
NS_ASSERTION(!aStartContinuation || found,
"aStartContinuation wasn't found in simple flow text run");
if (!userDataFrame->HasAnyStateBits(whichTextRunState)) {
} else {
auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
nsFrameState whichTextRunState =
userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
bool found = ClearAllTextRunReferences(
userDataFrame, aTextRun, aStartContinuation, whichTextRunState);
if (found) {
if (userDataFrame->HasAnyStateBits(whichTextRunState)) {
destroyFromIndex = i + 1;
} else {
destroyFromIndex = i;
aStartContinuation = nullptr;
NS_ASSERTION(destroyFromIndex >= 0,
"aStartContinuation wasn't found in multi flow text run");
if (destroyFromIndex == 0) {
} else {
userData->mMappedFlowCount = uint32_t(destroyFromIndex);
if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
static void InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame) {
PresShell* presShell = aFrame->PresShell();
for (nsIFrame* f = aFrame; f;
f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
// If this is a non-display text frame within SVG <text>, we need
// to reflow the SVGTextFrame. (This is similar to reflowing the
// SVGTextFrame in response to style changes, in
// SVGTextFrame::DidSetComputedStyle.)
if (SVGUtils::IsInSVGTextSubtree(f) &&
auto svgTextFrame = static_cast<SVGTextFrame*>(
nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
} else {
// Theoretically we could just update overflow areas, perhaps using
// OverflowChangedTracker, but that would do a bunch of work eagerly that
// we should probably do lazily here since there could be a lot
// of text frames affected and we'd like to coalesce the work. So that's
// not easy to do well.
presShell->FrameNeedsReflow(f, IntrinsicDirty::Resize, NS_FRAME_IS_DIRTY);
void GlyphObserver::NotifyGlyphsChanged() {
if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
int32_t nsTextFrame::GetContentEnd() const {
nsTextFrame* next = GetNextContinuation();
return next ? next->GetContentOffset() : TextFragment()->GetLength();
struct FlowLengthProperty {
int32_t mStartOffset;
// The offset of the next fixed continuation after mStartOffset, or
// of the end of the text if there is none
int32_t mEndFlowOffset;
int32_t nsTextFrame::GetInFlowContentLength() {
if (!(mState & NS_FRAME_IS_BIDI)) {
return mContent->TextLength() - mContentOffset;
FlowLengthProperty* flowLength =
? static_cast<FlowLengthProperty*>(
: nullptr;
* This frame must start inside the cached flow. If the flow starts at
* mContentOffset but this frame is empty, logically it might be before the
* start of the cached flow.
if (flowLength &&
(flowLength->mStartOffset < mContentOffset ||
(flowLength->mStartOffset == mContentOffset &&
GetContentEnd() > mContentOffset)) &&
flowLength->mEndFlowOffset > mContentOffset) {
#ifdef DEBUG
NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
"frame crosses fixed continuation boundary");
return flowLength->mEndFlowOffset - mContentOffset;
nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
int32_t endFlow =
nextBidi ? nextBidi->GetContentOffset() : GetContent()->TextLength();
if (!flowLength) {
flowLength = new FlowLengthProperty;
if (NS_FAILED(mContent->SetProperty(
nsGkAtoms::flowlength, flowLength,
nsINode::DeleteProperty<FlowLengthProperty>))) {
delete flowLength;
flowLength = nullptr;
if (flowLength) {
flowLength->mStartOffset = mContentOffset;
flowLength->mEndFlowOffset = endFlow;
return endFlow - mContentOffset;
// Smarter versions of dom::IsSpaceCharacter.
// Unicode is really annoying; sometimes a space character isn't whitespace ---
// when it combines with another character
// So we have several versions of IsSpace for use in different contexts.
static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag,
uint32_t aPos) {
NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
if (!aFrag->Is2b()) return false;
return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
// Check whether aPos is a space for CSS 'word-spacing' purposes
static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
const nsTextFrame* aFrame,
const nsStyleText* aStyleText) {
NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
char16_t ch = aFrag->CharAt(aPos);
switch (ch) {
case ' ':
case CH_NBSP:
return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
case '\r':
case '\t':
return !aStyleText->WhiteSpaceIsSignificant();
case '\n':
return !aStyleText->NewlineIsSignificant(aFrame);
return false;
constexpr char16_t kOghamSpaceMark = 0x1680;
// Check whether the string aChars/aLength starts with space that's
// trimmable according to CSS 'white-space:normal/nowrap'.
static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) {
NS_ASSERTION(aLength > 0, "No text for IsSpace!");
char16_t ch = *aChars;
if (ch == ' ' || ch == kOghamSpaceMark) {
return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1,
aLength - 1);
return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
// Check whether the character aCh is trimmable according to CSS
// 'white-space:normal/nowrap'
static bool IsTrimmableSpace(char aCh) {
return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
const nsStyleText* aStyleText,
bool aAllowHangingWS = false) {
NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
switch (aFrag->CharAt(aPos)) {
case ' ':
case kOghamSpaceMark:
return (!aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS) &&
!IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
case '\n':
return !aStyleText->NewlineIsSignificantStyle() &&
aStyleText->mWhiteSpace != mozilla::StyleWhiteSpace::PreSpace;
case '\t':
case '\r':
case '\f':
return !aStyleText->WhiteSpaceIsSignificant() || aAllowHangingWS;
return false;
static bool IsSelectionInlineWhitespace(const nsTextFragment* aFrag,
uint32_t aPos) {
NS_ASSERTION(aPos < aFrag->GetLength(),
"No text for IsSelectionInlineWhitespace!");
char16_t ch = aFrag->CharAt(aPos);
if (ch == ' ' || ch == CH_NBSP)
return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
return ch == '\t' || ch == '\f';
static bool IsSelectionNewline(const nsTextFragment* aFrag, uint32_t aPos) {
NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSelectionNewline!");
char16_t ch = aFrag->CharAt(aPos);
return ch == '\n' || ch == '\r';
// Count the amount of trimmable whitespace (as per CSS
// 'white-space:normal/nowrap') in a text fragment. The first
// character is at offset aStartOffset; the maximum number of characters
// to check is aLength. aDirection is -1 or 1 depending on whether we should
// progress backwards or forwards.
static uint32_t GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
int32_t aStartOffset,
int32_t aLength,
int32_t aDirection) {
if (!aLength) {
return 0;
int32_t count = 0;
if (aFrag->Is2b()) {
const char16_t* str = aFrag->Get2b() + aStartOffset;
int32_t fragLen = aFrag->GetLength() - aStartOffset;
for (; count < aLength; ++count) {
if (!IsTrimmableSpace(str, fragLen)) break;
str += aDirection;
fragLen -= aDirection;
} else {
const char* str = aFrag->Get1b() + aStartOffset;
for (; count < aLength; ++count) {
if (!IsTrimmableSpace(*str)) break;
str += aDirection;
return count;
static bool IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) {
if (aFrag->Is2b()) return false;
int32_t len = aFrag->GetLength();
const char* str = aFrag->Get1b();
for (int32_t i = 0; i < len; ++i) {
char ch = str[i];
if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
return false;
return true;
static void ClearObserversFromTextRun(gfxTextRun* aTextRun) {
if (!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
} else {
static void CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) {
if (!aTextRun->GetUserData()) {
nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
uint32_t numGlyphRuns;
const gfxTextRun::GlyphRun* glyphRuns = aTextRun->GetGlyphRuns(&numGlyphRuns);
for (uint32_t i = 0; i < numGlyphRuns; ++i) {
gfxFont* font = glyphRuns[i].mFont;
if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
if (fontsWithAnimatedGlyphs.IsEmpty()) {
// NB: Theoretically, we should clear the MightHaveGlyphChanges
// here. That would involve de-allocating the simple user data struct if
// present too, and resetting the pointer to the frame. In practice, I
// don't think worth doing that work here, given the flag's only purpose is
// to distinguish what kind of user data is there.
nsTArray<UniquePtr<GlyphObserver>>* observers;
if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsSimpleFlow) {
// Swap the frame pointer for a just-allocated SimpleTextRunUserData if
// appropriate.
if (!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
aTextRun->SetUserData(new SimpleTextRunUserData(frame));
auto data = static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
observers = &data->mGlyphObservers;
} else {
if (!(aTextRun->GetFlags2() &
nsTextFrameUtils::Flags::MightHaveGlyphChanges)) {
auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
ComplexTextRunUserData* data =
TextRunMappedFlow* dataMappedFlows =
reinterpret_cast<TextRunMappedFlow*>(data + 1);
data->mLastFlowIndex = oldData->mLastFlowIndex;
for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
dataMappedFlows[i] = oldMappedFlows[i];
auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
observers = &data->mGlyphObservers;
for (auto font : fontsWithAnimatedGlyphs) {
observers->AppendElement(new GlyphObserver(font, aTextRun));
* This class accumulates state as we scan a paragraph of text. It detects
* textrun boundaries (changes from text to non-text, hard
* line breaks, and font changes) and builds a gfxTextRun at each boundary.
* It also detects linebreaker run boundaries (changes from text to non-text,
* and hard line breaks) and at each boundary runs the linebreaker to compute
* potential line breaks. It also records actual line breaks to store them in
* the textruns.
class BuildTextRunsScanner {
BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
nsIFrame* aLineContainer,
nsTextFrame::TextRunType aWhichTextRun)
: mDrawTarget(aDrawTarget),
mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
~BuildTextRunsScanner() {
NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
void SetAtStartOfLine() {
mStartOfLine = true;
mCanStopOnThisLine = false;
void SetSkipIncompleteTextRuns(bool aSkip) {
mSkipIncompleteTextRuns = aSkip;
void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
mCommonAncestorWithLastFrame = aFrame;
bool CanStopOnThisLine() { return mCanStopOnThisLine; }
nsIFrame* GetCommonAncestorWithLastFrame() {
return mCommonAncestorWithLastFrame;
void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
if (mCommonAncestorWithLastFrame &&
mCommonAncestorWithLastFrame->GetParent() == aFrame) {
mCommonAncestorWithLastFrame = aFrame;
void ScanFrame(nsIFrame* aFrame);
bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
void ResetRunInfo() {
mLastFrame = nullptr;
mMaxTextLength = 0;
mDoubleByteText = false;
void AccumulateRunInfo(nsTextFrame* aFrame);
* @return null to indicate either textrun construction failed or
* we constructed just a partial textrun to set up linebreaker and other
* state for following textruns.
already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
bool SetupLineBreakerContext(gfxTextRun* aTextRun);
void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
struct FindBoundaryState {
nsIFrame* mStopAtFrame;
nsTextFrame* mFirstTextFrame;
nsTextFrame* mLastTextFrame;
bool mSeenTextRunBoundaryOnLaterLine;
bool mSeenTextRunBoundaryOnThisLine;
bool mSeenSpaceForLineBreakingOnThisLine;
nsTArray<char16_t>& mBuffer;
enum FindBoundaryResult {
FindBoundaryResult FindBoundaries(nsIFrame* aFrame,
FindBoundaryState* aState);
bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
// Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
// (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
// continuations starting from mStartFrame are a sequence of in-flow frames).
struct MappedFlow {
nsTextFrame* mStartFrame;
nsTextFrame* mEndFrame;
// When we consider breaking between elements, the nearest common
// ancestor of the elements containing the characters is the one whose
// CSS 'white-space' property governs. So this records the nearest common
// ancestor of mStartFrame and the previous text frame, or null if there
// was no previous text frame on this line.
nsIFrame* mAncestorControllingInitialBreak;
int32_t GetContentEnd() {
return mEndFrame ? mEndFrame->GetContentOffset()
: mStartFrame->TextFragment()->GetLength();
class BreakSink final : public nsILineBreakSink {
BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
uint32_t aOffsetIntoTextRun)
: mTextRun(aTextRun),
mOffsetIntoTextRun(aOffsetIntoTextRun) {}
void SetBreaks(uint32_t aOffset, uint32_t aLength,
uint8_t* aBreakBefore) final {
gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
aOffset + mOffsetIntoTextRun + aLength);
if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
// Be conservative and assume that some breaks have been set
void SetCapitalization(uint32_t aOffset, uint32_t aLength,
bool* aCapitalize) final {
MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed,
"Text run should be transformed!");
if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
nsTransformedTextRun* transformedTextRun =
transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun,
aLength, aCapitalize);
void Finish(gfxMissingFontRecorder* aMFR) {
!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::UnusedFlags),
"Flag set that should never be set! (memory safety error?)");
if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::IsTransformed) {
nsTransformedTextRun* transformedTextRun =
transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
// The way nsTransformedTextRun is implemented, its glyph runs aren't
// available until after nsTransformedTextRun::FinishSettingProperties()
// is called. So that's why we defer checking for animated glyphs to here.
RefPtr<gfxTextRun> mTextRun;
DrawTarget* mDrawTarget;
uint32_t mOffsetIntoTextRun;
AutoTArray<MappedFlow, 10> mMappedFlows;
AutoTArray<nsTextFrame*, 50> mLineBreakBeforeFrames;
AutoTArray<UniquePtr<BreakSink>, 10> mBreakSinks;
nsLineBreaker mLineBreaker;
RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
DrawTarget* mDrawTarget;
nsIFrame* mLineContainer;
nsTextFrame* mLastFrame;
// The common ancestor of the current frame and the previous leaf frame
// on the line, or null if there was no previous leaf frame.
nsIFrame* mCommonAncestorWithLastFrame;
gfxMissingFontRecorder* mMissingFonts;
// mMaxTextLength is an upper bound on the size of the text in all mapped
// frames The value UINT32_MAX represents overflow; text will be discarded
uint32_t mMaxTextLength;
bool mDoubleByteText;
bool mBidiEnabled;
bool mStartOfLine;
bool mSkipIncompleteTextRuns;
bool mCanStopOnThisLine;
nsTextFrame::TextRunType mWhichTextRun;
uint8_t mNextRunContextInfo;
uint8_t mCurrentRunContextInfo;
static nsIFrame* FindLineContainer(nsIFrame* aFrame) {
while (aFrame && (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
aFrame->CanContinueTextRun())) {
aFrame = aFrame->GetParent();
return aFrame;
static bool IsLineBreakingWhiteSpace(char16_t aChar) {
// 0x0A (\n) is not handled as white-space by the line breaker, since
// we break before it, if it isn't transformed to a normal space.
// (If we treat it as normal white-space then we'd only break after it.)
// However, it does induce a line break or is converted to a regular
// space, and either way it can be used to bound the region of text
// that needs to be analyzed for line breaking.
return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
static bool TextContainsLineBreakerWhiteSpace(const void* aText,
uint32_t aLength,
bool aIsDoubleByte) {
if (aIsDoubleByte) {
const char16_t* chars = static_cast<const char16_t*>(aText);
for (uint32_t i = 0; i < aLength; ++i) {
if (IsLineBreakingWhiteSpace(chars[i])) return true;
return false;
} else {
const uint8_t* chars = static_cast<const uint8_t*>(aText);
for (uint32_t i = 0; i < aLength; ++i) {
if (IsLineBreakingWhiteSpace(chars[i])) return true;
return false;
static nsTextFrameUtils::CompressionMode GetCSSWhitespaceToCompressionMode(
nsTextFrame* aFrame, const nsStyleText* aStyleText) {
switch (aStyleText->mWhiteSpace) {
case StyleWhiteSpace::Normal:
case StyleWhiteSpace::Nowrap:
case StyleWhiteSpace::Pre:
case StyleWhiteSpace::PreWrap:
case StyleWhiteSpace::BreakSpaces:
if (!aStyleText->NewlineIsSignificant(aFrame)) {
// If newline is set to be preserved, but then suppressed,
// transform newline to space.
return nsTextFrameUtils::COMPRESS_NONE;
case StyleWhiteSpace::PreSpace:
case StyleWhiteSpace::PreLine:
return nsTextFrameUtils::COMPRESS_WHITESPACE;
MOZ_ASSERT_UNREACHABLE("Unknown white-space value");
struct FrameTextTraversal {
: mFrameToScan(nullptr),
mTextRunCanCrossFrameBoundary(false) {}
// These fields identify which frames should be recursively scanned
// The first normal frame to scan (or null, if no such frame should be
// scanned)
nsIFrame* mFrameToScan;
// The first overflow frame to scan (or null, if no such frame should be
// scanned)
nsIFrame* mOverflowFrameToScan;
// Whether to scan the siblings of
// mFrameToDescendInto/mOverflowFrameToDescendInto
bool mScanSiblings;
// These identify the boundaries of the context required for
// line breaking or textrun construction
bool mLineBreakerCanCrossFrameBoundary;
bool mTextRunCanCrossFrameBoundary;
nsIFrame* NextFrameToScan() {
nsIFrame* f;
if (mFrameToScan) {
f = mFrameToScan;
mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
} else if (mOverflowFrameToScan) {
f = mOverflowFrameToScan;
mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
} else {
f = nullptr;
return f;
static FrameTextTraversal CanTextCrossFrameBoundary(nsIFrame* aFrame) {
FrameTextTraversal result;
bool continuesTextRun = aFrame->CanContinueTextRun();
if (aFrame->IsPlaceholderFrame()) {
// placeholders are "invisible", so a text run should be able to span
// across one. But don't descend into the out-of-flow.
result.mLineBreakerCanCrossFrameBoundary = true;
if (continuesTextRun) {
// ... Except for first-letter floats, which are really in-flow
// from the point of view of capitalization etc, so we'd better
// descend into them. But we actually need to break the textrun for
// first-letter floats since things look bad if, say, we try to make a
// ligature across the float boundary.
result.mFrameToScan =
} else {
result.mTextRunCanCrossFrameBoundary = true;
} else {
if (continuesTextRun) {
result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
result.mOverflowFrameToScan =
"Scanning overflow inline frames is something we should avoid");
result.mScanSiblings = true;
result.mTextRunCanCrossFrameBoundary = true;
result.mLineBreakerCanCrossFrameBoundary = true;
} else {
"Shouldn't call this method for ruby text container");
return result;
BuildTextRunsScanner::FindBoundaryResult BuildTextRunsScanner::FindBoundaries(
nsIFrame* aFrame, FindBoundaryState* aState) {
LayoutFrameType frameType = aFrame->Type();
if (frameType == LayoutFrameType::RubyTextContainer) {
// Don't stop a text run for ruby text container. We want ruby text
// containers to be skipped, but continue the text run across them.
nsTextFrame* textFrame = frameType == LayoutFrameType::Text
? static_cast<nsTextFrame*>(aFrame)
: nullptr;
if (textFrame) {
if (aState->mLastTextFrame &&
textFrame != aState->mLastTextFrame->GetNextInFlow() &&
!ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
aState->mSeenTextRunBoundaryOnThisLine = true;
if (aState->mSeenSpaceForLineBreakingOnThisLine)
if (!aState->mFirstTextFrame) {
aState->mFirstTextFrame = textFrame;
aState->mLastTextFrame = textFrame;
if (aFrame == aState->mStopAtFrame) return FB_STOPPED_AT_STOP_FRAME;
if (textFrame) {
if (aState->mSeenSpaceForLineBreakingOnThisLine) {
const nsTextFragment* frag = textFrame->TextFragment();
uint32_t start = textFrame->GetContentOffset();
uint32_t length = textFrame->GetContentLength();
const void* text;
if (frag->Is2b()) {
// It is possible that we may end up removing all whitespace in
// a piece of text because of The White Space Processing Rules,
// so we need to transform it before we can check existence of
// such whitespaces.
nsTextFrameUtils::CompressionMode compression =
GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
uint8_t incomingFlags = 0;
gfxSkipChars skipChars;
nsTextFrameUtils::Flags analysisFlags;
char16_t* bufStart = aState->mBuffer.Elements();
char16_t* bufEnd = nsTextFrameUtils::TransformText(
frag->Get2b() + start, length, bufStart, compression, &incomingFlags,
&skipChars, &analysisFlags);
text = bufStart;
length = bufEnd - bufStart;
} else {
// If the text only contains ASCII characters, it is currently
// impossible that TransformText would remove all whitespaces,
// and thus the check below should return the same result for
// transformed text and original text. So we don't need to try
// transforming it here.
text = static_cast<const void*>(frag->Get1b() + start);
if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
aState->mSeenSpaceForLineBreakingOnThisLine = true;
if (aState->mSeenTextRunBoundaryOnLaterLine) {
FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
if (!traversal.mTextRunCanCrossFrameBoundary) {
aState->mSeenTextRunBoundaryOnThisLine = true;
if (aState->mSeenSpaceForLineBreakingOnThisLine)
for (nsIFrame* f = traversal.NextFrameToScan(); f;
f = traversal.NextFrameToScan()) {
FindBoundaryResult result = FindBoundaries(f, aState);
if (result != FB_CONTINUE) return result;
if (!traversal.mTextRunCanCrossFrameBoundary) {
aState->mSeenTextRunBoundaryOnThisLine = true;
if (aState->mSeenSpaceForLineBreakingOnThisLine)
// build text runs for the 200 lines following aForFrame, and stop after that
// when we get a chance.
* General routine for building text runs. This is hairy because of the need
* to build text runs that span content nodes.
* @param aContext The gfxContext we're using to construct this text run.
* @param aForFrame The nsTextFrame for which we're building this text run.
* @param aLineContainer the line container containing aForFrame; if null,
* we'll walk the ancestors to find it. It's required to be non-null
* when aForFrameLine is non-null.
* @param aForFrameLine the line containing aForFrame; if null, we'll figure
* out the line (slowly)
* @param aWhichTextRun The type of text run we want to build. If font inflation
* is enabled, this will be eInflated, otherwise it's eNotInflated.
static void BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
nsIFrame* aLineContainer,
const nsLineList::iterator* aForFrameLine,
nsTextFrame::TextRunType aWhichTextRun) {
MOZ_ASSERT(aForFrame, "for no frame?");
NS_ASSERTION(!aForFrameLine || aLineContainer, "line but no line container");
nsIFrame* lineContainerChild = aForFrame;
if (!aLineContainer) {
if (aForFrame->IsFloatingFirstLetterChild()) {
lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
aLineContainer = FindLineContainer(lineContainerChild);
} else {
(aLineContainer == FindLineContainer(aForFrame) ||
(aLineContainer->IsLetterFrame() && aLineContainer->IsFloating())),
"Wrong line container hint");
if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
nsPresContext* presContext = aLineContainer->PresContext();
BuildTextRunsScanner scanner(presContext, aDrawTarget, aLineContainer,
nsBlockFrame* block = do_QueryFrame(aLineContainer);
if (!block) {
nsIFrame* textRunContainer = aLineContainer;
if (aLineContainer->IsRubyTextContainerFrame()) {
textRunContainer = aForFrame;
while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
textRunContainer = textRunContainer->GetParent();
MOZ_ASSERT(textRunContainer &&
textRunContainer->GetParent() == aLineContainer);
} else {
!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
"Breakable non-block line containers other than "
"ruby text container is not supported");
// Just loop through all the children of the linecontainer ... it's really
// just one line
for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
// Set mStartOfLine so FlushFrames knows its textrun ends a line
scanner.FlushFrames(true, false);
// Find the line containing 'lineContainerChild'.
bool isValid = true;
nsBlockInFlowLineIterator backIterator(block, &isValid);
if (aForFrameLine) {
backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
} else {
backIterator =
nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
NS_ASSERTION(backIterator.GetContainer() == block,
"Someone lied to us about the block");
nsBlockFrame::LineIterator startLine = backIterator.GetLine();
// Find a line where we can start building text runs. We choose the last line
// where:
// -- there is a textrun boundary between the start of the line and the
// start of aForFrame
// -- there is a space between the start of the line and the textrun boundary
// (this is so we can be sure the line breaks will be set properly
// on the textruns we construct).
// The possibly-partial text runs up to and including the first space
// are not reconstructed. We construct partial text runs for that text ---
// for the sake of simplifying the code and feeding the linebreaker ---
// but we discard them instead of assigning them to frames.
// This is a little awkward because we traverse lines in the reverse direction
// but we traverse the frames in each line in the forward direction.
nsBlockInFlowLineIterator forwardIterator = backIterator;
nsIFrame* stopAtFrame = lineContainerChild;
nsTextFrame* nextLineFirstTextFrame = nullptr;
AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
bool seenTextRunBoundaryOnLaterLine = false;
bool mayBeginInTextRun = true;
while (true) {
forwardIterator = backIterator;