/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef HTMLEditUtils_h #define HTMLEditUtils_h #include "mozilla/Attributes.h" #include "mozilla/EditorDOMPoint.h" #include "mozilla/EditorUtils.h" #include "mozilla/EnumSet.h" #include "mozilla/Maybe.h" #include "mozilla/dom/AbstractRange.h" #include "mozilla/dom/AncestorIterator.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Selection.h" #include "mozilla/dom/Text.h" #include "nsContentUtils.h" #include "nsCRT.h" #include "nsGkAtoms.h" #include "nsHTMLTags.h" #include "nsTArray.h" class nsAtom; namespace mozilla { enum class EditAction; class HTMLEditUtils final { using Element = dom::Element; using Selection = dom::Selection; public: static const char16_t kSpace = 0x0020; static const char16_t kNBSP = 0x00A0; /** * IsSimplyEditableNode() returns true when aNode is simply editable. * This does NOT means that aNode can be removed from current parent nor * aNode's data is editable. */ static bool IsSimplyEditableNode(const nsINode& aNode) { return aNode.IsEditable(); } /* * IsRemovalNode() returns true when parent of aContent is editable even * if aContent isn't editable. */ static bool IsRemovableNode(const nsIContent& aContent) { return aContent.GetParentNode() && aContent.GetParentNode()->IsEditable(); } /** * IsRemovableFromParentNode() returns true when aContent is editable, has a * parent node and the parent node is also editable. */ static bool IsRemovableFromParentNode(const nsIContent& aContent) { return aContent.IsEditable() && aContent.GetParentNode() && aContent.GetParentNode()->IsEditable(); } /** * CanContentsBeJoined() returns true if aLeftContent and aRightContent can be * joined. At least, Node.nodeName must be same when this returns true. */ enum class StyleDifference { // Ignore style information so that callers may join different styled // contents. Ignore, // Compare style information when the contents are any elements. CompareIfElements, // Compare style information only when the contents are elements. CompareIfSpanElements, }; static bool CanContentsBeJoined(const nsIContent& aLeftContent, const nsIContent& aRightContent, StyleDifference aStyleDifference); /** * IsBlockElement() returns true if aContent is an element and it should * be treated as a block. (This does not refer style information.) */ static bool IsBlockElement(const nsIContent& aContent); /** * IsInlineElement() returns true if aElement is an element node but * shouldn't be treated as a block or aElement is not an element. * XXX This looks odd. For example, how about a comment node? */ static bool IsInlineElement(const nsIContent& aContent) { return !IsBlockElement(aContent); } static bool IsInlineStyle(nsINode* aNode); /** * IsRemovableInlineStyleElement() returns true if aElement is an inline * element and can be removed or split to in order to modifying inline * styles. */ static bool IsRemovableInlineStyleElement(dom::Element& aElement); static bool IsFormatNode(nsINode* aNode); static bool IsNodeThatCanOutdent(nsINode* aNode); static bool IsHeader(nsINode& aNode); static bool IsListItem(nsINode* aNode); static bool IsTable(nsINode* aNode); static bool IsTableRow(nsINode* aNode); static bool IsAnyTableElement(nsINode* aNode); static bool IsAnyTableElementButNotTable(nsINode* aNode); static bool IsTableCell(nsINode* node); static bool IsTableCellOrCaption(nsINode& aNode); static bool IsAnyListElement(nsINode* aNode); static bool IsPre(nsINode* aNode); static bool IsImage(nsINode* aNode); static bool IsLink(nsINode* aNode); static bool IsNamedAnchor(nsINode* aNode); static bool IsMozDiv(nsINode* aNode); static bool IsMailCite(nsINode* aNode); static bool IsFormWidget(nsINode* aNode); static bool SupportsAlignAttr(nsINode& aNode); static bool CanNodeContain(const nsINode& aParent, const nsIContent& aChild) { switch (aParent.NodeType()) { case nsINode::ELEMENT_NODE: case nsINode::DOCUMENT_FRAGMENT_NODE: return HTMLEditUtils::CanNodeContain(*aParent.NodeInfo()->NameAtom(), aChild); } return false; } static bool CanNodeContain(const nsINode& aParent, nsAtom& aChildNodeName) { switch (aParent.NodeType()) { case nsINode::ELEMENT_NODE: case nsINode::DOCUMENT_FRAGMENT_NODE: return HTMLEditUtils::CanNodeContain(*aParent.NodeInfo()->NameAtom(), aChildNodeName); } return false; } static bool CanNodeContain(nsAtom& aParentNodeName, const nsIContent& aChild) { switch (aChild.NodeType()) { case nsINode::TEXT_NODE: case nsINode::ELEMENT_NODE: case nsINode::DOCUMENT_FRAGMENT_NODE: return HTMLEditUtils::CanNodeContain(aParentNodeName, *aChild.NodeInfo()->NameAtom()); } return false; } // XXX Only this overload does not check the node type. Therefore, only this // treat Document, Comment, CDATASection, etc. static bool CanNodeContain(nsAtom& aParentNodeName, nsAtom& aChildNodeName) { nsHTMLTag childTagEnum; // XXX Should this handle #cdata-section too? if (&aChildNodeName == nsGkAtoms::textTagName) { childTagEnum = eHTMLTag_text; } else { childTagEnum = nsHTMLTags::AtomTagToId(&aChildNodeName); } nsHTMLTag parentTagEnum = nsHTMLTags::AtomTagToId(&aParentNodeName); return HTMLEditUtils::CanNodeContain(parentTagEnum, childTagEnum); } /** * CanElementContainParagraph() returns true if aElement can have a

* element as its child or its descendant. */ static bool CanElementContainParagraph(const Element& aElement) { if (HTMLEditUtils::CanNodeContain(aElement, *nsGkAtoms::p)) { return true; } // Even if the element cannot have a

element as a child, it can contain //

element as a descendant if it's one of the following elements. if (aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul, nsGkAtoms::dl, nsGkAtoms::table, nsGkAtoms::thead, nsGkAtoms::tbody, nsGkAtoms::tfoot, nsGkAtoms::tr)) { return true; } // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it // for now. return false; } /** * IsContainerNode() returns true if aContent is a container node. */ static bool IsContainerNode(const nsIContent& aContent) { nsHTMLTag tagEnum; // XXX Should this handle #cdata-section too? if (aContent.IsText()) { tagEnum = eHTMLTag_text; } else { // XXX Why don't we use nsHTMLTags::AtomTagToId? Are there some // difference? tagEnum = nsHTMLTags::StringTagToId(aContent.NodeName()); } return HTMLEditUtils::IsContainerNode(tagEnum); } /** * IsSplittableNode() returns true if aContent can split. */ static bool IsSplittableNode(const nsIContent& aContent) { if (aContent.IsElement()) { // XXX Perhaps, instead of using container, we should have "splittable" // information in the DB. E.g., `