1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef HTMLEditUtils_h
7 #define HTMLEditUtils_h
9 #include "mozilla/Attributes.h"
10 #include "mozilla/EditorDOMPoint.h"
11 #include "mozilla/Maybe.h"
12 #include "mozilla/dom/AbstractRange.h"
13 #include "mozilla/dom/AncestorIterator.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/dom/Text.h"
17 #include "nsGkAtoms.h"
18 #include "nsHTMLTags.h"
24 enum class EditAction
;
26 class HTMLEditUtils final
{
27 using Element
= dom::Element
;
28 using Selection
= dom::Selection
;
31 static const char16_t kSpace
= 0x0020;
32 static const char16_t kNBSP
= 0x00A0;
35 * IsSimplyEditableNode() returns true when aNode is simply editable.
36 * This does NOT means that aNode can be removed from current parent nor
37 * aNode's data is editable.
39 static bool IsSimplyEditableNode(const nsINode
& aNode
) {
40 return aNode
.IsEditable();
44 * IsRemovableFromParentNode() returns true when aContent is editable, has a
45 * parent node and the parent node is also editable.
47 static bool IsRemovableFromParentNode(const nsIContent
& aContent
) {
48 return aContent
.IsEditable() && aContent
.GetParentNode() &&
49 aContent
.GetParentNode()->IsEditable();
53 * CanContentsBeJoined() returns true if aLeftContent and aRightContent can be
54 * joined. At least, Node.nodeName must be same when this returns true.
56 enum class StyleDifference
{
57 // Ignore style information so that callers may join different styled
60 // Compare style information when the contents are any elements.
62 // Compare style information only when the contents are <span> elements.
63 CompareIfSpanElements
,
65 static bool CanContentsBeJoined(const nsIContent
& aLeftContent
,
66 const nsIContent
& aRightContent
,
67 StyleDifference aStyleDifference
);
70 * IsBlockElement() returns true if aContent is an element and it should
71 * be treated as a block. (This does not refer style information.)
73 static bool IsBlockElement(const nsIContent
& aContent
);
75 * IsInlineElement() returns true if aElement is an element node but
76 * shouldn't be treated as a block or aElement is not an element.
77 * XXX This looks odd. For example, how about a comment node?
79 static bool IsInlineElement(const nsIContent
& aContent
) {
80 return !IsBlockElement(aContent
);
83 static bool IsInlineStyle(nsINode
* aNode
);
85 * IsRemovableInlineStyleElement() returns true if aElement is an inline
86 * element and can be removed or split to in order to modifying inline
89 static bool IsRemovableInlineStyleElement(dom::Element
& aElement
);
90 static bool IsFormatNode(nsINode
* aNode
);
91 static bool IsNodeThatCanOutdent(nsINode
* aNode
);
92 static bool IsHeader(nsINode
& aNode
);
93 static bool IsListItem(nsINode
* aNode
);
94 static bool IsTable(nsINode
* aNode
);
95 static bool IsTableRow(nsINode
* aNode
);
96 static bool IsAnyTableElement(nsINode
* aNode
);
97 static bool IsAnyTableElementButNotTable(nsINode
* aNode
);
98 static bool IsTableCell(nsINode
* node
);
99 static bool IsTableCellOrCaption(nsINode
& aNode
);
100 static bool IsAnyListElement(nsINode
* aNode
);
101 static bool IsPre(nsINode
* aNode
);
102 static bool IsImage(nsINode
* aNode
);
103 static bool IsLink(nsINode
* aNode
);
104 static bool IsNamedAnchor(nsINode
* aNode
);
105 static bool IsMozDiv(nsINode
* aNode
);
106 static bool IsMailCite(nsINode
* aNode
);
107 static bool IsFormWidget(nsINode
* aNode
);
108 static bool SupportsAlignAttr(nsINode
& aNode
);
110 static bool CanNodeContain(const nsINode
& aParent
, const nsIContent
& aChild
) {
111 switch (aParent
.NodeType()) {
112 case nsINode::ELEMENT_NODE
:
113 case nsINode::DOCUMENT_FRAGMENT_NODE
:
114 return HTMLEditUtils::CanNodeContain(*aParent
.NodeInfo()->NameAtom(),
120 static bool CanNodeContain(const nsINode
& aParent
, nsAtom
& aChildNodeName
) {
121 switch (aParent
.NodeType()) {
122 case nsINode::ELEMENT_NODE
:
123 case nsINode::DOCUMENT_FRAGMENT_NODE
:
124 return HTMLEditUtils::CanNodeContain(*aParent
.NodeInfo()->NameAtom(),
130 static bool CanNodeContain(nsAtom
& aParentNodeName
,
131 const nsIContent
& aChild
) {
132 switch (aChild
.NodeType()) {
133 case nsINode::TEXT_NODE
:
134 case nsINode::ELEMENT_NODE
:
135 case nsINode::DOCUMENT_FRAGMENT_NODE
:
136 return HTMLEditUtils::CanNodeContain(aParentNodeName
,
137 *aChild
.NodeInfo()->NameAtom());
142 // XXX Only this overload does not check the node type. Therefore, only this
143 // treat Document, Comment, CDATASection, etc.
144 static bool CanNodeContain(nsAtom
& aParentNodeName
, nsAtom
& aChildNodeName
) {
145 nsHTMLTag childTagEnum
;
146 // XXX Should this handle #cdata-section too?
147 if (&aChildNodeName
== nsGkAtoms::textTagName
) {
148 childTagEnum
= eHTMLTag_text
;
150 childTagEnum
= nsHTMLTags::AtomTagToId(&aChildNodeName
);
153 nsHTMLTag parentTagEnum
= nsHTMLTags::AtomTagToId(&aParentNodeName
);
154 return HTMLEditUtils::CanNodeContain(parentTagEnum
, childTagEnum
);
158 * CanElementContainParagraph() returns true if aElement can have a <p>
159 * element as its child or its descendant.
161 static bool CanElementContainParagraph(const Element
& aElement
) {
162 if (HTMLEditUtils::CanNodeContain(aElement
, *nsGkAtoms::p
)) {
166 // Even if the element cannot have a <p> element as a child, it can contain
167 // <p> element as a descendant if it's one of the following elements.
168 if (aElement
.IsAnyOfHTMLElements(nsGkAtoms::ol
, nsGkAtoms::ul
,
169 nsGkAtoms::dl
, nsGkAtoms::table
,
170 nsGkAtoms::thead
, nsGkAtoms::tbody
,
171 nsGkAtoms::tfoot
, nsGkAtoms::tr
)) {
175 // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it
181 * IsContainerNode() returns true if aContent is a container node.
183 static bool IsContainerNode(const nsIContent
& aContent
) {
185 // XXX Should this handle #cdata-section too?
186 if (aContent
.IsText()) {
187 tagEnum
= eHTMLTag_text
;
189 // XXX Why don't we use nsHTMLTags::AtomTagToId? Are there some
191 tagEnum
= nsHTMLTags::StringTagToId(aContent
.NodeName());
193 return HTMLEditUtils::IsContainerNode(tagEnum
);
197 * See execCommand spec:
198 * https://w3c.github.io/editing/execCommand.html#non-list-single-line-container
199 * https://w3c.github.io/editing/execCommand.html#single-line-container
201 static bool IsNonListSingleLineContainer(nsINode
& aNode
);
202 static bool IsSingleLineContainer(nsINode
& aNode
);
205 * GetLastLeafChild() returns rightmost leaf content in aNode. It depends on
206 * aChildBlockBoundary whether this scans into a block child or treat
209 enum class ChildBlockBoundary
{
210 // Even if there is a child block, keep scanning a leaf content in it.
212 // If there is a child block, return it.
215 static nsIContent
* GetLastLeafChild(nsINode
& aNode
,
216 ChildBlockBoundary aChildBlockBoundary
) {
217 for (nsIContent
* content
= aNode
.GetLastChild(); content
;
218 content
= content
->GetLastChild()) {
219 if (aChildBlockBoundary
== ChildBlockBoundary::TreatAsLeaf
&&
220 HTMLEditUtils::IsBlockElement(*content
)) {
223 if (!content
->HasChildren()) {
231 * GetFirstLeafChild() returns leftmost leaf content in aNode. It depends on
232 * aChildBlockBoundary whether this scans into a block child or treat
235 static nsIContent
* GetFirstLeafChild(nsINode
& aNode
,
236 ChildBlockBoundary aChildBlockBoundary
) {
237 for (nsIContent
* content
= aNode
.GetFirstChild(); content
;
238 content
= content
->GetFirstChild()) {
239 if (aChildBlockBoundary
== ChildBlockBoundary::TreatAsLeaf
&&
240 HTMLEditUtils::IsBlockElement(*content
)) {
243 if (!content
->HasChildren()) {
251 * GetNextLeafContentOrNextBlockElement() returns next leaf content or
252 * next block element of aStartContent inside aAncestorLimiter.
253 * Note that the result may be a contet outside aCurrentBlock if
254 * aStartContent equals aCurrentBlock.
256 * @param aStartContent The start content to scan next content.
257 * @param aCurrentBlock Must be ancestor of aStartContent. Dispite
258 * the name, inline content is allowed if
259 * aStartContent is in an inline editing host.
260 * @param aAncestorLimiter Optional, setting this guarantees the
261 * result is in aAncestorLimiter unless
262 * aStartContent is not a descendant of this.
264 static nsIContent
* GetNextLeafContentOrNextBlockElement(
265 const nsIContent
& aStartContent
, const nsIContent
& aCurrentBlock
,
266 const Element
* aAncestorLimiter
= nullptr) {
267 if (&aStartContent
== aAncestorLimiter
) {
271 nsIContent
* nextContent
= aStartContent
.GetNextSibling();
273 if (!aStartContent
.GetParentElement()) {
274 NS_WARNING("Reached orphan node while climbing up the DOM tree");
277 for (Element
* parentElement
: aStartContent
.AncestorsOfType
<Element
>()) {
278 if (parentElement
== &aCurrentBlock
) {
281 if (parentElement
== aAncestorLimiter
) {
282 NS_WARNING("Reached editing host while climbing up the DOM tree");
285 nextContent
= parentElement
->GetNextSibling();
289 if (!parentElement
->GetParentElement()) {
290 NS_WARNING("Reached orphan node while climbing up the DOM tree");
294 MOZ_ASSERT(nextContent
);
297 // We have a next content. If it's a block, return it.
298 if (HTMLEditUtils::IsBlockElement(*nextContent
)) {
301 if (HTMLEditUtils::IsContainerNode(*nextContent
)) {
302 // Else if it's a container, get deep leftmost child
303 if (nsIContent
* child
= HTMLEditUtils::GetFirstLeafChild(
304 *nextContent
, ChildBlockBoundary::Ignore
)) {
308 // Else return the next content itself.
313 * Similar to the above method, but take a DOM point to specify scan start
316 template <typename PT
, typename CT
>
317 static nsIContent
* GetNextLeafContentOrNextBlockElement(
318 const EditorDOMPointBase
<PT
, CT
>& aStartPoint
,
319 const nsIContent
& aCurrentBlock
,
320 const Element
* aAncestorLimiter
= nullptr) {
321 MOZ_ASSERT(aStartPoint
.IsSet());
323 if (!aStartPoint
.IsInContentNode()) {
326 if (aStartPoint
.IsInTextNode()) {
327 return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
328 *aStartPoint
.ContainerAsText(), aCurrentBlock
, aAncestorLimiter
);
330 if (!HTMLEditUtils::IsContainerNode(*aStartPoint
.ContainerAsContent())) {
331 return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
332 *aStartPoint
.ContainerAsContent(), aCurrentBlock
, aAncestorLimiter
);
335 nsCOMPtr
<nsIContent
> nextContent
= aStartPoint
.GetChild();
337 if (aStartPoint
.GetContainer() == &aCurrentBlock
) {
338 // We are at end of the block.
342 // We are at end of non-block container
343 return HTMLEditUtils::GetNextLeafContentOrNextBlockElement(
344 *aStartPoint
.ContainerAsContent(), aCurrentBlock
, aAncestorLimiter
);
347 // We have a next node. If it's a block, return it.
348 if (HTMLEditUtils::IsBlockElement(*nextContent
)) {
351 if (HTMLEditUtils::IsContainerNode(*nextContent
)) {
352 // else if it's a container, get deep leftmost child
353 if (nsIContent
* child
= HTMLEditUtils::GetFirstLeafChild(
354 *nextContent
, ChildBlockBoundary::Ignore
)) {
358 // Else return the node itself
363 * GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf
364 * content or previous block element of aStartContent inside
366 * Note that the result may be a contet outside aCurrentBlock if
367 * aStartContent equals aCurrentBlock.
369 * @param aStartContent The start content to scan previous content.
370 * @param aCurrentBlock Must be ancestor of aStartContent. Dispite
371 * the name, inline content is allowed if
372 * aStartContent is in an inline editing host.
373 * @param aAncestorLimiter Optional, setting this guarantees the
374 * result is in aAncestorLimiter unless
375 * aStartContent is not a descendant of this.
377 static nsIContent
* GetPreviousLeafContentOrPreviousBlockElement(
378 const nsIContent
& aStartContent
, const nsIContent
& aCurrentBlock
,
379 const Element
* aAncestorLimiter
= nullptr) {
380 if (&aStartContent
== aAncestorLimiter
) {
384 nsIContent
* previousContent
= aStartContent
.GetPreviousSibling();
385 if (!previousContent
) {
386 if (!aStartContent
.GetParentElement()) {
387 NS_WARNING("Reached orphan node while climbing up the DOM tree");
390 for (Element
* parentElement
: aStartContent
.AncestorsOfType
<Element
>()) {
391 if (parentElement
== &aCurrentBlock
) {
394 if (parentElement
== aAncestorLimiter
) {
395 NS_WARNING("Reached editing host while climbing up the DOM tree");
398 previousContent
= parentElement
->GetPreviousSibling();
399 if (previousContent
) {
402 if (!parentElement
->GetParentElement()) {
403 NS_WARNING("Reached orphan node while climbing up the DOM tree");
407 MOZ_ASSERT(previousContent
);
410 // We have a next content. If it's a block, return it.
411 if (HTMLEditUtils::IsBlockElement(*previousContent
)) {
412 return previousContent
;
414 if (HTMLEditUtils::IsContainerNode(*previousContent
)) {
415 // Else if it's a container, get deep rightmost child
416 if (nsIContent
* child
= HTMLEditUtils::GetLastLeafChild(
417 *previousContent
, ChildBlockBoundary::Ignore
)) {
421 // Else return the next content itself.
422 return previousContent
;
426 * Similar to the above method, but take a DOM point to specify scan start
429 template <typename PT
, typename CT
>
430 static nsIContent
* GetPreviousLeafContentOrPreviousBlockElement(
431 const EditorDOMPointBase
<PT
, CT
>& aStartPoint
,
432 const nsIContent
& aCurrentBlock
,
433 const Element
* aAncestorLimiter
= nullptr) {
434 MOZ_ASSERT(aStartPoint
.IsSet());
436 if (!aStartPoint
.IsInContentNode()) {
439 if (aStartPoint
.IsInTextNode()) {
440 return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
441 *aStartPoint
.ContainerAsText(), aCurrentBlock
, aAncestorLimiter
);
443 if (!HTMLEditUtils::IsContainerNode(*aStartPoint
.ContainerAsContent())) {
444 return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
445 *aStartPoint
.ContainerAsContent(), aCurrentBlock
, aAncestorLimiter
);
448 if (aStartPoint
.IsStartOfContainer()) {
449 if (aStartPoint
.GetContainer() == &aCurrentBlock
) {
450 // We are at start of the block.
454 // We are at start of non-block container
455 return HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElement(
456 *aStartPoint
.ContainerAsContent(), aCurrentBlock
, aAncestorLimiter
);
459 nsCOMPtr
<nsIContent
> previousContent
=
460 aStartPoint
.GetPreviousSiblingOfChild();
461 if (NS_WARN_IF(!previousContent
)) {
465 // We have a prior node. If it's a block, return it.
466 if (HTMLEditUtils::IsBlockElement(*previousContent
)) {
467 return previousContent
;
469 if (HTMLEditUtils::IsContainerNode(*previousContent
)) {
470 // Else if it's a container, get deep rightmost child
471 if (nsIContent
* child
= HTMLEditUtils::GetLastLeafChild(
472 *previousContent
, ChildBlockBoundary::Ignore
)) {
476 // Else return the node itself
477 return previousContent
;
481 * Get previous/next editable point from start or end of aContent.
483 enum class InvisibleWhiteSpaces
{
484 Ignore
, // Ignore invisible white-spaces, i.e., don't return middle of
486 Preserve
, // Preserve invisible white-spaces, i.e., result may be start or
487 // end of a text node even if it begins or ends with invisible
490 enum class TableBoundary
{
491 Ignore
, // May cross any table element boundary.
492 NoCrossTableElement
, // Won't cross `<table>` element boundary.
493 NoCrossAnyTableElement
, // Won't cross any table element boundary.
495 template <typename EditorDOMPointType
>
496 static EditorDOMPointType
GetPreviousEditablePoint(
497 nsIContent
& aContent
, const Element
* aAncestorLimiter
,
498 InvisibleWhiteSpaces aInvisibleWhiteSpaces
,
499 TableBoundary aHowToTreatTableBoundary
);
500 template <typename EditorDOMPointType
>
501 static EditorDOMPointType
GetNextEditablePoint(
502 nsIContent
& aContent
, const Element
* aAncestorLimiter
,
503 InvisibleWhiteSpaces aInvisibleWhiteSpaces
,
504 TableBoundary aHowToTreatTableBoundary
);
507 * GetAncestorBlockElement() returns parent or nearest ancestor of aContent
508 * which is a block element. If aAncestorLimiter is not nullptr,
509 * this stops looking for the result when it meets the limiter.
511 static Element
* GetAncestorBlockElement(
512 const nsIContent
& aContent
, const nsINode
* aAncestorLimiter
= nullptr) {
514 !aAncestorLimiter
|| aContent
.IsInclusiveDescendantOf(aAncestorLimiter
),
515 "aContent isn't in aAncestorLimiter");
517 // The caller has already reached the limiter.
518 if (&aContent
== aAncestorLimiter
) {
522 for (Element
* element
: aContent
.AncestorsOfType
<Element
>()) {
523 if (HTMLEditUtils::IsBlockElement(*element
)) {
526 // Now, we have reached the limiter, there is no block in its ancestors.
527 if (element
== aAncestorLimiter
) {
536 * GetInclusiveAncestorBlockElement() returns aContent itself, or parent or
537 * nearest ancestor of aContent which is a block element. If aAncestorLimiter
538 * is not nullptr, this stops looking for the result when it meets the
541 static Element
* GetInclusiveAncestorBlockElement(
542 const nsIContent
& aContent
, const nsINode
* aAncestorLimiter
= nullptr) {
544 !aAncestorLimiter
|| aContent
.IsInclusiveDescendantOf(aAncestorLimiter
),
545 "aContent isn't in aAncestorLimiter");
547 if (!aContent
.IsContent()) {
551 if (HTMLEditUtils::IsBlockElement(aContent
)) {
552 return const_cast<Element
*>(aContent
.AsElement());
554 return GetAncestorBlockElement(aContent
, aAncestorLimiter
);
558 * GetInclusiveAncestorBlockElementExceptHRElement() returns inclusive
559 * ancestor block element except `<hr>` element.
561 static Element
* GetInclusiveAncestorBlockElementExceptHRElement(
562 const nsIContent
& aContent
, const nsINode
* aAncestorLimiter
= nullptr) {
563 Element
* blockElement
=
564 GetInclusiveAncestorBlockElement(aContent
, aAncestorLimiter
);
565 if (!blockElement
|| !blockElement
->IsHTMLElement(nsGkAtoms::hr
)) {
568 if (!blockElement
->GetParentElement()) {
571 return GetInclusiveAncestorBlockElementExceptHRElement(
572 *blockElement
->GetParentElement(), aAncestorLimiter
);
576 * GetInclusiveAncestorEditableBlockElementOrInlineEditingHost() returns
577 * inclusive block ancestor element of aContent. If aContent is in inline
578 * editing host, returns the editing host instead.
580 static Element
* GetInclusiveAncestorEditableBlockElementOrInlineEditingHost(
581 nsIContent
& aContent
);
583 * GetClosestAncestorTableElement() returns the nearest inclusive ancestor
584 * <table> element of aContent.
586 static Element
* GetClosestAncestorTableElement(const nsIContent
& aContent
) {
587 // TODO: the method name and its documentation clash with the
588 // implementation. Split this method into
589 // `GetClosestAncestorTableElement` and
590 // `GetClosestInclusiveAncestorTableElement`.
591 if (!aContent
.GetParent()) {
594 for (Element
* element
: aContent
.InclusiveAncestorsOfType
<Element
>()) {
595 if (HTMLEditUtils::IsTable(element
)) {
602 static Element
* GetClosestAncestorAnyListElement(const nsIContent
& aContent
);
605 * GetMostDistantAnscestorEditableEmptyInlineElement() returns most distant
606 * ancestor which only has aEmptyContent or its ancestor, editable and
609 static Element
* GetMostDistantAnscestorEditableEmptyInlineElement(
610 const nsIContent
& aEmptyContent
, const Element
* aEditingHost
= nullptr) {
611 nsIContent
* lastEmptyContent
= const_cast<nsIContent
*>(&aEmptyContent
);
612 for (Element
* element
= aEmptyContent
.GetParentElement();
613 element
&& element
!= aEditingHost
&&
614 HTMLEditUtils::IsInlineElement(*element
) &&
615 HTMLEditUtils::IsSimplyEditableNode(*element
);
616 element
= element
->GetParentElement()) {
617 if (element
->GetChildCount() > 1) {
618 for (const nsIContent
* child
= element
->GetFirstChild(); child
;
619 child
= child
->GetNextSibling()) {
620 if (child
== lastEmptyContent
|| child
->IsComment()) {
623 return lastEmptyContent
!= &aEmptyContent
624 ? lastEmptyContent
->AsElement()
628 lastEmptyContent
= element
;
630 return lastEmptyContent
!= &aEmptyContent
? lastEmptyContent
->AsElement()
635 * GetElementIfOnlyOneSelected() returns an element if aRange selects only
636 * the element node (and its descendants).
638 static Element
* GetElementIfOnlyOneSelected(
639 const dom::AbstractRange
& aRange
) {
640 if (!aRange
.IsPositioned()) {
643 const RangeBoundary
& start
= aRange
.StartRef();
644 const RangeBoundary
& end
= aRange
.EndRef();
645 if (NS_WARN_IF(!start
.IsSetAndValid()) ||
646 NS_WARN_IF(!end
.IsSetAndValid()) ||
647 start
.Container() != end
.Container()) {
650 nsIContent
* childAtStart
= start
.GetChildAtOffset();
651 if (!childAtStart
|| !childAtStart
->IsElement()) {
654 // If start child is not the last sibling and only if end child is its
655 // next sibling, the start child is selected.
656 if (childAtStart
->GetNextSibling()) {
657 return childAtStart
->GetNextSibling() == end
.GetChildAtOffset()
658 ? childAtStart
->AsElement()
661 // If start child is the last sibling and only if no child at the end,
662 // the start child is selected.
663 return !end
.GetChildAtOffset() ? childAtStart
->AsElement() : nullptr;
666 static Element
* GetTableCellElementIfOnlyOneSelected(
667 const dom::AbstractRange
& aRange
) {
668 Element
* element
= HTMLEditUtils::GetElementIfOnlyOneSelected(aRange
);
669 return element
&& HTMLEditUtils::IsTableCell(element
) ? element
: nullptr;
672 static EditAction
GetEditActionForInsert(const nsAtom
& aTagName
);
673 static EditAction
GetEditActionForRemoveList(const nsAtom
& aTagName
);
674 static EditAction
GetEditActionForInsert(const Element
& aElement
);
675 static EditAction
GetEditActionForFormatText(const nsAtom
& aProperty
,
676 const nsAtom
* aAttribute
,
678 static EditAction
GetEditActionForAlignment(const nsAString
& aAlignType
);
681 * GetPreviousCharOffsetExceptASCIIWhiteSpace() returns offset of previous
682 * character which is not ASCII white-space characters.
684 static Maybe
<uint32_t> GetPreviousCharOffsetExceptASCIIWhiteSpaces(
685 const EditorDOMPointInText
& aPoint
) {
686 MOZ_ASSERT(aPoint
.IsSetAndValid());
687 return GetPreviousCharOffsetExceptASCIIWhiteSpaces(
688 *aPoint
.ContainerAsText(), aPoint
.Offset());
690 static Maybe
<uint32_t> GetPreviousCharOffsetExceptASCIIWhiteSpaces(
691 const dom::Text
& aTextNode
, uint32_t aOffset
) {
692 const nsTextFragment
& textFragment
= aTextNode
.TextFragment();
693 MOZ_ASSERT(aOffset
<= textFragment
.GetLength());
694 for (uint32_t i
= aOffset
; i
; i
--) {
695 if (!nsCRT::IsAsciiSpace(textFragment
.CharAt(i
- 1))) {
703 * GetNextCharOffsetExceptASCIIWhiteSpace() returns offset of next character
704 * which is not ASCII white-space characters.
706 static Maybe
<uint32_t> GetNextCharOffsetExceptASCIIWhiteSpaces(
707 const EditorDOMPointInText
& aPoint
) {
708 MOZ_ASSERT(aPoint
.IsSetAndValid());
709 return GetNextCharOffsetExceptASCIIWhiteSpaces(*aPoint
.ContainerAsText(),
712 static Maybe
<uint32_t> GetNextCharOffsetExceptASCIIWhiteSpaces(
713 const dom::Text
& aTextNode
, uint32_t aOffset
) {
714 const nsTextFragment
& textFragment
= aTextNode
.TextFragment();
715 MOZ_ASSERT(aOffset
<= textFragment
.GetLength());
716 for (uint32_t i
= aOffset
+ 1; i
< textFragment
.GetLength(); i
++) {
717 if (!nsCRT::IsAsciiSpace(textFragment
.CharAt(i
))) {
725 * GetPreviousCharOffsetExceptWhiteSpaces() returns first offset where
726 * the character is neither an ASCII white-space nor an NBSP before aPoint.
728 static Maybe
<uint32_t> GetPreviousCharOffsetExceptWhiteSpaces(
729 const EditorDOMPointInText
& aPoint
) {
730 MOZ_ASSERT(aPoint
.IsSetAndValid());
731 return GetPreviousCharOffsetExceptWhiteSpaces(*aPoint
.ContainerAsText(),
734 static Maybe
<uint32_t> GetPreviousCharOffsetExceptWhiteSpaces(
735 const dom::Text
& aTextNode
, uint32_t aOffset
) {
739 const nsTextFragment
& textFragment
= aTextNode
.TextFragment();
740 MOZ_ASSERT(aOffset
<= textFragment
.GetLength());
741 for (uint32_t i
= aOffset
; i
; i
--) {
742 char16_t ch
= textFragment
.CharAt(i
- 1);
743 if (!nsCRT::IsAsciiSpace(ch
) && ch
!= kNBSP
) {
751 * GetInclusiveNextCharOffsetExceptWhiteSpaces() returns first offset where
752 * the character is neither an ASCII white-space nor an NBSP at aPoint or
755 static Maybe
<uint32_t> GetInclusiveNextCharOffsetExceptWhiteSpaces(
756 const EditorDOMPointInText
& aPoint
) {
757 MOZ_ASSERT(aPoint
.IsSetAndValid());
758 return GetInclusiveNextCharOffsetExceptWhiteSpaces(
759 *aPoint
.ContainerAsText(), aPoint
.Offset());
761 static Maybe
<uint32_t> GetInclusiveNextCharOffsetExceptWhiteSpaces(
762 const dom::Text
& aTextNode
, uint32_t aOffset
) {
763 const nsTextFragment
& textFragment
= aTextNode
.TextFragment();
764 MOZ_ASSERT(aOffset
<= textFragment
.GetLength());
765 for (uint32_t i
= aOffset
; i
< textFragment
.GetLength(); i
++) {
766 char16_t ch
= textFragment
.CharAt(i
);
767 if (!nsCRT::IsAsciiSpace(ch
) && ch
!= kNBSP
) {
775 * GetFirstASCIIWhiteSpaceOffsetCollapsedWith() returns first ASCII
776 * white-space offset which is collapsed with a white-space at the given
777 * position. I.e., the character at the position must be an ASCII
780 static uint32_t GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
781 const EditorDOMPointInText
& aPoint
) {
782 MOZ_ASSERT(aPoint
.IsSetAndValid());
783 MOZ_ASSERT(!aPoint
.IsEndOfContainer());
784 MOZ_ASSERT(aPoint
.IsCharASCIISpace());
785 return GetFirstASCIIWhiteSpaceOffsetCollapsedWith(*aPoint
.ContainerAsText(),
788 static uint32_t GetFirstASCIIWhiteSpaceOffsetCollapsedWith(
789 const dom::Text
& aTextNode
, uint32_t aOffset
) {
790 MOZ_ASSERT(aOffset
< aTextNode
.TextLength());
791 MOZ_ASSERT(nsCRT::IsAsciiSpace(aTextNode
.TextFragment().CharAt(aOffset
)));
795 Maybe
<uint32_t> previousVisibleCharOffset
=
796 GetPreviousCharOffsetExceptASCIIWhiteSpaces(aTextNode
, aOffset
);
797 return previousVisibleCharOffset
.isSome()
798 ? previousVisibleCharOffset
.value() + 1
803 static bool CanNodeContain(nsHTMLTag aParentTagId
, nsHTMLTag aChildTagId
);
804 static bool IsContainerNode(nsHTMLTag aTagId
);
806 static bool CanCrossContentBoundary(nsIContent
& aContent
,
807 TableBoundary aHowToTreatTableBoundary
) {
808 const bool cannotCrossBoundary
=
809 (aHowToTreatTableBoundary
== TableBoundary::NoCrossAnyTableElement
&&
810 HTMLEditUtils::IsAnyTableElement(&aContent
)) ||
811 (aHowToTreatTableBoundary
== TableBoundary::NoCrossTableElement
&&
812 aContent
.IsHTMLElement(nsGkAtoms::table
));
813 return !cannotCrossBoundary
;
818 * DefinitionListItemScanner() scans given `<dl>` element's children.
819 * Then, you can check whether `<dt>` and/or `<dd>` elements are in it.
821 class MOZ_STACK_CLASS DefinitionListItemScanner final
{
823 DefinitionListItemScanner() = delete;
824 explicit DefinitionListItemScanner(dom::Element
& aDLElement
) {
825 MOZ_ASSERT(aDLElement
.IsHTMLElement(nsGkAtoms::dl
));
826 for (nsIContent
* child
= aDLElement
.GetFirstChild(); child
;
827 child
= child
->GetNextSibling()) {
828 if (child
->IsHTMLElement(nsGkAtoms::dt
)) {
835 if (child
->IsHTMLElement(nsGkAtoms::dd
)) {
845 bool DTElementFound() const { return mDTFound
; }
846 bool DDElementFound() const { return mDDFound
; }
849 bool mDTFound
= false;
850 bool mDDFound
= false;
853 } // namespace mozilla
855 #endif // #ifndef HTMLEditUtils_h