/* -*- 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/. */ #include "HTMLEditUtils.h" #include "AutoClonedRangeArray.h" // for AutoClonedRangeArray #include "CSSEditUtils.h" // for CSSEditUtils #include "EditAction.h" // for EditAction #include "EditorBase.h" // for EditorBase, EditorType #include "EditorDOMPoint.h" // for EditorDOMPoint, etc. #include "EditorForwards.h" // for CollectChildrenOptions #include "EditorUtils.h" // for EditorUtils #include "HTMLEditHelpers.h" // for EditorInlineStyle #include "WSRunScanner.h" // for WSRunScanner #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc. #include "mozilla/Attributes.h" #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_ #include "mozilla/RangeUtils.h" // for RangeUtils #include "mozilla/dom/CharacterDataBuffer.h" // for CharacterDataBuffer #include "mozilla/dom/DocumentInlines.h" // for GetBodyElement() #include "mozilla/dom/Element.h" // for Element, nsINode #include "mozilla/dom/ElementInlines.h" // for IsContentEditablePlainTextOnly() #include "mozilla/dom/HTMLAnchorElement.h" #include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/dom/HTMLInputElement.h" #include "mozilla/ServoCSSParser.h" // for ServoCSSParser #include "mozilla/dom/StaticRange.h" #include "mozilla/dom/Text.h" // for Text #include "nsAString.h" // for nsAString::IsEmpty #include "nsAtom.h" // for nsAtom #include "nsAttrValue.h" // nsAttrValue #include "nsCaseTreatment.h" #include "nsCOMPtr.h" // for nsCOMPtr, operator==, etc. #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle #include "nsDebug.h" // for NS_ASSERTION, etc. #include "nsElementTable.h" // for nsHTMLElement #include "nsError.h" // for NS_SUCCEEDED #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::a, etc. #include "nsHTMLTags.h" #include "nsIContentInlines.h" // for nsIContent::IsInDesignMode(), etc. #include "nsIObjectLoadingContent.h" #include "nsLiteralString.h" // for NS_LITERAL_STRING #include "nsNameSpaceManager.h" // for kNameSpaceID_None #include "nsPrintfCString.h" // nsPringfCString #include "nsString.h" // for nsAutoString #include "nsStyledElement.h" #include "nsStyleStruct.h" // for StyleDisplay #include "nsStyleUtil.h" // for nsStyleUtil #include "nsTextFrame.h" // for nsTextFrame namespace mozilla { using namespace dom; using EditorType = EditorBase::EditorType; template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( const EditorDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( const EditorRawDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( const EditorDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetNextLeafContentOrNextBlockElementImpl( const EditorRawDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( const EditorDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( const EditorRawDOMPoint&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( const EditorDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template nsIContent* HTMLEditUtils::GetPreviousLeafContentOrPreviousBlockElementImpl( const EditorRawDOMPointInText&, StopAtBlockSibling, const LeafNodeOptions&, BlockInlineCheck, const Element*); template EditorDOMPoint HTMLEditUtils::GetPreviousEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, InvisibleWhiteSpaces aInvisibleWhiteSpaces, TableBoundary aHowToTreatTableBoundary); template EditorRawDOMPoint HTMLEditUtils::GetPreviousEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, InvisibleWhiteSpaces aInvisibleWhiteSpaces, TableBoundary aHowToTreatTableBoundary); template EditorDOMPoint HTMLEditUtils::GetNextEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, InvisibleWhiteSpaces aInvisibleWhiteSpaces, TableBoundary aHowToTreatTableBoundary); template EditorRawDOMPoint HTMLEditUtils::GetNextEditablePoint( nsIContent& aContent, const Element* aAncestorLimiter, InvisibleWhiteSpaces aInvisibleWhiteSpaces, TableBoundary aHowToTreatTableBoundary); template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible( const EditorDOMPoint& aPoint, const Element& aEditingHost); template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible( const EditorRawDOMPoint& aPoint, const Element& aEditingHost); template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible( const EditorDOMPointInText& aPoint, const Element& aEditingHost); template EditorDOMPoint HTMLEditUtils::LineRequiresPaddingLineBreakToBeVisible( const EditorRawDOMPointInText& aPoint, const Element& aEditingHost); template nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles( const EditorDOMPoint& aPoint, const Element& aEditingHost); template nsIContent* HTMLEditUtils::GetContentToPreserveInlineStyles( const EditorRawDOMPoint& aPoint, const Element& aEditingHost); template EditorDOMPoint HTMLEditUtils::GetBetterInsertionPointFor( const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert); template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor( const nsIContent& aContentToInsert, const EditorRawDOMPoint& aPointToInsert); template EditorDOMPoint HTMLEditUtils::GetBetterInsertionPointFor( const nsIContent& aContentToInsert, const EditorRawDOMPoint& aPointToInsert); template EditorRawDOMPoint HTMLEditUtils::GetBetterInsertionPointFor( const nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert); template EditorDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText( const EditorDOMPoint& aPoint); template EditorDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText( const EditorRawDOMPoint& aPoint); template EditorRawDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText( const EditorDOMPoint& aPoint); template EditorRawDOMPoint HTMLEditUtils::GetBetterCaretPositionToInsertText( const EditorRawDOMPoint& aPoint); template Result HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( const Element& aElement, const EditorDOMPoint& aCurrentPoint); template Result HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( const Element& aElement, const EditorDOMPoint& aCurrentPoint); template Result HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( const Element& aElement, const EditorRawDOMPoint& aCurrentPoint); template Result HTMLEditUtils::ComputePointToPutCaretInElementIfOutside( const Element& aElement, const EditorRawDOMPoint& aCurrentPoint); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorDOMPoint&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorDOMPoint&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorRawDOMPoint&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorRawDOMPoint&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorDOMPointInText&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorDOMPointInText&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorRawDOMPointInText&, const Element&); template Maybe HTMLEditUtils::GetLineBreakBeforeBlockBoundaryIfPointIsBetweenThem( const EditorRawDOMPointInText&, const Element&); template bool HTMLEditUtils::IsSameCSSColorValue(const nsAString& aColorA, const nsAString& aColorB); template bool HTMLEditUtils::IsSameCSSColorValue(const nsACString& aColorA, const nsACString& aColorB); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorDOMPoint& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorRawDOMPoint& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorDOMPointInText& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorRawDOMPointInText& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak(const EditorDOMPoint& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorRawDOMPoint& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorDOMPointInText& aPoint); template Maybe HTMLEditUtils::GetFollowingUnnecessaryLineBreak( const EditorRawDOMPointInText& aPoint); template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary( const EditorDOMPoint& aPoint, IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak); template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary( const EditorRawDOMPoint& aPoint, IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak); template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary( const EditorDOMPointInText& aPoint, IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak); template bool HTMLEditUtils::PointIsImmediatelyBeforeCurrentBlockBoundary( const EditorRawDOMPointInText& aPoint, IgnoreInvisibleLineBreak aIgnoreInvisibleLineBreak); template Maybe HTMLEditUtils::GetUnnecessaryLineBreak( const Element& aBlockElement, ScanLineBreak aScanLineBreak); template Maybe HTMLEditUtils::GetUnnecessaryLineBreak( const Element& aBlockElement, ScanLineBreak aScanLineBreak); bool HTMLEditUtils::ElementIsEditableRoot(const Element& aElement) { MOZ_ASSERT(!aElement.IsInNativeAnonymousSubtree()); if (NS_WARN_IF(!aElement.IsEditable()) || NS_WARN_IF(!aElement.IsInComposedDoc())) { return false; } return !aElement.GetParent() || // root element !aElement.GetParent()->IsEditable() || // editing host aElement.OwnerDoc()->GetBody() == &aElement; // the } bool HTMLEditUtils::CanContentsBeJoined(const nsIContent& aLeftContent, const nsIContent& aRightContent) { if (aLeftContent.NodeInfo()->NameAtom() != aRightContent.NodeInfo()->NameAtom()) { return false; } if (!aLeftContent.IsElement()) { return true; // can join text nodes, etc } MOZ_ASSERT(aRightContent.IsElement()); if (aLeftContent.NodeInfo()->NameAtom() == nsGkAtoms::font) { const nsAttrValue* const leftSize = aLeftContent.AsElement()->GetParsedAttr(nsGkAtoms::size); const nsAttrValue* const rightSize = aRightContent.AsElement()->GetParsedAttr(nsGkAtoms::size); if (!leftSize ^ !rightSize || (leftSize && !leftSize->Equals(*rightSize))) { return false; } const nsAttrValue* const leftColor = aLeftContent.AsElement()->GetParsedAttr(nsGkAtoms::color); const nsAttrValue* const rightColor = aRightContent.AsElement()->GetParsedAttr(nsGkAtoms::color); if (!leftColor ^ !rightColor || (leftColor && !leftColor->Equals(*rightColor))) { return false; } const nsAttrValue* const leftFace = aLeftContent.AsElement()->GetParsedAttr(nsGkAtoms::face); const nsAttrValue* const rightFace = aRightContent.AsElement()->GetParsedAttr(nsGkAtoms::face); if (!leftFace ^ !rightFace || (leftFace && !leftFace->Equals(*rightFace))) { return false; } } nsStyledElement* leftStyledElement = nsStyledElement::FromNode(const_cast(&aLeftContent)); if (!leftStyledElement) { return false; } nsStyledElement* rightStyledElement = nsStyledElement::FromNode(const_cast(&aRightContent)); if (!rightStyledElement) { return false; } return CSSEditUtils::DoStyledElementsHaveSameStyle(*leftStyledElement, *rightStyledElement); } static bool IsHTMLBlockElementByDefault(const nsIContent& aContent) { if (!aContent.IsHTMLElement()) { return false; } if (aContent.IsHTMLElement(nsGkAtoms::br)) { // shortcut for TextEditor MOZ_ASSERT(!nsHTMLElement::IsBlock( nsHTMLTags::CaseSensitiveAtomTagToId(nsGkAtoms::br))); return false; } // We want to treat these as block nodes even though nsHTMLElement says // they're not. if (aContent.IsAnyOfHTMLElements( nsGkAtoms::body, nsGkAtoms::head, nsGkAtoms::tbody, nsGkAtoms::thead, nsGkAtoms::tfoot, nsGkAtoms::tr, nsGkAtoms::th, nsGkAtoms::td, nsGkAtoms::dt, nsGkAtoms::dd)) { return true; } return nsHTMLElement::IsBlock( nsHTMLTags::CaseSensitiveAtomTagToId(aContent.NodeInfo()->NameAtom())); } bool HTMLEditUtils::IsBlockElement(const nsIContent& aContent, BlockInlineCheck aBlockInlineCheck) { MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Unused); MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Auto); if (MOZ_UNLIKELY(!aContent.IsElement())) { // FIXME: If aContent is a visible `Text` and a flex/grid item, we should // treat it as block. return false; } // If it's a
, we should always treat it as an inline element because // its preceding collapse white-spaces and another
works same as usual // even if you set its style to `display:block`. if (aContent.IsHTMLElement(nsGkAtoms::br)) { return false; } if (aBlockInlineCheck == BlockInlineCheck::UseHTMLDefaultStyle) { return IsHTMLBlockElementByDefault(aContent); } // Let's treat the document element and the body element is a block to avoid // complicated things which may be detected by fuzzing. if (aContent.OwnerDoc()->GetDocumentElement() == &aContent || (aContent.IsHTMLElement(nsGkAtoms::body) && aContent.OwnerDoc()->GetBodyElement() == &aContent)) { return true; } RefPtr elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush(aContent.AsElement()); if (MOZ_UNLIKELY(!elementStyle)) { // If aContent is not in the composed tree return IsHTMLBlockElementByDefault(aContent); } const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay(); if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) { // Typically, we should not keep handling editing in invisible nodes, but if // we reach here, let's fallback to the default style for protecting the // structure as far as possible. return IsHTMLBlockElementByDefault(aContent); } // If the outside is not inline, treat it as block. if (!styleDisplay->IsInlineOutsideStyle()) { return true; } // Special case. If aContent is a grid or flex item, we want to treat it as a // block to handle it with the general paths. if (HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) { return true; } // If we're checking display-inside, inline-block, etc should be a block too. return aBlockInlineCheck == BlockInlineCheck::UseComputedDisplayStyle && styleDisplay->DisplayInside() == StyleDisplayInside::FlowRoot && // Treat widgets as inline since they won't hide collapsible // white-spaces around them. styleDisplay->EffectiveAppearance() == StyleAppearance::None; } bool HTMLEditUtils::IsInlineContent(const nsIContent& aContent, BlockInlineCheck aBlockInlineCheck) { MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Unused); MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Auto); if (!aContent.IsElement()) { // FIXME: If aContent is a visible `Text` and a flex/grid item, we should // treat it as block. return true; } // If it's a
, we should always treat it as an inline element because // its preceding collapse white-spaces and another
works same as usual // even if you set its style to `display:block`. if (aContent.IsHTMLElement(nsGkAtoms::br)) { return true; } if (aBlockInlineCheck == BlockInlineCheck::UseHTMLDefaultStyle) { return !IsHTMLBlockElementByDefault(aContent); } // Let's treat the document element and the body element is a block to avoid // complicated things which may be detected by fuzzing. if (aContent.OwnerDoc()->GetDocumentElement() == &aContent || (aContent.IsHTMLElement(nsGkAtoms::body) && aContent.OwnerDoc()->GetBodyElement() == &aContent)) { return false; } RefPtr elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush(aContent.AsElement()); if (MOZ_UNLIKELY(!elementStyle)) { // If aContent is not in the composed tree return !IsHTMLBlockElementByDefault(aContent); } const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay(); if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) { // Similar to IsBlockElement, let's fallback to refer the default style. // Note that if you change here, you may need to check the parent element // style if aContent. return !IsHTMLBlockElementByDefault(aContent); } // Special case. If aContent is a grid or flex item, we want to treat it as a // block to handle it with the general paths. if (HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) { return false; } // Different block IsBlockElement, when the display-outside is inline, it's // simply an inline element. return styleDisplay->IsInlineOutsideStyle(); } bool HTMLEditUtils::ParentElementIsGridOrFlexContainer( const nsIContent& aMaybeFlexOrGridItemContent) { if (!aMaybeFlexOrGridItemContent.IsElement()) { if (!aMaybeFlexOrGridItemContent.IsText() || !aMaybeFlexOrGridItemContent.AsText()->TextDataLength()) { return false; } // FIXME: If aMaybeFlexOrGridItemContent has only collapsible white-spaces // and next to a block boundary, it's invisible and shouldn't be a flex/grid // item. However, scanning block boundary requires to call this method. } Element* const parentElement = aMaybeFlexOrGridItemContent.GetParentElement(); // Editable state does not affect to elements across shadow DOM boundaries. // Therefore, we don't need to treat aElement as an flex item nor a grid item // as so if aElement is a root element of a shadow DOM unless we'll support // inline editing host support better (all browsers do not handle surrounding // content of the inline editing host strictly, e.g., when inserting a // collapsible white-space at start or end of it). if (MOZ_UNLIKELY(!parentElement)) { return false; } // We should consider whether the element is a flex item or a grid item // without nsIFrame since we don't want to refresh the layout while // `HTMLEditor` handles an action to avoid to run script. const RefPtr elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush( aMaybeFlexOrGridItemContent.IsElement() ? aMaybeFlexOrGridItemContent.AsElement() : parentElement); if (MOZ_UNLIKELY(!elementStyle)) { return false; } const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay(); if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) { return false; } const RefPtr parentElementStyle = aMaybeFlexOrGridItemContent.IsElement() ? nsComputedDOMStyle::GetComputedStyleNoFlush(parentElement) : elementStyle; if (MOZ_UNLIKELY(!parentElementStyle)) { return false; } const auto parentDisplayInside = parentElementStyle->StyleDisplay()->DisplayInside(); return parentDisplayInside == StyleDisplayInside::Flex || parentDisplayInside == StyleDisplayInside::Grid; } bool HTMLEditUtils::IsFlexOrGridItem(const nsIContent& aContent) { if (!HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) { return false; } // Note that if parent element's `display` is `contents`, the // `display-outside` style of aElement may not be block. However, even in // such case, HTMLEditUtils::IsBlockElement() should return true. MOZ_ASSERT_IF(aContent.IsElement(), HTMLEditUtils::IsBlockElement( *aContent.AsElement(), BlockInlineCheck::UseComputedDisplayOutsideStyle)); return true; } bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone( const nsIContent& aContent, const nsIContent* aAncestorLimiter /* = nullptr */) { if (NS_WARN_IF(!aContent.IsInComposedDoc())) { return true; } for (const Element* element : aContent.InclusiveFlatTreeAncestorsOfType()) { RefPtr elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush(element); if (MOZ_LIKELY(elementStyle)) { const nsStyleDisplay* styleDisplay = elementStyle->StyleDisplay(); if (MOZ_UNLIKELY(styleDisplay->mDisplay == StyleDisplay::None)) { return true; } } if (element == aAncestorLimiter) { break; } } return false; } bool HTMLEditUtils::IsVisibleElementEvenIfLeafNode(const nsIContent& aContent) { if (!aContent.IsElement()) { return false; } // Assume non-HTML element is visible. if (!aContent.IsHTMLElement()) { return true; } nsIFrame* const primaryFrame = aContent.GetPrimaryFrame(); if (primaryFrame && aContent.IsInComposedDoc() && HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone(aContent)) { return false; } if (HTMLEditUtils::IsBlockElement( aContent, BlockInlineCheck::UseComputedDisplayStyle)) { return true; } //
element may not have a frame, but it always affects surrounding // content. Therefore, it should be treated as visible. The others which are // checked here are replace elements which provide something visible content. if (aContent.IsAnyOfHTMLElements(nsGkAtoms::applet, nsGkAtoms::br, nsGkAtoms::iframe, nsGkAtoms::img, nsGkAtoms::meter, nsGkAtoms::progress, nsGkAtoms::select, nsGkAtoms::textarea)) { return true; } if (const HTMLInputElement* inputElement = HTMLInputElement::FromNode(&aContent)) { return inputElement->ControlType() != FormControlType::InputHidden; } if (primaryFrame) { // If the frame is not dirty or non-inline container frame, we can trust // whether the frame is empty or not. if (!primaryFrame->IsSubtreeDirty() || !primaryFrame->IsInlineFrame()) { return !primaryFrame->GetSize().IsEmpty(); } // Otherwise, the inner content may have been changed by the editor or JS. // Let's treat it's visible only when it has non-zero border or padding. return !primaryFrame->IsSelfEmpty(); } // If aContent does not have a primary frame, it may be inserted to the // document and has not been flushed the pending notifications. Then, we // cannot know the actual style so that let's assume it's invisible. return false; } bool HTMLEditUtils::IsInlineStyleElement(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements( nsGkAtoms::b, nsGkAtoms::i, nsGkAtoms::u, nsGkAtoms::tt, nsGkAtoms::s, nsGkAtoms::strike, nsGkAtoms::big, nsGkAtoms::small, nsGkAtoms::sub, nsGkAtoms::sup, nsGkAtoms::font); } bool HTMLEditUtils::IsDisplayOutsideInline(const Element& aElement) { RefPtr elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush(&aElement); if (!elementStyle) { return false; } return elementStyle->StyleDisplay()->DisplayOutside() == StyleDisplayOutside::Inline; } bool HTMLEditUtils::IsDisplayInsideFlowRoot(const Element& aElement) { RefPtr elementStyle = nsComputedDOMStyle::GetComputedStyleNoFlush(&aElement); if (!elementStyle) { return false; } return elementStyle->StyleDisplay()->DisplayInside() == StyleDisplayInside::FlowRoot; } bool HTMLEditUtils::IsRemovableInlineStyleElement(Element& aElement) { if (!aElement.IsHTMLElement()) { return false; } // https://w3c.github.io/editing/execCommand.html#removeformat-candidate if (aElement.IsAnyOfHTMLElements( nsGkAtoms::abbr, // Chrome ignores, but does not make sense. nsGkAtoms::acronym, nsGkAtoms::b, nsGkAtoms::bdi, // Chrome ignores, but does not make sense. nsGkAtoms::bdo, nsGkAtoms::big, nsGkAtoms::cite, nsGkAtoms::code, // nsGkAtoms::del, Chrome ignores, but does not make sense but // execCommand unofficial draft excludes this. Spec issue: // https://github.com/w3c/editing/issues/192 nsGkAtoms::dfn, nsGkAtoms::em, nsGkAtoms::font, nsGkAtoms::i, nsGkAtoms::ins, nsGkAtoms::kbd, nsGkAtoms::mark, // Chrome ignores, but does not make sense. nsGkAtoms::nobr, nsGkAtoms::q, nsGkAtoms::s, nsGkAtoms::samp, nsGkAtoms::small, nsGkAtoms::span, nsGkAtoms::strike, nsGkAtoms::strong, nsGkAtoms::sub, nsGkAtoms::sup, nsGkAtoms::tt, nsGkAtoms::u, nsGkAtoms::var)) { return true; } // If it's a element, we can remove it. nsAutoString tagName; aElement.GetTagName(tagName); return tagName.LowerCaseEqualsASCII("blink"); } bool HTMLEditUtils::IsOutdentable(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements( nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl, nsGkAtoms::li, nsGkAtoms::dd, nsGkAtoms::dt, nsGkAtoms::blockquote); } bool HTMLEditUtils::IsHeadingElement(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements(nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6); } bool HTMLEditUtils::IsListItemElement(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements(nsGkAtoms::li, nsGkAtoms::dd, nsGkAtoms::dt); } bool HTMLEditUtils::IsAnyTableElementExceptColumnElement( const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements( nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::thead, nsGkAtoms::tfoot, nsGkAtoms::tbody, nsGkAtoms::caption); } bool HTMLEditUtils::IsAnyTableElementExceptTableElementAndColumElement( const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements( nsGkAtoms::tr, nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::thead, nsGkAtoms::tfoot, nsGkAtoms::tbody, nsGkAtoms::caption); } bool HTMLEditUtils::IsTableRowElement(const nsIContent& aContent) { return aContent.IsHTMLElement(nsGkAtoms::tr); } bool HTMLEditUtils::IsTableCellElement(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th); } bool HTMLEditUtils::IsTableCellOrCaptionElement(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::caption); } bool HTMLEditUtils::IsListElement(const nsIContent& aContent) { return aContent.IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl); } bool HTMLEditUtils::IsImageElement(const nsIContent& aContent) { // XXX How about and ? return aContent.IsHTMLElement(nsGkAtoms::img); } bool HTMLEditUtils::IsHyperlinkElement(const nsIContent& aContent) { const dom::HTMLAnchorElement* const anchor = dom::HTMLAnchorElement::FromNode(aContent); if (!anchor) { return false; } // XXX Isn't it enough to check whether the `href` value is empty? nsAutoCString tmpText; anchor->GetHref(tmpText); return !tmpText.IsEmpty(); } bool HTMLEditUtils::IsNamedAnchorElement(const nsIContent& aContent) { const dom::HTMLAnchorElement* const anchor = dom::HTMLAnchorElement::FromNode(aContent); if (!anchor) { return false; } return anchor->HasName(); } bool HTMLEditUtils::IsMozDivElement(const nsIContent& aContent) { return aContent.IsHTMLElement(nsGkAtoms::div) && aContent.AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, u"_moz"_ns, eIgnoreCase); } bool HTMLEditUtils::IsMailCiteElement(const Element& aElement) { // don't ask me why, but our html mailcites are id'd by "type=cite"... if (aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, u"cite"_ns, eIgnoreCase)) { return true; } // ... but our plaintext mailcites by "_moz_quote=true". go figure. if (aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozquote, u"true"_ns, eIgnoreCase)) { return true; } return false; } bool HTMLEditUtils::IsReplacedElement(const Element& aElement) { if (!aElement.IsHTMLElement()) { // FIXME: Well known SVG, MathML elements should be tested here. return false; } if (aElement.IsHTMLElement(nsGkAtoms::input)) { return !aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::hidden, eIgnoreCase); } if (HTMLEditUtils::IsFormWidgetElement(aElement)) { return true; } // is a special element, it shows its subtree when it does not load // its content. if (aElement.IsHTMLElement(nsGkAtoms::object)) { const nsCOMPtr objectLoadingContent = do_QueryInterface(const_cast(&aElement)); uint32_t displayedType = nsIObjectLoadingContent::TYPE_FALLBACK; if (MOZ_LIKELY(objectLoadingContent)) { objectLoadingContent->GetDisplayedType(&displayedType); } return displayedType != nsIObjectLoadingContent::TYPE_FALLBACK; } return aElement.IsAnyOfHTMLElements( nsGkAtoms::audio, // In strictly speaking,
is not a replaced element, but treating it // as a replaced element makes HTMLEditor and its peers simpler. nsGkAtoms::br, nsGkAtoms::canvas, nsGkAtoms::embed, nsGkAtoms::iframe, nsGkAtoms::img, // and