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"
14 #include "nsCaseTreatment.h"
15 #include "nsComponentManagerUtils.h"
17 #include "nsEditRules.h"
19 #include "nsEditorUtils.h"
21 #include "nsGkAtoms.h"
22 #include "nsHTMLCSSUtils.h"
23 #include "nsHTMLEditUtils.h"
24 #include "nsHTMLEditor.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"
35 #include "nsISupportsImpl.h"
36 #include "nsLiteralString.h"
38 #include "nsReadableUtils.h"
39 #include "nsSelectionState.h"
41 #include "nsStringFwd.h"
43 #include "nsTextEditRules.h"
44 #include "nsTextEditUtils.h"
45 #include "nsUnicharUtils.h"
50 using namespace mozilla
;
51 using namespace mozilla::dom
;
54 IsEmptyTextNode(nsHTMLEditor
* aThis
, nsINode
* aNode
)
56 bool isEmptyTextNode
= false;
57 return nsEditor::IsTextNode(aNode
) &&
58 NS_SUCCEEDED(aThis
->IsEmptyNode(aNode
, &isEmptyTextNode
)) &&
62 NS_IMETHODIMP
nsHTMLEditor::AddDefaultProperty(nsIAtom
*aProperty
,
63 const nsAString
& aAttribute
,
64 const nsAString
& aValue
)
68 nsString
attr(aAttribute
);
69 if (TypeInState::FindPropInList(aProperty
, attr
, &outValue
, mDefaultStyles
, index
))
71 PropItem
*item
= mDefaultStyles
[index
];
76 nsString
value(aValue
);
77 PropItem
*propItem
= new PropItem(aProperty
, attr
, value
);
78 mDefaultStyles
.AppendElement(propItem
);
83 NS_IMETHODIMP
nsHTMLEditor::RemoveDefaultProperty(nsIAtom
*aProperty
,
84 const nsAString
& aAttribute
,
85 const nsAString
& aValue
)
89 nsString
attr(aAttribute
);
90 if (TypeInState::FindPropInList(aProperty
, attr
, &outValue
, mDefaultStyles
, index
))
92 delete mDefaultStyles
[index
];
93 mDefaultStyles
.RemoveElementAt(index
);
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();
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
);
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
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(),
158 *aProperty
, &aAttribute
, aValue
);
159 NS_ENSURE_SUCCESS(res
, res
);
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
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
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
);
222 return mRules
->DidDoAction(selection
, &ruleInfo
, res
);
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?
232 nsHTMLEditor::IsSimpleModifiableNode(nsIContent
* aContent
,
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
);
246 // First check for <b>, <i>, etc.
247 if (element
->IsHTMLElement(aProperty
) && !element
->GetAttrCount() &&
248 (!aAttribute
|| aAttribute
->IsEmpty())) {
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
)))) {
263 // Now look for things like <font>
264 if (aAttribute
&& !aAttribute
->IsEmpty()) {
265 nsCOMPtr
<nsIAtom
> atom
= do_GetAtom(*aAttribute
);
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).
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
283 if (!mHTMLCSSUtils
->IsCSSEditableProperty(element
, aProperty
, aAttribute
) ||
284 !element
->IsHTMLElement(nsGkAtoms::span
) ||
285 element
->GetAttrCount() != 1 ||
286 !element
->HasAttr(kNameSpaceID_None
, nsGkAtoms::style
)) {
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
,
299 /*suppress transaction*/ true);
301 return mHTMLCSSUtils
->ElementsSameStyle(newSpan
, element
);
306 nsHTMLEditor::SetInlinePropertyOnTextNode(Text
& aText
,
307 int32_t aStartOffset
,
310 const nsAString
* aAttribute
,
311 const nsAString
& aValue
)
313 if (!aText
.GetParentNode() ||
314 !CanContainTag(*aText
.GetParentNode(), aProperty
)) {
318 // Don't need to do anything if no characters actually selected
319 if (aStartOffset
== aEndOffset
) {
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
)) {
331 } else if (IsTextPropertySetByContent(&aText
, &aProperty
, aAttribute
,
336 // Do we need to split the text node?
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());
346 // We need to split off front of text node
347 SplitNode(*text
, aStartOffset
, rv
);
348 NS_ENSURE_TRUE(!rv
.Failed(), rv
.StealNSResult());
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
);
371 nsHTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent
& aNode
,
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();
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
,
397 NS_ENSURE_SUCCESS(rv
, rv
);
403 // First check if there's an adjacent sibling we can put our node into.
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
);
416 if (IsSimpleModifiableNode(nextSibling
, &aProperty
, aAttribute
, &aValue
)) {
417 res
= MoveNode(&aNode
, nextSibling
, 0);
418 NS_ENSURE_SUCCESS(res
, res
);
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
)) {
428 } else if (IsTextPropertySetByContent(&aNode
, &aProperty
,
429 aAttribute
, &aValue
)) {
433 bool useCSS
= (IsCSSEnabled() &&
434 mHTMLCSSUtils
->IsCSSEditableProperty(&aNode
, &aProperty
, aAttribute
)) ||
435 // bgcolor is always done using CSS
436 aAttribute
->EqualsLiteral("bgcolor");
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();
446 tmp
= InsertContainerAbove(&aNode
, nsGkAtoms::span
);
447 NS_ENSURE_STATE(tmp
);
450 // Add the CSS styles corresponding to the HTML style request
452 res
= mHTMLCSSUtils
->SetCSSEquivalentToHTMLStyle(tmp
->AsDOMNode(),
453 &aProperty
, aAttribute
,
454 &aValue
, &count
, false);
455 NS_ENSURE_SUCCESS(res
, res
);
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
,
469 NS_ENSURE_STATE(tmp
);
476 nsHTMLEditor::SetInlinePropertyOnNode(nsIContent
& aNode
,
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
,
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
);
521 nsHTMLEditor::SplitStyleAboveRange(nsRange
* inRange
, nsIAtom
* aProperty
,
522 const nsAString
* aAttribute
)
524 NS_ENSURE_TRUE(inRange
, NS_ERROR_NULL_POINTER
);
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
);
552 res
= inRange
->SetStart(startNode
, startOffset
);
553 NS_ENSURE_SUCCESS(res
, res
);
554 res
= inRange
->SetEnd(endNode
, endOffset
);
558 nsresult
nsHTMLEditor::SplitStyleAbovePoint(nsCOMPtr
<nsIDOMNode
> *aNode
,
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
);
573 bool useCSS
= IsCSSEnabled();
576 while (node
&& !IsBlockNode(node
) && node
->GetParentNode() &&
577 IsEditable(node
->GetParentNode())) {
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
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());
606 *outLeftNode
= GetAsDOMNode(outLeftContent
);
609 *outRightNode
= GetAsDOMNode(outRightContent
);
612 node
= node
->GetParent();
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
);
628 IsEmptyNode(leftNode
, &bIsEmptyNode
, false, true);
630 // delete leftNode if it became empty
631 res
= DeleteNode(leftNode
);
632 NS_ENSURE_SUCCESS(res
, res
);
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
;
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_
);
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.
671 res
= MoveNode(savedBR
, newSelParent
, 0);
672 NS_ENSURE_SUCCESS(res
, res
);
675 IsEmptyNode(rightNode
, &bIsEmptyNode
, false, true);
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
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
;
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
)) {
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
);
728 nsresult
nsHTMLEditor::RemoveStyleInside(nsIDOMNode
*aNode
,
729 // null here means remove all properties
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
);
742 nsHTMLEditor::RemoveStyleInside(nsIContent
& aNode
,
744 const nsAString
* aAttribute
,
745 const bool aChildrenOnly
/* = false */)
747 if (aNode
.NodeType() == nsIDOMNode::TEXT_NODE
) {
751 // first process the children
752 RefPtr
<nsIContent
> child
= aNode
.GetFirstChild();
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()))
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
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
);
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
);
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
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
827 mHTMLCSSUtils
->RemoveCSSEquivalentToHTMLStyle(aNode
.AsElement(),
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
);
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
);
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
)) {
876 nsAutoString attrString
;
877 name
->LocalName()->ToString(attrString
);
878 // if it's the attribute we know about, or a special _moz attribute,
880 if (!attrString
.Equals(aAttribute
, nsCaseInsensitiveStringComparator()) &&
881 !StringBeginsWith(attrString
, NS_LITERAL_STRING("_moz"))) {
885 // if we made it through all of them without finding a real attribute
886 // other than aAttribute, then 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
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
);
911 nsHTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange
* inRange
)
913 NS_ENSURE_TRUE(inRange
, NS_ERROR_NULL_POINTER
);
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
);
929 !nsTextEditUtils::IsBody(tmp
) &&
930 !nsHTMLEditUtils::IsNamedAnchor(tmp
))
932 parent
= GetNodeLocation(tmp
, &tmpOffset
);
935 NS_ENSURE_TRUE(tmp
, NS_ERROR_NULL_POINTER
);
936 if (nsHTMLEditUtils::IsNamedAnchor(tmp
))
938 parent
= GetNodeLocation(tmp
, &tmpOffset
);
940 startOffset
= tmpOffset
;
945 !nsTextEditUtils::IsBody(tmp
) &&
946 !nsHTMLEditUtils::IsNamedAnchor(tmp
))
948 parent
= GetNodeLocation(tmp
, &tmpOffset
);
951 NS_ENSURE_TRUE(tmp
, NS_ERROR_NULL_POINTER
);
952 if (nsHTMLEditUtils::IsNamedAnchor(tmp
))
954 parent
= GetNodeLocation(tmp
, &tmpOffset
);
956 endOffset
= tmpOffset
+ 1;
959 res
= inRange
->SetStart(startNode
, startOffset
);
960 NS_ENSURE_SUCCESS(res
, res
);
961 res
= inRange
->SetEnd(endNode
, endOffset
);
966 nsHTMLEditor::PromoteInlineRange(nsRange
* inRange
)
968 NS_ENSURE_TRUE(inRange
, NS_ERROR_NULL_POINTER
);
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
);
983 !nsTextEditUtils::IsBody(startNode
) &&
984 IsEditable(startNode
) &&
985 IsAtFrontOfNode(startNode
, startOffset
) )
987 parent
= GetNodeLocation(startNode
, &startOffset
);
990 NS_ENSURE_TRUE(startNode
, NS_ERROR_NULL_POINTER
);
993 !nsTextEditUtils::IsBody(endNode
) &&
994 IsEditable(endNode
) &&
995 IsAtEndOfNode(endNode
, endOffset
) )
997 parent
= GetNodeLocation(endNode
, &endOffset
);
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
);
1009 bool nsHTMLEditor::IsAtFrontOfNode(nsIDOMNode
*aNode
, int32_t aOffset
)
1011 nsCOMPtr
<nsINode
> node
= do_QueryInterface(aNode
);
1012 NS_ENSURE_TRUE(node
, false);
1017 if (IsTextNode(aNode
))
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;
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
))
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;
1054 nsHTMLEditor::GetInlinePropertyBase(nsIAtom
& aProperty
,
1055 const nsAString
* aAttribute
,
1056 const nsAString
* aValue
,
1060 nsAString
* outValue
,
1061 bool aCheckDefaults
)
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?
1076 // For each range, set a flag
1077 bool firstNodeInRange
= true;
1080 nsCOMPtr
<nsINode
> collapsedNode
= range
->GetStartParent();
1081 NS_ENSURE_TRUE(collapsedNode
, NS_ERROR_FAILURE
);
1082 bool isSet
, theSetting
;
1083 nsString tOutString
;
1085 nsString
tString(*aAttribute
);
1086 mTypeInState
->GetTypingState(isSet
, theSetting
, &aProperty
, tString
,
1089 outValue
->Assign(tOutString
);
1092 mTypeInState
->GetTypingState(isSet
, theSetting
, &aProperty
);
1095 *aFirst
= *aAny
= *aAll
= theSetting
;
1099 if (mHTMLCSSUtils
->IsCSSEditableProperty(collapsedNode
, &aProperty
,
1102 tOutString
.Assign(*aValue
);
1104 *aFirst
= *aAny
= *aAll
=
1105 mHTMLCSSUtils
->IsCSSEquivalentToHTMLInlineStyleSet(collapsedNode
,
1106 &aProperty
, aAttribute
, tOutString
, nsHTMLCSSUtils::eComputed
);
1108 outValue
->Assign(tOutString
);
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
1122 if (aAttribute
&& TypeInState::FindPropInList(&aProperty
, *aAttribute
,
1123 outValue
, mDefaultStyles
,
1125 *aFirst
= *aAny
= *aAll
= true;
1127 outValue
->Assign(mDefaultStyles
[index
]->value
);
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()) {
1146 nsCOMPtr
<nsIContent
> content
= iter
->GetCurrentNode()->AsContent();
1148 if (content
->IsHTMLElement(nsGkAtoms::body
)) {
1152 // just ignore any non-editable nodes
1153 if (content
->GetAsText() && (!IsEditable(content
) ||
1154 IsEmptyTextNode(this, content
))) {
1157 if (content
->GetAsText()) {
1158 if (!isCollapsed
&& first
&& firstNodeInRange
) {
1159 firstNodeInRange
= false;
1160 if (range
->StartOffset() == (int32_t)content
->Length()) {
1163 } else if (content
== endNode
&& !endOffset
) {
1166 } else if (content
->IsElement()) {
1167 // handle non-text leaf nodes here
1173 if (mHTMLCSSUtils
->IsCSSEditableProperty(content
, &aProperty
,
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
1179 firstValue
.Assign(*aValue
);
1181 isSet
= mHTMLCSSUtils
->IsCSSEquivalentToHTMLInlineStyleSet(content
,
1182 &aProperty
, aAttribute
, firstValue
, nsHTMLCSSUtils::eComputed
);
1184 isSet
= IsTextPropertySetByContent(content
, &aProperty
, aAttribute
,
1185 aValue
, &firstValue
);
1190 *outValue
= firstValue
;
1193 if (mHTMLCSSUtils
->IsCSSEditableProperty(content
, &aProperty
,
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
1199 theValue
.Assign(*aValue
);
1201 isSet
= mHTMLCSSUtils
->IsCSSEquivalentToHTMLInlineStyleSet(content
,
1202 &aProperty
, aAttribute
, theValue
, nsHTMLCSSUtils::eComputed
);
1204 isSet
= IsTextPropertySetByContent(content
, &aProperty
, aAttribute
,
1207 if (firstValue
!= theValue
) {
1220 // make sure that if none of the selection is set, we don't report all is
1228 NS_IMETHODIMP
nsHTMLEditor::GetInlineProperty(nsIAtom
*aProperty
,
1229 const nsAString
&aAttribute
,
1230 const nsAString
&aValue
,
1235 NS_ENSURE_TRUE(aProperty
&& aFirst
&& aAny
&& aAll
, NS_ERROR_NULL_POINTER
);
1236 const nsAString
*att
= nullptr;
1237 if (!aAttribute
.IsEmpty())
1239 const nsAString
*val
= nullptr;
1240 if (!aValue
.IsEmpty())
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
,
1252 nsAString
&outValue
)
1254 NS_ENSURE_TRUE(aProperty
&& aFirst
&& aAny
&& aAll
, NS_ERROR_NULL_POINTER
);
1255 const nsAString
*att
= nullptr;
1256 if (!aAttribute
.IsEmpty())
1258 const nsAString
*val
= nullptr;
1259 if (!aValue
.IsEmpty())
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
);
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
;
1301 mTypeInState
->ClearProp(aProperty
, *aAttribute
);
1303 mTypeInState
->ClearAllProps();
1308 nsAutoEditBatch
batchIt(this);
1309 nsAutoRules
beginRulesSniffing(this, EditAction::removeTextProperty
,
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
);
1330 // Adjust range to include any ancestors whose children are entirely
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
,
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
,
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
,
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
,
1409 res
= mRules
->DidDoAction(selection
, &ruleInfo
, res
);
1410 NS_ENSURE_SUCCESS(res
, res
);
1415 NS_IMETHODIMP
nsHTMLEditor::IncreaseFontSize()
1417 return RelativeFontChange(FontSize::incr
);
1420 NS_IMETHODIMP
nsHTMLEditor::DecreaseFontSize()
1422 return RelativeFontChange(FontSize::decr
);
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
:
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
)) {
1451 // Manipulating text attributes on a collapsed selection only sets state
1452 // for the next text insertion
1453 mTypeInState
->SetProp(&atom
, EmptyString(), EmptyString());
1457 // Wrap with txn batching, rules sniffing, and selection preservation code
1458 nsAutoEditBatch
batchIt(this);
1459 nsAutoRules
beginRulesSniffing(this, EditAction::setTextProperty
,
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
);
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
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,
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
);
1537 nsHTMLEditor::RelativeFontChangeOnTextNode( int32_t aSizeChange
,
1538 nsIDOMCharacterData
*aTextNode
,
1539 int32_t aStartOffset
,
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
)) {
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?
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
);
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);
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);
1598 // else reparent the node inside font node with appropriate relative size
1599 nsCOMPtr
<Element
> newElement
= InsertContainerAbove(node
, nodeType
);
1600 NS_ENSURE_STATE(newElement
);
1607 nsHTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange
, nsINode
* 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.
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
);
1647 nsHTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange
, nsIContent
* aNode
)
1650 // Can only change font size by + or - 1
1651 if (aSizeChange
!= 1 && aSizeChange
!= -1) {
1652 return NS_ERROR_ILLEGAL_VALUE
;
1656 if (aSizeChange
== 1) {
1657 atom
= nsGkAtoms::big
;
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
);
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
);
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
);
1713 nsHTMLEditor::GetFontFaceState(bool *aMixed
, nsAString
&outFace
)
1715 NS_ENSURE_TRUE(aMixed
, NS_ERROR_FAILURE
);
1720 bool first
, any
, all
;
1722 NS_NAMED_LITERAL_STRING(attr
, "face");
1723 res
= GetInlinePropertyBase(*nsGkAtoms::font
, &attr
, nullptr, &first
, &any
,
1725 NS_ENSURE_SUCCESS(res
, res
);
1726 if (any
&& !all
) return res
; // mixed
1733 // if there is no font face, check for tt
1734 res
= GetInlinePropertyBase(*nsGkAtoms::tt
, nullptr, nullptr, &first
, &any
,
1736 NS_ENSURE_SUCCESS(res
, res
);
1737 if (any
&& !all
) return res
; // mixed
1741 outFace
.AssignLiteral("tt");
1746 // there was no font face attrs of any kind. We are in normal font.
1754 nsHTMLEditor::GetFontColorState(bool *aMixed
, nsAString
&aOutColor
)
1756 NS_ENSURE_TRUE(aMixed
, NS_ERROR_NULL_POINTER
);
1758 aOutColor
.Truncate();
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
1776 // there was no font color attrs of any kind..
1777 aOutColor
.Truncate();
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
1787 nsHTMLEditor::GetIsCSSEnabled(bool *aIsCSSEnabled
)
1789 *aIsCSSEnabled
= IsCSSEnabled();
1794 HasNonEmptyAttribute(dom::Element
* aElement
, nsIAtom
* aName
)
1796 MOZ_ASSERT(aElement
);
1799 return aElement
->GetAttr(kNameSpaceID_None
, aName
, value
) && !value
.IsEmpty();
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
);
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
)) {
1824 return RemoveContainer(&aElement
);