1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "HTMLEditor.h"
7
8 #include "EditAction.h"
9 #include "EditorUtils.h"
10 #include "HTMLEditHelpers.h"
11 #include "HTMLEditUtils.h"
12 #include "SelectionState.h"
13 #include "TypeInState.h"
14
15 #include "mozilla/Assertions.h"
16 #include "mozilla/ContentIterator.h"
17 #include "mozilla/mozalloc.h"
18 #include "mozilla/dom/AncestorIterator.h"
19 #include "mozilla/dom/Element.h"
20 #include "mozilla/dom/HTMLBRElement.h"
21 #include "mozilla/dom/Selection.h"
22 #include "nsAString.h"
23 #include "nsAttrName.h"
24 #include "nsCOMPtr.h"
25 #include "nsCaseTreatment.h"
26 #include "nsComponentManagerUtils.h"
27 #include "nsDebug.h"
28 #include "nsError.h"
29 #include "nsGkAtoms.h"
30 #include "nsAtom.h"
31 #include "nsIContent.h"
32 #include "nsNameSpaceManager.h"
33 #include "nsINode.h"
34 #include "nsIPrincipal.h"
35 #include "nsISupportsImpl.h"
36 #include "nsLiteralString.h"
37 #include "nsRange.h"
38 #include "nsReadableUtils.h"
39 #include "nsString.h"
40 #include "nsStringFwd.h"
41 #include "nsStyledElement.h"
42 #include "nsTArray.h"
43 #include "nsUnicharUtils.h"
44 #include "nscore.h"
45
46 class nsISupports;
47
48 namespace mozilla {
49
50 using namespace dom;
51
52 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption;
53 using LeafNodeType = HTMLEditUtils::LeafNodeType;
54 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes;
55 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
56
SetInlinePropertyAsAction(nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aPrincipal)57 nsresult HTMLEditor::SetInlinePropertyAsAction(nsAtom& aProperty,
58 nsAtom* aAttribute,
59 const nsAString& aValue,
60 nsIPrincipal* aPrincipal) {
61 AutoEditActionDataSetter editActionData(
62 *this,
63 HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true),
64 aPrincipal);
65 switch (editActionData.GetEditAction()) {
66 case EditAction::eSetFontFamilyProperty:
67 MOZ_ASSERT(!aValue.IsVoid());
68 // XXX Should we trim unnecessary white-spaces?
69 editActionData.SetData(aValue);
70 break;
71 case EditAction::eSetColorProperty:
72 case EditAction::eSetBackgroundColorPropertyInline:
73 editActionData.SetColorData(aValue);
74 break;
75 default:
76 break;
77 }
78
79 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
80 if (NS_FAILED(rv)) {
81 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
82 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
83 return EditorBase::ToGenericNSResult(rv);
84 }
85
86 // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection
87 // into view after setting the new style.
88 AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::No,
89 __FUNCTION__);
90
91 nsAtom* property = &aProperty;
92 nsAtom* attribute = aAttribute;
93 nsAutoString value(aValue);
94
95 if (&aProperty == nsGkAtoms::sup) {
96 // Superscript and Subscript styles are mutually exclusive.
97 nsresult rv = RemoveInlinePropertyInternal(nsGkAtoms::sub, nullptr,
98 RemoveRelatedElements::No);
99 if (NS_FAILED(rv)) {
100 NS_WARNING(
101 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::sub, "
102 "RemoveRelatedElements::No) failed");
103 return EditorBase::ToGenericNSResult(rv);
104 }
105 } else if (&aProperty == nsGkAtoms::sub) {
106 // Superscript and Subscript styles are mutually exclusive.
107 nsresult rv = RemoveInlinePropertyInternal(nsGkAtoms::sup, nullptr,
108 RemoveRelatedElements::No);
109 if (NS_FAILED(rv)) {
110 NS_WARNING(
111 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::sup, "
112 "RemoveRelatedElements::No) failed");
113 return EditorBase::ToGenericNSResult(rv);
114 }
115 }
116 // Handling `<tt>` element code was implemented for composer (bug 115922).
117 // This shouldn't work with `Document.execCommand()`. Currently, aPrincipal
118 // is set only when the root caller is Document::ExecCommand() so that
119 // we should handle `<tt>` element only when aPrincipal is nullptr that
120 // must be only when XUL command is executed on composer.
121 else if (!aPrincipal) {
122 if (&aProperty == nsGkAtoms::tt) {
123 nsresult rv = RemoveInlinePropertyInternal(
124 nsGkAtoms::font, nsGkAtoms::face, RemoveRelatedElements::No);
125 if (NS_FAILED(rv)) {
126 NS_WARNING(
127 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::font, "
128 "nsGkAtoms::face, RemoveRelatedElements::No) failed");
129 return EditorBase::ToGenericNSResult(rv);
130 }
131 } else if (&aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::face) {
132 if (!value.LowerCaseEqualsASCII("tt")) {
133 nsresult rv = RemoveInlinePropertyInternal(nsGkAtoms::tt, nullptr,
134 RemoveRelatedElements::No);
135 if (NS_FAILED(rv)) {
136 NS_WARNING(
137 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::tt, "
138 "RemoveRelatedElements::No) failed");
139 return EditorBase::ToGenericNSResult(rv);
140 }
141 } else {
142 nsresult rv = RemoveInlinePropertyInternal(
143 nsGkAtoms::font, nsGkAtoms::face, RemoveRelatedElements::No);
144 if (NS_FAILED(rv)) {
145 NS_WARNING(
146 "HTMLEditor::RemoveInlinePropertyInternal(nsGkAtoms::font, "
147 "nsGkAtoms::face, RemoveRelatedElements::No) failed");
148 return EditorBase::ToGenericNSResult(rv);
149 }
150 // Override property, attribute and value if the new font face value is
151 // "tt".
152 property = nsGkAtoms::tt;
153 attribute = nullptr;
154 value.Truncate();
155 }
156 }
157 }
158 rv = SetInlinePropertyInternal(MOZ_KnownLive(*property),
159 MOZ_KnownLive(attribute), value);
160 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
161 "HTMLEditor::SetInlinePropertyInternal() failed");
162 return EditorBase::ToGenericNSResult(rv);
163 }
164
SetInlineProperty(const nsAString & aProperty,const nsAString & aAttribute,const nsAString & aValue)165 NS_IMETHODIMP HTMLEditor::SetInlineProperty(const nsAString& aProperty,
166 const nsAString& aAttribute,
167 const nsAString& aValue) {
168 RefPtr<nsAtom> property = NS_Atomize(aProperty);
169 if (NS_WARN_IF(!property)) {
170 return NS_ERROR_INVALID_ARG;
171 }
172 nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
173 AutoEditActionDataSetter editActionData(
174 *this,
175 HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true));
176 switch (editActionData.GetEditAction()) {
177 case EditAction::eSetFontFamilyProperty:
178 MOZ_ASSERT(!aValue.IsVoid());
179 // XXX Should we trim unnecessary white-spaces?
180 editActionData.SetData(aValue);
181 break;
182 case EditAction::eSetColorProperty:
183 case EditAction::eSetBackgroundColorPropertyInline:
184 editActionData.SetColorData(aValue);
185 break;
186 default:
187 break;
188 }
189 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
190 if (NS_FAILED(rv)) {
191 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
192 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
193 return EditorBase::ToGenericNSResult(rv);
194 }
195 rv = SetInlinePropertyInternal(*property, MOZ_KnownLive(attribute), aValue);
196 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
197 "HTMLEditor::SetInlinePropertyInternal() failed");
198 return EditorBase::ToGenericNSResult(rv);
199 }
200
SetInlinePropertyInternal(nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aAttributeValue)201 nsresult HTMLEditor::SetInlinePropertyInternal(
202 nsAtom& aProperty, nsAtom* aAttribute, const nsAString& aAttributeValue) {
203 MOZ_ASSERT(IsEditActionDataAvailable());
204
205 if (NS_WARN_IF(!mInitSucceeded)) {
206 return NS_ERROR_NOT_INITIALIZED;
207 }
208
209 DebugOnly<nsresult> rvIgnored = CommitComposition();
210 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
211 "EditorBase::CommitComposition() failed, but ignored");
212
213 if (SelectionRef().IsCollapsed()) {
214 // Manipulating text attributes on a collapsed selection only sets state
215 // for the next text insertion
216 mTypeInState->SetProp(&aProperty, aAttribute, aAttributeValue);
217 return NS_OK;
218 }
219
220 // XXX Shouldn't we return before calling `CommitComposition()`?
221 if (IsInPlaintextMode()) {
222 return NS_OK;
223 }
224
225 EditActionResult result = CanHandleHTMLEditSubAction();
226 if (result.Failed() || result.Canceled()) {
227 NS_WARNING_ASSERTION(result.Succeeded(),
228 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
229 return result.Rv();
230 }
231
232 AutoPlaceholderBatch treatAsOneTransaction(
233 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
234 IgnoredErrorResult ignoredError;
235 AutoEditSubActionNotifier startToHandleEditSubAction(
236 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
237 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
238 return ignoredError.StealNSResult();
239 }
240 NS_WARNING_ASSERTION(
241 !ignoredError.Failed(),
242 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
243
244 {
245 AutoSelectionRestorer restoreSelectionLater(*this);
246 AutoTransactionsConserveSelection dontChangeMySelection(*this);
247
248 // Loop through the ranges in the selection
249 // XXX This is different from `SetCSSBackgroundColorWithTransaction()`.
250 // It refers `Selection::GetRangeAt()` in each time. The result may
251 // be different if mutation event listener changes the `Selection`.
252 AutoSelectionRangeArray arrayOfRanges(SelectionRef());
253 for (auto& range : arrayOfRanges.mRanges) {
254 // Adjust range to include any ancestors whose children are entirely
255 // selected
256 nsresult rv = PromoteInlineRange(*range);
257 if (NS_FAILED(rv)) {
258 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
259 return rv;
260 }
261
262 // XXX Shouldn't we skip the range if it's been collapsed by mutation
263 // event listener?
264
265 EditorDOMPoint startOfRange(range->StartRef());
266 EditorDOMPoint endOfRange(range->EndRef());
267 if (NS_WARN_IF(!startOfRange.IsSet()) ||
268 NS_WARN_IF(!endOfRange.IsSet())) {
269 continue;
270 }
271
272 // If range is in a text node, apply new style simply.
273 if (startOfRange.GetContainer() == endOfRange.GetContainer() &&
274 startOfRange.IsInTextNode()) {
275 nsresult rv = SetInlinePropertyOnTextNode(
276 MOZ_KnownLive(*startOfRange.GetContainerAsText()),
277 startOfRange.Offset(), endOfRange.Offset(), aProperty, aAttribute,
278 aAttributeValue);
279 if (NS_FAILED(rv)) {
280 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
281 return rv;
282 }
283 continue;
284 }
285
286 // Collect editable nodes which are entirely contained in the range.
287 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
288 ContentSubtreeIterator subtreeIter;
289 // If there is no node which is entirely in the range,
290 // `ContentSubtreeIterator::Init()` fails, but this is possible case,
291 // don't warn it.
292 if (NS_SUCCEEDED(subtreeIter.Init(range))) {
293 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
294 nsINode* node = subtreeIter.GetCurrentNode();
295 if (NS_WARN_IF(!node)) {
296 return NS_ERROR_FAILURE;
297 }
298 if (node->IsContent() && EditorUtils::IsEditableContent(
299 *node->AsContent(), EditorType::HTML)) {
300 arrayOfContents.AppendElement(*node->AsContent());
301 }
302 }
303 }
304
305 // If start node is a text node, apply new style to a part of it.
306 if (startOfRange.IsInTextNode() &&
307 EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
308 EditorType::HTML)) {
309 nsresult rv = SetInlinePropertyOnTextNode(
310 MOZ_KnownLive(*startOfRange.GetContainerAsText()),
311 startOfRange.Offset(), startOfRange.GetContainer()->Length(),
312 aProperty, aAttribute, aAttributeValue);
313 if (NS_FAILED(rv)) {
314 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed");
315 return rv;
316 }
317 }
318
319 // Then, apply new style to all nodes in the range entirely.
320 for (auto& content : arrayOfContents) {
321 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
322 // keep it alive.
323 nsresult rv = SetInlinePropertyOnNode(
324 MOZ_KnownLive(*content), aProperty, aAttribute, aAttributeValue);
325 if (NS_WARN_IF(Destroyed())) {
326 return NS_ERROR_EDITOR_DESTROYED;
327 }
328 if (NS_FAILED(rv)) {
329 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
330 return rv;
331 }
332 }
333
334 // Finally, if end node is a text node, apply new style to a part of it.
335 if (endOfRange.IsInTextNode() &&
336 EditorUtils::IsEditableContent(*endOfRange.GetContainerAsText(),
337 EditorType::HTML)) {
338 nsresult rv = SetInlinePropertyOnTextNode(
339 MOZ_KnownLive(*endOfRange.GetContainerAsText()), 0,
340 endOfRange.Offset(), aProperty, aAttribute, aAttributeValue);
341 if (NS_FAILED(rv)) {
342 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
343 return rv;
344 }
345 }
346 }
347 }
348 // Restoring `Selection` may have destroyed us.
349 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
350 }
351
ElementIsGoodContainerForTheStyle(Element & aElement,nsAtom * aProperty,nsAtom * aAttribute,const nsAString * aValue)352 Result<bool, nsresult> HTMLEditor::ElementIsGoodContainerForTheStyle(
353 Element& aElement, nsAtom* aProperty, nsAtom* aAttribute,
354 const nsAString* aValue) {
355 // aContent can be null, in which case we'll return false in a few lines
356 MOZ_ASSERT(aProperty);
357 MOZ_ASSERT_IF(aAttribute, aValue);
358
359 // First check for <b>, <i>, etc.
360 if (aElement.IsHTMLElement(aProperty) && !aElement.GetAttrCount() &&
361 !aAttribute) {
362 return true;
363 }
364
365 // Special cases for various equivalencies: <strong>, <em>, <s>
366 if (!aElement.GetAttrCount() &&
367 ((aProperty == nsGkAtoms::b &&
368 aElement.IsHTMLElement(nsGkAtoms::strong)) ||
369 (aProperty == nsGkAtoms::i && aElement.IsHTMLElement(nsGkAtoms::em)) ||
370 (aProperty == nsGkAtoms::strike &&
371 aElement.IsHTMLElement(nsGkAtoms::s)))) {
372 return true;
373 }
374
375 // Now look for things like <font>
376 if (aAttribute) {
377 nsString attrValue;
378 if (aElement.IsHTMLElement(aProperty) &&
379 IsOnlyAttribute(&aElement, aAttribute) &&
380 aElement.GetAttr(kNameSpaceID_None, aAttribute, attrValue) &&
381 attrValue.Equals(*aValue, nsCaseInsensitiveStringComparator)) {
382 // This is not quite correct, because it excludes cases like
383 // <font face=000> being the same as <font face=#000000>.
384 // Property-specific handling is needed (bug 760211).
385 return true;
386 }
387 }
388
389 // No luck so far. Now we check for a <span> with a single style=""
390 // attribute that sets only the style we're looking for, if this type of
391 // style supports it
392 if (!CSSEditUtils::IsCSSEditableProperty(&aElement, aProperty, aAttribute) ||
393 !aElement.IsHTMLElement(nsGkAtoms::span) ||
394 aElement.GetAttrCount() != 1 ||
395 !aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::style)) {
396 return false;
397 }
398
399 // Some CSS styles are not so simple. For instance, underline is
400 // "text-decoration: underline", which decomposes into four different text-*
401 // properties. So for now, we just create a span, add the desired style, and
402 // see if it matches.
403 RefPtr<Element> newSpanElement = CreateHTMLContent(nsGkAtoms::span);
404 if (!newSpanElement) {
405 NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed");
406 return false;
407 }
408 nsStyledElement* styledNewSpanElement =
409 nsStyledElement::FromNode(newSpanElement);
410 if (!styledNewSpanElement) {
411 return false;
412 }
413 // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is
414 // RefPtr.
415 Result<int32_t, nsresult> result =
416 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithoutTransaction(
417 MOZ_KnownLive(*styledNewSpanElement), aProperty, aAttribute, aValue);
418 if (result.isErr()) {
419 // The call shouldn't return destroyed error because it must be
420 // impossible to run script with modifying the new orphan node.
421 MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?");
422 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) {
423 return Err(NS_ERROR_EDITOR_DESTROYED);
424 }
425 return false;
426 }
427 nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement);
428 if (!styledElement) {
429 return false;
430 }
431 return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement,
432 *styledElement);
433 }
434
SetInlinePropertyOnTextNode(Text & aText,uint32_t aStartOffset,uint32_t aEndOffset,nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue)435 nsresult HTMLEditor::SetInlinePropertyOnTextNode(
436 Text& aText, uint32_t aStartOffset, uint32_t aEndOffset, nsAtom& aProperty,
437 nsAtom* aAttribute, const nsAString& aValue) {
438 if (!aText.GetParentNode() ||
439 !HTMLEditUtils::CanNodeContain(*aText.GetParentNode(), aProperty)) {
440 return NS_OK;
441 }
442
443 // Don't need to do anything if no characters actually selected
444 if (aStartOffset == aEndOffset) {
445 return NS_OK;
446 }
447
448 // Don't need to do anything if property already set on node
449 if (CSSEditUtils::IsCSSEditableProperty(&aText, &aProperty, aAttribute)) {
450 // The HTML styles defined by aProperty/aAttribute have a CSS equivalence
451 // for node; let's check if it carries those CSS styles
452 nsAutoString value(aValue);
453 if (CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
454 aText, &aProperty, aAttribute, value)) {
455 return NS_OK;
456 }
457 } else if (HTMLEditUtils::IsInlineStyleSetByElement(aText, aProperty,
458 aAttribute, &aValue)) {
459 return NS_OK;
460 }
461
462 // Make the range an independent node.
463 RefPtr<Text> textNodeForTheRange = &aText;
464
465 // Split at the end of the range.
466 EditorDOMPoint atEnd(textNodeForTheRange, aEndOffset);
467 if (!atEnd.IsEndOfContainer()) {
468 // We need to split off back of text node
469 SplitNodeResult splitAtEndResult = SplitNodeWithTransaction(atEnd);
470 if (MOZ_UNLIKELY(splitAtEndResult.Failed())) {
471 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
472 return splitAtEndResult.Rv();
473 }
474 textNodeForTheRange =
475 Text::FromNodeOrNull(splitAtEndResult.GetPreviousContent());
476 }
477
478 // Split at the start of the range.
479 EditorDOMPoint atStart(textNodeForTheRange, aStartOffset);
480 if (!atStart.IsStartOfContainer()) {
481 // We need to split off front of text node
482 SplitNodeResult splitAtStartResult = SplitNodeWithTransaction(atStart);
483 if (MOZ_UNLIKELY(splitAtStartResult.Failed())) {
484 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
485 return splitAtStartResult.Rv();
486 }
487 }
488
489 if (aAttribute) {
490 // Look for siblings that are correct type of node
491 nsIContent* sibling = HTMLEditUtils::GetPreviousSibling(
492 *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
493 if (sibling && sibling->IsElement()) {
494 OwningNonNull<Element> element(*sibling->AsElement());
495 Result<bool, nsresult> result = ElementIsGoodContainerForTheStyle(
496 element, &aProperty, aAttribute, &aValue);
497 if (result.isErr()) {
498 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
499 return result.unwrapErr();
500 }
501 if (result.inspect()) {
502 // Previous sib is already right kind of inline node; slide this over
503 nsresult rv =
504 MoveNodeToEndWithTransaction(*textNodeForTheRange, element);
505 if (NS_WARN_IF(Destroyed())) {
506 return NS_ERROR_EDITOR_DESTROYED;
507 }
508 NS_WARNING_ASSERTION(
509 NS_SUCCEEDED(rv),
510 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
511 return rv;
512 }
513 }
514 sibling = HTMLEditUtils::GetNextSibling(
515 *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
516 if (sibling && sibling->IsElement()) {
517 OwningNonNull<Element> element(*sibling->AsElement());
518 Result<bool, nsresult> result = ElementIsGoodContainerForTheStyle(
519 element, &aProperty, aAttribute, &aValue);
520 if (result.isErr()) {
521 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
522 return result.unwrapErr();
523 }
524 if (result.inspect()) {
525 // Following sib is already right kind of inline node; slide this over
526 nsresult rv = MoveNodeWithTransaction(*textNodeForTheRange,
527 EditorDOMPoint(sibling, 0));
528 if (NS_WARN_IF(Destroyed())) {
529 return NS_ERROR_EDITOR_DESTROYED;
530 }
531 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
532 "HTMLEditor::MoveNodeWithTransaction() failed");
533 return rv;
534 }
535 }
536 }
537
538 // Reparent the node inside inline node with appropriate {attribute,value}
539 nsresult rv = SetInlinePropertyOnNode(*textNodeForTheRange, aProperty,
540 aAttribute, aValue);
541 if (NS_WARN_IF(Destroyed())) {
542 return NS_ERROR_EDITOR_DESTROYED;
543 }
544 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
545 "HTMLEditor::SetInlinePropertyOnNode() failed");
546 return rv;
547 }
548
SetInlinePropertyOnNodeImpl(nsIContent & aContent,nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue)549 nsresult HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aContent,
550 nsAtom& aProperty,
551 nsAtom* aAttribute,
552 const nsAString& aValue) {
553 // If this is an element that can't be contained in a span, we have to
554 // recurse to its children.
555 if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, aContent)) {
556 if (aContent.HasChildren()) {
557 nsTArray<OwningNonNull<nsIContent>> arrayOfNodes;
558
559 // Populate the list.
560 for (nsCOMPtr<nsIContent> child = aContent.GetFirstChild(); child;
561 child = child->GetNextSibling()) {
562 if (EditorUtils::IsEditableContent(*child, EditorType::HTML) &&
563 (!child->IsText() ||
564 HTMLEditUtils::IsVisibleTextNode(*child->AsText()))) {
565 arrayOfNodes.AppendElement(*child);
566 }
567 }
568
569 // Then loop through the list, set the property on each node.
570 for (auto& node : arrayOfNodes) {
571 // MOZ_KnownLive because 'arrayOfNodes' is guaranteed to
572 // keep it alive.
573 nsresult rv = SetInlinePropertyOnNode(MOZ_KnownLive(node), aProperty,
574 aAttribute, aValue);
575 if (NS_FAILED(rv)) {
576 NS_WARNING("HTMLEditor::SetInlinePropertyOnNode() failed");
577 return rv;
578 }
579 }
580 }
581 return NS_OK;
582 }
583
584 // First check if there's an adjacent sibling we can put our node into.
585 nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling(
586 aContent, {WalkTreeOption::IgnoreNonEditableNode});
587 nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling(
588 aContent, {WalkTreeOption::IgnoreNonEditableNode});
589 if (previousSibling && previousSibling->IsElement()) {
590 OwningNonNull<Element> previousElement(*previousSibling->AsElement());
591 Result<bool, nsresult> canMoveIntoPreviousSibling =
592 ElementIsGoodContainerForTheStyle(previousElement, &aProperty,
593 aAttribute, &aValue);
594 if (canMoveIntoPreviousSibling.isErr()) {
595 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
596 return canMoveIntoPreviousSibling.unwrapErr();
597 }
598 if (canMoveIntoPreviousSibling.inspect()) {
599 nsresult rv = MoveNodeToEndWithTransaction(aContent, *previousSibling);
600 if (NS_FAILED(rv)) {
601 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed");
602 return rv;
603 }
604 if (!nextSibling || !nextSibling->IsElement()) {
605 return NS_OK;
606 }
607 OwningNonNull<Element> nextElement(*nextSibling->AsElement());
608 Result<bool, nsresult> canMoveIntoNextSibling =
609 ElementIsGoodContainerForTheStyle(nextElement, &aProperty, aAttribute,
610 &aValue);
611 if (canMoveIntoNextSibling.isErr()) {
612 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
613 return canMoveIntoNextSibling.unwrapErr();
614 }
615 if (!canMoveIntoNextSibling.inspect()) {
616 return NS_OK;
617 }
618 JoinNodesResult joinNodesResult =
619 JoinNodesWithTransaction(*previousSibling, *nextSibling);
620 NS_WARNING_ASSERTION(joinNodesResult.Succeeded(),
621 "HTMLEditor::JoinNodesWithTransaction() failed");
622 return joinNodesResult.Rv();
623 }
624 }
625
626 if (nextSibling && nextSibling->IsElement()) {
627 OwningNonNull<Element> nextElement(*nextSibling->AsElement());
628 Result<bool, nsresult> canMoveIntoNextSibling =
629 ElementIsGoodContainerForTheStyle(nextElement, &aProperty, aAttribute,
630 &aValue);
631 if (canMoveIntoNextSibling.isErr()) {
632 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed");
633 return canMoveIntoNextSibling.unwrapErr();
634 }
635 if (canMoveIntoNextSibling.inspect()) {
636 nsresult rv =
637 MoveNodeWithTransaction(aContent, EditorDOMPoint(nextElement, 0));
638 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
639 "HTMLEditor::MoveNodeWithTransaction() failed");
640 return rv;
641 }
642 }
643
644 // Don't need to do anything if property already set on node
645 if (CSSEditUtils::IsCSSEditableProperty(&aContent, &aProperty, aAttribute)) {
646 nsAutoString value(aValue);
647 if (CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
648 aContent, &aProperty, aAttribute, value)) {
649 return NS_OK;
650 }
651 } else if (HTMLEditUtils::IsInlineStyleSetByElement(aContent, aProperty,
652 aAttribute, &aValue)) {
653 return NS_OK;
654 }
655
656 bool useCSS = (IsCSSEnabled() && CSSEditUtils::IsCSSEditableProperty(
657 &aContent, &aProperty, aAttribute)) ||
658 // bgcolor is always done using CSS
659 aAttribute == nsGkAtoms::bgcolor ||
660 // called for removing parent style, we should use CSS with
661 // `<span>` element.
662 aValue.EqualsLiteral("-moz-editor-invert-value");
663
664 if (useCSS) {
665 RefPtr<Element> spanElement;
666 // We only add style="" to <span>s with no attributes (bug 746515). If we
667 // don't have one, we need to make one.
668 if (aContent.IsHTMLElement(nsGkAtoms::span) &&
669 !aContent.AsElement()->GetAttrCount()) {
670 spanElement = aContent.AsElement();
671 } else {
672 spanElement = InsertContainerWithTransaction(aContent, *nsGkAtoms::span);
673 if (!spanElement) {
674 NS_WARNING(
675 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
676 "failed");
677 return NS_ERROR_FAILURE;
678 }
679 }
680
681 // Add the CSS styles corresponding to the HTML style request
682 if (nsStyledElement* spanStyledElement =
683 nsStyledElement::FromNode(spanElement)) {
684 // MOZ_KnownLive(*spanStyledElement): It's spanElement whose type is
685 // RefPtr.
686 Result<int32_t, nsresult> result =
687 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
688 MOZ_KnownLive(*spanStyledElement), &aProperty, aAttribute,
689 &aValue);
690 if (result.isErr()) {
691 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
692 NS_WARNING(
693 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
694 "destroyed the editor");
695 return NS_ERROR_EDITOR_DESTROYED;
696 }
697 NS_WARNING(
698 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction() "
699 "failed, "
700 "but ignored");
701 }
702 }
703 return NS_OK;
704 }
705
706 // is it already the right kind of node, but with wrong attribute?
707 if (aContent.IsHTMLElement(&aProperty)) {
708 if (NS_WARN_IF(!aAttribute)) {
709 return NS_ERROR_INVALID_ARG;
710 }
711 // Just set the attribute on it.
712 nsresult rv = SetAttributeWithTransaction(
713 MOZ_KnownLive(*aContent.AsElement()), *aAttribute, aValue);
714 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
715 "EditorBase::SetAttributeWithTransaction() failed");
716 return rv;
717 }
718
719 // ok, chuck it in its very own container
720 RefPtr<Element> newContainerElement = InsertContainerWithTransaction(
721 aContent, aProperty, aAttribute ? *aAttribute : *nsGkAtoms::_empty,
722 aValue);
723 NS_WARNING_ASSERTION(newContainerElement,
724 "HTMLEditor::InsertContainerWithTransaction() failed");
725 return newContainerElement ? NS_OK : NS_ERROR_FAILURE;
726 }
727
SetInlinePropertyOnNode(nsIContent & aNode,nsAtom & aProperty,nsAtom * aAttribute,const nsAString & aValue)728 nsresult HTMLEditor::SetInlinePropertyOnNode(nsIContent& aNode,
729 nsAtom& aProperty,
730 nsAtom* aAttribute,
731 const nsAString& aValue) {
732 nsCOMPtr<nsIContent> previousSibling = aNode.GetPreviousSibling(),
733 nextSibling = aNode.GetNextSibling();
734 if (NS_WARN_IF(!aNode.GetParentNode())) {
735 return NS_ERROR_INVALID_ARG;
736 }
737
738 OwningNonNull<nsINode> parent = *aNode.GetParentNode();
739 if (aNode.IsElement()) {
740 nsresult rv =
741 RemoveStyleInside(MOZ_KnownLive(*aNode.AsElement()), &aProperty,
742 aAttribute, SpecifiedStyle::Preserve);
743 if (NS_FAILED(rv)) {
744 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
745 return rv;
746 }
747 }
748
749 if (aNode.GetParentNode()) {
750 // The node is still where it was
751 nsresult rv =
752 SetInlinePropertyOnNodeImpl(aNode, aProperty, aAttribute, aValue);
753 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
754 "HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
755 return rv;
756 }
757
758 // It's vanished. Use the old siblings for reference to construct a
759 // list. But first, verify that the previous/next siblings are still
760 // where we expect them; otherwise we have to give up.
761 if (NS_WARN_IF(previousSibling &&
762 previousSibling->GetParentNode() != parent) ||
763 NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parent)) {
764 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
765 }
766 AutoTArray<OwningNonNull<nsIContent>, 24> nodesToSet;
767 for (nsIContent* content = previousSibling ? previousSibling->GetNextSibling()
768 : parent->GetFirstChild();
769 content && content != nextSibling; content = content->GetNextSibling()) {
770 if (EditorUtils::IsEditableContent(*content, EditorType::HTML)) {
771 nodesToSet.AppendElement(*content);
772 }
773 }
774
775 for (OwningNonNull<nsIContent>& content : nodesToSet) {
776 // MOZ_KnownLive because 'nodesToSet' is guaranteed to
777 // keep it alive.
778 nsresult rv = SetInlinePropertyOnNodeImpl(MOZ_KnownLive(content), aProperty,
779 aAttribute, aValue);
780 if (NS_FAILED(rv)) {
781 NS_WARNING("HTMLEditor::SetInlinePropertyOnNodeImpl() failed");
782 return rv;
783 }
784 }
785
786 return NS_OK;
787 }
788
SplitAncestorStyledInlineElementsAtRangeEdges(const EditorDOMPoint & aStartOfRange,const EditorDOMPoint & aEndOfRange,nsAtom * aProperty,nsAtom * aAttribute)789 SplitRangeOffResult HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges(
790 const EditorDOMPoint& aStartOfRange, const EditorDOMPoint& aEndOfRange,
791 nsAtom* aProperty, nsAtom* aAttribute) {
792 MOZ_ASSERT(IsEditActionDataAvailable());
793
794 if (NS_WARN_IF(!aStartOfRange.IsSet()) || NS_WARN_IF(!aEndOfRange.IsSet())) {
795 return SplitRangeOffResult(NS_ERROR_INVALID_ARG);
796 }
797
798 EditorDOMPoint startOfRange(aStartOfRange);
799 EditorDOMPoint endOfRange(aEndOfRange);
800
801 // split any matching style nodes above the start of range
802 SplitNodeResult resultAtStart(NS_ERROR_NOT_INITIALIZED);
803 {
804 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &endOfRange);
805 resultAtStart = SplitAncestorStyledInlineElementsAt(startOfRange, aProperty,
806 aAttribute);
807 if (resultAtStart.Failed()) {
808 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
809 return SplitRangeOffResult(resultAtStart.Rv());
810 }
811 if (resultAtStart.Handled()) {
812 startOfRange = resultAtStart.AtSplitPoint<EditorDOMPoint>();
813 if (!startOfRange.IsSet()) {
814 NS_WARNING(
815 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
816 "split point");
817 return SplitRangeOffResult(NS_ERROR_FAILURE);
818 }
819 }
820 }
821
822 // second verse, same as the first...
823 SplitNodeResult resultAtEnd(NS_ERROR_NOT_INITIALIZED);
824 {
825 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &startOfRange);
826 resultAtEnd =
827 SplitAncestorStyledInlineElementsAt(endOfRange, aProperty, aAttribute);
828 if (resultAtEnd.Failed()) {
829 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
830 return SplitRangeOffResult(resultAtEnd.Rv());
831 }
832 if (resultAtEnd.Handled()) {
833 endOfRange = resultAtEnd.AtSplitPoint<EditorDOMPoint>();
834 if (!endOfRange.IsSet()) {
835 NS_WARNING(
836 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return "
837 "split point");
838 return SplitRangeOffResult(NS_ERROR_FAILURE);
839 }
840 }
841 }
842
843 return SplitRangeOffResult(startOfRange, resultAtStart, endOfRange,
844 resultAtEnd);
845 }
846
SplitAncestorStyledInlineElementsAt(const EditorDOMPoint & aPointToSplit,nsAtom * aProperty,nsAtom * aAttribute)847 SplitNodeResult HTMLEditor::SplitAncestorStyledInlineElementsAt(
848 const EditorDOMPoint& aPointToSplit, nsAtom* aProperty,
849 nsAtom* aAttribute) {
850 if (NS_WARN_IF(!aPointToSplit.IsSet()) ||
851 NS_WARN_IF(!aPointToSplit.GetContainerAsContent())) {
852 return SplitNodeResult(NS_ERROR_INVALID_ARG);
853 }
854
855 // We assume that this method is called only when we're removing style(s).
856 // Even if we're in HTML mode and there is no presentation element in the
857 // block, we may need to overwrite the block's style with `<span>` element
858 // and CSS. For example, `<h1>` element has `font-weight: bold;` as its
859 // default style. If `Document.execCommand("bold")` is called for its
860 // text, we should make it unbold. Therefore, we shouldn't check
861 // IsCSSEnabled() in most cases. However, there is an exception.
862 // FontFaceStateCommand::SetState() calls RemoveInlinePropertyAsAction()
863 // with nsGkAtoms::tt before calling SetInlinePropertyAsAction() if we
864 // are handling a XUL command. Only in that case, we need to check
865 // IsCSSEnabled().
866 bool useCSS = aProperty != nsGkAtoms::tt || IsCSSEnabled();
867
868 AutoTArray<OwningNonNull<nsIContent>, 24> arrayOfParents;
869 for (nsIContent* content :
870 aPointToSplit.GetContainer()->InclusiveAncestorsOfType<nsIContent>()) {
871 if (HTMLEditUtils::IsBlockElement(*content) || !content->GetParent() ||
872 !EditorUtils::IsEditableContent(*content->GetParent(),
873 EditorType::HTML)) {
874 break;
875 }
876 arrayOfParents.AppendElement(*content);
877 }
878
879 // Split any matching style nodes above the point.
880 SplitNodeResult result(aPointToSplit);
881 MOZ_ASSERT(!result.Handled());
882 for (OwningNonNull<nsIContent>& content : arrayOfParents) {
883 bool isSetByCSS = false;
884 if (useCSS &&
885 CSSEditUtils::IsCSSEditableProperty(content, aProperty, aAttribute)) {
886 // The HTML style defined by aProperty/aAttribute has a CSS equivalence
887 // in this implementation for the node; let's check if it carries those
888 // CSS styles
889 nsAutoString firstValue;
890 isSetByCSS = CSSEditUtils::IsSpecifiedCSSEquivalentToHTMLInlineStyleSet(
891 *content, aProperty, aAttribute, firstValue);
892 }
893 if (!isSetByCSS) {
894 if (!content->IsElement()) {
895 continue;
896 }
897 // If aProperty is set, we need to split only elements which applies the
898 // given style.
899 if (aProperty) {
900 // If the content is an inline element represents aProperty or
901 // the content is a link element and aProperty is `href`, we should
902 // split the content.
903 if (!content->IsHTMLElement(aProperty) &&
904 !(aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(content))) {
905 continue;
906 }
907 }
908 // If aProperty is nullptr, we need to split any style.
909 else if (!EditorUtils::IsEditableContent(content, EditorType::HTML) ||
910 !HTMLEditUtils::IsRemovableInlineStyleElement(
911 *content->AsElement())) {
912 continue;
913 }
914 }
915
916 // Found a style node we need to split.
917 // XXX If first content is a text node and CSS is enabled, we call this
918 // with text node but in such case, this does nothing, but returns
919 // as handled with setting only previous or next node. If its parent
920 // is a block, we do nothing but return as handled.
921 SplitNodeResult splitNodeResult = SplitNodeDeepWithTransaction(
922 MOZ_KnownLive(content), result.AtSplitPoint<EditorDOMPoint>(),
923 SplitAtEdges::eAllowToCreateEmptyContainer);
924 if (MOZ_UNLIKELY(splitNodeResult.Failed())) {
925 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
926 return splitNodeResult;
927 }
928 // If it's not handled, it means that `content` is not a splitable node
929 // like a void element even if it has some children, and the split point
930 // is middle of it.
931 if (!splitNodeResult.Handled()) {
932 continue;
933 }
934 // Mark the final result as handled forcibly.
935 result = SplitNodeResult(splitNodeResult.GetPreviousContent(),
936 splitNodeResult.GetNextContent(),
937 SplitNodeDirection::LeftNodeIsNewOne);
938 MOZ_ASSERT(result.Handled());
939 }
940
941 return result;
942 }
943
ClearStyleAt(const EditorDOMPoint & aPoint,nsAtom * aProperty,nsAtom * aAttribute,SpecifiedStyle aSpecifiedStyle)944 EditResult HTMLEditor::ClearStyleAt(const EditorDOMPoint& aPoint,
945 nsAtom* aProperty, nsAtom* aAttribute,
946 SpecifiedStyle aSpecifiedStyle) {
947 MOZ_ASSERT(IsEditActionDataAvailable());
948
949 if (NS_WARN_IF(!aPoint.IsSet())) {
950 return EditResult(NS_ERROR_INVALID_ARG);
951 }
952
953 // First, split inline elements at the point.
954 // E.g., if aProperty is nsGkAtoms::b and `<p><b><i>a[]bc</i></b></p>`,
955 // we want to make it as `<p><b><i>a</i></b><b><i>bc</i></b></p>`.
956 SplitNodeResult splitResult =
957 SplitAncestorStyledInlineElementsAt(aPoint, aProperty, aAttribute);
958 if (splitResult.Failed()) {
959 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
960 return EditResult(splitResult.Rv());
961 }
962
963 // If there is no styled inline elements of aProperty/aAttribute, we just
964 // return the given point.
965 // E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b.
966 if (!splitResult.Handled()) {
967 return EditResult(aPoint);
968 }
969
970 // If it did split nodes, but topmost ancestor inline element is split
971 // at start of it, we don't need the empty inline element. Let's remove
972 // it now.
973 if (splitResult.GetPreviousContent() &&
974 HTMLEditUtils::IsEmptyNode(
975 *splitResult.GetPreviousContent(),
976 {EmptyCheckOption::TreatSingleBRElementAsVisible,
977 EmptyCheckOption::TreatListItemAsVisible,
978 EmptyCheckOption::TreatTableCellAsVisible})) {
979 // Delete previous node if it's empty.
980 // MOZ_KnownLive(splitResult.GetPreviousContent()):
981 // It's grabbed by splitResult.
982 nsresult rv = DeleteNodeWithTransaction(
983 MOZ_KnownLive(*splitResult.GetPreviousContent()));
984 if (NS_WARN_IF(Destroyed())) {
985 return EditResult(NS_ERROR_EDITOR_DESTROYED);
986 }
987 if (NS_FAILED(rv)) {
988 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
989 return EditResult(rv);
990 }
991 }
992
993 // If we reached block from end of a text node, we can do nothing here.
994 // E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and
995 // we're in CSS mode.
996 // XXX Chrome resets block style and creates `<span>` elements for each
997 // line in this case.
998 if (!splitResult.GetNextContent()) {
999 return EditResult(aPoint);
1000 }
1001
1002 // Otherwise, the next node is topmost ancestor inline element which has
1003 // the style. We want to put caret between the split nodes, but we need
1004 // to keep other styles. Therefore, next, we need to split at start of
1005 // the next node. The first example should become
1006 // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`.
1007 // ^^^^^^^^^^^^^^
1008 nsIContent* firstLeafChildOfNextNode = HTMLEditUtils::GetFirstLeafContent(
1009 *splitResult.GetNextContent(), {LeafNodeType::OnlyLeafNode});
1010 EditorDOMPoint atStartOfNextNode(firstLeafChildOfNextNode
1011 ? firstLeafChildOfNextNode
1012 : splitResult.GetNextContent(),
1013 0);
1014 RefPtr<HTMLBRElement> brElement;
1015 // But don't try to split non-containers like `<br>`, `<hr>` and `<img>`
1016 // element.
1017 if (!atStartOfNextNode.IsInContentNode() ||
1018 !HTMLEditUtils::IsContainerNode(
1019 *atStartOfNextNode.ContainerAsContent())) {
1020 // If it's a `<br>` element, let's move it into new node later.
1021 brElement = HTMLBRElement::FromNode(atStartOfNextNode.GetContainer());
1022 if (!atStartOfNextNode.GetContainerParentAsContent()) {
1023 NS_WARNING("atStartOfNextNode was in an orphan node");
1024 return EditResult(NS_ERROR_FAILURE);
1025 }
1026 atStartOfNextNode.Set(atStartOfNextNode.GetContainerParent(), 0);
1027 }
1028 SplitNodeResult splitResultAtStartOfNextNode =
1029 SplitAncestorStyledInlineElementsAt(atStartOfNextNode, aProperty,
1030 aAttribute);
1031 if (splitResultAtStartOfNextNode.Failed()) {
1032 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed");
1033 return EditResult(splitResultAtStartOfNextNode.Rv());
1034 }
1035
1036 // Let's remove the next node if it becomes empty by splitting it.
1037 // XXX Is this possible case without mutation event listener?
1038 if (splitResultAtStartOfNextNode.Handled() &&
1039 splitResultAtStartOfNextNode.GetNextContent() &&
1040 HTMLEditUtils::IsEmptyNode(
1041 *splitResultAtStartOfNextNode.GetNextContent(),
1042 {EmptyCheckOption::TreatSingleBRElementAsVisible,
1043 EmptyCheckOption::TreatListItemAsVisible,
1044 EmptyCheckOption::TreatTableCellAsVisible})) {
1045 // Delete next node if it's empty.
1046 // MOZ_KnownLive(splitResultAtStartOfNextNode.GetNextContent()):
1047 // It's grabbed by splitResultAtStartOfNextNode.
1048 nsresult rv = DeleteNodeWithTransaction(
1049 MOZ_KnownLive(*splitResultAtStartOfNextNode.GetNextContent()));
1050 if (NS_WARN_IF(Destroyed())) {
1051 return EditResult(NS_ERROR_EDITOR_DESTROYED);
1052 }
1053 if (NS_FAILED(rv)) {
1054 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1055 return EditResult(rv);
1056 }
1057 }
1058
1059 // If there is no content, we should return here.
1060 // XXX Is this possible case without mutation event listener?
1061 if (NS_WARN_IF(!splitResultAtStartOfNextNode.Handled()) ||
1062 !splitResultAtStartOfNextNode.GetPreviousContent()) {
1063 // XXX This is really odd, but we retrun this value...
1064 const EditorRawDOMPoint& splitPoint =
1065 splitResult.AtSplitPoint<EditorRawDOMPoint>();
1066 const EditorRawDOMPoint& splitPointAtStartOfNextNode =
1067 splitResultAtStartOfNextNode.AtSplitPoint<EditorRawDOMPoint>();
1068 return EditResult(EditorDOMPoint(splitPoint.GetContainer(),
1069 splitPointAtStartOfNextNode.Offset()));
1070 }
1071
1072 // Now, we want to put `<br>` element into the empty split node if
1073 // it was in next node of the first split.
1074 // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>`
1075 nsIContent* firstLeafChildOfPreviousNode = HTMLEditUtils::GetFirstLeafContent(
1076 *splitResultAtStartOfNextNode.GetPreviousContent(),
1077 {LeafNodeType::OnlyLeafNode});
1078 EditorDOMPoint pointToPutCaret(
1079 firstLeafChildOfPreviousNode
1080 ? firstLeafChildOfPreviousNode
1081 : splitResultAtStartOfNextNode.GetPreviousContent(),
1082 0);
1083 // If the right node starts with a `<br>`, suck it out of right node and into
1084 // the left node left node. This is so we you don't revert back to the
1085 // previous style if you happen to click at the end of a line.
1086 if (brElement) {
1087 nsresult rv = MoveNodeWithTransaction(*brElement, pointToPutCaret);
1088 if (NS_WARN_IF(Destroyed())) {
1089 return EditResult(NS_ERROR_EDITOR_DESTROYED);
1090 }
1091 if (NS_FAILED(rv)) {
1092 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed");
1093 return EditResult(rv);
1094 }
1095 // Update the child.
1096 pointToPutCaret.Set(pointToPutCaret.GetContainer(), 0);
1097 }
1098 // Finally, remove the specified style in the previous node at the
1099 // second split and tells good insertion point to the caller. I.e., we
1100 // want to make the first example as:
1101 // `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>`
1102 // ^^^^^^^^^
1103 if (Element* previousElementOfSplitPoint = Element::FromNode(
1104 splitResultAtStartOfNextNode.GetPreviousContent())) {
1105 // Track the point at the new hierarchy. This is so we can know where
1106 // to put the selection after we call RemoveStyleInside().
1107 // RemoveStyleInside() could remove any and all of those nodes, so I
1108 // have to use the range tracking system to find the right spot to put
1109 // selection.
1110 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToPutCaret);
1111 // MOZ_KnownLive(previousElementOfSplitPoint):
1112 // It's grabbed by splitResultAtStartOfNextNode.
1113 nsresult rv = RemoveStyleInside(MOZ_KnownLive(*previousElementOfSplitPoint),
1114 aProperty, aAttribute, aSpecifiedStyle);
1115 if (NS_FAILED(rv)) {
1116 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1117 return EditResult(rv);
1118 }
1119 }
1120 return EditResult(pointToPutCaret);
1121 }
1122
RemoveStyleInside(Element & aElement,nsAtom * aProperty,nsAtom * aAttribute,SpecifiedStyle aSpecifiedStyle)1123 nsresult HTMLEditor::RemoveStyleInside(Element& aElement, nsAtom* aProperty,
1124 nsAtom* aAttribute,
1125 SpecifiedStyle aSpecifiedStyle) {
1126 // First, handle all descendants.
1127 RefPtr<nsIContent> child = aElement.GetFirstChild();
1128 while (child) {
1129 // cache next sibling since we might remove child
1130 // XXX Well, the next sibling is moved from `aElement`, shouldn't we skip
1131 // it here?
1132 nsCOMPtr<nsIContent> nextSibling = child->GetNextSibling();
1133 if (child->IsElement()) {
1134 nsresult rv = RemoveStyleInside(MOZ_KnownLive(*child->AsElement()),
1135 aProperty, aAttribute, aSpecifiedStyle);
1136 if (NS_FAILED(rv)) {
1137 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
1138 return rv;
1139 }
1140 }
1141 child = ToRefPtr(std::move(nextSibling));
1142 }
1143
1144 // Next, remove the element or its attribute.
1145 bool removeHTMLStyle = false;
1146 if (aProperty) {
1147 removeHTMLStyle =
1148 // If the element is a presentation element of aProperty
1149 aElement.NodeInfo()->NameAtom() == aProperty ||
1150 // or an `<a>` element with `href` attribute
1151 (aProperty == nsGkAtoms::href && HTMLEditUtils::IsLink(&aElement)) ||
1152 // or an `<a>` element with `name` attribute
1153 (aProperty == nsGkAtoms::name &&
1154 HTMLEditUtils::IsNamedAnchor(&aElement));
1155 }
1156 // XXX Why do we check if aElement is editable only when aProperty is
1157 // nullptr?
1158 else if (EditorUtils::IsEditableContent(aElement, EditorType::HTML)) {
1159 // or removing all styles and the element is a presentation element.
1160 removeHTMLStyle = HTMLEditUtils::IsRemovableInlineStyleElement(aElement);
1161 }
1162
1163 if (removeHTMLStyle) {
1164 // If aAttribute is nullptr, we want to remove any matching inline styles
1165 // entirely.
1166 if (!aAttribute) {
1167 // If some style rules are specified to aElement, we need to keep them
1168 // as far as possible.
1169 // XXX Why don't we clone `id` attribute?
1170 if (aProperty && aSpecifiedStyle != SpecifiedStyle::Discard &&
1171 (aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::style) ||
1172 aElement.HasAttr(kNameSpaceID_None, nsGkAtoms::_class))) {
1173 // Move `style` attribute and `class` element to span element before
1174 // removing aElement from the tree.
1175 RefPtr<Element> spanElement =
1176 InsertContainerWithTransaction(aElement, *nsGkAtoms::span);
1177 if (NS_WARN_IF(Destroyed())) {
1178 return NS_ERROR_EDITOR_DESTROYED;
1179 }
1180 if (!spanElement) {
1181 NS_WARNING(
1182 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) "
1183 "failed");
1184 return NS_ERROR_FAILURE;
1185 }
1186 nsresult rv = CloneAttributeWithTransaction(*nsGkAtoms::style,
1187 *spanElement, aElement);
1188 if (NS_WARN_IF(Destroyed())) {
1189 return NS_ERROR_EDITOR_DESTROYED;
1190 }
1191 if (NS_FAILED(rv)) {
1192 NS_WARNING(
1193 "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::style) "
1194 "failed");
1195 return rv;
1196 }
1197 rv = CloneAttributeWithTransaction(*nsGkAtoms::_class, *spanElement,
1198 aElement);
1199 if (NS_WARN_IF(Destroyed())) {
1200 return NS_ERROR_EDITOR_DESTROYED;
1201 }
1202 if (NS_FAILED(rv)) {
1203 NS_WARNING(
1204 "EditorBase::CloneAttributeWithTransaction(nsGkAtoms::_class) "
1205 "failed");
1206 return rv;
1207 }
1208 }
1209 nsresult rv = RemoveContainerWithTransaction(aElement);
1210 if (NS_WARN_IF(Destroyed())) {
1211 return NS_ERROR_EDITOR_DESTROYED;
1212 }
1213 if (NS_FAILED(rv)) {
1214 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
1215 return rv;
1216 }
1217 }
1218 // If aAttribute is specified, we want to remove only the attribute
1219 // unless it's the last attribute of aElement.
1220 else if (aElement.HasAttr(kNameSpaceID_None, aAttribute)) {
1221 if (IsOnlyAttribute(&aElement, aAttribute)) {
1222 nsresult rv = RemoveContainerWithTransaction(aElement);
1223 if (NS_WARN_IF(Destroyed())) {
1224 return NS_ERROR_EDITOR_DESTROYED;
1225 }
1226 if (NS_FAILED(rv)) {
1227 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
1228 return rv;
1229 }
1230 } else {
1231 nsresult rv = RemoveAttributeWithTransaction(aElement, *aAttribute);
1232 if (NS_WARN_IF(Destroyed())) {
1233 return NS_ERROR_EDITOR_DESTROYED;
1234 }
1235 if (NS_FAILED(rv)) {
1236 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed");
1237 return rv;
1238 }
1239 }
1240 }
1241 }
1242
1243 // Then, remove CSS style if specified.
1244 // XXX aElement may have already been removed from the DOM tree. Why
1245 // do we keep handling aElement here??
1246 if (CSSEditUtils::IsCSSEditableProperty(&aElement, aProperty, aAttribute) &&
1247 CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(aElement, aProperty,
1248 aAttribute)) {
1249 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) {
1250 // If aElement has CSS declaration of the given style, remove it.
1251 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must be
1252 // guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
1253 nsresult rv =
1254 mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithTransaction(
1255 MOZ_KnownLive(*styledElement), aProperty, aAttribute, nullptr);
1256 if (rv == NS_ERROR_EDITOR_DESTROYED) {
1257 NS_WARNING(
1258 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1259 "destroyed the editor");
1260 return NS_ERROR_EDITOR_DESTROYED;
1261 }
1262 NS_WARNING_ASSERTION(
1263 NS_SUCCEEDED(rv),
1264 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyleWithTransaction() "
1265 "failed, but ignored");
1266 }
1267 // Additionally, remove aElement itself if it's a `<span>` or `<font>`
1268 // and it does not have non-empty `style`, `id` nor `class` attribute.
1269 if (aElement.IsAnyOfHTMLElements(nsGkAtoms::span, nsGkAtoms::font) &&
1270 !HTMLEditor::HasStyleOrIdOrClassAttribute(aElement)) {
1271 DebugOnly<nsresult> rvIgnored = RemoveContainerWithTransaction(aElement);
1272 if (NS_WARN_IF(Destroyed())) {
1273 return NS_ERROR_EDITOR_DESTROYED;
1274 }
1275 NS_WARNING_ASSERTION(
1276 NS_SUCCEEDED(rvIgnored),
1277 "HTMLEditor::RemoveContainerWithTransaction() failed, but ignored");
1278 }
1279 }
1280
1281 // Finally, remove aElement if it's a `<big>` or `<small>` element and
1282 // we're removing `<font size>`.
1283 if (aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::size &&
1284 aElement.IsAnyOfHTMLElements(nsGkAtoms::big, nsGkAtoms::small)) {
1285 nsresult rv = RemoveContainerWithTransaction(aElement);
1286 if (NS_WARN_IF(Destroyed())) {
1287 return NS_ERROR_EDITOR_DESTROYED;
1288 }
1289 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1290 "HTMLEditor::RemoveContainerWithTransaction() failed");
1291 return rv;
1292 }
1293
1294 return NS_OK;
1295 }
1296
IsOnlyAttribute(const Element * aElement,nsAtom * aAttribute)1297 bool HTMLEditor::IsOnlyAttribute(const Element* aElement, nsAtom* aAttribute) {
1298 MOZ_ASSERT(aElement);
1299
1300 uint32_t attrCount = aElement->GetAttrCount();
1301 for (uint32_t i = 0; i < attrCount; ++i) {
1302 const nsAttrName* name = aElement->GetAttrNameAt(i);
1303 if (!name->NamespaceEquals(kNameSpaceID_None)) {
1304 return false;
1305 }
1306
1307 // if it's the attribute we know about, or a special _moz attribute,
1308 // keep looking
1309 if (name->LocalName() != aAttribute) {
1310 nsAutoString attrString;
1311 name->LocalName()->ToString(attrString);
1312 if (!StringBeginsWith(attrString, u"_moz"_ns)) {
1313 return false;
1314 }
1315 }
1316 }
1317 // if we made it through all of them without finding a real attribute
1318 // other than aAttribute, then return true
1319 return true;
1320 }
1321
PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange & aRange)1322 nsresult HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange) {
1323 // We assume that <a> is not nested.
1324 // XXX Shouldn't ignore the editing host.
1325 if (NS_WARN_IF(!aRange.GetStartContainer()) ||
1326 NS_WARN_IF(!aRange.GetEndContainer())) {
1327 return NS_ERROR_INVALID_ARG;
1328 }
1329 EditorRawDOMPoint newRangeStart(aRange.StartRef());
1330 for (Element* element :
1331 aRange.GetStartContainer()->InclusiveAncestorsOfType<Element>()) {
1332 if (element->IsHTMLElement(nsGkAtoms::body)) {
1333 break;
1334 }
1335 if (!HTMLEditUtils::IsNamedAnchor(element)) {
1336 continue;
1337 }
1338 newRangeStart.Set(element);
1339 break;
1340 }
1341
1342 if (!newRangeStart.GetContainerAsContent()) {
1343 NS_WARNING(
1344 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1345 "element from start container");
1346 return NS_ERROR_FAILURE;
1347 }
1348
1349 EditorRawDOMPoint newRangeEnd(aRange.EndRef());
1350 for (Element* element :
1351 aRange.GetEndContainer()->InclusiveAncestorsOfType<Element>()) {
1352 if (element->IsHTMLElement(nsGkAtoms::body)) {
1353 break;
1354 }
1355 if (!HTMLEditUtils::IsNamedAnchor(element)) {
1356 continue;
1357 }
1358 newRangeEnd.SetAfter(element);
1359 break;
1360 }
1361
1362 if (!newRangeEnd.GetContainerAsContent()) {
1363 NS_WARNING(
1364 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() reached root "
1365 "element from end container");
1366 return NS_ERROR_FAILURE;
1367 }
1368
1369 if (newRangeStart == aRange.StartRef() && newRangeEnd == aRange.EndRef()) {
1370 return NS_OK;
1371 }
1372
1373 nsresult rv = aRange.SetStartAndEnd(newRangeStart.ToRawRangeBoundary(),
1374 newRangeEnd.ToRawRangeBoundary());
1375 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
1376 return rv;
1377 }
1378
PromoteInlineRange(nsRange & aRange)1379 nsresult HTMLEditor::PromoteInlineRange(nsRange& aRange) {
1380 if (NS_WARN_IF(!aRange.GetStartContainer()) ||
1381 NS_WARN_IF(!aRange.GetEndContainer())) {
1382 return NS_ERROR_INVALID_ARG;
1383 }
1384 EditorRawDOMPoint newRangeStart(aRange.StartRef());
1385 for (nsIContent* content :
1386 aRange.GetStartContainer()->InclusiveAncestorsOfType<nsIContent>()) {
1387 MOZ_ASSERT(newRangeStart.GetContainer() == content);
1388 if (content->IsHTMLElement(nsGkAtoms::body) ||
1389 !EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1390 !IsStartOfContainerOrBeforeFirstEditableChild(newRangeStart)) {
1391 break;
1392 }
1393 newRangeStart.Set(content);
1394 }
1395 if (!newRangeStart.GetContainerAsContent()) {
1396 NS_WARNING(
1397 "HTMLEditor::PromoteInlineRange() reached root element from start "
1398 "container");
1399 return NS_ERROR_FAILURE;
1400 }
1401
1402 EditorRawDOMPoint newRangeEnd(aRange.EndRef());
1403 for (nsIContent* content :
1404 aRange.GetEndContainer()->InclusiveAncestorsOfType<nsIContent>()) {
1405 MOZ_ASSERT(newRangeEnd.GetContainer() == content);
1406 if (content->IsHTMLElement(nsGkAtoms::body) ||
1407 !EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1408 !IsEndOfContainerOrEqualsOrAfterLastEditableChild(newRangeEnd)) {
1409 break;
1410 }
1411 newRangeEnd.SetAfter(content);
1412 }
1413 if (!newRangeEnd.GetContainerAsContent()) {
1414 NS_WARNING(
1415 "HTMLEditor::PromoteInlineRange() reached root element from end "
1416 "container");
1417 return NS_ERROR_FAILURE;
1418 }
1419
1420 if (newRangeStart == aRange.StartRef() && newRangeEnd == aRange.EndRef()) {
1421 return NS_OK;
1422 }
1423
1424 nsresult rv = aRange.SetStartAndEnd(newRangeStart.ToRawRangeBoundary(),
1425 newRangeEnd.ToRawRangeBoundary());
1426 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed");
1427 return rv;
1428 }
1429
IsStartOfContainerOrBeforeFirstEditableChild(const EditorRawDOMPoint & aPoint) const1430 bool HTMLEditor::IsStartOfContainerOrBeforeFirstEditableChild(
1431 const EditorRawDOMPoint& aPoint) const {
1432 MOZ_ASSERT(aPoint.IsSet());
1433
1434 if (aPoint.IsStartOfContainer()) {
1435 return true;
1436 }
1437
1438 if (aPoint.IsInTextNode()) {
1439 return false;
1440 }
1441
1442 nsIContent* firstEditableChild = HTMLEditUtils::GetFirstChild(
1443 *aPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode});
1444 if (!firstEditableChild) {
1445 return true;
1446 }
1447 return EditorRawDOMPoint(firstEditableChild).Offset() >= aPoint.Offset();
1448 }
1449
IsEndOfContainerOrEqualsOrAfterLastEditableChild(const EditorRawDOMPoint & aPoint) const1450 bool HTMLEditor::IsEndOfContainerOrEqualsOrAfterLastEditableChild(
1451 const EditorRawDOMPoint& aPoint) const {
1452 MOZ_ASSERT(aPoint.IsSet());
1453
1454 if (aPoint.IsEndOfContainer()) {
1455 return true;
1456 }
1457
1458 if (aPoint.IsInTextNode()) {
1459 return false;
1460 }
1461
1462 nsIContent* lastEditableChild = HTMLEditUtils::GetLastChild(
1463 *aPoint.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode});
1464 if (!lastEditableChild) {
1465 return true;
1466 }
1467 return EditorRawDOMPoint(lastEditableChild).Offset() < aPoint.Offset();
1468 }
1469
GetInlinePropertyBase(nsAtom & aHTMLProperty,nsAtom * aAttribute,const nsAString * aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString * outValue) const1470 nsresult HTMLEditor::GetInlinePropertyBase(nsAtom& aHTMLProperty,
1471 nsAtom* aAttribute,
1472 const nsAString* aValue,
1473 bool* aFirst, bool* aAny, bool* aAll,
1474 nsAString* outValue) const {
1475 MOZ_ASSERT(IsEditActionDataAvailable());
1476
1477 *aAny = false;
1478 *aAll = true;
1479 *aFirst = false;
1480 bool first = true;
1481
1482 bool isCollapsed = SelectionRef().IsCollapsed();
1483 RefPtr<nsRange> range = SelectionRef().GetRangeAt(0);
1484 // XXX: Should be a while loop, to get each separate range
1485 // XXX: ERROR_HANDLING can currentItem be null?
1486 if (range) {
1487 // For each range, set a flag
1488 bool firstNodeInRange = true;
1489
1490 if (isCollapsed) {
1491 nsCOMPtr<nsINode> collapsedNode = range->GetStartContainer();
1492 if (NS_WARN_IF(!collapsedNode)) {
1493 return NS_ERROR_FAILURE;
1494 }
1495 bool isSet, theSetting;
1496 nsString tOutString;
1497 if (aAttribute) {
1498 mTypeInState->GetTypingState(isSet, theSetting, &aHTMLProperty,
1499 aAttribute, &tOutString);
1500 if (outValue) {
1501 outValue->Assign(tOutString);
1502 }
1503 } else {
1504 mTypeInState->GetTypingState(isSet, theSetting, &aHTMLProperty);
1505 }
1506 if (isSet) {
1507 *aFirst = *aAny = *aAll = theSetting;
1508 return NS_OK;
1509 }
1510
1511 if (collapsedNode->IsContent() &&
1512 CSSEditUtils::IsCSSEditableProperty(collapsedNode, &aHTMLProperty,
1513 aAttribute)) {
1514 if (aValue) {
1515 tOutString.Assign(*aValue);
1516 }
1517 *aFirst = *aAny = *aAll =
1518 CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1519 MOZ_KnownLive(*collapsedNode->AsContent()), &aHTMLProperty,
1520 aAttribute, tOutString);
1521 if (NS_WARN_IF(Destroyed())) {
1522 return NS_ERROR_EDITOR_DESTROYED;
1523 }
1524 if (outValue) {
1525 outValue->Assign(tOutString);
1526 }
1527 return NS_OK;
1528 }
1529
1530 *aFirst = *aAny = *aAll = collapsedNode->IsContent() &&
1531 HTMLEditUtils::IsInlineStyleSetByElement(
1532 *collapsedNode->AsContent(), aHTMLProperty,
1533 aAttribute, aValue, outValue);
1534 return NS_OK;
1535 }
1536
1537 // Non-collapsed selection
1538
1539 nsAutoString firstValue, theValue;
1540
1541 nsCOMPtr<nsINode> endNode = range->GetEndContainer();
1542 uint32_t endOffset = range->EndOffset();
1543
1544 PostContentIterator postOrderIter;
1545 DebugOnly<nsresult> rvIgnored = postOrderIter.Init(range);
1546 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1547 "Failed to initialize post-order content iterator");
1548 for (; !postOrderIter.IsDone(); postOrderIter.Next()) {
1549 if (!postOrderIter.GetCurrentNode()->IsContent()) {
1550 continue;
1551 }
1552 nsCOMPtr<nsIContent> content =
1553 postOrderIter.GetCurrentNode()->AsContent();
1554
1555 if (content->IsHTMLElement(nsGkAtoms::body)) {
1556 break;
1557 }
1558
1559 // just ignore any non-editable nodes
1560 if (content->IsText() &&
1561 (!EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
1562 !HTMLEditUtils::IsVisibleTextNode(*content->AsText()))) {
1563 continue;
1564 }
1565 if (content->GetAsText()) {
1566 if (!isCollapsed && first && firstNodeInRange) {
1567 firstNodeInRange = false;
1568 if (range->StartOffset() == content->Length()) {
1569 continue;
1570 }
1571 } else if (content == endNode && !endOffset) {
1572 continue;
1573 }
1574 } else if (content->IsElement()) {
1575 // handle non-text leaf nodes here
1576 continue;
1577 }
1578
1579 bool isSet = false;
1580 bool useTextDecoration =
1581 &aHTMLProperty == nsGkAtoms::u || &aHTMLProperty == nsGkAtoms::strike;
1582 if (first) {
1583 if (CSSEditUtils::IsCSSEditableProperty(content, &aHTMLProperty,
1584 aAttribute)) {
1585 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
1586 // equivalence in this implementation for node; let's check if it
1587 // carries those CSS styles
1588 if (aValue) {
1589 firstValue.Assign(*aValue);
1590 }
1591 isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1592 *content, &aHTMLProperty, aAttribute, firstValue);
1593 if (NS_WARN_IF(Destroyed())) {
1594 return NS_ERROR_EDITOR_DESTROYED;
1595 }
1596 } else {
1597 isSet = HTMLEditUtils::IsInlineStyleSetByElement(
1598 *content, aHTMLProperty, aAttribute, aValue, &firstValue);
1599 }
1600 *aFirst = isSet;
1601 first = false;
1602 if (outValue) {
1603 *outValue = firstValue;
1604 }
1605 } else {
1606 if (CSSEditUtils::IsCSSEditableProperty(content, &aHTMLProperty,
1607 aAttribute)) {
1608 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS
1609 // equivalence in this implementation for node; let's check if it
1610 // carries those CSS styles
1611 if (aValue) {
1612 theValue.Assign(*aValue);
1613 }
1614 isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
1615 *content, &aHTMLProperty, aAttribute, theValue);
1616 if (NS_WARN_IF(Destroyed())) {
1617 return NS_ERROR_EDITOR_DESTROYED;
1618 }
1619 } else {
1620 isSet = HTMLEditUtils::IsInlineStyleSetByElement(
1621 *content, aHTMLProperty, aAttribute, aValue, &theValue);
1622 }
1623
1624 if (firstValue != theValue &&
1625 // For text-decoration related HTML properties, i.e. <u> and
1626 // <strike>, we have to also check |isSet| because text-decoration
1627 // is a shorthand property, and it may contains other unrelated
1628 // longhand components, e.g. text-decoration-color, so we have to do
1629 // an extra check before setting |*aAll| to false.
1630 // e.g.
1631 // firstValue: "underline rgb(0, 0, 0)"
1632 // theValue: "underline rgb(0, 0, 238)" // <a> uses blue color
1633 // These two values should be the same if we are checking `<u>`.
1634 // That's why we need to check |*aFirst| and |isSet|.
1635 //
1636 // This is a work-around for text-decoration.
1637 // The spec issue: https://github.com/w3c/editing/issues/241.
1638 // Once this spec issue is resolved, we could drop this work-around
1639 // check.
1640 (!useTextDecoration || *aFirst != isSet)) {
1641 *aAll = false;
1642 }
1643 }
1644
1645 if (isSet) {
1646 *aAny = true;
1647 } else {
1648 *aAll = false;
1649 }
1650 }
1651 }
1652 if (!*aAny) {
1653 // make sure that if none of the selection is set, we don't report all is
1654 // set
1655 *aAll = false;
1656 }
1657 return NS_OK;
1658 }
1659
GetInlineProperty(nsAtom * aHTMLProperty,nsAtom * aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll) const1660 nsresult HTMLEditor::GetInlineProperty(nsAtom* aHTMLProperty,
1661 nsAtom* aAttribute,
1662 const nsAString& aValue, bool* aFirst,
1663 bool* aAny, bool* aAll) const {
1664 if (NS_WARN_IF(!aHTMLProperty) || NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) ||
1665 NS_WARN_IF(!aAll)) {
1666 return NS_ERROR_INVALID_ARG;
1667 }
1668
1669 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1670 if (NS_WARN_IF(!editActionData.CanHandle())) {
1671 return NS_ERROR_NOT_INITIALIZED;
1672 }
1673
1674 const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr;
1675 nsresult rv = GetInlinePropertyBase(*aHTMLProperty, aAttribute, val, aFirst,
1676 aAny, aAll, nullptr);
1677 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1678 "HTMLEditor::GetInlinePropertyBase() failed");
1679 return EditorBase::ToGenericNSResult(rv);
1680 }
1681
GetInlinePropertyWithAttrValue(const nsAString & aHTMLProperty,const nsAString & aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString & outValue)1682 NS_IMETHODIMP HTMLEditor::GetInlinePropertyWithAttrValue(
1683 const nsAString& aHTMLProperty, const nsAString& aAttribute,
1684 const nsAString& aValue, bool* aFirst, bool* aAny, bool* aAll,
1685 nsAString& outValue) {
1686 RefPtr<nsAtom> property = NS_Atomize(aHTMLProperty);
1687 nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
1688 nsresult rv = GetInlinePropertyWithAttrValue(
1689 property, MOZ_KnownLive(attribute), aValue, aFirst, aAny, aAll, outValue);
1690 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1691 "HTMLEditor::GetInlinePropertyWithAttrValue() failed");
1692 return rv;
1693 }
1694
GetInlinePropertyWithAttrValue(nsAtom * aHTMLProperty,nsAtom * aAttribute,const nsAString & aValue,bool * aFirst,bool * aAny,bool * aAll,nsAString & outValue)1695 nsresult HTMLEditor::GetInlinePropertyWithAttrValue(
1696 nsAtom* aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue,
1697 bool* aFirst, bool* aAny, bool* aAll, nsAString& outValue) {
1698 if (NS_WARN_IF(!aHTMLProperty) || NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) ||
1699 NS_WARN_IF(!aAll)) {
1700 return NS_ERROR_INVALID_ARG;
1701 }
1702
1703 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1704 if (NS_WARN_IF(!editActionData.CanHandle())) {
1705 return NS_ERROR_NOT_INITIALIZED;
1706 }
1707
1708 const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr;
1709 nsresult rv = GetInlinePropertyBase(*aHTMLProperty, aAttribute, val, aFirst,
1710 aAny, aAll, &outValue);
1711 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1712 "HTMLEditor::GetInlinePropertyBase() failed");
1713 return EditorBase::ToGenericNSResult(rv);
1714 }
1715
RemoveAllInlinePropertiesAsAction(nsIPrincipal * aPrincipal)1716 nsresult HTMLEditor::RemoveAllInlinePropertiesAsAction(
1717 nsIPrincipal* aPrincipal) {
1718 AutoEditActionDataSetter editActionData(
1719 *this, EditAction::eRemoveAllInlineStyleProperties, aPrincipal);
1720 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1721 if (NS_FAILED(rv)) {
1722 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1723 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1724 return EditorBase::ToGenericNSResult(rv);
1725 }
1726
1727 AutoPlaceholderBatch treatAsOneTransaction(
1728 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1729 IgnoredErrorResult ignoredError;
1730 AutoEditSubActionNotifier startToHandleEditSubAction(
1731 *this, EditSubAction::eRemoveAllTextProperties, nsIEditor::eNext,
1732 ignoredError);
1733 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1734 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
1735 }
1736 NS_WARNING_ASSERTION(
1737 !ignoredError.Failed(),
1738 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1739
1740 rv =
1741 RemoveInlinePropertyInternal(nullptr, nullptr, RemoveRelatedElements::No);
1742 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1743 "HTMLEditor::RemoveInlinePropertyInternal(nullptr, "
1744 "nullptr, RemoveRelatedElements::No) failed");
1745 return EditorBase::ToGenericNSResult(rv);
1746 }
1747
RemoveInlinePropertyAsAction(nsStaticAtom & aHTMLProperty,nsStaticAtom * aAttribute,nsIPrincipal * aPrincipal)1748 nsresult HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom& aHTMLProperty,
1749 nsStaticAtom* aAttribute,
1750 nsIPrincipal* aPrincipal) {
1751 AutoEditActionDataSetter editActionData(
1752 *this,
1753 HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty, aAttribute,
1754 false),
1755 aPrincipal);
1756 switch (editActionData.GetEditAction()) {
1757 case EditAction::eRemoveFontFamilyProperty:
1758 MOZ_ASSERT(!u""_ns.IsVoid());
1759 editActionData.SetData(u""_ns);
1760 break;
1761 case EditAction::eRemoveColorProperty:
1762 case EditAction::eRemoveBackgroundColorPropertyInline:
1763 editActionData.SetColorData(u""_ns);
1764 break;
1765 default:
1766 break;
1767 }
1768 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1769 if (NS_FAILED(rv)) {
1770 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1771 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1772 return EditorBase::ToGenericNSResult(rv);
1773 }
1774
1775 rv = RemoveInlinePropertyInternal(&aHTMLProperty, aAttribute,
1776 RemoveRelatedElements::Yes);
1777 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1778 "HTMLEditor::RemoveInlinePropertyInternal("
1779 "RemoveRelatedElements::Yes) failed");
1780 return EditorBase::ToGenericNSResult(rv);
1781 }
1782
RemoveInlineProperty(const nsAString & aProperty,const nsAString & aAttribute)1783 NS_IMETHODIMP HTMLEditor::RemoveInlineProperty(const nsAString& aProperty,
1784 const nsAString& aAttribute) {
1785 nsStaticAtom* property = NS_GetStaticAtom(aProperty);
1786 nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute);
1787
1788 AutoEditActionDataSetter editActionData(
1789 *this,
1790 HTMLEditUtils::GetEditActionForFormatText(*property, attribute, false));
1791 switch (editActionData.GetEditAction()) {
1792 case EditAction::eRemoveFontFamilyProperty:
1793 MOZ_ASSERT(!u""_ns.IsVoid());
1794 editActionData.SetData(u""_ns);
1795 break;
1796 case EditAction::eRemoveColorProperty:
1797 case EditAction::eRemoveBackgroundColorPropertyInline:
1798 editActionData.SetColorData(u""_ns);
1799 break;
1800 default:
1801 break;
1802 }
1803 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1804 if (NS_FAILED(rv)) {
1805 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1806 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1807 return EditorBase::ToGenericNSResult(rv);
1808 }
1809
1810 rv = RemoveInlinePropertyInternal(MOZ_KnownLive(property),
1811 MOZ_KnownLive(attribute),
1812 RemoveRelatedElements::No);
1813 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1814 "HTMLEditor::RemoveInlinePropertyInternal("
1815 "RemoveRelatedElements::No) failed");
1816 return EditorBase::ToGenericNSResult(rv);
1817 }
1818
RemoveInlinePropertyInternal(nsStaticAtom * aProperty,nsStaticAtom * aAttribute,RemoveRelatedElements aRemoveRelatedElements)1819 nsresult HTMLEditor::RemoveInlinePropertyInternal(
1820 nsStaticAtom* aProperty, nsStaticAtom* aAttribute,
1821 RemoveRelatedElements aRemoveRelatedElements) {
1822 MOZ_ASSERT(IsEditActionDataAvailable());
1823 MOZ_ASSERT(aAttribute != nsGkAtoms::_empty);
1824
1825 if (NS_WARN_IF(!mInitSucceeded)) {
1826 return NS_ERROR_NOT_INITIALIZED;
1827 }
1828
1829 DebugOnly<nsresult> rvIgnored = CommitComposition();
1830 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1831 "EditorBase::CommitComposition() failed, but ignored");
1832
1833 // Also remove equivalent properties (bug 317093)
1834 struct HTMLStyle final {
1835 // HTML tag name or nsGkAtoms::href or nsGkAtoms::name.
1836 nsStaticAtom* mProperty = nullptr;
1837 // HTML attribute like nsGkAtom::color for nsGkAtoms::font.
1838 nsStaticAtom* mAttribute = nullptr;
1839
1840 explicit HTMLStyle(nsStaticAtom* aProperty,
1841 nsStaticAtom* aAttribute = nullptr)
1842 : mProperty(aProperty), mAttribute(aAttribute) {}
1843 };
1844 AutoTArray<HTMLStyle, 3> removeStyles;
1845 if (aRemoveRelatedElements == RemoveRelatedElements::Yes) {
1846 if (aProperty == nsGkAtoms::b) {
1847 removeStyles.AppendElement(HTMLStyle(nsGkAtoms::strong));
1848 } else if (aProperty == nsGkAtoms::i) {
1849 removeStyles.AppendElement(HTMLStyle(nsGkAtoms::em));
1850 } else if (aProperty == nsGkAtoms::strike) {
1851 removeStyles.AppendElement(HTMLStyle(nsGkAtoms::s));
1852 } else if (aProperty == nsGkAtoms::font) {
1853 if (aAttribute == nsGkAtoms::size) {
1854 removeStyles.AppendElement(HTMLStyle(nsGkAtoms::big));
1855 removeStyles.AppendElement(HTMLStyle(nsGkAtoms::small));
1856 }
1857 // Handling `<tt>` element code was implemented for composer (bug 115922).
1858 // This shouldn't work with `Document.execCommand()` for compatibility
1859 // with the other browsers. Currently, edit action principal is set only
1860 // when the root caller is Document::ExecCommand() so that we should
1861 // handle `<tt>` element only when the principal is nullptr that must be
1862 // only when XUL command is executed on composer.
1863 else if (aAttribute == nsGkAtoms::face && !GetEditActionPrincipal()) {
1864 removeStyles.AppendElement(HTMLStyle(nsGkAtoms::tt));
1865 }
1866 }
1867 }
1868 removeStyles.AppendElement(HTMLStyle(aProperty, aAttribute));
1869
1870 if (SelectionRef().IsCollapsed()) {
1871 // Manipulating text attributes on a collapsed selection only sets state
1872 // for the next text insertion
1873 if (removeStyles[0].mProperty) {
1874 for (HTMLStyle& style : removeStyles) {
1875 MOZ_ASSERT(style.mProperty);
1876 if (style.mProperty == nsGkAtoms::href ||
1877 style.mProperty == nsGkAtoms::name) {
1878 mTypeInState->ClearProp(nsGkAtoms::a, nullptr);
1879 } else {
1880 mTypeInState->ClearProp(style.mProperty, style.mAttribute);
1881 }
1882 }
1883 } else {
1884 mTypeInState->ClearAllProps();
1885 }
1886 return NS_OK;
1887 }
1888
1889 // XXX Shouldn't we quit before calling `CommitComposition()`?
1890 if (IsInPlaintextMode()) {
1891 return NS_OK;
1892 }
1893
1894 EditActionResult result = CanHandleHTMLEditSubAction();
1895 if (result.Failed() || result.Canceled()) {
1896 NS_WARNING_ASSERTION(result.Succeeded(),
1897 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1898 return result.Rv();
1899 }
1900
1901 AutoPlaceholderBatch treatAsOneTransaction(
1902 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
1903 IgnoredErrorResult ignoredError;
1904 AutoEditSubActionNotifier startToHandleEditSubAction(
1905 *this, EditSubAction::eRemoveTextProperty, nsIEditor::eNext,
1906 ignoredError);
1907 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1908 return ignoredError.StealNSResult();
1909 }
1910 NS_WARNING_ASSERTION(
1911 !ignoredError.Failed(),
1912 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1913
1914 {
1915 AutoSelectionRestorer restoreSelectionLater(*this);
1916 AutoTransactionsConserveSelection dontChangeMySelection(*this);
1917
1918 for (HTMLStyle& style : removeStyles) {
1919 // Loop through the ranges in the selection.
1920 // XXX Although `Selection` will be restored by AutoSelectionRestorer,
1921 // AutoSelectionRangeArray just grabs the ranges in `Selection`.
1922 // Therefore, modifying each range may notify selection listener. So
1923 // perhaps, we should clone each range here instead.
1924 AutoSelectionRangeArray arrayOfRanges(SelectionRef());
1925 for (auto& range : arrayOfRanges.mRanges) {
1926 if (style.mProperty == nsGkAtoms::name) {
1927 // Promote range if it starts or end in a named anchor and we want to
1928 // remove named anchors
1929 nsresult rv = PromoteRangeIfStartsOrEndsInNamedAnchor(*range);
1930 if (NS_FAILED(rv)) {
1931 NS_WARNING(
1932 "HTMLEditor::PromoteRangeIfStartsOrEndsInNamedAnchor() failed");
1933 return rv;
1934 }
1935 } else {
1936 // Adjust range to include any ancestors whose children are entirely
1937 // selected
1938 nsresult rv = PromoteInlineRange(*range);
1939 if (NS_FAILED(rv)) {
1940 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
1941 return rv;
1942 }
1943 }
1944
1945 // Remove this style from ancestors of our range endpoints, splitting
1946 // them as appropriate
1947 SplitRangeOffResult splitRangeOffResult =
1948 SplitAncestorStyledInlineElementsAtRangeEdges(
1949 EditorDOMPoint(range->StartRef()),
1950 EditorDOMPoint(range->EndRef()), MOZ_KnownLive(style.mProperty),
1951 MOZ_KnownLive(style.mAttribute));
1952 if (splitRangeOffResult.Failed()) {
1953 NS_WARNING(
1954 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() "
1955 "failed");
1956 return splitRangeOffResult.Rv();
1957 }
1958
1959 // XXX Modifying `range` means that we may modify ranges in `Selection`.
1960 // Is this intentional? Note that the range may be not in
1961 // `Selection` too. It seems that at least one of them is not
1962 // an unexpected case.
1963 const EditorDOMPoint& startOfRange(
1964 splitRangeOffResult.SplitPointAtStart());
1965 const EditorDOMPoint& endOfRange(splitRangeOffResult.SplitPointAtEnd());
1966 if (NS_WARN_IF(!startOfRange.IsSet()) ||
1967 NS_WARN_IF(!endOfRange.IsSet())) {
1968 continue;
1969 }
1970
1971 nsresult rv = range->SetStartAndEnd(startOfRange.ToRawRangeBoundary(),
1972 endOfRange.ToRawRangeBoundary());
1973 // Note that modifying a range in `Selection` may run script so that
1974 // we might have been destroyed here.
1975 if (NS_WARN_IF(Destroyed())) {
1976 return NS_ERROR_EDITOR_DESTROYED;
1977 }
1978 if (NS_FAILED(rv)) {
1979 NS_WARNING("nsRange::SetStartAndEnd() failed");
1980 return rv;
1981 }
1982
1983 // Collect editable nodes which are entirely contained in the range.
1984 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
1985 if (startOfRange.GetContainer() == endOfRange.GetContainer() &&
1986 startOfRange.IsInTextNode()) {
1987 if (!EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
1988 EditorType::HTML)) {
1989 continue;
1990 }
1991 arrayOfContents.AppendElement(*startOfRange.ContainerAsText());
1992 } else if (startOfRange.IsInTextNode() && endOfRange.IsInTextNode() &&
1993 startOfRange.GetContainer()->GetNextSibling() ==
1994 endOfRange.GetContainer()) {
1995 if (EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
1996 EditorType::HTML)) {
1997 arrayOfContents.AppendElement(*startOfRange.ContainerAsText());
1998 }
1999 if (EditorUtils::IsEditableContent(*endOfRange.ContainerAsText(),
2000 EditorType::HTML)) {
2001 arrayOfContents.AppendElement(*endOfRange.ContainerAsText());
2002 }
2003 if (arrayOfContents.IsEmpty()) {
2004 continue;
2005 }
2006 } else {
2007 // Append first node if it's a text node but selected not entirely.
2008 if (startOfRange.IsInTextNode() &&
2009 !startOfRange.IsStartOfContainer() &&
2010 EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
2011 EditorType::HTML)) {
2012 arrayOfContents.AppendElement(*startOfRange.ContainerAsText());
2013 }
2014 // Append all entirely selected nodes.
2015 ContentSubtreeIterator subtreeIter;
2016 if (NS_SUCCEEDED(subtreeIter.Init(range))) {
2017 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
2018 nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
2019 if (NS_WARN_IF(!node)) {
2020 return NS_ERROR_FAILURE;
2021 }
2022 if (node->IsContent() &&
2023 EditorUtils::IsEditableContent(*node->AsContent(),
2024 EditorType::HTML)) {
2025 arrayOfContents.AppendElement(*node->AsContent());
2026 }
2027 }
2028 }
2029 // Append last node if it's a text node but selected not entirely.
2030 if (startOfRange.GetContainer() != endOfRange.GetContainer() &&
2031 endOfRange.IsInTextNode() && !endOfRange.IsEndOfContainer() &&
2032 EditorUtils::IsEditableContent(*endOfRange.ContainerAsText(),
2033 EditorType::HTML)) {
2034 arrayOfContents.AppendElement(*endOfRange.ContainerAsText());
2035 }
2036 }
2037
2038 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
2039 if (content->IsElement()) {
2040 nsresult rv = RemoveStyleInside(
2041 MOZ_KnownLive(*content->AsElement()),
2042 MOZ_KnownLive(style.mProperty), MOZ_KnownLive(style.mAttribute),
2043 SpecifiedStyle::Preserve);
2044 if (NS_FAILED(rv)) {
2045 NS_WARNING("HTMLEditor::RemoveStyleInside() failed");
2046 return rv;
2047 }
2048 }
2049
2050 bool isRemovable = IsRemovableParentStyleWithNewSpanElement(
2051 MOZ_KnownLive(content), MOZ_KnownLive(style.mProperty),
2052 MOZ_KnownLive(style.mAttribute));
2053 if (NS_WARN_IF(Destroyed())) {
2054 return NS_ERROR_EDITOR_DESTROYED;
2055 }
2056 if (!isRemovable) {
2057 continue;
2058 }
2059
2060 if (!content->IsText()) {
2061 // XXX Do we need to call this even when data node or something? If
2062 // so, for what?
2063 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to
2064 // keep it alive.
2065 DebugOnly<nsresult> rvIgnored = SetInlinePropertyOnNode(
2066 MOZ_KnownLive(content), MOZ_KnownLive(*style.mProperty),
2067 MOZ_KnownLive(style.mAttribute),
2068 u"-moz-editor-invert-value"_ns);
2069 if (NS_WARN_IF(Destroyed())) {
2070 return NS_ERROR_EDITOR_DESTROYED;
2071 }
2072 NS_WARNING_ASSERTION(
2073 NS_SUCCEEDED(rvIgnored),
2074 "HTMLEditor::SetInlinePropertyOnNode(-moz-editor-invert-value) "
2075 "failed, but ignored");
2076 continue;
2077 }
2078
2079 // If current node is a text node, we need to create `<span>` element
2080 // for it to overwrite parent style. Unfortunately, all browsers
2081 // don't join text nodes when removing a style. Therefore, there
2082 // may be multiple text nodes as adjacent siblings. That's the
2083 // reason why we need to handle text nodes in this loop.
2084 uint32_t startOffset = content == startOfRange.GetContainer()
2085 ? startOfRange.Offset()
2086 : 0;
2087 uint32_t endOffset = content == endOfRange.GetContainer()
2088 ? endOfRange.Offset()
2089 : content->Length();
2090 nsresult rv = SetInlinePropertyOnTextNode(
2091 MOZ_KnownLive(*content->AsText()), startOffset, endOffset,
2092 MOZ_KnownLive(*style.mProperty), MOZ_KnownLive(style.mAttribute),
2093 u"-moz-editor-invert-value"_ns);
2094 if (NS_FAILED(rv)) {
2095 NS_WARNING(
2096 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2097 "value) failed");
2098 return rv;
2099 }
2100 }
2101
2102 // For avoiding unnecessary loop cost, check whether the style is
2103 // invertible first.
2104 if (style.mProperty &&
2105 CSSEditUtils::IsCSSInvertible(*style.mProperty, style.mAttribute)) {
2106 // Finally, we should remove the style from all leaf text nodes if
2107 // they still have the style.
2108 AutoTArray<OwningNonNull<Text>, 32> leafTextNodes;
2109 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
2110 if (content->IsElement()) {
2111 CollectEditableLeafTextNodes(*content->AsElement(),
2112 leafTextNodes);
2113 }
2114 }
2115 for (OwningNonNull<Text>& textNode : leafTextNodes) {
2116 bool isRemovable = IsRemovableParentStyleWithNewSpanElement(
2117 MOZ_KnownLive(textNode), MOZ_KnownLive(style.mProperty),
2118 MOZ_KnownLive(style.mAttribute));
2119 if (NS_WARN_IF(Destroyed())) {
2120 return NS_ERROR_EDITOR_DESTROYED;
2121 }
2122 if (!isRemovable) {
2123 continue;
2124 }
2125 // MOZ_KnownLive because 'leafTextNodes' is guaranteed to
2126 // keep it alive.
2127 nsresult rv = SetInlinePropertyOnTextNode(
2128 MOZ_KnownLive(textNode), 0, textNode->TextLength(),
2129 MOZ_KnownLive(*style.mProperty),
2130 MOZ_KnownLive(style.mAttribute),
2131 u"-moz-editor-invert-value"_ns);
2132 if (NS_FAILED(rv)) {
2133 NS_WARNING(
2134 "HTMLEditor::SetInlinePropertyOnTextNode(-moz-editor-invert-"
2135 "value) failed");
2136 return rv;
2137 }
2138 }
2139 }
2140 } // for-loop of selection ranges
2141 } // for-loop of styles
2142 } // AutoSelectionRestorer and AutoTransactionsConserveSelection
2143
2144 // Restoring `Selection` may cause destroying us.
2145 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
2146 }
2147
IsRemovableParentStyleWithNewSpanElement(nsIContent & aContent,nsAtom * aHTMLProperty,nsAtom * aAttribute) const2148 bool HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
2149 nsIContent& aContent, nsAtom* aHTMLProperty, nsAtom* aAttribute) const {
2150 // We don't support to remove all inline styles with this path.
2151 if (!aHTMLProperty) {
2152 return false;
2153 }
2154
2155 // First check whether the style is invertible since this is the fastest
2156 // check.
2157 if (!CSSEditUtils::IsCSSInvertible(*aHTMLProperty, aAttribute)) {
2158 return false;
2159 }
2160
2161 // If parent block has the removing style, we should create `<span>`
2162 // element to remove the style even in HTML mode since Chrome does it.
2163 if (!CSSEditUtils::IsCSSEditableProperty(&aContent, aHTMLProperty,
2164 aAttribute)) {
2165 return false;
2166 }
2167
2168 // aContent's computed style indicates the CSS equivalence to
2169 // the HTML style to remove is applied; but we found no element
2170 // in the ancestors of aContent carrying specified styles;
2171 // assume it comes from a rule and let's try to insert a span
2172 // "inverting" the style
2173 nsAutoString emptyString;
2174 bool isSet = CSSEditUtils::IsComputedCSSEquivalentToHTMLInlineStyleSet(
2175 aContent, aHTMLProperty, aAttribute, emptyString);
2176 return NS_WARN_IF(Destroyed()) ? false : isSet;
2177 }
2178
CollectEditableLeafTextNodes(Element & aElement,nsTArray<OwningNonNull<Text>> & aLeafTextNodes) const2179 void HTMLEditor::CollectEditableLeafTextNodes(
2180 Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const {
2181 for (nsIContent* child = aElement.GetFirstChild(); child;
2182 child = child->GetNextSibling()) {
2183 if (child->IsElement()) {
2184 CollectEditableLeafTextNodes(*child->AsElement(), aLeafTextNodes);
2185 continue;
2186 }
2187 if (child->IsText()) {
2188 aLeafTextNodes.AppendElement(*child->AsText());
2189 }
2190 }
2191 }
2192
IncreaseFontSizeAsAction(nsIPrincipal * aPrincipal)2193 nsresult HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
2194 AutoEditActionDataSetter editActionData(*this, EditAction::eIncrementFontSize,
2195 aPrincipal);
2196 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2197 if (NS_FAILED(rv)) {
2198 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2199 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2200 return EditorBase::ToGenericNSResult(rv);
2201 }
2202
2203 rv = RelativeFontChange(FontSize::incr);
2204 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2205 "HTMLEditor::RelativeFontChange(FontSize::incr) failed");
2206 return EditorBase::ToGenericNSResult(rv);
2207 }
2208
DecreaseFontSizeAsAction(nsIPrincipal * aPrincipal)2209 nsresult HTMLEditor::DecreaseFontSizeAsAction(nsIPrincipal* aPrincipal) {
2210 AutoEditActionDataSetter editActionData(*this, EditAction::eDecrementFontSize,
2211 aPrincipal);
2212 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2213 if (NS_FAILED(rv)) {
2214 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2215 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2216 return EditorBase::ToGenericNSResult(rv);
2217 }
2218
2219 rv = RelativeFontChange(FontSize::decr);
2220 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2221 "HTMLEditor::RelativeFontChange(FontSize::decr) failed");
2222 return EditorBase::ToGenericNSResult(rv);
2223 }
2224
RelativeFontChange(FontSize aDir)2225 nsresult HTMLEditor::RelativeFontChange(FontSize aDir) {
2226 MOZ_ASSERT(IsEditActionDataAvailable());
2227
2228 DebugOnly<nsresult> rvIgnored = CommitComposition();
2229 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2230 "EditorBase::CommitComposition() failed, but ignored");
2231
2232 // If selection is collapsed, set typing state
2233 if (SelectionRef().IsCollapsed()) {
2234 nsAtom& atom = aDir == FontSize::incr ? *nsGkAtoms::big : *nsGkAtoms::small;
2235
2236 // Let's see in what kind of element the selection is
2237 if (NS_WARN_IF(!SelectionRef().RangeCount())) {
2238 return NS_OK;
2239 }
2240 RefPtr<const nsRange> firstRange = SelectionRef().GetRangeAt(0);
2241 if (NS_WARN_IF(!firstRange) ||
2242 NS_WARN_IF(!firstRange->GetStartContainer())) {
2243 return NS_OK;
2244 }
2245 OwningNonNull<nsINode> selectedNode = *firstRange->GetStartContainer();
2246 if (selectedNode->IsText()) {
2247 if (NS_WARN_IF(!selectedNode->GetParentNode())) {
2248 return NS_OK;
2249 }
2250 selectedNode = *selectedNode->GetParentNode();
2251 }
2252 if (!HTMLEditUtils::CanNodeContain(selectedNode, atom)) {
2253 return NS_OK;
2254 }
2255
2256 // Manipulating text attributes on a collapsed selection only sets state
2257 // for the next text insertion
2258 mTypeInState->SetProp(&atom, nullptr, u""_ns);
2259 return NS_OK;
2260 }
2261
2262 // Wrap with txn batching, rules sniffing, and selection preservation code
2263 AutoPlaceholderBatch treatAsOneTransaction(
2264 *this, ScrollSelectionIntoView::Yes, __FUNCTION__);
2265 IgnoredErrorResult ignoredError;
2266 AutoEditSubActionNotifier startToHandleEditSubAction(
2267 *this, EditSubAction::eSetTextProperty, nsIEditor::eNext, ignoredError);
2268 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2269 return ignoredError.StealNSResult();
2270 }
2271 NS_WARNING_ASSERTION(
2272 !ignoredError.Failed(),
2273 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2274
2275 AutoSelectionRestorer restoreSelectionLater(*this);
2276 AutoTransactionsConserveSelection dontChangeMySelection(*this);
2277
2278 // Loop through the ranges in the selection
2279 AutoSelectionRangeArray arrayOfRanges(SelectionRef());
2280 for (auto& range : arrayOfRanges.mRanges) {
2281 // Adjust range to include any ancestors with entirely selected children
2282 nsresult rv = PromoteInlineRange(*range);
2283 if (NS_FAILED(rv)) {
2284 NS_WARNING("HTMLEditor::PromoteInlineRange() failed");
2285 return rv;
2286 }
2287
2288 // Check for easy case: both range endpoints in same text node
2289 nsCOMPtr<nsINode> startNode = range->GetStartContainer();
2290 nsCOMPtr<nsINode> endNode = range->GetEndContainer();
2291 MOZ_ASSERT(startNode);
2292 MOZ_ASSERT(endNode);
2293 if (startNode == endNode && startNode->IsText()) {
2294 nsresult rv = RelativeFontChangeOnTextNode(
2295 aDir, MOZ_KnownLive(*startNode->GetAsText()), range->StartOffset(),
2296 range->EndOffset());
2297 if (NS_FAILED(rv)) {
2298 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2299 return rv;
2300 }
2301 } else {
2302 // Not the easy case. Range not contained in single text node. There
2303 // are up to three phases here. There are all the nodes reported by the
2304 // subtree iterator to be processed. And there are potentially a
2305 // starting textnode and an ending textnode which are only partially
2306 // contained by the range.
2307
2308 // Let's handle the nodes reported by the iterator. These nodes are
2309 // entirely contained in the selection range. We build up a list of them
2310 // (since doing operations on the document during iteration would perturb
2311 // the iterator).
2312
2313 // Iterate range and build up array
2314 ContentSubtreeIterator subtreeIter;
2315 if (NS_SUCCEEDED(subtreeIter.Init(range))) {
2316 nsTArray<OwningNonNull<nsIContent>> arrayOfContents;
2317 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
2318 if (NS_WARN_IF(!subtreeIter.GetCurrentNode()->IsContent())) {
2319 return NS_ERROR_FAILURE;
2320 }
2321 OwningNonNull<nsIContent> content =
2322 *subtreeIter.GetCurrentNode()->AsContent();
2323
2324 if (EditorUtils::IsEditableContent(content, EditorType::HTML)) {
2325 arrayOfContents.AppendElement(content);
2326 }
2327 }
2328
2329 // Now that we have the list, do the font size change on each node
2330 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
2331 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to keep it
2332 // alive.
2333 nsresult rv = RelativeFontChangeOnNode(
2334 aDir == FontSize::incr ? +1 : -1, MOZ_KnownLive(content));
2335 if (NS_FAILED(rv)) {
2336 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2337 return rv;
2338 }
2339 }
2340 }
2341 // Now check the start and end parents of the range to see if they need
2342 // to be separately handled (they do if they are text nodes, due to how
2343 // the subtree iterator works - it will not have reported them).
2344 if (startNode->IsText() && EditorUtils::IsEditableContent(
2345 *startNode->AsText(), EditorType::HTML)) {
2346 nsresult rv = RelativeFontChangeOnTextNode(
2347 aDir, MOZ_KnownLive(*startNode->AsText()), range->StartOffset(),
2348 startNode->Length());
2349 if (NS_FAILED(rv)) {
2350 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2351 return rv;
2352 }
2353 }
2354 if (endNode->IsText() && EditorUtils::IsEditableContent(
2355 *endNode->AsText(), EditorType::HTML)) {
2356 nsresult rv = RelativeFontChangeOnTextNode(
2357 aDir, MOZ_KnownLive(*endNode->AsText()), 0, range->EndOffset());
2358 if (NS_FAILED(rv)) {
2359 NS_WARNING("HTMLEditor::RelativeFontChangeOnTextNode() failed");
2360 return rv;
2361 }
2362 }
2363 }
2364 }
2365
2366 return NS_OK;
2367 }
2368
RelativeFontChangeOnTextNode(FontSize aDir,Text & aTextNode,uint32_t aStartOffset,uint32_t aEndOffset)2369 nsresult HTMLEditor::RelativeFontChangeOnTextNode(FontSize aDir,
2370 Text& aTextNode,
2371 uint32_t aStartOffset,
2372 uint32_t aEndOffset) {
2373 // Don't need to do anything if no characters actually selected
2374 if (aStartOffset == aEndOffset) {
2375 return NS_OK;
2376 }
2377
2378 if (!aTextNode.GetParentNode() ||
2379 !HTMLEditUtils::CanNodeContain(*aTextNode.GetParentNode(),
2380 *nsGkAtoms::big)) {
2381 return NS_OK;
2382 }
2383
2384 aEndOffset = std::min(aTextNode.Length(), aEndOffset);
2385
2386 // Make the range an independent node.
2387 RefPtr<Text> textNodeForTheRange = &aTextNode;
2388
2389 // Split at the end of the range.
2390 EditorDOMPoint atEnd(textNodeForTheRange, aEndOffset);
2391 if (!atEnd.IsEndOfContainer()) {
2392 // We need to split off back of text node
2393 SplitNodeResult splitAtEndResult = SplitNodeWithTransaction(atEnd);
2394 if (MOZ_UNLIKELY(splitAtEndResult.Failed())) {
2395 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
2396 return splitAtEndResult.Rv();
2397 }
2398 textNodeForTheRange =
2399 Text::FromNodeOrNull(splitAtEndResult.GetPreviousContent());
2400 MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange);
2401 }
2402
2403 // Split at the start of the range.
2404 EditorDOMPoint atStart(textNodeForTheRange, aStartOffset);
2405 if (!atStart.IsStartOfContainer()) {
2406 // We need to split off front of text node
2407 SplitNodeResult splitAtStartResult = SplitNodeWithTransaction(atStart);
2408 if (MOZ_UNLIKELY(splitAtStartResult.Failed())) {
2409 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
2410 return splitAtStartResult.Rv();
2411 }
2412 }
2413
2414 // Look for siblings that are correct type of node
2415 nsAtom* nodeType = aDir == FontSize::incr ? nsGkAtoms::big : nsGkAtoms::small;
2416 nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
2417 *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
2418 if (sibling && sibling->IsHTMLElement(nodeType)) {
2419 // Previous sib is already right kind of inline node; slide this over
2420 nsresult rv = MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
2421 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2422 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2423 return rv;
2424 }
2425 sibling = HTMLEditUtils::GetNextSibling(
2426 *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode});
2427 if (sibling && sibling->IsHTMLElement(nodeType)) {
2428 // Following sib is already right kind of inline node; slide this over
2429 nsresult rv = MoveNodeWithTransaction(*textNodeForTheRange,
2430 EditorDOMPoint(sibling, 0));
2431 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2432 "HTMLEditor::MoveNodeWithTransaction() failed");
2433 return rv;
2434 }
2435
2436 // Else reparent the node inside font node with appropriate relative size
2437 RefPtr<Element> newElement = InsertContainerWithTransaction(
2438 *textNodeForTheRange, MOZ_KnownLive(*nodeType));
2439 NS_WARNING_ASSERTION(newElement,
2440 "HTMLEditor::InsertContainerWithTransaction() failed");
2441 return newElement ? NS_OK : NS_ERROR_FAILURE;
2442 }
2443
RelativeFontChangeHelper(int32_t aSizeChange,nsINode * aNode)2444 nsresult HTMLEditor::RelativeFontChangeHelper(int32_t aSizeChange,
2445 nsINode* aNode) {
2446 MOZ_ASSERT(aNode);
2447
2448 /* This routine looks for all the font nodes in the tree rooted by aNode,
2449 including aNode itself, looking for font nodes that have the size attr
2450 set. Any such nodes need to have big or small put inside them, since
2451 they override any big/small that are above them.
2452 */
2453
2454 // Can only change font size by + or - 1
2455 if (aSizeChange != 1 && aSizeChange != -1) {
2456 return NS_ERROR_ILLEGAL_VALUE;
2457 }
2458
2459 // If this is a font node with size, put big/small inside it.
2460 if (aNode->IsHTMLElement(nsGkAtoms::font) &&
2461 aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::size)) {
2462 // Cycle through children and adjust relative font size.
2463 AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
2464 for (nsIContent* child = aNode->GetFirstChild(); child;
2465 child = child->GetNextSibling()) {
2466 childList.AppendElement(child);
2467 }
2468
2469 for (const auto& child : childList) {
2470 // MOZ_KnownLive because 'childList' is guaranteed to
2471 // keep it alive.
2472 nsresult rv = RelativeFontChangeOnNode(aSizeChange, MOZ_KnownLive(child));
2473 if (NS_FAILED(rv)) {
2474 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2475 return rv;
2476 }
2477 }
2478
2479 // RelativeFontChangeOnNode already calls us recursively,
2480 // so we don't need to check our children again.
2481 return NS_OK;
2482 }
2483
2484 // Otherwise cycle through the children.
2485 AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
2486 for (nsIContent* child = aNode->GetFirstChild(); child;
2487 child = child->GetNextSibling()) {
2488 childList.AppendElement(child);
2489 }
2490
2491 for (const auto& child : childList) {
2492 // MOZ_KnownLive because 'childList' is guaranteed to
2493 // keep it alive.
2494 nsresult rv = RelativeFontChangeHelper(aSizeChange, MOZ_KnownLive(child));
2495 if (NS_FAILED(rv)) {
2496 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2497 return rv;
2498 }
2499 }
2500
2501 return NS_OK;
2502 }
2503
RelativeFontChangeOnNode(int32_t aSizeChange,nsIContent * aNode)2504 nsresult HTMLEditor::RelativeFontChangeOnNode(int32_t aSizeChange,
2505 nsIContent* aNode) {
2506 MOZ_ASSERT(aNode);
2507 // Can only change font size by + or - 1
2508 if (aSizeChange != 1 && aSizeChange != -1) {
2509 return NS_ERROR_ILLEGAL_VALUE;
2510 }
2511
2512 nsAtom* atom;
2513 if (aSizeChange == 1) {
2514 atom = nsGkAtoms::big;
2515 } else {
2516 atom = nsGkAtoms::small;
2517 }
2518
2519 // Is it the opposite of what we want?
2520 if ((aSizeChange == 1 && aNode->IsHTMLElement(nsGkAtoms::small)) ||
2521 (aSizeChange == -1 && aNode->IsHTMLElement(nsGkAtoms::big))) {
2522 // first populate any nested font tags that have the size attr set
2523 nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
2524 if (NS_FAILED(rv)) {
2525 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2526 return rv;
2527 }
2528 // in that case, just remove this node and pull up the children
2529 rv = RemoveContainerWithTransaction(MOZ_KnownLive(*aNode->AsElement()));
2530 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2531 "HTMLEditor::RemoveContainerWithTransaction() failed");
2532 return rv;
2533 }
2534
2535 // can it be put inside a "big" or "small"?
2536 if (HTMLEditUtils::CanNodeContain(*atom, *aNode)) {
2537 // first populate any nested font tags that have the size attr set
2538 nsresult rv = RelativeFontChangeHelper(aSizeChange, aNode);
2539 if (NS_FAILED(rv)) {
2540 NS_WARNING("HTMLEditor::RelativeFontChangeHelper() failed");
2541 return rv;
2542 }
2543
2544 // ok, chuck it in.
2545 // first look at siblings of aNode for matching bigs or smalls.
2546 // if we find one, move aNode into it.
2547 nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling(
2548 *aNode, {WalkTreeOption::IgnoreNonEditableNode});
2549 if (sibling && sibling->IsHTMLElement(atom)) {
2550 // previous sib is already right kind of inline node; slide this over into
2551 // it
2552 nsresult rv = MoveNodeToEndWithTransaction(*aNode, *sibling);
2553 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2554 "HTMLEditor::MoveNodeToEndWithTransaction() failed");
2555 return rv;
2556 }
2557
2558 sibling = HTMLEditUtils::GetNextSibling(
2559 *aNode, {WalkTreeOption::IgnoreNonEditableNode});
2560 if (sibling && sibling->IsHTMLElement(atom)) {
2561 // following sib is already right kind of inline node; slide this over
2562 // into it
2563 return MoveNodeWithTransaction(*aNode, EditorDOMPoint(sibling, 0));
2564 }
2565
2566 // else insert it above aNode
2567 RefPtr<Element> newElement =
2568 InsertContainerWithTransaction(*aNode, MOZ_KnownLive(*atom));
2569 NS_WARNING_ASSERTION(newElement,
2570 "HTMLEditor::InsertContainerWithTransaction() failed");
2571 return newElement ? NS_OK : NS_ERROR_FAILURE;
2572 }
2573
2574 // none of the above? then cycle through the children.
2575 // MOOSE: we should group the children together if possible
2576 // into a single "big" or "small". For the moment they are
2577 // each getting their own.
2578 AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
2579 for (nsIContent* child = aNode->GetFirstChild(); child;
2580 child = child->GetNextSibling()) {
2581 childList.AppendElement(child);
2582 }
2583
2584 for (const auto& child : childList) {
2585 // MOZ_KnownLive because 'childList' is guaranteed to
2586 // keep it alive.
2587 nsresult rv = RelativeFontChangeOnNode(aSizeChange, MOZ_KnownLive(child));
2588 if (NS_FAILED(rv)) {
2589 NS_WARNING("HTMLEditor::RelativeFontChangeOnNode() failed");
2590 return rv;
2591 }
2592 }
2593
2594 return NS_OK;
2595 }
2596
GetFontFaceState(bool * aMixed,nsAString & outFace)2597 NS_IMETHODIMP HTMLEditor::GetFontFaceState(bool* aMixed, nsAString& outFace) {
2598 if (NS_WARN_IF(!aMixed)) {
2599 return NS_ERROR_INVALID_ARG;
2600 }
2601
2602 *aMixed = true;
2603 outFace.Truncate();
2604
2605 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2606 if (NS_WARN_IF(!editActionData.CanHandle())) {
2607 return NS_ERROR_NOT_INITIALIZED;
2608 }
2609
2610 bool first, any, all;
2611
2612 nsresult rv = GetInlinePropertyBase(*nsGkAtoms::font, nsGkAtoms::face,
2613 nullptr, &first, &any, &all, &outFace);
2614 if (NS_FAILED(rv)) {
2615 NS_WARNING(
2616 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) "
2617 "failed");
2618 return EditorBase::ToGenericNSResult(rv);
2619 }
2620 if (any && !all) {
2621 return NS_OK; // mixed
2622 }
2623 if (all) {
2624 *aMixed = false;
2625 return NS_OK;
2626 }
2627
2628 // if there is no font face, check for tt
2629 rv = GetInlinePropertyBase(*nsGkAtoms::tt, nullptr, nullptr, &first, &any,
2630 &all, nullptr);
2631 if (NS_FAILED(rv)) {
2632 NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed");
2633 return EditorBase::ToGenericNSResult(rv);
2634 }
2635 if (any && !all) {
2636 return NS_OK; // mixed
2637 }
2638 if (all) {
2639 *aMixed = false;
2640 outFace.AssignLiteral("tt");
2641 }
2642
2643 if (!any) {
2644 // there was no font face attrs of any kind. We are in normal font.
2645 outFace.Truncate();
2646 *aMixed = false;
2647 }
2648 return NS_OK;
2649 }
2650
GetFontColorState(bool * aMixed,nsAString & aOutColor)2651 nsresult HTMLEditor::GetFontColorState(bool* aMixed, nsAString& aOutColor) {
2652 if (NS_WARN_IF(!aMixed)) {
2653 return NS_ERROR_INVALID_ARG;
2654 }
2655
2656 *aMixed = true;
2657 aOutColor.Truncate();
2658
2659 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2660 if (NS_WARN_IF(!editActionData.CanHandle())) {
2661 return NS_ERROR_NOT_INITIALIZED;
2662 }
2663
2664 bool first, any, all;
2665 nsresult rv = GetInlinePropertyBase(*nsGkAtoms::font, nsGkAtoms::color,
2666 nullptr, &first, &any, &all, &aOutColor);
2667 if (NS_FAILED(rv)) {
2668 NS_WARNING(
2669 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) "
2670 "failed");
2671 return EditorBase::ToGenericNSResult(rv);
2672 }
2673
2674 if (any && !all) {
2675 return NS_OK; // mixed
2676 }
2677 if (all) {
2678 *aMixed = false;
2679 return NS_OK;
2680 }
2681
2682 if (!any) {
2683 // there was no font color attrs of any kind..
2684 aOutColor.Truncate();
2685 *aMixed = false;
2686 }
2687 return NS_OK;
2688 }
2689
2690 // the return value is true only if the instance of the HTML editor we created
2691 // can handle CSS styles (for instance, Composer can, Messenger can't) and if
2692 // the CSS preference is checked
GetIsCSSEnabled(bool * aIsCSSEnabled)2693 NS_IMETHODIMP HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled) {
2694 *aIsCSSEnabled = IsCSSEnabled();
2695 return NS_OK;
2696 }
2697
HasStyleOrIdOrClassAttribute(Element & aElement)2698 bool HTMLEditor::HasStyleOrIdOrClassAttribute(Element& aElement) {
2699 return aElement.HasNonEmptyAttr(nsGkAtoms::style) ||
2700 aElement.HasNonEmptyAttr(nsGkAtoms::_class) ||
2701 aElement.HasNonEmptyAttr(nsGkAtoms::id);
2702 }
2703
2704 } // namespace mozilla
2705