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 "mozilla/HTMLEditor.h"
7 
8 #include "HTMLEditUtils.h"
9 #include "TextEditUtils.h"
10 #include "TypeInState.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/EditorUtils.h"
13 #include "mozilla/SelectionState.h"
14 #include "mozilla/dom/Selection.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/mozalloc.h"
17 #include "nsAString.h"
18 #include "nsAttrName.h"
19 #include "nsCOMPtr.h"
20 #include "nsCaseTreatment.h"
21 #include "nsComponentManagerUtils.h"
22 #include "nsDebug.h"
23 #include "nsError.h"
24 #include "nsGkAtoms.h"
25 #include "nsIAtom.h"
26 #include "nsIContent.h"
27 #include "nsIContentIterator.h"
28 #include "nsIDOMElement.h"
29 #include "nsIEditor.h"
30 #include "nsIEditorIMESupport.h"
31 #include "nsIEditRules.h"
32 #include "nsNameSpaceManager.h"
33 #include "nsINode.h"
34 #include "nsISupportsImpl.h"
35 #include "nsLiteralString.h"
36 #include "nsRange.h"
37 #include "nsReadableUtils.h"
38 #include "nsString.h"
39 #include "nsStringFwd.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 static bool
IsEmptyTextNode(HTMLEditor * aThis,nsINode * aNode)51 IsEmptyTextNode(HTMLEditor* aThis, nsINode* aNode)
52 {
53   bool isEmptyTextNode = false;
54   return EditorBase::IsTextNode(aNode) &&
55          NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) &&
56          isEmptyTextNode;
57 }
58 
59 NS_IMETHODIMP
AddDefaultProperty(nsIAtom * aProperty,const nsAString & aAttribute,const nsAString & aValue)60 HTMLEditor::AddDefaultProperty(nsIAtom* aProperty,
61                                const nsAString& aAttribute,
62                                const nsAString& aValue)
63 {
64   nsString outValue;
65   int32_t index;
66   nsString attr(aAttribute);
67   if (TypeInState::FindPropInList(aProperty, attr, &outValue,
68                                   mDefaultStyles, index)) {
69     PropItem *item = mDefaultStyles[index];
70     item->value = aValue;
71   } else {
72     nsString value(aValue);
73     PropItem *propItem = new PropItem(aProperty, attr, value);
74     mDefaultStyles.AppendElement(propItem);
75   }
76   return NS_OK;
77 }
78 
79 NS_IMETHODIMP
RemoveDefaultProperty(nsIAtom * aProperty,const nsAString & aAttribute,const nsAString & aValue)80 HTMLEditor::RemoveDefaultProperty(nsIAtom* aProperty,
81                                   const nsAString& aAttribute,
82                                   const nsAString& aValue)
83 {
84   nsString outValue;
85   int32_t index;
86   nsString attr(aAttribute);
87   if (TypeInState::FindPropInList(aProperty, attr, &outValue,
88                                   mDefaultStyles, index)) {
89     delete mDefaultStyles[index];
90     mDefaultStyles.RemoveElementAt(index);
91   }
92   return NS_OK;
93 }
94 
95 NS_IMETHODIMP
RemoveAllDefaultProperties()96 HTMLEditor::RemoveAllDefaultProperties()
97 {
98   size_t defcon = mDefaultStyles.Length();
99   for (size_t j = 0; j < defcon; j++) {
100     delete mDefaultStyles[j];
101   }
102   mDefaultStyles.Clear();
103   return NS_OK;
104 }
105 
106 
107 NS_IMETHODIMP
SetInlineProperty(nsIAtom * aProperty,const nsAString & aAttribute,const nsAString & aValue)108 HTMLEditor::SetInlineProperty(nsIAtom* aProperty,
109                               const nsAString& aAttribute,
110                               const nsAString& aValue)
111 {
112   NS_ENSURE_TRUE(aProperty, NS_ERROR_NULL_POINTER);
113   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
114   nsCOMPtr<nsIEditRules> rules(mRules);
115   ForceCompositionEnd();
116 
117   RefPtr<Selection> selection = GetSelection();
118   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
119 
120   if (selection->Collapsed()) {
121     // Manipulating text attributes on a collapsed selection only sets state
122     // for the next text insertion
123     mTypeInState->SetProp(aProperty, aAttribute, aValue);
124     return NS_OK;
125   }
126 
127   AutoEditBatch batchIt(this);
128   AutoRules beginRulesSniffing(this, EditAction::insertElement,
129                                nsIEditor::eNext);
130   AutoSelectionRestorer selectionRestorer(selection, this);
131   AutoTransactionsConserveSelection dontSpazMySelection(this);
132 
133   bool cancel, handled;
134   TextRulesInfo ruleInfo(EditAction::setTextProperty);
135   // Protect the edit rules object from dying
136   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
137   NS_ENSURE_SUCCESS(rv, rv);
138   if (!cancel && !handled) {
139     // Loop through the ranges in the selection
140     uint32_t rangeCount = selection->RangeCount();
141     for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
142       RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
143 
144       // Adjust range to include any ancestors whose children are entirely
145       // selected
146       rv = PromoteInlineRange(*range);
147       NS_ENSURE_SUCCESS(rv, rv);
148 
149       // Check for easy case: both range endpoints in same text node
150       nsCOMPtr<nsINode> startNode = range->GetStartParent();
151       nsCOMPtr<nsINode> endNode = range->GetEndParent();
152       if (startNode && startNode == endNode && startNode->GetAsText()) {
153         rv = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
154                                          range->StartOffset(),
155                                          range->EndOffset(),
156                                          *aProperty, &aAttribute, aValue);
157         NS_ENSURE_SUCCESS(rv, rv);
158         continue;
159       }
160 
161       // Not the easy case.  Range not contained in single text node.  There
162       // are up to three phases here.  There are all the nodes reported by the
163       // subtree iterator to be processed.  And there are potentially a
164       // starting textnode and an ending textnode which are only partially
165       // contained by the range.
166 
167       // Let's handle the nodes reported by the iterator.  These nodes are
168       // entirely contained in the selection range.  We build up a list of them
169       // (since doing operations on the document during iteration would perturb
170       // the iterator).
171 
172       OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
173 
174       nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
175 
176       // Iterate range and build up array
177       rv = iter->Init(range);
178       // Init returns an error if there are no nodes in range.  This can easily
179       // happen with the subtree iterator if the selection doesn't contain any
180       // *whole* nodes.
181       if (NS_SUCCEEDED(rv)) {
182         for (; !iter->IsDone(); iter->Next()) {
183           OwningNonNull<nsINode> node = *iter->GetCurrentNode();
184 
185           if (node->IsContent() && IsEditable(node)) {
186             arrayOfNodes.AppendElement(*node->AsContent());
187           }
188         }
189       }
190       // First check the start parent of the range to see if it needs to be
191       // separately handled (it does if it's a text node, due to how the
192       // subtree iterator works - it will not have reported it).
193       if (startNode && startNode->GetAsText() && IsEditable(startNode)) {
194         rv = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
195                                          range->StartOffset(),
196                                          startNode->Length(), *aProperty,
197                                          &aAttribute, aValue);
198         NS_ENSURE_SUCCESS(rv, rv);
199       }
200 
201       // Then loop through the list, set the property on each node
202       for (auto& node : arrayOfNodes) {
203         rv = SetInlinePropertyOnNode(*node, *aProperty, &aAttribute, aValue);
204         NS_ENSURE_SUCCESS(rv, rv);
205       }
206 
207       // Last check the end parent of the range to see if it needs to be
208       // separately handled (it does if it's a text node, due to how the
209       // subtree iterator works - it will not have reported it).
210       if (endNode && endNode->GetAsText() && IsEditable(endNode)) {
211         rv = SetInlinePropertyOnTextNode(*endNode->GetAsText(), 0,
212                                           range->EndOffset(), *aProperty,
213                                           &aAttribute, aValue);
214         NS_ENSURE_SUCCESS(rv, rv);
215       }
216     }
217   }
218   if (!cancel) {
219     // Post-process
220     return rules->DidDoAction(selection, &ruleInfo, rv);
221   }
222   return NS_OK;
223 }
224 
225 // Helper function for SetInlinePropertyOn*: is aNode a simple old <b>, <font>,
226 // <span style="">, etc. that we can reuse instead of creating a new one?
227 bool
IsSimpleModifiableNode(nsIContent * aContent,nsIAtom * aProperty,const nsAString * aAttribute,const nsAString * aValue)228 HTMLEditor::IsSimpleModifiableNode(nsIContent* aContent,
229                                    nsIAtom* aProperty,
230                                    const nsAString* aAttribute,
231                                    const nsAString* aValue)
232 {
233   // aContent can be null, in which case we'll return false in a few lines
234   MOZ_ASSERT(aProperty);
235   MOZ_ASSERT_IF(aAttribute, aValue);
236 
237   nsCOMPtr<dom::Element> element = do_QueryInterface(aContent);
238   if (!element) {
239     return false;
240   }
241 
242   // First check for <b>, <i>, etc.
243   if (element->IsHTMLElement(aProperty) && !element->GetAttrCount() &&
244       (!aAttribute || aAttribute->IsEmpty())) {
245     return true;
246   }
247 
248   // Special cases for various equivalencies: <strong>, <em>, <s>
249   if (!element->GetAttrCount() &&
250       ((aProperty == nsGkAtoms::b &&
251         element->IsHTMLElement(nsGkAtoms::strong)) ||
252        (aProperty == nsGkAtoms::i &&
253         element->IsHTMLElement(nsGkAtoms::em)) ||
254        (aProperty == nsGkAtoms::strike &&
255         element->IsHTMLElement(nsGkAtoms::s)))) {
256     return true;
257   }
258 
259   // Now look for things like <font>
260   if (aAttribute && !aAttribute->IsEmpty()) {
261     nsCOMPtr<nsIAtom> atom = NS_Atomize(*aAttribute);
262     MOZ_ASSERT(atom);
263 
264     nsString attrValue;
265     if (element->IsHTMLElement(aProperty) &&
266         IsOnlyAttribute(element, *aAttribute) &&
267         element->GetAttr(kNameSpaceID_None, atom, attrValue) &&
268         attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) {
269       // This is not quite correct, because it excludes cases like
270       // <font face=000> being the same as <font face=#000000>.
271       // Property-specific handling is needed (bug 760211).
272       return true;
273     }
274   }
275 
276   // No luck so far.  Now we check for a <span> with a single style=""
277   // attribute that sets only the style we're looking for, if this type of
278   // style supports it
279   if (!mCSSEditUtils->IsCSSEditableProperty(element, aProperty, aAttribute) ||
280       !element->IsHTMLElement(nsGkAtoms::span) ||
281       element->GetAttrCount() != 1 ||
282       !element->HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
283     return false;
284   }
285 
286   // Some CSS styles are not so simple.  For instance, underline is
287   // "text-decoration: underline", which decomposes into four different text-*
288   // properties.  So for now, we just create a span, add the desired style, and
289   // see if it matches.
290   nsCOMPtr<Element> newSpan = CreateHTMLContent(nsGkAtoms::span);
291   NS_ASSERTION(newSpan, "CreateHTMLContent failed");
292   NS_ENSURE_TRUE(newSpan, false);
293   mCSSEditUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty,
294                                              aAttribute, aValue,
295                                              /*suppress transaction*/ true);
296 
297   return mCSSEditUtils->ElementsSameStyle(newSpan, element);
298 }
299 
300 nsresult
SetInlinePropertyOnTextNode(Text & aText,int32_t aStartOffset,int32_t aEndOffset,nsIAtom & aProperty,const nsAString * aAttribute,const nsAString & aValue)301 HTMLEditor::SetInlinePropertyOnTextNode(Text& aText,
302                                         int32_t aStartOffset,
303                                         int32_t aEndOffset,
304                                         nsIAtom& aProperty,
305                                         const nsAString* aAttribute,
306                                         const nsAString& aValue)
307 {
308   if (!aText.GetParentNode() ||
309       !CanContainTag(*aText.GetParentNode(), aProperty)) {
310     return NS_OK;
311   }
312 
313   // Don't need to do anything if no characters actually selected
314   if (aStartOffset == aEndOffset) {
315     return NS_OK;
316   }
317 
318   // Don't need to do anything if property already set on node
319   if (mCSSEditUtils->IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
320     // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
321     // for node; let's check if it carries those CSS styles
322     if (mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aText, &aProperty,
323           aAttribute, aValue, CSSEditUtils::eComputed)) {
324       return NS_OK;
325     }
326   } else if (IsTextPropertySetByContent(&aText, &aProperty, aAttribute,
327                                         &aValue)) {
328     return NS_OK;
329   }
330 
331   // Do we need to split the text node?
332   ErrorResult rv;
333   RefPtr<Text> text = &aText;
334   if (uint32_t(aEndOffset) != aText.Length()) {
335     // We need to split off back of text node
336     text = SplitNode(aText, aEndOffset, rv)->GetAsText();
337     NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
338   }
339 
340   if (aStartOffset) {
341     // We need to split off front of text node
342     SplitNode(*text, aStartOffset, rv);
343     NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
344   }
345 
346   if (aAttribute) {
347     // Look for siblings that are correct type of node
348     nsIContent* sibling = GetPriorHTMLSibling(text);
349     if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
350       // Previous sib is already right kind of inline node; slide this over
351       return MoveNode(text, sibling, -1);
352     }
353     sibling = GetNextHTMLSibling(text);
354     if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
355       // Following sib is already right kind of inline node; slide this over
356       return MoveNode(text, sibling, 0);
357     }
358   }
359 
360   // Reparent the node inside inline node with appropriate {attribute,value}
361   return SetInlinePropertyOnNode(*text, aProperty, aAttribute, aValue);
362 }
363 
364 nsresult
SetInlinePropertyOnNodeImpl(nsIContent & aNode,nsIAtom & aProperty,const nsAString * aAttribute,const nsAString & aValue)365 HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aNode,
366                                         nsIAtom& aProperty,
367                                         const nsAString* aAttribute,
368                                         const nsAString& aValue)
369 {
370   nsCOMPtr<nsIAtom> attrAtom = aAttribute ? NS_Atomize(*aAttribute) : nullptr;
371 
372   // If this is an element that can't be contained in a span, we have to
373   // recurse to its children.
374   if (!TagCanContain(*nsGkAtoms::span, aNode)) {
375     if (aNode.HasChildren()) {
376       nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
377 
378       // Populate the list.
379       for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
380            child;
381            child = child->GetNextSibling()) {
382         if (IsEditable(child) && !IsEmptyTextNode(this, child)) {
383           arrayOfNodes.AppendElement(*child);
384         }
385       }
386 
387       // Then loop through the list, set the property on each node.
388       for (auto& node : arrayOfNodes) {
389         nsresult rv = SetInlinePropertyOnNode(node, aProperty, aAttribute,
390                                               aValue);
391         NS_ENSURE_SUCCESS(rv, rv);
392       }
393     }
394     return NS_OK;
395   }
396 
397   // First check if there's an adjacent sibling we can put our node into.
398   nsCOMPtr<nsIContent> previousSibling = GetPriorHTMLSibling(&aNode);
399   nsCOMPtr<nsIContent> nextSibling = GetNextHTMLSibling(&aNode);
400   if (IsSimpleModifiableNode(previousSibling, &aProperty, aAttribute, &aValue)) {
401     nsresult rv = MoveNode(&aNode, previousSibling, -1);
402     NS_ENSURE_SUCCESS(rv, rv);
403     if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
404       rv = JoinNodes(*previousSibling, *nextSibling);
405       NS_ENSURE_SUCCESS(rv, rv);
406     }
407     return NS_OK;
408   }
409   if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
410     nsresult rv = MoveNode(&aNode, nextSibling, 0);
411     NS_ENSURE_SUCCESS(rv, rv);
412     return NS_OK;
413   }
414 
415   // Don't need to do anything if property already set on node
416   if (mCSSEditUtils->IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) {
417     if (mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(
418           &aNode, &aProperty, aAttribute, aValue, CSSEditUtils::eComputed)) {
419       return NS_OK;
420     }
421   } else if (IsTextPropertySetByContent(&aNode, &aProperty,
422                                         aAttribute, &aValue)) {
423     return NS_OK;
424   }
425 
426   bool useCSS = (IsCSSEnabled() &&
427                  mCSSEditUtils->IsCSSEditableProperty(&aNode, &aProperty,
428                                                       aAttribute)) ||
429                 // bgcolor is always done using CSS
430                 aAttribute->EqualsLiteral("bgcolor");
431 
432   if (useCSS) {
433     nsCOMPtr<dom::Element> tmp;
434     // We only add style="" to <span>s with no attributes (bug 746515).  If we
435     // don't have one, we need to make one.
436     if (aNode.IsHTMLElement(nsGkAtoms::span) &&
437         !aNode.AsElement()->GetAttrCount()) {
438       tmp = aNode.AsElement();
439     } else {
440       tmp = InsertContainerAbove(&aNode, nsGkAtoms::span);
441       NS_ENSURE_STATE(tmp);
442     }
443 
444     // Add the CSS styles corresponding to the HTML style request
445     int32_t count;
446     nsresult rv =
447       mCSSEditUtils->SetCSSEquivalentToHTMLStyle(tmp->AsDOMNode(),
448                                                  &aProperty, aAttribute,
449                                                  &aValue, &count, false);
450     NS_ENSURE_SUCCESS(rv, rv);
451     return NS_OK;
452   }
453 
454   // is it already the right kind of node, but with wrong attribute?
455   if (aNode.IsHTMLElement(&aProperty)) {
456     // Just set the attribute on it.
457     nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
458     return SetAttribute(elem, *aAttribute, aValue);
459   }
460 
461   // ok, chuck it in its very own container
462   nsCOMPtr<Element> tmp = InsertContainerAbove(&aNode, &aProperty, attrAtom,
463                                                &aValue);
464   NS_ENSURE_STATE(tmp);
465 
466   return NS_OK;
467 }
468 
469 nsresult
SetInlinePropertyOnNode(nsIContent & aNode,nsIAtom & aProperty,const nsAString * aAttribute,const nsAString & aValue)470 HTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
471                                     nsIAtom& aProperty,
472                                     const nsAString* aAttribute,
473                                     const nsAString& aValue)
474 {
475   nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
476                        nextSibling = aNode.GetNextSibling();
477   NS_ENSURE_STATE(aNode.GetParentNode());
478   OwningNonNull<nsINode> parent = *aNode.GetParentNode();
479 
480   nsresult rv = RemoveStyleInside(aNode, &aProperty, aAttribute);
481   NS_ENSURE_SUCCESS(rv, rv);
482 
483   if (aNode.GetParentNode()) {
484     // The node is still where it was
485     return SetInlinePropertyOnNodeImpl(aNode, aProperty,
486                                        aAttribute, aValue);
487   }
488 
489   // It's vanished.  Use the old siblings for reference to construct a
490   // list.  But first, verify that the previous/next siblings are still
491   // where we expect them; otherwise we have to give up.
492   if ((previousSibling && previousSibling->GetParentNode() != parent) ||
493       (nextSibling && nextSibling->GetParentNode() != parent)) {
494     return NS_ERROR_UNEXPECTED;
495   }
496   nsTArray<OwningNonNull<nsIContent>> nodesToSet;
497   nsCOMPtr<nsIContent> cur = previousSibling
498     ? previousSibling->GetNextSibling() : parent->GetFirstChild();
499   for (; cur && cur != nextSibling; cur = cur->GetNextSibling()) {
500     if (IsEditable(cur)) {
501       nodesToSet.AppendElement(*cur);
502     }
503   }
504 
505   for (auto& node : nodesToSet) {
506     rv = SetInlinePropertyOnNodeImpl(node, aProperty, aAttribute, aValue);
507     NS_ENSURE_SUCCESS(rv, rv);
508   }
509 
510   return NS_OK;
511 }
512 
513 nsresult
SplitStyleAboveRange(nsRange * inRange,nsIAtom * aProperty,const nsAString * aAttribute)514 HTMLEditor::SplitStyleAboveRange(nsRange* inRange,
515                                  nsIAtom* aProperty,
516                                  const nsAString* aAttribute)
517 {
518   NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
519 
520   nsCOMPtr<nsINode> startNode = inRange->GetStartParent();
521   int32_t startOffset = inRange->StartOffset();
522   nsCOMPtr<nsINode> endNode = inRange->GetEndParent();
523   int32_t endOffset = inRange->EndOffset();
524 
525   nsCOMPtr<nsINode> origStartNode = startNode;
526 
527   // split any matching style nodes above the start of range
528   {
529     AutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset);
530     nsresult rv =
531       SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty,
532                            aAttribute);
533     NS_ENSURE_SUCCESS(rv, rv);
534   }
535 
536   // second verse, same as the first...
537   nsresult rv =
538     SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty,
539                          aAttribute);
540   NS_ENSURE_SUCCESS(rv, rv);
541 
542   // reset the range
543   rv = inRange->SetStart(startNode, startOffset);
544   NS_ENSURE_SUCCESS(rv, rv);
545   return inRange->SetEnd(endNode, endOffset);
546 }
547 
548 nsresult
SplitStyleAbovePoint(nsCOMPtr<nsINode> * aNode,int32_t * aOffset,nsIAtom * aProperty,const nsAString * aAttribute,nsIContent ** aOutLeftNode,nsIContent ** aOutRightNode)549 HTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsINode>* aNode,
550                                  int32_t* aOffset,
551                                  // null here means we split all properties
552                                  nsIAtom* aProperty,
553                                  const nsAString* aAttribute,
554                                  nsIContent** aOutLeftNode,
555                                  nsIContent** aOutRightNode)
556 {
557   NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER);
558   NS_ENSURE_TRUE((*aNode)->IsContent(), NS_OK);
559 
560   // Split any matching style nodes above the node/offset
561   OwningNonNull<nsIContent> node = *(*aNode)->AsContent();
562 
563   bool useCSS = IsCSSEnabled();
564 
565   bool isSet;
566   while (!IsBlockNode(node) && node->GetParent() &&
567          IsEditable(node->GetParent())) {
568     isSet = false;
569     if (useCSS && mCSSEditUtils->IsCSSEditableProperty(node, aProperty,
570                                                        aAttribute)) {
571       // The HTML style defined by aProperty/aAttribute has a CSS equivalence
572       // in this implementation for the node; let's check if it carries those
573       // CSS styles
574       nsAutoString firstValue;
575       mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(GetAsDOMNode(node),
576         aProperty, aAttribute, isSet, firstValue, CSSEditUtils::eSpecified);
577     }
578     if (// node is the correct inline prop
579         (aProperty && node->IsHTMLElement(aProperty)) ||
580         // node is href - test if really <a href=...
581         (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(node)) ||
582         // or node is any prop, and we asked to split them all
583         (!aProperty && NodeIsProperty(node)) ||
584         // or the style is specified in the style attribute
585         isSet) {
586       // Found a style node we need to split
587       int32_t offset = SplitNodeDeep(*node, *(*aNode)->AsContent(), *aOffset,
588                                      EmptyContainers::yes, aOutLeftNode,
589                                      aOutRightNode);
590       NS_ENSURE_TRUE(offset != -1, NS_ERROR_FAILURE);
591       // reset startNode/startOffset
592       *aNode = node->GetParent();
593       *aOffset = offset;
594     }
595     node = node->GetParent();
596   }
597 
598   return NS_OK;
599 }
600 
601 nsresult
ClearStyle(nsCOMPtr<nsINode> * aNode,int32_t * aOffset,nsIAtom * aProperty,const nsAString * aAttribute)602 HTMLEditor::ClearStyle(nsCOMPtr<nsINode>* aNode,
603                        int32_t* aOffset,
604                        nsIAtom* aProperty,
605                        const nsAString* aAttribute)
606 {
607   nsCOMPtr<nsIContent> leftNode, rightNode;
608   nsresult rv = SplitStyleAbovePoint(aNode, aOffset, aProperty,
609                                      aAttribute, getter_AddRefs(leftNode),
610                                      getter_AddRefs(rightNode));
611   NS_ENSURE_SUCCESS(rv, rv);
612 
613   if (leftNode) {
614     bool bIsEmptyNode;
615     IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
616     if (bIsEmptyNode) {
617       // delete leftNode if it became empty
618       rv = DeleteNode(leftNode);
619       NS_ENSURE_SUCCESS(rv, rv);
620     }
621   }
622   if (rightNode) {
623     nsCOMPtr<nsINode> secondSplitParent = GetLeftmostChild(rightNode);
624     // don't try to split non-containers (br's, images, hr's, etc.)
625     if (!secondSplitParent) {
626       secondSplitParent = rightNode;
627     }
628     nsCOMPtr<Element> savedBR;
629     if (!IsContainer(secondSplitParent)) {
630       if (TextEditUtils::IsBreak(secondSplitParent)) {
631         savedBR = do_QueryInterface(secondSplitParent);
632         NS_ENSURE_STATE(savedBR);
633       }
634 
635       secondSplitParent = secondSplitParent->GetParentNode();
636     }
637     *aOffset = 0;
638     rv = SplitStyleAbovePoint(address_of(secondSplitParent),
639                               aOffset, aProperty, aAttribute,
640                               getter_AddRefs(leftNode),
641                               getter_AddRefs(rightNode));
642     NS_ENSURE_SUCCESS(rv, rv);
643 
644     if (rightNode) {
645       bool bIsEmptyNode;
646       IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
647       if (bIsEmptyNode) {
648         // delete rightNode if it became empty
649         rv = DeleteNode(rightNode);
650         NS_ENSURE_SUCCESS(rv, rv);
651       }
652     }
653 
654     if (!leftNode) {
655       return NS_OK;
656     }
657 
658     // should be impossible to not get a new leftnode here
659     nsCOMPtr<nsINode> newSelParent = GetLeftmostChild(leftNode);
660     if (!newSelParent) {
661       newSelParent = leftNode;
662     }
663     // If rightNode starts with a br, suck it out of right node and into
664     // leftNode.  This is so we you don't revert back to the previous style
665     // if you happen to click at the end of a line.
666     if (savedBR) {
667       rv = MoveNode(savedBR, newSelParent, 0);
668       NS_ENSURE_SUCCESS(rv, rv);
669     }
670     // remove the style on this new hierarchy
671     int32_t newSelOffset = 0;
672     {
673       // Track the point at the new hierarchy.  This is so we can know where
674       // to put the selection after we call RemoveStyleInside().
675       // RemoveStyleInside() could remove any and all of those nodes, so I
676       // have to use the range tracking system to find the right spot to put
677       // selection.
678       AutoTrackDOMPoint tracker(mRangeUpdater,
679                                 address_of(newSelParent), &newSelOffset);
680       rv = RemoveStyleInside(*leftNode, aProperty, aAttribute);
681       NS_ENSURE_SUCCESS(rv, rv);
682     }
683     // reset our node offset values to the resulting new sel point
684     *aNode = newSelParent;
685     *aOffset = newSelOffset;
686   }
687 
688   return NS_OK;
689 }
690 
691 bool
NodeIsProperty(nsINode & aNode)692 HTMLEditor::NodeIsProperty(nsINode& aNode)
693 {
694   return IsContainer(&aNode) && IsEditable(&aNode) && !IsBlockNode(&aNode) &&
695          !aNode.IsHTMLElement(nsGkAtoms::a);
696 }
697 
698 nsresult
ApplyDefaultProperties()699 HTMLEditor::ApplyDefaultProperties()
700 {
701   size_t defcon = mDefaultStyles.Length();
702   for (size_t j = 0; j < defcon; j++) {
703     PropItem *propItem = mDefaultStyles[j];
704     NS_ENSURE_TRUE(propItem, NS_ERROR_NULL_POINTER);
705     nsresult rv =
706       SetInlineProperty(propItem->tag, propItem->attr, propItem->value);
707     NS_ENSURE_SUCCESS(rv, rv);
708   }
709   return NS_OK;
710 }
711 
712 nsresult
RemoveStyleInside(nsIContent & aNode,nsIAtom * aProperty,const nsAString * aAttribute,const bool aChildrenOnly)713 HTMLEditor::RemoveStyleInside(nsIContent& aNode,
714                               nsIAtom* aProperty,
715                               const nsAString* aAttribute,
716                               const bool aChildrenOnly /* = false */)
717 {
718   if (aNode.GetAsText()) {
719     return NS_OK;
720   }
721 
722   // first process the children
723   RefPtr<nsIContent> child = aNode.GetFirstChild();
724   while (child) {
725     // cache next sibling since we might remove child
726     nsCOMPtr<nsIContent> next = child->GetNextSibling();
727     nsresult rv = RemoveStyleInside(*child, aProperty, aAttribute);
728     NS_ENSURE_SUCCESS(rv, rv);
729     child = next.forget();
730   }
731 
732   // then process the node itself
733   if (!aChildrenOnly &&
734        // node is prop we asked for
735       ((aProperty && aNode.NodeInfo()->NameAtom() == aProperty) ||
736        // but check for link (<a href=...)
737        (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(&aNode)) ||
738        // and for named anchors
739        (aProperty == nsGkAtoms::name && HTMLEditUtils::IsNamedAnchor(&aNode)) ||
740        // or node is any prop and we asked for that
741        (!aProperty && NodeIsProperty(aNode)))) {
742     // if we weren't passed an attribute, then we want to
743     // remove any matching inlinestyles entirely
744     if (!aAttribute || aAttribute->IsEmpty()) {
745       NS_NAMED_LITERAL_STRING(styleAttr, "style");
746       NS_NAMED_LITERAL_STRING(classAttr, "class");
747 
748       bool hasStyleAttr = aNode.HasAttr(kNameSpaceID_None, nsGkAtoms::style);
749       bool hasClassAttr = aNode.HasAttr(kNameSpaceID_None, nsGkAtoms::_class);
750       if (aProperty && (hasStyleAttr || hasClassAttr)) {
751         // aNode carries inline styles or a class attribute so we can't
752         // just remove the element... We need to create above the element
753         // a span that will carry those styles or class, then we can delete
754         // the node.
755         nsCOMPtr<Element> spanNode =
756           InsertContainerAbove(&aNode, nsGkAtoms::span);
757         NS_ENSURE_STATE(spanNode);
758         nsresult rv =
759           CloneAttribute(styleAttr, spanNode->AsDOMNode(), aNode.AsDOMNode());
760         NS_ENSURE_SUCCESS(rv, rv);
761         rv =
762           CloneAttribute(classAttr, spanNode->AsDOMNode(), aNode.AsDOMNode());
763         NS_ENSURE_SUCCESS(rv, rv);
764       }
765       nsresult rv = RemoveContainer(&aNode);
766       NS_ENSURE_SUCCESS(rv, rv);
767     } else {
768       // otherwise we just want to eliminate the attribute
769       nsCOMPtr<nsIAtom> attribute = NS_Atomize(*aAttribute);
770       if (aNode.HasAttr(kNameSpaceID_None, attribute)) {
771         // if this matching attribute is the ONLY one on the node,
772         // then remove the whole node.  Otherwise just nix the attribute.
773         if (IsOnlyAttribute(&aNode, *aAttribute)) {
774           nsresult rv = RemoveContainer(&aNode);
775           if (NS_WARN_IF(NS_FAILED(rv))) {
776             return rv;
777           }
778         } else {
779           nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
780           NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER);
781           nsresult rv = RemoveAttribute(elem, *aAttribute);
782           if (NS_WARN_IF(NS_FAILED(rv))) {
783             return rv;
784           }
785         }
786       }
787     }
788   }
789 
790   if (!aChildrenOnly &&
791       mCSSEditUtils->IsCSSEditableProperty(&aNode, aProperty, aAttribute)) {
792     // the HTML style defined by aProperty/aAttribute has a CSS equivalence in
793     // this implementation for the node aNode; let's check if it carries those
794     // css styles
795     nsAutoString propertyValue;
796     bool isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aNode,
797       aProperty, aAttribute, propertyValue, CSSEditUtils::eSpecified);
798     if (isSet && aNode.IsElement()) {
799       // yes, tmp has the corresponding css declarations in its style attribute
800       // let's remove them
801       mCSSEditUtils->RemoveCSSEquivalentToHTMLStyle(aNode.AsElement(),
802                                                     aProperty,
803                                                     aAttribute,
804                                                     &propertyValue,
805                                                     false);
806       // remove the node if it is a span or font, if its style attribute is
807       // empty or absent, and if it does not have a class nor an id
808       RemoveElementIfNoStyleOrIdOrClass(*aNode.AsElement());
809     }
810   }
811 
812   // Or node is big or small and we are setting font size
813   if (aChildrenOnly) {
814     return NS_OK;
815   }
816   if (aProperty == nsGkAtoms::font &&
817       (aNode.IsHTMLElement(nsGkAtoms::big) ||
818        aNode.IsHTMLElement(nsGkAtoms::small)) &&
819       aAttribute && aAttribute->LowerCaseEqualsLiteral("size")) {
820     // if we are setting font size, remove any nested bigs and smalls
821     return RemoveContainer(&aNode);
822   }
823   return NS_OK;
824 }
825 
826 bool
IsOnlyAttribute(const nsIContent * aContent,const nsAString & aAttribute)827 HTMLEditor::IsOnlyAttribute(const nsIContent* aContent,
828                             const nsAString& aAttribute)
829 {
830   MOZ_ASSERT(aContent);
831 
832   uint32_t attrCount = aContent->GetAttrCount();
833   for (uint32_t i = 0; i < attrCount; ++i) {
834     const nsAttrName* name = aContent->GetAttrNameAt(i);
835     if (!name->NamespaceEquals(kNameSpaceID_None)) {
836       return false;
837     }
838 
839     nsAutoString attrString;
840     name->LocalName()->ToString(attrString);
841     // if it's the attribute we know about, or a special _moz attribute,
842     // keep looking
843     if (!attrString.Equals(aAttribute, nsCaseInsensitiveStringComparator()) &&
844         !StringBeginsWith(attrString, NS_LITERAL_STRING("_moz"))) {
845       return false;
846     }
847   }
848   // if we made it through all of them without finding a real attribute
849   // other than aAttribute, then return true
850   return true;
851 }
852 
853 nsresult
PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange & aRange)854 HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange)
855 {
856   // We assume that <a> is not nested.
857   nsCOMPtr<nsINode> startNode = aRange.GetStartParent();
858   int32_t startOffset = aRange.StartOffset();
859   nsCOMPtr<nsINode> endNode = aRange.GetEndParent();
860   int32_t endOffset = aRange.EndOffset();
861 
862   nsCOMPtr<nsINode> parent = startNode;
863 
864   while (parent && !parent->IsHTMLElement(nsGkAtoms::body) &&
865          !HTMLEditUtils::IsNamedAnchor(parent)) {
866     parent = parent->GetParentNode();
867   }
868   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
869 
870   if (HTMLEditUtils::IsNamedAnchor(parent)) {
871     startNode = parent->GetParentNode();
872     startOffset = startNode ? startNode->IndexOf(parent) : -1;
873   }
874 
875   parent = endNode;
876   while (parent && !parent->IsHTMLElement(nsGkAtoms::body) &&
877          !HTMLEditUtils::IsNamedAnchor(parent)) {
878     parent = parent->GetParentNode();
879   }
880   NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
881 
882   if (HTMLEditUtils::IsNamedAnchor(parent)) {
883     endNode = parent->GetParentNode();
884     endOffset = endNode ? endNode->IndexOf(parent) + 1 : 0;
885   }
886 
887   nsresult rv = aRange.SetStart(startNode, startOffset);
888   NS_ENSURE_SUCCESS(rv, rv);
889   rv = aRange.SetEnd(endNode, endOffset);
890   NS_ENSURE_SUCCESS(rv, rv);
891 
892   return NS_OK;
893 }
894 
895 nsresult
PromoteInlineRange(nsRange & aRange)896 HTMLEditor::PromoteInlineRange(nsRange& aRange)
897 {
898   nsCOMPtr<nsINode> startNode = aRange.GetStartParent();
899   int32_t startOffset = aRange.StartOffset();
900   nsCOMPtr<nsINode> endNode = aRange.GetEndParent();
901   int32_t endOffset = aRange.EndOffset();
902 
903   while (startNode && !startNode->IsHTMLElement(nsGkAtoms::body) &&
904          IsEditable(startNode) && IsAtFrontOfNode(*startNode, startOffset)) {
905     nsCOMPtr<nsINode> parent = startNode->GetParentNode();
906     NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
907     startOffset = parent->IndexOf(startNode);
908     startNode = parent;
909   }
910 
911   while (endNode && !endNode->IsHTMLElement(nsGkAtoms::body) &&
912          IsEditable(endNode) && IsAtEndOfNode(*endNode, endOffset)) {
913     nsCOMPtr<nsINode> parent = endNode->GetParentNode();
914     NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
915     // We are AFTER this node
916     endOffset = 1 + parent->IndexOf(endNode);
917     endNode = parent;
918   }
919 
920   nsresult rv = aRange.SetStart(startNode, startOffset);
921   NS_ENSURE_SUCCESS(rv, rv);
922   rv = aRange.SetEnd(endNode, endOffset);
923   NS_ENSURE_SUCCESS(rv, rv);
924 
925   return NS_OK;
926 }
927 
928 bool
IsAtFrontOfNode(nsINode & aNode,int32_t aOffset)929 HTMLEditor::IsAtFrontOfNode(nsINode& aNode,
930                             int32_t aOffset)
931 {
932   if (!aOffset) {
933     return true;
934   }
935 
936   if (IsTextNode(&aNode)) {
937     return false;
938   }
939 
940   nsCOMPtr<nsIContent> firstNode = GetFirstEditableChild(aNode);
941   NS_ENSURE_TRUE(firstNode, true);
942   if (aNode.IndexOf(firstNode) < aOffset) {
943     return false;
944   }
945   return true;
946 }
947 
948 bool
IsAtEndOfNode(nsINode & aNode,int32_t aOffset)949 HTMLEditor::IsAtEndOfNode(nsINode& aNode,
950                           int32_t aOffset)
951 {
952   if (aOffset == (int32_t)aNode.Length()) {
953     return true;
954   }
955 
956   if (IsTextNode(&aNode)) {
957     return false;
958   }
959 
960   nsCOMPtr<nsIContent> lastNode = GetLastEditableChild(aNode);
961   NS_ENSURE_TRUE(lastNode, true);
962   if (aNode.IndexOf(lastNode) < aOffset) {
963     return true;
964   }
965   return false;
966 }
967 
968 
969 nsresult
GetInlinePropertyBase(nsIAtom & aProperty,const nsAString * aAttribute,const nsAString * aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString * outValue,bool aCheckDefaults)970 HTMLEditor::GetInlinePropertyBase(nsIAtom& aProperty,
971                                   const nsAString* aAttribute,
972                                   const nsAString* aValue,
973                                   bool* aFirst,
974                                   bool* aAny,
975                                   bool* aAll,
976                                   nsAString* outValue,
977                                   bool aCheckDefaults)
978 {
979   *aAny = false;
980   *aAll = true;
981   *aFirst = false;
982   bool first = true;
983 
984   RefPtr<Selection> selection = GetSelection();
985   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
986 
987   bool isCollapsed = selection->Collapsed();
988   RefPtr<nsRange> range = selection->GetRangeAt(0);
989   // XXX: Should be a while loop, to get each separate range
990   // XXX: ERROR_HANDLING can currentItem be null?
991   if (range) {
992     // For each range, set a flag
993     bool firstNodeInRange = true;
994 
995     if (isCollapsed) {
996       nsCOMPtr<nsINode> collapsedNode = range->GetStartParent();
997       NS_ENSURE_TRUE(collapsedNode, NS_ERROR_FAILURE);
998       bool isSet, theSetting;
999       nsString tOutString;
1000       if (aAttribute) {
1001         nsString tString(*aAttribute);
1002         mTypeInState->GetTypingState(isSet, theSetting, &aProperty, tString,
1003                                      &tOutString);
1004         if (outValue) {
1005           outValue->Assign(tOutString);
1006         }
1007       } else {
1008         mTypeInState->GetTypingState(isSet, theSetting, &aProperty);
1009       }
1010       if (isSet) {
1011         *aFirst = *aAny = *aAll = theSetting;
1012         return NS_OK;
1013       }
1014 
1015       if (mCSSEditUtils->IsCSSEditableProperty(collapsedNode, &aProperty,
1016                                                aAttribute)) {
1017         if (aValue) {
1018           tOutString.Assign(*aValue);
1019         }
1020         *aFirst = *aAny = *aAll =
1021           mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(collapsedNode,
1022               &aProperty, aAttribute, tOutString, CSSEditUtils::eComputed);
1023         if (outValue) {
1024           outValue->Assign(tOutString);
1025         }
1026         return NS_OK;
1027       }
1028 
1029       isSet = IsTextPropertySetByContent(collapsedNode, &aProperty,
1030                                          aAttribute, aValue, outValue);
1031       *aFirst = *aAny = *aAll = isSet;
1032 
1033       if (!isSet && aCheckDefaults) {
1034         // Style not set, but if it is a default then it will appear if content
1035         // is inserted, so we should report it as set (analogous to
1036         // TypeInState).
1037         int32_t index;
1038         if (aAttribute && TypeInState::FindPropInList(&aProperty, *aAttribute,
1039                                                       outValue, mDefaultStyles,
1040                                                       index)) {
1041           *aFirst = *aAny = *aAll = true;
1042           if (outValue) {
1043             outValue->Assign(mDefaultStyles[index]->value);
1044           }
1045         }
1046       }
1047       return NS_OK;
1048     }
1049 
1050     // Non-collapsed selection
1051     nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
1052 
1053     nsAutoString firstValue, theValue;
1054 
1055     nsCOMPtr<nsINode> endNode = range->GetEndParent();
1056     int32_t endOffset = range->EndOffset();
1057 
1058     for (iter->Init(range); !iter->IsDone(); iter->Next()) {
1059       if (!iter->GetCurrentNode()->IsContent()) {
1060         continue;
1061       }
1062       nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
1063 
1064       if (content->IsHTMLElement(nsGkAtoms::body)) {
1065         break;
1066       }
1067 
1068       // just ignore any non-editable nodes
1069       if (content->GetAsText() && (!IsEditable(content) ||
1070                                    IsEmptyTextNode(this, content))) {
1071         continue;
1072       }
1073       if (content->GetAsText()) {
1074         if (!isCollapsed && first && firstNodeInRange) {
1075           firstNodeInRange = false;
1076           if (range->StartOffset() == (int32_t)content->Length()) {
1077             continue;
1078           }
1079         } else if (content == endNode && !endOffset) {
1080           continue;
1081         }
1082       } else if (content->IsElement()) {
1083         // handle non-text leaf nodes here
1084         continue;
1085       }
1086 
1087       bool isSet = false;
1088       if (first) {
1089         if (mCSSEditUtils->IsCSSEditableProperty(content, &aProperty,
1090                                                  aAttribute)) {
1091           // The HTML styles defined by aProperty/aAttribute have a CSS
1092           // equivalence in this implementation for node; let's check if it
1093           // carries those CSS styles
1094           if (aValue) {
1095             firstValue.Assign(*aValue);
1096           }
1097           isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
1098               &aProperty, aAttribute, firstValue, CSSEditUtils::eComputed);
1099         } else {
1100           isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
1101                                              aValue, &firstValue);
1102         }
1103         *aFirst = isSet;
1104         first = false;
1105         if (outValue) {
1106           *outValue = firstValue;
1107         }
1108       } else {
1109         if (mCSSEditUtils->IsCSSEditableProperty(content, &aProperty,
1110                                                  aAttribute)) {
1111           // The HTML styles defined by aProperty/aAttribute have a CSS
1112           // equivalence in this implementation for node; let's check if it
1113           // carries those CSS styles
1114           if (aValue) {
1115             theValue.Assign(*aValue);
1116           }
1117           isSet = mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
1118               &aProperty, aAttribute, theValue, CSSEditUtils::eComputed);
1119         } else {
1120           isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
1121                                              aValue, &theValue);
1122         }
1123         if (firstValue != theValue) {
1124           *aAll = false;
1125         }
1126       }
1127 
1128       if (isSet) {
1129         *aAny = true;
1130       } else {
1131         *aAll = false;
1132       }
1133     }
1134   }
1135   if (!*aAny) {
1136     // make sure that if none of the selection is set, we don't report all is
1137     // set
1138     *aAll = false;
1139   }
1140   return NS_OK;
1141 }
1142 
1143 NS_IMETHODIMP
GetInlineProperty(nsIAtom * aProperty,const nsAString & aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll)1144 HTMLEditor::GetInlineProperty(nsIAtom* aProperty,
1145                               const nsAString& aAttribute,
1146                               const nsAString& aValue,
1147                               bool* aFirst,
1148                               bool* aAny,
1149                               bool* aAll)
1150 {
1151   NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
1152   const nsAString *att = nullptr;
1153   if (!aAttribute.IsEmpty())
1154     att = &aAttribute;
1155   const nsAString *val = nullptr;
1156   if (!aValue.IsEmpty())
1157     val = &aValue;
1158   return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, nullptr);
1159 }
1160 
1161 NS_IMETHODIMP
GetInlinePropertyWithAttrValue(nsIAtom * aProperty,const nsAString & aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString & outValue)1162 HTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom* aProperty,
1163                                            const nsAString& aAttribute,
1164                                            const nsAString& aValue,
1165                                            bool* aFirst,
1166                                            bool* aAny,
1167                                            bool* aAll,
1168                                            nsAString& outValue)
1169 {
1170   NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
1171   const nsAString *att = nullptr;
1172   if (!aAttribute.IsEmpty())
1173     att = &aAttribute;
1174   const nsAString *val = nullptr;
1175   if (!aValue.IsEmpty())
1176     val = &aValue;
1177   return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, &outValue);
1178 }
1179 
1180 NS_IMETHODIMP
RemoveAllInlineProperties()1181 HTMLEditor::RemoveAllInlineProperties()
1182 {
1183   AutoEditBatch batchIt(this);
1184   AutoRules beginRulesSniffing(this, EditAction::resetTextProperties,
1185                                nsIEditor::eNext);
1186 
1187   nsresult rv = RemoveInlinePropertyImpl(nullptr, nullptr);
1188   NS_ENSURE_SUCCESS(rv, rv);
1189   return ApplyDefaultProperties();
1190 }
1191 
1192 NS_IMETHODIMP
RemoveInlineProperty(nsIAtom * aProperty,const nsAString & aAttribute)1193 HTMLEditor::RemoveInlineProperty(nsIAtom* aProperty,
1194                                  const nsAString& aAttribute)
1195 {
1196   return RemoveInlinePropertyImpl(aProperty, &aAttribute);
1197 }
1198 
1199 nsresult
RemoveInlinePropertyImpl(nsIAtom * aProperty,const nsAString * aAttribute)1200 HTMLEditor::RemoveInlinePropertyImpl(nsIAtom* aProperty,
1201                                      const nsAString* aAttribute)
1202 {
1203   MOZ_ASSERT_IF(aProperty, aAttribute);
1204   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
1205   ForceCompositionEnd();
1206 
1207   RefPtr<Selection> selection = GetSelection();
1208   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1209 
1210   if (selection->Collapsed()) {
1211     // Manipulating text attributes on a collapsed selection only sets state
1212     // for the next text insertion
1213 
1214     // For links, aProperty uses "href", use "a" instead
1215     if (aProperty == nsGkAtoms::href || aProperty == nsGkAtoms::name) {
1216       aProperty = nsGkAtoms::a;
1217     }
1218 
1219     if (aProperty) {
1220       mTypeInState->ClearProp(aProperty, *aAttribute);
1221     } else {
1222       mTypeInState->ClearAllProps();
1223     }
1224     return NS_OK;
1225   }
1226 
1227   AutoEditBatch batchIt(this);
1228   AutoRules beginRulesSniffing(this, EditAction::removeTextProperty,
1229                                nsIEditor::eNext);
1230   AutoSelectionRestorer selectionRestorer(selection, this);
1231   AutoTransactionsConserveSelection dontSpazMySelection(this);
1232 
1233   bool cancel, handled;
1234   TextRulesInfo ruleInfo(EditAction::removeTextProperty);
1235   // Protect the edit rules object from dying
1236   nsCOMPtr<nsIEditRules> rules(mRules);
1237   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1238   NS_ENSURE_SUCCESS(rv, rv);
1239   if (!cancel && !handled) {
1240     // Loop through the ranges in the selection
1241     uint32_t rangeCount = selection->RangeCount();
1242     // Since ranges might be modified by SplitStyleAboveRange, we need hold
1243     // current ranges
1244     AutoTArray<OwningNonNull<nsRange>, 8> arrayOfRanges;
1245     for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
1246       arrayOfRanges.AppendElement(*selection->GetRangeAt(rangeIdx));
1247     }
1248     for (auto& range : arrayOfRanges) {
1249       if (aProperty == nsGkAtoms::name) {
1250         // Promote range if it starts or end in a named anchor and we want to
1251         // remove named anchors
1252         rv = PromoteRangeIfStartsOrEndsInNamedAnchor(range);
1253         if (NS_WARN_IF(NS_FAILED(rv))) {
1254           return rv;
1255         }
1256       } else {
1257         // Adjust range to include any ancestors whose children are entirely
1258         // selected
1259         rv = PromoteInlineRange(range);
1260         if (NS_WARN_IF(NS_FAILED(rv))) {
1261           return rv;
1262         }
1263       }
1264 
1265       // Remove this style from ancestors of our range endpoints, splitting
1266       // them as appropriate
1267       rv = SplitStyleAboveRange(range, aProperty, aAttribute);
1268       NS_ENSURE_SUCCESS(rv, rv);
1269 
1270       // Check for easy case: both range endpoints in same text node
1271       nsCOMPtr<nsINode> startNode = range->GetStartParent();
1272       nsCOMPtr<nsINode> endNode = range->GetEndParent();
1273       if (startNode && startNode == endNode && startNode->GetAsText()) {
1274         // We're done with this range!
1275         if (IsCSSEnabled() &&
1276             mCSSEditUtils->IsCSSEditableProperty(startNode, aProperty,
1277                                                  aAttribute)) {
1278           // The HTML style defined by aProperty/aAttribute has a CSS
1279           // equivalence in this implementation for startNode
1280           if (mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(startNode,
1281                 aProperty, aAttribute, EmptyString(),
1282                 CSSEditUtils::eComputed)) {
1283             // startNode's computed style indicates the CSS equivalence to the
1284             // HTML style to remove is applied; but we found no element in the
1285             // ancestors of startNode carrying specified styles; assume it
1286             // comes from a rule and try to insert a span "inverting" the style
1287             if (mCSSEditUtils->IsCSSInvertible(*aProperty, aAttribute)) {
1288               NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
1289               SetInlinePropertyOnTextNode(*startNode->GetAsText(),
1290                                           range->StartOffset(),
1291                                           range->EndOffset(), *aProperty,
1292                                           aAttribute, value);
1293             }
1294           }
1295         }
1296       } else {
1297         // Not the easy case.  Range not contained in single text node.
1298         nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
1299 
1300         nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
1301 
1302         // Iterate range and build up array
1303         for (iter->Init(range); !iter->IsDone(); iter->Next()) {
1304           nsCOMPtr<nsINode> node = iter->GetCurrentNode();
1305           NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
1306 
1307           if (IsEditable(node) && node->IsContent()) {
1308             arrayOfNodes.AppendElement(*node->AsContent());
1309           }
1310         }
1311 
1312         // Loop through the list, remove the property on each node
1313         for (auto& node : arrayOfNodes) {
1314           rv = RemoveStyleInside(node, aProperty, aAttribute);
1315           NS_ENSURE_SUCCESS(rv, rv);
1316           if (IsCSSEnabled() &&
1317               mCSSEditUtils->IsCSSEditableProperty(node, aProperty,
1318                                                    aAttribute) &&
1319               mCSSEditUtils->IsCSSEquivalentToHTMLInlineStyleSet(node,
1320                   aProperty, aAttribute, EmptyString(),
1321                   CSSEditUtils::eComputed) &&
1322               // startNode's computed style indicates the CSS equivalence to
1323               // the HTML style to remove is applied; but we found no element
1324               // in the ancestors of startNode carrying specified styles;
1325               // assume it comes from a rule and let's try to insert a span
1326               // "inverting" the style
1327               mCSSEditUtils->IsCSSInvertible(*aProperty, aAttribute)) {
1328             NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
1329             SetInlinePropertyOnNode(node, *aProperty, aAttribute, value);
1330           }
1331         }
1332       }
1333     }
1334   }
1335   if (!cancel) {
1336     // Post-process
1337     rv = rules->DidDoAction(selection, &ruleInfo, rv);
1338     NS_ENSURE_SUCCESS(rv, rv);
1339   }
1340   return NS_OK;
1341 }
1342 
1343 NS_IMETHODIMP
IncreaseFontSize()1344 HTMLEditor::IncreaseFontSize()
1345 {
1346   return RelativeFontChange(FontSize::incr);
1347 }
1348 
1349 NS_IMETHODIMP
DecreaseFontSize()1350 HTMLEditor::DecreaseFontSize()
1351 {
1352   return RelativeFontChange(FontSize::decr);
1353 }
1354 
1355 nsresult
RelativeFontChange(FontSize aDir)1356 HTMLEditor::RelativeFontChange(FontSize aDir)
1357 {
1358   ForceCompositionEnd();
1359 
1360   // Get the selection
1361   RefPtr<Selection> selection = GetSelection();
1362   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1363   // If selection is collapsed, set typing state
1364   if (selection->Collapsed()) {
1365     nsIAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big :
1366                                              *nsGkAtoms::small;
1367 
1368     // Let's see in what kind of element the selection is
1369     NS_ENSURE_TRUE(selection->RangeCount() &&
1370                    selection->GetRangeAt(0)->GetStartParent(), NS_OK);
1371     OwningNonNull<nsINode> selectedNode =
1372       *selection->GetRangeAt(0)->GetStartParent();
1373     if (IsTextNode(selectedNode)) {
1374       NS_ENSURE_TRUE(selectedNode->GetParentNode(), NS_OK);
1375       selectedNode = *selectedNode->GetParentNode();
1376     }
1377     if (!CanContainTag(selectedNode, atom)) {
1378       return NS_OK;
1379     }
1380 
1381     // Manipulating text attributes on a collapsed selection only sets state
1382     // for the next text insertion
1383     mTypeInState->SetProp(&atom, EmptyString(), EmptyString());
1384     return NS_OK;
1385   }
1386 
1387   // Wrap with txn batching, rules sniffing, and selection preservation code
1388   AutoEditBatch batchIt(this);
1389   AutoRules beginRulesSniffing(this, EditAction::setTextProperty,
1390                                nsIEditor::eNext);
1391   AutoSelectionRestorer selectionRestorer(selection, this);
1392   AutoTransactionsConserveSelection dontSpazMySelection(this);
1393 
1394   // Loop through the ranges in the selection
1395   uint32_t rangeCount = selection->RangeCount();
1396   for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
1397     RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
1398 
1399     // Adjust range to include any ancestors with entirely selected children
1400     nsresult rv = PromoteInlineRange(*range);
1401     NS_ENSURE_SUCCESS(rv, rv);
1402 
1403     // Check for easy case: both range endpoints in same text node
1404     nsCOMPtr<nsINode> startNode = range->GetStartParent();
1405     nsCOMPtr<nsINode> endNode = range->GetEndParent();
1406     if (startNode == endNode && IsTextNode(startNode)) {
1407       rv = RelativeFontChangeOnTextNode(aDir, *startNode->GetAsText(),
1408                                         range->StartOffset(),
1409                                         range->EndOffset());
1410       NS_ENSURE_SUCCESS(rv, rv);
1411     } else {
1412       // Not the easy case.  Range not contained in single text node.  There
1413       // are up to three phases here.  There are all the nodes reported by the
1414       // subtree iterator to be processed.  And there are potentially a
1415       // starting textnode and an ending textnode which are only partially
1416       // contained by the range.
1417 
1418       // Let's handle the nodes reported by the iterator.  These nodes are
1419       // entirely contained in the selection range.  We build up a list of them
1420       // (since doing operations on the document during iteration would perturb
1421       // the iterator).
1422 
1423       OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
1424 
1425       // Iterate range and build up array
1426       rv = iter->Init(range);
1427       if (NS_SUCCEEDED(rv)) {
1428         nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
1429         for (; !iter->IsDone(); iter->Next()) {
1430           NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE);
1431           OwningNonNull<nsIContent> node = *iter->GetCurrentNode()->AsContent();
1432 
1433           if (IsEditable(node)) {
1434             arrayOfNodes.AppendElement(node);
1435           }
1436         }
1437 
1438         // Now that we have the list, do the font size change on each node
1439         for (auto& node : arrayOfNodes) {
1440           rv = RelativeFontChangeOnNode(aDir == FontSize::incr ? +1 : -1, node);
1441           NS_ENSURE_SUCCESS(rv, rv);
1442         }
1443       }
1444       // Now check the start and end parents of the range to see if they need
1445       // to be separately handled (they do if they are text nodes, due to how
1446       // the subtree iterator works - it will not have reported them).
1447       if (IsTextNode(startNode) && IsEditable(startNode)) {
1448         rv = RelativeFontChangeOnTextNode(aDir, *startNode->GetAsText(),
1449                                           range->StartOffset(),
1450                                           startNode->Length());
1451         NS_ENSURE_SUCCESS(rv, rv);
1452       }
1453       if (IsTextNode(endNode) && IsEditable(endNode)) {
1454         rv = RelativeFontChangeOnTextNode(aDir, *endNode->GetAsText(), 0,
1455                                           range->EndOffset());
1456         NS_ENSURE_SUCCESS(rv, rv);
1457       }
1458     }
1459   }
1460 
1461   return NS_OK;
1462 }
1463 
1464 nsresult
RelativeFontChangeOnTextNode(FontSize aDir,Text & aTextNode,int32_t aStartOffset,int32_t aEndOffset)1465 HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir,
1466                                          Text& aTextNode,
1467                                          int32_t aStartOffset,
1468                                          int32_t aEndOffset)
1469 {
1470   // Don't need to do anything if no characters actually selected
1471   if (aStartOffset == aEndOffset) {
1472     return NS_OK;
1473   }
1474 
1475   if (!aTextNode.GetParentNode() ||
1476       !CanContainTag(*aTextNode.GetParentNode(), *nsGkAtoms::big)) {
1477     return NS_OK;
1478   }
1479 
1480   OwningNonNull<nsIContent> node = aTextNode;
1481 
1482   // Do we need to split the text node?
1483 
1484   // -1 is a magic value meaning to the end of node
1485   if (aEndOffset == -1) {
1486     aEndOffset = aTextNode.Length();
1487   }
1488 
1489   ErrorResult rv;
1490   if ((uint32_t)aEndOffset != aTextNode.Length()) {
1491     // We need to split off back of text node
1492     node = SplitNode(node, aEndOffset, rv);
1493     NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
1494   }
1495   if (aStartOffset) {
1496     // We need to split off front of text node
1497     SplitNode(node, aStartOffset, rv);
1498     NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
1499   }
1500 
1501   // Look for siblings that are correct type of node
1502   nsIAtom* nodeType = aDir == FontSize::incr ? nsGkAtoms::big
1503                                              : nsGkAtoms::small;
1504   nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(node);
1505   if (sibling && sibling->IsHTMLElement(nodeType)) {
1506     // Previous sib is already right kind of inline node; slide this over
1507     nsresult rv = MoveNode(node, sibling, -1);
1508     NS_ENSURE_SUCCESS(rv, rv);
1509     return NS_OK;
1510   }
1511   sibling = GetNextHTMLSibling(node);
1512   if (sibling && sibling->IsHTMLElement(nodeType)) {
1513     // Following sib is already right kind of inline node; slide this over
1514     nsresult rv = MoveNode(node, sibling, 0);
1515     NS_ENSURE_SUCCESS(rv, rv);
1516     return NS_OK;
1517   }
1518 
1519   // Else reparent the node inside font node with appropriate relative size
1520   nsCOMPtr<Element> newElement = InsertContainerAbove(node, nodeType);
1521   NS_ENSURE_STATE(newElement);
1522 
1523   return NS_OK;
1524 }
1525 
1526 nsresult
RelativeFontChangeHelper(int32_t aSizeChange,nsINode * aNode)1527 HTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange,
1528                                      nsINode* aNode)
1529 {
1530   MOZ_ASSERT(aNode);
1531 
1532   /*  This routine looks for all the font nodes in the tree rooted by aNode,
1533       including aNode itself, looking for font nodes that have the size attr
1534       set.  Any such nodes need to have big or small put inside them, since
1535       they override any big/small that are above them.
1536   */
1537 
1538   // Can only change font size by + or - 1
1539   if (aSizeChange != 1 && aSizeChange != -1) {
1540     return NS_ERROR_ILLEGAL_VALUE;
1541   }
1542 
1543   // If this is a font node with size, put big/small inside it.
1544   if (aNode->IsHTMLElement(nsGkAtoms::font) &&
1545       aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
1546     // Cycle through children and adjust relative font size.
1547     for (uint32_t i = aNode->GetChildCount(); i--; ) {
1548       nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
1549       NS_ENSURE_SUCCESS(rv, rv);
1550     }
1551 
1552     // RelativeFontChangeOnNode already calls us recursively,
1553     // so we don't need to check our children again.
1554     return NS_OK;
1555   }
1556 
1557   // Otherwise cycle through the children.
1558   for (uint32_t i = aNode->GetChildCount(); i--; ) {
1559     nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode->GetChildAt(i));
1560     NS_ENSURE_SUCCESS(rv, rv);
1561   }
1562 
1563   return NS_OK;
1564 }
1565 
1566 nsresult
RelativeFontChangeOnNode(int32_t aSizeChange,nsIContent * aNode)1567 HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange,
1568                                      nsIContent* aNode)
1569 {
1570   MOZ_ASSERT(aNode);
1571   // Can only change font size by + or - 1
1572   if (aSizeChange != 1 && aSizeChange != -1) {
1573     return NS_ERROR_ILLEGAL_VALUE;
1574   }
1575 
1576   nsIAtom* atom;
1577   if (aSizeChange == 1) {
1578     atom = nsGkAtoms::big;
1579   } else {
1580     atom = nsGkAtoms::small;
1581   }
1582 
1583   // Is it the opposite of what we want?
1584   if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
1585        (aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
1586     // first populate any nested font tags that have the size attr set
1587     nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
1588     NS_ENSURE_SUCCESS(rv, rv);
1589     // in that case, just remove this node and pull up the children
1590     return RemoveContainer(aNode);
1591   }
1592 
1593   // can it be put inside a "big" or "small"?
1594   if (TagCanContain(*atom, *aNode)) {
1595     // first populate any nested font tags that have the size attr set
1596     nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
1597     NS_ENSURE_SUCCESS(rv, rv);
1598 
1599     // ok, chuck it in.
1600     // first look at siblings of aNode for matching bigs or smalls.
1601     // if we find one, move aNode into it.
1602     nsIContent* sibling = GetPriorHTMLSibling(aNode);
1603     if (sibling && sibling->IsHTMLElement(atom)) {
1604       // previous sib is already right kind of inline node; slide this over into it
1605       return MoveNode(aNode, sibling, -1);
1606     }
1607 
1608     sibling = GetNextHTMLSibling(aNode);
1609     if (sibling && sibling->IsHTMLElement(atom)) {
1610       // following sib is already right kind of inline node; slide this over into it
1611       return MoveNode(aNode, sibling, 0);
1612     }
1613 
1614     // else insert it above aNode
1615     nsCOMPtr<Element> newElement = InsertContainerAbove(aNode, atom);
1616     NS_ENSURE_STATE(newElement);
1617 
1618     return NS_OK;
1619   }
1620 
1621   // none of the above?  then cycle through the children.
1622   // MOOSE: we should group the children together if possible
1623   // into a single "big" or "small".  For the moment they are
1624   // each getting their own.
1625   for (uint32_t i = aNode->GetChildCount(); i--; ) {
1626     nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
1627     NS_ENSURE_SUCCESS(rv, rv);
1628   }
1629 
1630   return NS_OK;
1631 }
1632 
1633 NS_IMETHODIMP
GetFontFaceState(bool * aMixed,nsAString & outFace)1634 HTMLEditor::GetFontFaceState(bool* aMixed,
1635                              nsAString& outFace)
1636 {
1637   NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE);
1638   *aMixed = true;
1639   outFace.Truncate();
1640 
1641   bool first, any, all;
1642 
1643   NS_NAMED_LITERAL_STRING(attr, "face");
1644   nsresult rv =
1645     GetInlinePropertyBase(*nsGkAtoms::font, &attr, nullptr, &first, &any,
1646                           &all, &outFace);
1647   NS_ENSURE_SUCCESS(rv, rv);
1648   if (any && !all) {
1649     return NS_OK; // mixed
1650   }
1651   if (all) {
1652     *aMixed = false;
1653     return NS_OK;
1654   }
1655 
1656   // if there is no font face, check for tt
1657   rv = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
1658                              &all,nullptr);
1659   NS_ENSURE_SUCCESS(rv, rv);
1660   if (any && !all) {
1661     return rv; // mixed
1662   }
1663   if (all) {
1664     *aMixed = false;
1665     outFace.AssignLiteral("tt");
1666   }
1667 
1668   if (!any) {
1669     // there was no font face attrs of any kind.  We are in normal font.
1670     outFace.Truncate();
1671     *aMixed = false;
1672   }
1673   return NS_OK;
1674 }
1675 
1676 NS_IMETHODIMP
GetFontColorState(bool * aMixed,nsAString & aOutColor)1677 HTMLEditor::GetFontColorState(bool* aMixed,
1678                               nsAString& aOutColor)
1679 {
1680   NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
1681   *aMixed = true;
1682   aOutColor.Truncate();
1683 
1684   NS_NAMED_LITERAL_STRING(colorStr, "color");
1685   bool first, any, all;
1686 
1687   nsresult rv =
1688     GetInlinePropertyBase(*nsGkAtoms::font, &colorStr, nullptr, &first,
1689                           &any, &all, &aOutColor);
1690   NS_ENSURE_SUCCESS(rv, rv);
1691   if (any && !all) {
1692     return NS_OK; // mixed
1693   }
1694   if (all) {
1695     *aMixed = false;
1696     return NS_OK;
1697   }
1698 
1699   if (!any) {
1700     // there was no font color attrs of any kind..
1701     aOutColor.Truncate();
1702     *aMixed = false;
1703   }
1704   return NS_OK;
1705 }
1706 
1707 // the return value is true only if the instance of the HTML editor we created
1708 // can handle CSS styles (for instance, Composer can, Messenger can't) and if
1709 // the CSS preference is checked
1710 nsresult
GetIsCSSEnabled(bool * aIsCSSEnabled)1711 HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled)
1712 {
1713   *aIsCSSEnabled = IsCSSEnabled();
1714   return NS_OK;
1715 }
1716 
1717 static bool
HasNonEmptyAttribute(Element * aElement,nsIAtom * aName)1718 HasNonEmptyAttribute(Element* aElement,
1719                      nsIAtom* aName)
1720 {
1721   MOZ_ASSERT(aElement);
1722 
1723   nsAutoString value;
1724   return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty();
1725 }
1726 
1727 bool
HasStyleOrIdOrClass(Element * aElement)1728 HTMLEditor::HasStyleOrIdOrClass(Element* aElement)
1729 {
1730   MOZ_ASSERT(aElement);
1731 
1732   // remove the node if its style attribute is empty or absent,
1733   // and if it does not have a class nor an id
1734   return HasNonEmptyAttribute(aElement, nsGkAtoms::style) ||
1735          HasNonEmptyAttribute(aElement, nsGkAtoms::_class) ||
1736          HasNonEmptyAttribute(aElement, nsGkAtoms::id);
1737 }
1738 
1739 nsresult
RemoveElementIfNoStyleOrIdOrClass(Element & aElement)1740 HTMLEditor::RemoveElementIfNoStyleOrIdOrClass(Element& aElement)
1741 {
1742   // early way out if node is not the right kind of element
1743   if ((!aElement.IsHTMLElement(nsGkAtoms::span) &&
1744        !aElement.IsHTMLElement(nsGkAtoms::font)) ||
1745       HasStyleOrIdOrClass(&aElement)) {
1746     return NS_OK;
1747   }
1748 
1749   return RemoveContainer(&aElement);
1750 }
1751 
1752 } // namespace mozilla
1753