Commit 6cc8d22f authored by Masayuki Nakano's avatar Masayuki Nakano
Browse files

Bug 1727844 - part 4: Add API to check whether a char is collapsible or not r=m_kato

This patch adds `EditorUtils::IsNewLinePreformatted()` to check whether a
linefeed character is collapsible or not.

Then, a lot of users of `EditorDOMPointBase::Is*CharASCIISpace()` and
`EditorDOMPointBase::Is*CharASCIISpaceOrNBSP()` should check whether the
white-space at the point is collapsible or not.  Therefore, this patch adds
`Is*CharCollapsibleASCIISpace()` and `Is*CharCollapsibleASCIISpaceOrNBSP()`
too.

Then, makes such callers use the new API instead.

Differential Revision: https://phabricator.services.mozilla.com/D123871
parent 27194275
Loading
Loading
Loading
Loading
+40 −0
Original line number Diff line number Diff line
@@ -76,6 +76,12 @@ typedef EditorDOMPointBase<nsINode*, nsIContent*> EditorRawDOMPoint;
typedef EditorDOMPointBase<RefPtr<dom::Text>, nsIContent*> EditorDOMPointInText;
typedef EditorDOMPointBase<dom::Text*, nsIContent*> EditorRawDOMPointInText;

#define NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(aResultType, aMethodName) \
  template aResultType EditorDOMPoint::aMethodName;                      \
  template aResultType EditorRawDOMPoint::aMethodName;                   \
  template aResultType EditorDOMPointInText::aMethodName;                \
  template aResultType EditorRawDOMPointInText::aMethodName;

template <typename ParentType, typename ChildType>
class EditorDOMPointBase final {
  typedef EditorDOMPointBase<ParentType, ChildType> SelfType;
@@ -365,6 +371,16 @@ class EditorDOMPointBase final {
    char16_t ch = Char();
    return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
  }
  MOZ_NEVER_INLINE_DEBUG bool IsCharNewLine() const { return Char() == '\n'; }
  /**
   * IsCharCollapsibleASCIISpace(), IsCharCollapsibleNBSP() and
   * IsCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
   * preformatted or collapsible with the style of the container text node
   * without flushing pending notifications.
   */
  bool IsCharCollapsibleASCIISpace() const;
  bool IsCharCollapsibleNBSP() const;
  bool IsCharCollapsibleASCIISpaceOrNBSP() const;

