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/. */
5 
6 #ifndef mozilla_EditorUtils_h
7 #define mozilla_EditorUtils_h
8 
9 #include "mozilla/EditAction.h"
10 #include "mozilla/EditorBase.h"
11 #include "mozilla/EditorDOMPoint.h"
12 #include "mozilla/IntegerRange.h"
13 #include "mozilla/RangeBoundary.h"
14 #include "mozilla/Result.h"
15 #include "mozilla/dom/Element.h"
16 #include "mozilla/dom/HTMLBRElement.h"
17 #include "mozilla/dom/Selection.h"
18 #include "mozilla/dom/Text.h"
19 #include "nsAtom.h"
20 #include "nsCOMPtr.h"
21 #include "nsContentUtils.h"
22 #include "nsDebug.h"
23 #include "nsDirection.h"
24 #include "nsError.h"
25 #include "nsRange.h"
26 #include "nsString.h"
27 
28 class nsITransferable;
29 
30 namespace mozilla {
31 class MoveNodeResult;
32 template <class T>
33 class OwningNonNull;
34 
35 /***************************************************************************
36  * EditActionResult is useful to return multiple results of an editor
37  * action handler without out params.
38  * Note that when you return an anonymous instance from a method, you should
39  * use EditActionIgnored(), EditActionHandled() or EditActionCanceled() for
40  * easier to read.  In other words, EditActionResult should be used when
41  * declaring return type of a method, being an argument or defined as a local
42  * variable.
43  */
44 class MOZ_STACK_CLASS EditActionResult final {
45  public:
Succeeded()46   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()47   bool Failed() const { return NS_FAILED(mRv); }
Rv()48   nsresult Rv() const { return mRv; }
Canceled()49   bool Canceled() const { return mCanceled; }
Handled()50   bool Handled() const { return mHandled; }
Ignored()51   bool Ignored() const { return !mCanceled && !mHandled; }
EditorDestroyed()52   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
53 
SetResult(nsresult aRv)54   EditActionResult SetResult(nsresult aRv) {
55     mRv = aRv;
56     return *this;
57   }
MarkAsCanceled()58   EditActionResult MarkAsCanceled() {
59     mCanceled = true;
60     return *this;
61   }
MarkAsHandled()62   EditActionResult MarkAsHandled() {
63     mHandled = true;
64     return *this;
65   }
66 
EditActionResult(nsresult aRv)67   explicit EditActionResult(nsresult aRv)
68       : mRv(aRv), mCanceled(false), mHandled(false) {}
69 
70   EditActionResult& operator|=(const EditActionResult& aOther) {
71     mCanceled |= aOther.mCanceled;
72     mHandled |= aOther.mHandled;
73     // When both result are same, keep the result.
74     if (mRv == aOther.mRv) {
75       return *this;
76     }
77     // If one of the result is NS_ERROR_EDITOR_DESTROYED, use it since it's
78     // the most important error code for editor.
79     if (EditorDestroyed() || aOther.EditorDestroyed()) {
80       mRv = NS_ERROR_EDITOR_DESTROYED;
81     }
82     // If one of the results is error, use NS_ERROR_FAILURE.
83     else if (Failed() || aOther.Failed()) {
84       mRv = NS_ERROR_FAILURE;
85     } else {
86       // Otherwise, use generic success code, NS_OK.
87       mRv = NS_OK;
88     }
89     return *this;
90   }
91 
92   EditActionResult& operator|=(const MoveNodeResult& aMoveNodeResult);
93 
94  private:
95   nsresult mRv;
96   bool mCanceled;
97   bool mHandled;
98 
EditActionResult(nsresult aRv,bool aCanceled,bool aHandled)99   EditActionResult(nsresult aRv, bool aCanceled, bool aHandled)
100       : mRv(aRv), mCanceled(aCanceled), mHandled(aHandled) {}
101 
EditActionResult()102   EditActionResult()
103       : mRv(NS_ERROR_NOT_INITIALIZED), mCanceled(false), mHandled(false) {}
104 
105   friend EditActionResult EditActionIgnored(nsresult aRv);
106   friend EditActionResult EditActionHandled(nsresult aRv);
107   friend EditActionResult EditActionCanceled(nsresult aRv);
108 };
109 
110 /***************************************************************************
111  * When an edit action handler (or its helper) does nothing,
112  * EditActionIgnored should be returned.
113  */
114 inline EditActionResult EditActionIgnored(nsresult aRv = NS_OK) {
115   return EditActionResult(aRv, false, false);
116 }
117 
118 /***************************************************************************
119  * When an edit action handler (or its helper) handled and not canceled,
120  * EditActionHandled should be returned.
121  */
122 inline EditActionResult EditActionHandled(nsresult aRv = NS_OK) {
123   return EditActionResult(aRv, false, true);
124 }
125 
126 /***************************************************************************
127  * When an edit action handler (or its helper) handled and canceled,
128  * EditActionHandled should be returned.
129  */
130 inline EditActionResult EditActionCanceled(nsresult aRv = NS_OK) {
131   return EditActionResult(aRv, true, true);
132 }
133 
134 /***************************************************************************
135  * CreateNodeResultBase is a simple class for CreateSomething() methods
136  * which want to return new node.
137  */
138 template <typename NodeType>
139 class CreateNodeResultBase;
140 
141 typedef CreateNodeResultBase<dom::Element> CreateElementResult;
142 
143 template <typename NodeType>
144 class MOZ_STACK_CLASS CreateNodeResultBase final {
145   typedef CreateNodeResultBase<NodeType> SelfType;
146 
147  public:
Succeeded()148   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()149   bool Failed() const { return NS_FAILED(mRv); }
Rv()150   nsresult Rv() const { return mRv; }
EditorDestroyed()151   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
GetNewNode()152   NodeType* GetNewNode() const { return mNode; }
153 
154   CreateNodeResultBase() = delete;
155 
CreateNodeResultBase(nsresult aRv)156   explicit CreateNodeResultBase(nsresult aRv) : mRv(aRv) {
157     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
158   }
159 
CreateNodeResultBase(NodeType * aNode)160   explicit CreateNodeResultBase(NodeType* aNode)
161       : mNode(aNode), mRv(aNode ? NS_OK : NS_ERROR_FAILURE) {}
162 
CreateNodeResultBase(RefPtr<NodeType> && aNode)163   explicit CreateNodeResultBase(RefPtr<NodeType>&& aNode)
164       : mNode(std::move(aNode)), mRv(mNode.get() ? NS_OK : NS_ERROR_FAILURE) {}
165 
166   CreateNodeResultBase(const SelfType& aOther) = delete;
167   SelfType& operator=(const SelfType& aOther) = delete;
168   CreateNodeResultBase(SelfType&& aOther) = default;
169   SelfType& operator=(SelfType&& aOther) = default;
170 
forget()171   already_AddRefed<NodeType> forget() {
172     mRv = NS_ERROR_NOT_INITIALIZED;
173     return mNode.forget();
174   }
175 
176  private:
177   RefPtr<NodeType> mNode;
178   nsresult mRv;
179 };
180 
181 /***************************************************************************
182  * stack based helper class for calling EditorBase::EndTransaction() after
183  * EditorBase::BeginTransaction().  This shouldn't be used in editor classes
184  * or helper classes while an edit action is being handled.  Use
185  * AutoTransactionBatch in such cases since it uses non-virtual internal
186  * methods.
187  ***************************************************************************/
188 class MOZ_RAII AutoTransactionBatchExternal final {
189  public:
AutoTransactionBatchExternal(EditorBase & aEditorBase)190   MOZ_CAN_RUN_SCRIPT explicit AutoTransactionBatchExternal(
191       EditorBase& aEditorBase)
192       : mEditorBase(aEditorBase) {
193     MOZ_KnownLive(mEditorBase).BeginTransaction();
194   }
195 
~AutoTransactionBatchExternal()196   MOZ_CAN_RUN_SCRIPT ~AutoTransactionBatchExternal() {
197     MOZ_KnownLive(mEditorBase).EndTransaction();
198   }
199 
200  private:
201   EditorBase& mEditorBase;
202 };
203 
204 /******************************************************************************
205  * AutoSelectionRangeArray stores all ranges in `aSelection`.
206  * Note that modifying the ranges means modifing the selection ranges.
207  *****************************************************************************/
208 class MOZ_STACK_CLASS AutoSelectionRangeArray final {
209  public:
AutoSelectionRangeArray(dom::Selection & aSelection)210   explicit AutoSelectionRangeArray(dom::Selection& aSelection) {
211     for (const uint32_t i : IntegerRange(aSelection.RangeCount())) {
212       MOZ_ASSERT(aSelection.GetRangeAt(i));
213       mRanges.AppendElement(*aSelection.GetRangeAt(i));
214     }
215   }
216 
217   AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges;
218 };
219 
220 /******************************************************************************
221  * AutoRangeArray stores ranges which do no belong any `Selection`.
222  * So, different from `AutoSelectionRangeArray`, this can be used for
223  * ranges which may need to be modified before touching the DOM tree,
224  * but does not want to modify `Selection` for the performance.
225  *****************************************************************************/
226 class MOZ_STACK_CLASS AutoRangeArray final {
227  public:
AutoRangeArray(const dom::Selection & aSelection)228   explicit AutoRangeArray(const dom::Selection& aSelection) {
229     Initialize(aSelection);
230   }
231 
Initialize(const dom::Selection & aSelection)232   void Initialize(const dom::Selection& aSelection) {
233     mDirection = aSelection.GetDirection();
234     mRanges.Clear();
235     for (const uint32_t i : IntegerRange(aSelection.RangeCount())) {
236       MOZ_ASSERT(aSelection.GetRangeAt(i));
237       mRanges.AppendElement(aSelection.GetRangeAt(i)->CloneRange());
238       if (aSelection.GetRangeAt(i) == aSelection.GetAnchorFocusRange()) {
239         mAnchorFocusRange = mRanges.LastElement();
240       }
241     }
242   }
243 
244   /**
245    * EnsureOnlyEditableRanges() removes ranges which cannot modify.
246    * Note that this is designed only for `HTMLEditor` because this must not
247    * be required by `TextEditor`.
248    */
249   void EnsureOnlyEditableRanges(const dom::Element& aEditingHost);
250 
251   /**
252    * EnsureRangesInTextNode() is designed for TextEditor to guarantee that
253    * all ranges are in its text node which is first child of the anonymous <div>
254    * element and is first child.
255    */
256   void EnsureRangesInTextNode(const dom::Text& aTextNode);
257 
258   static bool IsEditableRange(const dom::AbstractRange& aRange,
259                               const dom::Element& aEditingHost);
260 
261   /**
262    * IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf() returns true
263    * if at least one of the containers of the range boundaries is an inclusive
264    * descendant of aContent.
265    */
IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(const nsIContent & aContent)266   bool IsAtLeastOneContainerOfRangeBoundariesInclusiveDescendantOf(
267       const nsIContent& aContent) const {
268     for (const OwningNonNull<nsRange>& range : mRanges) {
269       nsINode* startContainer = range->GetStartContainer();
270       if (startContainer &&
271           startContainer->IsInclusiveDescendantOf(&aContent)) {
272         return true;
273       }
274       nsINode* endContainer = range->GetEndContainer();
275       if (startContainer == endContainer) {
276         continue;
277       }
278       if (endContainer && endContainer->IsInclusiveDescendantOf(&aContent)) {
279         return true;
280       }
281     }
282     return false;
283   }
284 
Ranges()285   auto& Ranges() { return mRanges; }
Ranges()286   const auto& Ranges() const { return mRanges; }
FirstRangeRef()287   auto& FirstRangeRef() { return mRanges[0]; }
FirstRangeRef()288   const auto& FirstRangeRef() const { return mRanges[0]; }
289 
290   template <template <typename> typename StrongPtrType>
CloneRanges()291   AutoTArray<StrongPtrType<nsRange>, 8> CloneRanges() const {
292     AutoTArray<StrongPtrType<nsRange>, 8> ranges;
293     for (const auto& range : mRanges) {
294       ranges.AppendElement(range->CloneRange());
295     }
296     return ranges;
297   }
298 
GetStartPointOfFirstRange()299   EditorDOMPoint GetStartPointOfFirstRange() const {
300     if (mRanges.IsEmpty() || !mRanges[0]->IsPositioned()) {
301       return EditorDOMPoint();
302     }
303     return EditorDOMPoint(mRanges[0]->StartRef());
304   }
GetEndPointOfFirstRange()305   EditorDOMPoint GetEndPointOfFirstRange() const {
306     if (mRanges.IsEmpty() || !mRanges[0]->IsPositioned()) {
307       return EditorDOMPoint();
308     }
309     return EditorDOMPoint(mRanges[0]->EndRef());
310   }
311 
SelectNode(nsINode & aNode)312   nsresult SelectNode(nsINode& aNode) {
313     mRanges.Clear();
314     if (!mAnchorFocusRange) {
315       mAnchorFocusRange = nsRange::Create(&aNode);
316       if (!mAnchorFocusRange) {
317         return NS_ERROR_FAILURE;
318       }
319     }
320     ErrorResult error;
321     mAnchorFocusRange->SelectNode(aNode, error);
322     if (error.Failed()) {
323       mAnchorFocusRange = nullptr;
324       return error.StealNSResult();
325     }
326     mRanges.AppendElement(*mAnchorFocusRange);
327     return NS_OK;
328   }
329 
330   /**
331    * ExtendAnchorFocusRangeFor() extends the anchor-focus range for deleting
332    * content for aDirectionAndAmount.  The range won't be extended to outer of
333    * selection limiter.  Note that if a range is extened, the range is
334    * recreated.  Therefore, caller cannot cache pointer of any ranges before
335    * calling this.
336    */
337   [[nodiscard]] MOZ_CAN_RUN_SCRIPT Result<nsIEditor::EDirection, nsresult>
338   ExtendAnchorFocusRangeFor(const EditorBase& aEditorBase,
339                             nsIEditor::EDirection aDirectionAndAmount);
340 
341   /**
342    * For compatiblity with the other browsers, we should shrink ranges to
343    * start from an atomic content and/or end after one instead of start
344    * from end of a preceding text node and end by start of a follwing text
345    * node.  Returns true if this modifies a range.
346    */
347   enum class IfSelectingOnlyOneAtomicContent {
348     Collapse,  // Collapse to the range selecting only one atomic content to
349                // start or after of it.  Whether to collapse start or after
350                // it depends on aDirectionAndAmount.  This is ignored if
351                // there are multiple ranges.
352     KeepSelecting,  // Won't collapse the range.
353   };
354   Result<bool, nsresult> ShrinkRangesIfStartFromOrEndAfterAtomicContent(
355       const HTMLEditor& aHTMLEditor, nsIEditor::EDirection aDirectionAndAmount,
356       IfSelectingOnlyOneAtomicContent aIfSelectingOnlyOneAtomicContent,
357       const dom::Element* aEditingHost);
358 
359   /**
360    * The following methods are same as `Selection`'s methods.
361    */
IsCollapsed()362   bool IsCollapsed() const {
363     return mRanges.IsEmpty() ||
364            (mRanges.Length() == 1 && mRanges[0]->Collapsed());
365   }
366   template <typename PT, typename CT>
Collapse(const EditorDOMPointBase<PT,CT> & aPoint)367   nsresult Collapse(const EditorDOMPointBase<PT, CT>& aPoint) {
368     mRanges.Clear();
369     if (!mAnchorFocusRange) {
370       ErrorResult error;
371       mAnchorFocusRange = nsRange::Create(aPoint.ToRawRangeBoundary(),
372                                           aPoint.ToRawRangeBoundary(), error);
373       if (error.Failed()) {
374         mAnchorFocusRange = nullptr;
375         return error.StealNSResult();
376       }
377     } else {
378       nsresult rv = mAnchorFocusRange->CollapseTo(aPoint.ToRawRangeBoundary());
379       if (NS_FAILED(rv)) {
380         mAnchorFocusRange = nullptr;
381         return rv;
382       }
383     }
384     mRanges.AppendElement(*mAnchorFocusRange);
385     return NS_OK;
386   }
387   template <typename SPT, typename SCT, typename EPT, typename ECT>
SetStartAndEnd(const EditorDOMPointBase<SPT,SCT> & aStart,const EditorDOMPointBase<EPT,ECT> & aEnd)388   nsresult SetStartAndEnd(const EditorDOMPointBase<SPT, SCT>& aStart,
389                           const EditorDOMPointBase<EPT, ECT>& aEnd) {
390     mRanges.Clear();
391     if (!mAnchorFocusRange) {
392       ErrorResult error;
393       mAnchorFocusRange = nsRange::Create(aStart.ToRawRangeBoundary(),
394                                           aEnd.ToRawRangeBoundary(), error);
395       if (error.Failed()) {
396         mAnchorFocusRange = nullptr;
397         return error.StealNSResult();
398       }
399     } else {
400       nsresult rv = mAnchorFocusRange->SetStartAndEnd(
401           aStart.ToRawRangeBoundary(), aEnd.ToRawRangeBoundary());
402       if (NS_FAILED(rv)) {
403         mAnchorFocusRange = nullptr;
404         return rv;
405       }
406     }
407     mRanges.AppendElement(*mAnchorFocusRange);
408     return NS_OK;
409   }
GetAnchorFocusRange()410   const nsRange* GetAnchorFocusRange() const { return mAnchorFocusRange; }
GetDirection()411   nsDirection GetDirection() const { return mDirection; }
412 
AnchorRef()413   const RangeBoundary& AnchorRef() const {
414     if (!mAnchorFocusRange) {
415       static RangeBoundary sEmptyRangeBoundary;
416       return sEmptyRangeBoundary;
417     }
418     return mDirection == nsDirection::eDirNext ? mAnchorFocusRange->StartRef()
419                                                : mAnchorFocusRange->EndRef();
420   }
GetAnchorNode()421   nsINode* GetAnchorNode() const {
422     return AnchorRef().IsSet() ? AnchorRef().Container() : nullptr;
423   }
GetAnchorOffset()424   uint32_t GetAnchorOffset() const {
425     return AnchorRef().IsSet()
426                ? AnchorRef()
427                      .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
428                      .valueOr(0)
429                : 0;
430   }
GetChildAtAnchorOffset()431   nsIContent* GetChildAtAnchorOffset() const {
432     return AnchorRef().IsSet() ? AnchorRef().GetChildAtOffset() : nullptr;
433   }
434 
FocusRef()435   const RangeBoundary& FocusRef() const {
436     if (!mAnchorFocusRange) {
437       static RangeBoundary sEmptyRangeBoundary;
438       return sEmptyRangeBoundary;
439     }
440     return mDirection == nsDirection::eDirNext ? mAnchorFocusRange->EndRef()
441                                                : mAnchorFocusRange->StartRef();
442   }
GetFocusNode()443   nsINode* GetFocusNode() const {
444     return FocusRef().IsSet() ? FocusRef().Container() : nullptr;
445   }
FocusOffset()446   uint32_t FocusOffset() const {
447     return FocusRef().IsSet()
448                ? FocusRef()
449                      .Offset(RangeBoundary::OffsetFilter::kValidOffsets)
450                      .valueOr(0)
451                : 0;
452   }
GetChildAtFocusOffset()453   nsIContent* GetChildAtFocusOffset() const {
454     return FocusRef().IsSet() ? FocusRef().GetChildAtOffset() : nullptr;
455   }
456 
RemoveAllRanges()457   void RemoveAllRanges() {
458     mRanges.Clear();
459     mAnchorFocusRange = nullptr;
460     mDirection = nsDirection::eDirNext;
461   }
462 
463  private:
464   AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges;
465   RefPtr<nsRange> mAnchorFocusRange;
466   nsDirection mDirection = nsDirection::eDirNext;
467 };
468 
469 class EditorUtils final {
470  public:
471   using EditorType = EditorBase::EditorType;
472   using Selection = dom::Selection;
473 
474   /**
475    * IsDescendantOf() checks if aNode is a child or a descendant of aParent.
476    * aOutPoint is set to the child of aParent.
477    *
478    * @return            true if aNode is a child or a descendant of aParent.
479    */
480   static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
481                              EditorRawDOMPoint* aOutPoint = nullptr);
482   static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent,
483                              EditorDOMPoint* aOutPoint);
484 
485   /**
486    * Returns true if aContent is a <br> element and it's marked as padding for
487    * empty editor.
488    */
IsPaddingBRElementForEmptyEditor(const nsIContent & aContent)489   static bool IsPaddingBRElementForEmptyEditor(const nsIContent& aContent) {
490     const dom::HTMLBRElement* brElement =
491         dom::HTMLBRElement::FromNode(&aContent);
492     return brElement && brElement->IsPaddingForEmptyEditor();
493   }
494 
495   /**
496    * Returns true if aContent is a <br> element and it's marked as padding for
497    * empty last line.
498    */
IsPaddingBRElementForEmptyLastLine(const nsIContent & aContent)499   static bool IsPaddingBRElementForEmptyLastLine(const nsIContent& aContent) {
500     const dom::HTMLBRElement* brElement =
501         dom::HTMLBRElement::FromNode(&aContent);
502     return brElement && brElement->IsPaddingForEmptyLastLine();
503   }
504 
505   /**
506    * IsEditableContent() returns true if aContent's data or children is ediable
507    * for the given editor type.  Be aware, returning true does NOT mean the
508    * node can be removed from its parent node, and returning false does NOT
509    * mean the node cannot be removed from the parent node.
510    * XXX May be the anonymous nodes in TextEditor not editable?  If it's not
511    *     so, we can get rid of aEditorType.
512    */
IsEditableContent(const nsIContent & aContent,EditorType aEditorType)513   static bool IsEditableContent(const nsIContent& aContent,
514                                 EditorType aEditorType) {
515     if (aEditorType == EditorType::HTML &&
516         (!aContent.IsEditable() || !aContent.IsInComposedDoc())) {
517       // FIXME(emilio): Why only for HTML editors? All content from the root
518       // content in text editors is also editable, so afaict we can remove the
519       // special-case.
520       return false;
521     }
522     return IsElementOrText(aContent);
523   }
524 
525   /**
526    * Returns true if aContent is a usual element node (not padding <br> element
527    * for empty editor) or a text node.  In other words, returns true if
528    * aContent is a usual element node or visible data node.
529    */
IsElementOrText(const nsIContent & aContent)530   static bool IsElementOrText(const nsIContent& aContent) {
531     if (aContent.IsText()) {
532       return true;
533     }
534     return aContent.IsElement() && !IsPaddingBRElementForEmptyEditor(aContent);
535   }
536 
537   /**
538    * IsWhiteSpacePreformatted() checks the style info for the node for the
539    * preformatted text style.  This does NOT flush layout.
540    */
541   static bool IsWhiteSpacePreformatted(const nsIContent& aContent);
542 
543   /**
544    * IsNewLinePreformatted() checks whether the linefeed characters are
545    * preformatted or collapsible white-spaces.  This does NOT flush layout.
546    */
547   static bool IsNewLinePreformatted(const nsIContent& aContent);
548 
549   /**
550    * IsOnlyNewLinePreformatted() checks whether the linefeed characters are
551    * preformated but white-spaces are collapsed, or otherwise.  I.e., this
552    * returns true only when `white-space:pre-line`.
553    */
554   static bool IsOnlyNewLinePreformatted(const nsIContent& aContent);
555 
556   /**
557    * Helper method for `AppendString()` and `AppendSubString()`.  This should
558    * be called only when `aText` is in a password field.  This method masks
559    * A part of or all of `aText` (`aStartOffsetInText` and later) should've
560    * been copied (apppended) to `aString`.  `aStartOffsetInString` is where
561    * the password was appended into `aString`.
562    */
563   static void MaskString(nsString& aString, const dom::Text& aTextNode,
564                          uint32_t aStartOffsetInString,
565                          uint32_t aStartOffsetInText);
566 
GetTagNameAtom(const nsAString & aTagName)567   static nsStaticAtom* GetTagNameAtom(const nsAString& aTagName) {
568     if (aTagName.IsEmpty()) {
569       return nullptr;
570     }
571     nsAutoString lowerTagName;
572     nsContentUtils::ASCIIToLower(aTagName, lowerTagName);
573     return NS_GetStaticAtom(lowerTagName);
574   }
575 
GetAttributeAtom(const nsAString & aAttribute)576   static nsStaticAtom* GetAttributeAtom(const nsAString& aAttribute) {
577     if (aAttribute.IsEmpty()) {
578       return nullptr;  // Don't use nsGkAtoms::_empty for attribute.
579     }
580     return NS_GetStaticAtom(aAttribute);
581   }
582 
583   /**
584    * Helper method for deletion.  When this returns true, Selection will be
585    * computed with nsFrameSelection that also requires flushed layout
586    * information.
587    */
588   template <typename SelectionOrAutoRangeArray>
IsFrameSelectionRequiredToExtendSelection(nsIEditor::EDirection aDirectionAndAmount,SelectionOrAutoRangeArray & aSelectionOrAutoRangeArray)589   static bool IsFrameSelectionRequiredToExtendSelection(
590       nsIEditor::EDirection aDirectionAndAmount,
591       SelectionOrAutoRangeArray& aSelectionOrAutoRangeArray) {
592     switch (aDirectionAndAmount) {
593       case nsIEditor::eNextWord:
594       case nsIEditor::ePreviousWord:
595       case nsIEditor::eToBeginningOfLine:
596       case nsIEditor::eToEndOfLine:
597         return true;
598       case nsIEditor::ePrevious:
599       case nsIEditor::eNext:
600         return aSelectionOrAutoRangeArray.IsCollapsed();
601       default:
602         return false;
603     }
604   }
605 
606   /**
607    * Returns true if aSelection includes the point in aParentContent.
608    */
609   static bool IsPointInSelection(const Selection& aSelection,
610                                  const nsINode& aParentNode, uint32_t aOffset);
611 
612   /**
613    * Create an nsITransferable instance which has kUnicodeMime and
614    * kMozTextInternal flavors.
615    */
616   static Result<nsCOMPtr<nsITransferable>, nsresult>
617   CreateTransferableForPlainText(const dom::Document& aDocument);
618 };
619 
620 }  // namespace mozilla
621 
622 #endif  // #ifndef mozilla_EditorUtils_h
623