Bug 1686495 [wpt PR 27132] - Add tests for proposed WebDriver Shadow DOM support...
[gecko.git] / editor / libeditor / HTMLStyleEditor.cpp
blob5a62978a635f68f46079618bb41cf6d4d909918c
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"
22 #include "nsCOMPtr.h"
23 #include "nsCaseTreatment.h"
24 #include "nsComponentManagerUtils.h"
25 #include "nsDebug.h"
26 #include "nsError.h"
27 #include "nsGkAtoms.h"
28 #include "nsAtom.h"
29 #include "nsIContent.h"
30 #include "nsNameSpaceManager.h"
31 #include "nsINode.h"
32 #include "nsIPrincipal.h"
33 #include "nsISupportsImpl.h"
34 #include "nsLiteralString.h"
35 #include "nsRange.h"
36 #include "nsReadableUtils.h"
37 #include "nsString.h"
38 #include "nsStringFwd.h"
39 #include "nsStyledElement.h"
40 #include "nsTArray.h"
41 #include "nsUnicharUtils.h"
42 #include "nscore.h"
44 class nsISupports;
46 namespace mozilla {
48 using namespace dom;
50 using ChildBlockBoundary = HTMLEditUtils::ChildBlockBoundary;
52 nsresult HTMLEditor::SetInlinePropertyAsAction(nsAtom& aProperty,
53 nsAtom* aAttribute,
54 const nsAString& aValue,
55 nsIPrincipal* aPrincipal) {
56 AutoEditActionDataSetter editActionData(
57 *this,
58 HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true),
59 aPrincipal);
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);
65 break;
66 case EditAction::eSetColorProperty:
67 case EditAction::eSetBackgroundColorPropertyInline:
68 editActionData.SetColorData(aValue);
69 break;
70 default:
71 break;
74 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
75 if (NS_FAILED(rv)) {
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);
94 if (NS_FAILED(rv)) {
95 NS_WARNING(
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);
104 if (NS_FAILED(rv)) {
105 NS_WARNING(
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);
120 if (NS_FAILED(rv)) {
121 NS_WARNING(
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);
130 if (NS_FAILED(rv)) {
131 NS_WARNING(
132 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::tt, "
133 "RemoveRelatedElements::No) failed");
134 return EditorBase::ToGenericNSResult(rv);
136 } else {
137 nsresult rv = RemoveInlinePropertyInternal(
138 nsGkAtoms::font, nsGkAtoms::face, RemoveRelatedElements::No);
139 if (NS_FAILED(rv)) {
140 NS_WARNING(
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
146 // "tt".
147 property = nsGkAtoms::tt;
148 attribute = nullptr;
149 value.Truncate();
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(
169 *this,
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);
176 break;
177 case EditAction::eSetColorProperty:
178 case EditAction::eSetBackgroundColorPropertyInline:
179 editActionData.SetColorData(aValue);
180 break;
181 default:
182 break;
184 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
185 if (NS_FAILED(rv)) {
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);
212 return NS_OK;
215 // XXX Shouldn't we return before calling `CommitComposition()`?
216 if (IsPlaintextEditor()) {
217 return NS_OK;
220 EditActionResult result = CanHandleHTMLEditSubAction();
221 if (result.Failed() || result.Canceled()) {
222 NS_WARNING_ASSERTION(result.Succeeded(),
223 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
224 return result.Rv();
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
250 // selected
251 nsresult rv = PromoteInlineRange(*range);
252 if (NS_FAILED(rv)) {
253 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
254 return rv;
257 // XXX Shouldn't we skip the range if it's been collapsed by mutation
258 // event listener?
260 EditorDOMPoint startOfRange(range->StartRef());
261 EditorDOMPoint endOfRange(range->EndRef());
262 if (NS_WARN_IF(!startOfRange.IsSet()) ||
263 NS_WARN_IF(!endOfRange.IsSet())) {
264 continue;
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,
273 aAttributeValue);
274 if (NS_FAILED(rv)) {
275 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
276 return rv;
278 continue;
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,
286 // don't warn it.
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(),
303 EditorType::HTML)) {
304 nsresult rv = SetInlinePropertyOnTextNode(
305 MOZ_KnownLive(*startOfRange.GetContainerAsText()),
306 startOfRange.Offset(), startOfRange.GetContainer()->Length(),
307 aProperty, aAttribute, aAttributeValue);
308 if (NS_FAILED(rv)) {
309 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
310 return rv;
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
317 // keep it alive.
318 nsresult rv = SetInlinePropertyOnNode(
319 MOZ_KnownLive(*content), aProperty, aAttribute, aAttributeValue);
320 if (NS_WARN_IF(Destroyed())) {
321 return NS_ERROR_EDITOR_DESTROYED;
323 if (NS_FAILED(rv)) {
324 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
325 return rv;
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(),
332 EditorType::HTML)) {
333 nsresult rv = SetInlinePropertyOnTextNode(
334 MOZ_KnownLive(*endOfRange.GetContainerAsText()), 0,
335 endOfRange.Offset(), aProperty, aAttribute, aAttributeValue);
336 if (NS_FAILED(rv)) {
337 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
338 return rv;
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() &&
356 !aAttribute) {
357 return true;
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)))) {
367 return true;
370 // Now look for things like <font>
371 if (aAttribute) {
372 nsString attrValue;
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).
380 return true;
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
386 // style supports it
387 if (!CSSEditUtils::IsCSSEditableProperty(&aElement, aProperty, aAttribute) ||
388 !aElement.IsHTMLElement(nsGkAtoms::span) ||
389 aElement.GetAttrCount() != 1 ||
390 !aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
391 return false;
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");
401 return false;
403 nsStyledElement* styledNewSpanElement =
404 nsStyledElement::FromNode(newSpanElement);
405 if (!styledNewSpanElement) {
406 return false;
408 // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
409 // RefPtr.
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);
420 return false;
422 nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement);
423 if (!styledElement) {
424 return false;
426 return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
427 *styledElement);
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)) {
435 return NS_OK;
438 // Don't need to do anything if no characters actually selected
439 if (aStartOffset == aEndOffset) {
440 return NS_OK;
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)) {
450 return NS_OK;
452 } else if (IsTextPropertySetByContent(&aText, &aProperty, aAttribute,
453 &aValue)) {
454 return NS_OK;
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
464 ErrorResult error;
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
480 ErrorResult error;
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;
493 if (aAttribute) {
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
506 nsresult rv =
507 MoveNodeToEndWithTransaction(*textNodeForTheRange, element);
508 if (NS_WARN_IF(Destroyed())) {
509 return NS_ERROR_EDITOR_DESTROYED;
511 NS_WARNING_ASSERTION(
512 NS_SUCCEEDED(rv),
513 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
514 return rv;
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");
535 return rv;
540 // Reparent the node inside inline node with appropriate {attribute,value}
541 nsresult rv = SetInlinePropertyOnNode(*textNodeForTheRange, aProperty,
542 aAttribute, aValue);
543 if (NS_WARN_IF(Destroyed())) {
544 return NS_ERROR_EDITOR_DESTROYED;
546 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
547 "HTMLEditor::SetInlinePropertyOnNode() failed");
548 return rv;
551 nsresult HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aContent,
552 nsAtom& aProperty,
553 nsAtom* aAttribute,
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
573 // keep it alive.
574 nsresult rv = SetInlinePropertyOnNode(MOZ_KnownLive(node), aProperty,
575 aAttribute, aValue);
576 if (NS_FAILED(rv)) {
577 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
578 return rv;
582 return NS_OK;
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);
599 if (NS_FAILED(rv)) {
600 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
601 return rv;
603 if (!nextSibling || !nextSibling->IsElement()) {
604 return NS_OK;
606 OwningNonNull<Element> nextElement(*nextSibling->AsElement());
607 Result<bool, nsresult> canMoveIntoNextSibling =
608 ElementIsGoodContainerForTheStyle(nextElement, &aProperty, aAttribute,
609 &aValue);
610 if (canMoveIntoNextSibling.isErr()) {
611 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
612 return canMoveIntoNextSibling.unwrapErr();
614 if (!canMoveIntoNextSibling.inspect()) {
615 return NS_OK;
617 rv = JoinNodesWithTransaction(*previousSibling, *nextSibling);
618 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
619 "HTMLEditor::JoinNodesWithTransaction() failed");
620 return rv;
624 if (nextSibling && nextSibling->IsElement()) {
625 OwningNonNull<Element> nextElement(*nextSibling->AsElement());
626 Result<bool, nsresult> canMoveIntoNextSibling =
627 ElementIsGoodContainerForTheStyle(nextElement, &aProperty, aAttribute,
628 &aValue);
629 if (canMoveIntoNextSibling.isErr()) {
630 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
631 return canMoveIntoNextSibling.unwrapErr();
633 if (canMoveIntoNextSibling.inspect()) {
634 nsresult rv =
635 MoveNodeWithTransaction(aContent, EditorDOMPoint(nextElement, 0));
636 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
637 "HTMLEditor::MoveNodeWithTransaction() failed");
638 return rv;
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)) {
647 return NS_OK;
649 } else if (IsTextPropertySetByContent(&aContent, &aProperty, aAttribute,
650 &aValue)) {
651 return NS_OK;
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
659 // `<span>` element.
660 aValue.EqualsLiteral("-moz-editor-invert-value");
662 if (useCSS) {
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();
669 } else {
670 spanElement = InsertContainerWithTransaction(aContent, *nsGkAtoms::span);
671 if (!spanElement) {
672 NS_WARNING(
673 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
674 "failed");
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
683 // RefPtr.
684 Result<int32_t, nsresult> result =
685 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
686 MOZ_KnownLive(*spanStyledElement), &aProperty, aAttribute,
687 &aValue);
688 if (result.isErr()) {
689 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
690 NS_WARNING(
691 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
692 "destroyed the editor");
693 return NS_ERROR_EDITOR_DESTROYED;
695 NS_WARNING(
696 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
697 "failed, "
698 "but ignored");
701 return NS_OK;
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");
714 return rv;
717 // ok, chuck it in its very own container
718 RefPtr<Element> newContainerElement = InsertContainerWithTransaction(
719 aContent, aProperty, aAttribute ? *aAttribute : *nsGkAtoms::_empty,
720 aValue);
721 NS_WARNING_ASSERTION(newContainerElement,
722 "HTMLEditor::InsertContainerWithTransaction() failed");
723 return newContainerElement ? NS_OK : NS_ERROR_FAILURE;
726 nsresult HTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
727 nsAtom& aProperty,
728 nsAtom* aAttribute,
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);
740 if (NS_FAILED(rv)) {
741 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
742 return rv;
746 if (aNode.GetParentNode()) {
747 // The node is still where it was
748 nsresult rv =
749 SetInlinePropertyOnNodeImpl(aNode, aProperty, aAttribute, aValue);
750 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
751 "HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
752 return rv;
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
774 // keep it alive.
775 nsresult rv = SetInlinePropertyOnNodeImpl(MOZ_KnownLive(content), aProperty,
776 aAttribute, aValue);
777 if (NS_FAILED(rv)) {
778 NS_WARNING("HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
779 return rv;
783 return NS_OK;
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,
803 aAttribute);
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()) {
811 NS_WARNING(
812 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
813 "split point");
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);
823 resultAtEnd =
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()) {
832 NS_WARNING(
833 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
834 "split point");
835 return SplitRangeOffResult(NS_ERROR_FAILURE);
840 return SplitRangeOffResult(startOfRange, resultAtStart, endOfRange,
841 resultAtEnd);
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
862 // IsCSSEnabled().
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(),
870 EditorType::HTML)) {
871 break;
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;
881 if (useCSS &&
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
885 // CSS styles
886 nsAutoString firstValue;
887 isSetByCSS = CSSEditUtils::IsSpecifiedCSSEquivalentToHTMLInlineStyleSet(
888 *content, aProperty, aAttribute, firstValue);
890 if (!isSetByCSS) {
891 if (!content->IsElement()) {
892 continue;
894 // If aProperty is set, we need to split only elements which applies the
895 // given style.
896 if (aProperty) {
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))) {
902 continue;
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())) {
909 continue;
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());
932 return result;
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
962 // it now.
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);
971 if (NS_FAILED(rv)) {
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>`.
992 // ^^^^^^^^^^^^^^
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>`
1001 // element.
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,
1015 aAttribute);
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...
1043 return EditResult(
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.
1062 if (brElement) {
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>`
1078 // ^^^^^^^^^
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
1084 // selection.
1085 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToPutCaret);
1086 nsresult rv = RemoveStyleInside(
1087 MOZ_KnownLive(
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();
1102 while (child) {
1103 // cache next sibling since we might remove child
1104 // XXX Well, the next sibling is moved from `aElement`, shouldn't we skip
1105 // it here?
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");
1112 return rv;
1115 child = ToRefPtr(std::move(nextSibling));
1118 // Next, remove the element or its attribute.
1119 bool removeHTMLStyle = false;
1120 if (aProperty) {
1121 removeHTMLStyle =
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
1131 // nullptr?
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
1139 // entirely.
1140 if (!aAttribute) {
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?
1144 if (aProperty &&
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;
1154 if (!spanElement) {
1155 NS_WARNING(
1156 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
1157 "failed");
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)) {
1166 NS_WARNING(
1167 "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::style) "
1168 "failed");
1169 return rv;
1171 rv = CloneAttributeWithTransaction(*nsGkAtoms::_class, *spanElement,
1172 aElement);
1173 if (NS_WARN_IF(Destroyed())) {
1174 return NS_ERROR_EDITOR_DESTROYED;
1176 if (NS_FAILED(rv)) {
1177 NS_WARNING(
1178 "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::_class) "
1179 "failed");
1180 return rv;
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");
1189 return rv;
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");
1202 return rv;
1204 } else {
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");
1211 return rv;
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,
1222 aAttribute)) {
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.
1227 nsresult rv =
1228 mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithTransaction(
1229 MOZ_KnownLive(*styledElement), aProperty, aAttribute, nullptr);
1230 if (rv == NS_ERROR_EDITOR_DESTROYED) {
1231 NS_WARNING(
1232 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1233 "destroyed the editor");
1234 return NS_ERROR_EDITOR_DESTROYED;
1236 NS_WARNING_ASSERTION(
1237 NS_SUCCEEDED(rv),
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");
1265 return rv;
1268 return NS_OK;
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)) {
1278 return false;
1281 // if it's the attribute we know about, or a special _moz attribute,
1282 // keep looking
1283 if (name->LocalName() != aAttribute) {
1284 nsAutoString attrString;
1285 name->LocalName()->ToString(attrString);
1286 if (!StringBeginsWith(attrString, u"_moz"_ns)) {
1287 return false;
1291 // if we made it through all of them without finding a real attribute
1292 // other than aAttribute, then return true
1293 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)) {
1307 break;
1309 if (!HTMLEditUtils::IsNamedAnchor(element)) {
1310 continue;
1312 newRangeStart.Set(element);
1313 break;
1316 if (!newRangeStart.GetContainerAsContent()) {
1317 NS_WARNING(
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)) {
1327 break;
1329 if (!HTMLEditUtils::IsNamedAnchor(element)) {
1330 continue;
1332 newRangeEnd.SetAfter(element);
1333 break;
1336 if (!newRangeEnd.GetContainerAsContent()) {
1337 NS_WARNING(
1338 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1339 "element from end container");
1340 return NS_ERROR_FAILURE;
1343 if (newRangeStart == aRange.StartRef() && newRangeEnd == aRange.EndRef()) {
1344 return NS_OK;
1347 nsresult rv = aRange.SetStartAndEnd(newRangeStart.ToRawRangeBoundary(),
1348 newRangeEnd.ToRawRangeBoundary());
1349 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
1350 return rv;
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)) {
1365 break;
1367 newRangeStart.Set(content);
1369 if (!newRangeStart.GetContainerAsContent()) {
1370 NS_WARNING(
1371 "HTMLEditor::PromoteInlineRange() reached root element from start "
1372 "container");
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)) {
1383 break;
1385 newRangeEnd.SetAfter(content);
1387 if (!newRangeEnd.GetContainerAsContent()) {
1388 NS_WARNING(
1389 "HTMLEditor::PromoteInlineRange() reached root element from end "
1390 "container");
1391 return NS_ERROR_FAILURE;
1394 if (newRangeStart == aRange.StartRef() && newRangeEnd == aRange.EndRef()) {
1395 return NS_OK;
1398 nsresult rv = aRange.SetStartAndEnd(newRangeStart.ToRawRangeBoundary(),
1399 newRangeEnd.ToRawRangeBoundary());
1400 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
1401 return rv;
1404 bool HTMLEditor::IsStartOfContainerOrBeforeFirstEditableChild(
1405 const EditorRawDOMPoint& aPoint) const {
1406 MOZ_ASSERT(aPoint.IsSet());
1408 if (aPoint.IsStartOfContainer()) {
1409 return true;
1412 if (aPoint.IsInTextNode()) {
1413 return false;
1416 nsIContent* firstEditableChild =
1417 GetFirstEditableChild(*aPoint.GetContainer());
1418 if (!firstEditableChild) {
1419 return true;
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()) {
1429 return true;
1432 if (aPoint.IsInTextNode()) {
1433 return false;
1436 nsIContent* lastEditableChild = GetLastEditableChild(*aPoint.GetContainer());
1437 if (!lastEditableChild) {
1438 return true;
1440 return EditorRawDOMPoint(lastEditableChild).Offset() < aPoint.Offset();
1443 nsresult HTMLEditor::GetInlinePropertyBase(nsAtom& aHTMLProperty,
1444 nsAtom* aAttribute,
1445 const nsAString* aValue,
1446 bool* aFirst, bool* aAny, bool* aAll,
1447 nsAString* outValue) const {
1448 MOZ_ASSERT(IsEditActionDataAvailable());
1450 *aAny = false;
1451 *aAll = true;
1452 *aFirst = false;
1453 bool first = true;
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?
1459 if (range) {
1460 // For each range, set a flag
1461 bool firstNodeInRange = true;
1463 if (isCollapsed) {
1464 nsCOMPtr<nsINode> collapsedNode = range->GetStartContainer();
1465 if (NS_WARN_IF(!collapsedNode)) {
1466 return NS_ERROR_FAILURE;
1468 bool isSet, theSetting;
1469 nsString tOutString;
1470 if (aAttribute) {
1471 mTypeInState->GetTypingState(isSet, theSetting, &aHTMLProperty,
1472 aAttribute, &tOutString);
1473 if (outValue) {
1474 outValue->Assign(tOutString);
1476 } else {
1477 mTypeInState->GetTypingState(isSet, theSetting, &aHTMLProperty);
1479 if (isSet) {
1480 *aFirst = *aAny = *aAll = theSetting;
1481 return NS_OK;
1484 if (collapsedNode->IsContent() &&
1485 CSSEditUtils::IsCSSEditableProperty(collapsedNode, &aHTMLProperty,
1486 aAttribute)) {
1487 if (aValue) {
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;
1497 if (outValue) {
1498 outValue->Assign(tOutString);
1500 return NS_OK;
1503 isSet = IsTextPropertySetByContent(collapsedNode, &aHTMLProperty,
1504 aAttribute, aValue, outValue);
1505 *aFirst = *aAny = *aAll = isSet;
1506 return NS_OK;
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()) {
1522 continue;
1524 nsCOMPtr<nsIContent> content =
1525 postOrderIter.GetCurrentNode()->AsContent();
1527 if (content->IsHTMLElement(nsGkAtoms::body)) {
1528 break;
1531 // just ignore any non-editable nodes
1532 if (content->IsText() &&
1533 (!EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1534 IsEmptyTextNode(*content))) {
1535 continue;
1537 if (content->GetAsText()) {
1538 if (!isCollapsed && first && firstNodeInRange) {
1539 firstNodeInRange = false;
1540 if (range->StartOffset() == content->Length()) {
1541 continue;
1543 } else if (content == endNode && !endOffset) {
1544 continue;
1546 } else if (content->IsElement()) {
1547 // handle non-text leaf nodes here
1548 continue;
1551 bool isSet = false;
1552 bool useTextDecoration =
1553 &aHTMLProperty == nsGkAtoms::u || &aHTMLProperty == nsGkAtoms::strike;
1554 if (first) {
1555 if (CSSEditUtils::IsCSSEditableProperty(content, &aHTMLProperty,
1556 aAttribute)) {
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
1560 if (aValue) {
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;
1568 } else {
1569 isSet = IsTextPropertySetByContent(content, &aHTMLProperty,
1570 aAttribute, aValue, &firstValue);
1572 *aFirst = isSet;
1573 first = false;
1574 if (outValue) {
1575 *outValue = firstValue;
1577 } else {
1578 if (CSSEditUtils::IsCSSEditableProperty(content, &aHTMLProperty,
1579 aAttribute)) {
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
1583 if (aValue) {
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;
1591 } else {
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.
1602 // e.g.
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
1611 // check.
1612 (!useTextDecoration || *aFirst != isSet)) {
1613 *aAll = false;
1617 if (isSet) {
1618 *aAny = true;
1619 } else {
1620 *aAll = false;
1624 if (!*aAny) {
1625 // make sure that if none of the selection is set, we don't report all is
1626 // set
1627 *aAll = false;
1629 return NS_OK;
1632 NS_IMETHODIMP HTMLEditor::GetInlineProperty(const nsAString& aHTMLProperty,
1633 const nsAString& aAttribute,
1634 const nsAString& aValue,
1635 bool* aFirst, bool* aAny,
1636 bool* aAll) {
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");
1643 return rv;
1646 nsresult HTMLEditor::GetInlineProperty(nsAtom* aHTMLProperty,
1647 nsAtom* aAttribute,
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");
1678 return rv;
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,
1718 ignoredError);
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");
1726 rv =
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(
1738 *this,
1739 HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty, aAttribute,
1740 false),
1741 aPrincipal);
1742 switch (editActionData.GetEditAction()) {
1743 case EditAction::eRemoveFontFamilyProperty:
1744 MOZ_ASSERT(!u""_ns.IsVoid());
1745 editActionData.SetData(u""_ns);
1746 break;
1747 case EditAction::eRemoveColorProperty:
1748 case EditAction::eRemoveBackgroundColorPropertyInline:
1749 editActionData.SetColorData(u""_ns);
1750 break;
1751 default:
1752 break;
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(
1775 *this,
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);
1781 break;
1782 case EditAction::eRemoveColorProperty:
1783 case EditAction::eRemoveBackgroundColorPropertyInline:
1784 editActionData.SetColorData(u""_ns);
1785 break;
1786 default:
1787 break;
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);
1865 } else {
1866 mTypeInState->ClearProp(style.mProperty, style.mAttribute);
1869 } else {
1870 mTypeInState->ClearAllProps();
1872 return NS_OK;
1875 // XXX Shouldn't we quit before calling `CommitComposition()`?
1876 if (IsPlaintextEditor()) {
1877 return NS_OK;
1880 EditActionResult result = CanHandleHTMLEditSubAction();
1881 if (result.Failed() || result.Canceled()) {
1882 NS_WARNING_ASSERTION(result.Succeeded(),
1883 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1884 return result.Rv();
1887 AutoPlaceholderBatch treatAsOneTransaction(*this,
1888 ScrollSelectionIntoView::Yes);
1889 IgnoredErrorResult ignoredError;
1890 AutoEditSubActionNotifier startToHandleEditSubAction(
1891 *this, EditSubAction::eRemoveTextProperty, nsIEditor::eNext,
1892 ignoredError);
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)) {
1917 NS_WARNING(
1918 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() failed");
1919 return rv;
1921 } else {
1922 // Adjust range to include any ancestors whose children are entirely
1923 // selected
1924 nsresult rv = PromoteInlineRange(*range);
1925 if (NS_FAILED(rv)) {
1926 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
1927 return rv;
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()) {
1939 NS_WARNING(
1940 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
1941 "failed");
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())) {
1954 continue;
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");
1966 return rv;
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)) {
1975 continue;
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()) {
1990 continue;
1992 } else {
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()) {
2026 nsresult rv =
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");
2032 return rv;
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;
2042 if (!isRemovable) {
2043 continue;
2046 if (!content->IsText()) {
2047 // XXX Do we need to call this even when data node or something? If
2048 // so, for what?
2049 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
2050 // keep it alive.
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");
2062 continue;
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()
2072 : 0;
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)) {
2081 NS_WARNING(
2082 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2083 "value) failed");
2084 return rv;
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(),
2098 leafTextNodes);
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;
2108 if (!isRemovable) {
2109 continue;
2111 // MOZ_KnownLive because 'leafTextNodes' is guaranteed to
2112 // keep it alive.
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)) {
2119 NS_WARNING(
2120 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2121 "value) failed");
2122 return rv;
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) {
2138 return false;
2141 // First check whether the style is invertible since this is the fastest
2142 // check.
2143 if (!CSSEditUtils::IsCSSInvertible(*aHTMLProperty, aAttribute)) {
2144 return false;
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,
2150 aAttribute)) {
2151 return false;
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);
2171 continue;
2173 if (child->IsText()) {
2174 aLeafTextNodes.AppendElement(*child->AsText());
2179 nsresult HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
2180 AutoEditActionDataSetter editActionData(*this, EditAction::eIncrementFontSize,
2181 aPrincipal);
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,
2197 aPrincipal);
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())) {
2224 return NS_OK;
2226 RefPtr<const nsRange> firstRange = SelectionRefPtr()->GetRangeAt(0);
2227 if (NS_WARN_IF(!firstRange) ||
2228 NS_WARN_IF(!firstRange->GetStartContainer())) {
2229 return NS_OK;
2231 OwningNonNull<nsINode> selectedNode = *firstRange->GetStartContainer();
2232 if (selectedNode->IsText()) {
2233 if (NS_WARN_IF(!selectedNode->GetParentNode())) {
2234 return NS_OK;
2236 selectedNode = *selectedNode->GetParentNode();
2238 if (!HTMLEditUtils::CanNodeContain(selectedNode, atom)) {
2239 return NS_OK;
2242 // Manipulating text attributes on a collapsed selection only sets state
2243 // for the next text insertion
2244 mTypeInState->SetProp(&atom, nullptr, u""_ns);
2245 return NS_OK;
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");
2271 return rv;
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");
2285 return rv;
2287 } else {
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
2297 // the iterator).
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
2318 // alive.
2319 nsresult rv = RelativeFontChangeOnNode(
2320 aDir == FontSize::incr ? +1 : -1, MOZ_KnownLive(content));
2321 if (NS_FAILED(rv)) {
2322 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2323 return rv;
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");
2337 return rv;
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");
2346 return rv;
2352 return NS_OK;
2355 nsresult HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir,
2356 Text& aTextNode,
2357 int32_t aStartOffset,
2358 int32_t aEndOffset) {
2359 // Don't need to do anything if no characters actually selected
2360 if (aStartOffset == aEndOffset) {
2361 return NS_OK;
2364 if (!aTextNode.GetParentNode() ||
2365 !HTMLEditUtils::CanNodeContain(*aTextNode.GetParentNode(),
2366 *nsGkAtoms::big)) {
2367 return NS_OK;
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
2382 ErrorResult error;
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
2394 ErrorResult error;
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");
2411 return rv;
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");
2420 return rv;
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,
2432 nsINode* aNode) {
2433 MOZ_ASSERT(aNode);
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
2458 // keep it alive.
2459 nsresult rv = RelativeFontChangeOnNode(aSizeChange, MOZ_KnownLive(child));
2460 if (NS_FAILED(rv)) {
2461 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2462 return rv;
2466 // RelativeFontChangeOnNode already calls us recursively,
2467 // so we don't need to check our children again.
2468 return NS_OK;
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
2480 // keep it alive.
2481 nsresult rv = RelativeFontChangeHelper(aSizeChange, MOZ_KnownLive(child));
2482 if (NS_FAILED(rv)) {
2483 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2484 return rv;
2488 return NS_OK;
2491 nsresult HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange,
2492 nsIContent* aNode) {
2493 MOZ_ASSERT(aNode);
2494 // Can only change font size by + or - 1
2495 if (aSizeChange != 1 && aSizeChange != -1) {
2496 return NS_ERROR_ILLEGAL_VALUE;
2499 nsAtom* atom;
2500 if (aSizeChange == 1) {
2501 atom = nsGkAtoms::big;
2502 } else {
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");
2513 return rv;
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");
2519 return rv;
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");
2528 return rv;
2531 // ok, chuck it in.
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
2537 // it
2538 nsresult rv = MoveNodeToEndWithTransaction(*aNode, *sibling);
2539 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2540 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2541 return rv;
2544 sibling = GetNextHTMLSibling(aNode);
2545 if (sibling && sibling->IsHTMLElement(atom)) {
2546 // following sib is already right kind of inline node; slide this over
2547 // into it
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
2571 // keep it alive.
2572 nsresult rv = RelativeFontChangeOnNode(aSizeChange, MOZ_KnownLive(child));
2573 if (NS_FAILED(rv)) {
2574 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2575 return rv;
2579 return NS_OK;
2582 NS_IMETHODIMP HTMLEditor::GetFontFaceState(bool* aMixed, nsAString& outFace) {
2583 if (NS_WARN_IF(!aMixed)) {
2584 return NS_ERROR_INVALID_ARG;
2587 *aMixed = true;
2588 outFace.Truncate();
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)) {
2600 NS_WARNING(
2601 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
2602 "failed");
2603 return EditorBase::ToGenericNSResult(rv);
2605 if (any && !all) {
2606 return NS_OK; // mixed
2608 if (all) {
2609 *aMixed = false;
2610 return NS_OK;
2613 // if there is no font face, check for tt
2614 rv = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
2615 &all, nullptr);
2616 if (NS_FAILED(rv)) {
2617 NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed");
2618 return EditorBase::ToGenericNSResult(rv);
2620 if (any && !all) {
2621 return NS_OK; // mixed
2623 if (all) {
2624 *aMixed = false;
2625 outFace.AssignLiteral("tt");
2628 if (!any) {
2629 // there was no font face attrs of any kind. We are in normal font.
2630 outFace.Truncate();
2631 *aMixed = false;
2633 return NS_OK;
2636 nsresult HTMLEditor::GetFontColorState(bool* aMixed, nsAString& aOutColor) {
2637 if (NS_WARN_IF(!aMixed)) {
2638 return NS_ERROR_INVALID_ARG;
2641 *aMixed = true;
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)) {
2653 NS_WARNING(
2654 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
2655 "failed");
2656 return EditorBase::ToGenericNSResult(rv);
2659 if (any && !all) {
2660 return NS_OK; // mixed
2662 if (all) {
2663 *aMixed = false;
2664 return NS_OK;
2667 if (!any) {
2668 // there was no font color attrs of any kind..
2669 aOutColor.Truncate();
2670 *aMixed = false;
2672 return NS_OK;
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();
2680 return NS_OK;
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