no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / editor / libeditor / HTMLEditorState.cpp
blobbfd496add58261b8fa293de64b5f746bd0bb2cfe
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.ExtendRangesToWrapLines(
83 EditSubAction::eCreateOrChangeList,
84 BlockInlineCheck::UseHTMLDefaultStyle, *editingHostOrRoot);
85 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
86 aHTMLEditor, arrayOfContents, EditSubAction::eCreateOrChangeList,
87 AutoRangeArray::CollectNonEditableNodes::No);
88 if (NS_FAILED(rv)) {
89 NS_WARNING(
90 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
91 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
92 aRv = EditorBase::ToGenericNSResult(rv);
93 return;
97 // Examine list type for nodes in selection.
98 for (const auto& content : arrayOfContents) {
99 if (!content->IsElement()) {
100 mIsOtherContentSelected = true;
101 } else if (content->IsHTMLElement(nsGkAtoms::ul)) {
102 mIsULElementSelected = true;
103 } else if (content->IsHTMLElement(nsGkAtoms::ol)) {
104 mIsOLElementSelected = true;
105 } else if (content->IsHTMLElement(nsGkAtoms::li)) {
106 if (dom::Element* parent = content->GetParentElement()) {
107 if (parent->IsHTMLElement(nsGkAtoms::ul)) {
108 mIsULElementSelected = true;
109 } else if (parent->IsHTMLElement(nsGkAtoms::ol)) {
110 mIsOLElementSelected = true;
113 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::dl, nsGkAtoms::dt,
114 nsGkAtoms::dd)) {
115 mIsDLElementSelected = true;
116 } else {
117 mIsOtherContentSelected = true;
120 if (mIsULElementSelected && mIsOLElementSelected && mIsDLElementSelected &&
121 mIsOtherContentSelected) {
122 break;
127 /*****************************************************************************
128 * ListItemElementSelectionState
129 ****************************************************************************/
131 ListItemElementSelectionState::ListItemElementSelectionState(
132 HTMLEditor& aHTMLEditor, ErrorResult& aRv) {
133 MOZ_ASSERT(!aRv.Failed());
135 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
136 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
137 return;
140 // XXX Should we create another constructor which won't create
141 // AutoEditActionDataSetter? Or should we create another
142 // AutoEditActionDataSetter which won't nest edit action?
143 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
144 EditAction::eNotEditing);
145 if (NS_WARN_IF(!editActionData.CanHandle())) {
146 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
147 return;
150 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
151 if (!editingHostOrRoot) {
152 // This is not a handler of editing command so that if there is no active
153 // editing host, let's use the <body> or document element instead.
154 editingHostOrRoot = aHTMLEditor.GetRoot();
155 if (!editingHostOrRoot) {
156 return;
160 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
162 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
163 extendedSelectionRanges.ExtendRangesToWrapLines(
164 EditSubAction::eCreateOrChangeList,
165 BlockInlineCheck::UseHTMLDefaultStyle, *editingHostOrRoot);
166 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
167 aHTMLEditor, arrayOfContents, EditSubAction::eCreateOrChangeList,
168 AutoRangeArray::CollectNonEditableNodes::No);
169 if (NS_FAILED(rv)) {
170 NS_WARNING_ASSERTION(
171 NS_SUCCEEDED(rv),
172 "AutoRangeArray::CollectEditTargetNodes(EditSubAction::"
173 "eCreateOrChangeList, CollectNonEditableNodes::No) failed");
174 aRv = EditorBase::ToGenericNSResult(rv);
175 return;
179 // examine list type for nodes in selection
180 for (const auto& content : arrayOfContents) {
181 if (!content->IsElement()) {
182 mIsOtherElementSelected = true;
183 } else if (content->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol,
184 nsGkAtoms::li)) {
185 mIsLIElementSelected = true;
186 } else if (content->IsHTMLElement(nsGkAtoms::dt)) {
187 mIsDTElementSelected = true;
188 } else if (content->IsHTMLElement(nsGkAtoms::dd)) {
189 mIsDDElementSelected = true;
190 } else if (content->IsHTMLElement(nsGkAtoms::dl)) {
191 if (mIsDTElementSelected && mIsDDElementSelected) {
192 continue;
194 // need to look inside dl and see which types of items it has
195 DefinitionListItemScanner scanner(*content->AsElement());
196 mIsDTElementSelected |= scanner.DTElementFound();
197 mIsDDElementSelected |= scanner.DDElementFound();
198 } else {
199 mIsOtherElementSelected = true;
202 if (mIsLIElementSelected && mIsDTElementSelected && mIsDDElementSelected &&
203 mIsOtherElementSelected) {
204 break;
209 /*****************************************************************************
210 * AlignStateAtSelection
211 ****************************************************************************/
213 AlignStateAtSelection::AlignStateAtSelection(HTMLEditor& aHTMLEditor,
214 ErrorResult& aRv) {
215 MOZ_ASSERT(!aRv.Failed());
217 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
218 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
219 return;
222 // XXX Should we create another constructor which won't create
223 // AutoEditActionDataSetter? Or should we create another
224 // AutoEditActionDataSetter which won't nest edit action?
225 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
226 EditAction::eNotEditing);
227 if (NS_WARN_IF(!editActionData.CanHandle())) {
228 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
229 return;
232 if (aHTMLEditor.IsSelectionRangeContainerNotContent()) {
233 NS_WARNING("Some selection containers are not content node, but ignored");
234 return;
237 // For now, just return first alignment. We don't check if it's mixed.
238 // This is for efficiency given that our current UI doesn't care if it's
239 // mixed.
240 // cmanske: NOT TRUE! We would like to pay attention to mixed state in
241 // [Format] -> [Align] submenu!
243 // This routine assumes that alignment is done ONLY by `<div>` elements
244 // if aHTMLEditor is not in CSS mode.
246 if (NS_WARN_IF(!aHTMLEditor.GetRoot())) {
247 aRv.Throw(NS_ERROR_FAILURE);
248 return;
251 OwningNonNull<dom::Element> bodyOrDocumentElement = *aHTMLEditor.GetRoot();
252 EditorRawDOMPoint atBodyOrDocumentElement(bodyOrDocumentElement);
254 const nsRange* firstRange = aHTMLEditor.SelectionRef().GetRangeAt(0);
255 mFoundSelectionRanges = !!firstRange;
256 if (!mFoundSelectionRanges) {
257 NS_WARNING("There was no selection range");
258 aRv.Throw(NS_ERROR_FAILURE);
259 return;
261 EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
262 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
263 aRv.Throw(NS_ERROR_FAILURE);
264 return;
266 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
268 nsIContent* editTargetContent = nullptr;
269 // If selection is collapsed or in a text node, take the container.
270 if (aHTMLEditor.SelectionRef().IsCollapsed() ||
271 atStartOfSelection.IsInTextNode()) {
272 editTargetContent = atStartOfSelection.GetContainerAs<nsIContent>();
273 if (NS_WARN_IF(!editTargetContent)) {
274 aRv.Throw(NS_ERROR_FAILURE);
275 return;
278 // If selection container is the `<body>` element which is set to
279 // `HTMLDocument.body`, take first editable node in it.
280 // XXX Why don't we just compare `atStartOfSelection.GetChild()` and
281 // `bodyOrDocumentElement`? Then, we can avoid computing the
282 // offset.
283 else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) &&
284 atBodyOrDocumentElement.IsSet() &&
285 atStartOfSelection.Offset() == atBodyOrDocumentElement.Offset()) {
286 editTargetContent = HTMLEditUtils::GetNextContent(
287 atStartOfSelection, {WalkTreeOption::IgnoreNonEditableNode},
288 BlockInlineCheck::Unused, aHTMLEditor.ComputeEditingHost());
289 if (NS_WARN_IF(!editTargetContent)) {
290 aRv.Throw(NS_ERROR_FAILURE);
291 return;
294 // Otherwise, use first selected node.
295 // XXX Only for retrieving it, the following block treats all selected
296 // ranges. `HTMLEditor` should have
297 // `GetFirstSelectionRangeExtendedToHardLineStartAndEnd()`.
298 else {
299 Element* editingHostOrRoot = aHTMLEditor.ComputeEditingHost();
300 if (!editingHostOrRoot) {
301 // This is not a handler of editing command so that if there is no active
302 // editing host, let's use the <body> or document element instead.
303 editingHostOrRoot = aHTMLEditor.GetRoot();
304 if (!editingHostOrRoot) {
305 return;
308 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
309 extendedSelectionRanges.ExtendRangesToWrapLines(
310 EditSubAction::eSetOrClearAlignment,
311 BlockInlineCheck::UseHTMLDefaultStyle, *editingHostOrRoot);
313 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
314 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
315 aHTMLEditor, arrayOfContents, EditSubAction::eSetOrClearAlignment,
316 AutoRangeArray::CollectNonEditableNodes::Yes);
317 if (NS_FAILED(rv)) {
318 NS_WARNING(
319 "AutoRangeArray::CollectEditTargetNodes(eSetOrClearAlignment, "
320 "CollectNonEditableNodes::Yes) failed");
321 aRv.Throw(NS_ERROR_FAILURE);
322 return;
324 if (arrayOfContents.IsEmpty()) {
325 NS_WARNING(
326 "AutoRangeArray::CollectEditTargetNodes(eSetOrClearAlignment, "
327 "CollectNonEditableNodes::Yes) returned no contents");
328 aRv.Throw(NS_ERROR_FAILURE);
329 return;
331 editTargetContent = arrayOfContents[0];
334 const RefPtr<dom::Element> maybeNonEditableBlockElement =
335 HTMLEditUtils::GetInclusiveAncestorElement(
336 *editTargetContent, HTMLEditUtils::ClosestBlockElement,
337 BlockInlineCheck::UseHTMLDefaultStyle);
338 if (NS_WARN_IF(!maybeNonEditableBlockElement)) {
339 aRv.Throw(NS_ERROR_FAILURE);
340 return;
343 if (aHTMLEditor.IsCSSEnabled() && EditorElementStyle::Align().IsCSSSettable(
344 *maybeNonEditableBlockElement)) {
345 // We are in CSS mode and we know how to align this element with CSS
346 nsAutoString value;
347 // Let's get the value(s) of text-align or margin-left/margin-right
348 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedCSSEquivalentTo(
349 *maybeNonEditableBlockElement, EditorElementStyle::Align(), value);
350 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
351 aRv.Throw(NS_ERROR_EDITOR_DESTROYED);
352 return;
354 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
355 "CSSEditUtils::GetComputedCSSEquivalentTo("
356 "EditorElementStyle::Align()) failed, but ignored");
357 if (value.EqualsLiteral(u"center") || value.EqualsLiteral(u"-moz-center") ||
358 value.EqualsLiteral(u"auto auto")) {
359 mFirstAlign = nsIHTMLEditor::eCenter;
360 return;
362 if (value.EqualsLiteral(u"right") || value.EqualsLiteral(u"-moz-right") ||
363 value.EqualsLiteral(u"auto 0px")) {
364 mFirstAlign = nsIHTMLEditor::eRight;
365 return;
367 if (value.EqualsLiteral(u"justify")) {
368 mFirstAlign = nsIHTMLEditor::eJustify;
369 return;
371 // XXX In RTL document, is this expected?
372 mFirstAlign = nsIHTMLEditor::eLeft;
373 return;
376 for (Element* const containerElement :
377 editTargetContent->InclusiveAncestorsOfType<Element>()) {
378 // If the node is a parent `<table>` element of edit target, let's break
379 // here to materialize the 'inline-block' behaviour of html tables
380 // regarding to text alignment.
381 if (containerElement != editTargetContent &&
382 containerElement->IsHTMLElement(nsGkAtoms::table)) {
383 return;
386 if (EditorElementStyle::Align().IsCSSSettable(*containerElement)) {
387 nsAutoString value;
388 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetSpecifiedProperty(
389 *containerElement, *nsGkAtoms::textAlign, value);
390 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
391 "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::"
392 "textAlign) failed, but ignored");
393 if (!value.IsEmpty()) {
394 if (value.EqualsLiteral("center")) {
395 mFirstAlign = nsIHTMLEditor::eCenter;
396 return;
398 if (value.EqualsLiteral("right")) {
399 mFirstAlign = nsIHTMLEditor::eRight;
400 return;
402 if (value.EqualsLiteral("justify")) {
403 mFirstAlign = nsIHTMLEditor::eJustify;
404 return;
406 if (value.EqualsLiteral("left")) {
407 mFirstAlign = nsIHTMLEditor::eLeft;
408 return;
410 // XXX
411 // text-align: start and end aren't supported yet
415 if (!HTMLEditUtils::SupportsAlignAttr(*containerElement)) {
416 continue;
419 nsAutoString alignAttributeValue;
420 containerElement->GetAttr(nsGkAtoms::align, alignAttributeValue);
421 if (alignAttributeValue.IsEmpty()) {
422 continue;
425 if (alignAttributeValue.LowerCaseEqualsASCII("center")) {
426 mFirstAlign = nsIHTMLEditor::eCenter;
427 return;
429 if (alignAttributeValue.LowerCaseEqualsASCII("right")) {
430 mFirstAlign = nsIHTMLEditor::eRight;
431 return;
433 // XXX This is odd case. `<div align="justify">` is not in any standards.
434 if (alignAttributeValue.LowerCaseEqualsASCII("justify")) {
435 mFirstAlign = nsIHTMLEditor::eJustify;
436 return;
438 // XXX In RTL document, is this expected?
439 mFirstAlign = nsIHTMLEditor::eLeft;
440 return;
444 /*****************************************************************************
445 * ParagraphStateAtSelection
446 ****************************************************************************/
448 ParagraphStateAtSelection::ParagraphStateAtSelection(
449 HTMLEditor& aHTMLEditor, FormatBlockMode aFormatBlockMode,
450 ErrorResult& aRv) {
451 if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
452 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
453 return;
456 // XXX Should we create another constructor which won't create
457 // AutoEditActionDataSetter? Or should we create another
458 // AutoEditActionDataSetter which won't nest edit action?
459 EditorBase::AutoEditActionDataSetter editActionData(aHTMLEditor,
460 EditAction::eNotEditing);
461 if (NS_WARN_IF(!editActionData.CanHandle())) {
462 aRv = EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
463 return;
466 if (aHTMLEditor.IsSelectionRangeContainerNotContent()) {
467 NS_WARNING("Some selection containers are not content node, but ignored");
468 return;
471 if (MOZ_UNLIKELY(!aHTMLEditor.SelectionRef().RangeCount())) {
472 aRv.Throw(NS_ERROR_FAILURE);
473 return;
476 const Element* const editingHostOrBodyOrDocumentElement = [&]() -> Element* {
477 if (Element* editingHost = aHTMLEditor.ComputeEditingHost()) {
478 return editingHost;
480 return aHTMLEditor.GetRoot();
481 }();
482 if (!editingHostOrBodyOrDocumentElement ||
483 !HTMLEditUtils::IsSimplyEditableNode(
484 *editingHostOrBodyOrDocumentElement)) {
485 return;
488 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
489 nsresult rv = CollectEditableFormatNodesInSelection(
490 aHTMLEditor, aFormatBlockMode, *editingHostOrBodyOrDocumentElement,
491 arrayOfContents);
492 if (NS_FAILED(rv)) {
493 NS_WARNING(
494 "ParagraphStateAtSelection::CollectEditableFormatNodesInSelection() "
495 "failed");
496 aRv.Throw(rv);
497 return;
500 // We need to append descendant format block if block nodes are not format
501 // block. This is so we only have to look "up" the hierarchy to find
502 // format nodes, instead of both up and down.
503 for (size_t index : Reversed(IntegerRange(arrayOfContents.Length()))) {
504 OwningNonNull<nsIContent>& content = arrayOfContents[index];
505 if (HTMLEditUtils::IsBlockElement(content,
506 BlockInlineCheck::UseHTMLDefaultStyle) &&
507 !HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
508 // XXX This RemoveObject() call has already been commented out and
509 // the above comment explained we're trying to replace non-format
510 // block nodes in the array. According to the following blocks and
511 // `AppendDescendantFormatNodesAndFirstInlineNode()`, replacing
512 // non-format block with descendants format blocks makes sense.
513 // arrayOfContents.RemoveObject(node);
514 ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
515 arrayOfContents, aFormatBlockMode, *content->AsElement());
519 // We might have an empty node list. if so, find selection parent
520 // and put that on the list
521 if (arrayOfContents.IsEmpty()) {
522 const auto atCaret =
523 aHTMLEditor.GetFirstSelectionStartPoint<EditorRawDOMPoint>();
524 if (NS_WARN_IF(!atCaret.IsInContentNode())) {
525 MOZ_ASSERT(false,
526 "We've already checked whether there is a selection range, "
527 "but we have no range right now.");
528 aRv.Throw(NS_ERROR_FAILURE);
529 return;
531 arrayOfContents.AppendElement(*atCaret.ContainerAs<nsIContent>());
534 for (auto& content : Reversed(arrayOfContents)) {
535 const Element* formatElement = nullptr;
536 if (HTMLEditor::IsFormatElement(aFormatBlockMode, content)) {
537 formatElement = content->AsElement();
539 // Ignore inline contents since its children have been appended
540 // the list above so that we'll handle this descendants later.
541 // XXX: It's odd to ignore block children to consider the mixed state.
542 else if (HTMLEditUtils::IsBlockElement(
543 content, BlockInlineCheck::UseHTMLDefaultStyle)) {
544 continue;
546 // If we meet an inline node, let's get its parent format.
547 else {
548 for (Element* parentElement : content->AncestorsOfType<Element>()) {
549 // If we reach `HTMLDocument.body` or `Document.documentElement`,
550 // there is no format.
551 if (parentElement == editingHostOrBodyOrDocumentElement) {
552 break;
554 if (HTMLEditor::IsFormatElement(aFormatBlockMode, *parentElement)) {
555 MOZ_ASSERT(parentElement->NodeInfo()->NameAtom());
556 formatElement = parentElement;
557 break;
562 auto FormatElementIsInclusiveDescendantOfFormatDLElement = [&]() {
563 if (aFormatBlockMode == FormatBlockMode::XULParagraphStateCommand) {
564 return false;
566 if (!formatElement) {
567 return false;
569 for (const Element* const element :
570 formatElement->InclusiveAncestorsOfType<Element>()) {
571 if (element->IsHTMLElement(nsGkAtoms::dl)) {
572 return true;
574 if (element->IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt)) {
575 continue;
577 if (HTMLEditUtils::IsFormatElementForFormatBlockCommand(
578 *formatElement)) {
579 return false;
582 return false;
585 // if this is the first node, we've found, remember it as the format
586 if (!mFirstParagraphState) {
587 mFirstParagraphState = formatElement
588 ? formatElement->NodeInfo()->NameAtom()
589 : nsGkAtoms::_empty;
590 mIsInDLElement = FormatElementIsInclusiveDescendantOfFormatDLElement();
591 continue;
593 mIsInDLElement &= FormatElementIsInclusiveDescendantOfFormatDLElement();
594 // else make sure it matches previously found format
595 if ((!formatElement && mFirstParagraphState != nsGkAtoms::_empty) ||
596 (formatElement &&
597 !formatElement->IsHTMLElement(mFirstParagraphState))) {
598 mIsMixed = true;
599 break;
604 // static
605 void ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
606 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents,
607 FormatBlockMode aFormatBlockMode, dom::Element& aNonFormatBlockElement) {
608 MOZ_ASSERT(HTMLEditUtils::IsBlockElement(
609 aNonFormatBlockElement, BlockInlineCheck::UseHTMLDefaultStyle));
610 MOZ_ASSERT(
611 !HTMLEditor::IsFormatElement(aFormatBlockMode, aNonFormatBlockElement));
613 // We only need to place any one inline inside this node onto
614 // the list. They are all the same for purposes of determining
615 // paragraph style. We use foundInline to track this as we are
616 // going through the children in the loop below.
617 bool foundInline = false;
618 for (nsIContent* childContent = aNonFormatBlockElement.GetFirstChild();
619 childContent; childContent = childContent->GetNextSibling()) {
620 const bool isBlock = HTMLEditUtils::IsBlockElement(
621 *childContent, BlockInlineCheck::UseHTMLDefaultStyle);
622 const bool isFormat =
623 HTMLEditor::IsFormatElement(aFormatBlockMode, *childContent);
624 // If the child is a non-format block element, let's check its children
625 // recursively.
626 if (isBlock && !isFormat) {
627 ParagraphStateAtSelection::AppendDescendantFormatNodesAndFirstInlineNode(
628 aArrayOfContents, aFormatBlockMode, *childContent->AsElement());
629 continue;
632 // If it's a format block, append it.
633 if (isFormat) {
634 aArrayOfContents.AppendElement(*childContent);
635 continue;
638 MOZ_ASSERT(!isBlock);
640 // If we haven't found inline node, append only this first inline node.
641 // XXX I think that this makes sense if caller of this removes
642 // aNonFormatBlockElement from aArrayOfContents because the last loop
643 // of the constructor can check parent format block with
644 // aNonFormatBlockElement.
645 if (!foundInline) {
646 foundInline = true;
647 aArrayOfContents.AppendElement(*childContent);
648 continue;
653 // static
654 nsresult ParagraphStateAtSelection::CollectEditableFormatNodesInSelection(
655 HTMLEditor& aHTMLEditor, FormatBlockMode aFormatBlockMode,
656 const Element& aEditingHost,
657 nsTArray<OwningNonNull<nsIContent>>& aArrayOfContents) {
659 AutoRangeArray extendedSelectionRanges(aHTMLEditor.SelectionRef());
660 extendedSelectionRanges.ExtendRangesToWrapLines(
661 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
662 ? EditSubAction::eFormatBlockForHTMLCommand
663 : EditSubAction::eCreateOrRemoveBlock,
664 BlockInlineCheck::UseHTMLDefaultStyle, aEditingHost);
665 nsresult rv = extendedSelectionRanges.CollectEditTargetNodes(
666 aHTMLEditor, aArrayOfContents,
667 aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand
668 ? EditSubAction::eFormatBlockForHTMLCommand
669 : EditSubAction::eCreateOrRemoveBlock,
670 AutoRangeArray::CollectNonEditableNodes::Yes);
671 if (NS_FAILED(rv)) {
672 NS_WARNING(
673 "AutoRangeArray::CollectEditTargetNodes("
674 "CollectNonEditableNodes::Yes) failed");
675 return rv;
679 // Pre-process our list of nodes
680 for (size_t index : Reversed(IntegerRange(aArrayOfContents.Length()))) {
681 OwningNonNull<nsIContent> content = aArrayOfContents[index];
683 // Remove all non-editable nodes. Leave them be.
684 if (!EditorUtils::IsEditableContent(content, EditorType::HTML)) {
685 aArrayOfContents.RemoveElementAt(index);
686 continue;
689 // Scan for table elements. If we find table elements other than table,
690 // replace it with a list of any editable non-table content. Ditto for
691 // list elements.
692 if (HTMLEditUtils::IsAnyTableElement(content) ||
693 HTMLEditUtils::IsAnyListElement(content) ||
694 HTMLEditUtils::IsListItem(content)) {
695 aArrayOfContents.RemoveElementAt(index);
696 HTMLEditUtils::CollectChildren(
697 content, aArrayOfContents, index,
698 {CollectChildrenOption::CollectListChildren,
699 CollectChildrenOption::CollectTableChildren});
702 return NS_OK;
705 } // namespace mozilla