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/dom/Selection.h" 10 #include "mozilla/EditAction.h" 11 #include "mozilla/EditorBase.h" 12 #include "mozilla/EditorDOMPoint.h" 13 #include "mozilla/GuardObjects.h" 14 #include "nsCOMPtr.h" 15 #include "nsDebug.h" 16 #include "nsIDOMNode.h" 17 #include "nsIEditor.h" 18 #include "nscore.h" 19 20 class nsAtom; 21 class nsIContentIterator; 22 class nsIDOMDocument; 23 class nsIDOMEvent; 24 class nsISimpleEnumerator; 25 class nsITransferable; 26 class nsRange; 27 28 namespace mozilla { 29 template <class T> 30 class OwningNonNull; 31 32 /*************************************************************************** 33 * EditActionResult is useful to return multiple results of an editor 34 * action handler without out params. 35 * Note that when you return an anonymous instance from a method, you should 36 * use EditActionIgnored(), EditActionHandled() or EditActionCanceled() for 37 * easier to read. In other words, EditActionResult should be used when 38 * declaring return type of a method, being an argument or defined as a local 39 * variable. 40 */ 41 class MOZ_STACK_CLASS EditActionResult final { 42 public: Succeeded()43 bool Succeeded() const { return NS_SUCCEEDED(mRv); } Failed()44 bool Failed() const { return NS_FAILED(mRv); } Rv()45 nsresult Rv() const { return mRv; } Canceled()46 bool Canceled() const { return mCanceled; } Handled()47 bool Handled() const { return mHandled; } 48 SetResult(nsresult aRv)49 EditActionResult SetResult(nsresult aRv) { 50 mRv = aRv; 51 return *this; 52 } MarkAsCanceled()53 EditActionResult MarkAsCanceled() { 54 mCanceled = true; 55 return *this; 56 } MarkAsHandled()57 EditActionResult MarkAsHandled() { 58 mHandled = true; 59 return *this; 60 } 61 EditActionResult(nsresult aRv)62 explicit EditActionResult(nsresult aRv) 63 : mRv(aRv), mCanceled(false), mHandled(false) {} 64 65 EditActionResult& operator|=(const EditActionResult& aOther) { 66 mCanceled |= aOther.mCanceled; 67 mHandled |= aOther.mHandled; 68 // When both result are same, keep the result. 69 if (mRv == aOther.mRv) { 70 return *this; 71 } 72 // If one of the results is error, use NS_ERROR_FAILURE. 73 if (Failed() || aOther.Failed()) { 74 mRv = NS_ERROR_FAILURE; 75 } else { 76 // Otherwise, use generic success code, NS_OK. 77 mRv = NS_OK; 78 } 79 return *this; 80 } 81 82 private: 83 nsresult mRv; 84 bool mCanceled; 85 bool mHandled; 86 EditActionResult(nsresult aRv,bool aCanceled,bool aHandled)87 EditActionResult(nsresult aRv, bool aCanceled, bool aHandled) 88 : mRv(aRv), mCanceled(aCanceled), mHandled(aHandled) {} 89 EditActionResult()90 EditActionResult() 91 : mRv(NS_ERROR_NOT_INITIALIZED), mCanceled(false), mHandled(false) {} 92 93 friend EditActionResult EditActionIgnored(nsresult aRv); 94 friend EditActionResult EditActionHandled(nsresult aRv); 95 friend EditActionResult EditActionCanceled(nsresult aRv); 96 }; 97 98 /*************************************************************************** 99 * When an edit action handler (or its helper) does nothing, 100 * EditActionIgnored should be returned. 101 */ 102 inline EditActionResult EditActionIgnored(nsresult aRv = NS_OK) { 103 return EditActionResult(aRv, false, false); 104 } 105 106 /*************************************************************************** 107 * When an edit action handler (or its helper) handled and not canceled, 108 * EditActionHandled should be returned. 109 */ 110 inline EditActionResult EditActionHandled(nsresult aRv = NS_OK) { 111 return EditActionResult(aRv, false, true); 112 } 113 114 /*************************************************************************** 115 * When an edit action handler (or its helper) handled and canceled, 116 * EditActionHandled should be returned. 117 */ 118 inline EditActionResult EditActionCanceled(nsresult aRv = NS_OK) { 119 return EditActionResult(aRv, true, true); 120 } 121 122 /*************************************************************************** 123 * SplitNodeResult is a simple class for EditorBase::SplitNodeDeep(). 124 * This makes the callers' code easier to read. 125 */ 126 class MOZ_STACK_CLASS SplitNodeResult final { 127 public: Succeeded()128 bool Succeeded() const { return NS_SUCCEEDED(mRv); } Failed()129 bool Failed() const { return NS_FAILED(mRv); } Rv()130 nsresult Rv() const { return mRv; } 131 132 /** 133 * DidSplit() returns true if a node was actually split. 134 */ DidSplit()135 bool DidSplit() const { return mPreviousNode && mNextNode; } 136 137 /** 138 * GetLeftNode() simply returns the left node which was created at splitting. 139 * This returns nullptr if the node wasn't split. 140 */ GetLeftNode()141 nsIContent* GetLeftNode() const { 142 return mPreviousNode && mNextNode ? mPreviousNode.get() : nullptr; 143 } 144 145 /** 146 * GetRightNode() simply returns the right node which was split. 147 * This won't return nullptr unless failed to split due to invalid arguments. 148 */ GetRightNode()149 nsIContent* GetRightNode() const { 150 if (mGivenSplitPoint.IsSet()) { 151 return mGivenSplitPoint.GetChild(); 152 } 153 return mPreviousNode && !mNextNode ? mPreviousNode : mNextNode; 154 } 155 156 /** 157 * GetPreviousNode() returns previous node at the split point. 158 */ GetPreviousNode()159 nsIContent* GetPreviousNode() const { 160 if (mGivenSplitPoint.IsSet()) { 161 return mGivenSplitPoint.IsEndOfContainer() ? mGivenSplitPoint.GetChild() 162 : nullptr; 163 } 164 return mPreviousNode; 165 } 166 167 /** 168 * GetNextNode() returns next node at the split point. 169 */ GetNextNode()170 nsIContent* GetNextNode() const { 171 if (mGivenSplitPoint.IsSet()) { 172 return !mGivenSplitPoint.IsEndOfContainer() ? mGivenSplitPoint.GetChild() 173 : nullptr; 174 } 175 return mNextNode; 176 } 177 178 /** 179 * SplitPoint() returns the split point in the container. 180 * This is useful when callers insert an element at split point with 181 * EditorBase::CreateNode() or something similar methods. 182 * 183 * Note that the result is EditorRawDOMPoint but the nodes are grabbed 184 * by this instance. Therefore, the life time of both container node 185 * and child node are guaranteed while using the result temporarily. 186 */ SplitPoint()187 EditorRawDOMPoint SplitPoint() const { 188 if (Failed()) { 189 return EditorRawDOMPoint(); 190 } 191 if (mGivenSplitPoint.IsSet()) { 192 return mGivenSplitPoint.AsRaw(); 193 } 194 if (!mPreviousNode) { 195 return EditorRawDOMPoint(mNextNode); 196 } 197 EditorRawDOMPoint point(mPreviousNode); 198 DebugOnly<bool> advanced = point.AdvanceOffset(); 199 NS_WARNING_ASSERTION(advanced, 200 "Failed to advance offset to after previous node"); 201 return point; 202 } 203 204 /** 205 * This constructor shouldn't be used by anybody except methods which 206 * use this as result when it succeeds. 207 * 208 * @param aPreviousNodeOfSplitPoint Previous node immediately before 209 * split point. 210 * @param aNextNodeOfSplitPoint Next node immediately after split 211 * point. 212 */ SplitNodeResult(nsIContent * aPreviousNodeOfSplitPoint,nsIContent * aNextNodeOfSplitPoint)213 SplitNodeResult(nsIContent* aPreviousNodeOfSplitPoint, 214 nsIContent* aNextNodeOfSplitPoint) 215 : mPreviousNode(aPreviousNodeOfSplitPoint), 216 mNextNode(aNextNodeOfSplitPoint), 217 mRv(NS_OK) { 218 MOZ_DIAGNOSTIC_ASSERT(mPreviousNode || mNextNode); 219 } 220 221 /** 222 * This constructor should be used when the method didn't split any nodes 223 * but want to return given split point as right point. 224 */ SplitNodeResult(const EditorRawDOMPoint & aGivenSplitPoint)225 explicit SplitNodeResult(const EditorRawDOMPoint& aGivenSplitPoint) 226 : mGivenSplitPoint(aGivenSplitPoint), mRv(NS_OK) { 227 MOZ_DIAGNOSTIC_ASSERT(mGivenSplitPoint.IsSet()); 228 } 229 230 /** 231 * This constructor shouldn't be used by anybody except methods which 232 * use this as error result when it fails. 233 */ SplitNodeResult(nsresult aRv)234 explicit SplitNodeResult(nsresult aRv) : mRv(aRv) { 235 MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(mRv)); 236 } 237 238 private: 239 // When methods which return this class split some nodes actually, they 240 // need to set a set of left node and right node to this class. However, 241 // one or both of them may be moved or removed by mutation observer. 242 // In such case, we cannot represent the point with EditorDOMPoint since 243 // it requires current container node. Therefore, we need to use 244 // nsCOMPtr<nsIContent> here instead. 245 nsCOMPtr<nsIContent> mPreviousNode; 246 nsCOMPtr<nsIContent> mNextNode; 247 248 // Methods which return this class may not split any nodes actually. Then, 249 // they may want to return given split point as is since such behavior makes 250 // their callers simpler. In this case, the point may be in a text node 251 // which cannot be represented as a node. Therefore, we need EditorDOMPoint 252 // for representing the point. 253 EditorDOMPoint mGivenSplitPoint; 254 255 nsresult mRv; 256 257 SplitNodeResult() = delete; 258 }; 259 260 /*************************************************************************** 261 * stack based helper class for batching a collection of transactions inside a 262 * placeholder transaction. 263 */ 264 class MOZ_RAII AutoPlaceholderBatch final { 265 private: 266 RefPtr<EditorBase> mEditorBase; 267 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 268 269 public: AutoPlaceholderBatch(EditorBase * aEditorBase MOZ_GUARD_OBJECT_NOTIFIER_PARAM)270 explicit AutoPlaceholderBatch( 271 EditorBase* aEditorBase MOZ_GUARD_OBJECT_NOTIFIER_PARAM) 272 : mEditorBase(aEditorBase) { 273 MOZ_GUARD_OBJECT_NOTIFIER_INIT; 274 BeginPlaceholderTransaction(nullptr); 275 } AutoPlaceholderBatch(EditorBase * aEditorBase,nsAtom * aTransactionName MOZ_GUARD_OBJECT_NOTIFIER_PARAM)276 AutoPlaceholderBatch(EditorBase* aEditorBase, 277 nsAtom* aTransactionName MOZ_GUARD_OBJECT_NOTIFIER_PARAM) 278 : mEditorBase(aEditorBase) { 279 MOZ_GUARD_OBJECT_NOTIFIER_INIT; 280 BeginPlaceholderTransaction(aTransactionName); 281 } ~AutoPlaceholderBatch()282 ~AutoPlaceholderBatch() { 283 if (mEditorBase) { 284 mEditorBase->EndPlaceholderTransaction(); 285 } 286 } 287 288 private: BeginPlaceholderTransaction(nsAtom * aTransactionName)289 void BeginPlaceholderTransaction(nsAtom* aTransactionName) { 290 if (mEditorBase) { 291 mEditorBase->BeginPlaceholderTransaction(aTransactionName); 292 } 293 } 294 }; 295 296 /*************************************************************************** 297 * stack based helper class for saving/restoring selection. Note that this 298 * assumes that the nodes involved are still around afterwards! 299 */ 300 class MOZ_RAII AutoSelectionRestorer final { 301 private: 302 // Ref-counted reference to the selection that we are supposed to restore. 303 RefPtr<dom::Selection> mSelection; 304 EditorBase* mEditorBase; // Non-owning ref to EditorBase. 305 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 306 307 public: 308 /** 309 * Constructor responsible for remembering all state needed to restore 310 * aSelection. 311 */ 312 AutoSelectionRestorer(dom::Selection* aSelection, 313 EditorBase* aEditorBase 314 MOZ_GUARD_OBJECT_NOTIFIER_PARAM); 315 316 /** 317 * Destructor restores mSelection to its former state 318 */ 319 ~AutoSelectionRestorer(); 320 321 /** 322 * Abort() cancels to restore the selection. 323 */ 324 void Abort(); 325 }; 326 327 /*************************************************************************** 328 * stack based helper class for StartOperation()/EndOperation() sandwich 329 */ 330 class MOZ_RAII AutoRules final { 331 public: AutoRules(EditorBase * aEditorBase,EditAction aAction,nsIEditor::EDirection aDirection MOZ_GUARD_OBJECT_NOTIFIER_PARAM)332 AutoRules(EditorBase* aEditorBase, EditAction aAction, 333 nsIEditor::EDirection aDirection MOZ_GUARD_OBJECT_NOTIFIER_PARAM) 334 : mEditorBase(aEditorBase), mDoNothing(false) { 335 MOZ_GUARD_OBJECT_NOTIFIER_INIT; 336 // mAction will already be set if this is nested call 337 if (mEditorBase && !mEditorBase->mAction) { 338 mEditorBase->StartOperation(aAction, aDirection); 339 } else { 340 mDoNothing = true; // nested calls will end up here 341 } 342 } 343 ~AutoRules()344 ~AutoRules() { 345 if (mEditorBase && !mDoNothing) { 346 mEditorBase->EndOperation(); 347 } 348 } 349 350 protected: 351 EditorBase* mEditorBase; 352 bool mDoNothing; 353 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 354 }; 355 356 /*************************************************************************** 357 * stack based helper class for turning off active selection adjustment 358 * by low level transactions 359 */ 360 class MOZ_RAII AutoTransactionsConserveSelection final { 361 public: AutoTransactionsConserveSelection(EditorBase * aEditorBase MOZ_GUARD_OBJECT_NOTIFIER_PARAM)362 explicit AutoTransactionsConserveSelection( 363 EditorBase* aEditorBase MOZ_GUARD_OBJECT_NOTIFIER_PARAM) 364 : mEditorBase(aEditorBase), mOldState(true) { 365 MOZ_GUARD_OBJECT_NOTIFIER_INIT; 366 if (mEditorBase) { 367 mOldState = mEditorBase->GetShouldTxnSetSelection(); 368 mEditorBase->SetShouldTxnSetSelection(false); 369 } 370 } 371 ~AutoTransactionsConserveSelection()372 ~AutoTransactionsConserveSelection() { 373 if (mEditorBase) { 374 mEditorBase->SetShouldTxnSetSelection(mOldState); 375 } 376 } 377 378 protected: 379 EditorBase* mEditorBase; 380 bool mOldState; 381 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 382 }; 383 384 /*************************************************************************** 385 * stack based helper class for batching reflow and paint requests. 386 */ 387 class MOZ_RAII AutoUpdateViewBatch final { 388 public: AutoUpdateViewBatch(EditorBase * aEditorBase MOZ_GUARD_OBJECT_NOTIFIER_PARAM)389 explicit AutoUpdateViewBatch( 390 EditorBase* aEditorBase MOZ_GUARD_OBJECT_NOTIFIER_PARAM) 391 : mEditorBase(aEditorBase) { 392 MOZ_GUARD_OBJECT_NOTIFIER_INIT; 393 NS_ASSERTION(mEditorBase, "null mEditorBase pointer!"); 394 395 if (mEditorBase) { 396 mEditorBase->BeginUpdateViewBatch(); 397 } 398 } 399 ~AutoUpdateViewBatch()400 ~AutoUpdateViewBatch() { 401 if (mEditorBase) { 402 mEditorBase->EndUpdateViewBatch(); 403 } 404 } 405 406 protected: 407 EditorBase* mEditorBase; 408 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 409 }; 410 411 class MOZ_STACK_CLASS AutoRangeArray final { 412 public: AutoRangeArray(dom::Selection * aSelection)413 explicit AutoRangeArray(dom::Selection* aSelection) { 414 if (!aSelection) { 415 return; 416 } 417 uint32_t rangeCount = aSelection->RangeCount(); 418 for (uint32_t i = 0; i < rangeCount; i++) { 419 mRanges.AppendElement(*aSelection->GetRangeAt(i)); 420 } 421 } 422 423 AutoTArray<mozilla::OwningNonNull<nsRange>, 8> mRanges; 424 }; 425 426 /****************************************************************************** 427 * some helper classes for iterating the dom tree 428 *****************************************************************************/ 429 430 class BoolDomIterFunctor { 431 public: 432 virtual bool operator()(nsINode* aNode) const = 0; 433 }; 434 435 class MOZ_RAII DOMIterator { 436 public: 437 explicit DOMIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); 438 439 explicit DOMIterator(nsINode& aNode MOZ_GUARD_OBJECT_NOTIFIER_PARAM); 440 virtual ~DOMIterator(); 441 442 nsresult Init(nsRange& aRange); 443 444 void AppendList( 445 const BoolDomIterFunctor& functor, 446 nsTArray<mozilla::OwningNonNull<nsINode>>& arrayOfNodes) const; 447 448 protected: 449 nsCOMPtr<nsIContentIterator> mIter; 450 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER 451 }; 452 453 class MOZ_RAII DOMSubtreeIterator final : public DOMIterator { 454 public: 455 explicit DOMSubtreeIterator(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); 456 virtual ~DOMSubtreeIterator(); 457 458 nsresult Init(nsRange& aRange); 459 }; 460 461 class TrivialFunctor final : public BoolDomIterFunctor { 462 public: 463 // Used to build list of all nodes iterator covers operator()464 virtual bool operator()(nsINode* aNode) const override { return true; } 465 }; 466 467 class EditorUtils final { 468 public: 469 /** 470 * IsDescendantOf() checks if aNode is a child or a descendant of aParent. 471 * aOutPoint is set to the child of aParent. 472 * 473 * @return true if aNode is a child or a descendant of aParent. 474 */ 475 static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent, 476 EditorRawDOMPoint* aOutPoint = nullptr); 477 static bool IsDescendantOf(const nsINode& aNode, const nsINode& aParent, 478 EditorDOMPoint* aOutPoint); 479 }; 480 481 class EditorHookUtils final { 482 public: 483 static bool DoInsertionHook(nsIDOMDocument* aDoc, nsIDOMEvent* aEvent, 484 nsITransferable* aTrans); 485 486 private: 487 static nsresult GetHookEnumeratorFromDocument( 488 nsIDOMDocument* aDoc, nsISimpleEnumerator** aEnumerator); 489 }; 490 491 } // namespace mozilla 492 493 #endif // #ifndef mozilla_EditorUtils_h 494