Bug 1647556 - part 9-2: Make `WSRunObject::InsertBreak()` stop using `FindNearestFragment()` r=m_kato

It removes some invisible leading and/or trailing white-spaces when it inserts
`<br>` element into the invisible white-space sequence.  It currently checks
whether the insertion point is in invisible leading and trailing white-spaces
or not with `FindNearestFragment()`, but we can do same thing with
comparing the insertion point with the result of
`TextFragmentData::GetInvisibleLeadingWhiteSpaceRange()` and
`TextFragmentData::GetInvisibleLeadingWhiteSpaceRange()`.  However, current
implementation does not make sense because:
- It checks trailing white-spaces with `!IsEndOfHardLine()` and
  `IsStartOfHardLine()`, but this means that it does ignores invisible
   white-spaces which are the only content in a line.
- It checks leading white-spaces with `!IsStartOfHardLine()` and
  `IsEndOfHardLine()`, so, this also ignores invisible white-spaces which
  are the only content in a line.
- The important thing of the logic is prevent that invisible leading and
  trailing white-spaces become visible with new `<br>` element, but this
  is done only for trailing white-spaces.

Differential Revision: https://phabricator.services.mozilla.com/D82283
This commit is contained in:
Masayuki Nakano 2020-07-09 09:44:41 +00:00
Родитель d806bac0e0
Коммит dd8f12dbc3
3 изменённых файлов: 188 добавлений и 60 удалений

Просмотреть файл

