/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; using LeafNodeType = HTMLEditUtils::LeafNodeType; using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
const RefPtr<Element> editingHost =
ComputeEditingHost(LimitInBodyElement::No); if (NS_WARN_IF(!editingHost)) { return NS_ERROR_FAILURE;
} if (IsPlaintextMailComposer() ||
editingHost->IsContentEditablePlainTextOnly()) { return NS_SUCCESS_DOM_NO_OPERATION;
}
switch (editActionData.GetEditAction()) { case EditAction::eSetFontFamilyProperty:
MOZ_ASSERT(!aValue.IsVoid()); // XXX Should we trim unnecessary white-spaces?
editActionData.SetData(aValue); break; case EditAction::eSetColorProperty: case EditAction::eSetBackgroundColorPropertyInline:
editActionData.SetColorData(aValue); break; default: break;
}
// XXX Due to bug 1659276 and bug 1659924, we should not scroll selection // into view after setting the new style.
AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::No,
__FUNCTION__);
nsStaticAtom* property = &aProperty;
nsStaticAtom* attribute = aAttribute;
nsString value(aValue); if (attribute == nsGkAtoms::color || attribute == nsGkAtoms::bgcolor) { if (!IsCSSEnabled()) { // We allow CSS style color value even in the HTML mode. In the cases, // we will apply the style with CSS. For considering it in the value // as-is if it's a known CSS keyboard, `rgb()` or `rgba()` style. // NOTE: It may be later that we set the color into the DOM tree and at // that time, IsCSSEnabled() may return true. E.g., setting color value // to collapsed selection, then, change the CSS enabled, finally, user // types text there. if (!HTMLEditUtils::MaybeCSSSpecificColorValue(value)) {
HTMLEditUtils::GetNormalizedHTMLColorValue(value, value);
}
} else {
HTMLEditUtils::GetNormalizedCSSColorValue(
value, HTMLEditUtils::ZeroAlphaColor::RGBAValue, value);
}
}
AutoTArray<EditorInlineStyle, 1> stylesToRemove; if (&aProperty == nsGkAtoms::sup) { // Superscript and Subscript styles are mutually exclusive.
stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sub));
} elseif (&aProperty == nsGkAtoms::sub) { // Superscript and Subscript styles are mutually exclusive.
stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sup));
} // Handling `<tt>` element code was implemented for composer (bug 115922). // This shouldn't work with `Document.execCommand()`. Currently, aPrincipal // is set only when the root caller is Document::ExecCommand() so that // we should handle `<tt>` element only when aPrincipal is nullptr that // must be only when XUL command is executed on composer. elseif (!aPrincipal) { if (&aProperty == nsGkAtoms::tt) {
stylesToRemove.AppendElement(
EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face));
} elseif (&aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::face) { if (!value.LowerCaseEqualsASCII("tt")) {
stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::tt));
} else {
stylesToRemove.AppendElement(
EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face)); // Override property, attribute and value if the new font face value is // "tt".
property = nsGkAtoms::tt;
attribute = nullptr;
value.Truncate();
}
}
}
if (!stylesToRemove.IsEmpty()) {
nsresult rv =
RemoveInlinePropertiesAsSubAction(stylesToRemove, *editingHost); if (NS_FAILED(rv)) {
NS_WARNING("HTMLEditor::RemoveInlinePropertiesAsSubAction() failed"); return rv;
}
}
DebugOnly<nsresult> rvIgnored = CommitComposition();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "EditorBase::CommitComposition() failed, but ignored"); if (MOZ_UNLIKELY(&aEditingHost !=
ComputeEditingHost(LimitInBodyElement::No))) {
NS_WARNING("Editing host has been changed during committing composition"); return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
}
if (SelectionRef().IsCollapsed()) { // Manipulating text attributes on a collapsed selection only sets state // for the next text insertion
mPendingStylesToApplyToNewContent->PreserveStyles(aStylesToSet); return NS_OK;
}
{
Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); return result.unwrapErr();
} if (result.inspect().Canceled()) { return NS_OK;
}
}
// TODO: We don't need AutoTransactionsConserveSelection here in the normal // cases, but removing this may cause the behavior with the legacy // mutation event listeners. We should try to delete this in a bug.
AutoTransactionsConserveSelection dontChangeMySelection(*this);
template <size_t N>
nsresult HTMLEditor::SetInlinePropertiesAroundRanges(
AutoClonedRangeArray& aRanges, const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet) {
MOZ_ASSERT(!aRanges.HasSavedRanges()); for (const EditorInlineStyleAndValue& styleToSet : aStylesToSet) {
AutoInlineStyleSetter inlineStyleSetter(styleToSet); for (OwningNonNull<nsRange>& domRange : aRanges.Ranges()) {
inlineStyleSetter.Reset(); auto rangeOrError =
[&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMRange, nsresult> {
EditorDOMRange range(domRange); // If we're setting <font>, we want to remove ancestors which set // `font-size` or <font size="..."> recursively. Therefore, for // extending the ranges to contain all ancestors in the range, we need // to split ancestors first. // XXX: Blink and WebKit inserts <font> elements to inner most // elements, however, we cannot do it under current design because // once we contain ancestors which have `font-size` or are // <font size="...">, we lost the original ranges which we wanted to // apply the style. For fixing this, we need to manage both ranges, but // it's too expensive especially we allow to run script when we touch // the DOM tree. Additionally, font-size value affects the height // of the element, but does not affect the height of ancestor inline // elements. Therefore, following the behavior may cause similar issue // as bug 1808906. So at least for now, we should not do this big work. if (styleToSet.IsStyleOfFontElement()) {
Result<SplitRangeOffResult, nsresult> splitAncestorsResult =
SplitAncestorStyledInlineElementsAtRangeEdges(
range, styleToSet, SplitAtEdges::eDoNotCreateEmptyContainer); if (MOZ_UNLIKELY(splitAncestorsResult.isErr())) {
NS_WARNING( "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() " "failed"); return splitAncestorsResult.propagateErr();
}
SplitRangeOffResult unwrappedResult = splitAncestorsResult.unwrap();
unwrappedResult.IgnoreCaretPointSuggestion();
range = unwrappedResult.RangeRef(); if (NS_WARN_IF(!range.IsPositionedAndValid())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
}
Result<EditorRawDOMRange, nsresult> rangeOrError =
inlineStyleSetter.ExtendOrShrinkRangeToApplyTheStyle(*this, range); if (MOZ_UNLIKELY(rangeOrError.isErr())) {
NS_WARNING( "HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but " "ignored"); return EditorDOMRange();
} return EditorDOMRange(rangeOrError.unwrap());
}(); if (MOZ_UNLIKELY(rangeOrError.isErr())) { return rangeOrError.unwrapErr();
}
const EditorDOMRange range = rangeOrError.unwrap(); if (!range.IsPositioned()) { 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()); 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 inlineStyleSetter creates elements or setting styles, we should // select between start of first element and end of last element. if (inlineStyleSetter.FirstHandledPointRef().IsInContentNode()) {
MOZ_ASSERT(inlineStyleSetter.LastHandledPointRef().IsInContentNode()); constauto startPoint =
!inlineStyleSetter.FirstHandledPointRef().IsStartOfContainer()
? inlineStyleSetter.FirstHandledPointRef()
.To<EditorRawDOMPoint>()
: HTMLEditUtils::GetDeepestEditableStartPointOf<
EditorRawDOMPoint>(
*inlineStyleSetter.FirstHandledPointRef()
.ContainerAs<nsIContent>()); constauto endPoint =
!inlineStyleSetter.LastHandledPointRef().IsEndOfContainer()
? inlineStyleSetter.LastHandledPointRef()
.To<EditorRawDOMPoint>()
: HTMLEditUtils::GetDeepestEditableEndPointOf<
EditorRawDOMPoint>(
*inlineStyleSetter.LastHandledPointRef()
.ContainerAs<nsIContent>());
nsresult rv = domRange->SetStartAndEnd(
startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary()); if (NS_SUCCEEDED(rv)) {
trackRange.StopTracking(); return;
}
} // Otherwise, use the range computed with the tracking original range.
trackRange.FlushAndStopTracking();
domRange->SetStartAndEnd(range.StartRef().ToRawRangeBoundary(),
range.EndRef().ToRawRangeBoundary());
};
// If range is in a text node, apply new style simply. if (range.InSameContainer() && range.StartRef().IsInTextNode()) { // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`. // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
Result<SplitRangeOffFromNodeResult, nsresult>
wrapTextInStyledElementResult =
inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
*this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
range.StartRef().Offset(), range.EndRef().Offset()); if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed"); return wrapTextInStyledElementResult.unwrapErr();
} // 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;
}
// Collect editable nodes which are entirely contained in the range.
AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsAroundRange;
{
ContentSubtreeIterator subtreeIter; // If there is no node which is entirely in the range, // `ContentSubtreeIterator::Init()` fails, but this is possible case, // don't warn it. if (NS_SUCCEEDED(
subtreeIter.Init(range.StartRef().ToRawRangeBoundary(),
range.EndRef().ToRawRangeBoundary()))) { for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
nsINode* node = subtreeIter.GetCurrentNode(); if (NS_WARN_IF(!node)) { return NS_ERROR_FAILURE;
} if (MOZ_UNLIKELY(!node->IsContent())) { continue;
} // We don't need to wrap non-editable node in new inline element // nor shouldn't modify `style` attribute of non-editable element. if (!EditorUtils::IsEditableContent(*node->AsContent(),
EditorType::HTML)) { continue;
} // We shouldn't wrap invisible text node in new inline element. if (node->IsText() &&
!HTMLEditUtils::IsVisibleTextNode(*node->AsText())) { continue;
}
arrayOfContentsAroundRange.AppendElement(*node->AsContent());
}
}
}
// If start node is a text node, apply new style to a part of it. if (range.StartRef().IsInTextNode() &&
EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(),
EditorType::HTML)) { // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`. // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
Result<SplitRangeOffFromNodeResult, nsresult>
wrapTextInStyledElementResult =
inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
*this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()),
range.StartRef().Offset(),
range.StartRef().ContainerAs<Text>()->TextDataLength()); if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed"); return wrapTextInStyledElementResult.unwrapErr();
} // The caller should handle the ranges as Selection if necessary, and we // don't want to update aRanges with this result.
wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion();
}
// Then, apply new style to all nodes in the range entirely. for (auto& content : arrayOfContentsAroundRange) { // MOZ_KnownLive due to bug 1622253.
Result<CaretPoint, nsresult> pointToPutCaretOrError =
inlineStyleSetter
.ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
*this, MOZ_KnownLive(*content)); if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
NS_WARNING( "AutoInlineStyleSetter::" "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); return pointToPutCaretOrError.unwrapErr();
} // The caller should handle the ranges as Selection if necessary, and we // don't want to update aRanges with this result.
pointToPutCaretOrError.inspect().IgnoreCaretPointSuggestion();
}
// Finally, if end node is a text node, apply new style to a part of it. if (range.EndRef().IsInTextNode() &&
EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(),
EditorType::HTML)) { // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`. // MOZ_KnownLive(styleToSet.mAttribute) due to bug 1622253.
Result<SplitRangeOffFromNodeResult, nsresult>
wrapTextInStyledElementResult =
inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode(
*this, MOZ_KnownLive(*range.EndRef().ContainerAs<Text>()),
0, range.EndRef().Offset()); if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) {
NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed"); return wrapTextInStyledElementResult.unwrapErr();
} // 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();
}
} return NS_OK;
}
// static
Result<RefPtr<Text>, nsresult>
HTMLEditor::AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert) { auto pointToInsertNewText =
HTMLEditUtils::GetBetterCaretPositionToInsertText<EditorDOMPoint>(
aCandidatePointToInsert); if (MOZ_UNLIKELY(!pointToInsertNewText.IsSet())) { return RefPtr<Text>(); // cannot insert text there
} 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>
HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerForTheStyle(
HTMLEditor& aHTMLEditor, Element& aElement) const { // If the editor is in the CSS mode and the style can be specified with CSS, // we should not use existing HTML element as a new container. constbool isCSSEditable = IsCSSSettable(aElement); if (!aHTMLEditor.IsCSSEnabled() || !isCSSEditable) { // First check for <b>, <i>, etc. if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
!HTMLEditUtils::ElementHasAttribute(aElement) && !mAttribute) { returntrue;
}
// Now look for things like <font> if (mAttribute) {
nsString attrValue; if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
!HTMLEditUtils::ElementHasAttributeExcept(aElement, *mAttribute) &&
aElement.GetAttr(mAttribute, attrValue)) { if (attrValue.Equals(mAttributeValue,
nsCaseInsensitiveStringComparator)) { returntrue;
} if (mAttribute == nsGkAtoms::color ||
mAttribute == nsGkAtoms::bgcolor) { if (aHTMLEditor.IsCSSEnabled()) { if (HTMLEditUtils::IsSameCSSColorValue(mAttributeValue,
attrValue)) { returntrue;
}
} elseif (HTMLEditUtils::IsSameHTMLColorValue(
mAttributeValue, attrValue,
HTMLEditUtils::TransparentKeyword::Allowed)) { returntrue;
}
}
}
}
if (!isCSSEditable) { returnfalse;
}
}
// No luck so far. Now we check for a <span> with a single style="" // attribute that sets only the style we're looking for, if this type of // style supports it if (!aElement.IsHTMLElement(nsGkAtoms::span) ||
!aElement.HasAttr(nsGkAtoms::style) ||
HTMLEditUtils::ElementHasAttributeExcept(aElement, *nsGkAtoms::style)) { returnfalse;
}
nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement); if (MOZ_UNLIKELY(!styledElement)) { returnfalse;
}
// Some CSS styles are not so simple. For instance, underline is // "text-decoration: underline", which decomposes into four different text-* // properties. So for now, we just create a span, add the desired style, and // see if it matches.
RefPtr<Element> newSpanElement =
aHTMLEditor.CreateHTMLContent(nsGkAtoms::span); if (MOZ_UNLIKELY(!newSpanElement)) {
NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed"); returnfalse;
}
nsStyledElement* styledNewSpanElement =
nsStyledElement::FromNode(newSpanElement); if (MOZ_UNLIKELY(!styledNewSpanElement)) { returnfalse;
} // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is // RefPtr.
Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
WithTransaction::No, aHTMLEditor, MOZ_KnownLive(*styledNewSpanElement),
*this, &mAttributeValue); if (MOZ_UNLIKELY(result.isErr())) { // The call shouldn't return destroyed error because it must be // impossible to run script with modifying the new orphan node.
MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?"); if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
} returnfalse;
} return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
*styledElement);
}
// If it has `style` attribute, let's use it. if (aStyledElement.HasAttr(nsGkAtoms::style)) { returntrue;
}
// If it has `class` or `id` attribute, the element may have specific rule. // For applying the new style, we may need to set `style` attribute to it // to override the specified rule. if (aStyledElement.HasAttr(nsGkAtoms::id) ||
aStyledElement.HasAttr(nsGkAtoms::_class)) { returntrue;
}
// If we're setting text-decoration and the element represents a value of // text-decoration, <ins> or <del>, let's use it. if (IsStyleOfTextDecoration(IgnoreSElement::No) &&
aStyledElement.IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::s,
nsGkAtoms::strike, nsGkAtoms::ins,
nsGkAtoms::del)) { returntrue;
}
// If we're setting font-size, color or background-color, we should use <font> // for compatibility with the other browsers. if (&HTMLPropertyRef() == nsGkAtoms::font &&
aStyledElement.IsHTMLElement(nsGkAtoms::font)) { returntrue;
}
// If the element has one or more <br> (even if it's invisible), we don't // want to use the <span> for compatibility with the other browsers. if (aStyledElement.QuerySelector("br"_ns, IgnoreErrors())) { returnfalse;
}
// NOTE: The following code does not match with the other browsers not // completely. Blink considers this with relation with the range. // However, we cannot do it now. We should fix here after or at // fixing bug 1792386.
// If it's only visible element child of parent block, let's use it. // E.g., we don't want to create new <span> when // `<p>{ <span>abc</span> }</p>`. if (aStyledElement.GetParentElement() &&
HTMLEditUtils::IsBlockElement(
*aStyledElement.GetParentElement(),
BlockInlineCheck::UseComputedDisplayStyle)) { for (nsIContent* previousSibling = aStyledElement.GetPreviousSibling();
previousSibling;
previousSibling = previousSibling->GetPreviousSibling()) { if (previousSibling->IsElement()) { returnfalse; // Assume any elements visible.
} if (Text* text = Text::FromNode(previousSibling)) { if (HTMLEditUtils::IsVisibleTextNode(*text)) { returnfalse;
} continue;
}
} for (nsIContent* nextSibling = aStyledElement.GetNextSibling(); nextSibling;
nextSibling = nextSibling->GetNextSibling()) { if (nextSibling->IsElement()) { if (!HTMLEditUtils::IsInvisibleBRElement(*nextSibling)) { returnfalse;
} continue; // The invisible <br> element may be followed by a child // block, let's continue to check it.
} if (Text* text = Text::FromNode(nextSibling)) { if (HTMLEditUtils::IsVisibleTextNode(*text)) { returnfalse;
} continue;
}
} returntrue;
}
// Otherwise, wrap it into new <span> for making // `<span>[abc</span> <span>def]</span>` become // `<span style="..."><span>abc</span> <span>def</span></span>` rather // than `<span style="...">abc <span>def</span></span>`. returnfalse;
}
// Don't need to do anything if no characters actually selected if (aStartOffset == aEndOffset) {
OnHandled(EditorDOMPoint(&aText, aStartOffset),
EditorDOMPoint(&aText, aEndOffset)); return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
}
// Don't need to do anything if property already set on node if (IsCSSSettable(*element)) { // The HTML styles defined by this have a CSS equivalence for node; // let's check if it carries those CSS styles
nsAutoString value(mAttributeValue);
Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this,
value); if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); return isComputedCSSEquivalentToStyleOrError.propagateErr();
} if (isComputedCSSEquivalentToStyleOrError.unwrap()) {
OnHandled(EditorDOMPoint(&aText, aStartOffset),
EditorDOMPoint(&aText, aEndOffset)); return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
}
} elseif (HTMLEditUtils::IsInlineStyleSetByElement(aText, *this,
&mAttributeValue)) {
OnHandled(EditorDOMPoint(&aText, aStartOffset),
EditorDOMPoint(&aText, aEndOffset)); return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr);
}
// Make the range an independent node. auto splitAtEndResult =
[&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
EditorDOMPoint atEnd(&aText, aEndOffset); if (atEnd.IsEndOfContainer()) { return SplitNodeResult::NotHandled(atEnd);
} // We need to split off back of text node
Result<SplitNodeResult, nsresult> splitNodeResult =
aHTMLEditor.SplitNodeWithTransaction(atEnd); if (splitNodeResult.isErr()) {
NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); return splitNodeResult;
} if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) {
NS_WARNING( "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret " "point"); return Err(NS_ERROR_FAILURE);
} return splitNodeResult;
}(); if (MOZ_UNLIKELY(splitAtEndResult.isErr())) { return splitAtEndResult.propagateErr();
}
SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap();
EditorDOMPoint pointToPutCaret = unwrappedSplitAtEndResult.UnwrapCaretPoint(); auto splitAtStartResult =
[&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
EditorDOMPoint atStart(unwrappedSplitAtEndResult.DidSplit()
? unwrappedSplitAtEndResult.GetPreviousContent()
: &aText,
aStartOffset); if (atStart.IsStartOfContainer()) { return SplitNodeResult::NotHandled(atStart);
} // We need to split off front of text node
Result<SplitNodeResult, nsresult> splitNodeResult =
aHTMLEditor.SplitNodeWithTransaction(atStart); if (MOZ_UNLIKELY(splitNodeResult.isErr())) {
NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); return splitNodeResult;
} if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) {
NS_WARNING( "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret " "point"); return Err(NS_ERROR_FAILURE);
} return splitNodeResult;
}(); if (MOZ_UNLIKELY(splitAtStartResult.isErr())) { return splitAtStartResult.propagateErr();
}
SplitNodeResult unwrappedSplitAtStartResult = splitAtStartResult.unwrap(); if (unwrappedSplitAtStartResult.HasCaretPointSuggestion()) {
pointToPutCaret = unwrappedSplitAtStartResult.UnwrapCaretPoint();
}
MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(),
unwrappedSplitAtStartResult.GetPreviousContent()->IsText());
MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(),
unwrappedSplitAtStartResult.GetNextContent()->IsText());
MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(),
unwrappedSplitAtEndResult.GetPreviousContent()->IsText());
MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(),
unwrappedSplitAtEndResult.GetNextContent()->IsText()); // Note that those text nodes are grabbed by unwrappedSplitAtStartResult, // unwrappedSplitAtEndResult or the callers. Therefore, we don't need to make // them strong pointer.
Text* const leftTextNode =
unwrappedSplitAtStartResult.DidSplit()
? unwrappedSplitAtStartResult.GetPreviousContentAs<Text>()
: nullptr;
Text* const middleTextNode =
unwrappedSplitAtStartResult.DidSplit()
? unwrappedSplitAtStartResult.GetNextContentAs<Text>()
: (unwrappedSplitAtEndResult.DidSplit()
? unwrappedSplitAtEndResult.GetPreviousContentAs<Text>()
: &aText);
Text* const rightTextNode =
unwrappedSplitAtEndResult.DidSplit()
? unwrappedSplitAtEndResult.GetNextContentAs<Text>()
: nullptr; if (mAttribute) { // Look for siblings that are correct type of node
nsIContent* sibling = HTMLEditUtils::GetPreviousSibling(
*middleTextNode, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsElement()) {
OwningNonNull<Element> element(*sibling->AsElement());
Result<bool, nsresult> result =
ElementIsGoodContainerForTheStyle(aHTMLEditor, element); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); return result.propagateErr();
} if (result.inspect()) { // Previous sib is already right kind of inline node; slide this over
Result<MoveNodeResult, nsresult> moveTextNodeResult =
aHTMLEditor.MoveNodeToEndWithTransaction(
MOZ_KnownLive(*middleTextNode), element); if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); return moveTextNodeResult.propagateErr();
}
MoveNodeResult unwrappedMoveTextNodeResult =
moveTextNodeResult.unwrap();
unwrappedMoveTextNodeResult.MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
OnHandled(*middleTextNode); return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode,
rightTextNode,
std::move(pointToPutCaret));
}
}
sibling = HTMLEditUtils::GetNextSibling(
*middleTextNode, {WalkTreeOption::IgnoreNonEditableNode}); if (sibling && sibling->IsElement()) {
OwningNonNull<Element> element(*sibling->AsElement());
Result<bool, nsresult> result =
ElementIsGoodContainerForTheStyle(aHTMLEditor, element); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); return result.propagateErr();
} if (result.inspect()) { // Following sib is already right kind of inline node; slide this over
Result<MoveNodeResult, nsresult> moveTextNodeResult =
aHTMLEditor.MoveNodeWithTransaction(MOZ_KnownLive(*middleTextNode),
EditorDOMPoint(sibling, 0u)); if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) {
NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); return moveTextNodeResult.propagateErr();
}
MoveNodeResult unwrappedMoveTextNodeResult =
moveTextNodeResult.unwrap();
unwrappedMoveTextNodeResult.MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
OnHandled(*middleTextNode); return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode,
rightTextNode,
std::move(pointToPutCaret));
}
}
}
Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::ApplyStyle(
HTMLEditor& aHTMLEditor, nsIContent& aContent) { // If this is an element that can't be contained in a span, we have to // recurse to its children. if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, aContent)) { if (!aContent.HasChildren()) { return CaretPoint(EditorDOMPoint());
}
// Then loop through the list, set the property on each node.
EditorDOMPoint pointToPutCaret; for (const OwningNonNull<nsIContent>& content : arrayOfContents) { // MOZ_KnownLive because 'arrayOfContents' is guaranteed to // keep it alive.
Result<CaretPoint, nsresult> setInlinePropertyResult =
ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
aHTMLEditor, MOZ_KnownLive(content)); if (MOZ_UNLIKELY(setInlinePropertyResult.isErr())) {
NS_WARNING( "AutoInlineStyleSetter::" "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); return setInlinePropertyResult;
}
setInlinePropertyResult.unwrap().MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
} return CaretPoint(std::move(pointToPutCaret));
}
// First check if there's an adjacent sibling we can put our node into.
nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling(
aContent, {WalkTreeOption::IgnoreNonEditableNode});
nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
aContent, {WalkTreeOption::IgnoreNonEditableNode}); if (RefPtr<Element> previousElement =
Element::FromNodeOrNull(previousSibling)) {
Result<bool, nsresult> canMoveIntoPreviousSibling =
ElementIsGoodContainerForTheStyle(aHTMLEditor, *previousElement); if (MOZ_UNLIKELY(canMoveIntoPreviousSibling.isErr())) {
NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); return canMoveIntoPreviousSibling.propagateErr();
} if (canMoveIntoPreviousSibling.inspect()) {
Result<MoveNodeResult, nsresult> moveNodeResult =
aHTMLEditor.MoveNodeToEndWithTransaction(aContent, *previousSibling); if (MOZ_UNLIKELY(moveNodeResult.isErr())) {
NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); return moveNodeResult.propagateErr();
}
MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap();
RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling); if (!nextElement) {
OnHandled(aContent); return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint());
}
Result<bool, nsresult> canMoveIntoNextSibling =
ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement); if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) {
NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
unwrappedMoveNodeResult.IgnoreCaretPointSuggestion(); return canMoveIntoNextSibling.propagateErr();
} if (!canMoveIntoNextSibling.inspect()) {
OnHandled(aContent); return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint());
}
unwrappedMoveNodeResult.IgnoreCaretPointSuggestion();
// JoinNodesWithTransaction (DoJoinNodes) tries to collapse selection to // the joined point and we want to skip updating `Selection` here.
AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor);
Result<JoinNodesResult, nsresult> joinNodesResult =
aHTMLEditor.JoinNodesWithTransaction(*previousElement, *nextElement); if (MOZ_UNLIKELY(joinNodesResult.isErr())) {
NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed"); return joinNodesResult.propagateErr();
} // So, let's take it.
OnHandled(aContent); return CaretPoint(
joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>());
}
}
// Don't need to do anything if property already set on node if (const RefPtr<Element> element = aContent.GetAsElementOrParentElement()) { if (IsCSSSettable(*element)) {
nsAutoString value(mAttributeValue); // MOZ_KnownLive(element) because it's aContent.
Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this,
value); if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); return isComputedCSSEquivalentToStyleOrError.propagateErr();
} if (isComputedCSSEquivalentToStyleOrError.unwrap()) {
OnHandled(aContent); return CaretPoint(EditorDOMPoint());
}
} elseif (HTMLEditUtils::IsInlineStyleSetByElement(*element, *this,
&mAttributeValue)) {
OnHandled(aContent); return CaretPoint(EditorDOMPoint());
}
}
auto ShouldUseCSS = [&]() { if (aHTMLEditor.IsCSSEnabled() && aContent.GetAsElementOrParentElement() &&
IsCSSSettable(*aContent.GetAsElementOrParentElement())) { returntrue;
} // bgcolor is always done using CSS if (mAttribute == nsGkAtoms::bgcolor) { returntrue;
} // called for removing parent style, we should use CSS with <span> element. if (IsStyleToInvert()) { returntrue;
} // If we set color value, the value may be able to specified only with CSS. // In that case, we need to use CSS even in the HTML mode. if (mAttribute == nsGkAtoms::color) { return mAttributeValue.First() != '#' &&
!HTMLEditUtils::CanConvertToHTMLColorValue(mAttributeValue);
} returnfalse;
};
if (ShouldUseCSS()) { // We need special handlings for text-decoration. if (IsStyleOfTextDecoration(IgnoreSElement::No)) {
Result<CaretPoint, nsresult> result =
ApplyCSSTextDecoration(aHTMLEditor, aContent);
NS_WARNING_ASSERTION(
result.isOk(), "AutoInlineStyleSetter::ApplyCSSTextDecoration() failed"); return result;
}
EditorDOMPoint pointToPutCaret;
RefPtr<nsStyledElement> styledElement = [&]() -> nsStyledElement* { auto* const styledElement = nsStyledElement::FromNode(&aContent); return styledElement && ElementIsGoodContainerToSetStyle(*styledElement)
? styledElement
: nullptr;
}();
// If we don't have good element to set the style, let's create new <span>. if (!styledElement) {
Result<CreateElementResult, nsresult> wrapInSpanElementResult =
aHTMLEditor.InsertContainerWithTransaction(aContent,
*nsGkAtoms::span); if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
NS_WARNING( "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) " "failed"); return wrapInSpanElementResult.propagateErr();
}
CreateElementResult unwrappedWrapInSpanElementResult =
wrapInSpanElementResult.unwrap();
MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode());
unwrappedWrapInSpanElementResult.MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
styledElement = nsStyledElement::FromNode(
unwrappedWrapInSpanElementResult.GetNewNode());
MOZ_ASSERT(styledElement); if (MOZ_UNLIKELY(!styledElement)) { // Don't return error to avoid creating new path to throwing error.
OnHandled(aContent); return CaretPoint(pointToPutCaret);
}
}
// Add the CSS styles corresponding to the HTML style request if (IsCSSSettable(*styledElement)) {
Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle(
WithTransaction::Yes, aHTMLEditor, *styledElement, *this,
&mAttributeValue); if (MOZ_UNLIKELY(result.isErr())) { if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING( "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored");
}
}
OnHandled(aContent); return CaretPoint(pointToPutCaret);
}
nsAutoString attributeValue(mAttributeValue); if (mAttribute == nsGkAtoms::color && mAttributeValue.First() != '#') { // At here, all color values should be able to be parsed as a CSS color // value. Therefore, we need to convert it to normalized HTML color value.
HTMLEditUtils::ConvertToNormalizedHTMLColorValue(attributeValue,
attributeValue);
}
// is it already the right kind of node, but with wrong attribute? if (aContent.IsHTMLElement(&HTMLPropertyRef())) { if (NS_WARN_IF(!mAttribute)) { return Err(NS_ERROR_INVALID_ARG);
} // Just set the attribute on it.
nsresult rv = aHTMLEditor.SetAttributeWithTransaction(
MOZ_KnownLive(*aContent.AsElement()), *mAttribute, attributeValue); if (NS_WARN_IF(aHTMLEditor.Destroyed())) { return Err(NS_ERROR_EDITOR_DESTROYED);
} if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::SetAttributeWithTransaction() failed"); return Err(rv);
}
OnHandled(aContent); return CaretPoint(EditorDOMPoint());
}
// ok, chuck it in its very own container
Result<CreateElementResult, nsresult> wrapWithNewElementToFormatResult =
aHTMLEditor.InsertContainerWithTransaction(
aContent, MOZ_KnownLive(HTMLPropertyRef()),
!mAttribute ? HTMLEditor::DoNothingForNewElement // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
: [&](HTMLEditor& aHTMLEditor, Element& aNewElement, const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY {
nsresult rv =
aNewElement.SetAttr(kNameSpaceID_None, mAttribute,
attributeValue, false);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed"); return rv;
}); if (MOZ_UNLIKELY(wrapWithNewElementToFormatResult.isErr())) {
NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed"); return wrapWithNewElementToFormatResult.propagateErr();
}
OnHandled(aContent);
MOZ_ASSERT(wrapWithNewElementToFormatResult.inspect().GetNewNode()); return CaretPoint(
wrapWithNewElementToFormatResult.unwrap().UnwrapCaretPoint());
}
if (aContent.GetParentNode()) { // The node is still where it was
Result<CaretPoint, nsresult> pointToPutCaretOrError =
ApplyStyle(aHTMLEditor, aContent);
NS_WARNING_ASSERTION(pointToPutCaretOrError.isOk(), "AutoInlineStyleSetter::ApplyStyle() failed"); return pointToPutCaretOrError;
}
// It's vanished. Use the old siblings for reference to construct a // list. But first, verify that the previous/next siblings are still // where we expect them; otherwise we have to give up. if (NS_WARN_IF(previousSibling &&
previousSibling->GetParentNode() != parent) ||
NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parent) ||
NS_WARN_IF(!parent->IsInComposedDoc())) { return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
}
AutoTArray<OwningNonNull<nsIContent>, 24> nodesToSet; for (nsIContent* content = previousSibling ? previousSibling->GetNextSibling()
: parent->GetFirstChild();
content && content != nextSibling; content = content->GetNextSibling()) { if (EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
nodesToSet.AppendElement(*content);
}
}
for (OwningNonNull<nsIContent>& content : nodesToSet) { // MOZ_KnownLive because 'nodesToSet' is guaranteed to // keep it alive.
Result<CaretPoint, nsresult> pointToPutCaretOrError =
ApplyStyle(aHTMLEditor, MOZ_KnownLive(content)); if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
NS_WARNING("AutoInlineStyleSetter::ApplyStyle() failed"); return pointToPutCaretOrError;
}
pointToPutCaretOrError.unwrap().MoveCaretPointTo(
pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
}
return CaretPoint(pointToPutCaret);
}
bool HTMLEditor::AutoInlineStyleSetter::ContentIsElementSettingTheStyle( const HTMLEditor& aHTMLEditor, nsIContent& aContent) const {
Element* const element = Element::FromNode(&aContent); if (!element) { returnfalse;
} if (IsRepresentedBy(*element)) { returntrue;
}
Result<bool, nsresult> specified = IsSpecifiedBy(aHTMLEditor, *element);
NS_WARNING_ASSERTION(specified.isOk(), "EditorInlineStyle::IsSpecified() failed, but ignored"); return specified.unwrapOr(false);
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.67Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.