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