Bug 1616257 - part 3: Make `WSRunScanner::NextVisibleNode()` and `WSRunScanner::PriorVisibleNode()` return stack only class instance which storing the visible node as `nsIContent` r=m_kato

They are really messy because they take a lot of out parameters, and these
out parameter meaning is really unclear.  Therefore, they should return
a stack only class instance which explain the meaning with getter methods.
And also it should store the result node as `nsIContent`.

And also this patch adds static methods for them for their users which don't
need `WSRunScanner` instance.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2020-02-25 12:10:04 +00:00
Родитель 6382dae45f
Коммит f5407e4219
8 изменённых файлов: 524 добавлений и 376 удалений

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

@ -373,6 +373,11 @@ class EditorDOMPointBase final {
}
SetToEndOf(parentNode);
}
static SelfType After(const nsINode& aContainer) {
SelfType point;
point.SetAfter(&aContainer);
return point;
}
/**
* Clear() makes the instance not point anywhere.

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

@ -1920,8 +1920,7 @@ nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
return NS_ERROR_INVALID_ARG;
}
bool brElementIsAfterBlock = false;
bool brElementIsBeforeBlock = false;
bool brElementIsAfterBlock = false, brElementIsBeforeBlock = false;
// First, insert a <br> element.
RefPtr<Element> brElement;
@ -1936,15 +1935,12 @@ nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
} else {
EditorDOMPoint pointToBreak(aPointToBreak);
WSRunObject wsObj(this, pointToBreak);
WSType wsType;
wsObj.PriorVisibleNode(pointToBreak, &wsType);
if (wsType & WSType::block) {
brElementIsAfterBlock = true;
}
wsObj.NextVisibleNode(pointToBreak, &wsType);
if (wsType & WSType::block) {
brElementIsBeforeBlock = true;
}
brElementIsAfterBlock =
wsObj.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToBreak)
.ReachedBlockBoundary();
brElementIsBeforeBlock =
wsObj.ScanNextVisibleNodeOrBlockBoundaryFrom(pointToBreak)
.ReachedBlockBoundary();
// If the container of the break is a link, we need to split it and
// insert new <br> between the split links.
RefPtr<Element> linkNode =
@ -2003,22 +1999,21 @@ nsresult HTMLEditor::InsertBRElement(const EditorDOMPoint& aPointToBreak) {
DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset after the new <br> element");
WSRunObject wsObj(this, afterBRElement);
nsCOMPtr<nsINode> maybeSecondBRNode;
WSType wsType;
wsObj.NextVisibleNode(afterBRElement, address_of(maybeSecondBRNode), nullptr,
&wsType);
if (wsType == WSType::br) {
WSScanResult forwardScanFromAfterBRElementResult =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this, afterBRElement);
if (forwardScanFromAfterBRElementResult.ReachedBRElement()) {
// The next thing after the break we inserted is another break. Move the
// second break to be the first break's sibling. This will prevent them
// from being in different inline nodes, which would break
// SetInterlinePosition(). It will also assure that if the user clicks
// away and then clicks back on their new blank line, they will still get
// the style from the line above.
EditorDOMPoint atSecondBRElement(maybeSecondBRNode);
if (brElement->GetNextSibling() != maybeSecondBRNode) {
if (brElement->GetNextSibling() !=
forwardScanFromAfterBRElementResult.BRElementPtr()) {
MOZ_ASSERT(forwardScanFromAfterBRElementResult.BRElementPtr());
nsresult rv = MoveNodeWithTransaction(
MOZ_KnownLive(*maybeSecondBRNode->AsContent()), afterBRElement);
MOZ_KnownLive(*forwardScanFromAfterBRElementResult.BRElementPtr()),
afterBRElement);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -2077,18 +2072,14 @@ EditActionResult HTMLEditor::SplitMailCiteElements(
// The latter can confuse a user if they click there and start typing,
// because being in the mailquote may affect wrapping behavior, or font
// color, etc.
WSRunObject wsObj(this, pointToSplit);
nsCOMPtr<nsINode> visNode;
WSType wsType;
wsObj.NextVisibleNode(pointToSplit, address_of(visNode), nullptr, &wsType);
WSScanResult forwardScanFromPointToSplitResult =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this, pointToSplit);
// If selection start point is before a break and it's inside the mailquote,
// let's split it after the visible node.
if (wsType == WSType::br && visNode != citeNode &&
citeNode->Contains(visNode)) {
pointToSplit.Set(visNode);
DebugOnly<bool> advanced = pointToSplit.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset to after the visible node");
if (forwardScanFromPointToSplitResult.ReachedBRElement() &&
forwardScanFromPointToSplitResult.BRElementPtr() != citeNode &&
citeNode->Contains(forwardScanFromPointToSplitResult.BRElementPtr())) {
pointToSplit = forwardScanFromPointToSplitResult.PointAfterContent();
}
if (NS_WARN_IF(!pointToSplit.GetContainerAsContent())) {
@ -2174,21 +2165,26 @@ EditActionResult HTMLEditor::SplitMailCiteElements(
EditorDOMPoint pointToCreateNewBrNode(atBrNode.GetContainer(),
atBrNode.Offset());
WSRunObject wsObj(this, pointToCreateNewBrNode);
WSType wsType;
wsObj.PriorVisibleNode(pointToCreateNewBrNode, nullptr, nullptr, &wsType);
if (wsType == WSType::normalWS || wsType == WSType::text ||
wsType == WSType::special) {
WSScanResult backwardScanFromPointToCreateNewBRElementResult =
WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundary(
*this, pointToCreateNewBrNode);
if (backwardScanFromPointToCreateNewBRElementResult
.InNormalWhiteSpacesOrText() ||
backwardScanFromPointToCreateNewBRElementResult
.ReachedSpecialContent()) {
EditorRawDOMPoint pointAfterNewBrNode(pointToCreateNewBrNode);
DebugOnly<bool> advanced = pointAfterNewBrNode.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset after the <br> node");
WSRunObject wsObjAfterBR(this, pointAfterNewBrNode);
wsObjAfterBR.NextVisibleNode(pointAfterNewBrNode, &wsType);
if (wsType == WSType::normalWS || wsType == WSType::text ||
wsType == WSType::special ||
WSScanResult forwardScanFromPointAfterNewBRElementResult =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
pointAfterNewBrNode);
if (forwardScanFromPointAfterNewBRElementResult
.InNormalWhiteSpacesOrText() ||
forwardScanFromPointAfterNewBRElementResult.ReachedSpecialContent() ||
// In case we're at the very end.
wsType == WSType::thisBlock) {
forwardScanFromPointAfterNewBRElementResult
.ReachedCurrentBlockBoundary()) {
brElement = InsertBRElementWithTransaction(pointToCreateNewBrNode);
if (NS_WARN_IF(Destroyed())) {
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
@ -2397,24 +2393,15 @@ EditActionResult HTMLEditor::HandleDeleteAroundCollapsedSelection(
// What's in the direction we are deleting?
WSRunObject wsObj(this, startPoint);
nsCOMPtr<nsINode> visibleNode;
int32_t visibleNodeOffset;
WSType wsType;
// Find next visible node
if (aDirectionAndAmount == nsIEditor::eNext) {
wsObj.NextVisibleNode(startPoint, address_of(visibleNode),
&visibleNodeOffset, &wsType);
} else {
wsObj.PriorVisibleNode(startPoint, address_of(visibleNode),
&visibleNodeOffset, &wsType);
}
if (!visibleNode) {
WSScanResult scanFromStartPointResult =
aDirectionAndAmount == nsIEditor::eNext
? wsObj.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint)
: wsObj.ScanPreviousVisibleNodeOrBlockBoundaryFrom(startPoint);
if (!scanFromStartPointResult.GetContent()) {
return EditActionCanceled();
}
if (wsType == WSType::normalWS) {
if (scanFromStartPointResult.InNormalWhiteSpaces()) {
EditActionResult result =
HandleDeleteCollapsedSelectionAtWhiteSpaces(aDirectionAndAmount, wsObj);
NS_WARNING_ASSERTION(
@ -2423,53 +2410,53 @@ EditActionResult HTMLEditor::HandleDeleteAroundCollapsedSelection(
return result;
}
if (wsType == WSType::text) {
if (NS_WARN_IF(!visibleNode->IsText())) {
if (scanFromStartPointResult.InNormalText()) {
if (NS_WARN_IF(!scanFromStartPointResult.GetContent()->IsText())) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditActionResult result = HandleDeleteCollapsedSelectionAtTextNode(
aDirectionAndAmount, EditorDOMPoint(visibleNode, visibleNodeOffset));
aDirectionAndAmount, scanFromStartPointResult.Point());
NS_WARNING_ASSERTION(result.Succeeded(),
"HandleDeleteCollapsedSelectionAtTextNode() failed");
return result;
}
if (wsType == WSType::special || wsType == WSType::br ||
visibleNode->IsHTMLElement(nsGkAtoms::hr)) {
if (NS_WARN_IF(!visibleNode->IsContent())) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (scanFromStartPointResult.ReachedSpecialContent() ||
scanFromStartPointResult.ReachedBRElement() ||
scanFromStartPointResult.ReachedHRElement()) {
EditActionResult result = HandleDeleteCollapsedSelectionAtAtomicContent(
aDirectionAndAmount, aStripWrappers,
MOZ_KnownLive(*visibleNode->AsContent()), startPoint, wsObj);
MOZ_KnownLive(*scanFromStartPointResult.GetContent()), startPoint,
wsObj);
NS_WARNING_ASSERTION(
result.Succeeded(),
"HandleDeleteCollapsedSelectionAtAtomicContent() failed");
return result;
}
if (wsType == WSType::otherBlock) {
if (NS_WARN_IF(!visibleNode->IsElement())) {
if (scanFromStartPointResult.ReachedOtherBlockElement()) {
if (NS_WARN_IF(!scanFromStartPointResult.GetContent()->IsElement())) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditActionResult result =
HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
aDirectionAndAmount, aStripWrappers,
MOZ_KnownLive(*visibleNode->AsElement()), startPoint, wsObj);
MOZ_KnownLive(*scanFromStartPointResult.ElementPtr()), startPoint,
wsObj);
NS_WARNING_ASSERTION(
result.Succeeded(),
"HandleDeleteCollapsedSelectionAtOtherBlockBoundary() failed");
return result;
}
if (wsType == WSType::thisBlock) {
if (NS_WARN_IF(!visibleNode->IsElement())) {
if (scanFromStartPointResult.ReachedCurrentBlockBoundary()) {
if (NS_WARN_IF(!scanFromStartPointResult.GetContent()->IsElement())) {
return EditActionResult(NS_ERROR_FAILURE);
}
EditActionResult result =
HandleDeleteCollapsedSelectionAtCurrentBlockBoundary(
aDirectionAndAmount, MOZ_KnownLive(*visibleNode->AsElement()),
startPoint);
aDirectionAndAmount,
MOZ_KnownLive(*scanFromStartPointResult.ElementPtr()), startPoint);
NS_WARNING_ASSERTION(
result.Succeeded(),
"HandleDeleteCollapsedSelectionAtCurrentBlockBoundary() failed");
@ -2608,12 +2595,12 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtTextNode(
EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtAtomicContent(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, nsIContent& aAtomicContent,
const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret) {
const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aCaretPoint.IsSet());
// If the atomic element is editing host, we should do nothing.
if (&aAtomicContent == aWSRunObjectAtCaret.GetEditingHost()) {
if (&aAtomicContent == aWSRunScannerAtCaret.GetEditingHost()) {
return EditActionHandled();
}
@ -2700,30 +2687,24 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtAtomicContent(
// There is one exception to the move only case. If the <hr> is
// followed by a <br> we want to delete the <br>.
WSType otherWSType;
nsCOMPtr<nsINode> otherNode;
aWSRunObjectAtCaret.NextVisibleNode(aCaretPoint, address_of(otherNode),
nullptr, &otherWSType);
if (otherWSType != WSType::br) {
WSScanResult forwardScanFromCaretResult =
aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
aCaretPoint);
if (!forwardScanFromCaretResult.ReachedBRElement()) {
return EditActionHandled();
}
// Delete the <br>
if (NS_WARN_IF(!otherNode->IsContent())) {
return EditActionHandled(NS_ERROR_FAILURE);
}
nsIContent* otherContent = otherNode->AsContent();
nsresult rv =
WSRunObject::PrepareToDeleteNode(this, MOZ_KnownLive(otherContent));
nsresult rv = WSRunObject::PrepareToDeleteNode(
this, MOZ_KnownLive(forwardScanFromCaretResult.BRElementPtr()));
if (NS_WARN_IF(Destroyed())) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditActionHandled(rv);
}
rv = DeleteNodeWithTransaction(MOZ_KnownLive(*otherContent));
rv = DeleteNodeWithTransaction(
MOZ_KnownLive(*forwardScanFromCaretResult.BRElementPtr()));
if (NS_WARN_IF(Destroyed())) {
return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
}
@ -2799,7 +2780,7 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtAtomicContent(
EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement,
const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret) {
const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aCaretPoint.IsSet());
@ -2812,17 +2793,12 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
// Next to a block. See if we are between a block and a br. If so, we
// really want to delete the br. Else join content at selection to the
// block.
WSType otherWSType;
nsCOMPtr<nsINode> otherNode;
// Find node in other direction
if (aDirectionAndAmount == nsIEditor::eNext) {
aWSRunObjectAtCaret.PriorVisibleNode(aCaretPoint, address_of(otherNode),
nullptr, &otherWSType);
} else {
aWSRunObjectAtCaret.NextVisibleNode(aCaretPoint, address_of(otherNode),
nullptr, &otherWSType);
}
WSScanResult scanFromCaretResult =
aDirectionAndAmount == nsIEditor::eNext
? aWSRunScannerAtCaret.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
aCaretPoint)
: aWSRunScannerAtCaret.ScanNextVisibleNodeOrBlockBoundaryFrom(
aCaretPoint);
// First find the adjacent node in the block
nsCOMPtr<nsIContent> leafNode;
@ -2838,8 +2814,9 @@ EditActionResult HTMLEditor::HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
}
bool didBRElementDeleted = false;
if (otherNode->IsHTMLElement(nsGkAtoms::br)) {
nsresult rv = DeleteNodeWithTransaction(*otherNode);
if (scanFromCaretResult.ReachedBRElement()) {
nsresult rv = DeleteNodeWithTransaction(
MOZ_KnownLive(*scanFromCaretResult.BRElementPtr()));
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
@ -6895,20 +6872,20 @@ HTMLEditor::GetExtendedRangeToIncludeInvisibleNodes(
if (atStart.GetContainer() != commonAncestorBlock &&
atStart.GetContainer() != editingHost) {
for (;;) {
WSRunObject wsObj(this, atStart);
WSType wsType;
wsObj.PriorVisibleNode(atStart, &wsType);
if (wsType != WSType::thisBlock) {
WSRunScanner wsScannerAtStart(this, atStart);
if (!wsScannerAtStart.ScanPreviousVisibleNodeOrBlockBoundaryFrom(atStart)
.ReachedCurrentBlockBoundary()) {
break;
}
// We want to keep looking up. But stop if we are crossing table
// element boundaries, or if we hit the root.
if (HTMLEditUtils::IsTableElement(wsObj.GetStartReasonContent()) ||
wsObj.GetStartReasonContent() == commonAncestorBlock ||
wsObj.GetStartReasonContent() == editingHost) {
if (HTMLEditUtils::IsTableElement(
wsScannerAtStart.GetStartReasonContent()) ||
wsScannerAtStart.GetStartReasonContent() == commonAncestorBlock ||
wsScannerAtStart.GetStartReasonContent() == editingHost) {
break;
}
atStart.Set(wsObj.GetStartReasonContent());
atStart.Set(wsScannerAtStart.GetStartReasonContent());
}
}
@ -6921,31 +6898,30 @@ HTMLEditor::GetExtendedRangeToIncludeInvisibleNodes(
atEnd.GetContainer() != editingHost) {
EditorDOMPoint atFirstInvisibleBRElement;
for (;;) {
WSRunObject wsObj(this, atEnd);
WSType wsType;
wsObj.NextVisibleNode(atEnd, &wsType);
if (wsType == WSType::br) {
if (IsVisibleBRElement(wsObj.GetEndReasonContent())) {
WSRunScanner wsScannerAtEnd(this, atEnd);
WSScanResult forwardScanFromEndResult =
wsScannerAtEnd.ScanNextVisibleNodeOrBlockBoundaryFrom(atEnd);
if (forwardScanFromEndResult.ReachedBRElement()) {
if (IsVisibleBRElement(wsScannerAtEnd.GetEndReasonContent())) {
break;
}
if (!atFirstInvisibleBRElement.IsSet()) {
atFirstInvisibleBRElement = atEnd;
}
atEnd.Set(wsObj.GetEndReasonContent());
atEnd.AdvanceOffset();
atEnd.SetAfter(wsScannerAtEnd.GetEndReasonContent());
continue;
}
if (wsType == WSType::thisBlock) {
if (forwardScanFromEndResult.ReachedCurrentBlockBoundary()) {
// We want to keep looking up. But stop if we are crossing table
// element boundaries, or if we hit the root.
if (HTMLEditUtils::IsTableElement(wsObj.GetEndReasonContent()) ||
wsObj.GetEndReasonContent() == commonAncestorBlock ||
wsObj.GetEndReasonContent() == editingHost) {
if (HTMLEditUtils::IsTableElement(
wsScannerAtEnd.GetEndReasonContent()) ||
wsScannerAtEnd.GetEndReasonContent() == commonAncestorBlock ||
wsScannerAtEnd.GetEndReasonContent() == editingHost) {
break;
}
atEnd.Set(wsObj.GetEndReasonContent());
atEnd.AdvanceOffset();
atEnd.SetAfter(wsScannerAtEnd.GetEndReasonContent());
continue;
}
@ -7022,63 +6998,59 @@ nsresult HTMLEditor::MaybeExtendSelectionToHardLineEdgesForBlockEditAction() {
EditorDOMPoint newStartPoint(startPoint);
EditorDOMPoint newEndPoint(endPoint);
// some locals we need for whitespace code
WSType wsType;
// let the whitespace code do the heavy lifting
WSRunObject wsEndObj(this, endPoint);
// Is there any intervening visible whitespace? If so we can't push
// selection past that, it would visibly change meaning of users selection.
wsEndObj.PriorVisibleNode(endPoint, &wsType);
if (wsType != WSType::text && wsType != WSType::normalWS) {
WSRunScanner wsScannerAtEnd(this, endPoint);
if (wsScannerAtEnd.ScanPreviousVisibleNodeOrBlockBoundaryFrom(endPoint)
.ReachedSomething()) {
// eThisBlock and eOtherBlock conveniently distinguish cases
// of going "down" into a block and "up" out of a block.
if (wsEndObj.mStartReason == WSType::otherBlock) {
if (wsScannerAtEnd.StartReason() == WSType::otherBlock) {
// endpoint is just after the close of a block.
nsINode* child =
GetRightmostChild(wsEndObj.GetStartReasonContent(), true);
GetRightmostChild(wsScannerAtEnd.GetStartReasonContent(), true);
if (child) {
newEndPoint.SetAfter(child);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsEndObj.mStartReason == WSType::thisBlock) {
} else if (wsScannerAtEnd.StartReason() == WSType::thisBlock) {
// endpoint is just after start of this block
nsINode* child = GetPreviousEditableHTMLNode(endPoint);
if (child) {
newEndPoint.SetAfter(child);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsEndObj.mStartReason == WSType::br) {
} else if (wsScannerAtEnd.StartReason() == WSType::br) {
// endpoint is just after break. lets adjust it to before it.
newEndPoint.Set(wsEndObj.GetStartReasonContent());
newEndPoint.Set(wsScannerAtEnd.GetStartReasonContent());
}
}
// similar dealio for start of range
WSRunObject wsStartObj(this, startPoint);
// Is there any intervening visible whitespace? If so we can't push
// selection past that, it would visibly change meaning of users selection.
wsStartObj.NextVisibleNode(startPoint, &wsType);
if (wsType != WSType::text && wsType != WSType::normalWS) {
WSRunScanner wsScannerAtStart(this, startPoint);
if (wsScannerAtStart.ScanNextVisibleNodeOrBlockBoundaryFrom(startPoint)
.ReachedSomething()) {
// eThisBlock and eOtherBlock conveniently distinguish cases
// of going "down" into a block and "up" out of a block.
if (wsStartObj.mEndReason == WSType::otherBlock) {
if (wsScannerAtStart.EndReason() == WSType::otherBlock) {
// startpoint is just before the start of a block.
nsINode* child = GetLeftmostChild(wsStartObj.GetEndReasonContent(), true);
nsINode* child =
GetLeftmostChild(wsScannerAtStart.GetEndReasonContent(), true);
if (child) {
newStartPoint.Set(child);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsStartObj.mEndReason == WSType::thisBlock) {
} else if (wsScannerAtStart.EndReason() == WSType::thisBlock) {
// startpoint is just before end of this block
nsINode* child = GetNextEditableHTMLNode(startPoint);
if (child) {
newStartPoint.Set(child);
}
// else block is empty - we can leave selection alone here, i think.
} else if (wsStartObj.mEndReason == WSType::br) {
} else if (wsScannerAtStart.EndReason() == WSType::br) {
// startpoint is just before a break. lets adjust it to after it.
newStartPoint.SetAfter(wsStartObj.GetEndReasonContent());
newStartPoint.SetAfter(wsScannerAtStart.GetEndReasonContent());
}
}
@ -8635,20 +8607,19 @@ nsresult HTMLEditor::HandleInsertParagraphInListItemElement(Element& aListItem,
return NS_OK;
}
} else {
WSRunObject wsObj(this, &aListItem, 0);
nsCOMPtr<nsINode> visNode;
int32_t visOffset = 0;
WSType wsType;
wsObj.NextVisibleNode(EditorRawDOMPoint(&aListItem, 0),
address_of(visNode), &visOffset, &wsType);
if (wsType == WSType::special || wsType == WSType::br ||
visNode->IsHTMLElement(nsGkAtoms::hr)) {
EditorRawDOMPoint atVisNode(visNode);
if (NS_WARN_IF(!atVisNode.IsSetAndValid())) {
WSScanResult forwardScanFromStartOfListItemResult =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
*this, EditorRawDOMPoint(&aListItem, 0));
if (forwardScanFromStartOfListItemResult.ReachedSpecialContent() ||
forwardScanFromStartOfListItemResult.ReachedBRElement() ||
forwardScanFromStartOfListItemResult.ReachedHRElement()) {
EditorRawDOMPoint atFoundElement(
forwardScanFromStartOfListItemResult.RawPointAtContent());
if (NS_WARN_IF(!atFoundElement.IsSetAndValid())) {
return NS_ERROR_FAILURE;
}
ErrorResult error;
SelectionRefPtr()->Collapse(atVisNode, error);
SelectionRefPtr()->Collapse(atFoundElement, error);
if (NS_WARN_IF(Destroyed())) {
error.SuppressException();
return NS_ERROR_EDITOR_DESTROYED;
@ -8659,7 +8630,11 @@ nsresult HTMLEditor::HandleInsertParagraphInListItemElement(Element& aListItem,
return NS_OK;
}
rv = SelectionRefPtr()->Collapse(visNode, visOffset);
// XXX This may be not meaningful position if it reached block element
// in aListItem.
rv = SelectionRefPtr()->Collapse(
forwardScanFromStartOfListItemResult.RawPoint()
.ToRawRangeBoundary());
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}

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

@ -541,49 +541,47 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
// Find first editable and visible node.
EditorRawDOMPoint pointToPutCaret(editingHost, 0);
for (;;) {
WSRunObject wsObj(this, pointToPutCaret.GetContainer(),
pointToPutCaret.Offset());
int32_t visOffset = 0;
WSType visType;
nsCOMPtr<nsINode> visNode;
wsObj.NextVisibleNode(pointToPutCaret, address_of(visNode), &visOffset,
&visType);
WSScanResult forwardScanFromPointToPutCaretResult =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
pointToPutCaret);
// If we meet a non-editable node first, we should move caret to start of
// the editing host (perhaps, user may want to insert something before
// the first non-editable node? Chromium behaves so).
if (visNode && !visNode->IsEditable()) {
if (forwardScanFromPointToPutCaretResult.GetContent() &&
!forwardScanFromPointToPutCaretResult.IsContentEditable()) {
pointToPutCaret.Set(editingHost, 0);
break;
}
// WSRunObject::NextVisibleNode() returns WSType::special and the "special"
// node when it meets empty inline element. In this case, we should go to
// next sibling. For example, if current editor is:
// <div contenteditable><span></span><b><br></b></div>
// WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() returns
// WSType::special and the "special" node when it meets empty inline
// element. In this case, we should go to next sibling. For example, if
// current editor is: <div contenteditable><span></span><b><br></b></div>
// then, we should put caret at the <br> element. So, let's check if
// found node is an empty inline container element.
if (visType == WSType::special && visNode &&
TagCanContainTag(*visNode->NodeInfo()->NameAtom(),
if (forwardScanFromPointToPutCaretResult.ReachedSpecialContent() &&
forwardScanFromPointToPutCaretResult.GetContent() &&
TagCanContainTag(*forwardScanFromPointToPutCaretResult.GetContent()
->NodeInfo()
->NameAtom(),
*nsGkAtoms::textTagName)) {
pointToPutCaret.Set(visNode);
DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
NS_WARNING_ASSERTION(
advanced,
"Failed to advance offset from found empty inline container element");
pointToPutCaret =
forwardScanFromPointToPutCaretResult.RawPointAfterContent();
continue;
}
// If there is editable and visible text node, move caret at start of it.
if (visType == WSType::normalWS || visType == WSType::text) {
pointToPutCaret.Set(visNode, visOffset);
if (forwardScanFromPointToPutCaretResult.InNormalWhiteSpacesOrText()) {
pointToPutCaret = forwardScanFromPointToPutCaretResult.RawPoint();
break;
}
// If there is editable <br> or something inline special element like
// <img>, <input>, etc, move caret before it.
if (visType == WSType::br || visType == WSType::special) {
pointToPutCaret.Set(visNode);
if (forwardScanFromPointToPutCaretResult.ReachedBRElement() ||
forwardScanFromPointToPutCaretResult.ReachedSpecialContent()) {
pointToPutCaret =
forwardScanFromPointToPutCaretResult.RawPointAtContent();
break;
}
@ -592,34 +590,34 @@ nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
// host.
// XXX This may not make sense, but Chromium behaves so. Therefore, the
// reason why we do this is just compatibility with Chromium.
if (visType != WSType::otherBlock) {
if (!forwardScanFromPointToPutCaretResult.ReachedOtherBlockElement()) {
pointToPutCaret.Set(editingHost, 0);
break;
}
// By definition of WSRunObject, a block element terminates a whitespace
// By definition of WSRunScanner, a block element terminates a whitespace
// run. That is, although we are calling a method that is named
// "NextVisibleNode", the node returned might not be visible/editable!
// "ScanNextVisibleNodeOrBlockBoundary", the node returned might not
// be visible/editable!
// However, we were given a block that is not a container. Since the
// block can not contain anything that's visible, such a block only
// makes sense if it is visible by itself, like a <hr>. We want to
// place the caret in front of that block.
if (!IsContainer(visNode)) {
pointToPutCaret.Set(visNode);
if (!IsContainer(forwardScanFromPointToPutCaretResult.GetContent())) {
pointToPutCaret =
forwardScanFromPointToPutCaretResult.RawPointAtContent();
break;
}
// If the given block does not contain any visible/editable items, we want
// to skip it and continue our search.
if (IsEmptyNode(*visNode)) {
if (IsEmptyNode(*forwardScanFromPointToPutCaretResult.GetContent())) {
// Skip the empty block
pointToPutCaret.Set(visNode);
DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
NS_WARNING_ASSERTION(
advanced, "Failed to advance offset from the found empty block node");
pointToPutCaret =
forwardScanFromPointToPutCaretResult.RawPointAfterContent();
} else {
pointToPutCaret.Set(visNode, 0);
pointToPutCaret.Set(forwardScanFromPointToPutCaretResult.GetContent(), 0);
}
}
nsresult rv = SelectionRefPtr()->Collapse(pointToPutCaret);
@ -934,18 +932,13 @@ bool HTMLEditor::IsVisibleBRElement(nsINode* aNode) {
// Sigh. We have to use expensive whitespace calculation code to
// determine what is going on
int32_t selOffset;
nsCOMPtr<nsINode> selNode = GetNodeLocation(aNode, &selOffset);
// Let's look after the break
selOffset++;
WSRunObject wsObj(this, selNode, selOffset);
WSType visType;
wsObj.NextVisibleNode(EditorRawDOMPoint(selNode, selOffset), &visType);
if (visType & WSType::block) {
EditorRawDOMPoint afterBRElement(EditorRawDOMPoint::After(*aNode));
if (NS_WARN_IF(!afterBRElement.IsSet())) {
return false;
}
return true;
return !WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
afterBRElement)
.ReachedBlockBoundary();
}
NS_IMETHODIMP
@ -1474,18 +1467,18 @@ EditorRawDOMPoint HTMLEditor::GetBetterInsertionPointFor(
return pointToInsert;
}
WSRunObject wsObj(this, pointToInsert.GetContainer(), pointToInsert.Offset());
WSRunScanner wsScannerForPointToInsert(this, pointToInsert);
// If the insertion position is after the last visible item in a line,
// i.e., the insertion position is just before a visible line break <br>,
// we want to skip to the position just after the line break (see bug 68767).
nsCOMPtr<nsINode> nextVisibleNode;
WSType nextVisibleType;
wsObj.NextVisibleNode(pointToInsert, address_of(nextVisibleNode), nullptr,
&nextVisibleType);
WSScanResult forwardScanFromPointToInsertResult =
wsScannerForPointToInsert.ScanNextVisibleNodeOrBlockBoundaryFrom(
pointToInsert);
// So, if the next visible node isn't a <br> element, we can insert the block
// level element to the point.
if (!nextVisibleNode || !(nextVisibleType & WSType::br)) {
if (!forwardScanFromPointToInsertResult.GetContent() ||
!forwardScanFromPointToInsertResult.ReachedBRElement()) {
return pointToInsert;
}
@ -1493,25 +1486,21 @@ EditorRawDOMPoint HTMLEditor::GetBetterInsertionPointFor(
// positioned at the beginning of a block, in that case skipping the <br>
// would not insert the <br> at the caret position, but after the current
// empty line.
nsCOMPtr<nsINode> previousVisibleNode;
WSType previousVisibleType;
wsObj.PriorVisibleNode(pointToInsert, address_of(previousVisibleNode),
nullptr, &previousVisibleType);
WSScanResult backwardScanFromPointToInsertResult =
wsScannerForPointToInsert.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
pointToInsert);
// So, if there is no previous visible node,
// or, if both nodes of the insertion point is <br> elements,
// or, if the previous visible node is different block,
// we need to skip the following <br>. So, otherwise, we can insert the
// block at the insertion point.
if (!previousVisibleNode || (previousVisibleType & WSType::br) ||
(previousVisibleType & WSType::thisBlock)) {
if (!backwardScanFromPointToInsertResult.GetContent() ||
backwardScanFromPointToInsertResult.ReachedBRElement() ||
backwardScanFromPointToInsertResult.ReachedCurrentBlockBoundary()) {
return pointToInsert;
}
EditorRawDOMPoint afterBRNode(nextVisibleNode);
DebugOnly<bool> advanced = afterBRNode.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset to after the <br> node");
return afterBRNode;
return forwardScanFromPointToInsertResult.RawPointAfterContent();
}
NS_IMETHODIMP
@ -3128,21 +3117,23 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
MOZ_ASSERT(mPlaceholderBatch);
// First, check there is visible contents before the point in current block.
WSRunObject wsObj(this, aPoint);
if (!(wsObj.mStartReason & WSType::thisBlock)) {
WSRunScanner wsScannerForPoint(this, aPoint);
if (wsScannerForPoint.StartReason() != WSType::thisBlock) {
// If there is visible node before the point, we shouldn't remove the
// parent block.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
if (NS_WARN_IF(!wsObj.GetStartReasonContent()) ||
NS_WARN_IF(!wsObj.GetStartReasonContent()->GetParentNode())) {
if (NS_WARN_IF(!wsScannerForPoint.GetStartReasonContent()) ||
NS_WARN_IF(!wsScannerForPoint.GetStartReasonContent()->GetParentNode())) {
return NS_ERROR_FAILURE;
}
if (wsObj.GetEditingHost() == wsObj.GetStartReasonContent()) {
if (wsScannerForPoint.GetEditingHost() ==
wsScannerForPoint.GetStartReasonContent()) {
// If we reach editing host, there is no parent blocks which can be removed.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
if (HTMLEditUtils::IsTableCellOrCaption(*wsObj.GetStartReasonContent())) {
if (HTMLEditUtils::IsTableCellOrCaption(
*wsScannerForPoint.GetStartReasonContent())) {
// If we reach a <td>, <th> or <caption>, we shouldn't remove it even
// becomes empty because removing such element changes the structure of
// the <table>.
@ -3150,34 +3141,33 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
}
// Next, check there is visible contents after the point in current block.
WSType wsType = WSType::none;
wsObj.NextVisibleNode(aPoint, &wsType);
if (wsType == WSType::br) {
WSScanResult forwardScanFromPointResult =
wsScannerForPoint.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
if (forwardScanFromPointResult.ReachedBRElement()) {
// If the <br> element is visible, we shouldn't remove the parent block.
if (IsVisibleBRElement(wsObj.GetEndReasonContent())) {
if (IsVisibleBRElement(wsScannerForPoint.GetEndReasonContent())) {
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
if (wsObj.GetEndReasonContent()->GetNextSibling()) {
EditorRawDOMPoint afterBRElement;
afterBRElement.SetAfter(wsObj.GetEndReasonContent());
WSRunObject wsRunObjAfterBR(this, afterBRElement);
WSType wsTypeAfterBR = WSType::none;
wsRunObjAfterBR.NextVisibleNode(afterBRElement, &wsTypeAfterBR);
if (wsTypeAfterBR != WSType::thisBlock) {
if (wsScannerForPoint.GetEndReasonContent()->GetNextSibling()) {
if (!WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
*this, EditorRawDOMPoint::After(
*wsScannerForPoint.GetEndReasonContent()))
.ReachedCurrentBlockBoundary()) {
// If we couldn't reach the block's end after the invisible <br>,
// that means that there is visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
}
} else if (wsType != WSType::thisBlock) {
} else if (!forwardScanFromPointResult.ReachedCurrentBlockBoundary()) {
// If we couldn't reach the block's end, the block has visible content.
return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
}
// Delete the parent block.
EditorDOMPoint nextPoint(wsObj.GetStartReasonContent()->GetParentNode(), 0);
nsresult rv =
DeleteNodeWithTransaction(MOZ_KnownLive(*wsObj.GetStartReasonContent()));
EditorDOMPoint nextPoint(
wsScannerForPoint.GetStartReasonContent()->GetParentNode(), 0);
nsresult rv = DeleteNodeWithTransaction(
MOZ_KnownLive(*wsScannerForPoint.GetStartReasonContent()));
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -3185,7 +3175,7 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
return rv;
}
// If we reach editing host, return NS_OK.
if (nextPoint.GetContainer() == wsObj.GetEditingHost()) {
if (nextPoint.GetContainer() == wsScannerForPoint.GetEditingHost()) {
return NS_OK;
}
@ -3199,7 +3189,7 @@ nsresult HTMLEditor::DeleteParentBlocksWithTransactionIfEmpty(
NS_EVENT_BITS_MUTATION_SUBTREEMODIFIED)) {
Element* editingHost = GetActiveEditingHost();
if (NS_WARN_IF(!editingHost) ||
NS_WARN_IF(editingHost != wsObj.GetEditingHost())) {
NS_WARN_IF(editingHost != wsScannerForPoint.GetEditingHost())) {
return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
if (NS_WARN_IF(!EditorUtils::IsDescendantOf(*nextPoint.GetContainer(),
@ -3970,15 +3960,11 @@ bool HTMLEditor::IsVisibleTextNode(Text& aText) const {
return true;
}
WSRunScanner wsRunScanner(this, &aText, 0);
nsCOMPtr<nsINode> nextVisibleNode;
WSType visibleNodeType;
wsRunScanner.NextVisibleNode(EditorRawDOMPoint(&aText, 0),
address_of(nextVisibleNode), nullptr,
&visibleNodeType);
return (visibleNodeType == WSType::normalWS ||
visibleNodeType == WSType::text) &&
&aText == nextVisibleNode;
WSScanResult nextWSScanResult =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
*this, EditorRawDOMPoint(&aText, 0));
return nextWSScanResult.InNormalWhiteSpacesOrText() &&
nextWSScanResult.TextPtr() == &aText;
}
bool HTMLEditor::IsEmpty() const {

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

@ -54,6 +54,8 @@ class ResizerSelectionListener;
class SplitRangeOffFromNodeResult;
class SplitRangeOffResult;
class WSRunObject;
class WSRunScanner;
class WSScanResult;
enum class EditSubAction : int32_t;
struct PropItem;
template <class T>
@ -2246,14 +2248,14 @@ class HTMLEditor final : public TextEditor,
* @param aAtomicContent The atomic content to be deleted.
* @param aCaretPoint The caret point (i.e., selection start or
* end).
* @param aWSRunObjectAtCaret WSRunObject instance which was initialized with
* the caret point.
* @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
* with the caret point.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
HandleDeleteCollapsedSelectionAtAtomicContent(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, nsIContent& aAtomicContent,
const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret);
const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret);
/**
* HandleDeleteCollapsedSelectionAtOtherBlockBoundary() handles deletion at
@ -2267,14 +2269,14 @@ class HTMLEditor final : public TextEditor,
* is followed by caret.
* @param aCaretPoint The caret point (i.e., selection start or
* end).
* @param aWSRunObjectAtCaret WSRunObject instance which was initialized with
* the caret point.
* @param aWSRunScannerAtCaret WSRunScanner instance which was initialized
* with the caret point.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditActionResult
HandleDeleteCollapsedSelectionAtOtherBlockBoundary(
nsIEditor::EDirection aDirectionAndAmount,
nsIEditor::EStripWrappers aStripWrappers, Element& aOtherBlockElement,
const EditorDOMPoint& aCaretPoint, WSRunObject& aWSRunObjectAtCaret);
const EditorDOMPoint& aCaretPoint, WSRunScanner& aWSRunScannerAtCaret);
/**
* HandleDeleteCollapsedSelectionAtCurrentBlockBoundary() handles deletion
@ -4577,6 +4579,7 @@ class HTMLEditor final : public TextEditor,
friend class TextEditor;
friend class WSRunObject;
friend class WSRunScanner;
friend class WSScanResult;
};
/**

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

@ -632,24 +632,21 @@ nsresult HTMLEditor::DoInsertHTMLWithContext(
// Make sure we don't end up with selection collapsed after an invisible
// `<br>` element.
WSRunObject wsRunObj(this, pointToPutCaret);
WSType visType;
wsRunObj.PriorVisibleNode(pointToPutCaret, &visType);
if (visType == WSType::br &&
!IsVisibleBRElement(wsRunObj.GetStartReasonContent())) {
WSRunObject wsRunObj2(this,
EditorDOMPoint(wsRunObj.GetStartReasonContent()));
nsCOMPtr<nsINode> visibleNode;
int32_t visibleNodeOffset;
wsRunObj2.PriorVisibleNode(pointToPutCaret, address_of(visibleNode),
&visibleNodeOffset, &visType);
if (visType == WSType::text || visType == WSType::normalWS) {
pointToPutCaret.Set(visibleNode, visibleNodeOffset);
} else if (visType == WSType::special) {
pointToPutCaret.Set(wsRunObj2.GetStartReasonContent());
DebugOnly<bool> advanced = pointToPutCaret.AdvanceOffset();
NS_WARNING_ASSERTION(advanced,
"Failed to advance offset from found object");
WSRunScanner wsRunScannerAtCaret(this, pointToPutCaret);
if (wsRunScannerAtCaret
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(pointToPutCaret)
.ReachedBRElement() &&
!IsVisibleBRElement(wsRunScannerAtCaret.GetStartReasonContent())) {
WSRunScanner wsRunScannerAtStartReason(
this, EditorDOMPoint(wsRunScannerAtCaret.GetStartReasonContent()));
WSScanResult backwardScanFromPointToCaretResult =
wsRunScannerAtStartReason.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
pointToPutCaret);
if (backwardScanFromPointToCaretResult.InNormalWhiteSpacesOrText()) {
pointToPutCaret = backwardScanFromPointToCaretResult.Point();
} else if (backwardScanFromPointToCaretResult.ReachedSpecialContent()) {
pointToPutCaret.SetAfter(
wsRunScannerAtStartReason.GetStartReasonContent());
}
}
DebugOnly<nsresult> rvIgnored =

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

@ -44,22 +44,14 @@ template WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
template WSRunObject::WSRunObject(HTMLEditor* aHTMLEditor,
const EditorRawDOMPoint& aScanStartPoint,
const EditorRawDOMPoint& aScanEndPoint);
template void WSRunScanner::PriorVisibleNode(const EditorDOMPoint& aPoint,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType) const;
template void WSRunScanner::PriorVisibleNode(const EditorRawDOMPoint& aPoint,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType) const;
template void WSRunScanner::NextVisibleNode(const EditorDOMPoint& aPoint,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType) const;
template void WSRunScanner::NextVisibleNode(const EditorRawDOMPoint& aPoint,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType) const;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPoint& aPoint) const;
template WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPoint& aPoint) const;
template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPoint& aPoint) const;
template WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
const EditorRawDOMPoint& aPoint) const;
template void WSRunObject::GetASCIIWhitespacesBounds(
int16_t aDir, const EditorDOMPoint& aPoint, dom::Text** outStartNode,
int32_t* outStartOffset, dom::Text** outEndNode,
@ -537,14 +529,12 @@ nsresult WSRunObject::DeleteWSForward() {
}
template <typename PT, typename CT>
void WSRunScanner::PriorVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType) const {
WSScanResult WSRunScanner::ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const {
// Find first visible thing before the point. Position
// outVisNode/outVisOffset just _after_ that thing. If we don't find
// anything return start of ws.
MOZ_ASSERT(aPoint.IsSet() && outType);
MOZ_ASSERT(aPoint.IsSet());
WSFragment* run = FindNearestRun(aPoint, false);
@ -554,43 +544,30 @@ void WSRunScanner::PriorVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
WSPoint point = GetPreviousCharPoint(aPoint);
// When it's a non-empty text node, return it.
if (point.mTextNode && point.mTextNode->Length()) {
if (outVisNode) {
*outVisNode = point.mTextNode;
}
if (outVisOffset) {
*outVisOffset = point.mOffset + 1;
}
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
*outType = WSType::normalWS;
} else {
*outType = WSType::text;
}
return;
return WSScanResult(
point.mTextNode, point.mOffset + 1,
nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP
? WSType::normalWS
: WSType::text);
}
// If no text node, keep looking. We should eventually fall out of loop
}
}
if (outVisNode) {
// If we get here, then nothing in ws data to find. Return start reason.
*outVisNode = mStartReasonContent;
if (mStartReasonContent != mStartNode) {
// In this case, mStartOffset is not meaningful.
return WSScanResult(mStartReasonContent, mStartReason);
}
if (outVisOffset) {
// This really isn't meaningful if mStartReasonContent != mStartNode
*outVisOffset = mStartOffset;
}
*outType = mStartReason;
return WSScanResult(mStartReasonContent, mStartOffset, mStartReason);
}
template <typename PT, typename CT>
void WSRunScanner::NextVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
nsCOMPtr<nsINode>* outVisNode,
int32_t* outVisOffset,
WSType* outType) const {
WSScanResult WSRunScanner::ScanNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const {
// Find first visible thing after the point. Position
// outVisNode/outVisOffset just _before_ that thing. If we don't find
// anything return end of ws.
MOZ_ASSERT(aPoint.IsSet() && outType);
MOZ_ASSERT(aPoint.IsSet());
WSFragment* run = FindNearestRun(aPoint, true);
@ -600,32 +577,21 @@ void WSRunScanner::NextVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
WSPoint point = GetNextCharPoint(aPoint);
// When it's a non-empty text node, return it.
if (point.mTextNode && point.mTextNode->Length()) {
if (outVisNode) {
*outVisNode = point.mTextNode;
}
if (outVisOffset) {
*outVisOffset = point.mOffset;
}
if (nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP) {
*outType = WSType::normalWS;
} else {
*outType = WSType::text;
}
return;
return WSScanResult(
point.mTextNode, point.mOffset,
nsCRT::IsAsciiSpace(point.mChar) || point.mChar == kNBSP
? WSType::normalWS
: WSType::text);
}
// If no text node, keep looking. We should eventually fall out of loop
}
}
if (outVisNode) {
// If we get here, then nothing in ws data to find. Return end reason
*outVisNode = mEndReasonContent;
if (mEndReasonContent != mEndNode) {
// In this case, mEndOffset is not meaningful.
return WSScanResult(mEndReasonContent, mEndReason);
}
if (outVisOffset) {
// This really isn't meaningful if mEndReasonContent != mEndNode
*outVisOffset = mEndOffset;
}
*outType = mEndReason;
return WSScanResult(mEndReasonContent, mEndOffset, mEndReason);
}
nsresult WSRunObject::AdjustWhitespace() {
@ -2079,8 +2045,4 @@ nsresult WSRunObject::Scrub() {
return NS_OK;
}
bool WSRunScanner::IsBlockNode(nsINode* aNode) {
return aNode && aNode->IsElement() && HTMLEditor::NodeIsBlockStatic(*aNode);
}
} // namespace mozilla

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

@ -6,15 +6,20 @@
#ifndef WSRunObject_h
#define WSRunObject_h
#include "mozilla/dom/Text.h"
#include "mozilla/Assertions.h"
#include "mozilla/EditAction.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint
#include "mozilla/HTMLEditor.h"
#include "mozilla/Maybe.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Text.h"
#include "nsCOMPtr.h"
#include "nsIContent.h"
namespace mozilla {
class HTMLEditor;
// class WSRunObject represents the entire whitespace situation
// around a given point. It collects up a list of nodes that contain
// whitespace and categorizes in up to 3 different WSFragments (detailed
@ -131,6 +136,201 @@ inline const WSType operator|(const WSType::Enum& aLeft,
return WSType(aLeft) | WSType(aRight);
}
/**
* WSScanResult is result of ScanNextVisibleNodeOrBlockBoundaryFrom(),
* ScanPreviousVisibleNodeOrBlockBoundaryFrom(), and their static wrapper
* methods. This will have information of found visible content (and its
* position) or reached block element or topmost editable content at the
* start of scanner.
*/
class MOZ_STACK_CLASS WSScanResult final {
public:
WSScanResult() = delete;
MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, WSType aReason)
: mContent(aContent), mReason(aReason) {
AssertIfInvalidData();
}
MOZ_NEVER_INLINE_DEBUG WSScanResult(nsIContent* aContent, uint32_t aOffset,
WSType aReason)
: mContent(aContent), mOffset(Some(aOffset)), mReason(aReason) {
AssertIfInvalidData();
}
void AssertIfInvalidData() const {
#ifdef DEBUG
MOZ_ASSERT(mReason == WSType::text || mReason == WSType::normalWS ||
mReason == WSType::br || mReason == WSType::special ||
mReason == WSType::thisBlock || mReason == WSType::otherBlock);
MOZ_ASSERT_IF(mReason == WSType::text || mReason == WSType::normalWS,
mContent && mContent->IsText());
MOZ_ASSERT_IF(mReason == WSType::br,
mContent && mContent->IsHTMLElement(nsGkAtoms::br));
MOZ_ASSERT_IF(
mReason == WSType::special,
mContent && ((mContent->IsText() && !mContent->IsEditable()) ||
(!mContent->IsHTMLElement(nsGkAtoms::br) &&
!HTMLEditor::NodeIsBlockStatic(*mContent))));
MOZ_ASSERT_IF(mReason == WSType::otherBlock,
mContent && HTMLEditor::NodeIsBlockStatic(*mContent));
// If mReason is WSType::thisBlock, mContent can be any content. In most
// cases, it's current block element which is editable. However, if there
// is no editable block parent, this is topmost editable inline content.
// Additionally, if there is no editable content, this is the container
// start of scanner and is not editable.
MOZ_ASSERT_IF(
mReason == WSType::thisBlock,
!mContent || !mContent->GetParentElement() ||
HTMLEditor::NodeIsBlockStatic(*mContent) ||
HTMLEditor::NodeIsBlockStatic(*mContent->GetParentElement()) ||
!mContent->GetParentElement()->IsEditable());
#endif // #ifdef DEBUG
}
/**
* GetContent() returns found visible and editable content/element.
* See MOZ_ASSERT_IF()s in AssertIfInvalidData() for the detail.
*/
nsIContent* GetContent() const { return mContent; }
/**
* The following accessors makes it easier to understand each callers.
*/
MOZ_NEVER_INLINE_DEBUG dom::Element* ElementPtr() const {
MOZ_DIAGNOSTIC_ASSERT(mContent->IsElement());
return mContent->AsElement();
}
MOZ_NEVER_INLINE_DEBUG dom::HTMLBRElement* BRElementPtr() const {
MOZ_DIAGNOSTIC_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br));
return static_cast<dom::HTMLBRElement*>(mContent.get());
}
MOZ_NEVER_INLINE_DEBUG dom::Text* TextPtr() const {
MOZ_DIAGNOSTIC_ASSERT(mContent->IsText());
return mContent->AsText();
}
/**
* Returns true if found or reached content is ediable.
*/
bool IsContentEditable() const { return mContent && mContent->IsEditable(); }
/**
* Offset() returns meaningful value only when InNormalWhiteSpacesOrText()
* returns true or the scanner reached to start or end of its scanning
* range and that is same as start or end container which are specified
* when the scanner is initialized. If it's result of scanning backward,
* this offset means before the found point. Otherwise, i.e., scanning
* forward, this offset means after the found point.
*/
MOZ_NEVER_INLINE_DEBUG uint32_t Offset() const {
NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful offset");
return mOffset.valueOr(0);
}
/**
* Point() and RawPoint() return the position in found visible node or
* reached block boundary. So, they return meaningful point only when
* Offset() returns meaningful value.
*/
MOZ_NEVER_INLINE_DEBUG EditorDOMPoint Point() const {
NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful point");
return EditorDOMPoint(mContent, mOffset.valueOr(0));
}
MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPoint() const {
NS_ASSERTION(mOffset.isSome(), "Retrieved non-meaningful raw point");
return EditorRawDOMPoint(mContent, mOffset.valueOr(0));
}
/**
* PointAtContent() and RawPointAtContent() return the position of found
* visible content or reached block element.
*/
MOZ_NEVER_INLINE_DEBUG EditorDOMPoint PointAtContent() const {
MOZ_ASSERT(mContent);
return EditorDOMPoint(mContent);
}
MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPointAtContent() const {
MOZ_ASSERT(mContent);
return EditorRawDOMPoint(mContent);
}
/**
* PointAfterContent() and RawPointAfterContent() retrun the position after
* found visible content or reached block element.
*/
MOZ_NEVER_INLINE_DEBUG EditorDOMPoint PointAfterContent() const {
MOZ_ASSERT(mContent);
return mContent ? EditorDOMPoint::After(*mContent) : EditorDOMPoint();
}
MOZ_NEVER_INLINE_DEBUG EditorRawDOMPoint RawPointAfterContent() const {
MOZ_ASSERT(mContent);
return mContent ? EditorRawDOMPoint::After(*mContent) : EditorRawDOMPoint();
}
/**
* The scanner reached <img> or something which is inline and is not a
* container.
*/
bool ReachedSpecialContent() const { return mReason == WSType::special; }
/**
* The point is in normal whitespaces or text.
*/
bool InNormalWhiteSpacesOrText() const {
return mReason == WSType::normalWS || mReason == WSType::text;
}
/**
* The point is in normal whitespaces.
*/
bool InNormalWhiteSpaces() const { return mReason == WSType::normalWS; }
/**
* The point is in normal text.
*/
bool InNormalText() const { return mReason == WSType::text; }
/**
* The scanner reached a <br> element.
*/
bool ReachedBRElement() const { return mReason == WSType::br; }
/**
* The scanner reached a <hr> element.
*/
bool ReachedHRElement() const {
return mContent && mContent->IsHTMLElement(nsGkAtoms::hr);
}
/**
* The scanner reached current block boundary or other block element.
*/
bool ReachedBlockBoundary() const { return !!(mReason & WSType::block); }
/**
* The scanner reached current block element boundary.
*/
bool ReachedCurrentBlockBoundary() const {
return mReason == WSType::thisBlock;
}
/**
* The scanner reached other block element.
*/
bool ReachedOtherBlockElement() const {
return mReason == WSType::otherBlock;
}
/**
* The scanner reached something non-text node.
*/
bool ReachedSomething() const { return !InNormalWhiteSpacesOrText(); }
private:
nsCOMPtr<nsIContent> mContent;
Maybe<uint32_t> mOffset;
WSType mReason;
};
class MOZ_STACK_CLASS WSRunScanner {
public:
/**
@ -166,46 +366,62 @@ class MOZ_STACK_CLASS WSRunScanner {
EditorRawDOMPoint(aScanStartNode, aScanStartOffset)) {}
~WSRunScanner();
// NextVisibleNode() returns the first piece of visible thing after aPoint.
// If there is no visible ws qualifying it returns what is after the ws run.
// If outVisNode and/or outvisOffset is unused, callers can use nullptr.
// Note that {outVisNode,outVisOffset} is set to just BEFORE the visible
// object. Also outVisOffset might be invalid offset unless outVisNode is
// end reason node.
// ScanNextVisibleNodeOrBlockBoundaryForwardFrom() returns the first visible
// node after aPoint. If there is no visible nodes after aPoint, returns
// topmost editable inline ancestor at end of current block. See comments
// around WSScanResult for the detail.
template <typename PT, typename CT>
void NextVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
nsCOMPtr<nsINode>* outVisNode, int32_t* outVisOffset,
WSType* outType) const;
WSScanResult ScanNextVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const;
template <typename PT, typename CT>
void NextVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
WSType* outType) const {
NextVisibleNode(aPoint, nullptr, nullptr, outType);
static WSScanResult ScanNextVisibleNodeOrBlockBoundary(
const HTMLEditor& aHTMLEditor, const EditorDOMPointBase<PT, CT>& aPoint) {
return WSRunScanner(&aHTMLEditor, aPoint)
.ScanNextVisibleNodeOrBlockBoundaryFrom(aPoint);
}
// PriorVisibleNode() returns the first piece of visible thing before aPoint.
// If there is no visible ws qualifying it returns what is before the ws run.
// If outVisNode and/or outvisOffset is unused, callers can use nullptr.
// Note that {outVisNode,outVisOffset} is set to just AFTER the visible
// object. Also outVisOffset might be invalid offset unless outVisNode is
// start reason node.
// ScanPreviousVisibleNodeOrBlockBoundaryFrom() returns the first visible node
// before aPoint. If there is no visible nodes before aPoint, returns topmost
// editable inline ancestor at start of current block. See comments around
// WSScanResult for the detail.
template <typename PT, typename CT>
void PriorVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
nsCOMPtr<nsINode>* outVisNode, int32_t* outVisOffset,
WSType* outType) const;
WSScanResult ScanPreviousVisibleNodeOrBlockBoundaryFrom(
const EditorDOMPointBase<PT, CT>& aPoint) const;
template <typename PT, typename CT>
void PriorVisibleNode(const EditorDOMPointBase<PT, CT>& aPoint,
WSType* outType) const {
PriorVisibleNode(aPoint, nullptr, nullptr, outType);
static WSScanResult ScanPreviousVisibleNodeOrBlockBoundary(
const HTMLEditor& aHTMLEditor, const EditorDOMPointBase<PT, CT>& aPoint) {
return WSRunScanner(&aHTMLEditor, aPoint)
.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint);
}
/**
* TODO: Document us!
* GetStartReasonContent() and GetEndReasonContent() return a node which
* was found by scanning from mScanStartPoint backward or mScanEndPoint
* forward. If there was whitespaces or text from the point, returns the
* text node. Otherwise, returns an element which is explained by
* StartReason() or EndReason(). Note that when the reason is
* WSType::thisBlock, In most cases, it's current block element which is
* editable, but also may be non-element and/or non-editable. See
* MOZ_ASSERT_IF()s in WSScanResult::AssertIfInvalidData() for the detail.
*/
nsIContent* GetStartReasonContent() const { return mStartReasonContent; }
nsIContent* GetEndReasonContent() const { return mEndReasonContent; }
/**
* StartReason() and EndReason() return one of WSType::normalWS, WSType::text,
* WSType::br, WSType::special, WSType::thisBlock, WSType::otherBlock.
* If WSType::normalWS or WSType::text, scanning from mScanStartPoint backward
* or mScanEndPoint forward stopped in a text node.
* Otherwise, they reached an element which is what the WSType indicates.
*/
WSType StartReason() const { return mStartReason; }
WSType EndReason() const { return mEndReason; }
/**
* Active editing host when this instance is created.
*/
Element* GetEditingHost() const { return mEditingHost; }
protected:
// WSFragment represents a single run of ws (all leadingws, or all normalws,
// or all trailingws, or all leading+trailingws). Note that this single run
@ -304,7 +520,9 @@ class MOZ_STACK_CLASS WSRunScanner {
nsIContent* GetEditableBlockParentOrTopmotEditableInlineContent(
nsIContent* aContent) const;
static bool IsBlockNode(nsINode* aNode);
static bool IsBlockNode(nsINode* aNode) {
return aNode && aNode->IsElement() && HTMLEditor::NodeIsBlockStatic(*aNode);
}
nsIContent* GetPreviousWSNodeInner(nsINode* aStartNode,
nsINode* aBlockParent) const;
@ -370,6 +588,7 @@ class MOZ_STACK_CLASS WSRunScanner {
// The last WSFragment in the run, may be same as first.
WSFragment* mEndRun;
// See above comment for GetStartReasonContent() and GetEndReasonContent().
nsCOMPtr<nsIContent> mStartReasonContent;
nsCOMPtr<nsIContent> mEndReasonContent;
@ -526,8 +745,6 @@ class MOZ_STACK_CLASS WSRunObject final : public WSRunScanner {
// be safely converted to regular ascii space and converts them.
MOZ_CAN_RUN_SCRIPT nsresult AdjustWhitespace();
Element* GetEditingHost() const { return mEditingHost; }
protected:
using WSPoint = WSRunScanner::WSPoint;

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

@ -489,6 +489,7 @@
[insertparagraph.html?6001-last]
max-asserts: 2 # Only on W-fis on Linux1804-64-qr and on Android, there are 2 assertions of retrieving meaningless raw DOM point from WSScannerResult
disabled:
if (os == "android") and not e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1499003
[[["defaultparagraphseparator","div"\],["insertparagraph",""\]\] "<div class=a id=x><p class=b id=y>[\]foo</div>" queryCommandValue("defaultparagraphseparator") before]
@ -502,6 +503,8 @@
[insertparagraph.html?5001-6000]
min-asserts: 14 # Retrieving meaningless raw DOM point from WSScannerResult
max-asserts: 16 # Only on W-fis on Linux1804-64-qr and on Android, there are 2 additional assertions.
[[["defaultparagraphseparator","p"\],["insertparagraph",""\]\] "<ul><li><div>foo[\]</ul>" compare innerHTML]
expected: FAIL