Bug 1566795 - part 1: Clean up `HTMLEditor::ClearStyle()`, `HTMLEditor::SplitStyleAbovePoint()` and their callers r=m_kato

Both method take a DOM point with `nsCOMPtr<nsINode>*` and `int32_t*`.
This makes each caller complicated.  Instead, we should use stack only class
to return both `EditorDOMPoint` and `nsresult`.  I name it `EditResult`.

Additionally, this fixes a bug of `HTMLeditor::SplitStyleAboveRange()`.  That
is not tracking new selection start point while it splits elements at end
of given range.  This is detected by the debug assertion in
`ToRawRangeBoundary()` (i.e., this fix is required to pass some tests).

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-10-07 00:55:02 +00:00
Родитель 458afbbe35
Коммит 470b292007
5 изменённых файлов: 386 добавлений и 237 удалений

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

@ -7,21 +7,22 @@
#define mozilla_EditorUtils_h
#include "mozilla/ContentIterator.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/EditAction.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/GuardObjects.h"
#include "mozilla/RangeBoundary.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/StaticRange.h"
#include "nsCOMPtr.h"
#include "nsDebug.h"
#include "nsIEditor.h"
#include "nsRange.h"
#include "nscore.h"
class nsAtom;
class nsISimpleEnumerator;
class nsITransferable;
class nsRange;
namespace mozilla {
class MoveNodeResult;
@ -33,6 +34,65 @@ class Element;
class Text;
} // namespace dom
/***************************************************************************
* EditResult returns nsresult and preferred point where selection should be
* collapsed or the range where selection should select.
*
* NOTE: If we stop modifying selection at every DOM tree change, perhaps,
* the following classes need to inherit this class.
*/
class MOZ_STACK_CLASS EditResult final {
public:
bool Succeeded() const { return NS_SUCCEEDED(mRv); }
bool Failed() const { return NS_FAILED(mRv); }
nsresult Rv() const { return mRv; }
bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
const EditorDOMPoint& PointRefToCollapseSelection() const {
MOZ_DIAGNOSTIC_ASSERT(mStartPoint.IsSet());
MOZ_DIAGNOSTIC_ASSERT(mStartPoint == mEndPoint);
return mStartPoint;
}
const EditorDOMPoint& StartPointRef() const { return mStartPoint; }
const EditorDOMPoint& EndPointRef() const { return mEndPoint; }
already_AddRefed<dom::StaticRange> CreateStaticRange() const {
return dom::StaticRange::Create(mStartPoint.ToRawRangeBoundary(),
mEndPoint.ToRawRangeBoundary(),
IgnoreErrors());
}
already_AddRefed<nsRange> CreateRange() const {
return nsRange::Create(mStartPoint.ToRawRangeBoundary(),
mEndPoint.ToRawRangeBoundary(), IgnoreErrors());
}
EditResult() = delete;
explicit EditResult(nsresult aRv) : mRv(aRv) {
MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
}
template <typename PT, typename CT>
explicit EditResult(const EditorDOMPointBase<PT, CT>& aPointToPutCaret)
: mRv(aPointToPutCaret.IsSet() ? NS_OK : NS_ERROR_FAILURE),
mStartPoint(aPointToPutCaret),
mEndPoint(aPointToPutCaret) {}
template <typename SPT, typename SCT, typename EPT, typename ECT>
EditResult(const EditorDOMPointBase<SPT, SCT>& aStartPoint,
const EditorDOMPointBase<EPT, ECT>& aEndPoint)
: mRv(aStartPoint.IsSet() && aEndPoint.IsSet() ? NS_OK
: NS_ERROR_FAILURE),
mStartPoint(aStartPoint),
mEndPoint(aEndPoint) {}
EditResult(const EditResult& aOther) = delete;
EditResult& operator=(const EditResult& aOther) = delete;
EditResult(EditResult&& aOther) = default;
EditResult& operator=(EditResult&& aOther) = default;
private:
nsresult mRv;
EditorDOMPoint mStartPoint;
EditorDOMPoint mEndPoint;
};
/***************************************************************************
* EditActionResult is useful to return multiple results of an editor
* action handler without out params.
@ -329,6 +389,8 @@ class MOZ_STACK_CLASS SplitNodeResult final {
bool Succeeded() const { return NS_SUCCEEDED(mRv); }
bool Failed() const { return NS_FAILED(mRv); }
nsresult Rv() const { return mRv; }
bool Handled() const { return mPreviousNode || mNextNode; }
bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
/**
* DidSplit() returns true if a node was actually split.

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

@ -3773,29 +3773,23 @@ EditActionResult HTMLEditor::TryToJoinBlocksWithTransaction(
// XXX It's odd to continue handling this edit action if there is no
// editing host.
if (!editingHost || &aLeftContentInBlock != editingHost) {
nsCOMPtr<nsIContent> splittedPreviousContent;
nsCOMPtr<nsINode> previousContentParent =
atPreviousContent.GetContainer();
int32_t previousContentOffset = atPreviousContent.Offset();
rv = SplitStyleAbovePoint(
address_of(previousContentParent), &previousContentOffset, nullptr,
nullptr, nullptr, getter_AddRefs(splittedPreviousContent));
if (NS_WARN_IF(Destroyed())) {
return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditActionIgnored(rv);
SplitNodeResult splitResult = SplitAncestorStyledInlineElementsAt(
atPreviousContent, nullptr, nullptr);
if (NS_WARN_IF(splitResult.Failed())) {
return EditActionIgnored(splitResult.Rv());
}
if (splittedPreviousContent) {
atPreviousContent.Set(splittedPreviousContent);
if (NS_WARN_IF(!atPreviousContent.IsSet())) {
return EditActionIgnored(NS_ERROR_NULL_POINTER);
}
} else {
atPreviousContent.Set(previousContentParent, previousContentOffset);
if (NS_WARN_IF(!atPreviousContent.IsSet())) {
return EditActionIgnored(NS_ERROR_NULL_POINTER);
if (splitResult.Handled()) {
if (splitResult.GetNextNode()) {
atPreviousContent.Set(splitResult.GetNextNode());
if (NS_WARN_IF(!atPreviousContent.IsSet())) {
return EditActionIgnored(NS_ERROR_NULL_POINTER);
}
} else {
atPreviousContent = splitResult.SplitPoint();
if (NS_WARN_IF(!atPreviousContent.IsSet())) {
return EditActionIgnored(NS_ERROR_NULL_POINTER);
}
}
}
}
@ -6197,9 +6191,6 @@ nsresult HTMLEditor::CreateStyleForInsertText(AbstractRange& aAbstractRange) {
MOZ_ASSERT(aAbstractRange.IsPositioned());
MOZ_ASSERT(mTypeInState);
nsCOMPtr<nsINode> node = aAbstractRange.GetStartContainer();
int32_t offset = aAbstractRange.StartOffset();
RefPtr<Element> documentRootElement = GetDocument()->GetRootElement();
if (NS_WARN_IF(!documentRootElement)) {
return NS_ERROR_FAILURE;
@ -6208,25 +6199,21 @@ nsresult HTMLEditor::CreateStyleForInsertText(AbstractRange& aAbstractRange) {
// process clearing any styles first
UniquePtr<PropItem> item = mTypeInState->TakeClearProperty();
bool weDidSomething = false;
EditorDOMPoint pointToPutCaret(aAbstractRange.StartRef());
bool putCaret = false;
{
// Transactions may set selection, but we will set selection if necessary.
AutoTransactionsConserveSelection dontChangeMySelection(*this);
while (item && node != documentRootElement) {
// XXX If we redesign ClearStyle(), we can use EditorDOMPoint in this
// method.
nsresult rv =
ClearStyle(address_of(node), &offset, MOZ_KnownLive(item->tag),
MOZ_KnownLive(item->attr));
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
while (item && pointToPutCaret.GetContainer() != documentRootElement) {
EditResult result = ClearStyleAt(
pointToPutCaret, MOZ_KnownLive(item->tag), MOZ_KnownLive(item->attr));
if (NS_WARN_IF(result.Failed())) {
return result.Rv();
}
pointToPutCaret = result.PointRefToCollapseSelection();
item = mTypeInState->TakeClearProperty();
weDidSomething = true;
putCaret = true;
}
}
@ -6237,10 +6224,10 @@ nsresult HTMLEditor::CreateStyleForInsertText(AbstractRange& aAbstractRange) {
if (item || relFontSize) {
// we have at least one style to add; make a new text node to insert style
// nodes above.
if (RefPtr<Text> text = node->GetAsText()) {
if (pointToPutCaret.IsInTextNode()) {
// if we are in a text node, split it
SplitNodeResult splitTextNodeResult = SplitNodeDeepWithTransaction(
*text, EditorDOMPoint(text, offset),
MOZ_KnownLive(*pointToPutCaret.GetContainerAsText()), pointToPutCaret,
SplitAtEdges::eAllowToCreateEmptyContainer);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
@ -6248,35 +6235,31 @@ nsresult HTMLEditor::CreateStyleForInsertText(AbstractRange& aAbstractRange) {
if (NS_WARN_IF(splitTextNodeResult.Failed())) {
return splitTextNodeResult.Rv();
}
EditorRawDOMPoint splitPoint(splitTextNodeResult.SplitPoint());
node = splitPoint.GetContainer();
offset = splitPoint.Offset();
pointToPutCaret = splitTextNodeResult.SplitPoint();
}
if (!IsContainer(node)) {
if (!IsContainer(pointToPutCaret.GetContainer())) {
return NS_OK;
}
RefPtr<Text> newNode = CreateTextNode(EmptyString());
if (NS_WARN_IF(!newNode)) {
RefPtr<Text> newEmptyTextNode = CreateTextNode(EmptyString());
if (NS_WARN_IF(!newEmptyTextNode)) {
return NS_ERROR_FAILURE;
}
nsresult rv =
InsertNodeWithTransaction(*newNode, EditorDOMPoint(node, offset));
nsresult rv = InsertNodeWithTransaction(*newEmptyTextNode, pointToPutCaret);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
node = newNode;
offset = 0;
weDidSomething = true;
pointToPutCaret.Set(newEmptyTextNode, 0);
putCaret = true;
if (relFontSize) {
// dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
HTMLEditor::FontSize dir = relFontSize > 0 ? HTMLEditor::FontSize::incr
: HTMLEditor::FontSize::decr;
for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) {
rv = RelativeFontChangeOnTextNode(dir, *newNode, 0, -1);
rv = RelativeFontChangeOnTextNode(dir, *newEmptyTextNode, 0, -1);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -6287,9 +6270,9 @@ nsresult HTMLEditor::CreateStyleForInsertText(AbstractRange& aAbstractRange) {
}
while (item) {
rv = SetInlinePropertyOnNode(MOZ_KnownLive(*node->AsContent()),
MOZ_KnownLive(*item->tag),
MOZ_KnownLive(item->attr), item->value);
rv = SetInlinePropertyOnNode(
MOZ_KnownLive(*pointToPutCaret.GetContainerAsContent()),
MOZ_KnownLive(*item->tag), MOZ_KnownLive(item->attr), item->value);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
@ -6300,11 +6283,11 @@ nsresult HTMLEditor::CreateStyleForInsertText(AbstractRange& aAbstractRange) {
}
}
if (!weDidSomething) {
if (!putCaret) {
return NS_OK;
}
nsresult rv = SelectionRefPtr()->Collapse(node, offset);
nsresult rv = SelectionRefPtr()->Collapse(pointToPutCaret);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}

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

@ -44,6 +44,7 @@ class AlignStateAtSelection;
class AutoSelectionSetterAfterTableEdit;
class AutoSetTemporaryAncestorLimiter;
class EditActionResult;
class EditResult;
class EmptyEditableFunctor;
class ListElementSelectionState;
class ListItemElementSelectionState;
@ -977,11 +978,24 @@ class HTMLEditor final : public TextEditor,
nsresult SetInlinePropertyOnNode(nsIContent& aNode, nsAtom& aProperty,
nsAtom* aAttribute, const nsAString& aValue);
MOZ_CAN_RUN_SCRIPT
nsresult SplitStyleAbovePoint(nsCOMPtr<nsINode>* aNode, int32_t* aOffset,
nsAtom* aProperty, nsAtom* aAttribute,
nsIContent** aOutLeftNode = nullptr,
nsIContent** aOutRightNode = nullptr);
/**
* SplitAncestorStyledInlineElementsAt() splits ancestor inline elements at
* aPointToSplit if specified style matches with them.
*
* @param aPointToSplit The point to split style at.
* @param aProperty The style tag name which you want to split.
* Set nullptr if you want to split any styled
* elements.
* @param aAttribute Attribute name if aProperty has some styles
* like nsGkAtoms::font.
* @return The result of SplitNodeDeepWithTransaction()
* with topmost split element. If this didn't
* find inline elements to be split, Handled()
* returns false.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE SplitNodeResult
SplitAncestorStyledInlineElementsAt(const EditorDOMPoint& aPointToSplit,
nsAtom* aProperty, nsAtom* aAttribute);
nsIContent* GetPriorHTMLSibling(nsINode* aNode);
@ -1138,9 +1152,20 @@ class HTMLEditor final : public TextEditor,
bool* aAny, bool* aAll,
nsAString* outValue) const;
MOZ_CAN_RUN_SCRIPT
nsresult ClearStyle(nsCOMPtr<nsINode>* aNode, int32_t* aOffset,
nsAtom* aProperty, nsAtom* aAttribute);
/**
* ClearStyleAt() splits parent elements to remove the specified style.
* If this splits some parent elements at near their start or end, such
* empty elements will be removed. Then, remove the specified style
* from the point and returns DOM point to put caret.
*
* @param aPoint The point to clear style at.
* @param aProperty An HTML tag name which represents a style.
* Set nullptr if you want to clear all styles.
* @param aAttribute Attribute name if aProperty has some styles like
* nsGkAtoms::font.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE EditResult ClearStyleAt(
const EditorDOMPoint& aPoint, nsAtom* aProperty, nsAtom* aAttribute);
MOZ_CAN_RUN_SCRIPT nsresult SetPositionToAbsolute(Element& aElement);
MOZ_CAN_RUN_SCRIPT nsresult SetPositionToStatic(Element& aElement);

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

@ -288,12 +288,10 @@ nsresult HTMLEditor::DoInsertHTMLWithContext(
if (aClearStyle) {
// pasting does not inherit local inline styles
nsCOMPtr<nsINode> tmpNode = SelectionRefPtr()->GetAnchorNode();
int32_t tmpOffset =
static_cast<int32_t>(SelectionRefPtr()->AnchorOffset());
rv = ClearStyle(address_of(tmpNode), &tmpOffset, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
EditResult result = ClearStyleAt(
EditorDOMPoint(SelectionRefPtr()->AnchorRef()), nullptr, nullptr);
if (NS_WARN_IF(result.Failed())) {
return result.Rv();
}
}
} else {

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

@ -12,9 +12,10 @@
#include "mozilla/EditAction.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/SelectionState.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Element.h"
#include "mozilla/mozalloc.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h"
#include "nsAString.h"
#include "nsAttrName.h"
#include "nsCOMPtr.h"
@ -573,202 +574,282 @@ nsresult HTMLEditor::SplitStyleAboveRange(nsRange* aRange, nsAtom* aProperty,
nsAtom* aAttribute) {
MOZ_ASSERT(IsEditActionDataAvailable());
if (NS_WARN_IF(!aRange)) {
if (NS_WARN_IF(!aRange) || NS_WARN_IF(!aRange->IsPositioned())) {
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsINode> startNode = aRange->GetStartContainer();
int32_t startOffset = aRange->StartOffset();
nsCOMPtr<nsINode> endNode = aRange->GetEndContainer();
int32_t endOffset = aRange->EndOffset();
nsCOMPtr<nsINode> origStartNode = startNode;
EditorDOMPoint startOfRange(aRange->StartRef());
EditorDOMPoint endOfRange(aRange->EndRef());
// split any matching style nodes above the start of range
{
AutoTrackDOMPoint tracker(RangeUpdaterRef(), address_of(endNode),
&endOffset);
nsresult rv = SplitStyleAbovePoint(address_of(startNode), &startOffset,
aProperty, aAttribute);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &endOfRange);
SplitNodeResult result = SplitAncestorStyledInlineElementsAt(
startOfRange, aProperty, aAttribute);
if (NS_WARN_IF(result.Failed())) {
return result.Rv();
}
if (result.Handled()) {
startOfRange = result.SplitPoint();
if (NS_WARN_IF(!startOfRange.IsSet())) {
return NS_ERROR_FAILURE;
}
}
}
// second verse, same as the first...
nsresult rv = SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty,
aAttribute);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
{
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &startOfRange);
SplitNodeResult result =
SplitAncestorStyledInlineElementsAt(endOfRange, aProperty, aAttribute);
if (NS_WARN_IF(result.Failed())) {
return result.Rv();
}
if (result.Handled()) {
endOfRange = result.SplitPoint();
if (NS_WARN_IF(!endOfRange.IsSet())) {
return NS_ERROR_FAILURE;
}
}
}
// reset the range
rv = aRange->SetStartAndEnd(startNode, startOffset, endNode, endOffset);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
nsresult rv = aRange->SetStartAndEnd(startOfRange.ToRawRangeBoundary(),
endOfRange.ToRawRangeBoundary());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
return rv;
}
nsresult HTMLEditor::SplitStyleAbovePoint(
nsCOMPtr<nsINode>* aNode, int32_t* aOffset,
// null here means we split all properties
nsAtom* aProperty, nsAtom* aAttribute, nsIContent** aOutLeftNode,
nsIContent** aOutRightNode) {
NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER);
NS_ENSURE_TRUE((*aNode)->IsContent(), NS_OK);
if (aOutLeftNode) {
*aOutLeftNode = nullptr;
SplitNodeResult HTMLEditor::SplitAncestorStyledInlineElementsAt(
const EditorDOMPoint& aPointToSplit, nsAtom* aProperty,
nsAtom* aAttribute) {
if (NS_WARN_IF(!aPointToSplit.IsSet()) ||
NS_WARN_IF(!aPointToSplit.GetContainerAsContent())) {
return SplitNodeResult(NS_ERROR_INVALID_ARG);
}
if (aOutRightNode) {
*aOutRightNode = nullptr;
}
// Split any matching style nodes above the node/offset
nsCOMPtr<nsIContent> node = (*aNode)->AsContent();
bool useCSS = IsCSSEnabled();
bool isSet;
while (!IsBlockNode(node) && node->GetParent() &&
IsEditable(node->GetParent())) {
isSet = false;
// Split any matching style nodes above the point.
SplitNodeResult result(aPointToSplit);
MOZ_ASSERT(!result.Handled());
for (nsCOMPtr<nsIContent> content = aPointToSplit.GetContainerAsContent();
!IsBlockNode(content) && content->GetParent() &&
IsEditable(content->GetParent());
content = content->GetParent()) {
bool isSetByCSS = false;
if (useCSS &&
CSSEditUtils::IsCSSEditableProperty(node, aProperty, aAttribute)) {
CSSEditUtils::IsCSSEditableProperty(content, aProperty, aAttribute)) {
// The HTML style defined by aProperty/aAttribute has a CSS equivalence
// in this implementation for the node; let's check if it carries those
// CSS styles
nsAutoString firstValue;
isSet = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
node, aProperty, aAttribute, firstValue, CSSEditUtils::eSpecified);
isSetByCSS = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
content, aProperty, aAttribute, firstValue, CSSEditUtils::eSpecified);
}
if ( // node is the correct inline prop
(aProperty && node->IsHTMLElement(aProperty)) ||
// node is href - test if really <a href=...
(aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(node)) ||
// or node is any prop, and we asked to split them all
(!aProperty && node->IsElement() && IsEditable(node) &&
HTMLEditUtils::IsRemovableInlineStyleElement(*node->AsElement())) ||
// or the style is specified in the style attribute
isSet) {
// Found a style node we need to split
SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
*node, EditorDOMPoint(*aNode, *aOffset),
SplitAtEdges::eAllowToCreateEmptyContainer);
NS_WARNING_ASSERTION(splitNodeResult.Succeeded(),
"Failed to split the node");
EditorRawDOMPoint atRightNode(splitNodeResult.SplitPoint());
*aNode = atRightNode.GetContainer();
*aOffset = atRightNode.Offset();
if (aOutLeftNode) {
NS_IF_ADDREF(*aOutLeftNode = splitNodeResult.GetPreviousNode());
if (!isSetByCSS) {
if (!content->IsElement()) {
continue;
}
if (aOutRightNode) {
NS_IF_ADDREF(*aOutRightNode = splitNodeResult.GetNextNode());
}
}
node = node->GetParent();
if (NS_WARN_IF(!node)) {
return NS_ERROR_FAILURE;
}
}
return NS_OK;
}
nsresult HTMLEditor::ClearStyle(nsCOMPtr<nsINode>* aNode, int32_t* aOffset,
nsAtom* aProperty, nsAtom* aAttribute) {
MOZ_ASSERT(IsEditActionDataAvailable());
nsCOMPtr<nsIContent> leftNode, rightNode;
nsresult rv =
SplitStyleAbovePoint(aNode, aOffset, aProperty, aAttribute,
getter_AddRefs(leftNode), getter_AddRefs(rightNode));
NS_ENSURE_SUCCESS(rv, rv);
if (leftNode) {
bool bIsEmptyNode;
IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
if (bIsEmptyNode) {
// delete leftNode if it became empty
rv = DeleteNodeWithTransaction(*leftNode);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
}
}
if (rightNode) {
nsCOMPtr<nsINode> secondSplitParent = GetLeftmostChild(rightNode);
// don't try to split non-containers (br's, images, hr's, etc.)
if (!secondSplitParent) {
secondSplitParent = rightNode;
}
RefPtr<Element> savedBR;
if (!IsContainer(secondSplitParent)) {
if (secondSplitParent->IsHTMLElement(nsGkAtoms::br)) {
savedBR = Element::FromNode(secondSplitParent);
MOZ_ASSERT(savedBR);
}
secondSplitParent = secondSplitParent->GetParentNode();
}
*aOffset = 0;
rv = SplitStyleAbovePoint(address_of(secondSplitParent), aOffset, aProperty,
aAttribute, getter_AddRefs(leftNode),
getter_AddRefs(rightNode));
NS_ENSURE_SUCCESS(rv, rv);
if (rightNode) {
bool bIsEmptyNode;
IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
if (bIsEmptyNode) {
// delete rightNode if it became empty
rv = DeleteNodeWithTransaction(*rightNode);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
// If aProperty is set, we need to split only elements which applies the
// given style.
if (aProperty) {
// If the content is an inline element represents aProperty or
// the content is a link element and aProperty is `href`, we should
// split the content.
if (!content->IsHTMLElement(aProperty) &&
!(aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(content))) {
continue;
}
}
}
if (!leftNode) {
return NS_OK;
}
// should be impossible to not get a new leftnode here
nsCOMPtr<nsINode> newSelParent = GetLeftmostChild(leftNode);
if (!newSelParent) {
newSelParent = leftNode;
}
// If rightNode starts with a br, suck it out of right node and into
// leftNode. This is so we you don't revert back to the previous style
// if you happen to click at the end of a line.
if (savedBR) {
rv = MoveNodeWithTransaction(*savedBR, EditorDOMPoint(newSelParent, 0));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
// If aProperty is nullptr, we need to split any style.
else if (!IsEditable(content) ||
!HTMLEditUtils::IsRemovableInlineStyleElement(
*content->AsElement())) {
continue;
}
}
// remove the style on this new hierarchy
int32_t newSelOffset = 0;
{
// Track the point at the new hierarchy. This is so we can know where
// to put the selection after we call RemoveStyleInside().
// RemoveStyleInside() could remove any and all of those nodes, so I
// have to use the range tracking system to find the right spot to put
// selection.
AutoTrackDOMPoint tracker(RangeUpdaterRef(), address_of(newSelParent),
&newSelOffset);
rv = RemoveStyleInside(*leftNode, aProperty, aAttribute);
NS_ENSURE_SUCCESS(rv, rv);
// Found a style node we need to split.
// XXX If first content is a text node and CSS is enabled, we call this
// with text node but in such case, this does nothing, but returns
// as handled with setting only previous or next node. If its parent
// is a block, we do nothing but return as handled.
SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
*content, result.SplitPoint(),
SplitAtEdges::eAllowToCreateEmptyContainer);
if (NS_WARN_IF(splitNodeResult.Failed())) {
return splitNodeResult;
}
// reset our node offset values to the resulting new sel point
*aNode = newSelParent;
*aOffset = newSelOffset;
MOZ_ASSERT(splitNodeResult.Handled());
// Mark the final result as handled forcibly.
result = SplitNodeResult(splitNodeResult.GetPreviousNode(),
splitNodeResult.GetNextNode());
MOZ_ASSERT(result.Handled());
}
return NS_OK;
return result;
}
EditResult HTMLEditor::ClearStyleAt(const EditorDOMPoint& aPoint,
nsAtom* aProperty, nsAtom* aAttribute) {
MOZ_ASSERT(IsEditActionDataAvailable());
if (NS_WARN_IF(!aPoint.IsSet())) {
return EditResult(NS_ERROR_INVALID_ARG);
}
// First, split inline elements at the point.
// E.g., if aProperty is nsGkAtoms::b and `<p><b><i>a[]bc</i></b></p>`,
// we want to make it as `<p><b><i>a</i></b><b><i>bc</i></b></p>`.
SplitNodeResult splitResult =
SplitAncestorStyledInlineElementsAt(aPoint, aProperty, aAttribute);
if (NS_WARN_IF(splitResult.Failed())) {
return EditResult(splitResult.Rv());
}
// If there is no styled inline elements of aProperty/aAttribute, we just
// return the given point.
// E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b.
if (!splitResult.Handled()) {
return EditResult(aPoint);
}
// If it did split nodes, but topmost ancestor inline element is split
// at start of it, we don't need the empty inline element. Let's remove
// it now.
if (splitResult.GetPreviousNode()) {
bool isEmpty = false;
IsEmptyNode(splitResult.GetPreviousNode(), &isEmpty, false, true);
if (isEmpty) {
// Delete previous node if it's empty.
nsresult rv = DeleteNodeWithTransaction(
MOZ_KnownLive(*splitResult.GetPreviousNode()));
if (NS_WARN_IF(Destroyed())) {
return EditResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditResult(rv);
}
}
}
// If we reached block from end of a text node, we can do nothing here.
// E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and
// we're in CSS mode.
// XXX Chrome resets block style and creates `<span>` elements for each
// line in this case.
if (!splitResult.GetNextNode()) {
MOZ_ASSERT(IsCSSEnabled());
return EditResult(aPoint);
}
// Otherwise, the next node is topmost ancestor inline element which has
// the style. We want to put caret between the split nodes, but we need
// to keep other styles. Therefore, next, we need to split at start of
// the next node. The first example should become
// `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`.
// ^^^^^^^^^^^^^^
nsIContent* leftmostChildOfNextNode =
GetLeftmostChild(splitResult.GetNextNode());
EditorDOMPoint atStartOfNextNode(leftmostChildOfNextNode
? leftmostChildOfNextNode
: splitResult.GetNextNode(),
0);
RefPtr<HTMLBRElement> brElement;
// But don't try to split non-containers like `<br>`, `<hr>` and `<img>`
// element.
if (!IsContainer(atStartOfNextNode.GetContainer())) {
// If it's a `<br>` element, let's move it into new node later.
brElement = HTMLBRElement::FromNode(atStartOfNextNode.GetContainer());
if (NS_WARN_IF(!atStartOfNextNode.GetContainerParentAsContent())) {
return EditResult(NS_ERROR_FAILURE);
}
atStartOfNextNode.Set(atStartOfNextNode.GetContainerParent(), 0);
}
SplitNodeResult splitResultAtStartOfNextNode =
SplitAncestorStyledInlineElementsAt(atStartOfNextNode, aProperty,
aAttribute);
if (NS_WARN_IF(splitResultAtStartOfNextNode.Failed())) {
return EditResult(splitResultAtStartOfNextNode.Rv());
}
// Let's remove the next node if it becomes empty by splitting it.
// XXX Is this possible case without mutation event listener?
if (splitResultAtStartOfNextNode.Handled() &&
splitResultAtStartOfNextNode.GetNextNode()) {
bool isEmpty = false;
IsEmptyNode(splitResultAtStartOfNextNode.GetNextNode(), &isEmpty, false,
true);
if (isEmpty) {
// Delete next node if it's empty.
nsresult rv = DeleteNodeWithTransaction(
MOZ_KnownLive(*splitResultAtStartOfNextNode.GetNextNode()));
if (NS_WARN_IF(Destroyed())) {
return EditResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditResult(rv);
}
}
}
// If there is no content, we should return here.
// XXX Is this possible case without mutation event listener?
if (NS_WARN_IF(!splitResultAtStartOfNextNode.Handled()) ||
!splitResultAtStartOfNextNode.GetPreviousNode()) {
// XXX This is really odd, but we retrun this value...
return EditResult(
EditorDOMPoint(splitResult.SplitPoint().GetContainer(),
splitResultAtStartOfNextNode.SplitPoint().Offset()));
}
// Now, we want to put `<br>` element into the empty split node if
// it was in next node of the first split.
// E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>`
nsIContent* leftmostChild =
GetLeftmostChild(splitResultAtStartOfNextNode.GetPreviousNode());
EditorDOMPoint pointToPutCaret(
leftmostChild ? leftmostChild
: splitResultAtStartOfNextNode.GetPreviousNode(),
0);
// If the right node starts with a `<br>`, suck it out of right node and into
// the left node left node. This is so we you don't revert back to the
// previous style if you happen to click at the end of a line.
if (brElement) {
nsresult rv = MoveNodeWithTransaction(*brElement, pointToPutCaret);
if (NS_WARN_IF(Destroyed())) {
return EditResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditResult(rv);
}
// Update the child.
pointToPutCaret.Set(pointToPutCaret.GetContainer(), 0);
}
// Finally, remove the specified style in the previous node at the
// second split and tells good insertion point to the caller. I.e., we
// want to make the first example as:
// `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>`
// ^^^^^^^^^
{
// Track the point at the new hierarchy. This is so we can know where
// to put the selection after we call RemoveStyleInside().
// RemoveStyleInside() could remove any and all of those nodes, so I
// have to use the range tracking system to find the right spot to put
// selection.
AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToPutCaret);
nsresult rv = RemoveStyleInside(
MOZ_KnownLive(*splitResultAtStartOfNextNode.GetPreviousNode()),
aProperty, aAttribute);
if (NS_WARN_IF(Destroyed())) {
return EditResult(NS_ERROR_EDITOR_DESTROYED);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return EditResult(rv);
}
}
return EditResult(pointToPutCaret);
}
nsresult HTMLEditor::RemoveStyleInside(nsIContent& aNode, nsAtom* aProperty,