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