Commit 39bbc90b authored by Masayuki Nakano's avatar Masayuki Nakano
Browse files

Bug 1574852 - part 20: Move `HTMLEditRules::GetPromotedPoint()` to `HTMLEditor` r=m_kato

`HTMLEditRules::GetPromotedPoint()` does too many things.  Therefore, this patch
splits it to 3 methods.  One is for specific `EditSubAction` values, that's
the first block in `GetPromotedPoint()`.  It's named as
`GetWhiteSpaceEndPoint()`.  Next one is for expanding start of the range to
start of its line.  It's named as `GetCurrentHardLineStartPoint()`.  The last
one is for expanding end of the range to end of its line.  It's named as
`GetCurrentHardLineEndPoint()`.

Differential Revision: https://phabricator.services.mozilla.com/D42791

--HG--
extra : moz-landing-system : lando
parent 9efbc2ee
Loading
Loading
Loading
Loading
+209 −163
Original line number Diff line number Diff line
@@ -6862,86 +6862,84 @@ nsresult HTMLEditRules::NormalizeSelection() {
  return error.StealNSResult();
}

EditorDOMPoint HTMLEditRules::GetPromotedPoint(
    RulesEndpoint aWhere, nsINode& aNode, int32_t aOffset,
    EditSubAction aEditSubAction) const {
  MOZ_ASSERT(IsEditorDataAvailable());
// static
EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(const RangeBoundary& aPoint,
                                                 ScanDirection aScanDirection) {
  if (NS_WARN_IF(!aPoint.IsSet()) ||
      NS_WARN_IF(!aPoint.Container()->IsContent())) {
    return EditorDOMPoint();
  }

  // we do one thing for text actions, something else entirely for other
  // actions
  if (aEditSubAction == EditSubAction::eInsertText ||
      aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
      aEditSubAction == EditSubAction::eInsertLineBreak ||
      aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
      aEditSubAction == EditSubAction::eDeleteText) {
    bool isSpace, isNBSP;
    nsCOMPtr<nsIContent> content =
        aNode.IsContent() ? aNode.AsContent() : nullptr;
    nsCOMPtr<nsIContent> temp;
    int32_t newOffset = aOffset;
    // for text actions, we want to look backwards (or forwards, as
    // appropriate) for additional whitespace or nbsp's.  We may have to act on
    // these later even though they are outside of the initial selection.  Even
    // if they are in another node!
    while (content) {
      int32_t offset;
      if (aWhere == kStart) {
        HTMLEditorRef().IsPrevCharInNodeWhitespace(
            content, newOffset, &isSpace, &isNBSP, getter_AddRefs(temp),
  bool isSpace = false, isNBSP = false;
  nsIContent* newContent = aPoint.Container()->AsContent();
  int32_t newOffset = aPoint.Offset();
  while (newContent) {
    int32_t offset = -1;
    nsCOMPtr<nsIContent> content;
    if (aScanDirection == ScanDirection::Backward) {
      HTMLEditor::IsPrevCharInNodeWhitespace(newContent, newOffset, &isSpace,
                                             &isNBSP, getter_AddRefs(content),
                                             &offset);
    } else {
        HTMLEditorRef().IsNextCharInNodeWhitespace(
            content, newOffset, &isSpace, &isNBSP, getter_AddRefs(temp),
      HTMLEditor::IsNextCharInNodeWhitespace(newContent, newOffset, &isSpace,
                                             &isNBSP, getter_AddRefs(content),
                                             &offset);
    }
      if (isSpace || isNBSP) {
        content = temp;
        newOffset = offset;
      } else {
    if (!isSpace && !isNBSP) {
      break;
    }
    newContent = content;
    newOffset = offset;
  }

    return EditorDOMPoint(content, newOffset);
  return EditorDOMPoint(newContent, newOffset);
}

  EditorDOMPoint point(&aNode, aOffset);
EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
    const RangeBoundary& aPoint, EditSubAction aEditSubAction) {
  if (NS_WARN_IF(!aPoint.IsSet())) {
    return EditorDOMPoint();
  }

  // else not a text section.  In this case we want to see if we should grab
  // any adjacent inline nodes and/or parents and other ancestors
  if (aWhere == kStart) {
    // some special casing for text nodes
  EditorDOMPoint point(aPoint);
  // Start scanning from the container node if aPoint is in a text node.
  // XXX Perhaps, IsInDataNode() must be expected.
  if (point.IsInTextNode()) {
    if (!point.GetContainer()->GetParentNode()) {
      // Okay, can't promote any further
      // XXX Why don't we return start of the text node?
      return point;
    }
    point.Set(point.GetContainer());
  }

    // look back through any further inline nodes that aren't across a <br>
  // Look back through any further inline nodes that aren't across a <br>
  // from us, and that are enclosed in the same block.
    nsCOMPtr<nsINode> priorNode =
        HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);

    while (priorNode && priorNode->GetParentNode() &&
           !HTMLEditorRef().IsVisibleBRElement(priorNode) &&
           !HTMLEditor::NodeIsBlockStatic(*priorNode)) {
      point.Set(priorNode);
      priorNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
    }

    // finding the real start for this point.  look up the tree for as long as
    // we are the first node in the container, and as long as we haven't hit
    // the body node.
    nsCOMPtr<nsIContent> nearNode =
        HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
    while (!nearNode && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
           point.GetContainer()->GetParentNode()) {
      // some cutoffs are here: we don't need to also include them in the
      // aWhere == kEnd case.  as long as they are in one or the other it will
      // work.  special case for outdent: don't keep looking up if we have
      // found a blockquote element to act on
  // I.e., looking for start of current hard line.
  for (nsIContent* previousEditableContent =
           GetPreviousEditableHTMLNodeInBlock(point);
       previousEditableContent && previousEditableContent->GetParentNode() &&
       !IsVisibleBRElement(previousEditableContent) &&
       !HTMLEditor::NodeIsBlockStatic(*previousEditableContent);
       previousEditableContent = GetPreviousEditableHTMLNodeInBlock(point)) {
    point.Set(previousEditableContent);
    // XXX Why don't we check \n in pre-formated text node here?
  }

  // Finding the real start for this point unless current line starts after
  // <br> element.  Look up the tree for as long as we are the first node in
  // the container (typically, start of nearest block ancestor), and as long
  // as we haven't hit the body node.
  for (nsIContent* nearContent = GetPreviousEditableHTMLNodeInBlock(point);
       !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
       point.GetContainer()->GetParentNode();
       nearContent = GetPreviousEditableHTMLNodeInBlock(point)) {
    // Don't keep looking up if we have found a blockquote element to act on
    // when we handle outdent.
    // XXX Sounds like this is hacky.  If possible, it should be check in
    //     outdent handler for consistency between edit sub-actions.
    //     We should check Chromium's behavior of outdent when Selection
    //     starts from `<blockquote>` and starts from first child of
    //     `<blockquote>`.
    if (aEditSubAction == EditSubAction::eOutdent &&
        point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
      break;
@@ -6951,29 +6949,36 @@ EditorDOMPoint HTMLEditRules::GetPromotedPoint(
    // before walking up to a parent because we need to return the parent
    // object, so the parent itself might not be in the editable area, but
    // it's OK if we're not performing a block-level action.
    // XXX Here is too slow.  Let's cache active editing host first, then,
    //     compair with container of the point when we climb up the tree.
    bool blockLevelAction =
        aEditSubAction == EditSubAction::eIndent ||
        aEditSubAction == EditSubAction::eOutdent ||
        aEditSubAction == EditSubAction::eSetOrClearAlignment ||
        aEditSubAction == EditSubAction::eCreateOrRemoveBlock;
      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
              point.GetContainer()->GetParentNode()) &&
          (blockLevelAction ||
           !HTMLEditorRef().IsDescendantOfEditorRoot(point.GetContainer()))) {
    if (!IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode()) &&
        (blockLevelAction || !IsDescendantOfEditorRoot(point.GetContainer()))) {
      break;
    }

    point.Set(point.GetContainer());
      nearNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
  }
  return point;
}

  // aWhere == kEnd
  // some special casing for text nodes
EditorDOMPoint HTMLEditor::GetCurrentHardLineEndPoint(
    const RangeBoundary& aPoint) {
  if (NS_WARN_IF(!aPoint.IsSet())) {
    return EditorDOMPoint();
  }

  EditorDOMPoint point(aPoint);
  // Start scanning from the container node if aPoint is in a text node.
  // XXX Perhaps, IsInDataNode() must be expected.
  if (point.IsInTextNode()) {
    if (!point.GetContainer()->GetParentNode()) {
      // Okay, can't promote any further
      // XXX Why don't we return end of the text node?
      return point;
    }
    // want to be after the text node
@@ -6983,7 +6988,7 @@ EditorDOMPoint HTMLEditRules::GetPromotedPoint(
                         "Failed to advance offset to after the text node");
  }

  // look ahead through any further inline nodes that aren't across a <br> from
  // Look ahead through any further inline nodes that aren't across a <br> from
  // us, and that are enclosed in the same block.
  // XXX Currently, we stop block-extending when finding visible <br> element.
  //     This might be different from "block-extend" of execCommand spec.
@@ -6997,49 +7002,53 @@ EditorDOMPoint HTMLEditRules::GetPromotedPoint(
  //     * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
  //     Only in the first case, after the caret position isn't wrapped with
  //     new <div> element.
  nsCOMPtr<nsIContent> nextNode =
      HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);

  while (nextNode && !HTMLEditor::NodeIsBlockStatic(*nextNode) &&
         nextNode->GetParentNode()) {
    point.Set(nextNode);
  for (nsIContent* nextEditableContent = GetNextEditableHTMLNodeInBlock(point);
       nextEditableContent &&
       !HTMLEditor::NodeIsBlockStatic(*nextEditableContent) &&
       nextEditableContent->GetParent();
       nextEditableContent = GetNextEditableHTMLNodeInBlock(point)) {
    point.Set(nextEditableContent);
    if (NS_WARN_IF(!point.AdvanceOffset())) {
      break;
    }
    if (HTMLEditorRef().IsVisibleBRElement(nextNode)) {
    if (IsVisibleBRElement(nextEditableContent)) {
      break;
    }

    // Check for newlines in pre-formatted text nodes.
    if (EditorBase::IsPreformatted(nextNode) &&
        EditorBase::IsTextNode(nextNode)) {
      nsAutoString tempString;
      nextNode->GetAsText()->GetData(tempString);
      int32_t newlinePos = tempString.FindChar(nsCRT::LF);
    if (EditorBase::IsPreformatted(nextEditableContent) &&
        EditorBase::IsTextNode(nextEditableContent)) {
      nsAutoString textContent;
      nextEditableContent->GetAsText()->GetData(textContent);
      int32_t newlinePos = textContent.FindChar(nsCRT::LF);
      if (newlinePos >= 0) {
        if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
        if (static_cast<uint32_t>(newlinePos) + 1 == textContent.Length()) {
          // No need for special processing if the newline is at the end.
          break;
        }
        return EditorDOMPoint(nextNode, newlinePos + 1);
        return EditorDOMPoint(nextEditableContent, newlinePos + 1);
      }
    }
    nextNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
  }

  // finding the real end for this point.  look up the tree for as long as we
  // are the last node in the container, and as long as we haven't hit the body
  // Finding the real end for this point unless current line ends with a <br>
  // element.  Look up the tree for as long as we are the last node in the
  // container (typically, block node), and as long as we haven't hit the body
  // node.
  nsCOMPtr<nsIContent> nearNode =
      HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
  while (!nearNode && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
         point.GetContainer()->GetParentNode()) {
  for (nsIContent* nearContent = GetNextEditableHTMLNodeInBlock(point);
       !nearContent && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
       point.GetContainer()->GetParentNode();
       nearContent = GetNextEditableHTMLNodeInBlock(point)) {
    // Don't walk past the editable section. Note that we need to check before
    // walking up to a parent because we need to return the parent object, so
    // the parent itself might not be in the editable area, but it's OK.
    if (!HTMLEditorRef().IsDescendantOfEditorRoot(point.GetContainer()) &&
        !HTMLEditorRef().IsDescendantOfEditorRoot(
            point.GetContainer()->GetParentNode())) {
    // XXX Maybe returning parent of editing host is really error prone since
    //     everybody need to check whether the end point is in editing host
    //     when they touch there.
    // XXX Here is too slow.  Let's cache active editing host first, then,
    //     compair with container of the point when we climb up the tree.
    if (!IsDescendantOfEditorRoot(point.GetContainer()) &&
        !IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode())) {
      break;
    }

@@ -7047,7 +7056,6 @@ EditorDOMPoint HTMLEditRules::GetPromotedPoint(
    if (NS_WARN_IF(!point.AdvanceOffset())) {
      break;
    }
    nearNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point);
  }
  return point;
}
@@ -7090,9 +7098,11 @@ void HTMLEditRules::PromoteRange(nsRange& aRange,
  int32_t endOffset = aRange.EndOffset();

  // MOOSE major hack:
  // GetPromotedPoint doesn't really do the right thing for collapsed ranges
  // The following methods don't really do the right thing for collapsed ranges
  // inside block elements that contain nothing but a solo <br>.  It's easier
  // to put a workaround here than to revamp GetPromotedPoint.  :-(
  // to put a workaround here than to revamp them.  :-(
  // XXX This sounds odd in the cases using `GetWhiteSpaceEndPoint()`.  Don't
  //     we hack something in the edit sub-action handlers?
  if (startNode == endNode && startOffset == endOffset) {
    RefPtr<Element> block = HTMLEditorRef().GetBlock(*startNode);
    if (block) {
@@ -7114,18 +7124,46 @@ void HTMLEditRules::PromoteRange(nsRange& aRange,
    }
  }

  if (aEditSubAction == EditSubAction::eInsertText ||
      aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
      aEditSubAction == EditSubAction::eInsertLineBreak ||
      aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
      aEditSubAction == EditSubAction::eDeleteText) {
  switch (aEditSubAction) {
    case EditSubAction::eInsertText:
    case EditSubAction::eInsertTextComingFromIME:
    case EditSubAction::eInsertLineBreak:
    case EditSubAction::eInsertParagraphSeparator:
    case EditSubAction::eDeleteText: {
      if (!startNode->IsContent() || !endNode->IsContent()) {
      // GetPromotedPoint cannot promote node when action type is text
      // operation and selected node isn't content node.
        return;
      }
      // For text actions, we want to look backwards (or forwards, as
      // appropriate) for additional whitespace or nbsp's.  We may have to act
      // on these later even though they are outside of the initial selection.
      // Even if they are in another node!
      // XXX Although the comment mentioned that this may scan other text nodes,
      //     GetWhiteSpaceEndPoint() scans only in given container node.
      // XXX Looks like that we should make GetWhiteSpaceEndPoint() be a
      //     instance method and stop scanning whitespaces when it reaches
      //     active editing host.
      EditorDOMPoint startPoint = HTMLEditor::GetWhiteSpaceEndPoint(
          RangeBoundary(startNode, startOffset),
          HTMLEditor::ScanDirection::Backward);
      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
              EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
        return;
      }

      EditorDOMPoint endPoint =
          HTMLEditor::GetWhiteSpaceEndPoint(RangeBoundary(endNode, endOffset),
                                            HTMLEditor::ScanDirection::Forward);
      EditorRawDOMPoint lastRawPoint(endPoint);
      lastRawPoint.RewindOffset();
      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
              EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
        return;
      }
      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
          startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
      MOZ_ASSERT(NS_SUCCEEDED(rvIgnored));
      break;
    }
    default: {
      // Make a new adjusted range to represent the appropriate block content.
      // This is tricky.  The basic idea is to push out the range endpoints to
      // truly enclose the blocks that we will affect.
@@ -7134,24 +7172,33 @@ void HTMLEditRules::PromoteRange(nsRange& aRange,
      // XXX Looks like that this check wastes the time.  Perhaps, we should
      //     implement a method which checks both two DOM points in the editor
      //     root.
  EditorDOMPoint startPoint =
      GetPromotedPoint(kStart, *startNode, startOffset, aEditSubAction);

      EditorDOMPoint startPoint = HTMLEditorRef().GetCurrentHardLineStartPoint(
          RangeBoundary(startNode, startOffset), aEditSubAction);
      // XXX GetCurrentHardLineStartPoint() may return point of editing host.
      //     Perhaps, we should change it and stop checking it here since
      //     this check may be expensive.
      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
              EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
        return;
      }
  EditorDOMPoint endPoint =
      GetPromotedPoint(kEnd, *endNode, endOffset, aEditSubAction);
      EditorDOMPoint endPoint = HTMLEditorRef().GetCurrentHardLineEndPoint(
          RangeBoundary(endNode, endOffset));
      EditorRawDOMPoint lastRawPoint(endPoint);
      lastRawPoint.RewindOffset();
      // XXX GetCurrentHardLineEndPoint() may return point of editing host.
      //     Perhaps, we should change it and stop checking it here since this
      //     check may be expensive.
      if (!HTMLEditorRef().IsDescendantOfEditorRoot(
              EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
        return;
      }

  DebugOnly<nsresult> rv = aRange.SetStartAndEnd(
      DebugOnly<nsresult> rvIgnored = aRange.SetStartAndEnd(
          startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
  MOZ_ASSERT(NS_SUCCEEDED(rv));
      MOZ_ASSERT(NS_SUCCEEDED(rvIgnored));
      break;
    }
  }
}

class UniqueFunctor final : public BoolDomIterFunctor {
@@ -7196,9 +7243,8 @@ nsresult HTMLEditor::SplitInlinesAndCollectEditTargetNodes(

nsresult HTMLEditor::SplitTextNodesAtRangeEnd(
    nsTArray<RefPtr<nsRange>>& aArrayOfRanges) {
  // Split text nodes. This is necessary, since GetPromotedPoint() may return
  // a range ending in a text node in case where part of a pre-formatted
  // elements needs to be moved.
  // Split text nodes. This is necessary, since given ranges may end in text
  // nodes in case where part of a pre-formatted elements needs to be moved.
  for (RefPtr<nsRange>& range : aArrayOfRanges) {
    EditorDOMPoint atEnd(range->EndRef());
    if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode()) {
+0 −10
Original line number Diff line number Diff line
@@ -118,8 +118,6 @@ class HTMLEditRules : public TextEditRules {
    return mData->HTMLEditorRef();
  }

  enum RulesEndpoint { kStart, kEnd };

  /**
   * WillInsertParagraphSeparator() is called when insertParagraph command is
   * executed or something equivalent.  This method actually tries to insert
@@ -788,14 +786,6 @@ class HTMLEditRules : public TextEditRules {
  MOZ_CAN_RUN_SCRIPT
  MOZ_MUST_USE nsresult NormalizeSelection();

  /**
   * GetPromotedPoint() figures out where a start or end point for a block
   * operation really is.
   */
  EditorDOMPoint GetPromotedPoint(RulesEndpoint aWhere, nsINode& aNode,
                                  int32_t aOffset,
                                  EditSubAction aEditSubAction) const;

  /**
   * GetPromotedRanges() runs all the selection range endpoint through
   * GetPromotedPoint().
+33 −0
Original line number Diff line number Diff line
@@ -1320,6 +1320,39 @@ class HTMLEditor final : public TextEditor,
      nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
      EditSubAction aEditSubAction) const;

  /**
   * GetWhiteSpaceEndPoint() returns point at first or last ASCII whitespace
   * or non-breakable space starting from aPoint.  I.e., this returns next or
   * previous point whether the character is neither ASCII whitespace nor
   * non-brekable space.
   */
  enum class ScanDirection { Backward, Forward };
  static EditorDOMPoint GetWhiteSpaceEndPoint(const RangeBoundary& aPoint,
                                              ScanDirection aScanDirection);

  /**
   * GetCurrentHardLineStartPoint() returns start point of hard line
   * including aPoint.  If the line starts after a `<br>` element, returns
   * next sibling of the `<br>` element.  If the line is first line of a block,
   * returns point of the block.
   * NOTE: The result may be point of editing host.  I.e., the container may
   *       be outside of editing host.
   */
  EditorDOMPoint GetCurrentHardLineStartPoint(const RangeBoundary& aPoint,
                                              EditSubAction aEditSubAction);

  /**
   * GetCurrentHardLineEndPoint() returns end point of hard line including
   * aPoint.  If the line ends with a `<br>` element, returns the `<br>`
   * element unless it's the last node of a block.  If the line is last line
   * of a block, returns next sibling of the block.  Additionally, if the
   * line ends with a linefeed in pre-formated text node, returns point of
   * the linefeed.
   * NOTE: This result may be point of editing host.  I.e., the container
   *       may be outside of editing host.
   */
  EditorDOMPoint GetCurrentHardLineEndPoint(const RangeBoundary& aPoint);

 protected:  // Called by helper classes.
  virtual void OnStartToHandleTopLevelEditSubAction(
      EditSubAction aEditSubAction, nsIEditor::EDirection aDirection) override;