Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / editor / libeditor / HTMLEditUtils.h
blobd2a5b662141a52b391bc0bfe1bae5e333fc19719
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"
16 #include "nsCRT.h"
17 #include "nsGkAtoms.h"
18 #include "nsHTMLTags.h"
20 class nsAtom;
22 namespace mozilla {
24 enum class EditAction;
26 class HTMLEditUtils final {
27 using Element = dom::Element;
28 using Selection = dom::Selection;
30 public:
31 static const char16_t kSpace = 0x0020;
32 static const char16_t kNBSP = 0x00A0;
34 /**
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();
43 /**
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();
52 /**
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
58 // contents.
59 Ignore,
60 // Compare style information when the contents are any elements.
61 CompareIfElements,
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);
69 /**
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);
74 /**
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);
84 /**
85 * IsRemovableInlineStyleElement() returns true if aElement is an inline
86 * element and can be removed or split to in order to modifying inline
87 * styles.
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(),
115 aChild);
117 return false;
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(),
125 aChildNodeName);
127 return false;
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());
139 return false;
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;
149 } else {
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)) {
163 return true;
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)) {
172 return true;
175 // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it
176 // for now.
177 return false;
181 * IsContainerNode() returns true if aContent is a container node.
183 static bool IsContainerNode(const nsIContent& aContent) {
184 nsHTMLTag tagEnum;
185 // XXX Should this handle #cdata-section too?
186 if (aContent.IsText()) {
187 tagEnum = eHTMLTag_text;
188 } else {
189 // XXX Why don't we use nsHTMLTags::AtomTagToId? Are there some
190 // difference?
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
207 * block as a leaf.
209 enum class ChildBlockBoundary {
210 // Even if there is a child block, keep scanning a leaf content in it.
211 Ignore,
212 // If there is a child block, return it.
213 TreatAsLeaf,
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)) {
221 return content;
223 if (!content->HasChildren()) {
224 return content;
227 return nullptr;
231 * GetFirstLeafChild() returns leftmost leaf content in aNode. It depends on
232 * aChildBlockBoundary whether this scans into a block child or treat
233 * block as a leaf.
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)) {
241 return content;
243 if (!content->HasChildren()) {
244 return content;
247 return nullptr;
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) {
268 return nullptr;
271 nsIContent* nextContent = aStartContent.GetNextSibling();
272 if (!nextContent) {
273 if (!aStartContent.GetParentElement()) {
274 NS_WARNING("Reached orphan node while climbing up the DOM tree");
275 return nullptr;
277 for (Element* parentElement : aStartContent.AncestorsOfType<Element>()) {
278 if (parentElement == &aCurrentBlock) {
279 return nullptr;
281 if (parentElement == aAncestorLimiter) {
282 NS_WARNING("Reached editing host while climbing up the DOM tree");
283 return nullptr;
285 nextContent = parentElement->GetNextSibling();
286 if (nextContent) {
287 break;
289 if (!parentElement->GetParentElement()) {
290 NS_WARNING("Reached orphan node while climbing up the DOM tree");
291 return nullptr;
294 MOZ_ASSERT(nextContent);
297 // We have a next content. If it's a block, return it.
298 if (HTMLEditUtils::IsBlockElement(*nextContent)) {
299 return 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)) {
305 return child;
308 // Else return the next content itself.
309 return nextContent;
313 * Similar to the above method, but take a DOM point to specify scan start
314 * point.
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()) {
324 return nullptr;
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();
336 if (!nextContent) {
337 if (aStartPoint.GetContainer() == &aCurrentBlock) {
338 // We are at end of the block.
339 return nullptr;
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)) {
349 return 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)) {
355 return child;
358 // Else return the node itself
359 return nextContent;
363 * GetPreviousLeafContentOrPreviousBlockElement() returns previous leaf
364 * content or previous block element of aStartContent inside
365 * aAncestorLimiter.
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) {
381 return nullptr;
384 nsIContent* previousContent = aStartContent.GetPreviousSibling();
385 if (!previousContent) {
386 if (!aStartContent.GetParentElement()) {
387 NS_WARNING("Reached orphan node while climbing up the DOM tree");
388 return nullptr;
390 for (Element* parentElement : aStartContent.AncestorsOfType<Element>()) {
391 if (parentElement == &aCurrentBlock) {
392 return nullptr;
394 if (parentElement == aAncestorLimiter) {
395 NS_WARNING("Reached editing host while climbing up the DOM tree");
396 return nullptr;
398 previousContent = parentElement->GetPreviousSibling();
399 if (previousContent) {
400 break;
402 if (!parentElement->GetParentElement()) {
403 NS_WARNING("Reached orphan node while climbing up the DOM tree");
404 return nullptr;
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)) {
418 return child;
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
427 * point.
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()) {
437 return nullptr;
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.
451 return nullptr;
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)) {
462 return nullptr;
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)) {
473 return child;
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
485 // them.
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
488 // white-spaces.
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) {
513 MOZ_ASSERT(
514 !aAncestorLimiter || aContent.IsInclusiveDescendantOf(aAncestorLimiter),
515 "aContent isn't in aAncestorLimiter");
517 // The caller has already reached the limiter.
518 if (&aContent == aAncestorLimiter) {
519 return nullptr;
522 for (Element* element : aContent.AncestorsOfType<Element>()) {
523 if (HTMLEditUtils::IsBlockElement(*element)) {
524 return element;
526 // Now, we have reached the limiter, there is no block in its ancestors.
527 if (element == aAncestorLimiter) {
528 return nullptr;
532 return nullptr;
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
539 * limiter.
541 static Element* GetInclusiveAncestorBlockElement(
542 const nsIContent& aContent, const nsINode* aAncestorLimiter = nullptr) {
543 MOZ_ASSERT(
544 !aAncestorLimiter || aContent.IsInclusiveDescendantOf(aAncestorLimiter),
545 "aContent isn't in aAncestorLimiter");
547 if (!aContent.IsContent()) {
548 return nullptr;
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)) {
566 return blockElement;
568 if (!blockElement->GetParentElement()) {
569 return nullptr;
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()) {
592 return nullptr;
594 for (Element* element : aContent.InclusiveAncestorsOfType<Element>()) {
595 if (HTMLEditUtils::IsTable(element)) {
596 return element;
599 return nullptr;
602 static Element* GetClosestAncestorAnyListElement(const nsIContent& aContent);
605 * GetMostDistantAnscestorEditableEmptyInlineElement() returns most distant
606 * ancestor which only has aEmptyContent or its ancestor, editable and
607 * inline element.
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()) {
621 continue;
623 return lastEmptyContent != &aEmptyContent
624 ? lastEmptyContent->AsElement()
625 : nullptr;
628 lastEmptyContent = element;
630 return lastEmptyContent != &aEmptyContent ? lastEmptyContent->AsElement()
631 : nullptr;
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()) {
641 return nullptr;
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()) {
648 return nullptr;
650 nsIContent* childAtStart = start.GetChildAtOffset();
651 if (!childAtStart || !childAtStart->IsElement()) {
652 return nullptr;
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()
659 : nullptr;
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,
677 bool aToSetStyle);
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))) {
696 return Some(i - 1);
699 return Nothing();
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(),
710 aPoint.Offset());
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))) {
718 return Some(i);
721 return Nothing();
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(),
732 aPoint.Offset());
734 static Maybe<uint32_t> GetPreviousCharOffsetExceptWhiteSpaces(
735 const dom::Text& aTextNode, uint32_t aOffset) {
736 if (!aOffset) {
737 return Nothing();
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) {
744 return Some(i - 1);
747 return Nothing();
751 * GetInclusiveNextCharOffsetExceptWhiteSpaces() returns first offset where
752 * the character is neither an ASCII white-space nor an NBSP at aPoint or
753 * after it.
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) {
768 return Some(i);
771 return Nothing();
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
778 * white-space.
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(),
786 aPoint.Offset());
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)));
792 if (!aOffset) {
793 return 0;
795 Maybe<uint32_t> previousVisibleCharOffset =
796 GetPreviousCharOffsetExceptASCIIWhiteSpaces(aTextNode, aOffset);
797 return previousVisibleCharOffset.isSome()
798 ? previousVisibleCharOffset.value() + 1
799 : 0;
802 private:
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 {
822 public:
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)) {
829 mDTFound = true;
830 if (mDDFound) {
831 break;
833 continue;
835 if (child->IsHTMLElement(nsGkAtoms::dd)) {
836 mDDFound = true;
837 if (mDTFound) {
838 break;
840 continue;
845 bool DTElementFound() const { return mDTFound; }
846 bool DDElementFound() const { return mDDFound; }
848 private:
849 bool mDTFound = false;
850 bool mDDFound = false;
853 } // namespace mozilla
855 #endif // #ifndef HTMLEditUtils_h