--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -1726,19 +1726,20 @@ HTMLEditRules::WillInsertBreak(Selection
blockAncestor = HTMLEditor::GetBlockNodeParent(blockAncestor, host)) {
insertBRElement = !CanContainParagraph(*blockAncestor);
}
}
// If we cannot insert a <p>/<div> element at the selection, we should insert
// a <br> element instead.
if (insertBRElement) {
- nsresult rv = StandardBreakImpl(*atStartOfSelection.GetContainer(),
- atStartOfSelection.Offset(), aSelection);
- NS_ENSURE_SUCCESS(rv, rv);
+ nsresult rv = InsertBRElement(aSelection, atStartOfSelection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
*aHandled = true;
return NS_OK;
}
if (host == blockParent && separator != ParagraphSeparator::br) {
// Insert a new block first
MOZ_ASSERT(separator == ParagraphSeparator::div ||
separator == ParagraphSeparator::p);
@@ -1762,33 +1763,33 @@ HTMLEditRules::WillInsertBreak(Selection
blockParent =
mHTMLEditor->GetBlock(*atStartOfSelection.GetContainer(), host);
if (NS_WARN_IF(!blockParent)) {
return NS_ERROR_UNEXPECTED;
}
if (NS_WARN_IF(blockParent == host)) {
// Didn't create a new block for some reason, fall back to <br>
- rv = StandardBreakImpl(*atStartOfSelection.GetContainer(),
- atStartOfSelection.Offset(), aSelection);
+ rv = InsertBRElement(aSelection, atStartOfSelection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
*aHandled = true;
return NS_OK;
}
}
// If block is empty, populate with br. (For example, imagine a div that
// contains the word "text". The user selects "text" and types return.
// "Text" is deleted leaving an empty block. We want to put in one br to
// make block have a line. Then code further below will put in a second br.)
bool isEmpty;
IsEmptyBlock(*blockParent, &isEmpty);
if (isEmpty) {
+ AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
EditorRawDOMPoint endOfBlockParent;
endOfBlockParent.SetToEndOf(blockParent);
RefPtr<Element> br = htmlEditor->CreateBR(endOfBlockParent);
NS_ENSURE_STATE(br);
}
nsCOMPtr<Element> listItem = IsInListItem(blockParent);
if (listItem && listItem != host) {
@@ -1812,136 +1813,166 @@ HTMLEditRules::WillInsertBreak(Selection
// long time. Therefore, some web apps may depend on this behavior like
// Gmail. So, let's use traditional odd behavior only when the default
// paragraph separator is <br>. Otherwise, take consistent behavior
// between <p> container and <div> container.
if ((separator == ParagraphSeparator::br &&
blockParent->IsHTMLElement(nsGkAtoms::p)) ||
(separator != ParagraphSeparator::br &&
blockParent->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) {
+ AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
// Paragraphs: special rules to look for <br>s
EditActionResult result = ReturnInParagraph(aSelection, *blockParent);
if (NS_WARN_IF(result.Failed())) {
return result.Rv();
}
*aHandled = result.Handled();
*aCancel = result.Canceled();
// Fall through, we may not have handled it in ReturnInParagraph()
}
// If not already handled then do the standard thing
if (!(*aHandled)) {
*aHandled = true;
- return StandardBreakImpl(*atStartOfSelection.GetContainer(),
- atStartOfSelection.Offset(), aSelection);
+ return InsertBRElement(aSelection, atStartOfSelection);
}
return NS_OK;
}
nsresult
-HTMLEditRules::StandardBreakImpl(nsINode& aNode,
- int32_t aOffset,
- Selection& aSelection)
-{
- NS_ENSURE_STATE(mHTMLEditor);
+HTMLEditRules::InsertBRElement(Selection& aSelection,
+ const EditorDOMPoint& aPointToBreak)
+{
+ if (NS_WARN_IF(!aPointToBreak.IsSet())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (NS_WARN_IF(!mHTMLEditor)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
- RefPtr<Element> brNode;
- bool bAfterBlock = false;
- bool bBeforeBlock = false;
- nsCOMPtr<nsINode> node = &aNode;
-
+ bool brElementIsAfterBlock = false;
+ bool brElementIsBeforeBlock = false;
+
+ // First, insert a <br> element.
+ RefPtr<Element> brElement;
if (IsPlaintextEditor()) {
- brNode = htmlEditor->CreateBR(EditorRawDOMPoint(node, aOffset));
- NS_ENSURE_STATE(brNode);
+ brElement = htmlEditor->CreateBR(aPointToBreak.AsRaw());
+ if (NS_WARN_IF(!brElement)) {
+ return NS_ERROR_FAILURE;
+ }
} else {
- WSRunObject wsObj(htmlEditor, node, aOffset);
+ EditorDOMPoint pointToBreak(aPointToBreak);
+ WSRunObject wsObj(htmlEditor, pointToBreak.GetContainer(),
+ pointToBreak.Offset());
int32_t visOffset = 0;
WSType wsType;
nsCOMPtr<nsINode> visNode;
- wsObj.PriorVisibleNode(node, aOffset, address_of(visNode),
- &visOffset, &wsType);
+ wsObj.PriorVisibleNode(pointToBreak.GetContainer(), pointToBreak.Offset(),
+ address_of(visNode), &visOffset, &wsType);
if (wsType & WSType::block) {
- bAfterBlock = true;
- }
- wsObj.NextVisibleNode(node, aOffset, address_of(visNode),
- &visOffset, &wsType);
+ brElementIsAfterBlock = true;
+ }
+ wsObj.NextVisibleNode(pointToBreak.GetContainer(), pointToBreak.Offset(),
+ address_of(visNode), &visOffset, &wsType);
if (wsType & WSType::block) {
- bBeforeBlock = true;
- }
+ brElementIsBeforeBlock = true;
+ }
+ // If the container of the break is a link, we need to split it and
+ // insert new <br> between the split links.
nsCOMPtr<nsIDOMNode> linkDOMNode;
- if (htmlEditor->IsInLink(GetAsDOMNode(node), address_of(linkDOMNode))) {
- // Split the link
+ if (htmlEditor->IsInLink(pointToBreak.GetContainerAsDOMNode(),
+ address_of(linkDOMNode))) {
nsCOMPtr<Element> linkNode = do_QueryInterface(linkDOMNode);
- NS_ENSURE_STATE(linkNode || !linkDOMNode);
+ if (NS_WARN_IF(!linkNode)) {
+ return NS_ERROR_FAILURE;
+ }
SplitNodeResult splitLinkNodeResult =
- htmlEditor->SplitNodeDeep(*linkNode, EditorRawDOMPoint(node, aOffset),
+ htmlEditor->SplitNodeDeep(*linkNode, pointToBreak.AsRaw(),
SplitAtEdges::eDoNotCreateEmptyContainer);
if (NS_WARN_IF(splitLinkNodeResult.Failed())) {
return splitLinkNodeResult.Rv();
}
- EditorRawDOMPoint splitPoint(splitLinkNodeResult.SplitPoint());
- node = splitPoint.GetContainer();
- aOffset = splitPoint.Offset();
- }
- brNode =
- wsObj.InsertBreak(aSelection, EditorRawDOMPoint(node, aOffset),
- nsIEditor::eNone);
- if (NS_WARN_IF(!brNode)) {
+ pointToBreak = splitLinkNodeResult.SplitPoint();
+ }
+ brElement =
+ wsObj.InsertBreak(aSelection, pointToBreak.AsRaw(), nsIEditor::eNone);
+ if (NS_WARN_IF(!brElement)) {
return NS_ERROR_FAILURE;
}
}
- node = brNode->GetParentNode();
- NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
- if (bAfterBlock && bBeforeBlock) {
- // We just placed a br between block boundaries. This is the one case
+
+ // If the <br> element has already been removed from the DOM tree by a
+ // mutation observer, don't continue handling this.
+ if (NS_WARN_IF(!brElement->GetParentNode())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (brElementIsAfterBlock && brElementIsBeforeBlock) {
+ // We just placed a <br> between block boundaries. This is the one case
// where we want the selection to be before the br we just placed, as the
// br will be on a new line, rather than at end of prior line.
+ // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
+ // modifying the DOM tree. So, now, the <br> element may not be
+ // between blocks.
aSelection.SetInterlinePosition(true);
- EditorRawDOMPoint point(brNode);
- nsresult rv = aSelection.Collapse(point.AsRaw());
- NS_ENSURE_SUCCESS(rv, rv);
- } else {
- int32_t offset = node->IndexOf(brNode);
- WSRunObject wsObj(htmlEditor, node, offset + 1);
- nsCOMPtr<nsINode> secondBR;
- int32_t visOffset = 0;
- WSType wsType;
- wsObj.NextVisibleNode(node, offset + 1, address_of(secondBR),
- &visOffset, &wsType);
- if (wsType == WSType::br) {
- // 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.
- nsCOMPtr<nsINode> brParent = secondBR->GetParentNode();
- int32_t brOffset = brParent ? brParent->IndexOf(secondBR) : -1;
- if (brParent != node || brOffset != offset + 1) {
- nsresult rv =
- htmlEditor->MoveNode(secondBR->AsContent(), node, offset + 1);
- NS_ENSURE_SUCCESS(rv, rv);
- }
- }
- // SetInterlinePosition(true) means we want the caret to stick to the
- // content on the "right". We want the caret to stick to whatever is past
- // the break. This is because the break is on the same line we were on,
- // but the next content will be on the following line.
-
- // An exception to this is if the break has a next sibling that is a block
- // node. Then we stick to the left to avoid an uber caret.
- nsCOMPtr<nsIContent> siblingNode = brNode->GetNextSibling();
- if (siblingNode && IsBlockNode(*siblingNode)) {
- aSelection.SetInterlinePosition(false);
- } else {
- aSelection.SetInterlinePosition(true);
- }
- nsresult rv = aSelection.Collapse(node, offset + 1);
- NS_ENSURE_SUCCESS(rv, rv);
+ EditorRawDOMPoint point(brElement);
+ ErrorResult error;
+ aSelection.Collapse(point, error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
+ }
+ return NS_OK;
+ }
+
+ EditorDOMPoint afterBRElement(brElement);
+ DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
+ NS_WARNING_ASSERTION(advanced,
+ "Failed to advance offset after the new <br> element");
+ WSRunObject wsObj(htmlEditor, afterBRElement.GetContainer(),
+ afterBRElement.Offset());
+ nsCOMPtr<nsINode> maybeSecondBRNode;
+ int32_t visOffset = 0;
+ WSType wsType;
+ wsObj.NextVisibleNode(afterBRElement.GetContainer(), afterBRElement.Offset(),
+ address_of(maybeSecondBRNode), &visOffset, &wsType);
+ if (wsType == WSType::br) {
+ // 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) {
+ nsresult rv =
+ htmlEditor->MoveNode(maybeSecondBRNode->AsContent(),
+ afterBRElement.GetContainer(),
+ afterBRElement.Offset());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+ }
+
+ // SetInterlinePosition(true) means we want the caret to stick to the
+ // content on the "right". We want the caret to stick to whatever is past
+ // the break. This is because the break is on the same line we were on,
+ // but the next content will be on the following line.
+
+ // An exception to this is if the break has a next sibling that is a block
+ // node. Then we stick to the left to avoid an uber caret.
+ nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
+ aSelection.SetInterlinePosition(!(nextSiblingOfBRElement &&
+ IsBlockNode(*nextSiblingOfBRElement)));
+ ErrorResult error;
+ aSelection.Collapse(afterBRElement.AsRaw(), error);
+ if (NS_WARN_IF(error.Failed())) {
+ return error.StealNSResult();
}
return NS_OK;
}
nsresult
HTMLEditRules::DidInsertBreak(Selection* aSelection,
nsresult aResult)
{
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -153,18 +153,27 @@ protected:
bool* aCancel,
bool* aHandled,
const nsAString* inString,
nsAString* outString,
int32_t aMaxLength);
nsresult WillLoadHTML(Selection* aSelection, bool* aCancel);
nsresult WillInsertBreak(Selection& aSelection, bool* aCancel,
bool* aHandled);
- nsresult StandardBreakImpl(nsINode& aNode, int32_t aOffset,
- Selection& aSelection);
+
+ /**
+ * InsertBRElement() inserts a <br> element into aInsertToBreak.
+ *
+ * @param aSelection The selection.
+ * @param aInsertToBreak The point where new <br> element will be
+ * inserted before.
+ */
+ nsresult InsertBRElement(Selection& aSelection,
+ const EditorDOMPoint& aInsertToBreak);
+
nsresult DidInsertBreak(Selection* aSelection, nsresult aResult);
nsresult SplitMailCites(Selection* aSelection, bool* aHandled);
nsresult WillDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aAction,
nsIEditor::EStripWrappers aStripWrappers,
bool* aCancel, bool* aHandled);
nsresult DidDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aDir,