/* -*- 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 EditorLineBreak_h #define EditorLineBreak_h #include "EditorDOMPoint.h" #include "EditorForwards.h" #include "EditorUtils.h" #include "mozilla/Maybe.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLBRElement.h" #include "mozilla/dom/Text.h" #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsGkAtoms.h" #include "nsIContent.h" namespace mozilla { class AutoTrackLineBreak; /****************************************************************************** * EditorLineBreakBase stores
or a preformatted line break position. * This cannot represent no line break. Therefore, if a method may not return * a line break, they need to use Maybe. ******************************************************************************/ template class EditorLineBreakBase { using SelfType = EditorLineBreakBase; public: using HTMLBRElement = dom::HTMLBRElement; using Text = dom::Text; explicit EditorLineBreakBase(const HTMLBRElement& aBRElement) : mContent(const_cast(&aBRElement)) {} explicit EditorLineBreakBase(RefPtr&& aBRElement); explicit EditorLineBreakBase(RefPtr&& aBRElement); explicit EditorLineBreakBase(nsCOMPtr&& aBRElement); EditorLineBreakBase(const Text& aText, uint32_t aOffset) : mContent(const_cast(&aText)), mOffsetInText(Some(aOffset)) {} EditorLineBreakBase(RefPtr&& aText, uint32_t aOffset); EditorLineBreakBase(nsCOMPtr&& aText, uint32_t aOffset); [[nodiscard]] static SelfType AtLastChar(const Text& aText) { MOZ_RELEASE_ASSERT(aText.TextDataLength()); return SelfType(aText, aText.TextDataLength() - 1u); } [[nodiscard]] static SelfType AtLastChar(RefPtr&& aText) { MOZ_RELEASE_ASSERT(aText); MOZ_RELEASE_ASSERT(aText->TextDataLength()); const uint32_t lastCharIndex = aText->TextDataLength() - 1u; return SelfType(std::forward>(aText), lastCharIndex); } [[nodiscard]] static SelfType AtLastChar(nsCOMPtr&& aText) { MOZ_RELEASE_ASSERT(aText); MOZ_RELEASE_ASSERT(aText->IsText()); MOZ_RELEASE_ASSERT(aText->AsText()->TextDataLength()); const uint32_t lastCharIndex = aText->AsText()->TextDataLength() - 1u; return SelfType(std::forward>(aText), lastCharIndex); } [[nodiscard]] bool IsInComposedDoc() const { return mContent->IsInComposedDoc(); } template [[nodiscard]] EditorDOMPointType To() const { static_assert(std::is_same::value || std::is_same::value); return mOffsetInText.isSome() ? EditorDOMPointType(mContent, mOffsetInText.value()) : EditorDOMPointType(mContent); } template [[nodiscard]] EditorDOMPointType After() const { if (IsHTMLBRElement()) { return EditorDOMPointType::After(BRElementRef()); } if (mOffsetInText.value() + 1 < TextRef().TextDataLength()) { return EditorDOMPointType(&TextRef(), mOffsetInText.value() + 1); } // If the line break is end of a Text node and it's followed by another // Text, we should return start of the following Text. if (Text* const followingText = Text::FromNodeOrNull(TextRef().GetNextSibling())) { return EditorDOMPointType(followingText, 0); } return EditorDOMPointType::After(TextRef()); } template [[nodiscard]] EditorDOMPointType Before() const { if (IsHTMLBRElement()) { return EditorDOMPointType(&BRElementRef(), dom::Selection::InterlinePosition::EndOfLine); } return To(); } [[nodiscard]] bool IsHTMLBRElement() const { MOZ_ASSERT_IF(!mOffsetInText, mContent->IsHTMLElement(nsGkAtoms::br)); return mOffsetInText.isNothing(); } [[nodiscard]] bool IsPreformattedLineBreak() const { MOZ_ASSERT_IF(mOffsetInText, mContent->IsText()); return mOffsetInText.isSome(); } [[nodiscard]] bool TextIsOnlyPreformattedLineBreak() const { return IsPreformattedLineBreak() && !Offset() && TextRef().TextDataLength() == 1u; } [[nodiscard]] nsIContent& ContentRef() const { return *mContent; } [[nodiscard]] HTMLBRElement& BRElementRef() const { MOZ_DIAGNOSTIC_ASSERT(IsHTMLBRElement()); MOZ_DIAGNOSTIC_ASSERT(GetBRElement()); return *GetBRElement(); } [[nodiscard]] HTMLBRElement* GetBRElement() const { return HTMLBRElement::FromNode(mContent); } [[nodiscard]] Text& TextRef() const { MOZ_DIAGNOSTIC_ASSERT(IsPreformattedLineBreak()); MOZ_DIAGNOSTIC_ASSERT(GetText()); return *GetText(); } [[nodiscard]] Text* GetText() const { return Text::FromNode(mContent); } [[nodiscard]] uint32_t Offset() const { MOZ_ASSERT(IsPreformattedLineBreak()); return mOffsetInText.value(); } [[nodiscard]] bool CharAtOffsetIsLineBreak() const { MOZ_DIAGNOSTIC_ASSERT(IsPreformattedLineBreak()); return *mOffsetInText < TextRef().TextDataLength() && TextRef().DataBuffer().CharAt(*mOffsetInText) == '\n'; } [[nodiscard]] bool IsDeletableFromComposedDoc() const { if (IsPreformattedLineBreak()) { return TextRef().IsEditable(); } const nsIContent* const parent = BRElementRef().GetParent(); return parent && parent->IsEditable(); } private: ContentType mContent; Maybe mOffsetInText; friend class AutoTrackLineBreak; }; using EditorLineBreak = EditorLineBreakBase>; using EditorRawLineBreak = EditorLineBreakBase; template <> inline EditorLineBreakBase>::EditorLineBreakBase( RefPtr&& aBRElement) : mContent(aBRElement.forget()) { MOZ_RELEASE_ASSERT(mContent); } template <> inline EditorLineBreakBase>::EditorLineBreakBase( RefPtr&& aBRElement) : mContent(aBRElement.forget()) { MOZ_RELEASE_ASSERT(mContent); MOZ_RELEASE_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br)); } template <> inline EditorLineBreakBase>::EditorLineBreakBase( nsCOMPtr&& aBRElement) : mContent(aBRElement.forget()) { MOZ_RELEASE_ASSERT(mContent); MOZ_RELEASE_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br)); } template <> inline EditorLineBreakBase>::EditorLineBreakBase( RefPtr&& aText, uint32_t aOffset) : mContent(std::move(aText)), mOffsetInText(Some(aOffset)) { MOZ_RELEASE_ASSERT(mContent); MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(*mContent)); MOZ_RELEASE_ASSERT(GetText()->TextDataLength() > aOffset); MOZ_RELEASE_ASSERT(CharAtOffsetIsLineBreak()); } template <> inline EditorLineBreakBase>::EditorLineBreakBase( nsCOMPtr&& aText, uint32_t aOffset) : mContent(aText.forget()), mOffsetInText(Some(aOffset)) { MOZ_RELEASE_ASSERT(mContent); MOZ_RELEASE_ASSERT(mContent->IsText()); MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(*mContent)); MOZ_RELEASE_ASSERT(TextRef().TextDataLength() > aOffset); MOZ_ASSERT(CharAtOffsetIsLineBreak()); } template <> inline EditorLineBreakBase::EditorLineBreakBase( const HTMLBRElement& aBRElement) : mContent(const_cast(&aBRElement)) {} template <> inline EditorLineBreakBase::EditorLineBreakBase( RefPtr&& aBRElement) : mContent(aBRElement) { MOZ_RELEASE_ASSERT(mContent); aBRElement = nullptr; } template <> inline EditorLineBreakBase::EditorLineBreakBase( RefPtr&& aBRElement) : mContent(aBRElement) { MOZ_RELEASE_ASSERT(mContent); MOZ_RELEASE_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br)); aBRElement = nullptr; } template <> inline EditorLineBreakBase::EditorLineBreakBase( nsCOMPtr&& aBRElement) : mContent(aBRElement) { MOZ_RELEASE_ASSERT(mContent); MOZ_RELEASE_ASSERT(mContent->IsHTMLElement(nsGkAtoms::br)); aBRElement = nullptr; } template <> inline EditorLineBreakBase::EditorLineBreakBase( RefPtr&& aText, uint32_t aOffset) : mContent(aText), mOffsetInText(Some(aOffset)) { MOZ_RELEASE_ASSERT(mContent); MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(*mContent)); MOZ_RELEASE_ASSERT(GetText()->TextDataLength() > aOffset); MOZ_RELEASE_ASSERT(CharAtOffsetIsLineBreak()); aText = nullptr; } template <> inline EditorLineBreakBase::EditorLineBreakBase( nsCOMPtr&& aText, uint32_t aOffset) : mContent(aText), mOffsetInText(Some(aOffset)) { MOZ_RELEASE_ASSERT(mContent); MOZ_RELEASE_ASSERT(mContent->IsText()); MOZ_ASSERT(EditorUtils::IsNewLinePreformatted(*mContent)); MOZ_RELEASE_ASSERT(GetText()->TextDataLength() > aOffset); MOZ_ASSERT(CharAtOffsetIsLineBreak()); aText = nullptr; } class CreateLineBreakResult final : public CaretPoint { public: CreateLineBreakResult(const EditorLineBreak& aLineBreak, const EditorDOMPoint& aCaretPoint) : CaretPoint(aCaretPoint), mLineBreak(Some(aLineBreak)) {} CreateLineBreakResult(EditorLineBreak&& aLineBreak, const EditorDOMPoint& aCaretPoint) : CaretPoint(aCaretPoint), mLineBreak(Some(std::move(aLineBreak))) {} CreateLineBreakResult(const EditorLineBreak& aLineBreak, EditorDOMPoint&& aCaretPoint) : CaretPoint(aCaretPoint), mLineBreak(Some(aLineBreak)) {} CreateLineBreakResult(EditorLineBreak&& aLineBreak, EditorDOMPoint&& aCaretPoint) : CaretPoint(std::move(aCaretPoint)), mLineBreak(Some(std::move(aLineBreak))) {} explicit CreateLineBreakResult(CreateElementResult&& aCreateElementResult) : CaretPoint(aCreateElementResult.UnwrapCaretPoint()), mLineBreak(Some(aCreateElementResult.UnwrapNewNode())) {} [[nodiscard]] static CreateLineBreakResult NotHandled() { return CreateLineBreakResult(); } [[nodiscard]] constexpr bool Handled() const { return mLineBreak.isSome(); } [[nodiscard]] constexpr const EditorLineBreak& LineBreakRef() const { MOZ_ASSERT(Handled()); return mLineBreak.ref(); } [[nodiscard]] constexpr const EditorLineBreak* operator->() const { return &LineBreakRef(); } // Shortcut for unclear methods of EditorLineBreak if `->` operator is used. template [[nodiscard]] EditorDOMPointType AtLineBreak() const { return LineBreakRef().To(); } template [[nodiscard]] EditorDOMPointType BeforeLineBreak() const { return LineBreakRef().Before(); } template [[nodiscard]] EditorDOMPointType AfterLineBreak() const { return LineBreakRef().After(); } [[nodiscard]] nsIContent& LineBreakContentRef() const { return LineBreakRef().ContentRef(); } [[nodiscard]] bool LineBreakIsInComposedDoc() const { return LineBreakRef().IsInComposedDoc(); } private: CreateLineBreakResult() : CaretPoint(EditorDOMPoint()) {} Maybe mLineBreak; }; } // namespace mozilla #endif // #ifndef EditorLineBreak_h