Bug 1862524 [wpt PR 42903] - Use 6 reftest chunks rather than 5, a=testonly
[gecko.git] / editor / libeditor / HTMLEditorState.cpp
blobc484d17f7ca60c7eb1ba78760ca092dee7a84177
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "HTMLEditor.h"
9 #include <algorithm>
10 #include <utility>
12 #include "AutoRangeArray.h"
13 #include "CSSEditUtils.h"
14 #include "EditAction.h"
15 #include "EditorUtils.h"
16 #include "HTMLEditHelpers.h"
17 #include "HTMLEditUtils.h"
18 #include "WSRunObject.h"
20 #include "mozilla/Assertions.h"
21 #include "mozilla/OwningNonNull.h"
22 #include "mozilla/dom/Element.h"
23 #include "mozilla/dom/Selection.h"
25 #include "nsAString.h"
26 #include "nsAlgorithm.h"
27 #include "nsAtom.h"
28 #include "nsDebug.h"
29 #include "nsError.h"
30 #include "nsGkAtoms.h"
31 #include "nsIContent.h"
32 #include "nsINode.h"
33 #include "nsRange.h"
34 #include "nsString.h"
35 #include "nsStringFwd.h"
36 #include "nsTArray.h"
38 // NOTE: This file was split from:
39 // https://searchfox.org/mozilla-central/rev/c409dd9235c133ab41eba635f906aa16e050c197/editor/libeditor/HTMLEditSubActionHandler.cpp
41 namespace mozilla {
43 using EditorType = EditorUtils::EditorType;
44 using WalkTreeOption = HTMLEditUtils::WalkTreeOption;
46 /*****************************************************************************
47 * ListElementSelectionState
48 ****************************************************************************/
50 ListElementSelectionState::ListElementSelectionState(HTMLEditor& aHTMLEditor,
51 ErrorResult& aRv) {
52 MOZ_ASSERT(!aRv.Failed());
54 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
55 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
56 return;
59 // XXX Should we create another constructor which won't create
60 // AutoEditActionDataSetter? Or should we create another
61 // AutoEditActionDataSetter which won't nest edit action?
62 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
63 EditAction::eNotEditing);
64 if (NS_WARN_IF(!editActionData.CanHandle())) {
65 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
66 return;
69 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
70 if (!editingHostOrRoot) {
71 // This is not a handler of editing command so that if there is no active
72 // editing host, let's use the <body> or document element instead.
73 editingHostOrRoot = aHTMLEditor.GetRoot();
74 if (!editingHostOrRoot) {
75 return;
79 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
81 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
82 extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
83 EditSubAction::eCreateOrChangeList, *editingHostOrRoot);
84 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
85 aHTMLEditor, arrayOfContents, EditSubAction::eCreateOrChangeList,
86 AutoRangeArray::CollectNonEditableNodes::No);
87 if (NS_FAILED(rv)) {
88 NS_WARNING(
89 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
90 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
91 aRv = EditorBase::ToGenericNSResult(rv);
92 return;
96 // Examine list type for nodes in selection.
97 for (const auto& content : arrayOfContents) {
98 if (!content->IsElement()) {
99 mIsOtherContentSelected = true;
100 } else if (content->IsHTMLElement(nsGkAtoms::ul)) {
101 mIsULElementSelected = true;
102 } else if (content->IsHTMLElement(nsGkAtoms::ol)) {
103 mIsOLElementSelected = true;
104 } else if (content->IsHTMLElement(nsGkAtoms::li)) {
105 if (dom::Element* parent = content->GetParentElement()) {
106 if (parent->IsHTMLElement(nsGkAtoms::ul)) {
107 mIsULElementSelected = true;
108 } else if (parent->IsHTMLElement(nsGkAtoms::ol)) {
109 mIsOLElementSelected = true;
112 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dl, nsGkAtoms::dt,
113 nsGkAtoms::dd)) {
114 mIsDLElementSelected = true;
115 } else {
116 mIsOtherContentSelected = true;
119 if (mIsULElementSelected && mIsOLElementSelected && mIsDLElementSelected &&
120 mIsOtherContentSelected) {
121 break;
126 /*****************************************************************************
127 * ListItemElementSelectionState
128 ****************************************************************************/
130 ListItemElementSelectionState::ListItemElementSelectionState(
131 HTMLEditor& aHTMLEditor, ErrorResult& aRv) {
132 MOZ_ASSERT(!aRv.Failed());
134 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
135 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
136 return;
139 // XXX Should we create another constructor which won't create
140 // AutoEditActionDataSetter? Or should we create another
141 // AutoEditActionDataSetter which won't nest edit action?
142 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
143 EditAction::eNotEditing);
144 if (NS_WARN_IF(!editActionData.CanHandle())) {
145 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
146 return;
149 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
150 if (!editingHostOrRoot) {
151 // This is not a handler of editing command so that if there is no active
152 // editing host, let's use the <body> or document element instead.
153 editingHostOrRoot = aHTMLEditor.GetRoot();
154 if (!editingHostOrRoot) {
155 return;
159 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
161 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
162 extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
163 EditSubAction::eCreateOrChangeList, *editingHostOrRoot);
164 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
165 aHTMLEditor, arrayOfContents, EditSubAction::eCreateOrChangeList,
166 AutoRangeArray::CollectNonEditableNodes::No);
167 if (NS_FAILED(rv)) {
168 NS_WARNING_ASSERTION(
169 NS_SUCCEEDED(rv),
170 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
171 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
172 aRv = EditorBase::ToGenericNSResult(rv);
173 return;
177 // examine list type for nodes in selection
178 for (const auto& content : arrayOfContents) {
179 if (!content->IsElement()) {
180 mIsOtherElementSelected = true;
181 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol,
182 nsGkAtoms::li)) {
183 mIsLIElementSelected = true;
184 } else if (content->IsHTMLElement(nsGkAtoms::dt)) {
185 mIsDTElementSelected = true;
186 } else if (content->IsHTMLElement(nsGkAtoms::dd)) {
187 mIsDDElementSelected = true;
188 } else if (content->IsHTMLElement(nsGkAtoms::dl)) {
189 if (mIsDTElementSelected && mIsDDElementSelected) {
190 continue;
192 // need to look inside dl and see which types of items it has
193 DefinitionListItemScanner scanner(*content->AsElement());
194 mIsDTElementSelected |= scanner.DTElementFound();
195 mIsDDElementSelected |= scanner.DDElementFound();
196 } else {
197 mIsOtherElementSelected = true;
200 if (mIsLIElementSelected && mIsDTElementSelected && mIsDDElementSelected &&
201 mIsOtherElementSelected) {
202 break;
207 /*****************************************************************************
208 * AlignStateAtSelection
209 ****************************************************************************/
211 AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor,
212 ErrorResult& aRv) {
213 MOZ_ASSERT(!aRv.Failed());
215 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
216 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
217 return;
220 // XXX Should we create another constructor which won't create
221 // AutoEditActionDataSetter? Or should we create another
222 // AutoEditActionDataSetter which won't nest edit action?
223 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
224 EditAction::eNotEditing);
225 if (NS_WARN_IF(!editActionData.CanHandle())) {
226 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
227 return;
230 if (aHTMLEditor.IsSelectionRangeContainerNotContent()) {
231 NS_WARNING("Some selection containers are not content node, but ignored");
232 return;
235 // For now, just return first alignment. We don't check if it's mixed.
236 // This is for efficiency given that our current UI doesn't care if it's
237 // mixed.
238 // cmanske: NOT TRUE! We would like to pay attention to mixed state in
239 // [Format] -> [Align] submenu!
241 // This routine assumes that alignment is done ONLY by `<div>` elements
242 // if aHTMLEditor is not in CSS mode.
244 if (NS_WARN_IF(!aHTMLEditor.GetRoot())) {
245 aRv.Throw(NS_ERROR_FAILURE);
246 return;
249 OwningNonNull<dom::Element> bodyOrDocumentElement = *aHTMLEditor.GetRoot();
250 EditorRawDOMPoint atBodyOrDocumentElement(bodyOrDocumentElement);
252 const nsRange* firstRange = aHTMLEditor.SelectionRef().GetRangeAt(0);
253 mFoundSelectionRanges = !!firstRange;
254 if (!mFoundSelectionRanges) {
255 NS_WARNING("There was no selection range");
256 aRv.Throw(NS_ERROR_FAILURE);
257 return;
259 EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
260 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
261 aRv.Throw(NS_ERROR_FAILURE);
262 return;
264 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
266 nsIContent* editTargetContent = nullptr;
267 // If selection is collapsed or in a text node, take the container.
268 if (aHTMLEditor.SelectionRef().IsCollapsed() ||
269 atStartOfSelection.IsInTextNode()) {
270 editTargetContent = atStartOfSelection.GetContainerAs<nsIContent>();
271 if (NS_WARN_IF(!editTargetContent)) {
272 aRv.Throw(NS_ERROR_FAILURE);
273 return;
276 // If selection container is the `<body>` element which is set to
277 // `HTMLDocument.body`, take first editable node in it.
278 // XXX Why don't we just compare `atStartOfSelection.GetChild()` and
279 // `bodyOrDocumentElement`? Then, we can avoid computing the
280 // offset.
281 else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) &&
282 atBodyOrDocumentElement.IsSet() &&
283 atStartOfSelection.Offset() == atBodyOrDocumentElement.Offset()) {
284 editTargetContent = HTMLEditUtils::GetNextContent(
285 atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode},
286 BlockInlineCheck::Unused, aHTMLEditor.ComputeEditingHost());
287 if (NS_WARN_IF(!editTargetContent)) {
288 aRv.Throw(NS_ERROR_FAILURE);
289 return;
292 // Otherwise, use first selected node.
293 // XXX Only for retrieving it, the following block treats all selected
294 // ranges. `HTMLEditor` should have
295 // `GetFirstSelectionRangeExtendedToHardLineStartAndEnd()`.
296 else {
297 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
298 if (!editingHostOrRoot) {
299 // This is not a handler of editing command so that if there is no active
300 // editing host, let's use the <body> or document element instead.
301 editingHostOrRoot = aHTMLEditor.GetRoot();
302 if (!editingHostOrRoot) {
303 return;
306 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
307 extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
308 EditSubAction::eSetOrClearAlignment, *editingHostOrRoot);
310 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
311 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
312 aHTMLEditor, arrayOfContents, EditSubAction::eSetOrClearAlignment,
313 AutoRangeArray::CollectNonEditableNodes::Yes);
314 if (NS_FAILED(rv)) {
315 NS_WARNING(
316 "AutoRangeArray::CollectEditTargetNodes(eSetOrClearAlignment, "
317 "CollectNonEditableNodes::Yes) failed");
318 aRv.Throw(NS_ERROR_FAILURE);
319 return;
321 if (arrayOfContents.IsEmpty()) {
322 NS_WARNING(
323 "AutoRangeArray::CollectEditTargetNodes(eSetOrClearAlignment, "
324 "CollectNonEditableNodes::Yes) returned no contents");
325 aRv.Throw(NS_ERROR_FAILURE);
326 return;
328 editTargetContent = arrayOfContents[0];
331 const RefPtr<dom::Element> maybeNonEditableBlockElement =
332 HTMLEditUtils::GetInclusiveAncestorElement(
333 *editTargetContent, HTMLEditUtils::ClosestBlockElement,
334 BlockInlineCheck::UseHTMLDefaultStyle);
335 if (NS_WARN_IF(!maybeNonEditableBlockElement)) {
336 aRv.Throw(NS_ERROR_FAILURE);
337 return;
340 if (aHTMLEditor.IsCSSEnabled() && EditorElementStyle::Align().IsCSSSettable(
341 *maybeNonEditableBlockElement)) {
342 // We are in CSS mode and we know how to align this element with CSS
343 nsAutoString value;
344 // Let's get the value(s) of text-align or margin-left/margin-right
345 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedCSSEquivalentTo(
346 *maybeNonEditableBlockElement, EditorElementStyle::Align(), value);
347 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
348 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
349 return;
351 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
352 "CSSEditUtils::GetComputedCSSEquivalentTo("
353 "EditorElementStyle::Align()) failed, but ignored");
354 if (value.EqualsLiteral(u"center") || value.EqualsLiteral(u"-moz-center") ||
355 value.EqualsLiteral(u"auto auto")) {
356 mFirstAlign = nsIHTMLEditor::eCenter;
357 return;
359 if (value.EqualsLiteral(u"right") || value.EqualsLiteral(u"-moz-right") ||
360 value.EqualsLiteral(u"auto 0px")) {
361 mFirstAlign = nsIHTMLEditor::eRight;
362 return;
364 if (value.EqualsLiteral(u"justify")) {
365 mFirstAlign = nsIHTMLEditor::eJustify;
366 return;
368 // XXX In RTL document, is this expected?
369 mFirstAlign = nsIHTMLEditor::eLeft;
370 return;
373 for (Element* const containerElement :
374 editTargetContent->InclusiveAncestorsOfType<Element>()) {
375 // If the node is a parent `<table>` element of edit target, let's break
376 // here to materialize the 'inline-block' behaviour of html tables
377 // regarding to text alignment.
378 if (containerElement != editTargetContent &&
379 containerElement->IsHTMLElement(nsGkAtoms::table)) {
380 return;
383 if (EditorElementStyle::Align().IsCSSSettable(*containerElement)) {
384 nsAutoString value;
385 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
386 *containerElement, *nsGkAtoms::textAlign, value);
387 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
388 "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::"
389 "textAlign) failed, but ignored");
390 if (!value.IsEmpty()) {
391 if (value.EqualsLiteral("center")) {
392 mFirstAlign = nsIHTMLEditor::eCenter;
393 return;
395 if (value.EqualsLiteral("right")) {
396 mFirstAlign = nsIHTMLEditor::eRight;
397 return;
399 if (value.EqualsLiteral("justify")) {
400 mFirstAlign = nsIHTMLEditor::eJustify;
401 return;
403 if (value.EqualsLiteral("left")) {
404 mFirstAlign = nsIHTMLEditor::eLeft;
405 return;
407 // XXX
408 // text-align: start and end aren't supported yet
412 if (!HTMLEditUtils::SupportsAlignAttr(*containerElement)) {
413 continue;
416 nsAutoString alignAttributeValue;
417 containerElement->GetAttr(nsGkAtoms::align, alignAttributeValue);
418 if (alignAttributeValue.IsEmpty()) {
419 continue;
422 if (alignAttributeValue.LowerCaseEqualsASCII("center")) {
423 mFirstAlign = nsIHTMLEditor::eCenter;
424 return;
426 if (alignAttributeValue.LowerCaseEqualsASCII("right")) {
427 mFirstAlign = nsIHTMLEditor::eRight;
428 return;
430 // XXX This is odd case. `<div align="justify">` is not in any standards.
431 if (alignAttributeValue.LowerCaseEqualsASCII("justify")) {
432 mFirstAlign = nsIHTMLEditor::eJustify;
433 return;
435 // XXX In RTL document, is this expected?
436 mFirstAlign = nsIHTMLEditor::eLeft;
437 return;
441 /*****************************************************************************
442 * ParagraphStateAtSelection
443 ****************************************************************************/
445 ParagraphStateAtSelection::ParagraphStateAtSelection(
446 HTMLEditor& aHTMLEditor, FormatBlockMode aFormatBlockMode,
447 ErrorResult& aRv) {
448 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
449 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
450 return;
453 // XXX Should we create another constructor which won't create
454 // AutoEditActionDataSetter? Or should we create another
455 // AutoEditActionDataSetter which won't nest edit action?
456 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
457 EditAction::eNotEditing);
458 if (NS_WARN_IF(!editActionData.CanHandle())) {
459 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
460 return;
463 if (aHTMLEditor.IsSelectionRangeContainerNotContent()) {
464 NS_WARNING("Some selection containers are not content node, but ignored");
465 return;
468 if (MOZ_UNLIKELY(!aHTMLEditor.SelectionRef().RangeCount())) {
469 aRv.Throw(NS_ERROR_FAILURE);
470 return;
473 const Element* const editingHostOrBodyOrDocumentElement = [&]() -> Element* {
474 if (Element* editingHost = aHTMLEditor.ComputeEditingHost()) {
475 return editingHost;
477 return aHTMLEditor.GetRoot();
478 }();
479 if (!editingHostOrBodyOrDocumentElement ||
480 !HTMLEditUtils::IsSimplyEditableNode(
481 *editingHostOrBodyOrDocumentElement)) {
482 return;
485 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
486 nsresult rv = CollectEditableFormatNodesInSelection(
487 aHTMLEditor, aFormatBlockMode, *editingHostOrBodyOrDocumentElement,
488 arrayOfContents);
489 if (NS_FAILED(rv)) {
490 NS_WARNING(
491 "ParagraphStateAtSelection::CollectEditableFormatNodesInSelection() "
492 "failed");
493 aRv.Throw(rv);
494 return;
497 // We need to append descendant format block if block nodes are not format
498 // block. This is so we only have to look "up" the hierarchy to find
499 // format nodes, instead of both up and down.
500 for (size_t index : Reversed(IntegerRange(arrayOfContents.Length()))) {
501 OwningNonNull<nsIContent>& content = arrayOfContents[index];
502 if (HTMLEditUtils::IsBlockElement(content,
503 BlockInlineCheck::UseHTMLDefaultStyle) &&
504 !HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
505 // XXX This RemoveObject() call has already been commented out and
506 // the above comment explained we're trying to replace non-format
507 // block nodes in the array. According to the following blocks and
508 // `AppendDescendantFormatNodesAndFirstInlineNode()`, replacing
509 // non-format block with descendants format blocks makes sense.
510 // arrayOfContents.RemoveObject(node);
511 ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
512 arrayOfContents, aFormatBlockMode, *content->AsElement());
516 // We might have an empty node list. if so, find selection parent
517 // and put that on the list
518 if (arrayOfContents.IsEmpty()) {
519 const auto atCaret =
520 aHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>();
521 if (NS_WARN_IF(!atCaret.IsInContentNode())) {
522 MOZ_ASSERT(false,
523 "We've already checked whether there is a selection range, "
524 "but we have no range right now.");
525 aRv.Throw(NS_ERROR_FAILURE);
526 return;
528 arrayOfContents.AppendElement(*atCaret.ContainerAs<nsIContent>());
531 for (auto& content : Reversed(arrayOfContents)) {
532 const Element* formatElement = nullptr;
533 if (HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
534 formatElement = content->AsElement();
536 // Ignore inline contents since its children have been appended
537 // the list above so that we'll handle this descendants later.
538 // XXX: It's odd to ignore block children to consider the mixed state.
539 else if (HTMLEditUtils::IsBlockElement(
540 content, BlockInlineCheck::UseHTMLDefaultStyle)) {
541 continue;
543 // If we meet an inline node, let's get its parent format.
544 else {
545 for (Element* parentElement : content->AncestorsOfType<Element>()) {
546 // If we reach `HTMLDocument.body` or `Document.documentElement`,
547 // there is no format.
548 if (parentElement == editingHostOrBodyOrDocumentElement) {
549 break;
551 if (HTMLEditor::IsFormatElement(aFormatBlockMode, *parentElement)) {
552 MOZ_ASSERT(parentElement->NodeInfo()->NameAtom());
553 formatElement = parentElement;
554 break;
559 auto FormatElementIsInclusiveDescendantOfFormatDLElement = [&]() {
560 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
561 return false;
563 if (!formatElement) {
564 return false;
566 for (const Element* const element :
567 formatElement->InclusiveAncestorsOfType<Element>()) {
568 if (element->IsHTMLElement(nsGkAtoms::dl)) {
569 return true;
571 if (element->IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) {
572 continue;
574 if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(
575 *formatElement)) {
576 return false;
579 return false;
582 // if this is the first node, we've found, remember it as the format
583 if (!mFirstParagraphState) {
584 mFirstParagraphState = formatElement
585 ? formatElement->NodeInfo()->NameAtom()
586 : nsGkAtoms::_empty;
587 mIsInDLElement = FormatElementIsInclusiveDescendantOfFormatDLElement();
588 continue;
590 mIsInDLElement &= FormatElementIsInclusiveDescendantOfFormatDLElement();
591 // else make sure it matches previously found format
592 if ((!formatElement && mFirstParagraphState != nsGkAtoms::_empty) ||
593 (formatElement &&
594 !formatElement->IsHTMLElement(mFirstParagraphState))) {
595 mIsMixed = true;
596 break;
601 // static
602 void ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
603 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
604 FormatBlockMode aFormatBlockMode, dom::Element& aNonFormatBlockElement) {
605 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
606 aNonFormatBlockElement, BlockInlineCheck::UseHTMLDefaultStyle));
607 MOZ_ASSERT(
608 !HTMLEditor::IsFormatElement(aFormatBlockMode, aNonFormatBlockElement));
610 // We only need to place any one inline inside this node onto
611 // the list. They are all the same for purposes of determining
612 // paragraph style. We use foundInline to track this as we are
613 // going through the children in the loop below.
614 bool foundInline = false;
615 for (nsIContent* childContent = aNonFormatBlockElement.GetFirstChild();
616 childContent; childContent = childContent->GetNextSibling()) {
617 const bool isBlock = HTMLEditUtils::IsBlockElement(
618 *childContent, BlockInlineCheck::UseHTMLDefaultStyle);
619 const bool isFormat =
620 HTMLEditor::IsFormatElement(aFormatBlockMode, *childContent);
621 // If the child is a non-format block element, let's check its children
622 // recursively.
623 if (isBlock && !isFormat) {
624 ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
625 aArrayOfContents, aFormatBlockMode, *childContent->AsElement());
626 continue;
629 // If it's a format block, append it.
630 if (isFormat) {
631 aArrayOfContents.AppendElement(*childContent);
632 continue;
635 MOZ_ASSERT(!isBlock);
637 // If we haven't found inline node, append only this first inline node.
638 // XXX I think that this makes sense if caller of this removes
639 // aNonFormatBlockElement from aArrayOfContents because the last loop
640 // of the constructor can check parent format block with
641 // aNonFormatBlockElement.
642 if (!foundInline) {
643 foundInline = true;
644 aArrayOfContents.AppendElement(*childContent);
645 continue;
650 // static
651 nsresult ParagraphStateAtSelection::CollectEditableFormatNodesInSelection(
652 HTMLEditor& aHTMLEditor, FormatBlockMode aFormatBlockMode,
653 const Element& aEditingHost,
654 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) {
656 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
657 extendedSelectionRanges.ExtendRangesToWrapLinesToHandleBlockLevelEditAction(
658 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
659 ? EditSubAction::eFormatBlockForHTMLCommand
660 : EditSubAction::eCreateOrRemoveBlock,
661 aEditingHost);
662 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
663 aHTMLEditor, aArrayOfContents,
664 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
665 ? EditSubAction::eFormatBlockForHTMLCommand
666 : EditSubAction::eCreateOrRemoveBlock,
667 AutoRangeArray::CollectNonEditableNodes::Yes);
668 if (NS_FAILED(rv)) {
669 NS_WARNING(
670 "AutoRangeArray::CollectEditTargetNodes("
671 "CollectNonEditableNodes::Yes) failed");
672 return rv;
676 // Pre-process our list of nodes
677 for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) {
678 OwningNonNull<nsIContent> content = aArrayOfContents[index];
680 // Remove all non-editable nodes. Leave them be.
681 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
682 aArrayOfContents.RemoveElementAt(index);
683 continue;
686 // Scan for table elements. If we find table elements other than table,
687 // replace it with a list of any editable non-table content. Ditto for
688 // list elements.
689 if (HTMLEditUtils::IsAnyTableElement(content) ||
690 HTMLEditUtils::IsAnyListElement(content) ||
691 HTMLEditUtils::IsListItem(content)) {
692 aArrayOfContents.RemoveElementAt(index);
693 HTMLEditUtils::CollectChildren(
694 content, aArrayOfContents, index,
695 {CollectChildrenOption::CollectListChildren,
696 CollectChildrenOption::CollectTableChildren});
699 return NS_OK;
702 } // namespace mozilla