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