Bug 1807829 - Make `HTMLEditor::CreateStyleForInsertText` use same logic as `HTMLEditor::SetInlinePropertiesAsSubAction` to apply new style r=m_kato

The problem in Yahoo mail is, when you apply new font size to:
```
<div><font size="7">{}<br></font></div>
```

then, when you type text, you'll get:
```
<div><font size="7"><font size="4">abc<br></font></font></div>
```

because `HTMLEditor::CreateStyleForInsertText` does not check ancestors to
apply new style to empty text node which is created by the method for
placeholder.  Of course, the other browsers update the existing
`<font size="7">` instead and we should follow it.

However, there are 3 problems:
1. Our editor may adjust insertion point to nearest editable text node if caret
in empty inline elements.
2. Our editor supports increase/decrease font size which have been removed from
`Document.execCommand`, but Thunderbird keeps using it, and that's implemented
with an independent method.
3. Our style editor code does not support applying style to collapsed selection.

Therefore, this patch does:
1. keep create empty text node to apply new style at specific point.
2. give up to use new path if there is preserved font size increment/decrement.
3. separate `HTMLEditor::SetInlinePropertiesAsSubAction` and add new path for
applying style to collapsed range.

Then, `HTMLEditor::CreateStyleForInsertText` can use
`HTMLEditor::SetInlinePropertiesAsSubAction` which supports handling new styles
with existing ancestors.

Differential Revision: https://phabricator.services.mozilla.com/D166416
This commit is contained in:
Masayuki Nakano 2023-01-16 23:42:27 +00:00
Родитель 5077bb750f
Коммит 6d33379e63
9 изменённых файлов: 396 добавлений и 93 удалений

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

