зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
458afbbe35
Коммит
470b292007
|
@ -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,
|
||||
|
|
Загрузка…
Ссылка в новой задаче