Backed out 2 changesets (bug 1900622) for causing Bug 1908553 and ktlint failure...
[gecko.git] / editor / libeditor / HTMLStyleEditor.cpp
blobe63ee3404c0826cedff5ba9bd62be44b0f33929d
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/. */
6 #include "ErrorList.h"
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"
34 #include "nsAtom.h"
35 #include "nsAttrName.h"
36 #include "nsAttrValue.h"
37 #include "nsCaseTreatment.h"
38 #include "nsColor.h"
39 #include "nsComponentManagerUtils.h"
40 #include "nsDebug.h"
41 #include "nsError.h"
42 #include "nsGkAtoms.h"
43 #include "nsIContent.h"
44 #include "nsINode.h"
45 #include "nsIPrincipal.h"
46 #include "nsISupportsImpl.h"
47 #include "nsLiteralString.h"
48 #include "nsNameSpaceManager.h"
49 #include "nsRange.h"
50 #include "nsReadableUtils.h"
51 #include "nsString.h"
52 #include "nsStringFwd.h"
53 #include "nsStyledElement.h"
54 #include "nsTArray.h"
55 #include "nsTextNode.h"
56 #include "nsUnicharUtils.h"
58 namespace mozilla {
60 using namespace dom;
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(
86 *this,
87 HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true),
88 aPrincipal);
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);
94 break;
95 case EditAction::eSetColorProperty:
96 case EditAction::eSetBackgroundColorPropertyInline:
97 editActionData.SetColorData(aValue);
98 break;
99 default:
100 break;
103 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
104 if (NS_FAILED(rv)) {
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,
113 __FUNCTION__);
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
126 // types text there.
127 if (!HTMLEditUtils::MaybeCSSSpecificColorValue(value)) {
128 HTMLEditUtils::GetNormalizedHTMLColorValue(value, value);
130 } else {
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));
156 } else {
157 stylesToRemove.AppendElement(
158 EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face));
159 // Override property, attribute and value if the new font face value is
160 // "tt".
161 property = nsGkAtoms::tt;
162 attribute = nullptr;
163 value.Truncate();
168 if (!stylesToRemove.IsEmpty()) {
169 nsresult rv = RemoveInlinePropertiesAsSubAction(stylesToRemove);
170 if (NS_FAILED(rv)) {
171 NS_WARNING("HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
172 return rv;
176 AutoTArray<EditorInlineStyleAndValue, 1> styleToSet;
177 styleToSet.AppendElement(
178 attribute
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(
196 *this,
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);
203 break;
204 case EditAction::eSetColorProperty:
205 case EditAction::eSetBackgroundColorPropertyInline:
206 editActionData.SetColorData(aValue);
207 break;
208 default:
209 break;
211 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
212 if (NS_FAILED(rv)) {
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);
228 template <size_t N>
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);
242 return NS_OK;
245 // XXX Shouldn't we return before calling `CommitComposition()`?
246 if (IsPlaintextMailComposer()) {
247 return NS_OK;
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()) {
257 return NS_OK;
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,
286 *editingHost);
287 if (NS_FAILED(rv)) {
288 NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed");
289 return rv;
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");
297 return rv;
300 template <size_t N>
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();
310 auto rangeOrError =
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())) {
332 NS_WARNING(
333 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
334 "failed");
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,
346 aEditingHost);
347 if (MOZ_UNLIKELY(rangeOrError.isErr())) {
348 NS_WARNING(
349 "HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but "
350 "ignored");
351 return EditorDOMRange();
353 return EditorDOMRange(rangeOrError.unwrap());
354 }();
355 if (MOZ_UNLIKELY(rangeOrError.isErr())) {
356 return rangeOrError.unwrapErr();
359 const EditorDOMRange range = rangeOrError.unwrap();
360 if (!range.IsPositioned()) {
361 continue;
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())) {
370 NS_WARNING(
371 "AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle() "
372 "failed");
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 =
380 inlineStyleSetter
381 .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
382 *this, *emptyTextNode);
383 if (MOZ_UNLIKELY(caretPointOrError.isErr())) {
384 NS_WARNING(
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");
392 continue;
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<
408 EditorRawDOMPoint>(
409 *inlineStyleSetter.FirstHandledPointRef()
410 .ContainerAs<nsIContent>());
411 const auto endPoint =
412 !inlineStyleSetter.LastHandledPointRef().IsEndOfContainer()
413 ? inlineStyleSetter.LastHandledPointRef()
414 .To<EditorRawDOMPoint>()
415 : HTMLEditUtils::GetDeepestEditableEndPointOf<
416 EditorRawDOMPoint>(
417 *inlineStyleSetter.LastHandledPointRef()
418 .ContainerAs<nsIContent>());
419 nsresult rv = domRange->SetStartAndEnd(
420 startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary());
421 if (NS_SUCCEEDED(rv)) {
422 trackRange.StopTracking();
423 return;
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();
449 continue;
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,
458 // don't warn it.
459 if (NS_SUCCEEDED(
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())) {
468 continue;
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(),
473 EditorType::HTML)) {
474 continue;
476 // We shouldn't wrap invisible text node in new inline element.
477 if (node->IsText() &&
478 !HTMLEditUtils::IsVisibleTextNode(*node->AsText())) {
479 continue;
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>(),
489 EditorType::HTML)) {
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 =
511 inlineStyleSetter
512 .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
513 *this, MOZ_KnownLive(*content));
514 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) {
515 NS_WARNING(
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>(),
528 EditorType::HTML)) {
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();
547 return NS_OK;
550 // static
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>();
584 }();
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()) {
592 return RefPtr<Text>(
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) {
623 return true;
626 // Now look for things like <font>
627 if (mAttribute) {
628 nsString attrValue;
629 if (aElement.IsHTMLElement(&HTMLPropertyRef()) &&
630 !HTMLEditUtils::ElementHasAttributeExcept(aElement, *mAttribute) &&
631 aElement.GetAttr(mAttribute, attrValue)) {
632 if (attrValue.Equals(mAttributeValue,
633 nsCaseInsensitiveStringComparator)) {
634 return true;
636 if (mAttribute == nsGkAtoms::color ||
637 mAttribute == nsGkAtoms::bgcolor) {
638 if (aHTMLEditor.IsCSSEnabled()) {
639 if (HTMLEditUtils::IsSameCSSColorValue(mAttributeValue,
640 attrValue)) {
641 return true;
643 } else if (HTMLEditUtils::IsSameHTMLColorValue(
644 mAttributeValue, attrValue,
645 HTMLEditUtils::TransparentKeyword::Allowed)) {
646 return true;
652 if (!isCSSEditable) {
653 return false;
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
659 // style supports it
660 if (!aElement.IsHTMLElement(nsGkAtoms::span) ||
661 !aElement.HasAttr(nsGkAtoms::style) ||
662 HTMLEditUtils::ElementHasAttributeExcept(aElement, *nsGkAtoms::style)) {
663 return false;
666 nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement);
667 if (MOZ_UNLIKELY(!styledElement)) {
668 return false;
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");
679 return false;
681 nsStyledElement* styledNewSpanElement =
682 nsStyledElement::FromNode(newSpanElement);
683 if (MOZ_UNLIKELY(!styledNewSpanElement)) {
684 return false;
686 // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
687 // RefPtr.
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);
698 return false;
700 return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
701 *styledElement);
704 bool HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerToSetStyle(
705 nsStyledElement& aStyledElement) const {
706 if (!HTMLEditUtils::IsContainerNode(aStyledElement) ||
707 !EditorUtils::IsEditableContent(aStyledElement, EditorType::HTML)) {
708 return false;
711 // If it has `style` attribute, let's use it.
712 if (aStyledElement.HasAttr(nsGkAtoms::style)) {
713 return true;
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)) {
721 return true;
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,
729 nsGkAtoms::del)) {
730 return true;
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)) {
737 return true;
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())) {
743 return false;
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();
759 previousSibling;
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)) {
766 return false;
768 continue;
771 for (nsIContent* nextSibling = aStyledElement.GetNextSibling(); nextSibling;
772 nextSibling = nextSibling->GetNextSibling()) {
773 if (nextSibling->IsElement()) {
774 if (!HTMLEditUtils::IsInvisibleBRElement(*nextSibling)) {
775 return false;
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)) {
782 return false;
784 continue;
787 return true;
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>`.
794 return false;
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,
822 value);
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,
833 &mAttributeValue)) {
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())) {
854 NS_WARNING(
855 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
856 "point");
857 return Err(NS_ERROR_FAILURE);
859 return splitNodeResult;
860 }();
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()
870 : &aText,
871 aStartOffset);
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())) {
883 NS_WARNING(
884 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
885 "point");
886 return Err(NS_ERROR_FAILURE);
888 return splitNodeResult;
889 }();
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>()
912 : nullptr;
913 Text* const middleTextNode =
914 unwrappedSplitAtStartResult.DidSplit()
915 ? unwrappedSplitAtStartResult.GetNextContentAs<Text>()
916 : (unwrappedSplitAtEndResult.DidSplit()
917 ? unwrappedSplitAtEndResult.GetPreviousContentAs<Text>()
918 : &aText);
919 Text* const rightTextNode =
920 unwrappedSplitAtEndResult.DidSplit()
921 ? unwrappedSplitAtEndResult.GetNextContentAs<Text>()
922 : nullptr;
923 if (mAttribute) {
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,
950 rightTextNode,
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,
979 rightTextNode,
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())) {
990 NS_WARNING(
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
1019 // keep it alive.
1020 Result<CaretPoint, nsresult> setInlinePropertyResult =
1021 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(
1022 aHTMLEditor, MOZ_KnownLive(content));
1023 if (MOZ_UNLIKELY(setInlinePropertyResult.isErr())) {
1024 NS_WARNING(
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);
1057 if (!nextElement) {
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);
1085 return CaretPoint(
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,
1117 value);
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())) {
1136 return true;
1138 // bgcolor is always done using CSS
1139 if (mAttribute == nsGkAtoms::bgcolor) {
1140 return true;
1142 // called for removing parent style, we should use CSS with <span> element.
1143 if (IsStyleToInvert()) {
1144 return true;
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);
1152 return false;
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(
1161 result.isOk(),
1162 "AutoInlineStyleSetter::ApplyCSSTextDecoration() failed");
1163 return result;
1165 EditorDOMPoint pointToPutCaret;
1166 RefPtr<nsStyledElement> styledElement = [&]() -> nsStyledElement* {
1167 auto* const styledElement = nsStyledElement::FromNode(&aContent);
1168 return styledElement && ElementIsGoodContainerToSetStyle(*styledElement)
1169 ? styledElement
1170 : nullptr;
1171 }();
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,
1177 *nsGkAtoms::span);
1178 if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
1179 NS_WARNING(
1180 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
1181 "failed");
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,
1203 &mAttributeValue);
1204 if (MOZ_UNLIKELY(result.isErr())) {
1205 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
1206 return Err(NS_ERROR_EDITOR_DESTROYED);
1208 NS_WARNING(
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,
1221 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");
1237 return Err(rv);
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 {
1251 nsresult rv =
1252 aNewElement.SetAttr(kNameSpaceID_None, mAttribute,
1253 attributeValue, false);
1254 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1255 "Element::SetAttr() failed");
1256 return rv;
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());
1264 return CaretPoint(
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");
1281 } else {
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)) {
1292 NS_WARNING(
1293 "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::text_decoration) "
1294 "failed");
1295 return Err(rv);
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())) {
1305 NS_WARNING(
1306 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() "
1307 "failed");
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");
1322 } else {
1323 newTextDecorationValue.AppendLiteral(u"line-through");
1326 styledElement =
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
1334 // text-decoration.
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.
1351 else {
1352 Result<CreateElementResult, nsresult> wrapInSpanElementResult =
1353 aHTMLEditor.InsertContainerWithTransaction(aContent, *nsGkAtoms::span);
1354 if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) {
1355 NS_WARNING(
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");
1377 return Err(rv);
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
1449 // keep it alive.
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);
1466 if (!element) {
1467 return false;
1469 if (IsRepresentedBy(*element)) {
1470 return true;
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);
1478 // static
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())))) {
1490 return nullptr;
1492 if (nsIContent* nextSibling = parent->GetNextSibling()) {
1493 return nextSibling;
1496 return nullptr;
1497 }();
1498 return nextContentInRange &&
1499 EditorUtils::IsEditableContent(*nextContentInRange,
1500 EditorType::HTML) &&
1501 !HTMLEditUtils::IsBlockElement(
1502 *nextContentInRange,
1503 BlockInlineCheck::UseComputedDisplayOutsideStyle)
1504 ? nextContentInRange
1505 : nullptr;
1508 // static
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())))) {
1520 return nullptr;
1522 if (nsIContent* previousSibling = parent->GetPreviousSibling()) {
1523 return previousSibling;
1526 return nullptr;
1527 }();
1528 return previousContentInRange &&
1529 EditorUtils::IsEditableContent(*previousContentInRange,
1530 EditorType::HTML) &&
1531 !HTMLEditUtils::IsBlockElement(
1532 *previousContentInRange,
1533 BlockInlineCheck::UseComputedDisplayOutsideStyle)
1534 ? previousContentInRange
1535 : nullptr;
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()) {
1553 return nullptr;
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>();
1562 }();
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)) {
1578 break;
1580 // If we reach a text node, the minimized range starts from start of it.
1581 if (child->IsText()) {
1582 startPoint.Set(child, 0u);
1583 break;
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) {
1590 break;
1592 // We should not start from an atomic element such as <br>, <img>, etc.
1593 if (!HTMLEditUtils::IsContainerNode(*child)) {
1594 break;
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)) {
1599 break;
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)) {
1605 break;
1607 startPoint.Set(child, 0u);
1609 return startPoint;
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()) {
1627 return nullptr;
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>();
1636 }();
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)) {
1652 break;
1654 // If we reach a text node, the minimized range starts from start of it.
1655 if (child->IsText()) {
1656 endPoint.SetToEndOf(child);
1657 break;
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) {
1664 break;
1666 // We should not end in an atomic element such as <br>, <img>, etc.
1667 if (!HTMLEditUtils::IsContainerNode(*child)) {
1668 break;
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)) {
1673 break;
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)) {
1679 break;
1681 endPoint.SetToEndOf(child);
1683 return endPoint;
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()) {
1697 return startPoint;
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)) {
1712 break;
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);
1729 return startPoint;
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()) {
1743 return endPoint;
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)) {
1758 break;
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);
1775 return endPoint;
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>())))) {
1803 break;
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>())))) {
1818 break;
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>
1826 // element.
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 const WSScanResult nextContentData =
1918 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary(
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>();
1948 } else {
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 = [&]() {
1958 iter.Last();
1959 return nsIContent::FromNodeOrNull(iter.GetCurrentNode());
1960 }();
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>
1985 // </span>
1986 // ghi
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,
1992 startPoint);
1993 MOZ_ASSERT(startPoint.IsSet());
1994 endPoint =
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));
2007 #if 0
2008 fprintf(stderr,
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());
2016 #endif
2017 return finalRange;
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,
2038 aSplitAtEdges);
2039 if (MOZ_UNLIKELY(result.isErr())) {
2040 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
2041 return result;
2043 tracker.FlushAndStopTracking();
2044 if (result.inspect().Handled()) {
2045 auto startOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>();
2046 if (!startOfRange.IsSet()) {
2047 result.inspect().IgnoreCaretPointSuggestion();
2048 NS_WARNING(
2049 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
2050 "split point");
2051 return Err(NS_ERROR_FAILURE);
2053 range.SetStart(std::move(startOfRange));
2054 } else if (MOZ_UNLIKELY(!range.IsPositioned())) {
2055 NS_WARNING(
2056 "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected "
2057 "DOM tree");
2058 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2060 return result;
2061 }();
2062 if (MOZ_UNLIKELY(resultAtStart.isErr())) {
2063 return resultAtStart.propagateErr();
2065 SplitNodeResult unwrappedResultAtStart = resultAtStart.unwrap();
2067 // second verse, same as the first...
2068 auto resultAtEnd =
2069 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> {
2070 AutoTrackDOMRange tracker(RangeUpdaterRef(), &range);
2071 Result<SplitNodeResult, nsresult> result =
2072 SplitAncestorStyledInlineElementsAt(range.EndRef(), aStyle,
2073 aSplitAtEdges);
2074 if (MOZ_UNLIKELY(result.isErr())) {
2075 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
2076 return result;
2078 tracker.FlushAndStopTracking();
2079 if (result.inspect().Handled()) {
2080 auto endOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>();
2081 if (!endOfRange.IsSet()) {
2082 result.inspect().IgnoreCaretPointSuggestion();
2083 NS_WARNING(
2084 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
2085 "split point");
2086 return Err(NS_ERROR_FAILURE);
2088 range.SetEnd(std::move(endOfRange));
2089 } else if (MOZ_UNLIKELY(!range.IsPositioned())) {
2090 NS_WARNING(
2091 "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected "
2092 "DOM tree");
2093 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
2095 return result;
2096 }();
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
2127 // IsCSSEnabled().
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,
2135 nsGkAtoms::html) ||
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))) {
2142 break;
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> {
2153 if (!handleCSS) {
2154 return false;
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
2158 // styles
2159 if (aStyle.IsCSSRemovable(*element)) {
2160 nsAutoString firstValue;
2161 Result<bool, nsresult> isSpecifiedByCSSOrError =
2162 CSSEditUtils::IsSpecifiedCSSEquivalentTo(*this, *element, aStyle,
2163 firstValue);
2164 if (MOZ_UNLIKELY(isSpecifiedByCSSOrError.isErr())) {
2165 result.IgnoreCaretPointSuggestion();
2166 NS_WARNING("CSSEditUtils::IsSpecifiedCSSEquivalentTo() failed");
2167 return isSpecifiedByCSSOrError;
2169 if (isSpecifiedByCSSOrError.unwrap()) {
2170 return true;
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()) {
2180 nsAutoString value;
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();
2186 return Err(rv);
2188 if (!value.IsEmpty()) {
2189 return true;
2192 return false;
2193 }();
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))) {
2208 continue;
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);
2219 if (attrValue) {
2220 if (aStyle.mAttribute == nsGkAtoms::size) {
2221 if (nsContentUtils::ParseLegacyFontSize(
2222 aStyle.AsInlineStyleAndValue().mAttributeValue) ==
2223 attrValue->GetIntegerValue()) {
2224 continue;
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) {
2233 continue;
2235 } else if (attrValue->Equals(
2236 aStyle.AsInlineStyleAndValue().mAttributeValue,
2237 eIgnoreCase)) {
2238 continue;
2243 // If aProperty is nullptr, we need to split any style.
2244 else if (!EditorUtils::IsEditableContent(element, EditorType::HTML) ||
2245 !HTMLEditUtils::IsRemovableInlineStyleElement(*element)) {
2246 continue;
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>(),
2259 aSplitAtEdges);
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
2271 // is middle of it.
2272 if (!unwrappedSplitNodeResult.Handled()) {
2273 continue;
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
2322 // given point.
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
2331 // above case:
2332 // <p><b><i>bc</i></b></p>
2333 // ^^
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");
2348 return Err(rv);
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>`.
2366 // ^^^^^^^^^^^^^^
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>`
2375 // element.
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
2409 // changed to:
2410 // <div><b><i>abc</i></b><b><i>[]</i></b><b><i></i></b></div>
2411 // ^^^^^^^^^^^^^^
2412 // We will change it to:
2413 // <div><b><i>abc</i></b><b><i>[]</i></b></div>
2414 // ^^
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},
2422 &seenBR)) {
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
2430 // it later.
2431 if (brElement) {
2432 nsresult rv = DeleteNodeWithTransaction(*brElement);
2433 if (NS_FAILED(rv)) {
2434 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
2435 return Err(rv);
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");
2445 return Err(rv);
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.
2481 if (brElement) {
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});
2493 } else {
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>
2513 // ^^^^^^^
2514 // - <b><i>abc</i></b><i><br></i><b></b>
2515 // ^^^^^^^
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");
2527 return Err(rv);
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");
2555 return Err(rv);
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>`
2568 // ^^^^^^^^^
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
2576 // selection.
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()) {
2602 continue;
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)) {
2625 return false;
2627 MOZ_ASSERT(!aStyleToRemove.IsStyleToClearAllInlineStyles());
2628 Result<bool, nsresult> elementHasSpecifiedCSSEquivalentStylesOrError =
2629 CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(*this, aElement,
2630 aStyleToRemove);
2631 NS_WARNING_ASSERTION(
2632 elementHasSpecifiedCSSEquivalentStylesOrError.isOk(),
2633 "CSSEditUtils::HaveSpecifiedCSSEquivalentStyles() failed");
2634 return elementHasSpecifiedCSSEquivalentStylesOrError;
2635 }();
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(
2650 NS_SUCCEEDED(rv),
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()) {
2661 nsAutoString value;
2662 nsresult rv = CSSEditUtils::GetSpecifiedProperty(
2663 aElement, *nsGkAtoms::vertical_align, value);
2664 if (NS_FAILED(rv)) {
2665 NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed");
2666 return Err(rv);
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,
2672 value);
2673 if (NS_FAILED(rv)) {
2674 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
2675 return Err(rv);
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)) {
2693 return true;
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) {
2698 return true;
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
2712 // case.
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,
2735 attrKeepStaying)
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);
2743 }();
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.
2771 return true;
2774 if (ReplaceWithNewSpan()) {
2775 // Before cloning the attribute to new element, let's remove it.
2776 if (aStyleToRemove.mAttribute) {
2777 nsresult rv =
2778 RemoveAttributeWithTransaction(aElement, *aStyleToRemove.mAttribute);
2779 if (NS_FAILED(rv)) {
2780 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
2781 return Err(rv);
2784 if (aSpecifiedStyle == SpecifiedStyle::Discard) {
2785 nsresult rv = RemoveAttributeWithTransaction(aElement, *nsGkAtoms::style);
2786 if (NS_FAILED(rv)) {
2787 NS_WARNING(
2788 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::style) "
2789 "failed");
2790 return Err(rv);
2792 rv = RemoveAttributeWithTransaction(aElement, *nsGkAtoms::_class);
2793 if (NS_FAILED(rv)) {
2794 NS_WARNING(
2795 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::_class) "
2796 "failed");
2797 return Err(rv);
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);
2813 }();
2814 if (MOZ_UNLIKELY(replaceWithSpanResult.isErr())) {
2815 NS_WARNING(
2816 "HTMLEditor::ReplaceContainerWithTransaction(nsGkAtoms::span) "
2817 "failed");
2818 return replaceWithSpanResult.propagateErr();
2820 CreateElementResult unwrappedReplaceWithSpanResult =
2821 replaceWithSpanResult.unwrap();
2822 if (AllowsTransactionsToChangeSelection()) {
2823 unwrappedReplaceWithSpanResult.MoveCaretPointTo(
2824 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion});
2825 } else {
2826 unwrappedReplaceWithSpanResult.IgnoreCaretPointSuggestion();
2828 return pointToPutCaret;
2831 auto RemoveElement = [&]() {
2832 if (aStyleToRemove.IsStyleToClearAllInlineStyles()) {
2833 MOZ_ASSERT(HTMLEditUtils::IsRemovableInlineStyleElement(aElement));
2834 return true;
2836 // If the element still has some attributes, we should not remove it to keep
2837 // current presentation and/or semantics.
2838 if (elementHasNecessaryAttributes) {
2839 return false;
2841 // If the style is represented by the element, let's remove it.
2842 if (isStyleRepresentedByElement) {
2843 return true;
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)) {
2848 return true;
2850 return false;
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
2870 // CSS style above.
2871 if (isStyleRepresentedByElement && aStyleToRemove.mAttribute) {
2872 nsresult rv =
2873 RemoveAttributeWithTransaction(aElement, *aStyleToRemove.mAttribute);
2874 if (NS_FAILED(rv)) {
2875 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
2876 return Err(rv);
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)) {
2895 continue;
2897 newRange.SetStart(EditorRawDOMPoint(element));
2899 for (Element* element :
2900 aRange.EndRef().GetContainer()->InclusiveAncestorsOfType<Element>()) {
2901 if (!HTMLEditUtils::IsNamedAnchor(element)) {
2902 continue;
2904 newRange.SetEnd(EditorRawDOMPoint::After(*element));
2906 return newRange;
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)) {
2924 break;
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)) {
2932 break;
2934 newRange.SetEnd(
2935 EditorRawDOMPoint::After(*newRange.EndRef().ContainerAs<nsIContent>()));
2937 return newRange;
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());
2947 *aAny = false;
2948 *aAll = true;
2949 *aFirst = false;
2950 bool first = true;
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?
2956 if (range) {
2957 // For each range, set a flag
2958 bool firstNodeInRange = true;
2960 if (isCollapsed) {
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);
2969 if (outValue) {
2970 outValue->Assign(tOutString);
2972 return state;
2974 return mPendingStylesToApplyToNewContent->GetStyleState(
2975 *aStyle.mHTMLProperty);
2976 }();
2977 if (styleState != PendingStyleState::NotUpdated) {
2978 *aFirst = *aAny = *aAll =
2979 (styleState == PendingStyleState::BeingPreserved);
2980 return NS_OK;
2983 nsIContent* const collapsedContent =
2984 nsIContent::FromNode(range->GetStartContainer());
2985 if (MOZ_LIKELY(collapsedContent &&
2986 collapsedContent->GetAsElementOrParentElement()) &&
2987 aStyle.IsCSSSettable(
2988 *collapsedContent->GetAsElementOrParentElement())) {
2989 if (aValue) {
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();
3001 if (outValue) {
3002 outValue->Assign(tOutString);
3004 return NS_OK;
3007 *aFirst = *aAny = *aAll =
3008 collapsedContent && HTMLEditUtils::IsInlineStyleSetByElement(
3009 *collapsedContent, aStyle, aValue, outValue);
3010 return NS_OK;
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)) {
3026 break;
3028 RefPtr<Text> textNode = Text::FromNode(postOrderIter.GetCurrentNode());
3029 if (!textNode) {
3030 continue;
3033 // just ignore any non-editable nodes
3034 if (!EditorUtils::IsEditableContent(*textNode, EditorType::HTML) ||
3035 !HTMLEditUtils::IsVisibleTextNode(*textNode)) {
3036 continue;
3039 if (!isCollapsed && first && firstNodeInRange) {
3040 firstNodeInRange = false;
3041 if (range->StartOffset() == textNode->TextDataLength()) {
3042 continue;
3044 } else if (textNode == endNode && !endOffset) {
3045 continue;
3048 const RefPtr<Element> element = textNode->GetParentElement();
3050 bool isSet = false;
3051 if (first) {
3052 if (element) {
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
3057 if (aValue) {
3058 firstValue.Assign(*aValue);
3060 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
3061 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle,
3062 firstValue);
3063 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
3064 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
3065 return isComputedCSSEquivalentToStyleOrError.unwrapErr();
3067 isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
3068 } else {
3069 isSet = HTMLEditUtils::IsInlineStyleSetByElement(
3070 *element, aStyle, aValue, &firstValue);
3073 *aFirst = isSet;
3074 first = false;
3075 if (outValue) {
3076 *outValue = firstValue;
3078 } else {
3079 if (element) {
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
3084 if (aValue) {
3085 theValue.Assign(*aValue);
3087 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
3088 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle,
3089 theValue);
3090 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) {
3091 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed");
3092 return isComputedCSSEquivalentToStyleOrError.unwrapErr();
3094 isSet = isComputedCSSEquivalentToStyleOrError.unwrap();
3095 } else {
3096 isSet = HTMLEditUtils::IsInlineStyleSetByElement(*element, aStyle,
3097 aValue, &theValue);
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.
3107 // e.g.
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
3116 // check.
3117 (!aStyle.IsStyleOfTextDecoration(
3118 EditorInlineStyle::IgnoreSElement::Yes) ||
3119 *aFirst != isSet)) {
3120 *aAll = false;
3124 if (isSet) {
3125 *aAny = true;
3126 } else {
3127 *aAll = false;
3131 if (!*aAny) {
3132 // make sure that if none of the selection is set, we don't report all is
3133 // set
3134 *aAll = false;
3136 return NS_OK;
3139 nsresult HTMLEditor::GetInlineProperty(nsStaticAtom& aHTMLProperty,
3140 nsAtom* aAttribute,
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;
3153 nsresult rv =
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");
3176 return rv;
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;
3192 nsresult rv =
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,
3216 ignoredError);
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(
3228 NS_SUCCEEDED(rv),
3229 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed");
3230 return EditorBase::ToGenericNSResult(rv);
3233 nsresult HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom& aHTMLProperty,
3234 nsStaticAtom* aAttribute,
3235 nsIPrincipal* aPrincipal) {
3236 AutoEditActionDataSetter editActionData(
3237 *this,
3238 HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty, aAttribute,
3239 false),
3240 aPrincipal);
3241 switch (editActionData.GetEditAction()) {
3242 case EditAction::eRemoveFontFamilyProperty:
3243 MOZ_ASSERT(!u""_ns.IsVoid());
3244 editActionData.SetData(u""_ns);
3245 break;
3246 case EditAction::eRemoveColorProperty:
3247 case EditAction::eRemoveBackgroundColorPropertyInline:
3248 editActionData.SetColorData(u""_ns);
3249 break;
3250 default:
3251 break;
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(
3265 NS_SUCCEEDED(rv),
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(
3276 *this,
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);
3282 break;
3283 case EditAction::eRemoveColorProperty:
3284 case EditAction::eRemoveBackgroundColorPropertyInline:
3285 editActionData.SetColorData(u""_ns);
3286 break;
3287 default:
3288 break;
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(
3301 NS_SUCCEEDED(rv),
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);
3357 return NS_OK;
3360 // XXX Shouldn't we quit before calling `CommitComposition()`?
3361 if (IsPlaintextMailComposer()) {
3362 return NS_OK;
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()) {
3372 return NS_OK;
3376 AutoPlaceholderBatch treatAsOneTransaction(
3377 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
3378 IgnoredErrorResult ignoredError;
3379 AutoEditSubActionNotifier startToHandleEditSubAction(
3380 *this, EditSubAction::eRemoveTextProperty, nsIEditor::eNext,
3381 ignoredError);
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())) {
3420 continue;
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())) {
3429 NS_WARNING(
3430 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
3431 "failed");
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())) {
3445 continue;
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)) {
3459 continue;
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)) {
3470 continue;
3472 arrayOfContentsAroundRange.AppendElement(
3473 *splitRange.StartRef().ContainerAs<Text>());
3474 arrayOfContentsAroundRange.AppendElement(
3475 *splitRange.EndRef().ContainerAs<Text>());
3476 } else {
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;
3488 if (NS_SUCCEEDED(
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()) {
3534 continue;
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())) {
3548 return;
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>(),
3559 commonAncestor)
3560 : nullptr;
3561 nsIContent* const maybePreviousContent =
3562 range.EndRef().IsInContentNode() &&
3563 range.EndRef().IsStartOfContainer()
3564 ? AutoInlineStyleSetter::GetPreviousEditableInlineContent(
3565 *range.EndRef().ContainerAs<nsIContent>(),
3566 commonAncestor)
3567 : nullptr;
3568 if (!maybeNextContent && !maybePreviousContent) {
3569 return;
3571 const auto startPoint =
3572 maybeNextContent &&
3573 maybeNextContent != selectionRange->GetStartContainer()
3574 ? HTMLEditUtils::GetDeepestEditableStartPointOf<
3575 EditorRawDOMPoint>(*maybeNextContent)
3576 : range.StartRef();
3577 const auto endPoint =
3578 maybePreviousContent && maybePreviousContent !=
3579 selectionRange->GetEndContainer()
3580 ? HTMLEditUtils::GetDeepestEditableEndPointOf<
3581 EditorRawDOMPoint>(*maybePreviousContent)
3582 : range.EndRef();
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();
3592 continue;
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
3601 // so, for what?
3602 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
3603 // keep it alive.
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)) {
3608 NS_WARNING(
3609 "AutoInlineStyleSetter::InvertStyleIfApplied() failed");
3610 return NS_ERROR_EDITOR_DESTROYED;
3612 NS_WARNING(
3613 "AutoInlineStyleSetter::InvertStyleIfApplied() failed, but "
3614 "ignored");
3616 continue;
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
3622 // loop.
3623 if (Text* textNode = Text::FromNode(content)) {
3624 const uint32_t startOffset =
3625 content == splitRange.StartRef().GetContainer()
3626 ? splitRange.StartRef().Offset()
3627 : 0u;
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
3647 // the style.
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));
3661 continue;
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())) {
3684 NS_WARNING(
3685 "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() "
3686 "failed");
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
3698 // cases.
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");
3709 return rv;
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.
3725 return NS_OK;
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())) {
3733 NS_WARNING(
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();
3740 return NS_OK;
3743 Result<SplitRangeOffFromNodeResult, nsresult>
3744 HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied(HTMLEditor& aHTMLEditor,
3745 Text& aTextNode,
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()) {
3777 return false;
3780 // First check whether the style is invertible since this is the fastest
3781 // check.
3782 if (!aStyle.IsInvertibleWithCSS()) {
3783 return false;
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)) {
3791 return false;
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)) {
3797 return false;
3799 nsAutoString emptyString;
3800 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError =
3801 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle,
3802 emptyString);
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);
3814 continue;
3816 if (child->IsText()) {
3817 aLeafTextNodes.AppendElement(*child->AsText());
3822 nsresult HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
3823 AutoEditActionDataSetter editActionData(*this, EditAction::eIncrementFontSize,
3824 aPrincipal);
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,
3841 aPrincipal);
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
3871 ? *nsGkAtoms::big
3872 : *nsGkAtoms::small;
3874 // Let's see in what kind of element the selection is
3875 if (!SelectionRef().RangeCount()) {
3876 return NS_OK;
3878 const auto firstRangeStartPoint =
3879 EditorBase::GetFirstSelectionStartPoint<EditorRawDOMPoint>();
3880 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) {
3881 return NS_OK;
3883 Element* element =
3884 firstRangeStartPoint.GetContainerOrContainerParentElement();
3885 if (NS_WARN_IF(!element)) {
3886 return NS_OK;
3888 if (!HTMLEditUtils::CanNodeContain(*element, bigOrSmallTagName)) {
3889 return NS_OK;
3892 // Manipulating text attributes on a collapsed selection only sets state
3893 // for the next text insertion
3894 mPendingStylesToApplyToNewContent->PreserveStyle(bigOrSmallTagName, nullptr,
3895 u""_ns);
3896 return NS_OK;
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
3920 // soon.
3921 const EditorDOMRange range(GetExtendedRangeWrappingEntirelySelectedElements(
3922 EditorRawDOMRange(domRange)));
3923 if (NS_WARN_IF(!range.IsPositioned())) {
3924 continue;
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();
3940 continue;
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
3952 // the iterator).
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");
4030 return rv;
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(),
4043 *nsGkAtoms::big)) {
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();
4068 if (MOZ_UNLIKELY(
4069 !unwrappedSplitAtEndResult.HasCaretPointSuggestion())) {
4070 NS_WARNING(
4071 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
4072 "point");
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();
4095 if (MOZ_UNLIKELY(
4096 !unwrappedSplitAtStartResult.HasCaretPointSuggestion())) {
4097 NS_WARNING(
4098 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret "
4099 "point");
4100 return Err(NS_ERROR_FAILURE);
4102 unwrappedSplitAtStartResult.MoveCaretPointTo(pointToPutCaret, *this,
4103 {});
4104 MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(),
4105 pointToPutCaret.IsSet());
4106 textNodeForTheRange =
4107 unwrappedSplitAtStartResult.GetNextContentAs<Text>();
4108 MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange);
4111 return pointToPutCaret;
4112 }();
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
4123 : nsGkAtoms::small;
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
4234 : nsGkAtoms::small;
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;
4349 *aMixed = true;
4350 outFace.Truncate();
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)) {
4363 NS_WARNING(
4364 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
4365 "failed");
4366 return EditorBase::ToGenericNSResult(rv);
4368 if (any && !all) {
4369 return NS_OK; // mixed
4371 if (all) {
4372 *aMixed = false;
4373 return NS_OK;
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);
4383 if (any && !all) {
4384 return NS_OK; // mixed
4386 if (all) {
4387 *aMixed = false;
4388 outFace.AssignLiteral("tt");
4391 if (!any) {
4392 // there was no font face attrs of any kind. We are in normal font.
4393 outFace.Truncate();
4394 *aMixed = false;
4396 return NS_OK;
4399 nsresult HTMLEditor::GetFontColorState(bool* aMixed, nsAString& aOutColor) {
4400 if (NS_WARN_IF(!aMixed)) {
4401 return NS_ERROR_INVALID_ARG;
4404 *aMixed = true;
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)) {
4417 NS_WARNING(
4418 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
4419 "failed");
4420 return EditorBase::ToGenericNSResult(rv);
4423 if (any && !all) {
4424 return NS_OK; // mixed
4426 if (all) {
4427 *aMixed = false;
4428 return NS_OK;
4431 if (!any) {
4432 // there was no font color attrs of any kind..
4433 aOutColor.Truncate();
4434 *aMixed = false;
4436 return NS_OK;
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();
4443 return NS_OK;
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