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