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_HTMLEditHelpers_h
7 #define mozilla_HTMLEditHelpers_h
8 
9 /**
10  * This header declares/defines trivial helper classes which are used by
11  * HTMLEditor.  If you want to create or look for static utility methods,
12  * see HTMLEditUtils.h.
13  */
14 
15 #include "mozilla/AlreadyAddRefed.h"
16 #include "mozilla/Attributes.h"
17 #include "mozilla/ContentIterator.h"
18 #include "mozilla/EditorDOMPoint.h"
19 #include "mozilla/IntegerRange.h"
20 #include "mozilla/RangeBoundary.h"
21 #include "mozilla/dom/Element.h"
22 #include "mozilla/dom/StaticRange.h"
23 #include "nsCOMPtr.h"
24 #include "nsDebug.h"
25 #include "nsError.h"
26 #include "nsIContent.h"
27 #include "nsRange.h"
28 #include "nsString.h"
29 
30 class nsISimpleEnumerator;
31 
32 namespace mozilla {
33 template <class T>
34 class OwningNonNull;
35 
36 // JoinNodesDirection is also affected to which one is new node at splitting
37 // a node because a couple of undo/redo.
38 enum class JoinNodesDirection {
39   LeftNodeIntoRightNode,
40   RightNodeIntoLeftNode,
41 };
42 // SplitNodeDirection is also affected to which one is removed at joining a
43 // node because a couple of undo/redo.
44 enum class SplitNodeDirection {
45   LeftNodeIsNewOne,
46   RightNodeIsNewOne,
47 };
48 
49 /*****************************************************************************
50  * EditResult returns nsresult and preferred point where selection should be
51  * collapsed or the range where selection should select.
52  *
53  * NOTE: If we stop modifying selection at every DOM tree change, perhaps,
54  *       the following classes need to inherit this class.
55  *****************************************************************************/
56 class MOZ_STACK_CLASS EditResult final {
57  public:
Succeeded()58   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()59   bool Failed() const { return NS_FAILED(mRv); }
Rv()60   nsresult Rv() const { return mRv; }
EditorDestroyed()61   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
PointRefToCollapseSelection()62   const EditorDOMPoint& PointRefToCollapseSelection() const {
63     MOZ_DIAGNOSTIC_ASSERT(mStartPoint.IsSet());
64     MOZ_DIAGNOSTIC_ASSERT(mStartPoint == mEndPoint);
65     return mStartPoint;
66   }
StartPointRef()67   const EditorDOMPoint& StartPointRef() const { return mStartPoint; }
EndPointRef()68   const EditorDOMPoint& EndPointRef() const { return mEndPoint; }
CreateStaticRange()69   already_AddRefed<dom::StaticRange> CreateStaticRange() const {
70     return dom::StaticRange::Create(mStartPoint.ToRawRangeBoundary(),
71                                     mEndPoint.ToRawRangeBoundary(),
72                                     IgnoreErrors());
73   }
CreateRange()74   already_AddRefed<nsRange> CreateRange() const {
75     return nsRange::Create(mStartPoint.ToRawRangeBoundary(),
76                            mEndPoint.ToRawRangeBoundary(), IgnoreErrors());
77   }
78 
79   EditResult() = delete;
EditResult(nsresult aRv)80   explicit EditResult(nsresult aRv) : mRv(aRv) {
81     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
82   }
83   template <typename PT, typename CT>
EditResult(const EditorDOMPointBase<PT,CT> & aPointToPutCaret)84   explicit EditResult(const EditorDOMPointBase<PT, CT>& aPointToPutCaret)
85       : mRv(aPointToPutCaret.IsSet() ? NS_OK : NS_ERROR_FAILURE),
86         mStartPoint(aPointToPutCaret),
87         mEndPoint(aPointToPutCaret) {}
88 
89   template <typename SPT, typename SCT, typename EPT, typename ECT>
EditResult(const EditorDOMPointBase<SPT,SCT> & aStartPoint,const EditorDOMPointBase<EPT,ECT> & aEndPoint)90   EditResult(const EditorDOMPointBase<SPT, SCT>& aStartPoint,
91              const EditorDOMPointBase<EPT, ECT>& aEndPoint)
92       : mRv(aStartPoint.IsSet() && aEndPoint.IsSet() ? NS_OK
93                                                      : NS_ERROR_FAILURE),
94         mStartPoint(aStartPoint),
95         mEndPoint(aEndPoint) {}
96 
97   EditResult(const EditResult& aOther) = delete;
98   EditResult& operator=(const EditResult& aOther) = delete;
99   EditResult(EditResult&& aOther) = default;
100   EditResult& operator=(EditResult&& aOther) = default;
101 
102  private:
103   nsresult mRv;
104   EditorDOMPoint mStartPoint;
105   EditorDOMPoint mEndPoint;
106 };
107 
108 /*****************************************************************************
109  * MoveNodeResult is a simple class for MoveSomething() methods.
110  * This holds error code and next insertion point if moving contents succeeded.
111  *****************************************************************************/
112 class MOZ_STACK_CLASS MoveNodeResult final {
113  public:
Succeeded()114   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()115   bool Failed() const { return NS_FAILED(mRv); }
Handled()116   bool Handled() const { return mHandled; }
Ignored()117   bool Ignored() const { return !mHandled; }
Rv()118   nsresult Rv() const { return mRv; }
EditorDestroyed()119   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
NextInsertionPointRef()120   const EditorDOMPoint& NextInsertionPointRef() const {
121     return mNextInsertionPoint;
122   }
NextInsertionPoint()123   EditorDOMPoint NextInsertionPoint() const { return mNextInsertionPoint; }
124 
MarkAsHandled()125   void MarkAsHandled() { mHandled = true; }
126 
MoveNodeResult()127   MoveNodeResult() : mRv(NS_ERROR_NOT_INITIALIZED), mHandled(false) {}
128 
MoveNodeResult(nsresult aRv)129   explicit MoveNodeResult(nsresult aRv) : mRv(aRv), mHandled(false) {
130     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
131   }
132 
133   MoveNodeResult(const MoveNodeResult& aOther) = delete;
134   MoveNodeResult& operator=(const MoveNodeResult& aOther) = delete;
135   MoveNodeResult(MoveNodeResult&& aOther) = default;
136   MoveNodeResult& operator=(MoveNodeResult&& aOther) = default;
137 
138   MoveNodeResult& operator|=(const MoveNodeResult& aOther) {
139     mHandled |= aOther.mHandled;
140     // When both result are same, keep the result but use newer point.
141     if (mRv == aOther.mRv) {
142       mNextInsertionPoint = aOther.mNextInsertionPoint;
143       return *this;
144     }
145     // If one of the result is NS_ERROR_EDITOR_DESTROYED, use it since it's
146     // the most important error code for editor.
147     if (EditorDestroyed() || aOther.EditorDestroyed()) {
148       mRv = NS_ERROR_EDITOR_DESTROYED;
149       mNextInsertionPoint.Clear();
150       return *this;
151     }
152     // If the other one has not been set explicit nsresult, keep current
153     // value.
154     if (aOther.mRv == NS_ERROR_NOT_INITIALIZED) {
155       return *this;
156     }
157     // If this one has not been set explicit nsresult, copy the other one's.
158     if (mRv == NS_ERROR_NOT_INITIALIZED) {
159       mRv = aOther.mRv;
160       mNextInsertionPoint = aOther.mNextInsertionPoint;
161       return *this;
162     }
163     // If one of the results is error, use NS_ERROR_FAILURE.
164     if (Failed() || aOther.Failed()) {
165       mRv = NS_ERROR_FAILURE;
166       mNextInsertionPoint.Clear();
167       return *this;
168     }
169     // Otherwise, use generic success code, NS_OK, and use newer point.
170     mRv = NS_OK;
171     mNextInsertionPoint = aOther.mNextInsertionPoint;
172     return *this;
173   }
174 
175  private:
176   template <typename PT, typename CT>
MoveNodeResult(const EditorDOMPointBase<PT,CT> & aNextInsertionPoint,bool aHandled)177   explicit MoveNodeResult(const EditorDOMPointBase<PT, CT>& aNextInsertionPoint,
178                           bool aHandled)
179       : mNextInsertionPoint(aNextInsertionPoint),
180         mRv(aNextInsertionPoint.IsSet() ? NS_OK : NS_ERROR_FAILURE),
181         mHandled(aHandled && aNextInsertionPoint.IsSet()) {
182     if (mNextInsertionPoint.IsSet()) {
183       AutoEditorDOMPointChildInvalidator computeOffsetAndForgetChild(
184           mNextInsertionPoint);
185     }
186   }
187 
MoveNodeResult(nsINode * aParentNode,uint32_t aOffsetOfNextInsertionPoint,bool aHandled)188   MoveNodeResult(nsINode* aParentNode, uint32_t aOffsetOfNextInsertionPoint,
189                  bool aHandled) {
190     if (!aParentNode) {
191       mRv = NS_ERROR_FAILURE;
192       mHandled = false;
193       return;
194     }
195     aOffsetOfNextInsertionPoint =
196         std::min(aOffsetOfNextInsertionPoint, aParentNode->Length());
197     mNextInsertionPoint.Set(aParentNode, aOffsetOfNextInsertionPoint);
198     mRv = mNextInsertionPoint.IsSet() ? NS_OK : NS_ERROR_FAILURE;
199     mHandled = aHandled && mNextInsertionPoint.IsSet();
200   }
201 
202   EditorDOMPoint mNextInsertionPoint;
203   nsresult mRv;
204   bool mHandled;
205 
206   friend MoveNodeResult MoveNodeIgnored(nsINode* aParentNode,
207                                         uint32_t aOffsetOfNextInsertionPoint);
208   friend MoveNodeResult MoveNodeHandled(nsINode* aParentNode,
209                                         uint32_t aOffsetOfNextInsertionPoint);
210   template <typename PT, typename CT>
211   friend MoveNodeResult MoveNodeIgnored(
212       const EditorDOMPointBase<PT, CT>& aNextInsertionPoint);
213   template <typename PT, typename CT>
214   friend MoveNodeResult MoveNodeHandled(
215       const EditorDOMPointBase<PT, CT>& aNextInsertionPoint);
216 };
217 
218 /*****************************************************************************
219  * When a move node handler (or its helper) does nothing,
220  * MoveNodeIgnored should be returned.
221  *****************************************************************************/
MoveNodeIgnored(nsINode * aParentNode,uint32_t aOffsetOfNextInsertionPoint)222 inline MoveNodeResult MoveNodeIgnored(nsINode* aParentNode,
223                                       uint32_t aOffsetOfNextInsertionPoint) {
224   return MoveNodeResult(aParentNode, aOffsetOfNextInsertionPoint, false);
225 }
226 
227 template <typename PT, typename CT>
MoveNodeIgnored(const EditorDOMPointBase<PT,CT> & aNextInsertionPoint)228 inline MoveNodeResult MoveNodeIgnored(
229     const EditorDOMPointBase<PT, CT>& aNextInsertionPoint) {
230   return MoveNodeResult(aNextInsertionPoint, false);
231 }
232 
233 /*****************************************************************************
234  * When a move node handler (or its helper) handled and not canceled,
235  * MoveNodeHandled should be returned.
236  *****************************************************************************/
MoveNodeHandled(nsINode * aParentNode,uint32_t aOffsetOfNextInsertionPoint)237 inline MoveNodeResult MoveNodeHandled(nsINode* aParentNode,
238                                       uint32_t aOffsetOfNextInsertionPoint) {
239   return MoveNodeResult(aParentNode, aOffsetOfNextInsertionPoint, true);
240 }
241 
242 template <typename PT, typename CT>
MoveNodeHandled(const EditorDOMPointBase<PT,CT> & aNextInsertionPoint)243 inline MoveNodeResult MoveNodeHandled(
244     const EditorDOMPointBase<PT, CT>& aNextInsertionPoint) {
245   return MoveNodeResult(aNextInsertionPoint, true);
246 }
247 
248 /*****************************************************************************
249  * SplitNodeResult is a simple class for
250  * HTMLEditor::SplitNodeDeepWithTransaction().
251  * This makes the callers' code easier to read.
252  *****************************************************************************/
253 class MOZ_STACK_CLASS SplitNodeResult final {
254  public:
Succeeded()255   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()256   bool Failed() const { return NS_FAILED(mRv); }
Rv()257   nsresult Rv() const { return mRv; }
Handled()258   bool Handled() const { return mPreviousNode || mNextNode; }
EditorDestroyed()259   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
260 
261   /**
262    * DidSplit() returns true if a node was actually split.
263    */
DidSplit()264   bool DidSplit() const { return mPreviousNode && mNextNode; }
265 
266   /**
267    * GetPreviousContent() returns previous content node at the split point.
268    */
GetPreviousContent()269   MOZ_KNOWN_LIVE nsIContent* GetPreviousContent() const {
270     MOZ_ASSERT(Succeeded());
271     if (mGivenSplitPoint.IsSet()) {
272       return mGivenSplitPoint.IsEndOfContainer() ? mGivenSplitPoint.GetChild()
273                                                  : nullptr;
274     }
275     return mPreviousNode;
276   }
277   template <typename EditorDOMPointType>
AtPreviousContent()278   EditorDOMPointType AtPreviousContent() const {
279     if (nsIContent* previousContent = GetPreviousContent()) {
280       return EditorDOMPointType(previousContent);
281     }
282     return EditorDOMPointType();
283   }
284 
285   /**
286    * GetNextContent() returns next content node at the split point.
287    */
GetNextContent()288   MOZ_KNOWN_LIVE nsIContent* GetNextContent() const {
289     MOZ_ASSERT(Succeeded());
290     if (mGivenSplitPoint.IsSet()) {
291       return !mGivenSplitPoint.IsEndOfContainer() ? mGivenSplitPoint.GetChild()
292                                                   : nullptr;
293     }
294     return mNextNode;
295   }
296   template <typename EditorDOMPointType>
AtNextContent()297   EditorDOMPointType AtNextContent() const {
298     if (nsIContent* nextContent = GetNextContent()) {
299       return EditorDOMPointType(nextContent);
300     }
301     return EditorDOMPointType();
302   }
303 
304   /**
305    * Returns new content node which is created at splitting a node.  I.e., this
306    * returns nullptr if no node was split.
307    */
GetNewContent()308   MOZ_KNOWN_LIVE nsIContent* GetNewContent() const {
309     MOZ_ASSERT(Succeeded());
310     if (!DidSplit()) {
311       return nullptr;
312     }
313     return mDirection == SplitNodeDirection::LeftNodeIsNewOne ? mPreviousNode
314                                                               : mNextNode;
315   }
316   template <typename EditorDOMPointType>
AtNewContent()317   EditorDOMPointType AtNewContent() const {
318     if (nsIContent* newContent = GetNewContent()) {
319       return EditorDOMPointType(newContent);
320     }
321     return EditorDOMPointType();
322   }
323 
324   /**
325    * Returns original content node which is (or is just tried to be) split.
326    */
GetOriginalContent()327   MOZ_KNOWN_LIVE nsIContent* GetOriginalContent() const {
328     MOZ_ASSERT(Succeeded());
329     if (mGivenSplitPoint.IsSet()) {
330       return mGivenSplitPoint.GetChild();
331     }
332     if (mDirection == SplitNodeDirection::LeftNodeIsNewOne) {
333       return mNextNode ? mNextNode : mPreviousNode;
334     }
335     return mPreviousNode ? mPreviousNode : mNextNode;
336   }
337   template <typename EditorDOMPointType>
AtOriginalContent()338   EditorDOMPointType AtOriginalContent() const {
339     if (nsIContent* originalContent = GetOriginalContent()) {
340       return EditorDOMPointType(originalContent);
341     }
342     return EditorDOMPointType();
343   }
344 
345   /**
346    * AtSplitPoint() returns the split point in the container.
347    * HTMLEditor::CreateAndInsertElementWithTransaction() or something similar
348    * methods.
349    */
350   template <typename EditorDOMPointType>
AtSplitPoint()351   EditorDOMPointType AtSplitPoint() const {
352     if (Failed()) {
353       return EditorDOMPointType();
354     }
355     if (mGivenSplitPoint.IsSet()) {
356       return EditorDOMPointType(mGivenSplitPoint);
357     }
358     if (!mPreviousNode) {
359       return EditorDOMPointType(mNextNode);
360     }
361     return EditorDOMPointType::After(mPreviousNode);
362   }
363 
364   /**
365    * This constructor shouldn't be used by anybody except methods which
366    * use this as result when it succeeds.
367    *
368    * @param aPreviousNodeOfSplitPoint   Previous node immediately before
369    *                                    split point.
370    * @param aNextNodeOfSplitPoint       Next node immediately after split
371    *                                    point.
372    * @param aDirection                  The split direction which the HTML
373    *                                    editor tried to split a node with.
374    */
SplitNodeResult(nsIContent * aPreviousNodeOfSplitPoint,nsIContent * aNextNodeOfSplitPoint,SplitNodeDirection aDirection)375   SplitNodeResult(nsIContent* aPreviousNodeOfSplitPoint,
376                   nsIContent* aNextNodeOfSplitPoint,
377                   SplitNodeDirection aDirection)
378       : mPreviousNode(aPreviousNodeOfSplitPoint),
379         mNextNode(aNextNodeOfSplitPoint),
380         mRv(NS_OK),
381         mDirection(aDirection) {
382     MOZ_DIAGNOSTIC_ASSERT(mPreviousNode || mNextNode);
383   }
SplitNodeResult(nsCOMPtr<nsIContent> && aPreviousNodeOfSplitPoint,nsIContent * aNextNodeOfSplitPoint,SplitNodeDirection aDirection)384   SplitNodeResult(nsCOMPtr<nsIContent>&& aPreviousNodeOfSplitPoint,
385                   nsIContent* aNextNodeOfSplitPoint,
386                   SplitNodeDirection aDirection)
387       : mPreviousNode(std::move(aPreviousNodeOfSplitPoint)),
388         mNextNode(aNextNodeOfSplitPoint),
389         mRv(NS_OK),
390         mDirection(aDirection) {
391     MOZ_DIAGNOSTIC_ASSERT(mPreviousNode || mNextNode);
392   }
SplitNodeResult(nsIContent * aPreviousNodeOfSplitPoint,nsCOMPtr<nsIContent> && aNextNodeOfSplitPoint,SplitNodeDirection aDirection)393   SplitNodeResult(nsIContent* aPreviousNodeOfSplitPoint,
394                   nsCOMPtr<nsIContent>&& aNextNodeOfSplitPoint,
395                   SplitNodeDirection aDirection)
396       : mPreviousNode(aPreviousNodeOfSplitPoint),
397         mNextNode(std::move(aNextNodeOfSplitPoint)),
398         mRv(NS_OK),
399         mDirection(aDirection) {
400     MOZ_DIAGNOSTIC_ASSERT(mPreviousNode || mNextNode);
401   }
SplitNodeResult(nsCOMPtr<nsIContent> && aPreviousNodeOfSplitPoint,nsCOMPtr<nsIContent> && aNextNodeOfSplitPoint,SplitNodeDirection aDirection)402   SplitNodeResult(nsCOMPtr<nsIContent>&& aPreviousNodeOfSplitPoint,
403                   nsCOMPtr<nsIContent>&& aNextNodeOfSplitPoint,
404                   SplitNodeDirection aDirection)
405       : mPreviousNode(std::move(aPreviousNodeOfSplitPoint)),
406         mNextNode(std::move(aNextNodeOfSplitPoint)),
407         mRv(NS_OK),
408         mDirection(aDirection) {
409     MOZ_DIAGNOSTIC_ASSERT(mPreviousNode || mNextNode);
410   }
411 
412   /**
413    * This constructor should be used when the method didn't split any nodes
414    * but want to return given split point as right point.
415    */
SplitNodeResult(const EditorRawDOMPoint & aGivenSplitPoint)416   explicit SplitNodeResult(const EditorRawDOMPoint& aGivenSplitPoint)
417       : mGivenSplitPoint(aGivenSplitPoint),
418         mRv(NS_OK),
419         mDirection(SplitNodeDirection::LeftNodeIsNewOne) {
420     MOZ_DIAGNOSTIC_ASSERT(mGivenSplitPoint.IsSet());
421   }
422 
423   /**
424    * This constructor shouldn't be used by anybody except methods which
425    * use this as error result when it fails.
426    */
SplitNodeResult(nsresult aRv)427   explicit SplitNodeResult(nsresult aRv)
428       : mRv(aRv), mDirection(SplitNodeDirection::LeftNodeIsNewOne) {
429     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
430   }
431 
432  private:
433   // When methods which return this class split some nodes actually, they
434   // need to set a set of left node and right node to this class.  However,
435   // one or both of them may be moved or removed by mutation observer.
436   // In such case, we cannot represent the point with EditorDOMPoint since
437   // it requires current container node.  Therefore, we need to use
438   // nsCOMPtr<nsIContent> here instead.
439   nsCOMPtr<nsIContent> mPreviousNode;
440   nsCOMPtr<nsIContent> mNextNode;
441 
442   // Methods which return this class may not split any nodes actually.  Then,
443   // they may want to return given split point as is since such behavior makes
444   // their callers simpler.  In this case, the point may be in a text node
445   // which cannot be represented as a node.  Therefore, we need EditorDOMPoint
446   // for representing the point.
447   EditorDOMPoint mGivenSplitPoint;
448 
449   nsresult mRv;
450   SplitNodeDirection mDirection;
451 
452   SplitNodeResult() = delete;
453 };
454 
455 /*****************************************************************************
456  * JoinNodesResult is a simple class for HTMLEditor::JoinNodesWithTransaction().
457  * This makes the callers' code easier to read.
458  *****************************************************************************/
459 class MOZ_STACK_CLASS JoinNodesResult final {
460  public:
Succeeded()461   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()462   bool Failed() const { return NS_FAILED(mRv); }
Rv()463   nsresult Rv() const { return mRv; }
Handled()464   bool Handled() const { return Succeeded(); }
EditorDestroyed()465   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
466 
ExistingContent()467   MOZ_KNOWN_LIVE nsIContent* ExistingContent() const {
468     MOZ_ASSERT(Succeeded());
469     return mJoinedPoint.ContainerAsContent();
470   }
471   template <typename EditorDOMPointType>
AtExistingContent()472   EditorDOMPointType AtExistingContent() const {
473     MOZ_ASSERT(Succeeded());
474     return EditorDOMPointType(mJoinedPoint.ContainerAsContent());
475   }
476 
RemovedContent()477   MOZ_KNOWN_LIVE nsIContent* RemovedContent() const {
478     MOZ_ASSERT(Succeeded());
479     return mRemovedContent;
480   }
481   template <typename EditorDOMPointType>
AtRemovedContent()482   EditorDOMPointType AtRemovedContent() const {
483     MOZ_ASSERT(Succeeded());
484     if (mRemovedContent) {
485       return EditorDOMPointType(mRemovedContent);
486     }
487     return EditorDOMPointType();
488   }
489 
490   template <typename EditorDOMPointType>
AtJoinedPoint()491   EditorDOMPointType AtJoinedPoint() const {
492     MOZ_ASSERT(Succeeded());
493     return mJoinedPoint;
494   }
495 
496   JoinNodesResult() = delete;
497 
498   /**
499    * This constructor shouldn't be used by anybody except methods which
500    * use this as result when it succeeds.
501    *
502    * @param aJoinedPoint        First child of right node or first character.
503    * @param aRemovedContent     The node which was removed from the parent.
504    * @param aDirection          The join direction which the HTML editor tried
505    *                            to join the nodes with.
506    */
JoinNodesResult(const EditorDOMPoint & aJoinedPoint,nsIContent & aRemovedContent,JoinNodesDirection aDirection)507   JoinNodesResult(const EditorDOMPoint& aJoinedPoint,
508                   nsIContent& aRemovedContent, JoinNodesDirection aDirection)
509       : mJoinedPoint(aJoinedPoint),
510         mRemovedContent(&aRemovedContent),
511         mRv(NS_OK) {
512     MOZ_DIAGNOSTIC_ASSERT(aJoinedPoint.IsInContentNode());
513   }
514 
515   /**
516    * This constructor shouldn't be used by anybody except methods which
517    * use this as error result when it fails.
518    */
JoinNodesResult(nsresult aRv)519   explicit JoinNodesResult(nsresult aRv) : mRv(aRv) {
520     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
521   }
522 
523  private:
524   EditorDOMPoint mJoinedPoint;
525   nsCOMPtr<nsIContent> mRemovedContent;
526 
527   nsresult mRv;
528 };
529 
530 /*****************************************************************************
531  * SplitRangeOffFromNodeResult class is a simple class for methods which split a
532  * node at 2 points for making part of the node split off from the node.
533  *****************************************************************************/
534 class MOZ_STACK_CLASS SplitRangeOffFromNodeResult final {
535  public:
Succeeded()536   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()537   bool Failed() const { return NS_FAILED(mRv); }
Rv()538   nsresult Rv() const { return mRv; }
EditorDestroyed()539   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
540 
541   /**
542    * GetLeftContent() returns new created node before the part of quarried out.
543    * This may return nullptr if the method didn't split at start edge of
544    * the node.
545    */
GetLeftContent()546   nsIContent* GetLeftContent() const { return mLeftContent; }
GetLeftContentAsElement()547   dom::Element* GetLeftContentAsElement() const {
548     return dom::Element::FromNodeOrNull(mLeftContent);
549   }
550 
551   /**
552    * GetMiddleContent() returns new created node between left node and right
553    * node.  I.e., this is quarried out from the node.  This may return nullptr
554    * if the method unwrapped the middle node.
555    */
GetMiddleContent()556   nsIContent* GetMiddleContent() const { return mMiddleContent; }
GetMiddleContentAsElement()557   dom::Element* GetMiddleContentAsElement() const {
558     return dom::Element::FromNodeOrNull(mMiddleContent);
559   }
560 
561   /**
562    * GetRightContent() returns the right node after the part of quarried out.
563    * This may return nullptr it the method didn't split at end edge of the
564    * node.
565    */
GetRightContent()566   nsIContent* GetRightContent() const { return mRightContent; }
GetRightContentAsElement()567   dom::Element* GetRightContentAsElement() const {
568     return dom::Element::FromNodeOrNull(mRightContent);
569   }
570 
SplitRangeOffFromNodeResult(nsIContent * aLeftContent,nsIContent * aMiddleContent,nsIContent * aRightContent)571   SplitRangeOffFromNodeResult(nsIContent* aLeftContent,
572                               nsIContent* aMiddleContent,
573                               nsIContent* aRightContent)
574       : mLeftContent(aLeftContent),
575         mMiddleContent(aMiddleContent),
576         mRightContent(aRightContent),
577         mRv(NS_OK) {}
578 
SplitRangeOffFromNodeResult(SplitNodeResult & aSplitResultAtLeftOfMiddleNode,SplitNodeResult & aSplitResultAtRightOfMiddleNode)579   SplitRangeOffFromNodeResult(SplitNodeResult& aSplitResultAtLeftOfMiddleNode,
580                               SplitNodeResult& aSplitResultAtRightOfMiddleNode)
581       : mRv(NS_OK) {
582     if (aSplitResultAtLeftOfMiddleNode.Succeeded()) {
583       mLeftContent = aSplitResultAtLeftOfMiddleNode.GetPreviousContent();
584     }
585     if (aSplitResultAtRightOfMiddleNode.Succeeded()) {
586       mRightContent = aSplitResultAtRightOfMiddleNode.GetNextContent();
587       mMiddleContent = aSplitResultAtRightOfMiddleNode.GetPreviousContent();
588     }
589     if (!mMiddleContent && aSplitResultAtLeftOfMiddleNode.Succeeded()) {
590       mMiddleContent = aSplitResultAtLeftOfMiddleNode.GetNextContent();
591     }
592   }
593 
SplitRangeOffFromNodeResult(nsresult aRv)594   explicit SplitRangeOffFromNodeResult(nsresult aRv) : mRv(aRv) {
595     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
596   }
597 
598   SplitRangeOffFromNodeResult(const SplitRangeOffFromNodeResult& aOther) =
599       delete;
600   SplitRangeOffFromNodeResult& operator=(
601       const SplitRangeOffFromNodeResult& aOther) = delete;
602   SplitRangeOffFromNodeResult(SplitRangeOffFromNodeResult&& aOther) = default;
603   SplitRangeOffFromNodeResult& operator=(SplitRangeOffFromNodeResult&& aOther) =
604       default;
605 
606  private:
607   nsCOMPtr<nsIContent> mLeftContent;
608   nsCOMPtr<nsIContent> mMiddleContent;
609   nsCOMPtr<nsIContent> mRightContent;
610 
611   nsresult mRv;
612 
613   SplitRangeOffFromNodeResult() = delete;
614 };
615 
616 /*****************************************************************************
617  * SplitRangeOffResult class is a simple class for methods which splits
618  * specific ancestor elements at 2 DOM points.
619  *****************************************************************************/
620 class MOZ_STACK_CLASS SplitRangeOffResult final {
621  public:
Succeeded()622   bool Succeeded() const { return NS_SUCCEEDED(mRv); }
Failed()623   bool Failed() const { return NS_FAILED(mRv); }
Rv()624   nsresult Rv() const { return mRv; }
Handled()625   bool Handled() const { return mHandled; }
EditorDestroyed()626   bool EditorDestroyed() const { return mRv == NS_ERROR_EDITOR_DESTROYED; }
627 
628   /**
629    * This is at right node of split at start point.
630    */
SplitPointAtStart()631   const EditorDOMPoint& SplitPointAtStart() const { return mSplitPointAtStart; }
632   /**
633    * This is at right node of split at end point.  I.e., not in the range.
634    * This is after the range.
635    */
SplitPointAtEnd()636   const EditorDOMPoint& SplitPointAtEnd() const { return mSplitPointAtEnd; }
637 
638   SplitRangeOffResult() = delete;
639 
640   /**
641    * Constructor for success case.
642    *
643    * @param aTrackedRangeStart          This should be at topmost right node
644    *                                    child at start point if actually split
645    *                                    there, or at start point to be tried
646    *                                    to split.  Note that if the method
647    *                                    allows to run script after splitting
648    *                                    at start point, the point should be
649    *                                    tracked with AutoTrackDOMPoint.
650    * @param aSplitNodeResultAtStart     Raw split node result at start point.
651    * @param aTrackedRangeEnd            This should be at topmost right node
652    *                                    child at end point if actually split
653    *                                    here, or at end point to be tried to
654    *                                    split.  As same as aTrackedRangeStart,
655    *                                    this value should be tracked while
656    *                                    running some script.
657    * @param aSplitNodeResultAtEnd       Raw split node result at start point.
658    */
SplitRangeOffResult(const EditorDOMPoint & aTrackedRangeStart,const SplitNodeResult & aSplitNodeResultAtStart,const EditorDOMPoint & aTrackedRangeEnd,const SplitNodeResult & aSplitNodeResultAtEnd)659   SplitRangeOffResult(const EditorDOMPoint& aTrackedRangeStart,
660                       const SplitNodeResult& aSplitNodeResultAtStart,
661                       const EditorDOMPoint& aTrackedRangeEnd,
662                       const SplitNodeResult& aSplitNodeResultAtEnd)
663       : mSplitPointAtStart(aTrackedRangeStart),
664         mSplitPointAtEnd(aTrackedRangeEnd),
665         mRv(NS_OK),
666         mHandled(aSplitNodeResultAtStart.Handled() ||
667                  aSplitNodeResultAtEnd.Handled()) {
668     MOZ_ASSERT(mSplitPointAtStart.IsSet());
669     MOZ_ASSERT(mSplitPointAtEnd.IsSet());
670     MOZ_ASSERT(aSplitNodeResultAtStart.Succeeded());
671     MOZ_ASSERT(aSplitNodeResultAtEnd.Succeeded());
672   }
673 
SplitRangeOffResult(nsresult aRv)674   explicit SplitRangeOffResult(nsresult aRv) : mRv(aRv), mHandled(false) {
675     MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv));
676   }
677 
678   SplitRangeOffResult(const SplitRangeOffResult& aOther) = delete;
679   SplitRangeOffResult& operator=(const SplitRangeOffResult& aOther) = delete;
680   SplitRangeOffResult(SplitRangeOffResult&& aOther) = default;
681   SplitRangeOffResult& operator=(SplitRangeOffResult&& aOther) = default;
682 
683  private:
684   EditorDOMPoint mSplitPointAtStart;
685   EditorDOMPoint mSplitPointAtEnd;
686 
687   // If you need to store previous and/or next node at start/end point,
688   // you might be able to use `SplitNodeResult::GetPreviousNode()` etc in the
689   // constructor only when `SplitNodeResult::Handled()` returns true.  But
690   // the node might have gone with another DOM tree mutation.  So, be careful
691   // if you do it.
692 
693   nsresult mRv;
694 
695   bool mHandled;
696 };
697 
698 /******************************************************************************
699  * DOM tree iterators
700  *****************************************************************************/
701 
702 class MOZ_RAII DOMIterator {
703  public:
704   explicit DOMIterator();
705   explicit DOMIterator(nsINode& aNode);
706   virtual ~DOMIterator() = default;
707 
708   nsresult Init(nsRange& aRange);
709   nsresult Init(const RawRangeBoundary& aStartRef,
710                 const RawRangeBoundary& aEndRef);
711 
712   template <class NodeClass>
713   void AppendAllNodesToArray(
714       nsTArray<OwningNonNull<NodeClass>>& aArrayOfNodes) const;
715 
716   /**
717    * AppendNodesToArray() calls aFunctor before appending found node to
718    * aArrayOfNodes.  If aFunctor returns false, the node will be ignored.
719    * You can use aClosure instead of capturing something with lambda.
720    * Note that aNode is guaranteed that it's an instance of NodeClass
721    * or its sub-class.
722    * XXX If we can make type of aNode templated without std::function,
723    *     it'd be better, though.
724    */
725   typedef bool (*BoolFunctor)(nsINode& aNode, void* aClosure);
726   template <class NodeClass>
727   void AppendNodesToArray(BoolFunctor aFunctor,
728                           nsTArray<OwningNonNull<NodeClass>>& aArrayOfNodes,
729                           void* aClosure = nullptr) const;
730 
731  protected:
732   ContentIteratorBase* mIter;
733   PostContentIterator mPostOrderIter;
734 };
735 
736 class MOZ_RAII DOMSubtreeIterator final : public DOMIterator {
737  public:
738   explicit DOMSubtreeIterator();
739   virtual ~DOMSubtreeIterator() = default;
740 
741   nsresult Init(nsRange& aRange);
742 
743  private:
744   ContentSubtreeIterator mSubtreeIter;
745   explicit DOMSubtreeIterator(nsINode& aNode) = delete;
746 };
747 
748 /******************************************************************************
749  * ReplaceRangeData
750  *
751  * This represents range to be replaced and replacing string.
752  *****************************************************************************/
753 
754 template <typename EditorDOMPointType>
755 class MOZ_STACK_CLASS ReplaceRangeDataBase final {
756  public:
757   ReplaceRangeDataBase() = default;
758   template <typename OtherEditorDOMRangeType>
ReplaceRangeDataBase(const OtherEditorDOMRangeType & aRange,const nsAString & aReplaceString)759   ReplaceRangeDataBase(const OtherEditorDOMRangeType& aRange,
760                        const nsAString& aReplaceString)
761       : mRange(aRange), mReplaceString(aReplaceString) {}
762   template <typename StartPointType, typename EndPointType>
ReplaceRangeDataBase(const StartPointType & aStart,const EndPointType & aEnd,const nsAString & aReplaceString)763   ReplaceRangeDataBase(const StartPointType& aStart, const EndPointType& aEnd,
764                        const nsAString& aReplaceString)
765       : mRange(aStart, aEnd), mReplaceString(aReplaceString) {}
766 
IsSet()767   bool IsSet() const { return mRange.IsPositioned(); }
IsSetAndValid()768   bool IsSetAndValid() const { return mRange.IsPositionedAndValid(); }
Collapsed()769   bool Collapsed() const { return mRange.Collapsed(); }
HasReplaceString()770   bool HasReplaceString() const { return !mReplaceString.IsEmpty(); }
StartRef()771   const EditorDOMPointType& StartRef() const { return mRange.StartRef(); }
EndRef()772   const EditorDOMPointType& EndRef() const { return mRange.EndRef(); }
RangeRef()773   const EditorDOMRangeBase<EditorDOMPointType>& RangeRef() const {
774     return mRange;
775   }
ReplaceStringRef()776   const nsString& ReplaceStringRef() const { return mReplaceString; }
777 
778   template <typename PointType>
SetStart(const PointType & aStart)779   MOZ_NEVER_INLINE_DEBUG void SetStart(const PointType& aStart) {
780     mRange.SetStart(aStart);
781   }
782   template <typename PointType>
SetEnd(const PointType & aEnd)783   MOZ_NEVER_INLINE_DEBUG void SetEnd(const PointType& aEnd) {
784     mRange.SetEnd(aEnd);
785   }
786   template <typename StartPointType, typename EndPointType>
SetStartAndEnd(const StartPointType & aStart,const EndPointType & aEnd)787   MOZ_NEVER_INLINE_DEBUG void SetStartAndEnd(const StartPointType& aStart,
788                                              const EndPointType& aEnd) {
789     mRange.SetRange(aStart, aEnd);
790   }
791   template <typename OtherEditorDOMRangeType>
SetRange(const OtherEditorDOMRangeType & aRange)792   MOZ_NEVER_INLINE_DEBUG void SetRange(const OtherEditorDOMRangeType& aRange) {
793     mRange = aRange;
794   }
SetReplaceString(const nsAString & aReplaceString)795   void SetReplaceString(const nsAString& aReplaceString) {
796     mReplaceString = aReplaceString;
797   }
798   template <typename StartPointType, typename EndPointType>
SetStartAndEnd(const StartPointType & aStart,const EndPointType & aEnd,const nsAString & aReplaceString)799   MOZ_NEVER_INLINE_DEBUG void SetStartAndEnd(const StartPointType& aStart,
800                                              const EndPointType& aEnd,
801                                              const nsAString& aReplaceString) {
802     SetStartAndEnd(aStart, aEnd);
803     SetReplaceString(aReplaceString);
804   }
805   template <typename OtherEditorDOMRangeType>
Set(const OtherEditorDOMRangeType & aRange,const nsAString & aReplaceString)806   MOZ_NEVER_INLINE_DEBUG void Set(const OtherEditorDOMRangeType& aRange,
807                                   const nsAString& aReplaceString) {
808     SetRange(aRange);
809     SetReplaceString(aReplaceString);
810   }
811 
812  private:
813   EditorDOMRangeBase<EditorDOMPointType> mRange;
814   // This string may be used with ReplaceTextTransaction.  Therefore, for
815   // avoiding memory copy, we should store it with nsString rather than
816   // nsAutoString.
817   nsString mReplaceString;
818 };
819 
820 using ReplaceRangeData = ReplaceRangeDataBase<EditorDOMPoint>;
821 using ReplaceRangeInTextsData = ReplaceRangeDataBase<EditorDOMPointInText>;
822 
823 }  // namespace mozilla
824 
825 #endif  // #ifndef mozilla_HTMLEditHelpers_h
826