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/. */
5 
6 #include "HTMLEditor.h"
7 
8 #include "EditAction.h"
9 #include "EditorUtils.h"
10 #include "HTMLEditHelpers.h"
11 #include "HTMLEditUtils.h"
12 #include "SelectionState.h"
13 #include "TypeInState.h"
14 
15 #include "mozilla/Assertions.h"
16 #include "mozilla/ContentIterator.h"
17 #include "mozilla/mozalloc.h"
18 #include "mozilla/dom/AncestorIterator.h"
19 #include "mozilla/dom/Element.h"
20 #include "mozilla/dom/HTMLBRElement.h"
21 #include "mozilla/dom/Selection.h"
22 #include "nsAString.h"
23 #include "nsAttrName.h"
24 #include "nsCOMPtr.h"
25 #include "nsCaseTreatment.h"
26 #include "nsComponentManagerUtils.h"
27 #include "nsDebug.h"
28 #include "nsError.h"
29 #include "nsGkAtoms.h"
30 #include "nsAtom.h"
31 #include "nsIContent.h"
32 #include "nsNameSpaceManager.h"
33 #include "nsINode.h"
34 #include "nsIPrincipal.h"
35 #include "nsISupportsImpl.h"
36 #include "nsLiteralString.h"
37 #include "nsRange.h"
38 #include "nsReadableUtils.h"
39 #include "nsString.h"
40 #include "nsStringFwd.h"
41 #include "nsStyledElement.h"
42 #include "nsTArray.h"
43 #include "nsUnicharUtils.h"
44 #include "nscore.h"
45 
46 class nsISupports;
47 
48 namespace mozilla {
49 
50 using namespace dom;
51 
52 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
53 using LeafNodeType = HTMLEditUtils::LeafNodeType;
54 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
55 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
56 
SetInlinePropertyAsAction(nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aPrincipal)57 nsresult HTMLEditor::SetInlinePropertyAsAction(nsAtom& aProperty,
58                                                nsAtom* aAttribute,
59                                                const nsAString& aValue,
60                                                nsIPrincipal* aPrincipal) {
61   AutoEditActionDataSetter editActionData(
62       *this,
63       HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true),
64       aPrincipal);
65   switch (editActionData.GetEditAction()) {
66     case EditAction::eSetFontFamilyProperty:
67       MOZ_ASSERT(!aValue.IsVoid());
68       // XXX Should we trim unnecessary white-spaces?
69       editActionData.SetData(aValue);
70       break;
71     case EditAction::eSetColorProperty:
72     case EditAction::eSetBackgroundColorPropertyInline:
73       editActionData.SetColorData(aValue);
74       break;
75     default:
76       break;
77   }
78 
79   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
80   if (NS_FAILED(rv)) {
81     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
82                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
83     return EditorBase::ToGenericNSResult(rv);
84   }
85 
86   // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection
87   //     into view after setting the new style.
88   AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::No,
89                                              __FUNCTION__);
90 
91   nsAtom* property = &aProperty;
92   nsAtom* attribute = aAttribute;
93   nsAutoString value(aValue);
94 
95   if (&aProperty == nsGkAtoms::sup) {
96     // Superscript and Subscript styles are mutually exclusive.
97     nsresult rv = RemoveInlinePropertyInternal(nsGkAtoms::sub, nullptr,
98                                                RemoveRelatedElements::No);
99     if (NS_FAILED(rv)) {
100       NS_WARNING(
101           "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::sub, "
102           "RemoveRelatedElements::No) failed");
103       return EditorBase::ToGenericNSResult(rv);
104     }
105   } else if (&aProperty == nsGkAtoms::sub) {
106     // Superscript and Subscript styles are mutually exclusive.
107     nsresult rv = RemoveInlinePropertyInternal(nsGkAtoms::sup, nullptr,
108                                                RemoveRelatedElements::No);
109     if (NS_FAILED(rv)) {
110       NS_WARNING(
111           "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::sup, "
112           "RemoveRelatedElements::No) failed");
113       return EditorBase::ToGenericNSResult(rv);
114     }
115   }
116   // Handling `<tt>` element code was implemented for composer (bug 115922).
117   // This shouldn't work with `Document.execCommand()`.  Currently, aPrincipal
118   // is set only when the root caller is Document::ExecCommand() so that
119   // we should handle `<tt>` element only when aPrincipal is nullptr that
120   // must be only when XUL command is executed on composer.
121   else if (!aPrincipal) {
122     if (&aProperty == nsGkAtoms::tt) {
123       nsresult rv = RemoveInlinePropertyInternal(
124           nsGkAtoms::font, nsGkAtoms::face, RemoveRelatedElements::No);
125       if (NS_FAILED(rv)) {
126         NS_WARNING(
127             "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::font, "
128             "nsGkAtoms::face, RemoveRelatedElements::No) failed");
129         return EditorBase::ToGenericNSResult(rv);
130       }
131     } else if (&aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::face) {
132       if (!value.LowerCaseEqualsASCII("tt")) {
133         nsresult rv = RemoveInlinePropertyInternal(nsGkAtoms::tt, nullptr,
134                                                    RemoveRelatedElements::No);
135         if (NS_FAILED(rv)) {
136           NS_WARNING(
137               "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::tt, "
138               "RemoveRelatedElements::No) failed");
139           return EditorBase::ToGenericNSResult(rv);
140         }
141       } else {
142         nsresult rv = RemoveInlinePropertyInternal(
143             nsGkAtoms::font, nsGkAtoms::face, RemoveRelatedElements::No);
144         if (NS_FAILED(rv)) {
145           NS_WARNING(
146               "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::font, "
147               "nsGkAtoms::face, RemoveRelatedElements::No) failed");
148           return EditorBase::ToGenericNSResult(rv);
149         }
150         // Override property, attribute and value if the new font face value is
151         // "tt".
152         property = nsGkAtoms::tt;
153         attribute = nullptr;
154         value.Truncate();
155       }
156     }
157   }
158   rv = SetInlinePropertyInternal(MOZ_KnownLive(*property),
159                                  MOZ_KnownLive(attribute), value);
160   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
161                        "HTMLEditor::SetInlinePropertyInternal() failed");
162   return EditorBase::ToGenericNSResult(rv);
163 }
164 
SetInlineProperty(const nsAString & aProperty,const nsAString & aAttribute,const nsAString & aValue)165 NS_IMETHODIMP HTMLEditor::SetInlineProperty(const nsAString& aProperty,
166                                             const nsAString& aAttribute,
167                                             const nsAString& aValue) {
168   RefPtr<nsAtom> property = NS_Atomize(aProperty);
169   if (NS_WARN_IF(!property)) {
170     return NS_ERROR_INVALID_ARG;
171   }
172   nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
173   AutoEditActionDataSetter editActionData(
174       *this,
175       HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true));
176   switch (editActionData.GetEditAction()) {
177     case EditAction::eSetFontFamilyProperty:
178       MOZ_ASSERT(!aValue.IsVoid());
179       // XXX Should we trim unnecessary white-spaces?
180       editActionData.SetData(aValue);
181       break;
182     case EditAction::eSetColorProperty:
183     case EditAction::eSetBackgroundColorPropertyInline:
184       editActionData.SetColorData(aValue);
185       break;
186     default:
187       break;
188   }
189   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
190   if (NS_FAILED(rv)) {
191     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
192                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
193     return EditorBase::ToGenericNSResult(rv);
194   }
195   rv = SetInlinePropertyInternal(*property, MOZ_KnownLive(attribute), aValue);
196   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
197                        "HTMLEditor::SetInlinePropertyInternal() failed");
198   return EditorBase::ToGenericNSResult(rv);
199 }
200 
SetInlinePropertyInternal(nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aAttributeValue)201 nsresult HTMLEditor::SetInlinePropertyInternal(
202     nsAtom& aProperty, nsAtom* aAttribute, const nsAString& aAttributeValue) {
203   MOZ_ASSERT(IsEditActionDataAvailable());
204 
205   if (NS_WARN_IF(!mInitSucceeded)) {
206     return NS_ERROR_NOT_INITIALIZED;
207   }
208 
209   DebugOnly<nsresult> rvIgnored = CommitComposition();
210   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
211                        "EditorBase::CommitComposition() failed, but ignored");
212 
213   if (SelectionRef().IsCollapsed()) {
214     // Manipulating text attributes on a collapsed selection only sets state
215     // for the next text insertion
216     mTypeInState->SetProp(&aProperty, aAttribute, aAttributeValue);
217     return NS_OK;
218   }
219 
220   // XXX Shouldn't we return before calling `CommitComposition()`?
221   if (IsInPlaintextMode()) {
222     return NS_OK;
223   }
224 
225   EditActionResult result = CanHandleHTMLEditSubAction();
226   if (result.Failed() || result.Canceled()) {
227     NS_WARNING_ASSERTION(result.Succeeded(),
228                          "HTMLEditor::CanHandleHTMLEditSubAction() failed");
229     return result.Rv();
230   }
231 
232   AutoPlaceholderBatch treatAsOneTransaction(
233       *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
234   IgnoredErrorResult ignoredError;
235   AutoEditSubActionNotifier startToHandleEditSubAction(
236       *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
237   if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
238     return ignoredError.StealNSResult();
239   }
240   NS_WARNING_ASSERTION(
241       !ignoredError.Failed(),
242       "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
243 
244   {
245     AutoSelectionRestorer restoreSelectionLater(*this);
246     AutoTransactionsConserveSelection dontChangeMySelection(*this);
247 
248     // Loop through the ranges in the selection
249     // XXX This is different from `SetCSSBackgroundColorWithTransaction()`.
250     //     It refers `Selection::GetRangeAt()` in each time.  The result may
251     //     be different if mutation event listener changes the `Selection`.
252     AutoSelectionRangeArray arrayOfRanges(SelectionRef());
253     for (auto& range : arrayOfRanges.mRanges) {
254       // Adjust range to include any ancestors whose children are entirely
255       // selected
256       nsresult rv = PromoteInlineRange(*range);
257       if (NS_FAILED(rv)) {
258         NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
259         return rv;
260       }
261 
262       // XXX Shouldn't we skip the range if it's been collapsed by mutation
263       //     event listener?
264 
265       EditorDOMPoint startOfRange(range->StartRef());
266       EditorDOMPoint endOfRange(range->EndRef());
267       if (NS_WARN_IF(!startOfRange.IsSet()) ||
268           NS_WARN_IF(!endOfRange.IsSet())) {
269         continue;
270       }
271 
272       // If range is in a text node, apply new style simply.
273       if (startOfRange.GetContainer() == endOfRange.GetContainer() &&
274           startOfRange.IsInTextNode()) {
275         nsresult rv = SetInlinePropertyOnTextNode(
276             MOZ_KnownLive(*startOfRange.GetContainerAsText()),
277             startOfRange.Offset(), endOfRange.Offset(), aProperty, aAttribute,
278             aAttributeValue);
279         if (NS_FAILED(rv)) {
280           NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
281           return rv;
282         }
283         continue;
284       }
285 
286       // Collect editable nodes which are entirely contained in the range.
287       AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
288       ContentSubtreeIterator subtreeIter;
289       // If there is no node which is entirely in the range,
290       // `ContentSubtreeIterator::Init()` fails, but this is possible case,
291       // don't warn it.
292       if (NS_SUCCEEDED(subtreeIter.Init(range))) {
293         for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
294           nsINode* node = subtreeIter.GetCurrentNode();
295           if (NS_WARN_IF(!node)) {
296             return NS_ERROR_FAILURE;
297           }
298           if (node->IsContent() && EditorUtils::IsEditableContent(
299                                        *node->AsContent(), EditorType::HTML)) {
300             arrayOfContents.AppendElement(*node->AsContent());
301           }
302         }
303       }
304 
305       // If start node is a text node, apply new style to a part of it.
306       if (startOfRange.IsInTextNode() &&
307           EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
308                                          EditorType::HTML)) {
309         nsresult rv = SetInlinePropertyOnTextNode(
310             MOZ_KnownLive(*startOfRange.GetContainerAsText()),
311             startOfRange.Offset(), startOfRange.GetContainer()->Length(),
312             aProperty, aAttribute, aAttributeValue);
313         if (NS_FAILED(rv)) {
314           NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
315           return rv;
316         }
317       }
318 
319       // Then, apply new style to all nodes in the range entirely.
320       for (auto& content : arrayOfContents) {
321         // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
322         // keep it alive.
323         nsresult rv = SetInlinePropertyOnNode(
324             MOZ_KnownLive(*content), aProperty, aAttribute, aAttributeValue);
325         if (NS_WARN_IF(Destroyed())) {
326           return NS_ERROR_EDITOR_DESTROYED;
327         }
328         if (NS_FAILED(rv)) {
329           NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
330           return rv;
331         }
332       }
333 
334       // Finally, if end node is a text node, apply new style to a part of it.
335       if (endOfRange.IsInTextNode() &&
336           EditorUtils::IsEditableContent(*endOfRange.GetContainerAsText(),
337                                          EditorType::HTML)) {
338         nsresult rv = SetInlinePropertyOnTextNode(
339             MOZ_KnownLive(*endOfRange.GetContainerAsText()), 0,
340             endOfRange.Offset(), aProperty, aAttribute, aAttributeValue);
341         if (NS_FAILED(rv)) {
342           NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
343           return rv;
344         }
345       }
346     }
347   }
348   // Restoring `Selection` may have destroyed us.
349   return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
350 }
351 
ElementIsGoodContainerForTheStyle(Element & aElement,nsAtom * aProperty,nsAtom * aAttribute,const nsAString * aValue)352 Result<bool, nsresult> HTMLEditor::ElementIsGoodContainerForTheStyle(
353     Element& aElement, nsAtom* aProperty, nsAtom* aAttribute,
354     const nsAString* aValue) {
355   // aContent can be null, in which case we'll return false in a few lines
356   MOZ_ASSERT(aProperty);
357   MOZ_ASSERT_IF(aAttribute, aValue);
358 
359   // First check for <b>, <i>, etc.
360   if (aElement.IsHTMLElement(aProperty) && !aElement.GetAttrCount() &&
361       !aAttribute) {
362     return true;
363   }
364 
365   // Special cases for various equivalencies: <strong>, <em>, <s>
366   if (!aElement.GetAttrCount() &&
367       ((aProperty == nsGkAtoms::b &&
368         aElement.IsHTMLElement(nsGkAtoms::strong)) ||
369        (aProperty == nsGkAtoms::i && aElement.IsHTMLElement(nsGkAtoms::em)) ||
370        (aProperty == nsGkAtoms::strike &&
371         aElement.IsHTMLElement(nsGkAtoms::s)))) {
372     return true;
373   }
374 
375   // Now look for things like <font>
376   if (aAttribute) {
377     nsString attrValue;
378     if (aElement.IsHTMLElement(aProperty) &&
379         IsOnlyAttribute(&aElement, aAttribute) &&
380         aElement.GetAttr(kNameSpaceID_None, aAttribute, attrValue) &&
381         attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator)) {
382       // This is not quite correct, because it excludes cases like
383       // <font face=000> being the same as <font face=#000000>.
384       // Property-specific handling is needed (bug 760211).
385       return true;
386     }
387   }
388 
389   // No luck so far.  Now we check for a <span> with a single style=""
390   // attribute that sets only the style we're looking for, if this type of
391   // style supports it
392   if (!CSSEditUtils::IsCSSEditableProperty(&aElement, aProperty, aAttribute) ||
393       !aElement.IsHTMLElement(nsGkAtoms::span) ||
394       aElement.GetAttrCount() != 1 ||
395       !aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
396     return false;
397   }
398 
399   // Some CSS styles are not so simple.  For instance, underline is
400   // "text-decoration: underline", which decomposes into four different text-*
401   // properties.  So for now, we just create a span, add the desired style, and
402   // see if it matches.
403   RefPtr<Element> newSpanElement = CreateHTMLContent(nsGkAtoms::span);
404   if (!newSpanElement) {
405     NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed");
406     return false;
407   }
408   nsStyledElement* styledNewSpanElement =
409       nsStyledElement::FromNode(newSpanElement);
410   if (!styledNewSpanElement) {
411     return false;
412   }
413   // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
414   // RefPtr.
415   Result<int32_t, nsresult> result =
416       mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithoutTransaction(
417           MOZ_KnownLive(*styledNewSpanElement), aProperty, aAttribute, aValue);
418   if (result.isErr()) {
419     // The call shouldn't return destroyed error because it must be
420     // impossible to run script with modifying the new orphan node.
421     MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?");
422     if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
423       return Err(NS_ERROR_EDITOR_DESTROYED);
424     }
425     return false;
426   }
427   nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement);
428   if (!styledElement) {
429     return false;
430   }
431   return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
432                                                      *styledElement);
433 }
434 
SetInlinePropertyOnTextNode(Text & aText,uint32_t aStartOffset,uint32_t aEndOffset,nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue)435 nsresult HTMLEditor::SetInlinePropertyOnTextNode(
436     Text& aText, uint32_t aStartOffset, uint32_t aEndOffset, nsAtom& aProperty,
437     nsAtom* aAttribute, const nsAString& aValue) {
438   if (!aText.GetParentNode() ||
439       !HTMLEditUtils::CanNodeContain(*aText.GetParentNode(), aProperty)) {
440     return NS_OK;
441   }
442 
443   // Don't need to do anything if no characters actually selected
444   if (aStartOffset == aEndOffset) {
445     return NS_OK;
446   }
447 
448   // Don't need to do anything if property already set on node
449   if (CSSEditUtils::IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
450     // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
451     // for node; let's check if it carries those CSS styles
452     nsAutoString value(aValue);
453     if (CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
454             aText, &aProperty, aAttribute, value)) {
455       return NS_OK;
456     }
457   } else if (HTMLEditUtils::IsInlineStyleSetByElement(aText, aProperty,
458                                                       aAttribute, &aValue)) {
459     return NS_OK;
460   }
461 
462   // Make the range an independent node.
463   RefPtr<Text> textNodeForTheRange = &aText;
464 
465   // Split at the end of the range.
466   EditorDOMPoint atEnd(textNodeForTheRange, aEndOffset);
467   if (!atEnd.IsEndOfContainer()) {
468     // We need to split off back of text node
469     SplitNodeResult splitAtEndResult = SplitNodeWithTransaction(atEnd);
470     if (MOZ_UNLIKELY(splitAtEndResult.Failed())) {
471       NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
472       return splitAtEndResult.Rv();
473     }
474     textNodeForTheRange =
475         Text::FromNodeOrNull(splitAtEndResult.GetPreviousContent());
476   }
477 
478   // Split at the start of the range.
479   EditorDOMPoint atStart(textNodeForTheRange, aStartOffset);
480   if (!atStart.IsStartOfContainer()) {
481     // We need to split off front of text node
482     SplitNodeResult splitAtStartResult = SplitNodeWithTransaction(atStart);
483     if (MOZ_UNLIKELY(splitAtStartResult.Failed())) {
484       NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
485       return splitAtStartResult.Rv();
486     }
487   }
488 
489   if (aAttribute) {
490     // Look for siblings that are correct type of node
491     nsIContent* sibling = HTMLEditUtils::GetPreviousSibling(
492         *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
493     if (sibling && sibling->IsElement()) {
494       OwningNonNull<Element> element(*sibling->AsElement());
495       Result<bool, nsresult> result = ElementIsGoodContainerForTheStyle(
496           element, &aProperty, aAttribute, &aValue);
497       if (result.isErr()) {
498         NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
499         return result.unwrapErr();
500       }
501       if (result.inspect()) {
502         // Previous sib is already right kind of inline node; slide this over
503         nsresult rv =
504             MoveNodeToEndWithTransaction(*textNodeForTheRange, element);
505         if (NS_WARN_IF(Destroyed())) {
506           return NS_ERROR_EDITOR_DESTROYED;
507         }
508         NS_WARNING_ASSERTION(
509             NS_SUCCEEDED(rv),
510             "HTMLEditor::MoveNodeToEndWithTransaction() failed");
511         return rv;
512       }
513     }
514     sibling = HTMLEditUtils::GetNextSibling(
515         *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
516     if (sibling && sibling->IsElement()) {
517       OwningNonNull<Element> element(*sibling->AsElement());
518       Result<bool, nsresult> result = ElementIsGoodContainerForTheStyle(
519           element, &aProperty, aAttribute, &aValue);
520       if (result.isErr()) {
521         NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
522         return result.unwrapErr();
523       }
524       if (result.inspect()) {
525         // Following sib is already right kind of inline node; slide this over
526         nsresult rv = MoveNodeWithTransaction(*textNodeForTheRange,
527                                               EditorDOMPoint(sibling, 0));
528         if (NS_WARN_IF(Destroyed())) {
529           return NS_ERROR_EDITOR_DESTROYED;
530         }
531         NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
532                              "HTMLEditor::MoveNodeWithTransaction() failed");
533         return rv;
534       }
535     }
536   }
537 
538   // Reparent the node inside inline node with appropriate {attribute,value}
539   nsresult rv = SetInlinePropertyOnNode(*textNodeForTheRange, aProperty,
540                                         aAttribute, aValue);
541   if (NS_WARN_IF(Destroyed())) {
542     return NS_ERROR_EDITOR_DESTROYED;
543   }
544   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
545                        "HTMLEditor::SetInlinePropertyOnNode() failed");
546   return rv;
547 }
548 
SetInlinePropertyOnNodeImpl(nsIContent & aContent,nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue)549 nsresult HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aContent,
550                                                  nsAtom& aProperty,
551                                                  nsAtom* aAttribute,
552                                                  const nsAString& aValue) {
553   // If this is an element that can't be contained in a span, we have to
554   // recurse to its children.
555   if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, aContent)) {
556     if (aContent.HasChildren()) {
557       nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
558 
559       // Populate the list.
560       for (nsCOMPtr<nsIContent> child = aContent.GetFirstChild(); child;
561            child = child->GetNextSibling()) {
562         if (EditorUtils::IsEditableContent(*child, EditorType::HTML) &&
563             (!child->IsText() ||
564              HTMLEditUtils::IsVisibleTextNode(*child->AsText()))) {
565           arrayOfNodes.AppendElement(*child);
566         }
567       }
568 
569       // Then loop through the list, set the property on each node.
570       for (auto& node : arrayOfNodes) {
571         // MOZ_KnownLive because 'arrayOfNodes' is guaranteed to
572         // keep it alive.
573         nsresult rv = SetInlinePropertyOnNode(MOZ_KnownLive(node), aProperty,
574                                               aAttribute, aValue);
575         if (NS_FAILED(rv)) {
576           NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
577           return rv;
578         }
579       }
580     }
581     return NS_OK;
582   }
583 
584   // First check if there's an adjacent sibling we can put our node into.
585   nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling(
586       aContent, {WalkTreeOption::IgnoreNonEditableNode});
587   nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
588       aContent, {WalkTreeOption::IgnoreNonEditableNode});
589   if (previousSibling && previousSibling->IsElement()) {
590     OwningNonNull<Element> previousElement(*previousSibling->AsElement());
591     Result<bool, nsresult> canMoveIntoPreviousSibling =
592         ElementIsGoodContainerForTheStyle(previousElement, &aProperty,
593                                           aAttribute, &aValue);
594     if (canMoveIntoPreviousSibling.isErr()) {
595       NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
596       return canMoveIntoPreviousSibling.unwrapErr();
597     }
598     if (canMoveIntoPreviousSibling.inspect()) {
599       nsresult rv = MoveNodeToEndWithTransaction(aContent, *previousSibling);
600       if (NS_FAILED(rv)) {
601         NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
602         return rv;
603       }
604       if (!nextSibling || !nextSibling->IsElement()) {
605         return NS_OK;
606       }
607       OwningNonNull<Element> nextElement(*nextSibling->AsElement());
608       Result<bool, nsresult> canMoveIntoNextSibling =
609           ElementIsGoodContainerForTheStyle(nextElement, &aProperty, aAttribute,
610                                             &aValue);
611       if (canMoveIntoNextSibling.isErr()) {
612         NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
613         return canMoveIntoNextSibling.unwrapErr();
614       }
615       if (!canMoveIntoNextSibling.inspect()) {
616         return NS_OK;
617       }
618       JoinNodesResult joinNodesResult =
619           JoinNodesWithTransaction(*previousSibling, *nextSibling);
620       NS_WARNING_ASSERTION(joinNodesResult.Succeeded(),
621                            "HTMLEditor::JoinNodesWithTransaction() failed");
622       return joinNodesResult.Rv();
623     }
624   }
625 
626   if (nextSibling && nextSibling->IsElement()) {
627     OwningNonNull<Element> nextElement(*nextSibling->AsElement());
628     Result<bool, nsresult> canMoveIntoNextSibling =
629         ElementIsGoodContainerForTheStyle(nextElement, &aProperty, aAttribute,
630                                           &aValue);
631     if (canMoveIntoNextSibling.isErr()) {
632       NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
633       return canMoveIntoNextSibling.unwrapErr();
634     }
635     if (canMoveIntoNextSibling.inspect()) {
636       nsresult rv =
637           MoveNodeWithTransaction(aContent, EditorDOMPoint(nextElement, 0));
638       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
639                            "HTMLEditor::MoveNodeWithTransaction() failed");
640       return rv;
641     }
642   }
643 
644   // Don't need to do anything if property already set on node
645   if (CSSEditUtils::IsCSSEditableProperty(&aContent, &aProperty, aAttribute)) {
646     nsAutoString value(aValue);
647     if (CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
648             aContent, &aProperty, aAttribute, value)) {
649       return NS_OK;
650     }
651   } else if (HTMLEditUtils::IsInlineStyleSetByElement(aContent, aProperty,
652                                                       aAttribute, &aValue)) {
653     return NS_OK;
654   }
655 
656   bool useCSS = (IsCSSEnabled() && CSSEditUtils::IsCSSEditableProperty(
657                                        &aContent, &aProperty, aAttribute)) ||
658                 // bgcolor is always done using CSS
659                 aAttribute == nsGkAtoms::bgcolor ||
660                 // called for removing parent style, we should use CSS with
661                 // `<span>` element.
662                 aValue.EqualsLiteral("-moz-editor-invert-value");
663 
664   if (useCSS) {
665     RefPtr<Element> spanElement;
666     // We only add style="" to <span>s with no attributes (bug 746515).  If we
667     // don't have one, we need to make one.
668     if (aContent.IsHTMLElement(nsGkAtoms::span) &&
669         !aContent.AsElement()->GetAttrCount()) {
670       spanElement = aContent.AsElement();
671     } else {
672       spanElement = InsertContainerWithTransaction(aContent, *nsGkAtoms::span);
673       if (!spanElement) {
674         NS_WARNING(
675             "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
676             "failed");
677         return NS_ERROR_FAILURE;
678       }
679     }
680 
681     // Add the CSS styles corresponding to the HTML style request
682     if (nsStyledElement* spanStyledElement =
683             nsStyledElement::FromNode(spanElement)) {
684       // MOZ_KnownLive(*spanStyledElement): It's spanElement whose type is
685       // RefPtr.
686       Result<int32_t, nsresult> result =
687           mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
688               MOZ_KnownLive(*spanStyledElement), &aProperty, aAttribute,
689               &aValue);
690       if (result.isErr()) {
691         if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
692           NS_WARNING(
693               "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
694               "destroyed the editor");
695           return NS_ERROR_EDITOR_DESTROYED;
696         }
697         NS_WARNING(
698             "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
699             "failed, "
700             "but ignored");
701       }
702     }
703     return NS_OK;
704   }
705 
706   // is it already the right kind of node, but with wrong attribute?
707   if (aContent.IsHTMLElement(&aProperty)) {
708     if (NS_WARN_IF(!aAttribute)) {
709       return NS_ERROR_INVALID_ARG;
710     }
711     // Just set the attribute on it.
712     nsresult rv = SetAttributeWithTransaction(
713         MOZ_KnownLive(*aContent.AsElement()), *aAttribute, aValue);
714     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
715                          "EditorBase::SetAttributeWithTransaction() failed");
716     return rv;
717   }
718 
719   // ok, chuck it in its very own container
720   RefPtr<Element> newContainerElement = InsertContainerWithTransaction(
721       aContent, aProperty, aAttribute ? *aAttribute : *nsGkAtoms::_empty,
722       aValue);
723   NS_WARNING_ASSERTION(newContainerElement,
724                        "HTMLEditor::InsertContainerWithTransaction() failed");
725   return newContainerElement ? NS_OK : NS_ERROR_FAILURE;
726 }
727 
SetInlinePropertyOnNode(nsIContent & aNode,nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue)728 nsresult HTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
729                                              nsAtom& aProperty,
730                                              nsAtom* aAttribute,
731                                              const nsAString& aValue) {
732   nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
733                        nextSibling = aNode.GetNextSibling();
734   if (NS_WARN_IF(!aNode.GetParentNode())) {
735     return NS_ERROR_INVALID_ARG;
736   }
737 
738   OwningNonNull<nsINode> parent = *aNode.GetParentNode();
739   if (aNode.IsElement()) {
740     nsresult rv =
741         RemoveStyleInside(MOZ_KnownLive(*aNode.AsElement()), &aProperty,
742                           aAttribute, SpecifiedStyle::Preserve);
743     if (NS_FAILED(rv)) {
744       NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
745       return rv;
746     }
747   }
748 
749   if (aNode.GetParentNode()) {
750     // The node is still where it was
751     nsresult rv =
752         SetInlinePropertyOnNodeImpl(aNode, aProperty, aAttribute, aValue);
753     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
754                          "HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
755     return rv;
756   }
757 
758   // It's vanished.  Use the old siblings for reference to construct a
759   // list.  But first, verify that the previous/next siblings are still
760   // where we expect them; otherwise we have to give up.
761   if (NS_WARN_IF(previousSibling &&
762                  previousSibling->GetParentNode() != parent) ||
763       NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parent)) {
764     return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
765   }
766   AutoTArray<OwningNonNull<nsIContent>, 24> nodesToSet;
767   for (nsIContent* content = previousSibling ? previousSibling->GetNextSibling()
768                                              : parent->GetFirstChild();
769        content && content != nextSibling; content = content->GetNextSibling()) {
770     if (EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
771       nodesToSet.AppendElement(*content);
772     }
773   }
774 
775   for (OwningNonNull<nsIContent>& content : nodesToSet) {
776     // MOZ_KnownLive because 'nodesToSet' is guaranteed to
777     // keep it alive.
778     nsresult rv = SetInlinePropertyOnNodeImpl(MOZ_KnownLive(content), aProperty,
779                                               aAttribute, aValue);
780     if (NS_FAILED(rv)) {
781       NS_WARNING("HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
782       return rv;
783     }
784   }
785 
786   return NS_OK;
787 }
788 
SplitAncestorStyledInlineElementsAtRangeEdges(const EditorDOMPoint & aStartOfRange,const EditorDOMPoint & aEndOfRange,nsAtom * aProperty,nsAtom * aAttribute)789 SplitRangeOffResult HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
790     const EditorDOMPoint& aStartOfRange, const EditorDOMPoint& aEndOfRange,
791     nsAtom* aProperty, nsAtom* aAttribute) {
792   MOZ_ASSERT(IsEditActionDataAvailable());
793 
794   if (NS_WARN_IF(!aStartOfRange.IsSet()) || NS_WARN_IF(!aEndOfRange.IsSet())) {
795     return SplitRangeOffResult(NS_ERROR_INVALID_ARG);
796   }
797 
798   EditorDOMPoint startOfRange(aStartOfRange);
799   EditorDOMPoint endOfRange(aEndOfRange);
800 
801   // split any matching style nodes above the start of range
802   SplitNodeResult resultAtStart(NS_ERROR_NOT_INITIALIZED);
803   {
804     AutoTrackDOMPoint tracker(RangeUpdaterRef(), &endOfRange);
805     resultAtStart = SplitAncestorStyledInlineElementsAt(startOfRange, aProperty,
806                                                         aAttribute);
807     if (resultAtStart.Failed()) {
808       NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
809       return SplitRangeOffResult(resultAtStart.Rv());
810     }
811     if (resultAtStart.Handled()) {
812       startOfRange = resultAtStart.AtSplitPoint<EditorDOMPoint>();
813       if (!startOfRange.IsSet()) {
814         NS_WARNING(
815             "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
816             "split point");
817         return SplitRangeOffResult(NS_ERROR_FAILURE);
818       }
819     }
820   }
821 
822   // second verse, same as the first...
823   SplitNodeResult resultAtEnd(NS_ERROR_NOT_INITIALIZED);
824   {
825     AutoTrackDOMPoint tracker(RangeUpdaterRef(), &startOfRange);
826     resultAtEnd =
827         SplitAncestorStyledInlineElementsAt(endOfRange, aProperty, aAttribute);
828     if (resultAtEnd.Failed()) {
829       NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
830       return SplitRangeOffResult(resultAtEnd.Rv());
831     }
832     if (resultAtEnd.Handled()) {
833       endOfRange = resultAtEnd.AtSplitPoint<EditorDOMPoint>();
834       if (!endOfRange.IsSet()) {
835         NS_WARNING(
836             "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
837             "split point");
838         return SplitRangeOffResult(NS_ERROR_FAILURE);
839       }
840     }
841   }
842 
843   return SplitRangeOffResult(startOfRange, resultAtStart, endOfRange,
844                              resultAtEnd);
845 }
846 
SplitAncestorStyledInlineElementsAt(const EditorDOMPoint & aPointToSplit,nsAtom * aProperty,nsAtom * aAttribute)847 SplitNodeResult HTMLEditor::SplitAncestorStyledInlineElementsAt(
848     const EditorDOMPoint& aPointToSplit, nsAtom* aProperty,
849     nsAtom* aAttribute) {
850   if (NS_WARN_IF(!aPointToSplit.IsSet()) ||
851       NS_WARN_IF(!aPointToSplit.GetContainerAsContent())) {
852     return SplitNodeResult(NS_ERROR_INVALID_ARG);
853   }
854 
855   // We assume that this method is called only when we're removing style(s).
856   // Even if we're in HTML mode and there is no presentation element in the
857   // block, we may need to overwrite the block's style with `<span>` element
858   // and CSS.  For example, `<h1>` element has `font-weight: bold;` as its
859   // default style.  If `Document.execCommand("bold")` is called for its
860   // text, we should make it unbold.  Therefore, we shouldn't check
861   // IsCSSEnabled() in most cases.  However, there is an exception.
862   // FontFaceStateCommand::SetState() calls RemoveInlinePropertyAsAction()
863   // with nsGkAtoms::tt before calling SetInlinePropertyAsAction() if we
864   // are handling a XUL command.  Only in that case, we need to check
865   // IsCSSEnabled().
866   bool useCSS = aProperty != nsGkAtoms::tt || IsCSSEnabled();
867 
868   AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfParents;
869   for (nsIContent* content :
870        aPointToSplit.GetContainer()->InclusiveAncestorsOfType<nsIContent>()) {
871     if (HTMLEditUtils::IsBlockElement(*content) || !content->GetParent() ||
872         !EditorUtils::IsEditableContent(*content->GetParent(),
873                                         EditorType::HTML)) {
874       break;
875     }
876     arrayOfParents.AppendElement(*content);
877   }
878 
879   // Split any matching style nodes above the point.
880   SplitNodeResult result(aPointToSplit);
881   MOZ_ASSERT(!result.Handled());
882   for (OwningNonNull<nsIContent>& content : arrayOfParents) {
883     bool isSetByCSS = false;
884     if (useCSS &&
885         CSSEditUtils::IsCSSEditableProperty(content, aProperty, aAttribute)) {
886       // The HTML style defined by aProperty/aAttribute has a CSS equivalence
887       // in this implementation for the node; let's check if it carries those
888       // CSS styles
889       nsAutoString firstValue;
890       isSetByCSS = CSSEditUtils::IsSpecifiedCSSEquivalentToHTMLInlineStyleSet(
891           *content, aProperty, aAttribute, firstValue);
892     }
893     if (!isSetByCSS) {
894       if (!content->IsElement()) {
895         continue;
896       }
897       // If aProperty is set, we need to split only elements which applies the
898       // given style.
899       if (aProperty) {
900         // If the content is an inline element represents aProperty or
901         // the content is a link element and aProperty is `href`, we should
902         // split the content.
903         if (!content->IsHTMLElement(aProperty) &&
904             !(aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(content))) {
905           continue;
906         }
907       }
908       // If aProperty is nullptr, we need to split any style.
909       else if (!EditorUtils::IsEditableContent(content, EditorType::HTML) ||
910                !HTMLEditUtils::IsRemovableInlineStyleElement(
911                    *content->AsElement())) {
912         continue;
913       }
914     }
915 
916     // Found a style node we need to split.
917     // XXX If first content is a text node and CSS is enabled, we call this
918     //     with text node but in such case, this does nothing, but returns
919     //     as handled with setting only previous or next node.  If its parent
920     //     is a block, we do nothing but return as handled.
921     SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
922         MOZ_KnownLive(content), result.AtSplitPoint<EditorDOMPoint>(),
923         SplitAtEdges::eAllowToCreateEmptyContainer);
924     if (MOZ_UNLIKELY(splitNodeResult.Failed())) {
925       NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
926       return splitNodeResult;
927     }
928     // If it's not handled, it means that `content` is not a splitable node
929     // like a void element even if it has some children, and the split point
930     // is middle of it.
931     if (!splitNodeResult.Handled()) {
932       continue;
933     }
934     // Mark the final result as handled forcibly.
935     result = SplitNodeResult(splitNodeResult.GetPreviousContent(),
936                              splitNodeResult.GetNextContent(),
937                              SplitNodeDirection::LeftNodeIsNewOne);
938     MOZ_ASSERT(result.Handled());
939   }
940 
941   return result;
942 }
943 
ClearStyleAt(const EditorDOMPoint & aPoint,nsAtom * aProperty,nsAtom * aAttribute,SpecifiedStyle aSpecifiedStyle)944 EditResult HTMLEditor::ClearStyleAt(const EditorDOMPoint& aPoint,
945                                     nsAtom* aProperty, nsAtom* aAttribute,
946                                     SpecifiedStyle aSpecifiedStyle) {
947   MOZ_ASSERT(IsEditActionDataAvailable());
948 
949   if (NS_WARN_IF(!aPoint.IsSet())) {
950     return EditResult(NS_ERROR_INVALID_ARG);
951   }
952 
953   // First, split inline elements at the point.
954   // E.g., if aProperty is nsGkAtoms::b and `<p><b><i>a[]bc</i></b></p>`,
955   //       we want to make it as `<p><b><i>a</i></b><b><i>bc</i></b></p>`.
956   SplitNodeResult splitResult =
957       SplitAncestorStyledInlineElementsAt(aPoint, aProperty, aAttribute);
958   if (splitResult.Failed()) {
959     NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
960     return EditResult(splitResult.Rv());
961   }
962 
963   // If there is no styled inline elements of aProperty/aAttribute, we just
964   // return the given point.
965   // E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b.
966   if (!splitResult.Handled()) {
967     return EditResult(aPoint);
968   }
969 
970   // If it did split nodes, but topmost ancestor inline element is split
971   // at start of it, we don't need the empty inline element.  Let's remove
972   // it now.
973   if (splitResult.GetPreviousContent() &&
974       HTMLEditUtils::IsEmptyNode(
975           *splitResult.GetPreviousContent(),
976           {EmptyCheckOption::TreatSingleBRElementAsVisible,
977            EmptyCheckOption::TreatListItemAsVisible,
978            EmptyCheckOption::TreatTableCellAsVisible})) {
979     // Delete previous node if it's empty.
980     // MOZ_KnownLive(splitResult.GetPreviousContent()):
981     // It's grabbed by splitResult.
982     nsresult rv = DeleteNodeWithTransaction(
983         MOZ_KnownLive(*splitResult.GetPreviousContent()));
984     if (NS_WARN_IF(Destroyed())) {
985       return EditResult(NS_ERROR_EDITOR_DESTROYED);
986     }
987     if (NS_FAILED(rv)) {
988       NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
989       return EditResult(rv);
990     }
991   }
992 
993   // If we reached block from end of a text node, we can do nothing here.
994   // E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and
995   // we're in CSS mode.
996   // XXX Chrome resets block style and creates `<span>` elements for each
997   //     line in this case.
998   if (!splitResult.GetNextContent()) {
999     return EditResult(aPoint);
1000   }
1001 
1002   // Otherwise, the next node is topmost ancestor inline element which has
1003   // the style.  We want to put caret between the split nodes, but we need
1004   // to keep other styles.  Therefore, next, we need to split at start of
1005   // the next node.  The first example should become
1006   // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`.
1007   //                    ^^^^^^^^^^^^^^
1008   nsIContent* firstLeafChildOfNextNode = HTMLEditUtils::GetFirstLeafContent(
1009       *splitResult.GetNextContent(), {LeafNodeType::OnlyLeafNode});
1010   EditorDOMPoint atStartOfNextNode(firstLeafChildOfNextNode
1011                                        ? firstLeafChildOfNextNode
1012                                        : splitResult.GetNextContent(),
1013                                    0);
1014   RefPtr<HTMLBRElement> brElement;
1015   // But don't try to split non-containers like `<br>`, `<hr>` and `<img>`
1016   // element.
1017   if (!atStartOfNextNode.IsInContentNode() ||
1018       !HTMLEditUtils::IsContainerNode(
1019           *atStartOfNextNode.ContainerAsContent())) {
1020     // If it's a `<br>` element, let's move it into new node later.
1021     brElement = HTMLBRElement::FromNode(atStartOfNextNode.GetContainer());
1022     if (!atStartOfNextNode.GetContainerParentAsContent()) {
1023       NS_WARNING("atStartOfNextNode was in an orphan node");
1024       return EditResult(NS_ERROR_FAILURE);
1025     }
1026     atStartOfNextNode.Set(atStartOfNextNode.GetContainerParent(), 0);
1027   }
1028   SplitNodeResult splitResultAtStartOfNextNode =
1029       SplitAncestorStyledInlineElementsAt(atStartOfNextNode, aProperty,
1030                                           aAttribute);
1031   if (splitResultAtStartOfNextNode.Failed()) {
1032     NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
1033     return EditResult(splitResultAtStartOfNextNode.Rv());
1034   }
1035 
1036   // Let's remove the next node if it becomes empty by splitting it.
1037   // XXX Is this possible case without mutation event listener?
1038   if (splitResultAtStartOfNextNode.Handled() &&
1039       splitResultAtStartOfNextNode.GetNextContent() &&
1040       HTMLEditUtils::IsEmptyNode(
1041           *splitResultAtStartOfNextNode.GetNextContent(),
1042           {EmptyCheckOption::TreatSingleBRElementAsVisible,
1043            EmptyCheckOption::TreatListItemAsVisible,
1044            EmptyCheckOption::TreatTableCellAsVisible})) {
1045     // Delete next node if it's empty.
1046     // MOZ_KnownLive(splitResultAtStartOfNextNode.GetNextContent()):
1047     // It's grabbed by splitResultAtStartOfNextNode.
1048     nsresult rv = DeleteNodeWithTransaction(
1049         MOZ_KnownLive(*splitResultAtStartOfNextNode.GetNextContent()));
1050     if (NS_WARN_IF(Destroyed())) {
1051       return EditResult(NS_ERROR_EDITOR_DESTROYED);
1052     }
1053     if (NS_FAILED(rv)) {
1054       NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1055       return EditResult(rv);
1056     }
1057   }
1058 
1059   // If there is no content, we should return here.
1060   // XXX Is this possible case without mutation event listener?
1061   if (NS_WARN_IF(!splitResultAtStartOfNextNode.Handled()) ||
1062       !splitResultAtStartOfNextNode.GetPreviousContent()) {
1063     // XXX This is really odd, but we retrun this value...
1064     const EditorRawDOMPoint& splitPoint =
1065         splitResult.AtSplitPoint<EditorRawDOMPoint>();
1066     const EditorRawDOMPoint& splitPointAtStartOfNextNode =
1067         splitResultAtStartOfNextNode.AtSplitPoint<EditorRawDOMPoint>();
1068     return EditResult(EditorDOMPoint(splitPoint.GetContainer(),
1069                                      splitPointAtStartOfNextNode.Offset()));
1070   }
1071 
1072   // Now, we want to put `<br>` element into the empty split node if
1073   // it was in next node of the first split.
1074   // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>`
1075   nsIContent* firstLeafChildOfPreviousNode = HTMLEditUtils::GetFirstLeafContent(
1076       *splitResultAtStartOfNextNode.GetPreviousContent(),
1077       {LeafNodeType::OnlyLeafNode});
1078   EditorDOMPoint pointToPutCaret(
1079       firstLeafChildOfPreviousNode
1080           ? firstLeafChildOfPreviousNode
1081           : splitResultAtStartOfNextNode.GetPreviousContent(),
1082       0);
1083   // If the right node starts with a `<br>`, suck it out of right node and into
1084   // the left node left node.  This is so we you don't revert back to the
1085   // previous style if you happen to click at the end of a line.
1086   if (brElement) {
1087     nsresult rv = MoveNodeWithTransaction(*brElement, pointToPutCaret);
1088     if (NS_WARN_IF(Destroyed())) {
1089       return EditResult(NS_ERROR_EDITOR_DESTROYED);
1090     }
1091     if (NS_FAILED(rv)) {
1092       NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
1093       return EditResult(rv);
1094     }
1095     // Update the child.
1096     pointToPutCaret.Set(pointToPutCaret.GetContainer(), 0);
1097   }
1098   // Finally, remove the specified style in the previous node at the
1099   // second split and tells good insertion point to the caller.  I.e., we
1100   // want to make the first example as:
1101   // `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>`
1102   //                    ^^^^^^^^^
1103   if (Element* previousElementOfSplitPoint = Element::FromNode(
1104           splitResultAtStartOfNextNode.GetPreviousContent())) {
1105     // Track the point at the new hierarchy.  This is so we can know where
1106     // to put the selection after we call RemoveStyleInside().
1107     // RemoveStyleInside() could remove any and all of those nodes, so I
1108     // have to use the range tracking system to find the right spot to put
1109     // selection.
1110     AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToPutCaret);
1111     // MOZ_KnownLive(previousElementOfSplitPoint):
1112     // It's grabbed by splitResultAtStartOfNextNode.
1113     nsresult rv = RemoveStyleInside(MOZ_KnownLive(*previousElementOfSplitPoint),
1114                                     aProperty, aAttribute, aSpecifiedStyle);
1115     if (NS_FAILED(rv)) {
1116       NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1117       return EditResult(rv);
1118     }
1119   }
1120   return EditResult(pointToPutCaret);
1121 }
1122 
RemoveStyleInside(Element & aElement,nsAtom * aProperty,nsAtom * aAttribute,SpecifiedStyle aSpecifiedStyle)1123 nsresult HTMLEditor::RemoveStyleInside(Element& aElement, nsAtom* aProperty,
1124                                        nsAtom* aAttribute,
1125                                        SpecifiedStyle aSpecifiedStyle) {
1126   // First, handle all descendants.
1127   RefPtr<nsIContent> child = aElement.GetFirstChild();
1128   while (child) {
1129     // cache next sibling since we might remove child
1130     // XXX Well, the next sibling is moved from `aElement`, shouldn't we skip
1131     //     it here?
1132     nsCOMPtr<nsIContent> nextSibling = child->GetNextSibling();
1133     if (child->IsElement()) {
1134       nsresult rv = RemoveStyleInside(MOZ_KnownLive(*child->AsElement()),
1135                                       aProperty, aAttribute, aSpecifiedStyle);
1136       if (NS_FAILED(rv)) {
1137         NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1138         return rv;
1139       }
1140     }
1141     child = ToRefPtr(std::move(nextSibling));
1142   }
1143 
1144   // Next, remove the element or its attribute.
1145   bool removeHTMLStyle = false;
1146   if (aProperty) {
1147     removeHTMLStyle =
1148         // If the element is a presentation element of aProperty
1149         aElement.NodeInfo()->NameAtom() == aProperty ||
1150         // or an `<a>` element with `href` attribute
1151         (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(&aElement)) ||
1152         // or an `<a>` element with `name` attribute
1153         (aProperty == nsGkAtoms::name &&
1154          HTMLEditUtils::IsNamedAnchor(&aElement));
1155   }
1156   // XXX Why do we check if aElement is editable only when aProperty is
1157   //     nullptr?
1158   else if (EditorUtils::IsEditableContent(aElement, EditorType::HTML)) {
1159     // or removing all styles and the element is a presentation element.
1160     removeHTMLStyle = HTMLEditUtils::IsRemovableInlineStyleElement(aElement);
1161   }
1162 
1163   if (removeHTMLStyle) {
1164     // If aAttribute is nullptr, we want to remove any matching inline styles
1165     // entirely.
1166     if (!aAttribute) {
1167       // If some style rules are specified to aElement, we need to keep them
1168       // as far as possible.
1169       // XXX Why don't we clone `id` attribute?
1170       if (aProperty && aSpecifiedStyle != SpecifiedStyle::Discard &&
1171           (aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::style) ||
1172            aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::_class))) {
1173         // Move `style` attribute and `class` element to span element before
1174         // removing aElement from the tree.
1175         RefPtr<Element> spanElement =
1176             InsertContainerWithTransaction(aElement, *nsGkAtoms::span);
1177         if (NS_WARN_IF(Destroyed())) {
1178           return NS_ERROR_EDITOR_DESTROYED;
1179         }
1180         if (!spanElement) {
1181           NS_WARNING(
1182               "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
1183               "failed");
1184           return NS_ERROR_FAILURE;
1185         }
1186         nsresult rv = CloneAttributeWithTransaction(*nsGkAtoms::style,
1187                                                     *spanElement, aElement);
1188         if (NS_WARN_IF(Destroyed())) {
1189           return NS_ERROR_EDITOR_DESTROYED;
1190         }
1191         if (NS_FAILED(rv)) {
1192           NS_WARNING(
1193               "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::style) "
1194               "failed");
1195           return rv;
1196         }
1197         rv = CloneAttributeWithTransaction(*nsGkAtoms::_class, *spanElement,
1198                                            aElement);
1199         if (NS_WARN_IF(Destroyed())) {
1200           return NS_ERROR_EDITOR_DESTROYED;
1201         }
1202         if (NS_FAILED(rv)) {
1203           NS_WARNING(
1204               "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::_class) "
1205               "failed");
1206           return rv;
1207         }
1208       }
1209       nsresult rv = RemoveContainerWithTransaction(aElement);
1210       if (NS_WARN_IF(Destroyed())) {
1211         return NS_ERROR_EDITOR_DESTROYED;
1212       }
1213       if (NS_FAILED(rv)) {
1214         NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
1215         return rv;
1216       }
1217     }
1218     // If aAttribute is specified, we want to remove only the attribute
1219     // unless it's the last attribute of aElement.
1220     else if (aElement.HasAttr(kNameSpaceID_None, aAttribute)) {
1221       if (IsOnlyAttribute(&aElement, aAttribute)) {
1222         nsresult rv = RemoveContainerWithTransaction(aElement);
1223         if (NS_WARN_IF(Destroyed())) {
1224           return NS_ERROR_EDITOR_DESTROYED;
1225         }
1226         if (NS_FAILED(rv)) {
1227           NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
1228           return rv;
1229         }
1230       } else {
1231         nsresult rv = RemoveAttributeWithTransaction(aElement, *aAttribute);
1232         if (NS_WARN_IF(Destroyed())) {
1233           return NS_ERROR_EDITOR_DESTROYED;
1234         }
1235         if (NS_FAILED(rv)) {
1236           NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
1237           return rv;
1238         }
1239       }
1240     }
1241   }
1242 
1243   // Then, remove CSS style if specified.
1244   // XXX aElement may have already been removed from the DOM tree.  Why
1245   //     do we keep handling aElement here??
1246   if (CSSEditUtils::IsCSSEditableProperty(&aElement, aProperty, aAttribute) &&
1247       CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(aElement, aProperty,
1248                                                      aAttribute)) {
1249     if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
1250       // If aElement has CSS declaration of the given style, remove it.
1251       // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must be
1252       // guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
1253       nsresult rv =
1254           mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithTransaction(
1255               MOZ_KnownLive(*styledElement), aProperty, aAttribute, nullptr);
1256       if (rv == NS_ERROR_EDITOR_DESTROYED) {
1257         NS_WARNING(
1258             "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1259             "destroyed the editor");
1260         return NS_ERROR_EDITOR_DESTROYED;
1261       }
1262       NS_WARNING_ASSERTION(
1263           NS_SUCCEEDED(rv),
1264           "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1265           "failed, but ignored");
1266     }
1267     // Additionally, remove aElement itself if it's a `<span>` or `<font>`
1268     // and it does not have non-empty `style`, `id` nor `class` attribute.
1269     if (aElement.IsAnyOfHTMLElements(nsGkAtoms::span, nsGkAtoms::font) &&
1270         !HTMLEditor::HasStyleOrIdOrClassAttribute(aElement)) {
1271       DebugOnly<nsresult> rvIgnored = RemoveContainerWithTransaction(aElement);
1272       if (NS_WARN_IF(Destroyed())) {
1273         return NS_ERROR_EDITOR_DESTROYED;
1274       }
1275       NS_WARNING_ASSERTION(
1276           NS_SUCCEEDED(rvIgnored),
1277           "HTMLEditor::RemoveContainerWithTransaction() failed, but ignored");
1278     }
1279   }
1280 
1281   // Finally, remove aElement if it's a `<big>` or `<small>` element and
1282   // we're removing `<font size>`.
1283   if (aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::size &&
1284       aElement.IsAnyOfHTMLElements(nsGkAtoms::big, nsGkAtoms::small)) {
1285     nsresult rv = RemoveContainerWithTransaction(aElement);
1286     if (NS_WARN_IF(Destroyed())) {
1287       return NS_ERROR_EDITOR_DESTROYED;
1288     }
1289     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1290                          "HTMLEditor::RemoveContainerWithTransaction() failed");
1291     return rv;
1292   }
1293 
1294   return NS_OK;
1295 }
1296 
IsOnlyAttribute(const Element * aElement,nsAtom * aAttribute)1297 bool HTMLEditor::IsOnlyAttribute(const Element* aElement, nsAtom* aAttribute) {
1298   MOZ_ASSERT(aElement);
1299 
1300   uint32_t attrCount = aElement->GetAttrCount();
1301   for (uint32_t i = 0; i < attrCount; ++i) {
1302     const nsAttrName* name = aElement->GetAttrNameAt(i);
1303     if (!name->NamespaceEquals(kNameSpaceID_None)) {
1304       return false;
1305     }
1306 
1307     // if it's the attribute we know about, or a special _moz attribute,
1308     // keep looking
1309     if (name->LocalName() != aAttribute) {
1310       nsAutoString attrString;
1311       name->LocalName()->ToString(attrString);
1312       if (!StringBeginsWith(attrString, u"_moz"_ns)) {
1313         return false;
1314       }
1315     }
1316   }
1317   // if we made it through all of them without finding a real attribute
1318   // other than aAttribute, then return true
1319   return true;
1320 }
1321 
PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange & aRange)1322 nsresult HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange) {
1323   // We assume that <a> is not nested.
1324   // XXX Shouldn't ignore the editing host.
1325   if (NS_WARN_IF(!aRange.GetStartContainer()) ||
1326       NS_WARN_IF(!aRange.GetEndContainer())) {
1327     return NS_ERROR_INVALID_ARG;
1328   }
1329   EditorRawDOMPoint newRangeStart(aRange.StartRef());
1330   for (Element* element :
1331        aRange.GetStartContainer()->InclusiveAncestorsOfType<Element>()) {
1332     if (element->IsHTMLElement(nsGkAtoms::body)) {
1333       break;
1334     }
1335     if (!HTMLEditUtils::IsNamedAnchor(element)) {
1336       continue;
1337     }
1338     newRangeStart.Set(element);
1339     break;
1340   }
1341 
1342   if (!newRangeStart.GetContainerAsContent()) {
1343     NS_WARNING(
1344         "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1345         "element from start container");
1346     return NS_ERROR_FAILURE;
1347   }
1348 
1349   EditorRawDOMPoint newRangeEnd(aRange.EndRef());
1350   for (Element* element :
1351        aRange.GetEndContainer()->InclusiveAncestorsOfType<Element>()) {
1352     if (element->IsHTMLElement(nsGkAtoms::body)) {
1353       break;
1354     }
1355     if (!HTMLEditUtils::IsNamedAnchor(element)) {
1356       continue;
1357     }
1358     newRangeEnd.SetAfter(element);
1359     break;
1360   }
1361 
1362   if (!newRangeEnd.GetContainerAsContent()) {
1363     NS_WARNING(
1364         "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1365         "element from end container");
1366     return NS_ERROR_FAILURE;
1367   }
1368 
1369   if (newRangeStart == aRange.StartRef() && newRangeEnd == aRange.EndRef()) {
1370     return NS_OK;
1371   }
1372 
1373   nsresult rv = aRange.SetStartAndEnd(newRangeStart.ToRawRangeBoundary(),
1374                                       newRangeEnd.ToRawRangeBoundary());
1375   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
1376   return rv;
1377 }
1378 
PromoteInlineRange(nsRange & aRange)1379 nsresult HTMLEditor::PromoteInlineRange(nsRange& aRange) {
1380   if (NS_WARN_IF(!aRange.GetStartContainer()) ||
1381       NS_WARN_IF(!aRange.GetEndContainer())) {
1382     return NS_ERROR_INVALID_ARG;
1383   }
1384   EditorRawDOMPoint newRangeStart(aRange.StartRef());
1385   for (nsIContent* content :
1386        aRange.GetStartContainer()->InclusiveAncestorsOfType<nsIContent>()) {
1387     MOZ_ASSERT(newRangeStart.GetContainer() == content);
1388     if (content->IsHTMLElement(nsGkAtoms::body) ||
1389         !EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1390         !IsStartOfContainerOrBeforeFirstEditableChild(newRangeStart)) {
1391       break;
1392     }
1393     newRangeStart.Set(content);
1394   }
1395   if (!newRangeStart.GetContainerAsContent()) {
1396     NS_WARNING(
1397         "HTMLEditor::PromoteInlineRange() reached root element from start "
1398         "container");
1399     return NS_ERROR_FAILURE;
1400   }
1401 
1402   EditorRawDOMPoint newRangeEnd(aRange.EndRef());
1403   for (nsIContent* content :
1404        aRange.GetEndContainer()->InclusiveAncestorsOfType<nsIContent>()) {
1405     MOZ_ASSERT(newRangeEnd.GetContainer() == content);
1406     if (content->IsHTMLElement(nsGkAtoms::body) ||
1407         !EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1408         !IsEndOfContainerOrEqualsOrAfterLastEditableChild(newRangeEnd)) {
1409       break;
1410     }
1411     newRangeEnd.SetAfter(content);
1412   }
1413   if (!newRangeEnd.GetContainerAsContent()) {
1414     NS_WARNING(
1415         "HTMLEditor::PromoteInlineRange() reached root element from end "
1416         "container");
1417     return NS_ERROR_FAILURE;
1418   }
1419 
1420   if (newRangeStart == aRange.StartRef() && newRangeEnd == aRange.EndRef()) {
1421     return NS_OK;
1422   }
1423 
1424   nsresult rv = aRange.SetStartAndEnd(newRangeStart.ToRawRangeBoundary(),
1425                                       newRangeEnd.ToRawRangeBoundary());
1426   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
1427   return rv;
1428 }
1429 
IsStartOfContainerOrBeforeFirstEditableChild(const EditorRawDOMPoint & aPoint) const1430 bool HTMLEditor::IsStartOfContainerOrBeforeFirstEditableChild(
1431     const EditorRawDOMPoint& aPoint) const {
1432   MOZ_ASSERT(aPoint.IsSet());
1433 
1434   if (aPoint.IsStartOfContainer()) {
1435     return true;
1436   }
1437 
1438   if (aPoint.IsInTextNode()) {
1439     return false;
1440   }
1441 
1442   nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
1443       *aPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode});
1444   if (!firstEditableChild) {
1445     return true;
1446   }
1447   return EditorRawDOMPoint(firstEditableChild).Offset() >= aPoint.Offset();
1448 }
1449 
IsEndOfContainerOrEqualsOrAfterLastEditableChild(const EditorRawDOMPoint & aPoint) const1450 bool HTMLEditor::IsEndOfContainerOrEqualsOrAfterLastEditableChild(
1451     const EditorRawDOMPoint& aPoint) const {
1452   MOZ_ASSERT(aPoint.IsSet());
1453 
1454   if (aPoint.IsEndOfContainer()) {
1455     return true;
1456   }
1457 
1458   if (aPoint.IsInTextNode()) {
1459     return false;
1460   }
1461 
1462   nsIContent* lastEditableChild = HTMLEditUtils::GetLastChild(
1463       *aPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode});
1464   if (!lastEditableChild) {
1465     return true;
1466   }
1467   return EditorRawDOMPoint(lastEditableChild).Offset() < aPoint.Offset();
1468 }
1469 
GetInlinePropertyBase(nsAtom & aHTMLProperty,nsAtom * aAttribute,const nsAString * aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString * outValue) const1470 nsresult HTMLEditor::GetInlinePropertyBase(nsAtom& aHTMLProperty,
1471                                            nsAtom* aAttribute,
1472                                            const nsAString* aValue,
1473                                            bool* aFirst, bool* aAny, bool* aAll,
1474                                            nsAString* outValue) const {
1475   MOZ_ASSERT(IsEditActionDataAvailable());
1476 
1477   *aAny = false;
1478   *aAll = true;
1479   *aFirst = false;
1480   bool first = true;
1481 
1482   bool isCollapsed = SelectionRef().IsCollapsed();
1483   RefPtr<nsRange> range = SelectionRef().GetRangeAt(0);
1484   // XXX: Should be a while loop, to get each separate range
1485   // XXX: ERROR_HANDLING can currentItem be null?
1486   if (range) {
1487     // For each range, set a flag
1488     bool firstNodeInRange = true;
1489 
1490     if (isCollapsed) {
1491       nsCOMPtr<nsINode> collapsedNode = range->GetStartContainer();
1492       if (NS_WARN_IF(!collapsedNode)) {
1493         return NS_ERROR_FAILURE;
1494       }
1495       bool isSet, theSetting;
1496       nsString tOutString;
1497       if (aAttribute) {
1498         mTypeInState->GetTypingState(isSet, theSetting, &aHTMLProperty,
1499                                      aAttribute, &tOutString);
1500         if (outValue) {
1501           outValue->Assign(tOutString);
1502         }
1503       } else {
1504         mTypeInState->GetTypingState(isSet, theSetting, &aHTMLProperty);
1505       }
1506       if (isSet) {
1507         *aFirst = *aAny = *aAll = theSetting;
1508         return NS_OK;
1509       }
1510 
1511       if (collapsedNode->IsContent() &&
1512           CSSEditUtils::IsCSSEditableProperty(collapsedNode, &aHTMLProperty,
1513                                               aAttribute)) {
1514         if (aValue) {
1515           tOutString.Assign(*aValue);
1516         }
1517         *aFirst = *aAny = *aAll =
1518             CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1519                 MOZ_KnownLive(*collapsedNode->AsContent()), &aHTMLProperty,
1520                 aAttribute, tOutString);
1521         if (NS_WARN_IF(Destroyed())) {
1522           return NS_ERROR_EDITOR_DESTROYED;
1523         }
1524         if (outValue) {
1525           outValue->Assign(tOutString);
1526         }
1527         return NS_OK;
1528       }
1529 
1530       *aFirst = *aAny = *aAll = collapsedNode->IsContent() &&
1531                                 HTMLEditUtils::IsInlineStyleSetByElement(
1532                                     *collapsedNode->AsContent(), aHTMLProperty,
1533                                     aAttribute, aValue, outValue);
1534       return NS_OK;
1535     }
1536 
1537     // Non-collapsed selection
1538 
1539     nsAutoString firstValue, theValue;
1540 
1541     nsCOMPtr<nsINode> endNode = range->GetEndContainer();
1542     uint32_t endOffset = range->EndOffset();
1543 
1544     PostContentIterator postOrderIter;
1545     DebugOnly<nsresult> rvIgnored = postOrderIter.Init(range);
1546     NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1547                          "Failed to initialize post-order content iterator");
1548     for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
1549       if (!postOrderIter.GetCurrentNode()->IsContent()) {
1550         continue;
1551       }
1552       nsCOMPtr<nsIContent> content =
1553           postOrderIter.GetCurrentNode()->AsContent();
1554 
1555       if (content->IsHTMLElement(nsGkAtoms::body)) {
1556         break;
1557       }
1558 
1559       // just ignore any non-editable nodes
1560       if (content->IsText() &&
1561           (!EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1562            !HTMLEditUtils::IsVisibleTextNode(*content->AsText()))) {
1563         continue;
1564       }
1565       if (content->GetAsText()) {
1566         if (!isCollapsed && first && firstNodeInRange) {
1567           firstNodeInRange = false;
1568           if (range->StartOffset() == content->Length()) {
1569             continue;
1570           }
1571         } else if (content == endNode && !endOffset) {
1572           continue;
1573         }
1574       } else if (content->IsElement()) {
1575         // handle non-text leaf nodes here
1576         continue;
1577       }
1578 
1579       bool isSet = false;
1580       bool useTextDecoration =
1581           &aHTMLProperty == nsGkAtoms::u || &aHTMLProperty == nsGkAtoms::strike;
1582       if (first) {
1583         if (CSSEditUtils::IsCSSEditableProperty(content, &aHTMLProperty,
1584                                                 aAttribute)) {
1585           // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
1586           // equivalence in this implementation for node; let's check if it
1587           // carries those CSS styles
1588           if (aValue) {
1589             firstValue.Assign(*aValue);
1590           }
1591           isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1592               *content, &aHTMLProperty, aAttribute, firstValue);
1593           if (NS_WARN_IF(Destroyed())) {
1594             return NS_ERROR_EDITOR_DESTROYED;
1595           }
1596         } else {
1597           isSet = HTMLEditUtils::IsInlineStyleSetByElement(
1598               *content, aHTMLProperty, aAttribute, aValue, &firstValue);
1599         }
1600         *aFirst = isSet;
1601         first = false;
1602         if (outValue) {
1603           *outValue = firstValue;
1604         }
1605       } else {
1606         if (CSSEditUtils::IsCSSEditableProperty(content, &aHTMLProperty,
1607                                                 aAttribute)) {
1608           // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
1609           // equivalence in this implementation for node; let's check if it
1610           // carries those CSS styles
1611           if (aValue) {
1612             theValue.Assign(*aValue);
1613           }
1614           isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1615               *content, &aHTMLProperty, aAttribute, theValue);
1616           if (NS_WARN_IF(Destroyed())) {
1617             return NS_ERROR_EDITOR_DESTROYED;
1618           }
1619         } else {
1620           isSet = HTMLEditUtils::IsInlineStyleSetByElement(
1621               *content, aHTMLProperty, aAttribute, aValue, &theValue);
1622         }
1623 
1624         if (firstValue != theValue &&
1625             // For text-decoration related HTML properties, i.e. <u> and
1626             // <strike>, we have to also check |isSet| because text-decoration
1627             // is a shorthand property, and it may contains other unrelated
1628             // longhand components, e.g. text-decoration-color, so we have to do
1629             // an extra check before setting |*aAll| to false.
1630             // e.g.
1631             //   firstValue: "underline rgb(0, 0, 0)"
1632             //   theValue: "underline rgb(0, 0, 238)" // <a> uses blue color
1633             // These two values should be the same if we are checking `<u>`.
1634             // That's why we need to check |*aFirst| and |isSet|.
1635             //
1636             // This is a work-around for text-decoration.
1637             // The spec issue: https://github.com/w3c/editing/issues/241.
1638             // Once this spec issue is resolved, we could drop this work-around
1639             // check.
1640             (!useTextDecoration || *aFirst != isSet)) {
1641           *aAll = false;
1642         }
1643       }
1644 
1645       if (isSet) {
1646         *aAny = true;
1647       } else {
1648         *aAll = false;
1649       }
1650     }
1651   }
1652   if (!*aAny) {
1653     // make sure that if none of the selection is set, we don't report all is
1654     // set
1655     *aAll = false;
1656   }
1657   return NS_OK;
1658 }
1659 
GetInlineProperty(nsAtom * aHTMLProperty,nsAtom * aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll) const1660 nsresult HTMLEditor::GetInlineProperty(nsAtom* aHTMLProperty,
1661                                        nsAtom* aAttribute,
1662                                        const nsAString& aValue, bool* aFirst,
1663                                        bool* aAny, bool* aAll) const {
1664   if (NS_WARN_IF(!aHTMLProperty) || NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) ||
1665       NS_WARN_IF(!aAll)) {
1666     return NS_ERROR_INVALID_ARG;
1667   }
1668 
1669   AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1670   if (NS_WARN_IF(!editActionData.CanHandle())) {
1671     return NS_ERROR_NOT_INITIALIZED;
1672   }
1673 
1674   const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr;
1675   nsresult rv = GetInlinePropertyBase(*aHTMLProperty, aAttribute, val, aFirst,
1676                                       aAny, aAll, nullptr);
1677   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1678                        "HTMLEditor::GetInlinePropertyBase() failed");
1679   return EditorBase::ToGenericNSResult(rv);
1680 }
1681 
GetInlinePropertyWithAttrValue(const nsAString & aHTMLProperty,const nsAString & aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString & outValue)1682 NS_IMETHODIMP HTMLEditor::GetInlinePropertyWithAttrValue(
1683     const nsAString& aHTMLProperty, const nsAString& aAttribute,
1684     const nsAString& aValue, bool* aFirst, bool* aAny, bool* aAll,
1685     nsAString& outValue) {
1686   RefPtr<nsAtom> property = NS_Atomize(aHTMLProperty);
1687   nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
1688   nsresult rv = GetInlinePropertyWithAttrValue(
1689       property, MOZ_KnownLive(attribute), aValue, aFirst, aAny, aAll, outValue);
1690   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1691                        "HTMLEditor::GetInlinePropertyWithAttrValue() failed");
1692   return rv;
1693 }
1694 
GetInlinePropertyWithAttrValue(nsAtom * aHTMLProperty,nsAtom * aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString & outValue)1695 nsresult HTMLEditor::GetInlinePropertyWithAttrValue(
1696     nsAtom* aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue,
1697     bool* aFirst, bool* aAny, bool* aAll, nsAString& outValue) {
1698   if (NS_WARN_IF(!aHTMLProperty) || NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) ||
1699       NS_WARN_IF(!aAll)) {
1700     return NS_ERROR_INVALID_ARG;
1701   }
1702 
1703   AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1704   if (NS_WARN_IF(!editActionData.CanHandle())) {
1705     return NS_ERROR_NOT_INITIALIZED;
1706   }
1707 
1708   const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr;
1709   nsresult rv = GetInlinePropertyBase(*aHTMLProperty, aAttribute, val, aFirst,
1710                                       aAny, aAll, &outValue);
1711   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1712                        "HTMLEditor::GetInlinePropertyBase() failed");
1713   return EditorBase::ToGenericNSResult(rv);
1714 }
1715 
RemoveAllInlinePropertiesAsAction(nsIPrincipal * aPrincipal)1716 nsresult HTMLEditor::RemoveAllInlinePropertiesAsAction(
1717     nsIPrincipal* aPrincipal) {
1718   AutoEditActionDataSetter editActionData(
1719       *this, EditAction::eRemoveAllInlineStyleProperties, aPrincipal);
1720   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1721   if (NS_FAILED(rv)) {
1722     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1723                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1724     return EditorBase::ToGenericNSResult(rv);
1725   }
1726 
1727   AutoPlaceholderBatch treatAsOneTransaction(
1728       *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1729   IgnoredErrorResult ignoredError;
1730   AutoEditSubActionNotifier startToHandleEditSubAction(
1731       *this, EditSubAction::eRemoveAllTextProperties, nsIEditor::eNext,
1732       ignoredError);
1733   if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1734     return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
1735   }
1736   NS_WARNING_ASSERTION(
1737       !ignoredError.Failed(),
1738       "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1739 
1740   rv =
1741       RemoveInlinePropertyInternal(nullptr, nullptr, RemoveRelatedElements::No);
1742   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1743                        "HTMLEditor::RemoveInlinePropertyInternal(nullptr, "
1744                        "nullptr, RemoveRelatedElements::No) failed");
1745   return EditorBase::ToGenericNSResult(rv);
1746 }
1747 
RemoveInlinePropertyAsAction(nsStaticAtom & aHTMLProperty,nsStaticAtom * aAttribute,nsIPrincipal * aPrincipal)1748 nsresult HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom& aHTMLProperty,
1749                                                   nsStaticAtom* aAttribute,
1750                                                   nsIPrincipal* aPrincipal) {
1751   AutoEditActionDataSetter editActionData(
1752       *this,
1753       HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty, aAttribute,
1754                                                 false),
1755       aPrincipal);
1756   switch (editActionData.GetEditAction()) {
1757     case EditAction::eRemoveFontFamilyProperty:
1758       MOZ_ASSERT(!u""_ns.IsVoid());
1759       editActionData.SetData(u""_ns);
1760       break;
1761     case EditAction::eRemoveColorProperty:
1762     case EditAction::eRemoveBackgroundColorPropertyInline:
1763       editActionData.SetColorData(u""_ns);
1764       break;
1765     default:
1766       break;
1767   }
1768   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1769   if (NS_FAILED(rv)) {
1770     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1771                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1772     return EditorBase::ToGenericNSResult(rv);
1773   }
1774 
1775   rv = RemoveInlinePropertyInternal(&aHTMLProperty, aAttribute,
1776                                     RemoveRelatedElements::Yes);
1777   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1778                        "HTMLEditor::RemoveInlinePropertyInternal("
1779                        "RemoveRelatedElements::Yes) failed");
1780   return EditorBase::ToGenericNSResult(rv);
1781 }
1782 
RemoveInlineProperty(const nsAString & aProperty,const nsAString & aAttribute)1783 NS_IMETHODIMP HTMLEditor::RemoveInlineProperty(const nsAString& aProperty,
1784                                                const nsAString& aAttribute) {
1785   nsStaticAtom* property = NS_GetStaticAtom(aProperty);
1786   nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
1787 
1788   AutoEditActionDataSetter editActionData(
1789       *this,
1790       HTMLEditUtils::GetEditActionForFormatText(*property, attribute, false));
1791   switch (editActionData.GetEditAction()) {
1792     case EditAction::eRemoveFontFamilyProperty:
1793       MOZ_ASSERT(!u""_ns.IsVoid());
1794       editActionData.SetData(u""_ns);
1795       break;
1796     case EditAction::eRemoveColorProperty:
1797     case EditAction::eRemoveBackgroundColorPropertyInline:
1798       editActionData.SetColorData(u""_ns);
1799       break;
1800     default:
1801       break;
1802   }
1803   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1804   if (NS_FAILED(rv)) {
1805     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1806                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1807     return EditorBase::ToGenericNSResult(rv);
1808   }
1809 
1810   rv = RemoveInlinePropertyInternal(MOZ_KnownLive(property),
1811                                     MOZ_KnownLive(attribute),
1812                                     RemoveRelatedElements::No);
1813   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1814                        "HTMLEditor::RemoveInlinePropertyInternal("
1815                        "RemoveRelatedElements::No) failed");
1816   return EditorBase::ToGenericNSResult(rv);
1817 }
1818 
RemoveInlinePropertyInternal(nsStaticAtom * aProperty,nsStaticAtom * aAttribute,RemoveRelatedElements aRemoveRelatedElements)1819 nsresult HTMLEditor::RemoveInlinePropertyInternal(
1820     nsStaticAtom* aProperty, nsStaticAtom* aAttribute,
1821     RemoveRelatedElements aRemoveRelatedElements) {
1822   MOZ_ASSERT(IsEditActionDataAvailable());
1823   MOZ_ASSERT(aAttribute != nsGkAtoms::_empty);
1824 
1825   if (NS_WARN_IF(!mInitSucceeded)) {
1826     return NS_ERROR_NOT_INITIALIZED;
1827   }
1828 
1829   DebugOnly<nsresult> rvIgnored = CommitComposition();
1830   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1831                        "EditorBase::CommitComposition() failed, but ignored");
1832 
1833   // Also remove equivalent properties (bug 317093)
1834   struct HTMLStyle final {
1835     // HTML tag name or nsGkAtoms::href or nsGkAtoms::name.
1836     nsStaticAtom* mProperty = nullptr;
1837     // HTML attribute like nsGkAtom::color for nsGkAtoms::font.
1838     nsStaticAtom* mAttribute = nullptr;
1839 
1840     explicit HTMLStyle(nsStaticAtom* aProperty,
1841                        nsStaticAtom* aAttribute = nullptr)
1842         : mProperty(aProperty), mAttribute(aAttribute) {}
1843   };
1844   AutoTArray<HTMLStyle, 3> removeStyles;
1845   if (aRemoveRelatedElements == RemoveRelatedElements::Yes) {
1846     if (aProperty == nsGkAtoms::b) {
1847       removeStyles.AppendElement(HTMLStyle(nsGkAtoms::strong));
1848     } else if (aProperty == nsGkAtoms::i) {
1849       removeStyles.AppendElement(HTMLStyle(nsGkAtoms::em));
1850     } else if (aProperty == nsGkAtoms::strike) {
1851       removeStyles.AppendElement(HTMLStyle(nsGkAtoms::s));
1852     } else if (aProperty == nsGkAtoms::font) {
1853       if (aAttribute == nsGkAtoms::size) {
1854         removeStyles.AppendElement(HTMLStyle(nsGkAtoms::big));
1855         removeStyles.AppendElement(HTMLStyle(nsGkAtoms::small));
1856       }
1857       // Handling `<tt>` element code was implemented for composer (bug 115922).
1858       // This shouldn't work with `Document.execCommand()` for compatibility
1859       // with the other browsers.  Currently, edit action principal is set only
1860       // when the root caller is Document::ExecCommand() so that we should
1861       // handle `<tt>` element only when the principal is nullptr that must be
1862       // only when XUL command is executed on composer.
1863       else if (aAttribute == nsGkAtoms::face && !GetEditActionPrincipal()) {
1864         removeStyles.AppendElement(HTMLStyle(nsGkAtoms::tt));
1865       }
1866     }
1867   }
1868   removeStyles.AppendElement(HTMLStyle(aProperty, aAttribute));
1869 
1870   if (SelectionRef().IsCollapsed()) {
1871     // Manipulating text attributes on a collapsed selection only sets state
1872     // for the next text insertion
1873     if (removeStyles[0].mProperty) {
1874       for (HTMLStyle& style : removeStyles) {
1875         MOZ_ASSERT(style.mProperty);
1876         if (style.mProperty == nsGkAtoms::href ||
1877             style.mProperty == nsGkAtoms::name) {
1878           mTypeInState->ClearProp(nsGkAtoms::a, nullptr);
1879         } else {
1880           mTypeInState->ClearProp(style.mProperty, style.mAttribute);
1881         }
1882       }
1883     } else {
1884       mTypeInState->ClearAllProps();
1885     }
1886     return NS_OK;
1887   }
1888 
1889   // XXX Shouldn't we quit before calling `CommitComposition()`?
1890   if (IsInPlaintextMode()) {
1891     return NS_OK;
1892   }
1893 
1894   EditActionResult result = CanHandleHTMLEditSubAction();
1895   if (result.Failed() || result.Canceled()) {
1896     NS_WARNING_ASSERTION(result.Succeeded(),
1897                          "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1898     return result.Rv();
1899   }
1900 
1901   AutoPlaceholderBatch treatAsOneTransaction(
1902       *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1903   IgnoredErrorResult ignoredError;
1904   AutoEditSubActionNotifier startToHandleEditSubAction(
1905       *this, EditSubAction::eRemoveTextProperty, nsIEditor::eNext,
1906       ignoredError);
1907   if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1908     return ignoredError.StealNSResult();
1909   }
1910   NS_WARNING_ASSERTION(
1911       !ignoredError.Failed(),
1912       "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1913 
1914   {
1915     AutoSelectionRestorer restoreSelectionLater(*this);
1916     AutoTransactionsConserveSelection dontChangeMySelection(*this);
1917 
1918     for (HTMLStyle& style : removeStyles) {
1919       // Loop through the ranges in the selection.
1920       // XXX Although `Selection` will be restored by AutoSelectionRestorer,
1921       //     AutoSelectionRangeArray just grabs the ranges in `Selection`.
1922       //     Therefore, modifying each range may notify selection listener.  So
1923       //     perhaps, we should clone each range here instead.
1924       AutoSelectionRangeArray arrayOfRanges(SelectionRef());
1925       for (auto& range : arrayOfRanges.mRanges) {
1926         if (style.mProperty == nsGkAtoms::name) {
1927           // Promote range if it starts or end in a named anchor and we want to
1928           // remove named anchors
1929           nsresult rv = PromoteRangeIfStartsOrEndsInNamedAnchor(*range);
1930           if (NS_FAILED(rv)) {
1931             NS_WARNING(
1932                 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() failed");
1933             return rv;
1934           }
1935         } else {
1936           // Adjust range to include any ancestors whose children are entirely
1937           // selected
1938           nsresult rv = PromoteInlineRange(*range);
1939           if (NS_FAILED(rv)) {
1940             NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
1941             return rv;
1942           }
1943         }
1944 
1945         // Remove this style from ancestors of our range endpoints, splitting
1946         // them as appropriate
1947         SplitRangeOffResult splitRangeOffResult =
1948             SplitAncestorStyledInlineElementsAtRangeEdges(
1949                 EditorDOMPoint(range->StartRef()),
1950                 EditorDOMPoint(range->EndRef()), MOZ_KnownLive(style.mProperty),
1951                 MOZ_KnownLive(style.mAttribute));
1952         if (splitRangeOffResult.Failed()) {
1953           NS_WARNING(
1954               "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
1955               "failed");
1956           return splitRangeOffResult.Rv();
1957         }
1958 
1959         // XXX Modifying `range` means that we may modify ranges in `Selection`.
1960         //     Is this intentional?  Note that the range may be not in
1961         //     `Selection` too.  It seems that at least one of them is not
1962         //     an unexpected case.
1963         const EditorDOMPoint& startOfRange(
1964             splitRangeOffResult.SplitPointAtStart());
1965         const EditorDOMPoint& endOfRange(splitRangeOffResult.SplitPointAtEnd());
1966         if (NS_WARN_IF(!startOfRange.IsSet()) ||
1967             NS_WARN_IF(!endOfRange.IsSet())) {
1968           continue;
1969         }
1970 
1971         nsresult rv = range->SetStartAndEnd(startOfRange.ToRawRangeBoundary(),
1972                                             endOfRange.ToRawRangeBoundary());
1973         // Note that modifying a range in `Selection` may run script so that
1974         // we might have been destroyed here.
1975         if (NS_WARN_IF(Destroyed())) {
1976           return NS_ERROR_EDITOR_DESTROYED;
1977         }
1978         if (NS_FAILED(rv)) {
1979           NS_WARNING("nsRange::SetStartAndEnd() failed");
1980           return rv;
1981         }
1982 
1983         // Collect editable nodes which are entirely contained in the range.
1984         AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
1985         if (startOfRange.GetContainer() == endOfRange.GetContainer() &&
1986             startOfRange.IsInTextNode()) {
1987           if (!EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
1988                                               EditorType::HTML)) {
1989             continue;
1990           }
1991           arrayOfContents.AppendElement(*startOfRange.ContainerAsText());
1992         } else if (startOfRange.IsInTextNode() && endOfRange.IsInTextNode() &&
1993                    startOfRange.GetContainer()->GetNextSibling() ==
1994                        endOfRange.GetContainer()) {
1995           if (EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
1996                                              EditorType::HTML)) {
1997             arrayOfContents.AppendElement(*startOfRange.ContainerAsText());
1998           }
1999           if (EditorUtils::IsEditableContent(*endOfRange.ContainerAsText(),
2000                                              EditorType::HTML)) {
2001             arrayOfContents.AppendElement(*endOfRange.ContainerAsText());
2002           }
2003           if (arrayOfContents.IsEmpty()) {
2004             continue;
2005           }
2006         } else {
2007           // Append first node if it's a text node but selected not entirely.
2008           if (startOfRange.IsInTextNode() &&
2009               !startOfRange.IsStartOfContainer() &&
2010               EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
2011                                              EditorType::HTML)) {
2012             arrayOfContents.AppendElement(*startOfRange.ContainerAsText());
2013           }
2014           // Append all entirely selected nodes.
2015           ContentSubtreeIterator subtreeIter;
2016           if (NS_SUCCEEDED(subtreeIter.Init(range))) {
2017             for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
2018               nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
2019               if (NS_WARN_IF(!node)) {
2020                 return NS_ERROR_FAILURE;
2021               }
2022               if (node->IsContent() &&
2023                   EditorUtils::IsEditableContent(*node->AsContent(),
2024                                                  EditorType::HTML)) {
2025                 arrayOfContents.AppendElement(*node->AsContent());
2026               }
2027             }
2028           }
2029           // Append last node if it's a text node but selected not entirely.
2030           if (startOfRange.GetContainer() != endOfRange.GetContainer() &&
2031               endOfRange.IsInTextNode() && !endOfRange.IsEndOfContainer() &&
2032               EditorUtils::IsEditableContent(*endOfRange.ContainerAsText(),
2033                                              EditorType::HTML)) {
2034             arrayOfContents.AppendElement(*endOfRange.ContainerAsText());
2035           }
2036         }
2037 
2038         for (OwningNonNull<nsIContent>& content : arrayOfContents) {
2039           if (content->IsElement()) {
2040             nsresult rv = RemoveStyleInside(
2041                 MOZ_KnownLive(*content->AsElement()),
2042                 MOZ_KnownLive(style.mProperty), MOZ_KnownLive(style.mAttribute),
2043                 SpecifiedStyle::Preserve);
2044             if (NS_FAILED(rv)) {
2045               NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
2046               return rv;
2047             }
2048           }
2049 
2050           bool isRemovable = IsRemovableParentStyleWithNewSpanElement(
2051               MOZ_KnownLive(content), MOZ_KnownLive(style.mProperty),
2052               MOZ_KnownLive(style.mAttribute));
2053           if (NS_WARN_IF(Destroyed())) {
2054             return NS_ERROR_EDITOR_DESTROYED;
2055           }
2056           if (!isRemovable) {
2057             continue;
2058           }
2059 
2060           if (!content->IsText()) {
2061             // XXX Do we need to call this even when data node or something?  If
2062             //     so, for what?
2063             // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
2064             // keep it alive.
2065             DebugOnly<nsresult> rvIgnored = SetInlinePropertyOnNode(
2066                 MOZ_KnownLive(content), MOZ_KnownLive(*style.mProperty),
2067                 MOZ_KnownLive(style.mAttribute),
2068                 u"-moz-editor-invert-value"_ns);
2069             if (NS_WARN_IF(Destroyed())) {
2070               return NS_ERROR_EDITOR_DESTROYED;
2071             }
2072             NS_WARNING_ASSERTION(
2073                 NS_SUCCEEDED(rvIgnored),
2074                 "HTMLEditor::SetInlinePropertyOnNode(-moz-editor-invert-value) "
2075                 "failed, but ignored");
2076             continue;
2077           }
2078 
2079           // If current node is a text node, we need to create `<span>` element
2080           // for it to overwrite parent style.  Unfortunately, all browsers
2081           // don't join text nodes when removing a style.  Therefore, there
2082           // may be multiple text nodes as adjacent siblings.  That's the
2083           // reason why we need to handle text nodes in this loop.
2084           uint32_t startOffset = content == startOfRange.GetContainer()
2085                                      ? startOfRange.Offset()
2086                                      : 0;
2087           uint32_t endOffset = content == endOfRange.GetContainer()
2088                                    ? endOfRange.Offset()
2089                                    : content->Length();
2090           nsresult rv = SetInlinePropertyOnTextNode(
2091               MOZ_KnownLive(*content->AsText()), startOffset, endOffset,
2092               MOZ_KnownLive(*style.mProperty), MOZ_KnownLive(style.mAttribute),
2093               u"-moz-editor-invert-value"_ns);
2094           if (NS_FAILED(rv)) {
2095             NS_WARNING(
2096                 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2097                 "value) failed");
2098             return rv;
2099           }
2100         }
2101 
2102         // For avoiding unnecessary loop cost, check whether the style is
2103         // invertible first.
2104         if (style.mProperty &&
2105             CSSEditUtils::IsCSSInvertible(*style.mProperty, style.mAttribute)) {
2106           // Finally, we should remove the style from all leaf text nodes if
2107           // they still have the style.
2108           AutoTArray<OwningNonNull<Text>, 32> leafTextNodes;
2109           for (OwningNonNull<nsIContent>& content : arrayOfContents) {
2110             if (content->IsElement()) {
2111               CollectEditableLeafTextNodes(*content->AsElement(),
2112                                            leafTextNodes);
2113             }
2114           }
2115           for (OwningNonNull<Text>& textNode : leafTextNodes) {
2116             bool isRemovable = IsRemovableParentStyleWithNewSpanElement(
2117                 MOZ_KnownLive(textNode), MOZ_KnownLive(style.mProperty),
2118                 MOZ_KnownLive(style.mAttribute));
2119             if (NS_WARN_IF(Destroyed())) {
2120               return NS_ERROR_EDITOR_DESTROYED;
2121             }
2122             if (!isRemovable) {
2123               continue;
2124             }
2125             // MOZ_KnownLive because 'leafTextNodes' is guaranteed to
2126             // keep it alive.
2127             nsresult rv = SetInlinePropertyOnTextNode(
2128                 MOZ_KnownLive(textNode), 0, textNode->TextLength(),
2129                 MOZ_KnownLive(*style.mProperty),
2130                 MOZ_KnownLive(style.mAttribute),
2131                 u"-moz-editor-invert-value"_ns);
2132             if (NS_FAILED(rv)) {
2133               NS_WARNING(
2134                   "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2135                   "value) failed");
2136               return rv;
2137             }
2138           }
2139         }
2140       }  // for-loop of selection ranges
2141     }    // for-loop of styles
2142   }      // AutoSelectionRestorer and AutoTransactionsConserveSelection
2143 
2144   // Restoring `Selection` may cause destroying us.
2145   return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
2146 }
2147 
IsRemovableParentStyleWithNewSpanElement(nsIContent & aContent,nsAtom * aHTMLProperty,nsAtom * aAttribute) const2148 bool HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
2149     nsIContent& aContent, nsAtom* aHTMLProperty, nsAtom* aAttribute) const {
2150   // We don't support to remove all inline styles with this path.
2151   if (!aHTMLProperty) {
2152     return false;
2153   }
2154 
2155   // First check whether the style is invertible since this is the fastest
2156   // check.
2157   if (!CSSEditUtils::IsCSSInvertible(*aHTMLProperty, aAttribute)) {
2158     return false;
2159   }
2160 
2161   // If parent block has the removing style, we should create `<span>`
2162   // element to remove the style even in HTML mode since Chrome does it.
2163   if (!CSSEditUtils::IsCSSEditableProperty(&aContent, aHTMLProperty,
2164                                            aAttribute)) {
2165     return false;
2166   }
2167 
2168   // aContent's computed style indicates the CSS equivalence to
2169   // the HTML style to remove is applied; but we found no element
2170   // in the ancestors of aContent carrying specified styles;
2171   // assume it comes from a rule and let's try to insert a span
2172   // "inverting" the style
2173   nsAutoString emptyString;
2174   bool isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
2175       aContent, aHTMLProperty, aAttribute, emptyString);
2176   return NS_WARN_IF(Destroyed()) ? false : isSet;
2177 }
2178 
CollectEditableLeafTextNodes(Element & aElement,nsTArray<OwningNonNull<Text>> & aLeafTextNodes) const2179 void HTMLEditor::CollectEditableLeafTextNodes(
2180     Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const {
2181   for (nsIContent* child = aElement.GetFirstChild(); child;
2182        child = child->GetNextSibling()) {
2183     if (child->IsElement()) {
2184       CollectEditableLeafTextNodes(*child->AsElement(), aLeafTextNodes);
2185       continue;
2186     }
2187     if (child->IsText()) {
2188       aLeafTextNodes.AppendElement(*child->AsText());
2189     }
2190   }
2191 }
2192 
IncreaseFontSizeAsAction(nsIPrincipal * aPrincipal)2193 nsresult HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
2194   AutoEditActionDataSetter editActionData(*this, EditAction::eIncrementFontSize,
2195                                           aPrincipal);
2196   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2197   if (NS_FAILED(rv)) {
2198     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2199                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2200     return EditorBase::ToGenericNSResult(rv);
2201   }
2202 
2203   rv = RelativeFontChange(FontSize::incr);
2204   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2205                        "HTMLEditor::RelativeFontChange(FontSize::incr) failed");
2206   return EditorBase::ToGenericNSResult(rv);
2207 }
2208 
DecreaseFontSizeAsAction(nsIPrincipal * aPrincipal)2209 nsresult HTMLEditor::DecreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
2210   AutoEditActionDataSetter editActionData(*this, EditAction::eDecrementFontSize,
2211                                           aPrincipal);
2212   nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2213   if (NS_FAILED(rv)) {
2214     NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2215                          "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2216     return EditorBase::ToGenericNSResult(rv);
2217   }
2218 
2219   rv = RelativeFontChange(FontSize::decr);
2220   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2221                        "HTMLEditor::RelativeFontChange(FontSize::decr) failed");
2222   return EditorBase::ToGenericNSResult(rv);
2223 }
2224 
RelativeFontChange(FontSize aDir)2225 nsresult HTMLEditor::RelativeFontChange(FontSize aDir) {
2226   MOZ_ASSERT(IsEditActionDataAvailable());
2227 
2228   DebugOnly<nsresult> rvIgnored = CommitComposition();
2229   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2230                        "EditorBase::CommitComposition() failed, but ignored");
2231 
2232   // If selection is collapsed, set typing state
2233   if (SelectionRef().IsCollapsed()) {
2234     nsAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big : *nsGkAtoms::small;
2235 
2236     // Let's see in what kind of element the selection is
2237     if (NS_WARN_IF(!SelectionRef().RangeCount())) {
2238       return NS_OK;
2239     }
2240     RefPtr<const nsRange> firstRange = SelectionRef().GetRangeAt(0);
2241     if (NS_WARN_IF(!firstRange) ||
2242         NS_WARN_IF(!firstRange->GetStartContainer())) {
2243       return NS_OK;
2244     }
2245     OwningNonNull<nsINode> selectedNode = *firstRange->GetStartContainer();
2246     if (selectedNode->IsText()) {
2247       if (NS_WARN_IF(!selectedNode->GetParentNode())) {
2248         return NS_OK;
2249       }
2250       selectedNode = *selectedNode->GetParentNode();
2251     }
2252     if (!HTMLEditUtils::CanNodeContain(selectedNode, atom)) {
2253       return NS_OK;
2254     }
2255 
2256     // Manipulating text attributes on a collapsed selection only sets state
2257     // for the next text insertion
2258     mTypeInState->SetProp(&atom, nullptr, u""_ns);
2259     return NS_OK;
2260   }
2261 
2262   // Wrap with txn batching, rules sniffing, and selection preservation code
2263   AutoPlaceholderBatch treatAsOneTransaction(
2264       *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2265   IgnoredErrorResult ignoredError;
2266   AutoEditSubActionNotifier startToHandleEditSubAction(
2267       *this, EditSubAction::eSetTextProperty, nsIEditor::eNext, ignoredError);
2268   if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2269     return ignoredError.StealNSResult();
2270   }
2271   NS_WARNING_ASSERTION(
2272       !ignoredError.Failed(),
2273       "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2274 
2275   AutoSelectionRestorer restoreSelectionLater(*this);
2276   AutoTransactionsConserveSelection dontChangeMySelection(*this);
2277 
2278   // Loop through the ranges in the selection
2279   AutoSelectionRangeArray arrayOfRanges(SelectionRef());
2280   for (auto& range : arrayOfRanges.mRanges) {
2281     // Adjust range to include any ancestors with entirely selected children
2282     nsresult rv = PromoteInlineRange(*range);
2283     if (NS_FAILED(rv)) {
2284       NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
2285       return rv;
2286     }
2287 
2288     // Check for easy case: both range endpoints in same text node
2289     nsCOMPtr<nsINode> startNode = range->GetStartContainer();
2290     nsCOMPtr<nsINode> endNode = range->GetEndContainer();
2291     MOZ_ASSERT(startNode);
2292     MOZ_ASSERT(endNode);
2293     if (startNode == endNode && startNode->IsText()) {
2294       nsresult rv = RelativeFontChangeOnTextNode(
2295           aDir, MOZ_KnownLive(*startNode->GetAsText()), range->StartOffset(),
2296           range->EndOffset());
2297       if (NS_FAILED(rv)) {
2298         NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2299         return rv;
2300       }
2301     } else {
2302       // Not the easy case.  Range not contained in single text node.  There
2303       // are up to three phases here.  There are all the nodes reported by the
2304       // subtree iterator to be processed.  And there are potentially a
2305       // starting textnode and an ending textnode which are only partially
2306       // contained by the range.
2307 
2308       // Let's handle the nodes reported by the iterator.  These nodes are
2309       // entirely contained in the selection range.  We build up a list of them
2310       // (since doing operations on the document during iteration would perturb
2311       // the iterator).
2312 
2313       // Iterate range and build up array
2314       ContentSubtreeIterator subtreeIter;
2315       if (NS_SUCCEEDED(subtreeIter.Init(range))) {
2316         nsTArray<OwningNonNull<nsIContent>> arrayOfContents;
2317         for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
2318           if (NS_WARN_IF(!subtreeIter.GetCurrentNode()->IsContent())) {
2319             return NS_ERROR_FAILURE;
2320           }
2321           OwningNonNull<nsIContent> content =
2322               *subtreeIter.GetCurrentNode()->AsContent();
2323 
2324           if (EditorUtils::IsEditableContent(content, EditorType::HTML)) {
2325             arrayOfContents.AppendElement(content);
2326           }
2327         }
2328 
2329         // Now that we have the list, do the font size change on each node
2330         for (OwningNonNull<nsIContent>& content : arrayOfContents) {
2331           // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it
2332           // alive.
2333           nsresult rv = RelativeFontChangeOnNode(
2334               aDir == FontSize::incr ? +1 : -1, MOZ_KnownLive(content));
2335           if (NS_FAILED(rv)) {
2336             NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2337             return rv;
2338           }
2339         }
2340       }
2341       // Now check the start and end parents of the range to see if they need
2342       // to be separately handled (they do if they are text nodes, due to how
2343       // the subtree iterator works - it will not have reported them).
2344       if (startNode->IsText() && EditorUtils::IsEditableContent(
2345                                      *startNode->AsText(), EditorType::HTML)) {
2346         nsresult rv = RelativeFontChangeOnTextNode(
2347             aDir, MOZ_KnownLive(*startNode->AsText()), range->StartOffset(),
2348             startNode->Length());
2349         if (NS_FAILED(rv)) {
2350           NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2351           return rv;
2352         }
2353       }
2354       if (endNode->IsText() && EditorUtils::IsEditableContent(
2355                                    *endNode->AsText(), EditorType::HTML)) {
2356         nsresult rv = RelativeFontChangeOnTextNode(
2357             aDir, MOZ_KnownLive(*endNode->AsText()), 0, range->EndOffset());
2358         if (NS_FAILED(rv)) {
2359           NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2360           return rv;
2361         }
2362       }
2363     }
2364   }
2365 
2366   return NS_OK;
2367 }
2368 
RelativeFontChangeOnTextNode(FontSize aDir,Text & aTextNode,uint32_t aStartOffset,uint32_t aEndOffset)2369 nsresult HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir,
2370                                                   Text& aTextNode,
2371                                                   uint32_t aStartOffset,
2372                                                   uint32_t aEndOffset) {
2373   // Don't need to do anything if no characters actually selected
2374   if (aStartOffset == aEndOffset) {
2375     return NS_OK;
2376   }
2377 
2378   if (!aTextNode.GetParentNode() ||
2379       !HTMLEditUtils::CanNodeContain(*aTextNode.GetParentNode(),
2380                                      *nsGkAtoms::big)) {
2381     return NS_OK;
2382   }
2383 
2384   aEndOffset = std::min(aTextNode.Length(), aEndOffset);
2385 
2386   // Make the range an independent node.
2387   RefPtr<Text> textNodeForTheRange = &aTextNode;
2388 
2389   // Split at the end of the range.
2390   EditorDOMPoint atEnd(textNodeForTheRange, aEndOffset);
2391   if (!atEnd.IsEndOfContainer()) {
2392     // We need to split off back of text node
2393     SplitNodeResult splitAtEndResult = SplitNodeWithTransaction(atEnd);
2394     if (MOZ_UNLIKELY(splitAtEndResult.Failed())) {
2395       NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
2396       return splitAtEndResult.Rv();
2397     }
2398     textNodeForTheRange =
2399         Text::FromNodeOrNull(splitAtEndResult.GetPreviousContent());
2400     MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange);
2401   }
2402 
2403   // Split at the start of the range.
2404   EditorDOMPoint atStart(textNodeForTheRange, aStartOffset);
2405   if (!atStart.IsStartOfContainer()) {
2406     // We need to split off front of text node
2407     SplitNodeResult splitAtStartResult = SplitNodeWithTransaction(atStart);
2408     if (MOZ_UNLIKELY(splitAtStartResult.Failed())) {
2409       NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
2410       return splitAtStartResult.Rv();
2411     }
2412   }
2413 
2414   // Look for siblings that are correct type of node
2415   nsAtom* nodeType = aDir == FontSize::incr ? nsGkAtoms::big : nsGkAtoms::small;
2416   nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
2417       *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
2418   if (sibling && sibling->IsHTMLElement(nodeType)) {
2419     // Previous sib is already right kind of inline node; slide this over
2420     nsresult rv = MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
2421     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2422                          "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2423     return rv;
2424   }
2425   sibling = HTMLEditUtils::GetNextSibling(
2426       *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
2427   if (sibling && sibling->IsHTMLElement(nodeType)) {
2428     // Following sib is already right kind of inline node; slide this over
2429     nsresult rv = MoveNodeWithTransaction(*textNodeForTheRange,
2430                                           EditorDOMPoint(sibling, 0));
2431     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2432                          "HTMLEditor::MoveNodeWithTransaction() failed");
2433     return rv;
2434   }
2435 
2436   // Else reparent the node inside font node with appropriate relative size
2437   RefPtr<Element> newElement = InsertContainerWithTransaction(
2438       *textNodeForTheRange, MOZ_KnownLive(*nodeType));
2439   NS_WARNING_ASSERTION(newElement,
2440                        "HTMLEditor::InsertContainerWithTransaction() failed");
2441   return newElement ? NS_OK : NS_ERROR_FAILURE;
2442 }
2443 
RelativeFontChangeHelper(int32_t aSizeChange,nsINode * aNode)2444 nsresult HTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange,
2445                                               nsINode* aNode) {
2446   MOZ_ASSERT(aNode);
2447 
2448   /*  This routine looks for all the font nodes in the tree rooted by aNode,
2449       including aNode itself, looking for font nodes that have the size attr
2450       set.  Any such nodes need to have big or small put inside them, since
2451       they override any big/small that are above them.
2452   */
2453 
2454   // Can only change font size by + or - 1
2455   if (aSizeChange != 1 && aSizeChange != -1) {
2456     return NS_ERROR_ILLEGAL_VALUE;
2457   }
2458 
2459   // If this is a font node with size, put big/small inside it.
2460   if (aNode->IsHTMLElement(nsGkAtoms::font) &&
2461       aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
2462     // Cycle through children and adjust relative font size.
2463     AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
2464     for (nsIContent* child = aNode->GetFirstChild(); child;
2465          child = child->GetNextSibling()) {
2466       childList.AppendElement(child);
2467     }
2468 
2469     for (const auto& child : childList) {
2470       // MOZ_KnownLive because 'childList' is guaranteed to
2471       // keep it alive.
2472       nsresult rv = RelativeFontChangeOnNode(aSizeChange, MOZ_KnownLive(child));
2473       if (NS_FAILED(rv)) {
2474         NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2475         return rv;
2476       }
2477     }
2478 
2479     // RelativeFontChangeOnNode already calls us recursively,
2480     // so we don't need to check our children again.
2481     return NS_OK;
2482   }
2483 
2484   // Otherwise cycle through the children.
2485   AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
2486   for (nsIContent* child = aNode->GetFirstChild(); child;
2487        child = child->GetNextSibling()) {
2488     childList.AppendElement(child);
2489   }
2490 
2491   for (const auto& child : childList) {
2492     // MOZ_KnownLive because 'childList' is guaranteed to
2493     // keep it alive.
2494     nsresult rv = RelativeFontChangeHelper(aSizeChange, MOZ_KnownLive(child));
2495     if (NS_FAILED(rv)) {
2496       NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2497       return rv;
2498     }
2499   }
2500 
2501   return NS_OK;
2502 }
2503 
RelativeFontChangeOnNode(int32_t aSizeChange,nsIContent * aNode)2504 nsresult HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange,
2505                                               nsIContent* aNode) {
2506   MOZ_ASSERT(aNode);
2507   // Can only change font size by + or - 1
2508   if (aSizeChange != 1 && aSizeChange != -1) {
2509     return NS_ERROR_ILLEGAL_VALUE;
2510   }
2511 
2512   nsAtom* atom;
2513   if (aSizeChange == 1) {
2514     atom = nsGkAtoms::big;
2515   } else {
2516     atom = nsGkAtoms::small;
2517   }
2518 
2519   // Is it the opposite of what we want?
2520   if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
2521       (aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
2522     // first populate any nested font tags that have the size attr set
2523     nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
2524     if (NS_FAILED(rv)) {
2525       NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2526       return rv;
2527     }
2528     // in that case, just remove this node and pull up the children
2529     rv = RemoveContainerWithTransaction(MOZ_KnownLive(*aNode->AsElement()));
2530     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2531                          "HTMLEditor::RemoveContainerWithTransaction() failed");
2532     return rv;
2533   }
2534 
2535   // can it be put inside a "big" or "small"?
2536   if (HTMLEditUtils::CanNodeContain(*atom, *aNode)) {
2537     // first populate any nested font tags that have the size attr set
2538     nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
2539     if (NS_FAILED(rv)) {
2540       NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2541       return rv;
2542     }
2543 
2544     // ok, chuck it in.
2545     // first look at siblings of aNode for matching bigs or smalls.
2546     // if we find one, move aNode into it.
2547     nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
2548         *aNode, {WalkTreeOption::IgnoreNonEditableNode});
2549     if (sibling && sibling->IsHTMLElement(atom)) {
2550       // previous sib is already right kind of inline node; slide this over into
2551       // it
2552       nsresult rv = MoveNodeToEndWithTransaction(*aNode, *sibling);
2553       NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2554                            "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2555       return rv;
2556     }
2557 
2558     sibling = HTMLEditUtils::GetNextSibling(
2559         *aNode, {WalkTreeOption::IgnoreNonEditableNode});
2560     if (sibling && sibling->IsHTMLElement(atom)) {
2561       // following sib is already right kind of inline node; slide this over
2562       // into it
2563       return MoveNodeWithTransaction(*aNode, EditorDOMPoint(sibling, 0));
2564     }
2565 
2566     // else insert it above aNode
2567     RefPtr<Element> newElement =
2568         InsertContainerWithTransaction(*aNode, MOZ_KnownLive(*atom));
2569     NS_WARNING_ASSERTION(newElement,
2570                          "HTMLEditor::InsertContainerWithTransaction() failed");
2571     return newElement ? NS_OK : NS_ERROR_FAILURE;
2572   }
2573 
2574   // none of the above?  then cycle through the children.
2575   // MOOSE: we should group the children together if possible
2576   // into a single "big" or "small".  For the moment they are
2577   // each getting their own.
2578   AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
2579   for (nsIContent* child = aNode->GetFirstChild(); child;
2580        child = child->GetNextSibling()) {
2581     childList.AppendElement(child);
2582   }
2583 
2584   for (const auto& child : childList) {
2585     // MOZ_KnownLive because 'childList' is guaranteed to
2586     // keep it alive.
2587     nsresult rv = RelativeFontChangeOnNode(aSizeChange, MOZ_KnownLive(child));
2588     if (NS_FAILED(rv)) {
2589       NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2590       return rv;
2591     }
2592   }
2593 
2594   return NS_OK;
2595 }
2596 
GetFontFaceState(bool * aMixed,nsAString & outFace)2597 NS_IMETHODIMP HTMLEditor::GetFontFaceState(bool* aMixed, nsAString& outFace) {
2598   if (NS_WARN_IF(!aMixed)) {
2599     return NS_ERROR_INVALID_ARG;
2600   }
2601 
2602   *aMixed = true;
2603   outFace.Truncate();
2604 
2605   AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2606   if (NS_WARN_IF(!editActionData.CanHandle())) {
2607     return NS_ERROR_NOT_INITIALIZED;
2608   }
2609 
2610   bool first, any, all;
2611 
2612   nsresult rv = GetInlinePropertyBase(*nsGkAtoms::font, nsGkAtoms::face,
2613                                       nullptr, &first, &any, &all, &outFace);
2614   if (NS_FAILED(rv)) {
2615     NS_WARNING(
2616         "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
2617         "failed");
2618     return EditorBase::ToGenericNSResult(rv);
2619   }
2620   if (any && !all) {
2621     return NS_OK;  // mixed
2622   }
2623   if (all) {
2624     *aMixed = false;
2625     return NS_OK;
2626   }
2627 
2628   // if there is no font face, check for tt
2629   rv = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
2630                              &all, nullptr);
2631   if (NS_FAILED(rv)) {
2632     NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed");
2633     return EditorBase::ToGenericNSResult(rv);
2634   }
2635   if (any && !all) {
2636     return NS_OK;  // mixed
2637   }
2638   if (all) {
2639     *aMixed = false;
2640     outFace.AssignLiteral("tt");
2641   }
2642 
2643   if (!any) {
2644     // there was no font face attrs of any kind.  We are in normal font.
2645     outFace.Truncate();
2646     *aMixed = false;
2647   }
2648   return NS_OK;
2649 }
2650 
GetFontColorState(bool * aMixed,nsAString & aOutColor)2651 nsresult HTMLEditor::GetFontColorState(bool* aMixed, nsAString& aOutColor) {
2652   if (NS_WARN_IF(!aMixed)) {
2653     return NS_ERROR_INVALID_ARG;
2654   }
2655 
2656   *aMixed = true;
2657   aOutColor.Truncate();
2658 
2659   AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2660   if (NS_WARN_IF(!editActionData.CanHandle())) {
2661     return NS_ERROR_NOT_INITIALIZED;
2662   }
2663 
2664   bool first, any, all;
2665   nsresult rv = GetInlinePropertyBase(*nsGkAtoms::font, nsGkAtoms::color,
2666                                       nullptr, &first, &any, &all, &aOutColor);
2667   if (NS_FAILED(rv)) {
2668     NS_WARNING(
2669         "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
2670         "failed");
2671     return EditorBase::ToGenericNSResult(rv);
2672   }
2673 
2674   if (any && !all) {
2675     return NS_OK;  // mixed
2676   }
2677   if (all) {
2678     *aMixed = false;
2679     return NS_OK;
2680   }
2681 
2682   if (!any) {
2683     // there was no font color attrs of any kind..
2684     aOutColor.Truncate();
2685     *aMixed = false;
2686   }
2687   return NS_OK;
2688 }
2689 
2690 // the return value is true only if the instance of the HTML editor we created
2691 // can handle CSS styles (for instance, Composer can, Messenger can't) and if
2692 // the CSS preference is checked
GetIsCSSEnabled(bool * aIsCSSEnabled)2693 NS_IMETHODIMP HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled) {
2694   *aIsCSSEnabled = IsCSSEnabled();
2695   return NS_OK;
2696 }
2697 
HasStyleOrIdOrClassAttribute(Element & aElement)2698 bool HTMLEditor::HasStyleOrIdOrClassAttribute(Element& aElement) {
2699   return aElement.HasNonEmptyAttr(nsGkAtoms::style) ||
2700          aElement.HasNonEmptyAttr(nsGkAtoms::_class) ||
2701          aElement.HasNonEmptyAttr(nsGkAtoms::id);
2702 }
2703 
2704 }  // namespace mozilla
2705