1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "HTMLEditor.h"
8 #include "HTMLEditorInlines.h"
9 #include "HTMLEditorNestedClasses.h"
11 #include "AutoRangeArray.h"
12 #include "CSSEditUtils.h"
13 #include "EditAction.h"
14 #include "EditorUtils.h"
15 #include "HTMLEditHelpers.h"
16 #include "HTMLEditUtils.h"
17 #include "PendingStyles.h"
18 #include "SelectionState.h"
19 #include "WSRunObject.h"
21 #include "mozilla/Assertions.h"
22 #include "mozilla/ContentIterator.h"
23 #include "mozilla/EditorForwards.h"
24 #include "mozilla/mozalloc.h"
25 #include "mozilla/SelectionState.h"
26 #include "mozilla/dom/AncestorIterator.h"
27 #include "mozilla/dom/Element.h"
28 #include "mozilla/dom/HTMLBRElement.h"
29 #include "mozilla/dom/NameSpaceConstants.h"
30 #include "mozilla/dom/Selection.h"
31 #include "mozilla/dom/Text.h"
33 #include "nsAString.h"
35 #include "nsAttrName.h"
36 #include "nsAttrValue.h"
37 #include "nsCaseTreatment.h"
39 #include "nsComponentManagerUtils.h"
42 #include "nsGkAtoms.h"
43 #include "nsIContent.h"
45 #include "nsIPrincipal.h"
46 #include "nsISupportsImpl.h"
47 #include "nsLiteralString.h"
48 #include "nsNameSpaceManager.h"
50 #include "nsReadableUtils.h"
52 #include "nsStringFwd.h"
53 #include "nsStyledElement.h"
55 #include "nsTextNode.h"
56 #include "nsUnicharUtils.h"
62 using EmptyCheckOption
= HTMLEditUtils::EmptyCheckOption
;
63 using LeafNodeType
= HTMLEditUtils::LeafNodeType
;
64 using LeafNodeTypes
= HTMLEditUtils::LeafNodeTypes
;
65 using WalkTreeOption
= HTMLEditUtils::WalkTreeOption
;
67 template nsresult
HTMLEditor::SetInlinePropertiesAsSubAction(
68 const AutoTArray
<EditorInlineStyleAndValue
, 1>& aStylesToSet
);
69 template nsresult
HTMLEditor::SetInlinePropertiesAsSubAction(
70 const AutoTArray
<EditorInlineStyleAndValue
, 32>& aStylesToSet
);
72 template nsresult
HTMLEditor::SetInlinePropertiesAroundRanges(
73 AutoRangeArray
& aRanges
,
74 const AutoTArray
<EditorInlineStyleAndValue
, 1>& aStylesToSet
,
75 const Element
& aEditingHost
);
76 template nsresult
HTMLEditor::SetInlinePropertiesAroundRanges(
77 AutoRangeArray
& aRanges
,
78 const AutoTArray
<EditorInlineStyleAndValue
, 32>& aStylesToSet
,
79 const Element
& aEditingHost
);
81 nsresult
HTMLEditor::SetInlinePropertyAsAction(nsStaticAtom
& aProperty
,
82 nsStaticAtom
* aAttribute
,
83 const nsAString
& aValue
,
84 nsIPrincipal
* aPrincipal
) {
85 AutoEditActionDataSetter
editActionData(
87 HTMLEditUtils::GetEditActionForFormatText(aProperty
, aAttribute
, true),
89 switch (editActionData
.GetEditAction()) {
90 case EditAction::eSetFontFamilyProperty
:
91 MOZ_ASSERT(!aValue
.IsVoid());
92 // XXX Should we trim unnecessary white-spaces?
93 editActionData
.SetData(aValue
);
95 case EditAction::eSetColorProperty
:
96 case EditAction::eSetBackgroundColorPropertyInline
:
97 editActionData
.SetColorData(aValue
);
103 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
105 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
106 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
107 return EditorBase::ToGenericNSResult(rv
);
110 // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection
111 // into view after setting the new style.
112 AutoPlaceholderBatch
treatAsOneTransaction(*this, ScrollSelectionIntoView::No
,
115 nsStaticAtom
* property
= &aProperty
;
116 nsStaticAtom
* attribute
= aAttribute
;
117 nsString
value(aValue
);
118 if (attribute
== nsGkAtoms::color
|| attribute
== nsGkAtoms::bgcolor
) {
119 if (!IsCSSEnabled()) {
120 // We allow CSS style color value even in the HTML mode. In the cases,
121 // we will apply the style with CSS. For considering it in the value
122 // as-is if it's a known CSS keyboard, `rgb()` or `rgba()` style.
123 // NOTE: It may be later that we set the color into the DOM tree and at
124 // that time, IsCSSEnabled() may return true. E.g., setting color value
125 // to collapsed selection, then, change the CSS enabled, finally, user
127 if (!HTMLEditUtils::MaybeCSSSpecificColorValue(value
)) {
128 HTMLEditUtils::GetNormalizedHTMLColorValue(value
, value
);
131 HTMLEditUtils::GetNormalizedCSSColorValue(
132 value
, HTMLEditUtils::ZeroAlphaColor::RGBAValue
, value
);
136 AutoTArray
<EditorInlineStyle
, 1> stylesToRemove
;
137 if (&aProperty
== nsGkAtoms::sup
) {
138 // Superscript and Subscript styles are mutually exclusive.
139 stylesToRemove
.AppendElement(EditorInlineStyle(*nsGkAtoms::sub
));
140 } else if (&aProperty
== nsGkAtoms::sub
) {
141 // Superscript and Subscript styles are mutually exclusive.
142 stylesToRemove
.AppendElement(EditorInlineStyle(*nsGkAtoms::sup
));
144 // Handling `<tt>` element code was implemented for composer (bug 115922).
145 // This shouldn't work with `Document.execCommand()`. Currently, aPrincipal
146 // is set only when the root caller is Document::ExecCommand() so that
147 // we should handle `<tt>` element only when aPrincipal is nullptr that
148 // must be only when XUL command is executed on composer.
149 else if (!aPrincipal
) {
150 if (&aProperty
== nsGkAtoms::tt
) {
151 stylesToRemove
.AppendElement(
152 EditorInlineStyle(*nsGkAtoms::font
, nsGkAtoms::face
));
153 } else if (&aProperty
== nsGkAtoms::font
&& aAttribute
== nsGkAtoms::face
) {
154 if (!value
.LowerCaseEqualsASCII("tt")) {
155 stylesToRemove
.AppendElement(EditorInlineStyle(*nsGkAtoms::tt
));
157 stylesToRemove
.AppendElement(
158 EditorInlineStyle(*nsGkAtoms::font
, nsGkAtoms::face
));
159 // Override property, attribute and value if the new font face value is
161 property
= nsGkAtoms::tt
;
168 if (!stylesToRemove
.IsEmpty()) {
169 nsresult rv
= RemoveInlinePropertiesAsSubAction(stylesToRemove
);
171 NS_WARNING("HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
176 AutoTArray
<EditorInlineStyleAndValue
, 1> styleToSet
;
177 styleToSet
.AppendElement(
179 ? EditorInlineStyleAndValue(*property
, *attribute
, std::move(value
))
180 : EditorInlineStyleAndValue(*property
));
181 rv
= SetInlinePropertiesAsSubAction(styleToSet
);
182 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
183 "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
184 return EditorBase::ToGenericNSResult(rv
);
187 NS_IMETHODIMP
HTMLEditor::SetInlineProperty(const nsAString
& aProperty
,
188 const nsAString
& aAttribute
,
189 const nsAString
& aValue
) {
190 nsStaticAtom
* property
= NS_GetStaticAtom(aProperty
);
191 if (NS_WARN_IF(!property
)) {
192 return NS_ERROR_INVALID_ARG
;
194 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
195 AutoEditActionDataSetter
editActionData(
197 HTMLEditUtils::GetEditActionForFormatText(*property
, attribute
, true));
198 switch (editActionData
.GetEditAction()) {
199 case EditAction::eSetFontFamilyProperty
:
200 MOZ_ASSERT(!aValue
.IsVoid());
201 // XXX Should we trim unnecessary white-spaces?
202 editActionData
.SetData(aValue
);
204 case EditAction::eSetColorProperty
:
205 case EditAction::eSetBackgroundColorPropertyInline
:
206 editActionData
.SetColorData(aValue
);
211 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
213 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
214 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
215 return EditorBase::ToGenericNSResult(rv
);
218 AutoTArray
<EditorInlineStyleAndValue
, 1> styleToSet
;
219 styleToSet
.AppendElement(
220 attribute
? EditorInlineStyleAndValue(*property
, *attribute
, aValue
)
221 : EditorInlineStyleAndValue(*property
));
222 rv
= SetInlinePropertiesAsSubAction(styleToSet
);
223 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
224 "HTMLEditor::SetInlinePropertiesAsSubAction() failed");
225 return EditorBase::ToGenericNSResult(rv
);
229 nsresult
HTMLEditor::SetInlinePropertiesAsSubAction(
230 const AutoTArray
<EditorInlineStyleAndValue
, N
>& aStylesToSet
) {
231 MOZ_ASSERT(IsEditActionDataAvailable());
232 MOZ_ASSERT(!aStylesToSet
.IsEmpty());
234 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
235 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
236 "EditorBase::CommitComposition() failed, but ignored");
238 if (SelectionRef().IsCollapsed()) {
239 // Manipulating text attributes on a collapsed selection only sets state
240 // for the next text insertion
241 mPendingStylesToApplyToNewContent
->PreserveStyles(aStylesToSet
);
245 // XXX Shouldn't we return before calling `CommitComposition()`?
246 if (IsPlaintextMailComposer()) {
251 Result
<EditActionResult
, nsresult
> result
= CanHandleHTMLEditSubAction();
252 if (MOZ_UNLIKELY(result
.isErr())) {
253 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
254 return result
.unwrapErr();
256 if (result
.inspect().Canceled()) {
261 RefPtr
<Element
> const editingHost
=
262 ComputeEditingHost(LimitInBodyElement::No
);
263 if (NS_WARN_IF(!editingHost
)) {
264 return NS_ERROR_FAILURE
;
267 AutoPlaceholderBatch
treatAsOneTransaction(
268 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
269 IgnoredErrorResult ignoredError
;
270 AutoEditSubActionNotifier
startToHandleEditSubAction(
271 *this, EditSubAction::eInsertElement
, nsIEditor::eNext
, ignoredError
);
272 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
273 return ignoredError
.StealNSResult();
275 NS_WARNING_ASSERTION(
276 !ignoredError
.Failed(),
277 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
279 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
280 // cases, but removing this may cause the behavior with the legacy
281 // mutation event listeners. We should try to delete this in a bug.
282 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
284 AutoRangeArray
selectionRanges(SelectionRef());
285 nsresult rv
= SetInlinePropertiesAroundRanges(selectionRanges
, aStylesToSet
,
288 NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
291 MOZ_ASSERT(!selectionRanges
.HasSavedRanges());
292 rv
= selectionRanges
.ApplyTo(SelectionRef());
293 if (NS_WARN_IF(Destroyed())) {
294 return NS_ERROR_EDITOR_DESTROYED
;
296 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::ApplyTo() failed");
301 nsresult
HTMLEditor::SetInlinePropertiesAroundRanges(
302 AutoRangeArray
& aRanges
,
303 const AutoTArray
<EditorInlineStyleAndValue
, N
>& aStylesToSet
,
304 const Element
& aEditingHost
) {
305 MOZ_ASSERT(!aRanges
.HasSavedRanges());
306 for (const EditorInlineStyleAndValue
& styleToSet
: aStylesToSet
) {
307 AutoInlineStyleSetter
inlineStyleSetter(styleToSet
);
308 for (OwningNonNull
<nsRange
>& domRange
: aRanges
.Ranges()) {
309 inlineStyleSetter
.Reset();
311 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<EditorDOMRange
, nsresult
> {
312 EditorDOMRange
range(domRange
);
313 // If we're setting <font>, we want to remove ancestors which set
314 // `font-size` or <font size="..."> recursively. Therefore, for
315 // extending the ranges to contain all ancestors in the range, we need
316 // to split ancestors first.
317 // XXX: Blink and WebKit inserts <font> elements to inner most
318 // elements, however, we cannot do it under current design because
319 // once we contain ancestors which have `font-size` or are
320 // <font size="...">, we lost the original ranges which we wanted to
321 // apply the style. For fixing this, we need to manage both ranges, but
322 // it's too expensive especially we allow to run script when we touch
323 // the DOM tree. Additionally, font-size value affects the height
324 // of the element, but does not affect the height of ancestor inline
325 // elements. Therefore, following the behavior may cause similar issue
326 // as bug 1808906. So at least for now, we should not do this big work.
327 if (styleToSet
.IsStyleOfFontElement()) {
328 Result
<SplitRangeOffResult
, nsresult
> splitAncestorsResult
=
329 SplitAncestorStyledInlineElementsAtRangeEdges(
330 range
, styleToSet
, SplitAtEdges::eDoNotCreateEmptyContainer
);
331 if (MOZ_UNLIKELY(splitAncestorsResult
.isErr())) {
333 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
335 return splitAncestorsResult
.propagateErr();
337 SplitRangeOffResult unwrappedResult
= splitAncestorsResult
.unwrap();
338 unwrappedResult
.IgnoreCaretPointSuggestion();
339 range
= unwrappedResult
.RangeRef();
340 if (NS_WARN_IF(!range
.IsPositionedAndValid())) {
341 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
344 Result
<EditorRawDOMRange
, nsresult
> rangeOrError
=
345 inlineStyleSetter
.ExtendOrShrinkRangeToApplyTheStyle(*this, range
,
347 if (MOZ_UNLIKELY(rangeOrError
.isErr())) {
349 "HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but "
351 return EditorDOMRange();
353 return EditorDOMRange(rangeOrError
.unwrap());
355 if (MOZ_UNLIKELY(rangeOrError
.isErr())) {
356 return rangeOrError
.unwrapErr();
359 const EditorDOMRange range
= rangeOrError
.unwrap();
360 if (!range
.IsPositioned()) {
364 // If the range is collapsed, we should insert new element there.
365 if (range
.Collapsed()) {
366 Result
<RefPtr
<Text
>, nsresult
> emptyTextNodeOrError
=
367 AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
368 *this, range
.StartRef(), aEditingHost
);
369 if (MOZ_UNLIKELY(emptyTextNodeOrError
.isErr())) {
371 "AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle() "
373 return emptyTextNodeOrError
.unwrapErr();
375 if (MOZ_UNLIKELY(!emptyTextNodeOrError
.inspect())) {
376 continue; // Couldn't insert text node there
378 RefPtr
<Text
> emptyTextNode
= emptyTextNodeOrError
.unwrap();
379 Result
<CaretPoint
, nsresult
> caretPointOrError
=
381 .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
382 *this, *emptyTextNode
);
383 if (MOZ_UNLIKELY(caretPointOrError
.isErr())) {
385 "AutoInlineStyleSetter::"
386 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
387 return caretPointOrError
.unwrapErr();
389 DebugOnly
<nsresult
> rvIgnored
= domRange
->CollapseTo(emptyTextNode
, 0);
390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
391 "nsRange::CollapseTo() failed, but ignored");
395 // Use const_cast hack here for preventing the others to update the range.
396 AutoTrackDOMRange
trackRange(RangeUpdaterRef(),
397 const_cast<EditorDOMRange
*>(&range
));
398 auto UpdateSelectionRange
= [&]() MOZ_CAN_RUN_SCRIPT
{
399 // If inlineStyleSetter creates elements or setting styles, we should
400 // select between start of first element and end of last element.
401 if (inlineStyleSetter
.FirstHandledPointRef().IsInContentNode()) {
402 MOZ_ASSERT(inlineStyleSetter
.LastHandledPointRef().IsInContentNode());
403 const auto startPoint
=
404 !inlineStyleSetter
.FirstHandledPointRef().IsStartOfContainer()
405 ? inlineStyleSetter
.FirstHandledPointRef()
406 .To
<EditorRawDOMPoint
>()
407 : HTMLEditUtils::GetDeepestEditableStartPointOf
<
409 *inlineStyleSetter
.FirstHandledPointRef()
410 .ContainerAs
<nsIContent
>());
411 const auto endPoint
=
412 !inlineStyleSetter
.LastHandledPointRef().IsEndOfContainer()
413 ? inlineStyleSetter
.LastHandledPointRef()
414 .To
<EditorRawDOMPoint
>()
415 : HTMLEditUtils::GetDeepestEditableEndPointOf
<
417 *inlineStyleSetter
.LastHandledPointRef()
418 .ContainerAs
<nsIContent
>());
419 nsresult rv
= domRange
->SetStartAndEnd(
420 startPoint
.ToRawRangeBoundary(), endPoint
.ToRawRangeBoundary());
421 if (NS_SUCCEEDED(rv
)) {
422 trackRange
.StopTracking();
426 // Otherwise, use the range computed with the tracking original range.
427 trackRange
.FlushAndStopTracking();
428 domRange
->SetStartAndEnd(range
.StartRef().ToRawRangeBoundary(),
429 range
.EndRef().ToRawRangeBoundary());
432 // If range is in a text node, apply new style simply.
433 if (range
.InSameContainer() && range
.StartRef().IsInTextNode()) {
434 // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
435 // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
436 Result
<SplitRangeOffFromNodeResult
, nsresult
>
437 wrapTextInStyledElementResult
=
438 inlineStyleSetter
.SplitTextNodeAndApplyStyleToMiddleNode(
439 *this, MOZ_KnownLive(*range
.StartRef().ContainerAs
<Text
>()),
440 range
.StartRef().Offset(), range
.EndRef().Offset());
441 if (MOZ_UNLIKELY(wrapTextInStyledElementResult
.isErr())) {
442 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
443 return wrapTextInStyledElementResult
.unwrapErr();
445 // The caller should handle the ranges as Selection if necessary, and we
446 // don't want to update aRanges with this result.
447 wrapTextInStyledElementResult
.inspect().IgnoreCaretPointSuggestion();
448 UpdateSelectionRange();
452 // Collect editable nodes which are entirely contained in the range.
453 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContentsAroundRange
;
455 ContentSubtreeIterator subtreeIter
;
456 // If there is no node which is entirely in the range,
457 // `ContentSubtreeIterator::Init()` fails, but this is possible case,
460 subtreeIter
.Init(range
.StartRef().ToRawRangeBoundary(),
461 range
.EndRef().ToRawRangeBoundary()))) {
462 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
463 nsINode
* node
= subtreeIter
.GetCurrentNode();
464 if (NS_WARN_IF(!node
)) {
465 return NS_ERROR_FAILURE
;
467 if (MOZ_UNLIKELY(!node
->IsContent())) {
470 // We don't need to wrap non-editable node in new inline element
471 // nor shouldn't modify `style` attribute of non-editable element.
472 if (!EditorUtils::IsEditableContent(*node
->AsContent(),
476 // We shouldn't wrap invisible text node in new inline element.
477 if (node
->IsText() &&
478 !HTMLEditUtils::IsVisibleTextNode(*node
->AsText())) {
481 arrayOfContentsAroundRange
.AppendElement(*node
->AsContent());
486 // If start node is a text node, apply new style to a part of it.
487 if (range
.StartRef().IsInTextNode() &&
488 EditorUtils::IsEditableContent(*range
.StartRef().ContainerAs
<Text
>(),
490 // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
491 // MOZ_KnownLive(styleToSet.*) due to bug 1622253.
492 Result
<SplitRangeOffFromNodeResult
, nsresult
>
493 wrapTextInStyledElementResult
=
494 inlineStyleSetter
.SplitTextNodeAndApplyStyleToMiddleNode(
495 *this, MOZ_KnownLive(*range
.StartRef().ContainerAs
<Text
>()),
496 range
.StartRef().Offset(),
497 range
.StartRef().ContainerAs
<Text
>()->TextDataLength());
498 if (MOZ_UNLIKELY(wrapTextInStyledElementResult
.isErr())) {
499 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
500 return wrapTextInStyledElementResult
.unwrapErr();
502 // The caller should handle the ranges as Selection if necessary, and we
503 // don't want to update aRanges with this result.
504 wrapTextInStyledElementResult
.inspect().IgnoreCaretPointSuggestion();
507 // Then, apply new style to all nodes in the range entirely.
508 for (auto& content
: arrayOfContentsAroundRange
) {
509 // MOZ_KnownLive due to bug 1622253.
510 Result
<CaretPoint
, nsresult
> pointToPutCaretOrError
=
512 .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
513 *this, MOZ_KnownLive(*content
));
514 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
516 "AutoInlineStyleSetter::"
517 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
518 return pointToPutCaretOrError
.unwrapErr();
520 // The caller should handle the ranges as Selection if necessary, and we
521 // don't want to update aRanges with this result.
522 pointToPutCaretOrError
.inspect().IgnoreCaretPointSuggestion();
525 // Finally, if end node is a text node, apply new style to a part of it.
526 if (range
.EndRef().IsInTextNode() &&
527 EditorUtils::IsEditableContent(*range
.EndRef().ContainerAs
<Text
>(),
529 // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`.
530 // MOZ_KnownLive(styleToSet.mAttribute) due to bug 1622253.
531 Result
<SplitRangeOffFromNodeResult
, nsresult
>
532 wrapTextInStyledElementResult
=
533 inlineStyleSetter
.SplitTextNodeAndApplyStyleToMiddleNode(
534 *this, MOZ_KnownLive(*range
.EndRef().ContainerAs
<Text
>()),
535 0, range
.EndRef().Offset());
536 if (MOZ_UNLIKELY(wrapTextInStyledElementResult
.isErr())) {
537 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
538 return wrapTextInStyledElementResult
.unwrapErr();
540 // The caller should handle the ranges as Selection if necessary, and we
541 // don't want to update aRanges with this result.
542 wrapTextInStyledElementResult
.inspect().IgnoreCaretPointSuggestion();
544 UpdateSelectionRange();
551 Result
<RefPtr
<Text
>, nsresult
>
552 HTMLEditor::AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle(
553 HTMLEditor
& aHTMLEditor
, const EditorDOMPoint
& aCandidatePointToInsert
,
554 const Element
& aEditingHost
) {
555 auto pointToInsertNewText
=
556 HTMLEditUtils::GetBetterCaretPositionToInsertText
<EditorDOMPoint
>(
557 aCandidatePointToInsert
, aEditingHost
);
558 if (MOZ_UNLIKELY(!pointToInsertNewText
.IsSet())) {
559 return RefPtr
<Text
>(); // cannot insert text there
561 auto pointToInsertNewStyleOrError
=
562 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<EditorDOMPoint
, nsresult
> {
563 if (!pointToInsertNewText
.IsInTextNode()) {
564 return pointToInsertNewText
;
566 if (!pointToInsertNewText
.ContainerAs
<Text
>()->TextDataLength()) {
567 return pointToInsertNewText
; // Use it
569 if (pointToInsertNewText
.IsStartOfContainer()) {
570 return pointToInsertNewText
.ParentPoint();
572 if (pointToInsertNewText
.IsEndOfContainer()) {
573 return EditorDOMPoint::After(*pointToInsertNewText
.ContainerAs
<Text
>());
575 Result
<SplitNodeResult
, nsresult
> splitTextNodeResult
=
576 aHTMLEditor
.SplitNodeWithTransaction(pointToInsertNewText
);
577 if (MOZ_UNLIKELY(splitTextNodeResult
.isErr())) {
578 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
579 return splitTextNodeResult
.propagateErr();
581 SplitNodeResult unwrappedSplitTextNodeResult
= splitTextNodeResult
.unwrap();
582 unwrappedSplitTextNodeResult
.IgnoreCaretPointSuggestion();
583 return unwrappedSplitTextNodeResult
.AtSplitPoint
<EditorDOMPoint
>();
585 if (MOZ_UNLIKELY(pointToInsertNewStyleOrError
.isErr())) {
586 return pointToInsertNewStyleOrError
.propagateErr();
589 // If we already have empty text node which is available for placeholder in
590 // new styled element, let's use it.
591 if (pointToInsertNewStyleOrError
.inspect().IsInTextNode()) {
593 pointToInsertNewStyleOrError
.inspect().ContainerAs
<Text
>());
596 // Otherwise, we need an empty text node to create new inline style.
597 RefPtr
<Text
> newEmptyTextNode
= aHTMLEditor
.CreateTextNode(u
""_ns
);
598 if (MOZ_UNLIKELY(!newEmptyTextNode
)) {
599 NS_WARNING("EditorBase::CreateTextNode() failed");
600 return Err(NS_ERROR_FAILURE
);
602 Result
<CreateTextResult
, nsresult
> insertNewTextNodeResult
=
603 aHTMLEditor
.InsertNodeWithTransaction
<Text
>(
604 *newEmptyTextNode
, pointToInsertNewStyleOrError
.inspect());
605 if (MOZ_UNLIKELY(insertNewTextNodeResult
.isErr())) {
606 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
607 return insertNewTextNodeResult
.propagateErr();
609 insertNewTextNodeResult
.inspect().IgnoreCaretPointSuggestion();
610 return newEmptyTextNode
;
613 Result
<bool, nsresult
>
614 HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerForTheStyle(
615 HTMLEditor
& aHTMLEditor
, Element
& aElement
) const {
616 // If the editor is in the CSS mode and the style can be specified with CSS,
617 // we should not use existing HTML element as a new container.
618 const bool isCSSEditable
= IsCSSSettable(aElement
);
619 if (!aHTMLEditor
.IsCSSEnabled() || !isCSSEditable
) {
620 // First check for <b>, <i>, etc.
621 if (aElement
.IsHTMLElement(&HTMLPropertyRef()) &&
622 !HTMLEditUtils::ElementHasAttribute(aElement
) && !mAttribute
) {
626 // Now look for things like <font>
629 if (aElement
.IsHTMLElement(&HTMLPropertyRef()) &&
630 !HTMLEditUtils::ElementHasAttributeExcept(aElement
, *mAttribute
) &&
631 aElement
.GetAttr(mAttribute
, attrValue
)) {
632 if (attrValue
.Equals(mAttributeValue
,
633 nsCaseInsensitiveStringComparator
)) {
636 if (mAttribute
== nsGkAtoms::color
||
637 mAttribute
== nsGkAtoms::bgcolor
) {
638 if (aHTMLEditor
.IsCSSEnabled()) {
639 if (HTMLEditUtils::IsSameCSSColorValue(mAttributeValue
,
643 } else if (HTMLEditUtils::IsSameHTMLColorValue(
644 mAttributeValue
, attrValue
,
645 HTMLEditUtils::TransparentKeyword::Allowed
)) {
652 if (!isCSSEditable
) {
657 // No luck so far. Now we check for a <span> with a single style=""
658 // attribute that sets only the style we're looking for, if this type of
660 if (!aElement
.IsHTMLElement(nsGkAtoms::span
) ||
661 !aElement
.HasAttr(nsGkAtoms::style
) ||
662 HTMLEditUtils::ElementHasAttributeExcept(aElement
, *nsGkAtoms::style
)) {
666 nsStyledElement
* styledElement
= nsStyledElement::FromNode(&aElement
);
667 if (MOZ_UNLIKELY(!styledElement
)) {
671 // Some CSS styles are not so simple. For instance, underline is
672 // "text-decoration: underline", which decomposes into four different text-*
673 // properties. So for now, we just create a span, add the desired style, and
674 // see if it matches.
675 RefPtr
<Element
> newSpanElement
=
676 aHTMLEditor
.CreateHTMLContent(nsGkAtoms::span
);
677 if (MOZ_UNLIKELY(!newSpanElement
)) {
678 NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed");
681 nsStyledElement
* styledNewSpanElement
=
682 nsStyledElement::FromNode(newSpanElement
);
683 if (MOZ_UNLIKELY(!styledNewSpanElement
)) {
686 // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
688 Result
<size_t, nsresult
> result
= CSSEditUtils::SetCSSEquivalentToStyle(
689 WithTransaction::No
, aHTMLEditor
, MOZ_KnownLive(*styledNewSpanElement
),
690 *this, &mAttributeValue
);
691 if (MOZ_UNLIKELY(result
.isErr())) {
692 // The call shouldn't return destroyed error because it must be
693 // impossible to run script with modifying the new orphan node.
694 MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?");
695 if (NS_WARN_IF(result
.inspectErr() == NS_ERROR_EDITOR_DESTROYED
)) {
696 return Err(NS_ERROR_EDITOR_DESTROYED
);
700 return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement
,
704 bool HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerToSetStyle(
705 nsStyledElement
& aStyledElement
) const {
706 if (!HTMLEditUtils::IsContainerNode(aStyledElement
) ||
707 !EditorUtils::IsEditableContent(aStyledElement
, EditorType::HTML
)) {
711 // If it has `style` attribute, let's use it.
712 if (aStyledElement
.HasAttr(nsGkAtoms::style
)) {
716 // If it has `class` or `id` attribute, the element may have specific rule.
717 // For applying the new style, we may need to set `style` attribute to it
718 // to override the specified rule.
719 if (aStyledElement
.HasAttr(nsGkAtoms::id
) ||
720 aStyledElement
.HasAttr(nsGkAtoms::_class
)) {
724 // If we're setting text-decoration and the element represents a value of
725 // text-decoration, <ins> or <del>, let's use it.
726 if (IsStyleOfTextDecoration(IgnoreSElement::No
) &&
727 aStyledElement
.IsAnyOfHTMLElements(nsGkAtoms::u
, nsGkAtoms::s
,
728 nsGkAtoms::strike
, nsGkAtoms::ins
,
733 // If we're setting font-size, color or background-color, we should use <font>
734 // for compatibility with the other browsers.
735 if (&HTMLPropertyRef() == nsGkAtoms::font
&&
736 aStyledElement
.IsHTMLElement(nsGkAtoms::font
)) {
740 // If the element has one or more <br> (even if it's invisible), we don't
741 // want to use the <span> for compatibility with the other browsers.
742 if (aStyledElement
.QuerySelector("br"_ns
, IgnoreErrors())) {
746 // NOTE: The following code does not match with the other browsers not
747 // completely. Blink considers this with relation with the range.
748 // However, we cannot do it now. We should fix here after or at
749 // fixing bug 1792386.
751 // If it's only visible element child of parent block, let's use it.
752 // E.g., we don't want to create new <span> when
753 // `<p>{ <span>abc</span> }</p>`.
754 if (aStyledElement
.GetParentElement() &&
755 HTMLEditUtils::IsBlockElement(
756 *aStyledElement
.GetParentElement(),
757 BlockInlineCheck::UseComputedDisplayStyle
)) {
758 for (nsIContent
* previousSibling
= aStyledElement
.GetPreviousSibling();
760 previousSibling
= previousSibling
->GetPreviousSibling()) {
761 if (previousSibling
->IsElement()) {
762 return false; // Assume any elements visible.
764 if (Text
* text
= Text::FromNode(previousSibling
)) {
765 if (HTMLEditUtils::IsVisibleTextNode(*text
)) {
771 for (nsIContent
* nextSibling
= aStyledElement
.GetNextSibling(); nextSibling
;
772 nextSibling
= nextSibling
->GetNextSibling()) {
773 if (nextSibling
->IsElement()) {
774 if (!HTMLEditUtils::IsInvisibleBRElement(*nextSibling
)) {
777 continue; // The invisible <br> element may be followed by a child
778 // block, let's continue to check it.
780 if (Text
* text
= Text::FromNode(nextSibling
)) {
781 if (HTMLEditUtils::IsVisibleTextNode(*text
)) {
790 // Otherwise, wrap it into new <span> for making
791 // `<span>[abc</span> <span>def]</span>` become
792 // `<span style="..."><span>abc</span> <span>def</span></span>` rather
793 // than `<span style="...">abc <span>def</span></span>`.
797 Result
<SplitRangeOffFromNodeResult
, nsresult
>
798 HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode(
799 HTMLEditor
& aHTMLEditor
, Text
& aText
, uint32_t aStartOffset
,
800 uint32_t aEndOffset
) {
801 const RefPtr
<Element
> element
= aText
.GetParentElement();
802 if (!element
|| !HTMLEditUtils::CanNodeContain(*element
, HTMLPropertyRef())) {
803 OnHandled(EditorDOMPoint(&aText
, aStartOffset
),
804 EditorDOMPoint(&aText
, aEndOffset
));
805 return SplitRangeOffFromNodeResult(nullptr, &aText
, nullptr);
808 // Don't need to do anything if no characters actually selected
809 if (aStartOffset
== aEndOffset
) {
810 OnHandled(EditorDOMPoint(&aText
, aStartOffset
),
811 EditorDOMPoint(&aText
, aEndOffset
));
812 return SplitRangeOffFromNodeResult(nullptr, &aText
, nullptr);
815 // Don't need to do anything if property already set on node
816 if (IsCSSSettable(*element
)) {
817 // The HTML styles defined by this have a CSS equivalence for node;
818 // let's check if it carries those CSS styles
819 nsAutoString
value(mAttributeValue
);
820 Result
<bool, nsresult
> isComputedCSSEquivalentToStyleOrError
=
821 CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor
, *element
, *this,
823 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError
.isErr())) {
824 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
825 return isComputedCSSEquivalentToStyleOrError
.propagateErr();
827 if (isComputedCSSEquivalentToStyleOrError
.unwrap()) {
828 OnHandled(EditorDOMPoint(&aText
, aStartOffset
),
829 EditorDOMPoint(&aText
, aEndOffset
));
830 return SplitRangeOffFromNodeResult(nullptr, &aText
, nullptr);
832 } else if (HTMLEditUtils::IsInlineStyleSetByElement(aText
, *this,
834 OnHandled(EditorDOMPoint(&aText
, aStartOffset
),
835 EditorDOMPoint(&aText
, aEndOffset
));
836 return SplitRangeOffFromNodeResult(nullptr, &aText
, nullptr);
839 // Make the range an independent node.
840 auto splitAtEndResult
=
841 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<SplitNodeResult
, nsresult
> {
842 EditorDOMPoint
atEnd(&aText
, aEndOffset
);
843 if (atEnd
.IsEndOfContainer()) {
844 return SplitNodeResult::NotHandled(atEnd
);
846 // We need to split off back of text node
847 Result
<SplitNodeResult
, nsresult
> splitNodeResult
=
848 aHTMLEditor
.SplitNodeWithTransaction(atEnd
);
849 if (splitNodeResult
.isErr()) {
850 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
851 return splitNodeResult
;
853 if (MOZ_UNLIKELY(!splitNodeResult
.inspect().HasCaretPointSuggestion())) {
855 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
857 return Err(NS_ERROR_FAILURE
);
859 return splitNodeResult
;
861 if (MOZ_UNLIKELY(splitAtEndResult
.isErr())) {
862 return splitAtEndResult
.propagateErr();
864 SplitNodeResult unwrappedSplitAtEndResult
= splitAtEndResult
.unwrap();
865 EditorDOMPoint pointToPutCaret
= unwrappedSplitAtEndResult
.UnwrapCaretPoint();
866 auto splitAtStartResult
=
867 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<SplitNodeResult
, nsresult
> {
868 EditorDOMPoint
atStart(unwrappedSplitAtEndResult
.DidSplit()
869 ? unwrappedSplitAtEndResult
.GetPreviousContent()
872 if (atStart
.IsStartOfContainer()) {
873 return SplitNodeResult::NotHandled(atStart
);
875 // We need to split off front of text node
876 Result
<SplitNodeResult
, nsresult
> splitNodeResult
=
877 aHTMLEditor
.SplitNodeWithTransaction(atStart
);
878 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
879 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
880 return splitNodeResult
;
882 if (MOZ_UNLIKELY(!splitNodeResult
.inspect().HasCaretPointSuggestion())) {
884 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
886 return Err(NS_ERROR_FAILURE
);
888 return splitNodeResult
;
890 if (MOZ_UNLIKELY(splitAtStartResult
.isErr())) {
891 return splitAtStartResult
.propagateErr();
893 SplitNodeResult unwrappedSplitAtStartResult
= splitAtStartResult
.unwrap();
894 if (unwrappedSplitAtStartResult
.HasCaretPointSuggestion()) {
895 pointToPutCaret
= unwrappedSplitAtStartResult
.UnwrapCaretPoint();
898 MOZ_ASSERT_IF(unwrappedSplitAtStartResult
.DidSplit(),
899 unwrappedSplitAtStartResult
.GetPreviousContent()->IsText());
900 MOZ_ASSERT_IF(unwrappedSplitAtStartResult
.DidSplit(),
901 unwrappedSplitAtStartResult
.GetNextContent()->IsText());
902 MOZ_ASSERT_IF(unwrappedSplitAtEndResult
.DidSplit(),
903 unwrappedSplitAtEndResult
.GetPreviousContent()->IsText());
904 MOZ_ASSERT_IF(unwrappedSplitAtEndResult
.DidSplit(),
905 unwrappedSplitAtEndResult
.GetNextContent()->IsText());
906 // Note that those text nodes are grabbed by unwrappedSplitAtStartResult,
907 // unwrappedSplitAtEndResult or the callers. Therefore, we don't need to make
908 // them strong pointer.
909 Text
* const leftTextNode
=
910 unwrappedSplitAtStartResult
.DidSplit()
911 ? unwrappedSplitAtStartResult
.GetPreviousContentAs
<Text
>()
913 Text
* const middleTextNode
=
914 unwrappedSplitAtStartResult
.DidSplit()
915 ? unwrappedSplitAtStartResult
.GetNextContentAs
<Text
>()
916 : (unwrappedSplitAtEndResult
.DidSplit()
917 ? unwrappedSplitAtEndResult
.GetPreviousContentAs
<Text
>()
919 Text
* const rightTextNode
=
920 unwrappedSplitAtEndResult
.DidSplit()
921 ? unwrappedSplitAtEndResult
.GetNextContentAs
<Text
>()
924 // Look for siblings that are correct type of node
925 nsIContent
* sibling
= HTMLEditUtils::GetPreviousSibling(
926 *middleTextNode
, {WalkTreeOption::IgnoreNonEditableNode
});
927 if (sibling
&& sibling
->IsElement()) {
928 OwningNonNull
<Element
> element(*sibling
->AsElement());
929 Result
<bool, nsresult
> result
=
930 ElementIsGoodContainerForTheStyle(aHTMLEditor
, element
);
931 if (MOZ_UNLIKELY(result
.isErr())) {
932 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
933 return result
.propagateErr();
935 if (result
.inspect()) {
936 // Previous sib is already right kind of inline node; slide this over
937 Result
<MoveNodeResult
, nsresult
> moveTextNodeResult
=
938 aHTMLEditor
.MoveNodeToEndWithTransaction(
939 MOZ_KnownLive(*middleTextNode
), element
);
940 if (MOZ_UNLIKELY(moveTextNodeResult
.isErr())) {
941 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
942 return moveTextNodeResult
.propagateErr();
944 MoveNodeResult unwrappedMoveTextNodeResult
=
945 moveTextNodeResult
.unwrap();
946 unwrappedMoveTextNodeResult
.MoveCaretPointTo(
947 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
948 OnHandled(*middleTextNode
);
949 return SplitRangeOffFromNodeResult(leftTextNode
, middleTextNode
,
951 std::move(pointToPutCaret
));
954 sibling
= HTMLEditUtils::GetNextSibling(
955 *middleTextNode
, {WalkTreeOption::IgnoreNonEditableNode
});
956 if (sibling
&& sibling
->IsElement()) {
957 OwningNonNull
<Element
> element(*sibling
->AsElement());
958 Result
<bool, nsresult
> result
=
959 ElementIsGoodContainerForTheStyle(aHTMLEditor
, element
);
960 if (MOZ_UNLIKELY(result
.isErr())) {
961 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
962 return result
.propagateErr();
964 if (result
.inspect()) {
965 // Following sib is already right kind of inline node; slide this over
966 Result
<MoveNodeResult
, nsresult
> moveTextNodeResult
=
967 aHTMLEditor
.MoveNodeWithTransaction(MOZ_KnownLive(*middleTextNode
),
968 EditorDOMPoint(sibling
, 0u));
969 if (MOZ_UNLIKELY(moveTextNodeResult
.isErr())) {
970 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
971 return moveTextNodeResult
.propagateErr();
973 MoveNodeResult unwrappedMoveTextNodeResult
=
974 moveTextNodeResult
.unwrap();
975 unwrappedMoveTextNodeResult
.MoveCaretPointTo(
976 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
977 OnHandled(*middleTextNode
);
978 return SplitRangeOffFromNodeResult(leftTextNode
, middleTextNode
,
980 std::move(pointToPutCaret
));
985 // Wrap the node inside inline node.
986 Result
<CaretPoint
, nsresult
> setStyleResult
=
987 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
988 aHTMLEditor
, MOZ_KnownLive(*middleTextNode
));
989 if (MOZ_UNLIKELY(setStyleResult
.isErr())) {
991 "AutoInlineStyleSetter::"
992 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
993 return setStyleResult
.propagateErr();
995 return SplitRangeOffFromNodeResult(
996 leftTextNode
, middleTextNode
, rightTextNode
,
997 setStyleResult
.unwrap().UnwrapCaretPoint());
1000 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoInlineStyleSetter::ApplyStyle(
1001 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) {
1002 // If this is an element that can't be contained in a span, we have to
1003 // recurse to its children.
1004 if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span
, aContent
)) {
1005 if (!aContent
.HasChildren()) {
1006 return CaretPoint(EditorDOMPoint());
1009 AutoTArray
<OwningNonNull
<nsIContent
>, 32> arrayOfContents
;
1010 HTMLEditUtils::CollectChildren(
1011 aContent
, arrayOfContents
,
1012 {CollectChildrenOption::IgnoreNonEditableChildren
,
1013 CollectChildrenOption::IgnoreInvisibleTextNodes
});
1015 // Then loop through the list, set the property on each node.
1016 EditorDOMPoint pointToPutCaret
;
1017 for (const OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
1018 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
1020 Result
<CaretPoint
, nsresult
> setInlinePropertyResult
=
1021 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
1022 aHTMLEditor
, MOZ_KnownLive(content
));
1023 if (MOZ_UNLIKELY(setInlinePropertyResult
.isErr())) {
1025 "AutoInlineStyleSetter::"
1026 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
1027 return setInlinePropertyResult
;
1029 setInlinePropertyResult
.unwrap().MoveCaretPointTo(
1030 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1032 return CaretPoint(std::move(pointToPutCaret
));
1035 // First check if there's an adjacent sibling we can put our node into.
1036 nsCOMPtr
<nsIContent
> previousSibling
= HTMLEditUtils::GetPreviousSibling(
1037 aContent
, {WalkTreeOption::IgnoreNonEditableNode
});
1038 nsCOMPtr
<nsIContent
> nextSibling
= HTMLEditUtils::GetNextSibling(
1039 aContent
, {WalkTreeOption::IgnoreNonEditableNode
});
1040 if (RefPtr
<Element
> previousElement
=
1041 Element::FromNodeOrNull(previousSibling
)) {
1042 Result
<bool, nsresult
> canMoveIntoPreviousSibling
=
1043 ElementIsGoodContainerForTheStyle(aHTMLEditor
, *previousElement
);
1044 if (MOZ_UNLIKELY(canMoveIntoPreviousSibling
.isErr())) {
1045 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
1046 return canMoveIntoPreviousSibling
.propagateErr();
1048 if (canMoveIntoPreviousSibling
.inspect()) {
1049 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
1050 aHTMLEditor
.MoveNodeToEndWithTransaction(aContent
, *previousSibling
);
1051 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
1052 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
1053 return moveNodeResult
.propagateErr();
1055 MoveNodeResult unwrappedMoveNodeResult
= moveNodeResult
.unwrap();
1056 RefPtr
<Element
> nextElement
= Element::FromNodeOrNull(nextSibling
);
1058 OnHandled(aContent
);
1059 return CaretPoint(unwrappedMoveNodeResult
.UnwrapCaretPoint());
1061 Result
<bool, nsresult
> canMoveIntoNextSibling
=
1062 ElementIsGoodContainerForTheStyle(aHTMLEditor
, *nextElement
);
1063 if (MOZ_UNLIKELY(canMoveIntoNextSibling
.isErr())) {
1064 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
1065 unwrappedMoveNodeResult
.IgnoreCaretPointSuggestion();
1066 return canMoveIntoNextSibling
.propagateErr();
1068 if (!canMoveIntoNextSibling
.inspect()) {
1069 OnHandled(aContent
);
1070 return CaretPoint(unwrappedMoveNodeResult
.UnwrapCaretPoint());
1072 unwrappedMoveNodeResult
.IgnoreCaretPointSuggestion();
1074 // JoinNodesWithTransaction (DoJoinNodes) tries to collapse selection to
1075 // the joined point and we want to skip updating `Selection` here.
1076 AutoTransactionsConserveSelection
dontChangeMySelection(aHTMLEditor
);
1077 Result
<JoinNodesResult
, nsresult
> joinNodesResult
=
1078 aHTMLEditor
.JoinNodesWithTransaction(*previousElement
, *nextElement
);
1079 if (MOZ_UNLIKELY(joinNodesResult
.isErr())) {
1080 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
1081 return joinNodesResult
.propagateErr();
1083 // So, let's take it.
1084 OnHandled(aContent
);
1086 joinNodesResult
.inspect().AtJoinedPoint
<EditorDOMPoint
>());
1090 if (RefPtr
<Element
> nextElement
= Element::FromNodeOrNull(nextSibling
)) {
1091 Result
<bool, nsresult
> canMoveIntoNextSibling
=
1092 ElementIsGoodContainerForTheStyle(aHTMLEditor
, *nextElement
);
1093 if (MOZ_UNLIKELY(canMoveIntoNextSibling
.isErr())) {
1094 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
1095 return canMoveIntoNextSibling
.propagateErr();
1097 if (canMoveIntoNextSibling
.inspect()) {
1098 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
1099 aHTMLEditor
.MoveNodeWithTransaction(aContent
,
1100 EditorDOMPoint(nextElement
, 0u));
1101 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
1102 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
1103 return moveNodeResult
.propagateErr();
1105 OnHandled(aContent
);
1106 return CaretPoint(moveNodeResult
.unwrap().UnwrapCaretPoint());
1110 // Don't need to do anything if property already set on node
1111 if (const RefPtr
<Element
> element
= aContent
.GetAsElementOrParentElement()) {
1112 if (IsCSSSettable(*element
)) {
1113 nsAutoString
value(mAttributeValue
);
1114 // MOZ_KnownLive(element) because it's aContent.
1115 Result
<bool, nsresult
> isComputedCSSEquivalentToStyleOrError
=
1116 CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor
, *element
, *this,
1118 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError
.isErr())) {
1119 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
1120 return isComputedCSSEquivalentToStyleOrError
.propagateErr();
1122 if (isComputedCSSEquivalentToStyleOrError
.unwrap()) {
1123 OnHandled(aContent
);
1124 return CaretPoint(EditorDOMPoint());
1126 } else if (HTMLEditUtils::IsInlineStyleSetByElement(*element
, *this,
1127 &mAttributeValue
)) {
1128 OnHandled(aContent
);
1129 return CaretPoint(EditorDOMPoint());
1133 auto ShouldUseCSS
= [&]() {
1134 if (aHTMLEditor
.IsCSSEnabled() && aContent
.GetAsElementOrParentElement() &&
1135 IsCSSSettable(*aContent
.GetAsElementOrParentElement())) {
1138 // bgcolor is always done using CSS
1139 if (mAttribute
== nsGkAtoms::bgcolor
) {
1142 // called for removing parent style, we should use CSS with <span> element.
1143 if (IsStyleToInvert()) {
1146 // If we set color value, the value may be able to specified only with CSS.
1147 // In that case, we need to use CSS even in the HTML mode.
1148 if (mAttribute
== nsGkAtoms::color
) {
1149 return mAttributeValue
.First() != '#' &&
1150 !HTMLEditUtils::CanConvertToHTMLColorValue(mAttributeValue
);
1155 if (ShouldUseCSS()) {
1156 // We need special handlings for text-decoration.
1157 if (IsStyleOfTextDecoration(IgnoreSElement::No
)) {
1158 Result
<CaretPoint
, nsresult
> result
=
1159 ApplyCSSTextDecoration(aHTMLEditor
, aContent
);
1160 NS_WARNING_ASSERTION(
1162 "AutoInlineStyleSetter::ApplyCSSTextDecoration() failed");
1165 EditorDOMPoint pointToPutCaret
;
1166 RefPtr
<nsStyledElement
> styledElement
= [&]() -> nsStyledElement
* {
1167 auto* const styledElement
= nsStyledElement::FromNode(&aContent
);
1168 return styledElement
&& ElementIsGoodContainerToSetStyle(*styledElement
)
1173 // If we don't have good element to set the style, let's create new <span>.
1174 if (!styledElement
) {
1175 Result
<CreateElementResult
, nsresult
> wrapInSpanElementResult
=
1176 aHTMLEditor
.InsertContainerWithTransaction(aContent
,
1178 if (MOZ_UNLIKELY(wrapInSpanElementResult
.isErr())) {
1180 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
1182 return wrapInSpanElementResult
.propagateErr();
1184 CreateElementResult unwrappedWrapInSpanElementResult
=
1185 wrapInSpanElementResult
.unwrap();
1186 MOZ_ASSERT(unwrappedWrapInSpanElementResult
.GetNewNode());
1187 unwrappedWrapInSpanElementResult
.MoveCaretPointTo(
1188 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1189 styledElement
= nsStyledElement::FromNode(
1190 unwrappedWrapInSpanElementResult
.GetNewNode());
1191 MOZ_ASSERT(styledElement
);
1192 if (MOZ_UNLIKELY(!styledElement
)) {
1193 // Don't return error to avoid creating new path to throwing error.
1194 OnHandled(aContent
);
1195 return CaretPoint(pointToPutCaret
);
1199 // Add the CSS styles corresponding to the HTML style request
1200 if (IsCSSSettable(*styledElement
)) {
1201 Result
<size_t, nsresult
> result
= CSSEditUtils::SetCSSEquivalentToStyle(
1202 WithTransaction::Yes
, aHTMLEditor
, *styledElement
, *this,
1204 if (MOZ_UNLIKELY(result
.isErr())) {
1205 if (NS_WARN_IF(result
.inspectErr() == NS_ERROR_EDITOR_DESTROYED
)) {
1206 return Err(NS_ERROR_EDITOR_DESTROYED
);
1209 "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored");
1212 OnHandled(aContent
);
1213 return CaretPoint(pointToPutCaret
);
1216 nsAutoString
attributeValue(mAttributeValue
);
1217 if (mAttribute
== nsGkAtoms::color
&& mAttributeValue
.First() != '#') {
1218 // At here, all color values should be able to be parsed as a CSS color
1219 // value. Therefore, we need to convert it to normalized HTML color value.
1220 HTMLEditUtils::ConvertToNormalizedHTMLColorValue(attributeValue
,
1224 // is it already the right kind of node, but with wrong attribute?
1225 if (aContent
.IsHTMLElement(&HTMLPropertyRef())) {
1226 if (NS_WARN_IF(!mAttribute
)) {
1227 return Err(NS_ERROR_INVALID_ARG
);
1229 // Just set the attribute on it.
1230 nsresult rv
= aHTMLEditor
.SetAttributeWithTransaction(
1231 MOZ_KnownLive(*aContent
.AsElement()), *mAttribute
, attributeValue
);
1232 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
1233 return Err(NS_ERROR_EDITOR_DESTROYED
);
1235 if (NS_FAILED(rv
)) {
1236 NS_WARNING("EditorBase::SetAttributeWithTransaction() failed");
1239 OnHandled(aContent
);
1240 return CaretPoint(EditorDOMPoint());
1243 // ok, chuck it in its very own container
1244 Result
<CreateElementResult
, nsresult
> wrapWithNewElementToFormatResult
=
1245 aHTMLEditor
.InsertContainerWithTransaction(
1246 aContent
, MOZ_KnownLive(HTMLPropertyRef()),
1247 !mAttribute
? HTMLEditor::DoNothingForNewElement
1248 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868
1249 : [&](HTMLEditor
& aHTMLEditor
, Element
& aNewElement
,
1250 const EditorDOMPoint
&) MOZ_CAN_RUN_SCRIPT_BOUNDARY
{
1252 aNewElement
.SetAttr(kNameSpaceID_None
, mAttribute
,
1253 attributeValue
, false);
1254 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1255 "Element::SetAttr() failed");
1258 if (MOZ_UNLIKELY(wrapWithNewElementToFormatResult
.isErr())) {
1259 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
1260 return wrapWithNewElementToFormatResult
.propagateErr();
1262 OnHandled(aContent
);
1263 MOZ_ASSERT(wrapWithNewElementToFormatResult
.inspect().GetNewNode());
1265 wrapWithNewElementToFormatResult
.unwrap().UnwrapCaretPoint());
1268 Result
<CaretPoint
, nsresult
>
1269 HTMLEditor::AutoInlineStyleSetter::ApplyCSSTextDecoration(
1270 HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) {
1271 MOZ_ASSERT(IsStyleOfTextDecoration(IgnoreSElement::No
));
1273 EditorDOMPoint pointToPutCaret
;
1274 RefPtr
<nsStyledElement
> styledElement
= nsStyledElement::FromNode(aContent
);
1275 nsAutoString newTextDecorationValue
;
1276 if (&HTMLPropertyRef() == nsGkAtoms::u
) {
1277 newTextDecorationValue
.AssignLiteral(u
"underline");
1278 } else if (&HTMLPropertyRef() == nsGkAtoms::s
||
1279 &HTMLPropertyRef() == nsGkAtoms::strike
) {
1280 newTextDecorationValue
.AssignLiteral(u
"line-through");
1282 MOZ_ASSERT_UNREACHABLE(
1283 "Was new value added in "
1284 "IsStyleOfTextDecoration(IgnoreSElement::No))?");
1286 if (styledElement
&& IsCSSSettable(*styledElement
) &&
1287 ElementIsGoodContainerToSetStyle(*styledElement
)) {
1288 nsAutoString textDecorationValue
;
1289 nsresult rv
= CSSEditUtils::GetSpecifiedProperty(
1290 *styledElement
, *nsGkAtoms::text_decoration
, textDecorationValue
);
1291 if (NS_FAILED(rv
)) {
1293 "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::text_decoration) "
1297 // However, if the element is an element to style the text-decoration,
1298 // replace it with new <span>.
1299 if (styledElement
&& styledElement
->IsAnyOfHTMLElements(
1300 nsGkAtoms::u
, nsGkAtoms::s
, nsGkAtoms::strike
)) {
1301 Result
<CreateElementResult
, nsresult
> replaceResult
=
1302 aHTMLEditor
.ReplaceContainerAndCloneAttributesWithTransaction(
1303 *styledElement
, *nsGkAtoms::span
);
1304 if (MOZ_UNLIKELY(replaceResult
.isErr())) {
1306 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
1308 return replaceResult
.propagateErr();
1310 CreateElementResult unwrappedReplaceResult
= replaceResult
.unwrap();
1311 MOZ_ASSERT(unwrappedReplaceResult
.GetNewNode());
1312 unwrappedReplaceResult
.MoveCaretPointTo(
1313 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1314 // The new <span> needs to specify the original element's text-decoration
1315 // style unless it's specified explicitly.
1316 if (textDecorationValue
.IsEmpty()) {
1317 if (!newTextDecorationValue
.IsEmpty()) {
1318 newTextDecorationValue
.Append(HTMLEditUtils::kSpace
);
1320 if (styledElement
->IsHTMLElement(nsGkAtoms::u
)) {
1321 newTextDecorationValue
.AppendLiteral(u
"underline");
1323 newTextDecorationValue
.AppendLiteral(u
"line-through");
1327 nsStyledElement::FromNode(unwrappedReplaceResult
.GetNewNode());
1328 if (NS_WARN_IF(!styledElement
)) {
1329 OnHandled(aContent
);
1330 return CaretPoint(pointToPutCaret
);
1333 // If the element has default style, we need to keep it after specifying
1335 else if (textDecorationValue
.IsEmpty() &&
1336 styledElement
->IsAnyOfHTMLElements(nsGkAtoms::u
, nsGkAtoms::ins
)) {
1337 if (!newTextDecorationValue
.IsEmpty()) {
1338 newTextDecorationValue
.Append(HTMLEditUtils::kSpace
);
1340 newTextDecorationValue
.AppendLiteral(u
"underline");
1341 } else if (textDecorationValue
.IsEmpty() &&
1342 styledElement
->IsAnyOfHTMLElements(
1343 nsGkAtoms::s
, nsGkAtoms::strike
, nsGkAtoms::del
)) {
1344 if (!newTextDecorationValue
.IsEmpty()) {
1345 newTextDecorationValue
.Append(HTMLEditUtils::kSpace
);
1347 newTextDecorationValue
.AppendLiteral(u
"line-through");
1350 // Otherwise, use new <span> element.
1352 Result
<CreateElementResult
, nsresult
> wrapInSpanElementResult
=
1353 aHTMLEditor
.InsertContainerWithTransaction(aContent
, *nsGkAtoms::span
);
1354 if (MOZ_UNLIKELY(wrapInSpanElementResult
.isErr())) {
1356 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) failed");
1357 return wrapInSpanElementResult
.propagateErr();
1359 CreateElementResult unwrappedWrapInSpanElementResult
=
1360 wrapInSpanElementResult
.unwrap();
1361 MOZ_ASSERT(unwrappedWrapInSpanElementResult
.GetNewNode());
1362 unwrappedWrapInSpanElementResult
.MoveCaretPointTo(
1363 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1364 styledElement
= nsStyledElement::FromNode(
1365 unwrappedWrapInSpanElementResult
.GetNewNode());
1366 if (NS_WARN_IF(!styledElement
)) {
1367 OnHandled(aContent
);
1368 return CaretPoint(pointToPutCaret
);
1372 nsresult rv
= CSSEditUtils::SetCSSPropertyWithTransaction(
1373 aHTMLEditor
, *styledElement
, *nsGkAtoms::text_decoration
,
1374 newTextDecorationValue
);
1375 if (NS_FAILED(rv
)) {
1376 NS_WARNING("CSSEditUtils::SetCSSPropertyWithTransaction() failed");
1379 OnHandled(aContent
);
1380 return CaretPoint(pointToPutCaret
);
1383 Result
<CaretPoint
, nsresult
> HTMLEditor::AutoInlineStyleSetter::
1384 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor
& aHTMLEditor
,
1385 nsIContent
& aContent
) {
1386 if (NS_WARN_IF(!aContent
.GetParentNode())) {
1387 return Err(NS_ERROR_FAILURE
);
1389 OwningNonNull
<nsINode
> parent
= *aContent
.GetParentNode();
1390 nsCOMPtr
<nsIContent
> previousSibling
= aContent
.GetPreviousSibling(),
1391 nextSibling
= aContent
.GetNextSibling();
1392 EditorDOMPoint pointToPutCaret
;
1393 if (aContent
.IsElement()) {
1394 Result
<EditorDOMPoint
, nsresult
> removeStyleResult
=
1395 aHTMLEditor
.RemoveStyleInside(MOZ_KnownLive(*aContent
.AsElement()),
1396 *this, SpecifiedStyle::Preserve
);
1397 if (MOZ_UNLIKELY(removeStyleResult
.isErr())) {
1398 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1399 return removeStyleResult
.propagateErr();
1401 if (removeStyleResult
.inspect().IsSet()) {
1402 pointToPutCaret
= removeStyleResult
.unwrap();
1404 if (nsStaticAtom
* similarElementNameAtom
= GetSimilarElementNameAtom()) {
1405 Result
<EditorDOMPoint
, nsresult
> removeStyleResult
=
1406 aHTMLEditor
.RemoveStyleInside(
1407 MOZ_KnownLive(*aContent
.AsElement()),
1408 EditorInlineStyle(*similarElementNameAtom
),
1409 SpecifiedStyle::Preserve
);
1410 if (MOZ_UNLIKELY(removeStyleResult
.isErr())) {
1411 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1412 return removeStyleResult
.propagateErr();
1414 if (removeStyleResult
.inspect().IsSet()) {
1415 pointToPutCaret
= removeStyleResult
.unwrap();
1420 if (aContent
.GetParentNode()) {
1421 // The node is still where it was
1422 Result
<CaretPoint
, nsresult
> pointToPutCaretOrError
=
1423 ApplyStyle(aHTMLEditor
, aContent
);
1424 NS_WARNING_ASSERTION(pointToPutCaretOrError
.isOk(),
1425 "AutoInlineStyleSetter::ApplyStyle() failed");
1426 return pointToPutCaretOrError
;
1429 // It's vanished. Use the old siblings for reference to construct a
1430 // list. But first, verify that the previous/next siblings are still
1431 // where we expect them; otherwise we have to give up.
1432 if (NS_WARN_IF(previousSibling
&&
1433 previousSibling
->GetParentNode() != parent
) ||
1434 NS_WARN_IF(nextSibling
&& nextSibling
->GetParentNode() != parent
) ||
1435 NS_WARN_IF(!parent
->IsInComposedDoc())) {
1436 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1438 AutoTArray
<OwningNonNull
<nsIContent
>, 24> nodesToSet
;
1439 for (nsIContent
* content
= previousSibling
? previousSibling
->GetNextSibling()
1440 : parent
->GetFirstChild();
1441 content
&& content
!= nextSibling
; content
= content
->GetNextSibling()) {
1442 if (EditorUtils::IsEditableContent(*content
, EditorType::HTML
)) {
1443 nodesToSet
.AppendElement(*content
);
1447 for (OwningNonNull
<nsIContent
>& content
: nodesToSet
) {
1448 // MOZ_KnownLive because 'nodesToSet' is guaranteed to
1450 Result
<CaretPoint
, nsresult
> pointToPutCaretOrError
=
1451 ApplyStyle(aHTMLEditor
, MOZ_KnownLive(content
));
1452 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
1453 NS_WARNING("AutoInlineStyleSetter::ApplyStyle() failed");
1454 return pointToPutCaretOrError
;
1456 pointToPutCaretOrError
.unwrap().MoveCaretPointTo(
1457 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
1460 return CaretPoint(pointToPutCaret
);
1463 bool HTMLEditor::AutoInlineStyleSetter::ContentIsElementSettingTheStyle(
1464 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
) const {
1465 Element
* const element
= Element::FromNode(&aContent
);
1469 if (IsRepresentedBy(*element
)) {
1472 Result
<bool, nsresult
> specified
= IsSpecifiedBy(aHTMLEditor
, *element
);
1473 NS_WARNING_ASSERTION(specified
.isOk(),
1474 "EditorInlineStyle::IsSpecified() failed, but ignored");
1475 return specified
.unwrapOr(false);
1479 nsIContent
* HTMLEditor::AutoInlineStyleSetter::GetNextEditableInlineContent(
1480 const nsIContent
& aContent
, const nsINode
* aLimiter
) {
1481 auto* const nextContentInRange
= [&]() -> nsIContent
* {
1482 for (nsIContent
* parent
: aContent
.InclusiveAncestorsOfType
<nsIContent
>()) {
1483 if (parent
== aLimiter
||
1484 !EditorUtils::IsEditableContent(*parent
, EditorType::HTML
) ||
1485 (parent
->IsElement() &&
1486 (HTMLEditUtils::IsBlockElement(
1487 *parent
->AsElement(),
1488 BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
1489 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent
->AsElement())))) {
1492 if (nsIContent
* nextSibling
= parent
->GetNextSibling()) {
1498 return nextContentInRange
&&
1499 EditorUtils::IsEditableContent(*nextContentInRange
,
1500 EditorType::HTML
) &&
1501 !HTMLEditUtils::IsBlockElement(
1502 *nextContentInRange
,
1503 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
1504 ? nextContentInRange
1509 nsIContent
* HTMLEditor::AutoInlineStyleSetter::GetPreviousEditableInlineContent(
1510 const nsIContent
& aContent
, const nsINode
* aLimiter
) {
1511 auto* const previousContentInRange
= [&]() -> nsIContent
* {
1512 for (nsIContent
* parent
: aContent
.InclusiveAncestorsOfType
<nsIContent
>()) {
1513 if (parent
== aLimiter
||
1514 !EditorUtils::IsEditableContent(*parent
, EditorType::HTML
) ||
1515 (parent
->IsElement() &&
1516 (HTMLEditUtils::IsBlockElement(
1517 *parent
->AsElement(),
1518 BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
1519 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent
->AsElement())))) {
1522 if (nsIContent
* previousSibling
= parent
->GetPreviousSibling()) {
1523 return previousSibling
;
1528 return previousContentInRange
&&
1529 EditorUtils::IsEditableContent(*previousContentInRange
,
1530 EditorType::HTML
) &&
1531 !HTMLEditUtils::IsBlockElement(
1532 *previousContentInRange
,
1533 BlockInlineCheck::UseComputedDisplayOutsideStyle
)
1534 ? previousContentInRange
1538 EditorRawDOMPoint
HTMLEditor::AutoInlineStyleSetter::GetShrunkenRangeStart(
1539 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
1540 const nsINode
& aCommonAncestorOfRange
,
1541 const nsIContent
* aFirstEntirelySelectedContentNodeInRange
) const {
1542 const EditorDOMPoint
& startRef
= aRange
.StartRef();
1543 // <a> cannot be nested and it should be represented with one element as far
1544 // as possible. Therefore, we don't need to shrink the range.
1545 if (IsStyleOfAnchorElement()) {
1546 return startRef
.To
<EditorRawDOMPoint
>();
1548 // If the start boundary is at end of a node, we need to shrink the range
1549 // to next content, e.g., `abc[<b>def` should be `abc<b>[def` unless the
1550 // <b> is not entirely selected.
1551 auto* const nextContentOrStartContainer
= [&]() -> nsIContent
* {
1552 if (!startRef
.IsInContentNode()) {
1555 if (!startRef
.IsEndOfContainer()) {
1556 return startRef
.ContainerAs
<nsIContent
>();
1558 nsIContent
* const nextContent
=
1559 AutoInlineStyleSetter::GetNextEditableInlineContent(
1560 *startRef
.ContainerAs
<nsIContent
>(), &aCommonAncestorOfRange
);
1561 return nextContent
? nextContent
: startRef
.ContainerAs
<nsIContent
>();
1563 if (MOZ_UNLIKELY(!nextContentOrStartContainer
)) {
1564 return startRef
.To
<EditorRawDOMPoint
>();
1566 EditorRawDOMPoint startPoint
=
1567 nextContentOrStartContainer
!= startRef
.ContainerAs
<nsIContent
>()
1568 ? EditorRawDOMPoint(nextContentOrStartContainer
)
1569 : startRef
.To
<EditorRawDOMPoint
>();
1570 MOZ_ASSERT(startPoint
.IsSet());
1571 // If the start point points a content node, let's try to move it down to
1572 // start of the child recursively.
1573 while (nsIContent
* child
= startPoint
.GetChild()) {
1574 // We shouldn't cross editable and block boundary.
1575 if (!EditorUtils::IsEditableContent(*child
, EditorType::HTML
) ||
1576 HTMLEditUtils::IsBlockElement(
1577 *child
, BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
1580 // If we reach a text node, the minimized range starts from start of it.
1581 if (child
->IsText()) {
1582 startPoint
.Set(child
, 0u);
1585 // Don't shrink the range into element which applies the style to children
1586 // because we want to update the element. E.g., if we are setting
1587 // background color, we want to update style attribute of an element which
1588 // specifies background color with `style` attribute.
1589 if (child
== aFirstEntirelySelectedContentNodeInRange
) {
1592 // We should not start from an atomic element such as <br>, <img>, etc.
1593 if (!HTMLEditUtils::IsContainerNode(*child
)) {
1596 // If the element specifies the style, we should update it. Therefore, we
1597 // need to wrap it in the range.
1598 if (ContentIsElementSettingTheStyle(aHTMLEditor
, *child
)) {
1601 // If the child is an `<a>`, we should not shrink the range into it
1602 // because user may not want to keep editing in the link except when user
1603 // tries to update selection into it obviously.
1604 if (child
->IsHTMLElement(nsGkAtoms::a
)) {
1607 startPoint
.Set(child
, 0u);
1612 EditorRawDOMPoint
HTMLEditor::AutoInlineStyleSetter::GetShrunkenRangeEnd(
1613 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
1614 const nsINode
& aCommonAncestorOfRange
,
1615 const nsIContent
* aLastEntirelySelectedContentNodeInRange
) const {
1616 const EditorDOMPoint
& endRef
= aRange
.EndRef();
1617 // <a> cannot be nested and it should be represented with one element as far
1618 // as possible. Therefore, we don't need to shrink the range.
1619 if (IsStyleOfAnchorElement()) {
1620 return endRef
.To
<EditorRawDOMPoint
>();
1622 // If the end boundary is at start of a node, we need to shrink the range
1623 // to previous content, e.g., `abc</b>]def` should be `abc]</b>def` unless
1624 // the <b> is not entirely selected.
1625 auto* const previousContentOrEndContainer
= [&]() -> nsIContent
* {
1626 if (!endRef
.IsInContentNode()) {
1629 if (!endRef
.IsStartOfContainer()) {
1630 return endRef
.ContainerAs
<nsIContent
>();
1632 nsIContent
* const previousContent
=
1633 AutoInlineStyleSetter::GetPreviousEditableInlineContent(
1634 *endRef
.ContainerAs
<nsIContent
>(), &aCommonAncestorOfRange
);
1635 return previousContent
? previousContent
: endRef
.ContainerAs
<nsIContent
>();
1637 if (MOZ_UNLIKELY(!previousContentOrEndContainer
)) {
1638 return endRef
.To
<EditorRawDOMPoint
>();
1640 EditorRawDOMPoint endPoint
=
1641 previousContentOrEndContainer
!= endRef
.ContainerAs
<nsIContent
>()
1642 ? EditorRawDOMPoint::After(*previousContentOrEndContainer
)
1643 : endRef
.To
<EditorRawDOMPoint
>();
1644 MOZ_ASSERT(endPoint
.IsSet());
1645 // If the end point points after a content node, let's try to move it down
1646 // to end of the child recursively.
1647 while (nsIContent
* child
= endPoint
.GetPreviousSiblingOfChild()) {
1648 // We shouldn't cross editable and block boundary.
1649 if (!EditorUtils::IsEditableContent(*child
, EditorType::HTML
) ||
1650 HTMLEditUtils::IsBlockElement(
1651 *child
, BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
1654 // If we reach a text node, the minimized range starts from start of it.
1655 if (child
->IsText()) {
1656 endPoint
.SetToEndOf(child
);
1659 // Don't shrink the range into element which applies the style to children
1660 // because we want to update the element. E.g., if we are setting
1661 // background color, we want to update style attribute of an element which
1662 // specifies background color with `style` attribute.
1663 if (child
== aLastEntirelySelectedContentNodeInRange
) {
1666 // We should not end in an atomic element such as <br>, <img>, etc.
1667 if (!HTMLEditUtils::IsContainerNode(*child
)) {
1670 // If the element specifies the style, we should update it. Therefore, we
1671 // need to wrap it in the range.
1672 if (ContentIsElementSettingTheStyle(aHTMLEditor
, *child
)) {
1675 // If the child is an `<a>`, we should not shrink the range into it
1676 // because user may not want to keep editing in the link except when user
1677 // tries to update selection into it obviously.
1678 if (child
->IsHTMLElement(nsGkAtoms::a
)) {
1681 endPoint
.SetToEndOf(child
);
1686 EditorRawDOMPoint
HTMLEditor::AutoInlineStyleSetter::
1687 GetExtendedRangeStartToWrapAncestorApplyingSameStyle(
1688 const HTMLEditor
& aHTMLEditor
,
1689 const EditorRawDOMPoint
& aStartPoint
) const {
1690 MOZ_ASSERT(aStartPoint
.IsSetAndValid());
1692 EditorRawDOMPoint startPoint
= aStartPoint
;
1693 // FIXME: This should handle ignore invisible white-spaces before the position
1694 // if it's in a text node or invisible white-spaces.
1695 if (!startPoint
.IsStartOfContainer() ||
1696 startPoint
.GetContainer()->GetPreviousSibling()) {
1700 // FYI: Currently, we don't support setting `font-size` even in the CSS mode.
1701 // Therefore, if the style is <font size="...">, we always set a <font>.
1702 const bool isSettingFontElement
=
1703 IsStyleOfFontSize() ||
1704 (!aHTMLEditor
.IsCSSEnabled() && IsStyleOfFontElement());
1705 Element
* mostDistantStartParentHavingStyle
= nullptr;
1706 for (Element
* parent
:
1707 startPoint
.GetContainer()->InclusiveAncestorsOfType
<Element
>()) {
1708 if (!EditorUtils::IsEditableContent(*parent
, EditorType::HTML
) ||
1709 HTMLEditUtils::IsBlockElement(
1710 *parent
, BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
1711 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent
)) {
1714 if (ContentIsElementSettingTheStyle(aHTMLEditor
, *parent
)) {
1715 mostDistantStartParentHavingStyle
= parent
;
1717 // If we're setting <font> element and there is a <font> element which is
1718 // entirely selected, we should use it.
1719 else if (isSettingFontElement
&& parent
->IsHTMLElement(nsGkAtoms::font
)) {
1720 mostDistantStartParentHavingStyle
= parent
;
1722 if (parent
->GetPreviousSibling()) {
1723 break; // The parent is not first element in its parent, stop climbing.
1726 if (mostDistantStartParentHavingStyle
) {
1727 startPoint
.Set(mostDistantStartParentHavingStyle
);
1732 EditorRawDOMPoint
HTMLEditor::AutoInlineStyleSetter::
1733 GetExtendedRangeEndToWrapAncestorApplyingSameStyle(
1734 const HTMLEditor
& aHTMLEditor
,
1735 const EditorRawDOMPoint
& aEndPoint
) const {
1736 MOZ_ASSERT(aEndPoint
.IsSetAndValid());
1738 EditorRawDOMPoint endPoint
= aEndPoint
;
1739 // FIXME: This should ignore invisible white-spaces after the position if it's
1740 // in a text node, invisible <br> or following invisible text nodes.
1741 if (!endPoint
.IsEndOfContainer() ||
1742 endPoint
.GetContainer()->GetNextSibling()) {
1746 // FYI: Currently, we don't support setting `font-size` even in the CSS mode.
1747 // Therefore, if the style is <font size="...">, we always set a <font>.
1748 const bool isSettingFontElement
=
1749 IsStyleOfFontSize() ||
1750 (!aHTMLEditor
.IsCSSEnabled() && IsStyleOfFontElement());
1751 Element
* mostDistantEndParentHavingStyle
= nullptr;
1752 for (Element
* parent
:
1753 endPoint
.GetContainer()->InclusiveAncestorsOfType
<Element
>()) {
1754 if (!EditorUtils::IsEditableContent(*parent
, EditorType::HTML
) ||
1755 HTMLEditUtils::IsBlockElement(
1756 *parent
, BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
1757 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent
)) {
1760 if (ContentIsElementSettingTheStyle(aHTMLEditor
, *parent
)) {
1761 mostDistantEndParentHavingStyle
= parent
;
1763 // If we're setting <font> element and there is a <font> element which is
1764 // entirely selected, we should use it.
1765 else if (isSettingFontElement
&& parent
->IsHTMLElement(nsGkAtoms::font
)) {
1766 mostDistantEndParentHavingStyle
= parent
;
1768 if (parent
->GetNextSibling()) {
1769 break; // The parent is not last element in its parent, stop climbing.
1772 if (mostDistantEndParentHavingStyle
) {
1773 endPoint
.SetAfter(mostDistantEndParentHavingStyle
);
1778 EditorRawDOMRange
HTMLEditor::AutoInlineStyleSetter::
1779 GetExtendedRangeToMinimizeTheNumberOfNewElements(
1780 const HTMLEditor
& aHTMLEditor
, const nsINode
& aCommonAncestor
,
1781 EditorRawDOMPoint
&& aStartPoint
, EditorRawDOMPoint
&& aEndPoint
) const {
1782 MOZ_ASSERT(aStartPoint
.IsSet());
1783 MOZ_ASSERT(aEndPoint
.IsSet());
1785 // For minimizing the number of new elements, we should extend the range as
1786 // far as possible. E.g., `<span>[abc</span> <span>def]</span>` should be
1787 // styled as `<b><span>abc</span> <span>def</span></b>`.
1788 // Similarly, if the range crosses a block boundary, we should do same thing.
1789 // I.e., `<p><span>[abc</span></p><p><span>def]</span></p>` should become
1790 // `<p><b><span>abc</span></b></p><p><b><span>def</span></b></p>`.
1791 if (aStartPoint
.GetContainer() != aEndPoint
.GetContainer()) {
1792 while (aStartPoint
.GetContainer() != &aCommonAncestor
&&
1793 aStartPoint
.IsInContentNode() && aStartPoint
.GetContainerParent() &&
1794 aStartPoint
.IsStartOfContainer()) {
1795 if (!EditorUtils::IsEditableContent(
1796 *aStartPoint
.ContainerAs
<nsIContent
>(), EditorType::HTML
) ||
1797 (aStartPoint
.ContainerAs
<nsIContent
>()->IsElement() &&
1798 (HTMLEditUtils::IsBlockElement(
1799 *aStartPoint
.ContainerAs
<Element
>(),
1800 BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
1801 HTMLEditUtils::IsDisplayInsideFlowRoot(
1802 *aStartPoint
.ContainerAs
<Element
>())))) {
1805 aStartPoint
= aStartPoint
.ParentPoint();
1807 while (aEndPoint
.GetContainer() != &aCommonAncestor
&&
1808 aEndPoint
.IsInContentNode() && aEndPoint
.GetContainerParent() &&
1809 aEndPoint
.IsEndOfContainer()) {
1810 if (!EditorUtils::IsEditableContent(*aEndPoint
.ContainerAs
<nsIContent
>(),
1811 EditorType::HTML
) ||
1812 (aEndPoint
.ContainerAs
<nsIContent
>()->IsElement() &&
1813 (HTMLEditUtils::IsBlockElement(
1814 *aEndPoint
.ContainerAs
<Element
>(),
1815 BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
1816 HTMLEditUtils::IsDisplayInsideFlowRoot(
1817 *aEndPoint
.ContainerAs
<Element
>())))) {
1820 aEndPoint
.SetAfter(aEndPoint
.ContainerAs
<nsIContent
>());
1824 // Additionally, if we'll set a CSS style, we want to wrap elements which
1825 // should have the new style into the range to avoid creating new <span>
1827 if (!IsRepresentableWithHTML() ||
1828 (aHTMLEditor
.IsCSSEnabled() && IsCSSSettable(*nsGkAtoms::span
))) {
1829 // First, if pointing in a text node, use parent point.
1830 if (aStartPoint
.IsInContentNode() && aStartPoint
.IsStartOfContainer() &&
1831 aStartPoint
.GetContainerParentAs
<nsIContent
>() &&
1832 EditorUtils::IsEditableContent(
1833 *aStartPoint
.ContainerParentAs
<nsIContent
>(), EditorType::HTML
) &&
1834 (!aStartPoint
.GetContainerAs
<Element
>() ||
1835 !HTMLEditUtils::IsContainerNode(
1836 *aStartPoint
.ContainerAs
<nsIContent
>())) &&
1837 EditorUtils::IsEditableContent(*aStartPoint
.ContainerAs
<nsIContent
>(),
1838 EditorType::HTML
)) {
1839 aStartPoint
= aStartPoint
.ParentPoint();
1840 MOZ_ASSERT(aStartPoint
.IsSet());
1842 if (aEndPoint
.IsInContentNode() && aEndPoint
.IsEndOfContainer() &&
1843 aEndPoint
.GetContainerParentAs
<nsIContent
>() &&
1844 EditorUtils::IsEditableContent(
1845 *aEndPoint
.ContainerParentAs
<nsIContent
>(), EditorType::HTML
) &&
1846 (!aEndPoint
.GetContainerAs
<Element
>() ||
1847 !HTMLEditUtils::IsContainerNode(
1848 *aEndPoint
.ContainerAs
<nsIContent
>())) &&
1849 EditorUtils::IsEditableContent(*aEndPoint
.ContainerAs
<nsIContent
>(),
1850 EditorType::HTML
)) {
1851 aEndPoint
.SetAfter(aEndPoint
.GetContainer());
1852 MOZ_ASSERT(aEndPoint
.IsSet());
1854 // Then, wrap the container if it's a good element to set a CSS property.
1855 if (aStartPoint
.IsInContentNode() && aStartPoint
.GetContainerParent() &&
1856 // The point must be start of the container
1857 aStartPoint
.IsStartOfContainer() &&
1858 // only if the pointing first child node cannot have `style` attribute
1859 (!aStartPoint
.GetChildAs
<nsStyledElement
>() ||
1860 !ElementIsGoodContainerToSetStyle(
1861 *aStartPoint
.ChildAs
<nsStyledElement
>())) &&
1862 // but don't cross block boundary at climbing up the tree
1863 !HTMLEditUtils::IsBlockElement(
1864 *aStartPoint
.ContainerAs
<nsIContent
>(),
1865 BlockInlineCheck::UseComputedDisplayOutsideStyle
) &&
1866 // and the container is a good editable element to set CSS style
1867 aStartPoint
.GetContainerAs
<nsStyledElement
>() &&
1868 ElementIsGoodContainerToSetStyle(
1869 *aStartPoint
.ContainerAs
<nsStyledElement
>())) {
1870 aStartPoint
= aStartPoint
.ParentPoint();
1871 MOZ_ASSERT(aStartPoint
.IsSet());
1873 if (aEndPoint
.IsInContentNode() && aEndPoint
.GetContainerParent() &&
1874 // The point must be end of the container
1875 aEndPoint
.IsEndOfContainer() &&
1876 // only if the pointing last child node cannot have `style` attribute
1877 (aEndPoint
.IsStartOfContainer() ||
1878 !aEndPoint
.GetPreviousSiblingOfChildAs
<nsStyledElement
>() ||
1879 !ElementIsGoodContainerToSetStyle(
1880 *aEndPoint
.GetPreviousSiblingOfChildAs
<nsStyledElement
>())) &&
1881 // but don't cross block boundary at climbing up the tree
1882 !HTMLEditUtils::IsBlockElement(
1883 *aEndPoint
.ContainerAs
<nsIContent
>(),
1884 BlockInlineCheck::UseComputedDisplayOutsideStyle
) &&
1885 // and the container is a good editable element to set CSS style
1886 aEndPoint
.GetContainerAs
<nsStyledElement
>() &&
1887 ElementIsGoodContainerToSetStyle(
1888 *aEndPoint
.ContainerAs
<nsStyledElement
>())) {
1889 aEndPoint
.SetAfter(aEndPoint
.GetContainer());
1890 MOZ_ASSERT(aEndPoint
.IsSet());
1894 return EditorRawDOMRange(std::move(aStartPoint
), std::move(aEndPoint
));
1897 Result
<EditorRawDOMRange
, nsresult
>
1898 HTMLEditor::AutoInlineStyleSetter::ExtendOrShrinkRangeToApplyTheStyle(
1899 const HTMLEditor
& aHTMLEditor
, const EditorDOMRange
& aRange
,
1900 const Element
& aEditingHost
) const {
1901 if (NS_WARN_IF(!aRange
.IsPositioned())) {
1902 return Err(NS_ERROR_FAILURE
);
1905 // For avoiding assertion hits in the utility methods, check whether the
1906 // range is in same subtree, first. Even if the range crosses a subtree
1907 // boundary, it's not a bug of this module.
1908 nsINode
* commonAncestor
= aRange
.GetClosestCommonInclusiveAncestor();
1909 if (NS_WARN_IF(!commonAncestor
)) {
1910 return Err(NS_ERROR_FAILURE
);
1913 // If the range does not select only invisible <br> element, let's extend the
1914 // range to contain the <br> element.
1915 EditorDOMRange
range(aRange
);
1916 if (range
.EndRef().IsInContentNode()) {
1917 WSScanResult nextContentData
=
1918 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
1919 &aEditingHost
, range
.EndRef(),
1920 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
1921 if (nextContentData
.ReachedInvisibleBRElement() &&
1922 nextContentData
.BRElementPtr()->GetParentElement() &&
1923 HTMLEditUtils::IsInlineContent(
1924 *nextContentData
.BRElementPtr()->GetParentElement(),
1925 BlockInlineCheck::UseComputedDisplayOutsideStyle
)) {
1926 range
.SetEnd(EditorDOMPoint::After(*nextContentData
.BRElementPtr()));
1927 MOZ_ASSERT(range
.EndRef().IsSet());
1928 commonAncestor
= range
.GetClosestCommonInclusiveAncestor();
1929 if (NS_WARN_IF(!commonAncestor
)) {
1930 return Err(NS_ERROR_FAILURE
);
1935 // If the range is collapsed, we don't want to replace ancestors unless it's
1936 // in an empty element.
1937 if (range
.Collapsed() && range
.StartRef().GetContainer()->Length()) {
1938 return EditorRawDOMRange(range
);
1941 // First, shrink the given range to minimize new style applied contents.
1942 // However, we should not shrink the range into entirely selected element.
1943 // E.g., if `abc[<i>def</i>]ghi`, shouldn't shrink it as
1944 // `abc<i>[def]</i>ghi`.
1945 EditorRawDOMPoint startPoint
, endPoint
;
1946 if (range
.Collapsed()) {
1947 startPoint
= endPoint
= range
.StartRef().To
<EditorRawDOMPoint
>();
1949 ContentSubtreeIterator iter
;
1950 if (NS_FAILED(iter
.Init(range
.StartRef().ToRawRangeBoundary(),
1951 range
.EndRef().ToRawRangeBoundary()))) {
1952 NS_WARNING("ContentSubtreeIterator::Init() failed");
1953 return Err(NS_ERROR_FAILURE
);
1955 nsIContent
* const firstContentEntirelyInRange
=
1956 nsIContent::FromNodeOrNull(iter
.GetCurrentNode());
1957 nsIContent
* const lastContentEntirelyInRange
= [&]() {
1959 return nsIContent::FromNodeOrNull(iter
.GetCurrentNode());
1962 // Compute the shrunken range boundaries.
1963 startPoint
= GetShrunkenRangeStart(aHTMLEditor
, range
, *commonAncestor
,
1964 firstContentEntirelyInRange
);
1965 MOZ_ASSERT(startPoint
.IsSet());
1966 endPoint
= GetShrunkenRangeEnd(aHTMLEditor
, range
, *commonAncestor
,
1967 lastContentEntirelyInRange
);
1968 MOZ_ASSERT(endPoint
.IsSet());
1970 // If shrunken range is swapped, it could like this case:
1971 // `abc[</span><span>]def`, starts at very end of a node and ends at
1972 // very start of immediately next node. In this case, we should use
1973 // the original range instead.
1974 if (MOZ_UNLIKELY(!startPoint
.EqualsOrIsBefore(endPoint
))) {
1975 startPoint
= range
.StartRef().To
<EditorRawDOMPoint
>();
1976 endPoint
= range
.EndRef().To
<EditorRawDOMPoint
>();
1980 // Then, we may need to extend the range to wrap parent inline elements
1981 // which specify same style since we need to remove same style elements to
1982 // apply new value. E.g., abc
1983 // <span style="background-color: red">
1984 // <span style="background-color: blue">[def]</span>
1987 // In this case, we need to wrap the other <span> element if setting
1988 // background color. Then, the inner <span> element is removed and the
1989 // other <span> element's style attribute will be updated rather than
1990 // inserting new <span> element.
1991 startPoint
= GetExtendedRangeStartToWrapAncestorApplyingSameStyle(aHTMLEditor
,
1993 MOZ_ASSERT(startPoint
.IsSet());
1995 GetExtendedRangeEndToWrapAncestorApplyingSameStyle(aHTMLEditor
, endPoint
);
1996 MOZ_ASSERT(endPoint
.IsSet());
1998 // Finally, we need to extend the range unless the range is in an element to
1999 // reduce the number of creating new elements. E.g., if now selects
2000 // `<span>[abc</span><span>def]</span>`, we should make it
2001 // `<b><span>abc</span><span>def</span></b>` rather than
2002 // `<span><b>abc</b></span><span><b>def</b></span>`.
2003 EditorRawDOMRange finalRange
=
2004 GetExtendedRangeToMinimizeTheNumberOfNewElements(
2005 aHTMLEditor
, *commonAncestor
, std::move(startPoint
),
2006 std::move(endPoint
));
2009 "ExtendOrShrinkRangeToApplyTheStyle:\n"
2010 " Result: {(\n %s\n ) - (\n %s\n )},\n"
2011 " Input: {(\n %s\n ) - (\n %s\n )}\n",
2012 ToString(finalRange
.StartRef()).c_str(),
2013 ToString(finalRange
.EndRef()).c_str(),
2014 ToString(aRange
.StartRef()).c_str(),
2015 ToString(aRange
.EndRef()).c_str());
2020 Result
<SplitRangeOffResult
, nsresult
>
2021 HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
2022 const EditorDOMRange
& aRange
, const EditorInlineStyle
& aStyle
,
2023 SplitAtEdges aSplitAtEdges
) {
2024 MOZ_ASSERT(IsEditActionDataAvailable());
2026 if (NS_WARN_IF(!aRange
.IsPositioned())) {
2027 return Err(NS_ERROR_FAILURE
);
2030 EditorDOMRange
range(aRange
);
2032 // split any matching style nodes above the start of range
2033 auto resultAtStart
=
2034 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<SplitNodeResult
, nsresult
> {
2035 AutoTrackDOMRange
tracker(RangeUpdaterRef(), &range
);
2036 Result
<SplitNodeResult
, nsresult
> result
=
2037 SplitAncestorStyledInlineElementsAt(range
.StartRef(), aStyle
,
2039 if (MOZ_UNLIKELY(result
.isErr())) {
2040 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
2043 tracker
.FlushAndStopTracking();
2044 if (result
.inspect().Handled()) {
2045 auto startOfRange
= result
.inspect().AtSplitPoint
<EditorDOMPoint
>();
2046 if (!startOfRange
.IsSet()) {
2047 result
.inspect().IgnoreCaretPointSuggestion();
2049 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
2051 return Err(NS_ERROR_FAILURE
);
2053 range
.SetStart(std::move(startOfRange
));
2054 } else if (MOZ_UNLIKELY(!range
.IsPositioned())) {
2056 "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected "
2058 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2062 if (MOZ_UNLIKELY(resultAtStart
.isErr())) {
2063 return resultAtStart
.propagateErr();
2065 SplitNodeResult unwrappedResultAtStart
= resultAtStart
.unwrap();
2067 // second verse, same as the first...
2069 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<SplitNodeResult
, nsresult
> {
2070 AutoTrackDOMRange
tracker(RangeUpdaterRef(), &range
);
2071 Result
<SplitNodeResult
, nsresult
> result
=
2072 SplitAncestorStyledInlineElementsAt(range
.EndRef(), aStyle
,
2074 if (MOZ_UNLIKELY(result
.isErr())) {
2075 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
2078 tracker
.FlushAndStopTracking();
2079 if (result
.inspect().Handled()) {
2080 auto endOfRange
= result
.inspect().AtSplitPoint
<EditorDOMPoint
>();
2081 if (!endOfRange
.IsSet()) {
2082 result
.inspect().IgnoreCaretPointSuggestion();
2084 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
2086 return Err(NS_ERROR_FAILURE
);
2088 range
.SetEnd(std::move(endOfRange
));
2089 } else if (MOZ_UNLIKELY(!range
.IsPositioned())) {
2091 "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected "
2093 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
2097 if (MOZ_UNLIKELY(resultAtEnd
.isErr())) {
2098 unwrappedResultAtStart
.IgnoreCaretPointSuggestion();
2099 return resultAtEnd
.propagateErr();
2102 return SplitRangeOffResult(std::move(range
),
2103 std::move(unwrappedResultAtStart
),
2104 resultAtEnd
.unwrap());
2107 Result
<SplitNodeResult
, nsresult
>
2108 HTMLEditor::SplitAncestorStyledInlineElementsAt(
2109 const EditorDOMPoint
& aPointToSplit
, const EditorInlineStyle
& aStyle
,
2110 SplitAtEdges aSplitAtEdges
) {
2111 // If the point is in a non-content node, e.g., in the document node, we
2112 // should split nothing.
2113 if (MOZ_UNLIKELY(!aPointToSplit
.IsInContentNode())) {
2114 return SplitNodeResult::NotHandled(aPointToSplit
);
2117 // We assume that this method is called only when we're removing style(s).
2118 // Even if we're in HTML mode and there is no presentation element in the
2119 // block, we may need to overwrite the block's style with `<span>` element
2120 // and CSS. For example, `<h1>` element has `font-weight: bold;` as its
2121 // default style. If `Document.execCommand("bold")` is called for its
2122 // text, we should make it unbold. Therefore, we shouldn't check
2123 // IsCSSEnabled() in most cases. However, there is an exception.
2124 // FontFaceStateCommand::SetState() calls RemoveInlinePropertyAsAction()
2125 // with nsGkAtoms::tt before calling SetInlinePropertyAsAction() if we
2126 // are handling a XUL command. Only in that case, we need to check
2128 const bool handleCSS
=
2129 aStyle
.mHTMLProperty
!= nsGkAtoms::tt
|| IsCSSEnabled();
2131 AutoTArray
<OwningNonNull
<Element
>, 24> arrayOfParents
;
2132 for (Element
* element
:
2133 aPointToSplit
.GetContainer()->InclusiveAncestorsOfType
<Element
>()) {
2134 if (element
->IsAnyOfHTMLElements(nsGkAtoms::body
, nsGkAtoms::head
,
2136 HTMLEditUtils::IsBlockElement(
2137 *element
, BlockInlineCheck::UseComputedDisplayOutsideStyle
) ||
2138 !element
->GetParent() ||
2139 !EditorUtils::IsEditableContent(*element
->GetParent(),
2140 EditorType::HTML
) ||
2141 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*element
))) {
2144 arrayOfParents
.AppendElement(*element
);
2147 // Split any matching style nodes above the point.
2148 SplitNodeResult result
= SplitNodeResult::NotHandled(aPointToSplit
);
2149 MOZ_ASSERT(!result
.Handled());
2150 EditorDOMPoint pointToPutCaret
;
2151 for (OwningNonNull
<Element
>& element
: arrayOfParents
) {
2152 auto isSetByCSSOrError
= [&]() -> Result
<bool, nsresult
> {
2156 // The HTML style defined by aStyle has a CSS equivalence in this
2157 // implementation for the node; let's check if it carries those CSS
2159 if (aStyle
.IsCSSRemovable(*element
)) {
2160 nsAutoString firstValue
;
2161 Result
<bool, nsresult
> isSpecifiedByCSSOrError
=
2162 CSSEditUtils::IsSpecifiedCSSEquivalentTo(*this, *element
, aStyle
,
2164 if (MOZ_UNLIKELY(isSpecifiedByCSSOrError
.isErr())) {
2165 result
.IgnoreCaretPointSuggestion();
2166 NS_WARNING("CSSEditUtils::IsSpecifiedCSSEquivalentTo() failed");
2167 return isSpecifiedByCSSOrError
;
2169 if (isSpecifiedByCSSOrError
.unwrap()) {
2173 // If this is <sub> or <sup>, we won't use vertical-align CSS property
2174 // because <sub>/<sup> changes font size but neither `vertical-align:
2175 // sub` nor `vertical-align: super` changes it (bug 394304 comment 2).
2176 // Therefore, they are not equivalents. However, they're obviously
2177 // conflict with vertical-align style. Thus, we need to remove ancestor
2178 // elements having vertical-align style.
2179 if (aStyle
.IsStyleConflictingWithVerticalAlign()) {
2181 nsresult rv
= CSSEditUtils::GetSpecifiedProperty(
2182 *element
, *nsGkAtoms::vertical_align
, value
);
2183 if (NS_FAILED(rv
)) {
2184 NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed");
2185 result
.IgnoreCaretPointSuggestion();
2188 if (!value
.IsEmpty()) {
2194 if (MOZ_UNLIKELY(isSetByCSSOrError
.isErr())) {
2195 return isSetByCSSOrError
.propagateErr();
2197 if (!isSetByCSSOrError
.inspect()) {
2198 if (!aStyle
.IsStyleToClearAllInlineStyles()) {
2199 // If we're removing a link style and the element is an <a href>, we
2200 // need to split it.
2201 if (aStyle
.mHTMLProperty
== nsGkAtoms::href
&&
2202 HTMLEditUtils::IsLink(element
)) {
2204 // If we're removing HTML style, we should split only the element
2205 // which represents the style.
2206 else if (!element
->IsHTMLElement(aStyle
.mHTMLProperty
) ||
2207 (aStyle
.mAttribute
&& !element
->HasAttr(aStyle
.mAttribute
))) {
2210 // If we're setting <font> related styles, it means that we're not
2211 // toggling the style. In this case, we need to remove parent <font>
2212 // elements and/or update parent <font> elements if there are some
2213 // elements which have the attribute. However, we should not touch if
2214 // the value is same as what the caller setting to keep the DOM tree
2215 // as-is as far as possible.
2216 if (aStyle
.IsStyleOfFontElement() && aStyle
.MaybeHasValue()) {
2217 const nsAttrValue
* const attrValue
=
2218 element
->GetParsedAttr(aStyle
.mAttribute
);
2220 if (aStyle
.mAttribute
== nsGkAtoms::size
) {
2221 if (nsContentUtils::ParseLegacyFontSize(
2222 aStyle
.AsInlineStyleAndValue().mAttributeValue
) ==
2223 attrValue
->GetIntegerValue()) {
2226 } else if (aStyle
.mAttribute
== nsGkAtoms::color
) {
2227 nsAttrValue newValue
;
2228 nscolor oldColor
, newColor
;
2229 if (attrValue
->GetColorValue(oldColor
) &&
2230 newValue
.ParseColor(
2231 aStyle
.AsInlineStyleAndValue().mAttributeValue
) &&
2232 newValue
.GetColorValue(newColor
) && oldColor
== newColor
) {
2235 } else if (attrValue
->Equals(
2236 aStyle
.AsInlineStyleAndValue().mAttributeValue
,
2243 // If aProperty is nullptr, we need to split any style.
2244 else if (!EditorUtils::IsEditableContent(element
, EditorType::HTML
) ||
2245 !HTMLEditUtils::IsRemovableInlineStyleElement(*element
)) {
2250 // Found a style node we need to split.
2251 // XXX If first content is a text node and CSS is enabled, we call this
2252 // with text node but in such case, this does nothing, but returns
2253 // as handled with setting only previous or next node. If its parent
2254 // is a block, we do nothing but return as handled.
2255 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret
);
2256 Result
<SplitNodeResult
, nsresult
> splitNodeResult
=
2257 SplitNodeDeepWithTransaction(MOZ_KnownLive(element
),
2258 result
.AtSplitPoint
<EditorDOMPoint
>(),
2260 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
2261 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
2262 return splitNodeResult
;
2264 SplitNodeResult unwrappedSplitNodeResult
= splitNodeResult
.unwrap();
2265 trackPointToPutCaret
.FlushAndStopTracking();
2266 unwrappedSplitNodeResult
.MoveCaretPointTo(
2267 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2269 // If it's not handled, it means that `content` is not a splittable node
2270 // like a void element even if it has some children, and the split point
2272 if (!unwrappedSplitNodeResult
.Handled()) {
2275 // Respect the last split result which actually did it.
2276 if (!result
.DidSplit() || unwrappedSplitNodeResult
.DidSplit()) {
2277 result
= unwrappedSplitNodeResult
.ToHandledResult();
2279 MOZ_ASSERT(result
.Handled());
2282 return pointToPutCaret
.IsSet()
2283 ? SplitNodeResult(std::move(result
), std::move(pointToPutCaret
))
2284 : std::move(result
);
2287 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::ClearStyleAt(
2288 const EditorDOMPoint
& aPoint
, const EditorInlineStyle
& aStyleToRemove
,
2289 SpecifiedStyle aSpecifiedStyle
) {
2290 MOZ_ASSERT(IsEditActionDataAvailable());
2292 if (NS_WARN_IF(!aPoint
.IsSet())) {
2293 return Err(NS_ERROR_INVALID_ARG
);
2296 // TODO: We should rewrite this to stop unnecessary element creation and
2297 // deleting it later because it causes the original element may be
2298 // removed from the DOM tree even if same element is still in the
2299 // DOM tree from point of view of users.
2301 // First, split inline elements at the point.
2302 // E.g., if aStyleToRemove.mHTMLProperty is nsGkAtoms::b and
2303 // `<p><b><i>a[]bc</i></b></p>`, we want to make it as
2304 // `<p><b><i>a</i></b><b><i>bc</i></b></p>`.
2305 EditorDOMPoint
pointToPutCaret(aPoint
);
2306 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret
);
2307 Result
<SplitNodeResult
, nsresult
> splitNodeResult
=
2308 SplitAncestorStyledInlineElementsAt(
2309 aPoint
, aStyleToRemove
, SplitAtEdges::eAllowToCreateEmptyContainer
);
2310 if (MOZ_UNLIKELY(splitNodeResult
.isErr())) {
2311 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
2312 return splitNodeResult
.propagateErr();
2314 trackPointToPutCaret
.FlushAndStopTracking();
2315 SplitNodeResult unwrappedSplitNodeResult
= splitNodeResult
.unwrap();
2316 unwrappedSplitNodeResult
.MoveCaretPointTo(
2317 pointToPutCaret
, *this,
2318 {SuggestCaret::OnlyIfHasSuggestion
,
2319 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2321 // If there is no styled inline elements of aStyleToRemove, we just return the
2323 // E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b.
2324 if (!unwrappedSplitNodeResult
.Handled()) {
2325 return pointToPutCaret
;
2328 // If it did split nodes, but topmost ancestor inline element is split
2329 // at start of it, we don't need the empty inline element. Let's remove
2330 // it now. Then, we'll get the following DOM tree if there is no "a" in the
2332 // <p><b><i>bc</i></b></p>
2334 if (unwrappedSplitNodeResult
.GetPreviousContent() &&
2335 HTMLEditUtils::IsEmptyNode(
2336 *unwrappedSplitNodeResult
.GetPreviousContent(),
2337 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
2338 EmptyCheckOption::TreatListItemAsVisible
,
2339 EmptyCheckOption::TreatTableCellAsVisible
})) {
2340 AutoTrackDOMPoint
trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret
);
2341 // Delete previous node if it's empty.
2342 // MOZ_KnownLive(unwrappedSplitNodeResult.GetPreviousContent()):
2343 // It's grabbed by unwrappedSplitNodeResult.
2344 nsresult rv
= DeleteNodeWithTransaction(
2345 MOZ_KnownLive(*unwrappedSplitNodeResult
.GetPreviousContent()));
2346 if (NS_FAILED(rv
)) {
2347 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2352 // If we reached block from end of a text node, we can do nothing here.
2353 // E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and
2354 // we're in CSS mode.
2355 // XXX Chrome resets block style and creates `<span>` elements for each
2356 // line in this case.
2357 if (!unwrappedSplitNodeResult
.GetNextContent()) {
2358 return pointToPutCaret
;
2361 // Otherwise, the next node is topmost ancestor inline element which has
2362 // the style. We want to put caret between the split nodes, but we need
2363 // to keep other styles. Therefore, next, we need to split at start of
2364 // the next node. The first example should become
2365 // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`.
2367 nsIContent
* firstLeafChildOfNextNode
= HTMLEditUtils::GetFirstLeafContent(
2368 *unwrappedSplitNodeResult
.GetNextContent(), {LeafNodeType::OnlyLeafNode
});
2369 EditorDOMPoint
atStartOfNextNode(
2370 firstLeafChildOfNextNode
? firstLeafChildOfNextNode
2371 : unwrappedSplitNodeResult
.GetNextContent(),
2373 RefPtr
<HTMLBRElement
> brElement
;
2374 // But don't try to split non-containers like `<br>`, `<hr>` and `<img>`
2376 if (!atStartOfNextNode
.IsInContentNode() ||
2377 !HTMLEditUtils::IsContainerNode(
2378 *atStartOfNextNode
.ContainerAs
<nsIContent
>())) {
2379 // If it's a `<br>` element, let's move it into new node later.
2380 brElement
= HTMLBRElement::FromNode(atStartOfNextNode
.GetContainer());
2381 if (!atStartOfNextNode
.GetContainerParentAs
<nsIContent
>()) {
2382 NS_WARNING("atStartOfNextNode was in an orphan node");
2383 return Err(NS_ERROR_FAILURE
);
2385 atStartOfNextNode
.Set(atStartOfNextNode
.GetContainerParent(), 0);
2387 AutoTrackDOMPoint
trackPointToPutCaret2(RangeUpdaterRef(), &pointToPutCaret
);
2388 Result
<SplitNodeResult
, nsresult
> splitResultAtStartOfNextNode
=
2389 SplitAncestorStyledInlineElementsAt(
2390 atStartOfNextNode
, aStyleToRemove
,
2391 SplitAtEdges::eAllowToCreateEmptyContainer
);
2392 if (MOZ_UNLIKELY(splitResultAtStartOfNextNode
.isErr())) {
2393 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
2394 return splitResultAtStartOfNextNode
.propagateErr();
2396 trackPointToPutCaret2
.FlushAndStopTracking();
2397 SplitNodeResult unwrappedSplitResultAtStartOfNextNode
=
2398 splitResultAtStartOfNextNode
.unwrap();
2399 unwrappedSplitResultAtStartOfNextNode
.MoveCaretPointTo(
2400 pointToPutCaret
, *this,
2401 {SuggestCaret::OnlyIfHasSuggestion
,
2402 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2404 if (unwrappedSplitResultAtStartOfNextNode
.Handled() &&
2405 unwrappedSplitResultAtStartOfNextNode
.GetNextContent()) {
2406 // If the right inline elements are empty, we should remove them. E.g.,
2407 // if the split point is at end of a text node (or end of an inline
2408 // element), e.g., <div><b><i>abc[]</i></b></div>, then now, it's been
2410 // <div><b><i>abc</i></b><b><i>[]</i></b><b><i></i></b></div>
2412 // We will change it to:
2413 // <div><b><i>abc</i></b><b><i>[]</i></b></div>
2415 // And if it has only padding <br> element, we should move it into the
2416 // previous <i> which will have new content.
2417 bool seenBR
= false;
2418 if (HTMLEditUtils::IsEmptyNode(
2419 *unwrappedSplitResultAtStartOfNextNode
.GetNextContent(),
2420 {EmptyCheckOption::TreatListItemAsVisible
,
2421 EmptyCheckOption::TreatTableCellAsVisible
},
2423 if (seenBR
&& !brElement
) {
2424 brElement
= HTMLEditUtils::GetFirstBRElement(
2425 *unwrappedSplitResultAtStartOfNextNode
.GetNextContentAs
<Element
>());
2427 // Once we remove <br> element's parent, we lose the rights to remove it
2428 // from the parent because the parent becomes not editable. Therefore, we
2429 // need to delete the <br> element before removing its parents for reusing
2432 nsresult rv
= DeleteNodeWithTransaction(*brElement
);
2433 if (NS_FAILED(rv
)) {
2434 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2438 // Delete next node if it's empty.
2439 // MOZ_KnownLive because of grabbed by
2440 // unwrappedSplitResultAtStartOfNextNode.
2441 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(
2442 *unwrappedSplitResultAtStartOfNextNode
.GetNextContent()));
2443 if (NS_FAILED(rv
)) {
2444 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2450 if (!unwrappedSplitResultAtStartOfNextNode
.Handled()) {
2451 return std::move(pointToPutCaret
);
2454 // If there is no content, we should return here.
2455 // XXX Is this possible case without mutation event listener?
2456 if (!unwrappedSplitResultAtStartOfNextNode
.GetPreviousContent()) {
2457 // XXX This is really odd, but we return this value...
2458 const auto splitPoint
=
2459 unwrappedSplitNodeResult
.AtSplitPoint
<EditorRawDOMPoint
>();
2460 const auto splitPointAtStartOfNextNode
=
2461 unwrappedSplitResultAtStartOfNextNode
.AtSplitPoint
<EditorRawDOMPoint
>();
2462 return EditorDOMPoint(splitPoint
.GetContainer(),
2463 splitPointAtStartOfNextNode
.Offset());
2466 // Now, we want to put `<br>` element into the empty split node if
2467 // it was in next node of the first split.
2468 // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>`
2469 nsIContent
* firstLeafChildOfPreviousNode
= HTMLEditUtils::GetFirstLeafContent(
2470 *unwrappedSplitResultAtStartOfNextNode
.GetPreviousContent(),
2471 {LeafNodeType::OnlyLeafNode
});
2472 pointToPutCaret
.Set(
2473 firstLeafChildOfPreviousNode
2474 ? firstLeafChildOfPreviousNode
2475 : unwrappedSplitResultAtStartOfNextNode
.GetPreviousContent(),
2478 // If the right node starts with a `<br>`, suck it out of right node and into
2479 // the left node left node. This is so we you don't revert back to the
2480 // previous style if you happen to click at the end of a line.
2482 if (brElement
->GetParentNode()) {
2483 Result
<MoveNodeResult
, nsresult
> moveBRElementResult
=
2484 MoveNodeWithTransaction(*brElement
, pointToPutCaret
);
2485 if (MOZ_UNLIKELY(moveBRElementResult
.isErr())) {
2486 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
2487 return moveBRElementResult
.propagateErr();
2489 moveBRElementResult
.unwrap().MoveCaretPointTo(
2490 pointToPutCaret
, *this,
2491 {SuggestCaret::OnlyIfHasSuggestion
,
2492 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2494 Result
<CreateElementResult
, nsresult
> insertBRElementResult
=
2495 InsertNodeWithTransaction
<Element
>(*brElement
, pointToPutCaret
);
2496 if (MOZ_UNLIKELY(insertBRElementResult
.isErr())) {
2497 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
2498 return insertBRElementResult
.propagateErr();
2500 insertBRElementResult
.unwrap().MoveCaretPointTo(
2501 pointToPutCaret
, *this,
2502 {SuggestCaret::OnlyIfHasSuggestion
,
2503 SuggestCaret::OnlyIfTransactionsAllowedToDoIt
});
2506 if (unwrappedSplitResultAtStartOfNextNode
.GetNextContent() &&
2507 unwrappedSplitResultAtStartOfNextNode
.GetNextContent()
2508 ->IsInComposedDoc()) {
2509 // If we split inline elements at immediately before <br> element which is
2510 // the last visible content in the right element, we don't need the right
2511 // element anymore. Otherwise, we'll create the following DOM tree:
2512 // - <b>abc</b>{}<br><b></b>
2514 // - <b><i>abc</i></b><i><br></i><b></b>
2516 if (HTMLEditUtils::IsEmptyNode(
2517 *unwrappedSplitResultAtStartOfNextNode
.GetNextContent(),
2518 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
2519 EmptyCheckOption::TreatListItemAsVisible
,
2520 EmptyCheckOption::TreatTableCellAsVisible
})) {
2521 // MOZ_KnownLive because the result is grabbed by
2522 // unwrappedSplitResultAtStartOfNextNode.
2523 nsresult rv
= DeleteNodeWithTransaction(MOZ_KnownLive(
2524 *unwrappedSplitResultAtStartOfNextNode
.GetNextContent()));
2525 if (NS_FAILED(rv
)) {
2526 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2530 // If the next content has only one <br> element, there may be empty
2531 // inline elements around it. We don't need them anymore because user
2532 // cannot put caret into them. E.g., <b><i>abc[]<br></i><br></b> has
2533 // been changed to <b><i>abc</i></b><i>{}<br></i><b><i></i><br></b> now.
2534 // ^^^^^^^^^^^^^^^^^^
2535 // We don't need the empty <i>.
2536 else if (HTMLEditUtils::IsEmptyNode(
2537 *unwrappedSplitResultAtStartOfNextNode
.GetNextContent(),
2538 {EmptyCheckOption::TreatListItemAsVisible
,
2539 EmptyCheckOption::TreatTableCellAsVisible
})) {
2540 AutoTArray
<OwningNonNull
<nsIContent
>, 4> emptyInlineContainerElements
;
2541 HTMLEditUtils::CollectEmptyInlineContainerDescendants(
2542 *unwrappedSplitResultAtStartOfNextNode
.GetNextContentAs
<Element
>(),
2543 emptyInlineContainerElements
,
2544 {EmptyCheckOption::TreatSingleBRElementAsVisible
,
2545 EmptyCheckOption::TreatListItemAsVisible
,
2546 EmptyCheckOption::TreatTableCellAsVisible
},
2547 BlockInlineCheck::UseComputedDisplayOutsideStyle
);
2548 for (const OwningNonNull
<nsIContent
>& emptyInlineContainerElement
:
2549 emptyInlineContainerElements
) {
2550 // MOZ_KnownLive(emptyInlineContainerElement) due to bug 1622253.
2551 nsresult rv
= DeleteNodeWithTransaction(
2552 MOZ_KnownLive(emptyInlineContainerElement
));
2553 if (NS_FAILED(rv
)) {
2554 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2561 // Update the child.
2562 pointToPutCaret
.Set(pointToPutCaret
.GetContainer(), 0);
2564 // Finally, remove the specified style in the previous node at the
2565 // second split and tells good insertion point to the caller. I.e., we
2566 // want to make the first example as:
2567 // `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>`
2569 if (auto* const previousElementOfSplitPoint
=
2570 unwrappedSplitResultAtStartOfNextNode
2571 .GetPreviousContentAs
<Element
>()) {
2572 // Track the point at the new hierarchy. This is so we can know where
2573 // to put the selection after we call RemoveStyleInside().
2574 // RemoveStyleInside() could remove any and all of those nodes, so I
2575 // have to use the range tracking system to find the right spot to put
2577 AutoTrackDOMPoint
tracker(RangeUpdaterRef(), &pointToPutCaret
);
2578 // MOZ_KnownLive(previousElementOfSplitPoint):
2579 // It's grabbed by unwrappedSplitResultAtStartOfNextNode.
2580 Result
<EditorDOMPoint
, nsresult
> removeStyleResult
=
2581 RemoveStyleInside(MOZ_KnownLive(*previousElementOfSplitPoint
),
2582 aStyleToRemove
, aSpecifiedStyle
);
2583 if (MOZ_UNLIKELY(removeStyleResult
.isErr())) {
2584 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
2585 return removeStyleResult
;
2587 // We've already computed a suggested caret position at start of first leaf
2588 // which is stored in pointToPutCaret, so we don't need to update it here.
2590 return pointToPutCaret
;
2593 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::RemoveStyleInside(
2594 Element
& aElement
, const EditorInlineStyle
& aStyleToRemove
,
2595 SpecifiedStyle aSpecifiedStyle
) {
2596 // First, handle all descendants.
2597 AutoTArray
<OwningNonNull
<nsIContent
>, 32> arrayOfChildContents
;
2598 HTMLEditUtils::CollectAllChildren(aElement
, arrayOfChildContents
);
2599 EditorDOMPoint pointToPutCaret
;
2600 for (const OwningNonNull
<nsIContent
>& child
: arrayOfChildContents
) {
2601 if (!child
->IsElement()) {
2604 Result
<EditorDOMPoint
, nsresult
> removeStyleResult
= RemoveStyleInside(
2605 MOZ_KnownLive(*child
->AsElement()), aStyleToRemove
, aSpecifiedStyle
);
2606 if (MOZ_UNLIKELY(removeStyleResult
.isErr())) {
2607 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
2608 return removeStyleResult
;
2610 if (removeStyleResult
.inspect().IsSet()) {
2611 pointToPutCaret
= removeStyleResult
.unwrap();
2615 // TODO: It seems that if aElement is not editable, we should insert new
2616 // container to remove the style if possible.
2617 if (!EditorUtils::IsEditableContent(aElement
, EditorType::HTML
)) {
2618 return pointToPutCaret
;
2621 // Next, remove CSS style first. Then, `style` attribute will be removed if
2622 // the corresponding CSS property is last one.
2623 auto isStyleSpecifiedOrError
= [&]() -> Result
<bool, nsresult
> {
2624 if (!aStyleToRemove
.IsCSSRemovable(aElement
)) {
2627 MOZ_ASSERT(!aStyleToRemove
.IsStyleToClearAllInlineStyles());
2628 Result
<bool, nsresult
> elementHasSpecifiedCSSEquivalentStylesOrError
=
2629 CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(*this, aElement
,
2631 NS_WARNING_ASSERTION(
2632 elementHasSpecifiedCSSEquivalentStylesOrError
.isOk(),
2633 "CSSEditUtils::HaveSpecifiedCSSEquivalentStyles() failed");
2634 return elementHasSpecifiedCSSEquivalentStylesOrError
;
2636 if (MOZ_UNLIKELY(isStyleSpecifiedOrError
.isErr())) {
2637 return isStyleSpecifiedOrError
.propagateErr();
2639 bool styleSpecified
= isStyleSpecifiedOrError
.unwrap();
2640 if (nsStyledElement
* styledElement
= nsStyledElement::FromNode(&aElement
)) {
2641 if (styleSpecified
) {
2642 // MOZ_KnownLive(*styledElement) because it's an alias of aElement.
2643 nsresult rv
= CSSEditUtils::RemoveCSSEquivalentToStyle(
2644 WithTransaction::Yes
, *this, MOZ_KnownLive(*styledElement
),
2645 aStyleToRemove
, nullptr);
2646 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
2647 return Err(NS_ERROR_EDITOR_DESTROYED
);
2649 NS_WARNING_ASSERTION(
2651 "CSSEditUtils::RemoveCSSEquivalentToStyle() failed, but ignored");
2654 // If the style is <sub> or <sup>, we won't use vertical-align CSS
2655 // property because <sub>/<sup> changes font size but neither
2656 // `vertical-align: sub` nor `vertical-align: super` changes it
2657 // (bug 394304 comment 2). Therefore, they are not equivalents. However,
2658 // they're obviously conflict with vertical-align style. Thus, we need to
2659 // remove the vertical-align style from elements.
2660 if (aStyleToRemove
.IsStyleConflictingWithVerticalAlign()) {
2662 nsresult rv
= CSSEditUtils::GetSpecifiedProperty(
2663 aElement
, *nsGkAtoms::vertical_align
, value
);
2664 if (NS_FAILED(rv
)) {
2665 NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed");
2668 if (!value
.IsEmpty()) {
2669 // MOZ_KnownLive(*styledElement) because it's an alias of aElement.
2670 nsresult rv
= CSSEditUtils::RemoveCSSPropertyWithTransaction(
2671 *this, MOZ_KnownLive(*styledElement
), *nsGkAtoms::vertical_align
,
2673 if (NS_FAILED(rv
)) {
2674 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
2677 styleSpecified
= true;
2682 // Then, if we could and should remove or replace aElement, let's do it. Or
2683 // just remove attribute.
2684 const bool isStyleRepresentedByElement
=
2685 !aStyleToRemove
.IsStyleToClearAllInlineStyles() &&
2686 aStyleToRemove
.IsRepresentedBy(aElement
);
2688 auto ShouldUpdateDOMTree
= [&]() {
2689 // If we're removing any inline styles and aElement is an inline style
2690 // element, we can remove or replace it.
2691 if (aStyleToRemove
.IsStyleToClearAllInlineStyles() &&
2692 HTMLEditUtils::IsRemovableInlineStyleElement(aElement
)) {
2695 // If we're a specific style and aElement represents it, we can remove or
2696 // replace the element or remove the corresponding attribute.
2697 if (isStyleRepresentedByElement
) {
2700 // If we've removed a CSS style from the `style` attribute of aElement, we
2701 // could remove the element.
2702 return aElement
.IsHTMLElement(nsGkAtoms::span
) && styleSpecified
;
2704 if (!ShouldUpdateDOMTree()) {
2705 return pointToPutCaret
;
2708 const bool elementHasNecessaryAttributes
= [&]() {
2709 // If we're not removing nor replacing aElement itself, we don't need to
2710 // take care of its `style` and `class` attributes even if aSpecifiedStyle
2711 // is `Discard` because aSpecifiedStyle is not intended to be used in this
2713 if (!isStyleRepresentedByElement
) {
2714 return HTMLEditUtils::ElementHasAttributeExcept(aElement
,
2715 *nsGkAtoms::_empty
);
2717 // If we're removing links, we don't need to keep <a> even if it has some
2718 // specific attributes because it cannot be nested. However, if and only if
2719 // it has `style` attribute and aSpecifiedStyle is not `Discard`, we need to
2720 // replace it with new <span> to keep the style.
2721 if (aStyleToRemove
.IsStyleOfAnchorElement()) {
2722 return aSpecifiedStyle
== SpecifiedStyle::Preserve
&&
2723 (aElement
.HasNonEmptyAttr(nsGkAtoms::style
) ||
2724 aElement
.HasNonEmptyAttr(nsGkAtoms::_class
));
2726 nsAtom
& attrKeepStaying
= aStyleToRemove
.mAttribute
2727 ? *aStyleToRemove
.mAttribute
2728 : *nsGkAtoms::_empty
;
2729 return aSpecifiedStyle
== SpecifiedStyle::Preserve
2730 // If we're try to remove the element but the caller wants to
2731 // preserve the style, check whether aElement has attributes
2732 // except the removing attribute since `style` and `class` should
2733 // keep existing to preserve the style.
2734 ? HTMLEditUtils::ElementHasAttributeExcept(aElement
,
2736 // If we're try to remove the element and the caller wants to
2737 // discard the style specified to the element, check whether
2738 // aElement has attributes except the removing attribute, `style`
2739 // and `class` since we don't want to keep these attributes.
2740 : HTMLEditUtils::ElementHasAttributeExcept(
2741 aElement
, attrKeepStaying
, *nsGkAtoms::style
,
2742 *nsGkAtoms::_class
);
2745 // If the element is not a <span> and still has some attributes, we should
2746 // replace it with new <span>.
2747 auto ReplaceWithNewSpan
= [&]() {
2748 if (aStyleToRemove
.IsStyleToClearAllInlineStyles()) {
2749 return false; // Remove it even if it has attributes.
2751 if (aElement
.IsHTMLElement(nsGkAtoms::span
)) {
2752 return false; // Don't replace <span> with new <span>.
2754 if (!isStyleRepresentedByElement
) {
2755 return false; // Keep non-related element as-is.
2757 if (!elementHasNecessaryAttributes
) {
2758 return false; // Should remove it instead of replacing it.
2760 if (aElement
.IsHTMLElement(nsGkAtoms::font
)) {
2761 // Replace <font> if it won't have its specific attributes.
2762 return (aStyleToRemove
.mHTMLProperty
== nsGkAtoms::color
||
2763 !aElement
.HasAttr(nsGkAtoms::color
)) &&
2764 (aStyleToRemove
.mHTMLProperty
== nsGkAtoms::face
||
2765 !aElement
.HasAttr(nsGkAtoms::face
)) &&
2766 (aStyleToRemove
.mHTMLProperty
== nsGkAtoms::size
||
2767 !aElement
.HasAttr(nsGkAtoms::size
));
2769 // The styled element has only global attributes, let's replace it with new
2770 // <span> with cloning the attributes.
2774 if (ReplaceWithNewSpan()) {
2775 // Before cloning the attribute to new element, let's remove it.
2776 if (aStyleToRemove
.mAttribute
) {
2778 RemoveAttributeWithTransaction(aElement
, *aStyleToRemove
.mAttribute
);
2779 if (NS_FAILED(rv
)) {
2780 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
2784 if (aSpecifiedStyle
== SpecifiedStyle::Discard
) {
2785 nsresult rv
= RemoveAttributeWithTransaction(aElement
, *nsGkAtoms::style
);
2786 if (NS_FAILED(rv
)) {
2788 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::style) "
2792 rv
= RemoveAttributeWithTransaction(aElement
, *nsGkAtoms::_class
);
2793 if (NS_FAILED(rv
)) {
2795 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::_class) "
2800 // Move `style` attribute and `class` element to span element before
2801 // removing aElement from the tree.
2802 auto replaceWithSpanResult
=
2803 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<CreateElementResult
, nsresult
> {
2804 if (!aStyleToRemove
.IsStyleOfAnchorElement()) {
2805 return ReplaceContainerAndCloneAttributesWithTransaction(
2806 aElement
, *nsGkAtoms::span
);
2808 nsString styleValue
; // Use nsString to avoid copying the buffer at
2809 // setting the attribute.
2810 aElement
.GetAttr(nsGkAtoms::style
, styleValue
);
2811 return ReplaceContainerWithTransaction(aElement
, *nsGkAtoms::span
,
2812 *nsGkAtoms::style
, styleValue
);
2814 if (MOZ_UNLIKELY(replaceWithSpanResult
.isErr())) {
2816 "HTMLEditor::ReplaceContainerWithTransaction(nsGkAtoms::span) "
2818 return replaceWithSpanResult
.propagateErr();
2820 CreateElementResult unwrappedReplaceWithSpanResult
=
2821 replaceWithSpanResult
.unwrap();
2822 if (AllowsTransactionsToChangeSelection()) {
2823 unwrappedReplaceWithSpanResult
.MoveCaretPointTo(
2824 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
2826 unwrappedReplaceWithSpanResult
.IgnoreCaretPointSuggestion();
2828 return pointToPutCaret
;
2831 auto RemoveElement
= [&]() {
2832 if (aStyleToRemove
.IsStyleToClearAllInlineStyles()) {
2833 MOZ_ASSERT(HTMLEditUtils::IsRemovableInlineStyleElement(aElement
));
2836 // If the element still has some attributes, we should not remove it to keep
2837 // current presentation and/or semantics.
2838 if (elementHasNecessaryAttributes
) {
2841 // If the style is represented by the element, let's remove it.
2842 if (isStyleRepresentedByElement
) {
2845 // If we've removed a CSS style and that made the <span> element have no
2846 // attributes, we can delete it.
2847 if (styleSpecified
&& aElement
.IsHTMLElement(nsGkAtoms::span
)) {
2853 if (RemoveElement()) {
2854 Result
<EditorDOMPoint
, nsresult
> unwrapElementResult
=
2855 RemoveContainerWithTransaction(aElement
);
2856 if (MOZ_UNLIKELY(unwrapElementResult
.isErr())) {
2857 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
2858 return unwrapElementResult
.propagateErr();
2860 if (AllowsTransactionsToChangeSelection() &&
2861 unwrapElementResult
.inspect().IsSet()) {
2862 pointToPutCaret
= unwrapElementResult
.unwrap();
2864 return pointToPutCaret
;
2867 // If the element needs to keep having some attributes, just remove the
2868 // attribute. Note that we don't need to remove `style` attribute here when
2869 // aSpecifiedStyle is `Discard` because we've already removed unnecessary
2871 if (isStyleRepresentedByElement
&& aStyleToRemove
.mAttribute
) {
2873 RemoveAttributeWithTransaction(aElement
, *aStyleToRemove
.mAttribute
);
2874 if (NS_FAILED(rv
)) {
2875 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
2879 return pointToPutCaret
;
2882 EditorRawDOMRange
HTMLEditor::GetExtendedRangeWrappingNamedAnchor(
2883 const EditorRawDOMRange
& aRange
) const {
2884 MOZ_ASSERT(aRange
.StartRef().IsSet());
2885 MOZ_ASSERT(aRange
.EndRef().IsSet());
2887 // FYI: We don't want to stop at ancestor block boundaries to extend the range
2888 // because <a name> can have block elements with low level DOM API. We want
2889 // to remove any <a name> ancestors to remove the style.
2891 EditorRawDOMRange
newRange(aRange
);
2892 for (Element
* element
:
2893 aRange
.StartRef().GetContainer()->InclusiveAncestorsOfType
<Element
>()) {
2894 if (!HTMLEditUtils::IsNamedAnchor(element
)) {
2897 newRange
.SetStart(EditorRawDOMPoint(element
));
2899 for (Element
* element
:
2900 aRange
.EndRef().GetContainer()->InclusiveAncestorsOfType
<Element
>()) {
2901 if (!HTMLEditUtils::IsNamedAnchor(element
)) {
2904 newRange
.SetEnd(EditorRawDOMPoint::After(*element
));
2909 EditorRawDOMRange
HTMLEditor::GetExtendedRangeWrappingEntirelySelectedElements(
2910 const EditorRawDOMRange
& aRange
) const {
2911 MOZ_ASSERT(aRange
.StartRef().IsSet());
2912 MOZ_ASSERT(aRange
.EndRef().IsSet());
2914 // FYI: We don't want to stop at ancestor block boundaries to extend the range
2915 // because the style may come from inline parents of block elements which may
2916 // occur in invalid DOM tree. We want to split any (even invalid) ancestors
2917 // at removing the styles.
2919 EditorRawDOMRange
newRange(aRange
);
2920 while (newRange
.StartRef().IsInContentNode() &&
2921 newRange
.StartRef().IsStartOfContainer()) {
2922 if (!EditorUtils::IsEditableContent(
2923 *newRange
.StartRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
2926 newRange
.SetStart(newRange
.StartRef().ParentPoint());
2928 while (newRange
.EndRef().IsInContentNode() &&
2929 newRange
.EndRef().IsEndOfContainer()) {
2930 if (!EditorUtils::IsEditableContent(
2931 *newRange
.EndRef().ContainerAs
<nsIContent
>(), EditorType::HTML
)) {
2935 EditorRawDOMPoint::After(*newRange
.EndRef().ContainerAs
<nsIContent
>()));
2940 nsresult
HTMLEditor::GetInlinePropertyBase(const EditorInlineStyle
& aStyle
,
2941 const nsAString
* aValue
,
2942 bool* aFirst
, bool* aAny
, bool* aAll
,
2943 nsAString
* outValue
) const {
2944 MOZ_ASSERT(!aStyle
.IsStyleToClearAllInlineStyles());
2945 MOZ_ASSERT(IsEditActionDataAvailable());
2952 const bool isCollapsed
= SelectionRef().IsCollapsed();
2953 RefPtr
<nsRange
> range
= SelectionRef().GetRangeAt(0);
2954 // XXX: Should be a while loop, to get each separate range
2955 // XXX: ERROR_HANDLING can currentItem be null?
2957 // For each range, set a flag
2958 bool firstNodeInRange
= true;
2961 if (NS_WARN_IF(!range
->GetStartContainer())) {
2962 return NS_ERROR_FAILURE
;
2964 nsString tOutString
;
2965 const PendingStyleState styleState
= [&]() {
2966 if (aStyle
.mAttribute
) {
2967 auto state
= mPendingStylesToApplyToNewContent
->GetStyleState(
2968 *aStyle
.mHTMLProperty
, aStyle
.mAttribute
, &tOutString
);
2970 outValue
->Assign(tOutString
);
2974 return mPendingStylesToApplyToNewContent
->GetStyleState(
2975 *aStyle
.mHTMLProperty
);
2977 if (styleState
!= PendingStyleState::NotUpdated
) {
2978 *aFirst
= *aAny
= *aAll
=
2979 (styleState
== PendingStyleState::BeingPreserved
);
2983 nsIContent
* const collapsedContent
=
2984 nsIContent::FromNode(range
->GetStartContainer());
2985 if (MOZ_LIKELY(collapsedContent
&&
2986 collapsedContent
->GetAsElementOrParentElement()) &&
2987 aStyle
.IsCSSSettable(
2988 *collapsedContent
->GetAsElementOrParentElement())) {
2990 tOutString
.Assign(*aValue
);
2992 Result
<bool, nsresult
> isComputedCSSEquivalentToStyleOrError
=
2993 CSSEditUtils::IsComputedCSSEquivalentTo(
2994 *this, MOZ_KnownLive(*collapsedContent
), aStyle
, tOutString
);
2995 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError
.isErr())) {
2996 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
2997 return isComputedCSSEquivalentToStyleOrError
.unwrapErr();
2999 *aFirst
= *aAny
= *aAll
=
3000 isComputedCSSEquivalentToStyleOrError
.unwrap();
3002 outValue
->Assign(tOutString
);
3007 *aFirst
= *aAny
= *aAll
=
3008 collapsedContent
&& HTMLEditUtils::IsInlineStyleSetByElement(
3009 *collapsedContent
, aStyle
, aValue
, outValue
);
3013 // Non-collapsed selection
3015 nsAutoString firstValue
, theValue
;
3017 nsCOMPtr
<nsINode
> endNode
= range
->GetEndContainer();
3018 uint32_t endOffset
= range
->EndOffset();
3020 PostContentIterator postOrderIter
;
3021 DebugOnly
<nsresult
> rvIgnored
= postOrderIter
.Init(range
);
3022 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3023 "Failed to initialize post-order content iterator");
3024 for (; !postOrderIter
.IsDone(); postOrderIter
.Next()) {
3025 if (postOrderIter
.GetCurrentNode()->IsHTMLElement(nsGkAtoms::body
)) {
3028 RefPtr
<Text
> textNode
= Text::FromNode(postOrderIter
.GetCurrentNode());
3033 // just ignore any non-editable nodes
3034 if (!EditorUtils::IsEditableContent(*textNode
, EditorType::HTML
) ||
3035 !HTMLEditUtils::IsVisibleTextNode(*textNode
)) {
3039 if (!isCollapsed
&& first
&& firstNodeInRange
) {
3040 firstNodeInRange
= false;
3041 if (range
->StartOffset() == textNode
->TextDataLength()) {
3044 } else if (textNode
== endNode
&& !endOffset
) {
3048 const RefPtr
<Element
> element
= textNode
->GetParentElement();
3053 if (aStyle
.IsCSSSettable(*element
)) {
3054 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
3055 // equivalence in this implementation for node; let's check if it
3056 // carries those CSS styles
3058 firstValue
.Assign(*aValue
);
3060 Result
<bool, nsresult
> isComputedCSSEquivalentToStyleOrError
=
3061 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element
, aStyle
,
3063 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError
.isErr())) {
3064 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
3065 return isComputedCSSEquivalentToStyleOrError
.unwrapErr();
3067 isSet
= isComputedCSSEquivalentToStyleOrError
.unwrap();
3069 isSet
= HTMLEditUtils::IsInlineStyleSetByElement(
3070 *element
, aStyle
, aValue
, &firstValue
);
3076 *outValue
= firstValue
;
3080 if (aStyle
.IsCSSSettable(*element
)) {
3081 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
3082 // equivalence in this implementation for node; let's check if it
3083 // carries those CSS styles
3085 theValue
.Assign(*aValue
);
3087 Result
<bool, nsresult
> isComputedCSSEquivalentToStyleOrError
=
3088 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element
, aStyle
,
3090 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError
.isErr())) {
3091 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
3092 return isComputedCSSEquivalentToStyleOrError
.unwrapErr();
3094 isSet
= isComputedCSSEquivalentToStyleOrError
.unwrap();
3096 isSet
= HTMLEditUtils::IsInlineStyleSetByElement(*element
, aStyle
,
3101 if (firstValue
!= theValue
&&
3102 // For text-decoration related HTML properties, i.e. <u> and
3103 // <strike>, we have to also check |isSet| because text-decoration
3104 // is a shorthand property, and it may contains other unrelated
3105 // longhand components, e.g. text-decoration-color, so we have to do
3106 // an extra check before setting |*aAll| to false.
3108 // firstValue: "underline rgb(0, 0, 0)"
3109 // theValue: "underline rgb(0, 0, 238)" // <a> uses blue color
3110 // These two values should be the same if we are checking `<u>`.
3111 // That's why we need to check |*aFirst| and |isSet|.
3113 // This is a work-around for text-decoration.
3114 // The spec issue: https://github.com/w3c/editing/issues/241.
3115 // Once this spec issue is resolved, we could drop this work-around
3117 (!aStyle
.IsStyleOfTextDecoration(
3118 EditorInlineStyle::IgnoreSElement::Yes
) ||
3119 *aFirst
!= isSet
)) {
3132 // make sure that if none of the selection is set, we don't report all is
3139 nsresult
HTMLEditor::GetInlineProperty(nsStaticAtom
& aHTMLProperty
,
3141 const nsAString
& aValue
, bool* aFirst
,
3142 bool* aAny
, bool* aAll
) const {
3143 if (NS_WARN_IF(!aFirst
) || NS_WARN_IF(!aAny
) || NS_WARN_IF(!aAll
)) {
3144 return NS_ERROR_INVALID_ARG
;
3147 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
3148 if (NS_WARN_IF(!editActionData
.CanHandle())) {
3149 return NS_ERROR_NOT_INITIALIZED
;
3152 const nsAString
* val
= !aValue
.IsEmpty() ? &aValue
: nullptr;
3154 GetInlinePropertyBase(EditorInlineStyle(aHTMLProperty
, aAttribute
), val
,
3155 aFirst
, aAny
, aAll
, nullptr);
3156 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3157 "HTMLEditor::GetInlinePropertyBase() failed");
3158 return EditorBase::ToGenericNSResult(rv
);
3161 NS_IMETHODIMP
HTMLEditor::GetInlinePropertyWithAttrValue(
3162 const nsAString
& aHTMLProperty
, const nsAString
& aAttribute
,
3163 const nsAString
& aValue
, bool* aFirst
, bool* aAny
, bool* aAll
,
3164 nsAString
& outValue
) {
3165 nsStaticAtom
* property
= NS_GetStaticAtom(aHTMLProperty
);
3166 if (NS_WARN_IF(!property
)) {
3167 return NS_ERROR_INVALID_ARG
;
3169 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
3170 // MOZ_KnownLive because nsStaticAtom is available until shutting down.
3171 nsresult rv
= GetInlinePropertyWithAttrValue(MOZ_KnownLive(*property
),
3172 MOZ_KnownLive(attribute
), aValue
,
3173 aFirst
, aAny
, aAll
, outValue
);
3174 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3175 "HTMLEditor::GetInlinePropertyWithAttrValue() failed");
3179 nsresult
HTMLEditor::GetInlinePropertyWithAttrValue(
3180 nsStaticAtom
& aHTMLProperty
, nsAtom
* aAttribute
, const nsAString
& aValue
,
3181 bool* aFirst
, bool* aAny
, bool* aAll
, nsAString
& outValue
) {
3182 if (NS_WARN_IF(!aFirst
) || NS_WARN_IF(!aAny
) || NS_WARN_IF(!aAll
)) {
3183 return NS_ERROR_INVALID_ARG
;
3186 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
3187 if (NS_WARN_IF(!editActionData
.CanHandle())) {
3188 return NS_ERROR_NOT_INITIALIZED
;
3191 const nsAString
* val
= !aValue
.IsEmpty() ? &aValue
: nullptr;
3193 GetInlinePropertyBase(EditorInlineStyle(aHTMLProperty
, aAttribute
), val
,
3194 aFirst
, aAny
, aAll
, &outValue
);
3195 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3196 "HTMLEditor::GetInlinePropertyBase() failed");
3197 return EditorBase::ToGenericNSResult(rv
);
3200 nsresult
HTMLEditor::RemoveAllInlinePropertiesAsAction(
3201 nsIPrincipal
* aPrincipal
) {
3202 AutoEditActionDataSetter
editActionData(
3203 *this, EditAction::eRemoveAllInlineStyleProperties
, aPrincipal
);
3204 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3205 if (NS_FAILED(rv
)) {
3206 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3207 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3208 return EditorBase::ToGenericNSResult(rv
);
3211 AutoPlaceholderBatch
treatAsOneTransaction(
3212 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
3213 IgnoredErrorResult ignoredError
;
3214 AutoEditSubActionNotifier
startToHandleEditSubAction(
3215 *this, EditSubAction::eRemoveAllTextProperties
, nsIEditor::eNext
,
3217 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
3218 return EditorBase::ToGenericNSResult(ignoredError
.StealNSResult());
3220 NS_WARNING_ASSERTION(
3221 !ignoredError
.Failed(),
3222 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3224 AutoTArray
<EditorInlineStyle
, 1> removeAllInlineStyles
;
3225 removeAllInlineStyles
.AppendElement(EditorInlineStyle::RemoveAllStyles());
3226 rv
= RemoveInlinePropertiesAsSubAction(removeAllInlineStyles
);
3227 NS_WARNING_ASSERTION(
3229 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
3230 return EditorBase::ToGenericNSResult(rv
);
3233 nsresult
HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom
& aHTMLProperty
,
3234 nsStaticAtom
* aAttribute
,
3235 nsIPrincipal
* aPrincipal
) {
3236 AutoEditActionDataSetter
editActionData(
3238 HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty
, aAttribute
,
3241 switch (editActionData
.GetEditAction()) {
3242 case EditAction::eRemoveFontFamilyProperty
:
3243 MOZ_ASSERT(!u
""_ns
.IsVoid());
3244 editActionData
.SetData(u
""_ns
);
3246 case EditAction::eRemoveColorProperty
:
3247 case EditAction::eRemoveBackgroundColorPropertyInline
:
3248 editActionData
.SetColorData(u
""_ns
);
3253 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3254 if (NS_FAILED(rv
)) {
3255 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3256 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3257 return EditorBase::ToGenericNSResult(rv
);
3260 AutoTArray
<EditorInlineStyle
, 8> removeInlineStyleAndRelatedElements
;
3261 AppendInlineStyleAndRelatedStyle(EditorInlineStyle(aHTMLProperty
, aAttribute
),
3262 removeInlineStyleAndRelatedElements
);
3263 rv
= RemoveInlinePropertiesAsSubAction(removeInlineStyleAndRelatedElements
);
3264 NS_WARNING_ASSERTION(
3266 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
3267 return EditorBase::ToGenericNSResult(rv
);
3270 NS_IMETHODIMP
HTMLEditor::RemoveInlineProperty(const nsAString
& aProperty
,
3271 const nsAString
& aAttribute
) {
3272 nsStaticAtom
* property
= NS_GetStaticAtom(aProperty
);
3273 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
3275 AutoEditActionDataSetter
editActionData(
3277 HTMLEditUtils::GetEditActionForFormatText(*property
, attribute
, false));
3278 switch (editActionData
.GetEditAction()) {
3279 case EditAction::eRemoveFontFamilyProperty
:
3280 MOZ_ASSERT(!u
""_ns
.IsVoid());
3281 editActionData
.SetData(u
""_ns
);
3283 case EditAction::eRemoveColorProperty
:
3284 case EditAction::eRemoveBackgroundColorPropertyInline
:
3285 editActionData
.SetColorData(u
""_ns
);
3290 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3291 if (NS_FAILED(rv
)) {
3292 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3293 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3294 return EditorBase::ToGenericNSResult(rv
);
3297 AutoTArray
<EditorInlineStyle
, 1> removeOneInlineStyle
;
3298 removeOneInlineStyle
.AppendElement(EditorInlineStyle(*property
, attribute
));
3299 rv
= RemoveInlinePropertiesAsSubAction(removeOneInlineStyle
);
3300 NS_WARNING_ASSERTION(
3302 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
3303 return EditorBase::ToGenericNSResult(rv
);
3306 void HTMLEditor::AppendInlineStyleAndRelatedStyle(
3307 const EditorInlineStyle
& aStyleToRemove
,
3308 nsTArray
<EditorInlineStyle
>& aStylesToRemove
) const {
3309 if (nsStaticAtom
* similarElementName
=
3310 aStyleToRemove
.GetSimilarElementNameAtom()) {
3311 EditorInlineStyle
anotherStyle(*similarElementName
);
3312 if (!aStylesToRemove
.Contains(anotherStyle
)) {
3313 aStylesToRemove
.AppendElement(std::move(anotherStyle
));
3315 } else if (aStyleToRemove
.mHTMLProperty
== nsGkAtoms::font
) {
3316 if (aStyleToRemove
.mAttribute
== nsGkAtoms::size
) {
3317 EditorInlineStyle
big(*nsGkAtoms::big
), small(*nsGkAtoms::small
);
3318 if (!aStylesToRemove
.Contains(big
)) {
3319 aStylesToRemove
.AppendElement(std::move(big
));
3321 if (!aStylesToRemove
.Contains(small
)) {
3322 aStylesToRemove
.AppendElement(std::move(small
));
3325 // Handling <tt> element code was implemented for composer (bug 115922).
3326 // This shouldn't work with Document.execCommand() for compatibility with
3327 // the other browsers. Currently, edit action principal is set only when
3328 // the root caller is Document::ExecCommand() so that we should handle <tt>
3329 // element only when the principal is nullptr that must be only when XUL
3330 // command is executed on composer.
3331 else if (aStyleToRemove
.mAttribute
== nsGkAtoms::face
&&
3332 !GetEditActionPrincipal()) {
3333 EditorInlineStyle
tt(*nsGkAtoms::tt
);
3334 if (!aStylesToRemove
.Contains(tt
)) {
3335 aStylesToRemove
.AppendElement(std::move(tt
));
3339 if (!aStylesToRemove
.Contains(aStyleToRemove
)) {
3340 aStylesToRemove
.AppendElement(aStyleToRemove
);
3344 nsresult
HTMLEditor::RemoveInlinePropertiesAsSubAction(
3345 const nsTArray
<EditorInlineStyle
>& aStylesToRemove
) {
3346 MOZ_ASSERT(IsEditActionDataAvailable());
3347 MOZ_ASSERT(!aStylesToRemove
.IsEmpty());
3349 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
3350 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3351 "EditorBase::CommitComposition() failed, but ignored");
3353 if (SelectionRef().IsCollapsed()) {
3354 // Manipulating text attributes on a collapsed selection only sets state
3355 // for the next text insertion
3356 mPendingStylesToApplyToNewContent
->ClearStyles(aStylesToRemove
);
3360 // XXX Shouldn't we quit before calling `CommitComposition()`?
3361 if (IsPlaintextMailComposer()) {
3366 Result
<EditActionResult
, nsresult
> result
= CanHandleHTMLEditSubAction();
3367 if (MOZ_UNLIKELY(result
.isErr())) {
3368 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed");
3369 return result
.unwrapErr();
3371 if (result
.inspect().Canceled()) {
3376 AutoPlaceholderBatch
treatAsOneTransaction(
3377 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
3378 IgnoredErrorResult ignoredError
;
3379 AutoEditSubActionNotifier
startToHandleEditSubAction(
3380 *this, EditSubAction::eRemoveTextProperty
, nsIEditor::eNext
,
3382 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
3383 return ignoredError
.StealNSResult();
3385 NS_WARNING_ASSERTION(
3386 !ignoredError
.Failed(),
3387 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3389 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
3390 // cases, but removing this may cause the behavior with the legacy
3391 // mutation event listeners. We should try to delete this in a bug.
3392 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
3394 AutoRangeArray
selectionRanges(SelectionRef());
3395 for (const EditorInlineStyle
& styleToRemove
: aStylesToRemove
) {
3396 // The ranges may be updated by changing the DOM tree. In strictly
3397 // speaking, we should save and restore the ranges at every range loop,
3398 // but we've never done so and it may be expensive if there are a lot of
3399 // ranges. Therefore, we should do it for every style handling for now.
3400 // TODO: We should collect everything required for removing the style before
3401 // touching the DOM tree. Then, we need to save and restore the
3402 // ranges only once.
3403 Maybe
<AutoInlineStyleSetter
> styleInverter
;
3404 if (styleToRemove
.IsInvertibleWithCSS()) {
3405 styleInverter
.emplace(EditorInlineStyleAndValue::ToInvert(styleToRemove
));
3407 for (OwningNonNull
<nsRange
>& selectionRange
: selectionRanges
.Ranges()) {
3408 AutoTrackDOMRange
trackSelectionRange(RangeUpdaterRef(), &selectionRange
);
3409 // If we're removing <a name>, we don't want to split ancestors because
3410 // the split fragment will keep working as named anchor. Therefore, we
3411 // need to remove all <a name> elements which the selection range even
3412 // partially contains.
3413 const EditorDOMRange
range(
3414 styleToRemove
.mHTMLProperty
== nsGkAtoms::name
3415 ? GetExtendedRangeWrappingNamedAnchor(
3416 EditorRawDOMRange(selectionRange
))
3417 : GetExtendedRangeWrappingEntirelySelectedElements(
3418 EditorRawDOMRange(selectionRange
)));
3419 if (NS_WARN_IF(!range
.IsPositioned())) {
3423 // Remove this style from ancestors of our range endpoints, splitting
3424 // them as appropriate
3425 Result
<SplitRangeOffResult
, nsresult
> splitRangeOffResult
=
3426 SplitAncestorStyledInlineElementsAtRangeEdges(
3427 range
, styleToRemove
, SplitAtEdges::eAllowToCreateEmptyContainer
);
3428 if (MOZ_UNLIKELY(splitRangeOffResult
.isErr())) {
3430 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
3432 return splitRangeOffResult
.unwrapErr();
3434 // There is AutoTransactionsConserveSelection, so we don't need to
3435 // update selection here.
3436 splitRangeOffResult
.inspect().IgnoreCaretPointSuggestion();
3438 // XXX Modifying `range` means that we may modify ranges in `Selection`.
3439 // Is this intentional? Note that the range may be not in
3440 // `Selection` too. It seems that at least one of them is not
3441 // an unexpected case.
3442 const EditorDOMRange
& splitRange
=
3443 splitRangeOffResult
.inspect().RangeRef();
3444 if (NS_WARN_IF(!splitRange
.IsPositioned())) {
3448 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContentsToInvertStyle
;
3450 // Collect top level children in the range first.
3451 // TODO: Perhaps, HTMLEditUtils::IsSplittableNode should be used here
3452 // instead of EditorUtils::IsEditableContent.
3453 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContentsAroundRange
;
3454 if (splitRange
.InSameContainer() &&
3455 splitRange
.StartRef().IsInTextNode()) {
3456 if (!EditorUtils::IsEditableContent(
3457 *splitRange
.StartRef().ContainerAs
<Text
>(),
3458 EditorType::HTML
)) {
3461 arrayOfContentsAroundRange
.AppendElement(
3462 *splitRange
.StartRef().ContainerAs
<Text
>());
3463 } else if (splitRange
.IsInTextNodes() &&
3464 splitRange
.InAdjacentSiblings()) {
3465 // Adjacent siblings are in a same element, so the editable state of
3466 // both text nodes are always same.
3467 if (!EditorUtils::IsEditableContent(
3468 *splitRange
.StartRef().ContainerAs
<Text
>(),
3469 EditorType::HTML
)) {
3472 arrayOfContentsAroundRange
.AppendElement(
3473 *splitRange
.StartRef().ContainerAs
<Text
>());
3474 arrayOfContentsAroundRange
.AppendElement(
3475 *splitRange
.EndRef().ContainerAs
<Text
>());
3477 // Append first node if it's a text node but selected not entirely.
3478 if (splitRange
.StartRef().IsInTextNode() &&
3479 !splitRange
.StartRef().IsStartOfContainer() &&
3480 EditorUtils::IsEditableContent(
3481 *splitRange
.StartRef().ContainerAs
<Text
>(),
3482 EditorType::HTML
)) {
3483 arrayOfContentsAroundRange
.AppendElement(
3484 *splitRange
.StartRef().ContainerAs
<Text
>());
3486 // Append all entirely selected nodes.
3487 ContentSubtreeIterator subtreeIter
;
3489 subtreeIter
.Init(splitRange
.StartRef().ToRawRangeBoundary(),
3490 splitRange
.EndRef().ToRawRangeBoundary()))) {
3491 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
3492 nsCOMPtr
<nsINode
> node
= subtreeIter
.GetCurrentNode();
3493 if (NS_WARN_IF(!node
)) {
3494 return NS_ERROR_FAILURE
;
3496 if (node
->IsContent() &&
3497 EditorUtils::IsEditableContent(*node
->AsContent(),
3498 EditorType::HTML
)) {
3499 arrayOfContentsAroundRange
.AppendElement(*node
->AsContent());
3503 // Append last node if it's a text node but selected not entirely.
3504 if (!splitRange
.InSameContainer() &&
3505 splitRange
.EndRef().IsInTextNode() &&
3506 !splitRange
.EndRef().IsEndOfContainer() &&
3507 EditorUtils::IsEditableContent(
3508 *splitRange
.EndRef().ContainerAs
<Text
>(), EditorType::HTML
)) {
3509 arrayOfContentsAroundRange
.AppendElement(
3510 *splitRange
.EndRef().ContainerAs
<Text
>());
3513 if (styleToRemove
.IsInvertibleWithCSS()) {
3514 arrayOfContentsToInvertStyle
.SetCapacity(
3515 arrayOfContentsAroundRange
.Length());
3518 for (OwningNonNull
<nsIContent
>& content
: arrayOfContentsAroundRange
) {
3519 // We should remove style from the element and its descendants.
3520 if (content
->IsElement()) {
3521 Result
<EditorDOMPoint
, nsresult
> removeStyleResult
=
3522 RemoveStyleInside(MOZ_KnownLive(*content
->AsElement()),
3523 styleToRemove
, SpecifiedStyle::Preserve
);
3524 if (MOZ_UNLIKELY(removeStyleResult
.isErr())) {
3525 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
3526 return removeStyleResult
.unwrapErr();
3528 // There is AutoTransactionsConserveSelection, so we don't need to
3529 // update selection here.
3531 // If the element was removed from the DOM tree by
3532 // RemoveStyleInside, we need to do nothing for it anymore.
3533 if (!content
->GetParentNode()) {
3538 if (styleToRemove
.IsInvertibleWithCSS()) {
3539 arrayOfContentsToInvertStyle
.AppendElement(content
);
3541 } // for-loop for arrayOfContentsAroundRange
3544 auto FlushAndStopTrackingAndShrinkSelectionRange
=
3545 [&]() MOZ_CAN_RUN_SCRIPT
{
3546 trackSelectionRange
.FlushAndStopTracking();
3547 if (NS_WARN_IF(!selectionRange
->IsPositioned())) {
3550 EditorRawDOMRange
range(selectionRange
);
3551 nsINode
* const commonAncestor
=
3552 range
.GetClosestCommonInclusiveAncestor();
3553 // Shrink range for compatibility between browsers.
3554 nsIContent
* const maybeNextContent
=
3555 range
.StartRef().IsInContentNode() &&
3556 range
.StartRef().IsEndOfContainer()
3557 ? AutoInlineStyleSetter::GetNextEditableInlineContent(
3558 *range
.StartRef().ContainerAs
<nsIContent
>(),
3561 nsIContent
* const maybePreviousContent
=
3562 range
.EndRef().IsInContentNode() &&
3563 range
.EndRef().IsStartOfContainer()
3564 ? AutoInlineStyleSetter::GetPreviousEditableInlineContent(
3565 *range
.EndRef().ContainerAs
<nsIContent
>(),
3568 if (!maybeNextContent
&& !maybePreviousContent
) {
3571 const auto startPoint
=
3573 maybeNextContent
!= selectionRange
->GetStartContainer()
3574 ? HTMLEditUtils::GetDeepestEditableStartPointOf
<
3575 EditorRawDOMPoint
>(*maybeNextContent
)
3577 const auto endPoint
=
3578 maybePreviousContent
&& maybePreviousContent
!=
3579 selectionRange
->GetEndContainer()
3580 ? HTMLEditUtils::GetDeepestEditableEndPointOf
<
3581 EditorRawDOMPoint
>(*maybePreviousContent
)
3583 DebugOnly
<nsresult
> rvIgnored
= selectionRange
->SetStartAndEnd(
3584 startPoint
.ToRawRangeBoundary(), endPoint
.ToRawRangeBoundary());
3585 NS_WARNING_ASSERTION(
3586 NS_SUCCEEDED(rvIgnored
),
3587 "nsRange::SetStartAndEnd() failed, but ignored");
3590 if (arrayOfContentsToInvertStyle
.IsEmpty()) {
3591 FlushAndStopTrackingAndShrinkSelectionRange();
3594 MOZ_ASSERT(styleToRemove
.IsInvertibleWithCSS());
3596 // If the style is specified in parent block and we can remove the
3597 // style with inserting new <span> element, we should do it.
3598 for (OwningNonNull
<nsIContent
>& content
: arrayOfContentsToInvertStyle
) {
3599 if (Element
* element
= Element::FromNode(content
)) {
3600 // XXX Do we need to call this even when data node or something? If
3602 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
3604 nsresult rv
= styleInverter
->InvertStyleIfApplied(
3605 *this, MOZ_KnownLive(*element
));
3606 if (NS_FAILED(rv
)) {
3607 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
3609 "AutoInlineStyleSetter::InvertStyleIfApplied() failed");
3610 return NS_ERROR_EDITOR_DESTROYED
;
3613 "AutoInlineStyleSetter::InvertStyleIfApplied() failed, but "
3619 // Unfortunately, all browsers don't join text nodes when removing a
3620 // style. Therefore, there may be multiple text nodes as adjacent
3621 // siblings. That's the reason why we need to handle text nodes in this
3623 if (Text
* textNode
= Text::FromNode(content
)) {
3624 const uint32_t startOffset
=
3625 content
== splitRange
.StartRef().GetContainer()
3626 ? splitRange
.StartRef().Offset()
3628 const uint32_t endOffset
=
3629 content
== splitRange
.EndRef().GetContainer()
3630 ? splitRange
.EndRef().Offset()
3631 : textNode
->TextDataLength();
3632 Result
<SplitRangeOffFromNodeResult
, nsresult
>
3633 wrapTextInStyledElementResult
=
3634 styleInverter
->InvertStyleIfApplied(
3635 *this, MOZ_KnownLive(*textNode
), startOffset
, endOffset
);
3636 if (MOZ_UNLIKELY(wrapTextInStyledElementResult
.isErr())) {
3637 NS_WARNING("AutoInlineStyleSetter::InvertStyleIfApplied() failed");
3638 return wrapTextInStyledElementResult
.unwrapErr();
3640 SplitRangeOffFromNodeResult unwrappedWrapTextInStyledElementResult
=
3641 wrapTextInStyledElementResult
.unwrap();
3642 // There is AutoTransactionsConserveSelection, so we don't need to
3643 // update selection here.
3644 unwrappedWrapTextInStyledElementResult
.IgnoreCaretPointSuggestion();
3645 // If we've split the content, let's swap content in
3646 // arrayOfContentsToInvertStyle with the text node which is applied
3648 if (unwrappedWrapTextInStyledElementResult
.DidSplit() &&
3649 styleToRemove
.IsInvertibleWithCSS()) {
3650 MOZ_ASSERT(unwrappedWrapTextInStyledElementResult
3651 .GetMiddleContentAs
<Text
>());
3652 if (Text
* styledTextNode
= unwrappedWrapTextInStyledElementResult
3653 .GetMiddleContentAs
<Text
>()) {
3654 if (styledTextNode
!= content
) {
3655 arrayOfContentsToInvertStyle
.ReplaceElementAt(
3656 arrayOfContentsToInvertStyle
.Length() - 1,
3657 OwningNonNull
<nsIContent
>(*styledTextNode
));
3664 // If the node is not an element nor a text node, it's invisible.
3665 // In this case, we don't need to make it wrapped in new element.
3668 // Finally, we should remove the style from all leaf text nodes if
3669 // they still have the style.
3670 AutoTArray
<OwningNonNull
<Text
>, 32> leafTextNodes
;
3671 for (const OwningNonNull
<nsIContent
>& content
:
3672 arrayOfContentsToInvertStyle
) {
3673 // XXX Should we ignore content which has already removed from the
3674 // DOM tree by the previous for-loop?
3675 if (content
->IsElement()) {
3676 CollectEditableLeafTextNodes(*content
->AsElement(), leafTextNodes
);
3679 for (const OwningNonNull
<Text
>& textNode
: leafTextNodes
) {
3680 Result
<SplitRangeOffFromNodeResult
, nsresult
>
3681 wrapTextInStyledElementResult
= styleInverter
->InvertStyleIfApplied(
3682 *this, MOZ_KnownLive(*textNode
), 0, textNode
->TextLength());
3683 if (MOZ_UNLIKELY(wrapTextInStyledElementResult
.isErr())) {
3685 "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() "
3687 return wrapTextInStyledElementResult
.unwrapErr();
3689 // There is AutoTransactionsConserveSelection, so we don't need to
3690 // update selection here.
3691 wrapTextInStyledElementResult
.inspect().IgnoreCaretPointSuggestion();
3692 } // for-loop of leafTextNodes
3694 // styleInverter may have touched a part of the range. Therefore, we
3695 // cannot adjust the range without comparing DOM node position and
3696 // first/last touched positions, but it may be too expensive. I think
3697 // that shrinking only the tracked range boundaries must be enough in most
3699 FlushAndStopTrackingAndShrinkSelectionRange();
3700 } // for-loop of selectionRanges
3701 } // for-loop of styles
3703 MOZ_ASSERT(!selectionRanges
.HasSavedRanges());
3704 nsresult rv
= selectionRanges
.ApplyTo(SelectionRef());
3705 if (NS_WARN_IF(Destroyed())) {
3706 return NS_ERROR_EDITOR_DESTROYED
;
3708 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::ApplyTo() failed");
3712 nsresult
HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied(
3713 HTMLEditor
& aHTMLEditor
, Element
& aElement
) {
3714 MOZ_ASSERT(IsStyleToInvert());
3716 Result
<bool, nsresult
> isRemovableParentStyleOrError
=
3717 aHTMLEditor
.IsRemovableParentStyleWithNewSpanElement(aElement
, *this);
3718 if (MOZ_UNLIKELY(isRemovableParentStyleOrError
.isErr())) {
3719 NS_WARNING("HTMLEditor::IsRemovableParentStyleWithNewSpanElement() failed");
3720 return isRemovableParentStyleOrError
.unwrapErr();
3722 if (!isRemovableParentStyleOrError
.unwrap()) {
3723 // E.g., text-decoration cannot be override visually in children.
3724 // In such cases, we can do nothing.
3728 // Wrap it into a new element, move it into direct child which has same style,
3729 // or specify the style to its parent.
3730 Result
<CaretPoint
, nsresult
> pointToPutCaretOrError
=
3731 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(aHTMLEditor
, aElement
);
3732 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
3734 "AutoInlineStyleSetter::"
3735 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed");
3736 return pointToPutCaretOrError
.unwrapErr();
3738 // The caller must update `Selection` later so that we don't need this.
3739 pointToPutCaretOrError
.unwrap().IgnoreCaretPointSuggestion();
3743 Result
<SplitRangeOffFromNodeResult
, nsresult
>
3744 HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied(HTMLEditor
& aHTMLEditor
,
3746 uint32_t aStartOffset
,
3747 uint32_t aEndOffset
) {
3748 MOZ_ASSERT(IsStyleToInvert());
3750 Result
<bool, nsresult
> isRemovableParentStyleOrError
=
3751 aHTMLEditor
.IsRemovableParentStyleWithNewSpanElement(aTextNode
, *this);
3752 if (MOZ_UNLIKELY(isRemovableParentStyleOrError
.isErr())) {
3753 NS_WARNING("HTMLEditor::IsRemovableParentStyleWithNewSpanElement() failed");
3754 return isRemovableParentStyleOrError
.propagateErr();
3756 if (!isRemovableParentStyleOrError
.unwrap()) {
3757 // E.g., text-decoration cannot be override visually in children.
3758 // In such cases, we can do nothing.
3759 return SplitRangeOffFromNodeResult(nullptr, &aTextNode
, nullptr);
3762 // We need to use new `<span>` element or existing element if it's available
3763 // to overwrite parent style.
3764 Result
<SplitRangeOffFromNodeResult
, nsresult
> wrapTextInStyledElementResult
=
3765 SplitTextNodeAndApplyStyleToMiddleNode(aHTMLEditor
, aTextNode
,
3766 aStartOffset
, aEndOffset
);
3767 NS_WARNING_ASSERTION(
3768 wrapTextInStyledElementResult
.isOk(),
3769 "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() failed");
3770 return wrapTextInStyledElementResult
;
3773 Result
<bool, nsresult
> HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
3774 nsIContent
& aContent
, const EditorInlineStyle
& aStyle
) const {
3775 // We don't support to remove all inline styles with this path.
3776 if (aStyle
.IsStyleToClearAllInlineStyles()) {
3780 // First check whether the style is invertible since this is the fastest
3782 if (!aStyle
.IsInvertibleWithCSS()) {
3786 // If aContent is not an element and it's not in an element, it means that
3787 // aContent is disconnected non-element node. In this case, it's never
3788 // applied any styles which are invertible.
3789 const RefPtr
<Element
> element
= aContent
.GetAsElementOrParentElement();
3790 if (MOZ_UNLIKELY(!element
)) {
3794 // If parent block has invertible style, we should remove the style with
3795 // creating new `<span>` element even in HTML mode because Chrome does it.
3796 if (!aStyle
.IsCSSSettable(*element
)) {
3799 nsAutoString emptyString
;
3800 Result
<bool, nsresult
> isComputedCSSEquivalentToStyleOrError
=
3801 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element
, aStyle
,
3803 NS_WARNING_ASSERTION(isComputedCSSEquivalentToStyleOrError
.isOk(),
3804 "CSSEditUtils::IsComputedCSSEquivalentTo() failed");
3805 return isComputedCSSEquivalentToStyleOrError
;
3808 void HTMLEditor::CollectEditableLeafTextNodes(
3809 Element
& aElement
, nsTArray
<OwningNonNull
<Text
>>& aLeafTextNodes
) const {
3810 for (nsIContent
* child
= aElement
.GetFirstChild(); child
;
3811 child
= child
->GetNextSibling()) {
3812 if (child
->IsElement()) {
3813 CollectEditableLeafTextNodes(*child
->AsElement(), aLeafTextNodes
);
3816 if (child
->IsText()) {
3817 aLeafTextNodes
.AppendElement(*child
->AsText());
3822 nsresult
HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal
* aPrincipal
) {
3823 AutoEditActionDataSetter
editActionData(*this, EditAction::eIncrementFontSize
,
3825 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3826 if (NS_FAILED(rv
)) {
3827 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3828 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3829 return EditorBase::ToGenericNSResult(rv
);
3832 rv
= IncrementOrDecrementFontSizeAsSubAction(FontSize::incr
);
3833 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3834 "HTMLEditor::IncrementOrDecrementFontSizeAsSubAction("
3835 "FontSize::incr) failed");
3836 return EditorBase::ToGenericNSResult(rv
);
3839 nsresult
HTMLEditor::DecreaseFontSizeAsAction(nsIPrincipal
* aPrincipal
) {
3840 AutoEditActionDataSetter
editActionData(*this, EditAction::eDecrementFontSize
,
3842 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
3843 if (NS_FAILED(rv
)) {
3844 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
3845 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3846 return EditorBase::ToGenericNSResult(rv
);
3849 rv
= IncrementOrDecrementFontSizeAsSubAction(FontSize::decr
);
3850 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
3851 "HTMLEditor::IncrementOrDecrementFontSizeAsSubAction("
3852 "FontSize::decr) failed");
3853 return EditorBase::ToGenericNSResult(rv
);
3856 nsresult
HTMLEditor::IncrementOrDecrementFontSizeAsSubAction(
3857 FontSize aIncrementOrDecrement
) {
3858 MOZ_ASSERT(IsEditActionDataAvailable());
3860 // Committing composition and changing font size should be undone together.
3861 AutoPlaceholderBatch
treatAsOneTransaction(
3862 *this, ScrollSelectionIntoView::Yes
, __FUNCTION__
);
3864 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
3865 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
3866 "EditorBase::CommitComposition() failed, but ignored");
3868 // If selection is collapsed, set typing state
3869 if (SelectionRef().IsCollapsed()) {
3870 nsStaticAtom
& bigOrSmallTagName
= aIncrementOrDecrement
== FontSize::incr
3872 : *nsGkAtoms::small
;
3874 // Let's see in what kind of element the selection is
3875 if (!SelectionRef().RangeCount()) {
3878 const auto firstRangeStartPoint
=
3879 EditorBase::GetFirstSelectionStartPoint
<EditorRawDOMPoint
>();
3880 if (NS_WARN_IF(!firstRangeStartPoint
.IsSet())) {
3884 firstRangeStartPoint
.GetContainerOrContainerParentElement();
3885 if (NS_WARN_IF(!element
)) {
3888 if (!HTMLEditUtils::CanNodeContain(*element
, bigOrSmallTagName
)) {
3892 // Manipulating text attributes on a collapsed selection only sets state
3893 // for the next text insertion
3894 mPendingStylesToApplyToNewContent
->PreserveStyle(bigOrSmallTagName
, nullptr,
3899 IgnoredErrorResult ignoredError
;
3900 AutoEditSubActionNotifier
startToHandleEditSubAction(
3901 *this, EditSubAction::eSetTextProperty
, nsIEditor::eNext
, ignoredError
);
3902 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
3903 return ignoredError
.StealNSResult();
3905 NS_WARNING_ASSERTION(
3906 !ignoredError
.Failed(),
3907 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3909 // TODO: We don't need AutoTransactionsConserveSelection here in the normal
3910 // cases, but removing this may cause the behavior with the legacy
3911 // mutation event listeners. We should try to delete this in a bug.
3912 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
3914 AutoRangeArray
selectionRanges(SelectionRef());
3915 MOZ_ALWAYS_TRUE(selectionRanges
.SaveAndTrackRanges(*this));
3916 for (const OwningNonNull
<nsRange
>& domRange
: selectionRanges
.Ranges()) {
3917 // TODO: We should stop extending the range outside ancestor blocks because
3918 // we don't need to do it for setting inline styles. However, here is
3919 // chrome only handling path. Therefore, we don't need to fix here
3921 const EditorDOMRange
range(GetExtendedRangeWrappingEntirelySelectedElements(
3922 EditorRawDOMRange(domRange
)));
3923 if (NS_WARN_IF(!range
.IsPositioned())) {
3927 if (range
.InSameContainer() && range
.StartRef().IsInTextNode()) {
3928 Result
<CreateElementResult
, nsresult
> wrapInBigOrSmallElementResult
=
3929 SetFontSizeOnTextNode(
3930 MOZ_KnownLive(*range
.StartRef().ContainerAs
<Text
>()),
3931 range
.StartRef().Offset(), range
.EndRef().Offset(),
3932 aIncrementOrDecrement
);
3933 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult
.isErr())) {
3934 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
3935 return wrapInBigOrSmallElementResult
.unwrapErr();
3937 // There is an AutoTransactionsConserveSelection instance so that we don't
3938 // need to update selection for this change.
3939 wrapInBigOrSmallElementResult
.inspect().IgnoreCaretPointSuggestion();
3943 // Not the easy case. Range not contained in single text node. There
3944 // are up to three phases here. There are all the nodes reported by the
3945 // subtree iterator to be processed. And there are potentially a
3946 // starting textnode and an ending textnode which are only partially
3947 // contained by the range.
3949 // Let's handle the nodes reported by the iterator. These nodes are
3950 // entirely contained in the selection range. We build up a list of them
3951 // (since doing operations on the document during iteration would perturb
3954 // Iterate range and build up array
3955 ContentSubtreeIterator subtreeIter
;
3956 if (NS_SUCCEEDED(subtreeIter
.Init(range
.StartRef().ToRawRangeBoundary(),
3957 range
.EndRef().ToRawRangeBoundary()))) {
3958 nsTArray
<OwningNonNull
<nsIContent
>> arrayOfContents
;
3959 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
3960 if (NS_WARN_IF(!subtreeIter
.GetCurrentNode()->IsContent())) {
3961 return NS_ERROR_FAILURE
;
3963 OwningNonNull
<nsIContent
> content
=
3964 *subtreeIter
.GetCurrentNode()->AsContent();
3966 if (EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
3967 arrayOfContents
.AppendElement(content
);
3971 // Now that we have the list, do the font size change on each node
3972 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
3973 // MOZ_KnownLive because of bug 1622253
3974 Result
<EditorDOMPoint
, nsresult
> fontChangeOnNodeResult
=
3975 SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(content
),
3976 aIncrementOrDecrement
);
3977 if (MOZ_UNLIKELY(fontChangeOnNodeResult
.isErr())) {
3978 NS_WARNING("HTMLEditor::SetFontSizeWithBigOrSmallElement() failed");
3979 return fontChangeOnNodeResult
.unwrapErr();
3981 // There is an AutoTransactionsConserveSelection, so we don't need to
3982 // update selection here.
3985 // Now check the start and end parents of the range to see if they need
3986 // to be separately handled (they do if they are text nodes, due to how
3987 // the subtree iterator works - it will not have reported them).
3988 if (range
.StartRef().IsInTextNode() &&
3989 !range
.StartRef().IsEndOfContainer() &&
3990 EditorUtils::IsEditableContent(*range
.StartRef().ContainerAs
<Text
>(),
3991 EditorType::HTML
)) {
3992 Result
<CreateElementResult
, nsresult
> wrapInBigOrSmallElementResult
=
3993 SetFontSizeOnTextNode(
3994 MOZ_KnownLive(*range
.StartRef().ContainerAs
<Text
>()),
3995 range
.StartRef().Offset(),
3996 range
.StartRef().ContainerAs
<Text
>()->TextDataLength(),
3997 aIncrementOrDecrement
);
3998 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult
.isErr())) {
3999 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
4000 return wrapInBigOrSmallElementResult
.unwrapErr();
4002 // There is an AutoTransactionsConserveSelection instance so that we
4003 // don't need to update selection for this change.
4004 wrapInBigOrSmallElementResult
.inspect().IgnoreCaretPointSuggestion();
4006 if (range
.EndRef().IsInTextNode() && !range
.EndRef().IsStartOfContainer() &&
4007 EditorUtils::IsEditableContent(*range
.EndRef().ContainerAs
<Text
>(),
4008 EditorType::HTML
)) {
4009 Result
<CreateElementResult
, nsresult
> wrapInBigOrSmallElementResult
=
4010 SetFontSizeOnTextNode(
4011 MOZ_KnownLive(*range
.EndRef().ContainerAs
<Text
>()), 0u,
4012 range
.EndRef().Offset(), aIncrementOrDecrement
);
4013 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult
.isErr())) {
4014 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed");
4015 return wrapInBigOrSmallElementResult
.unwrapErr();
4017 // There is an AutoTransactionsConserveSelection instance so that we
4018 // don't need to update selection for this change.
4019 wrapInBigOrSmallElementResult
.inspect().IgnoreCaretPointSuggestion();
4023 MOZ_ASSERT(selectionRanges
.HasSavedRanges());
4024 selectionRanges
.RestoreFromSavedRanges();
4025 nsresult rv
= selectionRanges
.ApplyTo(SelectionRef());
4026 if (NS_WARN_IF(Destroyed())) {
4027 return NS_ERROR_EDITOR_DESTROYED
;
4029 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "AutoRangeArray::ApplyTo() failed");
4033 Result
<CreateElementResult
, nsresult
> HTMLEditor::SetFontSizeOnTextNode(
4034 Text
& aTextNode
, uint32_t aStartOffset
, uint32_t aEndOffset
,
4035 FontSize aIncrementOrDecrement
) {
4036 // Don't need to do anything if no characters actually selected
4037 if (aStartOffset
== aEndOffset
) {
4038 return CreateElementResult::NotHandled();
4041 if (!aTextNode
.GetParentNode() ||
4042 !HTMLEditUtils::CanNodeContain(*aTextNode
.GetParentNode(),
4044 return CreateElementResult::NotHandled();
4047 aEndOffset
= std::min(aTextNode
.Length(), aEndOffset
);
4049 // Make the range an independent node.
4050 RefPtr
<Text
> textNodeForTheRange
= &aTextNode
;
4052 EditorDOMPoint pointToPutCaret
;
4054 auto pointToPutCaretOrError
=
4055 [&]() MOZ_CAN_RUN_SCRIPT
-> Result
<EditorDOMPoint
, nsresult
> {
4056 EditorDOMPoint pointToPutCaret
;
4057 // Split at the end of the range.
4058 EditorDOMPoint
atEnd(textNodeForTheRange
, aEndOffset
);
4059 if (!atEnd
.IsEndOfContainer()) {
4060 // We need to split off back of text node
4061 Result
<SplitNodeResult
, nsresult
> splitAtEndResult
=
4062 SplitNodeWithTransaction(atEnd
);
4063 if (MOZ_UNLIKELY(splitAtEndResult
.isErr())) {
4064 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4065 return splitAtEndResult
.propagateErr();
4067 SplitNodeResult unwrappedSplitAtEndResult
= splitAtEndResult
.unwrap();
4069 !unwrappedSplitAtEndResult
.HasCaretPointSuggestion())) {
4071 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
4073 return Err(NS_ERROR_FAILURE
);
4075 unwrappedSplitAtEndResult
.MoveCaretPointTo(pointToPutCaret
, *this, {});
4076 MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(),
4077 pointToPutCaret
.IsSet());
4078 textNodeForTheRange
=
4079 unwrappedSplitAtEndResult
.GetPreviousContentAs
<Text
>();
4080 MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange
);
4083 // Split at the start of the range.
4084 EditorDOMPoint
atStart(textNodeForTheRange
, aStartOffset
);
4085 if (!atStart
.IsStartOfContainer()) {
4086 // We need to split off front of text node
4087 Result
<SplitNodeResult
, nsresult
> splitAtStartResult
=
4088 SplitNodeWithTransaction(atStart
);
4089 if (MOZ_UNLIKELY(splitAtStartResult
.isErr())) {
4090 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4091 return splitAtStartResult
.propagateErr();
4093 SplitNodeResult unwrappedSplitAtStartResult
=
4094 splitAtStartResult
.unwrap();
4096 !unwrappedSplitAtStartResult
.HasCaretPointSuggestion())) {
4098 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
4100 return Err(NS_ERROR_FAILURE
);
4102 unwrappedSplitAtStartResult
.MoveCaretPointTo(pointToPutCaret
, *this,
4104 MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(),
4105 pointToPutCaret
.IsSet());
4106 textNodeForTheRange
=
4107 unwrappedSplitAtStartResult
.GetNextContentAs
<Text
>();
4108 MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange
);
4111 return pointToPutCaret
;
4113 if (MOZ_UNLIKELY(pointToPutCaretOrError
.isErr())) {
4114 // Don't warn here since it should be done in the lambda.
4115 return pointToPutCaretOrError
.propagateErr();
4117 pointToPutCaret
= pointToPutCaretOrError
.unwrap();
4120 // Look for siblings that are correct type of node
4121 nsStaticAtom
* const bigOrSmallTagName
=
4122 aIncrementOrDecrement
== FontSize::incr
? nsGkAtoms::big
4124 nsCOMPtr
<nsIContent
> sibling
= HTMLEditUtils::GetPreviousSibling(
4125 *textNodeForTheRange
, {WalkTreeOption::IgnoreNonEditableNode
});
4126 if (sibling
&& sibling
->IsHTMLElement(bigOrSmallTagName
)) {
4127 // Previous sib is already right kind of inline node; slide this over
4128 Result
<MoveNodeResult
, nsresult
> moveTextNodeResult
=
4129 MoveNodeToEndWithTransaction(*textNodeForTheRange
, *sibling
);
4130 if (MOZ_UNLIKELY(moveTextNodeResult
.isErr())) {
4131 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4132 return moveTextNodeResult
.propagateErr();
4134 MoveNodeResult unwrappedMoveTextNodeResult
= moveTextNodeResult
.unwrap();
4135 unwrappedMoveTextNodeResult
.MoveCaretPointTo(
4136 pointToPutCaret
, *this, {SuggestCaret::OnlyIfHasSuggestion
});
4137 // XXX Should we return the new container?
4138 return CreateElementResult::NotHandled(std::move(pointToPutCaret
));
4140 sibling
= HTMLEditUtils::GetNextSibling(
4141 *textNodeForTheRange
, {WalkTreeOption::IgnoreNonEditableNode
});
4142 if (sibling
&& sibling
->IsHTMLElement(bigOrSmallTagName
)) {
4143 // Following sib is already right kind of inline node; slide this over
4144 Result
<MoveNodeResult
, nsresult
> moveTextNodeResult
=
4145 MoveNodeWithTransaction(*textNodeForTheRange
,
4146 EditorDOMPoint(sibling
, 0u));
4147 if (MOZ_UNLIKELY(moveTextNodeResult
.isErr())) {
4148 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4149 return moveTextNodeResult
.propagateErr();
4151 MoveNodeResult unwrappedMoveTextNodeResult
= moveTextNodeResult
.unwrap();
4152 unwrappedMoveTextNodeResult
.MoveCaretPointTo(
4153 pointToPutCaret
, *this, {SuggestCaret::OnlyIfHasSuggestion
});
4154 // XXX Should we return the new container?
4155 return CreateElementResult::NotHandled(std::move(pointToPutCaret
));
4158 // Else wrap the node inside font node with appropriate relative size
4159 Result
<CreateElementResult
, nsresult
> wrapTextInBigOrSmallElementResult
=
4160 InsertContainerWithTransaction(*textNodeForTheRange
,
4161 MOZ_KnownLive(*bigOrSmallTagName
));
4162 if (wrapTextInBigOrSmallElementResult
.isErr()) {
4163 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
4164 return wrapTextInBigOrSmallElementResult
;
4166 CreateElementResult unwrappedWrapTextInBigOrSmallElementResult
=
4167 wrapTextInBigOrSmallElementResult
.unwrap();
4168 unwrappedWrapTextInBigOrSmallElementResult
.MoveCaretPointTo(
4169 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
4170 return CreateElementResult(
4171 unwrappedWrapTextInBigOrSmallElementResult
.UnwrapNewNode(),
4172 std::move(pointToPutCaret
));
4175 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::SetFontSizeOfFontElementChildren(
4176 nsIContent
& aContent
, FontSize aIncrementOrDecrement
) {
4177 // This routine looks for all the font nodes in the tree rooted by aNode,
4178 // including aNode itself, looking for font nodes that have the size attr
4179 // set. Any such nodes need to have big or small put inside them, since
4180 // they override any big/small that are above them.
4182 // If this is a font node with size, put big/small inside it.
4183 if (aContent
.IsHTMLElement(nsGkAtoms::font
) &&
4184 aContent
.AsElement()->HasAttr(nsGkAtoms::size
)) {
4185 EditorDOMPoint pointToPutCaret
;
4187 // Cycle through children and adjust relative font size.
4188 AutoTArray
<OwningNonNull
<nsIContent
>, 32> arrayOfContents
;
4189 HTMLEditUtils::CollectAllChildren(aContent
, arrayOfContents
);
4190 for (const auto& child
: arrayOfContents
) {
4191 // MOZ_KnownLive because of bug 1622253
4192 Result
<EditorDOMPoint
, nsresult
> setFontSizeOfChildResult
=
4193 SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(child
),
4194 aIncrementOrDecrement
);
4195 if (MOZ_UNLIKELY(setFontSizeOfChildResult
.isErr())) {
4196 NS_WARNING("HTMLEditor::WrapContentInBigOrSmallElement() failed");
4197 return setFontSizeOfChildResult
;
4199 if (setFontSizeOfChildResult
.inspect().IsSet()) {
4200 pointToPutCaret
= setFontSizeOfChildResult
.unwrap();
4204 // WrapContentInBigOrSmallElement already calls us recursively,
4205 // so we don't need to check our children again.
4206 return pointToPutCaret
;
4209 // Otherwise cycle through the children.
4210 EditorDOMPoint pointToPutCaret
;
4211 AutoTArray
<OwningNonNull
<nsIContent
>, 32> arrayOfContents
;
4212 HTMLEditUtils::CollectAllChildren(aContent
, arrayOfContents
);
4213 for (const auto& child
: arrayOfContents
) {
4214 // MOZ_KnownLive because of bug 1622253
4215 Result
<EditorDOMPoint
, nsresult
> fontSizeChangeResult
=
4216 SetFontSizeOfFontElementChildren(MOZ_KnownLive(child
),
4217 aIncrementOrDecrement
);
4218 if (MOZ_UNLIKELY(fontSizeChangeResult
.isErr())) {
4219 NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed");
4220 return fontSizeChangeResult
;
4222 if (fontSizeChangeResult
.inspect().IsSet()) {
4223 pointToPutCaret
= fontSizeChangeResult
.unwrap();
4227 return pointToPutCaret
;
4230 Result
<EditorDOMPoint
, nsresult
> HTMLEditor::SetFontSizeWithBigOrSmallElement(
4231 nsIContent
& aContent
, FontSize aIncrementOrDecrement
) {
4232 nsStaticAtom
* const bigOrSmallTagName
=
4233 aIncrementOrDecrement
== FontSize::incr
? nsGkAtoms::big
4236 // Is aContent the opposite of what we want?
4237 if ((aIncrementOrDecrement
== FontSize::incr
&&
4238 aContent
.IsHTMLElement(nsGkAtoms::small
)) ||
4239 (aIncrementOrDecrement
== FontSize::decr
&&
4240 aContent
.IsHTMLElement(nsGkAtoms::big
))) {
4241 // First, populate any nested font elements that have the size attr set
4242 Result
<EditorDOMPoint
, nsresult
> fontSizeChangeOfDescendantsResult
=
4243 SetFontSizeOfFontElementChildren(aContent
, aIncrementOrDecrement
);
4244 if (MOZ_UNLIKELY(fontSizeChangeOfDescendantsResult
.isErr())) {
4245 NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed");
4246 return fontSizeChangeOfDescendantsResult
;
4248 EditorDOMPoint pointToPutCaret
= fontSizeChangeOfDescendantsResult
.unwrap();
4249 // In that case, just unwrap the <big> or <small> element.
4250 Result
<EditorDOMPoint
, nsresult
> unwrapBigOrSmallElementResult
=
4251 RemoveContainerWithTransaction(MOZ_KnownLive(*aContent
.AsElement()));
4252 if (MOZ_UNLIKELY(unwrapBigOrSmallElementResult
.isErr())) {
4253 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
4254 return unwrapBigOrSmallElementResult
;
4256 if (unwrapBigOrSmallElementResult
.inspect().IsSet()) {
4257 pointToPutCaret
= unwrapBigOrSmallElementResult
.unwrap();
4259 return pointToPutCaret
;
4262 if (HTMLEditUtils::CanNodeContain(*bigOrSmallTagName
, aContent
)) {
4263 // First, populate any nested font tags that have the size attr set
4264 Result
<EditorDOMPoint
, nsresult
> fontSizeChangeOfDescendantsResult
=
4265 SetFontSizeOfFontElementChildren(aContent
, aIncrementOrDecrement
);
4266 if (MOZ_UNLIKELY(fontSizeChangeOfDescendantsResult
.isErr())) {
4267 NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed");
4268 return fontSizeChangeOfDescendantsResult
;
4271 EditorDOMPoint pointToPutCaret
= fontSizeChangeOfDescendantsResult
.unwrap();
4273 // Next, if next or previous is <big> or <small>, move aContent into it.
4274 nsCOMPtr
<nsIContent
> sibling
= HTMLEditUtils::GetPreviousSibling(
4275 aContent
, {WalkTreeOption::IgnoreNonEditableNode
});
4276 if (sibling
&& sibling
->IsHTMLElement(bigOrSmallTagName
)) {
4277 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
4278 MoveNodeToEndWithTransaction(aContent
, *sibling
);
4279 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
4280 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
4281 return moveNodeResult
.propagateErr();
4283 MoveNodeResult unwrappedMoveNodeResult
= moveNodeResult
.unwrap();
4284 unwrappedMoveNodeResult
.MoveCaretPointTo(
4285 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
4286 return pointToPutCaret
;
4289 sibling
= HTMLEditUtils::GetNextSibling(
4290 aContent
, {WalkTreeOption::IgnoreNonEditableNode
});
4291 if (sibling
&& sibling
->IsHTMLElement(bigOrSmallTagName
)) {
4292 Result
<MoveNodeResult
, nsresult
> moveNodeResult
=
4293 MoveNodeWithTransaction(aContent
, EditorDOMPoint(sibling
, 0u));
4294 if (MOZ_UNLIKELY(moveNodeResult
.isErr())) {
4295 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
4296 return moveNodeResult
.propagateErr();
4298 MoveNodeResult unwrappedMoveNodeResult
= moveNodeResult
.unwrap();
4299 unwrappedMoveNodeResult
.MoveCaretPointTo(
4300 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
4301 return pointToPutCaret
;
4304 // Otherwise, wrap aContent in new <big> or <small>
4305 Result
<CreateElementResult
, nsresult
> wrapInBigOrSmallElementResult
=
4306 InsertContainerWithTransaction(aContent
,
4307 MOZ_KnownLive(*bigOrSmallTagName
));
4308 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult
.isErr())) {
4309 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
4310 return Err(wrapInBigOrSmallElementResult
.unwrapErr());
4312 CreateElementResult unwrappedWrapInBigOrSmallElementResult
=
4313 wrapInBigOrSmallElementResult
.unwrap();
4314 MOZ_ASSERT(unwrappedWrapInBigOrSmallElementResult
.GetNewNode());
4315 unwrappedWrapInBigOrSmallElementResult
.MoveCaretPointTo(
4316 pointToPutCaret
, {SuggestCaret::OnlyIfHasSuggestion
});
4317 return pointToPutCaret
;
4320 // none of the above? then cycle through the children.
4321 // MOOSE: we should group the children together if possible
4322 // into a single "big" or "small". For the moment they are
4323 // each getting their own.
4324 EditorDOMPoint pointToPutCaret
;
4325 AutoTArray
<OwningNonNull
<nsIContent
>, 32> arrayOfContents
;
4326 HTMLEditUtils::CollectAllChildren(aContent
, arrayOfContents
);
4327 for (const auto& child
: arrayOfContents
) {
4328 // MOZ_KnownLive because of bug 1622253
4329 Result
<EditorDOMPoint
, nsresult
> setFontSizeOfChildResult
=
4330 SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(child
),
4331 aIncrementOrDecrement
);
4332 if (MOZ_UNLIKELY(setFontSizeOfChildResult
.isErr())) {
4333 NS_WARNING("HTMLEditor::SetFontSizeWithBigOrSmallElement() failed");
4334 return setFontSizeOfChildResult
;
4336 if (setFontSizeOfChildResult
.inspect().IsSet()) {
4337 pointToPutCaret
= setFontSizeOfChildResult
.unwrap();
4341 return pointToPutCaret
;
4344 NS_IMETHODIMP
HTMLEditor::GetFontFaceState(bool* aMixed
, nsAString
& outFace
) {
4345 if (NS_WARN_IF(!aMixed
)) {
4346 return NS_ERROR_INVALID_ARG
;
4352 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
4353 if (NS_WARN_IF(!editActionData
.CanHandle())) {
4354 return NS_ERROR_NOT_INITIALIZED
;
4357 bool first
, any
, all
;
4359 nsresult rv
= GetInlinePropertyBase(
4360 EditorInlineStyle(*nsGkAtoms::font
, nsGkAtoms::face
), nullptr, &first
,
4361 &any
, &all
, &outFace
);
4362 if (NS_FAILED(rv
)) {
4364 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
4366 return EditorBase::ToGenericNSResult(rv
);
4369 return NS_OK
; // mixed
4376 // if there is no font face, check for tt
4377 rv
= GetInlinePropertyBase(EditorInlineStyle(*nsGkAtoms::tt
), nullptr, &first
,
4378 &any
, &all
, nullptr);
4379 if (NS_FAILED(rv
)) {
4380 NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed");
4381 return EditorBase::ToGenericNSResult(rv
);
4384 return NS_OK
; // mixed
4388 outFace
.AssignLiteral("tt");
4392 // there was no font face attrs of any kind. We are in normal font.
4399 nsresult
HTMLEditor::GetFontColorState(bool* aMixed
, nsAString
& aOutColor
) {
4400 if (NS_WARN_IF(!aMixed
)) {
4401 return NS_ERROR_INVALID_ARG
;
4405 aOutColor
.Truncate();
4407 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
4408 if (NS_WARN_IF(!editActionData
.CanHandle())) {
4409 return NS_ERROR_NOT_INITIALIZED
;
4412 bool first
, any
, all
;
4413 nsresult rv
= GetInlinePropertyBase(
4414 EditorInlineStyle(*nsGkAtoms::font
, nsGkAtoms::color
), nullptr, &first
,
4415 &any
, &all
, &aOutColor
);
4416 if (NS_FAILED(rv
)) {
4418 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
4420 return EditorBase::ToGenericNSResult(rv
);
4424 return NS_OK
; // mixed
4432 // there was no font color attrs of any kind..
4433 aOutColor
.Truncate();
4439 // The return value is true only if the instance of the HTML editor we created
4440 // can handle CSS styles and if the CSS preference is checked.
4441 NS_IMETHODIMP
HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled
) {
4442 *aIsCSSEnabled
= IsCSSEnabled();
4446 bool HTMLEditor::HasStyleOrIdOrClassAttribute(Element
& aElement
) {
4447 return aElement
.HasNonEmptyAttr(nsGkAtoms::style
) ||
4448 aElement
.HasNonEmptyAttr(nsGkAtoms::_class
) ||
4449 aElement
.HasNonEmptyAttr(nsGkAtoms::id
);
4452 } // namespace mozilla