editor/libeditor/html/nsHTMLEditorStyle.cpp

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

mercurial