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/. */
6 #include "HTMLEditor.h"
8 #include "HTMLEditUtils.h"
9 #include "TypeInState.h"
10 #include "mozilla/Assertions.h"
11 #include "mozilla/ContentIterator.h"
12 #include "mozilla/EditAction.h"
13 #include "mozilla/EditorUtils.h"
14 #include "mozilla/SelectionState.h"
15 #include "mozilla/mozalloc.h"
16 #include "mozilla/dom/AncestorIterator.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/HTMLBRElement.h"
19 #include "mozilla/dom/Selection.h"
20 #include "nsAString.h"
21 #include "nsAttrName.h"
23 #include "nsCaseTreatment.h"
24 #include "nsComponentManagerUtils.h"
27 #include "nsGkAtoms.h"
29 #include "nsIContent.h"
30 #include "nsNameSpaceManager.h"
32 #include "nsIPrincipal.h"
33 #include "nsISupportsImpl.h"
34 #include "nsLiteralString.h"
36 #include "nsReadableUtils.h"
38 #include "nsStringFwd.h"
39 #include "nsStyledElement.h"
41 #include "nsUnicharUtils.h"
50 using ChildBlockBoundary
= HTMLEditUtils::ChildBlockBoundary
;
52 nsresult
HTMLEditor::SetInlinePropertyAsAction(nsAtom
& aProperty
,
54 const nsAString
& aValue
,
55 nsIPrincipal
* aPrincipal
) {
56 AutoEditActionDataSetter
editActionData(
58 HTMLEditUtils::GetEditActionForFormatText(aProperty
, aAttribute
, true),
60 switch (editActionData
.GetEditAction()) {
61 case EditAction::eSetFontFamilyProperty
:
62 MOZ_ASSERT(!aValue
.IsVoid());
63 // XXX Should we trim unnecessary white-spaces?
64 editActionData
.SetData(aValue
);
66 case EditAction::eSetColorProperty
:
67 case EditAction::eSetBackgroundColorPropertyInline
:
68 editActionData
.SetColorData(aValue
);
74 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
76 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
77 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
78 return EditorBase::ToGenericNSResult(rv
);
81 // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection
82 // into view after setting the new style.
83 AutoPlaceholderBatch
treatAsOneTransaction(*this,
84 ScrollSelectionIntoView::No
);
86 nsAtom
* property
= &aProperty
;
87 nsAtom
* attribute
= aAttribute
;
88 nsAutoString
value(aValue
);
90 if (&aProperty
== nsGkAtoms::sup
) {
91 // Superscript and Subscript styles are mutually exclusive.
92 nsresult rv
= RemoveInlinePropertyInternal(nsGkAtoms::sub
, nullptr,
93 RemoveRelatedElements::No
);
96 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::sub, "
97 "RemoveRelatedElements::No) failed");
98 return EditorBase::ToGenericNSResult(rv
);
100 } else if (&aProperty
== nsGkAtoms::sub
) {
101 // Superscript and Subscript styles are mutually exclusive.
102 nsresult rv
= RemoveInlinePropertyInternal(nsGkAtoms::sup
, nullptr,
103 RemoveRelatedElements::No
);
106 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::sup, "
107 "RemoveRelatedElements::No) failed");
108 return EditorBase::ToGenericNSResult(rv
);
111 // Handling `<tt>` element code was implemented for composer (bug 115922).
112 // This shouldn't work with `Document.execCommand()`. Currently, aPrincipal
113 // is set only when the root caller is Document::ExecCommand() so that
114 // we should handle `<tt>` element only when aPrincipal is nullptr that
115 // must be only when XUL command is executed on composer.
116 else if (!aPrincipal
) {
117 if (&aProperty
== nsGkAtoms::tt
) {
118 nsresult rv
= RemoveInlinePropertyInternal(
119 nsGkAtoms::font
, nsGkAtoms::face
, RemoveRelatedElements::No
);
122 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::font, "
123 "nsGkAtoms::face, RemoveRelatedElements::No) failed");
124 return EditorBase::ToGenericNSResult(rv
);
126 } else if (&aProperty
== nsGkAtoms::font
&& aAttribute
== nsGkAtoms::face
) {
127 if (!value
.LowerCaseEqualsASCII("tt")) {
128 nsresult rv
= RemoveInlinePropertyInternal(nsGkAtoms::tt
, nullptr,
129 RemoveRelatedElements::No
);
132 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::tt, "
133 "RemoveRelatedElements::No) failed");
134 return EditorBase::ToGenericNSResult(rv
);
137 nsresult rv
= RemoveInlinePropertyInternal(
138 nsGkAtoms::font
, nsGkAtoms::face
, RemoveRelatedElements::No
);
141 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::font, "
142 "nsGkAtoms::face, RemoveRelatedElements::No) failed");
143 return EditorBase::ToGenericNSResult(rv
);
145 // Override property, attribute and value if the new font face value is
147 property
= nsGkAtoms::tt
;
153 rv
= SetInlinePropertyInternal(MOZ_KnownLive(*property
),
154 MOZ_KnownLive(attribute
), value
);
155 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
156 "HTMLEditor::SetInlinePropertyInternal() failed");
157 return EditorBase::ToGenericNSResult(rv
);
160 NS_IMETHODIMP
HTMLEditor::SetInlineProperty(const nsAString
& aProperty
,
161 const nsAString
& aAttribute
,
162 const nsAString
& aValue
) {
163 RefPtr
<nsAtom
> property
= NS_Atomize(aProperty
);
164 if (NS_WARN_IF(!property
)) {
165 return NS_ERROR_INVALID_ARG
;
167 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
168 AutoEditActionDataSetter
editActionData(
170 HTMLEditUtils::GetEditActionForFormatText(*property
, attribute
, true));
171 switch (editActionData
.GetEditAction()) {
172 case EditAction::eSetFontFamilyProperty
:
173 MOZ_ASSERT(!aValue
.IsVoid());
174 // XXX Should we trim unnecessary white-spaces?
175 editActionData
.SetData(aValue
);
177 case EditAction::eSetColorProperty
:
178 case EditAction::eSetBackgroundColorPropertyInline
:
179 editActionData
.SetColorData(aValue
);
184 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
186 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
187 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
188 return EditorBase::ToGenericNSResult(rv
);
190 rv
= SetInlinePropertyInternal(*property
, MOZ_KnownLive(attribute
), aValue
);
191 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
192 "HTMLEditor::SetInlinePropertyInternal() failed");
193 return EditorBase::ToGenericNSResult(rv
);
196 nsresult
HTMLEditor::SetInlinePropertyInternal(
197 nsAtom
& aProperty
, nsAtom
* aAttribute
, const nsAString
& aAttributeValue
) {
198 MOZ_ASSERT(IsEditActionDataAvailable());
200 if (NS_WARN_IF(!mInitSucceeded
)) {
201 return NS_ERROR_NOT_INITIALIZED
;
204 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
205 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
206 "EditorBase::CommitComposition() failed, but ignored");
208 if (SelectionRefPtr()->IsCollapsed()) {
209 // Manipulating text attributes on a collapsed selection only sets state
210 // for the next text insertion
211 mTypeInState
->SetProp(&aProperty
, aAttribute
, aAttributeValue
);
215 // XXX Shouldn't we return before calling `CommitComposition()`?
216 if (IsPlaintextEditor()) {
220 EditActionResult result
= CanHandleHTMLEditSubAction();
221 if (result
.Failed() || result
.Canceled()) {
222 NS_WARNING_ASSERTION(result
.Succeeded(),
223 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
227 AutoPlaceholderBatch
treatAsOneTransaction(*this,
228 ScrollSelectionIntoView::Yes
);
229 IgnoredErrorResult ignoredError
;
230 AutoEditSubActionNotifier
startToHandleEditSubAction(
231 *this, EditSubAction::eInsertElement
, nsIEditor::eNext
, ignoredError
);
232 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
233 return ignoredError
.StealNSResult();
235 NS_WARNING_ASSERTION(
236 !ignoredError
.Failed(),
237 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
240 AutoSelectionRestorer
restoreSelectionLater(*this);
241 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
243 // Loop through the ranges in the selection
244 // XXX This is different from `SetCSSBackgroundColorWithTransaction()`.
245 // It refers `Selection::GetRangeAt()` in each time. The result may
246 // be different if mutation event listener changes the `Selection`.
247 AutoSelectionRangeArray
arrayOfRanges(SelectionRefPtr());
248 for (auto& range
: arrayOfRanges
.mRanges
) {
249 // Adjust range to include any ancestors whose children are entirely
251 nsresult rv
= PromoteInlineRange(*range
);
253 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
257 // XXX Shouldn't we skip the range if it's been collapsed by mutation
260 EditorDOMPoint
startOfRange(range
->StartRef());
261 EditorDOMPoint
endOfRange(range
->EndRef());
262 if (NS_WARN_IF(!startOfRange
.IsSet()) ||
263 NS_WARN_IF(!endOfRange
.IsSet())) {
267 // If range is in a text node, apply new style simply.
268 if (startOfRange
.GetContainer() == endOfRange
.GetContainer() &&
269 startOfRange
.IsInTextNode()) {
270 nsresult rv
= SetInlinePropertyOnTextNode(
271 MOZ_KnownLive(*startOfRange
.GetContainerAsText()),
272 startOfRange
.Offset(), endOfRange
.Offset(), aProperty
, aAttribute
,
275 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
281 // Collect editable nodes which are entirely contained in the range.
282 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
283 ContentSubtreeIterator subtreeIter
;
284 // If there is no node which is entirely in the range,
285 // `ContentSubtreeIterator::Init()` fails, but this is possible case,
287 if (NS_SUCCEEDED(subtreeIter
.Init(range
))) {
288 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
289 nsINode
* node
= subtreeIter
.GetCurrentNode();
290 if (NS_WARN_IF(!node
)) {
291 return NS_ERROR_FAILURE
;
293 if (node
->IsContent() && EditorUtils::IsEditableContent(
294 *node
->AsContent(), EditorType::HTML
)) {
295 arrayOfContents
.AppendElement(*node
->AsContent());
300 // If start node is a text node, apply new style to a part of it.
301 if (startOfRange
.IsInTextNode() &&
302 EditorUtils::IsEditableContent(*startOfRange
.ContainerAsText(),
304 nsresult rv
= SetInlinePropertyOnTextNode(
305 MOZ_KnownLive(*startOfRange
.GetContainerAsText()),
306 startOfRange
.Offset(), startOfRange
.GetContainer()->Length(),
307 aProperty
, aAttribute
, aAttributeValue
);
309 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
314 // Then, apply new style to all nodes in the range entirely.
315 for (auto& content
: arrayOfContents
) {
316 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
318 nsresult rv
= SetInlinePropertyOnNode(
319 MOZ_KnownLive(*content
), aProperty
, aAttribute
, aAttributeValue
);
320 if (NS_WARN_IF(Destroyed())) {
321 return NS_ERROR_EDITOR_DESTROYED
;
324 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
329 // Finally, if end node is a text node, apply new style to a part of it.
330 if (endOfRange
.IsInTextNode() &&
331 EditorUtils::IsEditableContent(*endOfRange
.GetContainerAsText(),
333 nsresult rv
= SetInlinePropertyOnTextNode(
334 MOZ_KnownLive(*endOfRange
.GetContainerAsText()), 0,
335 endOfRange
.Offset(), aProperty
, aAttribute
, aAttributeValue
);
337 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
343 // Restoring `Selection` may have destroyed us.
344 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
: NS_OK
;
347 Result
<bool, nsresult
> HTMLEditor::ElementIsGoodContainerForTheStyle(
348 Element
& aElement
, nsAtom
* aProperty
, nsAtom
* aAttribute
,
349 const nsAString
* aValue
) {
350 // aContent can be null, in which case we'll return false in a few lines
351 MOZ_ASSERT(aProperty
);
352 MOZ_ASSERT_IF(aAttribute
, aValue
);
354 // First check for <b>, <i>, etc.
355 if (aElement
.IsHTMLElement(aProperty
) && !aElement
.GetAttrCount() &&
360 // Special cases for various equivalencies: <strong>, <em>, <s>
361 if (!aElement
.GetAttrCount() &&
362 ((aProperty
== nsGkAtoms::b
&&
363 aElement
.IsHTMLElement(nsGkAtoms::strong
)) ||
364 (aProperty
== nsGkAtoms::i
&& aElement
.IsHTMLElement(nsGkAtoms::em
)) ||
365 (aProperty
== nsGkAtoms::strike
&&
366 aElement
.IsHTMLElement(nsGkAtoms::s
)))) {
370 // Now look for things like <font>
373 if (aElement
.IsHTMLElement(aProperty
) &&
374 IsOnlyAttribute(&aElement
, aAttribute
) &&
375 aElement
.GetAttr(kNameSpaceID_None
, aAttribute
, attrValue
) &&
376 attrValue
.Equals(*aValue
, nsCaseInsensitiveStringComparator
)) {
377 // This is not quite correct, because it excludes cases like
378 // <font face=000> being the same as <font face=#000000>.
379 // Property-specific handling is needed (bug 760211).
384 // No luck so far. Now we check for a <span> with a single style=""
385 // attribute that sets only the style we're looking for, if this type of
387 if (!CSSEditUtils::IsCSSEditableProperty(&aElement
, aProperty
, aAttribute
) ||
388 !aElement
.IsHTMLElement(nsGkAtoms::span
) ||
389 aElement
.GetAttrCount() != 1 ||
390 !aElement
.HasAttr(kNameSpaceID_None
, nsGkAtoms::style
)) {
394 // Some CSS styles are not so simple. For instance, underline is
395 // "text-decoration: underline", which decomposes into four different text-*
396 // properties. So for now, we just create a span, add the desired style, and
397 // see if it matches.
398 RefPtr
<Element
> newSpanElement
= CreateHTMLContent(nsGkAtoms::span
);
399 if (!newSpanElement
) {
400 NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed");
403 nsStyledElement
* styledNewSpanElement
=
404 nsStyledElement::FromNode(newSpanElement
);
405 if (!styledNewSpanElement
) {
408 // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
410 Result
<int32_t, nsresult
> result
=
411 mCSSEditUtils
->SetCSSEquivalentToHTMLStyleWithoutTransaction(
412 MOZ_KnownLive(*styledNewSpanElement
), aProperty
, aAttribute
, aValue
);
413 if (result
.isErr()) {
414 // The call shouldn't return destroyed error because it must be
415 // impossible to run script with modifying the new orphan node.
416 MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?");
417 if (NS_WARN_IF(result
.inspectErr() == NS_ERROR_EDITOR_DESTROYED
)) {
418 return Err(NS_ERROR_EDITOR_DESTROYED
);
422 nsStyledElement
* styledElement
= nsStyledElement::FromNode(&aElement
);
423 if (!styledElement
) {
426 return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement
,
430 nsresult
HTMLEditor::SetInlinePropertyOnTextNode(
431 Text
& aText
, uint32_t aStartOffset
, uint32_t aEndOffset
, nsAtom
& aProperty
,
432 nsAtom
* aAttribute
, const nsAString
& aValue
) {
433 if (!aText
.GetParentNode() ||
434 !HTMLEditUtils::CanNodeContain(*aText
.GetParentNode(), aProperty
)) {
438 // Don't need to do anything if no characters actually selected
439 if (aStartOffset
== aEndOffset
) {
443 // Don't need to do anything if property already set on node
444 if (CSSEditUtils::IsCSSEditableProperty(&aText
, &aProperty
, aAttribute
)) {
445 // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
446 // for node; let's check if it carries those CSS styles
447 nsAutoString
value(aValue
);
448 if (CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
449 aText
, &aProperty
, aAttribute
, value
)) {
452 } else if (IsTextPropertySetByContent(&aText
, &aProperty
, aAttribute
,
457 // Make the range an independent node.
458 nsCOMPtr
<nsIContent
> textNodeForTheRange
= &aText
;
460 // Split at the end of the range.
461 EditorDOMPoint
atEnd(textNodeForTheRange
, aEndOffset
);
462 if (!atEnd
.IsEndOfContainer()) {
463 // We need to split off back of text node
465 textNodeForTheRange
= SplitNodeWithTransaction(atEnd
, error
);
466 if (NS_WARN_IF(Destroyed())) {
467 error
= NS_ERROR_EDITOR_DESTROYED
;
469 if (error
.Failed()) {
470 NS_WARNING_ASSERTION(error
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
),
471 "HTMLEditor::SplitNodeWithTransaction() failed");
472 return error
.StealNSResult();
476 // Split at the start of the range.
477 EditorDOMPoint
atStart(textNodeForTheRange
, aStartOffset
);
478 if (!atStart
.IsStartOfContainer()) {
479 // We need to split off front of text node
481 nsCOMPtr
<nsIContent
> newLeftNode
= SplitNodeWithTransaction(atStart
, error
);
482 if (NS_WARN_IF(Destroyed())) {
483 error
= NS_ERROR_EDITOR_DESTROYED
;
485 if (error
.Failed()) {
486 NS_WARNING_ASSERTION(error
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
),
487 "HTMLEditor::SplitNodeWithTransaction() failed");
488 return error
.StealNSResult();
490 Unused
<< newLeftNode
;
494 // Look for siblings that are correct type of node
495 nsIContent
* sibling
= GetPriorHTMLSibling(textNodeForTheRange
);
496 if (sibling
&& sibling
->IsElement()) {
497 OwningNonNull
<Element
> element(*sibling
->AsElement());
498 Result
<bool, nsresult
> result
= ElementIsGoodContainerForTheStyle(
499 element
, &aProperty
, aAttribute
, &aValue
);
500 if (result
.isErr()) {
501 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
502 return result
.unwrapErr();
504 if (result
.inspect()) {
505 // Previous sib is already right kind of inline node; slide this over
507 MoveNodeToEndWithTransaction(*textNodeForTheRange
, element
);
508 if (NS_WARN_IF(Destroyed())) {
509 return NS_ERROR_EDITOR_DESTROYED
;
511 NS_WARNING_ASSERTION(
513 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
517 sibling
= GetNextHTMLSibling(textNodeForTheRange
);
518 if (sibling
&& sibling
->IsElement()) {
519 OwningNonNull
<Element
> element(*sibling
->AsElement());
520 Result
<bool, nsresult
> result
= ElementIsGoodContainerForTheStyle(
521 element
, &aProperty
, aAttribute
, &aValue
);
522 if (result
.isErr()) {
523 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
524 return result
.unwrapErr();
526 if (result
.inspect()) {
527 // Following sib is already right kind of inline node; slide this over
528 nsresult rv
= MoveNodeWithTransaction(*textNodeForTheRange
,
529 EditorDOMPoint(sibling
, 0));
530 if (NS_WARN_IF(Destroyed())) {
531 return NS_ERROR_EDITOR_DESTROYED
;
533 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
534 "HTMLEditor::MoveNodeWithTransaction() failed");
540 // Reparent the node inside inline node with appropriate {attribute,value}
541 nsresult rv
= SetInlinePropertyOnNode(*textNodeForTheRange
, aProperty
,
543 if (NS_WARN_IF(Destroyed())) {
544 return NS_ERROR_EDITOR_DESTROYED
;
546 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
547 "HTMLEditor::SetInlinePropertyOnNode() failed");
551 nsresult
HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent
& aContent
,
554 const nsAString
& aValue
) {
555 // If this is an element that can't be contained in a span, we have to
556 // recurse to its children.
557 if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span
, aContent
)) {
558 if (aContent
.HasChildren()) {
559 nsTArray
<OwningNonNull
<nsIContent
>> arrayOfNodes
;
561 // Populate the list.
562 for (nsCOMPtr
<nsIContent
> child
= aContent
.GetFirstChild(); child
;
563 child
= child
->GetNextSibling()) {
564 if (EditorUtils::IsEditableContent(*child
, EditorType::HTML
) &&
565 !IsEmptyTextNode(*child
)) {
566 arrayOfNodes
.AppendElement(*child
);
570 // Then loop through the list, set the property on each node.
571 for (auto& node
: arrayOfNodes
) {
572 // MOZ_KnownLive because 'arrayOfNodes' is guaranteed to
574 nsresult rv
= SetInlinePropertyOnNode(MOZ_KnownLive(node
), aProperty
,
577 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
585 // First check if there's an adjacent sibling we can put our node into.
586 nsCOMPtr
<nsIContent
> previousSibling
= GetPriorHTMLSibling(&aContent
);
587 nsCOMPtr
<nsIContent
> nextSibling
= GetNextHTMLSibling(&aContent
);
588 if (previousSibling
&& previousSibling
->IsElement()) {
589 OwningNonNull
<Element
> previousElement(*previousSibling
->AsElement());
590 Result
<bool, nsresult
> canMoveIntoPreviousSibling
=
591 ElementIsGoodContainerForTheStyle(previousElement
, &aProperty
,
592 aAttribute
, &aValue
);
593 if (canMoveIntoPreviousSibling
.isErr()) {
594 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
595 return canMoveIntoPreviousSibling
.unwrapErr();
597 if (canMoveIntoPreviousSibling
.inspect()) {
598 nsresult rv
= MoveNodeToEndWithTransaction(aContent
, *previousSibling
);
600 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
603 if (!nextSibling
|| !nextSibling
->IsElement()) {
606 OwningNonNull
<Element
> nextElement(*nextSibling
->AsElement());
607 Result
<bool, nsresult
> canMoveIntoNextSibling
=
608 ElementIsGoodContainerForTheStyle(nextElement
, &aProperty
, aAttribute
,
610 if (canMoveIntoNextSibling
.isErr()) {
611 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
612 return canMoveIntoNextSibling
.unwrapErr();
614 if (!canMoveIntoNextSibling
.inspect()) {
617 rv
= JoinNodesWithTransaction(*previousSibling
, *nextSibling
);
618 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
619 "HTMLEditor::JoinNodesWithTransaction() failed");
624 if (nextSibling
&& nextSibling
->IsElement()) {
625 OwningNonNull
<Element
> nextElement(*nextSibling
->AsElement());
626 Result
<bool, nsresult
> canMoveIntoNextSibling
=
627 ElementIsGoodContainerForTheStyle(nextElement
, &aProperty
, aAttribute
,
629 if (canMoveIntoNextSibling
.isErr()) {
630 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
631 return canMoveIntoNextSibling
.unwrapErr();
633 if (canMoveIntoNextSibling
.inspect()) {
635 MoveNodeWithTransaction(aContent
, EditorDOMPoint(nextElement
, 0));
636 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
637 "HTMLEditor::MoveNodeWithTransaction() failed");
642 // Don't need to do anything if property already set on node
643 if (CSSEditUtils::IsCSSEditableProperty(&aContent
, &aProperty
, aAttribute
)) {
644 nsAutoString
value(aValue
);
645 if (CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
646 aContent
, &aProperty
, aAttribute
, value
)) {
649 } else if (IsTextPropertySetByContent(&aContent
, &aProperty
, aAttribute
,
654 bool useCSS
= (IsCSSEnabled() && CSSEditUtils::IsCSSEditableProperty(
655 &aContent
, &aProperty
, aAttribute
)) ||
656 // bgcolor is always done using CSS
657 aAttribute
== nsGkAtoms::bgcolor
||
658 // called for removing parent style, we should use CSS with
660 aValue
.EqualsLiteral("-moz-editor-invert-value");
663 RefPtr
<Element
> spanElement
;
664 // We only add style="" to <span>s with no attributes (bug 746515). If we
665 // don't have one, we need to make one.
666 if (aContent
.IsHTMLElement(nsGkAtoms::span
) &&
667 !aContent
.AsElement()->GetAttrCount()) {
668 spanElement
= aContent
.AsElement();
670 spanElement
= InsertContainerWithTransaction(aContent
, *nsGkAtoms::span
);
673 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
675 return NS_ERROR_FAILURE
;
679 // Add the CSS styles corresponding to the HTML style request
680 if (nsStyledElement
* spanStyledElement
=
681 nsStyledElement::FromNode(spanElement
)) {
682 // MOZ_KnownLive(*spanStyledElement): It's spanElement whose type is
684 Result
<int32_t, nsresult
> result
=
685 mCSSEditUtils
->SetCSSEquivalentToHTMLStyleWithTransaction(
686 MOZ_KnownLive(*spanStyledElement
), &aProperty
, aAttribute
,
688 if (result
.isErr()) {
689 if (result
.inspectErr() == NS_ERROR_EDITOR_DESTROYED
) {
691 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
692 "destroyed the editor");
693 return NS_ERROR_EDITOR_DESTROYED
;
696 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
704 // is it already the right kind of node, but with wrong attribute?
705 if (aContent
.IsHTMLElement(&aProperty
)) {
706 if (NS_WARN_IF(!aAttribute
)) {
707 return NS_ERROR_INVALID_ARG
;
709 // Just set the attribute on it.
710 nsresult rv
= SetAttributeWithTransaction(
711 MOZ_KnownLive(*aContent
.AsElement()), *aAttribute
, aValue
);
712 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
713 "EditorBase::SetAttributeWithTransaction() failed");
717 // ok, chuck it in its very own container
718 RefPtr
<Element
> newContainerElement
= InsertContainerWithTransaction(
719 aContent
, aProperty
, aAttribute
? *aAttribute
: *nsGkAtoms::_empty
,
721 NS_WARNING_ASSERTION(newContainerElement
,
722 "HTMLEditor::InsertContainerWithTransaction() failed");
723 return newContainerElement
? NS_OK
: NS_ERROR_FAILURE
;
726 nsresult
HTMLEditor::SetInlinePropertyOnNode(nsIContent
& aNode
,
729 const nsAString
& aValue
) {
730 nsCOMPtr
<nsIContent
> previousSibling
= aNode
.GetPreviousSibling(),
731 nextSibling
= aNode
.GetNextSibling();
732 if (NS_WARN_IF(!aNode
.GetParentNode())) {
733 return NS_ERROR_INVALID_ARG
;
736 OwningNonNull
<nsINode
> parent
= *aNode
.GetParentNode();
737 if (aNode
.IsElement()) {
738 nsresult rv
= RemoveStyleInside(MOZ_KnownLive(*aNode
.AsElement()),
739 &aProperty
, aAttribute
);
741 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
746 if (aNode
.GetParentNode()) {
747 // The node is still where it was
749 SetInlinePropertyOnNodeImpl(aNode
, aProperty
, aAttribute
, aValue
);
750 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
751 "HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
755 // It's vanished. Use the old siblings for reference to construct a
756 // list. But first, verify that the previous/next siblings are still
757 // where we expect them; otherwise we have to give up.
758 if (NS_WARN_IF(previousSibling
&&
759 previousSibling
->GetParentNode() != parent
) ||
760 NS_WARN_IF(nextSibling
&& nextSibling
->GetParentNode() != parent
)) {
761 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
;
763 AutoTArray
<OwningNonNull
<nsIContent
>, 24> nodesToSet
;
764 for (nsIContent
* content
= previousSibling
? previousSibling
->GetNextSibling()
765 : parent
->GetFirstChild();
766 content
&& content
!= nextSibling
; content
= content
->GetNextSibling()) {
767 if (EditorUtils::IsEditableContent(*content
, EditorType::HTML
)) {
768 nodesToSet
.AppendElement(*content
);
772 for (OwningNonNull
<nsIContent
>& content
: nodesToSet
) {
773 // MOZ_KnownLive because 'nodesToSet' is guaranteed to
775 nsresult rv
= SetInlinePropertyOnNodeImpl(MOZ_KnownLive(content
), aProperty
,
778 NS_WARNING("HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
786 SplitRangeOffResult
HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
787 const EditorDOMPoint
& aStartOfRange
, const EditorDOMPoint
& aEndOfRange
,
788 nsAtom
* aProperty
, nsAtom
* aAttribute
) {
789 MOZ_ASSERT(IsEditActionDataAvailable());
791 if (NS_WARN_IF(!aStartOfRange
.IsSet()) || NS_WARN_IF(!aEndOfRange
.IsSet())) {
792 return SplitRangeOffResult(NS_ERROR_INVALID_ARG
);
795 EditorDOMPoint
startOfRange(aStartOfRange
);
796 EditorDOMPoint
endOfRange(aEndOfRange
);
798 // split any matching style nodes above the start of range
799 SplitNodeResult
resultAtStart(NS_ERROR_NOT_INITIALIZED
);
801 AutoTrackDOMPoint
tracker(RangeUpdaterRef(), &endOfRange
);
802 resultAtStart
= SplitAncestorStyledInlineElementsAt(startOfRange
, aProperty
,
804 if (resultAtStart
.Failed()) {
805 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
806 return SplitRangeOffResult(resultAtStart
.Rv());
808 if (resultAtStart
.Handled()) {
809 startOfRange
= resultAtStart
.SplitPoint();
810 if (!startOfRange
.IsSet()) {
812 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
814 return SplitRangeOffResult(NS_ERROR_FAILURE
);
819 // second verse, same as the first...
820 SplitNodeResult
resultAtEnd(NS_ERROR_NOT_INITIALIZED
);
822 AutoTrackDOMPoint
tracker(RangeUpdaterRef(), &startOfRange
);
824 SplitAncestorStyledInlineElementsAt(endOfRange
, aProperty
, aAttribute
);
825 if (resultAtEnd
.Failed()) {
826 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
827 return SplitRangeOffResult(resultAtEnd
.Rv());
829 if (resultAtEnd
.Handled()) {
830 endOfRange
= resultAtEnd
.SplitPoint();
831 if (!endOfRange
.IsSet()) {
833 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
835 return SplitRangeOffResult(NS_ERROR_FAILURE
);
840 return SplitRangeOffResult(startOfRange
, resultAtStart
, endOfRange
,
844 SplitNodeResult
HTMLEditor::SplitAncestorStyledInlineElementsAt(
845 const EditorDOMPoint
& aPointToSplit
, nsAtom
* aProperty
,
846 nsAtom
* aAttribute
) {
847 if (NS_WARN_IF(!aPointToSplit
.IsSet()) ||
848 NS_WARN_IF(!aPointToSplit
.GetContainerAsContent())) {
849 return SplitNodeResult(NS_ERROR_INVALID_ARG
);
852 // We assume that this method is called only when we're removing style(s).
853 // Even if we're in HTML mode and there is no presentation element in the
854 // block, we may need to overwrite the block's style with `<span>` element
855 // and CSS. For example, `<h1>` element has `font-weight: bold;` as its
856 // default style. If `Document.execCommand("bold")` is called for its
857 // text, we should make it unbold. Therefore, we shouldn't check
858 // IsCSSEnabled() in most cases. However, there is an exception.
859 // FontFaceStateCommand::SetState() calls RemoveInlinePropertyAsAction()
860 // with nsGkAtoms::tt before calling SetInlinePropertyAsAction() if we
861 // are handling a XUL command. Only in that case, we need to check
863 bool useCSS
= aProperty
!= nsGkAtoms::tt
|| IsCSSEnabled();
865 AutoTArray
<OwningNonNull
<nsIContent
>, 24> arrayOfParents
;
866 for (nsIContent
* content
:
867 aPointToSplit
.GetContainer()->InclusiveAncestorsOfType
<nsIContent
>()) {
868 if (HTMLEditUtils::IsBlockElement(*content
) || !content
->GetParent() ||
869 !EditorUtils::IsEditableContent(*content
->GetParent(),
873 arrayOfParents
.AppendElement(*content
);
876 // Split any matching style nodes above the point.
877 SplitNodeResult
result(aPointToSplit
);
878 MOZ_ASSERT(!result
.Handled());
879 for (OwningNonNull
<nsIContent
>& content
: arrayOfParents
) {
880 bool isSetByCSS
= false;
882 CSSEditUtils::IsCSSEditableProperty(content
, aProperty
, aAttribute
)) {
883 // The HTML style defined by aProperty/aAttribute has a CSS equivalence
884 // in this implementation for the node; let's check if it carries those
886 nsAutoString firstValue
;
887 isSetByCSS
= CSSEditUtils::IsSpecifiedCSSEquivalentToHTMLInlineStyleSet(
888 *content
, aProperty
, aAttribute
, firstValue
);
891 if (!content
->IsElement()) {
894 // If aProperty is set, we need to split only elements which applies the
897 // If the content is an inline element represents aProperty or
898 // the content is a link element and aProperty is `href`, we should
899 // split the content.
900 if (!content
->IsHTMLElement(aProperty
) &&
901 !(aProperty
== nsGkAtoms::href
&& HTMLEditUtils::IsLink(content
))) {
905 // If aProperty is nullptr, we need to split any style.
906 else if (!EditorUtils::IsEditableContent(content
, EditorType::HTML
) ||
907 !HTMLEditUtils::IsRemovableInlineStyleElement(
908 *content
->AsElement())) {
913 // Found a style node we need to split.
914 // XXX If first content is a text node and CSS is enabled, we call this
915 // with text node but in such case, this does nothing, but returns
916 // as handled with setting only previous or next node. If its parent
917 // is a block, we do nothing but return as handled.
918 SplitNodeResult splitNodeResult
= SplitNodeDeepWithTransaction(
919 MOZ_KnownLive(content
), result
.SplitPoint(),
920 SplitAtEdges::eAllowToCreateEmptyContainer
);
921 if (splitNodeResult
.Failed()) {
922 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
923 return splitNodeResult
;
925 MOZ_ASSERT(splitNodeResult
.Handled());
926 // Mark the final result as handled forcibly.
927 result
= SplitNodeResult(splitNodeResult
.GetPreviousNode(),
928 splitNodeResult
.GetNextNode());
929 MOZ_ASSERT(result
.Handled());
935 EditResult
HTMLEditor::ClearStyleAt(const EditorDOMPoint
& aPoint
,
936 nsAtom
* aProperty
, nsAtom
* aAttribute
) {
937 MOZ_ASSERT(IsEditActionDataAvailable());
939 if (NS_WARN_IF(!aPoint
.IsSet())) {
940 return EditResult(NS_ERROR_INVALID_ARG
);
943 // First, split inline elements at the point.
944 // E.g., if aProperty is nsGkAtoms::b and `<p><b><i>a[]bc</i></b></p>`,
945 // we want to make it as `<p><b><i>a</i></b><b><i>bc</i></b></p>`.
946 SplitNodeResult splitResult
=
947 SplitAncestorStyledInlineElementsAt(aPoint
, aProperty
, aAttribute
);
948 if (splitResult
.Failed()) {
949 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
950 return EditResult(splitResult
.Rv());
953 // If there is no styled inline elements of aProperty/aAttribute, we just
954 // return the given point.
955 // E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b.
956 if (!splitResult
.Handled()) {
957 return EditResult(aPoint
);
960 // If it did split nodes, but topmost ancestor inline element is split
961 // at start of it, we don't need the empty inline element. Let's remove
963 if (splitResult
.GetPreviousNode() &&
964 IsEmptyNode(*splitResult
.GetPreviousNode(), false, true)) {
965 // Delete previous node if it's empty.
966 nsresult rv
= DeleteNodeWithTransaction(
967 MOZ_KnownLive(*splitResult
.GetPreviousNode()));
968 if (NS_WARN_IF(Destroyed())) {
969 return EditResult(NS_ERROR_EDITOR_DESTROYED
);
972 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
973 return EditResult(rv
);
977 // If we reached block from end of a text node, we can do nothing here.
978 // E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and
979 // we're in CSS mode.
980 // XXX Chrome resets block style and creates `<span>` elements for each
981 // line in this case.
982 if (!splitResult
.GetNextNode()) {
983 MOZ_ASSERT(IsCSSEnabled());
984 return EditResult(aPoint
);
987 // Otherwise, the next node is topmost ancestor inline element which has
988 // the style. We want to put caret between the split nodes, but we need
989 // to keep other styles. Therefore, next, we need to split at start of
990 // the next node. The first example should become
991 // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`.
993 nsIContent
* firstLeafChildOfNextNode
= HTMLEditUtils::GetFirstLeafChild(
994 *splitResult
.GetNextNode(), ChildBlockBoundary::Ignore
);
995 EditorDOMPoint
atStartOfNextNode(firstLeafChildOfNextNode
996 ? firstLeafChildOfNextNode
997 : splitResult
.GetNextNode(),
999 RefPtr
<HTMLBRElement
> brElement
;
1000 // But don't try to split non-containers like `<br>`, `<hr>` and `<img>`
1002 if (!atStartOfNextNode
.IsInContentNode() ||
1003 !HTMLEditUtils::IsContainerNode(
1004 *atStartOfNextNode
.ContainerAsContent())) {
1005 // If it's a `<br>` element, let's move it into new node later.
1006 brElement
= HTMLBRElement::FromNode(atStartOfNextNode
.GetContainer());
1007 if (!atStartOfNextNode
.GetContainerParentAsContent()) {
1008 NS_WARNING("atStartOfNextNode was in an orphan node");
1009 return EditResult(NS_ERROR_FAILURE
);
1011 atStartOfNextNode
.Set(atStartOfNextNode
.GetContainerParent(), 0);
1013 SplitNodeResult splitResultAtStartOfNextNode
=
1014 SplitAncestorStyledInlineElementsAt(atStartOfNextNode
, aProperty
,
1016 if (splitResultAtStartOfNextNode
.Failed()) {
1017 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
1018 return EditResult(splitResultAtStartOfNextNode
.Rv());
1021 // Let's remove the next node if it becomes empty by splitting it.
1022 // XXX Is this possible case without mutation event listener?
1023 if (splitResultAtStartOfNextNode
.Handled() &&
1024 splitResultAtStartOfNextNode
.GetNextNode() &&
1025 IsEmptyNode(*splitResultAtStartOfNextNode
.GetNextNode(), false, true)) {
1026 // Delete next node if it's empty.
1027 nsresult rv
= DeleteNodeWithTransaction(
1028 MOZ_KnownLive(*splitResultAtStartOfNextNode
.GetNextNode()));
1029 if (NS_WARN_IF(Destroyed())) {
1030 return EditResult(NS_ERROR_EDITOR_DESTROYED
);
1032 if (NS_FAILED(rv
)) {
1033 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1034 return EditResult(rv
);
1038 // If there is no content, we should return here.
1039 // XXX Is this possible case without mutation event listener?
1040 if (NS_WARN_IF(!splitResultAtStartOfNextNode
.Handled()) ||
1041 !splitResultAtStartOfNextNode
.GetPreviousNode()) {
1042 // XXX This is really odd, but we retrun this value...
1044 EditorDOMPoint(splitResult
.SplitPoint().GetContainer(),
1045 splitResultAtStartOfNextNode
.SplitPoint().Offset()));
1048 // Now, we want to put `<br>` element into the empty split node if
1049 // it was in next node of the first split.
1050 // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>`
1051 nsIContent
* firstLeafChildOfPreviousNode
= HTMLEditUtils::GetFirstLeafChild(
1052 *splitResultAtStartOfNextNode
.GetPreviousNode(),
1053 ChildBlockBoundary::Ignore
);
1054 EditorDOMPoint
pointToPutCaret(
1055 firstLeafChildOfPreviousNode
1056 ? firstLeafChildOfPreviousNode
1057 : splitResultAtStartOfNextNode
.GetPreviousNode(),
1059 // If the right node starts with a `<br>`, suck it out of right node and into
1060 // the left node left node. This is so we you don't revert back to the
1061 // previous style if you happen to click at the end of a line.
1063 nsresult rv
= MoveNodeWithTransaction(*brElement
, pointToPutCaret
);
1064 if (NS_WARN_IF(Destroyed())) {
1065 return EditResult(NS_ERROR_EDITOR_DESTROYED
);
1067 if (NS_FAILED(rv
)) {
1068 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
1069 return EditResult(rv
);
1071 // Update the child.
1072 pointToPutCaret
.Set(pointToPutCaret
.GetContainer(), 0);
1074 // Finally, remove the specified style in the previous node at the
1075 // second split and tells good insertion point to the caller. I.e., we
1076 // want to make the first example as:
1077 // `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>`
1079 if (splitResultAtStartOfNextNode
.GetPreviousNode()->IsElement()) {
1080 // Track the point at the new hierarchy. This is so we can know where
1081 // to put the selection after we call RemoveStyleInside().
1082 // RemoveStyleInside() could remove any and all of those nodes, so I
1083 // have to use the range tracking system to find the right spot to put
1085 AutoTrackDOMPoint
tracker(RangeUpdaterRef(), &pointToPutCaret
);
1086 nsresult rv
= RemoveStyleInside(
1088 *splitResultAtStartOfNextNode
.GetPreviousNode()->AsElement()),
1089 aProperty
, aAttribute
);
1090 if (NS_FAILED(rv
)) {
1091 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1092 return EditResult(rv
);
1095 return EditResult(pointToPutCaret
);
1098 nsresult
HTMLEditor::RemoveStyleInside(Element
& aElement
, nsAtom
* aProperty
,
1099 nsAtom
* aAttribute
) {
1100 // First, handle all descendants.
1101 RefPtr
<nsIContent
> child
= aElement
.GetFirstChild();
1103 // cache next sibling since we might remove child
1104 // XXX Well, the next sibling is moved from `aElement`, shouldn't we skip
1106 nsCOMPtr
<nsIContent
> nextSibling
= child
->GetNextSibling();
1107 if (child
->IsElement()) {
1108 nsresult rv
= RemoveStyleInside(MOZ_KnownLive(*child
->AsElement()),
1109 aProperty
, aAttribute
);
1110 if (NS_FAILED(rv
)) {
1111 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1115 child
= ToRefPtr(std::move(nextSibling
));
1118 // Next, remove the element or its attribute.
1119 bool removeHTMLStyle
= false;
1122 // If the element is a presentation element of aProperty
1123 aElement
.NodeInfo()->NameAtom() == aProperty
||
1124 // or an `<a>` element with `href` attribute
1125 (aProperty
== nsGkAtoms::href
&& HTMLEditUtils::IsLink(&aElement
)) ||
1126 // or an `<a>` element with `name` attribute
1127 (aProperty
== nsGkAtoms::name
&&
1128 HTMLEditUtils::IsNamedAnchor(&aElement
));
1130 // XXX Why do we check if aElement is editable only when aProperty is
1132 else if (EditorUtils::IsEditableContent(aElement
, EditorType::HTML
)) {
1133 // or removing all styles and the element is a presentation element.
1134 removeHTMLStyle
= HTMLEditUtils::IsRemovableInlineStyleElement(aElement
);
1137 if (removeHTMLStyle
) {
1138 // If aAttribute is nullptr, we want to remove any matching inline styles
1141 // If some style rules are specified to aElement, we need to keep them
1142 // as far as possible.
1143 // XXX Why don't we clone `id` attribute?
1145 (aElement
.HasAttr(kNameSpaceID_None
, nsGkAtoms::style
) ||
1146 aElement
.HasAttr(kNameSpaceID_None
, nsGkAtoms::_class
))) {
1147 // Move `style` attribute and `class` element to span element before
1148 // removing aElement from the tree.
1149 RefPtr
<Element
> spanElement
=
1150 InsertContainerWithTransaction(aElement
, *nsGkAtoms::span
);
1151 if (NS_WARN_IF(Destroyed())) {
1152 return NS_ERROR_EDITOR_DESTROYED
;
1156 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
1158 return NS_ERROR_FAILURE
;
1160 nsresult rv
= CloneAttributeWithTransaction(*nsGkAtoms::style
,
1161 *spanElement
, aElement
);
1162 if (NS_WARN_IF(Destroyed())) {
1163 return NS_ERROR_EDITOR_DESTROYED
;
1165 if (NS_FAILED(rv
)) {
1167 "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::style) "
1171 rv
= CloneAttributeWithTransaction(*nsGkAtoms::_class
, *spanElement
,
1173 if (NS_WARN_IF(Destroyed())) {
1174 return NS_ERROR_EDITOR_DESTROYED
;
1176 if (NS_FAILED(rv
)) {
1178 "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::_class) "
1183 nsresult rv
= RemoveContainerWithTransaction(aElement
);
1184 if (NS_WARN_IF(Destroyed())) {
1185 return NS_ERROR_EDITOR_DESTROYED
;
1187 if (NS_FAILED(rv
)) {
1188 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
1192 // If aAttribute is specified, we want to remove only the attribute
1193 // unless it's the last attribute of aElement.
1194 else if (aElement
.HasAttr(kNameSpaceID_None
, aAttribute
)) {
1195 if (IsOnlyAttribute(&aElement
, aAttribute
)) {
1196 nsresult rv
= RemoveContainerWithTransaction(aElement
);
1197 if (NS_WARN_IF(Destroyed())) {
1198 return NS_ERROR_EDITOR_DESTROYED
;
1200 if (NS_FAILED(rv
)) {
1201 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
1205 nsresult rv
= RemoveAttributeWithTransaction(aElement
, *aAttribute
);
1206 if (NS_WARN_IF(Destroyed())) {
1207 return NS_ERROR_EDITOR_DESTROYED
;
1209 if (NS_FAILED(rv
)) {
1210 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
1217 // Then, remove CSS style if specified.
1218 // XXX aElement may have already been removed from the DOM tree. Why
1219 // do we keep handling aElement here??
1220 if (CSSEditUtils::IsCSSEditableProperty(&aElement
, aProperty
, aAttribute
) &&
1221 CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(aElement
, aProperty
,
1223 if (nsStyledElement
* styledElement
= nsStyledElement::FromNode(&aElement
)) {
1224 // If aElement has CSS declaration of the given style, remove it.
1225 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must be
1226 // guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
1228 mCSSEditUtils
->RemoveCSSEquivalentToHTMLStyleWithTransaction(
1229 MOZ_KnownLive(*styledElement
), aProperty
, aAttribute
, nullptr);
1230 if (rv
== NS_ERROR_EDITOR_DESTROYED
) {
1232 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1233 "destroyed the editor");
1234 return NS_ERROR_EDITOR_DESTROYED
;
1236 NS_WARNING_ASSERTION(
1238 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1239 "failed, but ignored");
1241 // Additionally, remove aElement itself if it's a `<span>` or `<font>`
1242 // and it does not have non-empty `style`, `id` nor `class` attribute.
1243 if (aElement
.IsAnyOfHTMLElements(nsGkAtoms::span
, nsGkAtoms::font
) &&
1244 !HTMLEditor::HasStyleOrIdOrClassAttribute(aElement
)) {
1245 DebugOnly
<nsresult
> rvIgnored
= RemoveContainerWithTransaction(aElement
);
1246 if (NS_WARN_IF(Destroyed())) {
1247 return NS_ERROR_EDITOR_DESTROYED
;
1249 NS_WARNING_ASSERTION(
1250 NS_SUCCEEDED(rvIgnored
),
1251 "HTMLEditor::RemoveContainerWithTransaction() failed, but ignored");
1255 // Finally, remove aElement if it's a `<big>` or `<small>` element and
1256 // we're removing `<font size>`.
1257 if (aProperty
== nsGkAtoms::font
&& aAttribute
== nsGkAtoms::size
&&
1258 aElement
.IsAnyOfHTMLElements(nsGkAtoms::big
, nsGkAtoms::small
)) {
1259 nsresult rv
= RemoveContainerWithTransaction(aElement
);
1260 if (NS_WARN_IF(Destroyed())) {
1261 return NS_ERROR_EDITOR_DESTROYED
;
1263 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1264 "HTMLEditor::RemoveContainerWithTransaction() failed");
1271 bool HTMLEditor::IsOnlyAttribute(const Element
* aElement
, nsAtom
* aAttribute
) {
1272 MOZ_ASSERT(aElement
);
1274 uint32_t attrCount
= aElement
->GetAttrCount();
1275 for (uint32_t i
= 0; i
< attrCount
; ++i
) {
1276 const nsAttrName
* name
= aElement
->GetAttrNameAt(i
);
1277 if (!name
->NamespaceEquals(kNameSpaceID_None
)) {
1281 // if it's the attribute we know about, or a special _moz attribute,
1283 if (name
->LocalName() != aAttribute
) {
1284 nsAutoString attrString
;
1285 name
->LocalName()->ToString(attrString
);
1286 if (!StringBeginsWith(attrString
, u
"_moz"_ns
)) {
1291 // if we made it through all of them without finding a real attribute
1292 // other than aAttribute, then return true
1296 nsresult
HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange
& aRange
) {
1297 // We assume that <a> is not nested.
1298 // XXX Shouldn't ignore the editing host.
1299 if (NS_WARN_IF(!aRange
.GetStartContainer()) ||
1300 NS_WARN_IF(!aRange
.GetEndContainer())) {
1301 return NS_ERROR_INVALID_ARG
;
1303 EditorRawDOMPoint
newRangeStart(aRange
.StartRef());
1304 for (Element
* element
:
1305 aRange
.GetStartContainer()->InclusiveAncestorsOfType
<Element
>()) {
1306 if (element
->IsHTMLElement(nsGkAtoms::body
)) {
1309 if (!HTMLEditUtils::IsNamedAnchor(element
)) {
1312 newRangeStart
.Set(element
);
1316 if (!newRangeStart
.GetContainerAsContent()) {
1318 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1319 "element from start container");
1320 return NS_ERROR_FAILURE
;
1323 EditorRawDOMPoint
newRangeEnd(aRange
.EndRef());
1324 for (Element
* element
:
1325 aRange
.GetEndContainer()->InclusiveAncestorsOfType
<Element
>()) {
1326 if (element
->IsHTMLElement(nsGkAtoms::body
)) {
1329 if (!HTMLEditUtils::IsNamedAnchor(element
)) {
1332 newRangeEnd
.SetAfter(element
);
1336 if (!newRangeEnd
.GetContainerAsContent()) {
1338 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1339 "element from end container");
1340 return NS_ERROR_FAILURE
;
1343 if (newRangeStart
== aRange
.StartRef() && newRangeEnd
== aRange
.EndRef()) {
1347 nsresult rv
= aRange
.SetStartAndEnd(newRangeStart
.ToRawRangeBoundary(),
1348 newRangeEnd
.ToRawRangeBoundary());
1349 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::SetStartAndEnd() failed");
1353 nsresult
HTMLEditor::PromoteInlineRange(nsRange
& aRange
) {
1354 if (NS_WARN_IF(!aRange
.GetStartContainer()) ||
1355 NS_WARN_IF(!aRange
.GetEndContainer())) {
1356 return NS_ERROR_INVALID_ARG
;
1358 EditorRawDOMPoint
newRangeStart(aRange
.StartRef());
1359 for (nsIContent
* content
:
1360 aRange
.GetStartContainer()->InclusiveAncestorsOfType
<nsIContent
>()) {
1361 MOZ_ASSERT(newRangeStart
.GetContainer() == content
);
1362 if (content
->IsHTMLElement(nsGkAtoms::body
) ||
1363 !EditorUtils::IsEditableContent(*content
, EditorType::HTML
) ||
1364 !IsStartOfContainerOrBeforeFirstEditableChild(newRangeStart
)) {
1367 newRangeStart
.Set(content
);
1369 if (!newRangeStart
.GetContainerAsContent()) {
1371 "HTMLEditor::PromoteInlineRange() reached root element from start "
1373 return NS_ERROR_FAILURE
;
1376 EditorRawDOMPoint
newRangeEnd(aRange
.EndRef());
1377 for (nsIContent
* content
:
1378 aRange
.GetEndContainer()->InclusiveAncestorsOfType
<nsIContent
>()) {
1379 MOZ_ASSERT(newRangeEnd
.GetContainer() == content
);
1380 if (content
->IsHTMLElement(nsGkAtoms::body
) ||
1381 !EditorUtils::IsEditableContent(*content
, EditorType::HTML
) ||
1382 !IsEndOfContainerOrEqualsOrAfterLastEditableChild(newRangeEnd
)) {
1385 newRangeEnd
.SetAfter(content
);
1387 if (!newRangeEnd
.GetContainerAsContent()) {
1389 "HTMLEditor::PromoteInlineRange() reached root element from end "
1391 return NS_ERROR_FAILURE
;
1394 if (newRangeStart
== aRange
.StartRef() && newRangeEnd
== aRange
.EndRef()) {
1398 nsresult rv
= aRange
.SetStartAndEnd(newRangeStart
.ToRawRangeBoundary(),
1399 newRangeEnd
.ToRawRangeBoundary());
1400 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
), "nsRange::SetStartAndEnd() failed");
1404 bool HTMLEditor::IsStartOfContainerOrBeforeFirstEditableChild(
1405 const EditorRawDOMPoint
& aPoint
) const {
1406 MOZ_ASSERT(aPoint
.IsSet());
1408 if (aPoint
.IsStartOfContainer()) {
1412 if (aPoint
.IsInTextNode()) {
1416 nsIContent
* firstEditableChild
=
1417 GetFirstEditableChild(*aPoint
.GetContainer());
1418 if (!firstEditableChild
) {
1421 return EditorRawDOMPoint(firstEditableChild
).Offset() >= aPoint
.Offset();
1424 bool HTMLEditor::IsEndOfContainerOrEqualsOrAfterLastEditableChild(
1425 const EditorRawDOMPoint
& aPoint
) const {
1426 MOZ_ASSERT(aPoint
.IsSet());
1428 if (aPoint
.IsEndOfContainer()) {
1432 if (aPoint
.IsInTextNode()) {
1436 nsIContent
* lastEditableChild
= GetLastEditableChild(*aPoint
.GetContainer());
1437 if (!lastEditableChild
) {
1440 return EditorRawDOMPoint(lastEditableChild
).Offset() < aPoint
.Offset();
1443 nsresult
HTMLEditor::GetInlinePropertyBase(nsAtom
& aHTMLProperty
,
1445 const nsAString
* aValue
,
1446 bool* aFirst
, bool* aAny
, bool* aAll
,
1447 nsAString
* outValue
) const {
1448 MOZ_ASSERT(IsEditActionDataAvailable());
1455 bool isCollapsed
= SelectionRefPtr()->IsCollapsed();
1456 RefPtr
<nsRange
> range
= SelectionRefPtr()->GetRangeAt(0);
1457 // XXX: Should be a while loop, to get each separate range
1458 // XXX: ERROR_HANDLING can currentItem be null?
1460 // For each range, set a flag
1461 bool firstNodeInRange
= true;
1464 nsCOMPtr
<nsINode
> collapsedNode
= range
->GetStartContainer();
1465 if (NS_WARN_IF(!collapsedNode
)) {
1466 return NS_ERROR_FAILURE
;
1468 bool isSet
, theSetting
;
1469 nsString tOutString
;
1471 mTypeInState
->GetTypingState(isSet
, theSetting
, &aHTMLProperty
,
1472 aAttribute
, &tOutString
);
1474 outValue
->Assign(tOutString
);
1477 mTypeInState
->GetTypingState(isSet
, theSetting
, &aHTMLProperty
);
1480 *aFirst
= *aAny
= *aAll
= theSetting
;
1484 if (collapsedNode
->IsContent() &&
1485 CSSEditUtils::IsCSSEditableProperty(collapsedNode
, &aHTMLProperty
,
1488 tOutString
.Assign(*aValue
);
1490 *aFirst
= *aAny
= *aAll
=
1491 CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1492 MOZ_KnownLive(*collapsedNode
->AsContent()), &aHTMLProperty
,
1493 aAttribute
, tOutString
);
1494 if (NS_WARN_IF(Destroyed())) {
1495 return NS_ERROR_EDITOR_DESTROYED
;
1498 outValue
->Assign(tOutString
);
1503 isSet
= IsTextPropertySetByContent(collapsedNode
, &aHTMLProperty
,
1504 aAttribute
, aValue
, outValue
);
1505 *aFirst
= *aAny
= *aAll
= isSet
;
1509 // Non-collapsed selection
1511 nsAutoString firstValue
, theValue
;
1513 nsCOMPtr
<nsINode
> endNode
= range
->GetEndContainer();
1514 int32_t endOffset
= range
->EndOffset();
1516 PostContentIterator postOrderIter
;
1517 DebugOnly
<nsresult
> rvIgnored
= postOrderIter
.Init(range
);
1518 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1519 "Failed to initialize post-order content iterator");
1520 for (; !postOrderIter
.IsDone(); postOrderIter
.Next()) {
1521 if (!postOrderIter
.GetCurrentNode()->IsContent()) {
1524 nsCOMPtr
<nsIContent
> content
=
1525 postOrderIter
.GetCurrentNode()->AsContent();
1527 if (content
->IsHTMLElement(nsGkAtoms::body
)) {
1531 // just ignore any non-editable nodes
1532 if (content
->IsText() &&
1533 (!EditorUtils::IsEditableContent(*content
, EditorType::HTML
) ||
1534 IsEmptyTextNode(*content
))) {
1537 if (content
->GetAsText()) {
1538 if (!isCollapsed
&& first
&& firstNodeInRange
) {
1539 firstNodeInRange
= false;
1540 if (range
->StartOffset() == content
->Length()) {
1543 } else if (content
== endNode
&& !endOffset
) {
1546 } else if (content
->IsElement()) {
1547 // handle non-text leaf nodes here
1552 bool useTextDecoration
=
1553 &aHTMLProperty
== nsGkAtoms::u
|| &aHTMLProperty
== nsGkAtoms::strike
;
1555 if (CSSEditUtils::IsCSSEditableProperty(content
, &aHTMLProperty
,
1557 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
1558 // equivalence in this implementation for node; let's check if it
1559 // carries those CSS styles
1561 firstValue
.Assign(*aValue
);
1563 isSet
= CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1564 *content
, &aHTMLProperty
, aAttribute
, firstValue
);
1565 if (NS_WARN_IF(Destroyed())) {
1566 return NS_ERROR_EDITOR_DESTROYED
;
1569 isSet
= IsTextPropertySetByContent(content
, &aHTMLProperty
,
1570 aAttribute
, aValue
, &firstValue
);
1575 *outValue
= firstValue
;
1578 if (CSSEditUtils::IsCSSEditableProperty(content
, &aHTMLProperty
,
1580 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
1581 // equivalence in this implementation for node; let's check if it
1582 // carries those CSS styles
1584 theValue
.Assign(*aValue
);
1586 isSet
= CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1587 *content
, &aHTMLProperty
, aAttribute
, theValue
);
1588 if (NS_WARN_IF(Destroyed())) {
1589 return NS_ERROR_EDITOR_DESTROYED
;
1592 isSet
= IsTextPropertySetByContent(content
, &aHTMLProperty
,
1593 aAttribute
, aValue
, &theValue
);
1596 if (firstValue
!= theValue
&&
1597 // For text-decoration related HTML properties, i.e. <u> and
1598 // <strike>, we have to also check |isSet| because text-decoration
1599 // is a shorthand property, and it may contains other unrelated
1600 // longhand components, e.g. text-decoration-color, so we have to do
1601 // an extra check before setting |*aAll| to false.
1603 // firstValue: "underline rgb(0, 0, 0)"
1604 // theValue: "underline rgb(0, 0, 238)" // <a> uses blue color
1605 // These two values should be the same if we are checking `<u>`.
1606 // That's why we need to check |*aFirst| and |isSet|.
1608 // This is a work-around for text-decoration.
1609 // The spec issue: https://github.com/w3c/editing/issues/241.
1610 // Once this spec issue is resolved, we could drop this work-around
1612 (!useTextDecoration
|| *aFirst
!= isSet
)) {
1625 // make sure that if none of the selection is set, we don't report all is
1632 NS_IMETHODIMP
HTMLEditor::GetInlineProperty(const nsAString
& aHTMLProperty
,
1633 const nsAString
& aAttribute
,
1634 const nsAString
& aValue
,
1635 bool* aFirst
, bool* aAny
,
1637 RefPtr
<nsAtom
> property
= NS_Atomize(aHTMLProperty
);
1638 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
1639 nsresult rv
= GetInlineProperty(property
, MOZ_KnownLive(attribute
), aValue
,
1640 aFirst
, aAny
, aAll
);
1641 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1642 "HTMLEditor::GetInlineProperty() failed");
1646 nsresult
HTMLEditor::GetInlineProperty(nsAtom
* aHTMLProperty
,
1648 const nsAString
& aValue
, bool* aFirst
,
1649 bool* aAny
, bool* aAll
) const {
1650 if (NS_WARN_IF(!aHTMLProperty
) || NS_WARN_IF(!aFirst
) || NS_WARN_IF(!aAny
) ||
1651 NS_WARN_IF(!aAll
)) {
1652 return NS_ERROR_INVALID_ARG
;
1655 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
1656 if (NS_WARN_IF(!editActionData
.CanHandle())) {
1657 return NS_ERROR_NOT_INITIALIZED
;
1660 const nsAString
* val
= !aValue
.IsEmpty() ? &aValue
: nullptr;
1661 nsresult rv
= GetInlinePropertyBase(*aHTMLProperty
, aAttribute
, val
, aFirst
,
1662 aAny
, aAll
, nullptr);
1663 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1664 "HTMLEditor::GetInlinePropertyBase() failed");
1665 return EditorBase::ToGenericNSResult(rv
);
1668 NS_IMETHODIMP
HTMLEditor::GetInlinePropertyWithAttrValue(
1669 const nsAString
& aHTMLProperty
, const nsAString
& aAttribute
,
1670 const nsAString
& aValue
, bool* aFirst
, bool* aAny
, bool* aAll
,
1671 nsAString
& outValue
) {
1672 RefPtr
<nsAtom
> property
= NS_Atomize(aHTMLProperty
);
1673 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
1674 nsresult rv
= GetInlinePropertyWithAttrValue(
1675 property
, MOZ_KnownLive(attribute
), aValue
, aFirst
, aAny
, aAll
, outValue
);
1676 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1677 "HTMLEditor::GetInlinePropertyWithAttrValue() failed");
1681 nsresult
HTMLEditor::GetInlinePropertyWithAttrValue(
1682 nsAtom
* aHTMLProperty
, nsAtom
* aAttribute
, const nsAString
& aValue
,
1683 bool* aFirst
, bool* aAny
, bool* aAll
, nsAString
& outValue
) {
1684 if (NS_WARN_IF(!aHTMLProperty
) || NS_WARN_IF(!aFirst
) || NS_WARN_IF(!aAny
) ||
1685 NS_WARN_IF(!aAll
)) {
1686 return NS_ERROR_INVALID_ARG
;
1689 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
1690 if (NS_WARN_IF(!editActionData
.CanHandle())) {
1691 return NS_ERROR_NOT_INITIALIZED
;
1694 const nsAString
* val
= !aValue
.IsEmpty() ? &aValue
: nullptr;
1695 nsresult rv
= GetInlinePropertyBase(*aHTMLProperty
, aAttribute
, val
, aFirst
,
1696 aAny
, aAll
, &outValue
);
1697 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1698 "HTMLEditor::GetInlinePropertyBase() failed");
1699 return EditorBase::ToGenericNSResult(rv
);
1702 nsresult
HTMLEditor::RemoveAllInlinePropertiesAsAction(
1703 nsIPrincipal
* aPrincipal
) {
1704 AutoEditActionDataSetter
editActionData(
1705 *this, EditAction::eRemoveAllInlineStyleProperties
, aPrincipal
);
1706 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
1707 if (NS_FAILED(rv
)) {
1708 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
1709 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1710 return EditorBase::ToGenericNSResult(rv
);
1713 AutoPlaceholderBatch
treatAsOneTransaction(*this,
1714 ScrollSelectionIntoView::Yes
);
1715 IgnoredErrorResult ignoredError
;
1716 AutoEditSubActionNotifier
startToHandleEditSubAction(
1717 *this, EditSubAction::eRemoveAllTextProperties
, nsIEditor::eNext
,
1719 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
1720 return EditorBase::ToGenericNSResult(ignoredError
.StealNSResult());
1722 NS_WARNING_ASSERTION(
1723 !ignoredError
.Failed(),
1724 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1727 RemoveInlinePropertyInternal(nullptr, nullptr, RemoveRelatedElements::No
);
1728 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1729 "HTMLEditor::RemoveInlinePropertyInternal(nullptr, "
1730 "nullptr, RemoveRelatedElements::No) failed");
1731 return EditorBase::ToGenericNSResult(rv
);
1734 nsresult
HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom
& aHTMLProperty
,
1735 nsStaticAtom
* aAttribute
,
1736 nsIPrincipal
* aPrincipal
) {
1737 AutoEditActionDataSetter
editActionData(
1739 HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty
, aAttribute
,
1742 switch (editActionData
.GetEditAction()) {
1743 case EditAction::eRemoveFontFamilyProperty
:
1744 MOZ_ASSERT(!u
""_ns
.IsVoid());
1745 editActionData
.SetData(u
""_ns
);
1747 case EditAction::eRemoveColorProperty
:
1748 case EditAction::eRemoveBackgroundColorPropertyInline
:
1749 editActionData
.SetColorData(u
""_ns
);
1754 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
1755 if (NS_FAILED(rv
)) {
1756 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
1757 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1758 return EditorBase::ToGenericNSResult(rv
);
1761 rv
= RemoveInlinePropertyInternal(&aHTMLProperty
, aAttribute
,
1762 RemoveRelatedElements::Yes
);
1763 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1764 "HTMLEditor::RemoveInlinePropertyInternal("
1765 "RemoveRelatedElements::Yes) failed");
1766 return EditorBase::ToGenericNSResult(rv
);
1769 NS_IMETHODIMP
HTMLEditor::RemoveInlineProperty(const nsAString
& aProperty
,
1770 const nsAString
& aAttribute
) {
1771 nsStaticAtom
* property
= NS_GetStaticAtom(aProperty
);
1772 nsStaticAtom
* attribute
= EditorUtils::GetAttributeAtom(aAttribute
);
1774 AutoEditActionDataSetter
editActionData(
1776 HTMLEditUtils::GetEditActionForFormatText(*property
, attribute
, false));
1777 switch (editActionData
.GetEditAction()) {
1778 case EditAction::eRemoveFontFamilyProperty
:
1779 MOZ_ASSERT(!u
""_ns
.IsVoid());
1780 editActionData
.SetData(u
""_ns
);
1782 case EditAction::eRemoveColorProperty
:
1783 case EditAction::eRemoveBackgroundColorPropertyInline
:
1784 editActionData
.SetColorData(u
""_ns
);
1789 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
1790 if (NS_FAILED(rv
)) {
1791 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
1792 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1793 return EditorBase::ToGenericNSResult(rv
);
1796 rv
= RemoveInlinePropertyInternal(MOZ_KnownLive(property
),
1797 MOZ_KnownLive(attribute
),
1798 RemoveRelatedElements::No
);
1799 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
1800 "HTMLEditor::RemoveInlinePropertyInternal("
1801 "RemoveRelatedElements::No) failed");
1802 return EditorBase::ToGenericNSResult(rv
);
1805 nsresult
HTMLEditor::RemoveInlinePropertyInternal(
1806 nsStaticAtom
* aProperty
, nsStaticAtom
* aAttribute
,
1807 RemoveRelatedElements aRemoveRelatedElements
) {
1808 MOZ_ASSERT(IsEditActionDataAvailable());
1809 MOZ_ASSERT(aAttribute
!= nsGkAtoms::_empty
);
1811 if (NS_WARN_IF(!mInitSucceeded
)) {
1812 return NS_ERROR_NOT_INITIALIZED
;
1815 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
1816 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1817 "EditorBase::CommitComposition() failed, but ignored");
1819 // Also remove equivalent properties (bug 317093)
1820 struct HTMLStyle final
{
1821 // HTML tag name or nsGkAtoms::href or nsGkAtoms::name.
1822 nsStaticAtom
* mProperty
= nullptr;
1823 // HTML attribute like nsGkAtom::color for nsGkAtoms::font.
1824 nsStaticAtom
* mAttribute
= nullptr;
1826 explicit HTMLStyle(nsStaticAtom
* aProperty
,
1827 nsStaticAtom
* aAttribute
= nullptr)
1828 : mProperty(aProperty
), mAttribute(aAttribute
) {}
1830 AutoTArray
<HTMLStyle
, 3> removeStyles
;
1831 if (aRemoveRelatedElements
== RemoveRelatedElements::Yes
) {
1832 if (aProperty
== nsGkAtoms::b
) {
1833 removeStyles
.AppendElement(HTMLStyle(nsGkAtoms::strong
));
1834 } else if (aProperty
== nsGkAtoms::i
) {
1835 removeStyles
.AppendElement(HTMLStyle(nsGkAtoms::em
));
1836 } else if (aProperty
== nsGkAtoms::strike
) {
1837 removeStyles
.AppendElement(HTMLStyle(nsGkAtoms::s
));
1838 } else if (aProperty
== nsGkAtoms::font
) {
1839 if (aAttribute
== nsGkAtoms::size
) {
1840 removeStyles
.AppendElement(HTMLStyle(nsGkAtoms::big
));
1841 removeStyles
.AppendElement(HTMLStyle(nsGkAtoms::small
));
1843 // Handling `<tt>` element code was implemented for composer (bug 115922).
1844 // This shouldn't work with `Document.execCommand()` for compatibility
1845 // with the other browsers. Currently, edit action principal is set only
1846 // when the root caller is Document::ExecCommand() so that we should
1847 // handle `<tt>` element only when the principal is nullptr that must be
1848 // only when XUL command is executed on composer.
1849 else if (aAttribute
== nsGkAtoms::face
&& !GetEditActionPrincipal()) {
1850 removeStyles
.AppendElement(HTMLStyle(nsGkAtoms::tt
));
1854 removeStyles
.AppendElement(HTMLStyle(aProperty
, aAttribute
));
1856 if (SelectionRefPtr()->IsCollapsed()) {
1857 // Manipulating text attributes on a collapsed selection only sets state
1858 // for the next text insertion
1859 if (removeStyles
[0].mProperty
) {
1860 for (HTMLStyle
& style
: removeStyles
) {
1861 MOZ_ASSERT(style
.mProperty
);
1862 if (style
.mProperty
== nsGkAtoms::href
||
1863 style
.mProperty
== nsGkAtoms::name
) {
1864 mTypeInState
->ClearProp(nsGkAtoms::a
, nullptr);
1866 mTypeInState
->ClearProp(style
.mProperty
, style
.mAttribute
);
1870 mTypeInState
->ClearAllProps();
1875 // XXX Shouldn't we quit before calling `CommitComposition()`?
1876 if (IsPlaintextEditor()) {
1880 EditActionResult result
= CanHandleHTMLEditSubAction();
1881 if (result
.Failed() || result
.Canceled()) {
1882 NS_WARNING_ASSERTION(result
.Succeeded(),
1883 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1887 AutoPlaceholderBatch
treatAsOneTransaction(*this,
1888 ScrollSelectionIntoView::Yes
);
1889 IgnoredErrorResult ignoredError
;
1890 AutoEditSubActionNotifier
startToHandleEditSubAction(
1891 *this, EditSubAction::eRemoveTextProperty
, nsIEditor::eNext
,
1893 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
1894 return ignoredError
.StealNSResult();
1896 NS_WARNING_ASSERTION(
1897 !ignoredError
.Failed(),
1898 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1901 AutoSelectionRestorer
restoreSelectionLater(*this);
1902 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
1904 for (HTMLStyle
& style
: removeStyles
) {
1905 // Loop through the ranges in the selection.
1906 // XXX Although `Selection` will be restored by AutoSelectionRestorer,
1907 // AutoSelectionRangeArray just grabs the ranges in `Selection`.
1908 // Therefore, modifying each range may notify selection listener. So
1909 // perhaps, we should clone each range here instead.
1910 AutoSelectionRangeArray
arrayOfRanges(SelectionRefPtr());
1911 for (auto& range
: arrayOfRanges
.mRanges
) {
1912 if (style
.mProperty
== nsGkAtoms::name
) {
1913 // Promote range if it starts or end in a named anchor and we want to
1914 // remove named anchors
1915 nsresult rv
= PromoteRangeIfStartsOrEndsInNamedAnchor(*range
);
1916 if (NS_FAILED(rv
)) {
1918 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() failed");
1922 // Adjust range to include any ancestors whose children are entirely
1924 nsresult rv
= PromoteInlineRange(*range
);
1925 if (NS_FAILED(rv
)) {
1926 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
1931 // Remove this style from ancestors of our range endpoints, splitting
1932 // them as appropriate
1933 SplitRangeOffResult splitRangeOffResult
=
1934 SplitAncestorStyledInlineElementsAtRangeEdges(
1935 EditorDOMPoint(range
->StartRef()),
1936 EditorDOMPoint(range
->EndRef()), MOZ_KnownLive(style
.mProperty
),
1937 MOZ_KnownLive(style
.mAttribute
));
1938 if (splitRangeOffResult
.Failed()) {
1940 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
1942 return splitRangeOffResult
.Rv();
1945 // XXX Modifying `range` means that we may modify ranges in `Selection`.
1946 // Is this intentional? Note that the range may be not in
1947 // `Selection` too. It seems that at least one of them is not
1948 // an unexpected case.
1949 const EditorDOMPoint
& startOfRange(
1950 splitRangeOffResult
.SplitPointAtStart());
1951 const EditorDOMPoint
& endOfRange(splitRangeOffResult
.SplitPointAtEnd());
1952 if (NS_WARN_IF(!startOfRange
.IsSet()) ||
1953 NS_WARN_IF(!endOfRange
.IsSet())) {
1957 nsresult rv
= range
->SetStartAndEnd(startOfRange
.ToRawRangeBoundary(),
1958 endOfRange
.ToRawRangeBoundary());
1959 // Note that modifying a range in `Selection` may run script so that
1960 // we might have been destroyed here.
1961 if (NS_WARN_IF(Destroyed())) {
1962 return NS_ERROR_EDITOR_DESTROYED
;
1964 if (NS_FAILED(rv
)) {
1965 NS_WARNING("nsRange::SetStartAndEnd() failed");
1969 // Collect editable nodes which are entirely contained in the range.
1970 AutoTArray
<OwningNonNull
<nsIContent
>, 64> arrayOfContents
;
1971 if (startOfRange
.GetContainer() == endOfRange
.GetContainer() &&
1972 startOfRange
.IsInTextNode()) {
1973 if (!EditorUtils::IsEditableContent(*startOfRange
.ContainerAsText(),
1974 EditorType::HTML
)) {
1977 arrayOfContents
.AppendElement(*startOfRange
.ContainerAsText());
1978 } else if (startOfRange
.IsInTextNode() && endOfRange
.IsInTextNode() &&
1979 startOfRange
.GetContainer()->GetNextSibling() ==
1980 endOfRange
.GetContainer()) {
1981 if (EditorUtils::IsEditableContent(*startOfRange
.ContainerAsText(),
1982 EditorType::HTML
)) {
1983 arrayOfContents
.AppendElement(*startOfRange
.ContainerAsText());
1985 if (EditorUtils::IsEditableContent(*endOfRange
.ContainerAsText(),
1986 EditorType::HTML
)) {
1987 arrayOfContents
.AppendElement(*endOfRange
.ContainerAsText());
1989 if (arrayOfContents
.IsEmpty()) {
1993 // Append first node if it's a text node but selected not entirely.
1994 if (startOfRange
.IsInTextNode() &&
1995 !startOfRange
.IsStartOfContainer() &&
1996 EditorUtils::IsEditableContent(*startOfRange
.ContainerAsText(),
1997 EditorType::HTML
)) {
1998 arrayOfContents
.AppendElement(*startOfRange
.ContainerAsText());
2000 // Append all entirely selected nodes.
2001 ContentSubtreeIterator subtreeIter
;
2002 if (NS_SUCCEEDED(subtreeIter
.Init(range
))) {
2003 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
2004 nsCOMPtr
<nsINode
> node
= subtreeIter
.GetCurrentNode();
2005 if (NS_WARN_IF(!node
)) {
2006 return NS_ERROR_FAILURE
;
2008 if (node
->IsContent() &&
2009 EditorUtils::IsEditableContent(*node
->AsContent(),
2010 EditorType::HTML
)) {
2011 arrayOfContents
.AppendElement(*node
->AsContent());
2015 // Append last node if it's a text node but selected not entirely.
2016 if (startOfRange
.GetContainer() != endOfRange
.GetContainer() &&
2017 endOfRange
.IsInTextNode() && !endOfRange
.IsEndOfContainer() &&
2018 EditorUtils::IsEditableContent(*endOfRange
.ContainerAsText(),
2019 EditorType::HTML
)) {
2020 arrayOfContents
.AppendElement(*endOfRange
.ContainerAsText());
2024 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
2025 if (content
->IsElement()) {
2027 RemoveStyleInside(MOZ_KnownLive(*content
->AsElement()),
2028 MOZ_KnownLive(style
.mProperty
),
2029 MOZ_KnownLive(style
.mAttribute
));
2030 if (NS_FAILED(rv
)) {
2031 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
2036 bool isRemovable
= IsRemovableParentStyleWithNewSpanElement(
2037 MOZ_KnownLive(content
), MOZ_KnownLive(style
.mProperty
),
2038 MOZ_KnownLive(style
.mAttribute
));
2039 if (NS_WARN_IF(Destroyed())) {
2040 return NS_ERROR_EDITOR_DESTROYED
;
2046 if (!content
->IsText()) {
2047 // XXX Do we need to call this even when data node or something? If
2049 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
2051 DebugOnly
<nsresult
> rvIgnored
= SetInlinePropertyOnNode(
2052 MOZ_KnownLive(content
), MOZ_KnownLive(*style
.mProperty
),
2053 MOZ_KnownLive(style
.mAttribute
),
2054 u
"-moz-editor-invert-value"_ns
);
2055 if (NS_WARN_IF(Destroyed())) {
2056 return NS_ERROR_EDITOR_DESTROYED
;
2058 NS_WARNING_ASSERTION(
2059 NS_SUCCEEDED(rvIgnored
),
2060 "HTMLEditor::SetInlinePropertyOnNode(-moz-editor-invert-value) "
2061 "failed, but ignored");
2065 // If current node is a text node, we need to create `<span>` element
2066 // for it to overwrite parent style. Unfortunately, all browsers
2067 // don't join text nodes when removing a style. Therefore, there
2068 // may be multiple text nodes as adjacent siblings. That's the
2069 // reason why we need to handle text nodes in this loop.
2070 uint32_t startOffset
= content
== startOfRange
.GetContainer()
2071 ? startOfRange
.Offset()
2073 uint32_t endOffset
= content
== endOfRange
.GetContainer()
2074 ? endOfRange
.Offset()
2075 : content
->Length();
2076 nsresult rv
= SetInlinePropertyOnTextNode(
2077 MOZ_KnownLive(*content
->AsText()), startOffset
, endOffset
,
2078 MOZ_KnownLive(*style
.mProperty
), MOZ_KnownLive(style
.mAttribute
),
2079 u
"-moz-editor-invert-value"_ns
);
2080 if (NS_FAILED(rv
)) {
2082 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2088 // For avoiding unnecessary loop cost, check whether the style is
2089 // invertible first.
2090 if (style
.mProperty
&&
2091 CSSEditUtils::IsCSSInvertible(*style
.mProperty
, style
.mAttribute
)) {
2092 // Finally, we should remove the style from all leaf text nodes if
2093 // they still have the style.
2094 AutoTArray
<OwningNonNull
<Text
>, 32> leafTextNodes
;
2095 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
2096 if (content
->IsElement()) {
2097 CollectEditableLeafTextNodes(*content
->AsElement(),
2101 for (OwningNonNull
<Text
>& textNode
: leafTextNodes
) {
2102 bool isRemovable
= IsRemovableParentStyleWithNewSpanElement(
2103 MOZ_KnownLive(textNode
), MOZ_KnownLive(style
.mProperty
),
2104 MOZ_KnownLive(style
.mAttribute
));
2105 if (NS_WARN_IF(Destroyed())) {
2106 return NS_ERROR_EDITOR_DESTROYED
;
2111 // MOZ_KnownLive because 'leafTextNodes' is guaranteed to
2113 nsresult rv
= SetInlinePropertyOnTextNode(
2114 MOZ_KnownLive(textNode
), 0, textNode
->TextLength(),
2115 MOZ_KnownLive(*style
.mProperty
),
2116 MOZ_KnownLive(style
.mAttribute
),
2117 u
"-moz-editor-invert-value"_ns
);
2118 if (NS_FAILED(rv
)) {
2120 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2126 } // for-loop of selection ranges
2127 } // for-loop of styles
2128 } // AutoSelectionRestorer and AutoTransactionsConserveSelection
2130 // Restoring `Selection` may cause destroying us.
2131 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
: NS_OK
;
2134 bool HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
2135 nsIContent
& aContent
, nsAtom
* aHTMLProperty
, nsAtom
* aAttribute
) const {
2136 // We don't support to remove all inline styles with this path.
2137 if (!aHTMLProperty
) {
2141 // First check whether the style is invertible since this is the fastest
2143 if (!CSSEditUtils::IsCSSInvertible(*aHTMLProperty
, aAttribute
)) {
2147 // If parent block has the removing style, we should create `<span>`
2148 // element to remove the style even in HTML mode since Chrome does it.
2149 if (!CSSEditUtils::IsCSSEditableProperty(&aContent
, aHTMLProperty
,
2154 // aContent's computed style indicates the CSS equivalence to
2155 // the HTML style to remove is applied; but we found no element
2156 // in the ancestors of aContent carrying specified styles;
2157 // assume it comes from a rule and let's try to insert a span
2158 // "inverting" the style
2159 nsAutoString emptyString
;
2160 bool isSet
= CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
2161 aContent
, aHTMLProperty
, aAttribute
, emptyString
);
2162 return NS_WARN_IF(Destroyed()) ? false : isSet
;
2165 void HTMLEditor::CollectEditableLeafTextNodes(
2166 Element
& aElement
, nsTArray
<OwningNonNull
<Text
>>& aLeafTextNodes
) const {
2167 for (nsIContent
* child
= aElement
.GetFirstChild(); child
;
2168 child
= child
->GetNextSibling()) {
2169 if (child
->IsElement()) {
2170 CollectEditableLeafTextNodes(*child
->AsElement(), aLeafTextNodes
);
2173 if (child
->IsText()) {
2174 aLeafTextNodes
.AppendElement(*child
->AsText());
2179 nsresult
HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal
* aPrincipal
) {
2180 AutoEditActionDataSetter
editActionData(*this, EditAction::eIncrementFontSize
,
2182 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2183 if (NS_FAILED(rv
)) {
2184 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2185 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2186 return EditorBase::ToGenericNSResult(rv
);
2189 rv
= RelativeFontChange(FontSize::incr
);
2190 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2191 "HTMLEditor::RelativeFontChange(FontSize::incr) failed");
2192 return EditorBase::ToGenericNSResult(rv
);
2195 nsresult
HTMLEditor::DecreaseFontSizeAsAction(nsIPrincipal
* aPrincipal
) {
2196 AutoEditActionDataSetter
editActionData(*this, EditAction::eDecrementFontSize
,
2198 nsresult rv
= editActionData
.CanHandleAndMaybeDispatchBeforeInputEvent();
2199 if (NS_FAILED(rv
)) {
2200 NS_WARNING_ASSERTION(rv
== NS_ERROR_EDITOR_ACTION_CANCELED
,
2201 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2202 return EditorBase::ToGenericNSResult(rv
);
2205 rv
= RelativeFontChange(FontSize::decr
);
2206 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2207 "HTMLEditor::RelativeFontChange(FontSize::decr) failed");
2208 return EditorBase::ToGenericNSResult(rv
);
2211 nsresult
HTMLEditor::RelativeFontChange(FontSize aDir
) {
2212 MOZ_ASSERT(IsEditActionDataAvailable());
2214 DebugOnly
<nsresult
> rvIgnored
= CommitComposition();
2215 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
2216 "EditorBase::CommitComposition() failed, but ignored");
2218 // If selection is collapsed, set typing state
2219 if (SelectionRefPtr()->IsCollapsed()) {
2220 nsAtom
& atom
= aDir
== FontSize::incr
? *nsGkAtoms::big
: *nsGkAtoms::small
;
2222 // Let's see in what kind of element the selection is
2223 if (NS_WARN_IF(!SelectionRefPtr()->RangeCount())) {
2226 RefPtr
<const nsRange
> firstRange
= SelectionRefPtr()->GetRangeAt(0);
2227 if (NS_WARN_IF(!firstRange
) ||
2228 NS_WARN_IF(!firstRange
->GetStartContainer())) {
2231 OwningNonNull
<nsINode
> selectedNode
= *firstRange
->GetStartContainer();
2232 if (selectedNode
->IsText()) {
2233 if (NS_WARN_IF(!selectedNode
->GetParentNode())) {
2236 selectedNode
= *selectedNode
->GetParentNode();
2238 if (!HTMLEditUtils::CanNodeContain(selectedNode
, atom
)) {
2242 // Manipulating text attributes on a collapsed selection only sets state
2243 // for the next text insertion
2244 mTypeInState
->SetProp(&atom
, nullptr, u
""_ns
);
2248 // Wrap with txn batching, rules sniffing, and selection preservation code
2249 AutoPlaceholderBatch
treatAsOneTransaction(*this,
2250 ScrollSelectionIntoView::Yes
);
2251 IgnoredErrorResult ignoredError
;
2252 AutoEditSubActionNotifier
startToHandleEditSubAction(
2253 *this, EditSubAction::eSetTextProperty
, nsIEditor::eNext
, ignoredError
);
2254 if (NS_WARN_IF(ignoredError
.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED
))) {
2255 return ignoredError
.StealNSResult();
2257 NS_WARNING_ASSERTION(
2258 !ignoredError
.Failed(),
2259 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2261 AutoSelectionRestorer
restoreSelectionLater(*this);
2262 AutoTransactionsConserveSelection
dontChangeMySelection(*this);
2264 // Loop through the ranges in the selection
2265 AutoSelectionRangeArray
arrayOfRanges(SelectionRefPtr());
2266 for (auto& range
: arrayOfRanges
.mRanges
) {
2267 // Adjust range to include any ancestors with entirely selected children
2268 nsresult rv
= PromoteInlineRange(*range
);
2269 if (NS_FAILED(rv
)) {
2270 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
2274 // Check for easy case: both range endpoints in same text node
2275 nsCOMPtr
<nsINode
> startNode
= range
->GetStartContainer();
2276 nsCOMPtr
<nsINode
> endNode
= range
->GetEndContainer();
2277 MOZ_ASSERT(startNode
);
2278 MOZ_ASSERT(endNode
);
2279 if (startNode
== endNode
&& startNode
->IsText()) {
2280 nsresult rv
= RelativeFontChangeOnTextNode(
2281 aDir
, MOZ_KnownLive(*startNode
->GetAsText()), range
->StartOffset(),
2282 range
->EndOffset());
2283 if (NS_FAILED(rv
)) {
2284 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2288 // Not the easy case. Range not contained in single text node. There
2289 // are up to three phases here. There are all the nodes reported by the
2290 // subtree iterator to be processed. And there are potentially a
2291 // starting textnode and an ending textnode which are only partially
2292 // contained by the range.
2294 // Let's handle the nodes reported by the iterator. These nodes are
2295 // entirely contained in the selection range. We build up a list of them
2296 // (since doing operations on the document during iteration would perturb
2299 // Iterate range and build up array
2300 ContentSubtreeIterator subtreeIter
;
2301 if (NS_SUCCEEDED(subtreeIter
.Init(range
))) {
2302 nsTArray
<OwningNonNull
<nsIContent
>> arrayOfContents
;
2303 for (; !subtreeIter
.IsDone(); subtreeIter
.Next()) {
2304 if (NS_WARN_IF(!subtreeIter
.GetCurrentNode()->IsContent())) {
2305 return NS_ERROR_FAILURE
;
2307 OwningNonNull
<nsIContent
> content
=
2308 *subtreeIter
.GetCurrentNode()->AsContent();
2310 if (EditorUtils::IsEditableContent(content
, EditorType::HTML
)) {
2311 arrayOfContents
.AppendElement(content
);
2315 // Now that we have the list, do the font size change on each node
2316 for (OwningNonNull
<nsIContent
>& content
: arrayOfContents
) {
2317 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it
2319 nsresult rv
= RelativeFontChangeOnNode(
2320 aDir
== FontSize::incr
? +1 : -1, MOZ_KnownLive(content
));
2321 if (NS_FAILED(rv
)) {
2322 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2327 // Now check the start and end parents of the range to see if they need
2328 // to be separately handled (they do if they are text nodes, due to how
2329 // the subtree iterator works - it will not have reported them).
2330 if (startNode
->IsText() && EditorUtils::IsEditableContent(
2331 *startNode
->AsText(), EditorType::HTML
)) {
2332 nsresult rv
= RelativeFontChangeOnTextNode(
2333 aDir
, MOZ_KnownLive(*startNode
->AsText()), range
->StartOffset(),
2334 startNode
->Length());
2335 if (NS_FAILED(rv
)) {
2336 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2340 if (endNode
->IsText() && EditorUtils::IsEditableContent(
2341 *endNode
->AsText(), EditorType::HTML
)) {
2342 nsresult rv
= RelativeFontChangeOnTextNode(
2343 aDir
, MOZ_KnownLive(*endNode
->AsText()), 0, range
->EndOffset());
2344 if (NS_FAILED(rv
)) {
2345 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2355 nsresult
HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir
,
2357 int32_t aStartOffset
,
2358 int32_t aEndOffset
) {
2359 // Don't need to do anything if no characters actually selected
2360 if (aStartOffset
== aEndOffset
) {
2364 if (!aTextNode
.GetParentNode() ||
2365 !HTMLEditUtils::CanNodeContain(*aTextNode
.GetParentNode(),
2370 // -1 is a magic value meaning to the end of node
2371 if (aEndOffset
== -1) {
2372 aEndOffset
= aTextNode
.Length();
2375 // Make the range an independent node.
2376 nsCOMPtr
<nsIContent
> textNodeForTheRange
= &aTextNode
;
2378 // Split at the end of the range.
2379 EditorDOMPoint
atEnd(textNodeForTheRange
, aEndOffset
);
2380 if (!atEnd
.IsEndOfContainer()) {
2381 // We need to split off back of text node
2383 textNodeForTheRange
= SplitNodeWithTransaction(atEnd
, error
);
2384 if (error
.Failed()) {
2385 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
2386 return error
.StealNSResult();
2390 // Split at the start of the range.
2391 EditorDOMPoint
atStart(textNodeForTheRange
, aStartOffset
);
2392 if (!atStart
.IsStartOfContainer()) {
2393 // We need to split off front of text node
2395 nsCOMPtr
<nsIContent
> newLeftNode
= SplitNodeWithTransaction(atStart
, error
);
2396 if (error
.Failed()) {
2397 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
2398 return error
.StealNSResult();
2400 Unused
<< newLeftNode
;
2403 // Look for siblings that are correct type of node
2404 nsAtom
* nodeType
= aDir
== FontSize::incr
? nsGkAtoms::big
: nsGkAtoms::small
;
2405 nsCOMPtr
<nsIContent
> sibling
= GetPriorHTMLSibling(textNodeForTheRange
);
2406 if (sibling
&& sibling
->IsHTMLElement(nodeType
)) {
2407 // Previous sib is already right kind of inline node; slide this over
2408 nsresult rv
= MoveNodeToEndWithTransaction(*textNodeForTheRange
, *sibling
);
2409 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2410 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2413 sibling
= GetNextHTMLSibling(textNodeForTheRange
);
2414 if (sibling
&& sibling
->IsHTMLElement(nodeType
)) {
2415 // Following sib is already right kind of inline node; slide this over
2416 nsresult rv
= MoveNodeWithTransaction(*textNodeForTheRange
,
2417 EditorDOMPoint(sibling
, 0));
2418 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2419 "HTMLEditor::MoveNodeWithTransaction() failed");
2423 // Else reparent the node inside font node with appropriate relative size
2424 RefPtr
<Element
> newElement
= InsertContainerWithTransaction(
2425 *textNodeForTheRange
, MOZ_KnownLive(*nodeType
));
2426 NS_WARNING_ASSERTION(newElement
,
2427 "HTMLEditor::InsertContainerWithTransaction() failed");
2428 return newElement
? NS_OK
: NS_ERROR_FAILURE
;
2431 nsresult
HTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange
,
2435 /* This routine looks for all the font nodes in the tree rooted by aNode,
2436 including aNode itself, looking for font nodes that have the size attr
2437 set. Any such nodes need to have big or small put inside them, since
2438 they override any big/small that are above them.
2441 // Can only change font size by + or - 1
2442 if (aSizeChange
!= 1 && aSizeChange
!= -1) {
2443 return NS_ERROR_ILLEGAL_VALUE
;
2446 // If this is a font node with size, put big/small inside it.
2447 if (aNode
->IsHTMLElement(nsGkAtoms::font
) &&
2448 aNode
->AsElement()->HasAttr(kNameSpaceID_None
, nsGkAtoms::size
)) {
2449 // Cycle through children and adjust relative font size.
2450 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> childList
;
2451 for (nsIContent
* child
= aNode
->GetFirstChild(); child
;
2452 child
= child
->GetNextSibling()) {
2453 childList
.AppendElement(child
);
2456 for (const auto& child
: childList
) {
2457 // MOZ_KnownLive because 'childList' is guaranteed to
2459 nsresult rv
= RelativeFontChangeOnNode(aSizeChange
, MOZ_KnownLive(child
));
2460 if (NS_FAILED(rv
)) {
2461 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2466 // RelativeFontChangeOnNode already calls us recursively,
2467 // so we don't need to check our children again.
2471 // Otherwise cycle through the children.
2472 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> childList
;
2473 for (nsIContent
* child
= aNode
->GetFirstChild(); child
;
2474 child
= child
->GetNextSibling()) {
2475 childList
.AppendElement(child
);
2478 for (const auto& child
: childList
) {
2479 // MOZ_KnownLive because 'childList' is guaranteed to
2481 nsresult rv
= RelativeFontChangeHelper(aSizeChange
, MOZ_KnownLive(child
));
2482 if (NS_FAILED(rv
)) {
2483 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2491 nsresult
HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange
,
2492 nsIContent
* aNode
) {
2494 // Can only change font size by + or - 1
2495 if (aSizeChange
!= 1 && aSizeChange
!= -1) {
2496 return NS_ERROR_ILLEGAL_VALUE
;
2500 if (aSizeChange
== 1) {
2501 atom
= nsGkAtoms::big
;
2503 atom
= nsGkAtoms::small
;
2506 // Is it the opposite of what we want?
2507 if ((aSizeChange
== 1 && aNode
->IsHTMLElement(nsGkAtoms::small
)) ||
2508 (aSizeChange
== -1 && aNode
->IsHTMLElement(nsGkAtoms::big
))) {
2509 // first populate any nested font tags that have the size attr set
2510 nsresult rv
= RelativeFontChangeHelper(aSizeChange
, aNode
);
2511 if (NS_FAILED(rv
)) {
2512 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2515 // in that case, just remove this node and pull up the children
2516 rv
= RemoveContainerWithTransaction(MOZ_KnownLive(*aNode
->AsElement()));
2517 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2518 "HTMLEditor::RemoveContainerWithTransaction() failed");
2522 // can it be put inside a "big" or "small"?
2523 if (HTMLEditUtils::CanNodeContain(*atom
, *aNode
)) {
2524 // first populate any nested font tags that have the size attr set
2525 nsresult rv
= RelativeFontChangeHelper(aSizeChange
, aNode
);
2526 if (NS_FAILED(rv
)) {
2527 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2532 // first look at siblings of aNode for matching bigs or smalls.
2533 // if we find one, move aNode into it.
2534 nsCOMPtr
<nsIContent
> sibling
= GetPriorHTMLSibling(aNode
);
2535 if (sibling
&& sibling
->IsHTMLElement(atom
)) {
2536 // previous sib is already right kind of inline node; slide this over into
2538 nsresult rv
= MoveNodeToEndWithTransaction(*aNode
, *sibling
);
2539 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
2540 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2544 sibling
= GetNextHTMLSibling(aNode
);
2545 if (sibling
&& sibling
->IsHTMLElement(atom
)) {
2546 // following sib is already right kind of inline node; slide this over
2548 return MoveNodeWithTransaction(*aNode
, EditorDOMPoint(sibling
, 0));
2551 // else insert it above aNode
2552 RefPtr
<Element
> newElement
=
2553 InsertContainerWithTransaction(*aNode
, MOZ_KnownLive(*atom
));
2554 NS_WARNING_ASSERTION(newElement
,
2555 "HTMLEditor::InsertContainerWithTransaction() failed");
2556 return newElement
? NS_OK
: NS_ERROR_FAILURE
;
2559 // none of the above? then cycle through the children.
2560 // MOOSE: we should group the children together if possible
2561 // into a single "big" or "small". For the moment they are
2562 // each getting their own.
2563 AutoTArray
<nsCOMPtr
<nsIContent
>, 10> childList
;
2564 for (nsIContent
* child
= aNode
->GetFirstChild(); child
;
2565 child
= child
->GetNextSibling()) {
2566 childList
.AppendElement(child
);
2569 for (const auto& child
: childList
) {
2570 // MOZ_KnownLive because 'childList' is guaranteed to
2572 nsresult rv
= RelativeFontChangeOnNode(aSizeChange
, MOZ_KnownLive(child
));
2573 if (NS_FAILED(rv
)) {
2574 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2582 NS_IMETHODIMP
HTMLEditor::GetFontFaceState(bool* aMixed
, nsAString
& outFace
) {
2583 if (NS_WARN_IF(!aMixed
)) {
2584 return NS_ERROR_INVALID_ARG
;
2590 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
2591 if (NS_WARN_IF(!editActionData
.CanHandle())) {
2592 return NS_ERROR_NOT_INITIALIZED
;
2595 bool first
, any
, all
;
2597 nsresult rv
= GetInlinePropertyBase(*nsGkAtoms::font
, nsGkAtoms::face
,
2598 nullptr, &first
, &any
, &all
, &outFace
);
2599 if (NS_FAILED(rv
)) {
2601 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
2603 return EditorBase::ToGenericNSResult(rv
);
2606 return NS_OK
; // mixed
2613 // if there is no font face, check for tt
2614 rv
= GetInlinePropertyBase(*nsGkAtoms::tt
, nullptr, nullptr, &first
, &any
,
2616 if (NS_FAILED(rv
)) {
2617 NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed");
2618 return EditorBase::ToGenericNSResult(rv
);
2621 return NS_OK
; // mixed
2625 outFace
.AssignLiteral("tt");
2629 // there was no font face attrs of any kind. We are in normal font.
2636 nsresult
HTMLEditor::GetFontColorState(bool* aMixed
, nsAString
& aOutColor
) {
2637 if (NS_WARN_IF(!aMixed
)) {
2638 return NS_ERROR_INVALID_ARG
;
2642 aOutColor
.Truncate();
2644 AutoEditActionDataSetter
editActionData(*this, EditAction::eNotEditing
);
2645 if (NS_WARN_IF(!editActionData
.CanHandle())) {
2646 return NS_ERROR_NOT_INITIALIZED
;
2649 bool first
, any
, all
;
2650 nsresult rv
= GetInlinePropertyBase(*nsGkAtoms::font
, nsGkAtoms::color
,
2651 nullptr, &first
, &any
, &all
, &aOutColor
);
2652 if (NS_FAILED(rv
)) {
2654 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
2656 return EditorBase::ToGenericNSResult(rv
);
2660 return NS_OK
; // mixed
2668 // there was no font color attrs of any kind..
2669 aOutColor
.Truncate();
2675 // the return value is true only if the instance of the HTML editor we created
2676 // can handle CSS styles (for instance, Composer can, Messenger can't) and if
2677 // the CSS preference is checked
2678 NS_IMETHODIMP
HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled
) {
2679 *aIsCSSEnabled
= IsCSSEnabled();
2683 bool HTMLEditor::HasStyleOrIdOrClassAttribute(Element
& aElement
) {
2684 return aElement
.HasNonEmptyAttr(nsGkAtoms::style
) ||
2685 aElement
.HasNonEmptyAttr(nsGkAtoms::_class
) ||
2686 aElement
.HasNonEmptyAttr(nsGkAtoms::id
);
2689 } // namespace mozilla