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
This commit is contained in:
Masayuki Nakano 2019-08-23 06:05:38 +00:00
Родитель 9efbc2ee5b
Коммит 39bbc90bb9
3 изменённых файлов: 248 добавлений и 179 удалений

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

@ -6862,118 +6862,123 @@ nsresult HTMLEditRules::NormalizeSelection() {
return error.StealNSResult();
}
EditorDOMPoint HTMLEditRules::GetPromotedPoint(
RulesEndpoint aWhere, nsINode& aNode, int32_t aOffset,
EditSubAction aEditSubAction) const {
MOZ_ASSERT(IsEditorDataAvailable());
// 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),
&offset);
} else {
HTMLEditorRef().IsNextCharInNodeWhitespace(
content, newOffset, &isSpace, &isNBSP, getter_AddRefs(temp),
&offset);
}
if (isSpace || isNBSP) {
content = temp;
newOffset = offset;
} else {
break;
}
}
return EditorDOMPoint(content, newOffset);
// static
EditorDOMPoint HTMLEditor::GetWhiteSpaceEndPoint(const RangeBoundary& aPoint,
ScanDirection aScanDirection) {
if (NS_WARN_IF(!aPoint.IsSet()) ||
NS_WARN_IF(!aPoint.Container()->IsContent())) {
return EditorDOMPoint();
}
EditorDOMPoint point(&aNode, aOffset);
// 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
if (point.IsInTextNode()) {
if (!point.GetContainer()->GetParentNode()) {
// Okay, can't promote any further
return point;
}
point.Set(point.GetContainer());
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 {
HTMLEditor::IsNextCharInNodeWhitespace(newContent, newOffset, &isSpace,
&isNBSP, getter_AddRefs(content),
&offset);
}
// 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);
if (!isSpace && !isNBSP) {
break;
}
newContent = content;
newOffset = offset;
}
return EditorDOMPoint(newContent, newOffset);
}
// 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
if (aEditSubAction == EditSubAction::eOutdent &&
point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
break;
}
// 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 we're not performing a block-level action.
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()))) {
break;
}
point.Set(point.GetContainer());
nearNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point);
}
return point;
EditorDOMPoint HTMLEditor::GetCurrentHardLineStartPoint(
const RangeBoundary& aPoint, EditSubAction aEditSubAction) {
if (NS_WARN_IF(!aPoint.IsSet())) {
return EditorDOMPoint();
}
// aWhere == kEnd
// 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>
// from us, and that are enclosed in the same block.
// 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;
}
// 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 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 (!IsDescendantOfEditorRoot(point.GetContainer()->GetParentNode()) &&
(blockLevelAction || !IsDescendantOfEditorRoot(point.GetContainer()))) {
break;
}
point.Set(point.GetContainer());
}
return point;
}
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,44 +7124,81 @@ void HTMLEditRules::PromoteRange(nsRange& aRange,
}
}
if (aEditSubAction == EditSubAction::eInsertText ||
aEditSubAction == EditSubAction::eInsertTextComingFromIME ||
aEditSubAction == EditSubAction::eInsertLineBreak ||
aEditSubAction == EditSubAction::eInsertParagraphSeparator ||
aEditSubAction == 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;
switch (aEditSubAction) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
case EditSubAction::eInsertLineBreak:
case EditSubAction::eInsertParagraphSeparator:
case EditSubAction::eDeleteText: {
if (!startNode->IsContent() || !endNode->IsContent()) {
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.
// Make sure that the new range ends up to be in the editable section.
// 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 = 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 = 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> rvIgnored = aRange.SetStartAndEnd(
startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
MOZ_ASSERT(NS_SUCCEEDED(rvIgnored));
break;
}
}
// 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.
// Make sure that the new range ends up to be in the editable section.
// 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);
if (!HTMLEditorRef().IsDescendantOfEditorRoot(
EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) {
return;
}
EditorDOMPoint endPoint =
GetPromotedPoint(kEnd, *endNode, endOffset, aEditSubAction);
EditorRawDOMPoint lastRawPoint(endPoint);
lastRawPoint.RewindOffset();
if (!HTMLEditorRef().IsDescendantOfEditorRoot(
EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
return;
}
DebugOnly<nsresult> rv = aRange.SetStartAndEnd(
startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
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()) {

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

@ -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().

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

@ -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;