  MOZ_NEVER_INLINE_DEBUG bool IsCharHighSurrogateFollowedByLowSurrogate()
      const {
@@ -397,6 +413,18 @@ class EditorDOMPointBase final {
    char16_t ch = PreviousChar();
    return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
  }
  MOZ_NEVER_INLINE_DEBUG bool IsPreviousCharNewLine() const {
    return PreviousChar() == '\n';
  }
  /**
   * IsPreviousCharCollapsibleASCIISpace(), IsPreviousCharCollapsibleNBSP() and
   * IsPreviousCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space
   * is preformatted or collapsible with the style of the container text node
   * without flushing pending notifications.
   */
  bool IsPreviousCharCollapsibleASCIISpace() const;
  bool IsPreviousCharCollapsibleNBSP() const;
  bool IsPreviousCharCollapsibleASCIISpaceOrNBSP() const;

  MOZ_NEVER_INLINE_DEBUG char16_t NextChar() const {
    MOZ_ASSERT(IsSetAndValid());
@@ -413,6 +441,18 @@ class EditorDOMPointBase final {
    char16_t ch = NextChar();
    return nsCRT::IsAsciiSpace(ch) || ch == 0x00A0;
  }
  MOZ_NEVER_INLINE_DEBUG bool IsNextCharNewLine() const {
    return NextChar() == '\n';
  }
  /**
   * IsNextCharCollapsibleASCIISpace(), IsNextCharCollapsibleNBSP() and
   * IsNextCharCollapsibleASCIISpaceOrNBSP() checks whether the white-space is
   * preformatted or collapsible with the style of the container text node
   * without flushing pending notifications.
   */
  bool IsNextCharCollapsibleASCIISpace() const;
  bool IsNextCharCollapsibleNBSP() const;
  bool IsNextCharCollapsibleASCIISpaceOrNBSP() const;

  uint32_t Offset() const {
    if (mOffset.isSome()) {
+148 −0
Original line number Diff line number Diff line
@@ -558,6 +558,48 @@ bool EditorUtils::IsWhiteSpacePreformatted(const nsIContent& aContent) {
  return elementStyle->StyleText()->WhiteSpaceIsSignificant();
}

// static
bool EditorUtils::IsNewLinePreformatted(const nsIContent& aContent) {
  // Look at the node (and its parent if it's not an element), and grab its
  // ComputedStyle.
  Element* element = aContent.GetAsElementOrParentElement();
  if (!element) {
    return false;
  }

  RefPtr<ComputedStyle> elementStyle =
      nsComputedDOMStyle::GetComputedStyleNoFlush(element);
  if (!elementStyle) {
    // Consider nodes without a ComputedStyle to be NOT preformatted:
    // For instance, this is true of JS tags inside the body (which show
    // up as #text nodes but have no ComputedStyle).
    return false;
  }

  return elementStyle->StyleText()->NewlineIsSignificantStyle();
}

// static
bool EditorUtils::IsOnlyNewLinePreformatted(const nsIContent& aContent) {
  // Look at the node (and its parent if it's not an element), and grab its
  // ComputedStyle.
  Element* element = aContent.GetAsElementOrParentElement();
  if (!element) {
    return false;
  }

  RefPtr<ComputedStyle> elementStyle =
      nsComputedDOMStyle::GetComputedStyleNoFlush(element);
  if (!elementStyle) {
    // Consider nodes without a ComputedStyle to be NOT preformatted:
    // For instance, this is true of JS tags inside the body (which show
    // up as #text nodes but have no ComputedStyle).
    return false;
  }

  return elementStyle->StyleText()->mWhiteSpace == StyleWhiteSpace::PreLine;
}

bool EditorUtils::IsPointInSelection(const Selection& aSelection,
                                     const nsINode& aParentNode,
                                     uint32_t aOffset) {
@@ -622,4 +664,110 @@ EditorUtils::CreateTransferableForPlainText(const Document& aDocument) {
  return transferable;
}

/******************************************************************************
 * mozilla::EditorDOMPointBase
 *****************************************************************************/

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool,
                                       IsCharCollapsibleASCIISpace() const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpace() const {
  if (IsCharNewLine()) {
    return !EditorUtils::IsNewLinePreformatted(*ContainerAsText());
  }
  return IsCharASCIISpace() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool, IsCharCollapsibleNBSP() const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleNBSP() const {
  // TODO: Perhaps, we should return false if neither previous char nor
  //       next char is collapsible white-space or NBSP.
  return IsCharNBSP() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool, IsCharCollapsibleASCIISpaceOrNBSP()
                                                 const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpaceOrNBSP() const {
  if (IsCharNewLine()) {
    return !EditorUtils::IsNewLinePreformatted(*ContainerAsText());
  }
  return IsCharASCIISpaceOrNBSP() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool,
                                       IsPreviousCharCollapsibleASCIISpace()
                                           const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpace() const {
  if (IsPreviousCharNewLine()) {
    return !EditorUtils::IsNewLinePreformatted(*ContainerAsText());
  }
  return IsPreviousCharASCIISpace() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool,
                                       IsPreviousCharCollapsibleNBSP() const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleNBSP() const {
  return IsPreviousCharNBSP() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(
    bool, IsPreviousCharCollapsibleASCIISpaceOrNBSP() const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpaceOrNBSP()
    const {
  if (IsPreviousCharNewLine()) {
    return !EditorUtils::IsNewLinePreformatted(*ContainerAsText());
  }
  return IsPreviousCharASCIISpaceOrNBSP() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool,
                                       IsNextCharCollapsibleASCIISpace() const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpace() const {
  if (IsNextCharNewLine()) {
    return !EditorUtils::IsNewLinePreformatted(*ContainerAsText());
  }
  return IsNextCharASCIISpace() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool, IsNextCharCollapsibleNBSP() const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleNBSP() const {
  return IsNextCharNBSP() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

NS_INSTANTIATE_EDITOR_DOM_POINT_METHOD(bool,
                                       IsNextCharCollapsibleASCIISpaceOrNBSP()
                                           const)

template <typename PT, typename CT>
bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpaceOrNBSP() const {
  if (IsNextCharNewLine()) {
    return !EditorUtils::IsNewLinePreformatted(*ContainerAsText());
  }
  return IsNextCharASCIISpaceOrNBSP() &&
         !EditorUtils::IsWhiteSpacePreformatted(*ContainerAsText());
}

}  // namespace mozilla
+13 −0
Original line number Diff line number Diff line
@@ -1166,6 +1166,19 @@ class EditorUtils final {
   */
  static bool IsWhiteSpacePreformatted(const nsIContent& aContent);

  /**
   * IsNewLinePreformatted() checks whether the linefeed characters are
   * preformatted or collapsible white-spaces.  This does NOT flush layout.
   */
  static bool IsNewLinePreformatted(const nsIContent& aContent);

  /**
   * IsOnlyNewLinePreformatted() checks whether the linefeed characters are
   * preformated but white-spaces are collapsed, or otherwise.  I.e., this
   * returns true only when `white-space:pre-line`.
   */
  static bool IsOnlyNewLinePreformatted(const nsIContent& aContent);

  /**
   * Helper method for `AppendString()` and `AppendSubString()`.  This should
   * be called only when `aText` is in a password field.  This method masks
+2 −6
Original line number Diff line number Diff line
@@ -2114,16 +2114,12 @@ void HTMLEditor::ExtendRangeToDeleteWithNormalizingWhiteSpaces(
      !precedingCharPoint.IsEndOfContainer() &&
      precedingCharPoint.ContainerAsText() ==
          aStartToDelete.ContainerAsText() &&
      precedingCharPoint.IsCharASCIISpaceOrNBSP() &&
      !EditorUtils::IsWhiteSpacePreformatted(
          *precedingCharPoint.ContainerAsText());
      precedingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();
  const bool maybeNormalizeFollowingWhiteSpaces =
      followingCharPoint.IsSet() && !followingCharPoint.IsEndOfContainer() &&
      (followingCharPoint.ContainerAsText() == aEndToDelete.ContainerAsText() ||
       removingLastCharOfStartNode) &&
      followingCharPoint.IsCharASCIISpaceOrNBSP() &&
      !EditorUtils::IsWhiteSpacePreformatted(
          *followingCharPoint.ContainerAsText());
      followingCharPoint.IsCharCollapsibleASCIISpaceOrNBSP();

  if (!maybeNormalizePrecedingWhiteSpaces &&
      !maybeNormalizeFollowingWhiteSpaces) {
+19 −27
Original line number Diff line number Diff line
@@ -732,9 +732,7 @@ Result<RefPtr<Element>, nsresult> WhiteSpaceVisibilityKeeper::InsertBRElement(
              pointToInsert);
      if (atNextCharOfInsertionPoint.IsSet() &&
          !atNextCharOfInsertionPoint.IsEndOfContainer() &&
          atNextCharOfInsertionPoint.IsCharASCIISpace() &&
          !EditorUtils::IsWhiteSpacePreformatted(
              *atNextCharOfInsertionPoint.ContainerAsText())) {
          atNextCharOfInsertionPoint.IsCharCollapsibleASCIISpace()) {
        EditorRawDOMPointInText atPreviousCharOfNextCharOfInsertionPoint =
            textFragmentDataAtInsertionPoint.GetPreviousEditableCharPoint(
                atNextCharOfInsertionPoint);
@@ -1118,8 +1116,7 @@ nsresult WhiteSpaceVisibilityKeeper::DeletePreviousWhiteSpace(
  // Easy case, preformatted ws.
  if (EditorUtils::IsWhiteSpacePreformatted(
          *atPreviousCharOfStart.ContainerAsText())) {
    if (!atPreviousCharOfStart.IsCharASCIISpace() &&
        !atPreviousCharOfStart.IsCharNBSP()) {
    if (!atPreviousCharOfStart.IsCharASCIISpaceOrNBSP()) {
      return NS_OK;
    }
    nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
@@ -1203,8 +1200,7 @@ nsresult WhiteSpaceVisibilityKeeper::DeleteInclusiveNextWhiteSpace(
  // Easy case, preformatted ws.
  if (EditorUtils::IsWhiteSpacePreformatted(
          *atNextCharOfStart.ContainerAsText())) {
    if (!atNextCharOfStart.IsCharASCIISpace() &&
        !atNextCharOfStart.IsCharNBSP()) {
    if (!atNextCharOfStart.IsCharASCIISpaceOrNBSP()) {
      return NS_OK;
    }
    nsresult rv = aHTMLEditor.DeleteTextAndTextNodesWithTransaction(
@@ -2263,9 +2259,7 @@ WSRunScanner::TextFragmentData::GetReplaceRangeDataAtStartOfDeletionRange(
      GetPreviousEditableCharPoint(startToDelete);
  if (!atPreviousCharOfStart.IsSet() ||
      atPreviousCharOfStart.IsEndOfContainer() ||
      !atPreviousCharOfStart.IsCharASCIISpace() ||
      EditorUtils::IsWhiteSpacePreformatted(
          *atPreviousCharOfStart.ContainerAsText())) {
      !atPreviousCharOfStart.IsCharCollapsibleASCIISpace()) {
    return ReplaceRangeData();
  }
  if (atPreviousCharOfStart.IsStartOfContainer() ||
@@ -2316,9 +2310,7 @@ WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
        textFragmentDataAtSplitPoint.GetInclusiveNextEditableCharPoint(
            pointToSplit);
    if (atNextCharOfStart.IsSet() && !atNextCharOfStart.IsEndOfContainer() &&
        atNextCharOfStart.IsCharASCIISpace() &&
        !EditorUtils::IsWhiteSpacePreformatted(
            *atNextCharOfStart.ContainerAsText())) {
        atNextCharOfStart.IsCharCollapsibleASCIISpace()) {
      // pointToSplit will be referred bellow so that we need to keep
      // it a valid point.
      AutoEditorDOMPointChildInvalidator forgetChild(pointToSplit);
@@ -2355,9 +2347,7 @@ WhiteSpaceVisibilityKeeper::MakeSureToKeepVisibleWhiteSpacesVisibleAfterSplit(
        textFragmentDataAtSplitPoint.GetPreviousEditableCharPoint(pointToSplit);
    if (atPreviousCharOfStart.IsSet() &&
        !atPreviousCharOfStart.IsEndOfContainer() &&
        atPreviousCharOfStart.IsCharASCIISpace() &&
        !EditorUtils::IsWhiteSpacePreformatted(
            *atPreviousCharOfStart.ContainerAsText())) {
        atPreviousCharOfStart.IsCharCollapsibleASCIISpace()) {
      if (atPreviousCharOfStart.IsStartOfContainer() ||
          atPreviousCharOfStart.IsPreviousCharASCIISpace()) {
        atPreviousCharOfStart =
@@ -2597,10 +2587,12 @@ WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(
    const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const {
  MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet());
  MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer());
  MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsCharASCIISpace());
  NS_ASSERTION(!EditorUtils::IsWhiteSpacePreformatted(
                   *aPointAtASCIIWhiteSpace.ContainerAsText()),
               "aPointAtASCIIWhiteSpace should be in a formatted text node");
  MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted(
                    *aPointAtASCIIWhiteSpace.ContainerAsContent()),
                aPointAtASCIIWhiteSpace.IsCharCollapsibleASCIISpace());
  MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted(
                    *aPointAtASCIIWhiteSpace.ContainerAsContent()),
                aPointAtASCIIWhiteSpace.IsCharASCIISpace());

  // If it's not the last character in the text node, let's scan following
  // characters in it.
@@ -2637,9 +2629,7 @@ WSRunScanner::TextFragmentData::GetEndOfCollapsibleASCIIWhiteSpaces(

    // If next node starts with non-white-space character or next node is
    // preformatted, return end of previous text node.
    if (!atStartOfNextTextNode.IsCharASCIISpace() ||
        EditorUtils::IsWhiteSpacePreformatted(
            *atStartOfNextTextNode.ContainerAsText())) {
    if (!atStartOfNextTextNode.IsCharCollapsibleASCIISpace()) {
      return afterLastWhiteSpace;
    }

@@ -2663,10 +2653,12 @@ WSRunScanner::TextFragmentData::GetFirstASCIIWhiteSpacePointCollapsedTo(
    const EditorDOMPointInText& aPointAtASCIIWhiteSpace) const {
  MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsSet());
  MOZ_ASSERT(!aPointAtASCIIWhiteSpace.IsEndOfContainer());
  MOZ_ASSERT(aPointAtASCIIWhiteSpace.IsCharASCIISpace());
  NS_ASSERTION(!EditorUtils::IsWhiteSpacePreformatted(
                   *aPointAtASCIIWhiteSpace.ContainerAsText()),
               "aPointAtASCIIWhiteSpace should be in a formatted text node");
  MOZ_ASSERT_IF(!EditorUtils::IsNewLinePreformatted(
                    *aPointAtASCIIWhiteSpace.ContainerAsContent()),
                aPointAtASCIIWhiteSpace.IsCharCollapsibleASCIISpace());
  MOZ_ASSERT_IF(EditorUtils::IsNewLinePreformatted(
                    *aPointAtASCIIWhiteSpace.ContainerAsContent()),
                aPointAtASCIIWhiteSpace.IsCharASCIISpace());

  // If there is some characters before it, scan it in the text node first.
  if (!aPointAtASCIIWhiteSpace.IsStartOfContainer()) {