EditorBase.cpp (292624B)
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 #include "EditorBase.h" 7 8 #include <stdio.h> // for nullptr, stdout 9 #include <string.h> // for strcmp 10 11 #include "AutoClonedRangeArray.h" // for AutoClonedRangeArray and AutoClonedSelectionRangeArray 12 #include "AutoSelectionRestorer.h" 13 #include "ChangeAttributeTransaction.h" 14 #include "CompositionTransaction.h" 15 #include "DeleteContentTransactionBase.h" 16 #include "DeleteMultipleRangesTransaction.h" 17 #include "DeleteNodeTransaction.h" 18 #include "DeleteRangeTransaction.h" 19 #include "DeleteTextTransaction.h" 20 #include "EditAction.h" // for EditSubAction 21 #include "EditorDOMAPIWrapper.h" // for AutoCharacterDataAPIWrapper, etc 22 #include "EditorDOMPoint.h" // for EditorDOMPoint 23 #include "EditorForwards.h" 24 #include "EditorUtils.h" // for various helper classes. 25 #include "EditTransactionBase.h" // for EditTransactionBase 26 #include "EditorEventListener.h" // for EditorEventListener 27 #include "HTMLEditor.h" // for HTMLEditor 28 #include "HTMLEditorInlines.h" 29 #include "HTMLEditUtils.h" // for HTMLEditUtils 30 #include "InsertNodeTransaction.h" // for InsertNodeTransaction 31 #include "InsertTextTransaction.h" // for InsertTextTransaction 32 #include "JoinNodesTransaction.h" // for JoinNodesTransaction 33 #include "PlaceholderTransaction.h" // for PlaceholderTransaction 34 #include "SplitNodeTransaction.h" // for SplitNodeTransaction 35 #include "TextEditor.h" // for TextEditor 36 37 #include "ErrorList.h" 38 #include "gfxFontUtils.h" // for gfxFontUtils 39 #include "mozilla/Assertions.h" 40 #include "mozilla/AsyncEventDispatcher.h" 41 #include "mozilla/EditorDOMPoint.h" 42 #include "mozilla/intl/BidiEmbeddingLevel.h" 43 #include "mozilla/BasePrincipal.h" // for BasePrincipal 44 #include "mozilla/ComposerCommandsUpdater.h" // for ComposerCommandsUpdater 45 #include "mozilla/ContentEvents.h" // for InternalClipboardEvent 46 #include "mozilla/DebugOnly.h" // for DebugOnly 47 #include "mozilla/EditorSpellCheck.h" // for EditorSpellCheck 48 #include "mozilla/Encoding.h" // for Encoding (used in Document::GetDocumentCharacterSet) 49 #include "mozilla/EventDispatcher.h" // for EventChainPreVisitor, etc. 50 #include "mozilla/FlushType.h" // for FlushType::Frames 51 #include "mozilla/IMEContentObserver.h" // for IMEContentObserver 52 #include "mozilla/IMEStateManager.h" // for IMEStateManager 53 #include "mozilla/InputEventOptions.h" // for InputEventOptions 54 #include "mozilla/IntegerRange.h" // for IntegerRange 55 #include "mozilla/Logging.h" //for MOZ_LOG 56 #include "mozilla/mozalloc.h" // for operator new, etc. 57 #include "mozilla/mozInlineSpellChecker.h" // for mozInlineSpellChecker 58 #include "mozilla/mozSpellChecker.h" // for mozSpellChecker 59 #include "mozilla/Preferences.h" // for Preferences 60 #include "mozilla/PresShell.h" // for PresShell 61 #include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary 62 #include "mozilla/ScopeExit.h" // for MakeScopeExit 63 #include "mozilla/Services.h" // for GetObserverService 64 #include "mozilla/StaticPrefs_bidi.h" // for StaticPrefs::bidi_* 65 #include "mozilla/StaticPrefs_dom.h" // for StaticPrefs::dom_* 66 #include "mozilla/StaticPrefs_editor.h" // for StaticPrefs::editor_* 67 #include "mozilla/StaticPrefs_layout.h" // for StaticPrefs::layout_* 68 #include "mozilla/TextComposition.h" // for TextComposition 69 #include "mozilla/TextControlElement.h" // for TextControlElement 70 #include "mozilla/TextInputListener.h" // for TextInputListener 71 #include "mozilla/TextServicesDocument.h" // for TextServicesDocument 72 #include "mozilla/TextEvents.h" 73 #include "mozilla/ToString.h" 74 #include "mozilla/TransactionManager.h" // for TransactionManager 75 #include "mozilla/dom/AbstractRange.h" // for AbstractRange 76 #include "mozilla/dom/Attr.h" // for Attr 77 #include "mozilla/dom/BorrowedAttrInfo.h" // for BorrowedAttrInfo 78 #include "mozilla/dom/BrowsingContext.h" // for BrowsingContext 79 #include "mozilla/dom/CharacterData.h" // for CharacterData 80 #include "mozilla/dom/ContentParent.h" // for ContentParent 81 #include "mozilla/dom/DataTransfer.h" // for DataTransfer 82 #include "mozilla/dom/Document.h" // for Document 83 #include "mozilla/dom/DocumentInlines.h" // for GetObservingPresShell 84 #include "mozilla/dom/DragEvent.h" // for DragEvent 85 #include "mozilla/dom/Element.h" // for Element, nsINode::AsElement 86 #include "mozilla/dom/EventTarget.h" // for EventTarget 87 #include "mozilla/dom/HTMLBodyElement.h" 88 #include "mozilla/dom/HTMLBRElement.h" 89 #include "mozilla/dom/Selection.h" // for Selection, etc. 90 #include "mozilla/dom/StaticRange.h" // for StaticRange 91 #include "mozilla/dom/Text.h" 92 #include "mozilla/dom/Event.h" 93 #include "nsAString.h" // for nsAString::Length, etc. 94 #include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker 95 #include "nsCaret.h" // for nsCaret 96 #include "nsCaseTreatment.h" 97 #include "nsCharTraits.h" // for NS_IS_HIGH_SURROGATE, etc. 98 #include "nsContentUtils.h" // for nsContentUtils 99 #include "nsCopySupport.h" // for nsCopySupport 100 #include "nsDOMString.h" // for DOMStringIsNull 101 #include "nsDebug.h" // for NS_WARNING, etc. 102 #include "nsError.h" // for NS_OK, etc. 103 #include "nsFocusManager.h" // for nsFocusManager 104 #include "nsFrameSelection.h" // for nsFrameSelection 105 #include "nsGenericHTMLElement.h" // for nsGenericHTMLElement 106 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::dir 107 #include "nsIClipboard.h" // for nsIClipboard 108 #include "nsIContent.h" // for nsIContent 109 #include "nsIContentInlines.h" // for nsINode::IsInDesignMode() 110 #include "nsIDocumentEncoder.h" // for nsIDocumentEncoder 111 #include "nsIDocumentStateListener.h" // for nsIDocumentStateListener 112 #include "nsIDocShell.h" // for nsIDocShell 113 #include "nsIEditActionListener.h" // for nsIEditActionListener 114 #include "nsIFrame.h" // for nsIFrame 115 #include "nsIInlineSpellChecker.h" // for nsIInlineSpellChecker, etc. 116 #include "nsNameSpaceManager.h" // for kNameSpaceID_None, etc. 117 #include "nsINode.h" // for nsINode, etc. 118 #include "nsISelectionController.h" // for nsISelectionController, etc. 119 #include "nsISelectionDisplay.h" // for nsISelectionDisplay, etc. 120 #include "nsISupports.h" // for nsISupports 121 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_IF_ADDREF 122 #include "nsITransferable.h" // for nsITransferable 123 #include "nsIWeakReference.h" // for nsISupportsWeakReference 124 #include "nsIWidget.h" // for nsIWidget, IMEState, etc. 125 #include "nsPIDOMWindow.h" // for nsPIDOMWindow 126 #include "nsPresContext.h" // for nsPresContext 127 #include "nsRange.h" // for nsRange 128 #include "nsReadableUtils.h" // for EmptyString, ToNewCString 129 #include "nsString.h" // for nsAutoString, nsString, etc. 130 #include "nsStringFwd.h" // for nsString 131 #include "nsStyleConsts.h" // for StyleDirection::Rtl, etc. 132 #include "nsStyleStruct.h" // for nsStyleDisplay, nsStyleText, etc. 133 #include "nsStyleStructFwd.h" // for nsIFrame::StyleUIReset, etc. 134 #include "nsTextNode.h" // for nsTextNode 135 #include "nsThreadUtils.h" // for nsRunnable 136 #include "prtime.h" // for PR_Now 137 138 class nsIOutputStream; 139 class nsITransferable; 140 141 namespace mozilla { 142 143 using namespace dom; 144 using namespace widget; 145 146 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 147 using LeafNodeType = HTMLEditUtils::LeafNodeType; 148 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 149 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 150 151 static LazyLogModule gEventLog("EditorEvent"); 152 static LazyLogModule gHTMLEditorEditActionStartLog("HTMLEditorEditActionStart"); 153 154 LazyLogModule gTextInputLog("EditorTextInput"); 155 156 /***************************************************************************** 157 * mozilla::EditorBase 158 *****************************************************************************/ 159 template EditorDOMPoint EditorBase::GetFirstIMESelectionStartPoint() const; 160 template EditorRawDOMPoint EditorBase::GetFirstIMESelectionStartPoint() const; 161 template EditorDOMPoint EditorBase::GetLastIMESelectionEndPoint() const; 162 template EditorRawDOMPoint EditorBase::GetLastIMESelectionEndPoint() const; 163 164 template Result<CreateContentResult, nsresult> 165 EditorBase::InsertNodeWithTransaction(nsIContent& aContentToInsert, 166 const EditorDOMPoint& aPointToInsert); 167 template Result<CreateElementResult, nsresult> 168 EditorBase::InsertNodeWithTransaction(Element& aContentToInsert, 169 const EditorDOMPoint& aPointToInsert); 170 template Result<CreateTextResult, nsresult> 171 EditorBase::InsertNodeWithTransaction(Text& aContentToInsert, 172 const EditorDOMPoint& aPointToInsert); 173 174 template EditorDOMPoint EditorBase::GetFirstSelectionStartPoint() const; 175 template EditorRawDOMPoint EditorBase::GetFirstSelectionStartPoint() const; 176 template EditorDOMPoint EditorBase::GetFirstSelectionEndPoint() const; 177 template EditorRawDOMPoint EditorBase::GetFirstSelectionEndPoint() const; 178 179 template EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager( 180 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount, 181 const EditorDOMPoint& aPointAtCaret); 182 template EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager( 183 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount, 184 const EditorRawDOMPoint& aPointAtCaret); 185 186 EditorBase::EditorBase(EditorType aEditorType) 187 : mEditActionData(nullptr), 188 mPlaceholderName(nullptr), 189 mModCount(0), 190 mFlags(0), 191 mUpdateCount(0), 192 mPlaceholderBatch(0), 193 mNewlineHandling(StaticPrefs::editor_singleLine_pasteNewlines()), 194 mCaretStyle(StaticPrefs::layout_selection_caret_style()), 195 mDocDirtyState(-1), 196 mSpellcheckCheckboxState(eTriUnset), 197 mInitSucceeded(false), 198 mAllowsTransactionsToChangeSelection(true), 199 mDidPreDestroy(false), 200 mDidPostCreate(false), 201 mDispatchInputEvent(true), 202 mIsInEditSubAction(false), 203 mHidingCaret(false), 204 mSpellCheckerDictionaryUpdated(true), 205 mIsHTMLEditorClass(aEditorType == EditorType::HTML) { 206 #ifdef XP_WIN 207 if (!mCaretStyle && !IsTextEditor()) { 208 // Wordpad-like caret behavior. 209 mCaretStyle = 1; 210 } 211 #endif // #ifdef XP_WIN 212 if (mNewlineHandling < nsIEditor::eNewlinesPasteIntact || 213 mNewlineHandling > nsIEditor::eNewlinesStripSurroundingWhitespace) { 214 mNewlineHandling = nsIEditor::eNewlinesPasteToFirst; 215 } 216 } 217 218 EditorBase::~EditorBase() { 219 MOZ_ASSERT(!IsInitialized() || mDidPreDestroy, 220 "Why PreDestroy hasn't been called?"); 221 222 if (mComposition) { 223 mComposition->OnEditorDestroyed(); 224 mComposition = nullptr; 225 } 226 // If this editor is still hiding the caret, we need to restore it. 227 HideCaret(false); 228 mTransactionManager = nullptr; 229 } 230 231 NS_IMPL_CYCLE_COLLECTION_CLASS(EditorBase) 232 233 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(EditorBase) 234 // Remove event listeners first since EditorEventListener may need 235 // mDocument, mEventTarget, etc. 236 if (tmp->mEventListener) { 237 tmp->mEventListener->Disconnect(); 238 tmp->mEventListener = nullptr; 239 } 240 241 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement) 242 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectionController) 243 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) 244 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMEContentObserver) 245 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker) 246 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextServicesDocument) 247 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextInputListener) 248 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTransactionManager) 249 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners) 250 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners) 251 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget) 252 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaceholderTransaction) 253 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder) 254 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE 255 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 256 257 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(EditorBase) 258 Document* currentDoc = 259 tmp->mRootElement ? tmp->mRootElement->GetUncomposedDoc() : nullptr; 260 if (currentDoc && nsCCUncollectableMarker::InGeneration( 261 cb, currentDoc->GetMarkedCCGeneration())) { 262 return NS_SUCCESS_INTERRUPTED_TRAVERSE; 263 } 264 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement) 265 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectionController) 266 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) 267 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMEContentObserver) 268 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker) 269 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextServicesDocument) 270 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextInputListener) 271 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTransactionManager) 272 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners) 273 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners) 274 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget) 275 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener) 276 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaceholderTransaction) 277 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder) 278 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 279 280 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditorBase) 281 NS_INTERFACE_MAP_ENTRY(nsISelectionListener) 282 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) 283 NS_INTERFACE_MAP_ENTRY(nsIEditor) 284 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor) 285 NS_INTERFACE_MAP_END 286 287 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditorBase) 288 NS_IMPL_CYCLE_COLLECTING_RELEASE(EditorBase) 289 290 nsresult EditorBase::InitInternal(Document& aDocument, Element* aRootElement, 291 nsISelectionController& aSelectionController, 292 uint32_t aFlags) { 293 MOZ_ASSERT_IF( 294 !mEditActionData || 295 !mEditActionData->HasEditorDestroyedDuringHandlingEditAction(), 296 GetTopLevelEditSubAction() == EditSubAction::eNone); 297 298 // First only set flags, but other stuff shouldn't be initialized now. 299 // Note that SetFlags() will be called by PostCreate(). 300 mFlags = aFlags; 301 302 mDocument = &aDocument; 303 // nsISelectionController should be stored only when we're a `TextEditor`. 304 // Otherwise, in `HTMLEditor`, it's `PresShell`, and grabbing it causes 305 // a circular reference and memory leak. 306 // XXX Should we move `mSelectionController to `TextEditor`? 307 MOZ_ASSERT_IF(!IsTextEditor(), &aSelectionController == GetPresShell()); 308 if (IsTextEditor()) { 309 MOZ_ASSERT(&aSelectionController != GetPresShell()); 310 mSelectionController = &aSelectionController; 311 } 312 313 if (mEditActionData) { 314 // During edit action, selection is cached. But this selection is invalid 315 // now since selection controller is updated, so we have to update this 316 // cache. 317 Selection* selection = aSelectionController.GetSelection( 318 nsISelectionController::SELECTION_NORMAL); 319 NS_WARNING_ASSERTION(selection, 320 "SelectionController::GetSelection() failed"); 321 if (selection) { 322 mEditActionData->UpdateSelectionCache(*selection); 323 } 324 } 325 326 // set up root element if we are passed one. 327 if (aRootElement) { 328 mRootElement = aRootElement; 329 } 330 331 // If this is an editor for <input> or <textarea>, the text node which 332 // has composition string is always recreated with same content. Therefore, 333 // we need to nodify mComposition of text node destruction and replacing 334 // composing string when this receives eCompositionChange event next time. 335 if (mComposition && mComposition->GetContainerTextNode() && 336 !mComposition->GetContainerTextNode()->IsInComposedDoc()) { 337 mComposition->OnTextNodeRemoved(); 338 } 339 340 // Show the caret. 341 DebugOnly<nsresult> rvIgnored = aSelectionController.SetCaretReadOnly(false); 342 NS_WARNING_ASSERTION( 343 NS_SUCCEEDED(rvIgnored), 344 "nsISelectionController::SetCaretReadOnly(false) failed, but ignored"); 345 // Show all the selection reflected to user. 346 rvIgnored = 347 aSelectionController.SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL); 348 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 349 "nsISelectionController::SetSelectionFlags(" 350 "nsISelectionDisplay::DISPLAY_ALL) failed, but ignored"); 351 352 // Make sure that the editor will be destroyed properly 353 mDidPreDestroy = false; 354 // Make sure that the editor will be created properly 355 mDidPostCreate = false; 356 357 MOZ_ASSERT(IsBeingInitialized()); 358 359 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing); 360 if (NS_WARN_IF(!editActionData.CanHandle())) { 361 return NS_ERROR_FAILURE; 362 } 363 364 SelectionRef().AddSelectionListener(this); 365 366 return NS_OK; 367 } 368 369 bool EditorBase::MaybeNodeRemovalsObservedByDevTools() const { 370 if (IsTextEditor()) { 371 // DOM mutation event listeners cannot catch the changes of 372 // <input type="text"> nor <textarea>. 373 return false; 374 } 375 #ifdef DEBUG 376 // On debug build, this should always return true for testing complicated 377 // path without mutation event listeners because when mutation event 378 // listeners do not touch the DOM, editor needs to run as there is no 379 // mutation event listeners. 380 return true; 381 #else // #ifdef DEBUG 382 Document* const doc = GetDocument(); 383 return doc && doc->MaybeNeedsToNotifyDevToolsOfNodeRemovalsInOwnerDoc(); 384 #endif // #ifdef DEBUG #else 385 } 386 387 nsresult EditorBase::EnsureEmptyTextFirstChild() { 388 MOZ_ASSERT(IsTextEditor()); 389 RefPtr<Element> root = GetRoot(); 390 nsIContent* firstChild = root->GetFirstChild(); 391 392 if (!firstChild || !firstChild->IsText()) { 393 RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns); 394 if (!newTextNode) { 395 NS_WARNING("EditorBase::CreateTextNode() failed"); 396 return NS_ERROR_UNEXPECTED; 397 } 398 IgnoredErrorResult ignoredError; 399 root->InsertChildBefore(newTextNode, root->GetFirstChild(), true, 400 ignoredError); 401 MOZ_ASSERT(!ignoredError.Failed()); 402 } 403 404 return NS_OK; 405 } 406 407 nsresult EditorBase::PostCreateInternal() { 408 MOZ_ASSERT(IsEditActionDataAvailable()); 409 410 // Synchronize some stuff for the flags. SetFlags() will initialize 411 // something by the flag difference. This is first time of that, so, all 412 // initializations must be run. For such reason, we need to invert mFlags 413 // value first. 414 mFlags = ~mFlags; 415 nsresult rv = SetFlags(~mFlags); 416 if (NS_FAILED(rv)) { 417 NS_WARNING("EditorBase::SetFlags() failed"); 418 return EditorBase::ToGenericNSResult(rv); 419 } 420 421 // These operations only need to happen on the first PostCreate call 422 if (!mDidPostCreate) { 423 mDidPostCreate = true; 424 425 // Set up listeners 426 CreateEventListeners(); 427 nsresult rv = InstallEventListeners(); 428 if (NS_FAILED(rv)) { 429 NS_WARNING("EditorBase::InstallEventListeners() failed"); 430 return EditorBase::ToGenericNSResult(rv); 431 } 432 433 // nuke the modification count, so the doc appears unmodified 434 // do this before we notify listeners 435 DebugOnly<nsresult> rvIgnored = ResetModificationCount(); 436 NS_WARNING_ASSERTION( 437 NS_SUCCEEDED(rvIgnored), 438 "EditorBase::ResetModificationCount() failed, but ignored"); 439 440 // update the UI with our state 441 rvIgnored = NotifyDocumentListeners(eDocumentCreated); 442 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 443 "EditorBase::NotifyDocumentListeners(eDocumentCreated)" 444 " failed, but ignored"); 445 rvIgnored = NotifyDocumentListeners(eDocumentStateChanged); 446 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 447 "EditorBase::NotifyDocumentListeners(" 448 "eDocumentStateChanged) failed, but ignored"); 449 } 450 451 // update nsTextStateManager and caret if we have focus 452 if (RefPtr<Element> focusedElement = GetFocusedElement()) { 453 DebugOnly<nsresult> rvIgnored = InitializeSelection(*focusedElement); 454 NS_WARNING_ASSERTION( 455 NS_SUCCEEDED(rvIgnored), 456 "EditorBase::InitializeSelection() failed, but ignored"); 457 458 // If the text control gets reframed during focus, Focus() would not be 459 // called, so take a chance here to see if we need to spell check the text 460 // control. 461 nsresult rv = FlushPendingSpellCheck(); 462 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 463 NS_WARNING( 464 "EditorBase::FlushPendingSpellCheck() caused destroying the editor"); 465 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 466 } 467 NS_WARNING_ASSERTION( 468 NS_SUCCEEDED(rv), 469 "EditorBase::FlushPendingSpellCheck() failed, but ignored"); 470 471 Result<IMEState, nsresult> newStateOrError = GetPreferredIMEState(); 472 if (MOZ_UNLIKELY(newStateOrError.isErr())) { 473 NS_WARNING("EditorBase::GetPreferredIMEState() failed"); 474 return NS_OK; 475 } 476 IMEStateManager::UpdateIMEState(newStateOrError.unwrap(), focusedElement, 477 *this); 478 } 479 480 // FYI: This call might cause destroying this editor. 481 IMEStateManager::OnEditorInitialized(*this); 482 483 return NS_OK; 484 } 485 486 void EditorBase::SetTextInputListener(TextInputListener* aTextInputListener) { 487 MOZ_ASSERT(!mTextInputListener || !aTextInputListener || 488 mTextInputListener == aTextInputListener); 489 mTextInputListener = aTextInputListener; 490 } 491 492 void EditorBase::SetIMEContentObserver( 493 IMEContentObserver* aIMEContentObserver) { 494 MOZ_ASSERT(!mIMEContentObserver || !aIMEContentObserver || 495 mIMEContentObserver == aIMEContentObserver); 496 mIMEContentObserver = aIMEContentObserver; 497 } 498 499 void EditorBase::CreateEventListeners() { 500 // Don't create the handler twice 501 if (!mEventListener) { 502 mEventListener = new EditorEventListener(); 503 } 504 } 505 506 nsresult EditorBase::InstallEventListeners() { 507 // FIXME InstallEventListeners() should not be called if we failed to set 508 // document or create an event listener. So, these checks should be 509 // MOZ_DIAGNOSTIC_ASSERT instead. 510 MOZ_ASSERT(GetDocument()); 511 if (MOZ_UNLIKELY(!GetDocument()) || NS_WARN_IF(!mEventListener)) { 512 return NS_ERROR_NOT_INITIALIZED; 513 } 514 515 // Initialize the event target. 516 mEventTarget = GetExposedRoot(); 517 if (NS_WARN_IF(!mEventTarget)) { 518 return NS_ERROR_NOT_AVAILABLE; 519 } 520 521 nsresult rv = mEventListener->Connect(this); 522 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 523 "EditorEventListener::Connect() failed"); 524 if (mComposition) { 525 // If mComposition has already been destroyed, we should forget it. 526 // This may happen if it ended while we don't listen to composition 527 // events. 528 if (mComposition->Destroyed()) { 529 // XXX We may need to fix existing composition transaction here. 530 // However, this may be called when it's not safe. 531 // Perhaps, we should stop handling composition with events. 532 mComposition = nullptr; 533 } 534 // Otherwise, Restart to handle composition with new editor contents. 535 else { 536 mComposition->StartHandlingComposition(this); 537 } 538 } 539 return rv; 540 } 541 542 void EditorBase::RemoveEventListeners() { 543 if (!mEventListener) { 544 return; 545 } 546 mEventListener->Disconnect(); 547 if (mComposition) { 548 // Even if this is called, don't release mComposition because this is 549 // may be reused after reframing. 550 mComposition->EndHandlingComposition(this); 551 } 552 mEventTarget = nullptr; 553 } 554 555 bool EditorBase::IsListeningToEvents() const { 556 return mEventListener && !mEventListener->DetachedFromEditor(); 557 } 558 559 bool EditorBase::GetDesiredSpellCheckState() { 560 // Check user override on this element 561 if (mSpellcheckCheckboxState != eTriUnset) { 562 return (mSpellcheckCheckboxState == eTriTrue); 563 } 564 565 // Check user preferences 566 int32_t spellcheckLevel = StaticPrefs::layout_spellcheckDefault(); 567 568 if (!spellcheckLevel) { 569 return false; // Spellchecking forced off globally 570 } 571 572 if (!CanEnableSpellCheck()) { 573 return false; 574 } 575 576 PresShell* presShell = GetPresShell(); 577 if (presShell) { 578 nsPresContext* context = presShell->GetPresContext(); 579 if (context && !context->IsDynamic()) { 580 return false; 581 } 582 } 583 584 // Check DOM state 585 nsCOMPtr<nsIContent> content = GetExposedRoot(); 586 if (!content) { 587 return false; 588 } 589 590 auto element = nsGenericHTMLElement::FromNode(content); 591 if (!element) { 592 return false; 593 } 594 595 // XXX I'm not sure whether we don't use this path when we're a plaintext mail 596 // composer. 597 if (IsHTMLEditor() && !AsHTMLEditor()->IsPlaintextMailComposer()) { 598 // Some of the page content might be editable and some not, if spellcheck= 599 // is explicitly set anywhere, so if there's anything editable on the page, 600 // return true and let the spellchecker figure it out. 601 Document* doc = content->GetComposedDoc(); 602 return doc && doc->IsEditingOn(); 603 } 604 605 return element->Spellcheck(); 606 } 607 608 void EditorBase::PreDestroyInternal() { 609 MOZ_ASSERT(!mDidPreDestroy); 610 611 mInitSucceeded = false; 612 613 Selection* selection = GetSelection(); 614 if (selection) { 615 selection->RemoveSelectionListener(this); 616 } 617 618 IMEStateManager::OnEditorDestroying(*this); 619 620 // Let spellchecker clean up its observers etc. It is important not to 621 // actually free the spellchecker here, since the spellchecker could have 622 // caused flush notifications, which could have gotten here if a textbox 623 // is being removed. Setting the spellchecker to nullptr could free the 624 // object that is still in use! It will be freed when the editor is 625 // destroyed. 626 if (mInlineSpellChecker) { 627 DebugOnly<nsresult> rvIgnored = 628 mInlineSpellChecker->Cleanup(IsTextEditor()); 629 NS_WARNING_ASSERTION( 630 NS_SUCCEEDED(rvIgnored), 631 "mozInlineSpellChecker::Cleanup() failed, but ignored"); 632 } 633 634 // tell our listeners that the doc is going away 635 DebugOnly<nsresult> rvIgnored = 636 NotifyDocumentListeners(eDocumentToBeDestroyed); 637 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 638 "EditorBase::NotifyDocumentListeners(" 639 "eDocumentToBeDestroyed) failed, but ignored"); 640 641 // Unregister event listeners 642 RemoveEventListeners(); 643 // If this editor is still hiding the caret, we need to restore it. 644 HideCaret(false); 645 mActionListeners.Clear(); 646 mDocStateListeners.Clear(); 647 mInlineSpellChecker = nullptr; 648 mTextServicesDocument = nullptr; 649 mTextInputListener = nullptr; 650 mSpellcheckCheckboxState = eTriUnset; 651 mRootElement = nullptr; 652 653 // Transaction may grab this instance. Therefore, they should be released 654 // here for stopping the circular reference with this instance. 655 if (mTransactionManager) { 656 DebugOnly<bool> disabledUndoRedo = DisableUndoRedo(); 657 NS_WARNING_ASSERTION(disabledUndoRedo, 658 "EditorBase::DisableUndoRedo() failed, but ignored"); 659 mTransactionManager = nullptr; 660 } 661 662 if (mEditActionData) { 663 mEditActionData->OnEditorDestroy(); 664 } 665 666 mDidPreDestroy = true; 667 } 668 669 NS_IMETHODIMP EditorBase::GetFlags(uint32_t* aFlags) { 670 // NOTE: If you need to override this method, you need to make Flags() 671 // virtual. 672 *aFlags = Flags(); 673 return NS_OK; 674 } 675 676 NS_IMETHODIMP EditorBase::SetFlags(uint32_t aFlags) { 677 if (mFlags == aFlags) { 678 return NS_OK; 679 } 680 681 // If we're a `TextEditor` instance, it's always a plaintext editor. 682 // Therefore, `eEditorPlaintextMask` is not necessary and should not be set 683 // for the performance reason. 684 MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorPlaintextMask)); 685 // If we're an `HTMLEditor` instance, we cannot treat it as a single line 686 // editor. So, eEditorSingleLineMask is available only when we're a 687 // `TextEditor` instance. 688 MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorSingleLineMask)); 689 // If we're an `HTMLEditor` instance, we cannot treat it as a password editor. 690 // So, eEditorPasswordMask is available only when we're a `TextEditor` 691 // instance. 692 MOZ_ASSERT_IF(IsHTMLEditor(), !(aFlags & nsIEditor::eEditorPasswordMask)); 693 // eEditorAllowInteraction changes the behavior of `HTMLEditor`. So, it's 694 // not available with `TextEditor` instance. 695 MOZ_ASSERT_IF(IsTextEditor(), !(aFlags & nsIEditor::eEditorAllowInteraction)); 696 697 const bool isCalledByPostCreate = (mFlags == ~aFlags); 698 // We don't support dynamic password flag change. 699 MOZ_ASSERT_IF(!isCalledByPostCreate, 700 !((mFlags ^ aFlags) & nsIEditor::eEditorPasswordMask)); 701 bool spellcheckerWasEnabled = !isCalledByPostCreate && CanEnableSpellCheck(); 702 mFlags = aFlags; 703 704 if (!IsInitialized()) { 705 // If we're initializing, we shouldn't do anything now. 706 // SetFlags() will be called by PostCreate(), 707 // we should synchronize some stuff for the flags at that time. 708 return NS_OK; 709 } 710 711 // The flag change may cause the spellchecker state change 712 if (CanEnableSpellCheck() != spellcheckerWasEnabled) { 713 SyncRealTimeSpell(); 714 } 715 716 // If this is called from PostCreate(), it will update the IME state if it's 717 // necessary. 718 if (!mDidPostCreate) { 719 return NS_OK; 720 } 721 722 // Might be changing editable state, so, we need to reset current IME state 723 // if we're focused and the flag change causes IME state change. 724 if (RefPtr<Element> focusedElement = GetFocusedElement()) { 725 Result<IMEState, nsresult> newStateOrError = GetPreferredIMEState(); 726 NS_WARNING_ASSERTION(newStateOrError.isOk(), 727 "EditorBase::GetPreferredIMEState() failed"); 728 if (MOZ_LIKELY(newStateOrError.isOk())) { 729 // NOTE: When the enabled state isn't going to be modified, this method 730 // is going to do nothing. 731 IMEStateManager::UpdateIMEState(newStateOrError.unwrap(), focusedElement, 732 *this); 733 } 734 } 735 736 return NS_OK; 737 } 738 739 NS_IMETHODIMP EditorBase::GetIsSelectionEditable(bool* aIsSelectionEditable) { 740 if (NS_WARN_IF(!aIsSelectionEditable)) { 741 return NS_ERROR_INVALID_ARG; 742 } 743 *aIsSelectionEditable = IsSelectionEditable(); 744 return NS_OK; 745 } 746 747 bool EditorBase::IsSelectionEditable() { 748 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 749 if (NS_WARN_IF(!editActionData.CanHandle())) { 750 return false; 751 } 752 753 if (IsTextEditor()) { 754 // XXX we just check that the anchor node is editable at the moment 755 // we should check that all nodes in the selection are editable 756 const nsINode* anchorNode = SelectionRef().GetAnchorNode(); 757 return anchorNode && anchorNode->IsContent() && anchorNode->IsEditable(); 758 } 759 760 const nsINode* anchorNode = SelectionRef().GetAnchorNode(); 761 const nsINode* focusNode = SelectionRef().GetFocusNode(); 762 if (!anchorNode || !focusNode) { 763 return false; 764 } 765 766 // if anchorNode or focusNode is in a native anonymous subtree, HTMLEditor 767 // shouldn't edit content in it. 768 // XXX This must be a bug of Selection API. 769 if (MOZ_UNLIKELY(anchorNode->IsInNativeAnonymousSubtree() || 770 focusNode->IsInNativeAnonymousSubtree())) { 771 return false; 772 } 773 774 // Per the editing spec as of June 2012: we have to have a selection whose 775 // start and end nodes are editable, and which share an ancestor editing 776 // host. (Bug 766387.) 777 bool isSelectionEditable = SelectionRef().RangeCount() && 778 anchorNode->IsEditable() && 779 focusNode->IsEditable(); 780 if (!isSelectionEditable) { 781 return false; 782 } 783 784 const nsINode* commonAncestor = 785 SelectionRef().GetAnchorFocusRange()->GetClosestCommonInclusiveAncestor(); 786 while (commonAncestor && !commonAncestor->IsEditable()) { 787 commonAncestor = commonAncestor->GetParentNode(); 788 } 789 // If there is no editable common ancestor, return false. 790 return !!commonAncestor; 791 } 792 793 NS_IMETHODIMP EditorBase::GetIsDocumentEditable(bool* aIsDocumentEditable) { 794 if (NS_WARN_IF(!aIsDocumentEditable)) { 795 return NS_ERROR_INVALID_ARG; 796 } 797 RefPtr<Document> document = GetDocument(); 798 *aIsDocumentEditable = document && IsModifiable(); 799 return NS_OK; 800 } 801 802 NS_IMETHODIMP EditorBase::GetDocument(Document** aDocument) { 803 if (NS_WARN_IF(!aDocument)) { 804 return NS_ERROR_INVALID_ARG; 805 } 806 *aDocument = do_AddRef(mDocument).take(); 807 return NS_WARN_IF(!*aDocument) ? NS_ERROR_NOT_INITIALIZED : NS_OK; 808 } 809 810 already_AddRefed<nsIWidget> EditorBase::GetWidget() const { 811 nsPresContext* presContext = GetPresContext(); 812 if (NS_WARN_IF(!presContext)) { 813 return nullptr; 814 } 815 nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget(); 816 return NS_WARN_IF(!widget) ? nullptr : widget.forget(); 817 } 818 819 NS_IMETHODIMP EditorBase::GetContentsMIMEType(nsAString& aContentsMIMEType) { 820 aContentsMIMEType = mContentMIMEType; 821 return NS_OK; 822 } 823 824 NS_IMETHODIMP EditorBase::SetContentsMIMEType( 825 const nsAString& aContentsMIMEType) { 826 mContentMIMEType.Assign(aContentsMIMEType); 827 return NS_OK; 828 } 829 830 NS_IMETHODIMP EditorBase::GetSelectionController( 831 nsISelectionController** aSelectionController) { 832 if (NS_WARN_IF(!aSelectionController)) { 833 return NS_ERROR_INVALID_ARG; 834 } 835 *aSelectionController = do_AddRef(GetSelectionController()).take(); 836 return NS_WARN_IF(!*aSelectionController) ? NS_ERROR_FAILURE : NS_OK; 837 } 838 839 NS_IMETHODIMP EditorBase::DeleteSelection(EDirection aAction, 840 EStripWrappers aStripWrappers) { 841 nsresult rv = DeleteSelectionAsAction(aAction, aStripWrappers); 842 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 843 "EditorBase::DeleteSelectionAsAction() failed"); 844 return rv; 845 } 846 847 NS_IMETHODIMP EditorBase::GetSelection(Selection** aSelection) { 848 nsresult rv = GetSelection(SelectionType::eNormal, aSelection); 849 NS_WARNING_ASSERTION( 850 NS_SUCCEEDED(rv), 851 "EditorBase::GetSelection(SelectionType::eNormal) failed"); 852 return rv; 853 } 854 855 nsresult EditorBase::GetSelection(SelectionType aSelectionType, 856 Selection** aSelection) const { 857 if (NS_WARN_IF(!aSelection)) { 858 return NS_ERROR_INVALID_ARG; 859 } 860 if (IsEditActionDataAvailable()) { 861 *aSelection = do_AddRef(&SelectionRef()).take(); 862 return NS_OK; 863 } 864 nsISelectionController* selectionController = GetSelectionController(); 865 if (NS_WARN_IF(!selectionController)) { 866 *aSelection = nullptr; 867 return NS_ERROR_NOT_INITIALIZED; 868 } 869 *aSelection = do_AddRef(selectionController->GetSelection( 870 ToRawSelectionType(aSelectionType))) 871 .take(); 872 return NS_WARN_IF(!*aSelection) ? NS_ERROR_FAILURE : NS_OK; 873 } 874 875 nsresult EditorBase::DoTransactionInternal(nsITransaction* aTransaction) { 876 MOZ_ASSERT(IsEditActionDataAvailable()); 877 MOZ_ASSERT_IF( 878 // If the DOM is modified by a clipboard event handler, 879 // HTMLEditor::OnModifyDocument() may need to do some transactions before 880 // dispatching `beforeinput`. 881 // FIXME: It shouldn't happen, and I think that it should be done once 882 // before dispatching `input` event to hide the our editor hack from 883 // the event listeners. 884 GetEditAction() != EditAction::ePaste && 885 GetEditAction() != EditAction::eCut, 886 !ShouldAlreadyHaveHandledBeforeInputEventDispatching()); 887 888 if (mPlaceholderBatch && !mPlaceholderTransaction) { 889 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderName); 890 mPlaceholderTransaction = PlaceholderTransaction::Create( 891 *this, *mPlaceholderName, std::move(mSelState)); 892 MOZ_ASSERT(mSelState.isNothing()); 893 894 // We will recurse, but will not hit this case in the nested call 895 RefPtr<PlaceholderTransaction> placeholderTransaction = 896 mPlaceholderTransaction; 897 DebugOnly<nsresult> rvIgnored = 898 DoTransactionInternal(placeholderTransaction); 899 NS_WARNING_ASSERTION( 900 NS_SUCCEEDED(rvIgnored), 901 "EditorBase::DoTransactionInternal() failed, but ignored"); 902 903 if (mTransactionManager) { 904 if (nsCOMPtr<nsITransaction> topTransaction = 905 mTransactionManager->PeekUndoStack()) { 906 if (RefPtr<EditTransactionBase> topTransactionBase = 907 topTransaction->GetAsEditTransactionBase()) { 908 if (PlaceholderTransaction* topPlaceholderTransaction = 909 topTransactionBase->GetAsPlaceholderTransaction()) { 910 // there is a placeholder transaction on top of the undo stack. It 911 // is either the one we just created, or an earlier one that we are 912 // now merging into. From here on out remember this placeholder 913 // instead of the one we just created. 914 mPlaceholderTransaction = topPlaceholderTransaction; 915 } 916 } 917 } 918 } 919 } 920 921 if (aTransaction) { 922 // XXX: Why are we doing selection specific batching stuff here? 923 // XXX: Most entry points into the editor have auto variables that 924 // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make 925 // XXX: these selection batch calls no-ops. 926 // XXX: 927 // XXX: I suspect that this was placed here to avoid multiple 928 // XXX: selection changed notifications from happening until after 929 // XXX: the transaction was done. I suppose that can still happen 930 // XXX: if an embedding application called DoTransaction() directly 931 // XXX: to pump its own transactions through the system, but in that 932 // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or 933 // XXX: its auto equivalent AutoUpdateViewBatch to ensure that 934 // XXX: selection listeners have access to accurate frame data? 935 // XXX: 936 // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls 937 // XXX: we will need to make sure that they are disabled during 938 // XXX: the init of the editor for text widgets to avoid layout 939 // XXX: re-entry during initial reflow. - kin 940 941 // get the selection and start a batch change 942 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__); 943 944 if (mTransactionManager) { 945 RefPtr<TransactionManager> transactionManager(mTransactionManager); 946 nsresult rv = transactionManager->DoTransaction(aTransaction); 947 if (NS_FAILED(rv)) { 948 NS_WARNING("TransactionManager::DoTransaction() failed"); 949 return rv; 950 } 951 } else { 952 nsresult rv = aTransaction->DoTransaction(); 953 if (NS_FAILED(rv)) { 954 NS_WARNING("nsITransaction::DoTransaction() failed"); 955 return rv; 956 } 957 } 958 959 DoAfterDoTransaction(aTransaction); 960 } 961 962 return NS_OK; 963 } 964 965 NS_IMETHODIMP EditorBase::EnableUndo(bool aEnable) { 966 // XXX Should we return NS_ERROR_FAILURE if EdnableUndoRedo() or 967 // DisableUndoRedo() returns false? 968 if (aEnable) { 969 DebugOnly<bool> enabledUndoRedo = EnableUndoRedo(); 970 NS_WARNING_ASSERTION(enabledUndoRedo, 971 "EditorBase::EnableUndoRedo() failed, but ignored"); 972 return NS_OK; 973 } 974 DebugOnly<bool> disabledUndoRedo = DisableUndoRedo(); 975 NS_WARNING_ASSERTION(disabledUndoRedo, 976 "EditorBase::DisableUndoRedo() failed, but ignored"); 977 return NS_OK; 978 } 979 980 NS_IMETHODIMP EditorBase::ClearUndoRedoXPCOM() { 981 if (MOZ_UNLIKELY(!ClearUndoRedo())) { 982 return NS_ERROR_FAILURE; // We're handling a transaction 983 } 984 return NS_OK; 985 } 986 987 NS_IMETHODIMP EditorBase::Undo() { 988 nsresult rv = UndoAsAction(1u); 989 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed"); 990 return rv; 991 } 992 993 NS_IMETHODIMP EditorBase::UndoAll() { 994 if (!mTransactionManager) { 995 return NS_OK; 996 } 997 size_t numberOfUndoItems = mTransactionManager->NumberOfUndoItems(); 998 if (!numberOfUndoItems) { 999 return NS_OK; // no transactions 1000 } 1001 nsresult rv = UndoAsAction(numberOfUndoItems); 1002 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::UndoAsAction() failed"); 1003 return rv; 1004 } 1005 1006 NS_IMETHODIMP EditorBase::GetUndoRedoEnabled(bool* aIsEnabled) { 1007 MOZ_ASSERT(aIsEnabled); 1008 *aIsEnabled = IsUndoRedoEnabled(); 1009 return NS_OK; 1010 } 1011 1012 NS_IMETHODIMP EditorBase::GetCanUndo(bool* aCanUndo) { 1013 MOZ_ASSERT(aCanUndo); 1014 *aCanUndo = CanUndo(); 1015 return NS_OK; 1016 } 1017 1018 NS_IMETHODIMP EditorBase::Redo() { 1019 nsresult rv = RedoAsAction(1u); 1020 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::RedoAsAction() failed"); 1021 return rv; 1022 } 1023 1024 NS_IMETHODIMP EditorBase::GetCanRedo(bool* aCanRedo) { 1025 MOZ_ASSERT(aCanRedo); 1026 *aCanRedo = CanRedo(); 1027 return NS_OK; 1028 } 1029 1030 nsresult EditorBase::UndoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal) { 1031 if (aCount == 0 || IsReadonly()) { 1032 return NS_OK; 1033 } 1034 1035 // If we don't have transaction in the undo stack, we shouldn't notify 1036 // anybody of trying to undo since it's not useful notification but we 1037 // need to pay some runtime cost. 1038 if (!CanUndo()) { 1039 return NS_OK; 1040 } 1041 1042 // If there is composition, we shouldn't allow to undo with committing 1043 // composition since Chrome doesn't allow it and it doesn't make sense 1044 // because committing composition causes one transaction and Undo(1) 1045 // undoes the committing composition. 1046 if (GetComposition()) { 1047 return NS_OK; 1048 } 1049 1050 AutoEditActionDataSetter editActionData(*this, EditAction::eUndo, aPrincipal); 1051 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1052 if (NS_FAILED(rv)) { 1053 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1054 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 1055 return EditorBase::ToGenericNSResult(rv); 1056 } 1057 1058 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__); 1059 1060 NotifyEditorObservers(eNotifyEditorObserversOfBefore); 1061 if (NS_WARN_IF(!CanUndo()) || NS_WARN_IF(Destroyed())) { 1062 return NS_ERROR_FAILURE; 1063 } 1064 1065 rv = NS_OK; 1066 { 1067 IgnoredErrorResult ignoredError; 1068 AutoEditSubActionNotifier startToHandleEditSubAction( 1069 *this, EditSubAction::eUndo, nsIEditor::eNone, ignoredError); 1070 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 1071 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult()); 1072 } 1073 NS_WARNING_ASSERTION(!ignoredError.Failed(), 1074 "TextEditor::OnStartToHandleTopLevelEditSubAction() " 1075 "failed, but ignored"); 1076 1077 RefPtr<TransactionManager> transactionManager(mTransactionManager); 1078 for (uint32_t i = 0; i < aCount; ++i) { 1079 if (NS_FAILED(transactionManager->Undo())) { 1080 NS_WARNING("TransactionManager::Undo() failed"); 1081 break; 1082 } 1083 DoAfterUndoTransaction(); 1084 } 1085 1086 if (IsHTMLEditor()) { 1087 rv = AsHTMLEditor()->ReflectPaddingBRElementForEmptyEditor(); 1088 } 1089 } 1090 1091 NotifyEditorObservers(eNotifyEditorObserversOfEnd); 1092 return EditorBase::ToGenericNSResult(rv); 1093 } 1094 1095 nsresult EditorBase::RedoAsAction(uint32_t aCount, nsIPrincipal* aPrincipal) { 1096 if (aCount == 0 || IsReadonly()) { 1097 return NS_OK; 1098 } 1099 1100 // If we don't have transaction in the redo stack, we shouldn't notify 1101 // anybody of trying to redo since it's not useful notification but we 1102 // need to pay some runtime cost. 1103 if (!CanRedo()) { 1104 return NS_OK; 1105 } 1106 1107 // If there is composition, we shouldn't allow to redo with committing 1108 // composition since Chrome doesn't allow it and it doesn't make sense 1109 // because committing composition causes removing all transactions from 1110 // the redo queue. So, it becomes impossible to redo anything. 1111 if (GetComposition()) { 1112 return NS_OK; 1113 } 1114 1115 AutoEditActionDataSetter editActionData(*this, EditAction::eRedo, aPrincipal); 1116 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1117 if (NS_FAILED(rv)) { 1118 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1119 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 1120 return EditorBase::ToGenericNSResult(rv); 1121 } 1122 1123 AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__); 1124 1125 NotifyEditorObservers(eNotifyEditorObserversOfBefore); 1126 if (NS_WARN_IF(!CanRedo()) || NS_WARN_IF(Destroyed())) { 1127 return NS_ERROR_FAILURE; 1128 } 1129 1130 rv = NS_OK; 1131 { 1132 IgnoredErrorResult ignoredError; 1133 AutoEditSubActionNotifier startToHandleEditSubAction( 1134 *this, EditSubAction::eRedo, nsIEditor::eNone, ignoredError); 1135 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 1136 return ignoredError.StealNSResult(); 1137 } 1138 NS_WARNING_ASSERTION(!ignoredError.Failed(), 1139 "TextEditor::OnStartToHandleTopLevelEditSubAction() " 1140 "failed, but ignored"); 1141 1142 RefPtr<TransactionManager> transactionManager(mTransactionManager); 1143 for (uint32_t i = 0; i < aCount; ++i) { 1144 if (NS_FAILED(transactionManager->Redo())) { 1145 NS_WARNING("TransactionManager::Redo() failed"); 1146 break; 1147 } 1148 DoAfterRedoTransaction(); 1149 } 1150 1151 if (IsHTMLEditor()) { 1152 rv = AsHTMLEditor()->ReflectPaddingBRElementForEmptyEditor(); 1153 } 1154 } 1155 1156 NotifyEditorObservers(eNotifyEditorObserversOfEnd); 1157 return EditorBase::ToGenericNSResult(rv); 1158 } 1159 1160 NS_IMETHODIMP EditorBase::BeginTransaction() { 1161 AutoEditActionDataSetter editActionData(*this, EditAction::eUnknown); 1162 if (NS_WARN_IF(!editActionData.CanHandle())) { 1163 return NS_ERROR_FAILURE; 1164 } 1165 1166 BeginTransactionInternal(__FUNCTION__); 1167 return NS_OK; 1168 } 1169 1170 void EditorBase::BeginTransactionInternal(const char* aRequesterFuncName) { 1171 BeginUpdateViewBatch(aRequesterFuncName); 1172 1173 if (NS_WARN_IF(!mTransactionManager)) { 1174 return; 1175 } 1176 1177 RefPtr<TransactionManager> transactionManager(mTransactionManager); 1178 DebugOnly<nsresult> rvIgnored = transactionManager->BeginBatch(nullptr); 1179 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1180 "TransactionManager::BeginBatch() failed, but ignored"); 1181 } 1182 1183 NS_IMETHODIMP EditorBase::EndTransaction() { 1184 AutoEditActionDataSetter editActionData(*this, EditAction::eUnknown); 1185 if (NS_WARN_IF(!editActionData.CanHandle())) { 1186 return NS_ERROR_FAILURE; 1187 } 1188 1189 EndTransactionInternal(__FUNCTION__); 1190 return NS_OK; 1191 } 1192 1193 void EditorBase::EndTransactionInternal(const char* aRequesterFuncName) { 1194 if (mTransactionManager) { 1195 RefPtr<TransactionManager> transactionManager(mTransactionManager); 1196 DebugOnly<nsresult> rvIgnored = transactionManager->EndBatch(false); 1197 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1198 "TransactionManager::EndBatch() failed, but ignored"); 1199 } 1200 1201 EndUpdateViewBatch(aRequesterFuncName); 1202 } 1203 1204 void EditorBase::BeginPlaceholderTransaction(nsStaticAtom& aTransactionName, 1205 const char* aRequesterFuncName) { 1206 MOZ_ASSERT(IsEditActionDataAvailable()); 1207 MOZ_ASSERT(mPlaceholderBatch >= 0, "negative placeholder batch count!"); 1208 1209 if (!mPlaceholderBatch) { 1210 NotifyEditorObservers(eNotifyEditorObserversOfBefore); 1211 // time to turn on the batch 1212 BeginUpdateViewBatch(aRequesterFuncName); 1213 mPlaceholderTransaction = nullptr; 1214 mPlaceholderName = &aTransactionName; 1215 mSelState.emplace(); 1216 mSelState->SaveSelection(SelectionRef()); 1217 // Composition transaction can modify multiple nodes and it merges text 1218 // node for ime into single text node. 1219 // So if current selection is into IME text node, it might be failed 1220 // to restore selection by UndoTransaction. 1221 // So we need update selection by range updater. 1222 if (mPlaceholderName == nsGkAtoms::IMETxnName) { 1223 RangeUpdaterRef().RegisterSelectionState(*mSelState); 1224 } 1225 } 1226 mPlaceholderBatch++; 1227 } 1228 1229 void EditorBase::EndPlaceholderTransaction( 1230 ScrollSelectionIntoView aScrollSelectionIntoView, 1231 const char* aRequesterFuncName) { 1232 MOZ_ASSERT(IsEditActionDataAvailable()); 1233 MOZ_ASSERT(mPlaceholderBatch > 0, 1234 "zero or negative placeholder batch count when ending batch!"); 1235 1236 if (!(--mPlaceholderBatch)) { 1237 // By making the assumption that no reflow happens during the calls 1238 // to EndUpdateViewBatch and ScrollSelectionFocusIntoView, we are able to 1239 // allow the selection to cache a frame offset which is used by the 1240 // caret drawing code. We only enable this cache here; at other times, 1241 // we have no way to know whether reflow invalidates it 1242 // See bugs 35296 and 199412. 1243 SelectionRef().SetCanCacheFrameOffset(true); 1244 1245 // time to turn off the batch 1246 EndUpdateViewBatch(aRequesterFuncName); 1247 // make sure selection is in view 1248 1249 // After ScrollSelectionFocusIntoView(), the pending notifications might be 1250 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470. 1251 // XXX Even if we're destroyed, we need to keep handling below because 1252 // this method changes a lot of status. We should rewrite this safer. 1253 if (aScrollSelectionIntoView == ScrollSelectionIntoView::Yes) { 1254 DebugOnly<nsresult> rvIgnored = ScrollSelectionFocusIntoView(); 1255 NS_WARNING_ASSERTION( 1256 NS_SUCCEEDED(rvIgnored), 1257 "EditorBase::ScrollSelectionFocusIntoView() failed, but Ignored"); 1258 } 1259 1260 // cached for frame offset are Not available now 1261 SelectionRef().SetCanCacheFrameOffset(false); 1262 1263 if (mSelState) { 1264 // we saved the selection state, but never got to hand it to placeholder 1265 // (else we ould have nulled out this pointer), so destroy it to prevent 1266 // leaks. 1267 if (mPlaceholderName == nsGkAtoms::IMETxnName) { 1268 RangeUpdaterRef().DropSelectionState(*mSelState); 1269 } 1270 mSelState.reset(); 1271 } 1272 // We might have never made a placeholder if no action took place. 1273 if (mPlaceholderTransaction) { 1274 // FYI: Disconnect placeholder transaction before dispatching "input" 1275 // event because an input event listener may start other things. 1276 // TODO: We should forget EditActionDataSetter too. 1277 RefPtr<PlaceholderTransaction> placeholderTransaction = 1278 std::move(mPlaceholderTransaction); 1279 DebugOnly<nsresult> rvIgnored = 1280 placeholderTransaction->EndPlaceHolderBatch(); 1281 NS_WARNING_ASSERTION( 1282 NS_SUCCEEDED(rvIgnored), 1283 "PlaceholderTransaction::EndPlaceHolderBatch() failed, but ignored"); 1284 // notify editor observers of action but if composing, it's done by 1285 // compositionchange event handler. 1286 if (!mComposition) { 1287 NotifyEditorObservers(eNotifyEditorObserversOfEnd); 1288 } 1289 } else { 1290 NotifyEditorObservers(eNotifyEditorObserversOfCancel); 1291 } 1292 } 1293 } 1294 1295 NS_IMETHODIMP EditorBase::GetDocumentIsEmpty(bool* aDocumentIsEmpty) { 1296 MOZ_ASSERT(aDocumentIsEmpty); 1297 *aDocumentIsEmpty = IsEmpty(); 1298 return NS_OK; 1299 } 1300 1301 // XXX: The rule system should tell us which node to select all on (ie, the 1302 // root, or the body) 1303 NS_IMETHODIMP EditorBase::SelectAll() { 1304 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1305 if (NS_WARN_IF(!editActionData.CanHandle())) { 1306 return NS_ERROR_NOT_INITIALIZED; 1307 } 1308 1309 nsresult rv = SelectAllInternal(); 1310 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SelectAllInternal() failed"); 1311 // This is low level API for XUL applcation. So, we should return raw 1312 // error code here. 1313 return rv; 1314 } 1315 1316 nsresult EditorBase::SelectAllInternal() { 1317 MOZ_ASSERT(IsInitialized()); 1318 1319 DebugOnly<nsresult> rvIgnored = CommitComposition(); 1320 if (NS_WARN_IF(Destroyed())) { 1321 return NS_ERROR_EDITOR_DESTROYED; 1322 } 1323 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1324 "EditorBase::CommitComposition() failed, but ignored"); 1325 1326 // XXX Do we need to keep handling after committing composition causes moving 1327 // focus to different element? Although TextEditor has independent 1328 // selection, so, we may not see any odd behavior even in such case. 1329 1330 nsresult rv = SelectEntireDocument(); 1331 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1332 "EditorBase::SelectEntireDocument() failed"); 1333 return rv; 1334 } 1335 1336 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP EditorBase::BeginningOfDocument() { 1337 MOZ_ASSERT(IsTextEditor()); 1338 1339 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1340 if (NS_WARN_IF(!editActionData.CanHandle())) { 1341 return NS_ERROR_NOT_INITIALIZED; 1342 } 1343 1344 // get the root element 1345 RefPtr<Element> rootElement = GetRoot(); 1346 if (NS_WARN_IF(!rootElement)) { 1347 return NS_ERROR_NULL_POINTER; 1348 } 1349 1350 // find first editable thingy 1351 nsCOMPtr<nsIContent> firstEditableLeaf; 1352 // If we're `TextEditor`, the first editable leaf node is a text node or 1353 // padding `<br>` element. In the first case, we need to collapse selection 1354 // into it. 1355 if (rootElement->GetFirstChild() && rootElement->GetFirstChild()->IsText()) { 1356 firstEditableLeaf = rootElement->GetFirstChild(); 1357 } 1358 if (!firstEditableLeaf) { 1359 // just the root node, set selection to inside the root 1360 nsresult rv = CollapseSelectionToStartOf(*rootElement); 1361 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1362 "EditorBase::CollapseSelectionToStartOf() failed"); 1363 return rv; 1364 } 1365 1366 if (firstEditableLeaf->IsText()) { 1367 // If firstEditableLeaf is text, set selection to beginning of the text 1368 // node. 1369 nsresult rv = CollapseSelectionToStartOf(*firstEditableLeaf); 1370 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1371 "EditorBase::CollapseSelectionToStartOf() failed"); 1372 return rv; 1373 } 1374 1375 // Otherwise, it's a leaf node and we set the selection just in front of it. 1376 nsCOMPtr<nsIContent> parent = firstEditableLeaf->GetParent(); 1377 if (NS_WARN_IF(!parent)) { 1378 return NS_ERROR_NULL_POINTER; 1379 } 1380 1381 MOZ_ASSERT( 1382 parent->ComputeIndexOf(firstEditableLeaf).valueOr(UINT32_MAX) == 0, 1383 "How come the first node isn't the left most child in its parent?"); 1384 nsresult rv = CollapseSelectionToStartOf(*parent); 1385 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1386 "EditorBase::CollapseSelectionToStartOf() failed"); 1387 return rv; 1388 } 1389 1390 NS_IMETHODIMP EditorBase::EndOfDocument() { return NS_ERROR_NOT_IMPLEMENTED; } 1391 1392 NS_IMETHODIMP EditorBase::GetDocumentModified(bool* aOutDocModified) { 1393 if (NS_WARN_IF(!aOutDocModified)) { 1394 return NS_ERROR_INVALID_ARG; 1395 } 1396 1397 int32_t modCount = 0; 1398 DebugOnly<nsresult> rvIgnored = GetModificationCount(&modCount); 1399 NS_WARNING_ASSERTION( 1400 NS_SUCCEEDED(rvIgnored), 1401 "EditorBase::GetModificationCount() failed, but ignored"); 1402 1403 *aOutDocModified = (modCount != 0); 1404 return NS_OK; 1405 } 1406 1407 NS_IMETHODIMP EditorBase::GetDocumentCharacterSet(nsACString& aCharacterSet) { 1408 return NS_ERROR_NOT_AVAILABLE; 1409 } 1410 1411 nsresult EditorBase::GetDocumentCharsetInternal(nsACString& aCharset) const { 1412 Document* document = GetDocument(); 1413 if (NS_WARN_IF(!document)) { 1414 return NS_ERROR_NOT_INITIALIZED; 1415 } 1416 document->GetDocumentCharacterSet()->Name(aCharset); 1417 return NS_OK; 1418 } 1419 1420 NS_IMETHODIMP EditorBase::SetDocumentCharacterSet( 1421 const nsACString& aCharacterSet) { 1422 return NS_ERROR_NOT_AVAILABLE; 1423 } 1424 1425 NS_IMETHODIMP EditorBase::OutputToString(const nsAString& aFormatType, 1426 uint32_t aDocumentEncoderFlags, 1427 nsAString& aOutputString) { 1428 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1429 if (NS_WARN_IF(!editActionData.CanHandle())) { 1430 return NS_ERROR_NOT_INITIALIZED; 1431 } 1432 1433 nsresult rv = 1434 ComputeValueInternal(aFormatType, aDocumentEncoderFlags, aOutputString); 1435 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1436 "EditorBase::ComputeValueInternal() failed"); 1437 // This is low level API for XUL application. So, we should return raw 1438 // error code here. 1439 return rv; 1440 } 1441 1442 nsresult EditorBase::ComputeValueInternal(const nsAString& aFormatType, 1443 uint32_t aDocumentEncoderFlags, 1444 nsAString& aOutputString) const { 1445 MOZ_ASSERT(IsEditActionDataAvailable()); 1446 1447 // First, let's try to get the value simply only from text node if the 1448 // caller wants plaintext value. 1449 if (aFormatType.LowerCaseEqualsLiteral("text/plain") && 1450 !(aDocumentEncoderFlags & (nsIDocumentEncoder::OutputSelectionOnly | 1451 nsIDocumentEncoder::OutputWrap))) { 1452 // Shortcut for empty editor case. 1453 if (IsEmpty()) { 1454 aOutputString.Truncate(); 1455 return NS_OK; 1456 } 1457 // NOTE: If it's neither <input type="text"> nor <textarea>, e.g., an HTML 1458 // editor which is in plaintext mode (e.g., plaintext email composer on 1459 // Thunderbird), it should be handled by the expensive path. 1460 if (IsTextEditor()) { 1461 // If it's necessary to check selection range or the editor wraps hard, 1462 // we need some complicated handling. In such case, we need to use the 1463 // expensive path. 1464 // XXX Anything else what we cannot return the text node data simply? 1465 Result<EditActionResult, nsresult> result = 1466 AsTextEditor()->ComputeValueFromTextNodeAndBRElement(aOutputString); 1467 if (MOZ_UNLIKELY(result.isErr())) { 1468 NS_WARNING("TextEditor::ComputeValueFromTextNodeAndBRElement() failed"); 1469 return result.unwrapErr(); 1470 } 1471 if (!result.inspect().Ignored()) { 1472 return NS_OK; 1473 } 1474 } 1475 } 1476 1477 nsAutoCString charset; 1478 nsresult rv = GetDocumentCharsetInternal(charset); 1479 if (NS_FAILED(rv) || charset.IsEmpty()) { 1480 charset.AssignLiteral("windows-1252"); // XXX Why don't we use "UTF-8"? 1481 } 1482 1483 nsCOMPtr<nsIDocumentEncoder> encoder = 1484 GetAndInitDocEncoder(aFormatType, aDocumentEncoderFlags, charset); 1485 if (!encoder) { 1486 NS_WARNING("EditorBase::GetAndInitDocEncoder() failed"); 1487 return NS_ERROR_FAILURE; 1488 } 1489 1490 rv = encoder->EncodeToString(aOutputString); 1491 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1492 "nsIDocumentEncoder::EncodeToString() failed"); 1493 return rv; 1494 } 1495 1496 already_AddRefed<nsIDocumentEncoder> EditorBase::GetAndInitDocEncoder( 1497 const nsAString& aFormatType, uint32_t aDocumentEncoderFlags, 1498 const nsACString& aCharset) const { 1499 MOZ_ASSERT(IsEditActionDataAvailable()); 1500 1501 nsCOMPtr<nsIDocumentEncoder> docEncoder; 1502 if (!mCachedDocumentEncoder || 1503 !mCachedDocumentEncoderType.Equals(aFormatType)) { 1504 nsAutoCString formatType; 1505 LossyAppendUTF16toASCII(aFormatType, formatType); 1506 docEncoder = do_createDocumentEncoder(PromiseFlatCString(formatType).get()); 1507 if (NS_WARN_IF(!docEncoder)) { 1508 return nullptr; 1509 } 1510 mCachedDocumentEncoder = docEncoder; 1511 mCachedDocumentEncoderType = aFormatType; 1512 } else { 1513 docEncoder = mCachedDocumentEncoder; 1514 } 1515 1516 RefPtr<Document> doc = GetDocument(); 1517 NS_ASSERTION(doc, "Need a document"); 1518 1519 nsresult rv = docEncoder->Init( 1520 doc, aFormatType, 1521 aDocumentEncoderFlags | nsIDocumentEncoder::RequiresReinitAfterOutput); 1522 if (NS_FAILED(rv)) { 1523 NS_WARNING("nsIDocumentEncoder::NativeInit() failed"); 1524 return nullptr; 1525 } 1526 1527 if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) { 1528 DebugOnly<nsresult> rvIgnored = docEncoder->SetCharset(aCharset); 1529 NS_WARNING_ASSERTION( 1530 NS_SUCCEEDED(rvIgnored), 1531 "nsIDocumentEncoder::SetCharset() failed, but ignored"); 1532 } 1533 1534 const int32_t wrapWidth = std::max(WrapWidth(), 0); 1535 DebugOnly<nsresult> rvIgnored = docEncoder->SetWrapColumn(wrapWidth); 1536 NS_WARNING_ASSERTION( 1537 NS_SUCCEEDED(rvIgnored), 1538 "nsIDocumentEncoder::SetWrapColumn() failed, but ignored"); 1539 1540 // Set the selection, if appropriate. 1541 // We do this either if the OutputSelectionOnly flag is set, 1542 // in which case we use our existing selection ... 1543 if (aDocumentEncoderFlags & nsIDocumentEncoder::OutputSelectionOnly) { 1544 if (NS_FAILED(docEncoder->SetSelection(&SelectionRef()))) { 1545 NS_WARNING("nsIDocumentEncoder::SetSelection() failed"); 1546 return nullptr; 1547 } 1548 } 1549 // ... or if the root element is not a body, 1550 // in which case we set the selection to encompass the root. 1551 else { 1552 Element* rootElement = GetRoot(); 1553 if (NS_WARN_IF(!rootElement)) { 1554 return nullptr; 1555 } 1556 if (!rootElement->IsHTMLElement(nsGkAtoms::body)) { 1557 if (NS_FAILED(docEncoder->SetContainerNode(rootElement))) { 1558 NS_WARNING("nsIDocumentEncoder::SetContainerNode() failed"); 1559 return nullptr; 1560 } 1561 } 1562 } 1563 1564 return docEncoder.forget(); 1565 } 1566 1567 bool EditorBase::AreClipboardCommandsUnconditionallyEnabled() const { 1568 Document* document = GetDocument(); 1569 return document && document->AreClipboardCommandsUnconditionallyEnabled(); 1570 } 1571 1572 bool EditorBase::CheckForClipboardCommandListener( 1573 nsAtom* aCommand, EventMessage aEventMessage) const { 1574 RefPtr<Document> document = GetDocument(); 1575 if (!document) { 1576 return false; 1577 } 1578 1579 // We exclude XUL and chrome docs here to maintain current behavior where 1580 // in these cases the editor element alone is expected to handle clipboard 1581 // command availability. 1582 if (!document->AreClipboardCommandsUnconditionallyEnabled()) { 1583 return false; 1584 } 1585 1586 // So in web content documents, "unconditionally" enabled Cut/Copy are not 1587 // really unconditional; they're enabled if there is a listener that wants 1588 // to handle them. What they're not conditional on here is whether there is 1589 // currently a selection in the editor. 1590 RefPtr<PresShell> presShell = document->GetObservingPresShell(); 1591 if (!presShell) { 1592 return false; 1593 } 1594 RefPtr<nsPresContext> presContext = presShell->GetPresContext(); 1595 if (!presContext) { 1596 return false; 1597 } 1598 1599 RefPtr<EventTarget> et = IsHTMLEditor() 1600 ? AsHTMLEditor()->ComputeEditingHost( 1601 HTMLEditor::LimitInBodyElement::No) 1602 : GetDOMEventTarget(); 1603 1604 while (et) { 1605 EventListenerManager* elm = et->GetExistingListenerManager(); 1606 if (elm && elm->HasListenersFor(aCommand)) { 1607 return true; 1608 } 1609 InternalClipboardEvent event(true, aEventMessage); 1610 EventChainPreVisitor visitor(presContext, &event, nullptr, 1611 nsEventStatus_eIgnore, false, et); 1612 et->GetEventTargetParent(visitor); 1613 et = visitor.GetParentTarget(); 1614 } 1615 1616 return false; 1617 } 1618 1619 already_AddRefed<DataTransfer> EditorBase::CreateDataTransferForPaste( 1620 EventMessage aEventMessage, 1621 nsIClipboard::ClipboardType aClipboardType) const { 1622 nsIGlobalObject* scopeObject = nullptr; 1623 if (PresShell* presShell = GetPresShell()) { 1624 if (Document* doc = presShell->GetDocument()) { 1625 scopeObject = doc->GetScopeObject(); 1626 } 1627 } 1628 1629 auto dataTransfer = MakeRefPtr<DataTransfer>(scopeObject, aEventMessage, true, 1630 Some(aClipboardType)); 1631 return dataTransfer.forget(); 1632 } 1633 1634 Result<EditorBase::ClipboardEventResult, nsresult> 1635 EditorBase::DispatchClipboardEventAndUpdateClipboard( 1636 EventMessage aEventMessage, 1637 Maybe<nsIClipboard::ClipboardType> aClipboardType, 1638 DataTransfer* aDataTransfer /* = nullptr */) { 1639 MOZ_ASSERT(IsEditActionDataAvailable()); 1640 1641 // Clipboard events are fired before `beforeinput` event. Therefore, we 1642 // need to forget mLastCollapsibleWhiteSpaceAppendedTextNode here to avoid 1643 // infinite loop caused by the hack. 1644 if (IsHTMLEditor()) { 1645 AsHTMLEditor()->mLastCollapsibleWhiteSpaceAppendedTextNode = nullptr; 1646 } 1647 1648 const bool isPasting = 1649 aEventMessage == ePaste || aEventMessage == ePasteNoFormatting; 1650 if (isPasting) { 1651 CommitComposition(); 1652 if (NS_WARN_IF(Destroyed())) { 1653 return Err(NS_ERROR_EDITOR_DESTROYED); 1654 } 1655 } 1656 1657 RefPtr<PresShell> presShell = GetPresShell(); 1658 if (NS_WARN_IF(!presShell)) { 1659 return Err(NS_ERROR_NOT_AVAILABLE); 1660 } 1661 1662 const RefPtr<Selection> sel = [&]() { 1663 if (IsHTMLEditor() && aEventMessage == eCopy && 1664 SelectionRef().IsCollapsed()) { 1665 // If we don't have a usable selection for copy and we're an HTML 1666 // editor (which is global for the document) try to use the last 1667 // focused selection instead. 1668 return nsCopySupport::GetSelectionForCopy(GetDocument()); 1669 } 1670 return do_AddRef(&SelectionRef()); 1671 }(); 1672 1673 const auto GetDOMEventName = [&]() -> const char* { 1674 switch (aEventMessage) { 1675 case eCopy: 1676 return "copy"; 1677 case eCut: 1678 return "cut"; 1679 case ePaste: 1680 case ePasteNoFormatting: 1681 return "paste"; 1682 default: 1683 return ToChar(aEventMessage); 1684 } 1685 }; 1686 1687 MOZ_LOG( 1688 gEventLog, LogLevel::Info, 1689 ("%p %s: Dispatching \"%s\" event...", this, 1690 mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", GetDOMEventName())); 1691 bool actionTaken = false; 1692 const bool doDefault = nsCopySupport::FireClipboardEvent( 1693 aEventMessage, aClipboardType, presShell, sel, aDataTransfer, 1694 &actionTaken); 1695 MOZ_LOG(gEventLog, LogLevel::Info, 1696 ("%p %s: Dispatched \"%s\" event, defaultPrevented=%s", this, 1697 mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", GetDOMEventName(), 1698 doDefault ? "false" : "true")); 1699 NotifyOfDispatchingClipboardEvent(); 1700 1701 if (NS_WARN_IF(Destroyed())) { 1702 return Err(NS_ERROR_EDITOR_DESTROYED); 1703 } 1704 1705 if (doDefault) { 1706 MOZ_ASSERT(actionTaken); 1707 return ClipboardEventResult::DoDefault; 1708 } 1709 // If we handle a "paste" and nsCopySupport::FireClipboardEvent sets 1710 // actionTaken to "false" means that it's an error. Otherwise, the "paste" 1711 // event is just canceled. 1712 if (isPasting) { 1713 return actionTaken ? ClipboardEventResult::DefaultPreventedOfPaste 1714 : ClipboardEventResult::IgnoredOrError; 1715 } 1716 // If we handle a "copy", actionTaken is set to true only when 1717 // nsCopySupport::FireClipboardEvent does not meet an error. 1718 // If we handle a "cut", actionTaken is set to true only when 1719 // nsCopySupport::FireClipboardEvent does not meet an error and 1720 // - the selection is collapsed in editable elements when the event is not 1721 // canceled. 1722 // - the event is canceled but update the clipboard with the dataTransfer 1723 // of the event. 1724 return actionTaken ? ClipboardEventResult::CopyOrCutHandled 1725 : ClipboardEventResult::IgnoredOrError; 1726 } 1727 1728 NS_IMETHODIMP EditorBase::Cut() { 1729 nsresult rv = CutAsAction(); 1730 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::CutAsAction() failed"); 1731 return rv; 1732 } 1733 1734 nsresult EditorBase::CutAsAction(nsIPrincipal* aPrincipal) { 1735 AutoEditActionDataSetter editActionData(*this, EditAction::eCut, aPrincipal); 1736 if (NS_WARN_IF(!editActionData.CanHandle())) { 1737 return NS_ERROR_NOT_INITIALIZED; 1738 } 1739 1740 { 1741 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 1742 if (NS_WARN_IF(!focusManager)) { 1743 return NS_ERROR_UNEXPECTED; 1744 } 1745 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement(); 1746 1747 Result<ClipboardEventResult, nsresult> ret = 1748 DispatchClipboardEventAndUpdateClipboard( 1749 eCut, Some(nsIClipboard::kGlobalClipboard)); 1750 if (MOZ_UNLIKELY(ret.isErr())) { 1751 NS_WARNING( 1752 "EditorBase::DispatchClipboardEventAndUpdateClipboard(eCut, " 1753 "nsIClipboard::kGlobalClipboard) failed"); 1754 return EditorBase::ToGenericNSResult(ret.unwrapErr()); 1755 } 1756 switch (ret.unwrap()) { 1757 case ClipboardEventResult::DoDefault: 1758 break; 1759 case ClipboardEventResult::CopyOrCutHandled: 1760 return NS_OK; 1761 case ClipboardEventResult::IgnoredOrError: 1762 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 1763 case ClipboardEventResult::DefaultPreventedOfPaste: 1764 MOZ_ASSERT_UNREACHABLE("Invalid result for eCut"); 1765 } 1766 1767 // If focus is changed by a "cut" event listener, we should stop handling 1768 // the cut. 1769 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement(); 1770 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) { 1771 if (focusManager->GetFocusedWindow() != GetWindow()) { 1772 return NS_OK; 1773 } 1774 RefPtr<EditorBase> editorBase = 1775 nsContentUtils::GetActiveEditor(GetPresContext()); 1776 if (!editorBase || (editorBase->IsHTMLEditor() && 1777 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) { 1778 return NS_OK; 1779 } 1780 if (editorBase != this) { 1781 return NS_OK; 1782 } 1783 } 1784 } 1785 1786 // Dispatch "beforeinput" event after dispatching "cut" event. 1787 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 1788 if (NS_FAILED(rv)) { 1789 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1790 "MaybeDispatchBeforeInputEvent() failed"); 1791 return EditorBase::ToGenericNSResult(rv); 1792 } 1793 // XXX This transaction name is referred by PlaceholderTransaction::Merge() 1794 // so that we need to keep using it here. 1795 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName, 1796 ScrollSelectionIntoView::Yes, 1797 __FUNCTION__); 1798 rv = DeleteSelectionAsSubAction( 1799 eNone, IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip); 1800 NS_WARNING_ASSERTION( 1801 NS_SUCCEEDED(rv), 1802 "EditorBase::DeleteSelectionAsSubAction(eNone) failed, but ignored"); 1803 return EditorBase::ToGenericNSResult(rv); 1804 } 1805 1806 NS_IMETHODIMP EditorBase::CanCut(bool* aCanCut) { 1807 if (NS_WARN_IF(!aCanCut)) { 1808 return NS_ERROR_INVALID_ARG; 1809 } 1810 *aCanCut = IsCutCommandEnabled(); 1811 return NS_OK; 1812 } 1813 1814 bool EditorBase::IsCutCommandEnabled() const { 1815 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1816 if (NS_WARN_IF(!editActionData.CanHandle())) { 1817 return false; 1818 } 1819 1820 if (IsModifiable() && IsCopyToClipboardAllowedInternal()) { 1821 return true; 1822 } 1823 1824 // If there's an event listener for "cut", we always enable the command 1825 // as we don't really know what the listener may want to do in response. 1826 // We look up the event target chain for a possible listener on a parent 1827 // in addition to checking the immediate target. 1828 return CheckForClipboardCommandListener(nsGkAtoms::oncut, eCut); 1829 } 1830 1831 NS_IMETHODIMP EditorBase::Copy() { 1832 AutoEditActionDataSetter editActionData(*this, EditAction::eCopy); 1833 if (NS_WARN_IF(!editActionData.CanHandle())) { 1834 return NS_ERROR_NOT_INITIALIZED; 1835 } 1836 1837 Result<ClipboardEventResult, nsresult> ret = 1838 DispatchClipboardEventAndUpdateClipboard( 1839 eCopy, Some(nsIClipboard::kGlobalClipboard)); 1840 if (MOZ_UNLIKELY(ret.isErr())) { 1841 NS_WARNING( 1842 "EditorBase::DispatchClipboardEventAndUpdateClipboard(eCopy, " 1843 "nsIClipboard::kGlobalClipboard) failed"); 1844 return EditorBase::ToGenericNSResult(ret.unwrapErr()); 1845 } 1846 switch (ret.unwrap()) { 1847 case ClipboardEventResult::DoDefault: 1848 case ClipboardEventResult::CopyOrCutHandled: 1849 return NS_OK; 1850 case ClipboardEventResult::IgnoredOrError: 1851 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 1852 case ClipboardEventResult::DefaultPreventedOfPaste: 1853 MOZ_ASSERT_UNREACHABLE("Invalid result for eCopy"); 1854 } 1855 return NS_ERROR_UNEXPECTED; 1856 } 1857 1858 NS_IMETHODIMP EditorBase::CanCopy(bool* aCanCopy) { 1859 if (NS_WARN_IF(!aCanCopy)) { 1860 return NS_ERROR_INVALID_ARG; 1861 } 1862 *aCanCopy = IsCopyCommandEnabled(); 1863 return NS_OK; 1864 } 1865 1866 bool EditorBase::IsCopyCommandEnabled() const { 1867 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1868 if (NS_WARN_IF(!editActionData.CanHandle())) { 1869 return false; 1870 } 1871 1872 if (IsCopyToClipboardAllowedInternal()) { 1873 return true; 1874 } 1875 1876 // Like "cut", always enable "copy" if there's a listener. 1877 return CheckForClipboardCommandListener(nsGkAtoms::oncopy, eCopy); 1878 } 1879 1880 NS_IMETHODIMP EditorBase::Paste(nsIClipboard::ClipboardType aClipboardType) { 1881 if (uint32_t(aClipboardType) >= nsIClipboard::kClipboardTypeCount) { 1882 return NS_ERROR_INVALID_ARG; 1883 } 1884 const nsresult rv = PasteAsAction(aClipboardType, DispatchPasteEvent::Yes); 1885 NS_WARNING_ASSERTION( 1886 NS_SUCCEEDED(rv), 1887 "EditorBase::PasteAsAction(DispatchPasteEvent::Yes) failed"); 1888 return rv; 1889 } 1890 1891 nsresult EditorBase::PasteAsAction(nsIClipboard::ClipboardType aClipboardType, 1892 DispatchPasteEvent aDispatchPasteEvent, 1893 DataTransfer* aDataTransfer /* = nullptr */, 1894 nsIPrincipal* aPrincipal /* = nullptr */) { 1895 if (IsHTMLEditor() && IsReadonly()) { 1896 return NS_OK; 1897 } 1898 1899 // Create the same DataTransfer object here so we can share it between 1900 // the clipboard event and the call to HandlePaste below. This prevents 1901 // race conditions with Content Analysis on like we see in bug 1918027. 1902 // Note that this is not needed if we're not going to dispatch the paste 1903 // event and no aDataTransfer was passed in. 1904 RefPtr<DataTransfer> dataTransfer = aDataTransfer; 1905 if (!aDataTransfer && aDispatchPasteEvent == DispatchPasteEvent::Yes) { 1906 dataTransfer = CreateDataTransferForPaste(ePaste, aClipboardType); 1907 } 1908 AutoEditActionDataSetter editActionData(*this, EditAction::ePaste, 1909 aPrincipal); 1910 const auto clearDataTransfer = MakeScopeExit([&] { 1911 // If the caller passed in aDataTransfer, they are responsible for clearing 1912 // this. 1913 if (!aDataTransfer && dataTransfer) { 1914 dataTransfer->ClearForPaste(); 1915 } 1916 }); 1917 if (NS_WARN_IF(!editActionData.CanHandle())) { 1918 return NS_ERROR_NOT_INITIALIZED; 1919 } 1920 1921 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) { 1922 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 1923 if (NS_WARN_IF(!focusManager)) { 1924 return NS_ERROR_UNEXPECTED; 1925 } 1926 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement(); 1927 1928 Result<ClipboardEventResult, nsresult> ret = Err(NS_ERROR_FAILURE); 1929 { 1930 // This method is not set up to pass back the new aDataTransfer 1931 // if it changes. If we need this in the future, we can change 1932 // aDataTransfer to be a RefPtr<DataTransfer>*. 1933 AutoTrackDataTransferForPaste trackDataTransfer(*this, dataTransfer); 1934 1935 ret = DispatchClipboardEventAndUpdateClipboard( 1936 ePaste, Some(aClipboardType), dataTransfer); 1937 if (MOZ_UNLIKELY(ret.isErr())) { 1938 NS_WARNING( 1939 "EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) " 1940 "failed"); 1941 return EditorBase::ToGenericNSResult(ret.unwrapErr()); 1942 } 1943 } 1944 switch (ret.inspect()) { 1945 case ClipboardEventResult::DoDefault: 1946 break; 1947 case ClipboardEventResult::DefaultPreventedOfPaste: 1948 case ClipboardEventResult::IgnoredOrError: 1949 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 1950 case ClipboardEventResult::CopyOrCutHandled: 1951 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste"); 1952 } 1953 1954 // If focus is changed by a "paste" event listener, we should keep handling 1955 // the "pasting" in new focused editor because Chrome works as so. 1956 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement(); 1957 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) { 1958 // For the privacy reason, let's top handling it if new focused element is 1959 // in different document. 1960 if (focusManager->GetFocusedWindow() != GetWindow()) { 1961 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 1962 } 1963 RefPtr<EditorBase> editorBase = 1964 nsContentUtils::GetActiveEditor(GetPresContext()); 1965 if (!editorBase || (editorBase->IsHTMLEditor() && 1966 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) { 1967 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 1968 } 1969 if (editorBase != this) { 1970 nsresult rv = editorBase->PasteAsAction( 1971 aClipboardType, DispatchPasteEvent::No, dataTransfer, aPrincipal); 1972 NS_WARNING_ASSERTION( 1973 NS_SUCCEEDED(rv), 1974 "EditorBase::PasteAsAction(DispatchPasteEvent::No) failed"); 1975 return EditorBase::ToGenericNSResult(rv); 1976 } 1977 } 1978 } else { 1979 // The caller must already have dispatched a "paste" event. 1980 editActionData.NotifyOfDispatchingClipboardEvent(); 1981 } 1982 nsresult rv = HandlePaste(editActionData, aClipboardType, dataTransfer); 1983 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::HandlePaste() failed"); 1984 return EditorBase::ToGenericNSResult(rv); 1985 } 1986 1987 nsresult EditorBase::PasteAsQuotationAsAction( 1988 nsIClipboard::ClipboardType aClipboardType, 1989 DispatchPasteEvent aDispatchPasteEvent, 1990 DataTransfer* aDataTransfer /* = nullptr */, 1991 nsIPrincipal* aPrincipal /* = nullptr */) { 1992 MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard || 1993 aClipboardType == nsIClipboard::kSelectionClipboard); 1994 1995 if (IsHTMLEditor() && IsReadonly()) { 1996 return NS_OK; 1997 } 1998 1999 // Create the same DataTransfer object here so we can share it between 2000 // the clipboard event and the call to HandlePasteAsQuotation below. This 2001 // prevents race conditions with Content Analysis on like we see in bug 2002 // 1918027. 2003 // Note that this is not needed if we're not going to dispatch the paste 2004 // event. 2005 RefPtr<DataTransfer> dataTransfer; 2006 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) { 2007 dataTransfer = aDataTransfer 2008 ? RefPtr<DataTransfer>(aDataTransfer) 2009 : RefPtr<DataTransfer>(CreateDataTransferForPaste( 2010 ePaste, aClipboardType)); 2011 } 2012 const auto clearDataTransfer = MakeScopeExit([&] { 2013 // If the caller passed in aDataTransfer, they are responsible for clearing 2014 // this. 2015 if (!aDataTransfer && dataTransfer) { 2016 dataTransfer->ClearForPaste(); 2017 } 2018 }); 2019 AutoEditActionDataSetter editActionData(*this, EditAction::ePasteAsQuotation, 2020 aPrincipal); 2021 if (NS_WARN_IF(!editActionData.CanHandle())) { 2022 return NS_ERROR_NOT_INITIALIZED; 2023 } 2024 2025 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) { 2026 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 2027 if (NS_WARN_IF(!focusManager)) { 2028 return NS_ERROR_UNEXPECTED; 2029 } 2030 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement(); 2031 2032 Result<ClipboardEventResult, nsresult> ret = Err(NS_ERROR_FAILURE); 2033 { 2034 // This method is not set up to pass back the new aDataTransfer 2035 // if it changes. If we need this in the future, we can change 2036 // aDataTransfer to be a RefPtr<DataTransfer>*. 2037 MOZ_ASSERT(!aDataTransfer); 2038 AutoTrackDataTransferForPaste trackDataTransfer(*this, dataTransfer); 2039 2040 ret = DispatchClipboardEventAndUpdateClipboard( 2041 ePaste, Some(aClipboardType), dataTransfer); 2042 if (MOZ_UNLIKELY(ret.isErr())) { 2043 NS_WARNING( 2044 "EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) " 2045 "failed"); 2046 return EditorBase::ToGenericNSResult(ret.unwrapErr()); 2047 } 2048 } 2049 switch (ret.inspect()) { 2050 case ClipboardEventResult::DoDefault: 2051 break; 2052 case ClipboardEventResult::DefaultPreventedOfPaste: 2053 case ClipboardEventResult::IgnoredOrError: 2054 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2055 case ClipboardEventResult::CopyOrCutHandled: 2056 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste"); 2057 } 2058 2059 // If focus is changed by a "paste" event listener, we should keep handling 2060 // the "pasting" in new focused editor because Chrome works as so. 2061 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement(); 2062 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) { 2063 // For the privacy reason, let's top handling it if new focused element is 2064 // in different document. 2065 if (focusManager->GetFocusedWindow() != GetWindow()) { 2066 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2067 } 2068 RefPtr<EditorBase> editorBase = 2069 nsContentUtils::GetActiveEditor(GetPresContext()); 2070 if (!editorBase || (editorBase->IsHTMLEditor() && 2071 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) { 2072 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2073 } 2074 if (editorBase != this) { 2075 nsresult rv = editorBase->PasteAsQuotationAsAction( 2076 aClipboardType, DispatchPasteEvent::No, dataTransfer, aPrincipal); 2077 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2078 "EditorBase::PasteAsQuotationAsAction(" 2079 "DispatchPasteEvent::No) failed"); 2080 return EditorBase::ToGenericNSResult(rv); 2081 } 2082 } 2083 } else { 2084 // The caller must already have dispatched a "paste" event. 2085 editActionData.NotifyOfDispatchingClipboardEvent(); 2086 } 2087 2088 nsresult rv = 2089 HandlePasteAsQuotation(editActionData, aClipboardType, dataTransfer); 2090 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2091 "EditorBase::HandlePasteAsQuotation() failed"); 2092 return EditorBase::ToGenericNSResult(rv); 2093 } 2094 2095 nsresult EditorBase::PasteTransferableAsAction( 2096 nsITransferable* aTransferable, DispatchPasteEvent aDispatchPasteEvent, 2097 nsIPrincipal* aPrincipal /* = nullptr */) { 2098 // FIXME: This may be called as a call of nsIEditor::PasteTransferable. 2099 // In this case, we should keep handling the paste even in the readonly mode. 2100 if (IsHTMLEditor() && IsReadonly()) { 2101 return NS_OK; 2102 } 2103 2104 AutoEditActionDataSetter editActionData(*this, EditAction::ePaste, 2105 aPrincipal); 2106 if (NS_WARN_IF(!editActionData.CanHandle())) { 2107 return NS_ERROR_NOT_INITIALIZED; 2108 } 2109 2110 if (aDispatchPasteEvent == DispatchPasteEvent::Yes) { 2111 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 2112 if (NS_WARN_IF(!focusManager)) { 2113 return NS_ERROR_UNEXPECTED; 2114 } 2115 const RefPtr<Element> focusedElement = focusManager->GetFocusedElement(); 2116 2117 // Use a nothing value for the clipboard type as data comes from 2118 // aTransferable and we don't currently implement a way to put that in the 2119 // data transfer in TextEditor yet. 2120 Result<ClipboardEventResult, nsresult> ret = 2121 DispatchClipboardEventAndUpdateClipboard( 2122 ePaste, 2123 IsTextEditor() ? Nothing() : Some(nsIClipboard::kGlobalClipboard)); 2124 if (MOZ_UNLIKELY(ret.isErr())) { 2125 NS_WARNING( 2126 "EditorBase::DispatchClipboardEventAndUpdateClipboard(ePaste) " 2127 "failed"); 2128 return EditorBase::ToGenericNSResult(ret.unwrapErr()); 2129 } 2130 switch (ret.inspect()) { 2131 case ClipboardEventResult::DoDefault: 2132 break; 2133 case ClipboardEventResult::DefaultPreventedOfPaste: 2134 case ClipboardEventResult::IgnoredOrError: 2135 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2136 case ClipboardEventResult::CopyOrCutHandled: 2137 MOZ_ASSERT_UNREACHABLE("Invalid result for ePaste"); 2138 } 2139 2140 // If focus is changed by a "paste" event listener, we should keep handling 2141 // the "pasting" in new focused editor because Chrome works as so. 2142 const RefPtr<Element> newFocusedElement = focusManager->GetFocusedElement(); 2143 if (MOZ_UNLIKELY(focusedElement != newFocusedElement)) { 2144 // For the privacy reason, let's top handling it if new focused element is 2145 // in different document. 2146 if (focusManager->GetFocusedWindow() != GetWindow()) { 2147 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2148 } 2149 RefPtr<EditorBase> editorBase = 2150 nsContentUtils::GetActiveEditor(GetPresContext()); 2151 if (!editorBase || (editorBase->IsHTMLEditor() && 2152 !editorBase->AsHTMLEditor()->IsActiveInDOMWindow())) { 2153 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_ACTION_CANCELED); 2154 } 2155 if (editorBase != this) { 2156 nsresult rv = editorBase->PasteTransferableAsAction( 2157 aTransferable, DispatchPasteEvent::No, aPrincipal); 2158 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2159 "EditorBase::PasteTransferableAsAction(" 2160 "DispatchPasteEvent::No) failed"); 2161 return EditorBase::ToGenericNSResult(rv); 2162 } 2163 } 2164 } else { 2165 // The caller must already have dispatched a "paste" event. 2166 editActionData.NotifyOfDispatchingClipboardEvent(); 2167 } 2168 2169 if (NS_WARN_IF(!aTransferable)) { 2170 return NS_ERROR_INVALID_ARG; 2171 } 2172 2173 nsresult rv = HandlePasteTransferable(editActionData, *aTransferable); 2174 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2175 "EditorBase::HandlePasteTransferable() failed"); 2176 return EditorBase::ToGenericNSResult(rv); 2177 } 2178 2179 nsresult EditorBase::PrepareToInsertContent( 2180 const EditorDOMPoint& aPointToInsert, 2181 DeleteSelectedContent aDeleteSelectedContent) { 2182 // TODO: Move this method to `EditorBase`. 2183 MOZ_ASSERT(IsEditActionDataAvailable()); 2184 2185 MOZ_ASSERT(aPointToInsert.IsSet()); 2186 2187 EditorDOMPoint pointToInsert(aPointToInsert); 2188 if (aDeleteSelectedContent == DeleteSelectedContent::Yes) { 2189 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToInsert); 2190 nsresult rv = DeleteSelectionAsSubAction( 2191 nsIEditor::eNone, 2192 IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip); 2193 if (NS_FAILED(rv)) { 2194 NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed"); 2195 return rv; 2196 } 2197 } 2198 2199 nsresult rv = CollapseSelectionTo(pointToInsert); 2200 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2201 "EditorBase::CollapseSelectionTo() failed"); 2202 return rv; 2203 } 2204 2205 nsresult EditorBase::InsertTextAt( 2206 const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert, 2207 DeleteSelectedContent aDeleteSelectedContent) { 2208 MOZ_ASSERT(IsEditActionDataAvailable()); 2209 MOZ_ASSERT(aPointToInsert.IsSet()); 2210 2211 nsresult rv = PrepareToInsertContent(aPointToInsert, aDeleteSelectedContent); 2212 if (NS_FAILED(rv)) { 2213 NS_WARNING("EditorBase::PrepareToInsertContent() failed"); 2214 return rv; 2215 } 2216 2217 rv = InsertTextAsSubAction(aStringToInsert, InsertTextFor::NormalText); 2218 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2219 "EditorBase::InsertTextAsSubAction() failed"); 2220 return rv; 2221 } 2222 2223 EditorBase::SafeToInsertData EditorBase::IsSafeToInsertData( 2224 nsIPrincipal* aSourcePrincipal) const { 2225 // Try to determine whether we should use a sanitizing fragment sink 2226 RefPtr<Document> destdoc = GetDocument(); 2227 NS_ASSERTION(destdoc, "Where is our destination doc?"); 2228 2229 nsIDocShell* docShell = nullptr; 2230 if (RefPtr<BrowsingContext> bc = destdoc->GetBrowsingContext()) { 2231 RefPtr<BrowsingContext> root = bc->Top(); 2232 MOZ_ASSERT(root, "root should not be null"); 2233 2234 docShell = root->GetDocShell(); 2235 } 2236 2237 bool isSafe = 2238 docShell && docShell->GetAppType() == nsIDocShell::APP_TYPE_EDITOR; 2239 2240 if (!isSafe && aSourcePrincipal) { 2241 nsIPrincipal* destPrincipal = destdoc->NodePrincipal(); 2242 NS_ASSERTION(destPrincipal, "How come we don't have a principal?"); 2243 DebugOnly<nsresult> rvIgnored = 2244 aSourcePrincipal->Subsumes(destPrincipal, &isSafe); 2245 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2246 "nsIPrincipal::Subsumes() failed, but ignored"); 2247 } 2248 2249 return isSafe ? SafeToInsertData::Yes : SafeToInsertData::No; 2250 } 2251 2252 NS_IMETHODIMP EditorBase::PasteTransferable(nsITransferable* aTransferable) { 2253 nsresult rv = 2254 PasteTransferableAsAction(aTransferable, DispatchPasteEvent::Yes); 2255 NS_WARNING_ASSERTION( 2256 NS_SUCCEEDED(rv), 2257 "EditorBase::PasteTransferableAsAction(DispatchPasteEvent::Yes) failed"); 2258 return rv; 2259 } 2260 2261 NS_IMETHODIMP EditorBase::CanPaste(nsIClipboard::ClipboardType aClipboardType, 2262 bool* aCanPaste) { 2263 if (uint32_t(aClipboardType) >= nsIClipboard::kClipboardTypeCount) { 2264 return NS_ERROR_INVALID_ARG; 2265 } 2266 if (NS_WARN_IF(!aCanPaste)) { 2267 return NS_ERROR_INVALID_ARG; 2268 } 2269 *aCanPaste = CanPaste(aClipboardType); 2270 return NS_OK; 2271 } 2272 2273 NS_IMETHODIMP EditorBase::SetAttribute(Element* aElement, 2274 const nsAString& aAttribute, 2275 const nsAString& aValue) { 2276 if (NS_WARN_IF(aAttribute.IsEmpty()) || NS_WARN_IF(!aElement)) { 2277 return NS_ERROR_INVALID_ARG; 2278 } 2279 2280 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute); 2281 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 2282 if (NS_FAILED(rv)) { 2283 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2284 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 2285 return EditorBase::ToGenericNSResult(rv); 2286 } 2287 2288 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute); 2289 rv = SetAttributeWithTransaction(*aElement, *attribute, aValue); 2290 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2291 "EditorBase::SetAttributeWithTransaction() failed"); 2292 return EditorBase::ToGenericNSResult(rv); 2293 } 2294 2295 nsresult EditorBase::SetAttributeWithTransaction(Element& aElement, 2296 nsAtom& aAttribute, 2297 const nsAString& aValue) { 2298 const RefPtr<ChangeAttributeTransaction> transaction = 2299 ChangeAttributeTransaction::Create(*this, aElement, aAttribute, aValue); 2300 nsresult rv = DoTransactionInternal(transaction); 2301 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2302 "EditorBase::DoTransactionInternal() failed"); 2303 return rv; 2304 } 2305 2306 NS_IMETHODIMP EditorBase::RemoveAttribute(Element* aElement, 2307 const nsAString& aAttribute) { 2308 if (NS_WARN_IF(aAttribute.IsEmpty()) || NS_WARN_IF(!aElement)) { 2309 return NS_ERROR_INVALID_ARG; 2310 } 2311 2312 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute); 2313 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 2314 if (NS_FAILED(rv)) { 2315 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2316 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 2317 return EditorBase::ToGenericNSResult(rv); 2318 } 2319 2320 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute); 2321 rv = RemoveAttributeWithTransaction(*aElement, *attribute); 2322 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2323 "EditorBase::RemoveAttributeWithTransaction() failed"); 2324 return EditorBase::ToGenericNSResult(rv); 2325 } 2326 2327 nsresult EditorBase::RemoveAttributeWithTransaction(Element& aElement, 2328 nsAtom& aAttribute) { 2329 if (!aElement.HasAttr(&aAttribute)) { 2330 return NS_OK; 2331 } 2332 const RefPtr<ChangeAttributeTransaction> transaction = 2333 ChangeAttributeTransaction::CreateToRemove(*this, aElement, aAttribute); 2334 nsresult rv = DoTransactionInternal(transaction); 2335 if (NS_WARN_IF(Destroyed())) { 2336 return NS_ERROR_EDITOR_DESTROYED; 2337 } 2338 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2339 "EditorBase::DoTransactionInternal() failed"); 2340 return rv; 2341 } 2342 2343 nsresult EditorBase::MarkElementDirty(Element& aElement) { 2344 // Mark the node dirty, but not for webpages (bug 599983) 2345 if (!OutputsMozDirty()) { 2346 return NS_OK; 2347 } 2348 nsresult rv = AutoElementAttrAPIWrapper(*this, aElement) 2349 .SetAttr(nsGkAtoms::mozdirty, EmptyString(), false); 2350 NS_WARNING_ASSERTION( 2351 NS_SUCCEEDED(rv), 2352 "AutoElementAttrAPIWrapper::SetAttr() failed, but ignored"); 2353 return rv; 2354 } 2355 2356 NS_IMETHODIMP EditorBase::GetInlineSpellChecker( 2357 bool aAutoCreate, nsIInlineSpellChecker** aInlineSpellChecker) { 2358 if (NS_WARN_IF(!aInlineSpellChecker)) { 2359 return NS_ERROR_INVALID_ARG; 2360 } 2361 2362 if (mDidPreDestroy) { 2363 // Don't allow people to get or create the spell checker once the editor 2364 // is going away. 2365 *aInlineSpellChecker = nullptr; 2366 return aAutoCreate ? NS_ERROR_NOT_AVAILABLE : NS_OK; 2367 } 2368 2369 // We don't want to show the spell checking UI if there are no spell check 2370 // dictionaries available. 2371 if (!mozInlineSpellChecker::CanEnableInlineSpellChecking()) { 2372 *aInlineSpellChecker = nullptr; 2373 return NS_ERROR_FAILURE; 2374 } 2375 2376 if (!mInlineSpellChecker && aAutoCreate) { 2377 mInlineSpellChecker = new mozInlineSpellChecker(); 2378 } 2379 2380 if (mInlineSpellChecker) { 2381 nsresult rv = mInlineSpellChecker->Init(this); 2382 if (NS_FAILED(rv)) { 2383 NS_WARNING("mozInlineSpellChecker::Init() failed"); 2384 mInlineSpellChecker = nullptr; 2385 return rv; 2386 } 2387 } 2388 2389 *aInlineSpellChecker = do_AddRef(mInlineSpellChecker).take(); 2390 return NS_OK; 2391 } 2392 2393 void EditorBase::SyncRealTimeSpell() { 2394 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 2395 if (NS_WARN_IF(!editActionData.CanHandle())) { 2396 return; 2397 } 2398 2399 bool enable = GetDesiredSpellCheckState(); 2400 2401 // Initializes mInlineSpellChecker 2402 nsCOMPtr<nsIInlineSpellChecker> spellChecker; 2403 DebugOnly<nsresult> rvIgnored = 2404 GetInlineSpellChecker(enable, getter_AddRefs(spellChecker)); 2405 NS_WARNING_ASSERTION( 2406 NS_SUCCEEDED(rvIgnored), 2407 "EditorBase::GetInlineSpellChecker() failed, but ignored"); 2408 2409 if (mInlineSpellChecker) { 2410 if (!mSpellCheckerDictionaryUpdated && enable) { 2411 DebugOnly<nsresult> rvIgnored = 2412 mInlineSpellChecker->UpdateCurrentDictionary(); 2413 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2414 "mozInlineSpellChecker::UpdateCurrentDictionary() " 2415 "failed, but ignored"); 2416 mSpellCheckerDictionaryUpdated = true; 2417 } 2418 2419 // We might have a mInlineSpellChecker even if there are no dictionaries 2420 // available since we don't destroy the mInlineSpellChecker when the last 2421 // dictionariy is removed, but in that case spellChecker is null 2422 DebugOnly<nsresult> rvIgnored = 2423 mInlineSpellChecker->SetEnableRealTimeSpell(enable && spellChecker); 2424 NS_WARNING_ASSERTION( 2425 NS_SUCCEEDED(rvIgnored), 2426 "mozInlineSpellChecker::SetEnableRealTimeSpell() failed, but ignored"); 2427 } 2428 } 2429 2430 NS_IMETHODIMP EditorBase::SetSpellcheckUserOverride(bool enable) { 2431 mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse; 2432 SyncRealTimeSpell(); 2433 return NS_OK; 2434 } 2435 2436 NS_IMETHODIMP EditorBase::InsertNode(nsINode* aNodeToInsert, 2437 nsINode* aContainer, uint32_t aOffset, 2438 bool aPreserveSelection, 2439 uint8_t aOptionalArgCount) { 2440 MOZ_DIAGNOSTIC_ASSERT(IsHTMLEditor()); 2441 2442 nsCOMPtr<nsIContent> contentToInsert = 2443 nsIContent::FromNodeOrNull(aNodeToInsert); 2444 if (NS_WARN_IF(!contentToInsert) || NS_WARN_IF(!aContainer)) { 2445 return NS_ERROR_NULL_POINTER; 2446 } 2447 2448 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertNode); 2449 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 2450 if (NS_FAILED(rv)) { 2451 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2452 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 2453 return EditorBase::ToGenericNSResult(rv); 2454 } 2455 2456 // Make dispatch `input` event after stopping preserving selection. 2457 AutoPlaceholderBatch treatAsOneTransaction( 2458 *this, 2459 ScrollSelectionIntoView::No, // not a user interaction 2460 __FUNCTION__); 2461 2462 Maybe<AutoTransactionsConserveSelection> preseveSelection; 2463 if (aOptionalArgCount && aPreserveSelection) { 2464 preseveSelection.emplace(*this); 2465 } 2466 2467 const uint32_t offset = std::min(aOffset, aContainer->Length()); 2468 Result<CreateContentResult, nsresult> insertContentResult = 2469 InsertNodeWithTransaction(*contentToInsert, 2470 EditorDOMPoint(aContainer, offset)); 2471 if (MOZ_UNLIKELY(insertContentResult.isErr())) { 2472 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 2473 return EditorBase::ToGenericNSResult(insertContentResult.unwrapErr()); 2474 } 2475 rv = insertContentResult.inspect().SuggestCaretPointTo( 2476 *this, {SuggestCaret::OnlyIfHasSuggestion, 2477 SuggestCaret::OnlyIfTransactionsAllowedToDoIt, 2478 SuggestCaret::AndIgnoreTrivialError}); 2479 if (NS_FAILED(rv)) { 2480 NS_WARNING("CreateContentResult::SuggestCaretPointTo() failed"); 2481 return EditorBase::ToGenericNSResult(rv); 2482 } 2483 NS_WARNING_ASSERTION( 2484 rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, 2485 "CreateContentResult::SuggestCaretPointTo() failed, but ignored"); 2486 return NS_OK; 2487 } 2488 2489 template <typename ContentNodeType> 2490 Result<CreateNodeResultBase<ContentNodeType>, nsresult> 2491 EditorBase::InsertNodeWithTransaction(ContentNodeType& aContentToInsert, 2492 const EditorDOMPoint& aPointToInsert) { 2493 MOZ_ASSERT(IsEditActionDataAvailable()); 2494 MOZ_ASSERT_IF(IsTextEditor(), !aContentToInsert.IsText()); 2495 2496 if (NS_WARN_IF(!aPointToInsert.IsSet())) { 2497 return Err(NS_ERROR_INVALID_ARG); 2498 } 2499 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 2500 2501 IgnoredErrorResult ignoredError; 2502 AutoEditSubActionNotifier startToHandleEditSubAction( 2503 *this, EditSubAction::eInsertNode, nsIEditor::eNext, ignoredError); 2504 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 2505 return Err(ignoredError.StealNSResult()); 2506 } 2507 NS_WARNING_ASSERTION( 2508 !ignoredError.Failed(), 2509 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 2510 2511 RefPtr<InsertNodeTransaction> transaction = 2512 InsertNodeTransaction::Create(*this, aContentToInsert, aPointToInsert); 2513 nsresult rv = DoTransactionInternal(transaction); 2514 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2515 "EditorBase::DoTransactionInternal() failed"); 2516 2517 DebugOnly<nsresult> rvIgnored = 2518 RangeUpdaterRef().SelAdjInsertNode(aPointToInsert); 2519 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2520 "RangeUpdater::SelAdjInsertNode() failed, but ignored"); 2521 2522 if (NS_WARN_IF(Destroyed())) { 2523 return Err(NS_ERROR_EDITOR_DESTROYED); 2524 } 2525 if (NS_WARN_IF(aContentToInsert.GetParentNode() != 2526 aPointToInsert.GetContainer())) { 2527 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2528 } 2529 if (NS_FAILED(rv)) { 2530 return Err(rv); 2531 } 2532 2533 if (IsHTMLEditor()) { 2534 TopLevelEditSubActionDataRef().DidInsertContent(*this, aContentToInsert); 2535 } 2536 2537 return CreateNodeResultBase<ContentNodeType>( 2538 aContentToInsert, transaction->SuggestPointToPutCaret<EditorDOMPoint>()); 2539 } 2540 2541 Result<CreateElementResult, nsresult> 2542 EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction( 2543 const EditorDOMPoint& aPointToInsert) { 2544 MOZ_ASSERT(IsEditActionDataAvailable()); 2545 MOZ_ASSERT(IsHTMLEditor() || !aPointToInsert.IsInTextNode()); 2546 2547 if (MOZ_UNLIKELY(!aPointToInsert.IsSet())) { 2548 return Err(NS_ERROR_FAILURE); 2549 } 2550 2551 EditorDOMPoint pointToInsert; 2552 if (IsTextEditor()) { 2553 pointToInsert = aPointToInsert; 2554 } else { 2555 Result<EditorDOMPoint, nsresult> maybePointToInsert = 2556 MOZ_KnownLive(AsHTMLEditor()) 2557 ->PrepareToInsertLineBreak(HTMLEditor::LineBreakType::BRElement, 2558 aPointToInsert); 2559 if (maybePointToInsert.isErr()) { 2560 return maybePointToInsert.propagateErr(); 2561 } 2562 MOZ_ASSERT(maybePointToInsert.inspect().IsSetAndValid()); 2563 pointToInsert = maybePointToInsert.unwrap(); 2564 } 2565 2566 Result<CreateElementResult, nsresult> insertBRElementResultOrError = 2567 InsertBRElement(WithTransaction::Yes, 2568 BRElementType::PaddingForEmptyLastLine, pointToInsert); 2569 NS_WARNING_ASSERTION(insertBRElementResultOrError.isOk(), 2570 "EditorBase::InsertBRElement(WithTransaction::Yes, " 2571 "BRElementType::PaddingForEmptyLastLine) failed"); 2572 return insertBRElementResultOrError; 2573 } 2574 2575 nsresult EditorBase::UpdateBRElementType(HTMLBRElement& aBRElement, 2576 BRElementType aNewType) { 2577 const bool brElementIsHidden = aBRElement.IsPaddingForEmptyEditor() || 2578 aBRElement.IsPaddingForEmptyLastLine(); 2579 const bool brElementWillBeHidden = aNewType != BRElementType::Normal; 2580 const auto SetBRElementFlags = [&]() { 2581 switch (aNewType) { 2582 case BRElementType::Normal: 2583 if (brElementIsHidden) { 2584 aBRElement.UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR | 2585 NS_PADDING_FOR_EMPTY_LAST_LINE); 2586 } 2587 break; 2588 case BRElementType::PaddingForEmptyEditor: 2589 if (brElementIsHidden) { 2590 aBRElement.UnsetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE); 2591 } 2592 aBRElement.SetFlags(NS_PADDING_FOR_EMPTY_EDITOR); 2593 break; 2594 case BRElementType::PaddingForEmptyLastLine: 2595 if (brElementIsHidden) { 2596 aBRElement.UnsetFlags(NS_PADDING_FOR_EMPTY_EDITOR); 2597 } 2598 aBRElement.SetFlags(NS_PADDING_FOR_EMPTY_LAST_LINE); 2599 break; 2600 } 2601 }; 2602 // If the <br> element is in the composed doc, it must be observed by 2603 // IMEContentObserver. However, IMEContentObserver cannot observe the state 2604 // change, but changing the <br> type may make the <br> element visible or 2605 // invisible for ContentEventHandler. Therefore, IMEContentObserver needs to 2606 // notify IME of the state change as a text change notification of adding or 2607 // removing a line break. Therefore, we need to reconnect the <br> element 2608 // temporarily for making IMEContentObserver observable this change. 2609 if (!aBRElement.IsInComposedDoc() || 2610 brElementIsHidden == brElementWillBeHidden) { 2611 SetBRElementFlags(); 2612 return NS_OK; 2613 } 2614 EditorDOMPoint pointToInsert(&aBRElement); 2615 { 2616 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert); 2617 nsresult rv = DeleteNodeWithTransaction(aBRElement); 2618 if (NS_FAILED(rv)) { 2619 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2620 return rv; 2621 } 2622 } 2623 if (NS_WARN_IF(!pointToInsert.IsSetAndValid())) { 2624 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 2625 } 2626 SetBRElementFlags(); 2627 Result<CreateElementResult, nsresult> result = 2628 InsertNodeWithTransaction<Element>(aBRElement, pointToInsert); 2629 if (MOZ_UNLIKELY(result.isErr())) { 2630 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 2631 return result.unwrapErr(); 2632 } 2633 result.inspect().IgnoreCaretPointSuggestion(); 2634 return NS_OK; 2635 } 2636 2637 Result<CreateElementResult, nsresult> EditorBase::InsertBRElement( 2638 WithTransaction aWithTransaction, BRElementType aBRElementType, 2639 const EditorDOMPoint& aPointToInsert) { 2640 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 2641 2642 const RefPtr<HTMLBRElement> newBRElement = 2643 HTMLBRElement::FromNodeOrNull(RefPtr{CreateHTMLContent(nsGkAtoms::br)}); 2644 if (MOZ_UNLIKELY(!newBRElement)) { 2645 NS_WARNING("EditorBase::CreateHTMLContent() failed"); 2646 return Err(NS_ERROR_FAILURE); 2647 } 2648 nsresult rv = MarkElementDirty(*newBRElement); 2649 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 2650 NS_WARNING("EditorBase::MarkElementDirty() caused destroying the editor"); 2651 return Err(NS_ERROR_EDITOR_DESTROYED); 2652 } 2653 if (aBRElementType != BRElementType::Normal) { 2654 nsresult rv = UpdateBRElementType(*newBRElement, aBRElementType); 2655 if (NS_FAILED(rv)) { 2656 NS_WARNING("EditorBase::UpdateBRElementType() failed"); 2657 return Err(rv); 2658 } 2659 } 2660 if (aWithTransaction == WithTransaction::Yes) { 2661 Result<CreateElementResult, nsresult> insertBRElementResultOrError = 2662 InsertNodeWithTransaction<Element>(*newBRElement, aPointToInsert); 2663 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 2664 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 2665 return insertBRElementResultOrError.propagateErr(); 2666 } 2667 CreateElementResult insertBRElementResult = 2668 insertBRElementResultOrError.unwrap(); 2669 insertBRElementResult.IgnoreCaretPointSuggestion(); 2670 } else { 2671 (void)aPointToInsert.Offset(); 2672 RefPtr<InsertNodeTransaction> transaction = 2673 InsertNodeTransaction::Create(*this, *newBRElement, aPointToInsert); 2674 nsresult rv = transaction->DoTransaction(); 2675 if (NS_WARN_IF(Destroyed())) { 2676 return Err(NS_ERROR_EDITOR_DESTROYED); 2677 } 2678 if (NS_FAILED(rv)) { 2679 NS_WARNING("InsertNodeTransaction::DoTransaction() failed"); 2680 return Err(rv); 2681 } 2682 RangeUpdaterRef().SelAdjInsertNode(EditorRawDOMPoint( 2683 aPointToInsert.GetContainer(), aPointToInsert.Offset())); 2684 } 2685 if (NS_WARN_IF(newBRElement->GetParentNode() != 2686 aPointToInsert.GetContainer())) { 2687 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2688 } 2689 return CreateElementResult( 2690 *newBRElement, 2691 EditorDOMPoint(newBRElement, aBRElementType == BRElementType::Normal 2692 ? InterlinePosition::StartOfNextLine 2693 : InterlinePosition::EndOfLine)); 2694 } 2695 2696 NS_IMETHODIMP EditorBase::DeleteNode(nsINode* aNode, bool aPreserveSelection, 2697 uint8_t aOptionalArgCount) { 2698 MOZ_ASSERT_UNREACHABLE("Do not use this API with TextEditor"); 2699 return NS_ERROR_NOT_IMPLEMENTED; 2700 } 2701 2702 nsresult EditorBase::DeleteNodeWithTransaction(nsIContent& aContent) { 2703 MOZ_ASSERT(IsEditActionDataAvailable()); 2704 MOZ_ASSERT_IF(IsTextEditor(), !aContent.IsText()); 2705 2706 // Do nothing if the node is read-only. 2707 if (IsHTMLEditor() && NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aContent))) { 2708 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 2709 } 2710 2711 IgnoredErrorResult ignoredError; 2712 AutoEditSubActionNotifier startToHandleEditSubAction( 2713 *this, EditSubAction::eDeleteNode, nsIEditor::ePrevious, ignoredError); 2714 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 2715 return ignoredError.StealNSResult(); 2716 } 2717 NS_WARNING_ASSERTION( 2718 !ignoredError.Failed(), 2719 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 2720 2721 if (IsHTMLEditor()) { 2722 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContent); 2723 } 2724 2725 // FYI: DeleteNodeTransaction grabs aContent while it's alive. So, it's safe 2726 // to refer aContent even after calling DoTransaction(). 2727 RefPtr<DeleteNodeTransaction> deleteNodeTransaction = 2728 DeleteNodeTransaction::MaybeCreate(*this, aContent); 2729 NS_WARNING_ASSERTION(deleteNodeTransaction, 2730 "DeleteNodeTransaction::MaybeCreate() failed"); 2731 nsresult rv; 2732 if (deleteNodeTransaction) { 2733 rv = DoTransactionInternal(deleteNodeTransaction); 2734 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2735 "EditorBase::DoTransactionInternal() failed"); 2736 2737 if (mTextServicesDocument && NS_SUCCEEDED(rv)) { 2738 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument; 2739 textServicesDocument->DidDeleteContent(aContent); 2740 } 2741 } else { 2742 rv = NS_ERROR_FAILURE; 2743 } 2744 2745 if (!mActionListeners.IsEmpty()) { 2746 for (auto& listener : mActionListeners.Clone()) { 2747 DebugOnly<nsresult> rvIgnored = listener->DidDeleteNode(&aContent, rv); 2748 NS_WARNING_ASSERTION( 2749 NS_SUCCEEDED(rvIgnored), 2750 "nsIEditActionListener::DidDeleteNode() failed, but ignored"); 2751 } 2752 } 2753 2754 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv; 2755 } 2756 2757 NS_IMETHODIMP EditorBase::NotifySelectionChanged(Document* aDocument, 2758 Selection* aSelection, 2759 int16_t aReason, 2760 int32_t aAmount) { 2761 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) { 2762 return NS_ERROR_INVALID_ARG; 2763 } 2764 2765 if (mTextInputListener) { 2766 RefPtr<TextInputListener> textInputListener = mTextInputListener; 2767 textInputListener->OnSelectionChange(*aSelection, aReason); 2768 } 2769 2770 if (mIMEContentObserver) { 2771 RefPtr<IMEContentObserver> observer = mIMEContentObserver; 2772 observer->OnSelectionChange(*aSelection); 2773 } 2774 2775 return NS_OK; 2776 } 2777 2778 void EditorBase::NotifyEditorObservers( 2779 NotificationForEditorObservers aNotification) { 2780 MOZ_ASSERT(IsEditActionDataAvailable()); 2781 2782 switch (aNotification) { 2783 case eNotifyEditorObserversOfEnd: 2784 mIsInEditSubAction = false; 2785 2786 if (mEditActionData) { 2787 mEditActionData->MarkAsHandled(); 2788 } 2789 2790 if (mTextInputListener) { 2791 // TODO: TextInputListener::OnEditActionHandled() may return 2792 // NS_ERROR_OUT_OF_MEMORY. If so and if 2793 // TextControlState::SetValue() setting value with us, we should 2794 // return the result to EditorBase::ReplaceTextAsAction(), 2795 // EditorBase::DeleteSelectionAsAction() and 2796 // TextEditor::InsertTextAsAction(). However, it requires a lot 2797 // of changes in editor classes, but it's not so important since 2798 // editor does not use fallible allocation. Therefore, normally, 2799 // the process must be crashed anyway. 2800 RefPtr<TextInputListener> listener = mTextInputListener; 2801 nsresult rv = 2802 listener->OnEditActionHandled(MOZ_KnownLive(*AsTextEditor())); 2803 MOZ_RELEASE_ASSERT(rv != NS_ERROR_OUT_OF_MEMORY, 2804 "Setting value failed due to out of memory"); 2805 NS_WARNING_ASSERTION( 2806 NS_SUCCEEDED(rv), 2807 "TextInputListener::OnEditActionHandled() failed, but ignored"); 2808 } 2809 2810 if (mIMEContentObserver) { 2811 RefPtr<IMEContentObserver> observer = mIMEContentObserver; 2812 observer->OnEditActionHandled(); 2813 } 2814 2815 if (!mDispatchInputEvent || IsEditActionAborted() || 2816 IsEditActionCanceled()) { 2817 MOZ_LOG( 2818 gEventLog, LogLevel::Warning, 2819 ("%p %s: Not dispatching \"input\" event (mDispatchInputEvent=%s, " 2820 "IsEditActionAborted()=%s, IsEditActionCanceled()=%s", 2821 this, mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 2822 mDispatchInputEvent ? "true" : "false", 2823 IsEditActionAborted() ? "true" : "false", 2824 IsEditActionCanceled() ? "true" : "false")); 2825 break; 2826 } 2827 2828 DispatchInputEvent(); 2829 break; 2830 case eNotifyEditorObserversOfBefore: 2831 if (NS_WARN_IF(mIsInEditSubAction)) { 2832 return; 2833 } 2834 2835 mIsInEditSubAction = true; 2836 2837 if (mIMEContentObserver) { 2838 RefPtr<IMEContentObserver> observer = mIMEContentObserver; 2839 observer->BeforeEditAction(); 2840 } 2841 return; 2842 case eNotifyEditorObserversOfCancel: 2843 mIsInEditSubAction = false; 2844 2845 if (mEditActionData) { 2846 mEditActionData->MarkAsHandled(); 2847 } 2848 2849 if (mIMEContentObserver) { 2850 RefPtr<IMEContentObserver> observer = mIMEContentObserver; 2851 observer->CancelEditAction(); 2852 } 2853 break; 2854 default: 2855 MOZ_CRASH("Handle all notifications here"); 2856 break; 2857 } 2858 2859 if (IsHTMLEditor() && !Destroyed()) { 2860 // We may need to show resizing handles or update existing ones after 2861 // all transactions are done. This way of doing is preferred to DOM 2862 // mutation events listeners because all the changes the user can apply 2863 // to a document may result in multiple events, some of them quite hard 2864 // to listen too (in particular when an ancestor of the selection is 2865 // changed but the selection itself is not changed). 2866 DebugOnly<nsresult> rvIgnored = 2867 MOZ_KnownLive(AsHTMLEditor())->RefreshEditingUI(); 2868 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2869 "HTMLEditor::RefreshEditingUI() failed, but ignored"); 2870 } 2871 } 2872 2873 void EditorBase::DispatchInputEvent() { 2874 MOZ_ASSERT(IsEditActionDataAvailable()); 2875 MOZ_ASSERT(!IsEditActionCanceled(), 2876 "If preceding beforeinput event is canceled, we shouldn't " 2877 "dispatch input event"); 2878 MOZ_ASSERT( 2879 !ShouldAlreadyHaveHandledBeforeInputEventDispatching(), 2880 "We've not handled beforeinput event but trying to dispatch input event"); 2881 2882 // We don't need to dispatch multiple input events if there is a pending 2883 // input event. However, it may have different event target. If we resolved 2884 // this issue, we need to manage the pending events in an array. But it's 2885 // overwork. We don't need to do it for the very rare case. 2886 // TODO: However, we start to set InputEvent.inputType. So, each "input" 2887 // event now notifies web app each change. So, perhaps, we should 2888 // not omit input events. 2889 2890 RefPtr<Element> targetElement = GetInputEventTargetElement(); 2891 if (NS_WARN_IF(!targetElement)) { 2892 MOZ_LOG(gEventLog, LogLevel::Error, 2893 ("%p %s: Failed dispatching \"input\" event due to no target", this, 2894 mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor")); 2895 return; 2896 } 2897 RefPtr<DataTransfer> dataTransfer = GetInputEventDataTransfer(); 2898 mEditActionData->WillDispatchInputEvent(); 2899 MOZ_LOG(gEventLog, LogLevel::Info, 2900 ("%p %s: Dispatching \"input\" event: { inputType=\"%s\" }...", this, 2901 mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 2902 ToString(ToInputType(GetEditAction())).c_str())); 2903 DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent( 2904 targetElement, eEditorInput, ToInputType(GetEditAction()), this, 2905 dataTransfer ? InputEventOptions(dataTransfer, 2906 InputEventOptions::NeverCancelable::No) 2907 : InputEventOptions(GetInputEventData(), 2908 InputEventOptions::NeverCancelable::No)); 2909 MOZ_LOG(gEventLog, LogLevel::Debug, 2910 ("%p %s: Dispatched \"input\" event: { inputType=\"%s\" }", this, 2911 mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 2912 ToString(ToInputType(GetEditAction())).c_str())); 2913 mEditActionData->DidDispatchInputEvent(); 2914 NS_WARNING_ASSERTION( 2915 NS_SUCCEEDED(rvIgnored), 2916 "nsContentUtils::DispatchInputEvent() failed, but ignored"); 2917 } 2918 2919 NS_IMETHODIMP EditorBase::AddEditActionListener( 2920 nsIEditActionListener* aListener) { 2921 if (NS_WARN_IF(!aListener)) { 2922 return NS_ERROR_INVALID_ARG; 2923 } 2924 2925 // If given edit action listener is text services document for the inline 2926 // spell checker, store it as reference of concrete class for performance 2927 // reason. 2928 if (mInlineSpellChecker) { 2929 EditorSpellCheck* editorSpellCheck = 2930 mInlineSpellChecker->GetEditorSpellCheck(); 2931 if (editorSpellCheck) { 2932 mozSpellChecker* spellChecker = editorSpellCheck->GetSpellChecker(); 2933 if (spellChecker) { 2934 TextServicesDocument* textServicesDocument = 2935 spellChecker->GetTextServicesDocument(); 2936 if (static_cast<nsIEditActionListener*>(textServicesDocument) == 2937 aListener) { 2938 mTextServicesDocument = textServicesDocument; 2939 return NS_OK; 2940 } 2941 } 2942 } 2943 } 2944 2945 // Make sure the listener isn't already on the list 2946 if (!mActionListeners.Contains(aListener)) { 2947 mActionListeners.AppendElement(*aListener); 2948 NS_WARNING_ASSERTION( 2949 mActionListeners.Length() != 1, 2950 "nsIEditActionListener installed, this editor becomes slower"); 2951 } 2952 2953 return NS_OK; 2954 } 2955 2956 NS_IMETHODIMP EditorBase::RemoveEditActionListener( 2957 nsIEditActionListener* aListener) { 2958 if (NS_WARN_IF(!aListener)) { 2959 return NS_ERROR_INVALID_ARG; 2960 } 2961 2962 if (static_cast<nsIEditActionListener*>(mTextServicesDocument) == aListener) { 2963 mTextServicesDocument = nullptr; 2964 return NS_OK; 2965 } 2966 2967 NS_WARNING_ASSERTION(mActionListeners.Length() != 1, 2968 "All nsIEditActionListeners have been removed, this " 2969 "editor becomes faster"); 2970 mActionListeners.RemoveElement(aListener); 2971 2972 return NS_OK; 2973 } 2974 2975 NS_IMETHODIMP EditorBase::AddDocumentStateListener( 2976 nsIDocumentStateListener* aListener) { 2977 if (NS_WARN_IF(!aListener)) { 2978 return NS_ERROR_INVALID_ARG; 2979 } 2980 2981 if (!mDocStateListeners.Contains(aListener)) { 2982 mDocStateListeners.AppendElement(*aListener); 2983 } 2984 2985 return NS_OK; 2986 } 2987 2988 NS_IMETHODIMP EditorBase::RemoveDocumentStateListener( 2989 nsIDocumentStateListener* aListener) { 2990 if (NS_WARN_IF(!aListener)) { 2991 return NS_ERROR_INVALID_ARG; 2992 } 2993 2994 mDocStateListeners.RemoveElement(aListener); 2995 2996 return NS_OK; 2997 } 2998 2999 NS_IMETHODIMP EditorBase::ForceCompositionEnd() { 3000 nsresult rv = CommitComposition(); 3001 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3002 "EditorBase::CommitComposition() failed"); 3003 return rv; 3004 } 3005 3006 nsresult EditorBase::CommitComposition() { 3007 nsPresContext* presContext = GetPresContext(); 3008 if (NS_WARN_IF(!presContext)) { 3009 return NS_ERROR_NOT_AVAILABLE; 3010 } 3011 3012 if (!mComposition) { 3013 return NS_OK; 3014 } 3015 nsresult rv = 3016 IMEStateManager::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, presContext); 3017 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "IMEStateManager::NotifyIME() failed"); 3018 return rv; 3019 } 3020 3021 NS_IMETHODIMP EditorBase::GetComposing(bool* aResult) { 3022 if (NS_WARN_IF(!aResult)) { 3023 return NS_ERROR_INVALID_ARG; 3024 } 3025 *aResult = IsIMEComposing(); 3026 return NS_OK; 3027 } 3028 3029 NS_IMETHODIMP EditorBase::GetRootElement(Element** aRootElement) { 3030 if (NS_WARN_IF(!aRootElement)) { 3031 return NS_ERROR_INVALID_ARG; 3032 } 3033 *aRootElement = do_AddRef(mRootElement).take(); 3034 return NS_WARN_IF(!*aRootElement) ? NS_ERROR_NOT_AVAILABLE : NS_OK; 3035 } 3036 3037 void EditorBase::OnStartToHandleTopLevelEditSubAction( 3038 EditSubAction aTopLevelEditSubAction, 3039 nsIEditor::EDirection aDirectionOfTopLevelEditSubAction, ErrorResult& aRv) { 3040 MOZ_ASSERT(IsEditActionDataAvailable()); 3041 MOZ_ASSERT(!aRv.Failed()); 3042 mEditActionData->SetTopLevelEditSubAction(aTopLevelEditSubAction, 3043 aDirectionOfTopLevelEditSubAction); 3044 } 3045 3046 nsresult EditorBase::OnEndHandlingTopLevelEditSubAction() { 3047 MOZ_ASSERT(IsEditActionDataAvailable()); 3048 mEditActionData->SetTopLevelEditSubAction(EditSubAction::eNone, eNone); 3049 return NS_OK; 3050 } 3051 3052 void EditorBase::DoInsertText(Text& aText, uint32_t aOffset, 3053 const nsAString& aStringToInsert, 3054 ErrorResult& aRv) { 3055 { 3056 AutoCharacterDataAPIWrapper charDataWrapper(*this, aText); 3057 aRv = charDataWrapper.InsertData(aOffset, aStringToInsert); 3058 if (MOZ_UNLIKELY(aRv.Failed())) { 3059 NS_WARNING("AutoCharacterDataAPIWrapper::InsertData() failed"); 3060 return; 3061 } 3062 NS_WARNING_ASSERTION(charDataWrapper.IsExpectedResult(aStringToInsert), 3063 "Inserting data caused other mutations, but ignored"); 3064 } 3065 if (IsTextEditor() && !aStringToInsert.IsEmpty()) { 3066 aRv = MOZ_KnownLive(AsTextEditor()) 3067 ->DidInsertText(aText.TextLength(), aOffset, 3068 aStringToInsert.Length()); 3069 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed"); 3070 } 3071 } 3072 3073 void EditorBase::DoDeleteText(Text& aText, uint32_t aOffset, uint32_t aCount, 3074 ErrorResult& aRv) { 3075 if (IsTextEditor() && aCount > 0) { 3076 AsTextEditor()->WillDeleteText(aText.TextLength(), aOffset, aCount); 3077 } 3078 AutoCharacterDataAPIWrapper charDataWrapper(*this, aText); 3079 aRv = charDataWrapper.DeleteData(aOffset, aCount); 3080 if (MOZ_UNLIKELY(aRv.Failed())) { 3081 NS_WARNING("AutoCharacterDataAPIWrapper::DeleteData() failed"); 3082 return; 3083 } 3084 NS_WARNING_ASSERTION(charDataWrapper.IsExpectedResult(EmptyString()), 3085 "Deleting data caused other mutations, but ignored"); 3086 } 3087 3088 void EditorBase::DoReplaceText(Text& aText, uint32_t aOffset, uint32_t aCount, 3089 const nsAString& aStringToInsert, 3090 ErrorResult& aRv) { 3091 if (IsTextEditor() && aCount > 0) { 3092 AsTextEditor()->WillDeleteText(aText.TextLength(), aOffset, aCount); 3093 } 3094 { 3095 AutoCharacterDataAPIWrapper charDataWrapper(*this, aText); 3096 aRv = charDataWrapper.ReplaceData(aOffset, aCount, aStringToInsert); 3097 if (MOZ_UNLIKELY(aRv.Failed())) { 3098 NS_WARNING("AutoCharacterDataAPIWrapper::ReplaceData() failed"); 3099 return; 3100 } 3101 NS_WARNING_ASSERTION(charDataWrapper.IsExpectedResult(aStringToInsert), 3102 "Replacing data caused other mutations, but ignored"); 3103 } 3104 if (IsTextEditor() && !aStringToInsert.IsEmpty()) { 3105 aRv = MOZ_KnownLive(AsTextEditor()) 3106 ->DidInsertText(aText.TextLength(), aOffset, 3107 aStringToInsert.Length()); 3108 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed"); 3109 } 3110 } 3111 3112 void EditorBase::DoSetText(Text& aText, const nsAString& aStringToSet, 3113 ErrorResult& aRv) { 3114 if (IsTextEditor()) { 3115 uint32_t length = aText.TextLength(); 3116 if (length > 0) { 3117 AsTextEditor()->WillDeleteText(length, 0, length); 3118 } 3119 } 3120 { 3121 AutoCharacterDataAPIWrapper charDataWrapper(*this, aText); 3122 aRv = charDataWrapper.SetData(aStringToSet); 3123 if (MOZ_UNLIKELY(aRv.Failed())) { 3124 NS_WARNING("AutoCharacterDataAPIWrapper::SetData() failed"); 3125 return; 3126 } 3127 NS_WARNING_ASSERTION(charDataWrapper.IsExpectedResult(aStringToSet), 3128 "Setting data caused other mutations, but ignored"); 3129 } 3130 if (IsTextEditor() && !aStringToSet.IsEmpty()) { 3131 aRv = MOZ_KnownLive(AsTextEditor()) 3132 ->DidInsertText(aText.Length(), 0, aStringToSet.Length()); 3133 NS_WARNING_ASSERTION(!aRv.Failed(), "TextEditor::DidInsertText() failed"); 3134 } 3135 } 3136 3137 nsresult EditorBase::CloneAttributeWithTransaction(nsAtom& aAttribute, 3138 Element& aDestElement, 3139 Element& aSourceElement) { 3140 nsAutoString attrValue; 3141 if (aSourceElement.GetAttr(&aAttribute, attrValue)) { 3142 nsresult rv = 3143 SetAttributeWithTransaction(aDestElement, aAttribute, attrValue); 3144 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3145 "EditorBase::SetAttributeWithTransaction() failed"); 3146 return rv; 3147 } 3148 nsresult rv = RemoveAttributeWithTransaction(aDestElement, aAttribute); 3149 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3150 "EditorBase::RemoveAttributeWithTransaction() failed"); 3151 return rv; 3152 } 3153 3154 NS_IMETHODIMP EditorBase::CloneAttributes(Element* aDestElement, 3155 Element* aSourceElement) { 3156 if (NS_WARN_IF(!aDestElement) || NS_WARN_IF(!aSourceElement)) { 3157 return NS_ERROR_INVALID_ARG; 3158 } 3159 3160 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute); 3161 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3162 if (NS_FAILED(rv)) { 3163 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3164 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 3165 return EditorBase::ToGenericNSResult(rv); 3166 } 3167 3168 CloneAttributesWithTransaction(*aDestElement, *aSourceElement); 3169 3170 return NS_OK; 3171 } 3172 3173 void EditorBase::CloneAttributesWithTransaction(Element& aDestElement, 3174 Element& aSourceElement) { 3175 AutoPlaceholderBatch treatAsOneTransaction( 3176 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3177 3178 // Use transaction system for undo only if destination is already in the 3179 // document 3180 Element* rootElement = GetRoot(); 3181 if (NS_WARN_IF(!rootElement)) { 3182 return; 3183 } 3184 3185 const OwningNonNull<Element> destElement(aDestElement); 3186 const OwningNonNull<Element> sourceElement(aSourceElement); 3187 bool isDestElementInBody = rootElement->Contains(destElement); 3188 3189 // Clear existing attributes 3190 AutoTArray<OwningNonNull<nsAtom>, 16> destElementAttributes; 3191 if (const uint32_t attrCount = destElement->GetAttrCount()) { 3192 destElementAttributes.SetCapacity(attrCount); 3193 for (const uint32_t i : IntegerRange(attrCount)) { 3194 if (const nsAttrName* attrName = destElement->GetUnsafeAttrNameAt(i)) { 3195 MOZ_ASSERT(attrName->LocalName()); 3196 destElementAttributes.AppendElement(*attrName->LocalName()); 3197 } 3198 } 3199 } 3200 for (const OwningNonNull<nsAtom>& attr : destElementAttributes) { 3201 if (isDestElementInBody) { 3202 DebugOnly<nsresult> rvIgnored = 3203 RemoveAttributeWithTransaction(destElement, MOZ_KnownLive(*attr)); 3204 NS_WARNING_ASSERTION( 3205 NS_SUCCEEDED(rvIgnored), 3206 "EditorBase::RemoveAttributeWithTransaction() failed, but ignored"); 3207 } else { 3208 AutoElementAttrAPIWrapper elementWrapper(*this, destElement); 3209 if (NS_FAILED(elementWrapper.UnsetAttr(MOZ_KnownLive(attr), true))) { 3210 NS_WARNING( 3211 "AutoElementAttrAPIWrapper::UnsetAttr() failed, but ignored"); 3212 } else { 3213 NS_WARNING_ASSERTION( 3214 elementWrapper.IsExpectedResult(EmptyString()), 3215 "Removing attribute caused other mutations, but ignored"); 3216 } 3217 } 3218 } 3219 3220 // Set just the attributes that the source element has 3221 AutoTArray<std::pair<OwningNonNull<nsAtom>, nsString>, 16> 3222 sourceElementAttributes; 3223 if (const uint32_t attrCount = sourceElement->GetAttrCount()) { 3224 sourceElementAttributes.SetCapacity(attrCount); 3225 for (const uint32_t i : IntegerRange(attrCount)) { 3226 const BorrowedAttrInfo attrInfo = sourceElement->GetAttrInfoAt(i); 3227 if (const nsAttrName* attrName = attrInfo.mName) { 3228 MOZ_ASSERT(attrName->LocalName()); 3229 MOZ_ASSERT(attrInfo.mValue); 3230 nsString value; 3231 attrInfo.mValue->ToString(value); 3232 sourceElementAttributes.AppendElement(std::make_pair( 3233 OwningNonNull<nsAtom>(*attrName->LocalName()), std::move(value))); 3234 } 3235 } 3236 } 3237 for (const auto& attr : sourceElementAttributes) { 3238 if (isDestElementInBody) { 3239 DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent( 3240 destElement, MOZ_KnownLive(attr.first), attr.second, false); 3241 NS_WARNING_ASSERTION( 3242 NS_SUCCEEDED(rvIgnored), 3243 "EditorBase::SetAttributeOrEquivalent() failed, but ignored"); 3244 } else { 3245 // The element is not inserted in the document yet, we don't want to put 3246 // a transaction on the UndoStack 3247 DebugOnly<nsresult> rvIgnored = SetAttributeOrEquivalent( 3248 destElement, MOZ_KnownLive(attr.first), attr.second, true); 3249 NS_WARNING_ASSERTION( 3250 NS_SUCCEEDED(rvIgnored), 3251 "EditorBase::SetAttributeOrEquivalent() failed, but ignored"); 3252 } 3253 } 3254 } 3255 3256 nsresult EditorBase::ScrollSelectionFocusIntoView() const { 3257 nsISelectionController* selectionController = GetSelectionController(); 3258 if (!selectionController) { 3259 return NS_OK; 3260 } 3261 3262 DebugOnly<nsresult> rvIgnored = selectionController->ScrollSelectionIntoView( 3263 SelectionType::eNormal, nsISelectionController::SELECTION_FOCUS_REGION, 3264 ScrollAxis(), ScrollAxis(), ScrollFlags::ScrollOverflowHidden); 3265 NS_WARNING_ASSERTION( 3266 NS_SUCCEEDED(rvIgnored), 3267 "nsISelectionController::ScrollSelectionIntoView() failed, but ignored"); 3268 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK; 3269 } 3270 3271 EditorDOMPoint EditorBase::ComputePointToInsertText( 3272 const EditorDOMPoint& aPoint, InsertTextTo aInsertTextTo) const { 3273 if (aInsertTextTo == InsertTextTo::SpecifiedPoint) { 3274 return aPoint; 3275 } 3276 3277 if (IsTextEditor()) { 3278 // In some cases, the node may be the anonymous div element or a padding 3279 // <br> element for empty last line. Let's try to look for better insertion 3280 // point in the nearest text node if there is. 3281 return AsTextEditor()->FindBetterInsertionPoint(aPoint); 3282 } 3283 auto pointToInsert = 3284 aPoint.GetPointInTextNodeIfPointingAroundTextNode<EditorDOMPoint>(); 3285 // If the candidate point is in a Text node which has only a preformatted 3286 // linefeed, we should not insert text into the node because it may have 3287 // been inserted by us and that's compatible behavior with Chrome. 3288 if (pointToInsert.IsInTextNode() && 3289 HTMLEditUtils::TextHasOnlyOnePreformattedLinefeed( 3290 *pointToInsert.ContainerAs<Text>())) { 3291 if (pointToInsert.IsStartOfContainer()) { 3292 if (Text* const previousText = Text::FromNodeOrNull( 3293 pointToInsert.ContainerAs<Text>()->GetPreviousSibling())) { 3294 pointToInsert = EditorDOMPoint::AtEndOf(*previousText); 3295 } else { 3296 pointToInsert = pointToInsert.ParentPoint(); 3297 } 3298 } else { 3299 MOZ_ASSERT(pointToInsert.IsEndOfContainer()); 3300 if (Text* const nextText = Text::FromNodeOrNull( 3301 pointToInsert.ContainerAs<Text>()->GetNextSibling())) { 3302 pointToInsert = EditorDOMPoint(nextText, 0u); 3303 } else { 3304 pointToInsert = pointToInsert.AfterContainer(); 3305 } 3306 } 3307 } 3308 if (aInsertTextTo == InsertTextTo::AlwaysCreateNewTextNode) { 3309 NS_WARNING_ASSERTION(!pointToInsert.IsInTextNode() || 3310 pointToInsert.IsStartOfContainer() || 3311 pointToInsert.IsEndOfContainer(), 3312 "aPointToInsert is \"AlwaysCreateNewTextNode\", but " 3313 "specified point middle of a `Text`"); 3314 if (!pointToInsert.IsInTextNode()) { 3315 return pointToInsert; 3316 } 3317 return pointToInsert.IsStartOfContainer() 3318 ? EditorDOMPoint(pointToInsert.ContainerAs<Text>()) 3319 : (pointToInsert.IsEndOfContainer() 3320 ? EditorDOMPoint::After( 3321 *pointToInsert.ContainerAs<Text>()) 3322 : pointToInsert); 3323 } 3324 if (aInsertTextTo == InsertTextTo::ExistingTextNodeIfAvailableAndNotStart) { 3325 return !(pointToInsert.IsInTextNode() && pointToInsert.IsStartOfContainer()) 3326 ? pointToInsert 3327 : EditorDOMPoint(pointToInsert.ContainerAs<Text>()); 3328 } 3329 return pointToInsert; 3330 } 3331 3332 Result<InsertTextResult, nsresult> EditorBase::InsertTextWithTransaction( 3333 const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert, 3334 InsertTextTo aInsertTextTo) { 3335 MOZ_ASSERT_IF(IsTextEditor(), 3336 aInsertTextTo == InsertTextTo::ExistingTextNodeIfAvailable); 3337 3338 if (NS_WARN_IF(!aPointToInsert.IsSet())) { 3339 return Err(NS_ERROR_INVALID_ARG); 3340 } 3341 3342 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 3343 3344 if (!ShouldHandleIMEComposition() && aStringToInsert.IsEmpty()) { 3345 return InsertTextResult(); 3346 } 3347 3348 EditorDOMPoint pointToInsert = 3349 ComputePointToInsertText(aPointToInsert, aInsertTextTo); 3350 if (ShouldHandleIMEComposition()) { 3351 if (!pointToInsert.IsInTextNode()) { 3352 // create a text node 3353 RefPtr<nsTextNode> newTextNode = CreateTextNode(u""_ns); 3354 if (NS_WARN_IF(!newTextNode)) { 3355 return Err(NS_ERROR_FAILURE); 3356 } 3357 // then we insert it into the dom tree 3358 Result<CreateTextResult, nsresult> insertTextNodeResult = 3359 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert); 3360 if (MOZ_UNLIKELY(insertTextNodeResult.isErr())) { 3361 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 3362 return insertTextNodeResult.propagateErr(); 3363 } 3364 insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion(); 3365 pointToInsert.Set(newTextNode, 0u); 3366 } 3367 Result<InsertTextResult, nsresult> insertTextResult = 3368 InsertTextIntoTextNodeWithTransaction(aStringToInsert, 3369 pointToInsert.AsInText()); 3370 NS_WARNING_ASSERTION( 3371 insertTextResult.isOk(), 3372 "EditorBase::InsertTextIntoTextNodeWithTransaction() failed"); 3373 return insertTextResult; 3374 } 3375 3376 if (pointToInsert.IsInTextNode()) { 3377 // we are inserting text into an existing text node. 3378 Result<InsertTextResult, nsresult> insertTextResult = 3379 InsertTextIntoTextNodeWithTransaction(aStringToInsert, 3380 pointToInsert.AsInText()); 3381 NS_WARNING_ASSERTION( 3382 insertTextResult.isOk(), 3383 "EditorBase::InsertTextIntoTextNodeWithTransaction() failed"); 3384 return insertTextResult; 3385 } 3386 3387 // we are inserting text into a non-text node. first we have to create a 3388 // textnode (this also populates it with the text) 3389 RefPtr<nsTextNode> newTextNode = CreateTextNode(aStringToInsert); 3390 if (NS_WARN_IF(!newTextNode)) { 3391 return Err(NS_ERROR_FAILURE); 3392 } 3393 // then we insert it into the dom tree 3394 Result<CreateTextResult, nsresult> insertTextNodeResult = 3395 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert); 3396 if (MOZ_UNLIKELY(insertTextNodeResult.isErr())) { 3397 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 3398 return Err(insertTextNodeResult.unwrapErr()); 3399 } 3400 insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion(); 3401 if (NS_WARN_IF(!newTextNode->IsInComposedDoc())) { 3402 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3403 } 3404 return InsertTextResult(EditorDOMPoint::AtEndOf(*newTextNode), 3405 EditorDOMPoint::AtEndOf(*newTextNode)); 3406 } 3407 3408 std::tuple<EditorDOMPointInText, EditorDOMPointInText> 3409 EditorBase::ComputeInsertedRange(const EditorDOMPointInText& aInsertedPoint, 3410 const nsAString& aInsertedString) const { 3411 MOZ_ASSERT(aInsertedPoint.IsSet()); 3412 3413 EditorDOMPointInText endOfInsertion( 3414 aInsertedPoint.ContainerAs<Text>(), 3415 aInsertedPoint.Offset() + aInsertedString.Length()); 3416 return {aInsertedPoint, endOfInsertion}; 3417 } 3418 3419 Result<InsertTextResult, nsresult> 3420 EditorBase::InsertTextIntoTextNodeWithTransaction( 3421 const nsAString& aStringToInsert, 3422 const EditorDOMPointInText& aPointToInsert) { 3423 MOZ_ASSERT(IsEditActionDataAvailable()); 3424 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 3425 3426 RefPtr<EditTransactionBase> transaction; 3427 bool isIMETransaction = false; 3428 if (ShouldHandleIMEComposition()) { 3429 transaction = 3430 CompositionTransaction::Create(*this, aStringToInsert, aPointToInsert); 3431 isIMETransaction = true; 3432 } else { 3433 transaction = 3434 InsertTextTransaction::Create(*this, aStringToInsert, aPointToInsert); 3435 } 3436 3437 // XXX We may not need these view batches anymore. This is handled at a 3438 // higher level now I believe. 3439 BeginUpdateViewBatch(__FUNCTION__); 3440 nsresult rv = DoTransactionInternal(transaction); 3441 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3442 "EditorBase::DoTransactionInternal() failed"); 3443 EndUpdateViewBatch(__FUNCTION__); 3444 3445 // Don't check whether we've been destroyed here because we need to notify 3446 // listeners and observers below even if we've already destroyed. 3447 3448 auto pointToInsert = [&]() -> EditorDOMPointInText { 3449 if (!isIMETransaction) { 3450 return aPointToInsert; 3451 } 3452 if (NS_WARN_IF(!mComposition->GetContainerTextNode())) { 3453 return aPointToInsert; 3454 } 3455 return EditorDOMPointInText( 3456 mComposition->GetContainerTextNode(), 3457 std::min(mComposition->XPOffsetInTextNode(), 3458 mComposition->GetContainerTextNode()->TextDataLength())); 3459 }(); 3460 3461 EditorDOMPoint endOfInsertedText( 3462 pointToInsert.ContainerAs<Text>(), 3463 pointToInsert.Offset() + aStringToInsert.Length()); 3464 3465 if (IsHTMLEditor()) { 3466 auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert); 3467 if (begin.IsSet() && end.IsSet()) { 3468 TopLevelEditSubActionDataRef().DidInsertText( 3469 *this, begin.RefOrTo<EditorRawDOMPoint>(), 3470 end.RefOrTo<EditorRawDOMPoint>()); 3471 } 3472 if (isIMETransaction) { 3473 // Let's mark the text node as "modified frequently" if it interact with 3474 // IME since non-ASCII character may be inserted into it in most cases. 3475 pointToInsert.ContainerAs<Text>()->MarkAsMaybeModifiedFrequently(); 3476 } 3477 // XXX Should we update endOfInsertedText here? 3478 } 3479 3480 // let listeners know what happened 3481 if (!mActionListeners.IsEmpty()) { 3482 for (auto& listener : mActionListeners.Clone()) { 3483 // TODO: might need adaptation because of mutation event listeners called 3484 // during `DoTransactionInternal`. 3485 DebugOnly<nsresult> rvIgnored = listener->DidInsertText( 3486 pointToInsert.ContainerAs<Text>(), 3487 static_cast<int32_t>(pointToInsert.Offset()), aStringToInsert, rv); 3488 NS_WARNING_ASSERTION( 3489 NS_SUCCEEDED(rvIgnored), 3490 "nsIEditActionListener::DidInsertText() failed, but ignored"); 3491 } 3492 } 3493 3494 // Added some cruft here for bug 43366. Layout was crashing because we left 3495 // an empty text node lying around in the document. So I delete empty text 3496 // nodes caused by IME. I have to mark the IME transaction as "fixed", which 3497 // means that furure IME txns won't merge with it. This is because we don't 3498 // want future IME txns trying to put their text into a node that is no 3499 // longer in the document. This does not break undo/redo, because all these 3500 // txns are wrapped in a parent PlaceHolder txn, and placeholder txns are 3501 // already savvy to having multiple ime txns inside them. 3502 3503 // Delete empty IME text node if there is one 3504 if (IsHTMLEditor() && isIMETransaction && mComposition) { 3505 RefPtr<Text> textNode = mComposition->GetContainerTextNode(); 3506 if (textNode && !textNode->Length()) { 3507 endOfInsertedText.Set(textNode); 3508 AutoEditorDOMPointChildInvalidator lockIndex(endOfInsertedText); 3509 rv = DeleteNodeWithTransaction(*textNode); 3510 if (MOZ_LIKELY(!textNode->IsInComposedDoc())) { 3511 mComposition->OnTextNodeRemoved(); 3512 } 3513 static_cast<CompositionTransaction*>(transaction.get())->MarkFixed(); 3514 if (NS_FAILED(rv)) { 3515 NS_WARNING("EditorBase::DeleteNodeTransaction() failed"); 3516 return Err(rv); 3517 } 3518 if (NS_WARN_IF(!endOfInsertedText.IsSetAndValidInComposedDoc())) { 3519 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3520 } 3521 } 3522 } 3523 3524 if (NS_WARN_IF(Destroyed())) { 3525 return Err(NS_ERROR_EDITOR_DESTROYED); 3526 } 3527 3528 InsertTextTransaction* const insertTextTransaction = 3529 transaction->GetAsInsertTextTransaction(); 3530 return insertTextTransaction 3531 ? InsertTextResult(std::move(endOfInsertedText), 3532 insertTextTransaction 3533 ->SuggestPointToPutCaret<EditorDOMPoint>()) 3534 : InsertTextResult(std::move(endOfInsertedText)); 3535 } 3536 3537 nsresult EditorBase::NotifyDocumentListeners( 3538 TDocumentListenerNotification aNotificationType) { 3539 switch (aNotificationType) { 3540 case eDocumentCreated: 3541 if (IsTextEditor()) { 3542 return NS_OK; 3543 } 3544 if (RefPtr<ComposerCommandsUpdater> composerCommandsUpdate = 3545 AsHTMLEditor()->mComposerCommandsUpdater) { 3546 composerCommandsUpdate->OnHTMLEditorCreated(); 3547 } 3548 return NS_OK; 3549 3550 case eDocumentToBeDestroyed: { 3551 RefPtr<ComposerCommandsUpdater> composerCommandsUpdate = 3552 IsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr; 3553 if (!mDocStateListeners.Length() && !composerCommandsUpdate) { 3554 return NS_OK; 3555 } 3556 // Needs to store all listeners before notifying ComposerCommandsUpdate 3557 // since notifying it might change mDocStateListeners. 3558 const AutoDocumentStateListenerArray listeners( 3559 mDocStateListeners.Clone()); 3560 if (composerCommandsUpdate) { 3561 composerCommandsUpdate->OnBeforeHTMLEditorDestroyed(); 3562 } 3563 for (auto& listener : listeners) { 3564 // MOZ_KnownLive because 'listeners' is guaranteed to 3565 // keep it alive. 3566 // 3567 // This can go away once 3568 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. 3569 nsresult rv = MOZ_KnownLive(listener)->NotifyDocumentWillBeDestroyed(); 3570 if (NS_FAILED(rv)) { 3571 NS_WARNING( 3572 "nsIDocumentStateListener::NotifyDocumentWillBeDestroyed() " 3573 "failed"); 3574 return rv; 3575 } 3576 } 3577 return NS_OK; 3578 } 3579 case eDocumentStateChanged: { 3580 bool docIsDirty; 3581 nsresult rv = GetDocumentModified(&docIsDirty); 3582 if (NS_FAILED(rv)) { 3583 NS_WARNING("EditorBase::GetDocumentModified() failed"); 3584 return rv; 3585 } 3586 3587 if (static_cast<int8_t>(docIsDirty) == mDocDirtyState) { 3588 return NS_OK; 3589 } 3590 3591 mDocDirtyState = docIsDirty; 3592 3593 RefPtr<ComposerCommandsUpdater> composerCommandsUpdate = 3594 IsHTMLEditor() ? AsHTMLEditor()->mComposerCommandsUpdater : nullptr; 3595 if (!mDocStateListeners.Length() && !composerCommandsUpdate) { 3596 return NS_OK; 3597 } 3598 // Needs to store all listeners before notifying ComposerCommandsUpdate 3599 // since notifying it might change mDocStateListeners. 3600 const AutoDocumentStateListenerArray listeners( 3601 mDocStateListeners.Clone()); 3602 if (composerCommandsUpdate) { 3603 composerCommandsUpdate->OnHTMLEditorDirtyStateChanged(mDocDirtyState); 3604 } 3605 for (auto& listener : listeners) { 3606 // MOZ_KnownLive because 'listeners' is guaranteed to 3607 // keep it alive. 3608 // 3609 // This can go away once 3610 // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. 3611 nsresult rv = 3612 MOZ_KnownLive(listener)->NotifyDocumentStateChanged(mDocDirtyState); 3613 if (NS_FAILED(rv)) { 3614 NS_WARNING( 3615 "nsIDocumentStateListener::NotifyDocumentStateChanged() failed"); 3616 return rv; 3617 } 3618 } 3619 return NS_OK; 3620 } 3621 default: 3622 MOZ_ASSERT_UNREACHABLE("Unknown notification"); 3623 return NS_ERROR_FAILURE; 3624 } 3625 } 3626 3627 nsresult EditorBase::SetTextNodeWithoutTransaction(const nsAString& aString, 3628 Text& aTextNode) { 3629 MOZ_ASSERT(IsEditActionDataAvailable()); 3630 MOZ_ASSERT(IsTextEditor()); 3631 MOZ_ASSERT(!IsUndoRedoEnabled()); 3632 3633 const uint32_t length = aTextNode.Length(); 3634 3635 // Let listeners know what's up 3636 if (!mActionListeners.IsEmpty() && length) { 3637 for (auto& listener : mActionListeners.Clone()) { 3638 DebugOnly<nsresult> rvIgnored = 3639 listener->WillDeleteText(MOZ_KnownLive(&aTextNode), 0, length); 3640 if (NS_WARN_IF(Destroyed())) { 3641 NS_WARNING( 3642 "nsIEditActionListener::WillDeleteText() failed, but ignored"); 3643 return NS_ERROR_EDITOR_DESTROYED; 3644 } 3645 } 3646 } 3647 3648 // We don't support undo here, so we don't really need all of the transaction 3649 // machinery, therefore we can run our transaction directly, breaking all of 3650 // the rules! 3651 IgnoredErrorResult error; 3652 DoSetText(aTextNode, aString, error); 3653 if (MOZ_UNLIKELY(error.Failed())) { 3654 NS_WARNING("EditorBase::DoSetText() failed"); 3655 return error.StealNSResult(); 3656 } 3657 3658 CollapseSelectionTo(EditorRawDOMPoint(&aTextNode, aString.Length()), error); 3659 if (MOZ_UNLIKELY(error.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3660 NS_WARNING("EditorBase::CollapseSelection() caused destroying the editor"); 3661 return NS_ERROR_EDITOR_DESTROYED; 3662 } 3663 NS_ASSERTION(!error.Failed(), 3664 "EditorBase::CollapseSelectionTo() failed, but ignored"); 3665 3666 RangeUpdaterRef().SelAdjReplaceText(aTextNode, 0, length, aString.Length()); 3667 3668 // Let listeners know what happened 3669 if (!mActionListeners.IsEmpty() && !aString.IsEmpty()) { 3670 for (auto& listener : mActionListeners.Clone()) { 3671 DebugOnly<nsresult> rvIgnored = 3672 listener->DidInsertText(&aTextNode, 0, aString, NS_OK); 3673 if (NS_WARN_IF(Destroyed())) { 3674 return NS_ERROR_EDITOR_DESTROYED; 3675 } 3676 NS_WARNING_ASSERTION( 3677 NS_SUCCEEDED(rvIgnored), 3678 "nsIEditActionListener::DidInsertText() failed, but ignored"); 3679 } 3680 } 3681 3682 return NS_OK; 3683 } 3684 3685 Result<CaretPoint, nsresult> EditorBase::DeleteTextWithTransaction( 3686 Text& aTextNode, uint32_t aOffset, uint32_t aLength) { 3687 MOZ_ASSERT(IsEditActionDataAvailable()); 3688 3689 RefPtr<DeleteTextTransaction> transaction = 3690 DeleteTextTransaction::MaybeCreate(*this, aTextNode, aOffset, aLength); 3691 if (MOZ_UNLIKELY(!transaction)) { 3692 NS_WARNING("DeleteTextTransaction::MaybeCreate() failed"); 3693 return Err(NS_ERROR_FAILURE); 3694 } 3695 3696 IgnoredErrorResult ignoredError; 3697 AutoEditSubActionNotifier startToHandleEditSubAction( 3698 *this, EditSubAction::eDeleteText, nsIEditor::ePrevious, ignoredError); 3699 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3700 return Err(ignoredError.StealNSResult()); 3701 } 3702 NS_WARNING_ASSERTION( 3703 !ignoredError.Failed(), 3704 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3705 3706 // Let listeners know what's up 3707 if (!mActionListeners.IsEmpty()) { 3708 for (auto& listener : mActionListeners.Clone()) { 3709 DebugOnly<nsresult> rvIgnored = 3710 listener->WillDeleteText(&aTextNode, aOffset, aLength); 3711 NS_WARNING_ASSERTION( 3712 NS_SUCCEEDED(rvIgnored), 3713 "nsIEditActionListener::WillDeleteText() failed, but ignored"); 3714 } 3715 } 3716 3717 nsresult rv = DoTransactionInternal(transaction); 3718 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3719 "EditorBase::DoTransactionInternal() failed"); 3720 3721 if (IsHTMLEditor()) { 3722 TopLevelEditSubActionDataRef().DidDeleteText( 3723 *this, EditorRawDOMPoint(&aTextNode, aOffset)); 3724 } 3725 3726 if (NS_WARN_IF(Destroyed())) { 3727 return Err(NS_ERROR_EDITOR_DESTROYED); 3728 } 3729 if (NS_FAILED(rv)) { 3730 return Err(rv); 3731 } 3732 3733 return CaretPoint(transaction->SuggestPointToPutCaret()); 3734 } 3735 3736 bool EditorBase::IsRoot(const nsINode* inNode) const { 3737 if (NS_WARN_IF(!inNode)) { 3738 return false; 3739 } 3740 nsINode* rootNode = GetRoot(); 3741 return inNode == rootNode; 3742 } 3743 3744 bool EditorBase::IsDescendantOfRoot(const nsINode* inNode) const { 3745 if (NS_WARN_IF(!inNode)) { 3746 return false; 3747 } 3748 nsIContent* root = GetRoot(); 3749 if (NS_WARN_IF(!root)) { 3750 return false; 3751 } 3752 3753 return inNode->IsInclusiveDescendantOf(root); 3754 } 3755 3756 NS_IMETHODIMP EditorBase::IncrementModificationCount(int32_t inNumMods) { 3757 uint32_t oldModCount = mModCount; 3758 3759 mModCount += inNumMods; 3760 3761 if ((!oldModCount && mModCount) || (oldModCount && !mModCount)) { 3762 DebugOnly<nsresult> rvIgnored = 3763 NotifyDocumentListeners(eDocumentStateChanged); 3764 NS_WARNING_ASSERTION( 3765 NS_SUCCEEDED(rvIgnored), 3766 "EditorBase::NotifyDocumentListeners() failed, but ignored"); 3767 } 3768 return NS_OK; 3769 } 3770 3771 NS_IMETHODIMP EditorBase::GetModificationCount(int32_t* aOutModCount) { 3772 if (NS_WARN_IF(!aOutModCount)) { 3773 return NS_ERROR_INVALID_ARG; 3774 } 3775 *aOutModCount = mModCount; 3776 return NS_OK; 3777 } 3778 3779 NS_IMETHODIMP EditorBase::ResetModificationCount() { 3780 bool doNotify = (mModCount != 0); 3781 3782 mModCount = 0; 3783 3784 if (!doNotify) { 3785 return NS_OK; 3786 } 3787 3788 DebugOnly<nsresult> rvIgnored = 3789 NotifyDocumentListeners(eDocumentStateChanged); 3790 NS_WARNING_ASSERTION( 3791 NS_SUCCEEDED(rvIgnored), 3792 "EditorBase::NotifyDocumentListeners() failed, but ignored"); 3793 return NS_OK; 3794 } 3795 3796 template <typename EditorDOMPointType> 3797 EditorDOMPointType EditorBase::GetFirstSelectionStartPoint() const { 3798 MOZ_ASSERT(IsEditActionDataAvailable()); 3799 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) { 3800 return EditorDOMPointType(); 3801 } 3802 3803 const nsRange* range = SelectionRef().GetRangeAt(0); 3804 if (MOZ_UNLIKELY(NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()))) { 3805 return EditorDOMPointType(); 3806 } 3807 3808 return EditorDOMPointType(range->StartRef()); 3809 } 3810 3811 template <typename EditorDOMPointType> 3812 EditorDOMPointType EditorBase::GetFirstSelectionEndPoint() const { 3813 MOZ_ASSERT(IsEditActionDataAvailable()); 3814 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) { 3815 return EditorDOMPointType(); 3816 } 3817 3818 const nsRange* range = SelectionRef().GetRangeAt(0); 3819 if (MOZ_UNLIKELY(NS_WARN_IF(!range) || NS_WARN_IF(!range->IsPositioned()))) { 3820 return EditorDOMPointType(); 3821 } 3822 3823 return EditorDOMPointType(range->EndRef()); 3824 } 3825 3826 // static 3827 nsresult EditorBase::GetEndChildNode(const Selection& aSelection, 3828 nsIContent** aEndNode) { 3829 MOZ_ASSERT(aEndNode); 3830 3831 *aEndNode = nullptr; 3832 3833 if (NS_WARN_IF(!aSelection.RangeCount())) { 3834 return NS_ERROR_FAILURE; 3835 } 3836 3837 const nsRange* range = aSelection.GetRangeAt(0); 3838 if (NS_WARN_IF(!range)) { 3839 return NS_ERROR_FAILURE; 3840 } 3841 3842 if (NS_WARN_IF(!range->IsPositioned())) { 3843 return NS_ERROR_FAILURE; 3844 } 3845 3846 NS_IF_ADDREF(*aEndNode = range->GetChildAtEndOffset()); 3847 return NS_OK; 3848 } 3849 3850 nsresult EditorBase::EnsurePaddingBRElementInMultilineEditor() { 3851 MOZ_ASSERT(IsEditActionDataAvailable()); 3852 MOZ_ASSERT(IsTextEditor() || AsHTMLEditor()->IsPlaintextMailComposer()); 3853 MOZ_ASSERT(!IsSingleLineEditor()); 3854 3855 Element* anonymousDivOrBodyElement = GetRoot(); 3856 if (NS_WARN_IF(!anonymousDivOrBodyElement)) { 3857 return NS_ERROR_FAILURE; 3858 } 3859 3860 // Assuming EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() has been 3861 // called first. 3862 // XXX This assumption is wrong. This method may be called alone. Actually, 3863 // we see this warning in mochitest log. So, we should fix this bug 3864 // later. 3865 if (NS_WARN_IF(!anonymousDivOrBodyElement->GetLastChild())) { 3866 return NS_ERROR_FAILURE; 3867 } 3868 3869 RefPtr<HTMLBRElement> brElement = 3870 HTMLBRElement::FromNode(anonymousDivOrBodyElement->GetLastChild()); 3871 if (!brElement) { 3872 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary 3873 // in normal cases. However, it may be required for nested edit 3874 // actions which may be caused by legacy mutation event listeners or 3875 // chrome script. 3876 AutoTransactionsConserveSelection dontChangeMySelection(*this); 3877 EditorDOMPoint endOfAnonymousDiv( 3878 EditorDOMPoint::AtEndOf(*anonymousDivOrBodyElement)); 3879 Result<CreateElementResult, nsresult> insertPaddingBRElementResult = 3880 InsertPaddingBRElementForEmptyLastLineWithTransaction( 3881 endOfAnonymousDiv); 3882 if (MOZ_UNLIKELY(insertPaddingBRElementResult.isErr())) { 3883 NS_WARNING( 3884 "EditorBase::InsertPaddingBRElementForEmptyLastLineWithTransaction() " 3885 "failed"); 3886 return insertPaddingBRElementResult.unwrapErr(); 3887 } 3888 insertPaddingBRElementResult.inspect().IgnoreCaretPointSuggestion(); 3889 return NS_OK; 3890 } 3891 3892 // Check to see if the trailing BR is a former padding <br> element for empty 3893 // editor - this will have stuck around if we previously morphed a trailing 3894 // node into a padding <br> element. 3895 if (!brElement->IsPaddingForEmptyEditor()) { 3896 return NS_OK; 3897 } 3898 3899 // Morph it back to a padding <br> element for empty last line. 3900 nsresult rv = 3901 UpdateBRElementType(*brElement, BRElementType::PaddingForEmptyLastLine); 3902 if (NS_FAILED(rv)) { 3903 NS_WARNING("EditorBase::UpdateBRElementType() failed"); 3904 return rv; 3905 } 3906 3907 return NS_OK; 3908 } 3909 3910 void EditorBase::BeginUpdateViewBatch(const char* aRequesterFuncName) { 3911 MOZ_ASSERT(IsEditActionDataAvailable()); 3912 MOZ_ASSERT(mUpdateCount >= 0, "bad state"); 3913 3914 if (!mUpdateCount) { 3915 // Turn off selection updates and notifications. 3916 SelectionRef().StartBatchChanges(aRequesterFuncName); 3917 } 3918 3919 mUpdateCount++; 3920 } 3921 3922 void EditorBase::EndUpdateViewBatch(const char* aRequesterFuncName) { 3923 MOZ_ASSERT(IsEditActionDataAvailable()); 3924 MOZ_ASSERT(mUpdateCount > 0, "bad state"); 3925 3926 if (NS_WARN_IF(mUpdateCount <= 0)) { 3927 mUpdateCount = 0; 3928 return; 3929 } 3930 3931 if (--mUpdateCount) { 3932 return; 3933 } 3934 3935 // Turn selection updating and notifications back on. 3936 SelectionRef().EndBatchChanges(aRequesterFuncName); 3937 } 3938 3939 TextComposition* EditorBase::GetComposition() const { return mComposition; } 3940 3941 template <typename EditorDOMPointType> 3942 EditorDOMPointType EditorBase::GetFirstIMESelectionStartPoint() const { 3943 return mComposition 3944 ? EditorDOMPointType(mComposition->FirstIMESelectionStartRef()) 3945 : EditorDOMPointType(); 3946 } 3947 3948 template <typename EditorDOMPointType> 3949 EditorDOMPointType EditorBase::GetLastIMESelectionEndPoint() const { 3950 return mComposition 3951 ? EditorDOMPointType(mComposition->LastIMESelectionEndRef()) 3952 : EditorDOMPointType(); 3953 } 3954 3955 bool EditorBase::IsIMEComposing() const { 3956 return mComposition && mComposition->IsComposing(); 3957 } 3958 3959 bool EditorBase::ShouldHandleIMEComposition() const { 3960 // When the editor is being reframed, the old value may be restored with 3961 // InsertText(). In this time, the text should be inserted as not a part 3962 // of the composition. 3963 return mComposition && mDidPostCreate; 3964 } 3965 3966 bool EditorBase::EnsureComposition(WidgetCompositionEvent& aCompositionEvent) { 3967 if (mComposition) { 3968 return true; 3969 } 3970 // The compositionstart event must cause creating new TextComposition 3971 // instance at being dispatched by IMEStateManager. 3972 mComposition = IMEStateManager::GetTextCompositionFor(&aCompositionEvent); 3973 if (!mComposition) { 3974 // However, TextComposition may be committed before the composition 3975 // event comes here. 3976 return false; 3977 } 3978 mComposition->StartHandlingComposition(this); 3979 return true; 3980 } 3981 3982 nsresult EditorBase::OnCompositionStart( 3983 WidgetCompositionEvent& aCompositionStartEvent) { 3984 MOZ_LOG(gTextInputLog, LogLevel::Info, 3985 ("%p %s::OnCompositionStart(aCompositionStartEvent={ mData=\"%s\"}), " 3986 "mComposition=%p", 3987 this, mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 3988 NS_ConvertUTF16toUTF8(aCompositionStartEvent.mData).get(), 3989 mComposition.get())); 3990 3991 if (mComposition) { 3992 NS_WARNING("There was a composition at receiving compositionstart event"); 3993 return NS_OK; 3994 } 3995 3996 // "beforeinput" event shouldn't be fired before "compositionstart". 3997 AutoEditActionDataSetter editActionData(*this, EditAction::eStartComposition); 3998 if (NS_WARN_IF(!editActionData.CanHandle())) { 3999 return NS_ERROR_NOT_INITIALIZED; 4000 } 4001 4002 EnsureComposition(aCompositionStartEvent); 4003 NS_WARNING_ASSERTION(mComposition, "Failed to get TextComposition instance?"); 4004 return NS_OK; 4005 } 4006 4007 nsresult EditorBase::OnCompositionChange( 4008 WidgetCompositionEvent& aCompositionChangeEvent) { 4009 MOZ_ASSERT(aCompositionChangeEvent.mMessage == eCompositionChange, 4010 "The event should be eCompositionChange"); 4011 4012 MOZ_LOG( 4013 gTextInputLog, LogLevel::Info, 4014 ("%p %s::OnCompositionChange(aCompositionChangeEvent={ mData=\"%s\", " 4015 "IsFollowedByCompositionEnd()=%s }), mComposition=%p", 4016 this, mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 4017 NS_ConvertUTF16toUTF8(aCompositionChangeEvent.mData).get(), 4018 aCompositionChangeEvent.IsFollowedByCompositionEnd() ? "true" : "false", 4019 mComposition.get())); 4020 4021 if (!mComposition) { 4022 NS_WARNING( 4023 "There is no composition, but receiving compositionchange event"); 4024 return NS_ERROR_FAILURE; 4025 } 4026 4027 AutoEditActionDataSetter editActionData( 4028 *this, 4029 // We need to distinguish whether the composition change is followed by 4030 // compositionend or not (i.e., wether IME has already ended the 4031 // composition or still has the composition) because we need to dispatch 4032 // `textInput` event only for the last composition change. 4033 aCompositionChangeEvent.IsFollowedByCompositionEnd() 4034 ? EditAction::eUpdateCompositionToCommit 4035 : EditAction::eUpdateComposition); 4036 if (NS_WARN_IF(!editActionData.CanHandle())) { 4037 return NS_ERROR_NOT_INITIALIZED; 4038 } 4039 MOZ_ASSERT(!aCompositionChangeEvent.mData.IsVoid()); 4040 editActionData.SetData(aCompositionChangeEvent.mData); 4041 4042 // If we're an `HTMLEditor` and this is second or later composition change, 4043 // we should set target range to the range of composition string. 4044 // Otherwise, set target ranges to selection ranges (will be done by 4045 // editActionData itself before dispatching `beforeinput` event). 4046 if (IsHTMLEditor() && mComposition->GetContainerTextNode()) { 4047 RefPtr<StaticRange> targetRange = StaticRange::Create( 4048 mComposition->GetContainerTextNode(), 4049 mComposition->XPOffsetInTextNode(), 4050 mComposition->GetContainerTextNode(), 4051 mComposition->XPEndOffsetInTextNode(), IgnoreErrors()); 4052 NS_WARNING_ASSERTION(targetRange && targetRange->IsPositioned(), 4053 "StaticRange::Create() failed"); 4054 if (targetRange && targetRange->IsPositioned()) { 4055 editActionData.AppendTargetRange(*targetRange); 4056 } 4057 } 4058 4059 // TODO: We need to use different EditAction value for beforeinput event 4060 // if the event is followed by "compositionend" because corresponding 4061 // "input" event will be fired from OnCompositionEnd() later with 4062 // different EditAction value. 4063 // TODO: If Input Events Level 2 is enabled, "beforeinput" event may be 4064 // actually canceled if edit action is eDeleteByComposition. In such 4065 // case, we might need to keep selected text, but insert composition 4066 // string before or after the selection. However, the spec is still 4067 // unstable. We should keep handling the composition since other 4068 // parts including widget may not be ready for such complicated 4069 // behavior. 4070 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 4071 if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_FAILED(rv)) { 4072 NS_WARNING("MaybeDispatchBeforeInputEvent() failed"); 4073 return EditorBase::ToGenericNSResult(rv); 4074 } 4075 4076 if (!EnsureComposition(aCompositionChangeEvent)) { 4077 NS_WARNING("EditorBase::EnsureComposition() failed"); 4078 return NS_OK; 4079 } 4080 4081 if (NS_WARN_IF(!GetPresShell())) { 4082 return NS_ERROR_NOT_INITIALIZED; 4083 } 4084 4085 // NOTE: TextComposition should receive selection change notification before 4086 // CompositionChangeEventHandlingMarker notifies TextComposition of the 4087 // end of handling compositionchange event because TextComposition may 4088 // need to ignore selection changes caused by composition. Therefore, 4089 // CompositionChangeEventHandlingMarker must be destroyed after a call 4090 // of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or 4091 // NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies 4092 // TextComposition of a selection change. 4093 MOZ_ASSERT( 4094 !mPlaceholderBatch, 4095 "UpdateIMEComposition() must be called without place holder batch"); 4096 nsString data(aCompositionChangeEvent.mData); 4097 if (IsHTMLEditor()) { 4098 nsContentUtils::PlatformToDOMLineBreaks(data); 4099 } 4100 4101 { 4102 // This needs to be destroyed before dispatching "input" event from 4103 // the following call of `NotifyEditorObservers`. Therefore, we need to 4104 // put this in this block rather than outside of this. 4105 const bool wasComposing = mComposition->IsComposing(); 4106 TextComposition::CompositionChangeEventHandlingMarker 4107 compositionChangeEventHandlingMarker(mComposition, 4108 &aCompositionChangeEvent); 4109 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::IMETxnName, 4110 ScrollSelectionIntoView::Yes, 4111 __FUNCTION__); 4112 4113 // XXX Why don't we get caret after the DOM mutation? 4114 RefPtr<nsCaret> caret = GetCaret(); 4115 4116 MOZ_ASSERT( 4117 mIsInEditSubAction, 4118 "AutoPlaceholderBatch should've notified the observes of before-edit"); 4119 // If we're updating composition, we need to ignore normal selection 4120 // which may be updated by the web content. 4121 const auto purpose = [&]() -> InsertTextFor { 4122 if (!wasComposing) { 4123 return !aCompositionChangeEvent.IsFollowedByCompositionEnd() 4124 ? InsertTextFor::CompositionStart 4125 : InsertTextFor::CompositionStartAndEnd; 4126 } 4127 return !aCompositionChangeEvent.IsFollowedByCompositionEnd() 4128 ? InsertTextFor::CompositionUpdate 4129 : InsertTextFor::CompositionEnd; 4130 }(); 4131 rv = InsertTextAsSubAction(data, purpose); 4132 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4133 "EditorBase::InsertTextAsSubAction() failed"); 4134 4135 if (caret) { 4136 caret->SetSelection(&SelectionRef()); 4137 } 4138 } 4139 4140 // If still composing, we should fire input event via observer. 4141 // Note that if the composition will be committed by the following 4142 // compositionend event, we don't need to notify editor observes of this 4143 // change even if it's preferred by the pref. 4144 // NOTE: We must notify after the auto batch will be gone. 4145 if (!aCompositionChangeEvent.IsFollowedByCompositionEnd()) { 4146 // If we're a TextEditor, we'll be initialized with a new anonymous subtree, 4147 // which can be caused by reframing from a "input" event listener. At that 4148 // time, we'll move composition from current text node to the new text node 4149 // with using mComposition's data. Therefore, it's important that 4150 // mComposition already has the latest information here. 4151 MOZ_ASSERT_IF(mComposition, mComposition->String() == data); 4152 NotifyEditorObservers(eNotifyEditorObserversOfEnd); 4153 } 4154 // NOTE: When the pref is enabled, the last `input` event which will be fired 4155 // after `compositionend` won't be paired with corresponding `beforeinput` 4156 // event. 4157 else if (StaticPrefs::dom_input_events_dispatch_before_compositionend() && 4158 mDispatchInputEvent && !IsEditActionAborted()) { 4159 DispatchInputEvent(); 4160 } 4161 4162 return EditorBase::ToGenericNSResult(rv); 4163 } 4164 4165 void EditorBase::OnCompositionEnd( 4166 WidgetCompositionEvent& aCompositionEndEvent) { 4167 MOZ_LOG(gTextInputLog, LogLevel::Info, 4168 ("%p %s::OnCompositionEnd(aCompositionEndEvent={ mData=\"%s\"}), " 4169 "mComposition=%p", 4170 this, mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 4171 NS_ConvertUTF16toUTF8(aCompositionEndEvent.mData).get(), 4172 mComposition.get())); 4173 4174 if (!mComposition) { 4175 NS_WARNING("There is no composition, but receiving compositionend event"); 4176 return; 4177 } 4178 4179 const EditAction editAction = aCompositionEndEvent.mData.IsEmpty() 4180 ? EditAction::eCancelComposition 4181 : EditAction::eCommitComposition; 4182 AutoEditActionDataSetter editActionData(*this, editAction); 4183 // If Input Events Level 2 is enabled, EditAction::eCancelComposition is 4184 // mapped to EditorInputType::eDeleteCompositionText and it requires null 4185 // for InputEvent.data. Therefore, only otherwise, we should set data. 4186 if (ToInputType(editAction) != EditorInputType::eDeleteCompositionText) { 4187 MOZ_ASSERT( 4188 ToInputType(editAction) == EditorInputType::eInsertCompositionText || 4189 ToInputType(editAction) == EditorInputType::eInsertFromComposition); 4190 MOZ_ASSERT(!aCompositionEndEvent.mData.IsVoid()); 4191 editActionData.SetData(aCompositionEndEvent.mData); 4192 } 4193 4194 const RefPtr<PlaceholderTransaction> placeholderTransaction = 4195 [&]() -> PlaceholderTransaction* { 4196 if (!mTransactionManager) { 4197 return nullptr; 4198 } 4199 const nsCOMPtr<nsITransaction> transaction = 4200 mTransactionManager->PeekUndoStack(); 4201 if (MOZ_UNLIKELY(!transaction)) { 4202 return nullptr; 4203 } 4204 const RefPtr<EditTransactionBase> transactionBase = 4205 transaction->GetAsEditTransactionBase(); 4206 if (MOZ_UNLIKELY(!transactionBase)) { 4207 return nullptr; 4208 } 4209 return transactionBase->GetAsPlaceholderTransaction(); 4210 }(); 4211 // commit the IME transaction..we can get at it via the transaction mgr. 4212 // Note that this means IME won't work without an undo stack! 4213 if (placeholderTransaction) { 4214 placeholderTransaction->Commit(); 4215 } 4216 4217 // If the composition is canceled and the composition hasn't remove any 4218 // content, we should remove the transaction from the undo stack because 4219 // user "canceled" it, so, undoing the canceled composition is odd. That 4220 // would appear as a noop undo transaction. 4221 if (editAction == EditAction::eCancelComposition && placeholderTransaction) { 4222 const nsTArray<OwningNonNull<EditTransactionBase>>& childTransactions = 4223 placeholderTransaction->ChildTransactions(); 4224 MOZ_ASSERT(!childTransactions.IsEmpty()); 4225 // If the first transaction is inserting composition string, we didn't 4226 // replace selection with the composition string. Then, all of the 4227 // operations during the composition is canceled by the user. So, we should 4228 // not record it as an undo transaction. 4229 if (childTransactions[0]->GetAsCompositionTransaction()) { 4230 nsCOMPtr<nsITransaction> transaction = 4231 mTransactionManager->PopUndoStack(); 4232 MOZ_DIAGNOSTIC_ASSERT(transaction == placeholderTransaction); 4233 } 4234 } 4235 4236 // Note that this just marks as that we've already handled "beforeinput" for 4237 // preventing assertions in FireInputEvent(). Note that corresponding 4238 // "beforeinput" event for the following "input" event should've already 4239 // been dispatched from `OnCompositionChange()`. 4240 DebugOnly<nsresult> rvIgnored = 4241 editActionData.MaybeDispatchBeforeInputEvent(); 4242 MOZ_ASSERT(rvIgnored != NS_ERROR_EDITOR_ACTION_CANCELED, 4243 "Why beforeinput event was canceled in this case?"); 4244 MOZ_ASSERT(NS_SUCCEEDED(rvIgnored), 4245 "MaybeDispatchBeforeInputEvent() should just mark the instance as " 4246 "handled it"); 4247 4248 // Composition string may have hidden the caret. Therefore, we need to 4249 // cancel it here. 4250 HideCaret(false); 4251 4252 // FYI: mComposition still keeps storing container text node of committed 4253 // string, its offset and length. However, they will be invalidated 4254 // soon since its Destroy() will be called by IMEStateManager. 4255 mComposition->EndHandlingComposition(this); 4256 mComposition = nullptr; 4257 4258 // notify editor observers of action 4259 // FYI: With current draft, "input" event should be fired from 4260 // OnCompositionChange(), however, it requires a lot of our UI code 4261 // change and does not make sense. See spec issue: 4262 // https://github.com/w3c/uievents/issues/202 4263 NotifyEditorObservers(eNotifyEditorObserversOfEnd); 4264 } 4265 4266 bool EditorBase::WillHandleMouseButtonEvent(WidgetMouseEvent& aMouseEvent) { 4267 MOZ_ASSERT(aMouseEvent.mMessage == eMouseDown || 4268 aMouseEvent.mMessage == eMouseUp); 4269 if (!mEventListener) { 4270 return false; 4271 } 4272 OwningNonNull<EditorEventListener> editorEventListener(*mEventListener); 4273 return editorEventListener->WillHandleMouseButtonEvent(aMouseEvent); 4274 } 4275 4276 void EditorBase::DoAfterDoTransaction(nsITransaction* aTransaction) { 4277 bool isTransientTransaction; 4278 MOZ_ALWAYS_SUCCEEDS(aTransaction->GetIsTransient(&isTransientTransaction)); 4279 4280 if (!isTransientTransaction) { 4281 // we need to deal here with the case where the user saved after some 4282 // edits, then undid one or more times. Then, the undo count is -ve, 4283 // but we can't let a do take it back to zero. So we flip it up to 4284 // a +ve number. 4285 int32_t modCount; 4286 DebugOnly<nsresult> rvIgnored = GetModificationCount(&modCount); 4287 NS_WARNING_ASSERTION( 4288 NS_SUCCEEDED(rvIgnored), 4289 "EditorBase::GetModificationCount() failed, but ignored"); 4290 if (modCount < 0) { 4291 modCount = -modCount; 4292 } 4293 4294 // don't count transient transactions 4295 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(1)); 4296 } 4297 } 4298 4299 void EditorBase::DoAfterUndoTransaction() { 4300 // all undoable transactions are non-transient 4301 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(-1)); 4302 } 4303 4304 void EditorBase::DoAfterRedoTransaction() { 4305 // all redoable transactions are non-transient 4306 MOZ_ALWAYS_SUCCEEDS(IncrementModificationCount(1)); 4307 } 4308 4309 already_AddRefed<DeleteMultipleRangesTransaction> 4310 EditorBase::CreateTransactionForDeleteSelection( 4311 HowToHandleCollapsedRange aHowToHandleCollapsedRange, 4312 const AutoClonedRangeArray& aRangesToDelete) { 4313 MOZ_ASSERT(IsEditActionDataAvailable()); 4314 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 4315 4316 // Check whether the selection is collapsed and we should do nothing: 4317 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() && 4318 aHowToHandleCollapsedRange == 4319 HowToHandleCollapsedRange::Ignore)) { 4320 return nullptr; 4321 } 4322 4323 // allocate the out-param transaction 4324 RefPtr<DeleteMultipleRangesTransaction> transaction = 4325 DeleteMultipleRangesTransaction::Create(); 4326 for (const OwningNonNull<nsRange>& range : aRangesToDelete.Ranges()) { 4327 // Same with range as with selection; if it is collapsed and action 4328 // is eNone, do nothing. 4329 if (!range->Collapsed()) { 4330 RefPtr<DeleteRangeTransaction> deleteRangeTransaction = 4331 DeleteRangeTransaction::Create(*this, range); 4332 // XXX Oh, not checking if deleteRangeTransaction can modify the range... 4333 transaction->AppendChild(*deleteRangeTransaction); 4334 continue; 4335 } 4336 4337 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::Ignore) { 4338 continue; 4339 } 4340 4341 // Let's extend the collapsed range to delete content around it. 4342 RefPtr<DeleteContentTransactionBase> deleteNodeOrTextTransaction = 4343 CreateTransactionForCollapsedRange(range, aHowToHandleCollapsedRange); 4344 // XXX When there are two or more ranges and at least one of them is 4345 // not editable, deleteNodeOrTextTransaction may be nullptr. 4346 // In such case, should we stop removing other ranges too? 4347 if (!deleteNodeOrTextTransaction) { 4348 NS_WARNING("EditorBase::CreateTransactionForCollapsedRange() failed"); 4349 return nullptr; 4350 } 4351 transaction->AppendChild(*deleteNodeOrTextTransaction); 4352 } 4353 4354 return transaction.forget(); 4355 } 4356 4357 // XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior 4358 // are not implemented 4359 already_AddRefed<DeleteContentTransactionBase> 4360 EditorBase::CreateTransactionForCollapsedRange( 4361 const nsRange& aCollapsedRange, 4362 HowToHandleCollapsedRange aHowToHandleCollapsedRange) { 4363 MOZ_ASSERT(aCollapsedRange.Collapsed()); 4364 MOZ_ASSERT( 4365 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward || 4366 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward); 4367 4368 EditorRawDOMPoint point(aCollapsedRange.StartRef()); 4369 if (NS_WARN_IF(!point.IsSet())) { 4370 return nullptr; 4371 } 4372 if (IsTextEditor()) { 4373 // There should be only one text node in the anonymous `<div>` (but may 4374 // be followed by a padding `<br>`). We should adjust the point into 4375 // the text node (or return nullptr if there is no text to delete) for 4376 // avoiding finding the text node with complicated API. 4377 if (!point.IsInTextNode()) { 4378 const Element* anonymousDiv = GetRoot(); 4379 if (NS_WARN_IF(!anonymousDiv)) { 4380 return nullptr; 4381 } 4382 if (!anonymousDiv->GetFirstChild() || 4383 !anonymousDiv->GetFirstChild()->IsText()) { 4384 return nullptr; // The value is empty. 4385 } 4386 if (point.GetContainer() == anonymousDiv) { 4387 if (point.IsStartOfContainer()) { 4388 point.Set(anonymousDiv->GetFirstChild(), 0); 4389 } else { 4390 point.SetToEndOf(anonymousDiv->GetFirstChild()); 4391 } 4392 } else { 4393 // Must be referring a padding `<br>` element or after the text node. 4394 point.SetToEndOf(anonymousDiv->GetFirstChild()); 4395 } 4396 } 4397 MOZ_ASSERT(!point.ContainerAs<Text>()->GetPreviousSibling()); 4398 MOZ_ASSERT(!point.ContainerAs<Text>()->GetNextSibling() || 4399 !point.ContainerAs<Text>()->GetNextSibling()->IsText()); 4400 if (aHowToHandleCollapsedRange == 4401 HowToHandleCollapsedRange::ExtendBackward && 4402 point.IsStartOfContainer()) { 4403 return nullptr; 4404 } 4405 if (aHowToHandleCollapsedRange == 4406 HowToHandleCollapsedRange::ExtendForward && 4407 point.IsEndOfContainer()) { 4408 return nullptr; 4409 } 4410 } 4411 4412 // XXX: if the container of point is empty, then we'll need to delete the node 4413 // as well as the 1 child 4414 4415 // build a transaction for deleting the appropriate data 4416 // XXX: this has to come from rule section 4417 const Element* const anonymousDivOrEditingHost = 4418 IsTextEditor() ? GetRoot() : AsHTMLEditor()->ComputeEditingHost(); 4419 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward && 4420 point.IsStartOfContainer()) { 4421 MOZ_ASSERT(IsHTMLEditor()); 4422 // We're backspacing from the beginning of a node. Delete the last thing 4423 // of previous editable content. 4424 nsIContent* previousEditableContent = HTMLEditUtils::GetPreviousContent( 4425 *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, 4426 IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle 4427 : BlockInlineCheck::UseComputedDisplayOutsideStyle, 4428 anonymousDivOrEditingHost); 4429 if (!previousEditableContent) { 4430 NS_WARNING("There was no editable content before the collapsed range"); 4431 return nullptr; 4432 } 4433 4434 // There is an editable content, so delete its last child (if a text node, 4435 // delete the last char). If it has no children, delete it. 4436 if (previousEditableContent->IsText()) { 4437 uint32_t length = previousEditableContent->Length(); 4438 // Bail out for empty text node. 4439 // XXX Do we want to do something else? 4440 // XXX If other browsers delete empty text node, we should follow it. 4441 if (NS_WARN_IF(!length)) { 4442 NS_WARNING("Previous editable content was an empty text node"); 4443 return nullptr; 4444 } 4445 RefPtr<DeleteTextTransaction> deleteTextTransaction = 4446 DeleteTextTransaction::MaybeCreateForPreviousCharacter( 4447 *this, *previousEditableContent->AsText(), length); 4448 if (!deleteTextTransaction) { 4449 NS_WARNING( 4450 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed"); 4451 return nullptr; 4452 } 4453 return deleteTextTransaction.forget(); 4454 } 4455 4456 if (IsHTMLEditor() && 4457 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*previousEditableContent))) { 4458 return nullptr; 4459 } 4460 RefPtr<DeleteNodeTransaction> deleteNodeTransaction = 4461 DeleteNodeTransaction::MaybeCreate(*this, *previousEditableContent); 4462 if (!deleteNodeTransaction) { 4463 NS_WARNING("DeleteNodeTransaction::MaybeCreate() failed"); 4464 return nullptr; 4465 } 4466 return deleteNodeTransaction.forget(); 4467 } 4468 4469 if (aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendForward && 4470 point.IsEndOfContainer()) { 4471 MOZ_ASSERT(IsHTMLEditor()); 4472 // We're deleting from the end of a node. Delete the first thing of 4473 // next editable content. 4474 nsIContent* nextEditableContent = HTMLEditUtils::GetNextContent( 4475 *point.GetContainer(), {WalkTreeOption::IgnoreNonEditableNode}, 4476 IsTextEditor() ? BlockInlineCheck::UseHTMLDefaultStyle 4477 : BlockInlineCheck::UseComputedDisplayOutsideStyle, 4478 anonymousDivOrEditingHost); 4479 if (!nextEditableContent) { 4480 NS_WARNING("There was no editable content after the collapsed range"); 4481 return nullptr; 4482 } 4483 4484 // There is an editable content, so delete its first child (if a text node, 4485 // delete the first char). If it has no children, delete it. 4486 if (nextEditableContent->IsText()) { 4487 uint32_t length = nextEditableContent->Length(); 4488 // Bail out for empty text node. 4489 // XXX Do we want to do something else? 4490 // XXX If other browsers delete empty text node, we should follow it. 4491 if (!length) { 4492 NS_WARNING("Next editable content was an empty text node"); 4493 return nullptr; 4494 } 4495 RefPtr<DeleteTextTransaction> deleteTextTransaction = 4496 DeleteTextTransaction::MaybeCreateForNextCharacter( 4497 *this, *nextEditableContent->AsText(), 0); 4498 if (!deleteTextTransaction) { 4499 NS_WARNING( 4500 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed"); 4501 return nullptr; 4502 } 4503 return deleteTextTransaction.forget(); 4504 } 4505 4506 if (IsHTMLEditor() && 4507 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*nextEditableContent))) { 4508 return nullptr; 4509 } 4510 RefPtr<DeleteNodeTransaction> deleteNodeTransaction = 4511 DeleteNodeTransaction::MaybeCreate(*this, *nextEditableContent); 4512 if (!deleteNodeTransaction) { 4513 NS_WARNING("DeleteNodeTransaction::MaybeCreate() failed"); 4514 return nullptr; 4515 } 4516 return deleteNodeTransaction.forget(); 4517 } 4518 4519 if (point.IsInTextNode()) { 4520 if (aHowToHandleCollapsedRange == 4521 HowToHandleCollapsedRange::ExtendBackward) { 4522 RefPtr<DeleteTextTransaction> deleteTextTransaction = 4523 DeleteTextTransaction::MaybeCreateForPreviousCharacter( 4524 *this, *point.ContainerAs<Text>(), point.Offset()); 4525 NS_WARNING_ASSERTION( 4526 deleteTextTransaction, 4527 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed"); 4528 return deleteTextTransaction.forget(); 4529 } 4530 RefPtr<DeleteTextTransaction> deleteTextTransaction = 4531 DeleteTextTransaction::MaybeCreateForNextCharacter( 4532 *this, *point.ContainerAs<Text>(), point.Offset()); 4533 NS_WARNING_ASSERTION( 4534 deleteTextTransaction, 4535 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed"); 4536 return deleteTextTransaction.forget(); 4537 } 4538 4539 nsIContent* editableContent = nullptr; 4540 if (IsHTMLEditor()) { 4541 editableContent = 4542 aHowToHandleCollapsedRange == HowToHandleCollapsedRange::ExtendBackward 4543 ? HTMLEditUtils::GetPreviousContent( 4544 point, {WalkTreeOption::IgnoreNonEditableNode}, 4545 BlockInlineCheck::UseComputedDisplayOutsideStyle, 4546 anonymousDivOrEditingHost) 4547 : HTMLEditUtils::GetNextContent( 4548 point, {WalkTreeOption::IgnoreNonEditableNode}, 4549 BlockInlineCheck::UseComputedDisplayOutsideStyle, 4550 anonymousDivOrEditingHost); 4551 if (!editableContent) { 4552 NS_WARNING("There was no editable content around the collapsed range"); 4553 return nullptr; 4554 } 4555 while (editableContent && editableContent->IsCharacterData() && 4556 !editableContent->Length()) { 4557 // Can't delete an empty text node (bug 762183) 4558 editableContent = 4559 aHowToHandleCollapsedRange == 4560 HowToHandleCollapsedRange::ExtendBackward 4561 ? HTMLEditUtils::GetPreviousContent( 4562 *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, 4563 BlockInlineCheck::UseComputedDisplayOutsideStyle, 4564 anonymousDivOrEditingHost) 4565 : HTMLEditUtils::GetNextContent( 4566 *editableContent, {WalkTreeOption::IgnoreNonEditableNode}, 4567 BlockInlineCheck::UseComputedDisplayOutsideStyle, 4568 anonymousDivOrEditingHost); 4569 } 4570 if (!editableContent) { 4571 NS_WARNING( 4572 "There was no editable content which is not empty around the " 4573 "collapsed range"); 4574 return nullptr; 4575 } 4576 } else { 4577 MOZ_ASSERT(point.IsInTextNode()); 4578 editableContent = point.GetContainerAs<nsIContent>(); 4579 if (!editableContent) { 4580 NS_WARNING("If there was no text node, should've been handled first"); 4581 return nullptr; 4582 } 4583 } 4584 4585 if (editableContent->IsText()) { 4586 if (aHowToHandleCollapsedRange == 4587 HowToHandleCollapsedRange::ExtendBackward) { 4588 RefPtr<DeleteTextTransaction> deleteTextTransaction = 4589 DeleteTextTransaction::MaybeCreateForPreviousCharacter( 4590 *this, *editableContent->AsText(), editableContent->Length()); 4591 NS_WARNING_ASSERTION( 4592 deleteTextTransaction, 4593 "DeleteTextTransaction::MaybeCreateForPreviousCharacter() failed"); 4594 return deleteTextTransaction.forget(); 4595 } 4596 4597 RefPtr<DeleteTextTransaction> deleteTextTransaction = 4598 DeleteTextTransaction::MaybeCreateForNextCharacter( 4599 *this, *editableContent->AsText(), 0); 4600 NS_WARNING_ASSERTION( 4601 deleteTextTransaction, 4602 "DeleteTextTransaction::MaybeCreateForNextCharacter() failed"); 4603 return deleteTextTransaction.forget(); 4604 } 4605 4606 MOZ_ASSERT(IsHTMLEditor()); 4607 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*editableContent))) { 4608 return nullptr; 4609 } 4610 RefPtr<DeleteNodeTransaction> deleteNodeTransaction = 4611 DeleteNodeTransaction::MaybeCreate(*this, *editableContent); 4612 NS_WARNING_ASSERTION(deleteNodeTransaction, 4613 "DeleteNodeTransaction::MaybeCreate() failed"); 4614 return deleteNodeTransaction.forget(); 4615 } 4616 4617 bool EditorBase::FlushPendingNotificationsIfToHandleDeletionWithFrameSelection( 4618 nsIEditor::EDirection aDirectionAndAmount) const { 4619 MOZ_ASSERT(IsEditActionDataAvailable()); 4620 4621 if (NS_WARN_IF(Destroyed())) { 4622 return false; 4623 } 4624 if (!EditorUtils::IsFrameSelectionRequiredToExtendSelection( 4625 aDirectionAndAmount, SelectionRef())) { 4626 return true; 4627 } 4628 // Although AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() will 4629 // use nsFrameSelection, if it still has dirty frame, nsFrameSelection doesn't 4630 // extend selection since we block script. 4631 if (RefPtr<PresShell> presShell = GetPresShell()) { 4632 presShell->FlushPendingNotifications(FlushType::Layout); 4633 if (NS_WARN_IF(Destroyed())) { 4634 return false; 4635 } 4636 } 4637 return true; 4638 } 4639 4640 nsresult EditorBase::DeleteSelectionAsAction( 4641 nsIEditor::EDirection aDirectionAndAmount, 4642 nsIEditor::EStripWrappers aStripWrappers, nsIPrincipal* aPrincipal) { 4643 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); 4644 // Showing this assertion is fine if this method is called by outside via 4645 // mutation event listener or something. Otherwise, this is called by 4646 // wrong method. 4647 NS_ASSERTION( 4648 !mPlaceholderBatch, 4649 "Should be called only when this is the only edit action of the " 4650 "operation unless mutation event listener nests some operations"); 4651 4652 // If we're a TextEditor instance, we don't need to treat parent elements 4653 // so that we can ignore aStripWrappers for skipping unnecessary cost. 4654 if (IsTextEditor()) { 4655 aStripWrappers = nsIEditor::eNoStrip; 4656 } 4657 4658 EditAction editAction = EditAction::eDeleteSelection; 4659 switch (aDirectionAndAmount) { 4660 case nsIEditor::ePrevious: 4661 editAction = EditAction::eDeleteBackward; 4662 break; 4663 case nsIEditor::eNext: 4664 editAction = EditAction::eDeleteForward; 4665 break; 4666 case nsIEditor::ePreviousWord: 4667 editAction = EditAction::eDeleteWordBackward; 4668 break; 4669 case nsIEditor::eNextWord: 4670 editAction = EditAction::eDeleteWordForward; 4671 break; 4672 case nsIEditor::eToBeginningOfLine: 4673 editAction = EditAction::eDeleteToBeginningOfSoftLine; 4674 break; 4675 case nsIEditor::eToEndOfLine: 4676 editAction = EditAction::eDeleteToEndOfSoftLine; 4677 break; 4678 } 4679 4680 AutoEditActionDataSetter editActionData(*this, editAction, aPrincipal); 4681 if (NS_WARN_IF(!editActionData.CanHandle())) { 4682 return NS_ERROR_NOT_INITIALIZED; 4683 } 4684 4685 // If there is an existing selection when an extended delete is requested, 4686 // platforms that use "caret-style" caret positioning collapse the 4687 // selection to the start and then create a new selection. 4688 // Platforms that use "selection-style" caret positioning just delete the 4689 // existing selection without extending it. 4690 if (!SelectionRef().IsCollapsed()) { 4691 switch (aDirectionAndAmount) { 4692 case eNextWord: 4693 case ePreviousWord: 4694 case eToBeginningOfLine: 4695 case eToEndOfLine: { 4696 if (mCaretStyle != 1) { 4697 aDirectionAndAmount = eNone; 4698 break; 4699 } 4700 ErrorResult error; 4701 SelectionRef().CollapseToStart(error); 4702 if (NS_WARN_IF(Destroyed())) { 4703 error.SuppressException(); 4704 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 4705 } 4706 if (error.Failed()) { 4707 NS_WARNING("Selection::CollapseToStart() failed"); 4708 editActionData.Abort(); 4709 return EditorBase::ToGenericNSResult(error.StealNSResult()); 4710 } 4711 break; 4712 } 4713 default: 4714 break; 4715 } 4716 } 4717 4718 // If Selection is still NOT collapsed, it does not important removing 4719 // range of the operation since we'll remove the selected content. However, 4720 // information of direction (backward or forward) may be important for 4721 // web apps. E.g., web apps may want to mark selected range as "deleted" 4722 // and move caret before or after the range. Therefore, we should forget 4723 // only the range information but keep range information. See discussion 4724 // of the spec issue for the detail: 4725 // https://github.com/w3c/input-events/issues/82 4726 if (!SelectionRef().IsCollapsed()) { 4727 switch (editAction) { 4728 case EditAction::eDeleteWordBackward: 4729 case EditAction::eDeleteToBeginningOfSoftLine: 4730 editActionData.UpdateEditAction(EditAction::eDeleteBackward); 4731 break; 4732 case EditAction::eDeleteWordForward: 4733 case EditAction::eDeleteToEndOfSoftLine: 4734 editActionData.UpdateEditAction(EditAction::eDeleteForward); 4735 break; 4736 default: 4737 break; 4738 } 4739 } 4740 4741 editActionData.SetSelectionCreatedByDoubleclick( 4742 SelectionRef().GetFrameSelection() && 4743 SelectionRef().GetFrameSelection()->IsDoubleClickSelection()); 4744 4745 if (!FlushPendingNotificationsIfToHandleDeletionWithFrameSelection( 4746 aDirectionAndAmount)) { 4747 NS_WARNING("Flusing pending notifications caused destroying the editor"); 4748 editActionData.Abort(); 4749 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 4750 } 4751 4752 nsresult rv = 4753 editActionData.MaybeDispatchBeforeInputEvent(aDirectionAndAmount); 4754 if (NS_FAILED(rv)) { 4755 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 4756 "MaybeDispatchBeforeInputEvent() failed"); 4757 return EditorBase::ToGenericNSResult(rv); 4758 } 4759 4760 // delete placeholder txns merge. 4761 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::DeleteTxnName, 4762 ScrollSelectionIntoView::Yes, 4763 __FUNCTION__); 4764 rv = DeleteSelectionAsSubAction(aDirectionAndAmount, aStripWrappers); 4765 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4766 "EditorBase::DeleteSelectionAsSubAction() failed"); 4767 return EditorBase::ToGenericNSResult(rv); 4768 } 4769 4770 nsresult EditorBase::DeleteSelectionAsSubAction( 4771 nsIEditor::EDirection aDirectionAndAmount, 4772 nsIEditor::EStripWrappers aStripWrappers) { 4773 MOZ_ASSERT(IsEditActionDataAvailable()); 4774 // If handling edit action is for table editing, this may be called with 4775 // selecting an any table element by the caller, but it's not usual work of 4776 // this so that `MayEditActionDeleteSelection()` returns false. 4777 MOZ_ASSERT(MayEditActionDeleteSelection(GetEditAction()) || 4778 IsEditActionTableEditing(GetEditAction())); 4779 MOZ_ASSERT(mPlaceholderBatch); 4780 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); 4781 NS_ASSERTION(IsHTMLEditor() || aStripWrappers == nsIEditor::eNoStrip, 4782 "TextEditor does not support strip wrappers"); 4783 4784 if (NS_WARN_IF(!mInitSucceeded)) { 4785 return NS_ERROR_NOT_INITIALIZED; 4786 } 4787 4788 IgnoredErrorResult ignoredError; 4789 AutoEditSubActionNotifier startToHandleEditSubAction( 4790 *this, EditSubAction::eDeleteSelectedContent, aDirectionAndAmount, 4791 ignoredError); 4792 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 4793 return ignoredError.StealNSResult(); 4794 } 4795 NS_WARNING_ASSERTION( 4796 !ignoredError.Failed(), 4797 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 4798 4799 { 4800 Result<EditActionResult, nsresult> result = 4801 HandleDeleteSelection(aDirectionAndAmount, aStripWrappers); 4802 if (MOZ_UNLIKELY(result.isErr())) { 4803 // If HTMLEditor::HandleDeleteSelection() returns "no editable range" 4804 // error and the range is collapsed and the deletion is a preparation for 4805 // inserting something, we wan't to keep handling the insertion without 4806 // error. 4807 if (result.inspectErr() == NS_ERROR_EDITOR_NO_DELETABLE_RANGE && 4808 GetTopLevelEditSubAction() != EditSubAction::eDeleteSelectedContent) { 4809 return NS_OK; 4810 } 4811 NS_WARNING(nsPrintfCString("%s::HandleDeleteSelection() failed", 4812 IsTextEditor() ? "TextEditor" : "HTMLEditor") 4813 .get()); 4814 return result.unwrapErr(); 4815 } 4816 if (result.inspect().Canceled()) { 4817 return NS_OK; 4818 } 4819 } 4820 4821 // XXX This is odd. We just tries to remove empty text node here but we 4822 // refer `Selection`. It may be modified by mutation event listeners 4823 // so that we should remove the empty text node when we make it empty. 4824 const auto atNewStartOfSelection = 4825 GetFirstSelectionStartPoint<EditorDOMPoint>(); 4826 if (NS_WARN_IF(!atNewStartOfSelection.IsSet())) { 4827 // XXX And also it seems that we don't need to return error here. 4828 // Why don't we just ignore? `Selection::RemoveAllRanges()` may 4829 // have been called by mutation event listeners. 4830 return NS_ERROR_FAILURE; 4831 } 4832 if (IsHTMLEditor() && atNewStartOfSelection.IsInTextNode() && 4833 !atNewStartOfSelection.GetContainer()->Length()) { 4834 nsresult rv = DeleteNodeWithTransaction( 4835 MOZ_KnownLive(*atNewStartOfSelection.ContainerAs<Text>())); 4836 if (NS_FAILED(rv)) { 4837 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4838 return rv; 4839 } 4840 } 4841 4842 // XXX I don't think that this is necessary in anonymous `<div>` element of 4843 // TextEditor since there should be at most one text node and at most 4844 // one padding `<br>` element so that `<br>` element won't be before 4845 // caret. 4846 if (!TopLevelEditSubActionDataRef().mDidExplicitlySetInterLine) { 4847 // We prevent the caret from sticking on the left of previous `<br>` 4848 // element (i.e. the end of previous line) after this deletion. Bug 92124. 4849 if (MOZ_UNLIKELY(NS_FAILED(SelectionRef().SetInterlinePosition( 4850 InterlinePosition::StartOfNextLine)))) { 4851 NS_WARNING( 4852 "Selection::SetInterlinePosition(InterlinePosition::StartOfNextLine) " 4853 "failed"); 4854 return NS_ERROR_FAILURE; // Don't need to return NS_ERROR_NOT_INITIALIZED 4855 } 4856 } 4857 4858 return NS_OK; 4859 } 4860 4861 nsresult EditorBase::HandleDropEvent(DragEvent* aDropEvent) { 4862 if (NS_WARN_IF(!aDropEvent)) { 4863 return NS_ERROR_INVALID_ARG; 4864 } 4865 4866 DebugOnly<nsresult> rvIgnored = CommitComposition(); 4867 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 4868 "EditorBase::CommitComposition() failed, but ignored"); 4869 4870 AutoEditActionDataSetter editActionData(*this, EditAction::eDrop); 4871 // We need to initialize data or dataTransfer later. Therefore, we cannot 4872 // dispatch "beforeinput" event until then. 4873 if (NS_WARN_IF(!editActionData.CanHandle())) { 4874 return NS_ERROR_NOT_INITIALIZED; 4875 } 4876 4877 RefPtr<DataTransfer> dataTransfer = aDropEvent->GetDataTransfer(); 4878 if (NS_WARN_IF(!dataTransfer)) { 4879 return NS_ERROR_FAILURE; 4880 } 4881 4882 RefPtr<nsIWidget> widget = GetWidget(); 4883 nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession(widget); 4884 if (NS_WARN_IF(!dragSession)) { 4885 return NS_ERROR_FAILURE; 4886 } 4887 4888 nsCOMPtr<nsINode> sourceNode = dataTransfer->GetMozSourceNode(); 4889 4890 // If there is no source document, then the drag was from another application 4891 // or another process (such as an out of process subframe). The latter case is 4892 // not currently handled below when checking for a move/copy and deleting the 4893 // existing text. 4894 RefPtr<Document> srcdoc; 4895 if (sourceNode) { 4896 srcdoc = sourceNode->OwnerDoc(); 4897 } 4898 4899 nsCOMPtr<nsIPrincipal> sourcePrincipal; 4900 dragSession->GetTriggeringPrincipal(getter_AddRefs(sourcePrincipal)); 4901 4902 if (nsContentUtils::CheckForSubFrameDrop( 4903 dragSession, aDropEvent->WidgetEventPtr()->AsDragEvent())) { 4904 // Don't allow drags from subframe documents with different origins than 4905 // the drop destination. 4906 if (IsSafeToInsertData(sourcePrincipal) == SafeToInsertData::No) { 4907 return NS_OK; 4908 } 4909 } 4910 4911 // Current doc is destination 4912 RefPtr<Document> document = GetDocument(); 4913 if (NS_WARN_IF(!document)) { 4914 return NS_ERROR_NOT_INITIALIZED; 4915 } 4916 4917 const uint32_t numItems = dataTransfer->MozItemCount(); 4918 if (NS_WARN_IF(!numItems)) { 4919 return NS_ERROR_FAILURE; // Nothing to drop? 4920 } 4921 4922 // We have to figure out whether to delete and relocate caret only once 4923 // Parent and offset are under the mouse cursor. 4924 int32_t dropOffset = -1; 4925 nsCOMPtr<nsIContent> dropParentContent = 4926 aDropEvent->GetRangeParentContentAndOffset(&dropOffset); 4927 if (dropOffset < 0) { 4928 NS_WARNING( 4929 "DropEvent::GetRangeParentContentAndOffset() returned negative offset"); 4930 return NS_ERROR_FAILURE; 4931 } 4932 EditorDOMPoint droppedAt(dropParentContent, 4933 AssertedCast<uint32_t>(dropOffset)); 4934 if (NS_WARN_IF(!droppedAt.IsInContentNode())) { 4935 return NS_ERROR_FAILURE; 4936 } 4937 4938 // Check if dropping into a selected range. If so and the source comes from 4939 // same document, jump through some hoops to determine if mouse is over 4940 // selection (bail) and whether user wants to copy selection or delete it. 4941 if (sourceNode && sourceNode->IsEditable() && srcdoc == document) { 4942 bool isPointInSelection = nsContentUtils::IsPointInSelection( 4943 SelectionRef(), *droppedAt.GetContainer(), droppedAt.Offset()); 4944 if (isPointInSelection) { 4945 // If source document and destination document is same and dropping 4946 // into one of selected ranges, we don't need to do nothing. 4947 // XXX If the source comes from outside of this editor, this check 4948 // means that we don't allow to drop the item in the selected 4949 // range. However, the selection is hidden until the <input> or 4950 // <textarea> gets focus, therefore, this looks odd. 4951 return NS_OK; 4952 } 4953 } 4954 4955 // Delete if user doesn't want to copy when user moves selected content 4956 // to different place in same editor. 4957 // XXX Do we need the check whether it's in same document or not? 4958 RefPtr<EditorBase> editorToDeleteSelection; 4959 if (sourceNode && sourceNode->IsEditable() && srcdoc == document) { 4960 if ((dataTransfer->DropEffectInt() & 4961 nsIDragService::DRAGDROP_ACTION_MOVE) && 4962 !(dataTransfer->DropEffectInt() & 4963 nsIDragService::DRAGDROP_ACTION_COPY)) { 4964 // If the source node is in native anonymous tree, it must be in 4965 // <input> or <textarea> element. If so, its TextEditor can remove it. 4966 if (sourceNode->IsInNativeAnonymousSubtree()) { 4967 if (RefPtr textControlElement = TextControlElement::FromNodeOrNull( 4968 sourceNode 4969 ->GetClosestNativeAnonymousSubtreeRootParentOrHost())) { 4970 editorToDeleteSelection = textControlElement->GetTextEditor(); 4971 } 4972 } 4973 // Otherwise, must be the content is in HTMLEditor. 4974 else if (IsHTMLEditor()) { 4975 editorToDeleteSelection = this; 4976 } else { 4977 editorToDeleteSelection = 4978 nsContentUtils::GetHTMLEditor(srcdoc->GetPresContext()); 4979 } 4980 } 4981 // If the found editor isn't modifiable, we should not try to delete 4982 // selection. 4983 if (editorToDeleteSelection && !editorToDeleteSelection->IsModifiable()) { 4984 editorToDeleteSelection = nullptr; 4985 } 4986 // If the found editor has collapsed selection, we need to delete nothing 4987 // in the editor. 4988 if (editorToDeleteSelection) { 4989 if (Selection* selection = editorToDeleteSelection->GetSelection()) { 4990 if (selection->IsCollapsed()) { 4991 editorToDeleteSelection = nullptr; 4992 } 4993 } 4994 } 4995 } 4996 4997 // Combine any deletion and drop insertion into one transaction. 4998 AutoPlaceholderBatch treatAsOneTransaction( 4999 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 5000 5001 // Don't dispatch "selectionchange" event until inserting all contents. 5002 SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__); 5003 5004 // Track dropped point with nsRange because we shouldn't insert the 5005 // dropped content into different position even if some event listeners 5006 // modify selection. Note that Chrome's behavior is really odd. So, 5007 // we don't need to worry about web-compat about this. 5008 IgnoredErrorResult ignoredError; 5009 RefPtr<nsRange> rangeAtDropPoint = 5010 nsRange::Create(droppedAt.ToRawRangeBoundary(), 5011 droppedAt.ToRawRangeBoundary(), ignoredError); 5012 if (NS_WARN_IF(ignoredError.Failed()) || 5013 NS_WARN_IF(!rangeAtDropPoint->IsPositioned())) { 5014 editActionData.Abort(); 5015 return NS_ERROR_FAILURE; 5016 } 5017 5018 // Remove selected contents first here because we need to fire a pair of 5019 // "beforeinput" and "input" for deletion and web apps can cancel only 5020 // this deletion. Note that callee may handle insertion asynchronously. 5021 // Therefore, it is the best to remove selected content here. 5022 if (editorToDeleteSelection) { 5023 nsresult rv = editorToDeleteSelection->DeleteSelectionByDragAsAction( 5024 mDispatchInputEvent); 5025 if (NS_WARN_IF(Destroyed())) { 5026 editActionData.Abort(); 5027 return NS_OK; 5028 } 5029 // Ignore the editor instance specific error if it's another editor. 5030 if (this != editorToDeleteSelection && 5031 (rv == NS_ERROR_NOT_INITIALIZED || rv == NS_ERROR_EDITOR_DESTROYED)) { 5032 rv = NS_OK; 5033 } 5034 // Don't cancel "insertFromDrop" even if "deleteByDrag" is canceled. 5035 if (rv != NS_ERROR_EDITOR_ACTION_CANCELED && NS_FAILED(rv)) { 5036 NS_WARNING("EditorBase::DeleteSelectionByDragAsAction() failed"); 5037 editActionData.Abort(); 5038 return EditorBase::ToGenericNSResult(rv); 5039 } 5040 if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) || 5041 NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) { 5042 editActionData.Abort(); 5043 return NS_ERROR_FAILURE; 5044 } 5045 droppedAt = rangeAtDropPoint->StartRef(); 5046 MOZ_ASSERT(droppedAt.IsSetAndValid()); 5047 MOZ_ASSERT(droppedAt.IsInContentNode()); 5048 } 5049 5050 // Before inserting dropping content, we need to move focus for compatibility 5051 // with Chrome and firing "beforeinput" event on new editing host. 5052 RefPtr<Element> focusedElement, newFocusedElement; 5053 if (IsTextEditor()) { 5054 newFocusedElement = GetExposedRoot(); 5055 focusedElement = IsActiveInDOMWindow() ? newFocusedElement : nullptr; 5056 } 5057 // TODO: We need to add automated tests when dropping something into an 5058 // editing host for contenteditable which is in a shadow DOM tree 5059 // and its host which is in design mode. 5060 else if (!droppedAt.ContainerAs<nsIContent>()->IsInDesignMode()) { 5061 focusedElement = AsHTMLEditor()->ComputeEditingHost(); 5062 if (focusedElement && 5063 droppedAt.ContainerAs<nsIContent>()->IsInclusiveDescendantOf( 5064 focusedElement)) { 5065 newFocusedElement = focusedElement; 5066 } else { 5067 newFocusedElement = droppedAt.ContainerAs<nsIContent>()->GetEditingHost(); 5068 } 5069 } 5070 // Move selection right now. Note that this does not move focus because 5071 // `Selection` moves focus with selection change only when the API caller is 5072 // JS. And also this does not notify selection listeners (nor 5073 // "selectionchange") since we created SelectionBatcher above. 5074 ErrorResult error; 5075 SelectionRef().SetStartAndEnd(droppedAt.ToRawRangeBoundary(), 5076 droppedAt.ToRawRangeBoundary(), error); 5077 if (error.Failed()) { 5078 NS_WARNING("Selection::SetStartAndEnd() failed"); 5079 editActionData.Abort(); 5080 return error.StealNSResult(); 5081 } 5082 if (NS_WARN_IF(Destroyed())) { 5083 editActionData.Abort(); 5084 return NS_OK; 5085 } 5086 // Then, move focus if necessary. This must cause dispatching "blur" event 5087 // and "focus" event. 5088 if (newFocusedElement && focusedElement != newFocusedElement) { 5089 RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager(); 5090 DebugOnly<nsresult> rvIgnored = fm->SetFocus(newFocusedElement, 0); 5091 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5092 "nsFocusManager::SetFocus() failed to set focus " 5093 "to the element, but ignored"); 5094 if (NS_WARN_IF(Destroyed())) { 5095 editActionData.Abort(); 5096 return NS_OK; 5097 } 5098 // "blur" or "focus" event listener may have changed the value. 5099 // Let's keep using the original point. 5100 if (NS_WARN_IF(!rangeAtDropPoint->IsPositioned()) || 5101 NS_WARN_IF(!rangeAtDropPoint->GetStartContainer()->IsContent())) { 5102 return NS_ERROR_FAILURE; 5103 } 5104 droppedAt = rangeAtDropPoint->StartRef(); 5105 MOZ_ASSERT(droppedAt.IsSetAndValid()); 5106 5107 // If focus is changed to different element and we're handling drop in 5108 // contenteditable, we cannot handle it without focus. So, we should give 5109 // it up. 5110 if (IsHTMLEditor() && !AsHTMLEditor()->IsInDesignMode() && 5111 NS_WARN_IF(newFocusedElement != AsHTMLEditor()->ComputeEditingHost())) { 5112 editActionData.Abort(); 5113 return NS_OK; 5114 } 5115 } 5116 5117 nsresult rv = InsertDroppedDataTransferAsAction(editActionData, *dataTransfer, 5118 droppedAt, sourcePrincipal); 5119 if (rv == NS_ERROR_EDITOR_DESTROYED || 5120 rv == NS_ERROR_EDITOR_ACTION_CANCELED) { 5121 return EditorBase::ToGenericNSResult(rv); 5122 } 5123 NS_WARNING_ASSERTION( 5124 NS_SUCCEEDED(rv), 5125 "EditorBase::InsertDroppedDataTransferAsAction() failed, but ignored"); 5126 5127 rv = ScrollSelectionFocusIntoView(); 5128 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5129 "EditorBase::ScrollSelectionFocusIntoView() failed"); 5130 return rv; 5131 } 5132 5133 nsresult EditorBase::DeleteSelectionByDragAsAction(bool aDispatchInputEvent) { 5134 // TODO: Move this method to `EditorBase`. 5135 AutoRestore<bool> saveDispatchInputEvent(mDispatchInputEvent); 5136 mDispatchInputEvent = aDispatchInputEvent; 5137 // Even if we're handling "deleteByDrag" in same editor as "insertFromDrop", 5138 // we need to recreate edit action data here because 5139 // `AutoEditActionDataSetter` needs to manage event state separately. 5140 bool requestedByAnotherEditor = GetEditAction() != EditAction::eDrop; 5141 AutoEditActionDataSetter editActionData(*this, EditAction::eDeleteByDrag); 5142 MOZ_ASSERT(!SelectionRef().IsCollapsed()); 5143 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 5144 if (NS_FAILED(rv)) { 5145 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5146 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 5147 return rv; 5148 } 5149 // But keep using placeholder transaction for "insertFromDrop" if there is. 5150 Maybe<AutoPlaceholderBatch> treatAsOneTransaction; 5151 if (requestedByAnotherEditor) { 5152 treatAsOneTransaction.emplace(*this, ScrollSelectionIntoView::Yes, 5153 __FUNCTION__); 5154 } 5155 5156 // We may need to update the source node to dispatch "dragend" below. 5157 // Chrome restricts the new target under the <body> here. Therefore, we 5158 // should follow it here. 5159 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/editing_utilities.cc;l=254;drc=da35f4ed6398ae287d5adc828b9546eec95f668a 5160 const RefPtr<Element> editingHost = 5161 IsHTMLEditor() ? AsHTMLEditor()->ComputeEditingHost( 5162 HTMLEditor::LimitInBodyElement::Yes) 5163 : nullptr; 5164 5165 rv = DeleteSelectionAsSubAction(nsIEditor::eNone, IsTextEditor() 5166 ? nsIEditor::eNoStrip 5167 : nsIEditor::eStrip); 5168 if (NS_FAILED(rv)) { 5169 NS_WARNING("EditorBase::DeleteSelectionAsSubAction(eNone) failed"); 5170 return rv; 5171 } 5172 5173 if (!mDispatchInputEvent) { 5174 return NS_OK; 5175 } 5176 5177 if (treatAsOneTransaction.isNothing()) { 5178 DispatchInputEvent(); 5179 } 5180 5181 if (NS_WARN_IF(Destroyed())) { 5182 return NS_ERROR_EDITOR_DESTROYED; 5183 } 5184 5185 // If we success everything here, we may need to retarget "dragend" event 5186 // target for compatibility with the other browsers. They do this only when 5187 // their builtin editor delete the source node from the document. Then, 5188 // they retarget the source node to the editing host. 5189 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/page/drag_controller.cc;l=724;drc=d9ba13b8cd8ac0faed7afc3d1f7e4b67ebac2a0b 5190 if (editingHost) { 5191 RefPtr<nsIWidget> widget = GetWidget(); 5192 if (nsCOMPtr<nsIDragSession> dragSession = 5193 nsContentUtils::GetDragSession(widget)) { 5194 dragSession->MaybeEditorDeletedSourceNode(editingHost); 5195 } 5196 } 5197 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK; 5198 } 5199 5200 Result<CaretPoint, nsresult> EditorBase::DeleteRangeWithTransaction( 5201 nsIEditor::EDirection aDirectionAndAmount, 5202 nsIEditor::EStripWrappers aStripWrappers, nsRange& aRangeToDelete) { 5203 MOZ_ASSERT(IsEditActionDataAvailable()); 5204 MOZ_ASSERT(!Destroyed()); 5205 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); 5206 5207 HowToHandleCollapsedRange howToHandleCollapsedRange = 5208 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); 5209 if (MOZ_UNLIKELY(aRangeToDelete.Collapsed() && 5210 howToHandleCollapsedRange == 5211 HowToHandleCollapsedRange::Ignore)) { 5212 return CaretPoint(EditorDOMPoint(aRangeToDelete.StartRef())); 5213 } 5214 5215 AutoClonedRangeArray rangesToDelete(aRangeToDelete); 5216 Result<CaretPoint, nsresult> result = DeleteRangesWithTransaction( 5217 aDirectionAndAmount, aStripWrappers, rangesToDelete); 5218 NS_WARNING_ASSERTION(result.isOk(), 5219 "EditorBase::DeleteRangesWithTransaction() failed"); 5220 return result; 5221 } 5222 5223 Result<CaretPoint, nsresult> EditorBase::DeleteRangesWithTransaction( 5224 nsIEditor::EDirection aDirectionAndAmount, 5225 nsIEditor::EStripWrappers aStripWrappers, 5226 AutoClonedRangeArray& aRangesToDelete) { 5227 MOZ_ASSERT(IsEditActionDataAvailable()); 5228 MOZ_ASSERT(!Destroyed()); 5229 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); 5230 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 5231 5232 HowToHandleCollapsedRange howToHandleCollapsedRange = 5233 EditorBase::HowToHandleCollapsedRangeFor(aDirectionAndAmount); 5234 if (NS_WARN_IF(aRangesToDelete.IsCollapsed() && 5235 howToHandleCollapsedRange == 5236 HowToHandleCollapsedRange::Ignore)) { 5237 NS_ASSERTION( 5238 false, 5239 "For avoiding to throw incompatible exception for `execCommand`, fix " 5240 "the caller"); 5241 return Err(NS_ERROR_FAILURE); 5242 } 5243 5244 RefPtr<DeleteMultipleRangesTransaction> deleteSelectionTransaction = 5245 CreateTransactionForDeleteSelection(howToHandleCollapsedRange, 5246 aRangesToDelete); 5247 if (MOZ_UNLIKELY(!deleteSelectionTransaction)) { 5248 NS_WARNING("EditorBase::CreateTransactionForDeleteSelection() failed"); 5249 return Err(NS_ERROR_FAILURE); 5250 } 5251 5252 // XXX This is odd, this assumes that there are no multiple collapsed 5253 // ranges in `Selection`, but it's possible scenario. 5254 // XXX This loop looks slow, but it's rarely so because of multiple 5255 // selection is not used so many times. 5256 nsCOMPtr<nsIContent> deleteContent; 5257 uint32_t deleteCharOffset = 0; 5258 for (const OwningNonNull<EditTransactionBase>& transactionBase : 5259 Reversed(deleteSelectionTransaction->ChildTransactions())) { 5260 if (DeleteTextTransaction* deleteTextTransaction = 5261 transactionBase->GetAsDeleteTextTransaction()) { 5262 deleteContent = deleteTextTransaction->GetTextNode(); 5263 deleteCharOffset = deleteTextTransaction->Offset(); 5264 break; 5265 } 5266 if (DeleteNodeTransaction* deleteNodeTransaction = 5267 transactionBase->GetAsDeleteNodeTransaction()) { 5268 deleteContent = deleteNodeTransaction->GetContent(); 5269 break; 5270 } 5271 } 5272 5273 RefPtr<CharacterData> deleteCharData = 5274 CharacterData::FromNodeOrNull(deleteContent); 5275 IgnoredErrorResult ignoredError; 5276 AutoEditSubActionNotifier startToHandleEditSubAction( 5277 *this, EditSubAction::eDeleteSelectedContent, aDirectionAndAmount, 5278 ignoredError); 5279 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5280 return Err(ignoredError.StealNSResult()); 5281 } 5282 NS_WARNING_ASSERTION( 5283 !ignoredError.Failed(), 5284 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5285 5286 if (IsHTMLEditor()) { 5287 if (!deleteContent) { 5288 // XXX We may remove multiple ranges in the following. Therefore, 5289 // this must have a bug since we only add the first range into 5290 // the changed range. 5291 TopLevelEditSubActionDataRef().WillDeleteRange( 5292 *this, aRangesToDelete.GetFirstRangeStartPoint<EditorRawDOMPoint>(), 5293 aRangesToDelete.GetFirstRangeEndPoint<EditorRawDOMPoint>()); 5294 } else if (!deleteCharData) { 5295 TopLevelEditSubActionDataRef().WillDeleteContent(*this, *deleteContent); 5296 } 5297 } 5298 5299 // Notify nsIEditActionListener::WillDelete[Selection|Text] 5300 if (!mActionListeners.IsEmpty()) { 5301 if (!deleteContent) { 5302 MOZ_ASSERT(!aRangesToDelete.Ranges().IsEmpty()); 5303 AutoTArray<RefPtr<nsRange>, 8> rangesToDelete( 5304 aRangesToDelete.CloneRanges<RefPtr>()); 5305 AutoActionListenerArray listeners(mActionListeners.Clone()); 5306 for (auto& listener : listeners) { 5307 DebugOnly<nsresult> rvIgnored = 5308 listener->WillDeleteRanges(rangesToDelete); 5309 NS_WARNING_ASSERTION( 5310 NS_SUCCEEDED(rvIgnored), 5311 "nsIEditActionListener::WillDeleteRanges() failed, but ignored"); 5312 MOZ_DIAGNOSTIC_ASSERT(!Destroyed(), 5313 "nsIEditActionListener::WillDeleteRanges() " 5314 "must not destroy the editor"); 5315 } 5316 } else if (deleteCharData) { 5317 AutoActionListenerArray listeners(mActionListeners.Clone()); 5318 for (auto& listener : listeners) { 5319 // XXX Why don't we notify listeners of actual length? 5320 DebugOnly<nsresult> rvIgnored = 5321 listener->WillDeleteText(deleteCharData, deleteCharOffset, 1); 5322 NS_WARNING_ASSERTION( 5323 NS_SUCCEEDED(rvIgnored), 5324 "nsIEditActionListener::WillDeleteText() failed, but ignored"); 5325 MOZ_DIAGNOSTIC_ASSERT(!Destroyed(), 5326 "nsIEditActionListener::WillDeleteText() must " 5327 "not destroy the editor"); 5328 } 5329 } 5330 } 5331 5332 // Delete the specified amount 5333 nsresult rv = DoTransactionInternal(deleteSelectionTransaction); 5334 // I'm not sure whether we should keep notifying edit action listeners or 5335 // stop doing it. For now, just keep traditional behavior. 5336 bool destroyedByTransaction = Destroyed(); 5337 NS_WARNING_ASSERTION(destroyedByTransaction || NS_SUCCEEDED(rv), 5338 "EditorBase::DoTransactionInternal() failed"); 5339 5340 if (IsHTMLEditor() && deleteCharData) { 5341 MOZ_ASSERT(deleteContent); 5342 TopLevelEditSubActionDataRef().DidDeleteText( 5343 *this, EditorRawDOMPoint(deleteContent)); 5344 } 5345 5346 if (mTextServicesDocument && NS_SUCCEEDED(rv) && deleteContent && 5347 !deleteCharData) { 5348 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument; 5349 textServicesDocument->DidDeleteContent(*deleteContent); 5350 MOZ_ASSERT( 5351 destroyedByTransaction || !Destroyed(), 5352 "TextServicesDocument::DidDeleteContent() must not destroy the editor"); 5353 } 5354 5355 if (!mActionListeners.IsEmpty() && deleteContent && !deleteCharData) { 5356 for (auto& listener : mActionListeners.Clone()) { 5357 DebugOnly<nsresult> rvIgnored = 5358 listener->DidDeleteNode(deleteContent, rv); 5359 NS_WARNING_ASSERTION( 5360 NS_SUCCEEDED(rvIgnored), 5361 "nsIEditActionListener::DidDeleteNode() failed, but ignored"); 5362 MOZ_DIAGNOSTIC_ASSERT( 5363 destroyedByTransaction || !Destroyed(), 5364 "nsIEditActionListener::DidDeleteNode() must not destroy the editor"); 5365 } 5366 } 5367 5368 if (NS_WARN_IF(destroyedByTransaction)) { 5369 return Err(NS_ERROR_EDITOR_DESTROYED); 5370 } 5371 if (NS_FAILED(rv)) { 5372 return Err(rv); 5373 } 5374 5375 return CaretPoint(deleteSelectionTransaction->SuggestPointToPutCaret()); 5376 } 5377 5378 already_AddRefed<Element> EditorBase::CreateHTMLContent( 5379 const nsAtom* aTag) const { 5380 MOZ_ASSERT(aTag); 5381 5382 RefPtr<Document> document = GetDocument(); 5383 if (NS_WARN_IF(!document)) { 5384 return nullptr; 5385 } 5386 5387 // XXX Wallpaper over editor bug (editor tries to create elements with an 5388 // empty nodename). 5389 if (aTag == nsGkAtoms::_empty) { 5390 NS_ERROR( 5391 "Don't pass an empty tag to EditorBase::CreateHTMLContent, " 5392 "check caller."); 5393 return nullptr; 5394 } 5395 5396 return document->CreateElem(nsDependentAtomString(aTag), nullptr, 5397 kNameSpaceID_XHTML); 5398 } 5399 5400 already_AddRefed<nsTextNode> EditorBase::CreateTextNode( 5401 const nsAString& aData) const { 5402 MOZ_ASSERT(IsEditActionDataAvailable()); 5403 5404 Document* document = GetDocument(); 5405 if (NS_WARN_IF(!document)) { 5406 return nullptr; 5407 } 5408 RefPtr<nsTextNode> text = document->CreateEmptyTextNode(); 5409 text->MarkAsMaybeModifiedFrequently(); 5410 if (IsPasswordEditor()) { 5411 text->MarkAsMaybeMasked(); 5412 } 5413 // Don't notify; this node is still being created. 5414 DebugOnly<nsresult> rvIgnored = text->SetText(aData, false); 5415 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5416 "Text::SetText() failed, but ignored"); 5417 return text.forget(); 5418 } 5419 5420 NS_IMETHODIMP EditorBase::SetAttributeOrEquivalent(Element* aElement, 5421 const nsAString& aAttribute, 5422 const nsAString& aValue, 5423 bool aSuppressTransaction) { 5424 if (NS_WARN_IF(!aElement)) { 5425 return NS_ERROR_NULL_POINTER; 5426 } 5427 5428 AutoEditActionDataSetter editActionData(*this, EditAction::eSetAttribute); 5429 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 5430 if (NS_FAILED(rv)) { 5431 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5432 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 5433 return EditorBase::ToGenericNSResult(rv); 5434 } 5435 5436 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute); 5437 rv = SetAttributeOrEquivalent(aElement, attribute, aValue, 5438 aSuppressTransaction); 5439 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5440 "EditorBase::SetAttributeOrEquivalent() failed"); 5441 return EditorBase::ToGenericNSResult(rv); 5442 } 5443 5444 NS_IMETHODIMP EditorBase::RemoveAttributeOrEquivalent( 5445 Element* aElement, const nsAString& aAttribute, bool aSuppressTransaction) { 5446 if (NS_WARN_IF(!aElement)) { 5447 return NS_ERROR_NULL_POINTER; 5448 } 5449 5450 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveAttribute); 5451 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 5452 if (NS_FAILED(rv)) { 5453 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5454 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 5455 return EditorBase::ToGenericNSResult(rv); 5456 } 5457 5458 RefPtr<nsAtom> attribute = NS_Atomize(aAttribute); 5459 rv = RemoveAttributeOrEquivalent(aElement, attribute, aSuppressTransaction); 5460 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5461 "EditorBase::RemoveAttributeOrEquivalent() failed"); 5462 return EditorBase::ToGenericNSResult(rv); 5463 } 5464 5465 void EditorBase::HandleKeyPressEventInReadOnlyMode( 5466 WidgetKeyboardEvent& aKeyboardEvent) const { 5467 MOZ_ASSERT(IsReadonly()); 5468 MOZ_ASSERT(aKeyboardEvent.mMessage == eKeyPress); 5469 5470 switch (aKeyboardEvent.mKeyCode) { 5471 case NS_VK_BACK: 5472 // If it's a `Backspace` key, let's consume it because it may be mapped 5473 // to "Back" of the history navigation. So, it's possible that user 5474 // tries to delete a character with `Backspace` even in the read-only 5475 // editor. 5476 aKeyboardEvent.PreventDefault(); 5477 break; 5478 } 5479 // XXX How about space key (page up and page down in browser navigation)? 5480 } 5481 5482 nsresult EditorBase::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) { 5483 MOZ_ASSERT(!IsReadonly()); 5484 MOZ_ASSERT(aKeyboardEvent); 5485 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress); 5486 5487 // NOTE: When you change this method, you should also change: 5488 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html 5489 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html 5490 // 5491 // And also when you add new key handling, you need to change the subclass's 5492 // HandleKeyPressEvent()'s switch statement. 5493 5494 switch (aKeyboardEvent->mKeyCode) { 5495 case NS_VK_META: 5496 case NS_VK_WIN: 5497 case NS_VK_SHIFT: 5498 case NS_VK_CONTROL: 5499 case NS_VK_ALT: 5500 MOZ_ASSERT_UNREACHABLE( 5501 "eKeyPress event shouldn't be fired for modifier keys"); 5502 return NS_ERROR_UNEXPECTED; 5503 5504 case NS_VK_BACK: { 5505 if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() || 5506 aKeyboardEvent->IsMeta()) { 5507 return NS_OK; 5508 } 5509 DebugOnly<nsresult> rvIgnored = 5510 DeleteSelectionAsAction(nsIEditor::ePrevious, nsIEditor::eStrip); 5511 aKeyboardEvent->PreventDefault(); 5512 NS_WARNING_ASSERTION( 5513 NS_SUCCEEDED(rvIgnored), 5514 "EditorBase::DeleteSelectionAsAction() failed, but ignored"); 5515 return NS_OK; 5516 } 5517 case NS_VK_DELETE: { 5518 // on certain platforms (such as windows) the shift key 5519 // modifies what delete does (cmd_cut in this case). 5520 // bailing here to allow the keybindings to do the cut. 5521 if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() || 5522 aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta()) { 5523 return NS_OK; 5524 } 5525 DebugOnly<nsresult> rvIgnored = 5526 DeleteSelectionAsAction(nsIEditor::eNext, nsIEditor::eStrip); 5527 aKeyboardEvent->PreventDefault(); 5528 NS_WARNING_ASSERTION( 5529 NS_SUCCEEDED(rvIgnored), 5530 "EditorBase::DeleteSelectionAsAction() failed, but ignored"); 5531 return NS_OK; 5532 } 5533 } 5534 return NS_OK; 5535 } 5536 5537 nsresult EditorBase::OnInputText(const nsAString& aStringToInsert) { 5538 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText); 5539 MOZ_ASSERT(!aStringToInsert.IsVoid()); 5540 5541 MOZ_LOG(gTextInputLog, LogLevel::Info, 5542 ("%p %s::OnInputText(aStringToInsert=\"%s\")", this, 5543 mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 5544 NS_ConvertUTF16toUTF8(aStringToInsert).get())); 5545 5546 editActionData.SetData(aStringToInsert); 5547 // FYI: For conforming to current UI Events spec, we should dispatch 5548 // "beforeinput" event before "keypress" event, but here is in a 5549 // "keypress" event listener. However, the other browsers dispatch 5550 // "beforeinput" event after "keypress" event. Therefore, it makes 5551 // sense to follow the other browsers. Spec issue: 5552 // https://github.com/w3c/uievents/issues/220 5553 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 5554 if (NS_FAILED(rv)) { 5555 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5556 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 5557 return EditorBase::ToGenericNSResult(rv); 5558 } 5559 5560 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName, 5561 ScrollSelectionIntoView::Yes, 5562 __FUNCTION__); 5563 rv = InsertTextAsSubAction(aStringToInsert, InsertTextFor::NormalText); 5564 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5565 "EditorBase::InsertTextAsSubAction() failed"); 5566 return EditorBase::ToGenericNSResult(rv); 5567 } 5568 5569 nsresult EditorBase::ReplaceTextAsAction( 5570 const nsAString& aString, nsRange* aReplaceRange, 5571 AllowBeforeInputEventCancelable aAllowBeforeInputEventCancelable, 5572 PreventSetSelection aPreventSetSelection, nsIPrincipal* aPrincipal) { 5573 MOZ_ASSERT(aString.FindChar(nsCRT::CR) == kNotFound); 5574 MOZ_ASSERT_IF(!aReplaceRange, IsTextEditor()); 5575 5576 AutoEditActionDataSetter editActionData(*this, EditAction::eReplaceText, 5577 aPrincipal); 5578 if (NS_WARN_IF(!editActionData.CanHandle())) { 5579 return NS_ERROR_NOT_INITIALIZED; 5580 } 5581 if (aAllowBeforeInputEventCancelable == AllowBeforeInputEventCancelable::No) { 5582 editActionData.MakeBeforeInputEventNonCancelable(); 5583 } 5584 5585 RefPtr<nsRange> targetRange = [&]() -> already_AddRefed<nsRange> { 5586 if (aReplaceRange) { 5587 RefPtr<nsRange> range = nsRange::Create( 5588 aReplaceRange->GetStartContainer(), aReplaceRange->StartOffset(), 5589 aReplaceRange->GetEndContainer(), aReplaceRange->EndOffset(), 5590 IgnoreErrors()); 5591 NS_WARNING_ASSERTION(range && range->IsPositioned(), 5592 "nsRange::Create() failed"); 5593 return range.forget(); 5594 } 5595 nsIContent* const rootContentToSelectAll = 5596 IsTextEditor() 5597 ? AsTextEditor()->GetTextNode() 5598 : static_cast<nsIContent*>(AsHTMLEditor()->ComputeEditingHost()); 5599 if (NS_WARN_IF(!rootContentToSelectAll)) { 5600 return nullptr; 5601 } 5602 RefPtr<nsRange> range = 5603 nsRange::Create(rootContentToSelectAll, 0, rootContentToSelectAll, 5604 rootContentToSelectAll->Length(), IgnoreErrors()); 5605 NS_WARNING_ASSERTION(range && range->IsPositioned(), 5606 "nsRange::Create() failed"); 5607 return range.forget(); 5608 }(); 5609 if (NS_WARN_IF(!targetRange) || NS_WARN_IF(!targetRange->IsPositioned())) { 5610 return NS_ERROR_FAILURE; 5611 } 5612 if (IsTextEditor()) { 5613 editActionData.SetData(aString); 5614 } else { 5615 editActionData.InitializeDataTransfer(aString); 5616 RefPtr<StaticRange> staticTargetRange = StaticRange::Create( 5617 targetRange->StartRef(), targetRange->EndRef(), IgnoreErrors()); 5618 MOZ_ASSERT(staticTargetRange); 5619 MOZ_ASSERT(staticTargetRange->IsPositioned()); 5620 editActionData.AppendTargetRange(std::move(staticTargetRange)); 5621 } 5622 5623 AutoSelectionRestorer restorer( 5624 aPreventSetSelection == PreventSetSelection::Yes ? this : nullptr); 5625 nsresult rv = NS_OK; 5626 auto raii = MakeScopeExit([&] { 5627 if (aPreventSetSelection == PreventSetSelection::Yes && NS_FAILED(rv)) { 5628 restorer.Abort(); 5629 } 5630 }); 5631 5632 // Before dispatching eEditorBeforeInput, we should set `Selection` as the 5633 // target range. Then, we can expose the target range with 5634 // .selectionStart and .selectionEnd, etc even on TextEditor too. 5635 if (SelectionRef().RangeCount() != 1u || 5636 !targetRange->HasEqualBoundaries(*SelectionRef().GetRangeAt(0u))) { 5637 IgnoredErrorResult error; 5638 SelectionRef().RemoveAllRanges(error); 5639 if (MOZ_UNLIKELY(error.Failed())) { 5640 NS_WARNING("Selection::RemoveAllRanges() failed"); 5641 rv = error.StealNSResult(); // rv is used by `raii`. 5642 return rv; 5643 } 5644 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*targetRange, 5645 error); 5646 if (MOZ_UNLIKELY(error.Failed())) { 5647 NS_WARNING( 5648 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed"); 5649 rv = error.StealNSResult(); // rv is used by `raii`. 5650 return rv; 5651 } 5652 } 5653 5654 rv = editActionData.MaybeDispatchBeforeInputEvent(); 5655 if (NS_FAILED(rv)) { 5656 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5657 "MaybeDispatchBeforeInputEvent() failed"); 5658 return EditorBase::ToGenericNSResult(rv); 5659 } 5660 5661 // If a `beforeinput` event listener changed the `Selection`, we should should 5662 // not restore the original one because restoring Selection may confuse the 5663 // web app. 5664 if (SelectionRef().RangeCount() != 1u || 5665 !targetRange->HasEqualBoundaries(*SelectionRef().GetRangeAt(0u))) { 5666 restorer.Abort(); 5667 } 5668 5669 AutoPlaceholderBatch treatAsOneTransaction( 5670 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 5671 5672 // This should emulates inserting text for better undo/redo behavior. 5673 IgnoredErrorResult ignoredError; 5674 AutoEditSubActionNotifier startToHandleEditSubAction( 5675 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError); 5676 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5677 rv = NS_ERROR_EDITOR_DESTROYED; // rv is used by `raii`. 5678 return EditorBase::ToGenericNSResult(rv); 5679 } 5680 NS_WARNING_ASSERTION( 5681 !ignoredError.Failed(), 5682 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5683 5684 if (!aReplaceRange) { 5685 // Use fast path if we're `TextEditor` because it may be in a hot path. 5686 if (IsTextEditor()) { 5687 restorer.Abort(); // XXX Is this intended? 5688 nsresult rv = MOZ_KnownLive(AsTextEditor())->SetTextAsSubAction(aString); 5689 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5690 "TextEditor::SetTextAsSubAction() failed"); 5691 return EditorBase::ToGenericNSResult(rv); 5692 } 5693 5694 MOZ_ASSERT_UNREACHABLE("Setting value of `HTMLEditor` isn't supported"); 5695 rv = NS_ERROR_FAILURE; // rv is used by `raii`. 5696 return EditorBase::ToGenericNSResult(rv); 5697 } 5698 5699 if (aString.IsEmpty() && aReplaceRange->Collapsed()) { 5700 restorer.Abort(); // XXX Is this intended? 5701 5702 NS_WARNING("Setting value was empty and replaced range was empty"); 5703 return NS_OK; 5704 } 5705 5706 rv = ReplaceSelectionAsSubAction(aString); 5707 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5708 "EditorBase::ReplaceSelectionAsSubAction() failed"); 5709 5710 return EditorBase::ToGenericNSResult(rv); 5711 } 5712 5713 nsresult EditorBase::ReplaceSelectionAsSubAction(const nsAString& aString) { 5714 if (aString.IsEmpty()) { 5715 nsresult rv = DeleteSelectionAsSubAction( 5716 nsIEditor::eNone, 5717 IsTextEditor() ? nsIEditor::eNoStrip : nsIEditor::eStrip); 5718 NS_WARNING_ASSERTION( 5719 NS_SUCCEEDED(rv), 5720 "EditorBase::DeleteSelectionAsSubAction(eNone) failed"); 5721 return rv; 5722 } 5723 5724 nsresult rv = InsertTextAsSubAction(aString, InsertTextFor::NormalText); 5725 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5726 "EditorBase::InsertTextAsSubAction() failed"); 5727 return rv; 5728 } 5729 5730 nsresult EditorBase::HandleInlineSpellCheck( 5731 const EditorDOMPoint& aPreviouslySelectedStart, 5732 const AbstractRange* aRange) { 5733 MOZ_ASSERT(IsEditActionDataAvailable()); 5734 5735 if (!mInlineSpellChecker) { 5736 return NS_OK; 5737 } 5738 nsresult rv = mInlineSpellChecker->SpellCheckAfterEditorChange( 5739 GetTopLevelEditSubAction(), SelectionRef(), 5740 aPreviouslySelectedStart.GetContainer(), 5741 aPreviouslySelectedStart.Offset(), 5742 aRange ? aRange->GetStartContainer() : nullptr, 5743 aRange ? aRange->StartOffset() : 0, 5744 aRange ? aRange->GetEndContainer() : nullptr, 5745 aRange ? aRange->EndOffset() : 0); 5746 NS_WARNING_ASSERTION( 5747 NS_SUCCEEDED(rv), 5748 "mozInlineSpellChecker::SpellCheckAfterEditorChange() failed"); 5749 return rv; 5750 } 5751 5752 Element* EditorBase::FindSelectionRoot(const nsINode& aNode) const { 5753 return GetRoot(); 5754 } 5755 5756 void EditorBase::InitializeSelectionAncestorLimit( 5757 Element& aAncestorLimit) const { 5758 MOZ_ASSERT(IsEditActionDataAvailable()); 5759 5760 MOZ_KnownLive(SelectionRef()).SetAncestorLimiter(&aAncestorLimit); 5761 } 5762 5763 nsresult EditorBase::InitializeSelection( 5764 const nsINode& aOriginalEventTargetNode) { 5765 MOZ_ASSERT(IsEditActionDataAvailable()); 5766 5767 const RefPtr<Element> selectionRootContent = 5768 FindSelectionRoot(aOriginalEventTargetNode); 5769 if (!selectionRootContent) { 5770 return NS_OK; 5771 } 5772 5773 nsCOMPtr<nsISelectionController> selectionController = 5774 GetSelectionController(); 5775 if (NS_WARN_IF(!selectionController)) { 5776 return NS_ERROR_FAILURE; 5777 } 5778 5779 // Init the caret 5780 RefPtr<nsCaret> caret = GetCaret(); 5781 if (NS_WARN_IF(!caret)) { 5782 return NS_ERROR_FAILURE; 5783 } 5784 caret->SetSelection(&SelectionRef()); 5785 DebugOnly<nsresult> rvIgnored = 5786 selectionController->SetCaretReadOnly(IsReadonly()); 5787 NS_WARNING_ASSERTION( 5788 NS_SUCCEEDED(rvIgnored), 5789 "nsISelectionController::SetCaretReadOnly() failed, but ignored"); 5790 rvIgnored = selectionController->SetCaretEnabled(true); 5791 NS_WARNING_ASSERTION( 5792 NS_SUCCEEDED(rvIgnored), 5793 "nsISelectionController::SetCaretEnabled() failed, but ignored"); 5794 // Init selection 5795 rvIgnored = 5796 selectionController->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL); 5797 NS_WARNING_ASSERTION( 5798 NS_SUCCEEDED(rvIgnored), 5799 "nsISelectionController::SetSelectionFlags() failed, but ignored"); 5800 5801 selectionController->SelectionWillTakeFocus(); 5802 5803 // If the computed selection root isn't root content, we should set it 5804 // as selection ancestor limit. However, if that is root element, it means 5805 // there is not limitation of the selection, then, we must set nullptr. 5806 // NOTE: If we set a root element to the ancestor limit, some selection 5807 // methods don't work fine. 5808 if (selectionRootContent->GetParent()) { 5809 InitializeSelectionAncestorLimit(*selectionRootContent); 5810 } else { 5811 SelectionRef().SetAncestorLimiter(nullptr); 5812 } 5813 5814 // If there is composition in a text control when this method is called, we 5815 // may need to restore IME selection because if the text control is reframed, 5816 // this already forgot IME selection and the transaction. 5817 // Note that if this is an HTMLEditor, updating composition makes the new 5818 // composition string appear around IME or normal selection. Therefore, 5819 // we don't need to do nothing here. 5820 if (IsTextEditor() && mComposition && mComposition->IsMovingToNewTextNode()) { 5821 // We need to look for the new text node from current selection. 5822 // XXX If selection is changed during reframe, this doesn't work well! 5823 const auto atStartOfFirstRange = 5824 EditorBase::GetFirstSelectionStartPoint<EditorRawDOMPoint>(); 5825 EditorRawDOMPoint betterInsertionPoint = 5826 AsTextEditor()->FindBetterInsertionPoint(atStartOfFirstRange); 5827 RefPtr<Text> textNode = betterInsertionPoint.GetContainerAs<Text>(); 5828 MOZ_ASSERT(textNode, 5829 "There must be text node if composition string is not empty"); 5830 if (textNode) { 5831 MOZ_ASSERT(textNode->Length() >= mComposition->XPEndOffsetInTextNode(), 5832 "The text node must be different from the old text node"); 5833 RefPtr<TextRangeArray> ranges = mComposition->GetRanges(); 5834 DebugOnly<nsresult> rvIgnored = CompositionTransaction::SetIMESelection( 5835 *this, textNode, mComposition->XPOffsetInTextNode(), 5836 mComposition->XPLengthInTextNode(), ranges); 5837 NS_WARNING_ASSERTION( 5838 NS_SUCCEEDED(rvIgnored), 5839 "CompositionTransaction::SetIMESelection() failed, but ignored"); 5840 mComposition->OnUpdateCompositionInEditor( 5841 mComposition->String(), *textNode, 5842 mComposition->XPOffsetInTextNode()); 5843 } 5844 } 5845 5846 return NS_OK; 5847 } 5848 5849 nsresult EditorBase::FinalizeSelection() { 5850 nsCOMPtr<nsISelectionController> selectionController = 5851 GetSelectionController(); 5852 if (NS_WARN_IF(!selectionController)) { 5853 return NS_ERROR_FAILURE; 5854 } 5855 5856 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 5857 if (NS_WARN_IF(!editActionData.CanHandle())) { 5858 return NS_ERROR_NOT_INITIALIZED; 5859 } 5860 5861 SelectionRef().SetAncestorLimiter(nullptr); 5862 5863 if (NS_WARN_IF(!GetPresShell())) { 5864 return NS_ERROR_NOT_INITIALIZED; 5865 } 5866 5867 if (RefPtr<nsCaret> caret = GetCaret()) { 5868 DebugOnly<nsresult> rvIgnored = selectionController->SetCaretEnabled(false); 5869 NS_WARNING_ASSERTION( 5870 NS_SUCCEEDED(rvIgnored), 5871 "nsISelectionController::SetCaretEnabled(false) failed, but ignored"); 5872 } 5873 5874 RefPtr<nsFocusManager> focusManager = nsFocusManager::GetFocusManager(); 5875 if (NS_WARN_IF(!focusManager)) { 5876 return NS_ERROR_NOT_INITIALIZED; 5877 } 5878 // TODO: Running script from here makes harder to handle blur events. We 5879 // should do this asynchronously. 5880 focusManager->UpdateCaretForCaretBrowsingMode(); 5881 if (Element* rootElement = GetExposedRoot()) { 5882 if (rootElement->OwnerDoc()->GetUnretargetedFocusedContent() != 5883 rootElement) { 5884 selectionController->SelectionWillLoseFocus(); 5885 } else { 5886 // We leave this selection as the focused one. When the focus returns, it 5887 // either returns to us (nothing to do), or it returns to something else, 5888 // and nsDocumentViewerFocusListener::HandleEvent fixes it up. 5889 } 5890 } 5891 return NS_OK; 5892 } 5893 5894 Element* EditorBase::GetExposedRoot() const { 5895 Element* rootElement = GetRoot(); 5896 if (!rootElement || !rootElement->IsInNativeAnonymousSubtree()) { 5897 return rootElement; 5898 } 5899 return Element::FromNodeOrNull( 5900 rootElement->GetClosestNativeAnonymousSubtreeRootParentOrHost()); 5901 } 5902 5903 nsresult EditorBase::DetermineCurrentDirection() { 5904 // Get the current root direction from its frame 5905 Element* rootElement = GetExposedRoot(); 5906 if (NS_WARN_IF(!rootElement)) { 5907 return NS_ERROR_FAILURE; 5908 } 5909 5910 // If we don't have an explicit direction, determine our direction 5911 // from the content's direction 5912 if (!IsRightToLeft() && !IsLeftToRight()) { 5913 nsIFrame* frameForRootElement = rootElement->GetPrimaryFrame(); 5914 if (NS_WARN_IF(!frameForRootElement)) { 5915 return NS_ERROR_FAILURE; 5916 } 5917 5918 // Set the flag here, to enable us to use the same code path below. 5919 // It will be flipped before returning from the function. 5920 if (frameForRootElement->StyleVisibility()->mDirection == 5921 StyleDirection::Rtl) { 5922 mFlags |= nsIEditor::eEditorRightToLeft; 5923 } else { 5924 mFlags |= nsIEditor::eEditorLeftToRight; 5925 } 5926 } 5927 5928 return NS_OK; 5929 } 5930 5931 nsresult EditorBase::ToggleTextDirectionAsAction(nsIPrincipal* aPrincipal) { 5932 AutoEditActionDataSetter editActionData(*this, EditAction::eSetTextDirection, 5933 aPrincipal); 5934 if (NS_WARN_IF(!editActionData.CanHandle())) { 5935 return NS_ERROR_NOT_INITIALIZED; 5936 } 5937 5938 nsresult rv = DetermineCurrentDirection(); 5939 if (NS_FAILED(rv)) { 5940 NS_WARNING("EditorBase::DetermineCurrentDirection() failed"); 5941 return EditorBase::ToGenericNSResult(rv); 5942 } 5943 5944 MOZ_ASSERT(IsRightToLeft() || IsLeftToRight()); 5945 // Note that we need to consider new direction before dispatching 5946 // "beforeinput" event since "beforeinput" event listener may change it 5947 // but not canceled. 5948 TextDirection newDirection = 5949 IsRightToLeft() ? TextDirection::eLTR : TextDirection::eRTL; 5950 editActionData.SetData(IsRightToLeft() ? u"ltr"_ns : u"rtl"_ns); 5951 5952 // FYI: Oddly, Chrome does not dispatch beforeinput event in this case but 5953 // dispatches input event. 5954 rv = editActionData.MaybeDispatchBeforeInputEvent(); 5955 if (NS_FAILED(rv)) { 5956 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5957 "MaybeDispatchBeforeInputEvent() failed"); 5958 return EditorBase::ToGenericNSResult(rv); 5959 } 5960 5961 rv = SetTextDirectionTo(newDirection); 5962 if (NS_FAILED(rv)) { 5963 NS_WARNING("EditorBase::SetTextDirectionTo() failed"); 5964 return EditorBase::ToGenericNSResult(rv); 5965 } 5966 5967 editActionData.MarkAsHandled(); 5968 5969 // XXX When we don't change the text direction, do we really need to 5970 // dispatch input event? 5971 DispatchInputEvent(); 5972 5973 return NS_OK; 5974 } 5975 5976 void EditorBase::SwitchTextDirectionTo(TextDirection aTextDirection) { 5977 MOZ_ASSERT(aTextDirection == TextDirection::eLTR || 5978 aTextDirection == TextDirection::eRTL); 5979 5980 AutoEditActionDataSetter editActionData(*this, EditAction::eSetTextDirection); 5981 if (NS_WARN_IF(!editActionData.CanHandle())) { 5982 return; 5983 } 5984 5985 nsresult rv = DetermineCurrentDirection(); 5986 if (NS_WARN_IF(NS_FAILED(rv))) { 5987 return; 5988 } 5989 5990 editActionData.SetData(aTextDirection == TextDirection::eLTR ? u"ltr"_ns 5991 : u"rtl"_ns); 5992 5993 // FYI: Oddly, Chrome does not dispatch beforeinput event in this case but 5994 // dispatches input event. 5995 rv = editActionData.MaybeDispatchBeforeInputEvent(); 5996 if (NS_FAILED(rv)) { 5997 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 5998 "MaybeDispatchBeforeInputEvent() failed"); 5999 return; 6000 } 6001 6002 if ((aTextDirection == TextDirection::eLTR && IsRightToLeft()) || 6003 (aTextDirection == TextDirection::eRTL && IsLeftToRight())) { 6004 // Do it only when the direction is still different from the original 6005 // new direction. Note that "beforeinput" event listener may have already 6006 // changed the direction here, but they may not cancel the event. 6007 nsresult rv = SetTextDirectionTo(aTextDirection); 6008 if (NS_FAILED(rv)) { 6009 NS_WARNING("EditorBase::SetTextDirectionTo() failed"); 6010 return; 6011 } 6012 } 6013 6014 editActionData.MarkAsHandled(); 6015 6016 // XXX When we don't change the text direction, do we really need to 6017 // dispatch input event? 6018 DispatchInputEvent(); 6019 } 6020 6021 nsresult EditorBase::SetTextDirectionTo(TextDirection aTextDirection) { 6022 const RefPtr<Element> editingHostOrTextControlElement = 6023 IsHTMLEditor() ? AsHTMLEditor()->ComputeEditingHost( 6024 HTMLEditor::LimitInBodyElement::No) 6025 : GetExposedRoot(); 6026 if (!editingHostOrTextControlElement) { // Don't warn, HTMLEditor may have no 6027 // active editing host 6028 return NS_OK; 6029 } 6030 6031 if (aTextDirection == TextDirection::eLTR) { 6032 NS_ASSERTION(!IsLeftToRight(), "Unexpected mutually exclusive flag"); 6033 mFlags &= ~nsIEditor::eEditorRightToLeft; 6034 mFlags |= nsIEditor::eEditorLeftToRight; 6035 nsresult rv = 6036 AutoElementAttrAPIWrapper(*this, *editingHostOrTextControlElement) 6037 .SetAttr(nsGkAtoms::dir, u"ltr"_ns, true); 6038 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6039 "AutoElementAttrAPIWrapper::SetAttr() failed"); 6040 return rv; 6041 } 6042 6043 if (aTextDirection == TextDirection::eRTL) { 6044 NS_ASSERTION(!IsRightToLeft(), "Unexpected mutually exclusive flag"); 6045 mFlags |= nsIEditor::eEditorRightToLeft; 6046 mFlags &= ~nsIEditor::eEditorLeftToRight; 6047 nsresult rv = 6048 AutoElementAttrAPIWrapper(*this, *editingHostOrTextControlElement) 6049 .SetAttr(nsGkAtoms::dir, u"rtl"_ns, true); 6050 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6051 "AutoElementAttrAPIWrapper::SetAttr() failed"); 6052 return rv; 6053 } 6054 6055 return NS_OK; 6056 } 6057 6058 Element* EditorBase::GetFocusedElement() const { 6059 EventTarget* eventTarget = GetDOMEventTarget(); 6060 if (!eventTarget) { 6061 return nullptr; 6062 } 6063 6064 Element* const focusedElement = nsFocusManager::GetFocusedElementStatic(); 6065 MOZ_ASSERT((focusedElement == eventTarget) == 6066 SameCOMIdentity(focusedElement, eventTarget)); 6067 6068 return (focusedElement == eventTarget) ? focusedElement : nullptr; 6069 } 6070 6071 bool EditorBase::IsActiveInDOMWindow() const { 6072 EventTarget* piTarget = GetDOMEventTarget(); 6073 if (!piTarget) { 6074 return false; 6075 } 6076 6077 nsFocusManager* focusManager = nsFocusManager::GetFocusManager(); 6078 if (NS_WARN_IF(!focusManager)) { 6079 return false; // Do we need to check the singleton instance?? 6080 } 6081 6082 Document* document = GetDocument(); 6083 if (NS_WARN_IF(!document)) { 6084 return false; 6085 } 6086 nsPIDOMWindowOuter* ourWindow = document->GetWindow(); 6087 nsCOMPtr<nsPIDOMWindowOuter> win; 6088 nsIContent* content = nsFocusManager::GetFocusedDescendant( 6089 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win)); 6090 return SameCOMIdentity(content, piTarget); 6091 } 6092 6093 bool EditorBase::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const { 6094 // If the event is trusted, the event should always cause input. 6095 if (NS_WARN_IF(!aGUIEvent)) { 6096 return false; 6097 } 6098 6099 // If this is dispatched by using cordinates but this editor doesn't have 6100 // focus, we shouldn't handle it. 6101 if (aGUIEvent->IsUsingCoordinates() && !GetFocusedElement()) { 6102 return false; 6103 } 6104 6105 // If a composition event isn't dispatched via widget, we need to ignore them 6106 // since they cannot be managed by TextComposition. E.g., the event was 6107 // created by chrome JS. 6108 // Note that if we allow to handle such events, editor may be confused by 6109 // strange event order. 6110 bool needsWidget = false; 6111 switch (aGUIEvent->mMessage) { 6112 case eUnidentifiedEvent: 6113 // If events are not created with proper event interface, their message 6114 // are initialized with eUnidentifiedEvent. Let's ignore such event. 6115 return false; 6116 case eCompositionStart: 6117 case eCompositionEnd: 6118 case eCompositionUpdate: 6119 case eCompositionChange: 6120 case eCompositionCommitAsIs: 6121 // Don't allow composition events whose internal event are not 6122 // WidgetCompositionEvent. 6123 if (!aGUIEvent->AsCompositionEvent()) { 6124 return false; 6125 } 6126 needsWidget = true; 6127 break; 6128 default: 6129 break; 6130 } 6131 if (needsWidget && !aGUIEvent->mWidget) { 6132 return false; 6133 } 6134 6135 // Accept all trusted events. 6136 if (aGUIEvent->IsTrusted()) { 6137 return true; 6138 } 6139 6140 // Ignore untrusted mouse event. 6141 // XXX Why are we handling other untrusted input events? 6142 if (aGUIEvent->AsMouseEventBase()) { 6143 return false; 6144 } 6145 6146 // Otherwise, we shouldn't handle any input events when we're not an active 6147 // element of the DOM window. 6148 return IsActiveInDOMWindow(); 6149 } 6150 6151 nsresult EditorBase::FlushPendingSpellCheck() { 6152 // If the spell check skip flag is still enabled from creation time, 6153 // disable it because focused editors are allowed to spell check. 6154 if (!ShouldSkipSpellCheck()) { 6155 return NS_OK; 6156 } 6157 MOZ_ASSERT(!IsHTMLEditor(), "HTMLEditor should not has pending spell checks"); 6158 nsresult rv = RemoveFlags(nsIEditor::eEditorSkipSpellCheck); 6159 if (NS_WARN_IF(Destroyed())) { 6160 return NS_ERROR_EDITOR_DESTROYED; 6161 } 6162 NS_WARNING_ASSERTION( 6163 NS_SUCCEEDED(rv), 6164 "EditorBase::RemoveFlags(nsIEditor::eEditorSkipSpellCheck) failed"); 6165 return rv; 6166 } 6167 6168 bool EditorBase::CanKeepHandlingFocusEvent( 6169 const nsINode& aOriginalEventTargetNode) const { 6170 if (MOZ_UNLIKELY(!IsListeningToEvents() || Destroyed())) { 6171 return false; 6172 } 6173 6174 // If the event target is document mode, we only need to handle the focus 6175 // event when the document is still in designMode. Otherwise, the 6176 // mode has been disabled by somebody while we're handling the focus event. 6177 if (aOriginalEventTargetNode.IsDocument()) { 6178 return IsHTMLEditor() && aOriginalEventTargetNode.IsInDesignMode(); 6179 } 6180 MOZ_ASSERT(aOriginalEventTargetNode.IsContent()); 6181 6182 // If nobody has focus, the focus event target has been blurred by somebody 6183 // else. So the editor shouldn't initialize itself to start to handle 6184 // anything. 6185 const Element* const focusedElement = 6186 nsFocusManager::GetFocusedElementStatic(); 6187 if (!focusedElement) { 6188 return false; 6189 } 6190 6191 // If there's an HTMLEditor registered in the target document and we 6192 // are not that HTMLEditor (for cases like nested documents), let 6193 // that HTMLEditor to handle the focus event. 6194 if (IsHTMLEditor()) { 6195 const HTMLEditor* precedentHTMLEditor = 6196 aOriginalEventTargetNode.OwnerDoc()->GetHTMLEditor(); 6197 6198 if (precedentHTMLEditor && precedentHTMLEditor != this) { 6199 return false; 6200 } 6201 } 6202 6203 const nsIContent* exposedTargetContent = 6204 aOriginalEventTargetNode.AsContent() 6205 ->FindFirstNonChromeOnlyAccessContent(); 6206 const nsIContent* exposedFocusedContent = 6207 focusedElement->FindFirstNonChromeOnlyAccessContent(); 6208 return exposedTargetContent && exposedFocusedContent && 6209 exposedTargetContent == exposedFocusedContent; 6210 } 6211 6212 nsresult EditorBase::OnFocus(const nsINode& aOriginalEventTargetNode) { 6213 MOZ_ASSERT(IsEditActionDataAvailable()); 6214 6215 InitializeSelection(aOriginalEventTargetNode); 6216 mSpellCheckerDictionaryUpdated = false; 6217 if (mInlineSpellChecker && CanEnableSpellCheck()) { 6218 DebugOnly<nsresult> rvIgnored = 6219 mInlineSpellChecker->UpdateCurrentDictionary(); 6220 NS_WARNING_ASSERTION( 6221 NS_SUCCEEDED(rvIgnored), 6222 "mozInlineSpellCHecker::UpdateCurrentDictionary() failed, but ignored"); 6223 mSpellCheckerDictionaryUpdated = true; 6224 } 6225 // XXX Why don't we stop handling focus with the spell checker immediately 6226 // after calling InitializeSelection? 6227 if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) { 6228 return NS_ERROR_EDITOR_DESTROYED; 6229 } 6230 6231 const RefPtr<Element> focusedElement = GetFocusedElement(); 6232 RefPtr<nsPresContext> presContext = 6233 focusedElement ? focusedElement->GetPresContext( 6234 Element::PresContextFor::eForComposedDoc) 6235 : GetPresContext(); 6236 if (NS_WARN_IF(!presContext)) { 6237 return NS_ERROR_FAILURE; 6238 } 6239 IMEStateManager::OnFocusInEditor(*presContext, focusedElement, *this); 6240 6241 return NS_OK; 6242 } 6243 6244 void EditorBase::HideCaret(bool aHide) { 6245 if (mHidingCaret == aHide) { 6246 return; 6247 } 6248 6249 RefPtr<nsCaret> caret = GetCaret(); 6250 if (NS_WARN_IF(!caret)) { 6251 return; 6252 } 6253 6254 mHidingCaret = aHide; 6255 if (aHide) { 6256 caret->AddForceHide(); 6257 } else { 6258 caret->RemoveForceHide(); 6259 } 6260 } 6261 6262 NS_IMETHODIMP EditorBase::Unmask(uint32_t aStart, int64_t aEnd, 6263 uint32_t aTimeout, uint8_t aArgc) { 6264 if (NS_WARN_IF(!IsPasswordEditor())) { 6265 return NS_ERROR_NOT_AVAILABLE; 6266 } 6267 if (NS_WARN_IF(aArgc >= 1 && aStart == UINT32_MAX) || 6268 NS_WARN_IF(aArgc >= 2 && aEnd == 0) || 6269 NS_WARN_IF(aArgc >= 2 && aEnd > 0 && aStart >= aEnd)) { 6270 return NS_ERROR_INVALID_ARG; 6271 } 6272 6273 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword); 6274 if (NS_WARN_IF(!editActionData.CanHandle())) { 6275 return NS_ERROR_NOT_INITIALIZED; 6276 } 6277 6278 uint32_t start = aArgc < 1 ? 0 : aStart; 6279 uint32_t length = aArgc < 2 || aEnd < 0 ? UINT32_MAX : aEnd - start; 6280 uint32_t timeout = aArgc < 3 ? 0 : aTimeout; 6281 nsresult rv = MOZ_KnownLive(AsTextEditor()) 6282 ->SetUnmaskRangeAndNotify(start, length, timeout); 6283 if (NS_FAILED(rv)) { 6284 NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed"); 6285 return EditorBase::ToGenericNSResult(rv); 6286 } 6287 6288 // Flush pending layout right now since the caller may access us before 6289 // doing it. 6290 if (RefPtr<PresShell> presShell = GetPresShell()) { 6291 presShell->FlushPendingNotifications(FlushType::Layout); 6292 } 6293 6294 return NS_OK; 6295 } 6296 6297 NS_IMETHODIMP EditorBase::Mask() { 6298 if (NS_WARN_IF(!IsPasswordEditor())) { 6299 return NS_ERROR_NOT_AVAILABLE; 6300 } 6301 6302 AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword); 6303 if (NS_WARN_IF(!editActionData.CanHandle())) { 6304 return NS_ERROR_NOT_INITIALIZED; 6305 } 6306 6307 nsresult rv = MOZ_KnownLive(AsTextEditor())->MaskAllCharactersAndNotify(); 6308 if (NS_FAILED(rv)) { 6309 NS_WARNING("TextEditor::MaskAllCharactersAndNotify() failed"); 6310 return EditorBase::ToGenericNSResult(rv); 6311 } 6312 6313 // Flush pending layout right now since the caller may access us before 6314 // doing it. 6315 if (RefPtr<PresShell> presShell = GetPresShell()) { 6316 presShell->FlushPendingNotifications(FlushType::Layout); 6317 } 6318 6319 return NS_OK; 6320 } 6321 6322 NS_IMETHODIMP EditorBase::GetUnmaskedStart(uint32_t* aResult) { 6323 if (NS_WARN_IF(!IsPasswordEditor())) { 6324 *aResult = 0; 6325 return NS_ERROR_NOT_AVAILABLE; 6326 } 6327 *aResult = 6328 AsTextEditor()->IsAllMasked() ? 0 : AsTextEditor()->UnmaskedStart(); 6329 return NS_OK; 6330 } 6331 6332 NS_IMETHODIMP EditorBase::GetUnmaskedEnd(uint32_t* aResult) { 6333 if (NS_WARN_IF(!IsPasswordEditor())) { 6334 *aResult = 0; 6335 return NS_ERROR_NOT_AVAILABLE; 6336 } 6337 *aResult = AsTextEditor()->IsAllMasked() ? 0 : AsTextEditor()->UnmaskedEnd(); 6338 return NS_OK; 6339 } 6340 6341 NS_IMETHODIMP EditorBase::GetAutoMaskingEnabled(bool* aResult) { 6342 if (NS_WARN_IF(!IsPasswordEditor())) { 6343 *aResult = false; 6344 return NS_ERROR_NOT_AVAILABLE; 6345 } 6346 *aResult = AsTextEditor()->IsMaskingPassword(); 6347 return NS_OK; 6348 } 6349 6350 NS_IMETHODIMP EditorBase::GetPasswordMask(nsAString& aPasswordMask) { 6351 aPasswordMask.Assign(TextEditor::PasswordMask()); 6352 return NS_OK; 6353 } 6354 6355 template <typename PT, typename CT> 6356 EditorBase::AutoCaretBidiLevelManager::AutoCaretBidiLevelManager( 6357 const EditorBase& aEditorBase, nsIEditor::EDirection aDirectionAndAmount, 6358 const EditorDOMPointBase<PT, CT>& aPointAtCaret) { 6359 MOZ_ASSERT(aEditorBase.IsEditActionDataAvailable()); 6360 6361 nsPresContext* presContext = aEditorBase.GetPresContext(); 6362 if (NS_WARN_IF(!presContext)) { 6363 mFailed = true; 6364 return; 6365 } 6366 6367 if (!presContext->BidiEnabled()) { 6368 return; // Perform the deletion 6369 } 6370 6371 if (!aPointAtCaret.IsInContentNode()) { 6372 mFailed = true; 6373 return; 6374 } 6375 6376 // XXX Not sure whether this requires strong reference here. 6377 RefPtr<nsFrameSelection> frameSelection = 6378 aEditorBase.SelectionRef().GetFrameSelection(); 6379 if (NS_WARN_IF(!frameSelection)) { 6380 mFailed = true; 6381 return; 6382 } 6383 6384 nsPrevNextBidiLevels levels = frameSelection->GetPrevNextBidiLevels( 6385 aPointAtCaret.template ContainerAs<nsIContent>(), aPointAtCaret.Offset(), 6386 true); 6387 6388 mozilla::intl::BidiEmbeddingLevel levelBefore = levels.mLevelBefore; 6389 mozilla::intl::BidiEmbeddingLevel levelAfter = levels.mLevelAfter; 6390 6391 mozilla::intl::BidiEmbeddingLevel currentCaretLevel = 6392 frameSelection->GetCaretBidiLevel(); 6393 6394 mozilla::intl::BidiEmbeddingLevel levelOfDeletion; 6395 levelOfDeletion = (nsIEditor::eNext == aDirectionAndAmount || 6396 nsIEditor::eNextWord == aDirectionAndAmount) 6397 ? levelAfter 6398 : levelBefore; 6399 6400 if (currentCaretLevel == levelOfDeletion) { 6401 return; // Perform the deletion 6402 } 6403 6404 // Set the bidi level of the caret to that of the 6405 // character that will be (or would have been) deleted 6406 mNewCaretBidiLevel = Some(levelOfDeletion); 6407 mCanceled = 6408 !StaticPrefs::bidi_edit_delete_immediately() && levelBefore != levelAfter; 6409 } 6410 6411 void EditorBase::AutoCaretBidiLevelManager::MaybeUpdateCaretBidiLevel( 6412 const EditorBase& aEditorBase) const { 6413 MOZ_ASSERT(!mFailed); 6414 if (mNewCaretBidiLevel.isNothing()) { 6415 return; 6416 } 6417 RefPtr<nsFrameSelection> frameSelection = 6418 aEditorBase.SelectionRef().GetFrameSelection(); 6419 MOZ_ASSERT(frameSelection); 6420 frameSelection->SetCaretBidiLevelAndMaybeSchedulePaint( 6421 mNewCaretBidiLevel.value()); 6422 } 6423 6424 void EditorBase::UndefineCaretBidiLevel() const { 6425 MOZ_ASSERT(IsEditActionDataAvailable()); 6426 6427 /** 6428 * After inserting text the caret Bidi level must be set to the level of the 6429 * inserted text.This is difficult, because we cannot know what the level is 6430 * until after the Bidi algorithm is applied to the whole paragraph. 6431 * 6432 * So we set the caret Bidi level to UNDEFINED here, and the caret code will 6433 * set it correctly later 6434 */ 6435 nsFrameSelection* frameSelection = SelectionRef().GetFrameSelection(); 6436 if (frameSelection) { 6437 frameSelection->UndefineCaretBidiLevel(); 6438 } 6439 } 6440 6441 NS_IMETHODIMP EditorBase::GetTextLength(uint32_t* aCount) { 6442 return NS_ERROR_NOT_IMPLEMENTED; 6443 } 6444 6445 NS_IMETHODIMP EditorBase::GetNewlineHandling(int32_t* aNewlineHandling) { 6446 if (NS_WARN_IF(!aNewlineHandling)) { 6447 return NS_ERROR_INVALID_ARG; 6448 } 6449 *aNewlineHandling = mNewlineHandling; 6450 return NS_OK; 6451 } 6452 6453 NS_IMETHODIMP EditorBase::SetNewlineHandling(int32_t aNewlineHandling) { 6454 switch (aNewlineHandling) { 6455 case nsIEditor::eNewlinesPasteIntact: 6456 case nsIEditor::eNewlinesPasteToFirst: 6457 case nsIEditor::eNewlinesReplaceWithSpaces: 6458 case nsIEditor::eNewlinesStrip: 6459 case nsIEditor::eNewlinesReplaceWithCommas: 6460 case nsIEditor::eNewlinesStripSurroundingWhitespace: 6461 mNewlineHandling = aNewlineHandling; 6462 return NS_OK; 6463 default: 6464 NS_ERROR("SetNewlineHandling() is called with wrong value"); 6465 return NS_ERROR_INVALID_ARG; 6466 } 6467 } 6468 6469 bool EditorBase::IsSelectionRangeContainerNotContent() const { 6470 MOZ_ASSERT(IsEditActionDataAvailable()); 6471 6472 // TODO: Make all callers use !AutoClonedRangeArray::IsInContent() instead. 6473 const uint32_t rangeCount = SelectionRef().RangeCount(); 6474 for (const uint32_t i : IntegerRange(rangeCount)) { 6475 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount); 6476 const nsRange* range = SelectionRef().GetRangeAt(i); 6477 MOZ_ASSERT(range); 6478 if (MOZ_UNLIKELY(!range) || MOZ_UNLIKELY(!range->GetStartContainer()) || 6479 MOZ_UNLIKELY(!range->GetStartContainer()->IsContent()) || 6480 MOZ_UNLIKELY(!range->GetEndContainer()) || 6481 MOZ_UNLIKELY(!range->GetEndContainer()->IsContent())) { 6482 return true; 6483 } 6484 } 6485 return false; 6486 } 6487 6488 NS_IMETHODIMP EditorBase::InsertText(const nsAString& aStringToInsert) { 6489 nsresult rv = InsertTextAsAction(aStringToInsert); 6490 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6491 "EditorBase::InsertTextAsAction() failed"); 6492 return rv; 6493 } 6494 6495 nsresult EditorBase::InsertTextAsAction(const nsAString& aStringToInsert, 6496 nsIPrincipal* aPrincipal) { 6497 // Showing this assertion is fine if this method is called by outside via 6498 // mutation event listener or something. Otherwise, this is called by 6499 // wrong method. 6500 NS_ASSERTION(!mPlaceholderBatch, 6501 "Should be called only when this is the only edit action of the " 6502 "operation " 6503 "unless mutation event listener nests some operations"); 6504 6505 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertText, 6506 aPrincipal); 6507 // Note that we don't need to replace native line breaks with XP line breaks 6508 // here because Chrome does not do it. 6509 MOZ_ASSERT(!aStringToInsert.IsVoid()); 6510 editActionData.SetData(aStringToInsert); 6511 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 6512 if (NS_FAILED(rv)) { 6513 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 6514 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 6515 return EditorBase::ToGenericNSResult(rv); 6516 } 6517 6518 nsString stringToInsert(aStringToInsert); 6519 if (IsTextEditor()) { 6520 nsContentUtils::PlatformToDOMLineBreaks(stringToInsert); 6521 } 6522 AutoPlaceholderBatch treatAsOneTransaction( 6523 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 6524 rv = InsertTextAsSubAction(stringToInsert, InsertTextFor::NormalText); 6525 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6526 "EditorBase::InsertTextAsSubAction() failed"); 6527 return EditorBase::ToGenericNSResult(rv); 6528 } 6529 6530 nsresult EditorBase::InsertTextAsSubAction(const nsAString& aStringToInsert, 6531 InsertTextFor aPurpose) { 6532 MOZ_ASSERT(IsEditActionDataAvailable()); 6533 MOZ_ASSERT(mPlaceholderBatch); 6534 MOZ_ASSERT(IsHTMLEditor() || 6535 aStringToInsert.FindChar(nsCRT::CR) == kNotFound); 6536 MOZ_ASSERT_IF(aPurpose == InsertTextFor::CompositionStart || 6537 aPurpose == InsertTextFor::CompositionUpdate || 6538 aPurpose == InsertTextFor::CompositionEnd || 6539 aPurpose == InsertTextFor::CompositionStartAndEnd, 6540 mComposition); 6541 6542 if (NS_WARN_IF(!mInitSucceeded)) { 6543 return NS_ERROR_NOT_INITIALIZED; 6544 } 6545 6546 if (NS_WARN_IF(Destroyed())) { 6547 return NS_ERROR_EDITOR_DESTROYED; 6548 } 6549 6550 EditSubAction editSubAction = ShouldHandleIMEComposition() 6551 ? EditSubAction::eInsertTextComingFromIME 6552 : EditSubAction::eInsertText; 6553 6554 IgnoredErrorResult ignoredError; 6555 AutoEditSubActionNotifier startToHandleEditSubAction( 6556 *this, editSubAction, nsIEditor::eNext, ignoredError); 6557 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 6558 return ignoredError.StealNSResult(); 6559 } 6560 NS_WARNING_ASSERTION( 6561 !ignoredError.Failed(), 6562 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 6563 6564 Result<EditActionResult, nsresult> result = 6565 HandleInsertText(aStringToInsert, aPurpose); 6566 if (MOZ_UNLIKELY(result.isErr())) { 6567 NS_WARNING("EditorBase::HandleInsertText() failed"); 6568 return result.unwrapErr(); 6569 } 6570 return NS_OK; 6571 } 6572 6573 NS_IMETHODIMP EditorBase::InsertLineBreak() { return NS_ERROR_NOT_IMPLEMENTED; } 6574 6575 /***************************************************************************** 6576 * mozilla::EditorBase::AutoEditActionDataSetter 6577 *****************************************************************************/ 6578 6579 EditorBase::AutoEditActionDataSetter::AutoEditActionDataSetter( 6580 const EditorBase& aEditorBase, EditAction aEditAction, 6581 nsIPrincipal* aPrincipal /* = nullptr */) 6582 : mEditorBase(const_cast<EditorBase&>(aEditorBase)), 6583 mPrincipal(aPrincipal), 6584 mParentData(aEditorBase.mEditActionData), 6585 mData(VoidString()), 6586 mRawEditAction(aEditAction), 6587 mEditorWasDestroyedDuringHandlingEditAction( 6588 mParentData && 6589 mParentData->mEditorWasDestroyedDuringHandlingEditAction), 6590 mEditorWasReinitialized(mParentData && 6591 mParentData->mEditorWasReinitialized) { 6592 // If we're nested edit action, copies necessary data from the parent. 6593 if (mParentData) { 6594 mSelection = mParentData->mSelection; 6595 MOZ_ASSERT(!mSelection || 6596 (mSelection->GetType() == SelectionType::eNormal)); 6597 mTextNode = mParentData->mTextNode; 6598 6599 // If we're not editing something, we should inherit the parent's edit 6600 // action. This may occur if creator or its callee use public methods which 6601 // just returns something. 6602 if (IsEditActionInOrderToEditSomething(aEditAction)) { 6603 mEditAction = aEditAction; 6604 } else { 6605 mEditAction = mParentData->mEditAction; 6606 // If we inherit an edit action whose handler needs to dispatch a 6607 // clipboard event, we should inherit the clipboard dispatching state 6608 // too because this nest occurs by a clipboard event listener or 6609 // a beforeinput/mutation event listener is important for checking 6610 // whether we've already called `MaybeDispatchBeforeInputEvent()` 6611 // property in some points. If the former case, not yet dispatching 6612 // beforeinput event is okay (not fine). 6613 mHasTriedToDispatchClipboardEvent = 6614 mParentData->mHasTriedToDispatchClipboardEvent; 6615 } 6616 mTopLevelEditSubAction = mParentData->mTopLevelEditSubAction; 6617 6618 // Parent's mTopLevelEditSubActionData should be referred instead so that 6619 // we don't need to set mTopLevelEditSubActionData.mSelectedRange nor 6620 // mTopLevelEditActionData.mChangedRange here. 6621 6622 mDirectionOfTopLevelEditSubAction = 6623 mParentData->mDirectionOfTopLevelEditSubAction; 6624 } else { 6625 mSelection = mEditorBase.GetSelection(); 6626 if (NS_WARN_IF(!mSelection)) { 6627 return; 6628 } 6629 // Although we shouldn't have had the cached Text yet because we're the 6630 // topmost instance of AutoEditActionDataSetter and we'll register this to 6631 // aEditorBase below. However, for clarifying, let's explicitly ignore the 6632 // cached Text. Additionally, this may be called for initializing 6633 // aEditorBase too. Therefore, we need to avoid the assertions in 6634 // GetTextNode() so that we need to check whether the editor is initialized. 6635 mTextNode = mEditorBase.IsTextEditor() && mEditorBase.mInitSucceeded 6636 ? mEditorBase.AsTextEditor()->GetTextNode( 6637 TextEditor::IgnoreTextNodeCache::Yes) 6638 : nullptr; 6639 6640 MOZ_ASSERT(mSelection->GetType() == SelectionType::eNormal); 6641 6642 mEditAction = aEditAction; 6643 mDirectionOfTopLevelEditSubAction = eNone; 6644 if (mEditorBase.IsHTMLEditor()) { 6645 mTopLevelEditSubActionData.mSelectedRange = 6646 mEditorBase.AsHTMLEditor() 6647 ->GetSelectedRangeItemForTopLevelEditSubAction(); 6648 mTopLevelEditSubActionData.mChangedRange = 6649 mEditorBase.AsHTMLEditor()->GetChangedRangeForTopLevelEditSubAction(); 6650 mTopLevelEditSubActionData.mCachedPendingStyles.emplace(); 6651 } 6652 } 6653 mEditorBase.mEditActionData = this; 6654 6655 if (aEditorBase.IsHTMLEditor() && 6656 MOZ_LOG_TEST(gHTMLEditorEditActionStartLog, LogLevel::Info) && 6657 aEditAction != EditAction::eNone && 6658 aEditAction != EditAction::eNotEditing && 6659 aEditAction != EditAction::eInitializing) { 6660 const HTMLEditor& htmlEditor = *aEditorBase.AsHTMLEditor(); 6661 Element* const editingHost = 6662 htmlEditor.ComputeEditingHost(HTMLEditor::LimitInBodyElement::No); 6663 nsAutoString innerHTML; 6664 if (editingHost) { 6665 editingHost->GetInnerHTML(innerHTML, IgnoreErrors()); 6666 innerHTML.ReplaceSubstring(u"\n", u"\\n"); 6667 innerHTML.ReplaceSubstring(u"\r", u"\\r"); 6668 innerHTML.ReplaceSubstring(u"\t", u"\\t"); 6669 innerHTML.ReplaceSubstring(u"\f", u"\\f"); 6670 innerHTML.ReplaceSubstring(u"\u00A0", u" "); 6671 } 6672 MOZ_ASSERT(mSelection); 6673 MOZ_LOG( 6674 gHTMLEditorEditActionStartLog, LogLevel::Info, 6675 ("%s\nediting host: %s\ninnerHTML: \"%s\"\nselection range " 6676 "count: %u", 6677 ToString(aEditAction).c_str(), ToString(RefPtr{editingHost}).c_str(), 6678 NS_ConvertUTF16toUTF8(innerHTML).get(), mSelection->RangeCount())); 6679 for (const uint32_t index : IntegerRange(mSelection->RangeCount())) { 6680 nsRange* const range = mSelection->GetRangeAt(index); 6681 MOZ_ASSERT(range); 6682 EditorRawDOMRange editorRange(*range); 6683 MOZ_LOG(gHTMLEditorEditActionStartLog, LogLevel::Info, 6684 ("getRangeAt(%u): %s", index, ToString(editorRange).c_str())); 6685 } 6686 } 6687 } 6688 6689 EditorBase::AutoEditActionDataSetter::~AutoEditActionDataSetter() { 6690 MOZ_ASSERT(mHasCanHandleChecked); 6691 6692 if (!mSelection || NS_WARN_IF(mEditorBase.mEditActionData != this)) { 6693 return; 6694 } 6695 mEditorBase.mEditActionData = mParentData; 6696 6697 MOZ_ASSERT( 6698 !mTopLevelEditSubActionData.mSelectedRange || 6699 (!mTopLevelEditSubActionData.mSelectedRange->mStartContainer && 6700 !mTopLevelEditSubActionData.mSelectedRange->mEndContainer), 6701 "mTopLevelEditSubActionData.mSelectedRange should've been cleared"); 6702 } 6703 6704 void EditorBase::AutoEditActionDataSetter::OnEditorInitialized() { 6705 if (mEditorWasDestroyedDuringHandlingEditAction) { 6706 mEditorWasReinitialized = true; 6707 } 6708 if (mEditorBase.IsTextEditor()) { 6709 mTextNode = mEditorBase.AsTextEditor()->GetTextNode( 6710 TextEditor::IgnoreTextNodeCache::Yes); 6711 } 6712 if (mParentData) { 6713 mParentData->OnEditorInitialized(); 6714 } 6715 } 6716 6717 void EditorBase::AutoEditActionDataSetter::UpdateSelectionCache( 6718 Selection& aSelection) { 6719 MOZ_ASSERT(aSelection.GetType() == SelectionType::eNormal); 6720 6721 if (mSelection == &aSelection) { 6722 return; 6723 } 6724 6725 AutoEditActionDataSetter& topLevelEditActionData = 6726 [&]() -> AutoEditActionDataSetter& { 6727 for (AutoEditActionDataSetter* editActionData = this;; 6728 editActionData = editActionData->mParentData) { 6729 if (!editActionData->mParentData) { 6730 return *editActionData; 6731 } 6732 } 6733 MOZ_ASSERT_UNREACHABLE("You do something wrong"); 6734 }(); 6735 6736 RefPtr<Selection> previousSelection = mSelection; 6737 6738 // Keep grabbing the old selection in the top level edit action data until the 6739 // all owners end handling it. 6740 if (previousSelection) { 6741 topLevelEditActionData.mRetiredSelections.AppendElement(*previousSelection); 6742 } 6743 6744 // If the old selection is in batch, we should end the batch which 6745 // `EditorBase::BeginUpdateViewBatch` started. 6746 if (mEditorBase.mUpdateCount && previousSelection) { 6747 previousSelection->EndBatchChanges(__FUNCTION__); 6748 } 6749 6750 mSelection = &aSelection; 6751 for (AutoEditActionDataSetter* parentActionData = mParentData; 6752 parentActionData; parentActionData = parentActionData->mParentData) { 6753 if (!parentActionData->mSelection) { 6754 continue; 6755 } 6756 // Skip scanning mRetiredSelections if we've already handled the selection 6757 // previous time. 6758 if (parentActionData->mSelection != previousSelection) { 6759 if (!topLevelEditActionData.mRetiredSelections.Contains( 6760 OwningNonNull<Selection>(*parentActionData->mSelection))) { 6761 topLevelEditActionData.mRetiredSelections.AppendElement( 6762 *parentActionData->mSelection); 6763 } 6764 previousSelection = parentActionData->mSelection; 6765 } 6766 parentActionData->mSelection = &aSelection; 6767 } 6768 6769 // Restart the batching in the new selection. 6770 if (mEditorBase.mUpdateCount) { 6771 aSelection.StartBatchChanges(__FUNCTION__); 6772 } 6773 } 6774 6775 void EditorBase::AutoEditActionDataSetter::SetColorData( 6776 const nsAString& aData) { 6777 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), 6778 "It's too late to set data since this may have already dispatched " 6779 "a beforeinput event"); 6780 6781 if (aData.IsEmpty()) { 6782 // When removing color/background-color, let's use empty string. 6783 mData.Truncate(); 6784 MOZ_ASSERT(!mData.IsVoid()); 6785 return; 6786 } 6787 6788 DebugOnly<bool> validColorValue = HTMLEditUtils::GetNormalizedCSSColorValue( 6789 aData, HTMLEditUtils::ZeroAlphaColor::RGBAValue, mData); 6790 MOZ_ASSERT_IF(validColorValue, !mData.IsVoid()); 6791 } 6792 6793 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer( 6794 DataTransfer* aDataTransfer) { 6795 MOZ_ASSERT(aDataTransfer); 6796 MOZ_ASSERT(aDataTransfer->IsReadOnly()); 6797 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), 6798 "It's too late to set dataTransfer since this may have already " 6799 "dispatched a beforeinput event"); 6800 6801 mDataTransfer = aDataTransfer; 6802 } 6803 6804 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer( 6805 nsITransferable* aTransferable) { 6806 MOZ_ASSERT(aTransferable); 6807 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), 6808 "It's too late to set dataTransfer since this may have already " 6809 "dispatched a beforeinput event"); 6810 6811 Document* document = mEditorBase.GetDocument(); 6812 nsIGlobalObject* scopeObject = 6813 document ? document->GetScopeObject() : nullptr; 6814 mDataTransfer = new DataTransfer(scopeObject, eEditorInput, aTransferable); 6815 } 6816 6817 void EditorBase::AutoEditActionDataSetter::InitializeDataTransfer( 6818 const nsAString& aString) { 6819 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), 6820 "It's too late to set dataTransfer since this may have already " 6821 "dispatched a beforeinput event"); 6822 Document* document = mEditorBase.GetDocument(); 6823 nsIGlobalObject* scopeObject = 6824 document ? document->GetScopeObject() : nullptr; 6825 mDataTransfer = new DataTransfer(scopeObject, eEditorInput, aString); 6826 } 6827 6828 void EditorBase::AutoEditActionDataSetter::InitializeDataTransferWithClipboard( 6829 SettingDataTransfer aSettingDataTransfer, DataTransfer* aDataTransfer, 6830 nsIClipboard::ClipboardType aClipboardType) { 6831 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), 6832 "It's too late to set dataTransfer since this may have already " 6833 "dispatched a beforeinput event"); 6834 6835 Document* document = mEditorBase.GetDocument(); 6836 nsIGlobalObject* scopeObject = 6837 document ? document->GetScopeObject() : nullptr; 6838 // mDataTransfer will be used for eEditorInput event, but we can keep 6839 // using ePaste and ePasteNoFormatting here. If we need to use eEditorInput, 6840 // we need to create eEditorInputNoFormatting or something... 6841 EventMessage message = 6842 (aSettingDataTransfer == SettingDataTransfer::eWithFormat) 6843 ? ePaste 6844 : ePasteNoFormatting; 6845 if (aDataTransfer) { 6846 // The DataTransfer being passed in will be used in a paste event, which 6847 // means it will be cleared after that event is done firing. We don't want 6848 // that for "input" and "beforeinput" events, so make a copy of its data. 6849 aDataTransfer->Clone(scopeObject, message, 6850 /* aUserCancelled = */ false, 6851 /* aIsCrossDomainSubFrameDrop = */ false, 6852 getter_AddRefs(mDataTransfer)); 6853 } else { 6854 mDataTransfer = MakeRefPtr<DataTransfer>( 6855 scopeObject, message, true /* is external */, Some(aClipboardType)); 6856 } 6857 } 6858 6859 void EditorBase::AutoEditActionDataSetter::AppendTargetRange( 6860 StaticRange& aTargetRange) { 6861 mTargetRanges.AppendElement(aTargetRange); 6862 } 6863 6864 void EditorBase::AutoEditActionDataSetter::AppendTargetRange( 6865 RefPtr<StaticRange>&& aTargetRange) { 6866 mTargetRanges.AppendElement(std::move(aTargetRange)); 6867 } 6868 6869 bool EditorBase::AutoEditActionDataSetter::IsBeforeInputEventEnabled() const { 6870 // Don't dispatch "beforeinput" event when the editor user makes us stop 6871 // dispatching input event. 6872 if (mEditorBase.IsSuppressingDispatchingInputEvent()) { 6873 return false; 6874 } 6875 return EditorBase::TreatAsUserInput(mPrincipal); 6876 } 6877 6878 // static 6879 bool EditorBase::TreatAsUserInput(nsIPrincipal* aPrincipal) { 6880 // If aPrincipal it not nullptr, it means that the caller is handling an edit 6881 // action which is requested by JS. If it's not chrome script, we shouldn't 6882 // dispatch "beforeinput" event. 6883 if (aPrincipal && !aPrincipal->IsSystemPrincipal()) { 6884 // But if it's content script of an addon, `execCommand` calls are a 6885 // part of browser's default action from point of view of web apps. 6886 // Therefore, we should dispatch `beforeinput` event. 6887 // https://github.com/w3c/input-events/issues/91 6888 if (!aPrincipal->GetIsAddonOrExpandedAddonPrincipal()) { 6889 return false; 6890 } 6891 } 6892 6893 return true; 6894 } 6895 6896 nsresult EditorBase::AutoEditActionDataSetter::MaybeFlushPendingNotifications() 6897 const { 6898 MOZ_ASSERT(CanHandle()); 6899 if (!MayEditActionRequireLayout(mRawEditAction)) { 6900 return NS_SUCCESS_DOM_NO_OPERATION; 6901 } 6902 OwningNonNull<EditorBase> editorBase = mEditorBase; 6903 RefPtr<PresShell> presShell = editorBase->GetPresShell(); 6904 if (MOZ_UNLIKELY(NS_WARN_IF(!presShell))) { 6905 return NS_ERROR_NOT_AVAILABLE; 6906 } 6907 presShell->FlushPendingNotifications(FlushType::Layout); 6908 if (MOZ_UNLIKELY(NS_WARN_IF(editorBase->Destroyed()))) { 6909 return NS_ERROR_EDITOR_DESTROYED; 6910 } 6911 return NS_OK; 6912 } 6913 6914 void EditorBase::AutoEditActionDataSetter::MarkEditActionCanceled() { 6915 mBeforeInputEventCanceled = true; 6916 if (mEditorBase.IsHTMLEditor()) { 6917 mEditorBase.AsHTMLEditor()->mHasBeforeInputBeenCanceled = true; 6918 } 6919 } 6920 6921 nsresult EditorBase::AutoEditActionDataSetter::MaybeDispatchBeforeInputEvent( 6922 nsIEditor::EDirection aDeleteDirectionAndAmount /* = nsIEditor::eNone */) { 6923 MOZ_ASSERT(!HasTriedToDispatchBeforeInputEvent(), 6924 "We've already handled beforeinput event"); 6925 MOZ_ASSERT(CanHandle()); 6926 MOZ_ASSERT_IF(IsBeforeInputEventEnabled(), 6927 ShouldAlreadyHaveHandledBeforeInputEventDispatching()); 6928 MOZ_ASSERT_IF(!MayEditActionDeleteAroundCollapsedSelection(mEditAction), 6929 aDeleteDirectionAndAmount == nsIEditor::eNone); 6930 6931 mHasTriedToDispatchBeforeInputEvent = true; 6932 6933 if (!IsBeforeInputEventEnabled()) { 6934 return NS_OK; 6935 } 6936 6937 if (mEditorBase.IsHTMLEditor()) { 6938 mEditorBase.AsHTMLEditor()->mLastCollapsibleWhiteSpaceAppendedTextNode = 6939 nullptr; 6940 } 6941 6942 // If we're called from OnCompositionEnd(), we shouldn't dispatch 6943 // "beforeinput" event since the preceding OnCompositionChange() call has 6944 // already dispatched "beforeinput" event for this. 6945 if (mEditAction == EditAction::eCommitComposition || 6946 mEditAction == EditAction::eCancelComposition) { 6947 return NS_OK; 6948 } 6949 6950 RefPtr<Element> targetElement = mEditorBase.GetInputEventTargetElement(); 6951 if (!targetElement) { 6952 // If selection is not in editable element and it is outside of any 6953 // editing hosts, there may be no target element to dispatch `beforeinput` 6954 // event. In this case, the caller shouldn't keep handling the edit 6955 // action since web apps cannot override it with `beforeinput` event 6956 // listener, but for backward compatibility, we should return a special 6957 // success code instead of error. 6958 MOZ_LOG(gEventLog, LogLevel::Error, 6959 ("%p %s: Failed dispatching \"beforeinput\" event due to no target", 6960 &mEditorBase, 6961 mEditorBase.mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor")); 6962 return NS_OK; 6963 } 6964 OwningNonNull<EditorBase> editorBase = mEditorBase; 6965 EditorInputType inputType = ToInputType(mEditAction); 6966 if (editorBase->IsHTMLEditor() && mTargetRanges.IsEmpty()) { 6967 // If the edit action will delete selected ranges, compute the range 6968 // strictly. 6969 if (MayEditActionDeleteAroundCollapsedSelection(mEditAction) || 6970 (!editorBase->SelectionRef().IsCollapsed() && 6971 MayEditActionDeleteSelection(mEditAction))) { 6972 if (!editorBase 6973 ->FlushPendingNotificationsIfToHandleDeletionWithFrameSelection( 6974 aDeleteDirectionAndAmount)) { 6975 NS_WARNING( 6976 "Flusing pending notifications caused destroying the editor"); 6977 return NS_ERROR_EDITOR_DESTROYED; 6978 } 6979 6980 AutoClonedSelectionRangeArray rangesToDelete(editorBase->SelectionRef()); 6981 if (!rangesToDelete.Ranges().IsEmpty()) { 6982 nsresult rv = MOZ_KnownLive(editorBase->AsHTMLEditor()) 6983 ->ComputeTargetRanges(aDeleteDirectionAndAmount, 6984 rangesToDelete); 6985 if (rv == NS_ERROR_EDITOR_DESTROYED) { 6986 NS_WARNING("HTMLEditor::ComputeTargetRanges() destroyed the editor"); 6987 return NS_ERROR_EDITOR_DESTROYED; 6988 } 6989 if (rv == NS_ERROR_EDITOR_NO_EDITABLE_RANGE) { 6990 // For now, keep dispatching `beforeinput` event even if no selection 6991 // range can be editable. 6992 rv = NS_OK; 6993 } 6994 NS_WARNING_ASSERTION( 6995 NS_SUCCEEDED(rv), 6996 "HTMLEditor::ComputeTargetRanges() failed, but ignored"); 6997 for (auto& range : rangesToDelete.Ranges()) { 6998 RefPtr<StaticRange> staticRange = 6999 StaticRange::Create(range, IgnoreErrors()); 7000 if (NS_WARN_IF(!staticRange)) { 7001 continue; 7002 } 7003 AppendTargetRange(*staticRange); 7004 } 7005 } 7006 } 7007 // Otherwise, just set target ranges to selection ranges. 7008 else if (MayHaveTargetRangesOnHTMLEditor(inputType)) { 7009 if (uint32_t rangeCount = editorBase->SelectionRef().RangeCount()) { 7010 mTargetRanges.SetCapacity(rangeCount); 7011 for (const uint32_t i : IntegerRange(rangeCount)) { 7012 MOZ_ASSERT(editorBase->SelectionRef().RangeCount() == rangeCount); 7013 const nsRange* range = editorBase->SelectionRef().GetRangeAt(i); 7014 MOZ_ASSERT(range); 7015 MOZ_ASSERT(range->IsPositioned()); 7016 if (MOZ_UNLIKELY(NS_WARN_IF(!range)) || 7017 MOZ_UNLIKELY(NS_WARN_IF(!range->IsPositioned()))) { 7018 continue; 7019 } 7020 // Now, we need to fix the offset of target range because it may 7021 // be referred after modifying the DOM tree and range boundaries 7022 // of `range` may have not computed offset yet. 7023 RefPtr<StaticRange> targetRange = StaticRange::Create( 7024 range->GetStartContainer(), range->StartOffset(), 7025 range->GetEndContainer(), range->EndOffset(), IgnoreErrors()); 7026 if (NS_WARN_IF(!targetRange) || 7027 NS_WARN_IF(!targetRange->IsPositioned())) { 7028 continue; 7029 } 7030 mTargetRanges.AppendElement(std::move(targetRange)); 7031 } 7032 } 7033 } 7034 } 7035 nsEventStatus status = nsEventStatus_eIgnore; 7036 InputEventOptions::NeverCancelable neverCancelable = 7037 mMakeBeforeInputEventNonCancelable 7038 ? InputEventOptions::NeverCancelable::Yes 7039 : InputEventOptions::NeverCancelable::No; 7040 WillDispatchInputEvent(); 7041 MOZ_LOG(gEventLog, LogLevel::Info, 7042 ("%p %s: Dispatching \"beforeinput\" event: { inputType=\"%s\" }...", 7043 editorBase.get(), 7044 editorBase->mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 7045 ToString(ToInputType(GetEditAction())).c_str())); 7046 nsresult rv = nsContentUtils::DispatchInputEvent( 7047 targetElement, eEditorBeforeInput, inputType, editorBase, 7048 mDataTransfer 7049 ? InputEventOptions(mDataTransfer, std::move(mTargetRanges), 7050 neverCancelable) 7051 : InputEventOptions(mData, std::move(mTargetRanges), neverCancelable), 7052 &status); 7053 MOZ_LOG(gEventLog, LogLevel::Info, 7054 ("%p %s: Dispatched \"beforeinput\" event: { inputType=\"%s\" }, " 7055 "defaultPrevented=%s", 7056 editorBase.get(), 7057 editorBase->mIsHTMLEditorClass ? "HTMLEditor" : "TextEditor", 7058 ToString(ToInputType(GetEditAction())).c_str(), 7059 status == nsEventStatus_eConsumeNoDefault ? "true" : "false")); 7060 DidDispatchInputEvent(); 7061 if (NS_WARN_IF(mEditorBase.Destroyed())) { 7062 return NS_ERROR_EDITOR_DESTROYED; 7063 } 7064 if (NS_FAILED(rv)) { 7065 NS_WARNING("nsContentUtils::DispatchInputEvent() failed"); 7066 return rv; 7067 } 7068 if (status == nsEventStatus_eConsumeNoDefault) { 7069 MarkEditActionCanceled(); 7070 return NS_ERROR_EDITOR_ACTION_CANCELED; 7071 } 7072 7073 nsCOMPtr<nsIWidget> widget = editorBase->GetWidget(); 7074 if (!StaticPrefs::dom_events_textevent_enabled() || 7075 !targetElement->IsInComposedDoc() || !widget) { 7076 return NS_OK; 7077 } 7078 nsString textInputData; 7079 RefPtr<DataTransfer> textInputDataTransfer; 7080 switch (inputType) { 7081 case EditorInputType::eInsertCompositionText: 7082 // If the composition is still being composed, we should not dispatch 7083 // textInput event, but we need to dispatch it for the last composition 7084 // change because web apps should know the inserting commit string as 7085 // same as input from keyboard. 7086 if (mEditAction == EditAction::eUpdateComposition) { 7087 return NS_OK; 7088 } 7089 [[fallthrough]]; 7090 case EditorInputType::eInsertText: 7091 textInputData = mData; 7092 break; 7093 case EditorInputType::eInsertFromDrop: 7094 case EditorInputType::eInsertFromPaste: 7095 case EditorInputType::eInsertFromPasteAsQuotation: 7096 if (mDataTransfer) { 7097 textInputDataTransfer = mDataTransfer; 7098 } else { 7099 textInputData = mData; 7100 } 7101 break; 7102 case EditorInputType::eInsertLineBreak: 7103 case EditorInputType::eInsertParagraph: 7104 // Don't dispatch `textInput` on <input> because Chrome does not do it. 7105 // On the other hand, we need to dispatch it on <textarea> and 7106 // contenteditable. 7107 if (mEditorBase.IsTextEditor() && mEditorBase.IsSingleLineEditor()) { 7108 return NS_OK; 7109 } 7110 textInputData.Assign(u'\n'); 7111 break; 7112 default: 7113 return NS_OK; 7114 } 7115 7116 InternalLegacyTextEvent textEvent(true, eLegacyTextInput, widget); 7117 textEvent.mData = std::move(textInputData); 7118 textEvent.mDataTransfer = std::move(textInputDataTransfer); 7119 textEvent.mInputType = inputType; 7120 // Make it always cancelable even though we ignore it when inserting or 7121 // deleting composition. This is compatible with Chrome. 7122 // However, if and only if it's unsafe, let's set it not cancelable because of 7123 // asynchronous dispatching. 7124 textEvent.mFlags.mCancelable = nsContentUtils::IsSafeToRunScript(); 7125 7126 status = nsEventStatus_eIgnore; 7127 rv = AsyncEventDispatcher::RunDOMEventWhenSafe(*targetElement, textEvent, 7128 &status); 7129 if (NS_WARN_IF(mEditorBase.Destroyed())) { 7130 return NS_ERROR_EDITOR_DESTROYED; 7131 } 7132 if (NS_FAILED(rv)) { 7133 NS_WARNING("AsyncEventDispatcher::RunDOMEventWhenSafe() failed"); 7134 return rv; 7135 } 7136 if (status == nsEventStatus_eConsumeNoDefault) { 7137 MarkEditActionCanceled(); 7138 return NS_ERROR_EDITOR_ACTION_CANCELED; 7139 } 7140 return NS_OK; 7141 } 7142 7143 /***************************************************************************** 7144 * mozilla::EditorBase::TopLevelEditSubActionData 7145 *****************************************************************************/ 7146 7147 nsresult EditorBase::TopLevelEditSubActionData::AddNodeToChangedRange( 7148 const HTMLEditor& aHTMLEditor, nsINode& aNode) { 7149 EditorRawDOMPoint startPoint(&aNode); 7150 EditorRawDOMPoint endPoint(&aNode); 7151 DebugOnly<bool> advanced = endPoint.AdvanceOffset(); 7152 NS_WARNING_ASSERTION(advanced, "Failed to set endPoint to next to aNode"); 7153 nsresult rv = AddRangeToChangedRange(aHTMLEditor, startPoint, endPoint); 7154 NS_WARNING_ASSERTION( 7155 NS_SUCCEEDED(rv), 7156 "TopLevelEditSubActionData::AddRangeToChangedRange() failed"); 7157 return rv; 7158 } 7159 7160 nsresult EditorBase::TopLevelEditSubActionData::AddPointToChangedRange( 7161 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aPoint) { 7162 nsresult rv = AddRangeToChangedRange(aHTMLEditor, aPoint, aPoint); 7163 NS_WARNING_ASSERTION( 7164 NS_SUCCEEDED(rv), 7165 "TopLevelEditSubActionData::AddRangeToChangedRange() failed"); 7166 return rv; 7167 } 7168 7169 nsresult EditorBase::TopLevelEditSubActionData::AddRangeToChangedRange( 7170 const HTMLEditor& aHTMLEditor, const EditorRawDOMPoint& aStart, 7171 const EditorRawDOMPoint& aEnd) { 7172 if (NS_WARN_IF(!aStart.IsSet()) || NS_WARN_IF(!aEnd.IsSet())) { 7173 return NS_ERROR_INVALID_ARG; 7174 } 7175 7176 if (!aHTMLEditor.IsDescendantOfRoot(aStart.GetContainer()) || 7177 (aStart.GetContainer() != aEnd.GetContainer() && 7178 !aHTMLEditor.IsDescendantOfRoot(aEnd.GetContainer()))) { 7179 return NS_OK; 7180 } 7181 7182 // If mChangedRange hasn't been set, we can just set it to `aStart` and 7183 // `aEnd`. 7184 if (!mChangedRange->IsPositioned()) { 7185 nsresult rv = mChangedRange->SetStartAndEnd(aStart.ToRawRangeBoundary(), 7186 aEnd.ToRawRangeBoundary()); 7187 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "nsRange::SetStartAndEnd() failed"); 7188 return rv; 7189 } 7190 7191 Maybe<int32_t> relation = 7192 mChangedRange->StartRef().IsSet() 7193 ? nsContentUtils::ComparePoints(mChangedRange->StartRef(), 7194 aStart.ToRawRangeBoundary()) 7195 : Some(1); 7196 if (NS_WARN_IF(!relation)) { 7197 return NS_ERROR_FAILURE; 7198 } 7199 7200 // If aStart is before start of mChangedRange, reset the start. 7201 if (*relation > 0) { 7202 ErrorResult error; 7203 mChangedRange->SetStart(aStart.ToRawRangeBoundary(), error); 7204 if (error.Failed()) { 7205 NS_WARNING("nsRange::SetStart() failed"); 7206 return error.StealNSResult(); 7207 } 7208 } 7209 7210 relation = mChangedRange->EndRef().IsSet() 7211 ? nsContentUtils::ComparePoints(mChangedRange->EndRef(), 7212 aEnd.ToRawRangeBoundary()) 7213 : Some(1); 7214 if (NS_WARN_IF(!relation)) { 7215 return NS_ERROR_FAILURE; 7216 } 7217 7218 // If aEnd is after end of mChangedRange, reset the end. 7219 if (*relation < 0) { 7220 ErrorResult error; 7221 mChangedRange->SetEnd(aEnd.ToRawRangeBoundary(), error); 7222 if (error.Failed()) { 7223 NS_WARNING("nsRange::SetEnd() failed"); 7224 return error.StealNSResult(); 7225 } 7226 } 7227 7228 return NS_OK; 7229 } 7230 7231 void EditorBase::TopLevelEditSubActionData::DidCreateElement( 7232 EditorBase& aEditorBase, Element& aNewElement) { 7233 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7234 7235 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7236 return; // We have not been initialized yet or already been destroyed. 7237 } 7238 7239 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7240 return; // Temporarily disabled by edit sub-action handler. 7241 } 7242 7243 DebugOnly<nsresult> rvIgnored = 7244 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aNewElement); 7245 NS_WARNING_ASSERTION( 7246 NS_SUCCEEDED(rvIgnored), 7247 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored"); 7248 } 7249 7250 void EditorBase::TopLevelEditSubActionData::DidInsertContent( 7251 EditorBase& aEditorBase, nsIContent& aNewContent) { 7252 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7253 7254 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7255 return; // We have not been initialized yet or already been destroyed. 7256 } 7257 7258 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7259 return; // Temporarily disabled by edit sub-action handler. 7260 } 7261 7262 DebugOnly<nsresult> rvIgnored = 7263 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aNewContent); 7264 NS_WARNING_ASSERTION( 7265 NS_SUCCEEDED(rvIgnored), 7266 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored"); 7267 } 7268 7269 void EditorBase::TopLevelEditSubActionData::WillDeleteContent( 7270 EditorBase& aEditorBase, nsIContent& aRemovingContent) { 7271 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7272 7273 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7274 return; // We have not been initialized yet or already been destroyed. 7275 } 7276 7277 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7278 return; // Temporarily disabled by edit sub-action handler. 7279 } 7280 7281 DebugOnly<nsresult> rvIgnored = 7282 AddNodeToChangedRange(*aEditorBase.AsHTMLEditor(), aRemovingContent); 7283 NS_WARNING_ASSERTION( 7284 NS_SUCCEEDED(rvIgnored), 7285 "TopLevelEditSubActionData::AddNodeToChangedRange() failed, but ignored"); 7286 } 7287 7288 void EditorBase::TopLevelEditSubActionData::DidSplitContent( 7289 EditorBase& aEditorBase, nsIContent& aSplitContent, 7290 nsIContent& aNewContent) { 7291 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7292 7293 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7294 return; // We have not been initialized yet or already been destroyed. 7295 } 7296 7297 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7298 return; // Temporarily disabled by edit sub-action handler. 7299 } 7300 7301 DebugOnly<nsresult> rvIgnored = AddRangeToChangedRange( 7302 *aEditorBase.AsHTMLEditor(), EditorRawDOMPoint::AtEndOf(aSplitContent), 7303 EditorRawDOMPoint::AtEndOf(aNewContent)); 7304 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 7305 "TopLevelEditSubActionData::AddRangeToChangedRange() " 7306 "failed, but ignored"); 7307 } 7308 7309 void EditorBase::TopLevelEditSubActionData::DidJoinContents( 7310 EditorBase& aEditorBase, const EditorRawDOMPoint& aJoinedPoint) { 7311 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7312 7313 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7314 return; // We have not been initialized yet or already been destroyed. 7315 } 7316 7317 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7318 return; // Temporarily disabled by edit sub-action handler. 7319 } 7320 7321 DebugOnly<nsresult> rvIgnored = 7322 AddPointToChangedRange(*aEditorBase.AsHTMLEditor(), aJoinedPoint); 7323 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 7324 "TopLevelEditSubActionData::AddPointToChangedRange() " 7325 "failed, but ignored"); 7326 } 7327 7328 void EditorBase::TopLevelEditSubActionData::DidInsertText( 7329 EditorBase& aEditorBase, const EditorRawDOMPoint& aInsertionBegin, 7330 const EditorRawDOMPoint& aInsertionEnd) { 7331 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7332 7333 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7334 return; // We have not been initialized yet or already been destroyed. 7335 } 7336 7337 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7338 return; // Temporarily disabled by edit sub-action handler. 7339 } 7340 7341 DebugOnly<nsresult> rvIgnored = AddRangeToChangedRange( 7342 *aEditorBase.AsHTMLEditor(), aInsertionBegin, aInsertionEnd); 7343 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 7344 "TopLevelEditSubActionData::AddRangeToChangedRange() " 7345 "failed, but ignored"); 7346 } 7347 7348 void EditorBase::TopLevelEditSubActionData::DidDeleteText( 7349 EditorBase& aEditorBase, const EditorRawDOMPoint& aStartInTextNode) { 7350 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7351 7352 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7353 return; // We have not been initialized yet or already been destroyed. 7354 } 7355 7356 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7357 return; // Temporarily disabled by edit sub-action handler. 7358 } 7359 7360 DebugOnly<nsresult> rvIgnored = 7361 AddPointToChangedRange(*aEditorBase.AsHTMLEditor(), aStartInTextNode); 7362 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 7363 "TopLevelEditSubActionData::AddPointToChangedRange() " 7364 "failed, but ignored"); 7365 } 7366 7367 void EditorBase::TopLevelEditSubActionData::WillDeleteRange( 7368 EditorBase& aEditorBase, const EditorRawDOMPoint& aStart, 7369 const EditorRawDOMPoint& aEnd) { 7370 MOZ_ASSERT(aEditorBase.AsHTMLEditor()); 7371 MOZ_ASSERT(aStart.IsSet()); 7372 MOZ_ASSERT(aEnd.IsSet()); 7373 7374 if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) { 7375 return; // We have not been initialized yet or already been destroyed. 7376 } 7377 7378 if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) { 7379 return; // Temporarily disabled by edit sub-action handler. 7380 } 7381 7382 // XXX Looks like that this is wrong. We delete multiple selection ranges 7383 // once, but this adds only first range into the changed range. 7384 // Anyway, we should take the range as an argument. 7385 DebugOnly<nsresult> rvIgnored = 7386 AddRangeToChangedRange(*aEditorBase.AsHTMLEditor(), aStart, aEnd); 7387 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 7388 "TopLevelEditSubActionData::AddRangeToChangedRange() " 7389 "failed, but ignored"); 7390 } 7391 7392 nsPIDOMWindowOuter* EditorBase::GetWindow() const { 7393 return mDocument ? mDocument->GetWindow() : nullptr; 7394 } 7395 7396 nsPIDOMWindowInner* EditorBase::GetInnerWindow() const { 7397 return mDocument ? mDocument->GetInnerWindow() : nullptr; 7398 } 7399 7400 PresShell* EditorBase::GetPresShell() const { 7401 return mDocument ? mDocument->GetPresShell() : nullptr; 7402 } 7403 7404 nsPresContext* EditorBase::GetPresContext() const { 7405 PresShell* presShell = GetPresShell(); 7406 return presShell ? presShell->GetPresContext() : nullptr; 7407 } 7408 7409 already_AddRefed<nsCaret> EditorBase::GetCaret() const { 7410 PresShell* presShell = GetPresShell(); 7411 if (NS_WARN_IF(!presShell)) { 7412 return nullptr; 7413 } 7414 return presShell->GetCaret(); 7415 } 7416 7417 nsISelectionController* EditorBase::GetSelectionController() const { 7418 if (mSelectionController) { 7419 return mSelectionController; 7420 } 7421 if (!mDocument) { 7422 return nullptr; 7423 } 7424 return mDocument->GetPresShell(); 7425 } 7426 7427 bool EditorBase::ArePreservingSelection() const { 7428 return IsEditActionDataAvailable() && SavedSelectionRef().RangeCount(); 7429 } 7430 7431 void EditorBase::PreserveSelectionAcrossActions() { 7432 MOZ_ASSERT(IsEditActionDataAvailable()); 7433 7434 SavedSelectionRef().SaveSelection(SelectionRef()); 7435 RangeUpdaterRef().RegisterSelectionState(SavedSelectionRef()); 7436 } 7437 7438 nsresult EditorBase::RestorePreservedSelection() { 7439 MOZ_ASSERT(IsEditActionDataAvailable()); 7440 7441 if (!SavedSelectionRef().RangeCount()) { 7442 // XXX Returning error when it does not store is odd because no selection 7443 // ranges is not illegal case in general. 7444 return NS_ERROR_FAILURE; 7445 } 7446 DebugOnly<nsresult> rvIgnored = 7447 SavedSelectionRef().RestoreSelection(SelectionRef()); 7448 NS_WARNING_ASSERTION( 7449 NS_SUCCEEDED(rvIgnored), 7450 "SelectionState::RestoreSelection() failed, but ignored"); 7451 StopPreservingSelection(); 7452 return NS_OK; 7453 } 7454 7455 void EditorBase::StopPreservingSelection() { 7456 MOZ_ASSERT(IsEditActionDataAvailable()); 7457 7458 RangeUpdaterRef().DropSelectionState(SavedSelectionRef()); 7459 SavedSelectionRef().RemoveAllRanges(); 7460 } 7461 7462 nsresult EditorBase::GetDataFromDataTransferOrClipboard( 7463 DataTransfer* aDataTransfer, nsITransferable* aTransferable, 7464 nsIClipboard::ClipboardType aClipboardType) const { 7465 MOZ_ASSERT(aTransferable); 7466 if (aDataTransfer) { 7467 MOZ_ASSERT(aDataTransfer->ClipboardType() == Some(aClipboardType)); 7468 bool readFromClipboard = true; 7469 nsresult rv = [aDataTransfer, aTransferable, 7470 &readFromClipboard]() -> nsresult { 7471 nsIClipboardDataSnapshot* snapshot = 7472 aDataTransfer->GetClipboardDataSnapshot(); 7473 MOZ_ASSERT(snapshot); 7474 bool snapshotIsValid = false; 7475 snapshot->GetValid(&snapshotIsValid); 7476 // By default, if we have a valid snapshot, we should only read from that 7477 // and not fall back to the clipboard to avoid bypassing a BLOCK result 7478 // from Content Analysis (bug 1936204). There are some exceptions which 7479 // are dealt with later, however. 7480 readFromClipboard = !snapshotIsValid; 7481 if (!snapshotIsValid) { 7482 NS_WARNING( 7483 "DataTransfer::GetClipboardDataSnapshot() is not valid, falling " 7484 "back " 7485 "to clipboard"); 7486 return NS_ERROR_FAILURE; 7487 } 7488 AutoTArray<nsCString, 10> transferableFlavors; 7489 nsresult rv = 7490 aTransferable->FlavorsTransferableCanImport(transferableFlavors); 7491 if (NS_FAILED(rv)) { 7492 NS_WARNING("nsITransferable::FlavorsTransferableCanImport() failed"); 7493 return rv; 7494 } 7495 if (transferableFlavors.Length() == 1) { 7496 // avoid creating unneeded temporary transferables 7497 rv = snapshot->GetDataSync(aTransferable); 7498 if (NS_FAILED(rv)) { 7499 NS_WARNING("nsIClipboardDataSnapshot::GetDataSync() failed"); 7500 } 7501 // If this fails, it may be because the snapshot was invalid and we 7502 // didn't detect it until now, so fall back to reading the clipboard. 7503 readFromClipboard = rv == NS_ERROR_NOT_AVAILABLE; 7504 return rv; 7505 } 7506 AutoTArray<nsCString, 5> snapshotFlavors; 7507 rv = snapshot->GetFlavorList(snapshotFlavors); 7508 if (NS_FAILED(rv)) { 7509 NS_WARNING("nsIClipboardDataSnapshot::GetFlavorList() failed"); 7510 return rv; 7511 } 7512 for (const auto& transferableFlavor : transferableFlavors) { 7513 if (snapshotFlavors.Contains(transferableFlavor)) { 7514 AutoTArray<nsCString, 1> singleTypeArray{transferableFlavor}; 7515 auto singleTransferableToCheck = 7516 ContentParent::CreateClipboardTransferable(singleTypeArray); 7517 if (singleTransferableToCheck.isErr()) { 7518 NS_WARNING("Failed to CreateClipboardTransferable()"); 7519 return singleTransferableToCheck.unwrapErr(); 7520 } 7521 nsCOMPtr<nsITransferable> singleTransferable = 7522 singleTransferableToCheck.unwrap(); 7523 rv = snapshot->GetDataSync(singleTransferable); 7524 if (NS_FAILED(rv)) { 7525 NS_WARNING("nsIClipboardDataSnapshot::GetDataSync() failed"); 7526 // If this fails, it may be because the snapshot was invalid and we 7527 // didn't detect it until now, so fall back to reading the 7528 // clipboard. 7529 readFromClipboard = rv == NS_ERROR_NOT_AVAILABLE; 7530 return rv; 7531 } 7532 nsCOMPtr<nsISupports> data; 7533 rv = singleTransferable->GetTransferData(transferableFlavor.get(), 7534 getter_AddRefs(data)); 7535 if (NS_FAILED(rv)) { 7536 NS_WARNING("nsITransferable::GetTransferData() failed"); 7537 return rv; 7538 } 7539 rv = aTransferable->SetTransferData(transferableFlavor.get(), data); 7540 if (NS_FAILED(rv)) { 7541 NS_WARNING("nsITransferable::SetTransferData() failed"); 7542 return rv; 7543 } 7544 return NS_OK; 7545 } 7546 } 7547 // The snapshot doesn't have any relevant data. Leave aTransferable empty 7548 // but return NS_OK since the operation did succeed (there just isn't any 7549 // data) and some tests expect this. 7550 return NS_OK; 7551 }(); 7552 // If the operation failed, only fall back to the clipboard if indicated. 7553 if (NS_SUCCEEDED(rv) || !readFromClipboard) { 7554 return rv; 7555 } 7556 } 7557 7558 // Get Clipboard Service 7559 nsresult rv; 7560 nsCOMPtr<nsIClipboard> clipboard = 7561 do_GetService("@mozilla.org/widget/clipboard;1", &rv); 7562 if (NS_FAILED(rv)) { 7563 NS_WARNING("Failed to get nsIClipboard service"); 7564 return rv; 7565 } 7566 7567 auto* windowContext = GetDocument()->GetWindowContext(); 7568 if (!windowContext) { 7569 NS_WARNING("No window context"); 7570 return NS_ERROR_FAILURE; 7571 } 7572 rv = clipboard->GetData(aTransferable, aClipboardType, windowContext); 7573 if (NS_FAILED(rv)) { 7574 NS_WARNING("nsIClipboard::GetData() failed"); 7575 return rv; 7576 } 7577 return NS_OK; 7578 } 7579 } // namespace mozilla