Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / editor / libeditor / EditorUtils.h
blobc6b08952dd4f950d8ed55f13b1fada57932679e3
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 mozilla_EditorUtils_h
7 #define mozilla_EditorUtils_h
9 #include "mozilla/EditorBase.h" // for EditorBase
10 #include "mozilla/EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc
11 #include "mozilla/EditorForwards.h"
12 #include "mozilla/IntegerRange.h" // for IntegerRange
13 #include "mozilla/Maybe.h" // for Maybe
14 #include "mozilla/Result.h" // for Result<>
15 #include "mozilla/dom/Element.h" // for dom::Element
16 #include "mozilla/dom/HTMLBRElement.h" // for dom::HTMLBRElement
17 #include "mozilla/dom/Selection.h" // for dom::Selection
18 #include "mozilla/dom/Text.h" // for dom::Text
20 #include "nsAtom.h" // for nsStaticAtom
21 #include "nsCOMPtr.h" // for nsCOMPtr
22 #include "nsContentUtils.h" // for nsContentUtils
23 #include "nsDebug.h" // for NS_WARNING, etc
24 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
25 #include "nsRange.h" // for nsRange
26 #include "nsString.h" // for nsAString, nsString, etc
28 class nsITransferable;
30 namespace mozilla {
32 enum class StyleWhiteSpace : uint8_t;
34 enum class SuggestCaret {
35 // If specified, the method returns NS_OK when there is no recommended caret
36 // position.
37 OnlyIfHasSuggestion,
38 // If specified and if EditorBase::AllowsTransactionsToChangeSelection
39 // returns false, the method does nothing and returns NS_OK.
40 OnlyIfTransactionsAllowedToDoIt,
41 // If specified, the method returns
42 // NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR even if
43 // EditorBase::CollapseSelectionTo returns an error except when
44 // NS_ERROR_EDITOR_DESTROYED.
45 AndIgnoreTrivialError,
48 /******************************************************************************
49 * CaretPoint is a wrapper of EditorDOMPoint and provides a helper method to
50 * collapse Selection there, or move it to a local variable. This is typically
51 * used as the ok type of Result or a base class of DoSomethingResult classes.
52 ******************************************************************************/
53 class MOZ_STACK_CLASS CaretPoint {
54 public:
55 explicit CaretPoint(const EditorDOMPoint& aPointToPutCaret)
56 : mCaretPoint(aPointToPutCaret) {}
57 explicit CaretPoint(EditorDOMPoint&& aPointToPutCaret)
58 : mCaretPoint(std::move(aPointToPutCaret)) {}
60 CaretPoint(const CaretPoint&) = delete;
61 CaretPoint& operator=(const CaretPoint&) = delete;
62 CaretPoint(CaretPoint&&) = default;
63 CaretPoint& operator=(CaretPoint&&) = default;
65 /**
66 * Suggest caret position to aEditorBase.
68 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SuggestCaretPointTo(
69 EditorBase& aEditorBase, const SuggestCaretOptions& aOptions) const;
71 /**
72 * IgnoreCaretPointSuggestion() should be called if the method does not want
73 * to use caret position recommended by this instance.
75 void IgnoreCaretPointSuggestion() const { mHandledCaretPoint = true; }
77 /**
78 * When propagating the result, it may not want to the caller modify
79 * selection. In such case, this can clear the caret point. Use
80 * IgnoreCaretPointSuggestion() in the caller side instead.
82 void ForgetCaretPointSuggestion() { mCaretPoint.Clear(); }
84 bool HasCaretPointSuggestion() const { return mCaretPoint.IsSet(); }
85 constexpr const EditorDOMPoint& CaretPointRef() const { return mCaretPoint; }
86 constexpr EditorDOMPoint&& UnwrapCaretPoint() {
87 mHandledCaretPoint = true;
88 return std::move(mCaretPoint);
90 bool CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret,
91 const SuggestCaretOptions& aOptions) const {
92 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
93 MOZ_ASSERT(
94 !aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt));
95 mHandledCaretPoint = true;
96 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
97 !mCaretPoint.IsSet()) {
98 return false;
100 aPointToPutCaret = mCaretPoint;
101 return true;
103 bool MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
104 const SuggestCaretOptions& aOptions) {
105 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
106 MOZ_ASSERT(
107 !aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt));
108 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
109 !mCaretPoint.IsSet()) {
110 return false;
112 aPointToPutCaret = UnwrapCaretPoint();
113 return true;
115 bool CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret,
116 const EditorBase& aEditorBase,
117 const SuggestCaretOptions& aOptions) const;
118 bool MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
119 const EditorBase& aEditorBase,
120 const SuggestCaretOptions& aOptions);
122 protected:
123 constexpr bool CaretPointHandled() const { return mHandledCaretPoint; }
125 void SetCaretPoint(const EditorDOMPoint& aCaretPoint) {
126 mHandledCaretPoint = false;
127 mCaretPoint = aCaretPoint;
129 void SetCaretPoint(EditorDOMPoint&& aCaretPoint) {
130 mHandledCaretPoint = false;
131 mCaretPoint = std::move(aCaretPoint);
134 void UnmarkAsHandledCaretPoint() { mHandledCaretPoint = true; }
136 CaretPoint() = default;
138 private:
139 EditorDOMPoint mCaretPoint;
140 bool mutable mHandledCaretPoint = false;
143 /***************************************************************************
144 * EditActionResult is useful to return the handling state of edit sub actions
145 * without out params.
147 class MOZ_STACK_CLASS EditActionResult final {
148 public:
149 bool Canceled() const { return mCanceled; }
150 bool Handled() const { return mHandled; }
151 bool Ignored() const { return !mCanceled && !mHandled; }
153 void MarkAsCanceled() { mCanceled = true; }
154 void MarkAsHandled() { mHandled = true; }
156 EditActionResult& operator|=(const EditActionResult& aOther) {
157 mCanceled |= aOther.mCanceled;
158 mHandled |= aOther.mHandled;
159 return *this;
162 EditActionResult& operator|=(const MoveNodeResult& aMoveNodeResult);
164 static EditActionResult IgnoredResult() {
165 return EditActionResult(false, false);
167 static EditActionResult HandledResult() {
168 return EditActionResult(false, true);
170 static EditActionResult CanceledResult() {
171 return EditActionResult(true, true);
174 EditActionResult(const EditActionResult&) = delete;
175 EditActionResult& operator=(const EditActionResult&) = delete;
176 EditActionResult(EditActionResult&&) = default;
177 EditActionResult& operator=(EditActionResult&&) = default;
179 private:
180 bool mCanceled = false;
181 bool mHandled = false;
183 EditActionResult(bool aCanceled, bool aHandled)
184 : mCanceled(aCanceled), mHandled(aHandled) {}
186 EditActionResult() : mCanceled(false), mHandled(false) {}
189 /***************************************************************************
190 * CreateNodeResultBase is a simple class for CreateSomething() methods
191 * which want to return new node.
193 template <typename NodeType>
194 class MOZ_STACK_CLASS CreateNodeResultBase final : public CaretPoint {
195 using SelfType = CreateNodeResultBase<NodeType>;
197 public:
198 bool Handled() const { return mNode; }
199 NodeType* GetNewNode() const { return mNode; }
200 RefPtr<NodeType> UnwrapNewNode() { return std::move(mNode); }
202 CreateNodeResultBase() = delete;
203 explicit CreateNodeResultBase(NodeType& aNode) : mNode(&aNode) {}
204 explicit CreateNodeResultBase(NodeType& aNode,
205 const EditorDOMPoint& aCandidateCaretPoint)
206 : CaretPoint(aCandidateCaretPoint), mNode(&aNode) {}
207 explicit CreateNodeResultBase(NodeType& aNode,
208 EditorDOMPoint&& aCandidateCaretPoint)
209 : CaretPoint(std::move(aCandidateCaretPoint)), mNode(&aNode) {}
211 explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode)
212 : mNode(std::move(aNode)) {}
213 explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode,
214 const EditorDOMPoint& aCandidateCaretPoint)
215 : CaretPoint(aCandidateCaretPoint), mNode(std::move(aNode)) {
216 MOZ_ASSERT(mNode);
218 explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode,
219 EditorDOMPoint&& aCandidateCaretPoint)
220 : CaretPoint(std::move(aCandidateCaretPoint)), mNode(std::move(aNode)) {
221 MOZ_ASSERT(mNode);
224 [[nodiscard]] static SelfType NotHandled() {
225 return SelfType(EditorDOMPoint());
227 [[nodiscard]] static SelfType NotHandled(
228 const EditorDOMPoint& aPointToPutCaret) {
229 SelfType result(aPointToPutCaret);
230 return result;
232 [[nodiscard]] static SelfType NotHandled(EditorDOMPoint&& aPointToPutCaret) {
233 SelfType result(std::move(aPointToPutCaret));
234 return result;
237 #ifdef DEBUG
238 ~CreateNodeResultBase() {
239 MOZ_ASSERT(!HasCaretPointSuggestion() || CaretPointHandled());
241 #endif
243 CreateNodeResultBase(const SelfType& aOther) = delete;
244 SelfType& operator=(const SelfType& aOther) = delete;
245 CreateNodeResultBase(SelfType&& aOther) = default;
246 SelfType& operator=(SelfType&& aOther) = default;
248 private:
249 explicit CreateNodeResultBase(const EditorDOMPoint& aCandidateCaretPoint)
250 : CaretPoint(aCandidateCaretPoint) {}
251 explicit CreateNodeResultBase(EditorDOMPoint&& aCandidateCaretPoint)
252 : CaretPoint(std::move(aCandidateCaretPoint)) {}
254 RefPtr<NodeType> mNode;
258 * This is a result of inserting text. If the text inserted as a part of
259 * composition, this does not return CaretPoint. Otherwise, must return
260 * CaretPoint which is typically same as end of inserted text.
262 class MOZ_STACK_CLASS InsertTextResult final : public CaretPoint {
263 public:
264 InsertTextResult() : CaretPoint(EditorDOMPoint()) {}
265 template <typename EditorDOMPointType>
266 explicit InsertTextResult(const EditorDOMPointType& aEndOfInsertedText)
267 : CaretPoint(EditorDOMPoint()),
268 mEndOfInsertedText(aEndOfInsertedText.template To<EditorDOMPoint>()) {}
269 explicit InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText)
270 : CaretPoint(EditorDOMPoint()),
271 mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
272 template <typename PT, typename CT>
273 InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText,
274 const EditorDOMPointBase<PT, CT>& aCaretPoint)
275 : CaretPoint(aCaretPoint.template To<EditorDOMPoint>()),
276 mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
277 InsertTextResult(EditorDOMPointInText&& aEndOfInsertedText,
278 CaretPoint&& aCaretPoint)
279 : CaretPoint(std::move(aCaretPoint)),
280 mEndOfInsertedText(std::move(aEndOfInsertedText)) {
281 UnmarkAsHandledCaretPoint();
283 InsertTextResult(InsertTextResult&& aOther, EditorDOMPoint&& aCaretPoint)
284 : CaretPoint(std::move(aCaretPoint)),
285 mEndOfInsertedText(std::move(aOther.mEndOfInsertedText)) {}
287 [[nodiscard]] bool Handled() const { return mEndOfInsertedText.IsSet(); }
288 const EditorDOMPointInText& EndOfInsertedTextRef() const {
289 return mEndOfInsertedText;
292 private:
293 EditorDOMPointInText mEndOfInsertedText;
296 /***************************************************************************
297 * stack based helper class for calling EditorBase::EndTransaction() after
298 * EditorBase::BeginTransaction(). This shouldn't be used in editor classes
299 * or helper classes while an edit action is being handled. Use
300 * AutoTransactionBatch in such cases since it uses non-virtual internal
301 * methods.
302 ***************************************************************************/
303 class MOZ_RAII AutoTransactionBatchExternal final {
304 public:
305 MOZ_CAN_RUN_SCRIPT explicit AutoTransactionBatchExternal(
306 EditorBase& aEditorBase)
307 : mEditorBase(aEditorBase) {
308 MOZ_KnownLive(mEditorBase).BeginTransaction();
311 MOZ_CAN_RUN_SCRIPT ~AutoTransactionBatchExternal() {
312 MOZ_KnownLive(mEditorBase).EndTransaction();
315 private:
316 EditorBase& mEditorBase;
319 /******************************************************************************
320 * AutoSelectionRangeArray stores all ranges in `aSelection`.
321 * Note that modifying the ranges means modifing the selection ranges.
322 *****************************************************************************/
323 class MOZ_STACK_CLASS AutoSelectionRangeArray final {
324 public:
325 explicit AutoSelectionRangeArray(dom::Selection& aSelection) {
326 for (const uint32_t i : IntegerRange(aSelection.RangeCount())) {
327 MOZ_ASSERT(aSelection.GetRangeAt(i));
328 mRanges.AppendElement(*aSelection.GetRangeAt(i));
332 AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges;
335 class EditorUtils final {
336 public:
337 using EditorType = EditorBase::EditorType;
338 using Selection = dom::Selection;
341 * IsDescendantOf() checks if aNode is a child or a descendant of aParent.
342 * aOutPoint is set to the child of aParent.
344 * @return true if aNode is a child or a descendant of aParent.
346 static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
347 EditorRawDOMPoint* aOutPoint = nullptr);
348 static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
349 EditorDOMPoint* aOutPoint);
352 * Returns true if aContent is a <br> element and it's marked as padding for
353 * empty editor.
355 static bool IsPaddingBRElementForEmptyEditor(const nsIContent& aContent) {
356 const dom::HTMLBRElement* brElement =
357 dom::HTMLBRElement::FromNode(&aContent);
358 return brElement && brElement->IsPaddingForEmptyEditor();
362 * Returns true if aContent is a <br> element and it's marked as padding for
363 * empty last line.
365 static bool IsPaddingBRElementForEmptyLastLine(const nsIContent& aContent) {
366 const dom::HTMLBRElement* brElement =
367 dom::HTMLBRElement::FromNode(&aContent);
368 return brElement && brElement->IsPaddingForEmptyLastLine();
372 * IsEditableContent() returns true if aContent's data or children is ediable
373 * for the given editor type. Be aware, returning true does NOT mean the
374 * node can be removed from its parent node, and returning false does NOT
375 * mean the node cannot be removed from the parent node.
376 * XXX May be the anonymous nodes in TextEditor not editable? If it's not
377 * so, we can get rid of aEditorType.
379 static bool IsEditableContent(const nsIContent& aContent,
380 EditorType aEditorType) {
381 if (aEditorType == EditorType::HTML &&
382 (!aContent.IsEditable() || !aContent.IsInComposedDoc())) {
383 // FIXME(emilio): Why only for HTML editors? All content from the root
384 // content in text editors is also editable, so afaict we can remove the
385 // special-case.
386 return false;
388 return IsElementOrText(aContent);
392 * Returns true if aContent is a usual element node (not padding <br> element
393 * for empty editor) or a text node. In other words, returns true if
394 * aContent is a usual element node or visible data node.
396 static bool IsElementOrText(const nsIContent& aContent) {
397 if (aContent.IsText()) {
398 return true;
400 return aContent.IsElement() && !IsPaddingBRElementForEmptyEditor(aContent);
404 * Get the two longhands that make up computed white-space style of aContent.
406 static Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>>
407 GetComputedWhiteSpaceStyles(const nsIContent& aContent);
410 * IsWhiteSpacePreformatted() checks the style info for the node for the
411 * preformatted text style. This does NOT flush layout.
413 static bool IsWhiteSpacePreformatted(const nsIContent& aContent);
416 * IsNewLinePreformatted() checks whether the linefeed characters are
417 * preformatted or collapsible white-spaces. This does NOT flush layout.
419 static bool IsNewLinePreformatted(const nsIContent& aContent);
422 * IsOnlyNewLinePreformatted() checks whether the linefeed characters are
423 * preformated but white-spaces are collapsed, or otherwise. I.e., this
424 * returns true only when `white-space-collapse:pre-line`.
426 static bool IsOnlyNewLinePreformatted(const nsIContent& aContent);
428 static nsStaticAtom* GetTagNameAtom(const nsAString& aTagName) {
429 if (aTagName.IsEmpty()) {
430 return nullptr;
432 nsAutoString lowerTagName;
433 nsContentUtils::ASCIIToLower(aTagName, lowerTagName);
434 return NS_GetStaticAtom(lowerTagName);
437 static nsStaticAtom* GetAttributeAtom(const nsAString& aAttribute) {
438 if (aAttribute.IsEmpty()) {
439 return nullptr; // Don't use nsGkAtoms::_empty for attribute.
441 return NS_GetStaticAtom(aAttribute);
445 * Helper method for deletion. When this returns true, Selection will be
446 * computed with nsFrameSelection that also requires flushed layout
447 * information.
449 template <typename SelectionOrAutoRangeArray>
450 static bool IsFrameSelectionRequiredToExtendSelection(
451 nsIEditor::EDirection aDirectionAndAmount,
452 SelectionOrAutoRangeArray& aSelectionOrAutoRangeArray) {
453 switch (aDirectionAndAmount) {
454 case nsIEditor::eNextWord:
455 case nsIEditor::ePreviousWord:
456 case nsIEditor::eToBeginningOfLine:
457 case nsIEditor::eToEndOfLine:
458 return true;
459 case nsIEditor::ePrevious:
460 case nsIEditor::eNext:
461 return aSelectionOrAutoRangeArray.IsCollapsed();
462 default:
463 return false;
468 * Create an nsITransferable instance which has kTextMime and
469 * kMozTextInternal flavors.
471 static Result<nsCOMPtr<nsITransferable>, nsresult>
472 CreateTransferableForPlainText(const dom::Document& aDocument);
475 } // namespace mozilla
477 #endif // #ifndef mozilla_EditorUtils_h