@ -1014,6 +1014,11 @@ class EditorDOMRangeBase final {
return mStart == mEnd; return mStart == mEnd;
} }
bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); } bool IsPositioned() const { return mStart.IsSet() && mEnd.IsSet(); }
template <typename OtherPointType>
bool Contains(const OtherPointType& aPoint) const {
return IsPositioned() && mStart.EqualsOrIsBefore(aPoint) &&
mEnd.IsBefore(aPoint);
}
bool InSameContainer() const { bool InSameContainer() const {
MOZ_ASSERT(IsPositioned()); MOZ_ASSERT(IsPositioned());
return IsPositioned() && mStart.GetContainer() == mEnd.GetContainer(); return IsPositioned() && mStart.GetContainer() == mEnd.GetContainer();

Просмотреть файл

@ -202,12 +202,18 @@ already_AddRefed<Element> WSRunObject::InsertBreak(
// meanwhile, the pre case is handled in HandleInsertText() in // meanwhile, the pre case is handled in HandleInsertText() in
// HTMLEditSubActionHandler.cpp // HTMLEditSubActionHandler.cpp
const WSFragment* beforeRun = FindNearestFragment(aPointToInsert, false); TextFragmentData textFragmentData(mStart, mEnd, mNBSPData, mPRE);
const WSFragment* afterRun = FindNearestFragment(aPointToInsert, true); const EditorDOMRange invisibleLeadingWhiteSpaceRangeOfNewLine =
textFragmentData.GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
aPointToInsert);
const EditorDOMRange invisibleTrailingWhiteSpaceRangeOfCurrentLine =
textFragmentData.GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
aPointToInsert);
const Maybe<const WSFragment> visibleWSFragmentInMiddleOfLine = const Maybe<const WSFragment> visibleWSFragmentInMiddleOfLine =
TextFragmentData(mStart, mEnd, mNBSPData, mPRE) !invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned() ||
.CreateWSFragmentForVisibleAndMiddleOfLine(); !invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()
? textFragmentData.CreateWSFragmentForVisibleAndMiddleOfLine()
: Nothing();
const PointPosition pointPositionWithVisibleWhiteSpaces = const PointPosition pointPositionWithVisibleWhiteSpaces =
visibleWSFragmentInMiddleOfLine.isSome() visibleWSFragmentInMiddleOfLine.isSome()
? visibleWSFragmentInMiddleOfLine.ref().ComparePoint(aPointToInsert) ? visibleWSFragmentInMiddleOfLine.ref().ComparePoint(aPointToInsert)
@ -219,22 +225,23 @@ already_AddRefed<Element> WSRunObject::InsertBreak(
// point while we tweak any surrounding white-space // point while we tweak any surrounding white-space
AutoTrackDOMPoint tracker(mHTMLEditor.RangeUpdaterRef(), &pointToInsert); AutoTrackDOMPoint tracker(mHTMLEditor.RangeUpdaterRef(), &pointToInsert);
// Handle any changes needed to ws run after inserted br if (invisibleTrailingWhiteSpaceRangeOfCurrentLine.IsPositioned()) {
if (!afterRun || afterRun->IsEndOfHardLine()) { if (!invisibleTrailingWhiteSpaceRangeOfCurrentLine.Collapsed()) {
// Don't need to do anything. Just insert break. ws won't change. // XXX Why don't we remove all of the invisible white-spaces?
} else if (afterRun->IsStartOfHardLine()) { MOZ_ASSERT(invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef() ==
// Delete the leading ws that is after insertion point. We don't pointToInsert);
// have to (it would still not be significant after br), but it's nsresult rv =
// just more aesthetically pleasing to. MOZ_KnownLive(mHTMLEditor)
nsresult rv = MOZ_KnownLive(mHTMLEditor)
.DeleteTextAndTextNodesWithTransaction( .DeleteTextAndTextNodesWithTransaction(
pointToInsert, afterRun->EndPoint()); invisibleTrailingWhiteSpaceRangeOfCurrentLine.StartRef(),
invisibleTrailingWhiteSpaceRangeOfCurrentLine.EndRef());
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
NS_WARNING( NS_WARNING(
"HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed"); "HTMLEditor::DeleteTextAndTextNodesWithTransaction() failed");
return nullptr; return nullptr;
} }
} }
}
// If new line will start with visible white-spaces, it needs to be start // If new line will start with visible white-spaces, it needs to be start
// with an NBSP. // with an NBSP.
else if (pointPositionWithVisibleWhiteSpaces == else if (pointPositionWithVisibleWhiteSpaces ==
@ -265,21 +272,26 @@ already_AddRefed<Element> WSRunObject::InsertBreak(
} }
} }
// Handle any changes needed to ws run before inserted br if (invisibleLeadingWhiteSpaceRangeOfNewLine.IsPositioned()) {
if (!beforeRun || beforeRun->IsStartOfHardLine()) { if (!invisibleLeadingWhiteSpaceRangeOfNewLine.Collapsed()) {
// Don't need to do anything. Just insert break. ws won't change. // XXX Why don't we remove all of the invisible white-spaces?
} else if (beforeRun->IsEndOfHardLine()) { MOZ_ASSERT(invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef() ==
// Need to delete the trailing ws that is before insertion point, because pointToInsert);
// it would become significant after break inserted. // XXX If the DOM tree has been changed above,
nsresult rv = MOZ_KnownLive(mHTMLEditor) // invisibleLeadingWhiteSpaceRangeOfNewLine may be invalid now.
// So, we may do something wrong here.
nsresult rv =
MOZ_KnownLive(mHTMLEditor)
.DeleteTextAndTextNodesWithTransaction( .DeleteTextAndTextNodesWithTransaction(
beforeRun->StartPoint(), pointToInsert); invisibleLeadingWhiteSpaceRangeOfNewLine.StartRef(),
invisibleLeadingWhiteSpaceRangeOfNewLine.EndRef());
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
NS_WARNING( NS_WARNING(
"WSRunObject::DeleteTextAndTextNodesWithTransaction() failed"); "WSRunObject::DeleteTextAndTextNodesWithTransaction() failed");
return nullptr; return nullptr;
} }
} }
}
// If the `<br>` element is put immediately after an NBSP, it should be // If the `<br>` element is put immediately after an NBSP, it should be
// replaced with an ASCII white-space. // replaced with an ASCII white-space.
else if (pointPositionWithVisibleWhiteSpaces == else if (pointPositionWithVisibleWhiteSpaces ==
@ -982,41 +994,51 @@ void WSRunScanner::EnsureWSFragments() {
textFragmentData.InitializeWSFragmentArray(mFragments); textFragmentData.InitializeWSFragmentArray(mFragments);
} }
template <typename EditorDOMRangeType> EditorDOMRange
EditorDOMRangeType
WSRunScanner::TextFragmentData::GetInvisibleLeadingWhiteSpaceRange() const { WSRunScanner::TextFragmentData::GetInvisibleLeadingWhiteSpaceRange() const {
if (mLeadingWhiteSpaceRange.isSome()) {
return mLeadingWhiteSpaceRange.ref();
}
// If it's preformatted or not start of line, the range is not invisible // If it's preformatted or not start of line, the range is not invisible
// leading white-spaces. // leading white-spaces.
// TODO: We should check each text node style rather than WSRunScanner's // TODO: We should check each text node style rather than WSRunScanner's
// scan start position's style. // scan start position's style.
if (mIsPreformatted || !StartsFromHardLineBreak()) { if (mIsPreformatted || !StartsFromHardLineBreak()) {
return EditorDOMRangeType(); mLeadingWhiteSpaceRange.emplace();
return mLeadingWhiteSpaceRange.ref();
} }
// If there is no NBSP, all of the given range is leading white-spaces. // If there is no NBSP, all of the given range is leading white-spaces.
// Note that this result may be collapsed if there is no leading white-spaces. // Note that this result may be collapsed if there is no leading white-spaces.
if (!mNBSPData.FoundNBSP()) { if (!mNBSPData.FoundNBSP()) {
MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet()); MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet());
return EditorDOMRangeType(mStart.PointRef(), mEnd.PointRef()); mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef());
return mLeadingWhiteSpaceRange.ref();
} }
MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid()); MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid());
// Even if the first NBSP is the start, i.e., there is no invisible leading // Even if the first NBSP is the start, i.e., there is no invisible leading
// white-space, return collapsed range. // white-space, return collapsed range.
return EditorDOMRangeType(mStart.PointRef(), mNBSPData.FirstPointRef()); mLeadingWhiteSpaceRange.emplace(mStart.PointRef(), mNBSPData.FirstPointRef());
return mLeadingWhiteSpaceRange.ref();
} }
template <typename EditorDOMRangeType> EditorDOMRange
EditorDOMRangeType
WSRunScanner::TextFragmentData::GetInvisibleTrailingWhiteSpaceRange() const { WSRunScanner::TextFragmentData::GetInvisibleTrailingWhiteSpaceRange() const {
if (mTrailingWhiteSpaceRange.isSome()) {
return mTrailingWhiteSpaceRange.ref();
}
// If it's preformatted or not immediately before block boundary, the range is // If it's preformatted or not immediately before block boundary, the range is
// not invisible trailing white-spaces. Note that collapsible white-spaces // not invisible trailing white-spaces. Note that collapsible white-spaces
// before a `<br>` element is visible. // before a `<br>` element is visible.
// TODO: We should check each text node style rather than WSRunScanner's // TODO: We should check each text node style rather than WSRunScanner's
// scan start position's style. // scan start position's style.
if (mIsPreformatted || !EndsByBlockBoundary()) { if (mIsPreformatted || !EndsByBlockBoundary()) {
return EditorDOMRangeType(); mTrailingWhiteSpaceRange.emplace();
return mTrailingWhiteSpaceRange.ref();
} }
// If there is no NBSP, all of the given range is trailing white-spaces. // If there is no NBSP, all of the given range is trailing white-spaces.
@ -1024,7 +1046,8 @@ WSRunScanner::TextFragmentData::GetInvisibleTrailingWhiteSpaceRange() const {
// spaces. // spaces.
if (!mNBSPData.FoundNBSP()) { if (!mNBSPData.FoundNBSP()) {
MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet()); MOZ_ASSERT(mStart.PointRef().IsSet() || mEnd.PointRef().IsSet());
return EditorDOMRangeType(mStart.PointRef(), mEnd.PointRef()); mTrailingWhiteSpaceRange.emplace(mStart.PointRef(), mEnd.PointRef());
return mTrailingWhiteSpaceRange.ref();
} }
MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid()); MOZ_ASSERT(mNBSPData.LastPointRef().IsSetAndValid());
@ -1035,13 +1058,15 @@ WSRunScanner::TextFragmentData::GetInvisibleTrailingWhiteSpaceRange() const {
mNBSPData.LastPointRef().GetContainer() == mNBSPData.LastPointRef().GetContainer() ==
mEnd.PointRef().GetContainer() && mEnd.PointRef().GetContainer() &&
mNBSPData.LastPointRef().Offset() == mEnd.PointRef().Offset() - 1) { mNBSPData.LastPointRef().Offset() == mEnd.PointRef().Offset() - 1) {
return EditorDOMRangeType(); mTrailingWhiteSpaceRange.emplace();
return mTrailingWhiteSpaceRange.ref();
} }
// Otherwise, the may be some trailing white-spaces. // Otherwise, the may be some trailing white-spaces.
MOZ_ASSERT(!mNBSPData.LastPointRef().IsEndOfContainer()); MOZ_ASSERT(!mNBSPData.LastPointRef().IsEndOfContainer());
return EditorDOMRangeType(mNBSPData.LastPointRef().NextPoint(), mTrailingWhiteSpaceRange.emplace(mNBSPData.LastPointRef().NextPoint(),
mEnd.PointRef()); mEnd.PointRef());
return mTrailingWhiteSpaceRange.ref();
} }
Maybe<WSRunScanner::WSFragment> Maybe<WSRunScanner::WSFragment>
@ -1067,8 +1092,8 @@ WSRunScanner::TextFragmentData::CreateWSFragmentForVisibleAndMiddleOfLine()
// If all of the range is invisible leading or trailing white-spaces, // If all of the range is invisible leading or trailing white-spaces,
// there is no visible content. // there is no visible content.
const auto leadingWhiteSpaceRange = const EditorDOMRange leadingWhiteSpaceRange =
GetInvisibleLeadingWhiteSpaceRange<EditorRawDOMRange>(); GetInvisibleLeadingWhiteSpaceRange();
const bool maybeHaveLeadingWhiteSpaces = const bool maybeHaveLeadingWhiteSpaces =
leadingWhiteSpaceRange.StartRef().IsSet() || leadingWhiteSpaceRange.StartRef().IsSet() ||
leadingWhiteSpaceRange.EndRef().IsSet(); leadingWhiteSpaceRange.EndRef().IsSet();
@ -1077,8 +1102,8 @@ WSRunScanner::TextFragmentData::CreateWSFragmentForVisibleAndMiddleOfLine()
leadingWhiteSpaceRange.EndRef() == mEnd.PointRef()) { leadingWhiteSpaceRange.EndRef() == mEnd.PointRef()) {
return Nothing(); return Nothing();
} }
const auto trailingWhiteSpaceRange = const EditorDOMRange trailingWhiteSpaceRange =
GetInvisibleTrailingWhiteSpaceRange<EditorRawDOMRange>(); GetInvisibleTrailingWhiteSpaceRange();
const bool maybeHaveTrailingWhiteSpaces = const bool maybeHaveTrailingWhiteSpaces =
trailingWhiteSpaceRange.StartRef().IsSet() || trailingWhiteSpaceRange.StartRef().IsSet() ||
trailingWhiteSpaceRange.EndRef().IsSet(); trailingWhiteSpaceRange.EndRef().IsSet();
@ -1168,10 +1193,10 @@ void WSRunScanner::TextFragmentData::InitializeWSFragmentArray(
return; return;
} }
const auto leadingWhiteSpaceRange = const EditorDOMRange leadingWhiteSpaceRange =
GetInvisibleLeadingWhiteSpaceRange<EditorRawDOMRange>(); GetInvisibleLeadingWhiteSpaceRange();
const auto trailingWhiteSpaceRange = const EditorDOMRange trailingWhiteSpaceRange =
GetInvisibleTrailingWhiteSpaceRange<EditorRawDOMRange>(); GetInvisibleTrailingWhiteSpaceRange();
// XXX `IsPositioned()` is not availble here because currently it is not // XXX `IsPositioned()` is not availble here because currently it is not
// guaranteed that both boundaries are set or unset at same time. // guaranteed that both boundaries are set or unset at same time.
const bool maybeHaveLeadingWhiteSpaces = const bool maybeHaveLeadingWhiteSpaces =
@ -2142,12 +2167,12 @@ nsresult WSRunObject::MaybeReplaceInclusiveNextNBSPWithASCIIWhiteSpace(
nsresult WSRunObject::DeleteInvisibleASCIIWhiteSpacesInternal() { nsresult WSRunObject::DeleteInvisibleASCIIWhiteSpacesInternal() {
TextFragmentData textFragment(mStart, mEnd, mNBSPData, mPRE); TextFragmentData textFragment(mStart, mEnd, mNBSPData, mPRE);
auto leadingWhiteSpaceRange = EditorDOMRange leadingWhiteSpaceRange =
textFragment.GetInvisibleLeadingWhiteSpaceRange<EditorDOMRange>(); textFragment.GetInvisibleLeadingWhiteSpaceRange();
// XXX Getting trailing white-space range now must be wrong because // XXX Getting trailing white-space range now must be wrong because
// mutation event listener may invalidate it. // mutation event listener may invalidate it.
auto trailingWhiteSpaceRange = EditorDOMRange trailingWhiteSpaceRange =
textFragment.GetInvisibleTrailingWhiteSpaceRange<EditorDOMRange>(); textFragment.GetInvisibleTrailingWhiteSpaceRange();
DebugOnly<bool> leadingWhiteSpacesDeleted = false; DebugOnly<bool> leadingWhiteSpacesDeleted = false;
if (leadingWhiteSpaceRange.IsPositioned() && if (leadingWhiteSpaceRange.IsPositioned() &&
!leadingWhiteSpaceRange.Collapsed()) { !leadingWhiteSpaceRange.Collapsed()) {

Просмотреть файл

@ -774,8 +774,7 @@ class MOZ_STACK_CLASS WSRunScanner {
* Note that if there are only invisible white-spaces in a hard line, * Note that if there are only invisible white-spaces in a hard line,
* this returns all of the white-spaces. * this returns all of the white-spaces.
*/ */
template <typename EditorDOMRangeType> EditorDOMRange GetInvisibleLeadingWhiteSpaceRange() const;
EditorDOMRangeType GetInvisibleLeadingWhiteSpaceRange() const;
/** /**
* GetInvisibleTrailingWhiteSpaceRange() returns two DOM points, * GetInvisibleTrailingWhiteSpaceRange() returns two DOM points,
@ -785,8 +784,105 @@ class MOZ_STACK_CLASS WSRunScanner {
* Note that if there are only invisible white-spaces in a hard line, * Note that if there are only invisible white-spaces in a hard line,
* this returns all of the white-spaces. * this returns all of the white-spaces.
*/ */
template <typename EditorDOMRangeType> EditorDOMRange GetInvisibleTrailingWhiteSpaceRange() const;
EditorDOMRangeType GetInvisibleTrailingWhiteSpaceRange() const;
/**
* GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt() returns new
* invisible leading white-space range which should be removed if
* splitting invisible white-space sequence at aPointToSplit creates
* new invisible leading white-spaces in the new line.
* Note that the result may be collapsed range if the point is around
* invisible white-spaces.
*/
template <typename EditorDOMPointType>
EditorDOMRange GetNewInvisibleLeadingWhiteSpaceRangeIfSplittingAt(
const EditorDOMPointType& aPointToSplit) const {
// If there are invisible trailing white-spaces and some or all of them
// become invisible leading white-spaces in the new line, although we
// don't need to delete them, but for aesthetically and backward
// compatibility, we should remove them.
EditorDOMRange trailingWhiteSpaceRange =
GetInvisibleTrailingWhiteSpaceRange();
// XXX Why don't we check leading white-spaces too?
if (!trailingWhiteSpaceRange.IsPositioned()) {
return trailingWhiteSpaceRange;
}
// XXX Why don't we need to treat new trailing white-spaces are invisible
// when the trailing white-spaces are only the content in current
// line?
if (trailingWhiteSpaceRange != GetInvisibleLeadingWhiteSpaceRange()) {
return EditorDOMRange();
}
// If the point is before the trailing white-spaces, the new line won't
// start with leading white-spaces.
if (aPointToSplit.IsBefore(trailingWhiteSpaceRange.StartRef())) {
return EditorDOMRange();
}
// If the point is in the trailing white-spaces, the new line may
// start with some leading white-spaces. Returning collapsed range
// is intentional because the caller may want to know whether the
// point is in trailing white-spaces or not.
if (aPointToSplit.EqualsOrIsBefore(trailingWhiteSpaceRange.EndRef())) {
return EditorDOMRange(trailingWhiteSpaceRange.StartRef(),
aPointToSplit);
}
// Otherwise, if the point is after the trailing white-spaces, it may
// be just outside of the text node. E.g., end of parent element.
// This is possible case but the validation cost is not worthwhile
// due to the runtime cost in the worst case. Therefore, we should just
// return collapsed range at the end of trailing white-spaces. Then,
// callers can know the point is immediately after the trailing
// white-spaces.
return EditorDOMRange(trailingWhiteSpaceRange.EndRef());
}
/**
* GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt() returns new
* invisible trailing white-space range which should be removed if
* splitting invisible white-space sequence at aPointToSplit creates
* new invisible trailing white-spaces in the new line.
* Note that the result may be collapsed range if the point is around
* invisible white-spaces.
*/
template <typename EditorDOMPointType>
EditorDOMRange GetNewInvisibleTrailingWhiteSpaceRangeIfSplittingAt(
const EditorDOMPointType& aPointToSplit) const {
// If there are invisible leading white-spaces and some or all of them
// become end of current line, they will become visible. Therefore, we
// need to delete the invisible leading white-spaces before insertion
// point.
EditorDOMRange leadingWhiteSpaceRange =
GetInvisibleLeadingWhiteSpaceRange();
if (!leadingWhiteSpaceRange.IsPositioned()) {
return leadingWhiteSpaceRange;
}
// XXX Why don't we need to treat new leading white-spaces are invisible
// when the leading white-spaces are only the content in current
// line?
if (leadingWhiteSpaceRange != GetInvisibleTrailingWhiteSpaceRange()) {
return EditorDOMRange();
}
// If the point equals or is after the leading white-spaces, the line
// will end without trailing white-spaces.
if (leadingWhiteSpaceRange.EndRef().IsBefore(aPointToSplit)) {
return EditorDOMRange();
}
// If the point is in the leading white-spaces, the line may
// end with some trailing white-spaces. Returning collapsed range
// is intentional because the caller may want to know whether the
// point is in leading white-spaces or not.
if (leadingWhiteSpaceRange.StartRef().EqualsOrIsBefore(aPointToSplit)) {
return EditorDOMRange(aPointToSplit, leadingWhiteSpaceRange.EndRef());
}
// Otherwise, if the point is before the leading white-spaces, it may
// be just outside of the text node. E.g., start of parent element.
// This is possible case but the validation cost is not worthwhile
// due to the runtime cost in the worst case. Therefore, we should
// just return collapsed range at start of the leading white-spaces.
// Then, callers can know the point is immediately before the leading
// white-spaces.
return EditorDOMRange(leadingWhiteSpaceRange.StartRef());
}
/** /**
* CreateWSFragmentForVisibleAndMiddleOfLine() creates WSFragment whose * CreateWSFragmentForVisibleAndMiddleOfLine() creates WSFragment whose
@ -798,6 +894,8 @@ class MOZ_STACK_CLASS WSRunScanner {
BoundaryData mStart; BoundaryData mStart;
BoundaryData mEnd; BoundaryData mEnd;
NoBreakingSpaceData mNBSPData; NoBreakingSpaceData mNBSPData;
mutable Maybe<EditorDOMRange> mLeadingWhiteSpaceRange;
mutable Maybe<EditorDOMRange> mTrailingWhiteSpaceRange;
// XXX Currently we set mIsPreformatted to `WSRunScanner::mPRE` value // XXX Currently we set mIsPreformatted to `WSRunScanner::mPRE` value
// even if some text nodes between mStart and mEnd are different styled // even if some text nodes between mStart and mEnd are different styled
// nodes. This caused some bugs actually, but we now keep traditional // nodes. This caused some bugs actually, but we now keep traditional