@ -1094,7 +1094,7 @@ Result<EditActionResult, nsresult> HTMLEditor::HandleInsertText(
// for every property that is set, insert a new inline style node
Result<EditorDOMPoint, nsresult> setStyleResult =
CreateStyleForInsertText(pointToInsert);
CreateStyleForInsertText(pointToInsert, *editingHost);
if (MOZ_UNLIKELY(setStyleResult.isErr())) {
NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
return setStyleResult.propagateErr();
@ -2258,7 +2258,7 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::HandleInsertLinefeed(
// should be merged when we fix bug 92921.
Result<EditorDOMPoint, nsresult> setStyleResult =
CreateStyleForInsertText(aPointToBreak);
CreateStyleForInsertText(aPointToBreak, aEditingHost);
if (MOZ_UNLIKELY(setStyleResult.isErr())) {
NS_WARNING("HTMLEditor::CreateStyleForInsertText() failed");
return setStyleResult.propagateErr();
@ -6078,7 +6078,7 @@ Result<CreateElementResult, nsresult> HTMLEditor::ChangeListElementType(
}
Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
const EditorDOMPoint& aPointToInsertText) {
const EditorDOMPoint& aPointToInsertText, const Element& aEditingHost) {
MOZ_ASSERT(IsEditActionDataAvailable());
MOZ_ASSERT(aPointToInsertText.IsSetAndValid());
MOZ_ASSERT(mPendingStylesToApplyToNewContent);
@ -6116,9 +6116,17 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
// then process setting any styles
const int32_t relFontSize =
mPendingStylesToApplyToNewContent->TakeRelativeFontSize();
pendingStyle = mPendingStylesToApplyToNewContent->TakePreservedStyle();
AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet;
mPendingStylesToApplyToNewContent->TakeAllPreservedStyles(stylesToSet);
if (stylesToSet.IsEmpty() && !relFontSize) {
return pointToPutCaret;
}
if (pendingStyle || relFontSize) {
// We're in chrome, e.g., the email composer of Thunderbird, and there is
// relative font size changes, we need to keep using legacy path until we port
// IncrementOrDecrementFontSizeAsSubAction() to work with
// AutoInlineStyleSetter.
if (relFontSize) {
// we have at least one style to add; make a new text node to insert style
// nodes above.
EditorDOMPoint pointToInsertTextNode(pointToPutCaret);
@ -6164,34 +6172,26 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
pointToPutCaret.Set(newEmptyTextNode, 0u);
if (relFontSize) {
HTMLEditor::FontSize incrementOrDecrement =
relFontSize > 0 ? HTMLEditor::FontSize::incr
: HTMLEditor::FontSize::decr;
for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) {
Result<CreateElementResult, nsresult>
wrapTextInBigOrSmallElementResult = SetFontSizeOnTextNode(
*newEmptyTextNode, 0, UINT32_MAX, incrementOrDecrement);
if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) {
NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
return wrapTextInBigOrSmallElementResult.propagateErr();
}
// We don't need to update here because we'll suggest caret position
// which is computed above.
MOZ_ASSERT(pointToPutCaret.IsSet());
wrapTextInBigOrSmallElementResult.inspect()
.IgnoreCaretPointSuggestion();
HTMLEditor::FontSize incrementOrDecrement =
relFontSize > 0 ? HTMLEditor::FontSize::incr
: HTMLEditor::FontSize::decr;
for ([[maybe_unused]] uint32_t j : IntegerRange(Abs(relFontSize))) {
Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult =
SetFontSizeOnTextNode(*newEmptyTextNode, 0, UINT32_MAX,
incrementOrDecrement);
if (MOZ_UNLIKELY(wrapTextInBigOrSmallElementResult.isErr())) {
NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
return wrapTextInBigOrSmallElementResult.propagateErr();
}
// We don't need to update here because we'll suggest caret position
// which is computed above.
MOZ_ASSERT(pointToPutCaret.IsSet());
wrapTextInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion();
}
while (pendingStyle) {
AutoInlineStyleSetter inlineStyleSetter(
pendingStyle->GetAttribute()
? EditorInlineStyleAndValue(
*pendingStyle->GetTag(), *pendingStyle->GetAttribute(),
pendingStyle->AttributeValueOrCSSValueRef())
: EditorInlineStyleAndValue(*pendingStyle->GetTag()));
// MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret()
for (const EditorInlineStyleAndValue& styleToSet : stylesToSet) {
AutoInlineStyleSetter inlineStyleSetter(styleToSet);
// MOZ_KnownLive(...ContainerAs<nsIContent>()) because pointToPutCaret
// grabs the result.
Result<CaretPoint, nsresult> setStyleResult =
inlineStyleSetter.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
@ -6204,10 +6204,43 @@ Result<EditorDOMPoint, nsresult> HTMLEditor::CreateStyleForInsertText(
// is computed above.
MOZ_ASSERT(pointToPutCaret.IsSet());
setStyleResult.unwrap().IgnoreCaretPointSuggestion();
pendingStyle = mPendingStylesToApplyToNewContent->TakePreservedStyle();
}
return pointToPutCaret;
}
// If we have preserved commands except relative font style changes, we can
// use inline style setting code which reuse ancestors better.
AutoRangeArray ranges(pointToPutCaret);
if (MOZ_UNLIKELY(ranges.Ranges().IsEmpty())) {
NS_WARNING("AutoRangeArray::AutoRangeArray() failed");
return Err(NS_ERROR_FAILURE);
}
nsresult rv =
SetInlinePropertiesAroundRanges(ranges, stylesToSet, aEditingHost);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
return Err(rv);
}
if (NS_WARN_IF(ranges.Ranges().IsEmpty())) {
return Err(NS_ERROR_FAILURE);
}
// Now `ranges` selects new styled contents and the range may not be
// collapsed. We should use the deepest editable start point of the range
// to insert text.
nsINode* container = ranges.FirstRangeRef()->GetStartContainer();
if (MOZ_UNLIKELY(!container->IsContent())) {
container = ranges.FirstRangeRef()->GetChildAtStartOffset();
if (MOZ_UNLIKELY(!container)) {
NS_WARNING("How did we get lost insertion point?");
return Err(NS_ERROR_FAILURE);
}
}
pointToPutCaret =
HTMLEditUtils::GetDeepestEditableStartPointOf<EditorDOMPoint>(
*container->AsContent());
if (NS_WARN_IF(!pointToPutCaret.IsSet())) {
return Err(NS_ERROR_FAILURE);
}
return pointToPutCaret;
}

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

@ -2034,7 +2034,44 @@ EditorDOMPointType HTMLEditUtils::GetBetterInsertionPointFor(
.template PointAfterContent<EditorDOMPointType>();
}
// static
// static
template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
EditorDOMPointType HTMLEditUtils::GetBetterCaretPositionToInsertText(
const EditorDOMPointTypeInput& aPoint, const Element& aEditingHost) {
MOZ_ASSERT(aPoint.IsSetAndValid());
MOZ_ASSERT(
aPoint.GetContainer()->IsInclusiveFlatTreeDescendantOf(&aEditingHost));
if (aPoint.IsInTextNode()) {
return aPoint.template To<EditorDOMPointType>();
}
if (!aPoint.IsEndOfContainer() && aPoint.GetChild() &&
aPoint.GetChild()->IsText()) {
return EditorDOMPointType(aPoint.GetChild(), 0u);
}
if (aPoint.IsEndOfContainer()) {
WSRunScanner scanner(&aEditingHost, aPoint);
WSScanResult previousThing =
scanner.ScanPreviousVisibleNodeOrBlockBoundaryFrom(aPoint);
if (previousThing.InVisibleOrCollapsibleCharacters()) {
return EditorDOMPointType::AtEndOf(*previousThing.TextPtr());
}
}
if (HTMLEditUtils::CanNodeContain(*aPoint.GetContainer(),
*nsGkAtoms::textTagName)) {
return aPoint.template To<EditorDOMPointType>();
}
if (MOZ_UNLIKELY(aPoint.GetContainer() == &aEditingHost ||
!aPoint.template GetContainerParentAs<nsIContent>() ||
!HTMLEditUtils::CanNodeContain(
*aPoint.template ContainerParentAs<nsIContent>(),
*nsGkAtoms::textTagName))) {
return EditorDOMPointType();
}
return aPoint.ParentPoint().template To<EditorDOMPointType>();
}
// static
template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
Result<EditorDOMPointType, nsresult>
HTMLEditUtils::ComputePointToPutCaretInElementIfOutside(

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

@ -2001,6 +2001,14 @@ class HTMLEditUtils final {
const EditorDOMPointTypeInput& aPointToInsert,
const Element& aEditingHost);
/**
* GetBetterCaretPositionToInsertText() returns better point to put caret
* if aPoint is near a text node or in non-container node.
*/
template <typename EditorDOMPointType, typename EditorDOMPointTypeInput>
static EditorDOMPointType GetBetterCaretPositionToInsertText(
const EditorDOMPointTypeInput& aPoint, const Element& aEditingHost);
/**
* ComputePointToPutCaretInElementIfOutside() returns a good point in aElement
* to put caret if aCurrentPoint is outside of aElement.

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

@ -1079,10 +1079,12 @@ class HTMLEditor final : public EditorBase,
* PendingStyles to proper element node.
*
* @param aPointToInsertText The point to insert text.
* @param aEditingHost The editing host.
* @return A suggest point to put caret or unset point.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<EditorDOMPoint, nsresult>
CreateStyleForInsertText(const EditorDOMPoint& aPointToInsertText);
CreateStyleForInsertText(const EditorDOMPoint& aPointToInsertText,
const Element& aEditingHost);
/**
* GetMostDistantAncestorMailCiteElement() returns most-ancestor mail cite
@ -3266,6 +3268,16 @@ class HTMLEditor final : public EditorBase,
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertiesAsSubAction(
const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet);
/**
* SetInlinePropertiesAroundRanges() applying the styles to the ranges even if
* the ranges are collapsed.
*/
template <size_t N>
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetInlinePropertiesAroundRanges(
AutoRangeArray& aRanges,
const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet,
const Element& aEditingHost);
/**
* RemoveInlinePropertiesAsSubAction() removes specified styles from
* mPendingStylesToApplyToNewContent if `Selection` is collapsed. Otherwise,

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

@ -76,7 +76,8 @@ class MOZ_STACK_CLASS HTMLEditor::AutoInlineStyleSetter final
* See comments in the definition what this does.
*/
Result<EditorRawDOMRange, nsresult> ExtendOrShrinkRangeToApplyTheStyle(
const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) const;
const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
const Element& aEditingHost) const;
/**
* Returns next/previous sibling of aContent or an ancestor of it if it's
@ -87,6 +88,32 @@ class MOZ_STACK_CLASS HTMLEditor::AutoInlineStyleSetter final
[[nodiscard]] static nsIContent* GetPreviousEditableInlineContent(
const nsIContent& aContent, const nsINode* aLimiter = nullptr);
/**
* GetEmptyTextNodeToApplyNewStyle creates new empty text node to insert
* a new element which will contain newly inserted text or returns existing
* empty text node if aCandidatePointToInsert is around it.
*
* NOTE: Unfortunately, editor does not want to insert text into empty inline
* element in some places (e.g., automatically adjusting caret position to
* nearest text node). Therefore, we need to create new empty text node to
* prepare new styles for inserting text. This method is designed for the
* preparation.
*
* @param aHTMLEditor The editor.
* @param aCandidatePointToInsert The point where the caller wants to
* insert new text.
* @param aEditingHost The editing host.
* @return If this creates new empty text node returns it.
* If this couldn't create new empty text node due to
* the point or aEditingHost cannot have text node,
* returns nullptr.
* Otherwise, returns error.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT static Result<RefPtr<Text>, nsresult>
GetEmptyTextNodeToApplyNewStyle(HTMLEditor& aHTMLEditor,
const EditorDOMPoint& aCandidatePointToInsert,
const Element& aEditingHost);
private:
[[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<CaretPoint, nsresult> ApplyStyle(
HTMLEditor& aHTMLEditor, nsIContent& aContent);

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

@ -15,8 +15,8 @@
#include "HTMLEditUtils.h"
#include "PendingStyles.h"
#include "SelectionState.h"
#include "WSRunObject.h"
#include "ErrorList.h"
#include "mozilla/Assertions.h"
#include "mozilla/ContentIterator.h"
#include "mozilla/EditorForwards.h"
@ -28,29 +28,29 @@
#include "mozilla/dom/HTMLBRElement.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/Text.h"
#include "nsAString.h"
#include "nsAtom.h"
#include "nsAttrName.h"
#include "nsCOMPtr.h"
#include "nsCaseTreatment.h"
#include "nsComponentManagerUtils.h"
#include "nsDebug.h"
#include "nsError.h"
#include "nsGkAtoms.h"
#include "nsAtom.h"
#include "nsIContent.h"
#include "nsNameSpaceManager.h"
#include "nsINode.h"
#include "nsIPrincipal.h"
#include "nsISupportsImpl.h"
#include "nsLiteralString.h"
#include "nsNameSpaceManager.h"
#include "nsRange.h"
#include "nsReadableUtils.h"
#include "nsString.h"
#include "nsStringFwd.h"
#include "nsStyledElement.h"
#include "nsTArray.h"
#include "nsTextNode.h"
#include "nsUnicharUtils.h"
#include "nscore.h"
namespace mozilla {
@ -66,6 +66,15 @@ template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
template nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet);
template nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
AutoRangeArray& aRanges,
const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet,
const Element& aEditingHost);
template nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
AutoRangeArray& aRanges,
const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet,
const Element& aEditingHost);
nsresult HTMLEditor::SetInlinePropertyAsAction(nsStaticAtom& aProperty,
nsStaticAtom* aAttribute,
const nsAString& aValue,
@ -229,6 +238,12 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
}
}
RefPtr<Element> const editingHost =
ComputeEditingHost(LimitInBodyElement::No);
if (NS_WARN_IF(!editingHost)) {
return NS_ERROR_FAILURE;
}
AutoPlaceholderBatch treatAsOneTransaction(
*this, ScrollSelectionIntoView::Yes, __FUNCTION__);
IgnoredErrorResult ignoredError;
@ -247,23 +262,44 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
AutoTransactionsConserveSelection dontChangeMySelection(*this);
AutoRangeArray selectionRanges(SelectionRef());
nsresult rv = SetInlinePropertiesAroundRanges(selectionRanges, aStylesToSet,
*editingHost);
if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
return rv;
}
MOZ_ASSERT(!selectionRanges.HasSavedRanges());
rv = selectionRanges.ApplyTo(SelectionRef());
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::ApplyTo() failed");
return rv;
}
template <size_t N>
nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
AutoRangeArray& aRanges,
const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet,
const Element& aEditingHost) {
for (const EditorInlineStyleAndValue& styleToSet : aStylesToSet) {
if (!StaticPrefs::
editor_inline_style_range_compatible_with_the_other_browsers()) {
MOZ_ALWAYS_TRUE(selectionRanges.SaveAndTrackRanges(*this));
editor_inline_style_range_compatible_with_the_other_browsers() &&
!aRanges.IsCollapsed()) {
MOZ_ALWAYS_TRUE(aRanges.SaveAndTrackRanges(*this));
}
AutoInlineStyleSetter inlineStyleSetter(styleToSet);
for (OwningNonNull<nsRange>& selectionRange : selectionRanges.Ranges()) {
for (OwningNonNull<nsRange>& domRange : aRanges.Ranges()) {
inlineStyleSetter.Reset();
const EditorDOMRange range = [&]() {
if (selectionRanges.HasSavedRanges()) {
if (aRanges.HasSavedRanges()) {
return EditorDOMRange(
GetExtendedRangeWrappingEntirelySelectedElements(
EditorRawDOMRange(selectionRange)));
EditorRawDOMRange(domRange)));
}
Result<EditorRawDOMRange, nsresult> rangeOrError =
inlineStyleSetter.ExtendOrShrinkRangeToApplyTheStyle(
*this, EditorDOMRange(selectionRange));
*this, EditorDOMRange(domRange), aEditingHost);
if (MOZ_UNLIKELY(rangeOrError.isErr())) {
NS_WARNING(
"HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but "
@ -276,11 +312,42 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
continue;
}
// If the range is collapsed, we should insert new element there.
if (range.Collapsed()) {
Result<RefPtr<Text>, nsresult> emptyTextNodeOrError =
AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
*this, range.StartRef(), aEditingHost);
if (MOZ_UNLIKELY(emptyTextNodeOrError.isErr())) {
NS_WARNING(
"AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle() "
"failed");
return emptyTextNodeOrError.unwrapErr();
}
if (MOZ_UNLIKELY(!emptyTextNodeOrError.inspect())) {
continue; // Couldn't insert text node there
}
RefPtr<Text> emptyTextNode = emptyTextNodeOrError.unwrap();
Result<CaretPoint, nsresult> caretPointOrError =
inlineStyleSetter
.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
*this, *emptyTextNode);
if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
NS_WARNING(
"AutoInlineStyleSetter::"
"ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
return caretPointOrError.unwrapErr();
}
DebugOnly<nsresult> rvIgnored = domRange->CollapseTo(emptyTextNode, 0);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"nsRange::CollapseTo() failed, but ignored");
continue;
}
// Use const_cast hack here for preventing the others to update the range.
AutoTrackDOMRange trackRange(RangeUpdaterRef(),
const_cast<EditorDOMRange*>(&range));
auto UpdateSelectionRange = [&]() MOZ_CAN_RUN_SCRIPT {
if (selectionRanges.HasSavedRanges()) {
if (aRanges.HasSavedRanges()) {
return;
}
// If inlineStyleSetter creates elements or setting styles, we should
@ -303,7 +370,7 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
EditorRawDOMPoint>(
*inlineStyleSetter.LastHandledPointRef()
.ContainerAs<nsIContent>());
nsresult rv = selectionRange->SetStartAndEnd(
nsresult rv = domRange->SetStartAndEnd(
startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
if (NS_SUCCEEDED(rv)) {
trackRange.StopTracking();
@ -312,8 +379,8 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
}
// Otherwise, use the range computed with the tracking original range.
trackRange.FlushAndStopTracking();
selectionRange->SetStartAndEnd(range.StartRef().ToRawRangeBoundary(),
range.EndRef().ToRawRangeBoundary());
domRange->SetStartAndEnd(range.StartRef().ToRawRangeBoundary(),
range.EndRef().ToRawRangeBoundary());
};
// If range is in a text node, apply new style simply.
@ -329,8 +396,8 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
return wrapTextInStyledElementResult.unwrapErr();
}
// There is AutoTransactionsConserveSelection, so we don't need to
// update selection here.
// The caller should handle the ranges as Selection if necessary, and we
// don't want to update aRanges with this result.
wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
UpdateSelectionRange();
continue;
@ -386,8 +453,8 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
return wrapTextInStyledElementResult.unwrapErr();
}
// There is AutoTransactionsConserveSelection, so we don't need to
// update selection here.
// The caller should handle the ranges as Selection if necessary, and we
// don't want to update aRanges with this result.
wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
}
@ -404,8 +471,8 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
"ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
return pointToPutCaretOrError.unwrapErr();
}
// There is AutoTransactionsConserveSelection, so we don't need to
// update selection here.
// The caller should handle the ranges as Selection if necessary, and we
// don't want to update aRanges with this result.
pointToPutCaretOrError.inspect().IgnoreCaretPointSuggestion();
}
@ -424,24 +491,80 @@ nsresult HTMLEditor::SetInlinePropertiesAsSubAction(
NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
return wrapTextInStyledElementResult.unwrapErr();
}
// There is AutoTransactionsConserveSelection, so we don't need to
// update selection here.
// The caller should handle the ranges as Selection if necessary, and we
// don't want to update aRanges with this result.
wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
}
UpdateSelectionRange();
}
if (selectionRanges.HasSavedRanges()) {
selectionRanges.RestoreFromSavedRanges();
if (aRanges.HasSavedRanges()) {
aRanges.RestoreFromSavedRanges();
}
}
return NS_OK;
}
MOZ_ASSERT(!selectionRanges.HasSavedRanges());
nsresult rv = selectionRanges.ApplyTo(SelectionRef());
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
// static
Result<RefPtr<Text>, nsresult>
HTMLEditor::AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert,
const Element& aEditingHost) {
auto pointToInsertNewText =
HTMLEditUtils::GetBetterCaretPositionToInsertText<EditorDOMPoint>(
aCandidatePointToInsert, aEditingHost);
if (MOZ_UNLIKELY(!pointToInsertNewText.IsSet())) {
return RefPtr<Text>(); // cannot insert text there
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "AutoRangeArray::ApplyTo() failed");
return rv;
auto pointToInsertNewStyleOrError =
[&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> {
if (!pointToInsertNewText.IsInTextNode()) {
return pointToInsertNewText;
}
if (!pointToInsertNewText.ContainerAs<Text>()->TextDataLength()) {
return pointToInsertNewText; // Use it
}
if (pointToInsertNewText.IsStartOfContainer()) {
return pointToInsertNewText.ParentPoint();
}
if (pointToInsertNewText.IsEndOfContainer()) {
return EditorDOMPoint::After(*pointToInsertNewText.ContainerAs<Text>());
}
Result<SplitNodeResult, nsresult> splitTextNodeResult =
aHTMLEditor.SplitNodeWithTransaction(pointToInsertNewText);
if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) {
NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
return splitTextNodeResult.propagateErr();
}
SplitNodeResult unwrappedSplitTextNodeResult = splitTextNodeResult.unwrap();
unwrappedSplitTextNodeResult.IgnoreCaretPointSuggestion();
return unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>();
}();
if (MOZ_UNLIKELY(pointToInsertNewStyleOrError.isErr())) {
return pointToInsertNewStyleOrError.propagateErr();
}
// If we already have empty text node which is available for placeholder in
// new styled element, let's use it.
if (pointToInsertNewStyleOrError.inspect().IsInTextNode()) {
return RefPtr<Text>(
pointToInsertNewStyleOrError.inspect().ContainerAs<Text>());
}
// Otherwise, we need an empty text node to create new inline style.
RefPtr<Text> newEmptyTextNode = aHTMLEditor.CreateTextNode(u""_ns);
if (MOZ_UNLIKELY(!newEmptyTextNode)) {
NS_WARNING("EditorBase::CreateTextNode() failed");
return Err(NS_ERROR_FAILURE);
}
Result<CreateTextResult, nsresult> insertNewTextNodeResult =
aHTMLEditor.InsertNodeWithTransaction<Text>(
*newEmptyTextNode, pointToInsertNewStyleOrError.inspect());
if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) {
NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
return insertNewTextNodeResult.propagateErr();
}
insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion();
return newEmptyTextNode;
}
Result<bool, nsresult>
@ -1646,7 +1769,8 @@ EditorRawDOMRange HTMLEditor::AutoInlineStyleSetter::
Result<EditorRawDOMRange, nsresult>
HTMLEditor::AutoInlineStyleSetter::ExtendOrShrinkRangeToApplyTheStyle(
const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) const {
const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange,
const Element& aEditingHost) const {
if (NS_WARN_IF(!aRange.IsPositioned())) {
return Err(NS_ERROR_FAILURE);
}
@ -1654,43 +1778,70 @@ HTMLEditor::AutoInlineStyleSetter::ExtendOrShrinkRangeToApplyTheStyle(
// For avoiding assertion hits in the utility methods, check whether the
// range is in same subtree, first. Even if the range crosses a subtree
// boundary, it's not a bug of this module.
nsINode* const commonAncestor = aRange.GetClosestCommonInclusiveAncestor();
nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor();
if (NS_WARN_IF(!commonAncestor)) {
return Err(NS_ERROR_FAILURE);
}
// If the range does not select only invisible <br> element, let's extend the
// range to contain the <br> element.
EditorDOMRange range(aRange);
if (range.EndRef().IsInContentNode()) {
WSScanResult nextContentData =
WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(&aEditingHost,
range.EndRef());
if (nextContentData.ReachedInvisibleBRElement() &&
nextContentData.BRElementPtr()->GetParentElement() &&
HTMLEditUtils::IsInlineElement(
*nextContentData.BRElementPtr()->GetParentElement())) {
range.SetEnd(EditorDOMPoint::After(*nextContentData.BRElementPtr()));
MOZ_ASSERT(range.EndRef().IsSet());
}
}
// If the range is collapsed, we don't want to replace ancestors unless it's
// in an empty element.
if (range.Collapsed() && range.StartRef().GetContainer()->Length()) {
return EditorRawDOMRange(range);
}
// First, shrink the given range to minimize new style applied contents.
// However, we should not shrink the range into entirely selected element.
// E.g., if `abc[<i>def</i>]ghi`, shouldn't shrink it as
// `abc<i>[def]</i>ghi`.
ContentSubtreeIterator iter;
if (NS_FAILED(iter.Init(aRange.StartRef().ToRawRangeBoundary(),
aRange.EndRef().ToRawRangeBoundary()))) {
NS_WARNING("ContentSubtreeIterator::Init() failed");
return Err(NS_ERROR_FAILURE);
}
nsIContent* const firstContentEntirelyInRange =
nsIContent::FromNodeOrNull(iter.GetCurrentNode());
nsIContent* const lastContentEntirelyInRange = [&]() {
iter.Last();
return nsIContent::FromNodeOrNull(iter.GetCurrentNode());
}();
EditorRawDOMPoint startPoint, endPoint;
if (range.Collapsed()) {
startPoint = endPoint = range.StartRef().To<EditorRawDOMPoint>();
} else {
ContentSubtreeIterator iter;
if (NS_FAILED(iter.Init(range.StartRef().ToRawRangeBoundary(),
range.EndRef().ToRawRangeBoundary()))) {
NS_WARNING("ContentSubtreeIterator::Init() failed");
return Err(NS_ERROR_FAILURE);
}
nsIContent* const firstContentEntirelyInRange =
nsIContent::FromNodeOrNull(iter.GetCurrentNode());
nsIContent* const lastContentEntirelyInRange = [&]() {
iter.Last();
return nsIContent::FromNodeOrNull(iter.GetCurrentNode());
}();
// Compute the shrunken range boundaries.
EditorRawDOMPoint startPoint = GetShrunkenRangeStart(
aHTMLEditor, aRange, *commonAncestor, firstContentEntirelyInRange);
MOZ_ASSERT(startPoint.IsSet());
EditorRawDOMPoint endPoint = GetShrunkenRangeEnd(
aHTMLEditor, aRange, *commonAncestor, lastContentEntirelyInRange);
MOZ_ASSERT(endPoint.IsSet());
// Compute the shrunken range boundaries.
startPoint = GetShrunkenRangeStart(aHTMLEditor, range, *commonAncestor,
firstContentEntirelyInRange);
MOZ_ASSERT(startPoint.IsSet());
endPoint = GetShrunkenRangeEnd(aHTMLEditor, range, *commonAncestor,
lastContentEntirelyInRange);
MOZ_ASSERT(endPoint.IsSet());
// If shrunken range is swapped, it could like this case:
// `abc[</span><span>]def`, starts at very end of a node and ends at
// very start of immediately next node. In this case, we should use
// the original range instead.
if (MOZ_UNLIKELY(!startPoint.EqualsOrIsBefore(endPoint))) {
startPoint = aRange.StartRef().To<EditorRawDOMPoint>();
endPoint = aRange.EndRef().To<EditorRawDOMPoint>();
// If shrunken range is swapped, it could like this case:
// `abc[</span><span>]def`, starts at very end of a node and ends at
// very start of immediately next node. In this case, we should use
// the original range instead.
if (MOZ_UNLIKELY(!startPoint.EqualsOrIsBefore(endPoint))) {
startPoint = range.StartRef().To<EditorRawDOMPoint>();
endPoint = range.EndRef().To<EditorRawDOMPoint>();
}
}
// Then, we may need to extend the range to wrap parent inline elements

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

@ -408,6 +408,21 @@ void PendingStyles::ClearStyleInternal(
aHTMLProperty, aAttribute, u""_ns, aSpecifiedStyle));
}
void PendingStyles::TakeAllPreservedStyles(
nsTArray<EditorInlineStyleAndValue>& aOutStylesAndValues) {
aOutStylesAndValues.SetCapacity(aOutStylesAndValues.Length() +
mPreservingStyles.Length());
for (UniquePtr<PendingStyle> preservedStyle = TakePreservedStyle();
preservedStyle; preservedStyle = TakePreservedStyle()) {
aOutStylesAndValues.AppendElement(
preservedStyle->GetAttribute()
? EditorInlineStyleAndValue(
*preservedStyle->GetTag(), *preservedStyle->GetAttribute(),
preservedStyle->AttributeValueOrCSSValueRef())
: EditorInlineStyleAndValue(*preservedStyle->GetTag()));
}
}
/**
* TakeRelativeFontSize() hands back relative font value, which is then
* cleared out.

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

@ -262,6 +262,13 @@ class PendingStyles final {
return mPreservingStyles.PopLastElement();
}
/**
* TakeAllPreservedStyles() moves all preserved styles and values to
* aOutStylesAndValues.
*/
void TakeAllPreservedStyles(
nsTArray<EditorInlineStyleAndValue>& aOutStylesAndValues);
/**
* TakeRelativeFontSize() hands back relative font value, which is then
* cleared out.

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

@ -2853,4 +2853,17 @@ var browserTests = [
[true,true,true],
{}],
// <font> element should be reused when the font-size is change for new text.
["<font size=7>{}<br></font>",
[["stylewithcss","false"],["fontsize","4"],["insertText","a"]],
["<font size=\"4\">a[]<br></font>",
"<font size=\"4\">a[]</font>"],
[true,true,true],
{"fontsize":[false,false,"7",false,false,"4"]}],
["<span style=font-weight:bold>{}<br></span></b>",
[["stylewithcss","true"],["italic",""],["insertText","a"]],
["<span style=\"font-weight:bold; font-style:italic\">a[]<br></span>",
"<span style=\"font-weight:bold; font-style:italic\">a[]</span>"],
[true,true,true],
{}],
]