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