Bug 1247796. Use keyboardFocusIndicatorColor for ActiveBorder system color keyword...
[gecko.git] / editor / libeditor / nsHTMLEditorStyle.cpp
blob7a7d53856b2005ab17dcd9c49ce763de0e517caa
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 "nsCOMPtr.h"
14 #include "nsCaseTreatment.h"
15 #include "nsComponentManagerUtils.h"
16 #include "nsDebug.h"
17 #include "nsEditRules.h"
18 #include "nsEditor.h"
19 #include "nsEditorUtils.h"
20 #include "nsError.h"
21 #include "nsGkAtoms.h"
22 #include "nsHTMLCSSUtils.h"
23 #include "nsHTMLEditUtils.h"
24 #include "nsHTMLEditor.h"
25 #include "nsIAtom.h"
26 #include "nsIContent.h"
27 #include "nsIContentIterator.h"
28 #include "nsIDOMCharacterData.h"
29 #include "nsIDOMElement.h"
30 #include "nsIDOMNode.h"
31 #include "nsIEditor.h"
32 #include "nsIEditorIMESupport.h"
33 #include "nsNameSpaceManager.h"
34 #include "nsINode.h"
35 #include "nsISupportsImpl.h"
36 #include "nsLiteralString.h"
37 #include "nsRange.h"
38 #include "nsReadableUtils.h"
39 #include "nsSelectionState.h"
40 #include "nsString.h"
41 #include "nsStringFwd.h"
42 #include "nsTArray.h"
43 #include "nsTextEditRules.h"
44 #include "nsTextEditUtils.h"
45 #include "nsUnicharUtils.h"
46 #include "nscore.h"
48 class nsISupports;
50 using namespace mozilla;
51 using namespace mozilla::dom;
53 static bool
54 IsEmptyTextNode(nsHTMLEditor* aThis, nsINode* aNode)
56 bool isEmptyTextNode = false;
57 return nsEditor::IsTextNode(aNode) &&
58 NS_SUCCEEDED(aThis->IsEmptyNode(aNode, &isEmptyTextNode)) &&
59 isEmptyTextNode;
62 NS_IMETHODIMP nsHTMLEditor::AddDefaultProperty(nsIAtom *aProperty,
63 const nsAString & aAttribute,
64 const nsAString & aValue)
66 nsString outValue;
67 int32_t index;
68 nsString attr(aAttribute);
69 if (TypeInState::FindPropInList(aProperty, attr, &outValue, mDefaultStyles, index))
71 PropItem *item = mDefaultStyles[index];
72 item->value = aValue;
74 else
76 nsString value(aValue);
77 PropItem *propItem = new PropItem(aProperty, attr, value);
78 mDefaultStyles.AppendElement(propItem);
80 return NS_OK;
83 NS_IMETHODIMP nsHTMLEditor::RemoveDefaultProperty(nsIAtom *aProperty,
84 const nsAString & aAttribute,
85 const nsAString & aValue)
87 nsString outValue;
88 int32_t index;
89 nsString attr(aAttribute);
90 if (TypeInState::FindPropInList(aProperty, attr, &outValue, mDefaultStyles, index))
92 delete mDefaultStyles[index];
93 mDefaultStyles.RemoveElementAt(index);
95 return NS_OK;
98 NS_IMETHODIMP nsHTMLEditor::RemoveAllDefaultProperties()
100 uint32_t j, defcon = mDefaultStyles.Length();
101 for (j=0; j<defcon; j++)
103 delete mDefaultStyles[j];
105 mDefaultStyles.Clear();
106 return NS_OK;
110 NS_IMETHODIMP
111 nsHTMLEditor::SetInlineProperty(nsIAtom* aProperty,
112 const nsAString& aAttribute,
113 const nsAString& aValue)
115 NS_ENSURE_TRUE(aProperty, NS_ERROR_NULL_POINTER);
116 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
117 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
118 ForceCompositionEnd();
120 RefPtr<Selection> selection = GetSelection();
121 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
123 if (selection->Collapsed()) {
124 // Manipulating text attributes on a collapsed selection only sets state
125 // for the next text insertion
126 mTypeInState->SetProp(aProperty, aAttribute, aValue);
127 return NS_OK;
130 nsAutoEditBatch batchIt(this);
131 nsAutoRules beginRulesSniffing(this, EditAction::insertElement, nsIEditor::eNext);
132 nsAutoSelectionReset selectionResetter(selection, this);
133 nsAutoTxnsConserveSelection dontSpazMySelection(this);
135 bool cancel, handled;
136 nsTextRulesInfo ruleInfo(EditAction::setTextProperty);
137 // Protect the edit rules object from dying
138 nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
139 NS_ENSURE_SUCCESS(res, res);
140 if (!cancel && !handled) {
141 // Loop through the ranges in the selection
142 uint32_t rangeCount = selection->RangeCount();
143 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; rangeIdx++) {
144 RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
146 // Adjust range to include any ancestors whose children are entirely
147 // selected
148 res = PromoteInlineRange(range);
149 NS_ENSURE_SUCCESS(res, res);
151 // Check for easy case: both range endpoints in same text node
152 nsCOMPtr<nsINode> startNode = range->GetStartParent();
153 nsCOMPtr<nsINode> endNode = range->GetEndParent();
154 if (startNode && startNode == endNode && startNode->GetAsText()) {
155 res = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
156 range->StartOffset(),
157 range->EndOffset(),
158 *aProperty, &aAttribute, aValue);
159 NS_ENSURE_SUCCESS(res, res);
160 continue;
163 // Not the easy case. Range not contained in single text node. There
164 // are up to three phases here. There are all the nodes reported by the
165 // subtree iterator to be processed. And there are potentially a
166 // starting textnode and an ending textnode which are only partially
167 // contained by the range.
169 // Let's handle the nodes reported by the iterator. These nodes are
170 // entirely contained in the selection range. We build up a list of them
171 // (since doing operations on the document during iteration would perturb
172 // the iterator).
174 OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
176 nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
178 // Iterate range and build up array
179 res = iter->Init(range);
180 // Init returns an error if there are no nodes in range. This can easily
181 // happen with the subtree iterator if the selection doesn't contain any
182 // *whole* nodes.
183 if (NS_SUCCEEDED(res)) {
184 for (; !iter->IsDone(); iter->Next()) {
185 OwningNonNull<nsINode> node = *iter->GetCurrentNode();
187 if (node->IsContent() && IsEditable(node)) {
188 arrayOfNodes.AppendElement(*node->AsContent());
192 // First check the start parent of the range to see if it needs to be
193 // separately handled (it does if it's a text node, due to how the
194 // subtree iterator works - it will not have reported it).
195 if (startNode && startNode->GetAsText() && IsEditable(startNode)) {
196 res = SetInlinePropertyOnTextNode(*startNode->GetAsText(),
197 range->StartOffset(),
198 startNode->Length(), *aProperty,
199 &aAttribute, aValue);
200 NS_ENSURE_SUCCESS(res, res);
203 // Then loop through the list, set the property on each node
204 for (auto& node : arrayOfNodes) {
205 res = SetInlinePropertyOnNode(*node, *aProperty, &aAttribute, aValue);
206 NS_ENSURE_SUCCESS(res, res);
209 // Last check the end parent of the range to see if it needs to be
210 // separately handled (it does if it's a text node, due to how the
211 // subtree iterator works - it will not have reported it).
212 if (endNode && endNode->GetAsText() && IsEditable(endNode)) {
213 res = SetInlinePropertyOnTextNode(*endNode->GetAsText(), 0,
214 range->EndOffset(), *aProperty,
215 &aAttribute, aValue);
216 NS_ENSURE_SUCCESS(res, res);
220 if (!cancel) {
221 // Post-process
222 return mRules->DidDoAction(selection, &ruleInfo, res);
224 return NS_OK;
229 // Helper function for SetInlinePropertyOn*: is aNode a simple old <b>, <font>,
230 // <span style="">, etc. that we can reuse instead of creating a new one?
231 bool
232 nsHTMLEditor::IsSimpleModifiableNode(nsIContent* aContent,
233 nsIAtom* aProperty,
234 const nsAString* aAttribute,
235 const nsAString* aValue)
237 // aContent can be null, in which case we'll return false in a few lines
238 MOZ_ASSERT(aProperty);
239 MOZ_ASSERT_IF(aAttribute, aValue);
241 nsCOMPtr<dom::Element> element = do_QueryInterface(aContent);
242 if (!element) {
243 return false;
246 // First check for <b>, <i>, etc.
247 if (element->IsHTMLElement(aProperty) && !element->GetAttrCount() &&
248 (!aAttribute || aAttribute->IsEmpty())) {
249 return true;
252 // Special cases for various equivalencies: <strong>, <em>, <s>
253 if (!element->GetAttrCount() &&
254 ((aProperty == nsGkAtoms::b &&
255 element->IsHTMLElement(nsGkAtoms::strong)) ||
256 (aProperty == nsGkAtoms::i &&
257 element->IsHTMLElement(nsGkAtoms::em)) ||
258 (aProperty == nsGkAtoms::strike &&
259 element->IsHTMLElement(nsGkAtoms::s)))) {
260 return true;
263 // Now look for things like <font>
264 if (aAttribute && !aAttribute->IsEmpty()) {
265 nsCOMPtr<nsIAtom> atom = do_GetAtom(*aAttribute);
266 MOZ_ASSERT(atom);
268 nsString attrValue;
269 if (element->IsHTMLElement(aProperty) &&
270 IsOnlyAttribute(element, *aAttribute) &&
271 element->GetAttr(kNameSpaceID_None, atom, attrValue) &&
272 attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator())) {
273 // This is not quite correct, because it excludes cases like
274 // <font face=000> being the same as <font face=#000000>.
275 // Property-specific handling is needed (bug 760211).
276 return true;
280 // No luck so far. Now we check for a <span> with a single style=""
281 // attribute that sets only the style we're looking for, if this type of
282 // style supports it
283 if (!mHTMLCSSUtils->IsCSSEditableProperty(element, aProperty, aAttribute) ||
284 !element->IsHTMLElement(nsGkAtoms::span) ||
285 element->GetAttrCount() != 1 ||
286 !element->HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
287 return false;
290 // Some CSS styles are not so simple. For instance, underline is
291 // "text-decoration: underline", which decomposes into four different text-*
292 // properties. So for now, we just create a span, add the desired style, and
293 // see if it matches.
294 nsCOMPtr<Element> newSpan = CreateHTMLContent(nsGkAtoms::span);
295 NS_ASSERTION(newSpan, "CreateHTMLContent failed");
296 NS_ENSURE_TRUE(newSpan, false);
297 mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(newSpan, aProperty,
298 aAttribute, aValue,
299 /*suppress transaction*/ true);
301 return mHTMLCSSUtils->ElementsSameStyle(newSpan, element);
305 nsresult
306 nsHTMLEditor::SetInlinePropertyOnTextNode(Text& aText,
307 int32_t aStartOffset,
308 int32_t aEndOffset,
309 nsIAtom& aProperty,
310 const nsAString* aAttribute,
311 const nsAString& aValue)
313 if (!aText.GetParentNode() ||
314 !CanContainTag(*aText.GetParentNode(), aProperty)) {
315 return NS_OK;
318 // Don't need to do anything if no characters actually selected
319 if (aStartOffset == aEndOffset) {
320 return NS_OK;
323 // Don't need to do anything if property already set on node
324 if (mHTMLCSSUtils->IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
325 // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
326 // for node; let's check if it carries those CSS styles
327 if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aText, &aProperty,
328 aAttribute, aValue, nsHTMLCSSUtils::eComputed)) {
329 return NS_OK;
331 } else if (IsTextPropertySetByContent(&aText, &aProperty, aAttribute,
332 &aValue)) {
333 return NS_OK;
336 // Do we need to split the text node?
337 ErrorResult rv;
338 RefPtr<Text> text = &aText;
339 if (uint32_t(aEndOffset) != aText.Length()) {
340 // We need to split off back of text node
341 text = SplitNode(aText, aEndOffset, rv)->GetAsText();
342 NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
345 if (aStartOffset) {
346 // We need to split off front of text node
347 SplitNode(*text, aStartOffset, rv);
348 NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
351 if (aAttribute) {
352 // Look for siblings that are correct type of node
353 nsIContent* sibling = GetPriorHTMLSibling(text);
354 if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
355 // Previous sib is already right kind of inline node; slide this over
356 return MoveNode(text, sibling, -1);
358 sibling = GetNextHTMLSibling(text);
359 if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
360 // Following sib is already right kind of inline node; slide this over
361 return MoveNode(text, sibling, 0);
365 // Reparent the node inside inline node with appropriate {attribute,value}
366 return SetInlinePropertyOnNode(*text, aProperty, aAttribute, aValue);
370 nsresult
371 nsHTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aNode,
372 nsIAtom& aProperty,
373 const nsAString* aAttribute,
374 const nsAString& aValue)
376 nsCOMPtr<nsIAtom> attrAtom = aAttribute ? do_GetAtom(*aAttribute) : nullptr;
378 // If this is an element that can't be contained in a span, we have to
379 // recurse to its children.
380 if (!TagCanContain(*nsGkAtoms::span, aNode)) {
381 if (aNode.HasChildren()) {
382 nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
384 // Populate the list.
385 for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild();
386 child;
387 child = child->GetNextSibling()) {
388 if (IsEditable(child) && !IsEmptyTextNode(this, child)) {
389 arrayOfNodes.AppendElement(*child);
393 // Then loop through the list, set the property on each node.
394 for (auto& node : arrayOfNodes) {
395 nsresult rv = SetInlinePropertyOnNode(node, aProperty, aAttribute,
396 aValue);
397 NS_ENSURE_SUCCESS(rv, rv);
400 return NS_OK;
403 // First check if there's an adjacent sibling we can put our node into.
404 nsresult res;
405 nsCOMPtr<nsIContent> previousSibling = GetPriorHTMLSibling(&aNode);
406 nsCOMPtr<nsIContent> nextSibling = GetNextHTMLSibling(&aNode);
407 if (IsSimpleModifiableNode(previousSibling, &aProperty, aAttribute, &aValue)) {
408 res = MoveNode(&aNode, previousSibling, -1);
409 NS_ENSURE_SUCCESS(res, res);
410 if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
411 res = JoinNodes(*previousSibling, *nextSibling);
412 NS_ENSURE_SUCCESS(res, res);
414 return NS_OK;
416 if (IsSimpleModifiableNode(nextSibling, &aProperty, aAttribute, &aValue)) {
417 res = MoveNode(&aNode, nextSibling, 0);
418 NS_ENSURE_SUCCESS(res, res);
419 return NS_OK;
422 // Don't need to do anything if property already set on node
423 if (mHTMLCSSUtils->IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) {
424 if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(
425 &aNode, &aProperty, aAttribute, aValue, nsHTMLCSSUtils::eComputed)) {
426 return NS_OK;
428 } else if (IsTextPropertySetByContent(&aNode, &aProperty,
429 aAttribute, &aValue)) {
430 return NS_OK;
433 bool useCSS = (IsCSSEnabled() &&
434 mHTMLCSSUtils->IsCSSEditableProperty(&aNode, &aProperty, aAttribute)) ||
435 // bgcolor is always done using CSS
436 aAttribute->EqualsLiteral("bgcolor");
438 if (useCSS) {
439 nsCOMPtr<dom::Element> tmp;
440 // We only add style="" to <span>s with no attributes (bug 746515). If we
441 // don't have one, we need to make one.
442 if (aNode.IsHTMLElement(nsGkAtoms::span) &&
443 !aNode.AsElement()->GetAttrCount()) {
444 tmp = aNode.AsElement();
445 } else {
446 tmp = InsertContainerAbove(&aNode, nsGkAtoms::span);
447 NS_ENSURE_STATE(tmp);
450 // Add the CSS styles corresponding to the HTML style request
451 int32_t count;
452 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(tmp->AsDOMNode(),
453 &aProperty, aAttribute,
454 &aValue, &count, false);
455 NS_ENSURE_SUCCESS(res, res);
456 return NS_OK;
459 // is it already the right kind of node, but with wrong attribute?
460 if (aNode.IsHTMLElement(&aProperty)) {
461 // Just set the attribute on it.
462 nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
463 return SetAttribute(elem, *aAttribute, aValue);
466 // ok, chuck it in its very own container
467 nsCOMPtr<Element> tmp = InsertContainerAbove(&aNode, &aProperty, attrAtom,
468 &aValue);
469 NS_ENSURE_STATE(tmp);
471 return NS_OK;
475 nsresult
476 nsHTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
477 nsIAtom& aProperty,
478 const nsAString* aAttribute,
479 const nsAString& aValue)
481 nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
482 nextSibling = aNode.GetNextSibling();
483 NS_ENSURE_STATE(aNode.GetParentNode());
484 OwningNonNull<nsINode> parent = *aNode.GetParentNode();
486 nsresult res = RemoveStyleInside(aNode, &aProperty, aAttribute);
487 NS_ENSURE_SUCCESS(res, res);
489 if (aNode.GetParentNode()) {
490 // The node is still where it was
491 return SetInlinePropertyOnNodeImpl(aNode, aProperty,
492 aAttribute, aValue);
495 // It's vanished. Use the old siblings for reference to construct a
496 // list. But first, verify that the previous/next siblings are still
497 // where we expect them; otherwise we have to give up.
498 if ((previousSibling && previousSibling->GetParentNode() != parent) ||
499 (nextSibling && nextSibling->GetParentNode() != parent)) {
500 return NS_ERROR_UNEXPECTED;
502 nsTArray<OwningNonNull<nsIContent>> nodesToSet;
503 nsCOMPtr<nsIContent> cur = previousSibling
504 ? previousSibling->GetNextSibling() : parent->GetFirstChild();
505 for (; cur && cur != nextSibling; cur = cur->GetNextSibling()) {
506 if (IsEditable(cur)) {
507 nodesToSet.AppendElement(*cur);
511 for (auto& node : nodesToSet) {
512 res = SetInlinePropertyOnNodeImpl(node, aProperty, aAttribute, aValue);
513 NS_ENSURE_SUCCESS(res, res);
516 return NS_OK;
520 nsresult
521 nsHTMLEditor::SplitStyleAboveRange(nsRange* inRange, nsIAtom* aProperty,
522 const nsAString* aAttribute)
524 NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
525 nsresult res;
526 nsCOMPtr<nsIDOMNode> startNode, endNode, origStartNode;
527 int32_t startOffset, endOffset;
529 res = inRange->GetStartContainer(getter_AddRefs(startNode));
530 NS_ENSURE_SUCCESS(res, res);
531 res = inRange->GetStartOffset(&startOffset);
532 NS_ENSURE_SUCCESS(res, res);
533 res = inRange->GetEndContainer(getter_AddRefs(endNode));
534 NS_ENSURE_SUCCESS(res, res);
535 res = inRange->GetEndOffset(&endOffset);
536 NS_ENSURE_SUCCESS(res, res);
538 origStartNode = startNode;
540 // split any matching style nodes above the start of range
542 nsAutoTrackDOMPoint tracker(mRangeUpdater, address_of(endNode), &endOffset);
543 res = SplitStyleAbovePoint(address_of(startNode), &startOffset, aProperty, aAttribute);
544 NS_ENSURE_SUCCESS(res, res);
547 // second verse, same as the first...
548 res = SplitStyleAbovePoint(address_of(endNode), &endOffset, aProperty, aAttribute);
549 NS_ENSURE_SUCCESS(res, res);
551 // reset the range
552 res = inRange->SetStart(startNode, startOffset);
553 NS_ENSURE_SUCCESS(res, res);
554 res = inRange->SetEnd(endNode, endOffset);
555 return res;
558 nsresult nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr<nsIDOMNode> *aNode,
559 int32_t *aOffset,
560 nsIAtom *aProperty, // null here means we split all properties
561 const nsAString *aAttribute,
562 nsCOMPtr<nsIDOMNode> *outLeftNode,
563 nsCOMPtr<nsIDOMNode> *outRightNode)
565 NS_ENSURE_TRUE(aNode && *aNode && aOffset, NS_ERROR_NULL_POINTER);
566 if (outLeftNode) *outLeftNode = nullptr;
567 if (outRightNode) *outRightNode = nullptr;
568 // split any matching style nodes above the node/offset
569 nsCOMPtr<nsIContent> node = do_QueryInterface(*aNode);
570 NS_ENSURE_STATE(node);
571 int32_t offset;
573 bool useCSS = IsCSSEnabled();
575 bool isSet;
576 while (node && !IsBlockNode(node) && node->GetParentNode() &&
577 IsEditable(node->GetParentNode())) {
578 isSet = false;
579 if (useCSS && mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty, aAttribute)) {
580 // the HTML style defined by aProperty/aAttribute has a CSS equivalence
581 // in this implementation for the node; let's check if it carries those css styles
582 nsAutoString firstValue;
583 mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(GetAsDOMNode(node),
584 aProperty, aAttribute, isSet, firstValue, nsHTMLCSSUtils::eSpecified);
586 if (// node is the correct inline prop
587 (aProperty && node->IsHTMLElement(aProperty)) ||
588 // node is href - test if really <a href=...
589 (aProperty == nsGkAtoms::href && nsHTMLEditUtils::IsLink(node)) ||
590 // or node is any prop, and we asked to split them all
591 (!aProperty && NodeIsProperty(GetAsDOMNode(node))) ||
592 // or the style is specified in the style attribute
593 isSet) {
594 // found a style node we need to split
595 nsCOMPtr<nsIContent> outLeftContent, outRightContent;
596 nsCOMPtr<nsIContent> nodeParam = do_QueryInterface(*aNode);
597 NS_ENSURE_STATE(nodeParam || !*aNode);
598 offset = SplitNodeDeep(*node, *nodeParam, *aOffset, EmptyContainers::yes,
599 getter_AddRefs(outLeftContent),
600 getter_AddRefs(outRightContent));
601 NS_ENSURE_TRUE(offset != -1, NS_ERROR_FAILURE);
602 // reset startNode/startOffset
603 *aNode = GetAsDOMNode(node->GetParent());
604 *aOffset = offset;
605 if (outLeftNode) {
606 *outLeftNode = GetAsDOMNode(outLeftContent);
608 if (outRightNode) {
609 *outRightNode = GetAsDOMNode(outRightContent);
612 node = node->GetParent();
614 return NS_OK;
617 nsresult
618 nsHTMLEditor::ClearStyle(nsCOMPtr<nsIDOMNode>* aNode, int32_t* aOffset,
619 nsIAtom* aProperty, const nsAString* aAttribute)
621 nsCOMPtr<nsIDOMNode> leftNode, rightNode, tmp;
622 nsresult res = SplitStyleAbovePoint(aNode, aOffset, aProperty, aAttribute,
623 address_of(leftNode),
624 address_of(rightNode));
625 NS_ENSURE_SUCCESS(res, res);
626 if (leftNode) {
627 bool bIsEmptyNode;
628 IsEmptyNode(leftNode, &bIsEmptyNode, false, true);
629 if (bIsEmptyNode) {
630 // delete leftNode if it became empty
631 res = DeleteNode(leftNode);
632 NS_ENSURE_SUCCESS(res, res);
635 if (rightNode) {
636 nsCOMPtr<nsINode> rightNode_ = do_QueryInterface(rightNode);
637 NS_ENSURE_STATE(rightNode_);
638 nsCOMPtr<nsIDOMNode> secondSplitParent =
639 GetAsDOMNode(GetLeftmostChild(rightNode_));
640 // don't try to split non-containers (br's, images, hr's, etc)
641 if (!secondSplitParent) {
642 secondSplitParent = rightNode;
644 nsCOMPtr<Element> savedBR;
645 if (!IsContainer(secondSplitParent)) {
646 if (nsTextEditUtils::IsBreak(secondSplitParent)) {
647 savedBR = do_QueryInterface(secondSplitParent);
648 NS_ENSURE_STATE(savedBR);
651 secondSplitParent->GetParentNode(getter_AddRefs(tmp));
652 secondSplitParent = tmp;
654 *aOffset = 0;
655 res = SplitStyleAbovePoint(address_of(secondSplitParent),
656 aOffset, aProperty, aAttribute,
657 address_of(leftNode), address_of(rightNode));
658 NS_ENSURE_SUCCESS(res, res);
659 // should be impossible to not get a new leftnode here
660 nsCOMPtr<nsINode> leftNode_ = do_QueryInterface(leftNode);
661 NS_ENSURE_TRUE(leftNode_, NS_ERROR_FAILURE);
662 nsCOMPtr<nsINode> newSelParent = GetLeftmostChild(leftNode_);
663 if (!newSelParent) {
664 newSelParent = do_QueryInterface(leftNode);
665 NS_ENSURE_STATE(newSelParent);
667 // If rightNode starts with a br, suck it out of right node and into
668 // leftNode. This is so we you don't revert back to the previous style
669 // if you happen to click at the end of a line.
670 if (savedBR) {
671 res = MoveNode(savedBR, newSelParent, 0);
672 NS_ENSURE_SUCCESS(res, res);
674 bool bIsEmptyNode;
675 IsEmptyNode(rightNode, &bIsEmptyNode, false, true);
676 if (bIsEmptyNode) {
677 // delete rightNode if it became empty
678 res = DeleteNode(rightNode);
679 NS_ENSURE_SUCCESS(res, res);
681 // remove the style on this new hierarchy
682 int32_t newSelOffset = 0;
684 // Track the point at the new hierarchy. This is so we can know where
685 // to put the selection after we call RemoveStyleInside().
686 // RemoveStyleInside() could remove any and all of those nodes, so I
687 // have to use the range tracking system to find the right spot to put
688 // selection.
689 nsAutoTrackDOMPoint tracker(mRangeUpdater,
690 address_of(newSelParent), &newSelOffset);
691 res = RemoveStyleInside(leftNode, aProperty, aAttribute);
692 NS_ENSURE_SUCCESS(res, res);
694 // reset our node offset values to the resulting new sel point
695 *aNode = GetAsDOMNode(newSelParent);
696 *aOffset = newSelOffset;
699 return NS_OK;
702 bool nsHTMLEditor::NodeIsProperty(nsIDOMNode *aNode)
704 NS_ENSURE_TRUE(aNode, false);
705 if (!IsContainer(aNode)) return false;
706 if (!IsEditable(aNode)) return false;
707 if (IsBlockNode(aNode)) return false;
708 if (NodeIsType(aNode, nsGkAtoms::a)) {
709 return false;
711 return true;
714 nsresult nsHTMLEditor::ApplyDefaultProperties()
716 nsresult res = NS_OK;
717 uint32_t j, defcon = mDefaultStyles.Length();
718 for (j=0; j<defcon; j++)
720 PropItem *propItem = mDefaultStyles[j];
721 NS_ENSURE_TRUE(propItem, NS_ERROR_NULL_POINTER);
722 res = SetInlineProperty(propItem->tag, propItem->attr, propItem->value);
723 NS_ENSURE_SUCCESS(res, res);
725 return res;
728 nsresult nsHTMLEditor::RemoveStyleInside(nsIDOMNode *aNode,
729 // null here means remove all properties
730 nsIAtom *aProperty,
731 const nsAString *aAttribute,
732 const bool aChildrenOnly)
734 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
735 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
736 NS_ENSURE_STATE(content);
738 return RemoveStyleInside(*content, aProperty, aAttribute, aChildrenOnly);
741 nsresult
742 nsHTMLEditor::RemoveStyleInside(nsIContent& aNode,
743 nsIAtom* aProperty,
744 const nsAString* aAttribute,
745 const bool aChildrenOnly /* = false */)
747 if (aNode.NodeType() == nsIDOMNode::TEXT_NODE) {
748 return NS_OK;
751 // first process the children
752 RefPtr<nsIContent> child = aNode.GetFirstChild();
753 while (child) {
754 // cache next sibling since we might remove child
755 nsCOMPtr<nsIContent> next = child->GetNextSibling();
756 nsresult res = RemoveStyleInside(*child, aProperty, aAttribute);
757 NS_ENSURE_SUCCESS(res, res);
758 child = next.forget();
761 // then process the node itself
762 if (!aChildrenOnly &&
764 // node is prop we asked for
765 (aProperty && aNode.NodeInfo()->NameAtom() == aProperty) ||
766 // but check for link (<a href=...)
767 (aProperty == nsGkAtoms::href && nsHTMLEditUtils::IsLink(&aNode)) ||
768 // and for named anchors
769 (aProperty == nsGkAtoms::name && nsHTMLEditUtils::IsNamedAnchor(&aNode)) ||
770 // or node is any prop and we asked for that
771 (!aProperty && NodeIsProperty(aNode.AsDOMNode()))
774 nsresult res;
775 // if we weren't passed an attribute, then we want to
776 // remove any matching inlinestyles entirely
777 if (!aAttribute || aAttribute->IsEmpty()) {
778 NS_NAMED_LITERAL_STRING(styleAttr, "style");
779 NS_NAMED_LITERAL_STRING(classAttr, "class");
781 bool hasStyleAttr = aNode.HasAttr(kNameSpaceID_None, nsGkAtoms::style);
782 bool hasClassAttr = aNode.HasAttr(kNameSpaceID_None, nsGkAtoms::_class);
783 if (aProperty && (hasStyleAttr || hasClassAttr)) {
784 // aNode carries inline styles or a class attribute so we can't
785 // just remove the element... We need to create above the element
786 // a span that will carry those styles or class, then we can delete
787 // the node.
788 nsCOMPtr<Element> spanNode =
789 InsertContainerAbove(&aNode, nsGkAtoms::span);
790 NS_ENSURE_STATE(spanNode);
791 res = CloneAttribute(styleAttr, spanNode->AsDOMNode(), aNode.AsDOMNode());
792 NS_ENSURE_SUCCESS(res, res);
793 res = CloneAttribute(classAttr, spanNode->AsDOMNode(), aNode.AsDOMNode());
794 NS_ENSURE_SUCCESS(res, res);
796 res = RemoveContainer(&aNode);
797 NS_ENSURE_SUCCESS(res, res);
798 } else {
799 // otherwise we just want to eliminate the attribute
800 nsCOMPtr<nsIAtom> attribute = do_GetAtom(*aAttribute);
801 if (aNode.HasAttr(kNameSpaceID_None, attribute)) {
802 // if this matching attribute is the ONLY one on the node,
803 // then remove the whole node. Otherwise just nix the attribute.
804 if (IsOnlyAttribute(&aNode, *aAttribute)) {
805 res = RemoveContainer(&aNode);
806 } else {
807 nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(&aNode);
808 NS_ENSURE_TRUE(elem, NS_ERROR_NULL_POINTER);
809 res = RemoveAttribute(elem, *aAttribute);
811 NS_ENSURE_SUCCESS(res, res);
816 if (!aChildrenOnly &&
817 mHTMLCSSUtils->IsCSSEditableProperty(&aNode, aProperty, aAttribute)) {
818 // the HTML style defined by aProperty/aAttribute has a CSS equivalence in
819 // this implementation for the node aNode; let's check if it carries those
820 // css styles
821 nsAutoString propertyValue;
822 bool isSet = mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(&aNode,
823 aProperty, aAttribute, propertyValue, nsHTMLCSSUtils::eSpecified);
824 if (isSet && aNode.IsElement()) {
825 // yes, tmp has the corresponding css declarations in its style attribute
826 // let's remove them
827 mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle(aNode.AsElement(),
828 aProperty,
829 aAttribute,
830 &propertyValue,
831 false);
832 // remove the node if it is a span or font, if its style attribute is
833 // empty or absent, and if it does not have a class nor an id
834 RemoveElementIfNoStyleOrIdOrClass(*aNode.AsElement());
838 if (!aChildrenOnly &&
840 // Or node is big or small and we are setting font size
841 aProperty == nsGkAtoms::font &&
842 (aNode.IsHTMLElement(nsGkAtoms::big) || aNode.IsHTMLElement(nsGkAtoms::small)) &&
843 (aAttribute && aAttribute->LowerCaseEqualsLiteral("size"))
846 // if we are setting font size, remove any nested bigs and smalls
847 return RemoveContainer(&aNode);
849 return NS_OK;
852 bool nsHTMLEditor::IsOnlyAttribute(nsIDOMNode *aNode,
853 const nsAString *aAttribute)
855 NS_ENSURE_TRUE(aNode && aAttribute, false); // ooops
857 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
858 NS_ENSURE_TRUE(content, false); // ooops
860 return IsOnlyAttribute(content, *aAttribute);
863 bool
864 nsHTMLEditor::IsOnlyAttribute(const nsIContent* aContent,
865 const nsAString& aAttribute)
867 MOZ_ASSERT(aContent);
869 uint32_t attrCount = aContent->GetAttrCount();
870 for (uint32_t i = 0; i < attrCount; ++i) {
871 const nsAttrName* name = aContent->GetAttrNameAt(i);
872 if (!name->NamespaceEquals(kNameSpaceID_None)) {
873 return false;
876 nsAutoString attrString;
877 name->LocalName()->ToString(attrString);
878 // if it's the attribute we know about, or a special _moz attribute,
879 // keep looking
880 if (!attrString.Equals(aAttribute, nsCaseInsensitiveStringComparator()) &&
881 !StringBeginsWith(attrString, NS_LITERAL_STRING("_moz"))) {
882 return false;
885 // if we made it through all of them without finding a real attribute
886 // other than aAttribute, then return true
887 return true;
890 bool nsHTMLEditor::HasAttr(nsIDOMNode* aNode,
891 const nsAString* aAttribute)
893 NS_ENSURE_TRUE(aNode, false);
894 if (!aAttribute || aAttribute->IsEmpty()) {
895 // everybody has the 'null' attribute
896 return true;
899 // get element
900 nsCOMPtr<dom::Element> element = do_QueryInterface(aNode);
901 NS_ENSURE_TRUE(element, false);
903 nsCOMPtr<nsIAtom> atom = do_GetAtom(*aAttribute);
904 NS_ENSURE_TRUE(atom, false);
906 return element->HasAttr(kNameSpaceID_None, atom);
910 nsresult
911 nsHTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange* inRange)
913 NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
914 nsresult res;
915 nsCOMPtr<nsIDOMNode> startNode, endNode, parent, tmp;
916 int32_t startOffset, endOffset, tmpOffset;
918 res = inRange->GetStartContainer(getter_AddRefs(startNode));
919 NS_ENSURE_SUCCESS(res, res);
920 res = inRange->GetStartOffset(&startOffset);
921 NS_ENSURE_SUCCESS(res, res);
922 res = inRange->GetEndContainer(getter_AddRefs(endNode));
923 NS_ENSURE_SUCCESS(res, res);
924 res = inRange->GetEndOffset(&endOffset);
925 NS_ENSURE_SUCCESS(res, res);
927 tmp = startNode;
928 while ( tmp &&
929 !nsTextEditUtils::IsBody(tmp) &&
930 !nsHTMLEditUtils::IsNamedAnchor(tmp))
932 parent = GetNodeLocation(tmp, &tmpOffset);
933 tmp = parent;
935 NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER);
936 if (nsHTMLEditUtils::IsNamedAnchor(tmp))
938 parent = GetNodeLocation(tmp, &tmpOffset);
939 startNode = parent;
940 startOffset = tmpOffset;
943 tmp = endNode;
944 while ( tmp &&
945 !nsTextEditUtils::IsBody(tmp) &&
946 !nsHTMLEditUtils::IsNamedAnchor(tmp))
948 parent = GetNodeLocation(tmp, &tmpOffset);
949 tmp = parent;
951 NS_ENSURE_TRUE(tmp, NS_ERROR_NULL_POINTER);
952 if (nsHTMLEditUtils::IsNamedAnchor(tmp))
954 parent = GetNodeLocation(tmp, &tmpOffset);
955 endNode = parent;
956 endOffset = tmpOffset + 1;
959 res = inRange->SetStart(startNode, startOffset);
960 NS_ENSURE_SUCCESS(res, res);
961 res = inRange->SetEnd(endNode, endOffset);
962 return res;
965 nsresult
966 nsHTMLEditor::PromoteInlineRange(nsRange* inRange)
968 NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
969 nsresult res;
970 nsCOMPtr<nsIDOMNode> startNode, endNode, parent;
971 int32_t startOffset, endOffset;
973 res = inRange->GetStartContainer(getter_AddRefs(startNode));
974 NS_ENSURE_SUCCESS(res, res);
975 res = inRange->GetStartOffset(&startOffset);
976 NS_ENSURE_SUCCESS(res, res);
977 res = inRange->GetEndContainer(getter_AddRefs(endNode));
978 NS_ENSURE_SUCCESS(res, res);
979 res = inRange->GetEndOffset(&endOffset);
980 NS_ENSURE_SUCCESS(res, res);
982 while ( startNode &&
983 !nsTextEditUtils::IsBody(startNode) &&
984 IsEditable(startNode) &&
985 IsAtFrontOfNode(startNode, startOffset) )
987 parent = GetNodeLocation(startNode, &startOffset);
988 startNode = parent;
990 NS_ENSURE_TRUE(startNode, NS_ERROR_NULL_POINTER);
992 while ( endNode &&
993 !nsTextEditUtils::IsBody(endNode) &&
994 IsEditable(endNode) &&
995 IsAtEndOfNode(endNode, endOffset) )
997 parent = GetNodeLocation(endNode, &endOffset);
998 endNode = parent;
999 endOffset++; // we are AFTER this node
1001 NS_ENSURE_TRUE(endNode, NS_ERROR_NULL_POINTER);
1003 res = inRange->SetStart(startNode, startOffset);
1004 NS_ENSURE_SUCCESS(res, res);
1005 res = inRange->SetEnd(endNode, endOffset);
1006 return res;
1009 bool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode *aNode, int32_t aOffset)
1011 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1012 NS_ENSURE_TRUE(node, false);
1013 if (!aOffset) {
1014 return true;
1017 if (IsTextNode(aNode))
1019 return false;
1021 else
1023 nsCOMPtr<nsIContent> firstNode = GetFirstEditableChild(*node);
1024 NS_ENSURE_TRUE(firstNode, true);
1025 int32_t offset = node->IndexOf(firstNode);
1026 if (offset < aOffset) return false;
1027 return true;
1031 bool nsHTMLEditor::IsAtEndOfNode(nsIDOMNode *aNode, int32_t aOffset)
1033 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1034 NS_ENSURE_TRUE(node, false);
1035 uint32_t len = node->Length();
1036 if (aOffset == (int32_t)len) return true;
1038 if (IsTextNode(aNode))
1040 return false;
1042 else
1044 nsCOMPtr<nsIContent> lastNode = GetLastEditableChild(*node);
1045 NS_ENSURE_TRUE(lastNode, true);
1046 int32_t offset = node->IndexOf(lastNode);
1047 if (offset < aOffset) return true;
1048 return false;
1053 nsresult
1054 nsHTMLEditor::GetInlinePropertyBase(nsIAtom& aProperty,
1055 const nsAString* aAttribute,
1056 const nsAString* aValue,
1057 bool* aFirst,
1058 bool* aAny,
1059 bool* aAll,
1060 nsAString* outValue,
1061 bool aCheckDefaults)
1063 *aAny = false;
1064 *aAll = true;
1065 *aFirst = false;
1066 bool first = true;
1068 RefPtr<Selection> selection = GetSelection();
1069 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1071 bool isCollapsed = selection->Collapsed();
1072 RefPtr<nsRange> range = selection->GetRangeAt(0);
1073 // XXX: Should be a while loop, to get each separate range
1074 // XXX: ERROR_HANDLING can currentItem be null?
1075 if (range) {
1076 // For each range, set a flag
1077 bool firstNodeInRange = true;
1079 if (isCollapsed) {
1080 nsCOMPtr<nsINode> collapsedNode = range->GetStartParent();
1081 NS_ENSURE_TRUE(collapsedNode, NS_ERROR_FAILURE);
1082 bool isSet, theSetting;
1083 nsString tOutString;
1084 if (aAttribute) {
1085 nsString tString(*aAttribute);
1086 mTypeInState->GetTypingState(isSet, theSetting, &aProperty, tString,
1087 &tOutString);
1088 if (outValue) {
1089 outValue->Assign(tOutString);
1091 } else {
1092 mTypeInState->GetTypingState(isSet, theSetting, &aProperty);
1094 if (isSet) {
1095 *aFirst = *aAny = *aAll = theSetting;
1096 return NS_OK;
1099 if (mHTMLCSSUtils->IsCSSEditableProperty(collapsedNode, &aProperty,
1100 aAttribute)) {
1101 if (aValue) {
1102 tOutString.Assign(*aValue);
1104 *aFirst = *aAny = *aAll =
1105 mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(collapsedNode,
1106 &aProperty, aAttribute, tOutString, nsHTMLCSSUtils::eComputed);
1107 if (outValue) {
1108 outValue->Assign(tOutString);
1110 return NS_OK;
1113 isSet = IsTextPropertySetByContent(collapsedNode, &aProperty,
1114 aAttribute, aValue, outValue);
1115 *aFirst = *aAny = *aAll = isSet;
1117 if (!isSet && aCheckDefaults) {
1118 // Style not set, but if it is a default then it will appear if content
1119 // is inserted, so we should report it as set (analogous to
1120 // TypeInState).
1121 int32_t index;
1122 if (aAttribute && TypeInState::FindPropInList(&aProperty, *aAttribute,
1123 outValue, mDefaultStyles,
1124 index)) {
1125 *aFirst = *aAny = *aAll = true;
1126 if (outValue) {
1127 outValue->Assign(mDefaultStyles[index]->value);
1131 return NS_OK;
1134 // Non-collapsed selection
1135 nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
1137 nsAutoString firstValue, theValue;
1139 nsCOMPtr<nsINode> endNode = range->GetEndParent();
1140 int32_t endOffset = range->EndOffset();
1142 for (iter->Init(range); !iter->IsDone(); iter->Next()) {
1143 if (!iter->GetCurrentNode()->IsContent()) {
1144 continue;
1146 nsCOMPtr<nsIContent> content = iter->GetCurrentNode()->AsContent();
1148 if (content->IsHTMLElement(nsGkAtoms::body)) {
1149 break;
1152 // just ignore any non-editable nodes
1153 if (content->GetAsText() && (!IsEditable(content) ||
1154 IsEmptyTextNode(this, content))) {
1155 continue;
1157 if (content->GetAsText()) {
1158 if (!isCollapsed && first && firstNodeInRange) {
1159 firstNodeInRange = false;
1160 if (range->StartOffset() == (int32_t)content->Length()) {
1161 continue;
1163 } else if (content == endNode && !endOffset) {
1164 continue;
1166 } else if (content->IsElement()) {
1167 // handle non-text leaf nodes here
1168 continue;
1171 bool isSet = false;
1172 if (first) {
1173 if (mHTMLCSSUtils->IsCSSEditableProperty(content, &aProperty,
1174 aAttribute)) {
1175 // The HTML styles defined by aProperty/aAttribute have a CSS
1176 // equivalence in this implementation for node; let's check if it
1177 // carries those CSS styles
1178 if (aValue) {
1179 firstValue.Assign(*aValue);
1181 isSet = mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
1182 &aProperty, aAttribute, firstValue, nsHTMLCSSUtils::eComputed);
1183 } else {
1184 isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
1185 aValue, &firstValue);
1187 *aFirst = isSet;
1188 first = false;
1189 if (outValue) {
1190 *outValue = firstValue;
1192 } else {
1193 if (mHTMLCSSUtils->IsCSSEditableProperty(content, &aProperty,
1194 aAttribute)) {
1195 // The HTML styles defined by aProperty/aAttribute have a CSS
1196 // equivalence in this implementation for node; let's check if it
1197 // carries those CSS styles
1198 if (aValue) {
1199 theValue.Assign(*aValue);
1201 isSet = mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(content,
1202 &aProperty, aAttribute, theValue, nsHTMLCSSUtils::eComputed);
1203 } else {
1204 isSet = IsTextPropertySetByContent(content, &aProperty, aAttribute,
1205 aValue, &theValue);
1207 if (firstValue != theValue) {
1208 *aAll = false;
1212 if (isSet) {
1213 *aAny = true;
1214 } else {
1215 *aAll = false;
1219 if (!*aAny) {
1220 // make sure that if none of the selection is set, we don't report all is
1221 // set
1222 *aAll = false;
1224 return NS_OK;
1228 NS_IMETHODIMP nsHTMLEditor::GetInlineProperty(nsIAtom *aProperty,
1229 const nsAString &aAttribute,
1230 const nsAString &aValue,
1231 bool *aFirst,
1232 bool *aAny,
1233 bool *aAll)
1235 NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
1236 const nsAString *att = nullptr;
1237 if (!aAttribute.IsEmpty())
1238 att = &aAttribute;
1239 const nsAString *val = nullptr;
1240 if (!aValue.IsEmpty())
1241 val = &aValue;
1242 return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, nullptr);
1246 NS_IMETHODIMP nsHTMLEditor::GetInlinePropertyWithAttrValue(nsIAtom *aProperty,
1247 const nsAString &aAttribute,
1248 const nsAString &aValue,
1249 bool *aFirst,
1250 bool *aAny,
1251 bool *aAll,
1252 nsAString &outValue)
1254 NS_ENSURE_TRUE(aProperty && aFirst && aAny && aAll, NS_ERROR_NULL_POINTER);
1255 const nsAString *att = nullptr;
1256 if (!aAttribute.IsEmpty())
1257 att = &aAttribute;
1258 const nsAString *val = nullptr;
1259 if (!aValue.IsEmpty())
1260 val = &aValue;
1261 return GetInlinePropertyBase(*aProperty, att, val, aFirst, aAny, aAll, &outValue);
1265 NS_IMETHODIMP nsHTMLEditor::RemoveAllInlineProperties()
1267 nsAutoEditBatch batchIt(this);
1268 nsAutoRules beginRulesSniffing(this, EditAction::resetTextProperties, nsIEditor::eNext);
1270 nsresult res = RemoveInlinePropertyImpl(nullptr, nullptr);
1271 NS_ENSURE_SUCCESS(res, res);
1272 return ApplyDefaultProperties();
1275 NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsAString &aAttribute)
1277 return RemoveInlinePropertyImpl(aProperty, &aAttribute);
1280 nsresult
1281 nsHTMLEditor::RemoveInlinePropertyImpl(nsIAtom* aProperty,
1282 const nsAString* aAttribute)
1284 MOZ_ASSERT_IF(aProperty, aAttribute);
1285 NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
1286 ForceCompositionEnd();
1288 RefPtr<Selection> selection = GetSelection();
1289 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1291 if (selection->Collapsed()) {
1292 // Manipulating text attributes on a collapsed selection only sets state
1293 // for the next text insertion
1295 // For links, aProperty uses "href", use "a" instead
1296 if (aProperty == nsGkAtoms::href || aProperty == nsGkAtoms::name) {
1297 aProperty = nsGkAtoms::a;
1300 if (aProperty) {
1301 mTypeInState->ClearProp(aProperty, *aAttribute);
1302 } else {
1303 mTypeInState->ClearAllProps();
1305 return NS_OK;
1308 nsAutoEditBatch batchIt(this);
1309 nsAutoRules beginRulesSniffing(this, EditAction::removeTextProperty,
1310 nsIEditor::eNext);
1311 nsAutoSelectionReset selectionResetter(selection, this);
1312 nsAutoTxnsConserveSelection dontSpazMySelection(this);
1314 bool cancel, handled;
1315 nsTextRulesInfo ruleInfo(EditAction::removeTextProperty);
1316 // Protect the edit rules object from dying
1317 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
1318 nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1319 NS_ENSURE_SUCCESS(res, res);
1320 if (!cancel && !handled) {
1321 // Loop through the ranges in the selection
1322 uint32_t rangeCount = selection->RangeCount();
1323 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
1324 RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
1325 if (aProperty == nsGkAtoms::name) {
1326 // Promote range if it starts or end in a named anchor and we want to
1327 // remove named anchors
1328 res = PromoteRangeIfStartsOrEndsInNamedAnchor(range);
1329 } else {
1330 // Adjust range to include any ancestors whose children are entirely
1331 // selected
1332 res = PromoteInlineRange(range);
1334 NS_ENSURE_SUCCESS(res, res);
1336 // Remove this style from ancestors of our range endpoints, splitting
1337 // them as appropriate
1338 res = SplitStyleAboveRange(range, aProperty, aAttribute);
1339 NS_ENSURE_SUCCESS(res, res);
1341 // Check for easy case: both range endpoints in same text node
1342 nsCOMPtr<nsINode> startNode = range->GetStartParent();
1343 nsCOMPtr<nsINode> endNode = range->GetEndParent();
1344 if (startNode && startNode == endNode && startNode->GetAsText()) {
1345 // We're done with this range!
1346 if (IsCSSEnabled() &&
1347 mHTMLCSSUtils->IsCSSEditableProperty(startNode, aProperty,
1348 aAttribute)) {
1349 // The HTML style defined by aProperty/aAttribute has a CSS
1350 // equivalence in this implementation for startNode
1351 if (mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(startNode,
1352 aProperty, aAttribute, EmptyString(),
1353 nsHTMLCSSUtils::eComputed)) {
1354 // startNode's computed style indicates the CSS equivalence to the
1355 // HTML style to remove is applied; but we found no element in the
1356 // ancestors of startNode carrying specified styles; assume it
1357 // comes from a rule and try to insert a span "inverting" the style
1358 if (mHTMLCSSUtils->IsCSSInvertible(*aProperty, aAttribute)) {
1359 NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
1360 SetInlinePropertyOnTextNode(*startNode->GetAsText(),
1361 range->StartOffset(),
1362 range->EndOffset(), *aProperty,
1363 aAttribute, value);
1367 } else {
1368 // Not the easy case. Range not contained in single text node.
1369 nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
1371 nsTArray<nsCOMPtr<nsINode>> arrayOfNodes;
1373 // Iterate range and build up array
1374 for (iter->Init(range); !iter->IsDone(); iter->Next()) {
1375 nsCOMPtr<nsINode> node = iter->GetCurrentNode();
1376 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
1378 if (IsEditable(node)) {
1379 arrayOfNodes.AppendElement(node);
1383 // Loop through the list, remove the property on each node
1384 for (auto& node : arrayOfNodes) {
1385 res = RemoveStyleInside(GetAsDOMNode(node), aProperty, aAttribute);
1386 NS_ENSURE_SUCCESS(res, res);
1387 if (IsCSSEnabled() &&
1388 mHTMLCSSUtils->IsCSSEditableProperty(node, aProperty,
1389 aAttribute) &&
1390 mHTMLCSSUtils->IsCSSEquivalentToHTMLInlineStyleSet(node,
1391 aProperty, aAttribute, EmptyString(),
1392 nsHTMLCSSUtils::eComputed) &&
1393 // startNode's computed style indicates the CSS equivalence to
1394 // the HTML style to remove is applied; but we found no element
1395 // in the ancestors of startNode carrying specified styles;
1396 // assume it comes from a rule and let's try to insert a span
1397 // "inverting" the style
1398 mHTMLCSSUtils->IsCSSInvertible(*aProperty, aAttribute)) {
1399 NS_NAMED_LITERAL_STRING(value, "-moz-editor-invert-value");
1400 SetInlinePropertyOnNode(*node->AsContent(), *aProperty,
1401 aAttribute, value);
1407 if (!cancel) {
1408 // Post-process
1409 res = mRules->DidDoAction(selection, &ruleInfo, res);
1410 NS_ENSURE_SUCCESS(res, res);
1412 return NS_OK;
1415 NS_IMETHODIMP nsHTMLEditor::IncreaseFontSize()
1417 return RelativeFontChange(FontSize::incr);
1420 NS_IMETHODIMP nsHTMLEditor::DecreaseFontSize()
1422 return RelativeFontChange(FontSize::decr);
1425 nsresult
1426 nsHTMLEditor::RelativeFontChange(FontSize aDir)
1428 ForceCompositionEnd();
1430 // Get the selection
1431 RefPtr<Selection> selection = GetSelection();
1432 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1433 // If selection is collapsed, set typing state
1434 if (selection->Collapsed()) {
1435 nsIAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big :
1436 *nsGkAtoms::small;
1438 // Let's see in what kind of element the selection is
1439 NS_ENSURE_TRUE(selection->RangeCount() &&
1440 selection->GetRangeAt(0)->GetStartParent(), NS_OK);
1441 OwningNonNull<nsINode> selectedNode =
1442 *selection->GetRangeAt(0)->GetStartParent();
1443 if (IsTextNode(selectedNode)) {
1444 NS_ENSURE_TRUE(selectedNode->GetParentNode(), NS_OK);
1445 selectedNode = *selectedNode->GetParentNode();
1447 if (!CanContainTag(selectedNode, atom)) {
1448 return NS_OK;
1451 // Manipulating text attributes on a collapsed selection only sets state
1452 // for the next text insertion
1453 mTypeInState->SetProp(&atom, EmptyString(), EmptyString());
1454 return NS_OK;
1457 // Wrap with txn batching, rules sniffing, and selection preservation code
1458 nsAutoEditBatch batchIt(this);
1459 nsAutoRules beginRulesSniffing(this, EditAction::setTextProperty,
1460 nsIEditor::eNext);
1461 nsAutoSelectionReset selectionResetter(selection, this);
1462 nsAutoTxnsConserveSelection dontSpazMySelection(this);
1464 // Loop through the ranges in the selection
1465 uint32_t rangeCount = selection->RangeCount();
1466 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
1467 RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
1469 // Adjust range to include any ancestors with entirely selected children
1470 nsresult res = PromoteInlineRange(range);
1471 NS_ENSURE_SUCCESS(res, res);
1473 // Check for easy case: both range endpoints in same text node
1474 nsCOMPtr<nsINode> startNode = range->GetStartParent();
1475 nsCOMPtr<nsINode> endNode = range->GetEndParent();
1476 if (startNode == endNode && IsTextNode(startNode)) {
1477 res = RelativeFontChangeOnTextNode(aDir == FontSize::incr ? +1 : -1,
1478 static_cast<nsIDOMCharacterData*>(startNode->AsDOMNode()),
1479 range->StartOffset(), range->EndOffset());
1480 NS_ENSURE_SUCCESS(res, res);
1481 } else {
1482 // Not the easy case. Range not contained in single text node. There
1483 // are up to three phases here. There are all the nodes reported by the
1484 // subtree iterator to be processed. And there are potentially a
1485 // starting textnode and an ending textnode which are only partially
1486 // contained by the range.
1488 // Let's handle the nodes reported by the iterator. These nodes are
1489 // entirely contained in the selection range. We build up a list of them
1490 // (since doing operations on the document during iteration would perturb
1491 // the iterator).
1493 OwningNonNull<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
1495 // Iterate range and build up array
1496 res = iter->Init(range);
1497 if (NS_SUCCEEDED(res)) {
1498 nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
1499 for (; !iter->IsDone(); iter->Next()) {
1500 NS_ENSURE_TRUE(iter->GetCurrentNode()->IsContent(), NS_ERROR_FAILURE);
1501 OwningNonNull<nsIContent> node = *iter->GetCurrentNode()->AsContent();
1503 if (IsEditable(node)) {
1504 arrayOfNodes.AppendElement(node);
1508 // Now that we have the list, do the font size change on each node
1509 for (auto& node : arrayOfNodes) {
1510 res = RelativeFontChangeOnNode(aDir == FontSize::incr ? +1 : -1,
1511 node);
1512 NS_ENSURE_SUCCESS(res, res);
1515 // Now check the start and end parents of the range to see if they need
1516 // to be separately handled (they do if they are text nodes, due to how
1517 // the subtree iterator works - it will not have reported them).
1518 if (IsTextNode(startNode) && IsEditable(startNode)) {
1519 res = RelativeFontChangeOnTextNode(aDir == FontSize::incr ? +1 : -1,
1520 static_cast<nsIDOMCharacterData*>(startNode->AsDOMNode()),
1521 range->StartOffset(), startNode->Length());
1522 NS_ENSURE_SUCCESS(res, res);
1524 if (IsTextNode(endNode) && IsEditable(endNode)) {
1525 res = RelativeFontChangeOnTextNode(aDir == FontSize::incr ? +1 : -1,
1526 static_cast<nsIDOMCharacterData*>(endNode->AsDOMNode()),
1527 0, range->EndOffset());
1528 NS_ENSURE_SUCCESS(res, res);
1533 return NS_OK;
1536 nsresult
1537 nsHTMLEditor::RelativeFontChangeOnTextNode( int32_t aSizeChange,
1538 nsIDOMCharacterData *aTextNode,
1539 int32_t aStartOffset,
1540 int32_t aEndOffset)
1542 // Can only change font size by + or - 1
1543 if ( !( (aSizeChange==1) || (aSizeChange==-1) ) )
1544 return NS_ERROR_ILLEGAL_VALUE;
1545 nsCOMPtr<nsIContent> textNode = do_QueryInterface(aTextNode);
1546 NS_ENSURE_TRUE(textNode, NS_ERROR_NULL_POINTER);
1548 // don't need to do anything if no characters actually selected
1549 if (aStartOffset == aEndOffset) return NS_OK;
1551 if (!textNode->GetParentNode() ||
1552 !CanContainTag(*textNode->GetParentNode(), *nsGkAtoms::big)) {
1553 return NS_OK;
1556 nsCOMPtr<nsIDOMNode> tmp;
1557 nsCOMPtr<nsIContent> node = do_QueryInterface(aTextNode);
1558 NS_ENSURE_STATE(node);
1560 // do we need to split the text node?
1561 uint32_t textLen;
1562 aTextNode->GetLength(&textLen);
1564 // -1 is a magic value meaning to the end of node
1565 if (aEndOffset == -1) aEndOffset = textLen;
1567 nsresult res = NS_OK;
1568 if ( (uint32_t)aEndOffset != textLen )
1570 // we need to split off back of text node
1571 res = SplitNode(GetAsDOMNode(node), aEndOffset, getter_AddRefs(tmp));
1572 NS_ENSURE_SUCCESS(res, res);
1573 // remember left node
1574 node = do_QueryInterface(tmp);
1576 if ( aStartOffset )
1578 // we need to split off front of text node
1579 res = SplitNode(GetAsDOMNode(node), aStartOffset, getter_AddRefs(tmp));
1580 NS_ENSURE_SUCCESS(res, res);
1583 // look for siblings that are correct type of node
1584 nsIAtom* nodeType = aSizeChange == 1 ? nsGkAtoms::big : nsGkAtoms::small;
1585 nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(node);
1586 if (sibling && sibling->IsHTMLElement(nodeType)) {
1587 // previous sib is already right kind of inline node; slide this over into it
1588 res = MoveNode(node, sibling, -1);
1589 return res;
1591 sibling = GetNextHTMLSibling(node);
1592 if (sibling && sibling->IsHTMLElement(nodeType)) {
1593 // following sib is already right kind of inline node; slide this over into it
1594 res = MoveNode(node, sibling, 0);
1595 return res;
1598 // else reparent the node inside font node with appropriate relative size
1599 nsCOMPtr<Element> newElement = InsertContainerAbove(node, nodeType);
1600 NS_ENSURE_STATE(newElement);
1602 return NS_OK;
1606 nsresult
1607 nsHTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange, nsINode* aNode)
1609 MOZ_ASSERT(aNode);
1611 /* This routine looks for all the font nodes in the tree rooted by aNode,
1612 including aNode itself, looking for font nodes that have the size attr
1613 set. Any such nodes need to have big or small put inside them, since
1614 they override any big/small that are above them.
1617 // Can only change font size by + or - 1
1618 if (aSizeChange != 1 && aSizeChange != -1) {
1619 return NS_ERROR_ILLEGAL_VALUE;
1622 // If this is a font node with size, put big/small inside it.
1623 if (aNode->IsHTMLElement(nsGkAtoms::font) &&
1624 aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
1625 // Cycle through children and adjust relative font size.
1626 for (uint32_t i = aNode->GetChildCount(); i--; ) {
1627 nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
1628 NS_ENSURE_SUCCESS(rv, rv);
1631 // RelativeFontChangeOnNode already calls us recursively,
1632 // so we don't need to check our children again.
1633 return NS_OK;
1636 // Otherwise cycle through the children.
1637 for (uint32_t i = aNode->GetChildCount(); i--; ) {
1638 nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode->GetChildAt(i));
1639 NS_ENSURE_SUCCESS(rv, rv);
1642 return NS_OK;
1646 nsresult
1647 nsHTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange, nsIContent* aNode)
1649 MOZ_ASSERT(aNode);
1650 // Can only change font size by + or - 1
1651 if (aSizeChange != 1 && aSizeChange != -1) {
1652 return NS_ERROR_ILLEGAL_VALUE;
1655 nsIAtom* atom;
1656 if (aSizeChange == 1) {
1657 atom = nsGkAtoms::big;
1658 } else {
1659 atom = nsGkAtoms::small;
1662 // Is it the opposite of what we want?
1663 if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
1664 (aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
1665 // first populate any nested font tags that have the size attr set
1666 nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
1667 NS_ENSURE_SUCCESS(rv, rv);
1668 // in that case, just remove this node and pull up the children
1669 return RemoveContainer(aNode);
1672 // can it be put inside a "big" or "small"?
1673 if (TagCanContain(*atom, *aNode)) {
1674 // first populate any nested font tags that have the size attr set
1675 nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
1676 NS_ENSURE_SUCCESS(rv, rv);
1678 // ok, chuck it in.
1679 // first look at siblings of aNode for matching bigs or smalls.
1680 // if we find one, move aNode into it.
1681 nsIContent* sibling = GetPriorHTMLSibling(aNode);
1682 if (sibling && sibling->IsHTMLElement(atom)) {
1683 // previous sib is already right kind of inline node; slide this over into it
1684 return MoveNode(aNode, sibling, -1);
1687 sibling = GetNextHTMLSibling(aNode);
1688 if (sibling && sibling->IsHTMLElement(atom)) {
1689 // following sib is already right kind of inline node; slide this over into it
1690 return MoveNode(aNode, sibling, 0);
1693 // else insert it above aNode
1694 nsCOMPtr<Element> newElement = InsertContainerAbove(aNode, atom);
1695 NS_ENSURE_STATE(newElement);
1697 return NS_OK;
1700 // none of the above? then cycle through the children.
1701 // MOOSE: we should group the children together if possible
1702 // into a single "big" or "small". For the moment they are
1703 // each getting their own.
1704 for (uint32_t i = aNode->GetChildCount(); i--; ) {
1705 nsresult rv = RelativeFontChangeOnNode(aSizeChange, aNode->GetChildAt(i));
1706 NS_ENSURE_SUCCESS(rv, rv);
1709 return NS_OK;
1712 NS_IMETHODIMP
1713 nsHTMLEditor::GetFontFaceState(bool *aMixed, nsAString &outFace)
1715 NS_ENSURE_TRUE(aMixed, NS_ERROR_FAILURE);
1716 *aMixed = true;
1717 outFace.Truncate();
1719 nsresult res;
1720 bool first, any, all;
1722 NS_NAMED_LITERAL_STRING(attr, "face");
1723 res = GetInlinePropertyBase(*nsGkAtoms::font, &attr, nullptr, &first, &any,
1724 &all, &outFace);
1725 NS_ENSURE_SUCCESS(res, res);
1726 if (any && !all) return res; // mixed
1727 if (all)
1729 *aMixed = false;
1730 return res;
1733 // if there is no font face, check for tt
1734 res = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
1735 &all,nullptr);
1736 NS_ENSURE_SUCCESS(res, res);
1737 if (any && !all) return res; // mixed
1738 if (all)
1740 *aMixed = false;
1741 outFace.AssignLiteral("tt");
1744 if (!any)
1746 // there was no font face attrs of any kind. We are in normal font.
1747 outFace.Truncate();
1748 *aMixed = false;
1750 return res;
1753 NS_IMETHODIMP
1754 nsHTMLEditor::GetFontColorState(bool *aMixed, nsAString &aOutColor)
1756 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
1757 *aMixed = true;
1758 aOutColor.Truncate();
1760 nsresult res;
1761 NS_NAMED_LITERAL_STRING(colorStr, "color");
1762 bool first, any, all;
1764 res = GetInlinePropertyBase(*nsGkAtoms::font, &colorStr, nullptr, &first,
1765 &any, &all, &aOutColor);
1766 NS_ENSURE_SUCCESS(res, res);
1767 if (any && !all) return res; // mixed
1768 if (all)
1770 *aMixed = false;
1771 return res;
1774 if (!any)
1776 // there was no font color attrs of any kind..
1777 aOutColor.Truncate();
1778 *aMixed = false;
1780 return res;
1783 // the return value is true only if the instance of the HTML editor we created
1784 // can handle CSS styles (for instance, Composer can, Messenger can't) and if
1785 // the CSS preference is checked
1786 nsresult
1787 nsHTMLEditor::GetIsCSSEnabled(bool *aIsCSSEnabled)
1789 *aIsCSSEnabled = IsCSSEnabled();
1790 return NS_OK;
1793 static bool
1794 HasNonEmptyAttribute(dom::Element* aElement, nsIAtom* aName)
1796 MOZ_ASSERT(aElement);
1798 nsAutoString value;
1799 return aElement->GetAttr(kNameSpaceID_None, aName, value) && !value.IsEmpty();
1802 bool
1803 nsHTMLEditor::HasStyleOrIdOrClass(dom::Element* aElement)
1805 MOZ_ASSERT(aElement);
1807 // remove the node if its style attribute is empty or absent,
1808 // and if it does not have a class nor an id
1809 return HasNonEmptyAttribute(aElement, nsGkAtoms::style) ||
1810 HasNonEmptyAttribute(aElement, nsGkAtoms::_class) ||
1811 HasNonEmptyAttribute(aElement, nsGkAtoms::id);
1814 nsresult
1815 nsHTMLEditor::RemoveElementIfNoStyleOrIdOrClass(dom::Element& aElement)
1817 // early way out if node is not the right kind of element
1818 if ((!aElement.IsHTMLElement(nsGkAtoms::span) &&
1819 !aElement.IsHTMLElement(nsGkAtoms::font)) ||
1820 HasStyleOrIdOrClass(&aElement)) {
1821 return NS_OK;
1824 return RemoveContainer(&aElement);