Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / editor / libeditor / EditorUtils.h
blobad64cc79a023b22a1db1bf64bae4965f70a093fd
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/DataTransfer.h" // for dom::DataTransfer
16 #include "mozilla/dom/Element.h" // for dom::Element
17 #include "mozilla/dom/HTMLBRElement.h" // for dom::HTMLBRElement
18 #include "mozilla/dom/Selection.h" // for dom::Selection
19 #include "mozilla/dom/Text.h" // for dom::Text
21 #include "nsAtom.h" // for nsStaticAtom
22 #include "nsCOMPtr.h" // for nsCOMPtr
23 #include "nsContentUtils.h" // for nsContentUtils
24 #include "nsDebug.h" // for NS_WARNING, etc
25 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_*
26 #include "nsRange.h" // for nsRange
27 #include "nsString.h" // for nsAString, nsString, etc
29 class nsITransferable;
31 namespace mozilla {
33 enum class StyleWhiteSpace : uint8_t;
35 enum class SuggestCaret {
36 // If specified, the method returns NS_OK when there is no recommended caret
37 // position.
38 OnlyIfHasSuggestion,
39 // If specified and if EditorBase::AllowsTransactionsToChangeSelection
40 // returns false, the method does nothing and returns NS_OK.
41 OnlyIfTransactionsAllowedToDoIt,
42 // If specified, the method returns
43 // NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR even if
44 // EditorBase::CollapseSelectionTo returns an error except when
45 // NS_ERROR_EDITOR_DESTROYED.
46 AndIgnoreTrivialError,
49 /******************************************************************************
50 * CaretPoint is a wrapper of EditorDOMPoint and provides a helper method to
51 * collapse Selection there, or move it to a local variable. This is typically
52 * used as the ok type of Result or a base class of DoSomethingResult classes.
53 ******************************************************************************/
54 class MOZ_STACK_CLASS CaretPoint {
55 public:
56 explicit CaretPoint(const EditorDOMPoint& aPointToPutCaret)
57 : mCaretPoint(aPointToPutCaret) {}
58 explicit CaretPoint(EditorDOMPoint&& aPointToPutCaret)
59 : mCaretPoint(std::move(aPointToPutCaret)) {}
61 CaretPoint(const CaretPoint&) = delete;
62 CaretPoint& operator=(const CaretPoint&) = delete;
63 CaretPoint(CaretPoint&&) = default;
64 CaretPoint& operator=(CaretPoint&&) = default;
66 /**
67 * Suggest caret position to aEditorBase.
69 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SuggestCaretPointTo(
70 EditorBase& aEditorBase, const SuggestCaretOptions& aOptions) const;
72 /**
73 * IgnoreCaretPointSuggestion() should be called if the method does not want
74 * to use caret position recommended by this instance.
76 void IgnoreCaretPointSuggestion() const { mHandledCaretPoint = true; }
78 /**
79 * When propagating the result, it may not want to the caller modify
80 * selection. In such case, this can clear the caret point. Use
81 * IgnoreCaretPointSuggestion() in the caller side instead.
83 void ForgetCaretPointSuggestion() { mCaretPoint.Clear(); }
85 bool HasCaretPointSuggestion() const { return mCaretPoint.IsSet(); }
86 constexpr const EditorDOMPoint& CaretPointRef() const { return mCaretPoint; }
87 constexpr EditorDOMPoint&& UnwrapCaretPoint() {
88 mHandledCaretPoint = true;
89 return std::move(mCaretPoint);
91 bool CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret,
92 const SuggestCaretOptions& aOptions) const {
93 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
94 MOZ_ASSERT(
95 !aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt));
96 mHandledCaretPoint = true;
97 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
98 !mCaretPoint.IsSet()) {
99 return false;
101 aPointToPutCaret = mCaretPoint;
102 return true;
104 bool CopyCaretPointTo(CaretPoint& aCaretPoint,
105 const SuggestCaretOptions& aOptions) const {
106 return CopyCaretPointTo(aCaretPoint.mCaretPoint, aOptions);
108 bool MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
109 const SuggestCaretOptions& aOptions) {
110 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError));
111 MOZ_ASSERT(
112 !aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt));
113 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) &&
114 !mCaretPoint.IsSet()) {
115 return false;
117 aPointToPutCaret = UnwrapCaretPoint();
118 return true;
120 bool MoveCaretPointTo(CaretPoint& aCaretPoint,
121 const SuggestCaretOptions& aOptions) {
122 return MoveCaretPointTo(aCaretPoint.mCaretPoint, aOptions);
124 bool CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret,
125 const EditorBase& aEditorBase,
126 const SuggestCaretOptions& aOptions) const;
127 bool MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret,
128 const EditorBase& aEditorBase,
129 const SuggestCaretOptions& aOptions);
131 protected:
132 constexpr bool CaretPointHandled() const { return mHandledCaretPoint; }
134 void SetCaretPoint(const EditorDOMPoint& aCaretPoint) {
135 mHandledCaretPoint = false;
136 mCaretPoint = aCaretPoint;
138 void SetCaretPoint(EditorDOMPoint&& aCaretPoint) {
139 mHandledCaretPoint = false;
140 mCaretPoint = std::move(aCaretPoint);
143 void UnmarkAsHandledCaretPoint() { mHandledCaretPoint = true; }
145 CaretPoint() = default;
147 private:
148 EditorDOMPoint mCaretPoint;
149 bool mutable mHandledCaretPoint = false;
151 friend class AutoTrackDOMPoint;
154 /***************************************************************************
155 * EditActionResult is useful to return the handling state of edit sub actions
156 * without out params.
158 class MOZ_STACK_CLASS EditActionResult {
159 public:
160 bool Canceled() const { return mCanceled; }
161 bool Handled() const { return mHandled; }
162 bool Ignored() const { return !mCanceled && !mHandled; }
164 void MarkAsCanceled() { mCanceled = true; }
165 void MarkAsHandled() { mHandled = true; }
167 EditActionResult& operator|=(const EditActionResult& aOther) {
168 mCanceled |= aOther.mCanceled;
169 mHandled |= aOther.mHandled;
170 return *this;
173 static EditActionResult IgnoredResult() {
174 return EditActionResult(false, false);
176 static EditActionResult HandledResult() {
177 return EditActionResult(false, true);
179 static EditActionResult CanceledResult() {
180 return EditActionResult(true, true);
183 EditActionResult(const EditActionResult&) = delete;
184 EditActionResult& operator=(const EditActionResult&) = delete;
185 EditActionResult(EditActionResult&&) = default;
186 EditActionResult& operator=(EditActionResult&&) = default;
188 protected:
189 EditActionResult(bool aCanceled, bool aHandled)
190 : mCanceled(aCanceled), mHandled(aHandled) {}
192 EditActionResult() : mCanceled(false), mHandled(false) {}
194 void UnmarkAsCanceled() { mCanceled = false; }
196 private:
197 bool mCanceled = false;
198 bool mHandled = false;
201 /***************************************************************************
202 * CreateNodeResultBase is a simple class for CreateSomething() methods
203 * which want to return new node.
205 template <typename NodeType>
206 class MOZ_STACK_CLASS CreateNodeResultBase final : public CaretPoint {
207 using SelfType = CreateNodeResultBase<NodeType>;
209 public:
210 bool Handled() const { return mNode; }
211 NodeType* GetNewNode() const { return mNode; }
212 RefPtr<NodeType> UnwrapNewNode() { return std::move(mNode); }
214 CreateNodeResultBase() = delete;
215 explicit CreateNodeResultBase(NodeType& aNode) : mNode(&aNode) {}
216 explicit CreateNodeResultBase(NodeType& aNode,
217 const EditorDOMPoint& aCandidateCaretPoint)
218 : CaretPoint(aCandidateCaretPoint), mNode(&aNode) {}
219 explicit CreateNodeResultBase(NodeType& aNode,
220 EditorDOMPoint&& aCandidateCaretPoint)
221 : CaretPoint(std::move(aCandidateCaretPoint)), mNode(&aNode) {}
223 explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode)
224 : mNode(std::move(aNode)) {}
225 explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode,
226 const EditorDOMPoint& aCandidateCaretPoint)
227 : CaretPoint(aCandidateCaretPoint), mNode(std::move(aNode)) {
228 MOZ_ASSERT(mNode);
230 explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode,
231 EditorDOMPoint&& aCandidateCaretPoint)
232 : CaretPoint(std::move(aCandidateCaretPoint)), mNode(std::move(aNode)) {
233 MOZ_ASSERT(mNode);
236 [[nodiscard]] static SelfType NotHandled() {
237 return SelfType(EditorDOMPoint());
239 [[nodiscard]] static SelfType NotHandled(
240 const EditorDOMPoint& aPointToPutCaret) {
241 SelfType result(aPointToPutCaret);
242 return result;
244 [[nodiscard]] static SelfType NotHandled(EditorDOMPoint&& aPointToPutCaret) {
245 SelfType result(std::move(aPointToPutCaret));
246 return result;
249 #ifdef DEBUG
250 ~CreateNodeResultBase() {
251 MOZ_ASSERT(!HasCaretPointSuggestion() || CaretPointHandled());
253 #endif
255 CreateNodeResultBase(const SelfType& aOther) = delete;
256 SelfType& operator=(const SelfType& aOther) = delete;
257 CreateNodeResultBase(SelfType&& aOther) = default;
258 SelfType& operator=(SelfType&& aOther) = default;
260 private:
261 explicit CreateNodeResultBase(const EditorDOMPoint& aCandidateCaretPoint)
262 : CaretPoint(aCandidateCaretPoint) {}
263 explicit CreateNodeResultBase(EditorDOMPoint&& aCandidateCaretPoint)
264 : CaretPoint(std::move(aCandidateCaretPoint)) {}
266 RefPtr<NodeType> mNode;
270 * This is a result of inserting text. If the text inserted as a part of
271 * composition, this does not return CaretPoint. Otherwise, must return
272 * CaretPoint which is typically same as end of inserted text.
274 class MOZ_STACK_CLASS InsertTextResult final : public CaretPoint {
275 public:
276 InsertTextResult() : CaretPoint(EditorDOMPoint()) {}
277 template <typename EditorDOMPointType>
278 explicit InsertTextResult(const EditorDOMPointType& aEndOfInsertedText)
279 : CaretPoint(EditorDOMPoint()),
280 mEndOfInsertedText(aEndOfInsertedText.template To<EditorDOMPoint>()) {}
281 explicit InsertTextResult(EditorDOMPoint&& aEndOfInsertedText)
282 : CaretPoint(EditorDOMPoint()),
283 mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
284 template <typename PT, typename CT>
285 InsertTextResult(EditorDOMPoint&& aEndOfInsertedText,
286 const EditorDOMPointBase<PT, CT>& aCaretPoint)
287 : CaretPoint(aCaretPoint.template To<EditorDOMPoint>()),
288 mEndOfInsertedText(std::move(aEndOfInsertedText)) {}
289 InsertTextResult(EditorDOMPoint&& aEndOfInsertedText,
290 CaretPoint&& aCaretPoint)
291 : CaretPoint(std::move(aCaretPoint)),
292 mEndOfInsertedText(std::move(aEndOfInsertedText)) {
293 UnmarkAsHandledCaretPoint();
295 InsertTextResult(InsertTextResult&& aOther, EditorDOMPoint&& aCaretPoint)
296 : CaretPoint(std::move(aCaretPoint)),
297 mEndOfInsertedText(std::move(aOther.mEndOfInsertedText)) {}
299 [[nodiscard]] bool Handled() const { return mEndOfInsertedText.IsSet(); }
300 const EditorDOMPoint& EndOfInsertedTextRef() const {
301 return mEndOfInsertedText;
304 private:
305 EditorDOMPoint mEndOfInsertedText;
308 /***************************************************************************
309 * stack based helper class for calling EditorBase::EndTransaction() after
310 * EditorBase::BeginTransaction(). This shouldn't be used in editor classes
311 * or helper classes while an edit action is being handled. Use
312 * AutoTransactionBatch in such cases since it uses non-virtual internal
313 * methods.
314 ***************************************************************************/
315 class MOZ_RAII AutoTransactionBatchExternal final {
316 public:
317 MOZ_CAN_RUN_SCRIPT explicit AutoTransactionBatchExternal(
318 EditorBase& aEditorBase)
319 : mEditorBase(aEditorBase) {
320 MOZ_KnownLive(mEditorBase).BeginTransaction();
323 MOZ_CAN_RUN_SCRIPT ~AutoTransactionBatchExternal() {
324 MOZ_KnownLive(mEditorBase).EndTransaction();
327 private:
328 EditorBase& mEditorBase;
331 /******************************************************************************
332 * AutoSelectionRangeArray stores all ranges in `aSelection`.
333 * Note that modifying the ranges means modifing the selection ranges.
334 *****************************************************************************/
335 class MOZ_STACK_CLASS AutoSelectionRangeArray final {
336 public:
337 explicit AutoSelectionRangeArray(dom::Selection& aSelection) {
338 for (const uint32_t i : IntegerRange(aSelection.RangeCount())) {
339 MOZ_ASSERT(aSelection.GetRangeAt(i));
340 mRanges.AppendElement(*aSelection.GetRangeAt(i));
344 AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges;
347 /******************************************************************************
348 * AutoTrackDataTransferForPaste keeps track of whether the paste event handler
349 * in JS has modified the clipboard.
350 *****************************************************************************/
351 class MOZ_STACK_CLASS AutoTrackDataTransferForPaste {
352 public:
353 MOZ_CAN_RUN_SCRIPT AutoTrackDataTransferForPaste(
354 const EditorBase& aEditorBase,
355 RefPtr<dom::DataTransfer>& aDataTransferForPaste)
356 : mEditorBase(aEditorBase),
357 mDataTransferForPaste(aDataTransferForPaste.get_address()) {
358 mEditorBase.GetDocument()->ClearClipboardCopyTriggered();
361 ~AutoTrackDataTransferForPaste() { FlushAndStopTracking(); }
363 private:
364 void FlushAndStopTracking() {
365 if (!mDataTransferForPaste ||
366 !mEditorBase.GetDocument()->IsClipboardCopyTriggered()) {
367 return;
369 // The paste event copied new data to the clipboard, so we need to use
370 // that data to paste into the DOM element below.
371 if (*mDataTransferForPaste) {
372 (*mDataTransferForPaste)->ClearForPaste();
374 // Just null this out so this data won't be used and we will get it directly
375 // from the clipboard in the future.
376 *mDataTransferForPaste = nullptr;
377 mDataTransferForPaste = nullptr;
380 MOZ_KNOWN_LIVE const EditorBase& mEditorBase;
381 RefPtr<dom::DataTransfer>* mDataTransferForPaste;
384 class EditorUtils final {
385 public:
386 using EditorType = EditorBase::EditorType;
387 using Selection = dom::Selection;
390 * IsDescendantOf() checks if aNode is a child or a descendant of aParent.
391 * aOutPoint is set to the child of aParent.
393 * @return true if aNode is a child or a descendant of aParent.
395 static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
396 EditorRawDOMPoint* aOutPoint = nullptr);
397 static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
398 EditorDOMPoint* aOutPoint);
401 * Returns true if aContent is a <br> element and it's marked as padding for
402 * empty editor.
404 static bool IsPaddingBRElementForEmptyEditor(const nsIContent& aContent) {
405 const dom::HTMLBRElement* brElement =
406 dom::HTMLBRElement::FromNode(&aContent);
407 return brElement && brElement->IsPaddingForEmptyEditor();
411 * Returns true if aContent is a <br> element and it's marked as padding for
412 * empty last line.
414 static bool IsPaddingBRElementForEmptyLastLine(const nsIContent& aContent) {
415 const dom::HTMLBRElement* brElement =
416 dom::HTMLBRElement::FromNode(&aContent);
417 return brElement && brElement->IsPaddingForEmptyLastLine();
421 * IsEditableContent() returns true if aContent's data or children is ediable
422 * for the given editor type. Be aware, returning true does NOT mean the
423 * node can be removed from its parent node, and returning false does NOT
424 * mean the node cannot be removed from the parent node.
425 * XXX May be the anonymous nodes in TextEditor not editable? If it's not
426 * so, we can get rid of aEditorType.
428 static bool IsEditableContent(const nsIContent& aContent,
429 EditorType aEditorType) {
430 if (aEditorType == EditorType::HTML &&
431 (!aContent.IsEditable() || !aContent.IsInComposedDoc())) {
432 // FIXME(emilio): Why only for HTML editors? All content from the root
433 // content in text editors is also editable, so afaict we can remove the
434 // special-case.
435 return false;
437 return IsElementOrText(aContent);
441 * Returns true if aContent is a usual element node (not padding <br> element
442 * for empty editor) or a text node. In other words, returns true if
443 * aContent is a usual element node or visible data node.
445 static bool IsElementOrText(const nsIContent& aContent) {
446 if (aContent.IsText()) {
447 return true;
449 return aContent.IsElement() && !IsPaddingBRElementForEmptyEditor(aContent);
453 * Get the two longhands that make up computed white-space style of aContent.
455 static Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>>
456 GetComputedWhiteSpaceStyles(const nsIContent& aContent);
459 * IsWhiteSpacePreformatted() checks the style info for the node for the
460 * preformatted text style. This does NOT flush layout.
462 static bool IsWhiteSpacePreformatted(const nsIContent& aContent);
465 * IsNewLinePreformatted() checks whether the linefeed characters are
466 * preformatted or collapsible white-spaces. This does NOT flush layout.
468 static bool IsNewLinePreformatted(const nsIContent& aContent);
471 * IsOnlyNewLinePreformatted() checks whether the linefeed characters are
472 * preformated but white-spaces are collapsed, or otherwise. I.e., this
473 * returns true only when `white-space-collapse:pre-line`.
475 static bool IsOnlyNewLinePreformatted(const nsIContent& aContent);
477 static nsStaticAtom* GetTagNameAtom(const nsAString& aTagName) {
478 if (aTagName.IsEmpty()) {
479 return nullptr;
481 nsAutoString lowerTagName;
482 nsContentUtils::ASCIIToLower(aTagName, lowerTagName);
483 return NS_GetStaticAtom(lowerTagName);
486 static nsStaticAtom* GetAttributeAtom(const nsAString& aAttribute) {
487 if (aAttribute.IsEmpty()) {
488 return nullptr; // Don't use nsGkAtoms::_empty for attribute.
490 return NS_GetStaticAtom(aAttribute);
494 * Helper method for deletion. When this returns true, Selection will be
495 * computed with nsFrameSelection that also requires flushed layout
496 * information.
498 template <typename SelectionOrAutoClonedRangeArray>
499 static bool IsFrameSelectionRequiredToExtendSelection(
500 nsIEditor::EDirection aDirectionAndAmount,
501 SelectionOrAutoClonedRangeArray& aSelectionOrAutoClonedRangeArray) {
502 switch (aDirectionAndAmount) {
503 case nsIEditor::eNextWord:
504 case nsIEditor::ePreviousWord:
505 case nsIEditor::eToBeginningOfLine:
506 case nsIEditor::eToEndOfLine:
507 return true;
508 case nsIEditor::ePrevious:
509 case nsIEditor::eNext:
510 return aSelectionOrAutoClonedRangeArray.IsCollapsed();
511 default:
512 return false;
517 * Create an nsITransferable instance which has kTextMime and
518 * kMozTextInternal flavors.
520 static Result<nsCOMPtr<nsITransferable>, nsresult>
521 CreateTransferableForPlainText(const dom::Document& aDocument);
524 } // namespace mozilla
526 #endif // #ifndef mozilla_EditorUtils_h